From 6a5764374e483c86ca519829ec59904e724d9a1b Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Tue, 7 Feb 2023 03:33:28 -0500 Subject: [PATCH 01/61] 0.4.x: Backport fix of javalib ServerSocket#accept (#3143) --- javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala b/javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala index 6f9213e5bf..ab63274a82 100644 --- a/javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala +++ b/javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala @@ -234,7 +234,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl { val sa = storage.asInstanceOf[Ptr[in.sockaddr_in]] inet.inet_ntop( socket.AF_INET, - sa.sin_addr.asInstanceOf[Ptr[Byte]], + sa.sin_addr.at1.asInstanceOf[Ptr[Byte]], ipstr, in.INET6_ADDRSTRLEN.toUInt ) @@ -243,7 +243,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl { val sa = storage.asInstanceOf[Ptr[in.sockaddr_in6]] inet.inet_ntop( socket.AF_INET6, - sa.sin6_addr.asInstanceOf[Ptr[Byte]], + sa.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]], ipstr, in.INET6_ADDRSTRLEN.toUInt ) From f292cc8aea26e6a6dfd720a9a44effe94bb2c087 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Fri, 10 Mar 2023 11:55:14 -0800 Subject: [PATCH 02/61] Add `Atomic{Integer,Long}.{getAndUpdate,updateAndGet}` (#3216) --- .../concurrent/atomic/AtomicInteger.scala | 13 +++++++++++++ .../util/concurrent/atomic/AtomicLong.scala | 13 +++++++++++++ .../util/function/LongUnaryOperator.scala | 19 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 javalib/src/main/scala/java/util/function/LongUnaryOperator.scala diff --git a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala index 23c59d6bc4..9a17a48257 100644 --- a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala +++ b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicInteger.scala @@ -1,5 +1,7 @@ package java.util.concurrent.atomic +import java.util.function.IntUnaryOperator + class AtomicInteger(private[this] var value: Int) extends Number with Serializable { @@ -55,6 +57,17 @@ class AtomicInteger(private[this] var value: Int) newValue } + final def getAndUpdate(updateFunction: IntUnaryOperator): Int = { + val old = value + value = updateFunction.applyAsInt(old) + old + } + + final def updateAndGet(updateFunction: IntUnaryOperator): Int = { + value = updateFunction.applyAsInt(value) + value + } + override def toString(): String = value.toString() diff --git a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala index 14264aed46..5f43cd984b 100644 --- a/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala +++ b/javalib/src/main/scala/java/util/concurrent/atomic/AtomicLong.scala @@ -1,5 +1,7 @@ package java.util.concurrent.atomic +import java.util.function.LongUnaryOperator + class AtomicLong(private[this] var value: Long) extends Number with Serializable { @@ -54,6 +56,17 @@ class AtomicLong(private[this] var value: Long) newValue } + final def getAndUpdate(updateFunction: LongUnaryOperator): Long = { + val old = value + value = updateFunction.applyAsLong(old) + old + } + + final def updateAndGet(updateFunction: LongUnaryOperator): Long = { + value = updateFunction.applyAsLong(value) + value + } + override def toString(): String = value.toString() diff --git a/javalib/src/main/scala/java/util/function/LongUnaryOperator.scala b/javalib/src/main/scala/java/util/function/LongUnaryOperator.scala new file mode 100644 index 0000000000..9b0c894cf3 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongUnaryOperator.scala @@ -0,0 +1,19 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait LongUnaryOperator { + def applyAsLong(operand: Long): Long + + def andThen(after: LongUnaryOperator): LongUnaryOperator = { (l: Long) => + after.applyAsLong(applyAsLong(l)) + } + + def compose(before: LongUnaryOperator): LongUnaryOperator = { (l: Long) => + applyAsLong(before.applyAsLong(l)) + } +} + +object LongUnaryOperator { + def identity(): LongUnaryOperator = (l: Long) => l +} From 903f29bd9ce9979bbaeaed0c679e10779ebd7a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Costa?= Date: Wed, 15 Mar 2023 09:33:21 +0100 Subject: [PATCH 03/61] Trigger GC collction on Runtime#gc() (#3224) * Trigger GC collection on Runtime#gc() * Move Runtime#gc logic to System.gc --- javalib/src/main/scala/java/lang/Runtime.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javalib/src/main/scala/java/lang/Runtime.scala b/javalib/src/main/scala/java/lang/Runtime.scala index db21451ce7..cccffc39a1 100644 --- a/javalib/src/main/scala/java/lang/Runtime.scala +++ b/javalib/src/main/scala/java/lang/Runtime.scala @@ -8,7 +8,7 @@ class Runtime private () { import Runtime.ProcessBuilderOps def availableProcessors(): Int = 1 def exit(status: Int): Unit = stdlib.exit(status) - def gc(): Unit = () + def gc(): Unit = System.gc() @stub def addShutdownHook(thread: java.lang.Thread): Unit = ??? From 519e5bd196816d5ebd09d92770f4e843cea56a57 Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Fri, 27 Jan 2023 18:02:32 -0500 Subject: [PATCH 04/61] posixlib socket.c now compiles on FreeBSD arm64 (#3112) --- posixlib/src/main/resources/scala-native/sys/socket.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/posixlib/src/main/resources/scala-native/sys/socket.c b/posixlib/src/main/resources/scala-native/sys/socket.c index 61e9ae0b4f..f293140322 100644 --- a/posixlib/src/main/resources/scala-native/sys/socket.c +++ b/posixlib/src/main/resources/scala-native/sys/socket.c @@ -10,6 +10,9 @@ #pragma comment(lib, "Ws2_32.lib") typedef SSIZE_T ssize_t; #else +#if defined(__FreeBSD__) +#import // u_long & friends. Required by Amazon FreeBSD64 arm64 +#endif // __FreeBSD__ #include #include #if !(defined __STDC_VERSION__) || (__STDC_VERSION__ < 201112L) From 48d999ccfd69c7aabe4295d7600b6bda10c3b209 Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Sat, 28 Jan 2023 09:25:40 -0500 Subject: [PATCH 05/61] Fix #3090: MappedByteBufferTest now passes on FreeBSD64 (#3113) --- .../main/scala/java/nio/MappedByteBufferImpl.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/javalib/src/main/scala/java/nio/MappedByteBufferImpl.scala b/javalib/src/main/scala/java/nio/MappedByteBufferImpl.scala index fce1dd5839..08f326fed7 100644 --- a/javalib/src/main/scala/java/nio/MappedByteBufferImpl.scala +++ b/javalib/src/main/scala/java/nio/MappedByteBufferImpl.scala @@ -298,12 +298,19 @@ private[nio] object MappedByteBufferImpl { fd: FileDescriptor, mode: MapMode ): MappedByteBufferData = { + + /* FreeBSD requires that PROT_READ be explicit with MAP_SHARED. + * Linux, macOS, & FreeBSD MAP_PRIVATE allow PROT_WRITE to imply + * PROT_READ. Make PROT_READ explicit in all these cases to document + * the intention. + */ val (prot: Int, isPrivate: Int) = mode match { - case MapMode.PRIVATE => (PROT_WRITE, MAP_PRIVATE) + case MapMode.PRIVATE => (PROT_READ | PROT_WRITE, MAP_PRIVATE) case MapMode.READ_ONLY => (PROT_READ, MAP_SHARED) - case MapMode.READ_WRITE => (PROT_WRITE, MAP_SHARED) + case MapMode.READ_WRITE => (PROT_READ | PROT_WRITE, MAP_SHARED) case _ => throw new IllegalStateException("Unknown MapMode") } + val ptr = mmap( null, size.toUInt, From 3ea7922b9c46b15b0c6f33998e5dc0943c63e12b Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 30 Jan 2023 17:22:12 +0100 Subject: [PATCH 06/61] Use Choco-Install script to install llvm deps with retries (#3119) --- .github/actions/windows-setup-env/action.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/actions/windows-setup-env/action.yml b/.github/actions/windows-setup-env/action.yml index ece8e78806..c0d217a7f3 100644 --- a/.github/actions/windows-setup-env/action.yml +++ b/.github/actions/windows-setup-env/action.yml @@ -60,9 +60,10 @@ runs: key: ${{ runner.os }}-${{ inputs.scala-version }}-deps # Install LLVM in case if cache is missing + # Choco-Install is GH Actions wrappers around choco, which does retries - name: Install LLVM shell: pwsh - run: choco install llvm --version=11.0.0 --allow-downgrade + run: Choco-Install -PackageName llvm -ArgumentList "--version=11.0.0", "--allow-downgrade" - name: Add LLVM on Path shell: pwsh From 3ef1937689ddd3be361542033c2eab4352787df1 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 1 Feb 2023 13:12:29 +0100 Subject: [PATCH 07/61] Define null guards for methods using `this` (#3123) * Add reproducer for issue #3014 * Add generic nir traversal utility * Generate guards for methods using `this`. Every function which does not define or use `this` argument would not be exposed to additional overhead * Fix detecting of static and private methods in nir.Sig. Replace existing signature parsing, with way more stable unmangling of names --- .../scala/scala/scalanative/nir/Sig.scala | 26 +-- .../scala/scalanative/nir/Traverse.scala | 182 ++++++++++++++++++ .../scala/scalanative/codegen/Lower.scala | 74 +++++-- .../scala/issues/Scala3IssuesTest.scala | 23 +++ 4 files changed, 270 insertions(+), 35 deletions(-) create mode 100644 nir/src/main/scala/scala/scalanative/nir/Traverse.scala diff --git a/nir/src/main/scala/scala/scalanative/nir/Sig.scala b/nir/src/main/scala/scala/scalanative/nir/Sig.scala index f834605299..0d931d6424 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Sig.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Sig.scala @@ -38,29 +38,9 @@ final class Sig(val mangle: String) { final def isVirtual = !(isCtor || isClinit || isExtern) final def isPrivate: Boolean = privateIn.isDefined - final def isStatic: Boolean = { - def isPublicStatic = mangle.last == 'o' - def isPrivateStatic = { - val sigEnd = mangle.lastIndexOf('E') - val scopeIdx = sigEnd + 1 - def hasScope = mangle.length() > scopeIdx - sigEnd > 0 && hasScope && mangle(sigEnd + 1) == 'p' - } - isPublicStatic || isPrivateStatic - } - final lazy val privateIn: Option[Global.Top] = { - val sigEnd = mangle.lastIndexOf('E') - val scopeIdx = sigEnd + 1 - def hasScope = mangle.length() > scopeIdx - def isPrivate = { - val scopeIdent = mangle(scopeIdx) - scopeIdent == 'p' || scopeIdent == 'P' - } - if (sigEnd > 0 && hasScope && isPrivate) { - val global = Unmangle.unmangleGlobal(mangle.substring(sigEnd + 2)) - Some(global.top) - } else None - } + final def isStatic: Boolean = unmangled.sigScope.isStatic + final lazy val privateIn: Option[Global.Top] = + unmangled.sigScope.privateIn.map(_.top) } object Sig { sealed abstract class Scope( diff --git a/nir/src/main/scala/scala/scalanative/nir/Traverse.scala b/nir/src/main/scala/scala/scalanative/nir/Traverse.scala new file mode 100644 index 0000000000..7556666904 --- /dev/null +++ b/nir/src/main/scala/scala/scalanative/nir/Traverse.scala @@ -0,0 +1,182 @@ +package scala.scalanative.nir + +trait Traverse { + def onDefns(defns: Iterable[Defn]): Unit = defns.foreach(onDefn) + + def onDefn(defn: Defn): Unit = { + defn match { + case Defn.Var(_, _, ty, value) => + onType(ty) + onVal(value) + case Defn.Const(_, _, ty, value) => + onType(ty) + onVal(value) + case Defn.Declare(_, _, ty) => + onType(ty) + case Defn.Define(_, _, ty, insts) => + onInsts(insts) + case Defn.Trait(_, _, _) => () + case Defn.Class(_, _, _, _) => () + case Defn.Module(_, _, _, _) => () + } + } + + def onInsts(insts: Iterable[Inst]): Unit = + insts.foreach(onInst) + + def onInst(inst: Inst): Unit = { + inst match { + case Inst.Label(n, params) => + params.foreach { param => + onType(param.ty) + } + case Inst.Let(n, op, unwind) => + onOp(op) + onNext(unwind) + case Inst.Ret(v) => onVal(v) + case Inst.Jump(next) => onNext(next) + case Inst.If(v, thenp, elsep) => + onVal(v) + onNext(thenp) + onNext(elsep) + case Inst.Switch(v, default, cases) => + onVal(v) + onNext(default) + cases.foreach(onNext) + case Inst.Throw(v, unwind) => + onVal(v) + onNext(unwind) + case Inst.Unreachable(unwind) => + onNext(unwind) + case _: Inst.LinktimeCf => + () + } + } + + def onOp(op: Op): Unit = op match { + case Op.Call(ty, ptrv, argvs) => + onType(ty) + onVal(ptrv) + argvs.foreach(onVal) + case Op.Load(ty, ptrv, sync) => + onType(ty) + onVal(ptrv) + case Op.Store(ty, ptrv, v, sync) => + onType(ty) + onVal(ptrv) + onVal(v) + case Op.Elem(ty, ptrv, indexvs) => + onType(ty) + onVal(ptrv) + indexvs.foreach(onVal) + case Op.Extract(aggrv, indexvs) => + onVal(aggrv) + case Op.Insert(aggrv, v, indexvs) => + onVal(aggrv) + onVal(v) + case Op.Stackalloc(ty, v) => + onType(ty) + onVal(v) + case Op.Bin(bin, ty, lv, rv) => + onType(ty) + onVal(lv) + onVal(rv) + case Op.Comp(comp, ty, lv, rv) => + onType(ty) + onVal(lv) + onVal(rv) + case Op.Conv(conv, ty, v) => + onType(ty) + onVal(v) + + case Op.Classalloc(n) => () + case Op.Fieldload(ty, v, n) => + onType(ty) + onVal(v) + case Op.Fieldstore(ty, v1, n, v2) => + onType(ty) + onVal(v1) + onVal(v2) + case Op.Field(v, n) => + onVal(v) + case Op.Method(v, n) => + onVal(v) + case Op.Dynmethod(obj, signature) => + onVal(obj) + case Op.Module(n) => () + case Op.As(ty, v) => + onType(ty) + onVal(v) + case Op.Is(ty, v) => + onType(ty) + onVal(v) + case Op.Copy(v) => + onVal(v) + case Op.Sizeof(ty) => + onType(ty) + case Op.Box(code, obj) => + onVal(obj) + case Op.Unbox(code, obj) => + onVal(obj) + case Op.Var(ty) => + onType(ty) + case Op.Varload(elem) => + onVal(elem) + case Op.Varstore(elem, value) => + onVal(elem) + onVal(value) + case Op.Arrayalloc(ty, init) => + onType(ty) + onVal(init) + case Op.Arrayload(ty, arr, idx) => + onType(ty) + onVal(arr) + onVal(idx) + case Op.Arraystore(ty, arr, idx, value) => + onType(ty) + onVal(arr) + onVal(idx) + onVal(value) + case Op.Arraylength(arr) => + onVal(arr) + case Op.Fence(_) => + () + } + + def onVal(value: Val): Unit = value match { + case Val.Zero(ty) => onType(ty) + case Val.StructValue(values) => values.foreach(onVal) + case Val.ArrayValue(ty, values) => + onType(ty) + values.foreach(onVal) + case Val.Local(n, ty) => onType(ty) + case Val.Global(n, ty) => onType(ty) + case Val.Const(v) => onVal(v) + case _ => () + } + + def onType(ty: Type): Unit = ty match { + case Type.ArrayValue(ty, n) => + onType(ty) + case Type.Function(args, ty) => + args.foreach(onType) + onType(ty) + case Type.StructValue(tys) => + tys.foreach(onType) + case Type.Var(ty) => + onType(ty) + case Type.Array(ty, nullable) => + onType(ty) + case _ => + () + } + + def onNext(next: Next): Unit = next match { + case Next.None => () + case Next.Case(v, n) => + onVal(v) + onNext(n) + case Next.Unwind(n, next) => onNext(next) + case Next.Label(n, args) => args.foreach(onVal) + } +} diff --git a/tools/src/main/scala/scala/scalanative/codegen/Lower.scala b/tools/src/main/scala/scala/scalanative/codegen/Lower.scala index 30173b12aa..a8c3e2bc0a 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/Lower.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/Lower.scala @@ -4,17 +4,7 @@ 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.linker._ import scalanative.interflow.UseDef.eliminateDeadCode object Lower { @@ -43,6 +33,7 @@ object Lower { private val fresh = new util.ScopedVar[Fresh] private val unwindHandler = new util.ScopedVar[Option[Local]] + private val currentDefn = new util.ScopedVar[Defn.Define] private val unreachableSlowPath = mutable.Map.empty[Option[Local], Local] private val nullPointerSlowPath = mutable.Map.empty[Option[Local], Local] @@ -79,7 +70,8 @@ object Lower { case defn: Defn.Define => val Type.Function(_, ty) = defn.ty: @unchecked ScopedVar.scoped( - fresh := Fresh(defn.insts) + fresh := Fresh(defn.insts), + currentDefn := defn ) { super.onDefn(defn) } @@ -126,6 +118,19 @@ object Lower { () } + val Inst.Label(firstLabel, _) = insts.head: @unchecked + val labelPositions = insts + .collect { case Inst.Label(id, _) => id } + .zipWithIndex + .toMap + var currentBlockPosition = labelPositions(firstLabel) + + genThisValueNullGuardIfUsed( + currentDefn.get, + buf, + () => newUnwindHandler(Next.None)(insts.head.pos) + ) + insts.tail.foreach { case inst @ Inst.Let(n, op, unwind) => ScopedVar.scoped( @@ -1153,6 +1158,51 @@ object Lower { Val.Const(Val.StructValue(rtti(StringCls).const +: fieldValues)) } + + private def genThisValueNullGuardIfUsed( + defn: Defn.Define, + buf: nir.Buffer, + createUnwindHandler: () => Option[Local] + ) = { + def usesValue(expected: Val): Boolean = { + var wasUsed = false + import scala.util.control.Breaks._ + breakable { + new Traverse { + override def onVal(value: 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: Type): Unit = () + override def onNext(next: Next): Unit = () + }.onDefn(defn) + } + wasUsed + } + + val Global.Member(_, sig) = defn.name: @unchecked + val 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: Type.Ref if ref.isNullable && usesValue(thisValue) => + implicit def pos: Position = defn.pos + ScopedVar.scoped( + unwindHandler := createUnwindHandler() + ) { + genGuardNotNull(buf, thisValue) + } + case _ => () + } + } + } + } } // Update java.lang.String::hashCode whenever you change this method. diff --git a/unit-tests/shared/src/test/scala-3/scala/issues/Scala3IssuesTest.scala b/unit-tests/shared/src/test/scala-3/scala/issues/Scala3IssuesTest.scala index 860763789a..03f1e63ef1 100644 --- a/unit-tests/shared/src/test/scala-3/scala/issues/Scala3IssuesTest.scala +++ b/unit-tests/shared/src/test/scala-3/scala/issues/Scala3IssuesTest.scala @@ -76,6 +76,17 @@ class Scala3IssuesTest: assertEquals("42", q.baz(21)) } + @Test def issue3014(): Unit = { + import scala.issues.issue3014._ + def useUnit(unit: TimeUnit): Long = { + // Was throwing `MatchError` when calling `toNanos` + unit.toNanos(1L) + } + + assertEquals(1L, useUnit(TimeUnit.Nanos)) + assertThrows(classOf[NullPointerException], () => useUnit(null)) + } + end Scala3IssuesTest private object issue2484 { @@ -98,3 +109,15 @@ private object issue2484 { def map[A, B](fa: F[A])(f: A => B): F[B] } } + +private object issue3014 { + enum TimeUnit { + case Millis + case Nanos + + def toNanos(value: Long): Long = this match { + case Millis => value * 1000000 + case Nanos => value + } + } +} From f570709ad41a8b6a6f0b87489307d8d1d358fb97 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 1 Feb 2023 13:16:53 +0100 Subject: [PATCH 08/61] Refactor providing memory layout and platform information (#3122) * Make `Metadata` an implicit context parameter in tools codegen * Centralize definitions of common memory layout (rtti, object/array headers) in tools. Replace magic values with constants * Centralize runtime memory layouts for Rtti and object/array headers. Replace magic values with constants * Introduce PlatformInfo containing platform metadata, eg. size of ptr. It is implicitly passed to `MemoryLayout` and used in few other places --- .../scala/scalanative/runtime/Arrays.scala | 308 +++++++----------- .../scalanative/runtime/Arrays.scala.gyb | 37 +-- .../scalanative/runtime/MemoryLayout.scala | 45 +++ .../scala/scalanative/runtime/package.scala | 4 +- .../scala/scalanative/nir/Traverse.scala | 6 +- .../scalanative/codegen/AbstractCodeGen.scala | 8 +- .../scala/scalanative/codegen/CodeGen.scala | 13 +- .../codegen/CommonMemoryLayouts.scala | 72 ++++ .../scalanative/codegen/DynamicHashMap.scala | 11 +- .../scalanative/codegen/FieldLayout.scala | 24 +- .../scala/scalanative/codegen/Generate.scala | 2 +- .../scala/scalanative/codegen/Lower.scala | 76 +++-- .../scalanative/codegen/MemoryLayout.scala | 14 +- .../scala/scalanative/codegen/Metadata.scala | 27 +- .../scalanative/codegen/PlatformInfo.scala | 17 + .../codegen/RuntimeTypeInformation.scala | 57 ++-- .../scalanative/codegen/VirtualTable.scala | 2 +- .../scalanative/interflow/Interflow.scala | 9 +- .../scalanative/interflow/Whitelist.scala | 4 + 19 files changed, 389 insertions(+), 347 deletions(-) create mode 100644 nativelib/src/main/scala/scala/scalanative/runtime/MemoryLayout.scala create mode 100644 tools/src/main/scala/scala/scalanative/codegen/CommonMemoryLayouts.scala create mode 100644 tools/src/main/scala/scala/scalanative/codegen/PlatformInfo.scala diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala b/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala index e893befc89..49db30321f 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala +++ b/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala @@ -38,7 +38,7 @@ sealed abstract class Array[T] /** Number of elements of the array. */ @inline def length: Int = { val rawptr = castObjectToRawPtr(this) - val lenptr = elemRawPtr(rawptr, 8) + val lenptr = elemRawPtr(rawptr, MemoryLayout.Array.LengthOffset) loadInt(lenptr) } @@ -163,30 +163,16 @@ final class BooleanArray private () extends Array[Boolean] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 1 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 1 * i) } - @inline def apply(i: Int): Boolean = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 1 * i) - loadBoolean(ith) - } + @inline def apply(i: Int): Boolean = loadBoolean(atRaw(i)) - @inline def update(i: Int, value: Boolean): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 1 * i) - storeBoolean(ith, value) - } + @inline def update(i: Int, value: Boolean): Unit = storeBoolean(atRaw(i), value) @inline override def clone(): BooleanArray = { val arrcls = classOf[BooleanArray] - val arrsize = (16 + 1 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 1 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -197,11 +183,15 @@ final class BooleanArray private () extends Array[Boolean] { object BooleanArray { @inline def alloc(length: Int): BooleanArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[BooleanArray] - val arrsize = (16 + 1 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 1 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 1.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 1) castRawPtrToObject(arr).asInstanceOf[BooleanArray] } @@ -209,7 +199,7 @@ object BooleanArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (1 * length).toULong + val size = new ULong(1 * length) libc.memcpy(dst, src, size) arr } @@ -225,30 +215,16 @@ final class CharArray private () extends Array[Char] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 2 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 2 * i) } - @inline def apply(i: Int): Char = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 2 * i) - loadChar(ith) - } + @inline def apply(i: Int): Char = loadChar(atRaw(i)) - @inline def update(i: Int, value: Char): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 2 * i) - storeChar(ith, value) - } + @inline def update(i: Int, value: Char): Unit = storeChar(atRaw(i), value) @inline override def clone(): CharArray = { val arrcls = classOf[CharArray] - val arrsize = (16 + 2 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 2 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -259,11 +235,15 @@ final class CharArray private () extends Array[Char] { object CharArray { @inline def alloc(length: Int): CharArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[CharArray] - val arrsize = (16 + 2 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 2 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 2.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 2) castRawPtrToObject(arr).asInstanceOf[CharArray] } @@ -271,7 +251,7 @@ object CharArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (2 * length).toULong + val size = new ULong(2 * length) libc.memcpy(dst, src, size) arr } @@ -287,30 +267,16 @@ final class ByteArray private () extends Array[Byte] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 1 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 1 * i) } - @inline def apply(i: Int): Byte = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 1 * i) - loadByte(ith) - } + @inline def apply(i: Int): Byte = loadByte(atRaw(i)) - @inline def update(i: Int, value: Byte): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 1 * i) - storeByte(ith, value) - } + @inline def update(i: Int, value: Byte): Unit = storeByte(atRaw(i), value) @inline override def clone(): ByteArray = { val arrcls = classOf[ByteArray] - val arrsize = (16 + 1 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 1 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -321,11 +287,15 @@ final class ByteArray private () extends Array[Byte] { object ByteArray { @inline def alloc(length: Int): ByteArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[ByteArray] - val arrsize = (16 + 1 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 1 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 1.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 1) castRawPtrToObject(arr).asInstanceOf[ByteArray] } @@ -333,7 +303,7 @@ object ByteArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (1 * length).toULong + val size = new ULong(1 * length) libc.memcpy(dst, src, size) arr } @@ -349,30 +319,16 @@ final class ShortArray private () extends Array[Short] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 2 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 2 * i) } - @inline def apply(i: Int): Short = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 2 * i) - loadShort(ith) - } + @inline def apply(i: Int): Short = loadShort(atRaw(i)) - @inline def update(i: Int, value: Short): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 2 * i) - storeShort(ith, value) - } + @inline def update(i: Int, value: Short): Unit = storeShort(atRaw(i), value) @inline override def clone(): ShortArray = { val arrcls = classOf[ShortArray] - val arrsize = (16 + 2 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 2 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -383,11 +339,15 @@ final class ShortArray private () extends Array[Short] { object ShortArray { @inline def alloc(length: Int): ShortArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[ShortArray] - val arrsize = (16 + 2 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 2 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 2.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 2) castRawPtrToObject(arr).asInstanceOf[ShortArray] } @@ -395,7 +355,7 @@ object ShortArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (2 * length).toULong + val size = new ULong(2 * length) libc.memcpy(dst, src, size) arr } @@ -411,30 +371,16 @@ final class IntArray private () extends Array[Int] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 4 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 4 * i) } - @inline def apply(i: Int): Int = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 4 * i) - loadInt(ith) - } + @inline def apply(i: Int): Int = loadInt(atRaw(i)) - @inline def update(i: Int, value: Int): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 4 * i) - storeInt(ith, value) - } + @inline def update(i: Int, value: Int): Unit = storeInt(atRaw(i), value) @inline override def clone(): IntArray = { val arrcls = classOf[IntArray] - val arrsize = (16 + 4 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 4 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -445,11 +391,15 @@ final class IntArray private () extends Array[Int] { object IntArray { @inline def alloc(length: Int): IntArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[IntArray] - val arrsize = (16 + 4 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 4 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 4.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 4) castRawPtrToObject(arr).asInstanceOf[IntArray] } @@ -457,7 +407,7 @@ object IntArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (4 * length).toULong + val size = new ULong(4 * length) libc.memcpy(dst, src, size) arr } @@ -473,30 +423,16 @@ final class LongArray private () extends Array[Long] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 8 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 8 * i) } - @inline def apply(i: Int): Long = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 8 * i) - loadLong(ith) - } + @inline def apply(i: Int): Long = loadLong(atRaw(i)) - @inline def update(i: Int, value: Long): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 8 * i) - storeLong(ith, value) - } + @inline def update(i: Int, value: Long): Unit = storeLong(atRaw(i), value) @inline override def clone(): LongArray = { val arrcls = classOf[LongArray] - val arrsize = (16 + 8 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 8 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -507,11 +443,15 @@ final class LongArray private () extends Array[Long] { object LongArray { @inline def alloc(length: Int): LongArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[LongArray] - val arrsize = (16 + 8 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 8 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 8.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 8) castRawPtrToObject(arr).asInstanceOf[LongArray] } @@ -519,7 +459,7 @@ object LongArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (8 * length).toULong + val size = new ULong(8 * length) libc.memcpy(dst, src, size) arr } @@ -535,30 +475,16 @@ final class FloatArray private () extends Array[Float] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 4 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 4 * i) } - @inline def apply(i: Int): Float = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 4 * i) - loadFloat(ith) - } + @inline def apply(i: Int): Float = loadFloat(atRaw(i)) - @inline def update(i: Int, value: Float): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 4 * i) - storeFloat(ith, value) - } + @inline def update(i: Int, value: Float): Unit = storeFloat(atRaw(i), value) @inline override def clone(): FloatArray = { val arrcls = classOf[FloatArray] - val arrsize = (16 + 4 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 4 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -569,11 +495,15 @@ final class FloatArray private () extends Array[Float] { object FloatArray { @inline def alloc(length: Int): FloatArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[FloatArray] - val arrsize = (16 + 4 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 4 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 4.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 4) castRawPtrToObject(arr).asInstanceOf[FloatArray] } @@ -581,7 +511,7 @@ object FloatArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (4 * length).toULong + val size = new ULong(4 * length) libc.memcpy(dst, src, size) arr } @@ -597,30 +527,16 @@ final class DoubleArray private () extends Array[Double] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 8 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 8 * i) } - @inline def apply(i: Int): Double = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 8 * i) - loadDouble(ith) - } + @inline def apply(i: Int): Double = loadDouble(atRaw(i)) - @inline def update(i: Int, value: Double): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 8 * i) - storeDouble(ith, value) - } + @inline def update(i: Int, value: Double): Unit = storeDouble(atRaw(i), value) @inline override def clone(): DoubleArray = { val arrcls = classOf[DoubleArray] - val arrsize = (16 + 8 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 8 * length) val arr = GC.alloc_atomic(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -631,11 +547,15 @@ final class DoubleArray private () extends Array[Double] { object DoubleArray { @inline def alloc(length: Int): DoubleArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[DoubleArray] - val arrsize = (16 + 8 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 8 * length) val arr = GC.alloc_atomic(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 8.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 8) castRawPtrToObject(arr).asInstanceOf[DoubleArray] } @@ -643,7 +563,7 @@ object DoubleArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (8 * length).toULong + val size = new ULong(8 * length) libc.memcpy(dst, src, size) arr } @@ -659,30 +579,16 @@ final class ObjectArray private () extends Array[Object] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, 16 + 8 * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + 8 * i) } - @inline def apply(i: Int): Object = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 8 * i) - loadObject(ith) - } + @inline def apply(i: Int): Object = loadObject(atRaw(i)) - @inline def update(i: Int, value: Object): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, 16 + 8 * i) - storeObject(ith, value) - } + @inline def update(i: Int, value: Object): Unit = storeObject(atRaw(i), value) @inline override def clone(): ObjectArray = { val arrcls = classOf[ObjectArray] - val arrsize = (16 + 8 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 8 * length) val arr = GC.alloc(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -693,11 +599,15 @@ final class ObjectArray private () extends Array[Object] { object ObjectArray { @inline def alloc(length: Int): ObjectArray = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[ObjectArray] - val arrsize = (16 + 8 * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + 8 * length) val arr = GC.alloc(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), 8.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), 8) castRawPtrToObject(arr).asInstanceOf[ObjectArray] } @@ -705,7 +615,7 @@ object ObjectArray { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (8 * length).toULong + val size = new ULong(8 * length) libc.memcpy(dst, src, size) arr } diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala.gyb b/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala.gyb index 89954b67f0..e086aacdcc 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala.gyb +++ b/nativelib/src/main/scala/scala/scalanative/runtime/Arrays.scala.gyb @@ -32,7 +32,6 @@ import scalanative.unsigned._ import scalanative.runtime.Intrinsics._ % sizePtr = 8 -% sizeHeader = 16 sealed abstract class Array[T] extends java.io.Serializable with java.lang.Cloneable { @@ -40,7 +39,7 @@ sealed abstract class Array[T] /** Number of elements of the array. */ @inline def length: Int = { val rawptr = castObjectToRawPtr(this) - val lenptr = elemRawPtr(rawptr, ${sizePtr}) + val lenptr = elemRawPtr(rawptr, MemoryLayout.Array.LengthOffset) loadInt(lenptr) } @@ -181,30 +180,16 @@ final class ${T}Array private () extends Array[${T}] { throwOutOfBounds(i) } else { val rawptr = castObjectToRawPtr(this) - elemRawPtr(rawptr, ${sizeHeader} + ${sizeT} * i) + elemRawPtr(rawptr, MemoryLayout.Array.ValuesOffset + ${sizeT} * i) } - @inline def apply(i: Int): ${T} = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, ${sizeHeader} + ${sizeT} * i) - load${T}(ith) - } + @inline def apply(i: Int): ${T} = load${T}(atRaw(i)) - @inline def update(i: Int, value: ${T}): Unit = - if (i < 0 || i >= length) { - throwOutOfBounds(i) - } else { - val rawptr = castObjectToRawPtr(this) - val ith = elemRawPtr(rawptr, ${sizeHeader} + ${sizeT} * i) - store${T}(ith, value) - } + @inline def update(i: Int, value: ${T}): Unit = store${T}(atRaw(i), value) @inline override def clone(): ${T}Array = { val arrcls = classOf[${T}Array] - val arrsize = (${sizeHeader} + ${sizeT} * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + ${sizeT} * length) val arr = ${alloc}(arrcls, arrsize) val src = castObjectToRawPtr(this) libc.memcpy(arr, src, arrsize) @@ -215,11 +200,15 @@ final class ${T}Array private () extends Array[${T}] { object ${T}Array { @inline def alloc(length: Int): ${T}Array = { + if (length < 0) { + throw new NegativeArraySizeException + } + val arrcls = classOf[${T}Array] - val arrsize = (${sizeHeader} + ${sizeT} * length).toULong + val arrsize = new ULong(MemoryLayout.Array.ValuesOffset + ${sizeT} * length) val arr = ${alloc}(arrcls, arrsize) - storeInt(elemRawPtr(arr, 8), length) - storeInt(elemRawPtr(arr, 12), ${sizeT}.toInt) + storeInt(elemRawPtr(arr, MemoryLayout.Array.LengthOffset), length) + storeInt(elemRawPtr(arr, MemoryLayout.Array.StrideOffset), ${sizeT}) castRawPtrToObject(arr).asInstanceOf[${T}Array] } @@ -227,7 +216,7 @@ object ${T}Array { val arr = alloc(length) val dst = arr.atRaw(0) val src = data - val size = (${sizeT} * length).toULong + val size = new ULong(${sizeT} * length) libc.memcpy(dst, src, size) arr } diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/MemoryLayout.scala b/nativelib/src/main/scala/scala/scalanative/runtime/MemoryLayout.scala new file mode 100644 index 0000000000..f93a54ea71 --- /dev/null +++ b/nativelib/src/main/scala/scala/scalanative/runtime/MemoryLayout.scala @@ -0,0 +1,45 @@ +package scala.scalanative.runtime + +import scala.scalanative.annotation.alwaysinline + +object MemoryLayout { + + /* Even though it might seem non-idiomatic to use `def` instead of `final val` + * for the constants it actual can be faster at runtime. Vals would require + * a fieldload operation and loading the module instance. Def would be + * evaluated and inlined in the optimizer - it would result with replacing + * method call with a constant value. + */ + + @alwaysinline private def PtrSize = 8 + @alwaysinline private def IntSize = 4 + + private[scalanative] object Rtti { + @alwaysinline def ClassOffset = 0 + @alwaysinline def IdOffset = ClassOffset + PtrSize + @alwaysinline def TraitIdOffset = IdOffset + IntSize + @alwaysinline def NameOffset = TraitIdOffset + IntSize + + @alwaysinline def size = NameOffset + PtrSize + } + + private[scalanative] object ClassRtti { + @alwaysinline def RttiOffset = 0 + @alwaysinline def SizeOffset = RttiOffset + Rtti.size + // Remaining fields has optional or contain intrinsic data, + // they should never be accessed in the runtime + } + + private[scalanative] object Object { + @alwaysinline def RttiOffset = 0 + @alwaysinline def FieldsOffset = RttiOffset + PtrSize + } + + private[scalanative] object Array { + @alwaysinline def RttiOffset = 0 + @alwaysinline def LengthOffset = RttiOffset + PtrSize + @alwaysinline def StrideOffset = LengthOffset + IntSize + @alwaysinline def ValuesOffset = StrideOffset + IntSize + } + +} diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/package.scala b/nativelib/src/main/scala/scala/scalanative/runtime/package.scala index f5ad137d41..1fe10116b9 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/package.scala +++ b/nativelib/src/main/scala/scala/scalanative/runtime/package.scala @@ -64,8 +64,8 @@ package object runtime { /** Called by the generated code in case of incorrect class cast. */ @noinline def throwClassCast(from: RawPtr, to: RawPtr): Nothing = { - val fromName = loadObject(elemRawPtr(from, 16)) - val toName = loadObject(elemRawPtr(to, 16)) + val fromName = loadObject(elemRawPtr(from, MemoryLayout.Rtti.NameOffset)) + val toName = loadObject(elemRawPtr(to, MemoryLayout.Rtti.NameOffset)) throw new java.lang.ClassCastException( s"$fromName cannot be cast to $toName" ) diff --git a/nir/src/main/scala/scala/scalanative/nir/Traverse.scala b/nir/src/main/scala/scala/scalanative/nir/Traverse.scala index 7556666904..f5f6fb6269 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Traverse.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Traverse.scala @@ -58,10 +58,10 @@ trait Traverse { onType(ty) onVal(ptrv) argvs.foreach(onVal) - case Op.Load(ty, ptrv, sync) => + case Op.Load(ty, ptrv) => onType(ty) onVal(ptrv) - case Op.Store(ty, ptrv, v, sync) => + case Op.Store(ty, ptrv, v) => onType(ty) onVal(ptrv) onVal(v) @@ -139,8 +139,6 @@ trait Traverse { onVal(value) case Op.Arraylength(arr) => onVal(arr) - case Op.Fence(_) => - () } def onVal(value: Val): Unit = value match { diff --git a/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala b/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala index 87046f6aae..b33731361c 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala @@ -12,13 +12,13 @@ import scala.scalanative.util.{ShowBuilder, unreachable, unsupported} import scala.scalanative.{build, linker, nir} private[codegen] abstract class AbstractCodeGen( - val config: build.Config, env: Map[Global, Defn], defns: Seq[Defn] )(implicit meta: Metadata) { - val os: OsCompat + import meta.platform + import platform._ - private val targetTriple: Option[String] = config.compilerConfig.targetTriple + val os: OsCompat private var currentBlockName: Local = _ private var currentBlockSplit: Int = _ @@ -174,7 +174,7 @@ private[codegen] abstract class AbstractCodeGen( newline() str(if (isDecl) "declare " else "define ") - if (config.targetsWindows && !isDecl && attrs.isExtern) { + if (targetsWindows && !isDecl && attrs.isExtern) { // Generate export modifier only for extern (C-ABI compliant) signatures val Global.Member(_, sig) = name: @unchecked if (sig.isExtern) str("dllexport ") diff --git a/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala b/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala index d2abe79af9..4e4dfec78b 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala @@ -19,6 +19,7 @@ object CodeGen { val defns = linked.defns val proxies = GenerateReflectiveProxies(linked.dynimpls, defns) + implicit val platform: PlatformInfo = PlatformInfo(config) implicit val meta: Metadata = new Metadata(linked, proxies) val generated = Generate(encodedMainClass(config), defns ++ proxies) @@ -58,7 +59,7 @@ object CodeGen { .map { case (id, defns) => val sorted = defns.sortBy(_.name.show) - Impl(config, env, sorted).gen(id.toString, workdir) + Impl(env, sorted).gen(id.toString, workdir) } .toSeq .seq @@ -91,7 +92,7 @@ object CodeGen { val sorted = defns.sortBy(_.name.show) if (!Files.exists(ownerDirectory)) Files.createDirectories(ownerDirectory) - Impl(config, env, sorted).gen(packagePath, workdir) + Impl(env, sorted).gen(packagePath, workdir) } else { assert(ownerDirectory.toFile.exists()) config.logger.debug( @@ -114,7 +115,7 @@ object CodeGen { // Clang's LTO is not available. def single(): Seq[Path] = { val sorted = assembly.sortBy(_.name.show) - Impl(config, env, sorted).gen(id = "out", workdir) :: Nil + Impl(env, sorted).gen(id = "out", workdir) :: Nil } import build.Mode._ @@ -132,12 +133,12 @@ object CodeGen { import scala.scalanative.codegen.AbstractCodeGen import scala.scalanative.codegen.compat.os._ - def apply(config: Config, env: Map[Global, Defn], defns: Seq[Defn])(implicit + def apply(env: Map[Global, Defn], defns: Seq[Defn])(implicit meta: Metadata ): AbstractCodeGen = { - new AbstractCodeGen(config, env, defns) { + new AbstractCodeGen(env, defns) { override val os: OsCompat = { - if (this.config.targetsWindows) new WindowsCompat(this) + if (meta.platform.targetsWindows) new WindowsCompat(this) else new UnixCompat(this) } } diff --git a/tools/src/main/scala/scala/scalanative/codegen/CommonMemoryLayouts.scala b/tools/src/main/scala/scala/scalanative/codegen/CommonMemoryLayouts.scala new file mode 100644 index 0000000000..f8c76dd8ff --- /dev/null +++ b/tools/src/main/scala/scala/scalanative/codegen/CommonMemoryLayouts.scala @@ -0,0 +1,72 @@ +package scala.scalanative.codegen + +import scala.scalanative.nir._ + +class CommonMemoryLayouts(implicit meta: Metadata) { + sealed trait Layout { + val layout: Type.StructValue + lazy val fields: Int = layout.tys.size + } + + object Rtti extends Layout { + val layout = Type.StructValue( + Type.Ptr :: // ClassRtti + Type.Int :: // ClassId + Type.Int :: // Traitid + Type.Ptr :: // ClassName + Nil + ) + final val RttiIdx = 0 + final val ClassIdIdx = RttiIdx + 1 + final val TraitIdIdx = ClassIdIdx + 1 + final val ClassNameIdx = TraitIdIdx + 1 + } + + // RTTI specific for classess, see class RuntimeTypeInformation + object ClassRtti extends Layout { + val usesDynMap = meta.linked.dynsigs.nonEmpty + private val dynMapType = if (usesDynMap) Some(DynamicHashMap.ty) else None + // Common layout not including variable-sized virtual table + private val baseLayout = + Rtti.layout :: + Type.Int :: // class size + Type.Int :: // id range + FieldLayout.referenceOffsetsTy :: // reference offsets + dynMapType.toList + + val layout = genLayout(vtable = Type.ArrayValue(Type.Ptr, 0)) + + def genLayout(vtable: Type): Type.StructValue = Type.StructValue( + baseLayout ::: vtable :: Nil + ) + + final val RttiIdx = 0 + final val SizeIdx = RttiIdx + 1 + final val IdRangeIdx = SizeIdx + 1 + final val ReferenceOffsetsIdx = IdRangeIdx + 1 + final val DynmapIdx = + if (usesDynMap) ReferenceOffsetsIdx + 1 else -1 + final val VtableIdx = + (if (usesDynMap) DynmapIdx else ReferenceOffsetsIdx) + 1 + } + + object ObjectHeader extends Layout { + val layout = Type.StructValue( + Type.Ptr :: // RTTI + Nil + ) + } + + object ArrayHeader extends Layout { + val layout = Type.StructValue( + Type.Ptr :: // RTTI + Type.Int :: // length + Type.Int :: // stride (used only by GC) + Nil + ) + + final val RttiIdx = 0 + final val LengthIdx = RttiIdx + 1 + final val StrideIdx = LengthIdx + 1 + } +} diff --git a/tools/src/main/scala/scala/scalanative/codegen/DynamicHashMap.scala b/tools/src/main/scala/scala/scalanative/codegen/DynamicHashMap.scala index fd7e3aeb73..230e437562 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/DynamicHashMap.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/DynamicHashMap.scala @@ -4,7 +4,11 @@ package codegen import scalanative.nir._ import scalanative.linker.{Class, Method} -class DynamicHashMap(meta: Metadata, cls: Class, proxies: Seq[Defn]) { +object DynamicHashMap { + final val ty: Type = Type.Ptr +} + +class DynamicHashMap(cls: Class, proxies: Seq[Defn])(implicit meta: Metadata) { val methods: Seq[Global.Member] = { val own = proxies.collect { case p if p.name.top == cls.name => @@ -15,8 +19,5 @@ class DynamicHashMap(meta: Metadata, cls: Class, proxies: Seq[Defn]) { .fold(Seq.empty[Global.Member])(meta.dynmap(_).methods) .filterNot(m => sigs.contains(m.sig)) ++ own } - val ty: Type = - Type.Ptr - val value: Val = - DynmethodPerfectHashMap(methods, meta.linked.dynsigs) + val value: Val = DynmethodPerfectHashMap(methods, meta.linked.dynsigs) } diff --git a/tools/src/main/scala/scala/scalanative/codegen/FieldLayout.scala b/tools/src/main/scala/scala/scalanative/codegen/FieldLayout.scala index 0e85018dd4..4d1130eec7 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/FieldLayout.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/FieldLayout.scala @@ -4,9 +4,15 @@ package codegen import scalanative.nir._ import scalanative.linker.{Class, Field} -class FieldLayout(meta: Metadata, cls: Class) { - def index(fld: Field) = - entries.indexOf(fld) + 1 +object FieldLayout { + val referenceOffsetsTy = Type.StructValue(Seq(Type.Ptr)) +} + +class FieldLayout(cls: Class)(implicit meta: Metadata) { + import meta.layouts.ObjectHeader + import meta.platform + + def index(fld: Field) = entries.indexOf(fld) + ObjectHeader.fields val entries: Seq[Field] = { val base = cls.parent.fold { Seq.empty[Field] @@ -15,15 +21,11 @@ class FieldLayout(meta: Metadata, cls: Class) { } val struct: Type.StructValue = { val data = entries.map(_.ty) - val body = Type.Ptr +: data - Type.StructValue(body) + Type.StructValue(ObjectHeader.layout +: data) } val layout = MemoryLayout(struct.tys) val size = layout.size - val referenceOffsetsTy = - Type.StructValue(Seq(Type.Ptr)) - val referenceOffsetsValue = - Val.StructValue( - Seq(Val.Const(Val.ArrayValue(Type.Long, layout.offsetArray))) - ) + val referenceOffsetsValue = Val.StructValue( + Seq(Val.Const(Val.ArrayValue(Type.Long, layout.offsetArray))) + ) } diff --git a/tools/src/main/scala/scala/scalanative/codegen/Generate.scala b/tools/src/main/scala/scala/scalanative/codegen/Generate.scala index a8ba839be3..281ed2c15d 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/Generate.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/Generate.scala @@ -276,7 +276,7 @@ object Generate { val instanceDefn = Defn.Const( Attrs.None, instanceName, - Type.StructValue(Seq(Type.Ptr)), + meta.layouts.ObjectHeader.layout, instanceVal ) diff --git a/tools/src/main/scala/scala/scalanative/codegen/Lower.scala b/tools/src/main/scala/scala/scalanative/codegen/Lower.scala index a8c3e2bc0a..f3517f04c8 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/Lower.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/Lower.scala @@ -14,11 +14,20 @@ object Lower { private final class Impl(implicit meta: Metadata) extends Transform { import meta._ + import meta.layouts.{Rtti, ClassRtti, ArrayHeader} implicit val linked: Result = meta.linked val Object = linked.infos(Rt.Object.name).asInstanceOf[Class] + private val zero = Val.Int(0) + private val one = Val.Int(1) + val RttiClassIdPath = Seq(zero, Val.Int(Rtti.ClassIdIdx)) + val RttiTraitIdPath = Seq(zero, Val.Int(Rtti.TraitIdIdx)) + val ClassRttiDynmapPath = Seq(zero, Val.Int(ClassRtti.DynmapIdx)) + val ClassRttiVtablePath = Seq(zero, Val.Int(ClassRtti.VtableIdx)) + val ArrayHeaderLengthPath = Seq(zero, Val.Int(ArrayHeader.LengthIdx)) + // Type of the bare runtime type information struct. private val classRttiType = rtti(linked.infos(Global.Top("java.lang.Object"))).struct @@ -113,7 +122,7 @@ object Lower { insts.foreach { case inst @ Inst.Let(n, Op.Var(ty), unwind) => - buf.let(n, Op.Stackalloc(ty, Val.Int(1)), unwind)(inst.pos) + buf.let(n, Op.Stackalloc(ty, one), unwind)(inst.pos) case _ => () } @@ -423,7 +432,7 @@ object Lower { val outOfBoundsL = outOfBoundsSlowPath.getOrElseUpdate(unwindHandler, fresh()) - val gt0 = comp(Comp.Sge, Type.Int, idx, Val.Int(0), unwind) + val gt0 = comp(Comp.Sge, Type.Int, idx, zero, 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))) @@ -442,7 +451,7 @@ object Lower { val index = layout.index(fld) genGuardNotNull(buf, v) - elem(ty, v, Seq(Val.Int(0), Val.Int(index)), unwind) + elem(ty, v, Seq(zero, Val.Int(index)), unwind) } def genFieldloadOp(buf: Buffer, n: Local, op: Op.Fieldload)(implicit @@ -522,7 +531,7 @@ object Lower { Op.Elem( rtti(cls).struct, typeptr, - meta.RttiVtableIndex :+ Val.Int(vindex) + ClassRttiVtablePath :+ Val.Int(vindex) ), unwind ) @@ -534,7 +543,7 @@ object Lower { 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) + let(Op.Elem(Rtti.layout, typeptr, RttiTraitIdPath), unwind) val id = let(Op.Load(Type.Int, idptr), unwind) val rowptr = let( Op.Elem( @@ -635,7 +644,7 @@ object Lower { // 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 mapelem = elem(classRttiType, typeptr, ClassRttiDynmapPath, unwind) val mapptr = load(Type.Ptr, mapelem, unwind) // If hash map is not null, it has to contain at least one entry throwIfNull(mapptr) @@ -699,7 +708,10 @@ object Lower { 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) + let( + Op.Elem(Rtti.layout, typeptr, RttiClassIdPath), + 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) @@ -710,13 +722,16 @@ object Lower { case TraitRef(trt) => val typeptr = let(Op.Load(Type.Ptr, obj), unwind) val idptr = - let(Op.Elem(meta.Rtti, typeptr, meta.RttiClassIdIndex), unwind) + let( + Op.Elem(Rtti.layout, typeptr, RttiClassIdPath), + 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))) + Seq(zero, id, Val.Int(meta.ids(trt))) ), unwind ) @@ -763,7 +778,8 @@ object Lower { ): Unit = { val Op.Sizeof(ty) = op - buf.let(n, Op.Copy(Val.Long(MemoryLayout.sizeOf(ty))), unwind) + val memorySize = MemoryLayout.sizeOf(ty) + buf.let(n, Op.Copy(Val.Long(memorySize)), unwind) } def genClassallocOp(buf: Buffer, n: Local, op: Op.Classalloc)(implicit @@ -771,7 +787,7 @@ object Lower { ): Unit = { val Op.Classalloc(ClassRef(cls)) = op: @unchecked - val size = MemoryLayout.sizeOf(layout(cls).struct) + val size = layout(cls).size val allocMethod = if (size < LARGE_OBJECT_MIN_SIZE) alloc else largeAlloc @@ -1079,6 +1095,14 @@ object Lower { } } + private def arrayMemoryLayout( + ty: nir.Type, + length: Int = 0 + ): Type.StructValue = Type.StructValue( + Seq(ArrayHeader.layout, Type.ArrayValue(ty, length)) + ) + private def arrayValuePath(idx: Val) = Seq(zero, one, idx) + def genArrayloadOp(buf: Buffer, n: Local, op: Op.Arrayload)(implicit pos: Position ): Unit = { @@ -1090,11 +1114,8 @@ object Lower { 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) + val arrTy = arrayMemoryLayout(ty) + val elemPtr = buf.elem(arrTy, arr, arrayValuePath(idx), unwind) buf.let(n, Op.Load(ty, elemPtr), unwind) } @@ -1108,11 +1129,8 @@ object Lower { 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) + val arrTy = arrayMemoryLayout(ty) + val elemPtr = buf.elem(arrTy, arr, arrayValuePath(idx), unwind) genStoreOp(buf, n, Op.Store(ty, elemPtr, value)) } @@ -1126,8 +1144,8 @@ object Lower { 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) + val lenPtr = + buf.elem(ArrayHeader.layout, arr, ArrayHeaderLengthPath, unwind) buf.let(n, Op.Load(Type.Int, lenPtr), unwind) } @@ -1139,18 +1157,16 @@ object Lower { 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(_))) - ) + rtti(CharArrayCls).const :: + charsLength :: + zero :: // stride is used only by GC, global instances use it as padding + Val.ArrayValue(Type.Char, chars.toSeq.map(Val.Char(_))) :: Nil ) ) val fieldValues = stringFieldNames.map { case Rt.StringValueName => charsConst - case Rt.StringOffsetName => Val.Int(0) + case Rt.StringOffsetName => zero case Rt.StringCountName => charsLength case Rt.StringCachedHashCodeName => Val.Int(stringHashCode(value)) case _ => util.unreachable diff --git a/tools/src/main/scala/scala/scalanative/codegen/MemoryLayout.scala b/tools/src/main/scala/scala/scalanative/codegen/MemoryLayout.scala index 0d884a3b7b..b30ce7ea9e 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/MemoryLayout.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/MemoryLayout.scala @@ -5,18 +5,19 @@ import scala.collection.mutable import scalanative.nir.Type.RefKind import scalanative.nir.{Type, Val} import scalanative.util.unsupported -import scalanative.codegen.MemoryLayout.PositionedType final case class MemoryLayout( size: Long, tys: Seq[MemoryLayout.PositionedType] ) { - lazy val offsetArray: Seq[Val] = { + def offsetArray(implicit meta: Metadata): Seq[Val] = { val ptrOffsets = tys.collect { - // offset in words without rtti + // offset in words without object header case MemoryLayout.PositionedType(_: RefKind, offset) => - Val.Long(offset / MemoryLayout.WORD_SIZE - 1) + Val.Long( + offset / MemoryLayout.WORD_SIZE - meta.layouts.ObjectHeader.fields + ) } ptrOffsets :+ Val.Long(-1) @@ -74,7 +75,10 @@ object MemoryLayout { offset += sizeOf(ty) } - val alignment = if (tys.isEmpty) 1 else tys.map(alignmentOf).max + val alignment = { + if (tys.isEmpty) 1 + else tys.map(alignmentOf(_)).max + } MemoryLayout(align(offset, alignment), pos.toSeq) } diff --git a/tools/src/main/scala/scala/scalanative/codegen/Metadata.scala b/tools/src/main/scala/scala/scalanative/codegen/Metadata.scala index 290e19735f..9825ce4471 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/Metadata.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/Metadata.scala @@ -5,7 +5,12 @@ import scala.collection.mutable import scalanative.nir._ import scalanative.linker.{Trait, Class} -class Metadata(val linked: linker.Result, proxies: Seq[Defn]) { +class Metadata(val linked: linker.Result, proxies: Seq[Defn])(implicit + val platform: PlatformInfo +) { + implicit private def self: Metadata = this + + val layouts = new CommonMemoryLayouts() val rtti = mutable.Map.empty[linker.Info, RuntimeTypeInformation] val vtable = mutable.Map.empty[linker.Class, VirtualTable] val layout = mutable.Map.empty[linker.Class, FieldLayout] @@ -19,14 +24,6 @@ class Metadata(val linked: linker.Result, proxies: Seq[Defn]) { val dispatchTable = new TraitDispatchTable(this) val hasTraitTables = new HasTraitTables(this) - val Rtti = Type.StructValue(Seq(Type.Ptr, Type.Int, Type.Int, Type.Ptr)) - val RttiClassIdIndex = Seq(Val.Int(0), Val.Int(1)) - val RttiTraitIdIndex = Seq(Val.Int(0), Val.Int(2)) - val RttiVtableIndex = - Seq(Val.Int(0), Val.Int(if (linked.dynsigs.isEmpty) 4 else 5)) - val RttiDynmapIndex = - Seq(Val.Int(0), Val.Int(if (linked.dynsigs.isEmpty) -1 else 4)) - initClassMetadata() initTraitMetadata() @@ -66,18 +63,18 @@ class Metadata(val linked: linker.Result, proxies: Seq[Defn]) { def initClassMetadata(): Unit = { classes.foreach { node => - vtable(node) = new VirtualTable(this, node) - layout(node) = new FieldLayout(this, node) - if (linked.dynsigs.nonEmpty) { - dynmap(node) = new DynamicHashMap(this, node, proxies) + vtable(node) = new VirtualTable(node) + layout(node) = new FieldLayout(node) + if (layouts.ClassRtti.usesDynMap) { + dynmap(node) = new DynamicHashMap(node, proxies) } - rtti(node) = new RuntimeTypeInformation(this, node) + rtti(node) = new RuntimeTypeInformation(node) } } def initTraitMetadata(): Unit = { traits.foreach { node => - rtti(node) = new RuntimeTypeInformation(this, node) + rtti(node) = new RuntimeTypeInformation(node) } } } diff --git a/tools/src/main/scala/scala/scalanative/codegen/PlatformInfo.scala b/tools/src/main/scala/scala/scalanative/codegen/PlatformInfo.scala new file mode 100644 index 0000000000..32808a288f --- /dev/null +++ b/tools/src/main/scala/scala/scalanative/codegen/PlatformInfo.scala @@ -0,0 +1,17 @@ +package scala.scalanative.codegen + +import scala.scalanative.build.Config + +private[scalanative] case class PlatformInfo( + targetTriple: Option[String], + targetsWindows: Boolean +) { + val sizeOfPtr = 8 + val sizeOfPtrBits = sizeOfPtr * 8 +} +object PlatformInfo { + def apply(config: Config): PlatformInfo = PlatformInfo( + targetTriple = config.compilerConfig.targetTriple, + targetsWindows = config.targetsWindows + ) +} diff --git a/tools/src/main/scala/scala/scalanative/codegen/RuntimeTypeInformation.scala b/tools/src/main/scala/scala/scalanative/codegen/RuntimeTypeInformation.scala index 03dd20cf5d..3befbb9091 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/RuntimeTypeInformation.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/RuntimeTypeInformation.scala @@ -5,29 +5,16 @@ import scalanative.util.unreachable import scalanative.nir._ import scalanative.linker.{ScopeInfo, Class, Trait} -class RuntimeTypeInformation(meta: Metadata, info: ScopeInfo) { +class RuntimeTypeInformation(info: ScopeInfo)(implicit meta: Metadata) { + import RuntimeTypeInformation._ val name: Global = info.name.member(Sig.Generated("type")) val const: Val.Global = Val.Global(name, Type.Ptr) val struct: Type.StructValue = info match { case cls: Class => - val dynmap = - if (meta.linked.dynsigs.isEmpty) { - Seq.empty - } else { - Seq(meta.dynmap(cls).ty) - } - Type.StructValue( - Seq( - meta.Rtti, - Type.Int, // size - Type.Int, // idRangeUntil - meta.layout(cls).referenceOffsetsTy - ) ++ dynmap ++ Seq( - meta.vtable(cls).ty - ) + meta.layouts.ClassRtti.genLayout( + vtable = meta.vtable(cls).ty ) - case _ => - meta.Rtti + case _ => meta.layouts.Rtti.layout } val value: Val.StructValue = { val typeId = Val.Int(info match { @@ -36,37 +23,33 @@ class RuntimeTypeInformation(meta: Metadata, info: ScopeInfo) { }) val typeStr = Val.String(info.name.asInstanceOf[Global.Top].id) val traitId = Val.Int(info match { - case info: Class => - meta.dispatchTable.traitClassIds.get(info).getOrElse(-1) - case _ => - -1 + case info: Class => meta.dispatchTable.traitClassIds.getOrElse(info, -1) + case _ => -1 }) - val classConst = - Val.Global(Rt.Class.name.member(Sig.Generated("type")), Type.Ptr) val base = Val.StructValue( Seq(classConst, typeId, traitId, typeStr) ) info match { case cls: Class => val dynmap = - if (meta.linked.dynsigs.isEmpty) { - Seq.empty - } else { - Seq(meta.dynmap(cls).value) - } + if (!meta.layouts.ClassRtti.usesDynMap) Nil + else List(meta.dynmap(cls).value) val range = meta.ranges(cls) Val.StructValue( - Seq( - base, - Val.Int(meta.layout(cls).size.toInt), - Val.Int(range.last), - meta.layout(cls).referenceOffsetsValue - ) ++ dynmap ++ Seq( - meta.vtable(cls).value - ) + base :: + Val.Int(meta.layout(cls).size.toInt) :: + Val.Int(range.last) :: + meta.layout(cls).referenceOffsetsValue :: + dynmap ::: + meta.vtable(cls).value :: + Nil ) case _ => base } } } +object RuntimeTypeInformation { + private val classConst = + Val.Global(Rt.Class.name.member(Sig.Generated("type")), Type.Ptr) +} diff --git a/tools/src/main/scala/scala/scalanative/codegen/VirtualTable.scala b/tools/src/main/scala/scala/scalanative/codegen/VirtualTable.scala index 31db8e62dc..03c444994f 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/VirtualTable.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/VirtualTable.scala @@ -5,7 +5,7 @@ import scala.collection.mutable import scalanative.nir._ import scalanative.nir.Rt._ -class VirtualTable(meta: Metadata, cls: linker.Class) { +class VirtualTable(cls: linker.Class)(implicit meta: Metadata) { private val slots: mutable.UnrolledBuffer[Sig] = cls.parent.fold { mutable.UnrolledBuffer.empty[Sig] diff --git a/tools/src/main/scala/scala/scalanative/interflow/Interflow.scala b/tools/src/main/scala/scala/scalanative/interflow/Interflow.scala index 49ba0a9a8b..82b2016559 100644 --- a/tools/src/main/scala/scala/scalanative/interflow/Interflow.scala +++ b/tools/src/main/scala/scala/scalanative/interflow/Interflow.scala @@ -2,9 +2,10 @@ package scala.scalanative package interflow import scala.collection.mutable -import scalanative.nir._ -import scalanative.linker._ -import scalanative.util.ScopedVar +import scala.scalanative.codegen.PlatformInfo +import scala.scalanative.nir._ +import scala.scalanative.linker._ +import scala.scalanative.util.ScopedVar import java.util.function.Supplier class Interflow(val config: build.Config)(implicit @@ -18,6 +19,8 @@ class Interflow(val config: build.Config)(implicit with PolyInline with Intrinsics with Log { + implicit val platform: PlatformInfo = PlatformInfo(config) + private val originals = { val out = mutable.Map.empty[Global, Defn] linked.defns.foreach { defn => out(defn.name) = defn } diff --git a/tools/src/main/scala/scala/scalanative/interflow/Whitelist.scala b/tools/src/main/scala/scala/scalanative/interflow/Whitelist.scala index f6ec402b86..9a7dab7487 100644 --- a/tools/src/main/scala/scala/scalanative/interflow/Whitelist.scala +++ b/tools/src/main/scala/scala/scalanative/interflow/Whitelist.scala @@ -9,6 +9,10 @@ object Whitelist { val constantModules = { val out = collection.mutable.Set.empty[Global] out += Global.Top("scala.scalanative.runtime.BoxedUnit$") + out += Global.Top("scala.scalanative.runtime.MemoryLayout$") + out += Global.Top("scala.scalanative.runtime.MemoryLayout$Array$") + out += Global.Top("scala.scalanative.runtime.MemoryLayout$Object$") + out += Global.Top("scala.scalanative.runtime.MemoryLayout$Rtti$") out += Global.Top("scala.scalanative.unsafe.Tag$") out += Global.Top("scala.scalanative.unsafe.Tag$Unit$") out += Global.Top("scala.scalanative.unsafe.Tag$Boolean$") From 9e5639791414c8227dda4e5d174c040670646b71 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 2 Feb 2023 12:16:15 +0100 Subject: [PATCH 09/61] Port all missing `java.util.function` types (#3127) * Port `java.util.function` types from Scala.js, update existing definitions * Add helper script for porting Scala.js sources --- .../scala/java/util/function/BiConsumer.scala | 16 ++-- .../scala/java/util/function/BiFunction.scala | 10 +-- .../java/util/function/BiPredicate.scala | 29 +++---- .../java/util/function/BinaryOperator.scala | 22 +++--- .../java/util/function/BooleanSupplier.scala | 7 ++ .../scala/java/util/function/Consumer.scala | 12 ++- .../util/function/DoubleBinaryOperator.scala | 7 ++ .../java/util/function/DoubleConsumer.scala | 12 +++ .../java/util/function/DoubleFunction.scala | 7 ++ .../java/util/function/DoublePredicate.scala | 30 +++++++ .../java/util/function/DoubleSupplier.scala | 7 ++ .../util/function/DoubleToIntFunction.scala | 7 ++ .../util/function/DoubleToLongFunction.scala | 7 ++ .../util/function/DoubleUnaryOperator.scala | 21 +++++ .../scala/java/util/function/Function.scala | 24 +++--- .../util/function/IntBinaryOperator.scala | 7 ++ .../java/util/function/IntConsumer.scala | 12 +++ .../java/util/function/IntFunction.scala | 7 ++ .../java/util/function/IntPredicate.scala | 30 +++++++ .../java/util/function/IntSupplier.scala | 7 ++ .../util/function/IntToDoubleFunction.scala | 7 ++ .../util/function/IntToLongFunction.scala | 7 ++ .../java/util/function/IntUnaryOperator.scala | 3 +- .../util/function/LongBinaryOperator.scala | 7 ++ .../java/util/function/LongConsumer.scala | 12 +++ .../java/util/function/LongFunction.scala | 7 ++ .../java/util/function/LongPredicate.scala | 30 +++++++ .../java/util/function/LongSupplier.scala | 7 ++ .../util/function/LongToDoubleFunction.scala | 7 ++ .../util/function/LongToIntFunction.scala | 7 ++ .../util/function/ObjDoubleConsumer.scala | 7 ++ .../java/util/function/ObjIntConsumer.scala | 7 ++ .../java/util/function/ObjLongConsumer.scala | 7 ++ .../scala/java/util/function/Predicate.scala | 3 +- .../scala/java/util/function/Supplier.scala | 1 + .../util/function/ToDoubleBiFunction.scala | 7 ++ .../java/util/function/ToDoubleFunction.scala | 3 +- .../java/util/function/ToIntBiFunction.scala | 7 ++ .../java/util/function/ToIntFunction.scala | 3 +- .../java/util/function/ToLongBiFunction.scala | 7 ++ .../java/util/function/ToLongFunction.scala | 3 +- .../java/util/function/UnaryOperator.scala | 8 +- scripts/portScalaJsSource.scala | 78 +++++++++++++++++++ .../util/function/BiConsumerTest.scala | 9 ++- .../util/function/BiFunctionTest.scala | 3 +- .../util/function/BiPredicateTest.scala | 9 ++- .../util/function/BinaryOperatorTest.scala | 22 +++--- .../util/function/BooleanSupplierTest.scala | 24 ++++++ .../javalib/util/function/ConsumerTest.scala | 3 +- .../function/DoubleBinaryOperatorTest.scala | 17 ++++ .../util/function/DoubleConsumerTest.scala | 41 ++++++++++ .../util/function/DoubleFunctionTest.scala | 17 ++++ .../util/function/DoublePredicateTest.scala | 70 +++++++++++++++++ .../util/function/DoubleSupplierTest.scala | 23 ++++++ .../function/DoubleToIntFunctionTest.scala | 17 ++++ .../function/DoubleToLongFunctionTest.scala | 17 ++++ .../function/DoubleUnaryOperatorTest.scala | 40 ++++++++++ .../javalib/util/function/FunctionTest.scala | 7 +- .../util/function/IntBinaryOperatorTest.scala | 17 ++++ .../util/function/IntConsumerTest.scala | 41 ++++++++++ .../util/function/IntFunctionTest.scala | 17 ++++ .../util/function/IntPredicateTest.scala | 70 +++++++++++++++++ .../util/function/IntSupplierTest.scala | 25 ++++++ .../function/IntToDoubleFunctionTest.scala | 17 ++++ .../util/function/IntToLongFunctionTest.scala | 16 ++++ .../util/function/IntUnaryOperatorTest.scala | 3 +- .../function/LongBinaryOperatorTest.scala | 16 ++++ .../util/function/LongConsumerTest.scala | 41 ++++++++++ .../util/function/LongFunctionTest.scala | 17 ++++ .../util/function/LongPredicateTest.scala | 70 +++++++++++++++++ .../util/function/LongSupplierTest.scala | 25 ++++++ .../function/LongToDoubleFunctionTest.scala | 16 ++++ .../util/function/LongToIntFunctionTest.scala | 16 ++++ .../util/function/LongUnaryOperatorTest.scala | 35 +++++++++ .../util/function/ObjDoubleConsumerTest.scala | 23 ++++++ .../util/function/ObjIntConsumerTest.scala | 23 ++++++ .../util/function/ObjLongConsumerTest.scala | 22 ++++++ .../javalib/util/function/PredicateTest.scala | 3 +- .../javalib/util/function/SupplierTest.scala | 26 ++++--- .../function/ToDoubleBiFunctionTest.scala | 17 ++++ .../util/function/ToDoubleFunctionTest.scala | 3 +- .../util/function/ToIntBiFunctionTest.scala | 16 ++++ .../util/function/ToIntFunctionTest.scala | 3 +- .../util/function/ToLongBiFunctionTest.scala | 16 ++++ .../util/function/ToLongFunctionTest.scala | 3 +- .../util/function/UnaryOperatorTest.scala | 25 +++++- 86 files changed, 1323 insertions(+), 134 deletions(-) create mode 100644 javalib/src/main/scala/java/util/function/BooleanSupplier.scala create mode 100644 javalib/src/main/scala/java/util/function/DoubleBinaryOperator.scala create mode 100644 javalib/src/main/scala/java/util/function/DoubleConsumer.scala create mode 100644 javalib/src/main/scala/java/util/function/DoubleFunction.scala create mode 100644 javalib/src/main/scala/java/util/function/DoublePredicate.scala create mode 100644 javalib/src/main/scala/java/util/function/DoubleSupplier.scala create mode 100644 javalib/src/main/scala/java/util/function/DoubleToIntFunction.scala create mode 100644 javalib/src/main/scala/java/util/function/DoubleToLongFunction.scala create mode 100644 javalib/src/main/scala/java/util/function/DoubleUnaryOperator.scala create mode 100644 javalib/src/main/scala/java/util/function/IntBinaryOperator.scala create mode 100644 javalib/src/main/scala/java/util/function/IntConsumer.scala create mode 100644 javalib/src/main/scala/java/util/function/IntFunction.scala create mode 100644 javalib/src/main/scala/java/util/function/IntPredicate.scala create mode 100644 javalib/src/main/scala/java/util/function/IntSupplier.scala create mode 100644 javalib/src/main/scala/java/util/function/IntToDoubleFunction.scala create mode 100644 javalib/src/main/scala/java/util/function/IntToLongFunction.scala create mode 100644 javalib/src/main/scala/java/util/function/LongBinaryOperator.scala create mode 100644 javalib/src/main/scala/java/util/function/LongConsumer.scala create mode 100644 javalib/src/main/scala/java/util/function/LongFunction.scala create mode 100644 javalib/src/main/scala/java/util/function/LongPredicate.scala create mode 100644 javalib/src/main/scala/java/util/function/LongSupplier.scala create mode 100644 javalib/src/main/scala/java/util/function/LongToDoubleFunction.scala create mode 100644 javalib/src/main/scala/java/util/function/LongToIntFunction.scala create mode 100644 javalib/src/main/scala/java/util/function/ObjDoubleConsumer.scala create mode 100644 javalib/src/main/scala/java/util/function/ObjIntConsumer.scala create mode 100644 javalib/src/main/scala/java/util/function/ObjLongConsumer.scala create mode 100644 javalib/src/main/scala/java/util/function/ToDoubleBiFunction.scala create mode 100644 javalib/src/main/scala/java/util/function/ToIntBiFunction.scala create mode 100644 javalib/src/main/scala/java/util/function/ToLongBiFunction.scala create mode 100755 scripts/portScalaJsSource.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BooleanSupplierTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleBinaryOperatorTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleConsumerTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleFunctionTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoublePredicateTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleSupplierTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToIntFunctionTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToLongFunctionTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleUnaryOperatorTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntBinaryOperatorTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntConsumerTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntFunctionTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntPredicateTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntSupplierTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToDoubleFunctionTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToLongFunctionTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongBinaryOperatorTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongConsumerTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongFunctionTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongPredicateTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongSupplierTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToDoubleFunctionTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToIntFunctionTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongUnaryOperatorTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjDoubleConsumerTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjIntConsumerTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjLongConsumerTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleBiFunctionTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntBiFunctionTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongBiFunctionTest.scala diff --git a/javalib/src/main/scala/java/util/function/BiConsumer.scala b/javalib/src/main/scala/java/util/function/BiConsumer.scala index 04d0e1a5ea..2ad5df5cab 100644 --- a/javalib/src/main/scala/java/util/function/BiConsumer.scala +++ b/javalib/src/main/scala/java/util/function/BiConsumer.scala @@ -1,17 +1,11 @@ -// Ported from Scala.js commit: f86ed6 c2f5a43 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function trait BiConsumer[T, U] { - self => - def accept(t: T, u: U): Unit - def andThen(after: BiConsumer[T, U]): BiConsumer[T, U] = - new BiConsumer[T, U]() { - override def accept(t: T, u: U): Unit = { - self.accept(t, u) - after.accept(t, u) - } - } + def andThen(after: BiConsumer[T, U]): BiConsumer[T, U] = { (t: T, u: U) => + accept(t, u) + after.accept(t, u) + } } diff --git a/javalib/src/main/scala/java/util/function/BiFunction.scala b/javalib/src/main/scala/java/util/function/BiFunction.scala index 7eabfeb804..3ba125083a 100644 --- a/javalib/src/main/scala/java/util/function/BiFunction.scala +++ b/javalib/src/main/scala/java/util/function/BiFunction.scala @@ -1,13 +1,11 @@ -// Ported from Scala.js commit: d3a9711 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function -trait BiFunction[T, U, R] { self => +trait BiFunction[T, U, R] { def apply(t: T, u: U): R def andThen[V](after: Function[_ >: R, _ <: V]): BiFunction[T, U, V] = { - new BiFunction[T, U, V] { - def apply(t: T, u: U): V = after.apply(self.apply(t, u)) - } + (t: T, u: U) => + after.apply(this.apply(t, u)) } } diff --git a/javalib/src/main/scala/java/util/function/BiPredicate.scala b/javalib/src/main/scala/java/util/function/BiPredicate.scala index bc160335d6..e28893003f 100644 --- a/javalib/src/main/scala/java/util/function/BiPredicate.scala +++ b/javalib/src/main/scala/java/util/function/BiPredicate.scala @@ -1,25 +1,18 @@ -// Ported from Scala.js commit: 0c27b64 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function -trait BiPredicate[T, U] { self => +trait BiPredicate[T, U] { def test(t: T, u: U): Boolean - def and(other: BiPredicate[_ >: T, _ >: U]): BiPredicate[T, U] = - new BiPredicate[T, U] { - override def test(t: T, u: U): Boolean = - self.test(t, u) && other.test(t, u) - } + def and(other: BiPredicate[_ >: T, _ >: U]): BiPredicate[T, U] = { + (t: T, u: U) => + test(t, u) && other.test(t, u) + } - def negate(): BiPredicate[T, U] = - new BiPredicate[T, U] { - override def test(t: T, u: U): Boolean = - !self.test(t, u) - } + def negate(): BiPredicate[T, U] = (t: T, u: U) => !test(t, u) - def or(other: BiPredicate[_ >: T, _ >: U]): BiPredicate[T, U] = - new BiPredicate[T, U] { - override def test(t: T, u: U): Boolean = - self.test(t, u) || other.test(t, u) - } + def or(other: BiPredicate[_ >: T, _ >: U]): BiPredicate[T, U] = { + (t: T, u: U) => + test(t, u) || other.test(t, u) + } } diff --git a/javalib/src/main/scala/java/util/function/BinaryOperator.scala b/javalib/src/main/scala/java/util/function/BinaryOperator.scala index 503c24057f..43dd5e35bc 100644 --- a/javalib/src/main/scala/java/util/function/BinaryOperator.scala +++ b/javalib/src/main/scala/java/util/function/BinaryOperator.scala @@ -1,24 +1,20 @@ +// Ported from Scala.js, commit SHA: 1ef4c4e0f dated: 2020-09-06 package java.util.function -import java.util.{Comparator, Objects} +import java.util.Comparator -trait BinaryOperator[T] extends BiFunction[T, T, T] { self => } +trait BinaryOperator[T] extends BiFunction[T, T, T] object BinaryOperator { - def minBy[T](comparator: Comparator[_ >: T]): BinaryOperator[T] = { - Objects.requireNonNull(comparator) - new BinaryOperator[T] { - override def apply(a: T, b: T): T = - if (comparator.compare(a, b) <= 0) a else b - } + (a: T, b: T) => + if (comparator.compare(a, b) <= 0) a + else b } def maxBy[T](comparator: Comparator[_ >: T]): BinaryOperator[T] = { - Objects.requireNonNull(comparator) - new BinaryOperator[T] { - override def apply(a: T, b: T): T = - if (comparator.compare(a, b) >= 0) a else b - } + (a: T, b: T) => + if (comparator.compare(a, b) >= 0) a + else b } } diff --git a/javalib/src/main/scala/java/util/function/BooleanSupplier.scala b/javalib/src/main/scala/java/util/function/BooleanSupplier.scala new file mode 100644 index 0000000000..cb60c1f860 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/BooleanSupplier.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package java.util.function + +@FunctionalInterface +trait BooleanSupplier { + def getAsBoolean(): Boolean +} diff --git a/javalib/src/main/scala/java/util/function/Consumer.scala b/javalib/src/main/scala/java/util/function/Consumer.scala index 5a093ad3e7..a4bebd84fb 100644 --- a/javalib/src/main/scala/java/util/function/Consumer.scala +++ b/javalib/src/main/scala/java/util/function/Consumer.scala @@ -1,12 +1,16 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function +@FunctionalInterface trait Consumer[T] { self => def accept(t: T): Unit - def andThen(after: Consumer[T]): Consumer[T] = new Consumer[T]() { - def accept(t: T): Unit = { - self.accept(t) - after.accept(t) + def andThen(after: Consumer[_ >: T]): Consumer[T] = { + new Consumer[T] { + def accept(t: T): Unit = { + self.accept(t) + after.accept(t) + } } } } diff --git a/javalib/src/main/scala/java/util/function/DoubleBinaryOperator.scala b/javalib/src/main/scala/java/util/function/DoubleBinaryOperator.scala new file mode 100644 index 0000000000..2a531b87d4 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleBinaryOperator.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait DoubleBinaryOperator { + def applyAsDouble(left: Double, right: Double): Double +} diff --git a/javalib/src/main/scala/java/util/function/DoubleConsumer.scala b/javalib/src/main/scala/java/util/function/DoubleConsumer.scala new file mode 100644 index 0000000000..01181527d1 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleConsumer.scala @@ -0,0 +1,12 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait DoubleConsumer { + def accept(value: Double): Unit + + def andThen(after: DoubleConsumer): DoubleConsumer = { (value: Double) => + this.accept(value) + after.accept(value) + } +} diff --git a/javalib/src/main/scala/java/util/function/DoubleFunction.scala b/javalib/src/main/scala/java/util/function/DoubleFunction.scala new file mode 100644 index 0000000000..7c08b76c4a --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait DoubleFunction[R] { + def apply(value: Double): R +} diff --git a/javalib/src/main/scala/java/util/function/DoublePredicate.scala b/javalib/src/main/scala/java/util/function/DoublePredicate.scala new file mode 100644 index 0000000000..2e8119789e --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoublePredicate.scala @@ -0,0 +1,30 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait DoublePredicate { self => + def test(t: Double): Boolean + + def and(other: DoublePredicate): DoublePredicate = { + new DoublePredicate { + def test(value: Double): Boolean = + // the order and short-circuit are by-spec + self.test(value) && other.test(value) + } + } + + def negate(): DoublePredicate = { + new DoublePredicate { + def test(value: Double): Boolean = + !self.test(value) + } + } + + def or(other: DoublePredicate): DoublePredicate = { + new DoublePredicate { + def test(value: Double): Boolean = + // the order and short-circuit are by-spec + self.test(value) || other.test(value) + } + } +} diff --git a/javalib/src/main/scala/java/util/function/DoubleSupplier.scala b/javalib/src/main/scala/java/util/function/DoubleSupplier.scala new file mode 100644 index 0000000000..feed9e76ce --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleSupplier.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package java.util.function + +@FunctionalInterface +trait DoubleSupplier { + def getAsDouble(): Double +} diff --git a/javalib/src/main/scala/java/util/function/DoubleToIntFunction.scala b/javalib/src/main/scala/java/util/function/DoubleToIntFunction.scala new file mode 100644 index 0000000000..fa1123c230 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleToIntFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait DoubleToIntFunction { + def applyAsInt(value: Double): Int +} diff --git a/javalib/src/main/scala/java/util/function/DoubleToLongFunction.scala b/javalib/src/main/scala/java/util/function/DoubleToLongFunction.scala new file mode 100644 index 0000000000..ca6c2b53ba --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleToLongFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait DoubleToLongFunction { + def applyAsLong(value: Double): Long +} diff --git a/javalib/src/main/scala/java/util/function/DoubleUnaryOperator.scala b/javalib/src/main/scala/java/util/function/DoubleUnaryOperator.scala new file mode 100644 index 0000000000..a52dadc64b --- /dev/null +++ b/javalib/src/main/scala/java/util/function/DoubleUnaryOperator.scala @@ -0,0 +1,21 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait DoubleUnaryOperator { + def applyAsDouble(operand: Double): Double + + def andThen(after: DoubleUnaryOperator): DoubleUnaryOperator = { + (d: Double) => + after.applyAsDouble(applyAsDouble(d)) + } + + def compose(before: DoubleUnaryOperator): DoubleUnaryOperator = { + (d: Double) => + applyAsDouble(before.applyAsDouble(d)) + } +} + +object DoubleUnaryOperator { + def identity(): DoubleUnaryOperator = (d: Double) => d +} diff --git a/javalib/src/main/scala/java/util/function/Function.scala b/javalib/src/main/scala/java/util/function/Function.scala index 9abd8c367e..32c37d03e0 100644 --- a/javalib/src/main/scala/java/util/function/Function.scala +++ b/javalib/src/main/scala/java/util/function/Function.scala @@ -1,24 +1,18 @@ -// Ported from Scala.js commit: eb637e3 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function -trait Function[T, R] { self => +trait Function[T, R] { def apply(t: T): R - def andThen[V](after: Function[_ >: R, _ <: V]): Function[T, V] = - new Function[T, V] { - override def apply(t: T): V = after.apply(self.apply(t)) - } + def andThen[V](after: Function[_ >: R, _ <: V]): Function[T, V] = { (t: T) => + after.apply(apply(t)) + } - def compose[V](before: Function[_ >: V, _ <: T]): Function[V, R] = - new Function[V, R] { - override def apply(v: V): R = self.apply(before.apply(v)) - } + def compose[V](before: Function[_ >: V, _ <: T]): Function[V, R] = { (v: V) => + apply(before.apply(v)) + } } object Function { - def identity[T](): Function[T, T] = - new Function[T, T] { - override def apply(t: T): T = t - } + def identity[T](): Function[T, T] = (t: T) => t } diff --git a/javalib/src/main/scala/java/util/function/IntBinaryOperator.scala b/javalib/src/main/scala/java/util/function/IntBinaryOperator.scala new file mode 100644 index 0000000000..d5399d9c2b --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntBinaryOperator.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait IntBinaryOperator { + def applyAsInt(left: Int, right: Int): Int +} diff --git a/javalib/src/main/scala/java/util/function/IntConsumer.scala b/javalib/src/main/scala/java/util/function/IntConsumer.scala new file mode 100644 index 0000000000..b39ee7d292 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntConsumer.scala @@ -0,0 +1,12 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait IntConsumer { + def accept(value: Int): Unit + + def andThen(after: IntConsumer): IntConsumer = { (value: Int) => + this.accept(value) + after.accept(value) + } +} diff --git a/javalib/src/main/scala/java/util/function/IntFunction.scala b/javalib/src/main/scala/java/util/function/IntFunction.scala new file mode 100644 index 0000000000..ba442d8e0b --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait IntFunction[R] { + def apply(value: Int): R +} diff --git a/javalib/src/main/scala/java/util/function/IntPredicate.scala b/javalib/src/main/scala/java/util/function/IntPredicate.scala new file mode 100644 index 0000000000..0a3491814f --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntPredicate.scala @@ -0,0 +1,30 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait IntPredicate { self => + def test(t: Int): Boolean + + def and(other: IntPredicate): IntPredicate = { + new IntPredicate { + def test(value: Int): Boolean = + // the order and short-circuit are by-spec + self.test(value) && other.test(value) + } + } + + def negate(): IntPredicate = { + new IntPredicate { + def test(value: Int): Boolean = + !self.test(value) + } + } + + def or(other: IntPredicate): IntPredicate = { + new IntPredicate { + def test(value: Int): Boolean = + // the order and short-circuit are by-spec + self.test(value) || other.test(value) + } + } +} diff --git a/javalib/src/main/scala/java/util/function/IntSupplier.scala b/javalib/src/main/scala/java/util/function/IntSupplier.scala new file mode 100644 index 0000000000..26e62ad22f --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntSupplier.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package java.util.function + +@FunctionalInterface +trait IntSupplier { + def getAsInt(): Int +} diff --git a/javalib/src/main/scala/java/util/function/IntToDoubleFunction.scala b/javalib/src/main/scala/java/util/function/IntToDoubleFunction.scala new file mode 100644 index 0000000000..6d8ac63fb2 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntToDoubleFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait IntToDoubleFunction { + def applyAsDouble(value: Int): Double +} diff --git a/javalib/src/main/scala/java/util/function/IntToLongFunction.scala b/javalib/src/main/scala/java/util/function/IntToLongFunction.scala new file mode 100644 index 0000000000..eb98ea0c49 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/IntToLongFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait IntToLongFunction { + def applyAsLong(value: Int): Long +} diff --git a/javalib/src/main/scala/java/util/function/IntUnaryOperator.scala b/javalib/src/main/scala/java/util/function/IntUnaryOperator.scala index 821195415a..32d136da72 100644 --- a/javalib/src/main/scala/java/util/function/IntUnaryOperator.scala +++ b/javalib/src/main/scala/java/util/function/IntUnaryOperator.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: d028054 dated: 2022-05-16 - +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function @FunctionalInterface diff --git a/javalib/src/main/scala/java/util/function/LongBinaryOperator.scala b/javalib/src/main/scala/java/util/function/LongBinaryOperator.scala new file mode 100644 index 0000000000..8d9bb34729 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongBinaryOperator.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait LongBinaryOperator { + def applyAsLong(left: Long, right: Long): Long +} diff --git a/javalib/src/main/scala/java/util/function/LongConsumer.scala b/javalib/src/main/scala/java/util/function/LongConsumer.scala new file mode 100644 index 0000000000..28b6c0190b --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongConsumer.scala @@ -0,0 +1,12 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait LongConsumer { + def accept(value: Long): Unit + + def andThen(after: LongConsumer): LongConsumer = { (value: Long) => + this.accept(value) + after.accept(value) + } +} diff --git a/javalib/src/main/scala/java/util/function/LongFunction.scala b/javalib/src/main/scala/java/util/function/LongFunction.scala new file mode 100644 index 0000000000..62f53bea59 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait LongFunction[R] { + def apply(value: Long): R +} diff --git a/javalib/src/main/scala/java/util/function/LongPredicate.scala b/javalib/src/main/scala/java/util/function/LongPredicate.scala new file mode 100644 index 0000000000..c9e4179726 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongPredicate.scala @@ -0,0 +1,30 @@ +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 +package java.util.function + +@FunctionalInterface +trait LongPredicate { self => + def test(t: Long): Boolean + + def and(other: LongPredicate): LongPredicate = { + new LongPredicate { + def test(value: Long): Boolean = + // the order and short-circuit are by-spec + self.test(value) && other.test(value) + } + } + + def negate(): LongPredicate = { + new LongPredicate { + def test(value: Long): Boolean = + !self.test(value) + } + } + + def or(other: LongPredicate): LongPredicate = { + new LongPredicate { + def test(value: Long): Boolean = + // the order and short-circuit are by-spec + self.test(value) || other.test(value) + } + } +} diff --git a/javalib/src/main/scala/java/util/function/LongSupplier.scala b/javalib/src/main/scala/java/util/function/LongSupplier.scala new file mode 100644 index 0000000000..0bf66821ec --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongSupplier.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package java.util.function + +@FunctionalInterface +trait LongSupplier { + def getAsLong(): Long +} diff --git a/javalib/src/main/scala/java/util/function/LongToDoubleFunction.scala b/javalib/src/main/scala/java/util/function/LongToDoubleFunction.scala new file mode 100644 index 0000000000..d3cc1c3764 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongToDoubleFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait LongToDoubleFunction { + def applyAsDouble(value: Long): Double +} diff --git a/javalib/src/main/scala/java/util/function/LongToIntFunction.scala b/javalib/src/main/scala/java/util/function/LongToIntFunction.scala new file mode 100644 index 0000000000..18b27e5528 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/LongToIntFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait LongToIntFunction { + def applyAsInt(value: Long): Int +} diff --git a/javalib/src/main/scala/java/util/function/ObjDoubleConsumer.scala b/javalib/src/main/scala/java/util/function/ObjDoubleConsumer.scala new file mode 100644 index 0000000000..247539f074 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/ObjDoubleConsumer.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait ObjDoubleConsumer[T] { + def accept(t: T, value: Double): Unit +} diff --git a/javalib/src/main/scala/java/util/function/ObjIntConsumer.scala b/javalib/src/main/scala/java/util/function/ObjIntConsumer.scala new file mode 100644 index 0000000000..accd3df517 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/ObjIntConsumer.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait ObjIntConsumer[T] { + def accept(t: T, value: Int): Unit +} diff --git a/javalib/src/main/scala/java/util/function/ObjLongConsumer.scala b/javalib/src/main/scala/java/util/function/ObjLongConsumer.scala new file mode 100644 index 0000000000..84b638bb55 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/ObjLongConsumer.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait ObjLongConsumer[T] { + def accept(t: T, value: Long): Unit +} diff --git a/javalib/src/main/scala/java/util/function/Predicate.scala b/javalib/src/main/scala/java/util/function/Predicate.scala index 8524858a9d..17e122d81f 100644 --- a/javalib/src/main/scala/java/util/function/Predicate.scala +++ b/javalib/src/main/scala/java/util/function/Predicate.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: 137c11d dated: 2019-07-03 - +// Ported from Scala.js, commit SHA: 7b4e8a80b dated: 2022-12-06 package java.util.function import java.{util => ju} diff --git a/javalib/src/main/scala/java/util/function/Supplier.scala b/javalib/src/main/scala/java/util/function/Supplier.scala index ee7bfa7a3b..726f7edd4c 100644 --- a/javalib/src/main/scala/java/util/function/Supplier.scala +++ b/javalib/src/main/scala/java/util/function/Supplier.scala @@ -1,3 +1,4 @@ +// Ported from Scala.js, commit SHA: 5df5a4142 dated: 2020-09-06 package java.util.function trait Supplier[T] { diff --git a/javalib/src/main/scala/java/util/function/ToDoubleBiFunction.scala b/javalib/src/main/scala/java/util/function/ToDoubleBiFunction.scala new file mode 100644 index 0000000000..c2e90896b4 --- /dev/null +++ b/javalib/src/main/scala/java/util/function/ToDoubleBiFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait ToDoubleBiFunction[T, U] { + def applyAsDouble(t: T, u: U): Double +} diff --git a/javalib/src/main/scala/java/util/function/ToDoubleFunction.scala b/javalib/src/main/scala/java/util/function/ToDoubleFunction.scala index 5dbc90aed0..e6275c77d9 100644 --- a/javalib/src/main/scala/java/util/function/ToDoubleFunction.scala +++ b/javalib/src/main/scala/java/util/function/ToDoubleFunction.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit 00e462d dated: 2023-01-22 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package java.util.function @FunctionalInterface diff --git a/javalib/src/main/scala/java/util/function/ToIntBiFunction.scala b/javalib/src/main/scala/java/util/function/ToIntBiFunction.scala new file mode 100644 index 0000000000..f143f27afd --- /dev/null +++ b/javalib/src/main/scala/java/util/function/ToIntBiFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait ToIntBiFunction[T, U] { + def applyAsInt(t: T, u: U): Int +} diff --git a/javalib/src/main/scala/java/util/function/ToIntFunction.scala b/javalib/src/main/scala/java/util/function/ToIntFunction.scala index b55440d307..fdd276fcdc 100644 --- a/javalib/src/main/scala/java/util/function/ToIntFunction.scala +++ b/javalib/src/main/scala/java/util/function/ToIntFunction.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit 00e462d dated: 2023-01-22 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package java.util.function @FunctionalInterface diff --git a/javalib/src/main/scala/java/util/function/ToLongBiFunction.scala b/javalib/src/main/scala/java/util/function/ToLongBiFunction.scala new file mode 100644 index 0000000000..d61254c7ad --- /dev/null +++ b/javalib/src/main/scala/java/util/function/ToLongBiFunction.scala @@ -0,0 +1,7 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package java.util.function + +@FunctionalInterface +trait ToLongBiFunction[T, U] { + def applyAsLong(t: T, u: U): Long +} diff --git a/javalib/src/main/scala/java/util/function/ToLongFunction.scala b/javalib/src/main/scala/java/util/function/ToLongFunction.scala index b1847833ac..4c7c00f253 100644 --- a/javalib/src/main/scala/java/util/function/ToLongFunction.scala +++ b/javalib/src/main/scala/java/util/function/ToLongFunction.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit 00e462d dated: 2023-01-22 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package java.util.function @FunctionalInterface diff --git a/javalib/src/main/scala/java/util/function/UnaryOperator.scala b/javalib/src/main/scala/java/util/function/UnaryOperator.scala index ee62e672ab..5b734f6ca6 100644 --- a/javalib/src/main/scala/java/util/function/UnaryOperator.scala +++ b/javalib/src/main/scala/java/util/function/UnaryOperator.scala @@ -1,10 +1,8 @@ +// Ported from Scala.js, commit SHA: 4a394815e dated: 2020-09-06 package java.util.function -trait UnaryOperator[T] extends Function[T, T] { self => } +trait UnaryOperator[T] extends Function[T, T] object UnaryOperator { - def identity[T](): UnaryOperator[T] = - new UnaryOperator[T] { - override def apply(t: T): T = t - } + def identity[T](): UnaryOperator[T] = (t: T) => t } diff --git a/scripts/portScalaJsSource.scala b/scripts/portScalaJsSource.scala new file mode 100755 index 0000000000..38c5efc0df --- /dev/null +++ b/scripts/portScalaJsSource.scala @@ -0,0 +1,78 @@ +#!/usr/bin/env -S scala-cli shebang +//> using lib "org.virtuslab::toolkit-alpha::0.1.13" +//> using scala "3.2" + +// Combine with bash loop +// SJSPath="sjs-abs-path/unit-tests/shared/src/test/scala/org/scalanative" +// SNPath="sn-abs-path /test-suite/shared/src/test/scala/org/scalajs" +// for file in $(ls ${SJSPath}/javalib/util/function/* ); do +// ./scripts/portScalaJsSource.scala $SNPath $SJSPath $file; +// done + +import scala.util.chaining.scalaUtilChainingOps + +@main def portScalaJSSource( + scalaNativeAbsPath: os.Path, + scalaJSAbsPath: os.Path, + fileAbsPath: os.Path +) = + val relPath = fileAbsPath.relativeTo(scalaJSAbsPath) + val sjsPath = scalaJSAbsPath / relPath + val snPath = scalaNativeAbsPath / relPath + + def getShortSha(sjsFile: os.Path): String = + val format = "// Ported from Scala.js, commit SHA: %h dated: %as" + val out = os + .proc("git", "log", "-n1", s"--pretty=format:${format}", sjsFile) + .call(cwd = scalaJSAbsPath, check = true, stdout = os.Pipe) + out.out.text() + + println(s""" + |ScalaNative base dir: $scalaNativeAbsPath + |ScalaJS base dir: $scalaJSAbsPath + |Rel path: ${relPath} + |Porting ${sjsPath} into ${snPath} + """.stripMargin) + + assert( + os.exists(scalaNativeAbsPath), + "Scala Native directory does not exist" + ) + assert( + os.exists(scalaNativeAbsPath), + "Scala JS directory does not exists" + ) + assert(os.exists(sjsPath), s"ScalaJS file does not exits ${sjsPath}") + if os.exists(snPath) + then println(s"ScalaNative file alread exists ${snPath}, skipping") + else + val revisionMessage = getShortSha(sjsPath) + os.write(snPath, revisionMessage + "\n", createFolders = true) + val sjsSource = os + .read(sjsPath) + .pipe(stripHeader) + os.write.append(snPath, sjsSource) + os.write.append(snPath, System.lineSeparator()) + +private def stripHeader(input: String) = { + val nl = System.lineSeparator() + val commentsCtrl = Seq("/*", "*/", "*", "//") + input + .split(nl) + .dropWhile { line => + val trimmed = line.trim() + trimmed.isEmpty || commentsCtrl.exists(trimmed.startsWith(_)) + } + .mkString(nl) +} + +import scala.util.CommandLineParser.FromString +private given FromString[os.Path] = { str => + val nio = java.nio.file.Paths.get(str) + os.Path(nio.toRealPath()) +} + +private given FromString[os.RelPath] = { str => + val nio = java.nio.file.Paths.get(str) + os.RelPath(nio) +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiConsumerTest.scala index 7c0801d6b2..72ddd8b656 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiConsumerTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiConsumerTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: c2f5a43 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 package org.scalanative.testsuite.javalib.util.function import java.util.function.BiConsumer @@ -77,11 +76,13 @@ object BiConsumerTest { extends Exception(s"throwing consumer called with ($x, $y)") private val throwingConsumer: BiConsumer[Int, Int] = makeBiConsumer { - (t, u) => throw new ThrowingConsumerException(t, u) + (t, u) => + throw new ThrowingConsumerException(t, u) } private val dontCallConsumer: BiConsumer[Int, Int] = makeBiConsumer { - (t, u) => throw new AssertionError(s"dontCallConsumer.accept($t, $u)") + (t, u) => + throw new AssertionError(s"dontCallConsumer.accept($t, $u)") } def makeBiConsumer[T, U](f: (T, U) => Unit): BiConsumer[T, U] = { diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiFunctionTest.scala index 410e2c64d9..38f03fd7c5 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiFunctionTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiFunctionTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: d3a9711 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: cbf86bbb8 dated: 2020-10-23 package org.scalanative.testsuite.javalib.util.function import java.util.function.{Function, BiFunction} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiPredicateTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiPredicateTest.scala index 15196ec646..068317246f 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiPredicateTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BiPredicateTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: 0c27b64 dated: 2020-09-06 - +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 package org.scalanative.testsuite.javalib.util.function import java.util.function.BiPredicate @@ -90,11 +89,13 @@ object BiPredicateTest { } private val throwingPredicate: BiPredicate[Int, Int] = makeBiPredicate { - (t, _) => throw new ThrowingPredicateException(t) + (t, _) => + throw new ThrowingPredicateException(t) } private val dontCallPredicate: BiPredicate[Int, Int] = makeBiPredicate { - (t, u) => throw new AssertionError(s"dontCallPredicate.test($t, $u)") + (t, u) => + throw new AssertionError(s"dontCallPredicate.test($t, $u)") } private[this] def makeBiPredicate[T, U]( diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BinaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BinaryOperatorTest.scala index 5b52bbf4c5..aa34e3a5c0 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BinaryOperatorTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BinaryOperatorTest.scala @@ -1,23 +1,19 @@ +// Ported from Scala.js, commit SHA: 1ef4c4e0f dated: 2020-09-06 package org.scalanative.testsuite.javalib.util.function -import java.util.function._ -import java.util.Collections +import java.util.function.BinaryOperator -import org.junit.Test import org.junit.Assert._ +import org.junit.Test class BinaryOperatorTest { - @Test def testMinBy(): Unit = { - val binaryOperator = BinaryOperator.minBy[Int](Collections.reverseOrder()) - val min = binaryOperator.apply(2004, 2018) - - assertTrue(min == 2018) + @Test def minBy(): Unit = { + val binOp: BinaryOperator[Int] = BinaryOperator.minBy(Ordering[Int]) + assertEquals(10, binOp.apply(10, 20)) } - @Test def testMaxBy(): Unit = { - val binaryOperator = BinaryOperator.maxBy[Int](Collections.reverseOrder()) - val max = binaryOperator.apply(2004, 2018) - - assertTrue(max == 2004) + @Test def maxBy(): Unit = { + val binOp: BinaryOperator[Int] = BinaryOperator.maxBy(Ordering[Int]) + assertEquals(20, binOp.apply(10, 20)) } } diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BooleanSupplierTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BooleanSupplierTest.scala new file mode 100644 index 0000000000..6afc27907e --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/BooleanSupplierTest.scala @@ -0,0 +1,24 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.BooleanSupplier + +import org.junit.Assert._ +import org.junit.Test + +class BooleanSupplierTest { + import BooleanSupplierTest._ + + @Test def getAsBoolean(): Unit = { + assertEquals(true, makeSupplier(true).getAsBoolean()) + assertEquals(false, makeSupplier(false).getAsBoolean()) + } +} + +object BooleanSupplierTest { + def makeSupplier(f: => Boolean): BooleanSupplier = { + new BooleanSupplier { + def getAsBoolean(): Boolean = f + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ConsumerTest.scala index 95aa3ca876..455a41a433 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ConsumerTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ConsumerTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: 7fd9ebb dated: 2020=01-06 - +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 package org.scalanative.testsuite.javalib.util.function import java.util.function.Consumer diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleBinaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleBinaryOperatorTest.scala new file mode 100644 index 0000000000..24d3889cda --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleBinaryOperatorTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class DoubleBinaryOperatorTest { + @Test def applyAsDouble(): Unit = { + val sumOp = new DoubleBinaryOperator { + override def applyAsDouble(left: Double, right: Double): Double = + left + right + } + assertEquals(30, sumOp.applyAsDouble(10, 20), 0) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleConsumerTest.scala new file mode 100644 index 0000000000..800b139a22 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleConsumerTest.scala @@ -0,0 +1,41 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class DoubleConsumerTest { + @Test def accept(): Unit = { + // Side-effects + var current: Double = 0 + + val add = new DoubleConsumer { + override def accept(value: Double): Unit = current += value + } + + add.accept(5) + assertEquals(5, current, 0) + add.accept(15) + assertEquals(20, current, 0) + } + + @Test def andThen(): Unit = { + // Side-effects + var buffer = scala.collection.mutable.ListBuffer.empty[Double] + + val add = new DoubleConsumer { + override def accept(value: Double): Unit = buffer += value + } + val add2x = new DoubleConsumer { + override def accept(value: Double): Unit = buffer += value * 2 + } + val merged: DoubleConsumer = add.andThen(add2x) + + merged.accept(1d) + assertEquals(List(1d, 2d), buffer.toList) + merged.accept(4d) + assertEquals(List(1d, 2d, 4d, 8d), buffer.toList) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleFunctionTest.scala new file mode 100644 index 0000000000..2db0f2c512 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class DoubleFunctionTest { + @Test def testApply(): Unit = { + val f = new DoubleFunction[String] { + override def apply(value: Double): String = s"${value}d" + } + assertEquals(f.apply(0.5), "0.5d") + assertEquals(f.apply(3.3), "3.3d") + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoublePredicateTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoublePredicateTest.scala new file mode 100644 index 0000000000..1e0dd2f604 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoublePredicateTest.scala @@ -0,0 +1,70 @@ +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.DoublePredicate + +import org.junit.Assert._ +import org.junit.Test + +class DoublePredicateTest { + import DoublePredicateTest._ + + private val largerThan10 = makePredicate(_ > 10.0d) + private val even = makePredicate(_ % 2 == 0.0d) + + private val throwingPredicate = + makePredicate(x => throw new ThrowingPredicateException(x)) + + private val dontCallPredicate = + makePredicate(x => throw new AssertionError(s"dontCallPredicate.test($x)")) + + @Test def and(): Unit = { + // Truth table + val evenAndLargerThan10 = largerThan10.and(even) + assertTrue(evenAndLargerThan10.test(22.0d)) + assertFalse(evenAndLargerThan10.test(21.0d)) + assertFalse(evenAndLargerThan10.test(6.0d)) + assertFalse(evenAndLargerThan10.test(5.0d)) + + // Short-circuit + assertFalse(largerThan10.and(dontCallPredicate).test(5.0d)) + assertThrows( + classOf[ThrowingPredicateException], + () => throwingPredicate.and(dontCallPredicate).test(5.0d) + ) + } + + @Test def negate(): Unit = { + // Truth table + val notLargerThan10 = largerThan10.negate() + assertTrue(notLargerThan10.test(5.0d)) + assertFalse(notLargerThan10.test(15.0d)) + } + + @Test def or(): Unit = { + // Truth table + val evenOrLargerThan10 = largerThan10.or(even) + assertTrue(evenOrLargerThan10.test(22.0d)) + assertTrue(evenOrLargerThan10.test(21.0d)) + assertTrue(evenOrLargerThan10.test(6.0d)) + assertFalse(evenOrLargerThan10.test(5.0)) + + // Short-circuit + assertTrue(largerThan10.or(dontCallPredicate).test(15.0d)) + assertThrows( + classOf[ThrowingPredicateException], + () => throwingPredicate.or(dontCallPredicate).test(15.0d) + ) + } +} + +object DoublePredicateTest { + final class ThrowingPredicateException(x: Any) + extends Exception(s"throwing predicate called with $x") + + def makePredicate(f: Double => Boolean): DoublePredicate = { + new DoublePredicate { + def test(value: Double): Boolean = f(value) + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleSupplierTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleSupplierTest.scala new file mode 100644 index 0000000000..0f342e0dce --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleSupplierTest.scala @@ -0,0 +1,23 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.DoubleSupplier + +import org.junit.Assert._ +import org.junit.Test + +class DoubleSupplierTest { + import DoubleSupplierTest._ + + @Test def getAsDouble(): Unit = { + assertEquals(1.234d, makeSupplier(1.234d).getAsDouble(), 0.0d) + } +} + +object DoubleSupplierTest { + def makeSupplier(f: => Double): DoubleSupplier = { + new DoubleSupplier { + def getAsDouble(): Double = f + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToIntFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToIntFunctionTest.scala new file mode 100644 index 0000000000..1689f8cd40 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToIntFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class DoubleToIntFunctionTest { + @Test def applyAsInt(): Unit = { + val f = new DoubleToIntFunction { + override def applyAsInt(value: Double): Int = value.toInt + } + assertEquals(f.applyAsInt(0.5), 0) + assertEquals(f.applyAsInt(3.3), 3) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToLongFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToLongFunctionTest.scala new file mode 100644 index 0000000000..83c5edf1f8 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleToLongFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class DoubleToLongFunctionTest { + @Test def applyAsLong(): Unit = { + val f = new DoubleToLongFunction { + override def applyAsLong(value: Double): Long = (10 * value).toLong + } + assertEquals(f.applyAsLong(0.5), 5L) + assertEquals(f.applyAsLong(3.3), 33L) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleUnaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleUnaryOperatorTest.scala new file mode 100644 index 0000000000..954c40148f --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/DoubleUnaryOperatorTest.scala @@ -0,0 +1,40 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class DoubleUnaryOperatorTest { + private val minus5 = new DoubleUnaryOperator { + override def applyAsDouble(operand: Double): Double = operand - 5 + } + private val times2 = new DoubleUnaryOperator { + override def applyAsDouble(operand: Double): Double = operand * 2 + } + + @Test def applyAsDouble(): Unit = { + val times4 = new DoubleUnaryOperator { + override def applyAsDouble(operand: Double): Double = operand * 4 + } + assertEquals(times4.applyAsDouble(0.5), 2.0, 0) + assertEquals(times4.applyAsDouble(3.3), 13.2, 0) + } + + @Test def andThen(): Unit = { + val f: DoubleUnaryOperator = minus5.andThen(times2) + assertEquals(f.applyAsDouble(3), -4, 0) + } + + @Test def compose(): Unit = { + val f: DoubleUnaryOperator = minus5.compose(times2) + assertEquals(f.applyAsDouble(3), 1, 0) + } + + @Test def identity(): Unit = { + val id: DoubleUnaryOperator = DoubleUnaryOperator.identity() + assertEquals(id.applyAsDouble(3), 3, 0) + assertEquals(id.applyAsDouble(10), 10, 0) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/FunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/FunctionTest.scala index c2e9f2d563..44b86f6c19 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/FunctionTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/FunctionTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: eb637e3 dated: 200-09-06 - +// Ported from Scala.js, commit SHA: cbf86bbb8 dated: 2020-10-23 package org.scalanative.testsuite.javalib.util.function import java.util.function.Function @@ -14,7 +13,7 @@ class FunctionTest { assertEquals(10, identityFunc(10)) } - @Test def create_and_apply(): Unit = { + @Test def createAndApply(): Unit = { assertEquals(2, doubleFunc(1)) } @@ -28,7 +27,7 @@ class FunctionTest { assertEquals(22, incFunc.andThen(doubleFunc)(10)) } - @Test def identity_compose_andThen(): Unit = { + @Test def identityComposeAndThen(): Unit = { // i.e. (self + 1) * 2 val combined = identityFunc.andThen(doubleFunc).compose(incFunc) assertEquals(42, combined(20)) diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntBinaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntBinaryOperatorTest.scala new file mode 100644 index 0000000000..3947a87704 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntBinaryOperatorTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class IntBinaryOperatorTest { + @Test def applyAsInt(): Unit = { + val max = new IntBinaryOperator { + override def applyAsInt(left: Int, right: Int): Int = left.max(right) + } + assertEquals(max.applyAsInt(3, 5), 5) + assertEquals(max.applyAsInt(0, -2), 0) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntConsumerTest.scala new file mode 100644 index 0000000000..f76cfb7d10 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntConsumerTest.scala @@ -0,0 +1,41 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class IntConsumerTest { + @Test def accept(): Unit = { + // side-effects + var current: Int = 0 + + val add = new IntConsumer { + override def accept(value: Int): Unit = current += value + } + + add.accept(3) + assertEquals(current, 3) + add.accept(-10) + assertEquals(current, -7) + } + + @Test def andThen(): Unit = { + // side-effects + var buffer = scala.collection.mutable.ListBuffer.empty[Int] + + val add = new IntConsumer { + override def accept(value: Int): Unit = buffer += value + } + val add10x = new IntConsumer { + override def accept(value: Int): Unit = buffer += value * 10 + } + val f: IntConsumer = add.andThen(add10x) + + f.accept(1) + assertEquals(List(1, 10), buffer.toList) + f.accept(2) + assertEquals(List(1, 10, 2, 20), buffer.toList) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntFunctionTest.scala new file mode 100644 index 0000000000..a99d799f9e --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class IntFunctionTest { + @Test def testApply(): Unit = { + val repeat = new IntFunction[String] { + override def apply(value: Int): String = "." * value + } + assertEquals(repeat.apply(1), ".") + assertEquals(repeat.apply(3), "...") + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntPredicateTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntPredicateTest.scala new file mode 100644 index 0000000000..1a9d6dc03e --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntPredicateTest.scala @@ -0,0 +1,70 @@ +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.IntPredicate + +import org.junit.Assert._ +import org.junit.Test + +class IntPredicateTest { + import IntPredicateTest._ + + private val largerThan10 = makePredicate(_ > 10) + private val even = makePredicate(_ % 2 == 0) + + private val throwingPredicate = + makePredicate(x => throw new ThrowingPredicateException(x)) + + private val dontCallPredicate = + makePredicate(x => throw new AssertionError(s"dontCallPredicate.test($x)")) + + @Test def and(): Unit = { + // Truth table + val evenAndLargerThan10 = largerThan10.and(even) + assertTrue(evenAndLargerThan10.test(22)) + assertFalse(evenAndLargerThan10.test(21)) + assertFalse(evenAndLargerThan10.test(6)) + assertFalse(evenAndLargerThan10.test(5)) + + // Short-circuit + assertFalse(largerThan10.and(dontCallPredicate).test(5)) + assertThrows( + classOf[ThrowingPredicateException], + () => throwingPredicate.and(dontCallPredicate).test(5) + ) + } + + @Test def negate(): Unit = { + // Truth table + val notLargerThan10 = largerThan10.negate() + assertTrue(notLargerThan10.test(5)) + assertFalse(notLargerThan10.test(15)) + } + + @Test def or(): Unit = { + // Truth table + val evenOrLargerThan10 = largerThan10.or(even) + assertTrue(evenOrLargerThan10.test(22)) + assertTrue(evenOrLargerThan10.test(21)) + assertTrue(evenOrLargerThan10.test(6)) + assertFalse(evenOrLargerThan10.test(5)) + + // Short-circuit + assertTrue(largerThan10.or(dontCallPredicate).test(15)) + assertThrows( + classOf[ThrowingPredicateException], + () => throwingPredicate.or(dontCallPredicate).test(15) + ) + } +} + +object IntPredicateTest { + final class ThrowingPredicateException(x: Any) + extends Exception(s"throwing predicate called with $x") + + def makePredicate(f: Int => Boolean): IntPredicate = { + new IntPredicate { + def test(value: Int): Boolean = f(value) + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntSupplierTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntSupplierTest.scala new file mode 100644 index 0000000000..2090fe78cc --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntSupplierTest.scala @@ -0,0 +1,25 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.IntSupplier + +import org.junit.Assert._ +import org.junit.Test + +class IntSupplierTest { + import IntSupplierTest._ + + @Test def getAsInt(): Unit = { + assertEquals(Int.MinValue, makeSupplier(Int.MinValue).getAsInt()) + assertEquals(1024, makeSupplier(1024).getAsInt()) + assertEquals(Int.MaxValue, makeSupplier(Int.MaxValue).getAsInt()) + } +} + +object IntSupplierTest { + def makeSupplier(f: => Int): IntSupplier = { + new IntSupplier { + def getAsInt(): Int = f + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToDoubleFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToDoubleFunctionTest.scala new file mode 100644 index 0000000000..63126417fc --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToDoubleFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class IntToDoubleFunctionTest { + @Test def testApply(): Unit = { + val f = new IntToDoubleFunction { + override def applyAsDouble(value: Int): Double = value.toDouble / 10d + } + assertEquals(f.applyAsDouble(3), 0.3, 0.0) + assertEquals(f.applyAsDouble(20), 2, 0.0) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToLongFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToLongFunctionTest.scala new file mode 100644 index 0000000000..7ad763396d --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntToLongFunctionTest.scala @@ -0,0 +1,16 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class IntToLongFunctionTest { + @Test def testApply(): Unit = { + val f = new IntToLongFunction { + override def applyAsLong(value: Int): Long = value.toLong * Int.MaxValue + } + assertEquals(f.applyAsLong(3), 6442450941L) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntUnaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntUnaryOperatorTest.scala index cfc313a22d..410a3235f7 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntUnaryOperatorTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/IntUnaryOperatorTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: d028054 dated: 2022-05-16 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package org.scalanative.testsuite.javalib.util.function import org.junit.Assert._ diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongBinaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongBinaryOperatorTest.scala new file mode 100644 index 0000000000..ac293e3f46 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongBinaryOperatorTest.scala @@ -0,0 +1,16 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class LongBinaryOperatorTest { + @Test def applyAsLong(): Unit = { + val sumOp = new LongBinaryOperator { + override def applyAsLong(left: Long, right: Long): Long = left + right + } + assertEquals(30, sumOp.applyAsLong(10, 20)) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongConsumerTest.scala new file mode 100644 index 0000000000..5b5126c23c --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongConsumerTest.scala @@ -0,0 +1,41 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class LongConsumerTest { + @Test def accept(): Unit = { + // side-effects + var current: Long = 0 + + val add = new LongConsumer { + override def accept(value: Long): Unit = current += value + } + + add.accept(3) + assertEquals(current, 3) + add.accept(-10) + assertEquals(current, -7) + } + + @Test def andThen(): Unit = { + // side-effects + var buffer = scala.collection.mutable.ListBuffer.empty[Long] + + val add = new LongConsumer { + override def accept(value: Long): Unit = buffer += value + } + val add10x = new LongConsumer { + override def accept(value: Long): Unit = buffer += value * 10 + } + val f: LongConsumer = add.andThen(add10x) + + f.accept(1) + assertEquals(List(1L, 10L), buffer.toList) + f.accept(2) + assertEquals(List(1L, 10L, 2L, 20L), buffer.toList) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongFunctionTest.scala new file mode 100644 index 0000000000..9e4ad593dd --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class LongFunctionTest { + @Test def testApply(): Unit = { + val f = new LongFunction[Seq[Long]] { + override def apply(value: Long): Seq[Long] = List.fill(value.toInt)(value) + } + assertEquals(f.apply(1L), Seq(1L)) + assertEquals(f.apply(3L), Seq(3L, 3L, 3L)) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongPredicateTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongPredicateTest.scala new file mode 100644 index 0000000000..1f9fbeb298 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongPredicateTest.scala @@ -0,0 +1,70 @@ +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.LongPredicate + +import org.junit.Assert._ +import org.junit.Test + +class LongPredicateTest { + import LongPredicateTest._ + + private val largerThan10 = makePredicate(_ > 10L) + private val even = makePredicate(_ % 2 == 0L) + + private val throwingPredicate = + makePredicate(x => throw new ThrowingPredicateException(x)) + + private val dontCallPredicate = + makePredicate(x => throw new AssertionError(s"dontCallPredicate.test($x)")) + + @Test def and(): Unit = { + // Truth table + val evenAndLargerThan10 = largerThan10.and(even) + assertTrue(evenAndLargerThan10.test(22L)) + assertFalse(evenAndLargerThan10.test(21L)) + assertFalse(evenAndLargerThan10.test(6L)) + assertFalse(evenAndLargerThan10.test(5L)) + + // Short-circuit + assertFalse(largerThan10.and(dontCallPredicate).test(5L)) + assertThrows( + classOf[ThrowingPredicateException], + () => throwingPredicate.and(dontCallPredicate).test(5L) + ) + } + + @Test def negate(): Unit = { + // Truth table + val notLargerThan10 = largerThan10.negate() + assertTrue(notLargerThan10.test(5L)) + assertFalse(notLargerThan10.test(15L)) + } + + @Test def or(): Unit = { + // Truth table + val evenOrLargerThan10 = largerThan10.or(even) + assertTrue(evenOrLargerThan10.test(22L)) + assertTrue(evenOrLargerThan10.test(21L)) + assertTrue(evenOrLargerThan10.test(6L)) + assertFalse(evenOrLargerThan10.test(5L)) + + // Short-circuit + assertTrue(largerThan10.or(dontCallPredicate).test(15L)) + assertThrows( + classOf[ThrowingPredicateException], + () => throwingPredicate.or(dontCallPredicate).test(15L) + ) + } +} + +object LongPredicateTest { + final class ThrowingPredicateException(x: Any) + extends Exception(s"throwing predicate called with $x") + + def makePredicate(f: Long => Boolean): LongPredicate = { + new LongPredicate { + def test(value: Long): Boolean = f(value) + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongSupplierTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongSupplierTest.scala new file mode 100644 index 0000000000..4b5d12bb24 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongSupplierTest.scala @@ -0,0 +1,25 @@ +// Ported from Scala.js, commit SHA: db63dabed dated: 2020-10-06 +package org.scalanative.testsuite.javalib.util.function + +import java.util.function.LongSupplier + +import org.junit.Assert._ +import org.junit.Test + +class LongSupplierTest { + import LongSupplierTest._ + + @Test def getAsLong(): Unit = { + assertEquals(Long.MinValue, makeSupplier(Long.MinValue).getAsLong()) + assertEquals(1024L, makeSupplier(1024L).getAsLong()) + assertEquals(Long.MaxValue, makeSupplier(Long.MaxValue).getAsLong()) + } +} + +object LongSupplierTest { + def makeSupplier(f: => Long): LongSupplier = { + new LongSupplier { + def getAsLong(): Long = f + } + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToDoubleFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToDoubleFunctionTest.scala new file mode 100644 index 0000000000..e5274683f5 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToDoubleFunctionTest.scala @@ -0,0 +1,16 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class LongToDoubleFunctionTest { + @Test def testApply(): Unit = { + val f = new LongToDoubleFunction { + override def applyAsDouble(value: Long): Double = value.toDouble * 0.5 + } + assertEquals(f.applyAsDouble(3), 1.5, 0.0) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToIntFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToIntFunctionTest.scala new file mode 100644 index 0000000000..4b7ace1ca9 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongToIntFunctionTest.scala @@ -0,0 +1,16 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class LongToIntFunctionTest { + @Test def testApply(): Unit = { + val f = new LongToIntFunction { + override def applyAsInt(value: Long): Int = value.toInt / 2 + } + assertEquals(f.applyAsInt(3), 1) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongUnaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongUnaryOperatorTest.scala new file mode 100644 index 0000000000..90fe194626 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/LongUnaryOperatorTest.scala @@ -0,0 +1,35 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class LongUnaryOperatorTest { + private val f = new LongUnaryOperator { + override def applyAsLong(operand: Long): Long = operand * 10 + } + private val g = new LongUnaryOperator { + override def applyAsLong(operand: Long): Long = operand - 20 + } + + @Test def applyAsLong(): Unit = { + assertEquals(f.applyAsLong(3), 30) + } + + @Test def andThen(): Unit = { + val h: LongUnaryOperator = f.andThen(g) + assertEquals(h.applyAsLong(5), 30) + } + + @Test def compose(): Unit = { + val h: LongUnaryOperator = f.compose(g) + assertEquals(h.applyAsLong(5), -150) + } + + @Test def identity(): Unit = { + val f: LongUnaryOperator = LongUnaryOperator.identity() + assertEquals(1L, f.applyAsLong(1)) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjDoubleConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjDoubleConsumerTest.scala new file mode 100644 index 0000000000..5f82e12f0a --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjDoubleConsumerTest.scala @@ -0,0 +1,23 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class ObjDoubleConsumerTest { + @Test def accept(): Unit = { + // side-effects + var current: String = "" + + val op = new ObjDoubleConsumer[String] { + override def accept(left: String, right: Double): Unit = + current += s"$left $right " + } + + op.accept("First", 1.1) + op.accept("Second", 2.2) + assertEquals(current, "First 1.1 Second 2.2 ") + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjIntConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjIntConsumerTest.scala new file mode 100644 index 0000000000..b6a4893d6e --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjIntConsumerTest.scala @@ -0,0 +1,23 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class ObjIntConsumerTest { + @Test def accept(): Unit = { + // side-effects + var current: String = "" + + val op = new ObjIntConsumer[String] { + override def accept(left: String, right: Int): Unit = + current += left * right + } + + op.accept("First", 1) + op.accept("Second", 2) + assertEquals(current, "FirstSecondSecond") + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjLongConsumerTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjLongConsumerTest.scala new file mode 100644 index 0000000000..959b6646f6 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ObjLongConsumerTest.scala @@ -0,0 +1,22 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class ObjLongConsumerTest { + @Test def accept(): Unit = { + // side-effects + var current: String = "" + + val op = new ObjLongConsumer[String] { + override def accept(left: String, right: Long): Unit = + current += s"$left $right " + } + op.accept("First", 2L) + op.accept("Second", 3L) + assertEquals(current, "First 2 Second 3 ") + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/PredicateTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/PredicateTest.scala index f0ee70bf8b..8d4cfa5822 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/PredicateTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/PredicateTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit: 137c11d dated: 2019-07-03 - +// Ported from Scala.js, commit SHA: c473689c9 dated: 2021-05-03 package org.scalanative.testsuite.javalib.util.function import java.util.function.Predicate diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/SupplierTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/SupplierTest.scala index 4c15644053..3a6f7811cc 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/SupplierTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/SupplierTest.scala @@ -1,17 +1,25 @@ -package org.scalanative.testsuite.javalib.util -package function +// Ported from Scala.js, commit SHA: 5df5a4142 dated: 2020-09-06 +package org.scalanative.testsuite.javalib.util.function -import java.util.function._ -import java.util._ +import java.util.function.Supplier -import org.junit.Test import org.junit.Assert._ +import org.junit.Test class SupplierTest { - @Test def testGet(): Unit = { - val string = new Supplier[String] { - override def get(): String = "scala" + import SupplierTest._ + + @Test def get(): Unit = { + val supplier: Supplier[String] = makeSupplier("scala") + + assertEquals("scala", supplier.get()) + } +} + +object SupplierTest { + def makeSupplier[T](f: => T): Supplier[T] = { + new Supplier[T] { + def get(): T = f } - assertTrue(string.get() == "scala") } } diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleBiFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleBiFunctionTest.scala new file mode 100644 index 0000000000..be553145e6 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleBiFunctionTest.scala @@ -0,0 +1,17 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class ToDoubleBiFunctionTest { + @Test def applyAsDouble(): Unit = { + val op = new ToDoubleBiFunction[String, String] { + override def applyAsDouble(t: String, u: String): Double = + s"$t.$u".toDouble + } + assertEquals(op.applyAsDouble("123", "456"), 123.456, 0.0) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleFunctionTest.scala index bc4a0043a3..c5cc91eb8c 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleFunctionTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToDoubleFunctionTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit 00e462d dated: 2023-01-22 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package org.scalanative.testsuite.javalib.util.function import org.junit.Assert._ diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntBiFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntBiFunctionTest.scala new file mode 100644 index 0000000000..9a1c4d513c --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntBiFunctionTest.scala @@ -0,0 +1,16 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class ToIntBiFunctionTest { + @Test def applyAsInt(): Unit = { + val op = new ToIntBiFunction[String, String] { + override def applyAsInt(t: String, u: String): Int = s"$t$u".toInt + } + assertEquals(op.applyAsInt("10", "24"), 1024) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntFunctionTest.scala index eddeddb7a6..eabeeef4f6 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntFunctionTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToIntFunctionTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit 00e462d dated: 2023-01-22 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package org.scalanative.testsuite.javalib.util.function import org.junit.Assert._ diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongBiFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongBiFunctionTest.scala new file mode 100644 index 0000000000..30ab5decc8 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongBiFunctionTest.scala @@ -0,0 +1,16 @@ +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 +package org.scalanative.testsuite.javalib.util.function + +import org.junit.Assert._ +import org.junit.Test + +import java.util.function._ + +class ToLongBiFunctionTest { + @Test def applyAsLong(): Unit = { + val op = new ToLongBiFunction[String, String] { + override def applyAsLong(t: String, u: String): Long = t.toLong * u.toLong + } + assertEquals(op.applyAsLong("11111111", "2222222"), 24691355308642L) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongFunctionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongFunctionTest.scala index b8168b9769..d583719db7 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongFunctionTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/ToLongFunctionTest.scala @@ -1,5 +1,4 @@ -// Ported from Scala.js commit 00e462d dated: 2023-01-22 - +// Ported from Scala.js, commit SHA: cfb4888a6 dated: 2021-01-07 package org.scalanative.testsuite.javalib.util.function import org.junit.Assert._ diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/UnaryOperatorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/UnaryOperatorTest.scala index c4c6c87c64..5778523447 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/UnaryOperatorTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/function/UnaryOperatorTest.scala @@ -1,13 +1,30 @@ +// Ported from Scala.js, commit SHA: cbf86bbb8 dated: 2020-10-23 package org.scalanative.testsuite.javalib.util.function -import java.util.function._ +import java.util.function.UnaryOperator -import org.junit.Test import org.junit.Assert._ +import org.junit.Test class UnaryOperatorTest { - @Test def testUnaryOperator(): Unit = { + import UnaryOperatorTest._ + + @Test def identity(): Unit = { val unaryOperatorString: UnaryOperator[String] = UnaryOperator.identity() - assertTrue(unaryOperatorString.apply("scala") == "scala") + assertEquals("scala", unaryOperatorString.apply("scala")) + } + + @Test def createAndApply(): Unit = { + val double: UnaryOperator[Int] = makeUnaryOperator(_ * 2) + assertEquals(20, double.apply(10)) + assertEquals(20, double.apply(10)) + } +} + +object UnaryOperatorTest { + private def makeUnaryOperator[T](f: T => T): UnaryOperator[T] = { + new UnaryOperator[T] { + def apply(t: T): T = f(t) + } } } From 58af8f61ed81d1246af88eabb8ea11a631ecaa23 Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Mon, 6 Feb 2023 00:44:23 +0100 Subject: [PATCH 10/61] Fix #3131: javalib ServerSocket should now be more accepting (#3140) --- .../java/net/AbstractPlainSocketImpl.scala | 57 +- .../src/main/scala/java/net/InetAddress.scala | 11 +- .../scala/java/net/InterfaceAddress.scala | 43 + .../scala/java/net/NetworkInterface.scala | 984 ++++++++++++++++++ .../main/scala/java/net/SocketHelpers.scala | 27 + 5 files changed, 1087 insertions(+), 35 deletions(-) create mode 100644 javalib/src/main/scala/java/net/InterfaceAddress.scala create mode 100644 javalib/src/main/scala/java/net/NetworkInterface.scala diff --git a/javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala b/javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala index ab63274a82..9d31312c9d 100644 --- a/javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala +++ b/javalib/src/main/scala/java/net/AbstractPlainSocketImpl.scala @@ -5,23 +5,23 @@ import scala.scalanative.unsafe._ import scala.scalanative.runtime.ByteArray import scalanative.libc.string.memcpy -import scalanative.libc.errno.errno +import scala.scalanative.posix.arpa.inet // Import posix name errno as variable, not class or type. +import scala.scalanative.libc.errno.errno import scala.scalanative.posix.{errno => posixErrno}, posixErrno._ -import scala.scalanative.posix.unistd -import scala.scalanative.posix.sys.socket -import scala.scalanative.posix.sys.socketOps._ -import scala.scalanative.posix.sys.ioctl._ import scala.scalanative.posix.netinet.in import scala.scalanative.posix.netinet.inOps._ import scala.scalanative.posix.netinet.tcp -import scala.scalanative.posix.arpa.inet import scala.scalanative.posix.netdb._ import scala.scalanative.posix.netdbOps._ +import scala.scalanative.posix.string.strerror +import scala.scalanative.posix.sys.ioctl._ +import scala.scalanative.posix.sys.socket +import scala.scalanative.posix.sys.socketOps._ import scala.scalanative.posix.sys.time._ import scala.scalanative.posix.sys.timeOps._ -import scala.scalanative.posix.arpa.inet._ +import scala.scalanative.posix.unistd import scala.scalanative.meta.LinktimeInfo.isWindows import java.io.{FileDescriptor, IOException, OutputStream, InputStream} @@ -120,7 +120,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl { */ sa6.sin6_family = socket.AF_INET6.toUShort - sa6.sin6_port = htons(port.toUShort) + sa6.sin6_port = inet.htons(port.toUShort) val src = inetAddress.getAddress() @@ -217,7 +217,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl { if (timeout > 0) tryPollOnAccept() - val storage: Ptr[Byte] = stackalloc[Byte](sizeof[in.sockaddr_in6]) + val storage = stackalloc[Byte](sizeof[in.sockaddr_in6]) val len = stackalloc[socket.socklen_t]() !len = sizeof[in.sockaddr_in6].toUInt @@ -230,30 +230,33 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl { storage.asInstanceOf[Ptr[socket.sockaddr_storage]].ss_family.toInt val ipstr: Ptr[CChar] = stackalloc[CChar](in.INET6_ADDRSTRLEN.toUInt) - if (family == socket.AF_INET) { + val (srcPtr, port) = if (family == socket.AF_INET) { val sa = storage.asInstanceOf[Ptr[in.sockaddr_in]] - inet.inet_ntop( - socket.AF_INET, - sa.sin_addr.at1.asInstanceOf[Ptr[Byte]], - ipstr, - in.INET6_ADDRSTRLEN.toUInt - ) - s.port = inet.ntohs(sa.sin_port).toInt + val port = inet.ntohs(sa.sin_port).toInt + (sa.sin_addr.at1.asInstanceOf[Ptr[Byte]], port) } else { val sa = storage.asInstanceOf[Ptr[in.sockaddr_in6]] - inet.inet_ntop( - socket.AF_INET6, - sa.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]], - ipstr, - in.INET6_ADDRSTRLEN.toUInt + val port = inet.ntohs(sa.sin6_port).toInt + (sa.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]], port) + } + + val ret = inet.inet_ntop( + family, + srcPtr, + ipstr, + in.INET6_ADDRSTRLEN.toUInt + ) + + if (ret == null) { + throw new IOException( + s"inet_ntop failed: ${fromCString(strerror(errno))}" ) - s.port = inet.ntohs(sa.sin6_port).toInt } s.address = InetAddress.getByName(fromCString(ipstr)) - - s.fd = new FileDescriptor(newFd) + s.port = port s.localport = this.localport + s.fd = new FileDescriptor(newFd) } override def connect(host: String, port: Int): Unit = { @@ -433,7 +436,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl { } else if (isClosed) { 0 } else { - val cArr = buffer.asInstanceOf[ByteArray].at(offset) + val cArr = buffer.at(offset) var sent = 0 while (sent < count) { val ret = socket @@ -452,7 +455,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl { if (shutInput) -1 else { val bytesNum = socket - .recv(fd.fd, buffer.asInstanceOf[ByteArray].at(offset), count.toUInt, 0) + .recv(fd.fd, buffer.at(offset), count.toUInt, 0) .toInt def timeoutDetected = mapLastError( diff --git a/javalib/src/main/scala/java/net/InetAddress.scala b/javalib/src/main/scala/java/net/InetAddress.scala index 55839cda2a..edeaef3420 100644 --- a/javalib/src/main/scala/java/net/InetAddress.scala +++ b/javalib/src/main/scala/java/net/InetAddress.scala @@ -225,7 +225,7 @@ object InetAddress { new Inet4Address(addrinfoToByteArray(addrinfoP), effectiveHost) } else if (addrinfoP.ai_family == AF_INET6) { val addr = addrinfoP.ai_addr.asInstanceOf[Ptr[sockaddr_in6]] - val addrBytes = addr.sin6_addr.at1.asInstanceOf[Ptr[Byte]] + val addrBytes = addr.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]] // Scala JVM down-converts even when preferIPv6Addresses is "true" if (isIPv4MappedAddress(addrBytes)) { @@ -305,16 +305,11 @@ object InetAddress { } private def formatIn4Addr(pb: Ptr[Byte]): String = { - val addr = pb.asInstanceOf[Ptr[in_addr]] + // By contract, pb isInstanceOf[Ptr[in_addr]] val dstSize = INET_ADDRSTRLEN val dst = stackalloc[Byte](dstSize.toUInt) - val result = inet_ntop( - AF_INET, - addr.at1.asInstanceOf[Ptr[Byte]], - dst, - dstSize.toUInt - ) + val result = inet_ntop(AF_INET, pb, dst, dstSize.toUInt) if (result == null) throw new IOException(s"inet_ntop IPv4 failed, errno: ${errno}") diff --git a/javalib/src/main/scala/java/net/InterfaceAddress.scala b/javalib/src/main/scala/java/net/InterfaceAddress.scala new file mode 100644 index 0000000000..d09045c4c1 --- /dev/null +++ b/javalib/src/main/scala/java/net/InterfaceAddress.scala @@ -0,0 +1,43 @@ +package java.net + +class InterfaceAddress private[net] ( + inetAddr: InetAddress, + broadcastAddr: Option[Array[Byte]], + prefixLength: Short +) { + + override def equals(that: Any): Boolean = that match { + case that: InterfaceAddress => this.hashCode() == that.hashCode() + case _ => false + } + + def getAddress(): InetAddress = inetAddr + + lazy val bcAddress = { + if (broadcastAddr.isEmpty) null + else InetAddress.getByAddress(broadcastAddr.get) + } + + def getBroadcast(): InetAddress = bcAddress + + def getNetworkPrefixLength(): Short = prefixLength + + /** This hashCode is not intended or guaranteed to match Java. + */ + override def hashCode(): Int = + inetAddr.hashCode() + broadcastAddr.hashCode() + prefixLength + + override def toString(): String = { + val iaPart = inetAddr.getHostAddress() + + val broadcastPart = + if (broadcastAddr.isEmpty) "null" + else { + // Not the most runtime efficient algorithm, but easy to implement. + InetAddress.getByAddress(broadcastAddr.get).toString() + } + + s"/${iaPart}/${prefixLength} [${broadcastPart}]" + } + +} diff --git a/javalib/src/main/scala/java/net/NetworkInterface.scala b/javalib/src/main/scala/java/net/NetworkInterface.scala new file mode 100644 index 0000000000..437c66c33d --- /dev/null +++ b/javalib/src/main/scala/java/net/NetworkInterface.scala @@ -0,0 +1,984 @@ +package java.net + +import scala.scalanative.unsafe._ +import scala.scalanative.unsigned._ + +import scala.annotation.tailrec + +import java.net.SocketHelpers.sockaddrToByteArray + +import java.{util => ju} +import ju.Objects +import ju.stream.Stream + +import scala.scalanative.libc.errno.errno +import scala.scalanative.libc.string.strncpy +import scala.scalanative.posix.errno.ENXIO +import scala.scalanative.posix.net.`if`._ +import scala.scalanative.posix.net.ifOps._ +import scala.scalanative.posix.netinet.in._ +import scala.scalanative.posix.netinet.inOps._ +import scala.scalanative.posix.sys.ioctl.ioctl +import scala.scalanative.posix.sys.socket._ +import scala.scalanative.posix.sys.socketOps._ +import scala.scalanative.posix.string._ +import scala.scalanative.posix.unistd + +import scala.scalanative.meta.LinktimeInfo +import scala.scalanative.runtime.Platform + +import macOsIf._ +import macOsIfDl._ + +/* Design Notes: + * 1) This code is Unix only. On Windows, "empty" values are returned. + * A Windows implementation is left as an exercise for the reader. + * + * 2) The Unix implementation often splits into a Linux path and a + * macOS/BSD path. The former uses ioctl() calls and lets the + * operating system search for the named interface. Such a kernel + * search should be marginally faster and less error prone than + * the user land search of getifaddrs() results done on the + * macOS/BSD path. + * + * 3) Virtual and/or sub-interface methods rely on the convention that such + * interfaces have a colon (:) in the name. Improvements are welcome. + * + * 4) For future reference: + * GetAdaptersAddresses() function exist on Windows Vista and later. + * Function returns a linked list of detailed adapter information + * (much more than just addresses). + * C examples are provided in the documentation on MSDN. + */ + +class NetworkInterface private (ifName: String) { + + override def equals(that: Any): Boolean = that match { + case that: NetworkInterface => this.hashCode() == that.hashCode() + case _ => false + } + + def getDisplayName(): String = getName() + + def getHardwareAddress(): Array[Byte] = { + if (Platform.isWindows()) new Array[Byte](0) // No Windows support + else { + NetworkInterface.unixImplGetHardwareAddress(ifName) + } + } + + def getIndex(): Int = { + if (Platform.isWindows()) 0 // No Windows support + else { + NetworkInterface.unixImplGetIndex(ifName) + } + } + + def getInetAddresses(): ju.Enumeration[InetAddress] = { + if (Platform.isWindows()) { // No Windows support + ju.Collections.enumeration[InetAddress](new ju.ArrayList[InetAddress]) + } else { + NetworkInterface.unixImplGetInetAddresses(ifName) + } + } + + def getInterfaceAddresses(): ju.List[InterfaceAddress] = { + if (Platform.isWindows()) { // No Windows support + ju.Collections.emptyList[InterfaceAddress]() + } else { + NetworkInterface.unixImplGetInterfaceAddresses(ifName) + } + } + + def getMTU(): Int = { + if (Platform.isWindows()) 0 // No Windows support + else { + NetworkInterface.unixImplGetIfMTU(ifName) + } + } + + def getName(): String = ifName + + def getParent(): NetworkInterface = { + if (Platform.isWindows()) null // No Windows support + else if (!this.isVirtual()) null + else { + val parentName = ifName.split(":")(0) + NetworkInterface.getByName(parentName) + } + } + + def getSubInterfaces(): ju.Enumeration[NetworkInterface] = { + val ifList = new ju.ArrayList[NetworkInterface]() + + // No Windows support, so empty Enumeration will be returned. + if (!Platform.isWindows()) { + val allIfs = NetworkInterface.getNetworkInterfaces() + val matchMe = s"${ifName}:" + while (allIfs.hasMoreElements()) { + val elem = allIfs.nextElement() + val elemName = elem.getName() + if (elemName.startsWith(matchMe)) + ifList.add(elem) + } + } + ju.Collections.enumeration[NetworkInterface](ifList) + } + + def inetAddresses(): Stream[InetAddress] = { + if (Platform.isWindows()) Stream.empty[InetAddress]() // No Windows support + else { + NetworkInterface.unixImplInetAddresses(ifName) + } + } + + def isLoopback(): Boolean = { + if (Platform.isWindows()) false // No Windows support + else { + val ifFlags = NetworkInterface.unixImplGetIfFlags(ifName) + (ifFlags & unixIf.IFF_LOOPBACK) == unixIf.IFF_LOOPBACK + } + } + + def isPointToPoint(): Boolean = { + if (Platform.isWindows()) false // No Windows support + else { + val ifFlags = NetworkInterface.unixImplGetIfFlags(ifName) + (ifFlags & unixIf.IFF_POINTOPOINT) == unixIf.IFF_POINTOPOINT + } + } + + def isUp(): Boolean = { + if (Platform.isWindows()) false // No Windows support + else { + val ifFlags = NetworkInterface.unixImplGetIfFlags(ifName) + (ifFlags & unixIf.IFF_UP) == unixIf.IFF_UP + } + } + + // relies upon convention that Virtual or sub-interfaces have colon in name. + def isVirtual(): Boolean = ifName.indexOf(':') >= 0 // a best guess + + override def hashCode(): Int = ifName.hashCode() + + def subInterfaces(): Stream[NetworkInterface] = { + val allIfs = NetworkInterface.networkInterfaces() + val matchMe = s"${ifName}:" + allIfs.filter(_.getName().startsWith(matchMe)) + } + + def supportsMulticast(): Boolean = { + if (Platform.isWindows()) false // No Windows support + else { + val ifFlags = NetworkInterface.unixImplGetIfFlags(ifName) + (ifFlags & unixIf.IFF_MULTICAST) == unixIf.IFF_MULTICAST + } + } + + override def toString(): String = s"name:${ifName} (${ifName})" + +} + +object NetworkInterface { + import unixIfaddrs._ + import unixIfaddrsOps._ + + def getByIndex(index: Int): NetworkInterface = { + if (index < 0) + throw new IllegalArgumentException("Interface index can't be negative") + + if (Platform.isWindows()) { + null + } else { + unixGetByIndex(index) + } + } + + def getByInetAddress(addr: InetAddress): NetworkInterface = { + Objects.requireNonNull(addr) + if (Platform.isWindows()) { + null + } else { + unixGetByInetAddress(addr) + } + } + + def getByName(name: String): NetworkInterface = { + Objects.requireNonNull(name) + if (Platform.isWindows()) { + null + } else { + unixGetByName(name) + } + } + + def getNetworkInterfaces(): ju.Enumeration[NetworkInterface] = { + if (Platform.isWindows()) { + null + } else { + unixGetNetworkInterfaces() + } + } + + /** networkInterfaces() method is Java 9. It is provided because Streams are + * less clumsy than Enumerations. + */ + def networkInterfaces(): Stream[NetworkInterface] = { + if (Platform.isWindows()) { + null + } else { + unixNetworkInterfaces() + } + } + + private def createInetAddress( + ifa: Ptr[ifaddrs], + ifName: String + ): Option[InetAddress] = { + val sa = ifa.ifa_addr + val af = sa.sa_family.toInt + + if (!((af == AF_INET) || (af == AF_INET6))) None + else { + val bytes = sockaddrToByteArray(sa) + if (af == AF_INET) { + Some(InetAddress.getByAddress(bytes)) + } else { + val scopeId = sa.asInstanceOf[Ptr[sockaddr_in6]].sin6_scope_id.toInt + Some(Inet6Address(bytes, "", scopeId, ifName)) + } + } + } + + private def createInterfaceAddress( + ifa: Ptr[ifaddrs], + interfaceName: String + ): Option[InterfaceAddress] = { + + def decodePrefixLength(sa: Ptr[sockaddr]): Short = { + val result = + if (sa.sa_family.toInt == AF_INET) { + val sin4 = sa.asInstanceOf[Ptr[sockaddr_in]] + val mask = sin4.sin_addr.s_addr.toInt + Integer.bitCount(mask) + } else if (sa.sa_family.toInt == AF_INET6) { + val sin6 = sa.asInstanceOf[Ptr[sockaddr_in6]] + val longs = + sin6.sin6_addr.at1.at(0).asInstanceOf[Ptr[scala.Long]] + java.lang.Long.bitCount(longs(0)) + java.lang.Long.bitCount(longs(1)) + } else { + 0 // Blivet! Unknown address family, assume zero length prefix. + } + result.toShort + } + + val sa = ifa.ifa_addr + val af = sa.sa_family.toInt + if (!((af == AF_INET) || (af == AF_INET6))) { + None // Silently skip AF_PACKET (17) and such. + } else { + val bytes = sockaddrToByteArray(sa) + val inetAddress = if (af == AF_INET) { + InetAddress.getByAddress(bytes) + } else { + val scopeId = sa.asInstanceOf[Ptr[sockaddr_in6]].sin6_scope_id + Inet6Address(bytes, "", scopeId.toInt, interfaceName) + } + + val broadcastAddress: Option[Array[Byte]] = + if (sa.sa_family.toInt == AF_INET6) None + else if ((ifa.ifa_flags & unixIf.IFF_LOOPBACK.toUInt) != 0.toUInt) None + else Some(sockaddrToByteArray(ifa.ifa_broadaddr)) + + val prefixLen = decodePrefixLength(ifa.ifa_netmask) + + val ifAddress = + new InterfaceAddress(inetAddress, broadcastAddress, prefixLen) + + Some(ifAddress) + } + } + + private def createNetworkInterface(ifa: Ptr[ifaddrs]): NetworkInterface = { + val ifName = fromCString(ifa.ifa_name) + new NetworkInterface(ifName) + } + + private def unixGetByIndex(index: Int): NetworkInterface = { + val buf = stackalloc[Byte](IF_NAMESIZE.toUInt) + + val ret = if_indextoname(index.toUInt, buf) + + if (ret != null) unixGetByName(fromCString(ret)) + else if (errno == ENXIO) null // no interface has that index + else + throw new SocketException(fromCString(strerror(errno))) + } + + private def unixGetByInetAddress(addr: InetAddress): NetworkInterface = { + + def found(addr: Array[Byte], addrLen: Int, sa: Ptr[sockaddr]): Boolean = { + val sa_family = sa.sa_family.toInt + if (sa_family == AF_INET6) { + if (addrLen != 16) false + else { + val sa6 = sa.asInstanceOf[Ptr[sockaddr_in6]] + val sin6Addr = sa6.sin6_addr.at1.at(0).asInstanceOf[Ptr[Byte]] + memcmp(addr.at(0), sin6Addr, addrLen.toUInt) == 0 + } + } else if (sa_family == AF_INET) { + val sa4 = sa.asInstanceOf[Ptr[sockaddr_in]] + val sin4Addr = sa4.sin_addr.at1.asInstanceOf[Ptr[Byte]] + memcmp(addr.at(0), sin4Addr, addrLen.toUInt) == 0 + } else false + } + + @tailrec + def findIfInetAddress( + ipAddress: Array[Byte], + addrLen: Int, + ifa: Ptr[ifaddrs] + ): NetworkInterface = { + if (ifa == null) null + else if (found(ipAddress, addrLen, ifa.ifa_addr)) + createNetworkInterface(ifa) + else + findIfInetAddress( + ipAddress, + addrLen, + ifa.ifa_next.asInstanceOf[Ptr[ifaddrs]] + ) + } + + val addrBytes = addr.getAddress() + val len = addrBytes.length // check this once, not N times + + if (!((len == 4) || (len == 16))) + throw new SocketException( + s"unixGetByInetAddress: wrong Array[Byte] length: ${len}" + ) + else { + val ifap = stackalloc[Ptr[ifaddrs]]() + + val gifStatus = getifaddrs(ifap) + if (gifStatus == -1) + throw new SocketException( + s"getifaddrs failed: ${fromCString(strerror(errno))}" + ) + + val result = + try { + findIfInetAddress(addrBytes, len, !ifap) + } finally { + freeifaddrs(!ifap) + } + + result + } + } + + private def unixGetByName(name: String): NetworkInterface = Zone { + implicit z => + @tailrec + def findIfName( + cName: CString, + ifa: Ptr[ifaddrs] + ): NetworkInterface = { + if (ifa == null) null + else if (strcmp(ifa.ifa_name, cName) == 0) + createNetworkInterface(ifa) + else findIfName(cName, ifa.ifa_next.asInstanceOf[Ptr[ifaddrs]]) + } + + val cName = toCString(name) + val ifap = stackalloc[Ptr[ifaddrs]]() + + val gifStatus = getifaddrs(ifap) + if (gifStatus == -1) + throw new SocketException( + s"getifaddrs failed: ${fromCString(strerror(errno))}" + ) + + val result = + try { + findIfName(cName, !ifap) + } finally { + freeifaddrs(!ifap) + } + + result + } + + private def unixAccumulateNetworkInterfaces( + accumulator: (NetworkInterface) => Unit + ): Unit = { + + @tailrec + def accumulateNetIfs( + ni: Ptr[if_nameindex], + addOne: (NetworkInterface) => Unit + ): Unit = { + if ((ni.if_index.toInt != 0) || (ni.if_name != null)) { + val ifName = + if (ni.if_name == null) "" + else fromCString(ni.if_name) + + addOne(new NetworkInterface(ifName)) + + accumulateNetIfs( + ni + 1, // + 1 should skip entire structure + accumulator + ) + } + } + + val nameIndex = if_nameindex() + + if (nameIndex == null) + throw new SocketException( + s"if_nameindex() failed: ${fromCString(strerror(errno))}" + ) + + try { + accumulateNetIfs(nameIndex, accumulator) + } finally { + if_freenameindex(nameIndex) + } + } + + private def unixGetNetworkInterfaces(): ju.Enumeration[NetworkInterface] = { + val ifList = new ju.ArrayList[NetworkInterface]() + unixAccumulateNetworkInterfaces((netIf: NetworkInterface) => { + ifList.add(netIf); () + }) + ju.Collections.enumeration[NetworkInterface](ifList) + } + + private def unixNetworkInterfaces(): Stream[NetworkInterface] = { + val builder = Stream.builder[NetworkInterface]() + unixAccumulateNetworkInterfaces((netIf: NetworkInterface) => { + builder.add(netIf); () + }) + builder.build() + } + + /* Implement OS specific class & helper methods + */ + + private def linuxImplGetIoctlFd(): Int = { + val fd = socket(AF_INET, SOCK_DGRAM, 0) + + if (fd == -1) { + val msg = fromCString(strerror(errno)) + throw new SocketException(s"socket(AF_INET, SOCK_DGRAM) failed: ${msg}\n") + } + + fd + } + + private def macOsImplExecCallback( + ifName: String, + callback: Ptr[ifaddrs] => Tuple2[Int, Array[Byte]] + ): Tuple2[Int, Array[Byte]] = { + @tailrec + def findAfLinkIfName( + ifNameC: CString, + ifa: Ptr[ifaddrs] + ): Ptr[ifaddrs] = { + if (ifa == null) null + else if ((strcmp(ifNameC, ifa.ifa_name) == 0) + && (ifa.ifa_addr.sa_family.toInt == 18 /* AF_LINK */ )) + ifa + else + findAfLinkIfName(ifNameC, ifa.ifa_next) + } + + val ifap = stackalloc[Ptr[ifaddrs]]() + + val gifStatus = getifaddrs(ifap) + if (gifStatus == -1) + throw new SocketException( + s"getifaddrs failed: ${fromCString(strerror(errno))}" + ) + + try + Zone { implicit z => + val foundIfa = findAfLinkIfName(toCString(ifName), !ifap) + callback(foundIfa) + } + finally { + freeifaddrs(!ifap) + } + } + + private def unixImplGetIndex(ifName: String): Int = Zone { implicit z => + // toInt truncation OK, since index will never be larger than MAX_INT + if_nametoindex(toCString(ifName)).toInt + // Return 0 on error. Do not give errno error message. + } + + private def unixImplGetHardwareAddress(ifName: String): Array[Byte] = { + if (LinktimeInfo.isLinux) + linuxImplGetHardwareAddress(ifName) + else + macOsImplGetHardwareAddress(ifName) + } + + private def macOsImplGetHardwareAddress(ifName: String): Array[Byte] = { + def decodeSocketDl(sockaddrDl: Ptr[macOsIfDl.sockaddr_dl]): Array[Byte] = { + + val nBytes = if (sockaddrDl == null) 0 else sockaddrDl.sdl_alen.toInt + val bytes = new Array[Byte](nBytes) + + if (nBytes > 0) { // skip name + val src = sockaddrDl.sdl_data.at(sockaddrDl.sdl_nlen.toInt) + val dst = bytes.at(0) + memcpy(dst, src, nBytes.toUInt) + } + bytes + } + + def cb(ifa: Ptr[ifaddrs]): Tuple2[Int, Array[Byte]] = { + val arr = + if (ifa == null) new Array[Byte](0) + else + decodeSocketDl(ifa.ifa_addr.asInstanceOf[Ptr[sockaddr_dl]]) + + (0, arr) + } + + macOsImplExecCallback(ifName, cb)._2 + } + + private def linuxImplGetHardwareAddress(ifName: String): Array[Byte] = Zone { + implicit z => + // acknowledge: + // https://www.geekpage.jp/en/programming/linux-network/get-macaddr.php + + val request = stackalloc[unixIf.ifreq_hwaddress]() + + strncpy( + request.at1.asInstanceOf[CString], + toCString(ifName), + (unixIf.IFNAMSIZ - 1).toUInt + ) + + val saP = request.at2.asInstanceOf[Ptr[sockaddr]] + saP.sa_family = AF_INET.toUShort + + val fd = linuxImplGetIoctlFd() + + try { + val status = + ioctl(fd, unixIf.SIOCGIFHWADDR, request.asInstanceOf[Ptr[Byte]]); + if (status != 0) { + val msg = fromCString(strerror(errno)) + throw new SocketException(s"ioctl SIOCGIFHWADDR failed: ${msg}\n") + } + } finally { + unistd.close(fd) + } + + val hwAddress = new Array[Byte](6) + val hwAddrBytes = request.at2.sa_data + + for (j <- 0 until 6) + hwAddress(j) = hwAddrBytes(j) + + hwAddress + } + + private def unixImplGetIfMTU(ifName: String): Int = { + if (LinktimeInfo.isLinux) + linuxImplGetIfMTU(ifName) + else + macOsImplGetIfMTU(ifName) + } + + private def macOsImplGetIfMTU(ifName: String): Int = { + def cb(ifa: Ptr[ifaddrs]): Tuple2[Int, Array[Byte]] = { + val result = + if (ifa == null) 0 + else + ifa.ifa_data.asInstanceOf[Ptr[macOsIf.if_data]].ifi_mtu.toInt + + (result, null) + } + + macOsImplExecCallback(ifName, cb)._1 + } + + private def linuxImplGetIfMTU(ifName: String): Int = Zone { implicit z => + val request = stackalloc[unixIf.ifreq_mtu]() + + strncpy( + request.at1.asInstanceOf[CString], + toCString(ifName), + (unixIf.IFNAMSIZ - 1).toUInt + ) + + val saP = request.at2.asInstanceOf[Ptr[sockaddr]] + saP.sa_family = AF_INET.toUShort + + val fd = linuxImplGetIoctlFd() + + try { + val status = + ioctl(fd, unixIf.SIOCGIFMTU, request.asInstanceOf[Ptr[Byte]]); + if (status != 0) + throw new SocketException( + s"ioctl SIOCGIFMTU failed: ${fromCString(strerror(errno))}" + ) + + } finally { + unistd.close(fd) + } + + request._2 // ifr_mtu + } + + private def unixImplGetIfFlags(ifName: String): Short = { + if (LinktimeInfo.isLinux) + linuxImplGetIfFlags(ifName) + else + macOsImplGetIfFlags(ifName) + } + + private def macOsImplGetIfFlags(ifName: String): Short = { + def cb(ifa: Ptr[ifaddrs]): Tuple2[Int, Array[Byte]] = { + val result = + if (ifa == null) 0 + else ifa.ifa_flags.toInt + + (result, null) + } + + macOsImplExecCallback(ifName, cb)._1.toShort + } + + private def linuxImplGetIfFlags(ifName: String): Short = Zone { implicit z => + val request = stackalloc[unixIf.ifreq_flags]() + + strncpy( + request.at1.asInstanceOf[CString], + toCString(ifName), + (unixIf.IFNAMSIZ - 1).toUInt + ) + + val saP = request.at2.asInstanceOf[Ptr[sockaddr]] + saP.sa_family = AF_INET.toUShort + + val fd = linuxImplGetIoctlFd() + + try { + val status = + ioctl(fd, unixIf.SIOCGIFFLAGS, request.asInstanceOf[Ptr[Byte]]); + + if (status != 0) { + val msg = fromCString(strerror(errno)) + throw new SocketException(s"ioctl SIOCGIFFLAGS failed: ${msg}\n") + } + } finally { + unistd.close(fd) + } + + request._2 // ifr_flags + } + + private def unixAccumulateInetAddresses( + ifNameJ: String, + accumulator: (InetAddress) => Unit + ): Unit = Zone { implicit z => + @tailrec + def accumulateInetAddresses( + ifNameC: CString, + addOne: (InetAddress) => Unit, + ifa: Ptr[ifaddrs] + ): Unit = { + if (ifa != null) { + if (strcmp(ifNameC, ifa.ifa_name) == 0) { + createInetAddress(ifa, ifNameJ).map(ia => addOne(ia)) + } + accumulateInetAddresses( + ifNameC, + addOne, + ifa.ifa_next.asInstanceOf[Ptr[ifaddrs]] + ) + } + } + + val ifap = stackalloc[Ptr[ifaddrs]]() + + val gifStatus = getifaddrs(ifap) + + if (gifStatus == -1) + throw new SocketException( + s"getifaddrs failed: ${fromCString(strerror(errno))}" + ) + + try { + accumulateInetAddresses(toCString(ifNameJ), accumulator, !ifap) + } finally { + freeifaddrs(!ifap) + } + } + + private def unixAccumulateInterfaceAddresses( + ifName: String, + accumulator: (InterfaceAddress) => Unit + ): Unit = Zone { implicit z => + @tailrec + def accumulateInterfaceAddresses( + ifNameJ: String, + ifNameC: CString, + addOne: (InterfaceAddress) => Unit, + ifa: Ptr[ifaddrs] + ): Unit = { + if (ifa != null) { + if (strcmp(ifNameC, ifa.ifa_name) == 0) { + createInterfaceAddress(ifa, ifNameJ).map(ia => addOne(ia)) + } + accumulateInterfaceAddresses( + ifNameJ, + ifNameC, + addOne, + ifa.ifa_next.asInstanceOf[Ptr[ifaddrs]] + ) + } + } + + val ifap = stackalloc[Ptr[ifaddrs]]() + + val gifStatus = getifaddrs(ifap) + + if (gifStatus == -1) + throw new SocketException( + s"getifaddrs failed: ${fromCString(strerror(errno))}" + ) + + try { + accumulateInterfaceAddresses( + ifName, + toCString(ifName), + accumulator, + !ifap + ) + } finally { + freeifaddrs(!ifap) + } + } + + private def unixImplGetInterfaceAddresses( + ifName: String + ): ju.List[InterfaceAddress] = { + val ifaList = new ju.ArrayList[InterfaceAddress]() + unixAccumulateInterfaceAddresses( + ifName, + (ifa: InterfaceAddress) => { ifaList.add(ifa); () } + ) + ifaList + } + + private def unixImplGetInetAddresses( + ifName: String + ): ju.Enumeration[InetAddress] = { + val ifList = new ju.ArrayList[InetAddress]() + unixAccumulateInetAddresses( + ifName, + (ia: InetAddress) => { ifList.add(ia); () } + ) + ju.Collections.enumeration[InetAddress](ifList) + } + + private def unixImplInetAddresses(ifName: String): Stream[InetAddress] = { + val builder = Stream.builder[InetAddress]() + unixAccumulateInetAddresses( + ifName, + (ia: InetAddress) => { builder.add(ia); () } + ) + builder.build() + } + +} + +@extern +private object unixIfaddrs { + /* Reference: man getifaddrs + * #include + */ + + // format: off + type ifaddrs = CStruct7[ + Ptr[Byte], /* Ptr[ifaddrs] */ // ifa_next: Next item in list + CString, // ifa_name: Name of interface + CUnsignedInt, // ifa_flags: Flags from SIOCGIFFLAGS + Ptr[sockaddr], // ifa_addr: Address of interface + Ptr[sockaddr], // ifa_netmask: Netmask of interface + // ifu_broadaddr: Broadcast address of interface + // ifu_dstaddr: Point-to-point destination address + Ptr[sockaddr], // union: ifu_broadaddr, ifu_dstaddr + Ptr[Byte] // ifa_data: Address-specific data + ] + // format: on + + def getifaddrs(ifap: Ptr[Ptr[ifaddrs]]): CInt = extern + + def freeifaddrs(ifa: Ptr[ifaddrs]): Unit = extern +} + +private object unixIfaddrsOps { + import unixIfaddrs._ + + implicit class unixIfaddrOps(val ptr: Ptr[ifaddrs]) extends AnyVal { + def ifa_next: Ptr[ifaddrs] = ptr._1.asInstanceOf[Ptr[ifaddrs]] + def ifa_name: CString = ptr._2 + def ifa_flags: CUnsignedInt = ptr._3 + def ifa_addr: Ptr[sockaddr] = ptr._4 + def ifa_netmask: Ptr[sockaddr] = ptr._5 + def ifa_broadaddr: Ptr[sockaddr] = ptr._6 + def ifa_dstaddr: Ptr[sockaddr] = ptr._6 + def ifa_data: Ptr[Byte] = ptr._7 + + // ifa fields are read-only in use, so no Ops here to set them. + } +} + +@extern +private object unixIf { + /* Reference: man 7 netdevice + * #include + */ + + // Three SN-only types used to facilitate retrieving specific types of data. + type ifreq_hwaddress = CStruct2[ + CArray[CChar, Nat.Digit2[Nat._1, Nat._6]], + sockaddr + ] + + type ifreq_mtu = CStruct2[ + CArray[CChar, Nat.Digit2[Nat._1, Nat._6]], + CInt + ] + + type ifreq_flags = CStruct2[ + CArray[CChar, Nat.Digit2[Nat._1, Nat._6]], + CShort + ] + + @name("scalanative_ifnamesiz") + def IFNAMSIZ: CInt = extern + + @name("scalanative_iff_broadcast") + def IFF_BROADCAST: CInt = extern + + @name("scalanative_iff_loopback") + def IFF_LOOPBACK: CInt = extern + + @name("scalanative_iff_multicast") + def IFF_MULTICAST: CInt = extern + + @name("scalanative_iff_pointopoint") + def IFF_POINTOPOINT: CInt = extern + + @name("scalanative_iff_running") + def IFF_RUNNING: CInt = extern + + @name("scalanative_siocgifflags") + def SIOCGIFFLAGS: CInt = extern + + @name("scalanative_siocgifhwaddr") + def SIOCGIFHWADDR: CInt = extern + + @name("scalanative_siocgifmtu") + def SIOCGIFMTU: CInt = extern + + @name("scalanative_iff_up") + def IFF_UP: CInt = extern +} + +private object macOsIf { + + /* Scala if_data & corresponding ifDataOps definitions are not complete. + * Only items used in NetworkInterface are declared. + */ + + /* Reference: macOS + * /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include + * /net/if_var.h + * + * struct if_data { + * // generic interface information + * u_char ifi_type; // ethernet, tokenring, etc + * u_char ifi_typelen; // Length of frame type id + * u_char ifi_physical; // e.g., AUI, Thinnet, 10base-T, etc + * u_char ifi_addrlen; // media address length + * u_char ifi_hdrlen; // media header length + * u_char ifi_recvquota; // polling quota for receive intrs + * u_char ifi_xmitquota; // polling quota for xmit intrs + * u_char ifi_unused1; // for future use + * u_int32_t ifi_mtu; // maximum transmission unit + */ + + // Incomplete + type if_data = CStruct2[ + CLongLong, // Placeholder, consolidate & skip fields of no interest. + CUnsignedInt // ifi_mtu + ] + + // Incomplete, corresponding to incomplete if_data just above. + implicit class ifDataOps(val ptr: Ptr[if_data]) extends AnyVal { + def ifi_mtu: CUnsignedInt = ptr._2 + } + // ifi fields read-only fields in use, so no Ops here to set them. +} + +private object macOsIfDl { + /* Scala sockaddr_dl & corresponding sockaddrDlOps definitions are not + * complete. They are only what NetworkInterface uses. + */ + + /* Reference: FreeBSD man sockaddr_dl + * #include + * + * For sdl_data field, use the larger of macOS defined 12 and + * FreeBSD defined 46. + * + * struct sockaddr_dl + * The sockaddr_dl structure is used to describe a layer 2 link-level + * address. The structure has the following members: + * + * ushort_t sdl_family; // address family + * ushort_t sdl_index; // if != 0, system interface index + * uchar_t sdl_type; // interface type + * uchar_t sdl_nlen; // interface name length + * uchar_t sdl_alen; // link level address length + * uchar_t sdl_slen; // link layer selector length + * char sdl_data[46]; // contains both if name and ll address + */ + + // sdl_data, max(macOs == 12, FreeBsd == 46) + type _46 = Nat.Digit2[Nat._4, Nat._6] + type sdl_data_t = CArray[CChar, _46] + + type sockaddr_dl = CStruct8[ + Byte, // sdl_len; // Total length of sockaddr + Byte, // sdl_family; // address family + CShort, // sdl_index + Byte, // sdl_type + Byte, // sdl_nlen + Byte, // sdl_alen + Byte, // sdl_slen + sdl_data_t + ] + + implicit class sockaddrDlOps(val ptr: Ptr[sockaddr_dl]) extends AnyVal { + def sdl_len: UByte = ptr._1.toUByte + def sdl_family: UByte = ptr._2.toUByte + def sdl_index: UShort = ptr._3.toUShort + def sdl_type: UByte = ptr._4.toUByte + def sdl_nlen: UByte = ptr._5.toUByte + def sdl_alen: UByte = ptr._6.toUByte + def sdl_slen: UByte = ptr._7.toUByte + def sdl_data: sdl_data_t = ptr._8 + } +} diff --git a/javalib/src/main/scala/java/net/SocketHelpers.scala b/javalib/src/main/scala/java/net/SocketHelpers.scala index 9caa1a7c8c..30ebab2216 100644 --- a/javalib/src/main/scala/java/net/SocketHelpers.scala +++ b/javalib/src/main/scala/java/net/SocketHelpers.scala @@ -5,8 +5,10 @@ import scala.scalanative.unsafe._ import scala.scalanative.posix.{netdb, netdbOps}, netdb._, netdbOps._ import scala.scalanative.posix.netinet.in +import scala.scalanative.posix.netinet.inOps._ import scala.scalanative.posix.sys.socket._ import scala.scalanative.posix.sys.socketOps._ +import scala.scalanative.posix.string.memcpy import scala.scalanative.meta.LinktimeInfo.isWindows @@ -130,6 +132,31 @@ object SocketHelpers { } } + private[net] def sockaddrToByteArray(sockAddr: Ptr[sockaddr]): Array[Byte] = { + + val (src, byteArraySize) = { + val af = sockAddr.sa_family.toInt + if (af == AF_INET6) { + val v6addr = sockAddr.asInstanceOf[Ptr[in.sockaddr_in6]] + val sin6Addr = v6addr.sin6_addr.at1.asInstanceOf[Ptr[Byte]] + val arraySize = 16 + (sin6Addr, arraySize) + } else if (af == AF_INET) { + val v4addr = sockAddr.asInstanceOf[Ptr[in.sockaddr_in]] + val sin4Addr = v4addr.sin_addr.at1.asInstanceOf[Ptr[Byte]] + val arraySize = 4 + (sin4Addr, arraySize) + } else { + throw new SocketException(s"Unsupported address family: ${af}") + } + } + + val byteArray = new Array[Byte](byteArraySize) + memcpy(byteArray.at(0), src, byteArraySize.toUInt) + + byteArray + } + // Create copies of loopback & wildcard, so that originals never get changed // ScalaJVM shows loopbacks with null host, wildcards with numeric host. From 0a714c60d0807a2c60c132edb91705ddbfe28881 Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Sat, 11 Feb 2023 04:36:20 -0500 Subject: [PATCH 11/61] Fix #3153: j.nio.fs.FileHelpers better respects java.io.tmp property (#3155) --- .../scalanative/nio/fs/FileHelpers.scala | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/javalib/src/main/scala/scala/scalanative/nio/fs/FileHelpers.scala b/javalib/src/main/scala/scala/scalanative/nio/fs/FileHelpers.scala index 4ea41ffebf..d7c8e2deb4 100644 --- a/javalib/src/main/scala/scala/scalanative/nio/fs/FileHelpers.scala +++ b/javalib/src/main/scala/scala/scalanative/nio/fs/FileHelpers.scala @@ -9,8 +9,11 @@ import scalanative.unsafe._, stdlib._, stdio._, string._ import scalanative.meta.LinktimeInfo.isWindows import scala.collection.mutable.UnrolledBuffer import scala.reflect.ClassTag + import java.io.{File, IOException} import java.nio.charset.StandardCharsets +import java.{util => ju} + import scala.scalanative.windows._ import scala.scalanative.windows.HandleApiExt.INVALID_HANDLE_VALUE import scala.scalanative.windows.FileApi._ @@ -203,21 +206,14 @@ object FileHelpers { } lazy val tempDir: String = { - if (isWindows) { - val buffer: Ptr[WChar] = stackalloc[WChar](MAX_PATH) - GetTempPathW(MAX_PATH, buffer) - fromCWideString(buffer, StandardCharsets.UTF_16LE) - } else { - val dir = getenv(c"TMPDIR") - if (dir == null) { - System.getProperty("java.io.tmpdir") match { - case null => "/tmp" - case d => d - } - } else { - fromCString(dir) - } - } + val propertyName = "java.io.tmpdir" + // set at first lookup after program start. + val dir = System.getProperty(propertyName) + ju.Objects.requireNonNull( + dir, + s"Required Java System property ${propertyName} is not defined." + ) + dir } private def genTempFile( From b72f4abfdbe1b494fcc9d9bcd19e6f5ff0238602 Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Sat, 11 Feb 2023 10:37:22 +0100 Subject: [PATCH 12/61] Fix #3071, #3135: Implement Java 11 writeString & readString methods and Java 10 transferTo (#3159) * Fix #3071, #3135: Implement Java 11 writeString & readString methods and Java 10 transferTo * Delete nonsensical test --- javalib/src/main/scala/java/io/Reader.scala | 18 + .../src/main/scala/java/io/StringWriter.scala | 9 +- .../src/main/scala/java/nio/file/Files.scala | 89 +++- .../javalib/nio/file/FilesTestOnJDK11.scala | 428 ++++++++++++++++++ 4 files changed, 536 insertions(+), 8 deletions(-) create mode 100644 unit-tests/shared/src/test/require-jdk11/org/scalanative/testsuite/javalib/nio/file/FilesTestOnJDK11.scala diff --git a/javalib/src/main/scala/java/io/Reader.scala b/javalib/src/main/scala/java/io/Reader.scala index 5b477fd520..23c5817adc 100644 --- a/javalib/src/main/scala/java/io/Reader.scala +++ b/javalib/src/main/scala/java/io/Reader.scala @@ -1,6 +1,7 @@ package java.io // Ported from Scala.js, commit: 7d7a621, dated 2022-03-07 +// 2023-02-01 implemented Java 10 transferTo() method import java.nio.CharBuffer @@ -82,4 +83,21 @@ abstract class Reader() extends Readable with Closeable { def close(): Unit + // Since: Java 10 + def transferTo(out: Writer): Long = { + val buffer = new Array[Char](4096) + + @tailrec + def loop(nRead: Long): Long = { + val n = this.read(buffer) + if (n == -1) { + nRead + } else { + out.write(buffer, 0, n) + loop(nRead + n) + } + } + + loop(0) + } } diff --git a/javalib/src/main/scala/java/io/StringWriter.scala b/javalib/src/main/scala/java/io/StringWriter.scala index 3f8344c0ce..11d8e0feb4 100644 --- a/javalib/src/main/scala/java/io/StringWriter.scala +++ b/javalib/src/main/scala/java/io/StringWriter.scala @@ -1,10 +1,13 @@ package java.io -class StringWriter extends Writer { +class StringWriter(initialSize: Int) extends Writer { - private[this] val buf = new StringBuffer + def this() = this(128) - def this(initialSize: Int) = this() + if (initialSize < 0) + throw new IllegalArgumentException("Initial size < 0") + + private val buf = new StringBuffer(initialSize) override def write(c: Int): Unit = buf.append(c.toChar) diff --git a/javalib/src/main/scala/java/nio/file/Files.scala b/javalib/src/main/scala/java/nio/file/Files.scala index 4baae0e71a..5274bf13b0 100644 --- a/javalib/src/main/scala/java/nio/file/Files.scala +++ b/javalib/src/main/scala/java/nio/file/Files.scala @@ -11,8 +11,10 @@ import java.io.{ InputStreamReader, OutputStream, OutputStreamWriter, + StringWriter, UncheckedIOException } + import java.nio.file.attribute._ import java.nio.charset.{Charset, StandardCharsets} import java.nio.channels.{FileChannel, SeekableByteChannel} @@ -28,6 +30,9 @@ import java.util.{ Set } import java.util.stream.{Stream, WrappedScalaStream} + +import scala.annotation.tailrec + import scalanative.unsigned._ import scalanative.unsafe._ import scalanative.libc._ @@ -192,15 +197,14 @@ object Files { throw new IOException() } - def createFile(path: Path, attrs: Array[FileAttribute[_]]): Path = + def createFile(path: Path, attrs: Array[FileAttribute[_]]): Path = { if (exists(path, Array.empty)) throw new FileAlreadyExistsException(path.toString) - else if (FileHelpers.createNewFile(path.toString)) { + else if (FileHelpers.createNewFile(path.toString, throwOnError = true)) { setAttributes(path, attrs) - path - } else { - throw new IOException() } + path + } def createLink(link: Path, existing: Path): Path = Zone { implicit z => if (isWindows) { @@ -773,6 +777,24 @@ object Files { } } + // Since: Java 11 + def readString(path: Path): String = { + readString(path, StandardCharsets.UTF_8) + } + + // Since: Java 11 + def readString(path: Path, cs: Charset): String = { + val reader = newBufferedReader(path, cs) + try { + // Guess an cost-effective amortized size. + val writer = new StringWriter(2 * 1024) + reader.transferTo(writer) + writer.toString() + // No need to close() StringWriter, so no inner try/finally. + } finally + reader.close() + } + def readSymbolicLink(link: Path): Path = if (!isSymbolicLink(link)) { throw new NotLinkException(link.toString) @@ -1096,4 +1118,61 @@ object Files { "posix" -> classOf[PosixFileAttributeView] ) + // Since: Java 11 + def writeString( + path: Path, + csq: java.lang.CharSequence, + cs: Charset, + options: Array[OpenOption] + ): Path = { + import java.io.Reader + + // Java API has no CharSequenceReader, but the concept is useful here. + class CharSequenceReader(csq: CharSequence) extends Reader { + private var closed = false + private var pos = 0 + + override def close(): Unit = closed = true + + override def read(cbuf: Array[Char], off: Int, len: Int): Int = { + if (closed) + throw new IOException("Operation on closed stream") + + if (off < 0 || len < 0 || len > cbuf.length - off) + throw new IndexOutOfBoundsException + + if (len == 0) 0 + else { + val count = Math.min(len, csq.length() - pos) + var i = 0 + while (i < count) { + cbuf(off + i) = csq.charAt(pos + i) + i += 1 + } + pos += count + if (count == 0) -1 else count + } + } + } + + val reader = new CharSequenceReader(csq) + val writer = newBufferedWriter(path, cs, options) + try { + reader.transferTo(writer) + // No need to close() CharSequenceReader, so no inner try/finally. + } finally + writer.close() + + path + } + + // Since: Java 11 + def writeString( + path: Path, + csq: java.lang.CharSequence, + options: Array[OpenOption] + ): Path = { + writeString(path, csq, StandardCharsets.UTF_8, options) + } + } diff --git a/unit-tests/shared/src/test/require-jdk11/org/scalanative/testsuite/javalib/nio/file/FilesTestOnJDK11.scala b/unit-tests/shared/src/test/require-jdk11/org/scalanative/testsuite/javalib/nio/file/FilesTestOnJDK11.scala new file mode 100644 index 0000000000..68f51eedaf --- /dev/null +++ b/unit-tests/shared/src/test/require-jdk11/org/scalanative/testsuite/javalib/nio/file/FilesTestOnJDK11.scala @@ -0,0 +1,428 @@ +package org.scalanative.testsuite +package javalib.nio.file + +import java.{lang => jl} +import java.{util => ju} + +import java.nio.charset.StandardCharsets +import java.nio.CharBuffer +import java.nio.file.Files +import java.nio.file.{Path, Paths} +import java.nio.file.{FileAlreadyExistsException, StandardOpenOption} + +import org.junit.Test +import org.junit.Assert._ +import org.junit.{BeforeClass, AfterClass} +import org.junit.Ignore + +import org.scalanative.testsuite.utils.AssertThrows.assertThrows + +class FilesTestOnJDK11 { + import FilesTestOnJDK11._ + + /* Design Notes: + * + * 1) This set of Tests is designed to clean up after itself. That is + * delete all directories and files created. For debugging one + * can comment-out the Files.delete() in the afterClass() static method. + * + * 2) To simplify implementation, The readString() Tests call writeString(). + * This leaves open the possibility of complementary and/or compensating + * errors in the two methods. + * + * In a better World, writeString() would be Test'ed before possibly + * being used by readString(). Currently the order of execution of tests + * is hard to determine and harder (not possible) to specify. + * This Suite is forced to rely on writeString() eventually getting + * strongly tested. + * + * Normally one would expect to see the writeString() tests at the top + * of the file, expecting them to be run before being used by + * readString(). Here, the writeString() methods are later in the file + * because there are hints, but not guarantees, that Tests later in the + * file are run before those earlier. No wonder why it is hard to + * find Test developers. + * + * 3) The comment above the largeWriteStringThenReadString() Test explains + * why it is @Ignore'd in normal Continuous Integration. + * + * 4) There are no tests for CharSequence javax.swing.text.Segment because + * Segment is not implemented by Scala Native. + */ + + @Test def readStringUsingDefaultCharsetUTF8(): Unit = { + val ioPath = getCleanIoPath("utf8_forReadBack") + val dataOut = getDataOut() + + Files.writeString(ioPath, dataOut) + + val dataIn = Files.readString(ioPath) + + assertEquals("data read back does not match data written", dataOut, dataIn) + } + + @Test def readStringUTF16LEUsingExplicitCharsetUTF16LE(): Unit = { + val ioPath = getCleanIoPath("utf16LE_forReadBack") + val dataOut = getDataOut() + + Files.writeString(ioPath, dataOut, StandardCharsets.UTF_16LE) + + val dataIn = Files.readString(ioPath, StandardCharsets.UTF_16LE) + + assertEquals("data read back does not match data written", dataOut, dataIn) + } + + @Test def readStringUTF16BEUsingExplicitCharsetUTF16BE(): Unit = { + val ioPath = getCleanIoPath("utf16BE_forReadBack") + val dataOut = getDataOut() + + Files.writeString(ioPath, dataOut, StandardCharsets.UTF_16BE) + + val dataIn = Files.readString(ioPath, StandardCharsets.UTF_16BE) + + assertEquals("data read back does not match data written", dataOut, dataIn) + } + + @Test def writeStringFromStringUsingDefaultCharsetUTF8(): Unit = { + val ioPath = getCleanIoPath("utf8_file") + val dataOut = getDataOut() + + /* Test, at the same time, correctness of both writing the file and of + * using a variable number of arguments whilst doing so. + * + * Call without "OpenOption" third argument. + * Java documention says this will cause options "CREATE", + * "TRUNCATE_EXISTING", and "WRITE" to be used. CREATE and + * WRITE are exercised here. + */ + + Files.writeString(ioPath, dataOut) + + verifySmallUtf8Payload(ioPath) + } + + @Test def writeStringFromStringUsingExplicitCharsetUTF16LE(): Unit = { + val ioPath = getCleanIoPath("utf16LE_file") + val dataOut = getDataOut() + + // format: off + val expectedValues = Array( + 0xAC, + 0x20, + 0xA3, + 0x00, + 0x24, + 0x00, + ).map (_.toByte) + // format: on + + /* Euro, Pound, and Dollar characters are all 2 bytes in UTF-16. + * End-of-line newline will be 2 bytes. Windows carriage-return (CR), + * if present, will be another two bytes. + */ + val expectedDataLength = expectedValues.size + (EOLlen * 2) + + /* Write String out in "odd, non-standard 16LE" format instead of + * Java standard 16BE just to shake things up and invite faults. + */ + Files.writeString(ioPath, dataOut, StandardCharsets.UTF_16LE) + + val bytesRead = Files.readAllBytes(ioPath) + + assertEquals("bytes read", expectedDataLength, bytesRead.length) + + for (j <- 0 until (expectedValues.length)) { + assertEquals( + s"write/read mismatch at index ${j}", + expectedValues(j), + bytesRead(j) + ) + } + + verifyUtf16LeEOL(bytesRead) + } + + @Test def writeStringFromStringUsingExplicitCharsetUTF16BE(): Unit = { + val ioPath = getCleanIoPath("utf16BE_file") + val dataOut = getDataOut() + + // format: off + val expectedValues = Array( + 0x20, + 0xAC, + 0x00, + 0xA3, + 0x00, + 0x24, + ).map (_.toByte) + // format: on + + /* Euro, Pound, and Dollar characters are all 2 bytes in UTF-16. + * End-of-line newline will be 2 bytes. Windows carriage-return (CR), + * if present, will be another two bytes. + */ + val expectedDataLength = expectedValues.size + (EOLlen * 2) + + // Write as represented in Java Characters, Big Endian or network order. + Files.writeString(ioPath, dataOut, StandardCharsets.UTF_16BE) + + val bytesRead = Files.readAllBytes(ioPath) + + assertEquals("bytes read", expectedDataLength, bytesRead.length) + + for (j <- 0 until (expectedValues.length)) { + assertEquals( + s"write/read mismatch at index ${j}", + expectedValues(j), + bytesRead(j) + ) + } + + verifyUtf16BeEOL(bytesRead) + } + + @Test def writeStringFromCharBufferWrapSmallArray(): Unit = { + val ioPath = getCleanIoPath("CharBufferWrapSmallArray") + val dataOut = getDataOut() + + val output = CharBuffer.wrap(dataOut.toArray[Char]) + Files.writeString(ioPath, output) + + verifySmallUtf8Payload(ioPath) + } + + @Test def writeStringFromCharBufferWrapSmallString(): Unit = { + val ioPath = getCleanIoPath("CharBufferWrapSmallString") + val dataOut = getDataOut() + + val output = CharBuffer.wrap(dataOut) + Files.writeString(ioPath, output) + + verifySmallUtf8Payload(ioPath) + } + + @Test def writeStringFromStringBuilderSmall(): Unit = { + val ioPath = getCleanIoPath("StringBuilderSmall") + val dataOut = getDataOut() + + val output = jl.StringBuilder(dataOut) + Files.writeString(ioPath, output) + + verifySmallUtf8Payload(ioPath) + } + + @Test def writeStringFromStringBufferSmall(): Unit = { + val ioPath = getCleanIoPath("StringBufferSmall") + val dataOut = getDataOut() + + val output = jl.StringBuffer(dataOut) + Files.writeString(ioPath, output) + + verifySmallUtf8Payload(ioPath) + } + + @Test def writeStringFromStringUsingOptionArg(): Unit = { + /* Check that both writeString() variants properly pass an explicitly + * specified file open attributes varargs argument. + */ + val ioPath = getCleanIoPath("utf8_forCreateNewOptionArg") + val dataOut = getDataOut() + + Files.createFile(ioPath) + + assertThrows( + classOf[FileAlreadyExistsException], + Files.writeString( + ioPath, + dataOut, + StandardCharsets.UTF_8, + StandardOpenOption.CREATE_NEW + ) + ) + + assertThrows( + classOf[FileAlreadyExistsException], + Files.writeString( + ioPath, + dataOut, + StandardCharsets.UTF_8, + StandardOpenOption.CREATE_NEW + ) + ) + } + + /* This Test is next to essential for both development & debugging. + * It is Ignore'd for normal Continuous Integration (CI) because, by + * its very purpose it creates and verifies a larger-than-a-breadbox + * file. In CI, this takes CPU & IO resources and has the possibility + * of leaving a large otherwise useless file lying around. + * + * The SN standard is to use "/* */" for multiline comments, as is done here. + * If someone if offended by the @Ignore, they could convert the contents + * below to "//" line comments then enclose the region in a "/* */" pair. + */ + @Ignore + @Test def largeWriteStringThenReadString(): Unit = { + /* Same logic as small readString() tests but with enough data to + * exercise any internal buffering. + */ + + val ioPath = getCleanIoPath("LargeFile") + + /* Use an unexpected string size to try to reveal defects in any + * underlying buffering. + * + * This test does not, and should not, know the sizes of any + * internal buffers used by writeString() and readString(). + * If any such exist, they are likely to have a power-of-two size + * and more likely to have an even size. Developers like even sizes. + * + * Add an odd, and preferably prime, increment to a "reasonable" string + * size to almost certainly force any last buffer to be partial. + */ + + /* For a String filled with all but two 1-byte UTF-8 and two 2-byte + * UTF-8 characters, expect an actual file size of + * (40960 + 2 + 41) = 41003 bytes. + */ + val maxStringSize = (40 * 1024) + 41 + + val startChar = '\u03B1' // Greek lowercase alpha; file bytes 0xCE 0xB1 + val endChar = '\u03A9' // Greek uppercase omega; file bytes 0xCE 0xA9 + + val dataOut = getLargeDataOut(maxStringSize, startChar, endChar) + + Files.writeString(ioPath, dataOut) + + val dataIn = Files.readString(ioPath) + + assertEquals("Unexpected dataIn size", maxStringSize, dataIn.size) + assertEquals( + "dataOut & dataIn sizes do not match", + dataOut.size, + dataIn.size + ) + + assertEquals("Unexpected first dataIn character", startChar, dataIn(0)) + assertEquals( + "Unexpected last dataIn character", + endChar, + dataIn(maxStringSize - 1) + ) + + assertEquals("data read back does not match data written", dataOut, dataIn) + } +} + +object FilesTestOnJDK11 { + private var orgPath: Path = _ + private var workPath: Path = _ + + final val testsuitePackagePrefix = "org.scalanative." + + val EOL = System.getProperty("line.separator") // end-of-line + val EOLlen = EOL.size + + private def getCleanIoPath(fileName: String): Path = { + val ioPath = workPath.resolve(fileName) + Files.deleteIfExists(ioPath) + ioPath + } + + def getDataOut(): String = { + /* Euro sign, Pound sign, dollarSign + * Ref: https://www.compart.com/en/unicode + */ + + "\u20AC\u00A3\u0024" + EOL // ensure file ends with OS end-of-line. + } + + def getLargeDataOut(maxSize: Int, startChar: Char, endChar: Char): String = { + val sb = new StringBuilder(maxSize) + sb.insert(0, startChar) + sb.setLength(maxSize - 1) // extend to size, filled with NUL characters + sb.append(endChar) // final size should be maxSize + // leave the string _without_ a terminal line.separator to trip things up. + sb.toString() + } + + def verifyUtf8EOL(bytes: Array[Byte]): Unit = { + if (EOLlen == 2) + assertEquals("Expected Windows CR", '\r', bytes(bytes.length - EOLlen)) + + assertEquals("Expected newline", '\n', bytes(bytes.length - 1)) + } + + def verifyUtf16LeEOL(bytes: Array[Byte]): Unit = { + if (EOLlen == 2) + assertEquals("Expected Windows CR", '\r', bytes(bytes.length - 4)) + + assertEquals("Expected newline", '\n', bytes(bytes.length - 2)) + } + + def verifyUtf16BeEOL(bytes: Array[Byte]): Unit = { + if (EOLlen == 2) + assertEquals("Expected Windows CR", '\r', bytes(bytes.length - 3)) + + assertEquals("Expected newline", '\n', bytes(bytes.length - 1)) + } + + def verifySmallUtf8Payload(ioPath: Path): Unit = { + // format: off + val expectedValues = Array( + 0xE2, 0x82, 0xAC, // EURO SIGN + 0xC2, 0xA3, // POUND (sterling) SIGN + 0x24, // DOLLAR SIGN + ).map (_.toByte) + // format: on + + val expectedDataLength = expectedValues.size + EOLlen + + val bytesRead = Files.readAllBytes(ioPath) + assertEquals("bytes read", expectedDataLength, bytesRead.length) + + for (j <- 0 until (expectedValues.length)) { + assertEquals( + s"write/read mismatch at index ${j}", + expectedValues(j), + bytesRead(j) + ) + } + + verifyUtf8EOL(bytesRead) + } + + @BeforeClass + def beforeClass(): Unit = { + /* Scala package statement does not allow "-", so the testsuite + * packages are all "scalanative", not the "scala-native" used + * in distribution artifacts or the name of the GitHub repository. + */ + orgPath = Files.createTempDirectory(s"${testsuitePackagePrefix}testsuite") + + val tmpPath = + orgPath.resolve(s"javalib/nio/file/${this.getClass().getSimpleName()}") + workPath = Files.createDirectories(tmpPath) + } + + @AfterClass + def afterClass(): Unit = { + // Delete items created by this test. + + // Avoid blind "rm -r /" and other oops! catastrophes. + if (!orgPath.toString().contains(s"${testsuitePackagePrefix}")) + fail(s"Refusing recursive delete of unknown path: ${orgPath}") + + // Avoid resize overhead; 64 is a high guess. deque will grow if needed. + val stack = new ju.ArrayDeque[Path](64) + val stream = Files.walk(orgPath) + + try { + // Delete Files; start with deepest & work upwards to beginning of walk. + stream.forEach(stack.addFirst(_)) // push() Path + stack.forEach(Files.delete(_)) // pop() a Path then delete it's File. + } finally { + stream.close() + } + } +} From af7096bd9e0f53b7d914501ec018d486e07df49b Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Tue, 14 Feb 2023 18:04:24 +0100 Subject: [PATCH 13/61] Fix #1642: posixlib stdio.scala is now mostly Open Group 2018 compliant (#3160) * Fix #1642: posixlib stdio.scala is now mostly Open Group 2018 compliant --- .../src/main/resources/scala-native/stdio.c | 18 ++++ .../scala/scala/scalanative/posix/stdio.scala | 93 +++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 posixlib/src/main/resources/scala-native/stdio.c create mode 100644 posixlib/src/main/scala/scala/scalanative/posix/stdio.scala diff --git a/posixlib/src/main/resources/scala-native/stdio.c b/posixlib/src/main/resources/scala-native/stdio.c new file mode 100644 index 0000000000..74395a98fc --- /dev/null +++ b/posixlib/src/main/resources/scala-native/stdio.c @@ -0,0 +1,18 @@ +#include + +#if !defined(L_ctermid) +#if defined(_WIN32) +// Windows MAX_PATH is 260, plus 1 for terminating NUL/NULL/"\u0000". +#define L_ctermid 260 + 1 +#else +#error "L_ctermid is not defined in stdio.h" +#endif +#endif + +// This file contains functions that wrap posixlib +// built-in macros. We need this because Scala Native +// can not expand C macros, and that's the easiest way to +// get the values out of those in a portable manner. + +// CX extension +int scalanative_l_ctermid() { return L_ctermid; } diff --git a/posixlib/src/main/scala/scala/scalanative/posix/stdio.scala b/posixlib/src/main/scala/scala/scalanative/posix/stdio.scala new file mode 100644 index 0000000000..cd22c3328c --- /dev/null +++ b/posixlib/src/main/scala/scala/scalanative/posix/stdio.scala @@ -0,0 +1,93 @@ +package scala.scalanative +package posix + +import scalanative.unsafe, unsafe._ +import scalanative.posix.sys.types, types.{off_t, size_t} +import scalanative.libc.stdio._ + +@extern object stdio { + /* Open Group 2018 extensions to ISO/IEC C. + * Reference: + * https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/stdio.h.html + * + * These definitions are annotated CX (ISO/IEC C extension) + * in the above specification. + */ + + type ssize_t = types.ssize_t + + type va_list = unsafe.CVarArgList + +// Macros + + /* Open Group POSIX defines this as a C macro. + * To provide the value in a portable manner, it is implemented here as + * an external method. A slight but necessary deviation from the + * specification. The same idiom is used in an number of other posixlib + * files. + */ + @name("scalanative_l_ctermid") + def L_ctermid: CUnsignedInt = extern + +// Methods + + def ctermid(s: CString): CString = extern + + def dprintf(fd: Int, format: CString, valist: va_list): Int = extern + + def fdopen(fd: Int, mode: CString): Ptr[FILE] = extern + def fileno(stream: Ptr[FILE]): Int = extern + def flockfile(filehandle: Ptr[FILE]): Unit = extern + + def fmemopen( + buf: Ptr[Byte], + size: size_t, + mode: CString + ): Ptr[FILE] = extern + + def fseeko(stream: Ptr[FILE], offset: off_t, whence: Int): Int = + extern + + def ftello(stream: Ptr[FILE]): off_t = extern + + // Can not block; see "try" part of "ftry*" + def ftrylockfile(filehandle: Ptr[FILE]): Int = extern + + def funlockfile(filehandle: Ptr[FILE]): Unit = extern + + def getc_unlocked(stream: Ptr[CString]): Int = extern + def getchar_unlocked(): Int = extern + + def getdelim( + lineptr: Ptr[CString], + n: Ptr[size_t], + delim: Int, + stream: Ptr[FILE] + ): ssize_t = extern + + def getline( + lineptr: Ptr[CString], + n: Ptr[size_t], + stream: Ptr[FILE] + ): ssize_t = extern + + def open_memstream( + ptr: Ptr[CString], + sizeloc: Ptr[size_t] + ): Ptr[FILE] = + extern + + def pclose(stream: Ptr[FILE]): Int = extern + def popen(command: CString, typ: CString): Ptr[FILE] = extern + def putc_unlocked(c: Int, stream: Ptr[FILE]): Int = extern + def putchar_unlocked(c: Int): Int = extern + + def renameat( + olddirfd: Int, + oldpath: CString, + newdirdf: Int, + newpath: CString + ): Int = extern + + def vdprintf(fd: Int, format: CString, ap: va_list): Int = extern +} From 20403af61a56e645c8a6e6d6240764d249e97d55 Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Thu, 16 Feb 2023 04:42:59 -0500 Subject: [PATCH 14/61] Fix #2937, 3163: improved j.nio.f.Files default directory idiom handling (#3166) * Fix #2937, 3165: improved j.nio.f.Files current default directory idiom handling * Touchup: make two vals final * Add Test to show that glob: matcher was innocent * Add clue to PathMatcherGlobTest for future maintainers --- .../src/main/scala/java/nio/file/Files.scala | 30 +++++++-- .../nio/file/DirectoryStreamTest.scala | 64 +++++++++++++++++++ .../nio/file/PathMatcherGlobTest.scala | 13 +++- 3 files changed, 102 insertions(+), 5 deletions(-) diff --git a/javalib/src/main/scala/java/nio/file/Files.scala b/javalib/src/main/scala/java/nio/file/Files.scala index 5274bf13b0..0cd8101c4b 100644 --- a/javalib/src/main/scala/java/nio/file/Files.scala +++ b/javalib/src/main/scala/java/nio/file/Files.scala @@ -59,7 +59,9 @@ import java.io.FileNotFoundException object Files { - private val `1U` = 1.toUInt + private final val `1U` = 1.toUInt + + private final val emptyPath = Paths.get("", Array.empty) // def getFileStore(path: Path): FileStore // def probeContentType(path: Path): String @@ -532,11 +534,25 @@ object Files { def lines(path: Path, cs: Charset): Stream[String] = newBufferedReader(path, cs).lines(true) - def list(dir: Path): Stream[Path] = + def list(dir: Path): Stream[Path] = { + /* Fix Issue 3165 - From Java "Path" documentation URL: + * https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html + * + * "Accessing a file using an empty path is equivalent to accessing the + * default directory of the file system." + * + * Operating Systems can not opendir() an empty string, so expand "" to + * "./". + */ + val dirString = + if (dir.equals(emptyPath)) "./" + else dir.toString() + new WrappedScalaStream( - FileHelpers.list(dir.toString, (n, _) => dir.resolve(n)).toScalaStream, + FileHelpers.list(dirString, (n, _) => dir.resolve(n)).toScalaStream, None ) + } def move(source: Path, target: Path, options: Array[CopyOption]): Path = { lazy val replaceExisting = options.contains(REPLACE_EXISTING) @@ -651,7 +667,13 @@ object Files { val filter = new DirectoryStream.Filter[Path] { private val matcher = FileSystems.getDefault().getPathMatcher("glob:" + glob) - override def accept(p: Path): Boolean = matcher.matches(p) + + /* Fix Issue 2937 - Java considers "" & "./" to be the same: current + * default directory. To ease comparison here and follow JDK practice, + * change "./" to "" on candidate path. See related "" to "./ " + * comment in "def list()" above. + */ + override def accept(p: Path): Boolean = matcher.matches(p.normalize()) } newDirectoryStream(dir, filter) } diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/DirectoryStreamTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/DirectoryStreamTest.scala index 6f2df74623..d91a4bc19e 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/DirectoryStreamTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/DirectoryStreamTest.scala @@ -91,4 +91,68 @@ class DirectoryStreamTest { assertFalse(it.hasNext()) assertThrows(classOf[NoSuchElementException], it.next()) } + + /* Issue #2937 + * + * Note Well - This Test is fragile/sensitiveToChange/lessThanRobust. + * + * This Test is fragile in the sense that it assumes/requires that + * the current working directory contains at least one .sbt file. + * Such is true at the time this test is created. The current working + * directory when TestMain is started is the project directory. That + * directory contains a .sbt file because that file was used to start + * the execution of TestMain; Worm Ouroboros. + * + * An alternative approach of saving and restoring the current working + * directory was considered but judged to be more fragile. + */ + @Test def normalizesAcceptCandidatePathExpectMatch(): Unit = { + + val passGlob = "*.sbt" // passes in JVM + + // Path of current working directory, from empty string. + val emptyPathStream = Files.newDirectoryStream(Paths.get(""), passGlob) + val emptyPathPassed = emptyPathStream.iterator().hasNext() // count >= 1 + emptyPathStream.close() + + assertTrue( + s"current working directory stream has no match for '${passGlob}'", + emptyPathPassed + ) + + // Path of current working directory, from dot string. + val dotPathStream = Files.newDirectoryStream(Paths.get("."), passGlob) + val dotPathPassed = dotPathStream.iterator().hasNext() // count >= 1 + dotPathStream.close() + + assertTrue( + s"dot directory stream has no match for '${passGlob}'", + dotPathPassed + ) + } + + @Test def normalizesAcceptCandidatePathExpectNoMatch(): Unit = { + + val failGlob = "./*.sbt" // fails in JVM and should fail here + + // Path of current working directory, from empty string. + val emptyPathStream = Files.newDirectoryStream(Paths.get(""), failGlob) + val emptyPathPassed = emptyPathStream.iterator().hasNext() // count >= 1 + emptyPathStream.close() + + assertFalse( + s"current working directory stream has a match for '${failGlob}'", + emptyPathPassed + ) + + // Path of current working directory, from dot string. + val dotPathStream = Files.newDirectoryStream(Paths.get("."), failGlob) + val dotPathPassed = dotPathStream.iterator().hasNext() // count >= 1 + dotPathStream.close() + + assertFalse( + s"dot directory stream has a match for '${failGlob}'", + dotPathPassed + ) + } } diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/PathMatcherGlobTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/PathMatcherGlobTest.scala index 633982d57d..0e791cb4e2 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/PathMatcherGlobTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/nio/file/PathMatcherGlobTest.scala @@ -1,4 +1,5 @@ -package org.scalanative.testsuite.javalib.nio.file +package org.scalanative.testsuite +package javalib.nio.file import java.nio.file._ @@ -200,4 +201,14 @@ class PathMatcherGlobTest { pass("*", "") } + /* Issue #2937 + * Glob itself should not match glob "*.sbt" with "./local.sbt". + * Files.getNewDirectoryStream() must normalize candidate path before + * handing it off to glob. + */ + @Test def correctMatchingOfInitialDotSlash(): Unit = { + pass("*.sbt", "local.sbt") // establish baseline + pass("./*.sbt", "./local.sbt") + fail("*.sbt", "./local.sbt") // glob "*" will not cross "/", so no match + } } From e42241ea64f05aed9ee8f210afa6540ea6fbf0f3 Mon Sep 17 00:00:00 2001 From: Ondra Pelech Date: Thu, 23 Feb 2023 11:14:43 +0100 Subject: [PATCH 15/61] Add libstdc++-12-dev to Ubuntu setup instructions (#3179) --- docs/user/setup.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/setup.rst b/docs/user/setup.rst index be8a42a182..7e7ea3eac7 100644 --- a/docs/user/setup.rst +++ b/docs/user/setup.rst @@ -72,7 +72,7 @@ installation of macOS. .. code-block:: shell - $ sudo apt install clang + $ sudo apt install clang libstdc++-12-dev $ sudo apt install libgc-dev # optional **Arch Linux** From d62a1bbb12f6bf93e7aae61ea99e8368050ef50f Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 23 Feb 2023 14:53:33 +0100 Subject: [PATCH 16/61] [CI] Fix failing build due to missing Boehm GC (#3181) * Install BoehmGC in macos-setup-env when required --- .github/actions/macos-setup-env/action.yml | 7 +++++++ .github/workflows/run-tests-macos.yml | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/actions/macos-setup-env/action.yml b/.github/actions/macos-setup-env/action.yml index 6ca2bc0688..262546c2e7 100644 --- a/.github/actions/macos-setup-env/action.yml +++ b/.github/actions/macos-setup-env/action.yml @@ -7,6 +7,8 @@ inputs: java-version: description: "Java version to use in tests" default: "8" + gc: + description: "Garbage collector used, might require installing dependencies" runs: using: "composite" steps: @@ -26,6 +28,11 @@ runs: echo "binary-version=3" >> $GITHUB_ENV echo "project-version=3" >> $GITHUB_ENV fi + + - name: Install dependencies + shell: bash + if: ${{ startsWith(inputs.gc, 'boehm') }} + run: brew install bdw-gc # Loads cache with dependencies created in test-tools job - name: Cache dependencies diff --git a/.github/workflows/run-tests-macos.yml b/.github/workflows/run-tests-macos.yml index 35db8d626b..5b2c4993c7 100644 --- a/.github/workflows/run-tests-macos.yml +++ b/.github/workflows/run-tests-macos.yml @@ -7,11 +7,11 @@ on: - main - 0.4.x concurrency: - group: macOS-${{ github.head_ref }} + group: macOS-${{ github.head_ref }}-${{ github.event_name }} cancel-in-progress: true jobs: - run-tests: + test-runtime: name: Test runtime runs-on: macos-11 strategy: @@ -35,6 +35,7 @@ jobs: id: setup with: scala-version: ${{matrix.scala}} + gc: ${{ matrix.gc }} - name: Test runtime run: > From 9ca3ebb5645fd73eca420df6add9944e897ee8d1 Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Thu, 23 Feb 2023 11:31:36 -0500 Subject: [PATCH 17/61] sandbox project no longer uses private nativeConfig values (#3171) --- project/Build.scala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 5fd2dcf9a7..1a01460b51 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -441,11 +441,6 @@ object Build { lazy val sandbox = MultiScalaProject("sandbox", file("sandbox")) .enablePlugins(MyScalaNativePlugin) - .settings(nativeConfig ~= { c => - c.withLTO(LTO.default) - .withMode(Mode.default) - .withGC(GC.default) - }) .withNativeCompilerPlugin .withJUnitPlugin .dependsOn(scalalib, testInterface % "test") From 2c4e05d10be8df3bbf6a7b1ca45f96b2ad751a43 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Fri, 24 Feb 2023 10:20:14 +0100 Subject: [PATCH 18/61] Fix generation of CFuncPtr extern forwarders for functions using opaque types (#3182) --- .../scala/scalanative/nscplugin/NirGenUtil.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenUtil.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenUtil.scala index fe66e1e6c7..c1cca2f3d9 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenUtil.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenUtil.scala @@ -120,12 +120,16 @@ trait NirGenUtil(using Context) { self: NirCodeGen => } def resolveGiven = Option.when(s.is(Given)) { - atPhase(Phases.postTyperPhase) { + val evidenceType = atPhase(Phases.postTyperPhase) { val givenTpe = s.denot.info.argInfos.head - // make sure to dealias the type here, - // information about underlying opaque type would not be available after erasue - fromType(givenTpe.widenDealias) + // In the backend (genBCode on JVM or this compiler plugin) opaque type should be dealiased + // to the underlying type otherwise non-existing types might be missing when classloading. + // Information about underlying opaque type would not be available after erasue + givenTpe.typeSymbol.opaqueAlias + .orElse(givenTpe) + .widenDealias } + fromType(evidenceType) } materializePrimitiveTypeMethodTypes From d73473703017d275fa48d51f4a848f6d57070e56 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Sat, 25 Feb 2023 19:43:06 +0100 Subject: [PATCH 19/61] Update LLVM libunwind to 15.0.7 (was 12.0.1) (#3184) * Update LLVM libunwind to 15.0.7 (was 12.0.1) --- .../platform/posix/libunwind/AddressSpace.hpp | 49 +- .../posix/libunwind/CompactUnwinder.hpp | 76 +- .../posix/libunwind/DwarfInstructions.hpp | 87 +- .../platform/posix/libunwind/DwarfParser.hpp | 61 +- .../posix/libunwind/EHHeaderParser.hpp | 5 +- .../platform/posix/libunwind/RWMutex.hpp | 2 +- .../platform/posix/libunwind/Registers.hpp | 837 ++++++++++++++--- .../platform/posix/libunwind/Unwind-EHABI.cpp | 225 ++++- .../platform/posix/libunwind/Unwind-EHABI.h | 2 +- .../platform/posix/libunwind/Unwind-seh.cpp | 14 +- .../platform/posix/libunwind/Unwind-sjlj.c | 2 +- .../platform/posix/libunwind/UnwindCursor.hpp | 869 ++++++++++++++++-- .../posix/libunwind/UnwindLevel1-gcc-ext.c | 38 +- .../platform/posix/libunwind/UnwindLevel1.c | 62 +- .../posix/libunwind/UnwindRegistersRestore.S | 574 +++++++----- .../posix/libunwind/UnwindRegistersSave.S | 543 ++++++----- .../posix/libunwind/Unwind_AIXExtras.cpp | 67 ++ .../posix/libunwind/Unwind_AppleExtras.cpp | 2 +- .../posix/libunwind/__libunwind_config.h | 40 +- .../platform/posix/libunwind/assembly.h | 127 ++- .../platform/posix/libunwind/cet_unwind.h | 45 + .../platform/posix/libunwind/config.h | 39 +- .../platform/posix/libunwind/dwarf2.h | 2 +- .../platform/posix/libunwind/libunwind.cpp | 64 +- .../platform/posix/libunwind/libunwind.h | 264 ++++-- .../platform/posix/libunwind/libunwind_ext.h | 9 +- .../mach-o/compact_unwind_encoding.h | 2 +- .../platform/posix/libunwind/rev.txt | 2 +- .../platform/posix/libunwind/unwind.h | 200 +--- .../posix/libunwind/unwind_arm_ehabi.h | 174 ++++ .../platform/posix/libunwind/unwind_itanium.h | 80 ++ 31 files changed, 3512 insertions(+), 1051 deletions(-) create mode 100644 nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AIXExtras.cpp create mode 100644 nativelib/src/main/resources/scala-native/platform/posix/libunwind/cet_unwind.h create mode 100644 nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_arm_ehabi.h create mode 100644 nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_itanium.h diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/AddressSpace.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/AddressSpace.hpp index ecea81e9bc..841434074f 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/AddressSpace.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/AddressSpace.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------- AddressSpace.hpp ---------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -27,7 +27,7 @@ #include "Registers.hpp" #ifndef _LIBUNWIND_USE_DLADDR - #if !defined(_LIBUNWIND_IS_BAREMETAL) && !defined(_WIN32) + #if !(defined(_LIBUNWIND_IS_BAREMETAL) || defined(_WIN32) || defined(_AIX)) #define _LIBUNWIND_USE_DLADDR 1 #else #define _LIBUNWIND_USE_DLADDR 0 @@ -48,6 +48,13 @@ struct EHABIIndexEntry { }; #endif +#if defined(_AIX) +namespace libunwind { +char *getFuncNameFromTBTable(uintptr_t pc, uint16_t &NameLen, + unw_word_t *offset); +} +#endif + #ifdef __APPLE__ struct dyld_unwind_sections @@ -124,23 +131,23 @@ struct UnwindInfoSections { uintptr_t dso_base; #endif #if defined(_LIBUNWIND_USE_DL_ITERATE_PHDR) - uintptr_t text_segment_length; + size_t text_segment_length; #endif #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) uintptr_t dwarf_section; - uintptr_t dwarf_section_length; + size_t dwarf_section_length; #endif #if defined(_LIBUNWIND_SUPPORT_DWARF_INDEX) uintptr_t dwarf_index_section; - uintptr_t dwarf_index_section_length; + size_t dwarf_index_section_length; #endif #if defined(_LIBUNWIND_SUPPORT_COMPACT_UNWIND) uintptr_t compact_unwind_section; - uintptr_t compact_unwind_section_length; + size_t compact_unwind_section_length; #endif #if defined(_LIBUNWIND_ARM_EHABI) uintptr_t arm_section; - uintptr_t arm_section_length; + size_t arm_section_length; #endif }; @@ -433,7 +440,7 @@ static bool checkForUnwindInfoSegment(const Elf_Phdr *phdr, size_t image_base, // .eh_frame_hdr records the start of .eh_frame, but not its size. // Rely on a zero terminator to find the end of the section. cbdata->sects->dwarf_section = hdrInfo.eh_frame_ptr; - cbdata->sects->dwarf_section_length = UINTPTR_MAX; + cbdata->sects->dwarf_section_length = SIZE_MAX; return true; } } @@ -509,22 +516,22 @@ inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr, info.dso_base = (uintptr_t)dyldInfo.mh; #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) info.dwarf_section = (uintptr_t)dyldInfo.dwarf_section; - info.dwarf_section_length = dyldInfo.dwarf_section_length; + info.dwarf_section_length = (size_t)dyldInfo.dwarf_section_length; #endif info.compact_unwind_section = (uintptr_t)dyldInfo.compact_unwind_section; - info.compact_unwind_section_length = dyldInfo.compact_unwind_section_length; + info.compact_unwind_section_length = (size_t)dyldInfo.compact_unwind_section_length; return true; } #elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) && defined(_LIBUNWIND_IS_BAREMETAL) info.dso_base = 0; // Bare metal is statically linked, so no need to ask the dynamic loader - info.dwarf_section_length = (uintptr_t)(&__eh_frame_end - &__eh_frame_start); + info.dwarf_section_length = (size_t)(&__eh_frame_end - &__eh_frame_start); info.dwarf_section = (uintptr_t)(&__eh_frame_start); _LIBUNWIND_TRACE_UNWINDING("findUnwindSections: section %p length %p", (void *)info.dwarf_section, (void *)info.dwarf_section_length); #if defined(_LIBUNWIND_SUPPORT_DWARF_INDEX) info.dwarf_index_section = (uintptr_t)(&__eh_frame_hdr_start); - info.dwarf_index_section_length = (uintptr_t)(&__eh_frame_hdr_end - &__eh_frame_hdr_start); + info.dwarf_index_section_length = (size_t)(&__eh_frame_hdr_end - &__eh_frame_hdr_start); _LIBUNWIND_TRACE_UNWINDING("findUnwindSections: index section %p length %p", (void *)info.dwarf_index_section, (void *)info.dwarf_index_section_length); #endif @@ -533,7 +540,7 @@ inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr, #elif defined(_LIBUNWIND_ARM_EHABI) && defined(_LIBUNWIND_IS_BAREMETAL) // Bare metal is statically linked, so no need to ask the dynamic loader info.arm_section = (uintptr_t)(&__exidx_start); - info.arm_section_length = (uintptr_t)(&__exidx_end - &__exidx_start); + info.arm_section_length = (size_t)(&__exidx_end - &__exidx_start); _LIBUNWIND_TRACE_UNWINDING("findUnwindSections: section %p length %p", (void *)info.arm_section, (void *)info.arm_section_length); if (info.arm_section && info.arm_section_length) @@ -547,6 +554,7 @@ inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr, DWORD err = GetLastError(); _LIBUNWIND_TRACE_UNWINDING("findUnwindSections: EnumProcessModules failed, " "returned error %d", (int)err); + (void)err; return false; } @@ -583,11 +591,16 @@ inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr, (void)targetAddr; (void)info; return true; +#elif defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) + // The traceback table is used for unwinding. + (void)targetAddr; + (void)info; + return true; #elif defined(_LIBUNWIND_USE_DL_UNWIND_FIND_EXIDX) int length = 0; info.arm_section = (uintptr_t)dl_unwind_find_exidx((_Unwind_Ptr)targetAddr, &length); - info.arm_section_length = (uintptr_t)length * sizeof(EHABIIndexEntry); + info.arm_section_length = (size_t)length * sizeof(EHABIIndexEntry); if (info.arm_section && info.arm_section_length) return true; #elif defined(_LIBUNWIND_USE_DL_ITERATE_PHDR) @@ -599,7 +612,6 @@ inline bool LocalAddressSpace::findUnwindSections(pint_t targetAddr, return false; } - inline bool LocalAddressSpace::findOtherFDE(pint_t targetAddr, pint_t &fde) { // TO DO: if OS has way to dynamically register FDEs, check that. (void)targetAddr; @@ -619,6 +631,13 @@ inline bool LocalAddressSpace::findFunctionName(pint_t addr, char *buf, return true; } } +#elif defined(_AIX) + uint16_t nameLen; + char *funcName = getFuncNameFromTBTable(addr, nameLen, offset); + if (funcName != NULL) { + snprintf(buf, bufLen, "%.*s", nameLen, funcName); + return true; + } #else (void)addr; (void)buf; diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/CompactUnwinder.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/CompactUnwinder.hpp index b882bd0eef..fec1004b81 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/CompactUnwinder.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/CompactUnwinder.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===-------------------------- CompactUnwinder.hpp -----------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -540,65 +540,65 @@ int CompactUnwinder_arm64::stepWithCompactEncodingFrameless( uint64_t savedRegisterLoc = registers.getSP() + stackSize; if (encoding & UNWIND_ARM64_FRAME_X19_X20_PAIR) { - registers.setRegister(UNW_ARM64_X19, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X19, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X20, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X20, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X21_X22_PAIR) { - registers.setRegister(UNW_ARM64_X21, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X21, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X22, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X22, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X23_X24_PAIR) { - registers.setRegister(UNW_ARM64_X23, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X23, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X24, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X24, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X25_X26_PAIR) { - registers.setRegister(UNW_ARM64_X25, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X25, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X26, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X26, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X27_X28_PAIR) { - registers.setRegister(UNW_ARM64_X27, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X27, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X28, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X28, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D8_D9_PAIR) { - registers.setFloatRegister(UNW_ARM64_D8, + registers.setFloatRegister(UNW_AARCH64_V8, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D9, + registers.setFloatRegister(UNW_AARCH64_V9, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D10_D11_PAIR) { - registers.setFloatRegister(UNW_ARM64_D10, + registers.setFloatRegister(UNW_AARCH64_V10, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D11, + registers.setFloatRegister(UNW_AARCH64_V11, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D12_D13_PAIR) { - registers.setFloatRegister(UNW_ARM64_D12, + registers.setFloatRegister(UNW_AARCH64_V12, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D13, + registers.setFloatRegister(UNW_AARCH64_V13, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D14_D15_PAIR) { - registers.setFloatRegister(UNW_ARM64_D14, + registers.setFloatRegister(UNW_AARCH64_V14, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D15, + registers.setFloatRegister(UNW_AARCH64_V15, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } @@ -607,7 +607,7 @@ int CompactUnwinder_arm64::stepWithCompactEncodingFrameless( registers.setSP(savedRegisterLoc); // set pc to be value in lr - registers.setIP(registers.getRegister(UNW_ARM64_LR)); + registers.setIP(registers.getRegister(UNW_AARCH64_LR)); return UNW_STEP_SUCCESS; } @@ -619,65 +619,65 @@ int CompactUnwinder_arm64::stepWithCompactEncodingFrame( uint64_t savedRegisterLoc = registers.getFP() - 8; if (encoding & UNWIND_ARM64_FRAME_X19_X20_PAIR) { - registers.setRegister(UNW_ARM64_X19, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X19, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X20, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X20, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X21_X22_PAIR) { - registers.setRegister(UNW_ARM64_X21, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X21, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X22, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X22, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X23_X24_PAIR) { - registers.setRegister(UNW_ARM64_X23, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X23, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X24, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X24, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X25_X26_PAIR) { - registers.setRegister(UNW_ARM64_X25, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X25, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X26, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X26, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_X27_X28_PAIR) { - registers.setRegister(UNW_ARM64_X27, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X27, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setRegister(UNW_ARM64_X28, addressSpace.get64(savedRegisterLoc)); + registers.setRegister(UNW_AARCH64_X28, addressSpace.get64(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D8_D9_PAIR) { - registers.setFloatRegister(UNW_ARM64_D8, + registers.setFloatRegister(UNW_AARCH64_V8, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D9, + registers.setFloatRegister(UNW_AARCH64_V9, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D10_D11_PAIR) { - registers.setFloatRegister(UNW_ARM64_D10, + registers.setFloatRegister(UNW_AARCH64_V10, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D11, + registers.setFloatRegister(UNW_AARCH64_V11, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D12_D13_PAIR) { - registers.setFloatRegister(UNW_ARM64_D12, + registers.setFloatRegister(UNW_AARCH64_V12, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D13, + registers.setFloatRegister(UNW_AARCH64_V13, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } if (encoding & UNWIND_ARM64_FRAME_D14_D15_PAIR) { - registers.setFloatRegister(UNW_ARM64_D14, + registers.setFloatRegister(UNW_AARCH64_V14, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; - registers.setFloatRegister(UNW_ARM64_D15, + registers.setFloatRegister(UNW_AARCH64_V15, addressSpace.getDouble(savedRegisterLoc)); savedRegisterLoc -= 8; } diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfInstructions.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfInstructions.hpp index 9c9c9d1d99..20b6e9bb14 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfInstructions.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfInstructions.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===-------------------------- DwarfInstructions.hpp ---------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -75,8 +75,19 @@ class DwarfInstructions { assert(0 && "getCFA(): unknown location"); __builtin_unreachable(); } +#if defined(_LIBUNWIND_TARGET_AARCH64) + static bool getRA_SIGN_STATE(A &addressSpace, R registers, pint_t cfa, + PrologInfo &prolog); +#endif }; +template +auto getSparcWCookie(const R &r, int) -> decltype(r.getWCookie()) { + return r.getWCookie(); +} +template uint64_t getSparcWCookie(const R &, long) { + return 0; +} template typename A::pint_t DwarfInstructions::getSavedRegister( @@ -86,6 +97,10 @@ typename A::pint_t DwarfInstructions::getSavedRegister( case CFI_Parser::kRegisterInCFA: return (pint_t)addressSpace.getRegister(cfa + (pint_t)savedReg.value); + case CFI_Parser::kRegisterInCFADecrypt: // sparc64 specific + return (pint_t)(addressSpace.getP(cfa + (pint_t)savedReg.value) ^ + getSparcWCookie(registers, 0)); + case CFI_Parser::kRegisterAtExpression: return (pint_t)addressSpace.getRegister(evaluateExpression( (pint_t)savedReg.value, addressSpace, registers, cfa)); @@ -118,12 +133,16 @@ double DwarfInstructions::getSavedFloatRegister( return addressSpace.getDouble( evaluateExpression((pint_t)savedReg.value, addressSpace, registers, cfa)); - + case CFI_Parser::kRegisterUndefined: + return 0.0; + case CFI_Parser::kRegisterInRegister: +#ifndef _LIBUNWIND_TARGET_ARM + return registers.getFloatRegister((int)savedReg.value); +#endif case CFI_Parser::kRegisterIsExpression: case CFI_Parser::kRegisterUnused: - case CFI_Parser::kRegisterUndefined: case CFI_Parser::kRegisterOffsetFromCFA: - case CFI_Parser::kRegisterInRegister: + case CFI_Parser::kRegisterInCFADecrypt: // FIX ME break; } @@ -148,11 +167,27 @@ v128 DwarfInstructions::getSavedVectorRegister( case CFI_Parser::kRegisterUndefined: case CFI_Parser::kRegisterOffsetFromCFA: case CFI_Parser::kRegisterInRegister: + case CFI_Parser::kRegisterInCFADecrypt: // FIX ME break; } _LIBUNWIND_ABORT("unsupported restore location for vector register"); } +#if defined(_LIBUNWIND_TARGET_AARCH64) +template +bool DwarfInstructions::getRA_SIGN_STATE(A &addressSpace, R registers, + pint_t cfa, PrologInfo &prolog) { + pint_t raSignState; + auto regloc = prolog.savedRegisters[UNW_AARCH64_RA_SIGN_STATE]; + if (regloc.location == CFI_Parser::kRegisterUnused) + raSignState = static_cast(regloc.value); + else + raSignState = getSavedRegister(addressSpace, registers, cfa, regloc); + + // Only bit[0] is meaningful. + return raSignState & 0x01; +} +#endif template int DwarfInstructions::stepWithDwarf(A &addressSpace, pint_t pc, @@ -170,10 +205,21 @@ int DwarfInstructions::stepWithDwarf(A &addressSpace, pint_t pc, // restore registers that DWARF says were saved R newRegisters = registers; + + // Typically, the CFA is the stack pointer at the call site in + // the previous frame. However, there are scenarios in which this is not + // true. For example, if we switched to a new stack. In that case, the + // value of the previous SP might be indicated by a CFI directive. + // + // We set the SP here to the CFA, allowing for it to be overridden + // by a CFI directive later on. + newRegisters.setSP(cfa); + pint_t returnAddress = 0; - const int lastReg = R::lastDwarfRegNum(); - assert(static_cast(CFI_Parser::kMaxRegisterNumber) >= lastReg && - "register range too large"); + constexpr int lastReg = R::lastDwarfRegNum(); + static_assert(static_cast(CFI_Parser::kMaxRegisterNumber) >= + lastReg, + "register range too large"); assert(lastReg >= (int)cieInfo.returnAddressRegister && "register range does not contain return address register"); for (int i = 0; i <= lastReg; ++i) { @@ -203,10 +249,6 @@ int DwarfInstructions::stepWithDwarf(A &addressSpace, pint_t pc, } } - // By definition, the CFA is the stack pointer at the call site, so - // restoring SP means setting it to CFA. - newRegisters.setSP(cfa); - isSignalFrame = cieInfo.isSignalFrame; #if defined(_LIBUNWIND_TARGET_AARCH64) @@ -216,7 +258,8 @@ int DwarfInstructions::stepWithDwarf(A &addressSpace, pint_t pc, // restored. autia1716 is used instead of autia as autia1716 assembles // to a NOP on pre-v8.3a architectures. if ((R::getArch() == REGISTERS_ARM64) && - prolog.savedRegisters[UNW_ARM64_RA_SIGN_STATE].value) { + getRA_SIGN_STATE(addressSpace, registers, cfa, prolog) && + returnAddress != 0) { #if !defined(_LIBUNWIND_IS_NATIVE_ONLY) return UNW_ECROSSRASIGNING; #else @@ -235,6 +278,20 @@ int DwarfInstructions::stepWithDwarf(A &addressSpace, pint_t pc, } #endif +#if defined(_LIBUNWIND_IS_NATIVE_ONLY) && defined(_LIBUNWIND_TARGET_ARM) && \ + defined(__ARM_FEATURE_PAUTH) + if ((R::getArch() == REGISTERS_ARM) && + prolog.savedRegisters[UNW_ARM_RA_AUTH_CODE].value) { + pint_t pac = + getSavedRegister(addressSpace, registers, cfa, + prolog.savedRegisters[UNW_ARM_RA_AUTH_CODE]); + __asm__ __volatile__("autg %0, %1, %2" + : + : "r"(pac), "r"(returnAddress), "r"(cfa) + :); + } +#endif + #if defined(_LIBUNWIND_TARGET_SPARC) if (R::getArch() == REGISTERS_SPARC) { // Skip call site instruction and delay slot @@ -245,6 +302,12 @@ int DwarfInstructions::stepWithDwarf(A &addressSpace, pint_t pc, } #endif +#if defined(_LIBUNWIND_TARGET_SPARC64) + // Skip call site instruction and delay slot. + if (R::getArch() == REGISTERS_SPARC64) + returnAddress += 8; +#endif + #if defined(_LIBUNWIND_TARGET_PPC64) #define PPC64_ELFV1_R2_LOAD_INST_ENCODING 0xe8410028u // ld r2,40(r1) #define PPC64_ELFV1_R2_OFFSET 40 diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfParser.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfParser.hpp index a69e00aa37..439172197a 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfParser.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/DwarfParser.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------------- DwarfParser.hpp --------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -74,6 +74,7 @@ class CFI_Parser { kRegisterUnused, kRegisterUndefined, kRegisterInCFA, + kRegisterInCFADecrypt, // sparc64 specific kRegisterOffsetFromCFA, kRegisterInRegister, kRegisterAtExpression, @@ -154,10 +155,11 @@ class CFI_Parser { }; static bool findFDE(A &addressSpace, pint_t pc, pint_t ehSectionStart, - uintptr_t sectionLength, pint_t fdeHint, FDE_Info *fdeInfo, + size_t sectionLength, pint_t fdeHint, FDE_Info *fdeInfo, CIE_Info *cieInfo); static const char *decodeFDE(A &addressSpace, pint_t fdeStart, - FDE_Info *fdeInfo, CIE_Info *cieInfo); + FDE_Info *fdeInfo, CIE_Info *cieInfo, + bool useCIEInfo = false); static bool parseFDEInstructions(A &addressSpace, const FDE_Info &fdeInfo, const CIE_Info &cieInfo, pint_t upToPC, int arch, PrologInfo *results); @@ -165,10 +167,14 @@ class CFI_Parser { static const char *parseCIE(A &addressSpace, pint_t cie, CIE_Info *cieInfo); }; -/// Parse a FDE into a CIE_Info and an FDE_Info +/// Parse a FDE into a CIE_Info and an FDE_Info. If useCIEInfo is +/// true, treat cieInfo as already-parsed CIE_Info (whose start offset +/// must match the one specified by the FDE) rather than parsing the +/// one indicated within the FDE. template const char *CFI_Parser::decodeFDE(A &addressSpace, pint_t fdeStart, - FDE_Info *fdeInfo, CIE_Info *cieInfo) { + FDE_Info *fdeInfo, CIE_Info *cieInfo, + bool useCIEInfo) { pint_t p = fdeStart; pint_t cfiLength = (pint_t)addressSpace.get32(p); p += 4; @@ -184,9 +190,14 @@ const char *CFI_Parser::decodeFDE(A &addressSpace, pint_t fdeStart, return "FDE is really a CIE"; // this is a CIE not an FDE pint_t nextCFI = p + cfiLength; pint_t cieStart = p - ciePointer; - const char *err = parseCIE(addressSpace, cieStart, cieInfo); - if (err != NULL) - return err; + if (useCIEInfo) { + if (cieInfo->cieStart != cieStart) + return "CIE start does not match"; + } else { + const char *err = parseCIE(addressSpace, cieStart, cieInfo); + if (err != NULL) + return err; + } p += 4; // Parse pc begin and range. pint_t pcStart = @@ -223,11 +234,11 @@ const char *CFI_Parser::decodeFDE(A &addressSpace, pint_t fdeStart, /// Scan an eh_frame section to find an FDE for a pc template bool CFI_Parser::findFDE(A &addressSpace, pint_t pc, pint_t ehSectionStart, - uintptr_t sectionLength, pint_t fdeHint, + size_t sectionLength, pint_t fdeHint, FDE_Info *fdeInfo, CIE_Info *cieInfo) { //fprintf(stderr, "findFDE(0x%llX)\n", (long long)pc); pint_t p = (fdeHint != 0) ? fdeHint : ehSectionStart; - const pint_t ehSectionEnd = (sectionLength == UINTPTR_MAX) + const pint_t ehSectionEnd = (sectionLength == SIZE_MAX) ? static_cast(-1) : (ehSectionStart + sectionLength); while (p < ehSectionEnd) { @@ -726,7 +737,8 @@ bool CFI_Parser::parseFDEInstructions(A &addressSpace, "DW_CFA_GNU_negative_offset_extended(%" PRId64 ")\n", offset); break; -#if defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_SPARC) +#if defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_SPARC) || \ + defined(_LIBUNWIND_TARGET_SPARC64) // The same constant is used to represent different instructions on // AArch64 (negate_ra_state) and SPARC (window_save). static_assert(DW_CFA_AARCH64_negate_ra_state == DW_CFA_GNU_window_save, @@ -736,8 +748,8 @@ bool CFI_Parser::parseFDEInstructions(A &addressSpace, #if defined(_LIBUNWIND_TARGET_AARCH64) case REGISTERS_ARM64: { int64_t value = - results->savedRegisters[UNW_ARM64_RA_SIGN_STATE].value ^ 0x1; - results->setRegisterValue(UNW_ARM64_RA_SIGN_STATE, value, + results->savedRegisters[UNW_AARCH64_RA_SIGN_STATE].value ^ 0x1; + results->setRegisterValue(UNW_AARCH64_RA_SIGN_STATE, value, initialState); _LIBUNWIND_TRACE_DWARF("DW_CFA_AARCH64_negate_ra_state\n"); } break; @@ -760,8 +772,31 @@ bool CFI_Parser::parseFDEInstructions(A &addressSpace, } break; #endif + +#if defined(_LIBUNWIND_TARGET_SPARC64) + // case DW_CFA_GNU_window_save: + case REGISTERS_SPARC64: + // Don't save %o0-%o7 on sparc64. + // https://reviews.llvm.org/D32450#736405 + + for (reg = UNW_SPARC_L0; reg <= UNW_SPARC_I7; reg++) { + if (reg == UNW_SPARC_I7) + results->setRegister( + reg, kRegisterInCFADecrypt, + static_cast((reg - UNW_SPARC_L0) * sizeof(pint_t)), + initialState); + else + results->setRegister( + reg, kRegisterInCFA, + static_cast((reg - UNW_SPARC_L0) * sizeof(pint_t)), + initialState); + } + _LIBUNWIND_TRACE_DWARF("DW_CFA_GNU_window_save\n"); + break; +#endif } break; + #else (void)arch; #endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/EHHeaderParser.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/EHHeaderParser.hpp index 392e60e20e..dc251df983 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/EHHeaderParser.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/EHHeaderParser.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------- EHHeaderParser.hpp -------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -60,7 +60,8 @@ bool EHHeaderParser::decodeEHHdr(A &addressSpace, pint_t ehHdrStart, pint_t p = ehHdrStart; uint8_t version = addressSpace.get8(p++); if (version != 1) { - _LIBUNWIND_LOG0("Unsupported .eh_frame_hdr version"); + _LIBUNWIND_LOG("unsupported .eh_frame_hdr version: %" PRIu8 " at %" PRIx64, + version, static_cast(ehHdrStart)); return false; } diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/RWMutex.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/RWMutex.hpp index 884e935ee6..102bd16333 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/RWMutex.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/RWMutex.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===----------------------------- Registers.hpp --------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Registers.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Registers.hpp index bd2cbf212d..597e1ac9d4 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Registers.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Registers.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===----------------------------- Registers.hpp --------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -18,8 +18,9 @@ #include #include -#include "libunwind.h" +#include "cet_unwind.h" #include "config.h" +#include "libunwind.h" namespace libunwind { @@ -37,14 +38,23 @@ enum { REGISTERS_MIPS_O32, REGISTERS_MIPS_NEWABI, REGISTERS_SPARC, + REGISTERS_SPARC64, REGISTERS_HEXAGON, REGISTERS_RISCV, REGISTERS_VE, + REGISTERS_S390X, }; #if defined(_LIBUNWIND_TARGET_I386) class _LIBUNWIND_HIDDEN Registers_x86; extern "C" void __libunwind_Registers_x86_jumpto(Registers_x86 *); + +#if defined(_LIBUNWIND_USE_CET) +extern "C" void *__libunwind_cet_get_jump_target() { + return reinterpret_cast(&__libunwind_Registers_x86_jumpto); +} +#endif + /// Registers_x86 holds the register state of a thread in a 32-bit intel /// process. class _LIBUNWIND_HIDDEN Registers_x86 { @@ -63,7 +73,9 @@ class _LIBUNWIND_HIDDEN Registers_x86 { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto() { __libunwind_Registers_x86_jumpto(this); } - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_X86; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_X86; + } static int getArch() { return REGISTERS_X86; } uint32_t getSP() const { return _registers.__esp; } @@ -256,6 +268,13 @@ inline void Registers_x86::setVectorRegister(int, v128) { /// process. class _LIBUNWIND_HIDDEN Registers_x86_64; extern "C" void __libunwind_Registers_x86_64_jumpto(Registers_x86_64 *); + +#if defined(_LIBUNWIND_USE_CET) +extern "C" void *__libunwind_cet_get_jump_target() { + return reinterpret_cast(&__libunwind_Registers_x86_64_jumpto); +} +#endif + class _LIBUNWIND_HIDDEN Registers_x86_64 { public: Registers_x86_64(); @@ -272,7 +291,9 @@ class _LIBUNWIND_HIDDEN Registers_x86_64 { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto() { __libunwind_Registers_x86_64_jumpto(this); } - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_X86_64; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_X86_64; + } static int getArch() { return REGISTERS_X86_64; } uint64_t getSP() const { return _registers.__rsp; } @@ -342,7 +363,7 @@ inline bool Registers_x86_64::validRegister(int regNum) const { return true; if (regNum < 0) return false; - if (regNum > 15) + if (regNum > 16) return false; return true; } @@ -350,6 +371,7 @@ inline bool Registers_x86_64::validRegister(int regNum) const { inline uint64_t Registers_x86_64::getRegister(int regNum) const { switch (regNum) { case UNW_REG_IP: + case UNW_X86_64_RIP: return _registers.__rip; case UNW_REG_SP: return _registers.__rsp; @@ -392,6 +414,7 @@ inline uint64_t Registers_x86_64::getRegister(int regNum) const { inline void Registers_x86_64::setRegister(int regNum, uint64_t value) { switch (regNum) { case UNW_REG_IP: + case UNW_X86_64_RIP: _registers.__rip = value; return; case UNW_REG_SP: @@ -452,6 +475,7 @@ inline void Registers_x86_64::setRegister(int regNum, uint64_t value) { inline const char *Registers_x86_64::getRegisterName(int regNum) { switch (regNum) { case UNW_REG_IP: + case UNW_X86_64_RIP: return "rip"; case UNW_REG_SP: return "rsp"; @@ -586,13 +610,17 @@ class _LIBUNWIND_HIDDEN Registers_ppc { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC; + } static int getArch() { return REGISTERS_PPC; } uint64_t getSP() const { return _registers.__r1; } void setSP(uint32_t value) { _registers.__r1 = value; } uint64_t getIP() const { return _registers.__srr0; } void setIP(uint32_t value) { _registers.__srr0 = value; } + uint64_t getCR() const { return _registers.__cr; } + void setCR(uint32_t value) { _registers.__cr = value; } private: struct ppc_thread_state_t { @@ -1152,13 +1180,17 @@ class _LIBUNWIND_HIDDEN Registers_ppc64 { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC64; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC64; + } static int getArch() { return REGISTERS_PPC64; } uint64_t getSP() const { return _registers.__r1; } void setSP(uint64_t value) { _registers.__r1 = value; } uint64_t getIP() const { return _registers.__srr0; } void setIP(uint64_t value) { _registers.__srr0 = value; } + uint64_t getCR() const { return _registers.__cr; } + void setCR(uint64_t value) { _registers.__cr = value; } private: struct ppc64_thread_state_t { @@ -1797,7 +1829,9 @@ class _LIBUNWIND_HIDDEN Registers_arm64 { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto() { __libunwind_Registers_arm64_jumpto(this); } - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM64; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM64; + } static int getArch() { return REGISTERS_ARM64; } uint64_t getSP() const { return _registers.__sp; } @@ -1850,33 +1884,41 @@ inline bool Registers_arm64::validRegister(int regNum) const { return false; if (regNum > 95) return false; - if (regNum == UNW_ARM64_RA_SIGN_STATE) + if (regNum == UNW_AARCH64_RA_SIGN_STATE) return true; - if ((regNum > 31) && (regNum < 64)) + if ((regNum > 32) && (regNum < 64)) return false; return true; } inline uint64_t Registers_arm64::getRegister(int regNum) const { - if (regNum == UNW_REG_IP) + if (regNum == UNW_REG_IP || regNum == UNW_AARCH64_PC) return _registers.__pc; - if (regNum == UNW_REG_SP) + if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP) return _registers.__sp; - if (regNum == UNW_ARM64_RA_SIGN_STATE) + if (regNum == UNW_AARCH64_RA_SIGN_STATE) return _registers.__ra_sign_state; - if ((regNum >= 0) && (regNum < 32)) + if (regNum == UNW_AARCH64_FP) + return _registers.__fp; + if (regNum == UNW_AARCH64_LR) + return _registers.__lr; + if ((regNum >= 0) && (regNum < 29)) return _registers.__x[regNum]; _LIBUNWIND_ABORT("unsupported arm64 register"); } inline void Registers_arm64::setRegister(int regNum, uint64_t value) { - if (regNum == UNW_REG_IP) + if (regNum == UNW_REG_IP || regNum == UNW_AARCH64_PC) _registers.__pc = value; - else if (regNum == UNW_REG_SP) + else if (regNum == UNW_REG_SP || regNum == UNW_AARCH64_SP) _registers.__sp = value; - else if (regNum == UNW_ARM64_RA_SIGN_STATE) + else if (regNum == UNW_AARCH64_RA_SIGN_STATE) _registers.__ra_sign_state = value; - else if ((regNum >= 0) && (regNum < 32)) + else if (regNum == UNW_AARCH64_FP) + _registers.__fp = value; + else if (regNum == UNW_AARCH64_LR) + _registers.__lr = value; + else if ((regNum >= 0) && (regNum < 29)) _registers.__x[regNum] = value; else _LIBUNWIND_ABORT("unsupported arm64 register"); @@ -1888,133 +1930,135 @@ inline const char *Registers_arm64::getRegisterName(int regNum) { return "pc"; case UNW_REG_SP: return "sp"; - case UNW_ARM64_X0: + case UNW_AARCH64_X0: return "x0"; - case UNW_ARM64_X1: + case UNW_AARCH64_X1: return "x1"; - case UNW_ARM64_X2: + case UNW_AARCH64_X2: return "x2"; - case UNW_ARM64_X3: + case UNW_AARCH64_X3: return "x3"; - case UNW_ARM64_X4: + case UNW_AARCH64_X4: return "x4"; - case UNW_ARM64_X5: + case UNW_AARCH64_X5: return "x5"; - case UNW_ARM64_X6: + case UNW_AARCH64_X6: return "x6"; - case UNW_ARM64_X7: + case UNW_AARCH64_X7: return "x7"; - case UNW_ARM64_X8: + case UNW_AARCH64_X8: return "x8"; - case UNW_ARM64_X9: + case UNW_AARCH64_X9: return "x9"; - case UNW_ARM64_X10: + case UNW_AARCH64_X10: return "x10"; - case UNW_ARM64_X11: + case UNW_AARCH64_X11: return "x11"; - case UNW_ARM64_X12: + case UNW_AARCH64_X12: return "x12"; - case UNW_ARM64_X13: + case UNW_AARCH64_X13: return "x13"; - case UNW_ARM64_X14: + case UNW_AARCH64_X14: return "x14"; - case UNW_ARM64_X15: + case UNW_AARCH64_X15: return "x15"; - case UNW_ARM64_X16: + case UNW_AARCH64_X16: return "x16"; - case UNW_ARM64_X17: + case UNW_AARCH64_X17: return "x17"; - case UNW_ARM64_X18: + case UNW_AARCH64_X18: return "x18"; - case UNW_ARM64_X19: + case UNW_AARCH64_X19: return "x19"; - case UNW_ARM64_X20: + case UNW_AARCH64_X20: return "x20"; - case UNW_ARM64_X21: + case UNW_AARCH64_X21: return "x21"; - case UNW_ARM64_X22: + case UNW_AARCH64_X22: return "x22"; - case UNW_ARM64_X23: + case UNW_AARCH64_X23: return "x23"; - case UNW_ARM64_X24: + case UNW_AARCH64_X24: return "x24"; - case UNW_ARM64_X25: + case UNW_AARCH64_X25: return "x25"; - case UNW_ARM64_X26: + case UNW_AARCH64_X26: return "x26"; - case UNW_ARM64_X27: + case UNW_AARCH64_X27: return "x27"; - case UNW_ARM64_X28: + case UNW_AARCH64_X28: return "x28"; - case UNW_ARM64_X29: + case UNW_AARCH64_FP: return "fp"; - case UNW_ARM64_X30: + case UNW_AARCH64_LR: return "lr"; - case UNW_ARM64_X31: + case UNW_AARCH64_SP: return "sp"; - case UNW_ARM64_D0: + case UNW_AARCH64_PC: + return "pc"; + case UNW_AARCH64_V0: return "d0"; - case UNW_ARM64_D1: + case UNW_AARCH64_V1: return "d1"; - case UNW_ARM64_D2: + case UNW_AARCH64_V2: return "d2"; - case UNW_ARM64_D3: + case UNW_AARCH64_V3: return "d3"; - case UNW_ARM64_D4: + case UNW_AARCH64_V4: return "d4"; - case UNW_ARM64_D5: + case UNW_AARCH64_V5: return "d5"; - case UNW_ARM64_D6: + case UNW_AARCH64_V6: return "d6"; - case UNW_ARM64_D7: + case UNW_AARCH64_V7: return "d7"; - case UNW_ARM64_D8: + case UNW_AARCH64_V8: return "d8"; - case UNW_ARM64_D9: + case UNW_AARCH64_V9: return "d9"; - case UNW_ARM64_D10: + case UNW_AARCH64_V10: return "d10"; - case UNW_ARM64_D11: + case UNW_AARCH64_V11: return "d11"; - case UNW_ARM64_D12: + case UNW_AARCH64_V12: return "d12"; - case UNW_ARM64_D13: + case UNW_AARCH64_V13: return "d13"; - case UNW_ARM64_D14: + case UNW_AARCH64_V14: return "d14"; - case UNW_ARM64_D15: + case UNW_AARCH64_V15: return "d15"; - case UNW_ARM64_D16: + case UNW_AARCH64_V16: return "d16"; - case UNW_ARM64_D17: + case UNW_AARCH64_V17: return "d17"; - case UNW_ARM64_D18: + case UNW_AARCH64_V18: return "d18"; - case UNW_ARM64_D19: + case UNW_AARCH64_V19: return "d19"; - case UNW_ARM64_D20: + case UNW_AARCH64_V20: return "d20"; - case UNW_ARM64_D21: + case UNW_AARCH64_V21: return "d21"; - case UNW_ARM64_D22: + case UNW_AARCH64_V22: return "d22"; - case UNW_ARM64_D23: + case UNW_AARCH64_V23: return "d23"; - case UNW_ARM64_D24: + case UNW_AARCH64_V24: return "d24"; - case UNW_ARM64_D25: + case UNW_AARCH64_V25: return "d25"; - case UNW_ARM64_D26: + case UNW_AARCH64_V26: return "d26"; - case UNW_ARM64_D27: + case UNW_AARCH64_V27: return "d27"; - case UNW_ARM64_D28: + case UNW_AARCH64_V28: return "d28"; - case UNW_ARM64_D29: + case UNW_AARCH64_V29: return "d29"; - case UNW_ARM64_D30: + case UNW_AARCH64_V30: return "d30"; - case UNW_ARM64_D31: + case UNW_AARCH64_V31: return "d31"; default: return "unknown register"; @@ -2022,21 +2066,21 @@ inline const char *Registers_arm64::getRegisterName(int regNum) { } inline bool Registers_arm64::validFloatRegister(int regNum) const { - if (regNum < UNW_ARM64_D0) + if (regNum < UNW_AARCH64_V0) return false; - if (regNum > UNW_ARM64_D31) + if (regNum > UNW_AARCH64_V31) return false; return true; } inline double Registers_arm64::getFloatRegister(int regNum) const { assert(validFloatRegister(regNum)); - return _vectorHalfRegisters[regNum - UNW_ARM64_D0]; + return _vectorHalfRegisters[regNum - UNW_AARCH64_V0]; } inline void Registers_arm64::setFloatRegister(int regNum, double value) { assert(validFloatRegister(regNum)); - _vectorHalfRegisters[regNum - UNW_ARM64_D0] = value; + _vectorHalfRegisters[regNum - UNW_AARCH64_V0] = value; } inline bool Registers_arm64::validVectorRegister(int) const { @@ -2077,7 +2121,9 @@ class _LIBUNWIND_HIDDEN Registers_arm { restoreSavedFloatRegisters(); restoreCoreAndJumpTo(); } - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_ARM; + } static int getArch() { return REGISTERS_ARM; } uint32_t getSP() const { return _registers.__sp; } @@ -2115,6 +2161,10 @@ class _LIBUNWIND_HIDDEN Registers_arm { uint32_t __pc; // Program counter r15 }; + struct PseudoRegisters { + uint32_t __pac; // Return Authentication Code (PAC) + }; + static void saveVFPWithFSTMD(void*); static void saveVFPWithFSTMX(void*); static void saveVFPv3(void*); @@ -2131,6 +2181,7 @@ class _LIBUNWIND_HIDDEN Registers_arm { // ARM registers GPRs _registers; + PseudoRegisters _pseudo_registers; // We save floating point registers lazily because we can't know ahead of // time which ones are used. See EHABI #4.7. @@ -2168,6 +2219,7 @@ inline Registers_arm::Registers_arm(const void *registers) "arm registers do not fit into unw_context_t"); // See __unw_getcontext() note about data. memcpy(&_registers, registers, sizeof(_registers)); + memset(&_pseudo_registers, 0, sizeof(_pseudo_registers)); memset(&_vfp_d0_d15_pad, 0, sizeof(_vfp_d0_d15_pad)); memset(&_vfp_d16_d31, 0, sizeof(_vfp_d16_d31)); #if defined(__ARM_WMMX) @@ -2183,6 +2235,7 @@ inline Registers_arm::Registers_arm() _saved_vfp_d0_d15(false), _saved_vfp_d16_d31(false) { memset(&_registers, 0, sizeof(_registers)); + memset(&_pseudo_registers, 0, sizeof(_pseudo_registers)); memset(&_vfp_d0_d15_pad, 0, sizeof(_vfp_d0_d15_pad)); memset(&_vfp_d16_d31, 0, sizeof(_vfp_d16_d31)); #if defined(__ARM_WMMX) @@ -2210,6 +2263,11 @@ inline bool Registers_arm::validRegister(int regNum) const { return true; #endif +#ifdef __ARM_FEATURE_PAUTH + if (regNum == UNW_ARM_RA_AUTH_CODE) + return true; +#endif + return false; } @@ -2236,6 +2294,11 @@ inline uint32_t Registers_arm::getRegister(int regNum) const { } #endif +#ifdef __ARM_FEATURE_PAUTH + if (regNum == UNW_ARM_RA_AUTH_CODE) + return _pseudo_registers.__pac; +#endif + _LIBUNWIND_ABORT("unsupported arm register"); } @@ -2271,6 +2334,11 @@ inline void Registers_arm::setRegister(int regNum, uint32_t value) { } #endif + if (regNum == UNW_ARM_RA_AUTH_CODE) { + _pseudo_registers.__pac = value; + return; + } + _LIBUNWIND_ABORT("unsupported arm register"); } @@ -2555,7 +2623,9 @@ class _LIBUNWIND_HIDDEN Registers_or1k { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_OR1K; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_OR1K; + } static int getArch() { return REGISTERS_OR1K; } uint64_t getSP() const { return _registers.__r[1]; } @@ -2752,7 +2822,9 @@ class _LIBUNWIND_HIDDEN Registers_mips_o32 { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_MIPS; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_MIPS; + } static int getArch() { return REGISTERS_MIPS_O32; } uint32_t getSP() const { return _registers.__r[29]; } @@ -3079,7 +3151,9 @@ class _LIBUNWIND_HIDDEN Registers_mips_newabi { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_MIPS; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_MIPS; + } static int getArch() { return REGISTERS_MIPS_NEWABI; } uint64_t getSP() const { return _registers.__r[29]; } @@ -3374,7 +3448,9 @@ class _LIBUNWIND_HIDDEN Registers_sparc { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC; + } static int getArch() { return REGISTERS_SPARC; } uint64_t getSP() const { return _registers.__regs[UNW_SPARC_O6]; } @@ -3539,6 +3615,191 @@ inline const char *Registers_sparc::getRegisterName(int regNum) { } #endif // _LIBUNWIND_TARGET_SPARC +#if defined(_LIBUNWIND_TARGET_SPARC64) +/// Registers_sparc64 holds the register state of a thread in a 64-bit +/// sparc process. +class _LIBUNWIND_HIDDEN Registers_sparc64 { +public: + Registers_sparc64() = default; + Registers_sparc64(const void *registers); + + bool validRegister(int num) const; + uint64_t getRegister(int num) const; + void setRegister(int num, uint64_t value); + bool validFloatRegister(int num) const; + double getFloatRegister(int num) const; + void setFloatRegister(int num, double value); + bool validVectorRegister(int num) const; + v128 getVectorRegister(int num) const; + void setVectorRegister(int num, v128 value); + const char *getRegisterName(int num); + void jumpto(); + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC64; + } + static int getArch() { return REGISTERS_SPARC64; } + + uint64_t getSP() const { return _registers.__regs[UNW_SPARC_O6] + 2047; } + void setSP(uint64_t value) { _registers.__regs[UNW_SPARC_O6] = value - 2047; } + uint64_t getIP() const { return _registers.__regs[UNW_SPARC_O7]; } + void setIP(uint64_t value) { _registers.__regs[UNW_SPARC_O7] = value; } + uint64_t getWCookie() const { return _wcookie; } + +private: + struct sparc64_thread_state_t { + uint64_t __regs[32]; + }; + + sparc64_thread_state_t _registers{}; + uint64_t _wcookie = 0; +}; + +inline Registers_sparc64::Registers_sparc64(const void *registers) { + static_assert((check_fit::does_fit), + "sparc64 registers do not fit into unw_context_t"); + memcpy(&_registers, registers, sizeof(_registers)); + memcpy(&_wcookie, + static_cast(registers) + sizeof(_registers), + sizeof(_wcookie)); +} + +inline bool Registers_sparc64::validRegister(int regNum) const { + if (regNum == UNW_REG_IP) + return true; + if (regNum == UNW_REG_SP) + return true; + if (regNum < 0) + return false; + if (regNum <= UNW_SPARC_I7) + return true; + return false; +} + +inline uint64_t Registers_sparc64::getRegister(int regNum) const { + if (regNum >= UNW_SPARC_G0 && regNum <= UNW_SPARC_I7) + return _registers.__regs[regNum]; + + switch (regNum) { + case UNW_REG_IP: + return _registers.__regs[UNW_SPARC_O7]; + case UNW_REG_SP: + return _registers.__regs[UNW_SPARC_O6] + 2047; + } + _LIBUNWIND_ABORT("unsupported sparc64 register"); +} + +inline void Registers_sparc64::setRegister(int regNum, uint64_t value) { + if (regNum >= UNW_SPARC_G0 && regNum <= UNW_SPARC_I7) { + _registers.__regs[regNum] = value; + return; + } + + switch (regNum) { + case UNW_REG_IP: + _registers.__regs[UNW_SPARC_O7] = value; + return; + case UNW_REG_SP: + _registers.__regs[UNW_SPARC_O6] = value - 2047; + return; + } + _LIBUNWIND_ABORT("unsupported sparc64 register"); +} + +inline bool Registers_sparc64::validFloatRegister(int) const { return false; } + +inline double Registers_sparc64::getFloatRegister(int) const { + _LIBUNWIND_ABORT("no sparc64 float registers"); +} + +inline void Registers_sparc64::setFloatRegister(int, double) { + _LIBUNWIND_ABORT("no sparc64 float registers"); +} + +inline bool Registers_sparc64::validVectorRegister(int) const { return false; } + +inline v128 Registers_sparc64::getVectorRegister(int) const { + _LIBUNWIND_ABORT("no sparc64 vector registers"); +} + +inline void Registers_sparc64::setVectorRegister(int, v128) { + _LIBUNWIND_ABORT("no sparc64 vector registers"); +} + +inline const char *Registers_sparc64::getRegisterName(int regNum) { + switch (regNum) { + case UNW_REG_IP: + return "pc"; + case UNW_SPARC_G0: + return "g0"; + case UNW_SPARC_G1: + return "g1"; + case UNW_SPARC_G2: + return "g2"; + case UNW_SPARC_G3: + return "g3"; + case UNW_SPARC_G4: + return "g4"; + case UNW_SPARC_G5: + return "g5"; + case UNW_SPARC_G6: + return "g6"; + case UNW_SPARC_G7: + return "g7"; + case UNW_SPARC_O0: + return "o0"; + case UNW_SPARC_O1: + return "o1"; + case UNW_SPARC_O2: + return "o2"; + case UNW_SPARC_O3: + return "o3"; + case UNW_SPARC_O4: + return "o4"; + case UNW_SPARC_O5: + return "o5"; + case UNW_REG_SP: + case UNW_SPARC_O6: + return "o6"; + case UNW_SPARC_O7: + return "o7"; + case UNW_SPARC_L0: + return "l0"; + case UNW_SPARC_L1: + return "l1"; + case UNW_SPARC_L2: + return "l2"; + case UNW_SPARC_L3: + return "l3"; + case UNW_SPARC_L4: + return "l4"; + case UNW_SPARC_L5: + return "l5"; + case UNW_SPARC_L6: + return "l6"; + case UNW_SPARC_L7: + return "l7"; + case UNW_SPARC_I0: + return "i0"; + case UNW_SPARC_I1: + return "i1"; + case UNW_SPARC_I2: + return "i2"; + case UNW_SPARC_I3: + return "i3"; + case UNW_SPARC_I4: + return "i4"; + case UNW_SPARC_I5: + return "i5"; + case UNW_SPARC_I6: + return "i6"; + case UNW_SPARC_I7: + return "i7"; + default: + return "unknown register"; + } +} +#endif // _LIBUNWIND_TARGET_SPARC64 + #if defined(_LIBUNWIND_TARGET_HEXAGON) /// Registers_hexagon holds the register state of a thread in a Hexagon QDSP6 /// process. @@ -3558,7 +3819,9 @@ class _LIBUNWIND_HIDDEN Registers_hexagon { void setVectorRegister(int num, v128 value); const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_HEXAGON; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_HEXAGON; + } static int getArch() { return REGISTERS_HEXAGON; } uint32_t getSP() const { return _registers.__r[UNW_HEXAGON_R29]; } @@ -3721,52 +3984,100 @@ inline const char *Registers_hexagon::getRegisterName(int regNum) { #if defined(_LIBUNWIND_TARGET_RISCV) -/// Registers_riscv holds the register state of a thread in a 64-bit RISC-V +/// Registers_riscv holds the register state of a thread in a RISC-V /// process. + +// This check makes it safe when LIBUNWIND_ENABLE_CROSS_UNWINDING enabled. +# ifdef __riscv +# if __riscv_xlen == 32 +typedef uint32_t reg_t; +# elif __riscv_xlen == 64 +typedef uint64_t reg_t; +# else +# error "Unsupported __riscv_xlen" +# endif + +# if defined(__riscv_flen) +# if __riscv_flen == 64 +typedef double fp_t; +# elif __riscv_flen == 32 +typedef float fp_t; +# else +# error "Unsupported __riscv_flen" +# endif +# else +// This is just for supressing undeclared error of fp_t. +typedef double fp_t; +# endif +# else +// Use Max possible width when cross unwinding +typedef uint64_t reg_t; +typedef double fp_t; +# define __riscv_xlen 64 +# define __riscv_flen 64 +#endif + +/// Registers_riscv holds the register state of a thread. class _LIBUNWIND_HIDDEN Registers_riscv { public: Registers_riscv(); Registers_riscv(const void *registers); bool validRegister(int num) const; - uint64_t getRegister(int num) const; - void setRegister(int num, uint64_t value); + reg_t getRegister(int num) const; + void setRegister(int num, reg_t value); bool validFloatRegister(int num) const; - double getFloatRegister(int num) const; - void setFloatRegister(int num, double value); + fp_t getFloatRegister(int num) const; + void setFloatRegister(int num, fp_t value); bool validVectorRegister(int num) const; v128 getVectorRegister(int num) const; void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_RISCV; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_RISCV; + } static int getArch() { return REGISTERS_RISCV; } - uint64_t getSP() const { return _registers[2]; } - void setSP(uint64_t value) { _registers[2] = value; } - uint64_t getIP() const { return _registers[0]; } - void setIP(uint64_t value) { _registers[0] = value; } + reg_t getSP() const { return _registers[2]; } + void setSP(reg_t value) { _registers[2] = value; } + reg_t getIP() const { return _registers[0]; } + void setIP(reg_t value) { _registers[0] = value; } private: // _registers[0] holds the pc - uint64_t _registers[32]; - double _floats[32]; + reg_t _registers[32]; +# if defined(__riscv_flen) + fp_t _floats[32]; +# endif }; inline Registers_riscv::Registers_riscv(const void *registers) { static_assert((check_fit::does_fit), "riscv registers do not fit into unw_context_t"); memcpy(&_registers, registers, sizeof(_registers)); +# if __riscv_xlen == 32 + static_assert(sizeof(_registers) == 0x80, + "expected float registers to be at offset 128"); +# elif __riscv_xlen == 64 static_assert(sizeof(_registers) == 0x100, "expected float registers to be at offset 256"); +# else +# error "Unexpected float registers." +# endif + +# if defined(__riscv_flen) memcpy(_floats, static_cast(registers) + sizeof(_registers), sizeof(_floats)); +# endif } inline Registers_riscv::Registers_riscv() { memset(&_registers, 0, sizeof(_registers)); +# if defined(__riscv_flen) memset(&_floats, 0, sizeof(_floats)); +# endif } inline bool Registers_riscv::validRegister(int regNum) const { @@ -3781,7 +4092,7 @@ inline bool Registers_riscv::validRegister(int regNum) const { return true; } -inline uint64_t Registers_riscv::getRegister(int regNum) const { +inline reg_t Registers_riscv::getRegister(int regNum) const { if (regNum == UNW_REG_IP) return _registers[0]; if (regNum == UNW_REG_SP) @@ -3793,7 +4104,7 @@ inline uint64_t Registers_riscv::getRegister(int regNum) const { _LIBUNWIND_ABORT("unsupported riscv register"); } -inline void Registers_riscv::setRegister(int regNum, uint64_t value) { +inline void Registers_riscv::setRegister(int regNum, reg_t value) { if (regNum == UNW_REG_IP) _registers[0] = value; else if (regNum == UNW_REG_SP) @@ -3947,32 +4258,37 @@ inline const char *Registers_riscv::getRegisterName(int regNum) { } inline bool Registers_riscv::validFloatRegister(int regNum) const { +# if defined(__riscv_flen) if (regNum < UNW_RISCV_F0) return false; if (regNum > UNW_RISCV_F31) return false; return true; +# else + (void)regNum; + return false; +# endif } -inline double Registers_riscv::getFloatRegister(int regNum) const { -#if defined(__riscv_flen) && __riscv_flen == 64 +inline fp_t Registers_riscv::getFloatRegister(int regNum) const { +# if defined(__riscv_flen) assert(validFloatRegister(regNum)); return _floats[regNum - UNW_RISCV_F0]; -#else +# else (void)regNum; _LIBUNWIND_ABORT("libunwind not built with float support"); -#endif +# endif } -inline void Registers_riscv::setFloatRegister(int regNum, double value) { -#if defined(__riscv_flen) && __riscv_flen == 64 +inline void Registers_riscv::setFloatRegister(int regNum, fp_t value) { +# if defined(__riscv_flen) assert(validFloatRegister(regNum)); _floats[regNum - UNW_RISCV_F0] = value; -#else +# else (void)regNum; (void)value; _LIBUNWIND_ABORT("libunwind not built with float support"); -#endif +# endif } inline bool Registers_riscv::validVectorRegister(int) const { @@ -4006,7 +4322,9 @@ class _LIBUNWIND_HIDDEN Registers_ve { void setVectorRegister(int num, v128 value); static const char *getRegisterName(int num); void jumpto(); - static int lastDwarfRegNum() { return _LIBUNWIND_HIGHEST_DWARF_REGISTER_VE; } + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_VE; + } static int getArch() { return REGISTERS_VE; } uint64_t getSP() const { return _registers.__s[11]; } @@ -4428,6 +4746,295 @@ inline const char *Registers_ve::getRegisterName(int regNum) { } #endif // _LIBUNWIND_TARGET_VE +#if defined(_LIBUNWIND_TARGET_S390X) +/// Registers_s390x holds the register state of a thread in a +/// 64-bit Linux on IBM zSystems process. +class _LIBUNWIND_HIDDEN Registers_s390x { +public: + Registers_s390x(); + Registers_s390x(const void *registers); + + bool validRegister(int num) const; + uint64_t getRegister(int num) const; + void setRegister(int num, uint64_t value); + bool validFloatRegister(int num) const; + double getFloatRegister(int num) const; + void setFloatRegister(int num, double value); + bool validVectorRegister(int num) const; + v128 getVectorRegister(int num) const; + void setVectorRegister(int num, v128 value); + static const char *getRegisterName(int num); + void jumpto(); + static constexpr int lastDwarfRegNum() { + return _LIBUNWIND_HIGHEST_DWARF_REGISTER_S390X; + } + static int getArch() { return REGISTERS_S390X; } + + uint64_t getSP() const { return _registers.__gpr[15]; } + void setSP(uint64_t value) { _registers.__gpr[15] = value; } + uint64_t getIP() const { return _registers.__pswa; } + void setIP(uint64_t value) { _registers.__pswa = value; } + +private: + struct s390x_thread_state_t { + uint64_t __pswm; // Problem Status Word: Mask + uint64_t __pswa; // Problem Status Word: Address (PC) + uint64_t __gpr[16]; // General Purpose Registers + double __fpr[16]; // Floating-Point Registers + }; + + s390x_thread_state_t _registers; +}; + +inline Registers_s390x::Registers_s390x(const void *registers) { + static_assert((check_fit::does_fit), + "s390x registers do not fit into unw_context_t"); + memcpy(&_registers, static_cast(registers), + sizeof(_registers)); +} + +inline Registers_s390x::Registers_s390x() { + memset(&_registers, 0, sizeof(_registers)); +} + +inline bool Registers_s390x::validRegister(int regNum) const { + switch (regNum) { + case UNW_S390X_PSWM: + case UNW_S390X_PSWA: + case UNW_REG_IP: + case UNW_REG_SP: + return true; + } + + if (regNum >= UNW_S390X_R0 && regNum <= UNW_S390X_R15) + return true; + + return false; +} + +inline uint64_t Registers_s390x::getRegister(int regNum) const { + if (regNum >= UNW_S390X_R0 && regNum <= UNW_S390X_R15) + return _registers.__gpr[regNum - UNW_S390X_R0]; + + switch (regNum) { + case UNW_S390X_PSWM: + return _registers.__pswm; + case UNW_S390X_PSWA: + case UNW_REG_IP: + return _registers.__pswa; + case UNW_REG_SP: + return _registers.__gpr[15]; + } + _LIBUNWIND_ABORT("unsupported s390x register"); +} + +inline void Registers_s390x::setRegister(int regNum, uint64_t value) { + if (regNum >= UNW_S390X_R0 && regNum <= UNW_S390X_R15) { + _registers.__gpr[regNum - UNW_S390X_R0] = value; + return; + } + + switch (regNum) { + case UNW_S390X_PSWM: + _registers.__pswm = value; + return; + case UNW_S390X_PSWA: + case UNW_REG_IP: + _registers.__pswa = value; + return; + case UNW_REG_SP: + _registers.__gpr[15] = value; + return; + } + _LIBUNWIND_ABORT("unsupported s390x register"); +} + +inline bool Registers_s390x::validFloatRegister(int regNum) const { + return regNum >= UNW_S390X_F0 && regNum <= UNW_S390X_F15; +} + +inline double Registers_s390x::getFloatRegister(int regNum) const { + // NOTE: FPR DWARF register numbers are not consecutive. + switch (regNum) { + case UNW_S390X_F0: + return _registers.__fpr[0]; + case UNW_S390X_F1: + return _registers.__fpr[1]; + case UNW_S390X_F2: + return _registers.__fpr[2]; + case UNW_S390X_F3: + return _registers.__fpr[3]; + case UNW_S390X_F4: + return _registers.__fpr[4]; + case UNW_S390X_F5: + return _registers.__fpr[5]; + case UNW_S390X_F6: + return _registers.__fpr[6]; + case UNW_S390X_F7: + return _registers.__fpr[7]; + case UNW_S390X_F8: + return _registers.__fpr[8]; + case UNW_S390X_F9: + return _registers.__fpr[9]; + case UNW_S390X_F10: + return _registers.__fpr[10]; + case UNW_S390X_F11: + return _registers.__fpr[11]; + case UNW_S390X_F12: + return _registers.__fpr[12]; + case UNW_S390X_F13: + return _registers.__fpr[13]; + case UNW_S390X_F14: + return _registers.__fpr[14]; + case UNW_S390X_F15: + return _registers.__fpr[15]; + } + _LIBUNWIND_ABORT("unsupported s390x register"); +} + +inline void Registers_s390x::setFloatRegister(int regNum, double value) { + // NOTE: FPR DWARF register numbers are not consecutive. + switch (regNum) { + case UNW_S390X_F0: + _registers.__fpr[0] = value; + return; + case UNW_S390X_F1: + _registers.__fpr[1] = value; + return; + case UNW_S390X_F2: + _registers.__fpr[2] = value; + return; + case UNW_S390X_F3: + _registers.__fpr[3] = value; + return; + case UNW_S390X_F4: + _registers.__fpr[4] = value; + return; + case UNW_S390X_F5: + _registers.__fpr[5] = value; + return; + case UNW_S390X_F6: + _registers.__fpr[6] = value; + return; + case UNW_S390X_F7: + _registers.__fpr[7] = value; + return; + case UNW_S390X_F8: + _registers.__fpr[8] = value; + return; + case UNW_S390X_F9: + _registers.__fpr[9] = value; + return; + case UNW_S390X_F10: + _registers.__fpr[10] = value; + return; + case UNW_S390X_F11: + _registers.__fpr[11] = value; + return; + case UNW_S390X_F12: + _registers.__fpr[12] = value; + return; + case UNW_S390X_F13: + _registers.__fpr[13] = value; + return; + case UNW_S390X_F14: + _registers.__fpr[14] = value; + return; + case UNW_S390X_F15: + _registers.__fpr[15] = value; + return; + } + _LIBUNWIND_ABORT("unsupported s390x register"); +} + +inline bool Registers_s390x::validVectorRegister(int /*regNum*/) const { + return false; +} + +inline v128 Registers_s390x::getVectorRegister(int /*regNum*/) const { + _LIBUNWIND_ABORT("s390x vector support not implemented"); +} + +inline void Registers_s390x::setVectorRegister(int /*regNum*/, v128 /*value*/) { + _LIBUNWIND_ABORT("s390x vector support not implemented"); +} + +inline const char *Registers_s390x::getRegisterName(int regNum) { + switch (regNum) { + case UNW_REG_IP: + return "ip"; + case UNW_REG_SP: + return "sp"; + case UNW_S390X_R0: + return "r0"; + case UNW_S390X_R1: + return "r1"; + case UNW_S390X_R2: + return "r2"; + case UNW_S390X_R3: + return "r3"; + case UNW_S390X_R4: + return "r4"; + case UNW_S390X_R5: + return "r5"; + case UNW_S390X_R6: + return "r6"; + case UNW_S390X_R7: + return "r7"; + case UNW_S390X_R8: + return "r8"; + case UNW_S390X_R9: + return "r9"; + case UNW_S390X_R10: + return "r10"; + case UNW_S390X_R11: + return "r11"; + case UNW_S390X_R12: + return "r12"; + case UNW_S390X_R13: + return "r13"; + case UNW_S390X_R14: + return "r14"; + case UNW_S390X_R15: + return "r15"; + case UNW_S390X_F0: + return "f0"; + case UNW_S390X_F1: + return "f1"; + case UNW_S390X_F2: + return "f2"; + case UNW_S390X_F3: + return "f3"; + case UNW_S390X_F4: + return "f4"; + case UNW_S390X_F5: + return "f5"; + case UNW_S390X_F6: + return "f6"; + case UNW_S390X_F7: + return "f7"; + case UNW_S390X_F8: + return "f8"; + case UNW_S390X_F9: + return "f9"; + case UNW_S390X_F10: + return "f10"; + case UNW_S390X_F11: + return "f11"; + case UNW_S390X_F12: + return "f12"; + case UNW_S390X_F13: + return "f13"; + case UNW_S390X_F14: + return "f14"; + case UNW_S390X_F15: + return "f15"; + } + return "unknown register"; +} +#endif // _LIBUNWIND_TARGET_S390X + + } // namespace libunwind #endif // __REGISTERS_HPP__ diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.cpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.cpp index 0361f04cd5..164ca14b1b 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.cpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.cpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------------- Unwind-EHABI.cpp -------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -100,9 +100,11 @@ _Unwind_Reason_Code ProcessDescriptors( case Descriptor::LU32: descriptor = getNextWord(descriptor, &length); descriptor = getNextWord(descriptor, &offset); + break; case Descriptor::LU16: descriptor = getNextNibble(descriptor, &length); descriptor = getNextNibble(descriptor, &offset); + break; default: assert(false); return _URC_FAILURE; @@ -188,9 +190,14 @@ static _Unwind_Reason_Code unwindOneFrame(_Unwind_State state, if (result != _URC_CONTINUE_UNWIND) return result; - if (__unw_step(reinterpret_cast(context)) != UNW_STEP_SUCCESS) + switch (__unw_step(reinterpret_cast(context))) { + case UNW_STEP_SUCCESS: + return _URC_CONTINUE_UNWIND; + case UNW_STEP_END: + return _URC_END_OF_STACK; + default: return _URC_FAILURE; - return _URC_CONTINUE_UNWIND; + } } // Generates mask discriminator for _Unwind_VRS_Pop, e.g. for _UVRSC_CORE / @@ -257,6 +264,7 @@ _Unwind_VRS_Interpret(_Unwind_Context *context, const uint32_t *data, size_t offset, size_t len) { bool wrotePC = false; bool finish = false; + bool hasReturnAddrAuthCode = false; while (offset < len && !finish) { uint8_t byte = getByte(data, offset++); if ((byte & 0x80) == 0) { @@ -343,6 +351,10 @@ _Unwind_VRS_Interpret(_Unwind_Context *context, const uint32_t *data, break; } case 0xb4: + hasReturnAddrAuthCode = true; + _Unwind_VRS_Pop(context, _UVRSC_PSEUDO, + 0 /* Return Address Auth Code */, _UVRSD_UINT32); + break; case 0xb5: case 0xb6: case 0xb7: @@ -418,6 +430,17 @@ _Unwind_VRS_Interpret(_Unwind_Context *context, const uint32_t *data, if (!wrotePC) { uint32_t lr; _Unwind_VRS_Get(context, _UVRSC_CORE, UNW_ARM_LR, _UVRSD_UINT32, &lr); +#ifdef __ARM_FEATURE_PAUTH + if (hasReturnAddrAuthCode) { + uint32_t sp; + uint32_t pac; + _Unwind_VRS_Get(context, _UVRSC_CORE, UNW_ARM_SP, _UVRSD_UINT32, &sp); + _Unwind_VRS_Get(context, _UVRSC_PSEUDO, 0, _UVRSD_UINT32, &pac); + __asm__ __volatile__("autg %0, %1, %2" : : "r"(pac), "r"(lr), "r"(sp) :); + } +#else + (void)hasReturnAddrAuthCode; +#endif _Unwind_VRS_Set(context, _UVRSC_CORE, UNW_ARM_IP, _UVRSD_UINT32, &lr); } return _URC_CONTINUE_UNWIND; @@ -464,6 +487,7 @@ unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except return _URC_FATAL_PHASE1_ERROR; } +#ifndef NDEBUG // When tracing, print state information. if (_LIBUNWIND_TRACING_UNWINDING) { char functionBuf[512]; @@ -482,6 +506,7 @@ unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except frameInfo.start_ip, functionName, frameInfo.lsda, frameInfo.handler); } +#endif // If there is a personality routine, ask it if it will want to stop at // this frame. @@ -583,6 +608,7 @@ static _Unwind_Reason_Code unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor return _URC_FATAL_PHASE2_ERROR; } +#ifndef NDEBUG // When tracing, print state information. if (_LIBUNWIND_TRACING_UNWINDING) { char functionBuf[512]; @@ -599,11 +625,12 @@ static _Unwind_Reason_Code unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor functionName, sp, frameInfo.lsda, frameInfo.handler); } +#endif // If there is a personality routine, tell it we are unwinding. if (frameInfo.handler != 0) { _Unwind_Personality_Fn p = - (_Unwind_Personality_Fn)(long)(frameInfo.handler); + (_Unwind_Personality_Fn)(intptr_t)(frameInfo.handler); struct _Unwind_Context *context = (struct _Unwind_Context *)(cursor); // EHABI #7.2 exception_object->pr_cache.fnstart = frameInfo.start_ip; @@ -671,6 +698,123 @@ static _Unwind_Reason_Code unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor return _URC_FATAL_PHASE2_ERROR; } +static _Unwind_Reason_Code +unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor, + _Unwind_Exception *exception_object, _Unwind_Stop_Fn stop, + void *stop_parameter) { + bool endOfStack = false; + // See comment at the start of unwind_phase1 regarding VRS integrity. + __unw_init_local(cursor, uc); + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_force(ex_ojb=%p)", + static_cast(exception_object)); + // Walk each frame until we reach where search phase said to stop + while (!endOfStack) { + // Update info about this frame. + unw_proc_info_t frameInfo; + if (__unw_get_proc_info(cursor, &frameInfo) != UNW_ESUCCESS) { + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_forced(ex_ojb=%p): __unw_step " + "failed => _URC_END_OF_STACK", + (void *)exception_object); + return _URC_FATAL_PHASE2_ERROR; + } + +#ifndef NDEBUG + // When tracing, print state information. + if (_LIBUNWIND_TRACING_UNWINDING) { + char functionBuf[512]; + const char *functionName = functionBuf; + unw_word_t offset; + if ((__unw_get_proc_name(cursor, functionBuf, sizeof(functionBuf), + &offset) != UNW_ESUCCESS) || + (frameInfo.start_ip + offset > frameInfo.end_ip)) + functionName = ".anonymous."; + _LIBUNWIND_TRACE_UNWINDING( + "unwind_phase2_forced(ex_ojb=%p): start_ip=0x%" PRIxPTR + ", func=%s, lsda=0x%" PRIxPTR ", personality=0x%" PRIxPTR, + (void *)exception_object, frameInfo.start_ip, functionName, + frameInfo.lsda, frameInfo.handler); + } +#endif + + // Call stop function at each frame. + _Unwind_Action action = + (_Unwind_Action)(_UA_FORCE_UNWIND | _UA_CLEANUP_PHASE); + _Unwind_Reason_Code stopResult = + (*stop)(1, action, exception_object->exception_class, exception_object, + (_Unwind_Context *)(cursor), stop_parameter); + _LIBUNWIND_TRACE_UNWINDING( + "unwind_phase2_forced(ex_ojb=%p): stop function returned %d", + (void *)exception_object, stopResult); + if (stopResult != _URC_NO_REASON) { + _LIBUNWIND_TRACE_UNWINDING( + "unwind_phase2_forced(ex_ojb=%p): stopped by stop function", + (void *)exception_object); + return _URC_FATAL_PHASE2_ERROR; + } + + // If there is a personality routine, tell it we are unwinding. + if (frameInfo.handler != 0) { + _Unwind_Personality_Fn p = + (_Unwind_Personality_Fn)(uintptr_t)(frameInfo.handler); + struct _Unwind_Context *context = (struct _Unwind_Context *)(cursor); + // EHABI #7.2 + exception_object->pr_cache.fnstart = frameInfo.start_ip; + exception_object->pr_cache.ehtp = + (_Unwind_EHT_Header *)frameInfo.unwind_info; + exception_object->pr_cache.additional = frameInfo.flags; + _Unwind_Reason_Code personalityResult = + (*p)(_US_FORCE_UNWIND | _US_UNWIND_FRAME_STARTING, exception_object, + context); + switch (personalityResult) { + case _URC_CONTINUE_UNWIND: + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_forced(ex_ojb=%p): " + "personality returned " + "_URC_CONTINUE_UNWIND", + (void *)exception_object); + // Destructors called, continue unwinding + break; + case _URC_INSTALL_CONTEXT: + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_forced(ex_ojb=%p): " + "personality returned " + "_URC_INSTALL_CONTEXT", + (void *)exception_object); + // We may get control back if landing pad calls _Unwind_Resume(). + __unw_resume(cursor); + break; + case _URC_END_OF_STACK: + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_forced(ex_ojb=%p): " + "personality returned " + "_URC_END_OF_STACK", + (void *)exception_object); + // Personalty routine did the step and it can't step forward. + endOfStack = true; + break; + default: + // Personality routine returned an unknown result code. + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_forced(ex_ojb=%p): " + "personality returned %d, " + "_URC_FATAL_PHASE2_ERROR", + (void *)exception_object, personalityResult); + return _URC_FATAL_PHASE2_ERROR; + } + } + } + + // Call stop function one last time and tell it we've reached the end + // of the stack. + _LIBUNWIND_TRACE_UNWINDING("unwind_phase2_forced(ex_ojb=%p): calling stop " + "function with _UA_END_OF_STACK", + (void *)exception_object); + _Unwind_Action lastAction = + (_Unwind_Action)(_UA_FORCE_UNWIND | _UA_CLEANUP_PHASE | _UA_END_OF_STACK); + (*stop)(1, lastAction, exception_object->exception_class, exception_object, + (struct _Unwind_Context *)(cursor), stop_parameter); + + // Clean up phase did not resume at the frame that the search phase said it + // would. + return _URC_FATAL_PHASE2_ERROR; +} + /// Called by __cxa_throw. Only returns if there is a fatal error. _LIBUNWIND_EXPORT _Unwind_Reason_Code _Unwind_RaiseException(_Unwind_Exception *exception_object) { @@ -718,10 +862,13 @@ _Unwind_Resume(_Unwind_Exception *exception_object) { unw_cursor_t cursor; __unw_getcontext(&uc); - // _Unwind_RaiseException on EHABI will always set the reserved1 field to 0, - // which is in the same position as private_1 below. - // TODO(ajwong): Who wronte the above? Why is it true? - unwind_phase2(&uc, &cursor, exception_object, true); + if (exception_object->unwinder_cache.reserved1) + unwind_phase2_forced( + &uc, &cursor, exception_object, + (_Unwind_Stop_Fn)exception_object->unwinder_cache.reserved1, + (void *)exception_object->unwinder_cache.reserved3); + else + unwind_phase2(&uc, &cursor, exception_object, true); // Clients assume _Unwind_Resume() does not return, so all we can do is abort. _LIBUNWIND_ABORT("_Unwind_Resume() can't return"); @@ -813,6 +960,15 @@ _Unwind_VRS_Set(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, case _UVRSC_WMMXD: break; #endif + case _UVRSC_PSEUDO: + // There's only one pseudo-register, PAC, with regno == 0. + if (representation != _UVRSD_UINT32 || regno != 0) + return _UVRSR_FAILED; + return __unw_set_reg(cursor, (unw_regnum_t)(UNW_ARM_RA_AUTH_CODE), + *(unw_word_t *)valuep) == UNW_ESUCCESS + ? _UVRSR_OK + : _UVRSR_FAILED; + break; } _LIBUNWIND_ABORT("unsupported register class"); } @@ -867,6 +1023,15 @@ _Unwind_VRS_Get_Internal(_Unwind_Context *context, case _UVRSC_WMMXD: break; #endif + case _UVRSC_PSEUDO: + // There's only one pseudo-register, PAC, with regno == 0. + if (representation != _UVRSD_UINT32 || regno != 0) + return _UVRSR_FAILED; + return __unw_get_reg(cursor, (unw_regnum_t)(UNW_ARM_RA_AUTH_CODE), + (unw_word_t *)valuep) == UNW_ESUCCESS + ? _UVRSR_OK + : _UVRSR_FAILED; + break; } _LIBUNWIND_ABORT("unsupported register class"); } @@ -964,10 +1129,44 @@ _Unwind_VRS_Pop(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, return _Unwind_VRS_Set(context, _UVRSC_CORE, UNW_ARM_SP, _UVRSD_UINT32, &sp); } + case _UVRSC_PSEUDO: { + if (representation != _UVRSD_UINT32 || discriminator != 0) + return _UVRSR_FAILED; + // Return Address Authentication code (PAC) - discriminator 0 + uint32_t *sp; + if (_Unwind_VRS_Get(context, _UVRSC_CORE, UNW_ARM_SP, _UVRSD_UINT32, + &sp) != _UVRSR_OK) { + return _UVRSR_FAILED; + } + uint32_t pac = *sp++; + _Unwind_VRS_Set(context, _UVRSC_CORE, UNW_ARM_SP, _UVRSD_UINT32, &sp); + return _Unwind_VRS_Set(context, _UVRSC_PSEUDO, 0, _UVRSD_UINT32, &pac); + } } _LIBUNWIND_ABORT("unsupported register class"); } +/// Not used by C++. +/// Unwinds stack, calling "stop" function at each frame. +/// Could be used to implement longjmp(). +_LIBUNWIND_EXPORT _Unwind_Reason_Code +_Unwind_ForcedUnwind(_Unwind_Exception *exception_object, _Unwind_Stop_Fn stop, + void *stop_parameter) { + _LIBUNWIND_TRACE_API("_Unwind_ForcedUnwind(ex_obj=%p, stop=%p)", + (void *)exception_object, (void *)(uintptr_t)stop); + unw_context_t uc; + unw_cursor_t cursor; + __unw_getcontext(&uc); + + // Mark that this is a forced unwind, so _Unwind_Resume() can do + // the right thing. + exception_object->unwinder_cache.reserved1 = (uintptr_t)stop; + exception_object->unwinder_cache.reserved3 = (uintptr_t)stop_parameter; + + return unwind_phase2_forced(&uc, &cursor, exception_object, stop, + stop_parameter); +} + /// Called by personality handler during phase 2 to find the start of the /// function. _LIBUNWIND_EXPORT uintptr_t @@ -997,10 +1196,16 @@ _Unwind_DeleteException(_Unwind_Exception *exception_object) { extern "C" _LIBUNWIND_EXPORT _Unwind_Reason_Code __gnu_unwind_frame(_Unwind_Exception *exception_object, struct _Unwind_Context *context) { + (void)exception_object; unw_cursor_t *cursor = (unw_cursor_t *)context; - if (__unw_step(cursor) != UNW_STEP_SUCCESS) + switch (__unw_step(cursor)) { + case UNW_STEP_SUCCESS: + return _URC_OK; + case UNW_STEP_END: + return _URC_END_OF_STACK; + default: return _URC_FAILURE; - return _URC_OK; + } } #endif // defined(_LIBUNWIND_ARM_EHABI) diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.h index 4ca3f90a81..46c7e3098b 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-EHABI.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------- Unwind-EHABI.hpp ---------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-seh.cpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-seh.cpp index d892c2f4df..0ff679f209 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-seh.cpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-seh.cpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------------- Unwind-seh.cpp ---------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -107,7 +107,7 @@ _GCC_specific_handler(PEXCEPTION_RECORD ms_exc, PVOID frame, PCONTEXT ms_ctx, if (!ctx) { __unw_init_seh(&cursor, disp->ContextRecord); __unw_seh_set_disp_ctx(&cursor, disp); - __unw_set_reg(&cursor, UNW_REG_IP, disp->ControlPc - 1); + __unw_set_reg(&cursor, UNW_REG_IP, disp->ControlPc); ctx = (struct _Unwind_Context *)&cursor; if (!IS_UNWINDING(ms_exc->ExceptionFlags)) { @@ -172,8 +172,8 @@ _GCC_specific_handler(PEXCEPTION_RECORD ms_exc, PVOID frame, PCONTEXT ms_ctx, __unw_get_reg(&cursor, UNW_ARM_R1, &exc->private_[3]); #elif defined(__aarch64__) exc->private_[2] = disp->TargetPc; - __unw_get_reg(&cursor, UNW_ARM64_X0, &retval); - __unw_get_reg(&cursor, UNW_ARM64_X1, &exc->private_[3]); + __unw_get_reg(&cursor, UNW_AARCH64_X0, &retval); + __unw_get_reg(&cursor, UNW_AARCH64_X1, &exc->private_[3]); #endif __unw_get_reg(&cursor, UNW_REG_IP, &target); ms_exc->ExceptionCode = STATUS_GCC_UNWIND; @@ -247,6 +247,7 @@ unwind_phase2_forced(unw_context_t *uc, return _URC_FATAL_PHASE2_ERROR; } +#ifndef NDEBUG // When tracing, print state information. if (_LIBUNWIND_TRACING_UNWINDING) { char functionBuf[512]; @@ -257,11 +258,12 @@ unwind_phase2_forced(unw_context_t *uc, (frameInfo.start_ip + offset > frameInfo.end_ip)) functionName = ".anonymous."; _LIBUNWIND_TRACE_UNWINDING( - "unwind_phase2_forced(ex_ojb=%p): start_ip=0x%" PRIx64 - ", func=%s, lsda=0x%" PRIx64 ", personality=0x%" PRIx64, + "unwind_phase2_forced(ex_ojb=%p): start_ip=0x%" PRIxPTR + ", func=%s, lsda=0x%" PRIxPTR ", personality=0x%" PRIxPTR, (void *)exception_object, frameInfo.start_ip, functionName, frameInfo.lsda, frameInfo.handler); } +#endif // Call stop function at each frame. _Unwind_Action action = diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-sjlj.c b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-sjlj.c index ba9b2dafe6..02c3828229 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-sjlj.c +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind-sjlj.c @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------------- Unwind-sjlj.c ----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindCursor.hpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindCursor.hpp index e2082f0a6e..a575f3702b 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindCursor.hpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindCursor.hpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------- UnwindCursor.hpp ---------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -14,6 +14,7 @@ #ifndef __UNWINDCURSOR_HPP__ #define __UNWINDCURSOR_HPP__ +#include "cet_unwind.h" #include #include #include @@ -26,6 +27,19 @@ #ifdef __APPLE__ #include "mach-o/dyld.h" #endif +#ifdef _AIX +#include +#include +#include +#endif + +#if defined(_LIBUNWIND_TARGET_LINUX) && \ + (defined(_LIBUNWIND_TARGET_AARCH64) || defined(_LIBUNWIND_TARGET_S390X)) +#include +#include +#include +#define _LIBUNWIND_CHECK_LINUX_SIGRETURN 1 +#endif #if defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) // Provide a definition for the DISPATCHER_CONTEXT struct for old (Win7 and @@ -452,6 +466,18 @@ class _LIBUNWIND_HIDDEN AbstractUnwindCursor { #ifdef __arm__ virtual void saveVFPAsX() { _LIBUNWIND_ABORT("saveVFPAsX not implemented"); } #endif + +#ifdef _AIX + virtual uintptr_t getDataRelBase() { + _LIBUNWIND_ABORT("getDataRelBase not implemented"); + } +#endif + +#if defined(_LIBUNWIND_USE_CET) + virtual void *get_registers() { + _LIBUNWIND_ABORT("get_registers not implemented"); + } +#endif }; #if defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) && defined(_WIN32) @@ -495,6 +521,14 @@ class UnwindCursor : public AbstractUnwindCursor { pint_t getLastPC() const { return _dispContext.ControlPc; } void setLastPC(pint_t pc) { _dispContext.ControlPc = pc; } RUNTIME_FUNCTION *lookUpSEHUnwindInfo(pint_t pc, pint_t *base) { +#ifdef __arm__ + // Remove the thumb bit; FunctionEntry ranges don't include the thumb bit. + pc &= ~1U; +#endif + // If pc points exactly at the end of the range, we might resolve the + // next function instead. Decrement pc by 1 to fit inside the current + // function. + pc -= 1; _dispContext.FunctionEntry = RtlLookupFunctionEntry(pc, &_dispContext.ImageBase, _dispContext.HistoryTable); @@ -623,12 +657,12 @@ UnwindCursor::UnwindCursor(unw_context_t *context, A &as) _msContext.D[i - UNW_ARM_D0] = d.w; } #elif defined(_LIBUNWIND_TARGET_AARCH64) - for (int i = UNW_ARM64_X0; i <= UNW_ARM64_X30; ++i) - _msContext.X[i - UNW_ARM64_X0] = r.getRegister(i); + for (int i = UNW_AARCH64_X0; i <= UNW_ARM64_X30; ++i) + _msContext.X[i - UNW_AARCH64_X0] = r.getRegister(i); _msContext.Sp = r.getRegister(UNW_REG_SP); _msContext.Pc = r.getRegister(UNW_REG_IP); - for (int i = UNW_ARM64_D0; i <= UNW_ARM64_D31; ++i) - _msContext.V[i - UNW_ARM64_D0].D[0] = r.getFloatRegister(i); + for (int i = UNW_AARCH64_V0; i <= UNW_ARM64_D31; ++i) + _msContext.V[i - UNW_AARCH64_V0].D[0] = r.getFloatRegister(i); #endif } @@ -651,9 +685,11 @@ bool UnwindCursor::validReg(int regNum) { #if defined(_LIBUNWIND_TARGET_X86_64) if (regNum >= UNW_X86_64_RAX && regNum <= UNW_X86_64_R15) return true; #elif defined(_LIBUNWIND_TARGET_ARM) - if (regNum >= UNW_ARM_R0 && regNum <= UNW_ARM_R15) return true; + if ((regNum >= UNW_ARM_R0 && regNum <= UNW_ARM_R15) || + regNum == UNW_ARM_RA_AUTH_CODE) + return true; #elif defined(_LIBUNWIND_TARGET_AARCH64) - if (regNum >= UNW_ARM64_X0 && regNum <= UNW_ARM64_X30) return true; + if (regNum >= UNW_AARCH64_X0 && regNum <= UNW_ARM64_X30) return true; #endif return false; } @@ -702,7 +738,7 @@ unw_word_t UnwindCursor::getReg(int regNum) { #elif defined(_LIBUNWIND_TARGET_AARCH64) case UNW_REG_SP: return _msContext.Sp; case UNW_REG_IP: return _msContext.Pc; - default: return _msContext.X[regNum - UNW_ARM64_X0]; + default: return _msContext.X[regNum - UNW_AARCH64_X0]; #endif } _LIBUNWIND_ABORT("unsupported register"); @@ -752,37 +788,37 @@ void UnwindCursor::setReg(int regNum, unw_word_t value) { #elif defined(_LIBUNWIND_TARGET_AARCH64) case UNW_REG_SP: _msContext.Sp = value; break; case UNW_REG_IP: _msContext.Pc = value; break; - case UNW_ARM64_X0: - case UNW_ARM64_X1: - case UNW_ARM64_X2: - case UNW_ARM64_X3: - case UNW_ARM64_X4: - case UNW_ARM64_X5: - case UNW_ARM64_X6: - case UNW_ARM64_X7: - case UNW_ARM64_X8: - case UNW_ARM64_X9: - case UNW_ARM64_X10: - case UNW_ARM64_X11: - case UNW_ARM64_X12: - case UNW_ARM64_X13: - case UNW_ARM64_X14: - case UNW_ARM64_X15: - case UNW_ARM64_X16: - case UNW_ARM64_X17: - case UNW_ARM64_X18: - case UNW_ARM64_X19: - case UNW_ARM64_X20: - case UNW_ARM64_X21: - case UNW_ARM64_X22: - case UNW_ARM64_X23: - case UNW_ARM64_X24: - case UNW_ARM64_X25: - case UNW_ARM64_X26: - case UNW_ARM64_X27: - case UNW_ARM64_X28: - case UNW_ARM64_FP: - case UNW_ARM64_LR: _msContext.X[regNum - UNW_ARM64_X0] = value; break; + case UNW_AARCH64_X0: + case UNW_AARCH64_X1: + case UNW_AARCH64_X2: + case UNW_AARCH64_X3: + case UNW_AARCH64_X4: + case UNW_AARCH64_X5: + case UNW_AARCH64_X6: + case UNW_AARCH64_X7: + case UNW_AARCH64_X8: + case UNW_AARCH64_X9: + case UNW_AARCH64_X10: + case UNW_AARCH64_X11: + case UNW_AARCH64_X12: + case UNW_AARCH64_X13: + case UNW_AARCH64_X14: + case UNW_AARCH64_X15: + case UNW_AARCH64_X16: + case UNW_AARCH64_X17: + case UNW_AARCH64_X18: + case UNW_AARCH64_X19: + case UNW_AARCH64_X20: + case UNW_AARCH64_X21: + case UNW_AARCH64_X22: + case UNW_AARCH64_X23: + case UNW_AARCH64_X24: + case UNW_AARCH64_X25: + case UNW_AARCH64_X26: + case UNW_AARCH64_X27: + case UNW_AARCH64_X28: + case UNW_AARCH64_FP: + case UNW_AARCH64_LR: _msContext.X[regNum - UNW_ARM64_X0] = value; break; #endif default: _LIBUNWIND_ABORT("unsupported register"); @@ -795,7 +831,7 @@ bool UnwindCursor::validFloatReg(int regNum) { if (regNum >= UNW_ARM_S0 && regNum <= UNW_ARM_S31) return true; if (regNum >= UNW_ARM_D0 && regNum <= UNW_ARM_D31) return true; #elif defined(_LIBUNWIND_TARGET_AARCH64) - if (regNum >= UNW_ARM64_D0 && regNum <= UNW_ARM64_D31) return true; + if (regNum >= UNW_AARCH64_V0 && regNum <= UNW_ARM64_D31) return true; #else (void)regNum; #endif @@ -823,7 +859,7 @@ unw_fpreg_t UnwindCursor::getFloatReg(int regNum) { } _LIBUNWIND_ABORT("unsupported float register"); #elif defined(_LIBUNWIND_TARGET_AARCH64) - return _msContext.V[regNum - UNW_ARM64_D0].D[0]; + return _msContext.V[regNum - UNW_AARCH64_V0].D[0]; #else (void)regNum; _LIBUNWIND_ABORT("float registers unimplemented"); @@ -838,7 +874,7 @@ void UnwindCursor::setFloatReg(int regNum, unw_fpreg_t value) { uint32_t w; float f; } d; - d.f = value; + d.f = (float)value; _msContext.S[regNum - UNW_ARM_S0] = d.w; } if (regNum >= UNW_ARM_D0 && regNum <= UNW_ARM_D31) { @@ -851,7 +887,7 @@ void UnwindCursor::setFloatReg(int regNum, unw_fpreg_t value) { } _LIBUNWIND_ABORT("unsupported float register"); #elif defined(_LIBUNWIND_TARGET_AARCH64) - _msContext.V[regNum - UNW_ARM64_D0].D[0] = value; + _msContext.V[regNum - UNW_AARCH64_V0].D[0] = value; #else (void)regNum; (void)value; @@ -904,6 +940,14 @@ class UnwindCursor : public AbstractUnwindCursor{ virtual void saveVFPAsX(); #endif +#ifdef _AIX + virtual uintptr_t getDataRelBase(); +#endif + +#if defined(_LIBUNWIND_USE_CET) + virtual void *get_registers() { return &_registers; } +#endif + // libunwind does not and should not depend on C++ library which means that we // need our own defition of inline placement new. static void *operator new(size_t, UnwindCursor *p) { return p; } @@ -928,7 +972,7 @@ class UnwindCursor : public AbstractUnwindCursor{ } #endif -#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) bool setInfoForSigReturn() { R dummy; return setInfoForSigReturn(dummy); @@ -937,8 +981,14 @@ class UnwindCursor : public AbstractUnwindCursor{ R dummy; return stepThroughSigReturn(dummy); } +#if defined(_LIBUNWIND_TARGET_AARCH64) bool setInfoForSigReturn(Registers_arm64 &); int stepThroughSigReturn(Registers_arm64 &); +#endif +#if defined(_LIBUNWIND_TARGET_S390X) + bool setInfoForSigReturn(Registers_s390x &); + int stepThroughSigReturn(Registers_s390x &); +#endif template bool setInfoForSigReturn(Registers &) { return false; } @@ -1023,6 +1073,10 @@ class UnwindCursor : public AbstractUnwindCursor{ int stepWithCompactEncoding(Registers_sparc &) { return UNW_EINVAL; } #endif +#if defined(_LIBUNWIND_TARGET_SPARC64) + int stepWithCompactEncoding(Registers_sparc64 &) { return UNW_EINVAL; } +#endif + #if defined (_LIBUNWIND_TARGET_RISCV) int stepWithCompactEncoding(Registers_riscv &) { return UNW_EINVAL; @@ -1095,6 +1149,12 @@ class UnwindCursor : public AbstractUnwindCursor{ bool compactSaysUseDwarf(Registers_sparc &, uint32_t *) const { return true; } #endif +#if defined(_LIBUNWIND_TARGET_SPARC64) + bool compactSaysUseDwarf(Registers_sparc64 &, uint32_t *) const { + return true; + } +#endif + #if defined (_LIBUNWIND_TARGET_RISCV) bool compactSaysUseDwarf(Registers_riscv &, uint32_t *) const { return true; @@ -1173,12 +1233,24 @@ class UnwindCursor : public AbstractUnwindCursor{ compact_unwind_encoding_t dwarfEncoding(Registers_sparc &) const { return 0; } #endif +#if defined(_LIBUNWIND_TARGET_SPARC64) + compact_unwind_encoding_t dwarfEncoding(Registers_sparc64 &) const { + return 0; + } +#endif + #if defined (_LIBUNWIND_TARGET_RISCV) compact_unwind_encoding_t dwarfEncoding(Registers_riscv &) const { return 0; } #endif +#if defined (_LIBUNWIND_TARGET_S390X) + compact_unwind_encoding_t dwarfEncoding(Registers_s390x &) const { + return 0; + } +#endif + #endif // defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) #if defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) @@ -1195,13 +1267,23 @@ class UnwindCursor : public AbstractUnwindCursor{ int stepWithSEHData() { /* FIXME: Implement */ return 0; } #endif // defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) +#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) + bool getInfoFromTBTable(pint_t pc, R ®isters); + int stepWithTBTable(pint_t pc, tbtable *TBTable, R ®isters, + bool &isSignalFrame); + int stepWithTBTableData() { + return stepWithTBTable(reinterpret_cast(this->getReg(UNW_REG_IP)), + reinterpret_cast(_info.unwind_info), + _registers, _isSignalFrame); + } +#endif // defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) A &_addressSpace; R _registers; unw_proc_info_t _info; bool _unwindInfoMissing; bool _isSignalFrame; -#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) bool _isSigReturn = false; #endif }; @@ -1267,6 +1349,13 @@ template void UnwindCursor::saveVFPAsX() { } #endif +#ifdef _AIX +template +uintptr_t UnwindCursor::getDataRelBase() { + return reinterpret_cast(_info.extra); +} +#endif + template const char *UnwindCursor::getRegisterName(int regNum) { return _registers.getRegisterName(regNum); @@ -1740,14 +1829,16 @@ bool UnwindCursor::getInfoFromCompactEncodingSection(pint_t pc, else funcEnd = firstLevelNextPageFunctionOffset + sects.dso_base; if (pc < funcStart) { - _LIBUNWIND_DEBUG_LOG("malformed __unwind_info, pc=0x%llX not in second " - "level compressed unwind table. funcStart=0x%llX", + _LIBUNWIND_DEBUG_LOG("malformed __unwind_info, pc=0x%llX " + "not in second level compressed unwind table. " + "funcStart=0x%llX", (uint64_t) pc, (uint64_t) funcStart); return false; } if (pc > funcEnd) { - _LIBUNWIND_DEBUG_LOG("malformed __unwind_info, pc=0x%llX not in second " - "level compressed unwind table. funcEnd=0x%llX", + _LIBUNWIND_DEBUG_LOG("malformed __unwind_info, pc=0x%llX " + "not in second level compressed unwind table. " + "funcEnd=0x%llX", (uint64_t) pc, (uint64_t) funcEnd); return false; } @@ -1767,9 +1858,9 @@ bool UnwindCursor::getInfoFromCompactEncodingSection(pint_t pc, pageEncodingIndex * sizeof(uint32_t)); } } else { - _LIBUNWIND_DEBUG_LOG("malformed __unwind_info at 0x%0llX bad second " - "level page", - (uint64_t) sects.compact_unwind_section); + _LIBUNWIND_DEBUG_LOG( + "malformed __unwind_info at 0x%0llX bad second level page", + (uint64_t)sects.compact_unwind_section); return false; } @@ -1885,20 +1976,517 @@ bool UnwindCursor::getInfoFromSEH(pint_t pc) { _info.handler = 0; } } -#elif defined(_LIBUNWIND_TARGET_ARM) - _info.end_ip = _info.start_ip + unwindEntry->FunctionLength; - _info.lsda = 0; // FIXME - _info.handler = 0; // FIXME #endif setLastPC(pc); return true; } #endif +#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) +// Masks for traceback table field xtbtable. +enum xTBTableMask : uint8_t { + reservedBit = 0x02, // The traceback table was incorrectly generated if set + // (see comments in function getInfoFromTBTable(). + ehInfoBit = 0x08 // Exception handling info is present if set +}; + +enum frameType : unw_word_t { + frameWithXLEHStateTable = 0, + frameWithEHInfo = 1 +}; + +extern "C" { +typedef _Unwind_Reason_Code __xlcxx_personality_v0_t(int, _Unwind_Action, + uint64_t, + _Unwind_Exception *, + struct _Unwind_Context *); +__attribute__((__weak__)) __xlcxx_personality_v0_t __xlcxx_personality_v0; +} + +static __xlcxx_personality_v0_t *xlcPersonalityV0; +static RWMutex xlcPersonalityV0InitLock; + +template +bool UnwindCursor::getInfoFromTBTable(pint_t pc, R ®isters) { + uint32_t *p = reinterpret_cast(pc); + + // Keep looking forward until a word of 0 is found. The traceback + // table starts at the following word. + while (*p) + ++p; + tbtable *TBTable = reinterpret_cast(p + 1); + + if (_LIBUNWIND_TRACING_UNWINDING) { + char functionBuf[512]; + const char *functionName = functionBuf; + unw_word_t offset; + if (!getFunctionName(functionBuf, sizeof(functionBuf), &offset)) { + functionName = ".anonymous."; + } + _LIBUNWIND_TRACE_UNWINDING("%s: Look up traceback table of func=%s at %p", + __func__, functionName, + reinterpret_cast(TBTable)); + } + + // If the traceback table does not contain necessary info, bypass this frame. + if (!TBTable->tb.has_tboff) + return false; + + // Structure tbtable_ext contains important data we are looking for. + p = reinterpret_cast(&TBTable->tb_ext); + + // Skip field parminfo if it exists. + if (TBTable->tb.fixedparms || TBTable->tb.floatparms) + ++p; + + // p now points to tb_offset, the offset from start of function to TB table. + unw_word_t start_ip = + reinterpret_cast(TBTable) - *p - sizeof(uint32_t); + unw_word_t end_ip = reinterpret_cast(TBTable); + ++p; + + _LIBUNWIND_TRACE_UNWINDING("start_ip=%p, end_ip=%p\n", + reinterpret_cast(start_ip), + reinterpret_cast(end_ip)); + + // Skip field hand_mask if it exists. + if (TBTable->tb.int_hndl) + ++p; + + unw_word_t lsda = 0; + unw_word_t handler = 0; + unw_word_t flags = frameType::frameWithXLEHStateTable; + + if (TBTable->tb.lang == TB_CPLUSPLUS && TBTable->tb.has_ctl) { + // State table info is available. The ctl_info field indicates the + // number of CTL anchors. There should be only one entry for the C++ + // state table. + assert(*p == 1 && "libunwind: there must be only one ctl_info entry"); + ++p; + // p points to the offset of the state table into the stack. + pint_t stateTableOffset = *p++; + + int framePointerReg; + + // Skip fields name_len and name if exist. + if (TBTable->tb.name_present) { + const uint16_t name_len = *(reinterpret_cast(p)); + p = reinterpret_cast(reinterpret_cast(p) + name_len + + sizeof(uint16_t)); + } + + if (TBTable->tb.uses_alloca) + framePointerReg = *(reinterpret_cast(p)); + else + framePointerReg = 1; // default frame pointer == SP + + _LIBUNWIND_TRACE_UNWINDING( + "framePointerReg=%d, framePointer=%p, " + "stateTableOffset=%#lx\n", + framePointerReg, + reinterpret_cast(_registers.getRegister(framePointerReg)), + stateTableOffset); + lsda = _registers.getRegister(framePointerReg) + stateTableOffset; + + // Since the traceback table generated by the legacy XLC++ does not + // provide the location of the personality for the state table, + // function __xlcxx_personality_v0(), which is the personality for the state + // table and is exported from libc++abi, is directly assigned as the + // handler here. When a legacy XLC++ frame is encountered, the symbol + // is resolved dynamically using dlopen() to avoid hard dependency from + // libunwind on libc++abi. + + // Resolve the function pointer to the state table personality if it has + // not already. + if (xlcPersonalityV0 == NULL) { + xlcPersonalityV0InitLock.lock(); + if (xlcPersonalityV0 == NULL) { + // If libc++abi is statically linked in, symbol __xlcxx_personality_v0 + // has been resolved at the link time. + xlcPersonalityV0 = &__xlcxx_personality_v0; + if (xlcPersonalityV0 == NULL) { + // libc++abi is dynamically linked. Resolve __xlcxx_personality_v0 + // using dlopen(). + const char libcxxabi[] = "libc++abi.a(libc++abi.so.1)"; + void *libHandle; + libHandle = dlopen(libcxxabi, RTLD_MEMBER | RTLD_NOW); + if (libHandle == NULL) { + _LIBUNWIND_TRACE_UNWINDING("dlopen() failed with errno=%d\n", + errno); + assert(0 && "dlopen() failed"); + } + xlcPersonalityV0 = reinterpret_cast<__xlcxx_personality_v0_t *>( + dlsym(libHandle, "__xlcxx_personality_v0")); + if (xlcPersonalityV0 == NULL) { + _LIBUNWIND_TRACE_UNWINDING("dlsym() failed with errno=%d\n", errno); + assert(0 && "dlsym() failed"); + } + dlclose(libHandle); + } + } + xlcPersonalityV0InitLock.unlock(); + } + handler = reinterpret_cast(xlcPersonalityV0); + _LIBUNWIND_TRACE_UNWINDING("State table: LSDA=%p, Personality=%p\n", + reinterpret_cast(lsda), + reinterpret_cast(handler)); + } else if (TBTable->tb.longtbtable) { + // This frame has the traceback table extension. Possible cases are + // 1) a C++ frame that has the 'eh_info' structure; 2) a C++ frame that + // is not EH aware; or, 3) a frame of other languages. We need to figure out + // if the traceback table extension contains the 'eh_info' structure. + // + // We also need to deal with the complexity arising from some XL compiler + // versions use the wrong ordering of 'longtbtable' and 'has_vec' bits + // where the 'longtbtable' bit is meant to be the 'has_vec' bit and vice + // versa. For frames of code generated by those compilers, the 'longtbtable' + // bit may be set but there isn't really a traceback table extension. + // + // In , there is the following definition of + // 'struct tbtable_ext'. It is not really a structure but a dummy to + // collect the description of optional parts of the traceback table. + // + // struct tbtable_ext { + // ... + // char alloca_reg; /* Register for alloca automatic storage */ + // struct vec_ext vec_ext; /* Vector extension (if has_vec is set) */ + // unsigned char xtbtable; /* More tbtable fields, if longtbtable is set*/ + // }; + // + // Depending on how the 'has_vec'/'longtbtable' bit is interpreted, the data + // following 'alloca_reg' can be treated either as 'struct vec_ext' or + // 'unsigned char xtbtable'. 'xtbtable' bits are defined in + // as flags. The 7th bit '0x02' is currently + // unused and should not be set. 'struct vec_ext' is defined in + // as follows: + // + // struct vec_ext { + // unsigned vr_saved:6; /* Number of non-volatile vector regs saved + // */ + // /* first register saved is assumed to be */ + // /* 32 - vr_saved */ + // unsigned saves_vrsave:1; /* Set if vrsave is saved on the stack */ + // unsigned has_varargs:1; + // ... + // }; + // + // Here, the 7th bit is used as 'saves_vrsave'. To determine whether it + // is 'struct vec_ext' or 'xtbtable' that follows 'alloca_reg', + // we checks if the 7th bit is set or not because 'xtbtable' should + // never have the 7th bit set. The 7th bit of 'xtbtable' will be reserved + // in the future to make sure the mitigation works. This mitigation + // is not 100% bullet proof because 'struct vec_ext' may not always have + // 'saves_vrsave' bit set. + // + // 'reservedBit' is defined in enum 'xTBTableMask' above as the mask for + // checking the 7th bit. + + // p points to field name len. + uint8_t *charPtr = reinterpret_cast(p); + + // Skip fields name_len and name if they exist. + if (TBTable->tb.name_present) { + const uint16_t name_len = *(reinterpret_cast(charPtr)); + charPtr = charPtr + name_len + sizeof(uint16_t); + } + + // Skip field alloc_reg if it exists. + if (TBTable->tb.uses_alloca) + ++charPtr; + + // Check traceback table bit has_vec. Skip struct vec_ext if it exists. + if (TBTable->tb.has_vec) + // Note struct vec_ext does exist at this point because whether the + // ordering of longtbtable and has_vec bits is correct or not, both + // are set. + charPtr += sizeof(struct vec_ext); + + // charPtr points to field 'xtbtable'. Check if the EH info is available. + // Also check if the reserved bit of the extended traceback table field + // 'xtbtable' is set. If it is, the traceback table was incorrectly + // generated by an XL compiler that uses the wrong ordering of 'longtbtable' + // and 'has_vec' bits and this is in fact 'struct vec_ext'. So skip the + // frame. + if ((*charPtr & xTBTableMask::ehInfoBit) && + !(*charPtr & xTBTableMask::reservedBit)) { + // Mark this frame has the new EH info. + flags = frameType::frameWithEHInfo; + + // eh_info is available. + charPtr++; + // The pointer is 4-byte aligned. + if (reinterpret_cast(charPtr) % 4) + charPtr += 4 - reinterpret_cast(charPtr) % 4; + uintptr_t *ehInfo = + reinterpret_cast(*(reinterpret_cast( + registers.getRegister(2) + + *(reinterpret_cast(charPtr))))); + + // ehInfo points to structure en_info. The first member is version. + // Only version 0 is currently supported. + assert(*(reinterpret_cast(ehInfo)) == 0 && + "libunwind: ehInfo version other than 0 is not supported"); + + // Increment ehInfo to point to member lsda. + ++ehInfo; + lsda = *ehInfo++; + + // enInfo now points to member personality. + handler = *ehInfo; + + _LIBUNWIND_TRACE_UNWINDING("Range table: LSDA=%#lx, Personality=%#lx\n", + lsda, handler); + } + } + + _info.start_ip = start_ip; + _info.end_ip = end_ip; + _info.lsda = lsda; + _info.handler = handler; + _info.gp = 0; + _info.flags = flags; + _info.format = 0; + _info.unwind_info = reinterpret_cast(TBTable); + _info.unwind_info_size = 0; + _info.extra = registers.getRegister(2); + + return true; +} + +// Step back up the stack following the frame back link. +template +int UnwindCursor::stepWithTBTable(pint_t pc, tbtable *TBTable, + R ®isters, bool &isSignalFrame) { + if (_LIBUNWIND_TRACING_UNWINDING) { + char functionBuf[512]; + const char *functionName = functionBuf; + unw_word_t offset; + if (!getFunctionName(functionBuf, sizeof(functionBuf), &offset)) { + functionName = ".anonymous."; + } + _LIBUNWIND_TRACE_UNWINDING("%s: Look up traceback table of func=%s at %p", + __func__, functionName, + reinterpret_cast(TBTable)); + } + +#if defined(__powerpc64__) + // Instruction to reload TOC register "l r2,40(r1)" + const uint32_t loadTOCRegInst = 0xe8410028; + const int32_t unwPPCF0Index = UNW_PPC64_F0; + const int32_t unwPPCV0Index = UNW_PPC64_V0; +#else + // Instruction to reload TOC register "l r2,20(r1)" + const uint32_t loadTOCRegInst = 0x80410014; + const int32_t unwPPCF0Index = UNW_PPC_F0; + const int32_t unwPPCV0Index = UNW_PPC_V0; +#endif + + R newRegisters = registers; + + // lastStack points to the stack frame of the next routine up. + pint_t lastStack = *(reinterpret_cast(registers.getSP())); + + // Return address is the address after call site instruction. + pint_t returnAddress; + + if (isSignalFrame) { + _LIBUNWIND_TRACE_UNWINDING("Possible signal handler frame: lastStack=%p", + reinterpret_cast(lastStack)); + + sigcontext *sigContext = reinterpret_cast( + reinterpret_cast(lastStack) + STKMIN); + returnAddress = sigContext->sc_jmpbuf.jmp_context.iar; + + _LIBUNWIND_TRACE_UNWINDING("From sigContext=%p, returnAddress=%p\n", + reinterpret_cast(sigContext), + reinterpret_cast(returnAddress)); + + if (returnAddress < 0x10000000) { + // Try again using STKMINALIGN + sigContext = reinterpret_cast( + reinterpret_cast(lastStack) + STKMINALIGN); + returnAddress = sigContext->sc_jmpbuf.jmp_context.iar; + if (returnAddress < 0x10000000) { + _LIBUNWIND_TRACE_UNWINDING("Bad returnAddress=%p\n", + reinterpret_cast(returnAddress)); + return UNW_EBADFRAME; + } else { + _LIBUNWIND_TRACE_UNWINDING("Tried again using STKMINALIGN: " + "sigContext=%p, returnAddress=%p. " + "Seems to be a valid address\n", + reinterpret_cast(sigContext), + reinterpret_cast(returnAddress)); + } + } + // Restore the condition register from sigcontext. + newRegisters.setCR(sigContext->sc_jmpbuf.jmp_context.cr); + + // Restore GPRs from sigcontext. + for (int i = 0; i < 32; ++i) + newRegisters.setRegister(i, sigContext->sc_jmpbuf.jmp_context.gpr[i]); + + // Restore FPRs from sigcontext. + for (int i = 0; i < 32; ++i) + newRegisters.setFloatRegister(i + unwPPCF0Index, + sigContext->sc_jmpbuf.jmp_context.fpr[i]); + + // Restore vector registers if there is an associated extended context + // structure. + if (sigContext->sc_jmpbuf.jmp_context.msr & __EXTCTX) { + ucontext_t *uContext = reinterpret_cast(sigContext); + if (uContext->__extctx->__extctx_magic == __EXTCTX_MAGIC) { + for (int i = 0; i < 32; ++i) + newRegisters.setVectorRegister( + i + unwPPCV0Index, *(reinterpret_cast( + &(uContext->__extctx->__vmx.__vr[i])))); + } + } + } else { + // Step up a normal frame. + returnAddress = reinterpret_cast(lastStack)[2]; + + _LIBUNWIND_TRACE_UNWINDING("Extract info from lastStack=%p, " + "returnAddress=%p\n", + reinterpret_cast(lastStack), + reinterpret_cast(returnAddress)); + _LIBUNWIND_TRACE_UNWINDING("fpr_regs=%d, gpr_regs=%d, saves_cr=%d\n", + TBTable->tb.fpr_saved, TBTable->tb.gpr_saved, + TBTable->tb.saves_cr); + + // Restore FP registers. + char *ptrToRegs = reinterpret_cast(lastStack); + double *FPRegs = reinterpret_cast( + ptrToRegs - (TBTable->tb.fpr_saved * sizeof(double))); + for (int i = 0; i < TBTable->tb.fpr_saved; ++i) + newRegisters.setFloatRegister( + 32 - TBTable->tb.fpr_saved + i + unwPPCF0Index, FPRegs[i]); + + // Restore GP registers. + ptrToRegs = reinterpret_cast(FPRegs); + uintptr_t *GPRegs = reinterpret_cast( + ptrToRegs - (TBTable->tb.gpr_saved * sizeof(uintptr_t))); + for (int i = 0; i < TBTable->tb.gpr_saved; ++i) + newRegisters.setRegister(32 - TBTable->tb.gpr_saved + i, GPRegs[i]); + + // Restore Vector registers. + ptrToRegs = reinterpret_cast(GPRegs); + + // Restore vector registers only if this is a Clang frame. Also + // check if traceback table bit has_vec is set. If it is, structure + // vec_ext is available. + if (_info.flags == frameType::frameWithEHInfo && TBTable->tb.has_vec) { + + // Get to the vec_ext structure to check if vector registers are saved. + uint32_t *p = reinterpret_cast(&TBTable->tb_ext); + + // Skip field parminfo if exists. + if (TBTable->tb.fixedparms || TBTable->tb.floatparms) + ++p; + + // Skip field tb_offset if exists. + if (TBTable->tb.has_tboff) + ++p; + + // Skip field hand_mask if exists. + if (TBTable->tb.int_hndl) + ++p; + + // Skip fields ctl_info and ctl_info_disp if exist. + if (TBTable->tb.has_ctl) { + // Skip field ctl_info. + ++p; + // Skip field ctl_info_disp. + ++p; + } + + // Skip fields name_len and name if exist. + // p is supposed to point to field name_len now. + uint8_t *charPtr = reinterpret_cast(p); + if (TBTable->tb.name_present) { + const uint16_t name_len = *(reinterpret_cast(charPtr)); + charPtr = charPtr + name_len + sizeof(uint16_t); + } + + // Skip field alloc_reg if it exists. + if (TBTable->tb.uses_alloca) + ++charPtr; + + struct vec_ext *vec_ext = reinterpret_cast(charPtr); + + _LIBUNWIND_TRACE_UNWINDING("vr_saved=%d\n", vec_ext->vr_saved); + + // Restore vector register(s) if saved on the stack. + if (vec_ext->vr_saved) { + // Saved vector registers are 16-byte aligned. + if (reinterpret_cast(ptrToRegs) % 16) + ptrToRegs -= reinterpret_cast(ptrToRegs) % 16; + v128 *VecRegs = reinterpret_cast(ptrToRegs - vec_ext->vr_saved * + sizeof(v128)); + for (int i = 0; i < vec_ext->vr_saved; ++i) { + newRegisters.setVectorRegister( + 32 - vec_ext->vr_saved + i + unwPPCV0Index, VecRegs[i]); + } + } + } + if (TBTable->tb.saves_cr) { + // Get the saved condition register. The condition register is only + // a single word. + newRegisters.setCR( + *(reinterpret_cast(lastStack + sizeof(uintptr_t)))); + } + + // Restore the SP. + newRegisters.setSP(lastStack); + + // The first instruction after return. + uint32_t firstInstruction = *(reinterpret_cast(returnAddress)); + + // Do we need to set the TOC register? + _LIBUNWIND_TRACE_UNWINDING( + "Current gpr2=%p\n", + reinterpret_cast(newRegisters.getRegister(2))); + if (firstInstruction == loadTOCRegInst) { + _LIBUNWIND_TRACE_UNWINDING( + "Set gpr2=%p from frame\n", + reinterpret_cast(reinterpret_cast(lastStack)[5])); + newRegisters.setRegister(2, reinterpret_cast(lastStack)[5]); + } + } + _LIBUNWIND_TRACE_UNWINDING("lastStack=%p, returnAddress=%p, pc=%p\n", + reinterpret_cast(lastStack), + reinterpret_cast(returnAddress), + reinterpret_cast(pc)); + + // The return address is the address after call site instruction, so + // setting IP to that simualates a return. + newRegisters.setIP(reinterpret_cast(returnAddress)); + + // Simulate the step by replacing the register set with the new ones. + registers = newRegisters; + + // Check if the next frame is a signal frame. + pint_t nextStack = *(reinterpret_cast(registers.getSP())); + + // Return address is the address after call site instruction. + pint_t nextReturnAddress = reinterpret_cast(nextStack)[2]; + + if (nextReturnAddress > 0x01 && nextReturnAddress < 0x10000) { + _LIBUNWIND_TRACE_UNWINDING("The next is a signal handler frame: " + "nextStack=%p, next return address=%p\n", + reinterpret_cast(nextStack), + reinterpret_cast(nextReturnAddress)); + isSignalFrame = true; + } else { + isSignalFrame = false; + } + + return UNW_STEP_SUCCESS; +} +#endif // defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) template void UnwindCursor::setInfoBasedOnIPRegister(bool isReturnAddress) { -#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) _isSigReturn = false; #endif @@ -1921,7 +2509,14 @@ void UnwindCursor::setInfoBasedOnIPRegister(bool isReturnAddress) { // To disambiguate this, back up the pc when we know it is a return // address. if (isReturnAddress) +#if defined(_AIX) + // PC needs to be a 4-byte aligned address to be able to look for a + // word of 0 that indicates the start of the traceback table at the end + // of a function on AIX. + pc -= 4; +#else --pc; +#endif // Ask address space object to find unwind sections for this pc. UnwindInfoSections sects; @@ -1955,6 +2550,12 @@ void UnwindCursor::setInfoBasedOnIPRegister(bool isReturnAddress) { return; #endif +#if defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) + // If there is unwind info in the traceback table, look there next. + if (this->getInfoFromTBTable(pc, _registers)) + return; +#endif + #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) // If there is dwarf unwind info, look there next. if (sects.dwarf_section != 0) { @@ -2000,7 +2601,7 @@ void UnwindCursor::setInfoBasedOnIPRegister(bool isReturnAddress) { } #endif // #if defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) -#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) if (setInfoForSigReturn()) return; #endif @@ -2009,7 +2610,8 @@ void UnwindCursor::setInfoBasedOnIPRegister(bool isReturnAddress) { _unwindInfoMissing = true; } -#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) && \ + defined(_LIBUNWIND_TARGET_AARCH64) template bool UnwindCursor::setInfoForSigReturn(Registers_arm64 &) { // Look for the sigreturn trampoline. The trampoline's body is two @@ -2028,14 +2630,28 @@ bool UnwindCursor::setInfoForSigReturn(Registers_arm64 &) { // // [1] https://github.com/torvalds/linux/blob/master/arch/arm64/kernel/vdso/sigreturn.S const pint_t pc = static_cast(this->getReg(UNW_REG_IP)); + // The PC might contain an invalid address if the unwind info is bad, so + // directly accessing it could cause a segfault. Use process_vm_readv to read + // the memory safely instead. process_vm_readv was added in Linux 3.2, and + // AArch64 supported was added in Linux 3.7, so the syscall is guaranteed to + // be present. Unfortunately, there are Linux AArch64 environments where the + // libc wrapper for the syscall might not be present (e.g. Android 5), so call + // the syscall directly instead. + uint32_t instructions[2]; + struct iovec local_iov = {&instructions, sizeof instructions}; + struct iovec remote_iov = {reinterpret_cast(pc), sizeof instructions}; + long bytesRead = + syscall(SYS_process_vm_readv, getpid(), &local_iov, 1, &remote_iov, 1, 0); // Look for instructions: mov x8, #0x8b; svc #0x0 - if (_addressSpace.get32(pc) == 0xd2801168 && - _addressSpace.get32(pc + 4) == 0xd4000001) { - _info = {}; - _isSigReturn = true; - return true; - } - return false; + if (bytesRead != sizeof instructions || instructions[0] != 0xd2801168 || + instructions[1] != 0xd4000001) + return false; + + _info = {}; + _info.start_ip = pc; + _info.end_ip = pc + 4; + _isSigReturn = true; + return true; } template @@ -2062,14 +2678,121 @@ int UnwindCursor::stepThroughSigReturn(Registers_arm64 &) { for (int i = 0; i <= 30; ++i) { uint64_t value = _addressSpace.get64(sigctx + kOffsetGprs + static_cast(i * 8)); - _registers.setRegister(UNW_ARM64_X0 + i, value); + _registers.setRegister(UNW_AARCH64_X0 + i, value); } _registers.setSP(_addressSpace.get64(sigctx + kOffsetSp)); _registers.setIP(_addressSpace.get64(sigctx + kOffsetPc)); _isSignalFrame = true; return UNW_STEP_SUCCESS; } -#endif // defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#endif // defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) && + // defined(_LIBUNWIND_TARGET_AARCH64) + +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) && \ + defined(_LIBUNWIND_TARGET_S390X) +template +bool UnwindCursor::setInfoForSigReturn(Registers_s390x &) { + // Look for the sigreturn trampoline. The trampoline's body is a + // specific instruction (see below). Typically the trampoline comes from the + // vDSO (i.e. the __kernel_[rt_]sigreturn function). A libc might provide its + // own restorer function, though, or user-mode QEMU might write a trampoline + // onto the stack. + const pint_t pc = static_cast(this->getReg(UNW_REG_IP)); + // The PC might contain an invalid address if the unwind info is bad, so + // directly accessing it could cause a segfault. Use process_vm_readv to + // read the memory safely instead. + uint16_t inst; + struct iovec local_iov = {&inst, sizeof inst}; + struct iovec remote_iov = {reinterpret_cast(pc), sizeof inst}; + long bytesRead = process_vm_readv(getpid(), &local_iov, 1, &remote_iov, 1, 0); + if (bytesRead == sizeof inst && (inst == 0x0a77 || inst == 0x0aad)) { + _info = {}; + _info.start_ip = pc; + _info.end_ip = pc + 2; + _isSigReturn = true; + return true; + } + return false; +} + +template +int UnwindCursor::stepThroughSigReturn(Registers_s390x &) { + // Determine current SP. + const pint_t sp = static_cast(this->getReg(UNW_REG_SP)); + // According to the s390x ABI, the CFA is at (incoming) SP + 160. + const pint_t cfa = sp + 160; + + // Determine current PC and instruction there (this must be either + // a "svc __NR_sigreturn" or "svc __NR_rt_sigreturn"). + const pint_t pc = static_cast(this->getReg(UNW_REG_IP)); + const uint16_t inst = _addressSpace.get16(pc); + + // Find the addresses of the signo and sigcontext in the frame. + pint_t pSigctx = 0; + pint_t pSigno = 0; + + // "svc __NR_sigreturn" uses a non-RT signal trampoline frame. + if (inst == 0x0a77) { + // Layout of a non-RT signal trampoline frame, starting at the CFA: + // - 8-byte signal mask + // - 8-byte pointer to sigcontext, followed by signo + // - 4-byte signo + pSigctx = _addressSpace.get64(cfa + 8); + pSigno = pSigctx + 344; + } + + // "svc __NR_rt_sigreturn" uses a RT signal trampoline frame. + if (inst == 0x0aad) { + // Layout of a RT signal trampoline frame, starting at the CFA: + // - 8-byte retcode (+ alignment) + // - 128-byte siginfo struct (starts with signo) + // - ucontext struct: + // - 8-byte long (uc_flags) + // - 8-byte pointer (uc_link) + // - 24-byte stack_t + // - 8 bytes of padding because sigcontext has 16-byte alignment + // - sigcontext/mcontext_t + pSigctx = cfa + 8 + 128 + 8 + 8 + 24 + 8; + pSigno = cfa + 8; + } + + assert(pSigctx != 0); + assert(pSigno != 0); + + // Offsets from sigcontext to each register. + const pint_t kOffsetPc = 8; + const pint_t kOffsetGprs = 16; + const pint_t kOffsetFprs = 216; + + // Restore all registers. + for (int i = 0; i < 16; ++i) { + uint64_t value = _addressSpace.get64(pSigctx + kOffsetGprs + + static_cast(i * 8)); + _registers.setRegister(UNW_S390X_R0 + i, value); + } + for (int i = 0; i < 16; ++i) { + static const int fpr[16] = { + UNW_S390X_F0, UNW_S390X_F1, UNW_S390X_F2, UNW_S390X_F3, + UNW_S390X_F4, UNW_S390X_F5, UNW_S390X_F6, UNW_S390X_F7, + UNW_S390X_F8, UNW_S390X_F9, UNW_S390X_F10, UNW_S390X_F11, + UNW_S390X_F12, UNW_S390X_F13, UNW_S390X_F14, UNW_S390X_F15 + }; + double value = _addressSpace.getDouble(pSigctx + kOffsetFprs + + static_cast(i * 8)); + _registers.setFloatRegister(fpr[i], value); + } + _registers.setIP(_addressSpace.get64(pSigctx + kOffsetPc)); + + // SIGILL, SIGFPE and SIGTRAP are delivered with psw_addr + // after the faulting instruction rather than before it. + // Do not set _isSignalFrame in that case. + uint32_t signo = _addressSpace.get32(pSigno); + _isSignalFrame = (signo != 4 && signo != 5 && signo != 8); + + return UNW_STEP_SUCCESS; +} +#endif // defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) && + // defined(_LIBUNWIND_TARGET_S390X) template int UnwindCursor::step() { @@ -2079,7 +2802,7 @@ int UnwindCursor::step() { // Use unwinding info to modify register set as if function returned. int result; -#if defined(_LIBUNWIND_TARGET_LINUX) && defined(_LIBUNWIND_TARGET_AARCH64) +#if defined(_LIBUNWIND_CHECK_LINUX_SIGRETURN) if (_isSigReturn) { result = this->stepThroughSigReturn(); } else @@ -2089,6 +2812,8 @@ int UnwindCursor::step() { result = this->stepWithCompactEncoding(); #elif defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) result = this->stepWithSEHData(); +#elif defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) + result = this->stepWithTBTableData(); #elif defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) result = this->stepWithDwarfFDE(); #elif defined(_LIBUNWIND_ARM_EHABI) @@ -2126,6 +2851,12 @@ bool UnwindCursor::getFunctionName(char *buf, size_t bufLen, buf, bufLen, offset); } +#if defined(_LIBUNWIND_USE_CET) +extern "C" void *__libunwind_cet_get_registers(unw_cursor_t *cursor) { + AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor; + return co->get_registers(); +} +#endif } // namespace libunwind #endif // __UNWINDCURSOR_HPP__ diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1-gcc-ext.c b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1-gcc-ext.c index b88de202a8..371b75fbb9 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1-gcc-ext.c +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1-gcc-ext.c @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------- UnwindLevel1-gcc-ext.c -------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -28,31 +28,24 @@ #if defined(_LIBUNWIND_BUILD_ZERO_COST_APIS) #if defined(_LIBUNWIND_SUPPORT_SEH_UNWIND) -#define private_1 private_[0] +#define PRIVATE_1 private_[0] +#elif defined(_LIBUNWIND_ARM_EHABI) +#define PRIVATE_1 unwinder_cache.reserved1 +#else +#define PRIVATE_1 private_1 #endif /// Called by __cxa_rethrow(). _LIBUNWIND_EXPORT _Unwind_Reason_Code _Unwind_Resume_or_Rethrow(_Unwind_Exception *exception_object) { -#if defined(_LIBUNWIND_ARM_EHABI) - _LIBUNWIND_TRACE_API("_Unwind_Resume_or_Rethrow(ex_obj=%p), private_1=%ld", - (void *)exception_object, - (long)exception_object->unwinder_cache.reserved1); -#else - _LIBUNWIND_TRACE_API("_Unwind_Resume_or_Rethrow(ex_obj=%p), private_1=%" PRIdPTR, - (void *)exception_object, - (intptr_t)exception_object->private_1); -#endif + _LIBUNWIND_TRACE_API( + "_Unwind_Resume_or_Rethrow(ex_obj=%p), private_1=%" PRIdPTR, + (void *)exception_object, (intptr_t)exception_object->PRIVATE_1); -#if defined(_LIBUNWIND_ARM_EHABI) - // _Unwind_RaiseException on EHABI will always set the reserved1 field to 0, - // which is in the same position as private_1 below. - return _Unwind_RaiseException(exception_object); -#else // If this is non-forced and a stopping place was found, then this is a // re-throw. // Call _Unwind_RaiseException() as if this was a new exception - if (exception_object->private_1 == 0) { + if (exception_object->PRIVATE_1 == 0) { return _Unwind_RaiseException(exception_object); // Will return if there is no catch clause, so that __cxa_rethrow can call // std::terminate(). @@ -63,20 +56,21 @@ _Unwind_Resume_or_Rethrow(_Unwind_Exception *exception_object) { _Unwind_Resume(exception_object); _LIBUNWIND_ABORT("_Unwind_Resume_or_Rethrow() called _Unwind_RaiseException()" " which unexpectedly returned"); -#endif } - /// Called by personality handler during phase 2 to get base address for data /// relative encodings. _LIBUNWIND_EXPORT uintptr_t _Unwind_GetDataRelBase(struct _Unwind_Context *context) { - (void)context; _LIBUNWIND_TRACE_API("_Unwind_GetDataRelBase(context=%p)", (void *)context); +#if defined(_AIX) + return unw_get_data_rel_base((unw_cursor_t *)context); +#else + (void)context; _LIBUNWIND_ABORT("_Unwind_GetDataRelBase() not implemented"); +#endif } - /// Called by personality handler during phase 2 to get base address for text /// relative encodings. _LIBUNWIND_EXPORT uintptr_t @@ -121,7 +115,7 @@ _Unwind_Backtrace(_Unwind_Trace_Fn callback, void *ref) { // Create a mock exception object for force unwinding. _Unwind_Exception ex; memset(&ex, '\0', sizeof(ex)); - ex.exception_class = 0x434C4E47554E5700; // CLNGUNW\0 + strcpy((char *)&ex.exception_class, "CLNGUNW"); #endif // walk each frame diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1.c b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1.c index e9d9aeae83..1f20260d64 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1.c +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindLevel1.c @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------- UnwindLevel1.c -----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -28,6 +28,7 @@ #include #include +#include "cet_unwind.h" #include "config.h" #include "libunwind.h" #include "libunwind_ext.h" @@ -37,6 +38,42 @@ #ifndef _LIBUNWIND_SUPPORT_SEH_UNWIND +// When CET is enabled, each "call" instruction will push return address to +// CET shadow stack, each "ret" instruction will pop current CET shadow stack +// top and compare it with target address which program will return. +// In exception handing, some stack frames will be skipped before jumping to +// landing pad and we must adjust CET shadow stack accordingly. +// _LIBUNWIND_POP_CET_SSP is used to adjust CET shadow stack pointer and we +// directly jump to __libunwind_Registerts_x86/x86_64_jumpto instead of using +// a regular function call to avoid pushing to CET shadow stack again. +#if !defined(_LIBUNWIND_USE_CET) +#define __unw_phase2_resume(cursor, fn) \ + do { \ + (void)fn; \ + __unw_resume((cursor)); \ + } while (0) +#elif defined(_LIBUNWIND_TARGET_I386) +#define __unw_phase2_resume(cursor, fn) \ + do { \ + _LIBUNWIND_POP_CET_SSP((fn)); \ + void *cetRegContext = __libunwind_cet_get_registers((cursor)); \ + void *cetJumpAddress = __libunwind_cet_get_jump_target(); \ + __asm__ volatile("push %%edi\n\t" \ + "sub $4, %%esp\n\t" \ + "jmp *%%edx\n\t" :: "D"(cetRegContext), \ + "d"(cetJumpAddress)); \ + } while (0) +#elif defined(_LIBUNWIND_TARGET_X86_64) +#define __unw_phase2_resume(cursor, fn) \ + do { \ + _LIBUNWIND_POP_CET_SSP((fn)); \ + void *cetRegContext = __libunwind_cet_get_registers((cursor)); \ + void *cetJumpAddress = __libunwind_cet_get_jump_target(); \ + __asm__ volatile("jmpq *%%rdx\n\t" :: "D"(cetRegContext), \ + "d"(cetJumpAddress)); \ + } while (0) +#endif + static _Unwind_Reason_Code unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *exception_object) { __unw_init_local(cursor, uc); @@ -71,6 +108,7 @@ unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except return _URC_FATAL_PHASE1_ERROR; } +#ifndef NDEBUG // When tracing, print state information. if (_LIBUNWIND_TRACING_UNWINDING) { char functionBuf[512]; @@ -88,6 +126,7 @@ unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except (void *)exception_object, pc, frameInfo.start_ip, functionName, frameInfo.lsda, frameInfo.handler); } +#endif // If there is a personality routine, ask it if it will want to stop at // this frame. @@ -138,6 +177,9 @@ unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except _LIBUNWIND_TRACE_UNWINDING("unwind_phase2(ex_ojb=%p)", (void *)exception_object); + // uc is initialized by __unw_getcontext in the parent frame. The first stack + // frame walked is unwind_phase2. + unsigned framesWalked = 1; // Walk each frame until we reach where search phase said to stop. while (true) { @@ -170,6 +212,7 @@ unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except return _URC_FATAL_PHASE2_ERROR; } +#ifndef NDEBUG // When tracing, print state information. if (_LIBUNWIND_TRACING_UNWINDING) { char functionBuf[512]; @@ -186,7 +229,9 @@ unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except functionName, sp, frameInfo.lsda, frameInfo.handler); } +#endif + ++framesWalked; // If there is a personality routine, tell it we are unwinding. if (frameInfo.handler != 0) { _Unwind_Personality_Fn p = @@ -226,8 +271,9 @@ unwind_phase2(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *except ", sp=0x%" PRIxPTR, (void *)exception_object, pc, sp); } - __unw_resume(cursor); - // __unw_resume() only returns if there was an error. + + __unw_phase2_resume(cursor, framesWalked); + // __unw_phase2_resume() only returns if there was an error. return _URC_FATAL_PHASE2_ERROR; default: // Personality routine returned an unknown result code. @@ -249,6 +295,9 @@ unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Stop_Fn stop, void *stop_parameter) { __unw_init_local(cursor, uc); + // uc is initialized by __unw_getcontext in the parent frame. The first stack + // frame walked is unwind_phase2_forced. + unsigned framesWalked = 1; // Walk each frame until we reach where search phase said to stop while (__unw_step(cursor) > 0) { @@ -261,6 +310,7 @@ unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor, return _URC_FATAL_PHASE2_ERROR; } +#ifndef NDEBUG // When tracing, print state information. if (_LIBUNWIND_TRACING_UNWINDING) { char functionBuf[512]; @@ -276,6 +326,7 @@ unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor, (void *)exception_object, frameInfo.start_ip, functionName, frameInfo.lsda, frameInfo.handler); } +#endif // Call stop function at each frame. _Unwind_Action action = @@ -293,6 +344,7 @@ unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor, return _URC_FATAL_PHASE2_ERROR; } + ++framesWalked; // If there is a personality routine, tell it we are unwinding. if (frameInfo.handler != 0) { _Unwind_Personality_Fn p = @@ -317,7 +369,7 @@ unwind_phase2_forced(unw_context_t *uc, unw_cursor_t *cursor, "_URC_INSTALL_CONTEXT", (void *)exception_object); // We may get control back if landing pad calls _Unwind_Resume(). - __unw_resume(cursor); + __unw_phase2_resume(cursor, framesWalked); break; default: // Personality routine returned an unknown result code. @@ -435,11 +487,13 @@ _Unwind_GetLanguageSpecificData(struct _Unwind_Context *context) { _LIBUNWIND_TRACE_API( "_Unwind_GetLanguageSpecificData(context=%p) => 0x%" PRIxPTR, (void *)context, result); +#if !defined(_LIBUNWIND_SUPPORT_TBTAB_UNWIND) if (result != 0) { if (*((uint8_t *)result) != 0xFF) _LIBUNWIND_DEBUG_LOG("lsda at 0x%" PRIxPTR " does not start with 0xFF", result); } +#endif return result; } diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersRestore.S b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersRestore.S index 239d4105c4..4aaf6d3964 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersRestore.S +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersRestore.S @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===-------------------- UnwindRegistersRestore.S ------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -11,7 +11,11 @@ #include "assembly.h" +#if defined(_AIX) + .toc +#else .text +#endif #if !defined(__USING_SJLJ_EXCEPTIONS__) @@ -28,6 +32,8 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_x86_jumpto) # + return address + # +-----------------------+ <-- SP # + + + + _LIBUNWIND_CET_ENDBR movl 4(%esp), %eax # set up eax and ret on new stack location movl 28(%eax), %edx # edx holds new stack pointer @@ -49,7 +55,8 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_x86_jumpto) # skip ss # skip eflags pop %eax # eax was already pushed on new stack - ret # eip was already pushed on new stack + pop %ecx + jmp *%ecx # skip cs # skip ds # skip es @@ -73,6 +80,7 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_x86_64_jumpto) # On entry, thread_state pointer is in rdi #endif + _LIBUNWIND_CET_ENDBR movq 56(%rdi), %rax # rax holds new stack pointer subq $16, %rax movq %rax, 56(%rdi) @@ -122,7 +130,8 @@ DEFINE_LIBUNWIND_FUNCTION(__libunwind_Registers_x86_64_jumpto) #endif movq 56(%rdi), %rsp # cut back rsp to new location pop %rdi # rdi was saved here earlier - ret # rip was saved here + pop %rcx + jmpq *%rcx #elif defined(__powerpc64__) @@ -137,7 +146,7 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_ppc646jumptoEv) // load register (GPR) #define PPC64_LR(n) \ - ld %r##n, (8 * (n + 2))(%r3) + ld n, (8 * (n + 2))(3) // restore integral registers // skip r0 for now @@ -179,12 +188,12 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_ppc646jumptoEv) // (note that this also restores floating point registers and V registers, // because part of VS is mapped to these registers) - addi %r4, %r3, PPC64_OFFS_FP + addi 4, 3, PPC64_OFFS_FP // load VS register #define PPC64_LVS(n) \ - lxvd2x %vs##n, 0, %r4 ;\ - addi %r4, %r4, 16 + lxvd2x n, 0, 4 ;\ + addi 4, 4, 16 // restore the first 32 VS regs (and also all floating point regs) PPC64_LVS(0) @@ -220,27 +229,36 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_ppc646jumptoEv) PPC64_LVS(30) PPC64_LVS(31) - // use VRSAVE to conditionally restore the remaining VS regs, - // that are where the V regs are mapped +#define PPC64_CLVS_RESTORE(n) \ + addi 4, 3, PPC64_OFFS_FP + n * 16 ;\ + lxvd2x n, 0, 4 - ld %r5, PPC64_OFFS_VRSAVE(%r3) // test VRsave - cmpwi %r5, 0 +#if !defined(_AIX) + // use VRSAVE to conditionally restore the remaining VS regs, that are + // where the V regs are mapped. In the AIX ABI, VRSAVE is not used. + ld 5, PPC64_OFFS_VRSAVE(3) // test VRsave + cmpwi 5, 0 beq Lnovec // conditionally load VS -#define PPC64_CLVS_BOTTOM(n) \ - beq Ldone##n ;\ - addi %r4, %r3, PPC64_OFFS_FP + n * 16 ;\ - lxvd2x %vs##n, 0, %r4 ;\ +#define PPC64_CLVSl(n) \ + andis. 0, 5, (1 PPC_LEFT_SHIFT(47-n)) ;\ + beq Ldone##n ;\ + PPC64_CLVS_RESTORE(n) ;\ Ldone##n: -#define PPC64_CLVSl(n) \ - andis. %r0, %r5, (1<<(47-n)) ;\ -PPC64_CLVS_BOTTOM(n) +#define PPC64_CLVSh(n) \ + andi. 0, 5, (1 PPC_LEFT_SHIFT(63-n)) ;\ + beq Ldone##n ;\ + PPC64_CLVS_RESTORE(n) ;\ +Ldone##n: -#define PPC64_CLVSh(n) \ - andi. %r0, %r5, (1<<(63-n)) ;\ -PPC64_CLVS_BOTTOM(n) +#else + +#define PPC64_CLVSl(n) PPC64_CLVS_RESTORE(n) +#define PPC64_CLVSh(n) PPC64_CLVS_RESTORE(n) + +#endif // !defined(_AIX) PPC64_CLVSl(32) PPC64_CLVSl(33) @@ -279,7 +297,7 @@ PPC64_CLVS_BOTTOM(n) // load FP register #define PPC64_LF(n) \ - lfd %f##n, (PPC64_OFFS_FP + n * 16)(%r3) + lfd n, (PPC64_OFFS_FP + n * 16)(3) // restore float registers PPC64_LF(0) @@ -316,32 +334,44 @@ PPC64_CLVS_BOTTOM(n) PPC64_LF(31) #if defined(__ALTIVEC__) - // restore vector registers if any are in use - ld %r5, PPC64_OFFS_VRSAVE(%r3) // test VRsave - cmpwi %r5, 0 + +#define PPC64_CLV_UNALIGNED_RESTORE(n) \ + ld 0, (PPC64_OFFS_V + n * 16)(3) ;\ + std 0, 0(4) ;\ + ld 0, (PPC64_OFFS_V + n * 16 + 8)(3) ;\ + std 0, 8(4) ;\ + lvx n, 0, 4 + +#if !defined(_AIX) + // restore vector registers if any are in use. In the AIX ABI, VRSAVE is + // not used. + ld 5, PPC64_OFFS_VRSAVE(3) // test VRsave + cmpwi 5, 0 beq Lnovec - subi %r4, %r1, 16 - // r4 is now a 16-byte aligned pointer into the red zone - // the _vectorScalarRegisters may not be 16-byte aligned - // so copy via red zone temp buffer +#define PPC64_CLV_UNALIGNEDl(n) \ + andis. 0, 5, (1 PPC_LEFT_SHIFT(15-n)) ;\ + beq Ldone##n ;\ + PPC64_CLV_UNALIGNED_RESTORE(n) ;\ +Ldone ## n: -#define PPC64_CLV_UNALIGNED_BOTTOM(n) \ - beq Ldone##n ;\ - ld %r0, (PPC64_OFFS_V + n * 16)(%r3) ;\ - std %r0, 0(%r4) ;\ - ld %r0, (PPC64_OFFS_V + n * 16 + 8)(%r3) ;\ - std %r0, 8(%r4) ;\ - lvx %v##n, 0, %r4 ;\ +#define PPC64_CLV_UNALIGNEDh(n) \ + andi. 0, 5, (1 PPC_LEFT_SHIFT(31-n)) ;\ + beq Ldone##n ;\ + PPC64_CLV_UNALIGNED_RESTORE(n) ;\ Ldone ## n: -#define PPC64_CLV_UNALIGNEDl(n) \ - andis. %r0, %r5, (1<<(15-n)) ;\ -PPC64_CLV_UNALIGNED_BOTTOM(n) +#else + +#define PPC64_CLV_UNALIGNEDl(n) PPC64_CLV_UNALIGNED_RESTORE(n) +#define PPC64_CLV_UNALIGNEDh(n) PPC64_CLV_UNALIGNED_RESTORE(n) + +#endif // !defined(_AIX) -#define PPC64_CLV_UNALIGNEDh(n) \ - andi. %r0, %r5, (1<<(31-n)) ;\ -PPC64_CLV_UNALIGNED_BOTTOM(n) + subi 4, 1, 16 + // r4 is now a 16-byte aligned pointer into the red zone + // the _vectorScalarRegisters may not be 16-byte aligned + // so copy via red zone temp buffer PPC64_CLV_UNALIGNEDl(0) PPC64_CLV_UNALIGNEDl(1) @@ -380,19 +410,31 @@ PPC64_CLV_UNALIGNED_BOTTOM(n) #endif Lnovec: - ld %r0, PPC64_OFFS_CR(%r3) - mtcr %r0 - ld %r0, PPC64_OFFS_SRR0(%r3) - mtctr %r0 - + ld 0, PPC64_OFFS_CR(3) + mtcr 0 + ld 0, PPC64_OFFS_SRR0(3) + mtctr 0 + +#if defined(_AIX) + // After setting GPR1 to a higher address, AIX wipes out the original + // stack space below that address invalidated by the new GPR1 value. Use + // GPR0 to save the value of GPR3 in the context before it is wiped out. + // This compromises the content of GPR0 which is a volatile register. + ld 0, (8 * (3 + 2))(3) +#else PPC64_LR(0) +#endif PPC64_LR(5) PPC64_LR(4) PPC64_LR(1) +#if defined(_AIX) + mr 3, 0 +#else PPC64_LR(3) +#endif bctr -#elif defined(__ppc__) +#elif defined(__powerpc__) DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind13Registers_ppc6jumptoEv) // @@ -405,113 +447,116 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind13Registers_ppc6jumptoEv) // restore integral registerrs // skip r0 for now // skip r1 for now - lwz %r2, 16(%r3) + lwz 2, 16(3) // skip r3 for now // skip r4 for now // skip r5 for now - lwz %r6, 32(%r3) - lwz %r7, 36(%r3) - lwz %r8, 40(%r3) - lwz %r9, 44(%r3) - lwz %r10, 48(%r3) - lwz %r11, 52(%r3) - lwz %r12, 56(%r3) - lwz %r13, 60(%r3) - lwz %r14, 64(%r3) - lwz %r15, 68(%r3) - lwz %r16, 72(%r3) - lwz %r17, 76(%r3) - lwz %r18, 80(%r3) - lwz %r19, 84(%r3) - lwz %r20, 88(%r3) - lwz %r21, 92(%r3) - lwz %r22, 96(%r3) - lwz %r23,100(%r3) - lwz %r24,104(%r3) - lwz %r25,108(%r3) - lwz %r26,112(%r3) - lwz %r27,116(%r3) - lwz %r28,120(%r3) - lwz %r29,124(%r3) - lwz %r30,128(%r3) - lwz %r31,132(%r3) + lwz 6, 32(3) + lwz 7, 36(3) + lwz 8, 40(3) + lwz 9, 44(3) + lwz 10, 48(3) + lwz 11, 52(3) + lwz 12, 56(3) + lwz 13, 60(3) + lwz 14, 64(3) + lwz 15, 68(3) + lwz 16, 72(3) + lwz 17, 76(3) + lwz 18, 80(3) + lwz 19, 84(3) + lwz 20, 88(3) + lwz 21, 92(3) + lwz 22, 96(3) + lwz 23,100(3) + lwz 24,104(3) + lwz 25,108(3) + lwz 26,112(3) + lwz 27,116(3) + lwz 28,120(3) + lwz 29,124(3) + lwz 30,128(3) + lwz 31,132(3) #ifndef __NO_FPRS__ // restore float registers - lfd %f0, 160(%r3) - lfd %f1, 168(%r3) - lfd %f2, 176(%r3) - lfd %f3, 184(%r3) - lfd %f4, 192(%r3) - lfd %f5, 200(%r3) - lfd %f6, 208(%r3) - lfd %f7, 216(%r3) - lfd %f8, 224(%r3) - lfd %f9, 232(%r3) - lfd %f10,240(%r3) - lfd %f11,248(%r3) - lfd %f12,256(%r3) - lfd %f13,264(%r3) - lfd %f14,272(%r3) - lfd %f15,280(%r3) - lfd %f16,288(%r3) - lfd %f17,296(%r3) - lfd %f18,304(%r3) - lfd %f19,312(%r3) - lfd %f20,320(%r3) - lfd %f21,328(%r3) - lfd %f22,336(%r3) - lfd %f23,344(%r3) - lfd %f24,352(%r3) - lfd %f25,360(%r3) - lfd %f26,368(%r3) - lfd %f27,376(%r3) - lfd %f28,384(%r3) - lfd %f29,392(%r3) - lfd %f30,400(%r3) - lfd %f31,408(%r3) + lfd 0, 160(3) + lfd 1, 168(3) + lfd 2, 176(3) + lfd 3, 184(3) + lfd 4, 192(3) + lfd 5, 200(3) + lfd 6, 208(3) + lfd 7, 216(3) + lfd 8, 224(3) + lfd 9, 232(3) + lfd 10,240(3) + lfd 11,248(3) + lfd 12,256(3) + lfd 13,264(3) + lfd 14,272(3) + lfd 15,280(3) + lfd 16,288(3) + lfd 17,296(3) + lfd 18,304(3) + lfd 19,312(3) + lfd 20,320(3) + lfd 21,328(3) + lfd 22,336(3) + lfd 23,344(3) + lfd 24,352(3) + lfd 25,360(3) + lfd 26,368(3) + lfd 27,376(3) + lfd 28,384(3) + lfd 29,392(3) + lfd 30,400(3) + lfd 31,408(3) #endif #if defined(__ALTIVEC__) - // restore vector registers if any are in use - lwz %r5, 156(%r3) // test VRsave - cmpwi %r5, 0 + +#define LOAD_VECTOR_RESTORE(_index) \ + lwz 0, 424+_index*16(3) SEPARATOR \ + stw 0, 0(4) SEPARATOR \ + lwz 0, 424+_index*16+4(3) SEPARATOR \ + stw 0, 4(4) SEPARATOR \ + lwz 0, 424+_index*16+8(3) SEPARATOR \ + stw 0, 8(4) SEPARATOR \ + lwz 0, 424+_index*16+12(3) SEPARATOR \ + stw 0, 12(4) SEPARATOR \ + lvx _index, 0, 4 + +#if !defined(_AIX) + // restore vector registers if any are in use. In the AIX ABI, VRSAVE + // is not used. + lwz 5, 156(3) // test VRsave + cmpwi 5, 0 beq Lnovec - subi %r4, %r1, 16 - rlwinm %r4, %r4, 0, 0, 27 // mask low 4-bits - // r4 is now a 16-byte aligned pointer into the red zone - // the _vectorRegisters may not be 16-byte aligned so copy via red zone temp buffer - - -#define LOAD_VECTOR_UNALIGNEDl(_index) \ - andis. %r0, %r5, (1<<(15-_index)) SEPARATOR \ - beq Ldone ## _index SEPARATOR \ - lwz %r0, 424+_index*16(%r3) SEPARATOR \ - stw %r0, 0(%r4) SEPARATOR \ - lwz %r0, 424+_index*16+4(%r3) SEPARATOR \ - stw %r0, 4(%r4) SEPARATOR \ - lwz %r0, 424+_index*16+8(%r3) SEPARATOR \ - stw %r0, 8(%r4) SEPARATOR \ - lwz %r0, 424+_index*16+12(%r3) SEPARATOR \ - stw %r0, 12(%r4) SEPARATOR \ - lvx %v ## _index, 0, %r4 SEPARATOR \ +#define LOAD_VECTOR_UNALIGNEDl(_index) \ + andis. 0, 5, (1 PPC_LEFT_SHIFT(15-_index)) SEPARATOR \ + beq Ldone ## _index SEPARATOR \ + LOAD_VECTOR_RESTORE(_index) SEPARATOR \ Ldone ## _index: -#define LOAD_VECTOR_UNALIGNEDh(_index) \ - andi. %r0, %r5, (1<<(31-_index)) SEPARATOR \ - beq Ldone ## _index SEPARATOR \ - lwz %r0, 424+_index*16(%r3) SEPARATOR \ - stw %r0, 0(%r4) SEPARATOR \ - lwz %r0, 424+_index*16+4(%r3) SEPARATOR \ - stw %r0, 4(%r4) SEPARATOR \ - lwz %r0, 424+_index*16+8(%r3) SEPARATOR \ - stw %r0, 8(%r4) SEPARATOR \ - lwz %r0, 424+_index*16+12(%r3) SEPARATOR \ - stw %r0, 12(%r4) SEPARATOR \ - lvx %v ## _index, 0, %r4 SEPARATOR \ +#define LOAD_VECTOR_UNALIGNEDh(_index) \ + andi. 0, 5, (1 PPC_LEFT_SHIFT(31-_index)) SEPARATOR \ + beq Ldone ## _index SEPARATOR \ + LOAD_VECTOR_RESTORE(_index) SEPARATOR \ Ldone ## _index: +#else + +#define LOAD_VECTOR_UNALIGNEDl(_index) LOAD_VECTOR_RESTORE(_index) +#define LOAD_VECTOR_UNALIGNEDh(_index) LOAD_VECTOR_RESTORE(_index) + +#endif // !defined(_AIX) + + subi 4, 1, 16 + rlwinm 4, 4, 0, 0, 27 // mask low 4-bits + // r4 is now a 16-byte aligned pointer into the red zone + // the _vectorRegisters may not be 16-byte aligned so copy via red zone temp buffer LOAD_VECTOR_UNALIGNEDl(0) LOAD_VECTOR_UNALIGNEDl(1) @@ -548,17 +593,17 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind13Registers_ppc6jumptoEv) #endif Lnovec: - lwz %r0, 136(%r3) // __cr - mtcr %r0 - lwz %r0, 148(%r3) // __ctr - mtctr %r0 - lwz %r0, 0(%r3) // __ssr0 - mtctr %r0 - lwz %r0, 8(%r3) // do r0 now - lwz %r5, 28(%r3) // do r5 now - lwz %r4, 24(%r3) // do r4 now - lwz %r1, 12(%r3) // do sp now - lwz %r3, 20(%r3) // do r3 last + lwz 0, 136(3) // __cr + mtcr 0 + lwz 0, 148(3) // __ctr + mtctr 0 + lwz 0, 0(3) // __ssr0 + mtctr 0 + lwz 0, 8(3) // do r0 now + lwz 5, 28(3) // do r5 now + lwz 4, 24(3) // do r4 now + lwz 1, 12(3) // do sp now + lwz 3, 20(3) // do r3 last bctr #elif defined(__aarch64__) @@ -658,7 +703,13 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind13Registers_arm20restoreCoreAndJumpToEv) ldr sp, [lr, #52] ldr lr, [lr, #60] @ restore pc into lr #endif +#if defined(__ARM_FEATURE_BTI_DEFAULT) && !defined(__ARM_ARCH_ISA_ARM) + // 'bx' is not BTI setting when used with lr, therefore r12 is used instead + mov r12, lr + JMP(r12) +#else JMP(lr) +#endif @ @ static void libunwind::Registers_arm::restoreVFPWithFLDMD(unw_fpreg_t* values) @@ -803,11 +854,12 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind14Registers_or1k6jumptoEv) l.lwz r30,120(r3) l.lwz r31,124(r3) + # load new pc into ra + l.lwz r9, 128(r3) + # at last, restore r3 l.lwz r3, 12(r3) - # load new pc into ra - l.lwz r9, 128(r3) # jump to pc l.jr r9 l.nop @@ -1053,6 +1105,53 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind21Registers_mips_newabi6jumptoEv) ld $4, (8 * 4)($4) .set pop +#elif defined(__sparc__) && defined(__arch64__) + +DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind17Registers_sparc646jumptoEv) +// +// void libunwind::Registers_sparc64::jumpto() +// +// On entry: +// thread_state pointer is in %o0 +// + .register %g2, #scratch + .register %g3, #scratch + .register %g6, #scratch + .register %g7, #scratch + flushw + ldx [%o0 + 0x08], %g1 + ldx [%o0 + 0x10], %g2 + ldx [%o0 + 0x18], %g3 + ldx [%o0 + 0x20], %g4 + ldx [%o0 + 0x28], %g5 + ldx [%o0 + 0x30], %g6 + ldx [%o0 + 0x38], %g7 + ldx [%o0 + 0x48], %o1 + ldx [%o0 + 0x50], %o2 + ldx [%o0 + 0x58], %o3 + ldx [%o0 + 0x60], %o4 + ldx [%o0 + 0x68], %o5 + ldx [%o0 + 0x70], %o6 + ldx [%o0 + 0x78], %o7 + ldx [%o0 + 0x80], %l0 + ldx [%o0 + 0x88], %l1 + ldx [%o0 + 0x90], %l2 + ldx [%o0 + 0x98], %l3 + ldx [%o0 + 0xa0], %l4 + ldx [%o0 + 0xa8], %l5 + ldx [%o0 + 0xb0], %l6 + ldx [%o0 + 0xb8], %l7 + ldx [%o0 + 0xc0], %i0 + ldx [%o0 + 0xc8], %i1 + ldx [%o0 + 0xd0], %i2 + ldx [%o0 + 0xd8], %i3 + ldx [%o0 + 0xe0], %i4 + ldx [%o0 + 0xe8], %i5 + ldx [%o0 + 0xf0], %i6 + ldx [%o0 + 0xf8], %i7 + jmp %o7 + ldx [%o0 + 0x40], %o0 + #elif defined(__sparc__) // @@ -1075,7 +1174,7 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_sparc6jumptoEv) jmp %o7 nop -#elif defined(__riscv) && __riscv_xlen == 64 +#elif defined(__riscv) // // void libunwind::Registers_riscv::jumpto() @@ -1085,77 +1184,114 @@ DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_sparc6jumptoEv) // .p2align 2 DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_riscv6jumptoEv) -#if defined(__riscv_flen) && __riscv_flen == 64 - fld f0, (8 * 32 + 8 * 0)(a0) - fld f1, (8 * 32 + 8 * 1)(a0) - fld f2, (8 * 32 + 8 * 2)(a0) - fld f3, (8 * 32 + 8 * 3)(a0) - fld f4, (8 * 32 + 8 * 4)(a0) - fld f5, (8 * 32 + 8 * 5)(a0) - fld f6, (8 * 32 + 8 * 6)(a0) - fld f7, (8 * 32 + 8 * 7)(a0) - fld f8, (8 * 32 + 8 * 8)(a0) - fld f9, (8 * 32 + 8 * 9)(a0) - fld f10, (8 * 32 + 8 * 10)(a0) - fld f11, (8 * 32 + 8 * 11)(a0) - fld f12, (8 * 32 + 8 * 12)(a0) - fld f13, (8 * 32 + 8 * 13)(a0) - fld f14, (8 * 32 + 8 * 14)(a0) - fld f15, (8 * 32 + 8 * 15)(a0) - fld f16, (8 * 32 + 8 * 16)(a0) - fld f17, (8 * 32 + 8 * 17)(a0) - fld f18, (8 * 32 + 8 * 18)(a0) - fld f19, (8 * 32 + 8 * 19)(a0) - fld f20, (8 * 32 + 8 * 20)(a0) - fld f21, (8 * 32 + 8 * 21)(a0) - fld f22, (8 * 32 + 8 * 22)(a0) - fld f23, (8 * 32 + 8 * 23)(a0) - fld f24, (8 * 32 + 8 * 24)(a0) - fld f25, (8 * 32 + 8 * 25)(a0) - fld f26, (8 * 32 + 8 * 26)(a0) - fld f27, (8 * 32 + 8 * 27)(a0) - fld f28, (8 * 32 + 8 * 28)(a0) - fld f29, (8 * 32 + 8 * 29)(a0) - fld f30, (8 * 32 + 8 * 30)(a0) - fld f31, (8 * 32 + 8 * 31)(a0) -#endif +# if defined(__riscv_flen) + FLOAD f0, (RISCV_FOFFSET + RISCV_FSIZE * 0)(a0) + FLOAD f1, (RISCV_FOFFSET + RISCV_FSIZE * 1)(a0) + FLOAD f2, (RISCV_FOFFSET + RISCV_FSIZE * 2)(a0) + FLOAD f3, (RISCV_FOFFSET + RISCV_FSIZE * 3)(a0) + FLOAD f4, (RISCV_FOFFSET + RISCV_FSIZE * 4)(a0) + FLOAD f5, (RISCV_FOFFSET + RISCV_FSIZE * 5)(a0) + FLOAD f6, (RISCV_FOFFSET + RISCV_FSIZE * 6)(a0) + FLOAD f7, (RISCV_FOFFSET + RISCV_FSIZE * 7)(a0) + FLOAD f8, (RISCV_FOFFSET + RISCV_FSIZE * 8)(a0) + FLOAD f9, (RISCV_FOFFSET + RISCV_FSIZE * 9)(a0) + FLOAD f10, (RISCV_FOFFSET + RISCV_FSIZE * 10)(a0) + FLOAD f11, (RISCV_FOFFSET + RISCV_FSIZE * 11)(a0) + FLOAD f12, (RISCV_FOFFSET + RISCV_FSIZE * 12)(a0) + FLOAD f13, (RISCV_FOFFSET + RISCV_FSIZE * 13)(a0) + FLOAD f14, (RISCV_FOFFSET + RISCV_FSIZE * 14)(a0) + FLOAD f15, (RISCV_FOFFSET + RISCV_FSIZE * 15)(a0) + FLOAD f16, (RISCV_FOFFSET + RISCV_FSIZE * 16)(a0) + FLOAD f17, (RISCV_FOFFSET + RISCV_FSIZE * 17)(a0) + FLOAD f18, (RISCV_FOFFSET + RISCV_FSIZE * 18)(a0) + FLOAD f19, (RISCV_FOFFSET + RISCV_FSIZE * 19)(a0) + FLOAD f20, (RISCV_FOFFSET + RISCV_FSIZE * 20)(a0) + FLOAD f21, (RISCV_FOFFSET + RISCV_FSIZE * 21)(a0) + FLOAD f22, (RISCV_FOFFSET + RISCV_FSIZE * 22)(a0) + FLOAD f23, (RISCV_FOFFSET + RISCV_FSIZE * 23)(a0) + FLOAD f24, (RISCV_FOFFSET + RISCV_FSIZE * 24)(a0) + FLOAD f25, (RISCV_FOFFSET + RISCV_FSIZE * 25)(a0) + FLOAD f26, (RISCV_FOFFSET + RISCV_FSIZE * 26)(a0) + FLOAD f27, (RISCV_FOFFSET + RISCV_FSIZE * 27)(a0) + FLOAD f28, (RISCV_FOFFSET + RISCV_FSIZE * 28)(a0) + FLOAD f29, (RISCV_FOFFSET + RISCV_FSIZE * 29)(a0) + FLOAD f30, (RISCV_FOFFSET + RISCV_FSIZE * 30)(a0) + FLOAD f31, (RISCV_FOFFSET + RISCV_FSIZE * 31)(a0) +# endif // x0 is zero - ld x1, (8 * 0)(a0) // restore pc into ra - ld x2, (8 * 2)(a0) - ld x3, (8 * 3)(a0) - ld x4, (8 * 4)(a0) - ld x5, (8 * 5)(a0) - ld x6, (8 * 6)(a0) - ld x7, (8 * 7)(a0) - ld x8, (8 * 8)(a0) - ld x9, (8 * 9)(a0) + ILOAD x1, (RISCV_ISIZE * 0)(a0) // restore pc into ra + ILOAD x2, (RISCV_ISIZE * 2)(a0) + ILOAD x3, (RISCV_ISIZE * 3)(a0) + ILOAD x4, (RISCV_ISIZE * 4)(a0) + ILOAD x5, (RISCV_ISIZE * 5)(a0) + ILOAD x6, (RISCV_ISIZE * 6)(a0) + ILOAD x7, (RISCV_ISIZE * 7)(a0) + ILOAD x8, (RISCV_ISIZE * 8)(a0) + ILOAD x9, (RISCV_ISIZE * 9)(a0) // skip a0 for now - ld x11, (8 * 11)(a0) - ld x12, (8 * 12)(a0) - ld x13, (8 * 13)(a0) - ld x14, (8 * 14)(a0) - ld x15, (8 * 15)(a0) - ld x16, (8 * 16)(a0) - ld x17, (8 * 17)(a0) - ld x18, (8 * 18)(a0) - ld x19, (8 * 19)(a0) - ld x20, (8 * 20)(a0) - ld x21, (8 * 21)(a0) - ld x22, (8 * 22)(a0) - ld x23, (8 * 23)(a0) - ld x24, (8 * 24)(a0) - ld x25, (8 * 25)(a0) - ld x26, (8 * 26)(a0) - ld x27, (8 * 27)(a0) - ld x28, (8 * 28)(a0) - ld x29, (8 * 29)(a0) - ld x30, (8 * 30)(a0) - ld x31, (8 * 31)(a0) - ld x10, (8 * 10)(a0) // restore a0 + ILOAD x11, (RISCV_ISIZE * 11)(a0) + ILOAD x12, (RISCV_ISIZE * 12)(a0) + ILOAD x13, (RISCV_ISIZE * 13)(a0) + ILOAD x14, (RISCV_ISIZE * 14)(a0) + ILOAD x15, (RISCV_ISIZE * 15)(a0) + ILOAD x16, (RISCV_ISIZE * 16)(a0) + ILOAD x17, (RISCV_ISIZE * 17)(a0) + ILOAD x18, (RISCV_ISIZE * 18)(a0) + ILOAD x19, (RISCV_ISIZE * 19)(a0) + ILOAD x20, (RISCV_ISIZE * 20)(a0) + ILOAD x21, (RISCV_ISIZE * 21)(a0) + ILOAD x22, (RISCV_ISIZE * 22)(a0) + ILOAD x23, (RISCV_ISIZE * 23)(a0) + ILOAD x24, (RISCV_ISIZE * 24)(a0) + ILOAD x25, (RISCV_ISIZE * 25)(a0) + ILOAD x26, (RISCV_ISIZE * 26)(a0) + ILOAD x27, (RISCV_ISIZE * 27)(a0) + ILOAD x28, (RISCV_ISIZE * 28)(a0) + ILOAD x29, (RISCV_ISIZE * 29)(a0) + ILOAD x30, (RISCV_ISIZE * 30)(a0) + ILOAD x31, (RISCV_ISIZE * 31)(a0) + ILOAD x10, (RISCV_ISIZE * 10)(a0) // restore a0 ret // jump to ra +#elif defined(__s390x__) + +DEFINE_LIBUNWIND_FUNCTION(_ZN9libunwind15Registers_s390x6jumptoEv) +// +// void libunwind::Registers_s390x::jumpto() +// +// On entry: +// thread_state pointer is in r2 +// + + // Skip PSWM, but load PSWA into r1 + lg %r1, 8(%r2) + + // Restore FPRs + ld %f0, 144(%r2) + ld %f1, 152(%r2) + ld %f2, 160(%r2) + ld %f3, 168(%r2) + ld %f4, 176(%r2) + ld %f5, 184(%r2) + ld %f6, 192(%r2) + ld %f7, 200(%r2) + ld %f8, 208(%r2) + ld %f9, 216(%r2) + ld %f10, 224(%r2) + ld %f11, 232(%r2) + ld %f12, 240(%r2) + ld %f13, 248(%r2) + ld %f14, 256(%r2) + ld %f15, 264(%r2) + + // Restore GPRs - skipping %r0 and %r1 + lmg %r2, %r15, 32(%r2) + + // Return to PSWA (was loaded into %r1 above) + br %r1 + #endif #endif /* !defined(__USING_SJLJ_EXCEPTIONS__) */ diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersSave.S b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersSave.S index 071efe585b..b4f72f5c18 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersSave.S +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/UnwindRegistersSave.S @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------ UnwindRegistersSave.S -----------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -11,7 +11,11 @@ #include "assembly.h" +#if defined(_AIX) + .toc +#else .text +#endif #if !defined(__USING_SJLJ_EXCEPTIONS__) @@ -30,6 +34,8 @@ # + + # DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) + + _LIBUNWIND_CET_ENDBR push %eax movl 8(%esp), %eax movl %ebx, 4(%eax) @@ -73,6 +79,7 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) #define TMP %rsi #endif + _LIBUNWIND_CET_ENDBR movq %rax, (PTR) movq %rbx, 8(PTR) movq %rcx, 16(PTR) @@ -334,16 +341,19 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) // On entry: // thread_state pointer is in r3 // +#if defined(_AIX) +DEFINE_LIBUNWIND_FUNCTION_AND_WEAK_ALIAS(__unw_getcontext, unw_getcontext) +#else DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) - +#endif // store register (GPR) #define PPC64_STR(n) \ - std %r##n, (8 * (n + 2))(%r3) + std n, (8 * (n + 2))(3) // save GPRs PPC64_STR(0) - mflr %r0 - std %r0, PPC64_OFFS_SRR0(%r3) // store lr as ssr0 + mflr 0 + std 0, PPC64_OFFS_SRR0(3) // store lr as ssr0 PPC64_STR(1) PPC64_STR(2) PPC64_STR(3) @@ -376,28 +386,28 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) PPC64_STR(30) PPC64_STR(31) - mfcr %r0 - std %r0, PPC64_OFFS_CR(%r3) - mfxer %r0 - std %r0, PPC64_OFFS_XER(%r3) - mflr %r0 - std %r0, PPC64_OFFS_LR(%r3) - mfctr %r0 - std %r0, PPC64_OFFS_CTR(%r3) - mfvrsave %r0 - std %r0, PPC64_OFFS_VRSAVE(%r3) + mfcr 0 + std 0, PPC64_OFFS_CR(3) + mfxer 0 + std 0, PPC64_OFFS_XER(3) + mflr 0 + std 0, PPC64_OFFS_LR(3) + mfctr 0 + std 0, PPC64_OFFS_CTR(3) + mfvrsave 0 + std 0, PPC64_OFFS_VRSAVE(3) #if defined(__VSX__) // save VS registers // (note that this also saves floating point registers and V registers, // because part of VS is mapped to these registers) - addi %r4, %r3, PPC64_OFFS_FP + addi 4, 3, PPC64_OFFS_FP // store VS register #define PPC64_STVS(n) \ - stxvd2x %vs##n, 0, %r4 ;\ - addi %r4, %r4, 16 + stxvd2x n, 0, 4 ;\ + addi 4, 4, 16 PPC64_STVS(0) PPC64_STVS(1) @@ -468,7 +478,7 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) // store FP register #define PPC64_STF(n) \ - stfd %f##n, (PPC64_OFFS_FP + n * 16)(%r3) + stfd n, (PPC64_OFFS_FP + n * 16)(3) // save float registers PPC64_STF(0) @@ -510,14 +520,14 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) // Use 16-bytes below the stack pointer as an // aligned buffer to save each vector register. // Note that the stack pointer is always 16-byte aligned. - subi %r4, %r1, 16 + subi 4, 1, 16 -#define PPC64_STV_UNALIGNED(n) \ - stvx %v##n, 0, %r4 ;\ - ld %r5, 0(%r4) ;\ - std %r5, (PPC64_OFFS_V + n * 16)(%r3) ;\ - ld %r5, 8(%r4) ;\ - std %r5, (PPC64_OFFS_V + n * 16 + 8)(%r3) +#define PPC64_STV_UNALIGNED(n) \ + stvx n, 0, 4 ;\ + ld 5, 0(4) ;\ + std 5, (PPC64_OFFS_V + n * 16)(3) ;\ + ld 5, 8(4) ;\ + std 5, (PPC64_OFFS_V + n * 16 + 8)(3) PPC64_STV_UNALIGNED(0) PPC64_STV_UNALIGNED(1) @@ -555,11 +565,11 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) #endif #endif - li %r3, 0 // return UNW_ESUCCESS + li 3, 0 // return UNW_ESUCCESS blr -#elif defined(__ppc__) +#elif defined(__powerpc__) // // extern int unw_getcontext(unw_context_t* thread_state) @@ -567,141 +577,147 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) // On entry: // thread_state pointer is in r3 // +#if defined(_AIX) +DEFINE_LIBUNWIND_FUNCTION_AND_WEAK_ALIAS(__unw_getcontext, unw_getcontext) +#else DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) - stw %r0, 8(%r3) - mflr %r0 - stw %r0, 0(%r3) // store lr as ssr0 - stw %r1, 12(%r3) - stw %r2, 16(%r3) - stw %r3, 20(%r3) - stw %r4, 24(%r3) - stw %r5, 28(%r3) - stw %r6, 32(%r3) - stw %r7, 36(%r3) - stw %r8, 40(%r3) - stw %r9, 44(%r3) - stw %r10, 48(%r3) - stw %r11, 52(%r3) - stw %r12, 56(%r3) - stw %r13, 60(%r3) - stw %r14, 64(%r3) - stw %r15, 68(%r3) - stw %r16, 72(%r3) - stw %r17, 76(%r3) - stw %r18, 80(%r3) - stw %r19, 84(%r3) - stw %r20, 88(%r3) - stw %r21, 92(%r3) - stw %r22, 96(%r3) - stw %r23,100(%r3) - stw %r24,104(%r3) - stw %r25,108(%r3) - stw %r26,112(%r3) - stw %r27,116(%r3) - stw %r28,120(%r3) - stw %r29,124(%r3) - stw %r30,128(%r3) - stw %r31,132(%r3) +#endif + stw 0, 8(3) + mflr 0 + stw 0, 0(3) // store lr as ssr0 + stw 1, 12(3) + stw 2, 16(3) + stw 3, 20(3) + stw 4, 24(3) + stw 5, 28(3) + stw 6, 32(3) + stw 7, 36(3) + stw 8, 40(3) + stw 9, 44(3) + stw 10, 48(3) + stw 11, 52(3) + stw 12, 56(3) + stw 13, 60(3) + stw 14, 64(3) + stw 15, 68(3) + stw 16, 72(3) + stw 17, 76(3) + stw 18, 80(3) + stw 19, 84(3) + stw 20, 88(3) + stw 21, 92(3) + stw 22, 96(3) + stw 23,100(3) + stw 24,104(3) + stw 25,108(3) + stw 26,112(3) + stw 27,116(3) + stw 28,120(3) + stw 29,124(3) + stw 30,128(3) + stw 31,132(3) +#if defined(__ALTIVEC__) // save VRSave register - mfspr %r0, 256 - stw %r0, 156(%r3) + mfspr 0, 256 + stw 0, 156(3) +#endif // save CR registers - mfcr %r0 - stw %r0, 136(%r3) + mfcr 0 + stw 0, 136(3) // save CTR register - mfctr %r0 - stw %r0, 148(%r3) + mfctr 0 + stw 0, 148(3) #if !defined(__NO_FPRS__) // save float registers - stfd %f0, 160(%r3) - stfd %f1, 168(%r3) - stfd %f2, 176(%r3) - stfd %f3, 184(%r3) - stfd %f4, 192(%r3) - stfd %f5, 200(%r3) - stfd %f6, 208(%r3) - stfd %f7, 216(%r3) - stfd %f8, 224(%r3) - stfd %f9, 232(%r3) - stfd %f10,240(%r3) - stfd %f11,248(%r3) - stfd %f12,256(%r3) - stfd %f13,264(%r3) - stfd %f14,272(%r3) - stfd %f15,280(%r3) - stfd %f16,288(%r3) - stfd %f17,296(%r3) - stfd %f18,304(%r3) - stfd %f19,312(%r3) - stfd %f20,320(%r3) - stfd %f21,328(%r3) - stfd %f22,336(%r3) - stfd %f23,344(%r3) - stfd %f24,352(%r3) - stfd %f25,360(%r3) - stfd %f26,368(%r3) - stfd %f27,376(%r3) - stfd %f28,384(%r3) - stfd %f29,392(%r3) - stfd %f30,400(%r3) - stfd %f31,408(%r3) + stfd 0, 160(3) + stfd 1, 168(3) + stfd 2, 176(3) + stfd 3, 184(3) + stfd 4, 192(3) + stfd 5, 200(3) + stfd 6, 208(3) + stfd 7, 216(3) + stfd 8, 224(3) + stfd 9, 232(3) + stfd 10,240(3) + stfd 11,248(3) + stfd 12,256(3) + stfd 13,264(3) + stfd 14,272(3) + stfd 15,280(3) + stfd 16,288(3) + stfd 17,296(3) + stfd 18,304(3) + stfd 19,312(3) + stfd 20,320(3) + stfd 21,328(3) + stfd 22,336(3) + stfd 23,344(3) + stfd 24,352(3) + stfd 25,360(3) + stfd 26,368(3) + stfd 27,376(3) + stfd 28,384(3) + stfd 29,392(3) + stfd 30,400(3) + stfd 31,408(3) #endif #if defined(__ALTIVEC__) // save vector registers - subi %r4, %r1, 16 - rlwinm %r4, %r4, 0, 0, 27 // mask low 4-bits + subi 4, 1, 16 + rlwinm 4, 4, 0, 0, 27 // mask low 4-bits // r4 is now a 16-byte aligned pointer into the red zone #define SAVE_VECTOR_UNALIGNED(_vec, _offset) \ - stvx _vec, 0, %r4 SEPARATOR \ - lwz %r5, 0(%r4) SEPARATOR \ - stw %r5, _offset(%r3) SEPARATOR \ - lwz %r5, 4(%r4) SEPARATOR \ - stw %r5, _offset+4(%r3) SEPARATOR \ - lwz %r5, 8(%r4) SEPARATOR \ - stw %r5, _offset+8(%r3) SEPARATOR \ - lwz %r5, 12(%r4) SEPARATOR \ - stw %r5, _offset+12(%r3) - - SAVE_VECTOR_UNALIGNED( %v0, 424+0x000) - SAVE_VECTOR_UNALIGNED( %v1, 424+0x010) - SAVE_VECTOR_UNALIGNED( %v2, 424+0x020) - SAVE_VECTOR_UNALIGNED( %v3, 424+0x030) - SAVE_VECTOR_UNALIGNED( %v4, 424+0x040) - SAVE_VECTOR_UNALIGNED( %v5, 424+0x050) - SAVE_VECTOR_UNALIGNED( %v6, 424+0x060) - SAVE_VECTOR_UNALIGNED( %v7, 424+0x070) - SAVE_VECTOR_UNALIGNED( %v8, 424+0x080) - SAVE_VECTOR_UNALIGNED( %v9, 424+0x090) - SAVE_VECTOR_UNALIGNED(%v10, 424+0x0A0) - SAVE_VECTOR_UNALIGNED(%v11, 424+0x0B0) - SAVE_VECTOR_UNALIGNED(%v12, 424+0x0C0) - SAVE_VECTOR_UNALIGNED(%v13, 424+0x0D0) - SAVE_VECTOR_UNALIGNED(%v14, 424+0x0E0) - SAVE_VECTOR_UNALIGNED(%v15, 424+0x0F0) - SAVE_VECTOR_UNALIGNED(%v16, 424+0x100) - SAVE_VECTOR_UNALIGNED(%v17, 424+0x110) - SAVE_VECTOR_UNALIGNED(%v18, 424+0x120) - SAVE_VECTOR_UNALIGNED(%v19, 424+0x130) - SAVE_VECTOR_UNALIGNED(%v20, 424+0x140) - SAVE_VECTOR_UNALIGNED(%v21, 424+0x150) - SAVE_VECTOR_UNALIGNED(%v22, 424+0x160) - SAVE_VECTOR_UNALIGNED(%v23, 424+0x170) - SAVE_VECTOR_UNALIGNED(%v24, 424+0x180) - SAVE_VECTOR_UNALIGNED(%v25, 424+0x190) - SAVE_VECTOR_UNALIGNED(%v26, 424+0x1A0) - SAVE_VECTOR_UNALIGNED(%v27, 424+0x1B0) - SAVE_VECTOR_UNALIGNED(%v28, 424+0x1C0) - SAVE_VECTOR_UNALIGNED(%v29, 424+0x1D0) - SAVE_VECTOR_UNALIGNED(%v30, 424+0x1E0) - SAVE_VECTOR_UNALIGNED(%v31, 424+0x1F0) + stvx _vec, 0, 4 SEPARATOR \ + lwz 5, 0(4) SEPARATOR \ + stw 5, _offset(3) SEPARATOR \ + lwz 5, 4(4) SEPARATOR \ + stw 5, _offset+4(3) SEPARATOR \ + lwz 5, 8(4) SEPARATOR \ + stw 5, _offset+8(3) SEPARATOR \ + lwz 5, 12(4) SEPARATOR \ + stw 5, _offset+12(3) + + SAVE_VECTOR_UNALIGNED( 0, 424+0x000) + SAVE_VECTOR_UNALIGNED( 1, 424+0x010) + SAVE_VECTOR_UNALIGNED( 2, 424+0x020) + SAVE_VECTOR_UNALIGNED( 3, 424+0x030) + SAVE_VECTOR_UNALIGNED( 4, 424+0x040) + SAVE_VECTOR_UNALIGNED( 5, 424+0x050) + SAVE_VECTOR_UNALIGNED( 6, 424+0x060) + SAVE_VECTOR_UNALIGNED( 7, 424+0x070) + SAVE_VECTOR_UNALIGNED( 8, 424+0x080) + SAVE_VECTOR_UNALIGNED( 9, 424+0x090) + SAVE_VECTOR_UNALIGNED(10, 424+0x0A0) + SAVE_VECTOR_UNALIGNED(11, 424+0x0B0) + SAVE_VECTOR_UNALIGNED(12, 424+0x0C0) + SAVE_VECTOR_UNALIGNED(13, 424+0x0D0) + SAVE_VECTOR_UNALIGNED(14, 424+0x0E0) + SAVE_VECTOR_UNALIGNED(15, 424+0x0F0) + SAVE_VECTOR_UNALIGNED(16, 424+0x100) + SAVE_VECTOR_UNALIGNED(17, 424+0x110) + SAVE_VECTOR_UNALIGNED(18, 424+0x120) + SAVE_VECTOR_UNALIGNED(19, 424+0x130) + SAVE_VECTOR_UNALIGNED(20, 424+0x140) + SAVE_VECTOR_UNALIGNED(21, 424+0x150) + SAVE_VECTOR_UNALIGNED(22, 424+0x160) + SAVE_VECTOR_UNALIGNED(23, 424+0x170) + SAVE_VECTOR_UNALIGNED(24, 424+0x180) + SAVE_VECTOR_UNALIGNED(25, 424+0x190) + SAVE_VECTOR_UNALIGNED(26, 424+0x1A0) + SAVE_VECTOR_UNALIGNED(27, 424+0x1B0) + SAVE_VECTOR_UNALIGNED(28, 424+0x1C0) + SAVE_VECTOR_UNALIGNED(29, 424+0x1D0) + SAVE_VECTOR_UNALIGNED(30, 424+0x1E0) + SAVE_VECTOR_UNALIGNED(31, 424+0x1F0) #endif - li %r3, 0 // return UNW_ESUCCESS + li 3, 0 // return UNW_ESUCCESS blr @@ -999,6 +1015,64 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) jumpr r31 +#elif defined(__sparc__) && defined(__arch64__) + +# +# extern int __unw_getcontext(unw_context_t* thread_state) +# +# On entry: +# thread_state pointer is in %o0 +# +DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) + .register %g2, #scratch + .register %g3, #scratch + .register %g6, #scratch + .register %g7, #scratch + stx %g1, [%o0 + 0x08] + stx %g2, [%o0 + 0x10] + stx %g3, [%o0 + 0x18] + stx %g4, [%o0 + 0x20] + stx %g5, [%o0 + 0x28] + stx %g6, [%o0 + 0x30] + stx %g7, [%o0 + 0x38] + stx %o0, [%o0 + 0x40] + stx %o1, [%o0 + 0x48] + stx %o2, [%o0 + 0x50] + stx %o3, [%o0 + 0x58] + stx %o4, [%o0 + 0x60] + stx %o5, [%o0 + 0x68] + stx %o6, [%o0 + 0x70] + stx %o7, [%o0 + 0x78] + stx %l0, [%o0 + 0x80] + stx %l1, [%o0 + 0x88] + stx %l2, [%o0 + 0x90] + stx %l3, [%o0 + 0x98] + stx %l4, [%o0 + 0xa0] + stx %l5, [%o0 + 0xa8] + stx %l6, [%o0 + 0xb0] + stx %l7, [%o0 + 0xb8] + stx %i0, [%o0 + 0xc0] + stx %i1, [%o0 + 0xc8] + stx %i2, [%o0 + 0xd0] + stx %i3, [%o0 + 0xd8] + stx %i4, [%o0 + 0xe0] + stx %i5, [%o0 + 0xe8] + stx %i6, [%o0 + 0xf0] + stx %i7, [%o0 + 0xf8] + + # save StackGhost cookie + mov %i7, %g4 + save %sp, -176, %sp + # register window flush necessary even without StackGhost + flushw + restore + ldx [%sp + 2047 + 0x78], %g5 + xor %g4, %g5, %g4 + stx %g4, [%o0 + 0x100] + retl + # return UNW_ESUCCESS + clr %o0 + #elif defined(__sparc__) # @@ -1029,7 +1103,7 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) jmp %o7 clr %o0 // return UNW_ESUCCESS -#elif defined(__riscv) && __riscv_xlen == 64 +#elif defined(__riscv) # # extern int __unw_getcontext(unw_context_t* thread_state) @@ -1038,76 +1112,119 @@ DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) # thread_state pointer is in a0 # DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) - sd x1, (8 * 0)(a0) // store ra as pc - sd x1, (8 * 1)(a0) - sd x2, (8 * 2)(a0) - sd x3, (8 * 3)(a0) - sd x4, (8 * 4)(a0) - sd x5, (8 * 5)(a0) - sd x6, (8 * 6)(a0) - sd x7, (8 * 7)(a0) - sd x8, (8 * 8)(a0) - sd x9, (8 * 9)(a0) - sd x10, (8 * 10)(a0) - sd x11, (8 * 11)(a0) - sd x12, (8 * 12)(a0) - sd x13, (8 * 13)(a0) - sd x14, (8 * 14)(a0) - sd x15, (8 * 15)(a0) - sd x16, (8 * 16)(a0) - sd x17, (8 * 17)(a0) - sd x18, (8 * 18)(a0) - sd x19, (8 * 19)(a0) - sd x20, (8 * 20)(a0) - sd x21, (8 * 21)(a0) - sd x22, (8 * 22)(a0) - sd x23, (8 * 23)(a0) - sd x24, (8 * 24)(a0) - sd x25, (8 * 25)(a0) - sd x26, (8 * 26)(a0) - sd x27, (8 * 27)(a0) - sd x28, (8 * 28)(a0) - sd x29, (8 * 29)(a0) - sd x30, (8 * 30)(a0) - sd x31, (8 * 31)(a0) - -#if defined(__riscv_flen) && __riscv_flen == 64 - fsd f0, (8 * 32 + 8 * 0)(a0) - fsd f1, (8 * 32 + 8 * 1)(a0) - fsd f2, (8 * 32 + 8 * 2)(a0) - fsd f3, (8 * 32 + 8 * 3)(a0) - fsd f4, (8 * 32 + 8 * 4)(a0) - fsd f5, (8 * 32 + 8 * 5)(a0) - fsd f6, (8 * 32 + 8 * 6)(a0) - fsd f7, (8 * 32 + 8 * 7)(a0) - fsd f8, (8 * 32 + 8 * 8)(a0) - fsd f9, (8 * 32 + 8 * 9)(a0) - fsd f10, (8 * 32 + 8 * 10)(a0) - fsd f11, (8 * 32 + 8 * 11)(a0) - fsd f12, (8 * 32 + 8 * 12)(a0) - fsd f13, (8 * 32 + 8 * 13)(a0) - fsd f14, (8 * 32 + 8 * 14)(a0) - fsd f15, (8 * 32 + 8 * 15)(a0) - fsd f16, (8 * 32 + 8 * 16)(a0) - fsd f17, (8 * 32 + 8 * 17)(a0) - fsd f18, (8 * 32 + 8 * 18)(a0) - fsd f19, (8 * 32 + 8 * 19)(a0) - fsd f20, (8 * 32 + 8 * 20)(a0) - fsd f21, (8 * 32 + 8 * 21)(a0) - fsd f22, (8 * 32 + 8 * 22)(a0) - fsd f23, (8 * 32 + 8 * 23)(a0) - fsd f24, (8 * 32 + 8 * 24)(a0) - fsd f25, (8 * 32 + 8 * 25)(a0) - fsd f26, (8 * 32 + 8 * 26)(a0) - fsd f27, (8 * 32 + 8 * 27)(a0) - fsd f28, (8 * 32 + 8 * 28)(a0) - fsd f29, (8 * 32 + 8 * 29)(a0) - fsd f30, (8 * 32 + 8 * 30)(a0) - fsd f31, (8 * 32 + 8 * 31)(a0) -#endif + ISTORE x1, (RISCV_ISIZE * 0)(a0) // store ra as pc + ISTORE x1, (RISCV_ISIZE * 1)(a0) + ISTORE x2, (RISCV_ISIZE * 2)(a0) + ISTORE x3, (RISCV_ISIZE * 3)(a0) + ISTORE x4, (RISCV_ISIZE * 4)(a0) + ISTORE x5, (RISCV_ISIZE * 5)(a0) + ISTORE x6, (RISCV_ISIZE * 6)(a0) + ISTORE x7, (RISCV_ISIZE * 7)(a0) + ISTORE x8, (RISCV_ISIZE * 8)(a0) + ISTORE x9, (RISCV_ISIZE * 9)(a0) + ISTORE x10, (RISCV_ISIZE * 10)(a0) + ISTORE x11, (RISCV_ISIZE * 11)(a0) + ISTORE x12, (RISCV_ISIZE * 12)(a0) + ISTORE x13, (RISCV_ISIZE * 13)(a0) + ISTORE x14, (RISCV_ISIZE * 14)(a0) + ISTORE x15, (RISCV_ISIZE * 15)(a0) + ISTORE x16, (RISCV_ISIZE * 16)(a0) + ISTORE x17, (RISCV_ISIZE * 17)(a0) + ISTORE x18, (RISCV_ISIZE * 18)(a0) + ISTORE x19, (RISCV_ISIZE * 19)(a0) + ISTORE x20, (RISCV_ISIZE * 20)(a0) + ISTORE x21, (RISCV_ISIZE * 21)(a0) + ISTORE x22, (RISCV_ISIZE * 22)(a0) + ISTORE x23, (RISCV_ISIZE * 23)(a0) + ISTORE x24, (RISCV_ISIZE * 24)(a0) + ISTORE x25, (RISCV_ISIZE * 25)(a0) + ISTORE x26, (RISCV_ISIZE * 26)(a0) + ISTORE x27, (RISCV_ISIZE * 27)(a0) + ISTORE x28, (RISCV_ISIZE * 28)(a0) + ISTORE x29, (RISCV_ISIZE * 29)(a0) + ISTORE x30, (RISCV_ISIZE * 30)(a0) + ISTORE x31, (RISCV_ISIZE * 31)(a0) + +# if defined(__riscv_flen) + FSTORE f0, (RISCV_FOFFSET + RISCV_FSIZE * 0)(a0) + FSTORE f1, (RISCV_FOFFSET + RISCV_FSIZE * 1)(a0) + FSTORE f2, (RISCV_FOFFSET + RISCV_FSIZE * 2)(a0) + FSTORE f3, (RISCV_FOFFSET + RISCV_FSIZE * 3)(a0) + FSTORE f4, (RISCV_FOFFSET + RISCV_FSIZE * 4)(a0) + FSTORE f5, (RISCV_FOFFSET + RISCV_FSIZE * 5)(a0) + FSTORE f6, (RISCV_FOFFSET + RISCV_FSIZE * 6)(a0) + FSTORE f7, (RISCV_FOFFSET + RISCV_FSIZE * 7)(a0) + FSTORE f8, (RISCV_FOFFSET + RISCV_FSIZE * 8)(a0) + FSTORE f9, (RISCV_FOFFSET + RISCV_FSIZE * 9)(a0) + FSTORE f10, (RISCV_FOFFSET + RISCV_FSIZE * 10)(a0) + FSTORE f11, (RISCV_FOFFSET + RISCV_FSIZE * 11)(a0) + FSTORE f12, (RISCV_FOFFSET + RISCV_FSIZE * 12)(a0) + FSTORE f13, (RISCV_FOFFSET + RISCV_FSIZE * 13)(a0) + FSTORE f14, (RISCV_FOFFSET + RISCV_FSIZE * 14)(a0) + FSTORE f15, (RISCV_FOFFSET + RISCV_FSIZE * 15)(a0) + FSTORE f16, (RISCV_FOFFSET + RISCV_FSIZE * 16)(a0) + FSTORE f17, (RISCV_FOFFSET + RISCV_FSIZE * 17)(a0) + FSTORE f18, (RISCV_FOFFSET + RISCV_FSIZE * 18)(a0) + FSTORE f19, (RISCV_FOFFSET + RISCV_FSIZE * 19)(a0) + FSTORE f20, (RISCV_FOFFSET + RISCV_FSIZE * 20)(a0) + FSTORE f21, (RISCV_FOFFSET + RISCV_FSIZE * 21)(a0) + FSTORE f22, (RISCV_FOFFSET + RISCV_FSIZE * 22)(a0) + FSTORE f23, (RISCV_FOFFSET + RISCV_FSIZE * 23)(a0) + FSTORE f24, (RISCV_FOFFSET + RISCV_FSIZE * 24)(a0) + FSTORE f25, (RISCV_FOFFSET + RISCV_FSIZE * 25)(a0) + FSTORE f26, (RISCV_FOFFSET + RISCV_FSIZE * 26)(a0) + FSTORE f27, (RISCV_FOFFSET + RISCV_FSIZE * 27)(a0) + FSTORE f28, (RISCV_FOFFSET + RISCV_FSIZE * 28)(a0) + FSTORE f29, (RISCV_FOFFSET + RISCV_FSIZE * 29)(a0) + FSTORE f30, (RISCV_FOFFSET + RISCV_FSIZE * 30)(a0) + FSTORE f31, (RISCV_FOFFSET + RISCV_FSIZE * 31)(a0) +# endif li a0, 0 // return UNW_ESUCCESS ret // jump to ra + +#elif defined(__s390x__) + +// +// extern int __unw_getcontext(unw_context_t* thread_state) +// +// On entry: +// thread_state pointer is in r2 +// +DEFINE_LIBUNWIND_FUNCTION(__unw_getcontext) + + // Save GPRs + stmg %r0, %r15, 16(%r2) + + // Save PSWM + epsw %r0, %r1 + stm %r0, %r1, 0(%r2) + + // Store return address as PSWA + stg %r14, 8(%r2) + + // Save FPRs + std %f0, 144(%r2) + std %f1, 152(%r2) + std %f2, 160(%r2) + std %f3, 168(%r2) + std %f4, 176(%r2) + std %f5, 184(%r2) + std %f6, 192(%r2) + std %f7, 200(%r2) + std %f8, 208(%r2) + std %f9, 216(%r2) + std %f10, 224(%r2) + std %f11, 232(%r2) + std %f12, 240(%r2) + std %f13, 248(%r2) + std %f14, 256(%r2) + std %f15, 264(%r2) + + // Return UNW_ESUCCESS + lghi %r2, 0 + br %r14 + #endif WEAK_ALIAS(__unw_getcontext, unw_getcontext) diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AIXExtras.cpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AIXExtras.cpp new file mode 100644 index 0000000000..548f21a3cb --- /dev/null +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AIXExtras.cpp @@ -0,0 +1,67 @@ +// clang-format off +#if defined(__unix__) || defined(__unix) || defined(unix) || \ + (defined(__APPLE__) && defined(__MACH__)) +//===--------------------- Unwind_AIXExtras.cpp -------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +//===----------------------------------------------------------------------===// + +// This file is only used for AIX. +#if defined(_AIX) + +#include "config.h" +#include "libunwind_ext.h" +#include + +namespace libunwind { +// getFuncNameFromTBTable +// Get the function name from its traceback table. +char *getFuncNameFromTBTable(uintptr_t Pc, uint16_t &NameLen, + unw_word_t *Offset) { + uint32_t *p = reinterpret_cast(Pc); + *Offset = 0; + + // Keep looking forward until a word of 0 is found. The traceback + // table starts at the following word. + while (*p) + p++; + tbtable *TBTable = reinterpret_cast(p + 1); + + if (!TBTable->tb.name_present) + return NULL; + + // Get to the name of the function. + p = reinterpret_cast(&TBTable->tb_ext); + + // Skip field parminfo if it exists. + if (TBTable->tb.fixedparms || TBTable->tb.floatparms) + p++; + + // If the tb_offset field exisits, get the offset from the start of + // the function to pc. Skip the field. + if (TBTable->tb.has_tboff) { + unw_word_t StartIp = + reinterpret_cast(TBTable) - *p - sizeof(uint32_t); + *Offset = Pc - StartIp; + p++; + } + + // Skip field hand_mask if it exists. + if (TBTable->tb.int_hndl) + p++; + + // Skip fields ctl_info and ctl_info_disp if they exist. + if (TBTable->tb.has_ctl) { + p += 1 + *p; + } + + NameLen = *(reinterpret_cast(p)); + return reinterpret_cast(p) + sizeof(uint16_t); +} +} // namespace libunwind +#endif // defined(_AIX) +#endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AppleExtras.cpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AppleExtras.cpp index ad080b4e31..7abdaaed72 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AppleExtras.cpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/Unwind_AppleExtras.cpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------- Unwind_AppleExtras.cpp -------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/__libunwind_config.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/__libunwind_config.h index adf86ee4e8..900d5a2a27 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/__libunwind_config.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/__libunwind_config.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------- __libunwind_config.h -----------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -12,8 +12,10 @@ #ifndef ____LIBUNWIND_CONFIG_H__ #define ____LIBUNWIND_CONFIG_H__ +#define _LIBUNWIND_VERSION 15000 + #if defined(__arm__) && !defined(__USING_SJLJ_EXCEPTIONS__) && \ - !defined(__ARM_DWARF_EH__) + !defined(__ARM_DWARF_EH__) && !defined(__SEH__) #define _LIBUNWIND_ARM_EHABI #endif @@ -26,9 +28,11 @@ #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_OR1K 32 #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_MIPS 65 #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC 31 +#define _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC64 31 #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_HEXAGON 34 #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_RISCV 64 #define _LIBUNWIND_HIGHEST_DWARF_REGISTER_VE 143 +#define _LIBUNWIND_HIGHEST_DWARF_REGISTER_S390X 83 #if defined(_LIBUNWIND_IS_NATIVE_ONLY) # if defined(__linux__) @@ -58,7 +62,7 @@ # define _LIBUNWIND_CONTEXT_SIZE 167 # define _LIBUNWIND_CURSOR_SIZE 179 # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_PPC64 -# elif defined(__ppc__) +# elif defined(__powerpc__) # define _LIBUNWIND_TARGET_PPC 1 # define _LIBUNWIND_CONTEXT_SIZE 117 # define _LIBUNWIND_CURSOR_SIZE 124 @@ -128,18 +132,31 @@ # error "Unsupported MIPS ABI and/or environment" # endif # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_MIPS +#elif defined(__sparc__) && defined(__arch64__) +#define _LIBUNWIND_TARGET_SPARC64 1 +#define _LIBUNWIND_HIGHEST_DWARF_REGISTER \ + _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC64 +#define _LIBUNWIND_CONTEXT_SIZE 33 +#define _LIBUNWIND_CURSOR_SIZE 45 # elif defined(__sparc__) #define _LIBUNWIND_TARGET_SPARC 1 #define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_SPARC #define _LIBUNWIND_CONTEXT_SIZE 16 #define _LIBUNWIND_CURSOR_SIZE 23 # elif defined(__riscv) -# if __riscv_xlen == 64 -# define _LIBUNWIND_TARGET_RISCV 1 -# define _LIBUNWIND_CONTEXT_SIZE 64 -# define _LIBUNWIND_CURSOR_SIZE 76 +# define _LIBUNWIND_TARGET_RISCV 1 +# if defined(__riscv_flen) +# define RISCV_FLEN __riscv_flen +# else +# define RISCV_FLEN 0 +# endif +# define _LIBUNWIND_CONTEXT_SIZE (32 * (__riscv_xlen + RISCV_FLEN) / 64) +# if __riscv_xlen == 32 +# define _LIBUNWIND_CURSOR_SIZE (_LIBUNWIND_CONTEXT_SIZE + 7) +# elif __riscv_xlen == 64 +# define _LIBUNWIND_CURSOR_SIZE (_LIBUNWIND_CONTEXT_SIZE + 12) # else -# error "Unsupported RISC-V ABI" +# error "Unsupported RISC-V ABI" # endif # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_RISCV # elif defined(__ve__) @@ -147,6 +164,11 @@ # define _LIBUNWIND_CONTEXT_SIZE 67 # define _LIBUNWIND_CURSOR_SIZE 79 # define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_VE +# elif defined(__s390x__) +# define _LIBUNWIND_TARGET_S390X 1 +# define _LIBUNWIND_CONTEXT_SIZE 34 +# define _LIBUNWIND_CURSOR_SIZE 46 +# define _LIBUNWIND_HIGHEST_DWARF_REGISTER _LIBUNWIND_HIGHEST_DWARF_REGISTER_S390X # else # error "Unsupported architecture." # endif @@ -161,9 +183,11 @@ # define _LIBUNWIND_TARGET_MIPS_O32 1 # define _LIBUNWIND_TARGET_MIPS_NEWABI 1 # define _LIBUNWIND_TARGET_SPARC 1 +# define _LIBUNWIND_TARGET_SPARC64 1 # define _LIBUNWIND_TARGET_HEXAGON 1 # define _LIBUNWIND_TARGET_RISCV 1 # define _LIBUNWIND_TARGET_VE 1 +# define _LIBUNWIND_TARGET_S390X 1 # define _LIBUNWIND_CONTEXT_SIZE 167 # define _LIBUNWIND_CURSOR_SIZE 179 # define _LIBUNWIND_HIGHEST_DWARF_REGISTER 287 diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/assembly.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/assembly.h index 266e52e63b..1ea1759c8d 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/assembly.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/assembly.h @@ -18,6 +18,13 @@ #ifndef UNWIND_ASSEMBLY_H #define UNWIND_ASSEMBLY_H +#if defined(__linux__) && defined(__CET__) +#include +#define _LIBUNWIND_CET_ENDBR _CET_ENDBR +#else +#define _LIBUNWIND_CET_ENDBR +#endif + #if defined(__powerpc64__) #define SEPARATOR ; #define PPC64_OFFS_SRR0 0 @@ -30,11 +37,41 @@ #define PPC64_OFFS_V 824 #elif defined(__APPLE__) && defined(__aarch64__) #define SEPARATOR %% +#elif defined(__riscv) +# define RISCV_ISIZE (__riscv_xlen / 8) +# define RISCV_FOFFSET (RISCV_ISIZE * 32) +# if defined(__riscv_flen) +# define RISCV_FSIZE (__riscv_flen / 8) +# endif + +# if __riscv_xlen == 64 +# define ILOAD ld +# define ISTORE sd +# elif __riscv_xlen == 32 +# define ILOAD lw +# define ISTORE sw +# else +# error "Unsupported __riscv_xlen" +# endif + +# if defined(__riscv_flen) +# if __riscv_flen == 64 +# define FLOAD fld +# define FSTORE fsd +# elif __riscv_flen == 32 +# define FLOAD flw +# define FSTORE fsw +# else +# error "Unsupported __riscv_flen" +# endif +# endif +# define SEPARATOR ; #else #define SEPARATOR ; #endif -#if defined(__powerpc64__) && (!defined(_CALL_ELF) || _CALL_ELF == 1) +#if defined(__powerpc64__) && (!defined(_CALL_ELF) || _CALL_ELF == 1) && \ + !defined(_AIX) #define PPC64_OPD1 .section .opd,"aw",@progbits SEPARATOR #define PPC64_OPD2 SEPARATOR \ .p2align 3 SEPARATOR \ @@ -48,7 +85,7 @@ #define PPC64_OPD2 #endif -#if defined(__ARM_FEATURE_BTI_DEFAULT) +#if defined(__aarch64__) && defined(__ARM_FEATURE_BTI_DEFAULT) .pushsection ".note.gnu.property", "a" SEPARATOR \ .balign 8 SEPARATOR \ .long 4 SEPARATOR \ @@ -66,6 +103,17 @@ #define AARCH64_BTI #endif +#if !defined(__aarch64__) +#ifdef __ARM_FEATURE_PAC_DEFAULT + .eabi_attribute Tag_PAC_extension, 2 + .eabi_attribute Tag_PACRET_use, 1 +#endif +#ifdef __ARM_FEATURE_BTI_DEFAULT + .eabi_attribute Tag_BTI_extension, 1 + .eabi_attribute Tag_BTI_use, 1 +#endif +#endif + #define GLUE2(a, b) a ## b #define GLUE(a, b) GLUE2(a, b) #define SYMBOL_NAME(name) GLUE(__USER_LABEL_PREFIX__, name) @@ -73,12 +121,15 @@ #if defined(__APPLE__) #define SYMBOL_IS_FUNC(name) -#define EXPORT_SYMBOL(name) #define HIDDEN_SYMBOL(name) .private_extern name -#define WEAK_SYMBOL(name) .weak_reference name +#if defined(_LIBUNWIND_HIDE_SYMBOLS) +#define EXPORT_SYMBOL(name) HIDDEN_SYMBOL(name) +#else +#define EXPORT_SYMBOL(name) +#endif #define WEAK_ALIAS(name, aliasname) \ .globl SYMBOL_NAME(aliasname) SEPARATOR \ - WEAK_SYMBOL(aliasname) SEPARATOR \ + EXPORT_SYMBOL(SYMBOL_NAME(aliasname)) SEPARATOR \ SYMBOL_NAME(aliasname) = SYMBOL_NAME(name) #define NO_EXEC_STACK_DIRECTIVE @@ -90,17 +141,23 @@ #else #define SYMBOL_IS_FUNC(name) .type name,@function #endif -#define EXPORT_SYMBOL(name) #define HIDDEN_SYMBOL(name) .hidden name +#if defined(_LIBUNWIND_HIDE_SYMBOLS) +#define EXPORT_SYMBOL(name) HIDDEN_SYMBOL(name) +#else +#define EXPORT_SYMBOL(name) +#endif #define WEAK_SYMBOL(name) .weak name #if defined(__hexagon__) -#define WEAK_ALIAS(name, aliasname) \ - WEAK_SYMBOL(aliasname) SEPARATOR \ +#define WEAK_ALIAS(name, aliasname) \ + EXPORT_SYMBOL(SYMBOL_NAME(aliasname)) SEPARATOR \ + WEAK_SYMBOL(SYMBOL_NAME(aliasname)) SEPARATOR \ .equiv SYMBOL_NAME(aliasname), SYMBOL_NAME(name) #else #define WEAK_ALIAS(name, aliasname) \ - WEAK_SYMBOL(aliasname) SEPARATOR \ + EXPORT_SYMBOL(SYMBOL_NAME(aliasname)) SEPARATOR \ + WEAK_SYMBOL(SYMBOL_NAME(aliasname)) SEPARATOR \ SYMBOL_NAME(aliasname) = SYMBOL_NAME(name) #endif @@ -122,7 +179,7 @@ .section .drectve,"yn" SEPARATOR \ .ascii "-export:", #name, "\0" SEPARATOR \ .text -#if defined(_LIBUNWIND_DISABLE_VISIBILITY_ANNOTATIONS) +#if defined(_LIBUNWIND_HIDE_SYMBOLS) #define EXPORT_SYMBOL(name) #else #define EXPORT_SYMBOL(name) EXPORT_SYMBOL2(name) @@ -150,12 +207,57 @@ #elif defined(__sparc__) +#elif defined(_AIX) + +#if defined(__powerpc64__) +#define VBYTE_LEN 8 +#define CSECT_ALIGN 3 +#else +#define VBYTE_LEN 4 +#define CSECT_ALIGN 2 +#endif + +// clang-format off +#define DEFINE_LIBUNWIND_FUNCTION_AND_WEAK_ALIAS(name, aliasname) \ + .csect .text[PR], 2 SEPARATOR \ + .csect .name[PR], 2 SEPARATOR \ + .globl name[DS] SEPARATOR \ + .globl .name[PR] SEPARATOR \ + .align 4 SEPARATOR \ + .csect name[DS], CSECT_ALIGN SEPARATOR \ +aliasname: \ + .vbyte VBYTE_LEN, .name[PR] SEPARATOR \ + .vbyte VBYTE_LEN, TOC[TC0] SEPARATOR \ + .vbyte VBYTE_LEN, 0 SEPARATOR \ + .weak aliasname SEPARATOR \ + .weak .aliasname SEPARATOR \ + .csect .name[PR], 2 SEPARATOR \ +.aliasname: \ + +#define WEAK_ALIAS(name, aliasname) +#define NO_EXEC_STACK_DIRECTIVE + +// clang-format on #else #error Unsupported target #endif +#if defined(_AIX) +// clang-format off +#define DEFINE_LIBUNWIND_FUNCTION(name) \ + .globl name[DS] SEPARATOR \ + .globl .name SEPARATOR \ + .align 4 SEPARATOR \ + .csect name[DS], CSECT_ALIGN SEPARATOR \ + .vbyte VBYTE_LEN, .name SEPARATOR \ + .vbyte VBYTE_LEN, TOC[TC0] SEPARATOR \ + .vbyte VBYTE_LEN, 0 SEPARATOR \ + .csect .text[PR], 2 SEPARATOR \ +.name: +// // clang-format on +#else #define DEFINE_LIBUNWIND_FUNCTION(name) \ .globl SYMBOL_NAME(name) SEPARATOR \ HIDDEN_SYMBOL(SYMBOL_NAME(name)) SEPARATOR \ @@ -164,6 +266,7 @@ SYMBOL_NAME(name): \ PPC64_OPD2 \ AARCH64_BTI +#endif #if defined(__arm__) #if !defined(__ARM_ARCH) @@ -181,5 +284,9 @@ #endif #endif /* __arm__ */ +#if defined(__powerpc__) +#define PPC_LEFT_SHIFT(index) << (index) +#endif + #endif /* UNWIND_ASSEMBLY_H */ #endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/cet_unwind.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/cet_unwind.h new file mode 100644 index 0000000000..5a9f3281fd --- /dev/null +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/cet_unwind.h @@ -0,0 +1,45 @@ +// clang-format off +#if defined(__unix__) || defined(__unix) || defined(unix) || \ + (defined(__APPLE__) && defined(__MACH__)) +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +//===----------------------------------------------------------------------===// + +#ifndef LIBUNWIND_CET_UNWIND_H +#define LIBUNWIND_CET_UNWIND_H + +#include "libunwind.h" + +// Currently, CET is implemented on Linux x86 platforms. +#if defined(_LIBUNWIND_TARGET_LINUX) && defined(__CET__) && defined(__SHSTK__) +#define _LIBUNWIND_USE_CET 1 +#endif + +#if defined(_LIBUNWIND_USE_CET) +#include +#include + +#define _LIBUNWIND_POP_CET_SSP(x) \ + do { \ + unsigned long ssp = _get_ssp(); \ + if (ssp != 0) { \ + unsigned int tmp = (x); \ + while (tmp > 255) { \ + _inc_ssp(255); \ + tmp -= 255; \ + } \ + _inc_ssp(tmp); \ + } \ + } while (0) +#endif + +extern void *__libunwind_cet_get_registers(unw_cursor_t *); +extern void *__libunwind_cet_get_jump_target(void); + +#endif +#endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/config.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/config.h index de7b82af56..e6eee472ee 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/config.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/config.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===----------------------------- config.h -------------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -46,6 +46,9 @@ // For ARM EHABI, Bionic didn't implement dl_iterate_phdr until API 21. After // API 21, dl_iterate_phdr exists, but dl_unwind_find_exidx is much faster. #define _LIBUNWIND_USE_DL_UNWIND_FIND_EXIDX 1 +#elif defined(_AIX) +// The traceback table at the end of each function is used for unwinding. +#define _LIBUNWIND_SUPPORT_TBTAB_UNWIND 1 #else // Assume an ELF system with a dl_iterate_phdr function. #define _LIBUNWIND_USE_DL_ITERATE_PHDR 1 @@ -55,11 +58,12 @@ #endif #endif -#if defined(_LIBUNWIND_DISABLE_VISIBILITY_ANNOTATIONS) +#if defined(_LIBUNWIND_HIDE_SYMBOLS) + // The CMake file passes -fvisibility=hidden to control ELF/Mach-O visibility. #define _LIBUNWIND_EXPORT #define _LIBUNWIND_HIDDEN #else - #if !defined(__ELF__) && !defined(__MACH__) + #if !defined(__ELF__) && !defined(__MACH__) && !defined(_AIX) #define _LIBUNWIND_EXPORT __declspec(dllexport) #define _LIBUNWIND_HIDDEN #else @@ -73,12 +77,16 @@ #define SYMBOL_NAME(name) XSTR(__USER_LABEL_PREFIX__) #name #if defined(__APPLE__) +#if defined(_LIBUNWIND_HIDE_SYMBOLS) +#define _LIBUNWIND_ALIAS_VISIBILITY(name) __asm__(".private_extern " name); +#else +#define _LIBUNWIND_ALIAS_VISIBILITY(name) +#endif #define _LIBUNWIND_WEAK_ALIAS(name, aliasname) \ __asm__(".globl " SYMBOL_NAME(aliasname)); \ __asm__(SYMBOL_NAME(aliasname) " = " SYMBOL_NAME(name)); \ - extern "C" _LIBUNWIND_EXPORT __typeof(name) aliasname \ - __attribute__((weak_import)); -#elif defined(__ELF__) + _LIBUNWIND_ALIAS_VISIBILITY(SYMBOL_NAME(aliasname)) +#elif defined(__ELF__) || defined(_AIX) #define _LIBUNWIND_WEAK_ALIAS(name, aliasname) \ extern "C" _LIBUNWIND_EXPORT __typeof(name) aliasname \ __attribute__((weak, alias(#name))); @@ -103,17 +111,14 @@ #define _LIBUNWIND_BUILD_SJLJ_APIS #endif -#if defined(__i386__) || defined(__x86_64__) || defined(__ppc__) || defined(__ppc64__) || defined(__powerpc64__) +#if defined(__i386__) || defined(__x86_64__) || defined(__powerpc__) #define _LIBUNWIND_SUPPORT_FRAME_APIS #endif -#if defined(__i386__) || defined(__x86_64__) || \ - defined(__ppc__) || defined(__ppc64__) || defined(__powerpc64__) || \ - (!defined(__APPLE__) && defined(__arm__)) || \ - defined(__aarch64__) || \ - defined(__mips__) || \ - defined(__riscv) || \ - defined(__hexagon__) +#if defined(__i386__) || defined(__x86_64__) || defined(__powerpc__) || \ + (!defined(__APPLE__) && defined(__arm__)) || defined(__aarch64__) || \ + defined(__mips__) || defined(__riscv) || defined(__hexagon__) || \ + defined(__sparc__) || defined(__s390x__) #if !defined(_LIBUNWIND_BUILD_SJLJ_APIS) #define _LIBUNWIND_BUILD_ZERO_COST_APIS #endif @@ -189,9 +194,9 @@ #ifdef __cplusplus extern "C" { #endif - extern bool logAPIs(); - extern bool logUnwinding(); - extern bool logDWARF(); + extern bool logAPIs(void); + extern bool logUnwinding(void); + extern bool logDWARF(void); #ifdef __cplusplus } #endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/dwarf2.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/dwarf2.h index 5fea08bafe..0e96ee5f7c 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/dwarf2.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/dwarf2.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------------- dwarf2.h -----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.cpp b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.cpp index 5feca1c4e5..a6c2c92586 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.cpp +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.cpp @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===--------------------------- libunwind.cpp ----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -14,11 +14,20 @@ #include "libunwind.h" -#include "libunwind_ext.h" #include "config.h" +#include "libunwind_ext.h" #include +// Define the __has_feature extension for compilers that do not support it so +// that we can later check for the presence of ASan in a compiler-neutral way. +#if !defined(__has_feature) +#define __has_feature(feature) 0 +#endif + +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +#include +#endif #if !defined(__USING_SJLJ_EXCEPTIONS__) #include "AddressSpace.hpp" @@ -45,7 +54,7 @@ _LIBUNWIND_HIDDEN int __unw_init_local(unw_cursor_t *cursor, # define REGISTER_KIND Registers_x86_64 #elif defined(__powerpc64__) # define REGISTER_KIND Registers_ppc64 -#elif defined(__ppc__) +#elif defined(__powerpc__) # define REGISTER_KIND Registers_ppc #elif defined(__aarch64__) # define REGISTER_KIND Registers_arm64 @@ -61,12 +70,16 @@ _LIBUNWIND_HIDDEN int __unw_init_local(unw_cursor_t *cursor, # define REGISTER_KIND Registers_mips_newabi #elif defined(__mips__) # warning The MIPS architecture is not supported with this ABI and environment! +#elif defined(__sparc__) && defined(__arch64__) +#define REGISTER_KIND Registers_sparc64 #elif defined(__sparc__) # define REGISTER_KIND Registers_sparc -#elif defined(__riscv) && __riscv_xlen == 64 +#elif defined(__riscv) # define REGISTER_KIND Registers_riscv #elif defined(__ve__) # define REGISTER_KIND Registers_ve +#elif defined(__s390x__) +# define REGISTER_KIND Registers_s390x #else # error Architecture not supported #endif @@ -187,6 +200,10 @@ _LIBUNWIND_WEAK_ALIAS(__unw_get_proc_info, unw_get_proc_info) /// Resume execution at cursor position (aka longjump). _LIBUNWIND_HIDDEN int __unw_resume(unw_cursor_t *cursor) { _LIBUNWIND_TRACE_API("__unw_resume(cursor=%p)", static_cast(cursor)); +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + // Inform the ASan runtime that now might be a good time to clean stuff up. + __asan_handle_no_return(); +#endif AbstractUnwindCursor *co = (AbstractUnwindCursor *)cursor; co->jumpto(); return UNW_EUNSPEC; @@ -235,6 +252,16 @@ _LIBUNWIND_HIDDEN int __unw_is_signal_frame(unw_cursor_t *cursor) { } _LIBUNWIND_WEAK_ALIAS(__unw_is_signal_frame, unw_is_signal_frame) +#ifdef _AIX +_LIBUNWIND_EXPORT uintptr_t __unw_get_data_rel_base(unw_cursor_t *cursor) { + _LIBUNWIND_TRACE_API("unw_get_data_rel_base(cursor=%p)", + static_cast(cursor)); + AbstractUnwindCursor *co = reinterpret_cast(cursor); + return co->getDataRelBase(); +} +_LIBUNWIND_WEAK_ALIAS(__unw_get_data_rel_base, unw_get_data_rel_base) +#endif + #ifdef __arm__ // Save VFP registers d0-d15 using FSTMIADX instead of FSTMIADD _LIBUNWIND_HIDDEN void __unw_save_vfp_as_X(unw_cursor_t *cursor) { @@ -282,6 +309,35 @@ void __unw_remove_dynamic_fde(unw_word_t fde) { // fde is own mh_group DwarfFDECache::removeAllIn((LocalAddressSpace::pint_t)fde); } + +void __unw_add_dynamic_eh_frame_section(unw_word_t eh_frame_start) { + // The eh_frame section start serves as the mh_group + unw_word_t mh_group = eh_frame_start; + CFI_Parser::CIE_Info cieInfo; + CFI_Parser::FDE_Info fdeInfo; + auto p = (LocalAddressSpace::pint_t)eh_frame_start; + while (true) { + if (CFI_Parser::decodeFDE( + LocalAddressSpace::sThisAddressSpace, p, &fdeInfo, &cieInfo, + true) == NULL) { + DwarfFDECache::add((LocalAddressSpace::pint_t)mh_group, + fdeInfo.pcStart, fdeInfo.pcEnd, + fdeInfo.fdeStart); + p += fdeInfo.fdeLength; + } else if (CFI_Parser::parseCIE( + LocalAddressSpace::sThisAddressSpace, p, &cieInfo) == NULL) { + p += cieInfo.cieLength; + } else + return; + } +} + +void __unw_remove_dynamic_eh_frame_section(unw_word_t eh_frame_start) { + // The eh_frame section start serves as the mh_group + DwarfFDECache::removeAllIn( + (LocalAddressSpace::pint_t)eh_frame_start); +} + #endif // defined(_LIBUNWIND_SUPPORT_DWARF_UNWIND) #endif // !defined(__USING_SJLJ_EXCEPTIONS__) diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.h index 786ecaad93..bf0edbf511 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===---------------------------- libunwind.h -----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -84,7 +84,7 @@ typedef struct unw_addr_space *unw_addr_space_t; typedef int unw_regnum_t; typedef uintptr_t unw_word_t; -#if defined(__arm__) && !defined(__ARM_DWARF_EH__) +#if defined(__arm__) && !defined(__ARM_DWARF_EH__) && !defined(__SEH__) typedef uint64_t unw_fpreg_t; #else typedef double unw_fpreg_t; @@ -123,6 +123,9 @@ extern int unw_resume(unw_cursor_t *) LIBUNWIND_AVAIL; extern void unw_save_vfp_as_X(unw_cursor_t *) LIBUNWIND_AVAIL; #endif +#ifdef _AIX +extern uintptr_t unw_get_data_rel_base(unw_cursor_t *) LIBUNWIND_AVAIL; +#endif extern const char *unw_regname(unw_cursor_t *, unw_regnum_t) LIBUNWIND_AVAIL; extern int unw_get_proc_info(unw_cursor_t *, unw_proc_info_t *) LIBUNWIND_AVAIL; @@ -496,76 +499,150 @@ enum { // 64-bit ARM64 registers enum { - UNW_ARM64_X0 = 0, - UNW_ARM64_X1 = 1, - UNW_ARM64_X2 = 2, - UNW_ARM64_X3 = 3, - UNW_ARM64_X4 = 4, - UNW_ARM64_X5 = 5, - UNW_ARM64_X6 = 6, - UNW_ARM64_X7 = 7, - UNW_ARM64_X8 = 8, - UNW_ARM64_X9 = 9, - UNW_ARM64_X10 = 10, - UNW_ARM64_X11 = 11, - UNW_ARM64_X12 = 12, - UNW_ARM64_X13 = 13, - UNW_ARM64_X14 = 14, - UNW_ARM64_X15 = 15, - UNW_ARM64_X16 = 16, - UNW_ARM64_X17 = 17, - UNW_ARM64_X18 = 18, - UNW_ARM64_X19 = 19, - UNW_ARM64_X20 = 20, - UNW_ARM64_X21 = 21, - UNW_ARM64_X22 = 22, - UNW_ARM64_X23 = 23, - UNW_ARM64_X24 = 24, - UNW_ARM64_X25 = 25, - UNW_ARM64_X26 = 26, - UNW_ARM64_X27 = 27, - UNW_ARM64_X28 = 28, - UNW_ARM64_X29 = 29, - UNW_ARM64_FP = 29, - UNW_ARM64_X30 = 30, - UNW_ARM64_LR = 30, - UNW_ARM64_X31 = 31, - UNW_ARM64_SP = 31, - // reserved block - UNW_ARM64_RA_SIGN_STATE = 34, + UNW_AARCH64_X0 = 0, + UNW_AARCH64_X1 = 1, + UNW_AARCH64_X2 = 2, + UNW_AARCH64_X3 = 3, + UNW_AARCH64_X4 = 4, + UNW_AARCH64_X5 = 5, + UNW_AARCH64_X6 = 6, + UNW_AARCH64_X7 = 7, + UNW_AARCH64_X8 = 8, + UNW_AARCH64_X9 = 9, + UNW_AARCH64_X10 = 10, + UNW_AARCH64_X11 = 11, + UNW_AARCH64_X12 = 12, + UNW_AARCH64_X13 = 13, + UNW_AARCH64_X14 = 14, + UNW_AARCH64_X15 = 15, + UNW_AARCH64_X16 = 16, + UNW_AARCH64_X17 = 17, + UNW_AARCH64_X18 = 18, + UNW_AARCH64_X19 = 19, + UNW_AARCH64_X20 = 20, + UNW_AARCH64_X21 = 21, + UNW_AARCH64_X22 = 22, + UNW_AARCH64_X23 = 23, + UNW_AARCH64_X24 = 24, + UNW_AARCH64_X25 = 25, + UNW_AARCH64_X26 = 26, + UNW_AARCH64_X27 = 27, + UNW_AARCH64_X28 = 28, + UNW_AARCH64_X29 = 29, + UNW_AARCH64_FP = 29, + UNW_AARCH64_X30 = 30, + UNW_AARCH64_LR = 30, + UNW_AARCH64_X31 = 31, + UNW_AARCH64_SP = 31, + UNW_AARCH64_PC = 32, + // reserved block - UNW_ARM64_D0 = 64, - UNW_ARM64_D1 = 65, - UNW_ARM64_D2 = 66, - UNW_ARM64_D3 = 67, - UNW_ARM64_D4 = 68, - UNW_ARM64_D5 = 69, - UNW_ARM64_D6 = 70, - UNW_ARM64_D7 = 71, - UNW_ARM64_D8 = 72, - UNW_ARM64_D9 = 73, - UNW_ARM64_D10 = 74, - UNW_ARM64_D11 = 75, - UNW_ARM64_D12 = 76, - UNW_ARM64_D13 = 77, - UNW_ARM64_D14 = 78, - UNW_ARM64_D15 = 79, - UNW_ARM64_D16 = 80, - UNW_ARM64_D17 = 81, - UNW_ARM64_D18 = 82, - UNW_ARM64_D19 = 83, - UNW_ARM64_D20 = 84, - UNW_ARM64_D21 = 85, - UNW_ARM64_D22 = 86, - UNW_ARM64_D23 = 87, - UNW_ARM64_D24 = 88, - UNW_ARM64_D25 = 89, - UNW_ARM64_D26 = 90, - UNW_ARM64_D27 = 91, - UNW_ARM64_D28 = 92, - UNW_ARM64_D29 = 93, - UNW_ARM64_D30 = 94, - UNW_ARM64_D31 = 95, + UNW_AARCH64_RA_SIGN_STATE = 34, + + // FP/vector registers + UNW_AARCH64_V0 = 64, + UNW_AARCH64_V1 = 65, + UNW_AARCH64_V2 = 66, + UNW_AARCH64_V3 = 67, + UNW_AARCH64_V4 = 68, + UNW_AARCH64_V5 = 69, + UNW_AARCH64_V6 = 70, + UNW_AARCH64_V7 = 71, + UNW_AARCH64_V8 = 72, + UNW_AARCH64_V9 = 73, + UNW_AARCH64_V10 = 74, + UNW_AARCH64_V11 = 75, + UNW_AARCH64_V12 = 76, + UNW_AARCH64_V13 = 77, + UNW_AARCH64_V14 = 78, + UNW_AARCH64_V15 = 79, + UNW_AARCH64_V16 = 80, + UNW_AARCH64_V17 = 81, + UNW_AARCH64_V18 = 82, + UNW_AARCH64_V19 = 83, + UNW_AARCH64_V20 = 84, + UNW_AARCH64_V21 = 85, + UNW_AARCH64_V22 = 86, + UNW_AARCH64_V23 = 87, + UNW_AARCH64_V24 = 88, + UNW_AARCH64_V25 = 89, + UNW_AARCH64_V26 = 90, + UNW_AARCH64_V27 = 91, + UNW_AARCH64_V28 = 92, + UNW_AARCH64_V29 = 93, + UNW_AARCH64_V30 = 94, + UNW_AARCH64_V31 = 95, + + // Compatibility aliases + UNW_ARM64_X0 = UNW_AARCH64_X0, + UNW_ARM64_X1 = UNW_AARCH64_X1, + UNW_ARM64_X2 = UNW_AARCH64_X2, + UNW_ARM64_X3 = UNW_AARCH64_X3, + UNW_ARM64_X4 = UNW_AARCH64_X4, + UNW_ARM64_X5 = UNW_AARCH64_X5, + UNW_ARM64_X6 = UNW_AARCH64_X6, + UNW_ARM64_X7 = UNW_AARCH64_X7, + UNW_ARM64_X8 = UNW_AARCH64_X8, + UNW_ARM64_X9 = UNW_AARCH64_X9, + UNW_ARM64_X10 = UNW_AARCH64_X10, + UNW_ARM64_X11 = UNW_AARCH64_X11, + UNW_ARM64_X12 = UNW_AARCH64_X12, + UNW_ARM64_X13 = UNW_AARCH64_X13, + UNW_ARM64_X14 = UNW_AARCH64_X14, + UNW_ARM64_X15 = UNW_AARCH64_X15, + UNW_ARM64_X16 = UNW_AARCH64_X16, + UNW_ARM64_X17 = UNW_AARCH64_X17, + UNW_ARM64_X18 = UNW_AARCH64_X18, + UNW_ARM64_X19 = UNW_AARCH64_X19, + UNW_ARM64_X20 = UNW_AARCH64_X20, + UNW_ARM64_X21 = UNW_AARCH64_X21, + UNW_ARM64_X22 = UNW_AARCH64_X22, + UNW_ARM64_X23 = UNW_AARCH64_X23, + UNW_ARM64_X24 = UNW_AARCH64_X24, + UNW_ARM64_X25 = UNW_AARCH64_X25, + UNW_ARM64_X26 = UNW_AARCH64_X26, + UNW_ARM64_X27 = UNW_AARCH64_X27, + UNW_ARM64_X28 = UNW_AARCH64_X28, + UNW_ARM64_X29 = UNW_AARCH64_X29, + UNW_ARM64_FP = UNW_AARCH64_FP, + UNW_ARM64_X30 = UNW_AARCH64_X30, + UNW_ARM64_LR = UNW_AARCH64_LR, + UNW_ARM64_X31 = UNW_AARCH64_X31, + UNW_ARM64_SP = UNW_AARCH64_SP, + UNW_ARM64_PC = UNW_AARCH64_PC, + UNW_ARM64_RA_SIGN_STATE = UNW_AARCH64_RA_SIGN_STATE, + UNW_ARM64_D0 = UNW_AARCH64_V0, + UNW_ARM64_D1 = UNW_AARCH64_V1, + UNW_ARM64_D2 = UNW_AARCH64_V2, + UNW_ARM64_D3 = UNW_AARCH64_V3, + UNW_ARM64_D4 = UNW_AARCH64_V4, + UNW_ARM64_D5 = UNW_AARCH64_V5, + UNW_ARM64_D6 = UNW_AARCH64_V6, + UNW_ARM64_D7 = UNW_AARCH64_V7, + UNW_ARM64_D8 = UNW_AARCH64_V8, + UNW_ARM64_D9 = UNW_AARCH64_V9, + UNW_ARM64_D10 = UNW_AARCH64_V10, + UNW_ARM64_D11 = UNW_AARCH64_V11, + UNW_ARM64_D12 = UNW_AARCH64_V12, + UNW_ARM64_D13 = UNW_AARCH64_V13, + UNW_ARM64_D14 = UNW_AARCH64_V14, + UNW_ARM64_D15 = UNW_AARCH64_V15, + UNW_ARM64_D16 = UNW_AARCH64_V16, + UNW_ARM64_D17 = UNW_AARCH64_V17, + UNW_ARM64_D18 = UNW_AARCH64_V18, + UNW_ARM64_D19 = UNW_AARCH64_V19, + UNW_ARM64_D20 = UNW_AARCH64_V20, + UNW_ARM64_D21 = UNW_AARCH64_V21, + UNW_ARM64_D22 = UNW_AARCH64_V22, + UNW_ARM64_D23 = UNW_AARCH64_V23, + UNW_ARM64_D24 = UNW_AARCH64_V24, + UNW_ARM64_D25 = UNW_AARCH64_V25, + UNW_ARM64_D26 = UNW_AARCH64_V26, + UNW_ARM64_D27 = UNW_AARCH64_V27, + UNW_ARM64_D28 = UNW_AARCH64_V28, + UNW_ARM64_D29 = UNW_AARCH64_V29, + UNW_ARM64_D30 = UNW_AARCH64_V30, + UNW_ARM64_D31 = UNW_AARCH64_V31, }; // 32-bit ARM registers. Numbers match DWARF for ARM spec #3.1 Table 1. @@ -647,7 +724,8 @@ enum { UNW_ARM_WR14 = 126, UNW_ARM_WR15 = 127, // 128-133 -- SPSR, SPSR_{FIQ|IRQ|ABT|UND|SVC} - // 134-143 -- Reserved + // 134-142 -- Reserved + UNW_ARM_RA_AUTH_CODE = 143, // 144-150 -- R8_USR-R14_USR // 151-157 -- R8_FIQ-R14_FIQ // 158-159 -- R13_IRQ-R14_IRQ @@ -1102,5 +1180,47 @@ enum { UNW_VE_VL = 145, }; +// s390x register numbers +enum { + UNW_S390X_R0 = 0, + UNW_S390X_R1 = 1, + UNW_S390X_R2 = 2, + UNW_S390X_R3 = 3, + UNW_S390X_R4 = 4, + UNW_S390X_R5 = 5, + UNW_S390X_R6 = 6, + UNW_S390X_R7 = 7, + UNW_S390X_R8 = 8, + UNW_S390X_R9 = 9, + UNW_S390X_R10 = 10, + UNW_S390X_R11 = 11, + UNW_S390X_R12 = 12, + UNW_S390X_R13 = 13, + UNW_S390X_R14 = 14, + UNW_S390X_R15 = 15, + UNW_S390X_F0 = 16, + UNW_S390X_F2 = 17, + UNW_S390X_F4 = 18, + UNW_S390X_F6 = 19, + UNW_S390X_F1 = 20, + UNW_S390X_F3 = 21, + UNW_S390X_F5 = 22, + UNW_S390X_F7 = 23, + UNW_S390X_F8 = 24, + UNW_S390X_F10 = 25, + UNW_S390X_F12 = 26, + UNW_S390X_F14 = 27, + UNW_S390X_F9 = 28, + UNW_S390X_F11 = 29, + UNW_S390X_F13 = 30, + UNW_S390X_F15 = 31, + // 32-47 Control Registers + // 48-63 Access Registers + UNW_S390X_PSWM = 64, + UNW_S390X_PSWA = 65, + // 66-67 Reserved + // 68-83 Vector Registers %v16-%v31 +}; + #endif #endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind_ext.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind_ext.h index 3807f99b3d..19c38b7570 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind_ext.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/libunwind_ext.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------ libunwind_ext.h -----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -46,6 +46,10 @@ extern int __unw_is_fpreg(unw_cursor_t *, unw_regnum_t); extern int __unw_is_signal_frame(unw_cursor_t *); extern int __unw_get_proc_name(unw_cursor_t *, char *, size_t, unw_word_t *); +#if defined(_AIX) +extern uintptr_t __unw_get_data_rel_base(unw_cursor_t *); +#endif + // SPI extern void __unw_iterate_dwarf_unwind_cache(void (*func)( unw_word_t ip_start, unw_word_t ip_end, unw_word_t fde, unw_word_t mh)); @@ -54,6 +58,9 @@ extern void __unw_iterate_dwarf_unwind_cache(void (*func)( extern void __unw_add_dynamic_fde(unw_word_t fde); extern void __unw_remove_dynamic_fde(unw_word_t fde); +extern void __unw_add_dynamic_eh_frame_section(unw_word_t eh_frame_start); +extern void __unw_remove_dynamic_eh_frame_section(unw_word_t eh_frame_start); + #if defined(_LIBUNWIND_ARM_EHABI) extern const uint32_t* decode_eht_entry(const uint32_t*, size_t*, size_t*); extern _Unwind_Reason_Code _Unwind_VRS_Interpret(_Unwind_Context *context, diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/mach-o/compact_unwind_encoding.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/mach-o/compact_unwind_encoding.h index ae35364eb9..f49f859e00 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/mach-o/compact_unwind_encoding.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/mach-o/compact_unwind_encoding.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------ mach-o/compact_unwind_encoding.h ------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/rev.txt b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/rev.txt index d6b5d35133..563d51c5ed 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/rev.txt +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/rev.txt @@ -1 +1 @@ -llvmorg-12.0.1 +llvmorg-15.0.7 diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind.h index e1425adf75..454f2cef30 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind.h +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind.h @@ -1,7 +1,7 @@ // clang-format off #if defined(__unix__) || defined(__unix) || defined(unix) || \ (defined(__APPLE__) && defined(__MACH__)) -//===------------------------------- unwind.h -----------------------------===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -59,211 +59,23 @@ typedef enum { typedef struct _Unwind_Context _Unwind_Context; // opaque #if defined(_LIBUNWIND_ARM_EHABI) -typedef uint32_t _Unwind_State; - -static const _Unwind_State _US_VIRTUAL_UNWIND_FRAME = 0; -static const _Unwind_State _US_UNWIND_FRAME_STARTING = 1; -static const _Unwind_State _US_UNWIND_FRAME_RESUME = 2; -static const _Unwind_State _US_ACTION_MASK = 3; -/* Undocumented flag for force unwinding. */ -static const _Unwind_State _US_FORCE_UNWIND = 8; - -typedef uint32_t _Unwind_EHT_Header; - -struct _Unwind_Control_Block; -typedef struct _Unwind_Control_Block _Unwind_Control_Block; -typedef struct _Unwind_Control_Block _Unwind_Exception; /* Alias */ - -struct _Unwind_Control_Block { - uint64_t exception_class; - void (*exception_cleanup)(_Unwind_Reason_Code, _Unwind_Control_Block*); - - /* Unwinder cache, private fields for the unwinder's use */ - struct { - uint32_t reserved1; /* init reserved1 to 0, then don't touch */ - uint32_t reserved2; - uint32_t reserved3; - uint32_t reserved4; - uint32_t reserved5; - } unwinder_cache; - - /* Propagation barrier cache (valid after phase 1): */ - struct { - uint32_t sp; - uint32_t bitpattern[5]; - } barrier_cache; - - /* Cleanup cache (preserved over cleanup): */ - struct { - uint32_t bitpattern[4]; - } cleanup_cache; - - /* Pr cache (for pr's benefit): */ - struct { - uint32_t fnstart; /* function start address */ - _Unwind_EHT_Header* ehtp; /* pointer to EHT entry header word */ - uint32_t additional; - uint32_t reserved1; - } pr_cache; - - long long int :0; /* Enforce the 8-byte alignment */ -} __attribute__((__aligned__(8))); - -typedef _Unwind_Reason_Code (*_Unwind_Stop_Fn) - (_Unwind_State state, - _Unwind_Exception* exceptionObject, - struct _Unwind_Context* context); - -typedef _Unwind_Reason_Code (*_Unwind_Personality_Fn)( - _Unwind_State state, _Unwind_Exception *exceptionObject, - struct _Unwind_Context *context); +#include "unwind_arm_ehabi.h" #else -struct _Unwind_Context; // opaque -struct _Unwind_Exception; // forward declaration -typedef struct _Unwind_Exception _Unwind_Exception; - -struct _Unwind_Exception { - uint64_t exception_class; - void (*exception_cleanup)(_Unwind_Reason_Code reason, - _Unwind_Exception *exc); -#if defined(__SEH__) && !defined(__USING_SJLJ_EXCEPTIONS__) - uintptr_t private_[6]; -#else - uintptr_t private_1; // non-zero means forced unwind - uintptr_t private_2; // holds sp that phase1 found for phase2 to use +#include "unwind_itanium.h" #endif -#if __SIZEOF_POINTER__ == 4 - // The implementation of _Unwind_Exception uses an attribute mode on the - // above fields which has the side effect of causing this whole struct to - // round up to 32 bytes in size (48 with SEH). To be more explicit, we add - // pad fields added for binary compatibility. - uint32_t reserved[3]; -#endif - // The Itanium ABI requires that _Unwind_Exception objects are "double-word - // aligned". GCC has interpreted this to mean "use the maximum useful - // alignment for the target"; so do we. -} __attribute__((__aligned__)); typedef _Unwind_Reason_Code (*_Unwind_Stop_Fn) (int version, _Unwind_Action actions, - uint64_t exceptionClass, + _Unwind_Exception_Class exceptionClass, _Unwind_Exception* exceptionObject, struct _Unwind_Context* context, - void* stop_parameter ); - -typedef _Unwind_Reason_Code (*_Unwind_Personality_Fn)( - int version, _Unwind_Action actions, uint64_t exceptionClass, - _Unwind_Exception *exceptionObject, struct _Unwind_Context *context); -#endif + void* stop_parameter); #ifdef __cplusplus extern "C" { #endif -// -// The following are the base functions documented by the C++ ABI -// -#ifdef __USING_SJLJ_EXCEPTIONS__ -extern _Unwind_Reason_Code - _Unwind_SjLj_RaiseException(_Unwind_Exception *exception_object); -extern void _Unwind_SjLj_Resume(_Unwind_Exception *exception_object); -#else -extern _Unwind_Reason_Code - _Unwind_RaiseException(_Unwind_Exception *exception_object); -extern void _Unwind_Resume(_Unwind_Exception *exception_object); -#endif -extern void _Unwind_DeleteException(_Unwind_Exception *exception_object); - -#if defined(_LIBUNWIND_ARM_EHABI) -typedef enum { - _UVRSC_CORE = 0, /* integer register */ - _UVRSC_VFP = 1, /* vfp */ - _UVRSC_WMMXD = 3, /* Intel WMMX data register */ - _UVRSC_WMMXC = 4 /* Intel WMMX control register */ -} _Unwind_VRS_RegClass; - -typedef enum { - _UVRSD_UINT32 = 0, - _UVRSD_VFPX = 1, - _UVRSD_UINT64 = 3, - _UVRSD_FLOAT = 4, - _UVRSD_DOUBLE = 5 -} _Unwind_VRS_DataRepresentation; - -typedef enum { - _UVRSR_OK = 0, - _UVRSR_NOT_IMPLEMENTED = 1, - _UVRSR_FAILED = 2 -} _Unwind_VRS_Result; - -extern void _Unwind_Complete(_Unwind_Exception* exception_object); - -extern _Unwind_VRS_Result -_Unwind_VRS_Get(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, - uint32_t regno, _Unwind_VRS_DataRepresentation representation, - void *valuep); - -extern _Unwind_VRS_Result -_Unwind_VRS_Set(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, - uint32_t regno, _Unwind_VRS_DataRepresentation representation, - void *valuep); - -extern _Unwind_VRS_Result -_Unwind_VRS_Pop(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, - uint32_t discriminator, - _Unwind_VRS_DataRepresentation representation); -#endif - -#if !defined(_LIBUNWIND_ARM_EHABI) - -extern uintptr_t _Unwind_GetGR(struct _Unwind_Context *context, int index); -extern void _Unwind_SetGR(struct _Unwind_Context *context, int index, - uintptr_t new_value); -extern uintptr_t _Unwind_GetIP(struct _Unwind_Context *context); -extern void _Unwind_SetIP(struct _Unwind_Context *, uintptr_t new_value); - -#else // defined(_LIBUNWIND_ARM_EHABI) - -#if defined(_LIBUNWIND_UNWIND_LEVEL1_EXTERNAL_LINKAGE) -#define _LIBUNWIND_EXPORT_UNWIND_LEVEL1 extern -#else -#define _LIBUNWIND_EXPORT_UNWIND_LEVEL1 static __inline__ -#endif - -// These are de facto helper functions for ARM, which delegate the function -// calls to _Unwind_VRS_Get/Set(). These are not a part of ARM EHABI -// specification, thus these function MUST be inlined. Please don't replace -// these with the "extern" function declaration; otherwise, the program -// including this "unwind.h" header won't be ABI compatible and will result in -// link error when we are linking the program with libgcc. - -_LIBUNWIND_EXPORT_UNWIND_LEVEL1 -uintptr_t _Unwind_GetGR(struct _Unwind_Context *context, int index) { - uintptr_t value = 0; - _Unwind_VRS_Get(context, _UVRSC_CORE, (uint32_t)index, _UVRSD_UINT32, &value); - return value; -} - -_LIBUNWIND_EXPORT_UNWIND_LEVEL1 -void _Unwind_SetGR(struct _Unwind_Context *context, int index, - uintptr_t value) { - _Unwind_VRS_Set(context, _UVRSC_CORE, (uint32_t)index, _UVRSD_UINT32, &value); -} - -_LIBUNWIND_EXPORT_UNWIND_LEVEL1 -uintptr_t _Unwind_GetIP(struct _Unwind_Context *context) { - // remove the thumb-bit before returning - return _Unwind_GetGR(context, 15) & (~(uintptr_t)0x1); -} - -_LIBUNWIND_EXPORT_UNWIND_LEVEL1 -void _Unwind_SetIP(struct _Unwind_Context *context, uintptr_t value) { - uintptr_t thumb_bit = _Unwind_GetGR(context, 15) & ((uintptr_t)0x1); - _Unwind_SetGR(context, 15, value | thumb_bit); -} -#endif // defined(_LIBUNWIND_ARM_EHABI) - extern uintptr_t _Unwind_GetRegionStart(struct _Unwind_Context *context); extern uintptr_t _Unwind_GetLanguageSpecificData(struct _Unwind_Context *context); @@ -351,7 +163,7 @@ extern const void *_Unwind_Find_FDE(const void *pc, struct dwarf_eh_bases *); extern void *_Unwind_FindEnclosingFunction(void *pc); // Mac OS X does not support text-rel and data-rel addressing so these functions -// are unimplemented +// are unimplemented. extern uintptr_t _Unwind_GetDataRelBase(struct _Unwind_Context *context) LIBUNWIND_UNAVAIL; extern uintptr_t _Unwind_GetTextRelBase(struct _Unwind_Context *context) diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_arm_ehabi.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_arm_ehabi.h new file mode 100644 index 0000000000..af3289c18d --- /dev/null +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_arm_ehabi.h @@ -0,0 +1,174 @@ +// clang-format off +#if defined(__unix__) || defined(__unix) || defined(unix) || \ + (defined(__APPLE__) && defined(__MACH__)) +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +// C++ ABI Level 1 ABI documented at: +// https://github.com/ARM-software/abi-aa/blob/main/ehabi32/ehabi32.rst +// +//===----------------------------------------------------------------------===// + +#ifndef __ARM_EHABI_UNWIND_H__ +#define __ARM_EHABI_UNWIND_H__ + +typedef uint32_t _Unwind_State; + +static const _Unwind_State _US_VIRTUAL_UNWIND_FRAME = 0; +static const _Unwind_State _US_UNWIND_FRAME_STARTING = 1; +static const _Unwind_State _US_UNWIND_FRAME_RESUME = 2; +static const _Unwind_State _US_ACTION_MASK = 3; +/* Undocumented flag for force unwinding. */ +static const _Unwind_State _US_FORCE_UNWIND = 8; + +typedef uint32_t _Unwind_EHT_Header; + +struct _Unwind_Control_Block; +typedef struct _Unwind_Control_Block _Unwind_Control_Block; +#define _Unwind_Exception _Unwind_Control_Block /* Alias */ +typedef uint8_t _Unwind_Exception_Class[8]; + +struct _Unwind_Control_Block { + _Unwind_Exception_Class exception_class; + void (*exception_cleanup)(_Unwind_Reason_Code, _Unwind_Control_Block*); + + /* Unwinder cache, private fields for the unwinder's use */ + struct { + uint32_t reserved1; /* init reserved1 to 0, then don't touch */ + uint32_t reserved2; + uint32_t reserved3; + uint32_t reserved4; + uint32_t reserved5; + } unwinder_cache; + + /* Propagation barrier cache (valid after phase 1): */ + struct { + uint32_t sp; + uint32_t bitpattern[5]; + } barrier_cache; + + /* Cleanup cache (preserved over cleanup): */ + struct { + uint32_t bitpattern[4]; + } cleanup_cache; + + /* Pr cache (for pr's benefit): */ + struct { + uint32_t fnstart; /* function start address */ + _Unwind_EHT_Header* ehtp; /* pointer to EHT entry header word */ + uint32_t additional; + uint32_t reserved1; + } pr_cache; + + long long int :0; /* Enforce the 8-byte alignment */ +} __attribute__((__aligned__(8))); + +typedef _Unwind_Reason_Code (*_Unwind_Personality_Fn)( + _Unwind_State state, _Unwind_Exception *exceptionObject, + struct _Unwind_Context *context); + +#ifdef __cplusplus +extern "C" { +#endif + +// +// The following are the base functions documented by the C++ ABI +// +#ifdef __USING_SJLJ_EXCEPTIONS__ +extern _Unwind_Reason_Code + _Unwind_SjLj_RaiseException(_Unwind_Exception *exception_object); +extern void _Unwind_SjLj_Resume(_Unwind_Exception *exception_object); +#else +extern _Unwind_Reason_Code + _Unwind_RaiseException(_Unwind_Exception *exception_object); +extern void _Unwind_Resume(_Unwind_Exception *exception_object); +#endif +extern void _Unwind_DeleteException(_Unwind_Exception *exception_object); + +typedef enum { + _UVRSC_CORE = 0, /* integer register */ + _UVRSC_VFP = 1, /* vfp */ + _UVRSC_WMMXD = 3, /* Intel WMMX data register */ + _UVRSC_WMMXC = 4, /* Intel WMMX control register */ + _UVRSC_PSEUDO = 5 /* Special purpose pseudo register */ +} _Unwind_VRS_RegClass; + +typedef enum { + _UVRSD_UINT32 = 0, + _UVRSD_VFPX = 1, + _UVRSD_UINT64 = 3, + _UVRSD_FLOAT = 4, + _UVRSD_DOUBLE = 5 +} _Unwind_VRS_DataRepresentation; + +typedef enum { + _UVRSR_OK = 0, + _UVRSR_NOT_IMPLEMENTED = 1, + _UVRSR_FAILED = 2 +} _Unwind_VRS_Result; + +extern void _Unwind_Complete(_Unwind_Exception* exception_object); + +extern _Unwind_VRS_Result +_Unwind_VRS_Get(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, + uint32_t regno, _Unwind_VRS_DataRepresentation representation, + void *valuep); + +extern _Unwind_VRS_Result +_Unwind_VRS_Set(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, + uint32_t regno, _Unwind_VRS_DataRepresentation representation, + void *valuep); + +extern _Unwind_VRS_Result +_Unwind_VRS_Pop(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, + uint32_t discriminator, + _Unwind_VRS_DataRepresentation representation); + +#if defined(_LIBUNWIND_UNWIND_LEVEL1_EXTERNAL_LINKAGE) +#define _LIBUNWIND_EXPORT_UNWIND_LEVEL1 extern +#else +#define _LIBUNWIND_EXPORT_UNWIND_LEVEL1 static __inline__ +#endif + +// These are de facto helper functions for ARM, which delegate the function +// calls to _Unwind_VRS_Get/Set(). These are not a part of ARM EHABI +// specification, thus these function MUST be inlined. Please don't replace +// these with the "extern" function declaration; otherwise, the program +// including this "unwind.h" header won't be ABI compatible and will result in +// link error when we are linking the program with libgcc. + +_LIBUNWIND_EXPORT_UNWIND_LEVEL1 +uintptr_t _Unwind_GetGR(struct _Unwind_Context *context, int index) { + uintptr_t value = 0; + _Unwind_VRS_Get(context, _UVRSC_CORE, (uint32_t)index, _UVRSD_UINT32, &value); + return value; +} + +_LIBUNWIND_EXPORT_UNWIND_LEVEL1 +void _Unwind_SetGR(struct _Unwind_Context *context, int index, + uintptr_t value) { + _Unwind_VRS_Set(context, _UVRSC_CORE, (uint32_t)index, _UVRSD_UINT32, &value); +} + +_LIBUNWIND_EXPORT_UNWIND_LEVEL1 +uintptr_t _Unwind_GetIP(struct _Unwind_Context *context) { + // remove the thumb-bit before returning + return _Unwind_GetGR(context, 15) & (~(uintptr_t)0x1); +} + +_LIBUNWIND_EXPORT_UNWIND_LEVEL1 +void _Unwind_SetIP(struct _Unwind_Context *context, uintptr_t value) { + uintptr_t thumb_bit = _Unwind_GetGR(context, 15) & ((uintptr_t)0x1); + _Unwind_SetGR(context, 15, value | thumb_bit); +} + +#ifdef __cplusplus +} +#endif + +#endif // __ARM_EHABI_UNWIND_H__ +#endif diff --git a/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_itanium.h b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_itanium.h new file mode 100644 index 0000000000..e0ff9a2bd9 --- /dev/null +++ b/nativelib/src/main/resources/scala-native/platform/posix/libunwind/unwind_itanium.h @@ -0,0 +1,80 @@ +// clang-format off +#if defined(__unix__) || defined(__unix) || defined(unix) || \ + (defined(__APPLE__) && defined(__MACH__)) +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +// C++ ABI Level 1 ABI documented at: +// https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html +// +//===----------------------------------------------------------------------===// + +#ifndef __ITANIUM_UNWIND_H__ +#define __ITANIUM_UNWIND_H__ + +struct _Unwind_Context; // opaque +struct _Unwind_Exception; // forward declaration +typedef struct _Unwind_Exception _Unwind_Exception; +typedef uint64_t _Unwind_Exception_Class; + +struct _Unwind_Exception { + _Unwind_Exception_Class exception_class; + void (*exception_cleanup)(_Unwind_Reason_Code reason, + _Unwind_Exception *exc); +#if defined(__SEH__) && !defined(__USING_SJLJ_EXCEPTIONS__) + uintptr_t private_[6]; +#else + uintptr_t private_1; // non-zero means forced unwind + uintptr_t private_2; // holds sp that phase1 found for phase2 to use +#endif +#if __SIZEOF_POINTER__ == 4 + // The implementation of _Unwind_Exception uses an attribute mode on the + // above fields which has the side effect of causing this whole struct to + // round up to 32 bytes in size (48 with SEH). To be more explicit, we add + // pad fields added for binary compatibility. + uint32_t reserved[3]; +#endif + // The Itanium ABI requires that _Unwind_Exception objects are "double-word + // aligned". GCC has interpreted this to mean "use the maximum useful + // alignment for the target"; so do we. +} __attribute__((__aligned__)); + +typedef _Unwind_Reason_Code (*_Unwind_Personality_Fn)( + int version, _Unwind_Action actions, uint64_t exceptionClass, + _Unwind_Exception *exceptionObject, struct _Unwind_Context *context); + +#ifdef __cplusplus +extern "C" { +#endif + +// +// The following are the base functions documented by the C++ ABI +// +#ifdef __USING_SJLJ_EXCEPTIONS__ +extern _Unwind_Reason_Code + _Unwind_SjLj_RaiseException(_Unwind_Exception *exception_object); +extern void _Unwind_SjLj_Resume(_Unwind_Exception *exception_object); +#else +extern _Unwind_Reason_Code + _Unwind_RaiseException(_Unwind_Exception *exception_object); +extern void _Unwind_Resume(_Unwind_Exception *exception_object); +#endif +extern void _Unwind_DeleteException(_Unwind_Exception *exception_object); + + +extern uintptr_t _Unwind_GetGR(struct _Unwind_Context *context, int index); +extern void _Unwind_SetGR(struct _Unwind_Context *context, int index, + uintptr_t new_value); +extern uintptr_t _Unwind_GetIP(struct _Unwind_Context *context); +extern void _Unwind_SetIP(struct _Unwind_Context *, uintptr_t new_value); + +#ifdef __cplusplus +} +#endif + +#endif // __ITANIUM_UNWIND_H__ +#endif From 5514b93390b8c65dfd4677234417fd3b2e0cce93 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Sun, 26 Feb 2023 13:35:54 +0100 Subject: [PATCH 20/61] Commix GC - fix deadlocks due to missaligned pointers when marking range (#3185) * When marking region in Commix ignore misalligned pointers. These can be an interim pointers. The offset of misalligned pointers was leading to undefined behaviour when accessing class field offsets. The resulting SIGSEV would trigger signal handlers defined in SignalConfig, which would again try to allocate objects using GC leading to deadlock * Apply fix to ImmixGC to prevent marking missaligned objects when marking ranges of memory (eg. stack) * Apply fix when marking Commix stack --- .../resources/scala-native/gc/commix/Marker.c | 12 ++-- .../gc/commix/datastructures/Bytemap.h | 15 +++-- .../resources/scala-native/gc/immix/Marker.c | 60 +++++++++---------- .../gc/immix/datastructures/Bytemap.h | 17 +++--- 4 files changed, 49 insertions(+), 55 deletions(-) diff --git a/nativelib/src/main/resources/scala-native/gc/commix/Marker.c b/nativelib/src/main/resources/scala-native/gc/commix/Marker.c index feb2200fdc..209e7f626c 100644 --- a/nativelib/src/main/resources/scala-native/gc/commix/Marker.c +++ b/nativelib/src/main/resources/scala-native/gc/commix/Marker.c @@ -143,7 +143,10 @@ int Marker_markRange(Heap *heap, Stats *stats, GreyPacket **outHolder, word_t **limit = fields + length; for (word_t **current = fields; current < limit; current++) { word_t *field = *current; - if (Heap_IsWordInHeap(heap, field)) { + // Memory allocated by GC is alligned, ignore unaligned pointers e.g. + // interim pointers, otherwise we risk undefined behaviour when assuming + // memory layout of underlying object. + if (Heap_IsWordInHeap(heap, field) && Bytemap_isPtrAligned(field)) { ObjectMeta *fieldMeta = Bytemap_Get(bytemap, field); if (ObjectMeta_IsAllocated(fieldMeta)) { Marker_markObject(heap, stats, outHolder, outWeakRefHolder, @@ -386,11 +389,10 @@ void Marker_markProgramStack(Heap *heap, Stats *stats, GreyPacket **outHolder, word_t **stackBottom = __stack_bottom; while (current <= stackBottom) { - - word_t *stackObject = *current; - if (Heap_IsWordInHeap(heap, stackObject)) { + word_t *obj = *current; + if (Heap_IsWordInHeap(heap, obj) && Bytemap_isPtrAligned(obj)) { Marker_markConservative(heap, stats, outHolder, outWeakRefHolder, - stackObject); + obj); } current += 1; } diff --git a/nativelib/src/main/resources/scala-native/gc/commix/datastructures/Bytemap.h b/nativelib/src/main/resources/scala-native/gc/commix/datastructures/Bytemap.h index 88369ead0a..e67c0cd543 100644 --- a/nativelib/src/main/resources/scala-native/gc/commix/datastructures/Bytemap.h +++ b/nativelib/src/main/resources/scala-native/gc/commix/datastructures/Bytemap.h @@ -18,23 +18,22 @@ typedef struct { void Bytemap_Init(Bytemap *bytemap, word_t *firstAddress, size_t size); +static inline bool Bytemap_isPtrAligned(word_t *address) { + word_t aligned = ((word_t)address & ALLOCATION_ALIGNMENT_INVERSE_MASK); + return (word_t *)aligned == address; +} + static inline size_t Bytemap_index(Bytemap *bytemap, word_t *address) { size_t index = (address - bytemap->firstAddress) / ALLOCATION_ALIGNMENT_WORDS; assert(address >= bytemap->firstAddress); assert(index < bytemap->size); - assert(((word_t)address & ALLOCATION_ALIGNMENT_INVERSE_MASK) == - (word_t)address); + assert(Bytemap_isPtrAligned(address)); return index; } static inline ObjectMeta *Bytemap_Get(Bytemap *bytemap, word_t *address) { - size_t index = - (address - bytemap->firstAddress) / ALLOCATION_ALIGNMENT_WORDS; - assert(address >= bytemap->firstAddress); - assert(index < bytemap->size); - assert(((word_t)address & ALLOCATION_ALIGNMENT_INVERSE_MASK) == - (word_t)address); + size_t index = Bytemap_index(bytemap, address); return &bytemap->data[index]; } diff --git a/nativelib/src/main/resources/scala-native/gc/immix/Marker.c b/nativelib/src/main/resources/scala-native/gc/immix/Marker.c index af331abd45..aaeb295ce3 100644 --- a/nativelib/src/main/resources/scala-native/gc/immix/Marker.c +++ b/nativelib/src/main/resources/scala-native/gc/immix/Marker.c @@ -18,6 +18,7 @@ extern word_t **__stack_bottom; void Marker_markObject(Heap *heap, Stack *stack, Bytemap *bytemap, Object *object, ObjectMeta *objectMeta) { assert(ObjectMeta_IsAllocated(objectMeta)); + assert(object->rtti != NULL); if (Object_IsWeakReference(object)) { // Added to the WeakReference stack for additional later visit @@ -29,6 +30,16 @@ void Marker_markObject(Heap *heap, Stack *stack, Bytemap *bytemap, Stack_Push(stack, object); } +static inline void Marker_markField(Heap *heap, Stack *stack, Field_t field) { + if (Heap_IsWordInHeap(heap, field)) { + ObjectMeta *fieldMeta = Bytemap_Get(heap->bytemap, field); + if (ObjectMeta_IsAllocated(fieldMeta)) { + Object *object = (Object *)field; + Marker_markObject(heap, stack, heap->bytemap, object, fieldMeta); + } + } +} + void Marker_markConservative(Heap *heap, Stack *stack, word_t *address) { assert(Heap_IsWordInHeap(heap, address)); Object *object = Object_GetUnmarkedObject(heap, address); @@ -46,21 +57,13 @@ void Marker_Mark(Heap *heap, Stack *stack) { Bytemap *bytemap = heap->bytemap; while (!Stack_IsEmpty(stack)) { Object *object = Stack_Pop(stack); - if (Object_IsArray(object)) { if (object->rtti->rt.id == __object_array_id) { ArrayHeader *arrayHeader = (ArrayHeader *)object; size_t length = arrayHeader->length; word_t **fields = (word_t **)(arrayHeader + 1); for (int i = 0; i < length; i++) { - word_t *field = fields[i]; - if (Heap_IsWordInHeap(heap, field)) { - ObjectMeta *fieldMeta = Bytemap_Get(bytemap, field); - if (ObjectMeta_IsAllocated(fieldMeta)) { - Marker_markObject(heap, stack, bytemap, - (Object *)field, fieldMeta); - } - } + Marker_markField(heap, stack, fields[i]); } } // non-object arrays do not contain pointers @@ -69,20 +72,24 @@ void Marker_Mark(Heap *heap, Stack *stack) { for (int i = 0; ptr_map[i] != LAST_FIELD_OFFSET; i++) { if (Object_IsReferantOfWeakReference(object, ptr_map[i])) continue; - - word_t *field = object->fields[ptr_map[i]]; - if (Heap_IsWordInHeap(heap, field)) { - ObjectMeta *fieldMeta = Bytemap_Get(bytemap, field); - if (ObjectMeta_IsAllocated(fieldMeta)) { - Marker_markObject(heap, stack, bytemap, (Object *)field, - fieldMeta); - } - } + Marker_markField(heap, stack, object->fields[ptr_map[i]]); } } } } +NO_SANITIZE void Marker_markRange(Heap *heap, Stack *stack, word_t **from, + word_t **to) { + assert(from != NULL); + assert(to != NULL); + for (word_t **current = from; current <= to; current += 1) { + word_t *addr = *current; + if (Heap_IsWordInHeap(heap, addr) && Bytemap_isPtrAligned(addr)) { + Marker_markConservative(heap, stack, addr); + } + } +} + void Marker_markProgramStack(Heap *heap, Stack *stack) { // Dumps registers into 'regs' which is on stack jmp_buf regs; @@ -92,14 +99,7 @@ void Marker_markProgramStack(Heap *heap, Stack *stack) { word_t **current = &dummy; word_t **stackBottom = __stack_bottom; - while (current <= stackBottom) { - - word_t *stackObject = *current; - if (Heap_IsWordInHeap(heap, stackObject)) { - Marker_markConservative(heap, stack, stackObject); - } - current += 1; - } + Marker_markRange(heap, stack, stackTop, stackBottom); } void Marker_markModules(Heap *heap, Stack *stack) { @@ -108,13 +108,7 @@ void Marker_markModules(Heap *heap, Stack *stack) { Bytemap *bytemap = heap->bytemap; for (int i = 0; i < nb_modules; i++) { Object *object = (Object *)modules[i]; - if (Heap_IsWordInHeap(heap, (word_t *)object)) { - // is within heap - ObjectMeta *objectMeta = Bytemap_Get(bytemap, (word_t *)object); - if (ObjectMeta_IsAllocated(objectMeta)) { - Marker_markObject(heap, stack, bytemap, object, objectMeta); - } - } + Marker_markField(heap, stack, (Field_t)object); } } diff --git a/nativelib/src/main/resources/scala-native/gc/immix/datastructures/Bytemap.h b/nativelib/src/main/resources/scala-native/gc/immix/datastructures/Bytemap.h index 79859ae48c..e67c0cd543 100644 --- a/nativelib/src/main/resources/scala-native/gc/immix/datastructures/Bytemap.h +++ b/nativelib/src/main/resources/scala-native/gc/immix/datastructures/Bytemap.h @@ -5,8 +5,8 @@ #include #include #include "GCTypes.h" -#include "Log.h" #include "../Constants.h" +#include "Log.h" #include "../metadata/ObjectMeta.h" typedef struct { @@ -18,23 +18,22 @@ typedef struct { void Bytemap_Init(Bytemap *bytemap, word_t *firstAddress, size_t size); +static inline bool Bytemap_isPtrAligned(word_t *address) { + word_t aligned = ((word_t)address & ALLOCATION_ALIGNMENT_INVERSE_MASK); + return (word_t *)aligned == address; +} + static inline size_t Bytemap_index(Bytemap *bytemap, word_t *address) { size_t index = (address - bytemap->firstAddress) / ALLOCATION_ALIGNMENT_WORDS; assert(address >= bytemap->firstAddress); assert(index < bytemap->size); - assert(((word_t)address & ALLOCATION_ALIGNMENT_INVERSE_MASK) == - (word_t)address); + assert(Bytemap_isPtrAligned(address)); return index; } static inline ObjectMeta *Bytemap_Get(Bytemap *bytemap, word_t *address) { - size_t index = - (address - bytemap->firstAddress) / ALLOCATION_ALIGNMENT_WORDS; - assert(address >= bytemap->firstAddress); - assert(index < bytemap->size); - assert(((word_t)address & ALLOCATION_ALIGNMENT_INVERSE_MASK) == - (word_t)address); + size_t index = Bytemap_index(bytemap, address); return &bytemap->data[index]; } From 04476d7ade8b81426c55082f8b54f2f9c91ce9f1 Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Sun, 26 Feb 2023 07:38:39 -0500 Subject: [PATCH 21/61] Port JSR-166 interfaces `java.util.concurrent.{BlockingDeque,TransferQueue} (#3188) --- .../java/util/concurrent/BlockingDeque.scala | 31 +++++++++++++++++++ .../java/util/concurrent/TransferQueue.scala | 21 +++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 javalib/src/main/scala/java/util/concurrent/BlockingDeque.scala create mode 100644 javalib/src/main/scala/java/util/concurrent/TransferQueue.scala diff --git a/javalib/src/main/scala/java/util/concurrent/BlockingDeque.scala b/javalib/src/main/scala/java/util/concurrent/BlockingDeque.scala new file mode 100644 index 0000000000..dda0e77c49 --- /dev/null +++ b/javalib/src/main/scala/java/util/concurrent/BlockingDeque.scala @@ -0,0 +1,31 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util +package concurrent + +trait BlockingDeque[E] extends BlockingQueue[E] with Deque[E] { + + def addFirst(e: E): Unit + + def addLast(e: E): Unit + + def putFirst(e: E): Unit + + def putLast(e: E): Unit + + def offerFirst(e: E, timeout: Long, unit: TimeUnit): Boolean + + def offerLast(e: E, timeout: Long, unit: TimeUnit): Boolean + + def takeFirst(): E + + def takeLast(): E + + def pollFirst(timeout: Long, unit: TimeUnit): E + + def pollLast(timeout: Long, unit: TimeUnit): E +} diff --git a/javalib/src/main/scala/java/util/concurrent/TransferQueue.scala b/javalib/src/main/scala/java/util/concurrent/TransferQueue.scala new file mode 100644 index 0000000000..b4d6dfabaf --- /dev/null +++ b/javalib/src/main/scala/java/util/concurrent/TransferQueue.scala @@ -0,0 +1,21 @@ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package java.util +package concurrent + +trait TransferQueue[E] extends BlockingQueue[E] { + + def tryTransfer(e: E): Boolean + + def transfer(e: E): Unit + + def tryTransfer(e: E, timeout: Long, unit: TimeUnit): Boolean + + def hasWaitingConsumer(): Boolean + + def getWaitingConsumerCount(): Int +} From 764f0501affdf240cf3443aa64dc1ba7920d5359 Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Sun, 26 Feb 2023 13:44:37 +0100 Subject: [PATCH 22/61] Fix #3173: Linux executable file .comment section shows build info (#3183) * Fix #3173: Linux executable ident shows build info * Incorporate naming improvement from review --- .../scala/scala/scalanative/build/Build.scala | 3 +- .../scala/scalanative/build/Config.scala | 5 +++ .../scala/scala/scalanative/build/LLVM.scala | 35 +++++++++++++++++++ .../scalanative/build/core/ScalaNative.scala | 3 ++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/tools/src/main/scala/scala/scalanative/build/Build.scala b/tools/src/main/scala/scala/scalanative/build/Build.scala index ee17f6f913..292377d2ea 100644 --- a/tools/src/main/scala/scala/scalanative/build/Build.scala +++ b/tools/src/main/scala/scala/scalanative/build/Build.scala @@ -76,7 +76,8 @@ object Build { // optimize and generate ll val generated = { val optimized = ScalaNative.optimize(fconfig, linked) - ScalaNative.codegen(fconfig, optimized) + ScalaNative.codegen(fconfig, optimized) ++: + ScalaNative.genBuildInfo(fconfig) // ident list may be empty } val objectPaths = config.logger.time("Compiling to native code") { diff --git a/tools/src/main/scala/scala/scalanative/build/Config.scala b/tools/src/main/scala/scala/scalanative/build/Config.scala index 7655a8391f..a47015ca17 100644 --- a/tools/src/main/scala/scala/scalanative/build/Config.scala +++ b/tools/src/main/scala/scala/scalanative/build/Config.scala @@ -88,6 +88,11 @@ sealed trait Config { compilerConfig.targetTriple.exists { customTriple => Seq("mac", "apple", "darwin").exists(customTriple.contains(_)) } + + private[scalanative] lazy val targetsLinux: Boolean = Platform.isLinux || + compilerConfig.targetTriple.exists { customTriple => + Seq("linux").exists(customTriple.contains(_)) + } } object Config { diff --git a/tools/src/main/scala/scala/scalanative/build/LLVM.scala b/tools/src/main/scala/scala/scalanative/build/LLVM.scala index 36555519d7..658be0cb19 100644 --- a/tools/src/main/scala/scala/scalanative/build/LLVM.scala +++ b/tools/src/main/scala/scala/scalanative/build/LLVM.scala @@ -308,4 +308,39 @@ private[scalanative] object LLVM { else str } + private def constructIdent(config: Config): String = { + val snVersion = scala.scalanative.nir.Versions.current + + val ident1 = s"Scala Native ${snVersion}" + val ident2 = s"Mode: ${config.mode}, LTO: ${config.LTO}, GC: ${config.gc}" + + s"${ident1} (${ident2})" + } + + private[scalanative] def generateLLVMIdent( + config: Config + ): Seq[java.nio.file.Path] = { + + /* Enable feature only where known to work. Add to list as experience grows + * FreeBSD uses elf format so it _should_ work, but it has not been + * exercised. + */ + if (!config.targetsLinux) Seq.empty[Path] + else { + // From lld.llvm.org doc: readelf --string-dump .comment + val workDir = config.workdir + val identPath = workDir.resolve("ScalaNativeIdent.ll") + val ident = constructIdent(config) + + val pw = new java.io.PrintWriter(identPath.toFile) // truncate if exists + + try { + pw.println("!llvm.ident = !{!0}") + pw.println(s"""!0 = !{!"${ident}"}""") + } finally pw.close() + + Seq(identPath) + } + } + } diff --git a/tools/src/main/scala/scala/scalanative/build/core/ScalaNative.scala b/tools/src/main/scala/scala/scalanative/build/core/ScalaNative.scala index 6071c9d536..957382e114 100644 --- a/tools/src/main/scala/scala/scalanative/build/core/ScalaNative.scala +++ b/tools/src/main/scala/scala/scalanative/build/core/ScalaNative.scala @@ -185,4 +185,7 @@ private[scalanative] object ScalaNative { Global.Top(encoded) } + def genBuildInfo(config: Config): Seq[java.nio.file.Path] = + LLVM.generateLLVMIdent(config) + } From d0975d95d99817c1fb754dcd122ba74b79bc4cdf Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Sun, 26 Feb 2023 15:36:56 +0100 Subject: [PATCH 23/61] Allow to print stacktraces in GC on Windows (#3189) * Enable printing stack traces in GC on Windows. Define extern bindings to resolve sizeof unwind context/cursor. Allocate long strings in Zone instead of stack * Adapt SignalConfig and reduce stackallocated array sizes * Run clangfmt --- .../src/main/scala/java/lang/Throwables.scala | 29 +++++++++++-------- .../scala-native/gc/immix_commix/StackTrace.c | 25 ++++++++-------- .../scala-native/gc/immix_commix/StackTrace.h | 4 +-- .../scala-native/platform/posix/unwind.c | 3 ++ .../resources/scala-native/platform/unwind.h | 2 ++ .../scala-native/platform/windows/unwind.c | 28 +++++++++++------- .../scala/scalanative/runtime/unwind.scala | 8 ++++- .../signalhandling/SignalConfig.scala | 19 ++++++------ 8 files changed, 70 insertions(+), 48 deletions(-) diff --git a/javalib/src/main/scala/java/lang/Throwables.scala b/javalib/src/main/scala/java/lang/Throwables.scala index e90d6a430b..74ac7eb7e6 100644 --- a/javalib/src/main/scala/java/lang/Throwables.scala +++ b/javalib/src/main/scala/java/lang/Throwables.scala @@ -1,6 +1,7 @@ package java.lang import scala.collection.mutable +import scala.scalanative.meta.LinktimeInfo import scalanative.unsafe._ import scalanative.unsigned._ import scalanative.runtime.unwind @@ -11,10 +12,10 @@ private[lang] object StackTrace { private def makeStackTraceElement( cursor: Ptr[scala.Byte] - ): StackTraceElement = { + )(implicit zone: Zone): StackTraceElement = { val nameMax = 1024 - val name: Ptr[CChar] = stackalloc[CChar](nameMax.toUInt) - val offset: Ptr[scala.Byte] = stackalloc[scala.Byte](8.toUInt) + val name = alloc[CChar](nameMax.toUInt) + val offset = alloc[scala.Long]() unwind.get_proc_name(cursor, name, nameMax.toUInt, offset) @@ -33,7 +34,7 @@ private[lang] object StackTrace { private def cachedStackTraceElement( cursor: Ptr[scala.Byte], ip: CUnsignedLong - ): StackTraceElement = + )(implicit zone: Zone): StackTraceElement = cache.getOrElseUpdate(ip, makeStackTraceElement(cursor)) @noinline private[lang] def currentStackTrace(): Array[StackTraceElement] = { @@ -41,15 +42,19 @@ private[lang] object StackTrace { val context: Ptr[scala.Byte] = stackalloc[scala.Byte](2048.toUInt) val offset: Ptr[scala.Byte] = stackalloc[scala.Byte](8.toUInt) val ip = stackalloc[CUnsignedLongLong]() - var buffer = mutable.ArrayBuffer.empty[StackTraceElement] - - unwind.get_context(context) - unwind.init_local(cursor, context) - while (unwind.step(cursor) > 0) { - unwind.get_reg(cursor, unwind.UNW_REG_IP, ip) - buffer += cachedStackTraceElement(cursor, !ip) + val buffer = mutable.ArrayBuffer.empty[StackTraceElement] + Zone { implicit z => + val cursor = alloc[scala.Byte](unwind.sizeOfCursor) + val context = alloc[scala.Byte](unwind.sizeOfContext) + val ip = stackalloc[CSize]() + + unwind.get_context(context) + unwind.init_local(cursor, context) + while (unwind.step(cursor) > 0) { + unwind.get_reg(cursor, unwind.UNW_REG_IP, ip) + buffer += cachedStackTraceElement(cursor, !ip) + } } - buffer.toArray } } diff --git a/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c b/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c index 86be060c91..8ff68c8158 100644 --- a/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c +++ b/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c @@ -1,26 +1,27 @@ #include +#include +#include #include "StackTrace.h" void StackTrace_PrintStackTrace() { -#if defined(_WIN32) - printf("Stacktrace not implemented in Windows\n"); -#else - unw_cursor_t cursor; - unw_context_t context; - unw_getcontext(&context); - unw_init_local(&cursor, &context); + void *cursor = malloc(scalanative_unwind_sizeof_cursor()); + void *context = malloc(scalanative_unwind_sizeof_context()); + scalanative_unwind_get_context(context); + scalanative_unwind_init_local(cursor, context); - while (unw_step(&cursor) > 0) { - unw_word_t offset, pc; - unw_get_reg(&cursor, UNW_REG_IP, &pc); + while (scalanative_unwind_step(cursor) > 0) { + uint64_t offset, pc; + scalanative_unwind_get_reg(cursor, scalanative_unw_reg_ip(), &pc); if (pc == 0) { break; } char sym[256]; - if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) { + if (scalanative_unwind_get_proc_name(cursor, sym, sizeof(sym), + &offset) == 0) { printf("\tat %s\n", sym); } } -#endif + free(cursor); + free(context); } \ No newline at end of file diff --git a/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.h b/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.h index ed529875ae..ce5e33f4e0 100644 --- a/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.h +++ b/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.h @@ -1,9 +1,7 @@ #ifndef IMMIX_STACKTRACE_H #define IMMIX_STACKTRACE_H -#ifndef _WIN32 -#include "../../platform/posix/libunwind/libunwind.h" -#endif +#include "../../platform/unwind.h" void StackTrace_PrintStackTrace(); diff --git a/nativelib/src/main/resources/scala-native/platform/posix/unwind.c b/nativelib/src/main/resources/scala-native/platform/posix/unwind.c index 256462191e..6d136219ea 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/unwind.c +++ b/nativelib/src/main/resources/scala-native/platform/posix/unwind.c @@ -29,4 +29,7 @@ int scalanative_unwind_get_reg(void *cursor, int regnum, int scalanative_unw_reg_ip() { return UNW_REG_IP; } +size_t scalanative_unwind_sizeof_context() { return sizeof(unw_context_t); } +size_t scalanative_unwind_sizeof_cursor() { return sizeof(unw_cursor_t); } + #endif // Unix or Mac OS diff --git a/nativelib/src/main/resources/scala-native/platform/unwind.h b/nativelib/src/main/resources/scala-native/platform/unwind.h index 0a5b02fb81..1d5818b553 100644 --- a/nativelib/src/main/resources/scala-native/platform/unwind.h +++ b/nativelib/src/main/resources/scala-native/platform/unwind.h @@ -11,5 +11,7 @@ int scalanative_unwind_get_proc_name(void *cursor, char *buffer, size_t length, int scalanative_unwind_get_reg(void *cursor, int regnum, unsigned long long *valp); int scalanative_unw_reg_ip(); +size_t scalanative_unwind_sizeof_context(); +size_t scalanative_unwind_sizeof_cursor(); #endif diff --git a/nativelib/src/main/resources/scala-native/platform/windows/unwind.c b/nativelib/src/main/resources/scala-native/platform/windows/unwind.c index aac99266c8..820ffe842d 100644 --- a/nativelib/src/main/resources/scala-native/platform/windows/unwind.c +++ b/nativelib/src/main/resources/scala-native/platform/windows/unwind.c @@ -8,22 +8,27 @@ #include "../unwind.h" #define MAX_LENGTH_OF_CALLSTACK 255 +#define MAX_LENGHT_OF_NAME 255 typedef struct _UnwindContext { - void **stack; + void *stack[MAX_LENGTH_OF_CALLSTACK]; unsigned short frames; HANDLE process; DWORD64 cursor; - SYMBOL_INFOW symbol; + struct { + SYMBOL_INFOW info; + wchar_t nameBuffer[MAX_LENGHT_OF_NAME + 1]; + } symbol; } UnwindContext; int scalanative_unwind_get_context(void *context) { return 0; } int scalanative_unwind_init_local(void *cursor, void *context) { static int symInitialized = 0; - UnwindContext *ucontext = (UnwindContext *)cursor; + UnwindContext *ucontext = (UnwindContext *)context; + UnwindContext **ucontextRef = (UnwindContext **)cursor; + *ucontextRef = ucontext; memset(ucontext, 0, sizeof(UnwindContext)); - ucontext->stack = (void **)context; ucontext->process = GetCurrentProcess(); if (!symInitialized) { if (SymInitialize(ucontext->process, NULL, TRUE) == FALSE) { @@ -34,13 +39,13 @@ int scalanative_unwind_init_local(void *cursor, void *context) { ucontext->frames = CaptureStackBackTrace(0, MAX_LENGTH_OF_CALLSTACK, ucontext->stack, NULL); ucontext->cursor = 0; - ucontext->symbol.MaxNameLen = 255; - ucontext->symbol.SizeOfStruct = sizeof(SYMBOL_INFOW); + ucontext->symbol.info.MaxNameLen = MAX_LENGHT_OF_NAME; + ucontext->symbol.info.SizeOfStruct = sizeof(ucontext->symbol.info); return 0; } int scalanative_unwind_step(void *cursor) { - UnwindContext *ucontext = (UnwindContext *)cursor; + UnwindContext *ucontext = *(UnwindContext **)cursor; return ucontext->frames - (++ucontext->cursor); } @@ -49,10 +54,10 @@ int scalanative_unwind_get_proc_name(void *cursor, char *buffer, size_t length, DWORD displacement = 0; IMAGEHLP_LINE line; int fileNameLen = 0; - UnwindContext *ucontext = (UnwindContext *)cursor; + UnwindContext *ucontext = *(UnwindContext **)cursor; if (ucontext->cursor < ucontext->frames) { void *address = ucontext->stack[ucontext->cursor]; - PSYMBOL_INFOW symbol = &ucontext->symbol; + PSYMBOL_INFOW symbol = &ucontext->symbol.info; SymFromAddrW(ucontext->process, (DWORD64)address, 0, symbol); snprintf(buffer, length, "%ws", symbol->Name); memcpy(offset, &(symbol->Address), sizeof(void *)); @@ -70,7 +75,7 @@ int scalanative_unwind_get_proc_name(void *cursor, char *buffer, size_t length, int scalanative_unwind_get_reg(void *cursor, int regnum, unsigned long long *valp) { - UnwindContext *ucontext = (UnwindContext *)cursor; + UnwindContext *ucontext = *(UnwindContext **)cursor; *valp = (unsigned long long)(ucontext->stack[ucontext->cursor]); return 0; } @@ -79,4 +84,7 @@ int scalanative_unwind_get_reg(void *cursor, int regnum, // used int scalanative_unw_reg_ip() { return -1; } +size_t scalanative_unwind_sizeof_context() { return sizeof(UnwindContext); } +size_t scalanative_unwind_sizeof_cursor() { return sizeof(UnwindContext *); } + #endif // _WIN32 diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/unwind.scala b/nativelib/src/main/scala/scala/scalanative/runtime/unwind.scala index d2c2871879..5ec1957047 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/unwind.scala +++ b/nativelib/src/main/scala/scala/scalanative/runtime/unwind.scala @@ -16,7 +16,7 @@ object unwind { cursor: Ptr[Byte], buffer: CString, length: CSize, - offset: Ptr[Byte] + offset: Ptr[Long] ): CInt = extern @name("scalanative_unwind_get_reg") def get_reg( @@ -27,4 +27,10 @@ object unwind { @name("scalanative_unw_reg_ip") def UNW_REG_IP: CInt = extern + + @name("scalanative_unwind_sizeof_context") + def sizeOfContext: CSize = extern + + @name("scalanative_unwind_sizeof_cursor") + def sizeOfCursor: CSize = extern } diff --git a/test-interface/src/main/scala/scala/scalanative/testinterface/signalhandling/SignalConfig.scala b/test-interface/src/main/scala/scala/scalanative/testinterface/signalhandling/SignalConfig.scala index 900bc776a9..c8bf69963b 100644 --- a/test-interface/src/main/scala/scala/scalanative/testinterface/signalhandling/SignalConfig.scala +++ b/test-interface/src/main/scala/scala/scalanative/testinterface/signalhandling/SignalConfig.scala @@ -1,6 +1,6 @@ package scala.scalanative.testinterface.signalhandling -import scala.scalanative.meta.LinktimeInfo.isWindows +import scala.scalanative.meta.LinktimeInfo._ import scala.scalanative.libc.stdlib._ import scala.scalanative.libc.signal._ import scala.scalanative.libc.string._ @@ -69,22 +69,21 @@ private[testinterface] object SignalConfig { str } - val stackTraceHeader: Ptr[CChar] = stackalloc[CChar](2048.toUInt) - stackTraceHeader(0.toUInt) = 0.toByte + val stackTraceHeader: Ptr[CChar] = stackalloc[CChar](100.toUInt) strcat(stackTraceHeader, errorTag) strcat(stackTraceHeader, c" Fatal signal ") strcat(stackTraceHeader, signalNumberStr) strcat(stackTraceHeader, c" caught\n") printError(stackTraceHeader) - val cursor: Ptr[scala.Byte] = stackalloc[scala.Byte](2048.toUInt) - val context: Ptr[scala.Byte] = stackalloc[scala.Byte](2048.toUInt) + val cursor = stackalloc[Byte](unwind.sizeOfCursor) + val context = stackalloc[Byte](unwind.sizeOfContext) unwind.get_context(context) unwind.init_local(cursor, context) while (unwind.step(cursor) > 0) { - val offset: Ptr[scala.Byte] = stackalloc[scala.Byte](8.toUInt) - val pc = stackalloc[CUnsignedLongLong]() + val offset = stackalloc[Long]() + val pc = stackalloc[CSize]() unwind.get_reg(cursor, unwind.UNW_REG_IP, pc) if (!pc == 0.toUInt) return val symMax = 1024 @@ -96,11 +95,11 @@ private[testinterface] object SignalConfig { offset ) == 0) { sym(symMax - 1) = 0.toByte - val className: Ptr[CChar] = stackalloc[CChar](1024.toUInt) - val methodName: Ptr[CChar] = stackalloc[CChar](1024.toUInt) + val className: Ptr[CChar] = stackalloc[CChar](512.toUInt) + val methodName: Ptr[CChar] = stackalloc[CChar](512.toUInt) SymbolFormatter.asyncSafeFromSymbol(sym, className, methodName) - val formattedSymbol: Ptr[CChar] = stackalloc[CChar](2048.toUInt) + val formattedSymbol: Ptr[CChar] = stackalloc[CChar](1100.toUInt) formattedSymbol(0) = 0.toByte strcat(formattedSymbol, errorTag) strcat(formattedSymbol, c" at ") From 7a0fa9d835017f9f3dd7ae95561b5a75cfb72108 Mon Sep 17 00:00:00 2001 From: philwalk Date: Tue, 28 Feb 2023 10:43:19 +0100 Subject: [PATCH 24/61] Allow to build on Windows cygwin and msys platforms (#3180) These changes add support for building on Windows in cygwin or msys environments. The most significant changes include: * convert backslashes to forward slashes in source file paths *add methods to detect cygwin and msys targets * for cygwin or msys targets, clang compiles are consecutive rather than parallel --- clib/src/main/resources/scala-native/math.c | 6 ++++ .../scala-native/platform/platform.c | 8 +++++ .../scala/scalanative/runtime/Platform.scala | 3 ++ .../main/resources/scala-native/sys/socket.c | 4 +++ .../scala/scalanative/build/Config.scala | 11 ++++++ .../scala/scala/scalanative/build/LLVM.scala | 35 ++++++++++++------- .../scalanative/build/NativeConfig.scala | 8 ++++- .../scala/scalanative/build/Platform.scala | 17 +++++++++ .../scala/scalanative/build/core/IO.scala | 7 +++- .../linker/LinktimeValueResolver.scala | 4 ++- .../windows/accCtrl/securityObjectType.c | 2 +- 11 files changed, 89 insertions(+), 16 deletions(-) diff --git a/clib/src/main/resources/scala-native/math.c b/clib/src/main/resources/scala-native/math.c index f36640db91..223b9743ab 100644 --- a/clib/src/main/resources/scala-native/math.c +++ b/clib/src/main/resources/scala-native/math.c @@ -8,8 +8,14 @@ float scalanative_infinity() { return INFINITY; } float scalanative_nan() { return NAN; } +#if defined(math_errhandling) int scalanative_math_errhandling() { return math_errhandling; } +#endif +#if defined(MATH_ERRNO) int scalanative_math_errno() { return MATH_ERRNO; } +#endif +#if defined(MATH_ERREXCEPT) int scalanative_math_errexcept() { return MATH_ERREXCEPT; } +#endif diff --git a/nativelib/src/main/resources/scala-native/platform/platform.c b/nativelib/src/main/resources/scala-native/platform/platform.c index 6d58eb9f3b..fa9f09a7c9 100644 --- a/nativelib/src/main/resources/scala-native/platform/platform.c +++ b/nativelib/src/main/resources/scala-native/platform/platform.c @@ -103,6 +103,14 @@ char *scalanative_windows_get_user_country() { return ""; } +int scalanative_platform_is_msys() { +#ifdef __MSYS__ + return 1; +#else + return 0; +#endif +} + // See http://stackoverflow.com/a/4181991 int scalanative_little_endian() { int n = 1; diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/Platform.scala b/nativelib/src/main/scala/scala/scalanative/runtime/Platform.scala index 43eca344c9..0fb521cbc0 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/Platform.scala +++ b/nativelib/src/main/scala/scala/scalanative/runtime/Platform.scala @@ -38,4 +38,7 @@ object Platform { @name("scalanative_wide_char_size") final def SizeOfWChar: CSize = extern + + @name("scalanative_platform_is_msys") + def isMsys(): Boolean = extern } diff --git a/posixlib/src/main/resources/scala-native/sys/socket.c b/posixlib/src/main/resources/scala-native/sys/socket.c index f293140322..5a530fe151 100644 --- a/posixlib/src/main/resources/scala-native/sys/socket.c +++ b/posixlib/src/main/resources/scala-native/sys/socket.c @@ -5,6 +5,10 @@ #include #include "socket_conversions.h" +#if defined(__MINGW64__) +#include +#include +#endif #ifdef _WIN32 #include #pragma comment(lib, "Ws2_32.lib") diff --git a/tools/src/main/scala/scala/scalanative/build/Config.scala b/tools/src/main/scala/scala/scalanative/build/Config.scala index a47015ca17..4ff29f4900 100644 --- a/tools/src/main/scala/scala/scalanative/build/Config.scala +++ b/tools/src/main/scala/scala/scalanative/build/Config.scala @@ -89,6 +89,17 @@ sealed trait Config { Seq("mac", "apple", "darwin").exists(customTriple.contains(_)) } + private[scalanative] lazy val targetsMsys: Boolean = { + compilerConfig.targetTriple.fold(Platform.isMsys) { customTriple => + customTriple.contains("windows-msys") + } + } + private[scalanative] lazy val targetsCygwin: Boolean = { + compilerConfig.targetTriple.fold(Platform.isCygwin) { customTriple => + customTriple.contains("windows-cygnus") + } + } + private[scalanative] lazy val targetsLinux: Boolean = Platform.isLinux || compilerConfig.targetTriple.exists { customTriple => Seq("linux").exists(customTriple.contains(_)) diff --git a/tools/src/main/scala/scala/scalanative/build/LLVM.scala b/tools/src/main/scala/scala/scalanative/build/LLVM.scala index 658be0cb19..6a0a2647be 100644 --- a/tools/src/main/scala/scala/scalanative/build/LLVM.scala +++ b/tools/src/main/scala/scala/scalanative/build/LLVM.scala @@ -67,8 +67,11 @@ private[scalanative] object LLVM { } else Seq("-std=gnu11") } val platformFlags = { - if (config.targetsWindows) Seq("-g") - else Nil + if (config.targetsWindows) { + val common = Seq("-g") // needed for debug symbols in stack traces + val optional = if (config.targetsMsys) msysExtras else Nil + common ++ optional + } else Nil } val exceptionsHandling = { val opt = if (isCpp) List("-fcxx-exceptions") else Nil @@ -82,12 +85,14 @@ private[scalanative] object LLVM { val compilec: Seq[String] = Seq(compiler, "-c", inpath, "-o", outpath) ++ flags + // compile config.logger.running(compilec) val result = Process(compilec, config.workdir.toFile) ! Logger.toProcessLogger(config.logger) if (result != 0) { throw new BuildException(s"Failed to compile ${inpath}") } + objPath } @@ -308,18 +313,24 @@ private[scalanative] object LLVM { else str } - private def constructIdent(config: Config): String = { - val snVersion = scala.scalanative.nir.Versions.current + lazy val msysExtras = Seq( + "-D_WIN64", + "-D__MINGW64__", + "-D_X86_64_ -D__X86_64__ -D__x86_64", + "-D__USING_SJLJ_EXCEPTIONS__", + "-DNO_OLDNAMES", + "-D_LIBUNWIND_BUILD_ZERO_COST_APIS" + ) - val ident1 = s"Scala Native ${snVersion}" - val ident2 = s"Mode: ${config.mode}, LTO: ${config.LTO}, GC: ${config.gc}" + private[scalanative] def generateLLVMIdent(config: Config): Seq[Path] = { + def constructIdent: String = { + val snVersion = scala.scalanative.nir.Versions.current - s"${ident1} (${ident2})" - } + val ident1 = s"Scala Native ${snVersion}" + val ident2 = s"Mode: ${config.mode}, LTO: ${config.LTO}, GC: ${config.gc}" - private[scalanative] def generateLLVMIdent( - config: Config - ): Seq[java.nio.file.Path] = { + s"${ident1} (${ident2})" + } /* Enable feature only where known to work. Add to list as experience grows * FreeBSD uses elf format so it _should_ work, but it has not been @@ -330,7 +341,7 @@ private[scalanative] object LLVM { // From lld.llvm.org doc: readelf --string-dump .comment val workDir = config.workdir val identPath = workDir.resolve("ScalaNativeIdent.ll") - val ident = constructIdent(config) + val ident = constructIdent val pw = new java.io.PrintWriter(identPath.toFile) // truncate if exists diff --git a/tools/src/main/scala/scala/scalanative/build/NativeConfig.scala b/tools/src/main/scala/scala/scalanative/build/NativeConfig.scala index 2fca4142e1..f2e162c2d8 100644 --- a/tools/src/main/scala/scala/scalanative/build/NativeConfig.scala +++ b/tools/src/main/scala/scala/scalanative/build/NativeConfig.scala @@ -185,8 +185,14 @@ object NativeConfig { def withCompileOptions(value: Seq[String]): NativeConfig = copy(compileOptions = value) - def withTargetTriple(value: Option[String]): NativeConfig = + def withTargetTriple(value: Option[String]): NativeConfig = { + val propertyName = "target.triple" + value match { + case Some(triple) => System.setProperty(propertyName, triple) + case None => System.clearProperty(propertyName) + } copy(targetTriple = value) + } def withTargetTriple(value: String): NativeConfig = { withTargetTriple(Some(value)) diff --git a/tools/src/main/scala/scala/scalanative/build/Platform.scala b/tools/src/main/scala/scala/scalanative/build/Platform.scala index 263a393ca1..1c2f043769 100644 --- a/tools/src/main/scala/scala/scalanative/build/Platform.scala +++ b/tools/src/main/scala/scala/scalanative/build/Platform.scala @@ -11,4 +11,21 @@ object Platform { lazy val isUnix: Boolean = isLinux || osUsed.contains("unix") lazy val isMac: Boolean = osUsed.contains("mac") lazy val isFreeBSD: Boolean = osUsed.contains("freebsd") + + /** Test for the target type + * + * @return + * true if `msys`, false otherwise + */ + lazy val isMsys: Boolean = target.endsWith("msys") + + /** Test for the target type + * + * @return + * true if `cygnus`, false otherwise + */ + lazy val isCygwin: Boolean = target.endsWith("cygnus") + + private lazy val target = + System.getProperty("target.triple", "unknown").toLowerCase(Locale.ROOT) } diff --git a/tools/src/main/scala/scala/scalanative/build/core/IO.scala b/tools/src/main/scala/scala/scalanative/build/core/IO.scala index c6a6d74af2..ec499aa390 100644 --- a/tools/src/main/scala/scala/scalanative/build/core/IO.scala +++ b/tools/src/main/scala/scala/scalanative/build/core/IO.scala @@ -22,7 +22,12 @@ import java.security.{DigestInputStream, MessageDigest} private[scalanative] object IO { implicit class RichPath(val path: Path) extends AnyVal { - def abs: String = path.toAbsolutePath.toString + def abs: String = path.toAbsolutePath.toString.norm + } + implicit class RichString(val s: String) extends AnyVal { + // commands issued in shell environments require forward slash + // clang and llvm command line tools accept forward slash + def norm: String = s.replace('\\', '/') } /** Write bytes to given file. */ diff --git a/tools/src/main/scala/scala/scalanative/linker/LinktimeValueResolver.scala b/tools/src/main/scala/scala/scalanative/linker/LinktimeValueResolver.scala index 2d7fd82608..77ff27dacd 100644 --- a/tools/src/main/scala/scala/scalanative/linker/LinktimeValueResolver.scala +++ b/tools/src/main/scala/scala/scalanative/linker/LinktimeValueResolver.scala @@ -20,7 +20,9 @@ trait LinktimeValueResolver { self: Reach => s"$linktimeInfo.isWeakReferenceSupported" -> { conf.gc == GC.Immix || conf.gc == GC.Commix - } + }, + s"$linktimeInfo.isMsys" -> Platform.isMsys, + s"$linktimeInfo.isCygwin" -> Platform.isCygwin ) NativeConfig.checkLinktimeProperties(predefined) predefined ++ conf.linktimeProperties diff --git a/windowslib/src/main/resources/scala-native/windows/accCtrl/securityObjectType.c b/windowslib/src/main/resources/scala-native/windows/accCtrl/securityObjectType.c index 7046909d78..e01ae03f51 100644 --- a/windowslib/src/main/resources/scala-native/windows/accCtrl/securityObjectType.c +++ b/windowslib/src/main/resources/scala-native/windows/accCtrl/securityObjectType.c @@ -1,4 +1,4 @@ -#if defined(_WIN32) || defined(WIN32) +#if (defined(_WIN32) || defined(WIN32)) && !defined(__MINGW64__) #define WIN32_LEAN_AND_MEAN #include From af902010808b3438483a123e4b744091a7f41cd7 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 28 Feb 2023 23:17:57 +0100 Subject: [PATCH 25/61] Don't emit logs when skipping embeding source files (#3191) --- .../scalanative/codegen/ResourceEmbedder.scala | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/tools/src/main/scala/scala/scalanative/codegen/ResourceEmbedder.scala b/tools/src/main/scala/scala/scalanative/codegen/ResourceEmbedder.scala index 36d4de2032..e26a2d51e7 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/ResourceEmbedder.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/ResourceEmbedder.scala @@ -54,21 +54,12 @@ private[scalanative] object ResourceEmbedder { s"Did not embed: $pathName - file in the ignored \'scala-native\' folder." ) None - } else if (isSourceFile((path))) { - config.logger.debug( - s"Did not embed: $pathName - source file extension detected." - ) - None - } else if (Files.isDirectory(correctedPath)) { - None - } else { - Some(ClasspathFile(path, pathName, virtualDir)) - } + } else if (isSourceFile((path))) None + else if (Files.isDirectory(correctedPath)) None + else Some(ClasspathFile(path, pathName, virtualDir)) } } - } else { - Seq.empty - } + } else Seq.empty def filterEqualPathNames( path: List[ClasspathFile] From 926f6edb81e35aa66a3dbfdbd3b083c7bc7fd2ee Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 1 Mar 2023 14:17:31 +0100 Subject: [PATCH 26/61] Use opaque pointers in generated LLVM IR when possible (#3190) * Add detection of Opaque Pointers capabiltiy based on used clang version * Add custom flags when compiling llvm to enable opaque pointers supper if needed * Adapt CodeGen to emit opaque pointers whenever possible * Skip unnecessary bitcasts in lowering when using opaque pointers * Allow opaque pointers when using at least clag 15. On clang 13/14 opaque pointers are experimental: might require additional flags with differents names on each platform * Disable opaque pointes on llvm 14 - due to problems when using lld linker and portability issues. --- .github/actions/linux-setup-env/action.yml | 10 + .../scala/scala/scalanative/nir/Types.scala | 21 +- .../scala/scalanative/build/Discover.scala | 157 +++++++++++--- .../scala/scala/scalanative/build/LLVM.scala | 10 +- .../scalanative/codegen/AbstractCodeGen.scala | 196 +++++++++++------- .../scala/scalanative/codegen/CodeGen.scala | 2 +- .../scala/scalanative/codegen/Lower.scala | 11 +- .../scalanative/codegen/PlatformInfo.scala | 9 +- .../codegen/compat/os/OsCompat.scala | 11 +- .../codegen/compat/os/UnixCompat.scala | 34 +-- .../codegen/compat/os/WindowsCompat.scala | 42 ++-- 11 files changed, 357 insertions(+), 146 deletions(-) diff --git a/.github/actions/linux-setup-env/action.yml b/.github/actions/linux-setup-env/action.yml index 1d11cdcfd5..71cdd6d38c 100644 --- a/.github/actions/linux-setup-env/action.yml +++ b/.github/actions/linux-setup-env/action.yml @@ -7,6 +7,8 @@ inputs: java-version: description: "Java version to use in tests" default: "8" + llvm-version: + description: "LLVM toolchain version" runs: using: "composite" steps: @@ -33,6 +35,14 @@ runs: sudo apt-get update sudo apt-get install libgc-dev + - name: Install explicit LLVM toolchain + shell: bash + if: ${{ inputs.llvm-version != '' }} + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + yes "" | sudo ./llvm.sh ${{ inputs.llvm-version }} + # Loads cache with dependencies created in test-tools job - name: Cache dependencies uses: actions/cache@v3 diff --git a/nir/src/main/scala/scala/scalanative/nir/Types.scala b/nir/src/main/scala/scala/scalanative/nir/Types.scala index 805fc6c23b..e6dfda9a69 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Types.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Types.scala @@ -120,11 +120,22 @@ object Type { unreachable }.toSeq - def isPtrBox(ty: Type): Boolean = ty match { - case refty: Type.RefKind => - unbox.get(Type.Ref(refty.className)).contains(Type.Ptr) - case _ => - false + private def isBoxOf(primitiveType: Type)(boxType: Type) = + unbox + .get(normalize(boxType)) + .contains(primitiveType) + def isPtrBox(ty: Type): Boolean = isBoxOf(Type.Ptr)(ty) + def isPtrType(ty: Type): Boolean = + ty == Type.Ptr || ty.isInstanceOf[Type.RefKind] + + def normalize(ty: Type): Type = ty match { + case ArrayValue(ty, n) => ArrayValue(normalize(ty), n) + case StructValue(tys) => StructValue(tys.map(normalize)) + case Array(ty, nullable) => Array(normalize(ty)) + case Ref(name, exact, nullable) => Ref(name) + case Var(ty) => Var(normalize(ty)) + case Function(args, ret) => Function(args.map(normalize), normalize(ret)) + case other => other } val typeToArray = Map[Type, Global]( diff --git a/tools/src/main/scala/scala/scalanative/build/Discover.scala b/tools/src/main/scala/scala/scalanative/build/Discover.scala index 2a09829dbb..56bff1a781 100644 --- a/tools/src/main/scala/scala/scalanative/build/Discover.scala +++ b/tools/src/main/scala/scala/scalanative/build/Discover.scala @@ -91,41 +91,56 @@ object Discover { libs } + private case class ClangInfo( + majorVersion: Int, + fullVersion: String, + targetTriple: String + ) + private def clangInfo(implicit config: NativeConfig): ClangInfo = + cache("clang-info")(config => clangInfo(config.clang)) + + private def clangInfo(clang: Path): ClangInfo = { + val versionCommand = Seq(clang.abs, "--version") + val cmdString = versionCommand.mkString(" ") + val processLines = Process(versionCommand) + .lineStream_!(silentLogger()) + .toList + + val (versionString, targetString) = processLines match { + case version :: target :: _ => (version, target) + case _ => + throw new BuildException( + s"""Problem running '$cmdString'. Please check clang setup. + |Refer to ($docSetup)""".stripMargin + ) + } + + // Apple macOS clang is different vs brew installed or Linux + // Apple LLVM version 10.0.1 (clang-1001.0.46.4) + // clang version 11.0.0 + try { + val versionArray = versionString.split(" ") + val versionIndex = versionArray.indexWhere(_.equals("version")) + val version = versionArray(versionIndex + 1) + ClangInfo( + majorVersion = version.takeWhile(_.isDigit).toInt, + fullVersion = version, + targetTriple = targetString.drop("Target: ".size) + ) + } catch { + case t: Throwable => + throw new BuildException(s"""Output from '$versionCommand' unexpected. + |Was expecting '... version n.n.n ...'. + |Got '$versionString'. + |Cause: ${t}""".stripMargin) + } + } + /** Tests whether the clang compiler is greater or equal to the minumum * version required. */ private[scalanative] def checkClangVersion(pathToClangBinary: Path): Unit = { - def versionMajorFull(clang: String): (Int, String) = { - val versionCommand = Seq(clang, "--version") - val versionString = Process(versionCommand) - .lineStream_!(silentLogger()) - .headOption - .getOrElse { - throw new BuildException( - s"""Problem running '${versionCommand - .mkString(" ")}'. Please check clang setup. - |Refer to ($docSetup)""".stripMargin - ) - } - // Apple macOS clang is different vs brew installed or Linux - // Apple LLVM version 10.0.1 (clang-1001.0.46.4) - // clang version 11.0.0 - try { - val versionArray = versionString.split(" ") - val versionIndex = versionArray.indexWhere(_.equals("version")) - val version = versionArray(versionIndex + 1) - val majorVersion = version.split("\\.").head - (majorVersion.toInt, version) - } catch { - case t: Throwable => - throw new BuildException(s"""Output from '$versionCommand' unexpected. - |Was expecting '... version n.n.n ...'. - |Got '$versionString'. - |Cause: ${t}""".stripMargin) - } - } - - val (majorVersion, version) = versionMajorFull(pathToClangBinary.abs) + val ClangInfo(majorVersion, version, _) = clangInfo(pathToClangBinary) if (majorVersion < clangMinVersion) { throw new BuildException( @@ -178,9 +193,89 @@ object Discover { path } + /** Detect the target architecture. + * + * @param clang + * A path to the executable `clang`. + * @return + * The detected target triple describing the target architecture. + */ + def targetTriple(clang: Path): String = clangInfo(clang).targetTriple + + def targetTriple(implicit config: NativeConfig) = cache("target-triple") { + _ => clangInfo.targetTriple + } + private def silentLogger(): ProcessLogger = ProcessLogger(_ => (), _ => ()) private def getenv(key: String): Option[String] = Option(System.getenv.get(key)) + + private object cache extends ContextBasedCache[NativeConfig, String, AnyRef] + + private[scalanative] object features { + import FeatureSupport._ + + def opaquePointers(implicit config: NativeConfig): FeatureSupport = + cache("opaque-pointers") { _ => + try { + val version = clangInfo.majorVersion + // if version == 13 EnabledWithFlag("--force-opaque-pointers"): works on Unix and probably on Homebrew Clang; on Apple Clang missing or exists with different name + // if version == 14 EnabledWithFlag("--opaque-pointers"): might require additional flag `--plugin-opt=opaque-pointers` to ld.lld linker on Unix, this opt is missing on ld64.lld in MacOS + if (version < 15) Unavailable + else Enabled + } catch { + case ex: Exception => + System.err.println( + "Failed to detect version of clang, assuming opaque-pointers are not supported" + ) + Unavailable + } + } + + sealed trait FeatureSupport { + def isAvailable: Boolean = this match { + case Unavailable => false + case _ => true + } + def requiredFlag: Option[String] = this match { + case EnabledWithFlag(flag) => Some(flag) + case _ => None + } + } + object FeatureSupport { + case object Unavailable extends FeatureSupport + case object Enabled extends FeatureSupport + case class EnabledWithFlag(compilationFlag: String) extends FeatureSupport + } + } + + class ContextBasedCache[Ctx, Key, Value <: AnyRef] { + private val cachedValues = scala.collection.mutable.Map.empty[Key, Value] + private var lastContext: Ctx = _ + + def apply[T <: Value: reflect.ClassTag]( + key: Key + )(resolve: Ctx => T)(implicit context: Ctx): T = { + lastContext match { + case `context` => + val result = cachedValues.getOrElseUpdate(key, resolve(context)) + // Make sure stored value has correct type in case of duplicate keys + val expectedType = implicitly[reflect.ClassTag[T]].runtimeClass + assert( + expectedType.isAssignableFrom(result.getClass), + s"unexpected type of result for entry: `$key`, got ${result + .getClass()}, expected $expectedType" + ) + result.asInstanceOf[T] + + case _ => + // Context have changed + cachedValues.clear() + lastContext = context + this(key)(resolve) // retry with cleaned cached + } + } + } } diff --git a/tools/src/main/scala/scala/scalanative/build/LLVM.scala b/tools/src/main/scala/scala/scalanative/build/LLVM.scala index 6a0a2647be..c36aa05a0c 100644 --- a/tools/src/main/scala/scala/scalanative/build/LLVM.scala +++ b/tools/src/main/scala/scala/scalanative/build/LLVM.scala @@ -58,7 +58,7 @@ private[scalanative] object LLVM { val compiler = if (isCpp) config.clangPP.abs else config.clang.abs val stdflag = { - if (isLl) Seq.empty + if (isLl) llvmIrFeatures else if (isCpp) { // C++14 or newer standard is needed to compile code using Windows API // shipped with Windows 10 / Server 2016+ (we do not plan supporting older versions) @@ -280,6 +280,14 @@ private[scalanative] object LLVM { case Mode.ReleaseFull => "-O3" } + private def llvmIrFeatures(implicit config: Config): Seq[String] = { + implicit def nativeConfig: NativeConfig = config.compilerConfig + val opaquePointers = Discover.features.opaquePointers.requiredFlag.toList + .flatMap(Seq("-mllvm", _)) + + opaquePointers + } + private def buildTargetCompileOpts(implicit config: Config): Seq[String] = config.compilerConfig.buildTarget match { case BuildTarget.Application => diff --git a/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala b/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala index b33731361c..d91ac75d8a 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala @@ -3,6 +3,7 @@ package scala.scalanative.codegen import java.nio.file.{Path, Paths} import java.{lang => jl} import scala.collection.mutable +import scala.scalanative.build.Discover import scala.scalanative.codegen.compat.os.OsCompat import scala.scalanative.io.VirtualDirectory import scala.scalanative.nir.ControlFlow.{Block, Graph => CFG} @@ -14,11 +15,12 @@ import scala.scalanative.{build, linker, nir} private[codegen] abstract class AbstractCodeGen( env: Map[Global, Defn], defns: Seq[Defn] -)(implicit meta: Metadata) { +)(implicit val meta: Metadata) { import meta.platform import platform._ val os: OsCompat + val pointerType = if (useOpaquePointers) "ptr" else "i8*" private var currentBlockName: Local = _ private var currentBlockSplit: Int = _ @@ -364,9 +366,10 @@ private[codegen] abstract class AbstractCodeGen( private[codegen] def genType(ty: Type)(implicit sb: ShowBuilder): Unit = { import sb._ ty match { - case Type.Vararg => str("...") - case _: Type.RefKind | Type.Ptr | Type.Null | Type.Nothing => str("i8*") - case Type.Bool => str("i1") + case Type.Vararg => str("...") + case _: Type.RefKind | Type.Ptr | Type.Null | Type.Nothing => + str(pointerType) + case Type.Bool => str("i1") case i: Type.I => str("i"); str(i.width) case Type.Float => str("float") case Type.Double => str("double") @@ -445,11 +448,17 @@ private[codegen] abstract class AbstractCodeGen( str("%") genLocal(n) case Val.Global(n, ty) => - str("bitcast (") - genType(lookup(n)) - str("* @") - genGlobal(n) - str(" to i8*)") + if (useOpaquePointers) { + lookup(n) + str("@") + genGlobal(n) + } else { + str("bitcast (") + genType(lookup(n)) + str("* @") + genGlobal(n) + str(" to i8*)") + } case _ => unsupported(v) } @@ -649,23 +658,28 @@ private[codegen] abstract class AbstractCodeGen( case Op.Load(ty, ptr) => val pointee = fresh() - newline() - str("%") - genLocal(pointee) - str(" = bitcast ") - genVal(ptr) - str(" to ") - genType(ty) - str("*") + if (!useOpaquePointers) { + newline() + str("%") + genLocal(pointee) + str(" = bitcast ") + genVal(ptr) + str(" to ") + genType(ty) + str("*") + } newline() genBind() str("load ") genType(ty) str(", ") - genType(ty) - str("* %") - genLocal(pointee) + if (useOpaquePointers) genVal(ptr) + else { + genType(ty) + str("* %") + genLocal(pointee) + } ty match { case refty: Type.RefKind => val (nonnull, deref, size) = toDereferenceable(refty) @@ -684,76 +698,106 @@ private[codegen] abstract class AbstractCodeGen( case Op.Store(ty, ptr, value) => val pointee = fresh() - newline() - str("%") - genLocal(pointee) - str(" = bitcast ") - genVal(ptr) - str(" to ") - genType(ty) - str("*") + if (!useOpaquePointers) { + newline() + str("%") + genLocal(pointee) + str(" = bitcast ") + genVal(ptr) + str(" to ") + genType(ty) + str("*") + } newline() genBind() str("store ") genVal(value) - str(", ") - genType(ty) - str("* %") - genLocal(pointee) + if (useOpaquePointers) { + str(", ptr") + genJustVal(ptr) + } else { + str(", ") + genType(ty) + str("* %") + genLocal(pointee) + } + str(", align ") + str(MemoryLayout.alignmentOf(ty)) case Op.Elem(ty, ptr, indexes) => val pointee = fresh() val derived = fresh() - newline() - str("%") - genLocal(pointee) - str(" = bitcast ") - genVal(ptr) - str(" to ") - genType(ty) - str("*") + if (!useOpaquePointers) { + newline() + str("%") + genLocal(pointee) + str(" = bitcast ") + genVal(ptr) + str(" to ") + genType(ty) + str("*") + } newline() - str("%") - genLocal(derived) - str(" = getelementptr ") + if (useOpaquePointers) genBind() + else { + str("%") + genLocal(derived) + str(" = ") + } + str("getelementptr ") genType(ty) str(", ") - genType(ty) - str("* %") - genLocal(pointee) + if (ty.isInstanceOf[Type.AggregateKind] || !useOpaquePointers) { + genType(ty) + str("*") + } else str(pointerType) + str(" ") + if (useOpaquePointers) genJustVal(ptr) + else { + str("%") + genLocal(pointee) + } str(", ") rep(indexes, sep = ", ")(genVal) - newline() - genBind() - str("bitcast ") - genType(ty.elemty(indexes.tail)) - str("* %") - genLocal(derived) - str(" to i8*") + if (!useOpaquePointers) { + newline() + genBind() + str("bitcast ") + genType(ty.elemty(indexes.tail)) + str("* %") + genLocal(derived) + str(" to i8*") + } case Op.Stackalloc(ty, n) => val pointee = fresh() newline() - str("%") - genLocal(pointee) - str(" = alloca ") + if (useOpaquePointers) genBind() + else { + str("%") + genLocal(pointee) + str(" = ") + } + str("alloca ") genType(ty) str(", ") genVal(n) str(", align 8") - newline() - genBind() - str("bitcast ") - genType(ty) - str("* %") - genLocal(pointee) - str(" to i8*") + if (!useOpaquePointers) { + newline() + genBind() + str("bitcast ") + genType(ty) + str("* %") + genLocal(pointee) + str(" to i8*") + } case _ => newline() @@ -800,21 +844,27 @@ private[codegen] abstract class AbstractCodeGen( val pointee = fresh() - newline() - str("%") - genLocal(pointee) - str(" = bitcast ") - genVal(ptr) - str(" to ") - genType(ty) - str("*") + if (!useOpaquePointers) { + newline() + str("%") + genLocal(pointee) + str(" = bitcast ") + genVal(ptr) + str(" to ") + genType(ty) + str("*") + } newline() genBind() str(if (unwind ne Next.None) "invoke " else "call ") genCallFunctionType(ty) - str(" %") - genLocal(pointee) + str(" ") + if (useOpaquePointers) genJustVal(ptr) + else { + str("%") + genLocal(pointee) + } str("(") rep(args, sep = ", ")(genCallArgument) str(")") diff --git a/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala b/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala index 4e4dfec78b..ac637b029a 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/CodeGen.scala @@ -138,7 +138,7 @@ object CodeGen { ): AbstractCodeGen = { new AbstractCodeGen(env, defns) { override val os: OsCompat = { - if (meta.platform.targetsWindows) new WindowsCompat(this) + if (this.meta.platform.targetsWindows) new WindowsCompat(this) else new UnixCompat(this) } } diff --git a/tools/src/main/scala/scala/scalanative/codegen/Lower.scala b/tools/src/main/scala/scala/scalanative/codegen/Lower.scala index f3517f04c8..989319548c 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/Lower.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/Lower.scala @@ -391,7 +391,11 @@ object Lower { 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) + buf.let( + n, + Op.Store(ty, Val.Local(slot, Type.Ptr), genVal(buf, value)), + unwind + ) case op: Op.Arrayalloc => genArrayallocOp(buf, n, op) case op: Op.Arrayload => @@ -766,7 +770,10 @@ object Lower { branch(isInstanceOf, Next(castL), Next.Label(failL, Seq(v, toTy))) label(castL) - let(n, Op.Conv(Conv.Bitcast, ty, v), unwind) + if (platform.useOpaquePointers) + let(n, Op.Copy(v), unwind) + else + 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") diff --git a/tools/src/main/scala/scala/scalanative/codegen/PlatformInfo.scala b/tools/src/main/scala/scala/scalanative/codegen/PlatformInfo.scala index 32808a288f..9dc875e281 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/PlatformInfo.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/PlatformInfo.scala @@ -1,10 +1,11 @@ package scala.scalanative.codegen -import scala.scalanative.build.Config +import scala.scalanative.build.{Config, Discover} private[scalanative] case class PlatformInfo( targetTriple: Option[String], - targetsWindows: Boolean + targetsWindows: Boolean, + useOpaquePointers: Boolean ) { val sizeOfPtr = 8 val sizeOfPtrBits = sizeOfPtr * 8 @@ -12,6 +13,8 @@ private[scalanative] case class PlatformInfo( object PlatformInfo { def apply(config: Config): PlatformInfo = PlatformInfo( targetTriple = config.compilerConfig.targetTriple, - targetsWindows = config.targetsWindows + targetsWindows = config.targetsWindows, + useOpaquePointers = + Discover.features.opaquePointers(config.compilerConfig).isAvailable ) } diff --git a/tools/src/main/scala/scala/scalanative/codegen/compat/os/OsCompat.scala b/tools/src/main/scala/scala/scalanative/codegen/compat/os/OsCompat.scala index 238908798e..3cb300c836 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/compat/os/OsCompat.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/compat/os/OsCompat.scala @@ -1,13 +1,16 @@ -package scala.scalanative.codegen.compat.os +package scala.scalanative.codegen +package compat.os import scala.scalanative.nir.ControlFlow.Block import scala.scalanative.nir.{Fresh, Next, Position} import scala.scalanative.util.ShowBuilder private[codegen] trait OsCompat { - + protected def codegen: AbstractCodeGen protected def osPersonalityType: String + def useOpaquePointers = codegen.meta.platform.useOpaquePointers + def genPrelude()(implicit sb: ShowBuilder): Unit def genLandingPad( unwind: Next.Unwind @@ -15,6 +18,6 @@ private[codegen] trait OsCompat { def genBlockAlloca(block: Block)(implicit sb: ShowBuilder): Unit final lazy val gxxPersonality = - s"personality i8* bitcast (i32 (...)* $osPersonalityType to i8*)" - + if (useOpaquePointers) s"personality ptr $osPersonalityType" + else s"personality i8* bitcast (i32 (...)* $osPersonalityType to i8*)" } diff --git a/tools/src/main/scala/scala/scalanative/codegen/compat/os/UnixCompat.scala b/tools/src/main/scala/scala/scalanative/codegen/compat/os/UnixCompat.scala index 58f85d1918..ab3a5ddc95 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/compat/os/UnixCompat.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/compat/os/UnixCompat.scala @@ -5,15 +5,20 @@ import scala.scalanative.nir.ControlFlow.Block import scala.scalanative.nir._ import scala.scalanative.util.ShowBuilder -private[codegen] class UnixCompat(codeGen: AbstractCodeGen) extends OsCompat { +private[codegen] class UnixCompat(protected val codegen: AbstractCodeGen) + extends OsCompat { + import codegen.{pointerType => ptrT} val ehWrapperTy = "@_ZTIN11scalanative16ExceptionWrapperE" - val excRecTy = "{ i8*, i32 }" + val excRecTy = s"{ $ptrT, i32 }" val beginCatch = "@__cxa_begin_catch" val endCatch = "@__cxa_end_catch" + val catchSig = + if (useOpaquePointers) s"$ptrT $ehWrapperTy" + else s"i8* bitcast ({ i8*, i8*, i8* }* $ehWrapperTy to i8*)" val landingpad = - s"landingpad $excRecTy catch i8* bitcast ({ i8*, i8*, i8* }* $ehWrapperTy to i8*)" + s"landingpad $excRecTy catch $catchSig" val typeid = - s"call i32 @llvm.eh.typeid.for(i8* bitcast ({ i8*, i8*, i8* }* $ehWrapperTy to i8*))" + s"call i32 @llvm.eh.typeid.for($catchSig)" protected val osPersonalityType: String = "@__gxx_personality_v0" @@ -48,12 +53,17 @@ private[codegen] class UnixCompat(codeGen: AbstractCodeGen) extends OsCompat { line(s"$excsucc:") indent() - line(s"$w0 = call i8* $beginCatch(i8* $r0)") - line(s"$w1 = bitcast i8* $w0 to i8**") - line(s"$w2 = getelementptr i8*, i8** $w1, i32 1") - line(s"$exc = load i8*, i8** $w2") + line(s"$w0 = call $ptrT $beginCatch($ptrT $r0)") + if (useOpaquePointers) { + line(s"$w2 = getelementptr ptr, ptr $w0, i32 1") + line(s"$exc = load ptr, ptr $w2") + } else { + line(s"$w1 = bitcast i8* $w0 to i8**") + line(s"$w2 = getelementptr i8*, i8** $w1, i32 1") + line(s"$exc = load i8*, i8** $w2") + } line(s"call void $endCatch()") - codeGen.genInst(Inst.Jump(next)) + codegen.genInst(Inst.Jump(next)) unindent() line(s"$excfail:") @@ -64,10 +74,10 @@ private[codegen] class UnixCompat(codeGen: AbstractCodeGen) extends OsCompat { def genPrelude()(implicit builder: ShowBuilder): Unit = { import builder._ - line("declare i32 @llvm.eh.typeid.for(i8*)") + line(s"declare i32 @llvm.eh.typeid.for($ptrT)") line(s"declare i32 $osPersonalityType(...)") - line(s"declare i8* $beginCatch(i8*)") + line(s"declare $ptrT $beginCatch($ptrT)") line(s"declare void $endCatch()") - line(s"$ehWrapperTy = external constant { i8*, i8*, i8* }") + line(s"$ehWrapperTy = external constant { $ptrT, $ptrT, $ptrT }") } } diff --git a/tools/src/main/scala/scala/scalanative/codegen/compat/os/WindowsCompat.scala b/tools/src/main/scala/scala/scalanative/codegen/compat/os/WindowsCompat.scala index 2cba8ba0d0..deb6df087e 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/compat/os/WindowsCompat.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/compat/os/WindowsCompat.scala @@ -5,8 +5,9 @@ import scala.scalanative.nir.ControlFlow.Block import scala.scalanative.nir.{Fresh, Next, Position, Val} import scala.scalanative.util.ShowBuilder -private[codegen] class WindowsCompat(codegen: AbstractCodeGen) +private[codegen] class WindowsCompat(protected val codegen: AbstractCodeGen) extends OsCompat { + import codegen.{pointerType => ptrT} val ehWrapperTy = "\"??_R0?AVExceptionWrapper@scalanative@@@8\"" val ehWrapperName = "c\".?AVExceptionWrapper@scalanative@@\\00\"" val ehClass = "%\"class.scalanative::ExceptionWrapper\"" @@ -28,16 +29,17 @@ private[codegen] class WindowsCompat(codegen: AbstractCodeGen) override def genPrelude()(implicit sb: ShowBuilder): Unit = { import sb._ - line("declare i32 @llvm.eh.typeid.for(i8*)") + def PtrRef = if (useOpaquePointers) ptrT else s"$ptrT*" + line(s"declare i32 @llvm.eh.typeid.for($ptrT*)") line(s"declare i32 $osPersonalityType(...)") - line(s"$typeDescriptor = type { i8**, i8*, [35 x i8] }") - line(s"%$stdExceptionData = type { i8*, i8 }") + line(s"$typeDescriptor = type { $PtrRef, $ptrT, [35 x i8] }") + line(s"%$stdExceptionData = type { $ptrT, i8 }") line(s"%$stdExceptionClass = type { i32 (...)**, %$stdExceptionData }") - line(s"$ehClass = type { %$stdExceptionClass, i8* }") - line(s"@$typeInfo = external constant i8*") + line(s"$ehClass = type { %$stdExceptionClass, $ptrT }") + line(s"@$typeInfo = external constant $ptrT") line(s"$$$ehWrapperTy = comdat any") line( - s"@$ehWrapperTy = linkonce_odr global $typeDescriptor { i8** @$typeInfo, i8* null, [35 x i8] $ehWrapperName }, comdat" + s"@$ehWrapperTy = linkonce_odr global $typeDescriptor { $PtrRef @$typeInfo, $ptrT null, [35 x i8] $ehWrapperName }, comdat" ) } @@ -63,13 +65,25 @@ private[codegen] class WindowsCompat(codegen: AbstractCodeGen) line(s"$excsucc:") indent() - line( - s"$cpad = catchpad within $rec [$typeDescriptor* @$ehWrapperTy, i32 8, $ehClass** $ehVar]" - ) - line(s"$w1 = load $ehClass*, $ehClass** $ehVar, align 8") - line(s"$w2 = getelementptr inbounds $ehClass, $ehClass* $w1, i32 0, i32 1") - line(s"$exc = load i8*, i8** $w2, align 8") - line(s"catchret from $cpad to ") + if (useOpaquePointers) { + line( + s"$cpad = catchpad within $rec [ptr @$ehWrapperTy, i32 8, ptr $ehVar]" + ) + line(s"$w1 = load ptr, ptr $ehVar, align 8") + line(s"$w2 = getelementptr inbounds $ehClass, ptr $w1, i32 0, i32 1") + line(s"$exc = load ptr, ptr $w2, align 8") + line(s"catchret from $cpad to ") + } else { + line( + s"$cpad = catchpad within $rec [$typeDescriptor* @$ehWrapperTy, i32 8, $ehClass** $ehVar]" + ) + line(s"$w1 = load $ehClass*, $ehClass** $ehVar, align 8") + line( + s"$w2 = getelementptr inbounds $ehClass, $ehClass* $w1, i32 0, i32 1" + ) + line(s"$exc = load i8*, i8** $w2, align 8") + line(s"catchret from $cpad to ") + } genNext(next) unindent() } From b5cf79d46b93394ba2eceb3c4acb2e45362ee3c9 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 8 Mar 2023 08:35:47 +0100 Subject: [PATCH 27/61] Don't emit `Inst.Jump`/`Inst.Label` in NIR taking single `Unit` argument (#3201) * Don't emit `Inst.Jump` and `Inst.Label` in NIR for `Unit` values * Fix Scala3 nir generation --- .../scalanative/nscplugin/NirGenExpr.scala | 41 ++++++++----- .../scalanative/nscplugin/NirCodeGen.scala | 5 ++ .../scalanative/nscplugin/NirGenExpr.scala | 57 +++++++++++++------ 3 files changed, 72 insertions(+), 31 deletions(-) diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala index 793c9a1f6b..a8a056067b 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala @@ -205,15 +205,14 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => locally { buf.label(thenn)(thenp.pos) val thenv = genExpr(thenp) - buf.jump(mergen, Seq(thenv)) + buf.jumpExcludeUnitValue(retty)(mergen, thenv) } locally { buf.label(elsen)(elsep.pos) val elsev = genExpr(elsep) - buf.jump(mergen, Seq(elsev)) + buf.jumpExcludeUnitValue(retty)(mergen, elsev) } - buf.label(mergen, Seq(mergev)) - mergev + buf.labelExcludeUnitValue(mergen, mergev) } def genMatch(m: Match): Val = { @@ -261,15 +260,16 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => val scrut = genExpr(scrutp) buf.switch(scrut, defaultnext, casenexts) buf.label(defaultnext.name)(defaultp.pos) - buf.jump(merge, Seq(genExpr(defaultp)))(defaultp.pos) + buf.jumpExcludeUnitValue(retty)(merge, genExpr(defaultp))( + defaultp.pos + ) caseps.foreach { case (n, _, expr, pos) => buf.label(n)(pos) val caseres = genExpr(expr) - buf.jump(merge, Seq(caseres))(pos) + buf.jumpExcludeUnitValue(retty)(merge, caseres)(pos) } - buf.label(merge, Seq(mergev)) - mergev + buf.labelExcludeUnitValue(merge, mergev) } def genIfsChain(): Val = { @@ -378,13 +378,13 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => scoped(curUnwindHandler := Some(handler)) { nested.label(normaln) val res = nested.genExpr(expr) - nested.jump(mergen, Seq(res)) + nested.jumpExcludeUnitValue(retty)(mergen, res) } } locally { nested.label(handler, Seq(excv)) val res = nested.genTryCatch(retty, excv, mergen, catches)(expr.pos) - nested.jump(mergen, Seq(res)) + nested.jumpExcludeUnitValue(retty)(mergen, res) } // Append finally to the try/catch instructions and merge them back. @@ -398,8 +398,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => // Append try/catch instructions to the outher instruction buffer. buf.jump(Next(normaln)) buf ++= insts - buf.label(mergen, Seq(mergev)) - mergev + buf.labelExcludeUnitValue(mergen, mergev) } def genTryCatch( @@ -424,7 +423,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => curMethodEnv.enter(sym, cast) } val res = genExpr(body) - buf.jump(mergen, Seq(res)) + buf.jumpExcludeUnitValue(retty)(mergen, res) Val.Unit } (excty, f, exprPos) @@ -2450,5 +2449,21 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => def genSimpleArgs(argsp: Seq[Tree]): Seq[Val] = { argsp.map(genExpr) } + + private def labelExcludeUnitValue(label: Local, value: nir.Val.Local)( + implicit pos: nir.Position + ): nir.Val = + value.ty match { + case Type.Unit => buf.label(label); Val.Unit + case _ => buf.label(label, Seq(value)); value + } + + private def jumpExcludeUnitValue( + mergeType: nir.Type + )(label: Local, value: nir.Val)(implicit pos: nir.Position): Unit = + mergeType match { + case Type.Unit => buf.jump(label, Nil) + case _ => buf.jump(label, Seq(value)) + } } } diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirCodeGen.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirCodeGen.scala index d3f40b8af3..4e46301f71 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirCodeGen.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirCodeGen.scala @@ -154,6 +154,7 @@ class NirCodeGen(val settings: GenNIR.Settings)(using ctx: Context) class MethodLabelsEnv(val fresh: nir.Fresh) { private val entries, exits = mutable.Map.empty[Symbol, Local] + private val exitTypes = mutable.Map.empty[Local, nir.Type] def enterLabel(ld: Labeled): (nir.Local, nir.Local) = { val sym = ld.bind.symbol @@ -168,6 +169,10 @@ class NirCodeGen(val settings: GenNIR.Settings)(using ctx: Context) def resolveExit(sym: Symbol): nir.Local = exits(sym) def resolveExit(label: Labeled): nir.Local = exits(label.bind.symbol) + + def enterExitType(local: nir.Local, exitType: nir.Type): Unit = + exitTypes += local -> exitType + def resolveExitType(local: nir.Local): nir.Type = exitTypes(local) } class MethodEnv(val fresh: nir.Fresh) { diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala index 6e877e1bee..97310fd728 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala @@ -452,16 +452,15 @@ trait NirGenExpr(using Context) { given nir.Position = thenp.span buf.label(thenn) val thenv = genExpr(thenp) - buf.jump(mergen, Seq(thenv)) + buf.jumpExcludeUnitValue(retty)(mergen, thenv) } locally { given nir.Position = elsep.span buf.label(elsen) val elsev = genExpr(elsep) - buf.jump(mergen, Seq(elsev)) + buf.jumpExcludeUnitValue(retty)(mergen, elsev) } - buf.label(mergen, Seq(mergev)) - mergev + buf.labelExcludeUnitValue(mergen, mergev) } def genJavaSeqLiteral(tree: JavaSeqLiteral): Val = { @@ -490,14 +489,17 @@ trait NirGenExpr(using Context) { val (labelEntry, labelExit) = curMethodLabels.enterLabel(label) val labelExitParam = Val.Local(fresh(), genType(bind.tpe)) + curMethodLabels.enterExitType(labelExit, labelExitParam.ty) buf.jump(Next(labelEntry)) buf.label(labelEntry, Nil) - buf.jump(labelExit, Seq(genExpr(label.expr))) + buf.jumpExcludeUnitValue(labelExitParam.ty)( + labelExit, + genExpr(label.expr) + ) - buf.label(labelExit, Seq(labelExitParam)) - labelExitParam + buf.labelExcludeUnitValue(labelExit, labelExitParam) } def genLiteral(lit: Literal): Val = { @@ -578,16 +580,17 @@ trait NirGenExpr(using Context) { val scrut = genExpr(scrutp) buf.switch(scrut, defaultnext, casenexts) buf.label(defaultnext.name)(using defaultCasePos) - buf.jump(merge, Seq(genExpr(defaultp)))(using defaultCasePos) + buf.jumpExcludeUnitValue(retty)(merge, genExpr(defaultp))(using + defaultCasePos + ) caseps.foreach { case Case(n, _, expr, pos) => given nir.Position = pos buf.label(n) val caseres = genExpr(expr) - buf.jump(merge, Seq(caseres)) + buf.jumpExcludeUnitValue(retty)(merge, caseres) } - buf.label(merge, Seq(mergev)) - mergev + buf.labelExcludeUnitValue(merge, mergev) } def genIfsChain(): Val = { @@ -695,8 +698,11 @@ trait NirGenExpr(using Context) { else value from match { - case Some(label) => buf.jump(label, Seq(retv)) - case _ => buf.ret(retv) + case Some(label) => + val retty = curMethodLabels.resolveExitType(label) + buf.jumpExcludeUnitValue(retty)(label, retv) + case _ if retv.ty == Type.Unit => buf.ret(Val.Unit) + case _ => buf.ret(retv) } Val.Unit } @@ -781,12 +787,12 @@ trait NirGenExpr(using Context) { scoped(curUnwindHandler := Some(handler)) { nested.label(normaln) val res = nested.genExpr(expr) - nested.jump(mergen, Seq(res)) + nested.jumpExcludeUnitValue(retty)(mergen, res) } locally { nested.label(handler, Seq(excv)) val res = nested.genTryCatch(retty, excv, mergen, catches) - nested.jump(mergen, Seq(res)) + nested.jumpExcludeUnitValue(retty)(mergen, res) } // Append finally to the try/catch instructions and merge them back. @@ -797,8 +803,7 @@ trait NirGenExpr(using Context) { // Append try/catch instructions to the outher instruction buffer. buf.jump(Next(normaln)) buf ++= insts - buf.label(mergen, Seq(mergev)) - mergev + buf.labelExcludeUnitValue(mergen, mergev) } private def genTryCatch( @@ -823,7 +828,7 @@ trait NirGenExpr(using Context) { curMethodEnv.enter(sym, cast) } val res = genExpr(body) - buf.jump(mergen, Seq(res)) + buf.jumpExcludeUnitValue(retty)(mergen, res) Val.Unit } (excty, f, exprPos) @@ -2505,6 +2510,22 @@ trait NirGenExpr(using Context) { } } + private def labelExcludeUnitValue(label: Local, value: nir.Val.Local)(using + nir.Position + ): nir.Val = + value.ty match + case Type.Unit => buf.label(label); Val.Unit + case _ => buf.label(label, Seq(value)); value + + private def jumpExcludeUnitValue( + mergeType: nir.Type + )(label: Local, value: nir.Val)(using + nir.Position + ): Unit = + mergeType match + case Type.Unit => buf.jump(label, Nil) + case _ => buf.jump(label, Seq(value)) + } sealed class FixupBuffer(using fresh: Fresh) extends nir.Buffer { From 50f851229c7ebc55a659309398184fb3d862d88d Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 8 Mar 2023 13:48:39 +0100 Subject: [PATCH 28/61] Emit `Val.Unit`/`Type.Unit` as `void` in LLVM IR instead of ref to `BoxedUnit` (#3200) * Emit `Unit` as `void` in LLVM IR * Add missing handling for `Val.Unit` in `genVal` * Make sure to not erase BoxedUnit type in Interflow * Fix generation of reflective proxies, ensure usage of BoxedUnit * Fix generating primtive unit values in codegen * Return BoxedUnit in Lower only when it it required by checking definition result type --- .../scalanative/codegen/AbstractCodeGen.scala | 27 +++++++++------- .../codegen/GenerateReflectiveProxies.scala | 31 ++++++++++++------- .../scala/scalanative/codegen/Lower.scala | 31 +++++++++++++++++-- .../scala/scalanative/interflow/Opt.scala | 6 +++- 4 files changed, 69 insertions(+), 26 deletions(-) diff --git a/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala b/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala index d91ac75d8a..a8601ad030 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala @@ -229,14 +229,12 @@ private[codegen] abstract class AbstractCodeGen( private[codegen] def genFunctionReturnType( retty: Type - )(implicit sb: ShowBuilder): Unit = { - retty match { - case refty: Type.RefKind => - genReferenceTypeAttribute(refty) - case _ => - () - } - genType(retty) + )(implicit sb: ShowBuilder): Unit = retty match { + case refty: Type.RefKind if refty != Type.Unit => + genReferenceTypeAttribute(refty) + genType(retty) + case _ => + genType(retty) } private[codegen] def genReferenceTypeAttribute( @@ -310,6 +308,7 @@ private[codegen] abstract class AbstractCodeGen( if (!block.isEntry) { val params = block.params params.zipWithIndex.foreach { + case (Val.Local(name, Type.Unit), n) => () // skip case (Val.Local(name, ty), n) => newline() str("%") @@ -367,6 +366,7 @@ private[codegen] abstract class AbstractCodeGen( import sb._ ty match { case Type.Vararg => str("...") + case Type.Unit => str("void") case _: Type.RefKind | Type.Ptr | Type.Null | Type.Nothing => str(pointerType) case Type.Bool => str("i1") @@ -426,6 +426,7 @@ private[codegen] abstract class AbstractCodeGen( case Val.True => str("true") case Val.False => str("false") case Val.Null => str("null") + case Val.Unit => str("void") case Val.Zero(ty) => str("zeroinitializer") case Val.Byte(v) => str(v) case Val.Char(v) => str(v.toInt) @@ -501,8 +502,10 @@ private[codegen] abstract class AbstractCodeGen( private[codegen] def genVal(value: Val)(implicit sb: ShowBuilder): Unit = { import sb._ - genType(value.ty) - str(" ") + if (value != Val.Unit) { + genType(value.ty) + str(" ") + } genJustVal(value) } @@ -906,7 +909,9 @@ private[codegen] abstract class AbstractCodeGen( v match { case Val.Local(_, refty: Type.RefKind) => val (nonnull, deref, size) = toDereferenceable(refty) - genType(refty) + // Primitive unit value cannot be passed as argument, probably BoxedUnit is expected + if (refty == Type.Unit) genType(Type.Ptr) + else genType(refty) if (nonnull) { str(" nonnull") } diff --git a/tools/src/main/scala/scala/scalanative/codegen/GenerateReflectiveProxies.scala b/tools/src/main/scala/scala/scalanative/codegen/GenerateReflectiveProxies.scala index 66281e67e8..7d252dabf0 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/GenerateReflectiveProxies.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/GenerateReflectiveProxies.scala @@ -21,8 +21,7 @@ object GenerateReflectiveProxies { val unboxInsts = genArgUnboxes(label, defnType.args) val method = Inst.Let(Op.Method(label.params.head, sig), Next.None) val call = genCall(defnType, method, label.params, unboxInsts) - val box = genRetValBox(call.name, defnType.ret, proxyTy.ret) - val retInst = genRet(box.name, proxyTy.ret) + val retInsts = genRet(call.name, defnType.ret, proxyTy.ret) Defn.Define( Attrs.fromSeq(Seq(Attr.Dyn)), @@ -31,7 +30,8 @@ object GenerateReflectiveProxies { Seq( Seq(label), unboxInsts, - Seq(method, call, box, retInst) + Seq(method, call), + retInsts ).flatten ) } @@ -43,8 +43,8 @@ object GenerateReflectiveProxies { Type.Function( args, defnTy.ret match { - case Type.Unit => Type.Unit - case _ => Type.Ref(Global.Top("java.lang.Object")) + case Type.Unit => Rt.BoxedUnit + case _ => Rt.Object } ) @@ -105,7 +105,7 @@ object GenerateReflectiveProxies { implicit pos: nir.Position, fresh: Fresh - ) = + ): Inst.Let = Type.box.get(defnRetTy) match { case Some(boxTy) => Inst.Let(Op.Box(boxTy, Val.Local(callName, defnRetTy)), Next.None) @@ -113,13 +113,20 @@ object GenerateReflectiveProxies { Inst.Let(Op.Copy(Val.Local(callName, defnRetTy)), Next.None) } - private def genRet(retValBoxName: Local, proxyRetTy: Type)(implicit - pos: nir.Position - ) = - proxyRetTy match { - case Type.Unit => Inst.Ret(Val.Unit) - case _ => Inst.Ret(Val.Local(retValBoxName, proxyRetTy)) + private def genRet(callName: Local, defnRetTy: Type, proxyRetTy: Type)( + implicit + pos: nir.Position, + fresh: Fresh + ): Seq[Inst] = { + defnRetTy match { + case Type.Unit => + Inst.Ret(Val.Unit) :: Nil + case _ => + val box = genRetValBox(callName, defnRetTy, proxyRetTy) + val ret = Inst.Ret(Val.Local(box.name, proxyRetTy)) + Seq(box, ret) } + } def apply(dynimpls: Seq[Global], defns: Seq[Defn]): Seq[Defn.Define] = { diff --git a/tools/src/main/scala/scala/scalanative/codegen/Lower.scala b/tools/src/main/scala/scala/scalanative/codegen/Lower.scala index 989319548c..969a8159c8 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/Lower.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/Lower.scala @@ -43,6 +43,10 @@ object Lower { private val fresh = new util.ScopedVar[Fresh] private val unwindHandler = new util.ScopedVar[Option[Local]] private val currentDefn = new util.ScopedVar[Defn.Define] + private def currentDefnRetType = { + val Type.Function(_, ret) = currentDefn.get.ty: @unchecked + ret + } private val unreachableSlowPath = mutable.Map.empty[Option[Local], Local] private val nullPointerSlowPath = mutable.Map.empty[Option[Local], Local] @@ -101,6 +105,17 @@ object Lower { } } + private def optionallyBoxedUnit(v: nir.Val)(implicit + pos: nir.Position + ): nir.Val = { + require( + v.ty == Type.Unit, + s"Definition is expected to return Unit type, found ${v.ty}" + ) + if (currentDefnRetType == Type.Unit) Val.Unit + else unit + } + override def onInsts(insts: Seq[Inst]): Seq[Inst] = { val buf = new nir.Buffer()(fresh) val handlers = new nir.Buffer()(fresh) @@ -164,7 +179,10 @@ object Lower { case inst @ Inst.Ret(v) => implicit val pos: Position = inst.pos - buf += Inst.Ret(genVal(buf, v)) + val retVal = + if (v.ty == Type.Unit) optionallyBoxedUnit(v) + else genVal(buf, v) + buf += Inst.Ret(retVal) case inst @ Inst.Jump(next) => implicit val pos: Position = inst.pos @@ -191,7 +209,16 @@ object Lower { buf ++= handlers - eliminateDeadCode(buf.toSeq.map(super.onInst)) + eliminateDeadCode(buf.toSeq.map(onInst)) + } + + override def onInst(inst: Inst): Inst = { + implicit def pos: nir.Position = inst.pos + inst match { + case Inst.Ret(v) if v.ty == Type.Unit => + Inst.Ret(optionallyBoxedUnit(v)) + case _ => super.onInst(inst) + } } override def onVal(value: Val): Val = value match { diff --git a/tools/src/main/scala/scala/scalanative/interflow/Opt.scala b/tools/src/main/scala/scala/scalanative/interflow/Opt.scala index f8c6bfc80d..14ba1a245c 100644 --- a/tools/src/main/scala/scala/scalanative/interflow/Opt.scala +++ b/tools/src/main/scala/scala/scalanative/interflow/Opt.scala @@ -98,11 +98,15 @@ trait Opt { self: Interflow => case Inst.Ret(v) => v.ty } - val retty = rets match { + val retty0 = rets match { case Seq() => Type.Nothing case Seq(ty) => ty case tys => Sub.lub(tys, Some(origRetTy)) } + // Make sure to not override expected BoxedUnit with primitive Unit + val retty = + if (retty0 == Type.Unit && origRetTy.isInstanceOf[Type.Ref]) origRetTy + else retty0 result(retty, insts) } From cb88697ff67623fe3b10af13427bc7d5dc7408bf Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 8 Mar 2023 16:27:57 +0100 Subject: [PATCH 29/61] Restore stack state after executing inlined function to prevent stack overflows (#3199) * Restore stack state after executing inlined function to prevent unexpected stack overflows --- .../scalanative/runtime/LLVMIntrinsics.scala | 2 ++ .../scala/scala/scalanative/nir/Buffer.scala | 17 ++++++-------- .../scalanative/build/core/ScalaNative.scala | 4 +++- .../scala/scalanative/interflow/Inline.scala | 22 ++++++++++++++----- .../scalanative/interflow/Interflow.scala | 18 +++++++++++++++ .../scala/scala/scalanative/IssuesTest.scala | 20 +++++++++++++++++ 6 files changed, 67 insertions(+), 16 deletions(-) diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/LLVMIntrinsics.scala b/nativelib/src/main/scala/scala/scalanative/runtime/LLVMIntrinsics.scala index 577941f4ad..7ec2adfc1a 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/LLVMIntrinsics.scala +++ b/nativelib/src/main/scala/scala/scalanative/runtime/LLVMIntrinsics.scala @@ -82,4 +82,6 @@ object LLVMIntrinsics { def `llvm.cttz.i16`(source: Short, iszeroundef: Boolean): Short = extern def `llvm.cttz.i32`(source: Int, iszeroundef: Boolean): Int = extern def `llvm.cttz.i64`(source: Long, iszeroundef: Boolean): Long = extern + def `llvm.stacksave`(): RawPtr = extern + def `llvm.stackrestore`(state: RawPtr): Unit = extern } diff --git a/nir/src/main/scala/scala/scalanative/nir/Buffer.scala b/nir/src/main/scala/scala/scalanative/nir/Buffer.scala index 2298e1d884..282094a95a 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Buffer.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Buffer.scala @@ -5,16 +5,13 @@ import scala.collection.mutable class Buffer(implicit fresh: Fresh) { private val buffer = mutable.UnrolledBuffer.empty[Inst] - def +=(inst: Inst): Unit = - buffer += inst - def ++=(insts: Seq[Inst]): Unit = - buffer ++= insts - def ++=(other: Buffer): Unit = - buffer ++= other.buffer - def toSeq: Seq[Inst] = - buffer.toSeq - def size: Int = - buffer.size + def +=(inst: Inst): Unit = buffer += inst + def ++=(insts: Seq[Inst]): Unit = buffer ++= insts + def ++=(other: Buffer): Unit = buffer ++= other.buffer + + def toSeq: Seq[Inst] = buffer.toSeq + def size: Int = buffer.size + def exists(pred: Inst => Boolean) = buffer.exists(pred) // Control-flow ops def label(name: Local)(implicit pos: Position): Unit = diff --git a/tools/src/main/scala/scala/scalanative/build/core/ScalaNative.scala b/tools/src/main/scala/scala/scalanative/build/core/ScalaNative.scala index 957382e114..0b9a6961d6 100644 --- a/tools/src/main/scala/scala/scalanative/build/core/ScalaNative.scala +++ b/tools/src/main/scala/scala/scalanative/build/core/ScalaNative.scala @@ -6,6 +6,7 @@ import java.nio.file.{Path, Files} import scala.collection.mutable import scala.scalanative.checker.Check import scala.scalanative.codegen.CodeGen +import scala.scalanative.interflow.Interflow import scala.scalanative.linker.Link import scala.scalanative.nir._ import scala.scalanative.util.Scope @@ -18,7 +19,8 @@ private[scalanative] object ScalaNative { */ def entries(config: Config): Seq[Global] = { val entry = encodedMainClass(config).map(_.member(Rt.ScalaMainSig)) - entry ++: CodeGen.depends + val dependencies = CodeGen.depends ++ Interflow.depends + entry ++: dependencies } /** Given the classpath and main entry point, link under closed-world diff --git a/tools/src/main/scala/scala/scalanative/interflow/Inline.scala b/tools/src/main/scala/scala/scalanative/interflow/Inline.scala index dd03ce19c4..a782779689 100644 --- a/tools/src/main/scala/scala/scalanative/interflow/Inline.scala +++ b/tools/src/main/scala/scala/scalanative/interflow/Inline.scala @@ -154,10 +154,8 @@ trait Inline { self: Interflow => ): Val = in(s"inlining ${name.show}") { val defn = mode match { - case build.Mode.Debug => - getOriginal(name) - case _: build.Mode.Release => - getDone(name) + case build.Mode.Debug => getOriginal(name) + case _: build.Mode.Release => getDone(name) } val Type.Function(_, origRetTy) = defn.ty: @unchecked @@ -223,7 +221,21 @@ trait Inline { self: Interflow => } } - state.emit ++= emit + // Check if inlined function performed stack allocation, if so add + // insert stacksave/stackrestore LLVM Intrinsics to prevent affecting. + // By definition every stack allocation of inlined function is only needed within it's body + val allocatesOnStack = emit.exists { + case Inst.Let(_, _: Op.Stackalloc, _) => true + case _ => false + } + if (allocatesOnStack) { + import Interflow.LLVMIntrinsics._ + val stackState = state.emit + .call(StackSaveSig, StackSave, Nil, Next.None) + state.emit ++= emit + state.emit + .call(StackRestoreSig, StackRestore, Seq(stackState), Next.None) + } else state.emit ++= emit state.inherit(endState, res +: args) val Type.Function(_, retty) = defn.ty: @unchecked diff --git a/tools/src/main/scala/scala/scalanative/interflow/Interflow.scala b/tools/src/main/scala/scala/scalanative/interflow/Interflow.scala index 82b2016559..8a84c334dd 100644 --- a/tools/src/main/scala/scala/scalanative/interflow/Interflow.scala +++ b/tools/src/main/scala/scala/scalanative/interflow/Interflow.scala @@ -160,4 +160,22 @@ object Interflow { interflow.visitLoop() interflow.result() } + + object LLVMIntrinsics { + private val externAttrs = Attrs(isExtern = true) + private val LLVMI = Global.Top("scala.scalanative.runtime.LLVMIntrinsics$") + private def llvmIntrinsic(id: String) = + Val.Global(LLVMI.member(Sig.Extern(id)), Type.Ptr) + + val StackSave = llvmIntrinsic("llvm.stacksave") + val StackSaveSig = Type.Function(Nil, Type.Ptr) + + val StackRestore = llvmIntrinsic("llvm.stackrestore") + val StackRestoreSig = Type.Function(Seq(Type.Ptr), Type.Unit) + } + + val depends: Seq[Global] = Seq( + LLVMIntrinsics.StackSave.name, + LLVMIntrinsics.StackRestore.name + ) } diff --git a/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala b/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala index b0d81a2c1f..e454555198 100644 --- a/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala +++ b/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala @@ -7,6 +7,7 @@ import org.scalanative.testsuite.utils.AssertThrows.assertThrows import scalanative.unsigned._ import scalanative.unsafe._ import scala.annotation.nowarn +import scala.scalanative.annotation.alwaysinline class IssuesTest { @@ -590,6 +591,25 @@ class IssuesTest { assertEquals(Foo.fooLiteral, Foo.fooLazy) } + @Test def i3195(): Unit = { + // Make sure that inlined calls are resetting the stack upon returning + // Otherwise calls to functions allocating on stack in loop might lead to stack overflow + @alwaysinline def allocatingFunction(): CSize = { + import scala.scalanative.unsafe.{CArray, Nat} + import Nat._ + def `64KB` = (64 * 1024).toUSize + val chunk = stackalloc[Byte](`64KB`) + assertNotNull("stackalloc was null", chunk) + `64KB` + } + // 32MB, may more then available stack 1MB on Windows, < 8 MB on Unix + val toAllocate = (32 * 1024 * 1024).toUSize + var allocated = 0.toUSize + while (allocated < toAllocate) { + allocated += allocatingFunction() + } + } + } package issue1090 { From 705db8eb2253b8df6b8dc0fc39f3bc92a18b5c15 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 8 Mar 2023 16:55:23 +0100 Subject: [PATCH 30/61] Various fixes to runtime and compile time multithreading issues (#3187) * Process runner prints both decimal and hexadecimal exit code of the process for easier resolving of unhandled Windows exception codes * Fix to `FilesTestOnJDK11` test not containing `new` keyword when allocating `java.lang.StringBuilder` --- .../scala/scala/scalanative/testinterface/ProcessRunner.scala | 2 +- .../testsuite/javalib/nio/file/FilesTestOnJDK11.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test-runner/src/main/scala/scala/scalanative/testinterface/ProcessRunner.scala b/test-runner/src/main/scala/scala/scalanative/testinterface/ProcessRunner.scala index 88ee19e361..3ff8e4d6fe 100644 --- a/test-runner/src/main/scala/scala/scalanative/testinterface/ProcessRunner.scala +++ b/test-runner/src/main/scala/scala/scalanative/testinterface/ProcessRunner.scala @@ -54,7 +54,7 @@ private[testinterface] class ProcessRunner( else { runnerPromise.tryFailure( new RuntimeException( - s"Process $executableFile finished with non-zero value $exitCode" + s"Process $executableFile finished with non-zero value $exitCode (0x${exitCode.toHexString})" ) ) // Similarly to Bash programs, exitcode values higher diff --git a/unit-tests/shared/src/test/require-jdk11/org/scalanative/testsuite/javalib/nio/file/FilesTestOnJDK11.scala b/unit-tests/shared/src/test/require-jdk11/org/scalanative/testsuite/javalib/nio/file/FilesTestOnJDK11.scala index 68f51eedaf..d7efa8aae8 100644 --- a/unit-tests/shared/src/test/require-jdk11/org/scalanative/testsuite/javalib/nio/file/FilesTestOnJDK11.scala +++ b/unit-tests/shared/src/test/require-jdk11/org/scalanative/testsuite/javalib/nio/file/FilesTestOnJDK11.scala @@ -205,7 +205,7 @@ class FilesTestOnJDK11 { val ioPath = getCleanIoPath("StringBuilderSmall") val dataOut = getDataOut() - val output = jl.StringBuilder(dataOut) + val output = new jl.StringBuilder(dataOut) Files.writeString(ioPath, output) verifySmallUtf8Payload(ioPath) @@ -215,7 +215,7 @@ class FilesTestOnJDK11 { val ioPath = getCleanIoPath("StringBufferSmall") val dataOut = getDataOut() - val output = jl.StringBuffer(dataOut) + val output = new jl.StringBuffer(dataOut) Files.writeString(ioPath, output) verifySmallUtf8Payload(ioPath) From 43553283966f13c091c673a43d8793b4deb4b2bb Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Thu, 9 Mar 2023 08:48:00 +0100 Subject: [PATCH 31/61] Port `definedTestNames` override from Scala.js (#3203) * Port `definedTestNames` override from Scala.js allowing to skip linking of tests upon running `Test/compile` --- .../sbtplugin/ScalaNativePluginInternal.scala | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala b/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala index e7818809ea..25274b2f44 100644 --- a/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala +++ b/sbt-scala-native/src/main/scala/scala/scalanative/sbtplugin/ScalaNativePluginInternal.scala @@ -19,7 +19,8 @@ import scala.scalanative.testinterface.adapter.TestAdapter import scala.sys.process.Process import scala.util.Try import scala.scalanative.build.Platform -import java.nio.file.Files +import sjsonnew.BasicJsonProtocol._ +import java.nio.file.{Files, Path} object ScalaNativePluginInternal { @@ -335,6 +336,15 @@ object ScalaNativePluginInternal { case (tf, Some(adapter)) => (tf, adapter) } .toMap + }, + // Override default to avoid triggering a Test/nativeLink in a Test/compile + // without losing autocompletion. + definedTestNames := { + definedTests + .map(_.map(_.name).distinct) + .storeAs(definedTestNames) + .triggeredBy(loadedTestFrameworks) + .value } ) From 2eb0c333f7c51d5383bd96e030a83c3fcc9df170 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 9 Mar 2023 18:08:06 +0100 Subject: [PATCH 32/61] Restore support for C VarArgs alongside current CVarArgLists (#3204) The NIR, it's code and partially compiler plugin were already prepared for supporting C VarArgs, `printf(format, ...)`. The only missing piece to allow for their usage was to update logic responsible for detecting usage of repeated params and adding additional rules when generating arguments taking varrargs as arguments. * Compiler plugins are now able to detect varargs in extern methods and generate correct arguments for their calls * Resolved method signatures are now cached within a compilation unit (file) * Added bindings for C stdio methods taking var args, eg. `printf`, `sprintf`, `printf_s`, `scanf` * Added tests for VarArgs based on the `CVarArgsListTest` --- .../scala/scala/scalanative/libc/stdio.scala | 245 ++++- .../scala/scala/scalanative/nir/Types.scala | 23 +- .../scalanative/nscplugin/NirGenExpr.scala | 106 ++- .../scalanative/nscplugin/NirGenPhase.scala | 3 + .../scalanative/nscplugin/NirGenType.scala | 67 +- .../scalanative/nscplugin/NirCodeGen.scala | 3 + .../scalanative/nscplugin/NirGenExpr.scala | 105 ++- .../scalanative/nscplugin/NirGenType.scala | 77 +- .../scalanative/unsafe/CVarArgTest.scala | 835 ++++++++++++++++++ 9 files changed, 1347 insertions(+), 117 deletions(-) create mode 100644 unit-tests/native/src/test/scala/scala/scalanative/unsafe/CVarArgTest.scala diff --git a/clib/src/main/scala/scala/scalanative/libc/stdio.scala b/clib/src/main/scala/scala/scalanative/libc/stdio.scala index dbf94dd2af..658a49509a 100644 --- a/clib/src/main/scala/scala/scalanative/libc/stdio.scala +++ b/clib/src/main/scala/scala/scalanative/libc/stdio.scala @@ -2,6 +2,7 @@ package scala.scalanative package libc import scalanative.unsafe._ +import stddef.size_t @extern object stdio { @@ -116,6 +117,7 @@ object stdio { def fwide(stream: Ptr[FILE], mode: CInt): CInt = extern // Direct input/output + /** Reads up to count objects into the array buffer from the given input * stream stream as if by calling fgetc size times for each object, and * storing the results, in the order obtained, into the successive positions @@ -153,8 +155,7 @@ object stdio { size: CSize, count: CSize, stream: Ptr[FILE] - ): CSize = - extern + ): CSize = extern /** Writes count of objects from the given array buffer to the output stream * stream. The objects are written as if by reinterpreting each object as an @@ -412,6 +413,77 @@ object stdio { def ungetc(ch: CInt, stream: Ptr[FILE]): CInt = extern // Formatted input/output + + /** Reads data from stdin and stores them according to the parameter format + * into the locations pointed by the additional arguments. + * @param format + * C string that contains a sequence of characters that control how + * characters extracted from the stream are treated + * + * @param vargs + * Depending on the format string, the function may expect a sequence of + * additional arguments, each containing a pointer to allocated storage + * where the interpretation of the extracted characters is stored with the + * appropriate type. There should be at least as many of these arguments as + * the number of values stored by the format specifiers. Additional + * arguments are ignored by the function. + * @return + * the number of items of the argument listsuccessfully filled on success. + * If a reading error happens or the end-of-file is reached while reading, + * the proper indicator is set (feof or ferror). And, if either happens + * before any data could be successfully read, EOF is returned. + */ + def scanf(format: CString, vargs: Any*): CInt = extern + + /** Reads data from the stream and stores them according to the parameter + * format into the locations pointed by the additional arguments. + * @param stream + * Pointer to a FILE object that identifies the input stream to read data + * from. + * @param format + * C string that contains a sequence of characters that control how + * characters extracted from the stream are treated + * + * @param vargs + * Depending on the format string, the function may expect a sequence of + * additional arguments, each containing a pointer to allocated storage + * where the interpretation of the extracted characters is stored with the + * appropriate type. There should be at least as many of these arguments as + * the number of values stored by the format specifiers. Additional + * arguments are ignored by the function. + * @return + * the number of items of the argument listsuccessfully filled on success. + * If a reading error happens or the end-of-file is reached while reading, + * the proper indicator is set (feof or ferror). And, if either happens + * before any data could be successfully read, EOF is returned. + */ + def fscanf(stream: Ptr[FILE], format: CString, vargs: Any*): CInt = + extern + + /** Reads data from s and stores them according to parameter format into the + * locations given by the additional arguments, as if scanf was used, but + * reading from s instead of the standard input + * @param s + * C string that the function processes as its source to retrieve the data. + * @param format + * C string that contains a sequence of characters that control how + * characters extracted from the stream are treated + * + * @param vargs + * Depending on the format string, the function may expect a sequence of + * additional arguments, each containing a pointer to allocated storage + * where the interpretation of the extracted characters is stored with the + * appropriate type. There should be at least as many of these arguments as + * the number of values stored by the format specifiers. Additional + * arguments are ignored by the function. + * @return + * the number of items of the argument listsuccessfully filled on success. + * If a reading error happens or the end-of-file is reached while reading, + * the proper indicator is set (feof or ferror). And, if either happens + * before any data could be successfully read, EOF is returned. + */ + def sscanf(s: CString, format: CString, vargs: Any*): CInt = extern + /** Read formatted data into variable argument list Reads data from the * standard input (stdin) and stores them according to parameter format into * the locations pointed by the elements in the variable argument list @@ -472,6 +544,175 @@ object stdio { def vsscanf(buffer: CString, format: CString, valist: CVarArgList): CInt = extern + /** Writes the results to stdout. + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/printf]] + */ + def printf(format: CString, vargs: Any*): CInt = extern + + /** Writes the results to selected stream. + * @param stream + * output file stream to write to + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/fprintf]] + */ + def fprintf(stream: Ptr[FILE], format: CString, vargs: Any*): CInt = + extern + + /** Writes the results to a character string buffer. + * @param buffer + * pointer to a character string to write to + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/sprintf]] + */ + def sprintf( + buffer: Ptr[CChar], + format: CString, + vargs: Any* + ): CInt = extern + + /** Writes the results to a character string buffer. At most bufsz - 1 + * characters are written. The resulting character string will be terminated + * with a null character, unless bufsz is zero. If bufsz is zero, nothing is + * written and buffer may be a null pointer, however the return value (number + * of bytes that would be written not including the null terminator) is still + * calculated and returned. + * @param buffer + * pointer to a character string to write to + * @param busz + * number of character to write + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/snprintf]] + */ + def snprintf( + buffer: Ptr[CChar], + bufsz: size_t, + format: CString, + vargs: Any* + ): CInt = extern + + /** Writes the results to stdout. + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/printf_s]] + */ + def printf_s(format: CString, vargs: Any*): CInt = extern + + /** Writes the results to selected stream. + * @param stream + * output file stream to write to + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/fprintf_s]] + */ + def fprintf_s( + stream: Ptr[FILE], + format: CString, + vargs: Any* + ): CInt = extern + + /** Writes the results to a character string buffer. + * @param buffer + * pointer to a character string to write to + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/sprintf_s]] + */ + def sprintf_s( + buffer: Ptr[CChar], + format: CString, + vargs: Any* + ): CInt = extern + + /** Writes the results to a character string buffer. At most bufsz - 1 + * characters are written. The resulting character string will be terminated + * with a null character, unless bufsz is zero. If bufsz is zero, nothing is + * written and buffer may be a null pointer, however the return value (number + * of bytes that would be written not including the null terminator) is still + * calculated and returned. + * @param buffer + * pointer to a character string to write to + * @param busz + * number of character to write + * @param format + * pointer to a null-terminated character string specifying how to + * interpret the data + * @param vargs + * variable argument list containing the data to print. + * + * @return + * The number of characters written if successful or negative value if an + * error occurred. + * @see + * [[https://en.cppreference.com/w/c/io/snprintf_s]] + */ + def snprintf_s( + buffer: Ptr[CChar], + bufsz: size_t, + format: CString, + vargs: Any* + ): CInt = extern + /** Writes the results to stdout. * @param format * pointer to a null-terminated character string specifying how to diff --git a/nir/src/main/scala/scala/scalanative/nir/Types.scala b/nir/src/main/scala/scala/scalanative/nir/Types.scala index e6dfda9a69..245cb75244 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Types.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Types.scala @@ -89,11 +89,22 @@ object Type { final case class Var(ty: Type) extends SpecialKind final case class Function(args: Seq[Type], ret: Type) extends SpecialKind - val boxesTo = Seq[(Type, Type)]( - Type.Ref(Global.Top("scala.scalanative.unsigned.UByte")) -> Type.Byte, - Type.Ref(Global.Top("scala.scalanative.unsigned.UShort")) -> Type.Short, - Type.Ref(Global.Top("scala.scalanative.unsigned.UInt")) -> Type.Int, - Type.Ref(Global.Top("scala.scalanative.unsigned.ULong")) -> Type.Long, + private object unsigned { + val Byte = Type.Ref(Global.Top("scala.scalanative.unsigned.UByte")) + val Short = Type.Ref(Global.Top("scala.scalanative.unsigned.UShort")) + val Int = Type.Ref(Global.Top("scala.scalanative.unsigned.UInt")) + val Long = Type.Ref(Global.Top("scala.scalanative.unsigned.ULong")) + + val values: Set[nir.Type] = Set(Byte, Short, Int, Long) + } + private val unsignedBoxesTo = Seq[(Type, Type)]( + unsigned.Byte -> Type.Byte, + unsigned.Short -> Type.Short, + unsigned.Int -> Type.Int, + unsigned.Long -> Type.Long + ) + + val boxesTo: Seq[(Type, Type)] = unsignedBoxesTo ++ Seq( Type.Ref(Global.Top("scala.scalanative.unsafe.CArray")) -> Type.Ptr, Type.Ref(Global.Top("scala.scalanative.unsafe.CVarArgList")) -> Type.Ptr, Type.Ref(Global.Top("scala.scalanative.unsafe.Ptr")) -> Type.Ptr, @@ -127,6 +138,8 @@ object Type { def isPtrBox(ty: Type): Boolean = isBoxOf(Type.Ptr)(ty) def isPtrType(ty: Type): Boolean = ty == Type.Ptr || ty.isInstanceOf[Type.RefKind] + def isUnsignedType(ty: Type): Boolean = + unsigned.values.contains(normalize(ty)) def normalize(ty: Type): Type = ty match { case ArrayValue(ty, n) => ArrayValue(normalize(ty), n) diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala index a8a056067b..0bde204f8b 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala @@ -1855,7 +1855,8 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => case (_: Type.I, _: Type.RefKind) => Some(nir.Conv.Inttoptr) case (Type.I(w1, _), Type.F(w2)) if w1 == w2 => Some(nir.Conv.Bitcast) case (Type.F(w1), Type.I(w2, _)) if w1 == w2 => Some(nir.Conv.Bitcast) - case _ if fromty == toty => None + case _ if fromty == toty => Nonecase (Type.Float, Type.Double) => Some(nir.Conv.Fpext) + case (Type.Double, Type.Float) => Some(nir.Conv.Fptrunc) case _ => unsupported(s"cast from $fromty to $toty") } @@ -2418,36 +2419,87 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => } def genMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] = - if (!sym.owner.isExternModule) { - genSimpleArgs(argsp) - } else { - val res = Seq.newBuilder[Val] - - argsp.zip(sym.tpe.params).foreach { - case (argp, paramSym) => - val externType = genExternType(paramSym.tpe) - val arg = (genExpr(argp), Type.box.get(externType)) match { - case (value @ Val.Null, Some(unboxedType)) => - externType match { - case Type.Ptr | _: Type.RefKind => value - case _ => - reporter.warning( - argp.pos, - s"Passing null as argument of ${paramSym}: ${paramSym.tpe} to the extern method is unsafe. " + - s"The argument would be unboxed to primitive value of type $externType." - ) - Val.Zero(unboxedType) + if (sym.owner.isExternModule) genExternMethodArgs(sym, argsp) + else genSimpleArgs(argsp) + + private def genSimpleArgs(argsp: Seq[Tree]): Seq[Val] = argsp.map(genExpr) + + private def genExternMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] = { + val res = Seq.newBuilder[Val] + val nir.Type.Function(argTypes, _) = genExternMethodSig(sym) + val paramSyms = sym.tpe.params + assert( + argTypes.size == argsp.size && argTypes.size == paramSyms.size, + "Different number of arguments passed to method signature and apply method" + ) + + def genArg( + argp: Tree, + paramTpe: global.Type + ): nir.Val = { + implicit def pos: nir.Position = argp.pos + val externType = genExternType(paramTpe) + val value = (genExpr(argp), Type.box.get(externType)) match { + case (value @ Val.Null, Some(unboxedType)) => + externType match { + case Type.Ptr | _: Type.RefKind => value + case _ => + reporter.warning( + argp.pos, + s"Passing null as argument of type ${paramTpe} to the extern method is unsafe. " + + s"The argument would be unboxed to primitive value of type $externType." + ) + Val.Zero(unboxedType) + } + case (value, _) => value + } + toExtern(externType, value) + } + + for (((argp, sigType), paramSym) <- argsp zip argTypes zip paramSyms) { + sigType match { + case nir.Type.Vararg => + argp match { + case Apply(_, List(ArrayValue(_, args))) => + for (tree <- args) { + implicit def pos: nir.Position = tree.pos + val sym = tree.symbol + val tpe = + if (tree.symbol != null && tree.symbol.exists) + tree.symbol.tpe.finalResultType + else tree.tpe + val arg = genArg(tree, tpe) + def isUnsigned = Type.isUnsignedType(genType(tpe)) + // Decimal varargs needs to be promoted to at least Int, and float needs to be promoted to Double + val promotedArg = arg.ty match { + case Type.Float => + this.genCastOp(Type.Float, Type.Double, arg) + case Type.FixedSizeI(width, _) if width < Type.Int.width => + val conv = + if (isUnsigned) nir.Conv.Zext + else nir.Conv.Sext + buf.conv(conv, Type.Int, arg, unwind) + case Type.Long => + // On 32-bit systems Long needs to be truncated to Int + // Cast it to size to make undependent from architecture + val conv = + if (isUnsigned) nir.Conv.ZSizeCast + else nir.Conv.SSizeCast + buf.conv(conv, Type.Size, arg, unwind) + case _ => arg + } + res += promotedArg } - case (value, _) => value + case _ => + reporter.error( + argp.pos, + "Unable to extract vararg arguments, varargs to extern methods must be passed directly to the applied function" + ) } - res += toExtern(externType, arg)(argp.pos) + case _ => res += genArg(argp, paramSym.tpe) } - - res.result() } - - def genSimpleArgs(argsp: Seq[Tree]): Seq[Val] = { - argsp.map(genExpr) + res.result() } private def labelExcludeUnitValue(label: Local, value: nir.Val.Local)( diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenPhase.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenPhase.scala index 0bc6813ee9..a47066680f 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenPhase.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenPhase.scala @@ -39,6 +39,8 @@ abstract class NirGenPhase[G <: Global with Singleton](override val global: G) protected val curFresh = new util.ScopedVar[nir.Fresh] protected val curUnwindHandler = new util.ScopedVar[Option[nir.Local]] protected val curStatBuffer = new util.ScopedVar[StatBuffer] + protected val cachedMethodSig = + collection.mutable.Map.empty[(Symbol, Boolean), nir.Type.Function] protected def unwind(implicit fresh: Fresh): Next = curUnwindHandler.get.fold[Next](Next.None) { handler => @@ -158,6 +160,7 @@ abstract class NirGenPhase[G <: Global with Singleton](override val global: G) .forEach { case (path, stats) => genIRFile(path, stats) } } finally { generatedMirrorClasses.clear() + cachedMethodSig.clear() } } diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala index 9084999cc6..1822a7ff45 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala @@ -164,47 +164,46 @@ trait NirGenType[G <: Global with Singleton] { self: NirGenPhase[G] => sym: Symbol, isExtern: Boolean ): nir.Type.Function = { - require(sym.isMethod || sym.isStaticMember, "symbol is not a method") - - val tpe = sym.tpe - val owner = sym.owner - val paramtys = genMethodSigParamsImpl(sym, isExtern) - val selfty = - if (isExtern || sym.isStaticInNIR) None - else Some(genType(owner.tpe)) - val retty = - if (sym.isClassConstructor) nir.Type.Unit - else if (isExtern) genExternType(sym.tpe.resultType) - else genType(sym.tpe.resultType) - - nir.Type.Function(selfty ++: paramtys, retty) + def resolve() = { + require(sym.isMethod || sym.isStaticMember, "symbol is not a method") + + val tpe = sym.tpe + val owner = sym.owner + val paramtys = genMethodSigParamsImpl(sym, isExtern) + val selfty = + if (isExtern || sym.isStaticInNIR) None + else Some(genType(owner.tpe)) + val retty = + if (sym.isClassConstructor) nir.Type.Unit + else if (isExtern) genExternType(sym.tpe.resultType) + else genType(sym.tpe.resultType) + + nir.Type.Function(selfty ++: paramtys, retty) + } + cachedMethodSig.getOrElseUpdate((sym, isExtern), resolve()) } private def genMethodSigParamsImpl( sym: Symbol, isExtern: Boolean ): Seq[nir.Type] = { - val wereRepeated = exitingPhase(currentRun.typerPhase) { - for { - params <- sym.tpe.paramss - param <- params - } yield { - param.name -> isScalaRepeatedParamType(param.tpe) - } - }.toMap - - sym.tpe.params.map { - case p - if wereRepeated.getOrElse(p.name, false) && - sym.owner.isExternModule => - nir.Type.Vararg - - case p => - if (isExtern) { - genExternType(p.tpe) - } else { - genType(p.tpe) + val params = sym.tpe.params + if (!isExtern && !sym.owner.isExternType) + params.map { p => genType(p.tpe) } + else { + val wereRepeated = exitingPhase(currentRun.typerPhase) { + for { + params <- sym.tpe.paramss + param <- params + } yield { + param.name -> isScalaRepeatedParamType(param.tpe) } + }.toMap + + params.map { p => + if (wereRepeated.getOrElse(p.name, false)) nir.Type.Vararg + else genExternType(p.tpe) + } } } } diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirCodeGen.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirCodeGen.scala index 4e46301f71..8b356b1e00 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirCodeGen.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirCodeGen.scala @@ -29,6 +29,8 @@ class NirCodeGen(val settings: GenNIR.Settings)(using ctx: Context) protected val defnNir = NirDefinitions.get protected val nirPrimitives = new NirPrimitives() protected val positionsConversions = new NirPositions(settings.sourceURIMaps) + protected val cachedMethodSig = + collection.mutable.Map.empty[(Symbol, Boolean), nir.Type.Function] protected val curClassSym = new util.ScopedVar[ClassSymbol] protected val curClassFresh = new util.ScopedVar[nir.Fresh] @@ -60,6 +62,7 @@ class NirCodeGen(val settings: GenNIR.Settings)(using ctx: Context) generatedDefns.clear() generatedMirrorClasses.clear() reflectiveInstantiationBuffers.clear() + cachedMethodSig.clear() } } diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala index 97310fd728..f4f4040d82 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala @@ -1458,36 +1458,85 @@ trait NirGenExpr(using Context) { } } - def genMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] = { - if !sym.isExtern then genSimpleArgs(argsp) - else { - val res = Seq.newBuilder[Val] - argsp.zip(sym.paramInfo.paramInfoss.flatten).foreach { - case (argp, paramTpe) => - given nir.Position = argp.span - val externType = genExternType(paramTpe.finalResultType) - val arg = (genExpr(argp), Type.box.get(externType)) match { - case (value @ Val.Null, Some(unboxedType)) => - externType match { - case Type.Ptr | _: Type.RefKind => value - case _ => - report.warning( - s"Passing null as argument of type ${paramTpe.show} to the extern method is unsafe. " + - s"The argument would be unboxed to primitive value of type $externType.", - argp.srcPos - ) - Val.Zero(unboxedType) - } - case (value, _) => value + def genMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] = + if sym.isExtern + then genExternMethodArgs(sym, argsp) + else genSimpleArgs(argsp) + + private def genSimpleArgs(argsp: Seq[Tree]): Seq[Val] = argsp.map(genExpr) + + private def genExternMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] = { + val res = Seq.newBuilder[Val] + val Type.Function(argTypes, _) = genExternMethodSig(sym) + val paramTypes = sym.paramInfo.paramInfoss.flatten + assert( + argTypes.size == argsp.size && argTypes.size == paramTypes.size, + "Different number of arguments passed to method signature and apply method" + ) + + def genArg( + argp: Tree, + paramTpe: Types.Type + ): nir.Val = { + given nir.Position = argp.span + val externType = genExternType(paramTpe.finalResultType) + val value = (genExpr(argp), Type.box.get(externType)) match { + case (value @ Val.Null, Some(unboxedType)) => + externType match { + case Type.Ptr | _: Type.RefKind => value + case _ => + report.warning( + s"Passing null as argument of type ${paramTpe.show} to the extern method is unsafe. " + + s"The argument would be unboxed to primitive value of type $externType.", + argp.srcPos + ) + Val.Zero(unboxedType) } - res += toExtern(externType, arg) + case (value, _) => value } - res.result() + toExtern(externType, value) } - } - private def genSimpleArgs(argsp: Seq[Tree]): Seq[Val] = { - argsp.map(genExpr) + for ((argp, sigType), paramTpe) <- argsp zip argTypes zip paramTypes + do + sigType match { + case nir.Type.Vararg => + argp match { + case Apply(_, List(seqLiteral: JavaSeqLiteral)) => + for tree <- seqLiteral.elems + do + given nir.Position = tree.span + val arg = genArg(tree, tree.tpe) + def isUnsigned = Type.isUnsignedType(genType(tree.tpe)) + // Decimal varargs needs to be promoted to at least Int, and Float needs to be promoted to Double + val promotedArg = arg.ty match { + case Type.Float => + this.genCastOp(Type.Float, Type.Double, arg) + case Type.FixedSizeI(width, _) if width < Type.Int.width => + val conv = + if (isUnsigned) nir.Conv.Zext + else nir.Conv.Sext + buf.conv(conv, Type.Int, arg, unwind) + case Type.Long => + // On 32-bit systems Long needs to be truncated to Int + // Cast it to size to make undependent from architecture + val conv = + if (isUnsigned) nir.Conv.ZSizeCast + else nir.Conv.SSizeCast + buf.conv(conv, Type.Size, arg, unwind) + + case _ => arg + } + res += promotedArg + case _ => + report.error( + "Unable to extract vararg arguments, varargs to extern methods must be passed directly to the applied function", + argp.srcPos + ) + } + case _ => res += genArg(argp, paramTpe) + } + res.result() } private def genArrayOp(app: Apply, code: Int): Val = { @@ -1748,7 +1797,9 @@ trait NirGenExpr(using Context) { case (Type.I(w1, _), Type.F(w2)) if w1 == w2 => Some(nir.Conv.Bitcast) case (Type.F(w1), Type.I(w2, _)) if w1 == w2 => Some(nir.Conv.Bitcast) case _ if fromty == toty => None - case _ => unsupported(s"cast from $fromty to $toty") + case (Type.Float, Type.Double) => Some(nir.Conv.Fpext) + case (Type.Double, Type.Float) => Some(nir.Conv.Fptrunc) + case_ => unsupported(s"cast from $fromty to $toty") } /** Boxes a value of the given type before `elimErasedValueType`. diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala index 091a386553..eddf59b43f 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala @@ -11,6 +11,8 @@ import core.Types._ import core.Symbols._ import core.StdNames._ import core.TypeErasure._ +import dotty.tools.dotc.report +import dotty.tools.dotc.typer.TyperPhase import dotty.tools.dotc.transform.SymUtils._ import scala.scalanative.nir @@ -230,38 +232,69 @@ trait NirGenType(using Context) { sym: Symbol, isExtern: Boolean ): nir.Type.Function = { - require( - sym.is(Method) || sym.isStatic, - s"symbol ${sym.owner} $sym is not a method" - ) - - val owner = sym.owner - val paramtys = genMethodSigParamsImpl(sym, isExtern) - val selfty = Option.unless(isExtern || sym.isStaticInNIR) { - genType(owner) + def resolve() = { + require( + sym.is(Method) || sym.isStatic, + s"symbol ${sym.owner} $sym is not a method" + ) + + val owner = sym.owner + val paramtys = genMethodSigParamsImpl(sym, isExtern) + val selfty = Option.unless(isExtern || sym.isStaticInNIR) { + genType(owner) + } + val resultType = sym.info.resultType + val retty = + if (sym.isConstructor) nir.Type.Unit + else if (isExtern) genExternType(resultType) + else genType(resultType) + nir.Type.Function(selfty ++: paramtys, retty) } - val resultType = sym.info.resultType - val retty = - if (sym.isConstructor) nir.Type.Unit - else if (isExtern) genExternType(resultType) - else genType(resultType) - nir.Type.Function(selfty ++: paramtys, retty) + cachedMethodSig.getOrElseUpdate((sym, isExtern), resolve()) } private def genMethodSigParamsImpl( sym: Symbol, isExtern: Boolean - ): Seq[nir.Type] = { + )(using Context): Seq[nir.Type] = { + import core.Phases._ + val repeatedParams = if (sym.isExtern) { + atPhase(typerPhase) { + sym.paramInfo match { + // @extern def foo(a: Int): Int + case MethodTpe(paramNames, paramTypes, _) => + for (name, tpe) <- paramNames zip paramTypes + yield name -> tpe.isRepeatedParam + // @extern def foo[T](ptr: Ptr[T]): Int + case PolyType(_, MethodTpe(paramNames, paramTypes, _)) => + for (name, tpe) <- paramNames zip paramTypes + yield name -> tpe.isRepeatedParam + // @extern def foo: Int + case ExprType(_) => Nil + // @extern var foo: Int + case TypeRef(_, _) => Nil + // @extern def foo: Ptr[Int] + case AppliedType(_, _) => Nil + case _ => + report.warning( + "Unable to resolve method sig params for symbol, extern VarArgs would not work", + sym.srcPos + ) + Nil + } + }.toMap + } else Map.empty + val info = sym.info for { - paramList <- sym.info.paramInfoss - param <- paramList + (paramTypes, paramNames) <- info.paramInfoss zip info.paramNamess + (paramType, paramName) <- paramTypes zip paramNames } yield { - if (param.isRepeatedParam && sym.isExtern) - nir.Type.Vararg - else if (isExtern) genExternType(param) - else genType(param) + def isRepeated = repeatedParams.getOrElse(paramName, false) + if (isExtern && isRepeated) nir.Type.Vararg + else if (isExtern) genExternType(paramType) + else genType(paramType) } } } diff --git a/unit-tests/native/src/test/scala/scala/scalanative/unsafe/CVarArgTest.scala b/unit-tests/native/src/test/scala/scala/scalanative/unsafe/CVarArgTest.scala new file mode 100644 index 0000000000..6a03cf6451 --- /dev/null +++ b/unit-tests/native/src/test/scala/scala/scalanative/unsafe/CVarArgTest.scala @@ -0,0 +1,835 @@ +package scala.scalanative +package unsafe + +import org.junit.{Test, BeforeClass} +import org.junit.Assert._ +import org.junit.Assume._ + +import scalanative.unsigned._ +import scalanative.unsafe._ +import scalanative.libc.{stdio, stdlib, string} + +import scala.scalanative.junit.utils.AssumesHelper._ + +class CVarArgTest { + def vatest(cstr: CString, output: String)( + generator: (CString, Ptr[CChar]) => Unit + ): Unit = { + val buff: Ptr[CChar] = stackalloc[CChar](1024.toUSize) + generator(buff, cstr) + val got = fromCString(buff) + assertEquals(got, output) + } + + @Test def byteValue0(): Unit = + vatest(c"%d", "0")(stdio.sprintf(_, _, 0.toByte)) + @Test def byteValue1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toByte)) + @Test def byteValueMinus1(): Unit = + vatest(c"%d", "-1")(stdio.sprintf(_, _, -1.toByte)) + @Test def byteValueMin(): Unit = + vatest(c"%d", "-128")(stdio.sprintf(_, _, java.lang.Byte.MIN_VALUE)) + @Test def byteValueMax(): Unit = + vatest(c"%d", "127")(stdio.sprintf(_, _, java.lang.Byte.MAX_VALUE)) + @Test def byteArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toByte)) + @Test def byteArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1.toByte, 2.toByte)) + @Test def byteArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")( + stdio.sprintf(_, _, 1.toByte, 2.toByte, 3.toByte) + ) + @Test def byteArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")( + stdio.sprintf(_, _, 1.toByte, 2.toByte, 3.toByte, 4.toByte) + ) + @Test def byteArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf(_, _, 1.toByte, 2.toByte, 3.toByte, 4.toByte, 5.toByte) + ) + @Test def byteArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf( + _, + _, + 1.toByte, + 2.toByte, + 3.toByte, + 4.toByte, + 5.toByte, + 6.toByte + ) + ) + @Test def byteArgs7(): Unit = + vatest(c"%d %d %d %d %d %d %d", "1 2 3 4 5 6 7")( + stdio.sprintf( + _, + _, + 1.toByte, + 2.toByte, + 3.toByte, + 4.toByte, + 5.toByte, + 6.toByte, + 7.toByte + ) + ) + @Test def byteArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8" + )( + stdio.sprintf( + _, + _, + 1.toByte, + 2.toByte, + 3.toByte, + 4.toByte, + 5.toByte, + 6.toByte, + 7.toByte, + 8.toByte + ) + ) + @Test def byteArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8 9" + )( + stdio.sprintf( + _, + _, + 1.toByte, + 2.toByte, + 3.toByte, + 4.toByte, + 5.toByte, + 6.toByte, + 7.toByte, + 8.toByte, + 9.toByte + ) + ) + + @Test def shortValue0(): Unit = + vatest(c"%d", "0")(stdio.sprintf(_, _, 0.toShort)) + @Test def shortValue1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toShort)) + @Test def shortValueMinus1(): Unit = + vatest(c"%d", "-1")(stdio.sprintf(_, _, -1.toShort)) + @Test def shortValueMin(): Unit = + vatest(c"%d", "-32768")(stdio.sprintf(_, _, java.lang.Short.MIN_VALUE)) + @Test def shortValueMax(): Unit = + vatest(c"%d", "32767")(stdio.sprintf(_, _, java.lang.Short.MAX_VALUE)) + @Test def shortArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toShort)) + @Test def shortArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1.toShort, 2.toShort)) + @Test def shortArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")( + stdio.sprintf(_, _, 1.toShort, 2.toShort, 3.toShort) + ) + @Test def shortArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")( + stdio.sprintf(_, _, 1.toShort, 2.toShort, 3.toShort, 4.toShort) + ) + @Test def shortArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf(_, _, 1.toShort, 2.toShort, 3.toShort, 4.toShort, 5.toShort) + ) + @Test def shortArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf( + _, + _, + 1.toShort, + 2.toShort, + 3.toShort, + 4.toShort, + 5.toShort, + 6.toShort + ) + ) + @Test def shortArgs7(): Unit = + vatest( + c"%d %d %d %d %d %d %d", + "1 2 3 4 5 6 7" + )( + stdio.sprintf( + _, + _, + 1.toShort, + 2.toShort, + 3.toShort, + 4.toShort, + 5.toShort, + 6.toShort, + 7.toShort + ) + ) + @Test def shortArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8" + )( + stdio.sprintf( + _, + _, + 1.toShort, + 2.toShort, + 3.toShort, + 4.toShort, + 5.toShort, + 6.toShort, + 7.toShort, + 8.toShort + ) + ) + @Test def shortArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8 9" + )( + stdio.sprintf( + _, + _, + 1.toShort, + 2.toShort, + 3.toShort, + 4.toShort, + 5.toShort, + 6.toShort, + 7.toShort, + 8.toShort, + 9.toShort + ) + ) + + @Test def intValue0(): Unit = + vatest(c"%d", "0")(stdio.sprintf(_, _, 0)) + @Test def intValue1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1)) + @Test def intValueMinus1(): Unit = + vatest(c"%d", "-1")(stdio.sprintf(_, _, -1)) + @Test def intValueMin(): Unit = + vatest(c"%d", "-2147483648")( + stdio.sprintf(_, _, java.lang.Integer.MIN_VALUE) + ) + @Test def intValueMax(): Unit = + vatest(c"%d", "2147483647")( + stdio.sprintf(_, _, java.lang.Integer.MAX_VALUE) + ) + @Test def intArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1)) + @Test def intArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1, 2)) + @Test def intArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")(stdio.sprintf(_, _, 1, 2, 3)) + @Test def intArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")(stdio.sprintf(_, _, 1, 2, 3, 4)) + @Test def intArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")(stdio.sprintf(_, _, 1, 2, 3, 4, 5)) + @Test def intArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf(_, _, 1, 2, 3, 4, 5, 6) + ) + @Test def intArgs7(): Unit = + vatest(c"%d %d %d %d %d %d %d", "1 2 3 4 5 6 7")( + stdio.sprintf(_, _, 1, 2, 3, 4, 5, 6, 7) + ) + @Test def intArgs8(): Unit = + vatest(c"%d %d %d %d %d %d %d %d", "1 2 3 4 5 6 7 8")( + stdio.sprintf(_, _, 1, 2, 3, 4, 5, 6, 7, 8) + ) + @Test def intArgs9(): Unit = + vatest(c"%d %d %d %d %d %d %d %d %d", "1 2 3 4 5 6 7 8 9")( + stdio.sprintf(_, _, 1, 2, 3, 4, 5, 6, 7, 8, 9) + ) + + @Test def longValue0(): Unit = + vatest(c"%d", "0")(stdio.sprintf(_, _, 0L)) + @Test def longValue1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1L)) + @Test def longValueMinus1(): Unit = + vatest(c"%d", "-1")(stdio.sprintf(_, _, -1L)) + @Test def longValueMin(): Unit = { + assumeNot32Bit() + vatest(c"%lld", "-9223372036854775808")( + stdio.sprintf(_, _, java.lang.Long.MIN_VALUE) + ) + } + @Test def longValueMax(): Unit = { + assumeNot32Bit() + vatest(c"%lld", "9223372036854775807")( + stdio.sprintf(_, _, java.lang.Long.MAX_VALUE) + ) + } + @Test def longArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1L)) + @Test def longArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1L, 2L)) + @Test def longArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")(stdio.sprintf(_, _, 1L, 2L, 3L)) + @Test def longArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")(stdio.sprintf(_, _, 1L, 2L, 3L, 4L)) + @Test def longArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf(_, _, 1L, 2L, 3L, 4L, 5L) + ) + @Test def longArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf(_, _, 1L, 2L, 3L, 4L, 5L, 6L) + ) + @Test def longArgs7(): Unit = + vatest(c"%d %d %d %d %d %d %d", "1 2 3 4 5 6 7")( + stdio.sprintf(_, _, 1L, 2L, 3L, 4L, 5L, 6L, 7L) + ) + @Test def longArgs8(): Unit = + vatest(c"%d %d %d %d %d %d %d %d", "1 2 3 4 5 6 7 8")( + stdio.sprintf(_, _, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L) + ) + @Test def longArgs9(): Unit = + vatest(c"%d %d %d %d %d %d %d %d %d", "1 2 3 4 5 6 7 8 9")( + stdio.sprintf(_, _, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L) + ) + + @Test def ubyteValueMin(): Unit = + vatest(c"%d", "0")(stdio.sprintf(_, _, UByte.MinValue)) + @Test def ubyteValueMax(): Unit = + vatest(c"%d", "255")(stdio.sprintf(_, _, UByte.MaxValue)) + @Test def ubyteArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toUByte)) + @Test def ubyteArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1.toUByte, 2.toUByte)) + @Test def ubyteArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")( + stdio.sprintf(_, _, 1.toUByte, 2.toUByte, 3.toUByte) + ) + @Test def ubyteArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")( + stdio.sprintf(_, _, 1.toUByte, 2.toUByte, 3.toUByte, 4.toUByte) + ) + @Test def ubyteArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf(_, _, 1.toUByte, 2.toUByte, 3.toUByte, 4.toUByte, 5.toUByte) + ) + @Test def ubyteArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf( + _, + _, + 1.toUByte, + 2.toUByte, + 3.toUByte, + 4.toUByte, + 5.toUByte, + 6.toUByte + ) + ) + @Test def ubyteArgs7(): Unit = + vatest( + c"%d %d %d %d %d %d %d", + "1 2 3 4 5 6 7" + )( + stdio.sprintf( + _, + _, + 1.toUByte, + 2.toUByte, + 3.toUByte, + 4.toUByte, + 5.toUByte, + 6.toUByte, + 7.toUByte + ) + ) + @Test def ubyteArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8" + )( + stdio.sprintf( + _, + _, + 1.toUByte, + 2.toUByte, + 3.toUByte, + 4.toUByte, + 5.toUByte, + 6.toUByte, + 7.toUByte, + 8.toUByte + ) + ) + @Test def ubyteArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8 9" + )( + stdio.sprintf( + _, + _, + 1.toUByte, + 2.toUByte, + 3.toUByte, + 4.toUByte, + 5.toUByte, + 6.toUByte, + 7.toUByte, + 8.toUByte, + 9.toUByte + ) + ) + + @Test def ushortValueMin(): Unit = + vatest(c"%d", "0")(stdio.sprintf(_, _, UShort.MinValue)) + @Test def ushortValueMax(): Unit = + vatest(c"%d", "65535")(stdio.sprintf(_, _, UShort.MaxValue)) + @Test def ushortArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toUShort)) + @Test def ushortArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1.toUShort, 2.toUShort)) + @Test def ushortArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")( + stdio.sprintf(_, _, 1.toUShort, 2.toUShort, 3.toUShort) + ) + @Test def ushortArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")( + stdio.sprintf(_, _, 1.toUShort, 2.toUShort, 3.toUShort, 4.toUShort) + ) + @Test def ushortArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf( + _, + _, + 1.toUShort, + 2.toUShort, + 3.toUShort, + 4.toUShort, + 5.toUShort + ) + ) + @Test def ushortArgs6(): Unit = + vatest( + c"%d %d %d %d %d %d", + "1 2 3 4 5 6" + )( + stdio.sprintf( + _, + _, + 1.toUShort, + 2.toUShort, + 3.toUShort, + 4.toUShort, + 5.toUShort, + 6.toUShort + ) + ) + @Test def ushortArgs7(): Unit = + vatest( + c"%d %d %d %d %d %d %d", + "1 2 3 4 5 6 7" + )( + stdio.sprintf( + _, + _, + 1.toUShort, + 2.toUShort, + 3.toUShort, + 4.toUShort, + 5.toUShort, + 6.toUShort, + 7.toUShort + ) + ) + @Test def ushortArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8" + )( + stdio.sprintf( + _, + _, + 1.toUShort, + 2.toUShort, + 3.toUShort, + 4.toUShort, + 5.toUShort, + 6.toUShort, + 7.toUShort, + 8.toUShort + ) + ) + @Test def ushortArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8 9" + )( + stdio.sprintf( + _, + _, + 1.toUShort, + 2.toUShort, + 3.toUShort, + 4.toUShort, + 5.toUShort, + 6.toUShort, + 7.toUShort, + 8.toUShort, + 9.toUShort + ) + ) + + @Test def uintValueMin(): Unit = + vatest(c"%u", "0")(stdio.sprintf(_, _, UInt.MinValue)) + @Test def uintValueMax(): Unit = + vatest(c"%u", "4294967295")(stdio.sprintf(_, _, UInt.MaxValue)) + @Test def uintArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toUInt)) + @Test def uintArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1.toUInt, 2.toUInt)) + @Test def uintArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")( + stdio.sprintf(_, _, 1.toUInt, 2.toUInt, 3.toUInt) + ) + @Test def uintArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")( + stdio.sprintf(_, _, 1.toUInt, 2.toUInt, 3.toUInt, 4.toUInt) + ) + @Test def uintArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf(_, _, 1.toUInt, 2.toUInt, 3.toUInt, 4.toUInt, 5.toUInt) + ) + @Test def uintArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf( + _, + _, + 1.toUInt, + 2.toUInt, + 3.toUInt, + 4.toUInt, + 5.toUInt, + 6.toUInt + ) + ) + @Test def uintArgs7(): Unit = + vatest(c"%d %d %d %d %d %d %d", "1 2 3 4 5 6 7")( + stdio.sprintf( + _, + _, + 1.toUInt, + 2.toUInt, + 3.toUInt, + 4.toUInt, + 5.toUInt, + 6.toUInt, + 7.toUInt + ) + ) + @Test def uintArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8" + )( + stdio.sprintf( + _, + _, + 1.toUInt, + 2.toUInt, + 3.toUInt, + 4.toUInt, + 5.toUInt, + 6.toUInt, + 7.toUInt, + 8.toUInt + ) + ) + @Test def uintArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8 9" + )( + stdio.sprintf( + _, + _, + 1.toUInt, + 2.toUInt, + 3.toUInt, + 4.toUInt, + 5.toUInt, + 6.toUInt, + 7.toUInt, + 8.toUInt, + 9.toUInt + ) + ) + + @Test def ulongValueMin(): Unit = { + assumeNot32Bit() + vatest(c"%llu", "0")(stdio.sprintf(_, _, ULong.MinValue)) + } + @Test def ulongValueMax(): Unit = { + assumeNot32Bit() + vatest(c"%llu", "18446744073709551615")(stdio.sprintf(_, _, ULong.MaxValue)) + } + @Test def ulongArgs1(): Unit = + vatest(c"%d", "1")(stdio.sprintf(_, _, 1.toULong)) + @Test def ulongArgs2(): Unit = + vatest(c"%d %d", "1 2")(stdio.sprintf(_, _, 1.toULong, 2.toULong)) + @Test def ulongArgs3(): Unit = + vatest(c"%d %d %d", "1 2 3")( + stdio.sprintf(_, _, 1.toULong, 2.toULong, 3.toULong) + ) + @Test def ulongArgs4(): Unit = + vatest(c"%d %d %d %d", "1 2 3 4")( + stdio.sprintf(_, _, 1.toULong, 2.toULong, 3.toULong, 4.toULong) + ) + @Test def ulongArgs5(): Unit = + vatest(c"%d %d %d %d %d", "1 2 3 4 5")( + stdio.sprintf(_, _, 1.toULong, 2.toULong, 3.toULong, 4.toULong, 5.toULong) + ) + @Test def ulongArgs6(): Unit = + vatest(c"%d %d %d %d %d %d", "1 2 3 4 5 6")( + stdio.sprintf( + _, + _, + 1.toULong, + 2.toULong, + 3.toULong, + 4.toULong, + 5.toULong, + 6.toULong + ) + ) + @Test def ulongArgs7(): Unit = + vatest( + c"%d %d %d %d %d %d %d", + "1 2 3 4 5 6 7" + )( + stdio.sprintf( + _, + _, + 1.toULong, + 2.toULong, + 3.toULong, + 4.toULong, + 5.toULong, + 6.toULong, + 7.toULong + ) + ) + @Test def ulongArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8" + )( + stdio.sprintf( + _, + _, + 1.toULong, + 2.toULong, + 3.toULong, + 4.toULong, + 5.toULong, + 6.toULong, + 7.toULong, + 8.toULong + ) + ) + @Test def ulongArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d", + "1 2 3 4 5 6 7 8 9" + )( + stdio.sprintf( + _, + _, + 1.toULong, + 2.toULong, + 3.toULong, + 4.toULong, + 5.toULong, + 6.toULong, + 7.toULong, + 8.toULong, + 9.toULong + ) + ) + + @Test def floatArgs1(): Unit = + vatest(c"%1.1f", "1.1")(stdio.sprintf(_, _, 1.1f)) + @Test def floatArgs2(): Unit = + vatest(c"%1.1f %1.1f", "1.1 2.2")(stdio.sprintf(_, _, 1.1f, 2.2f)) + @Test def floatArgs3(): Unit = + vatest(c"%1.1f %1.1f %1.1f", "1.1 2.2 3.3")( + stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f) + ) + @Test def floatArgs4(): Unit = + vatest(c"%1.1f %1.1f %1.1f %1.1f", "1.1 2.2 3.3 4.4")( + stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f, 4.4f) + ) + @Test def floatArgs5(): Unit = + vatest(c"%1.1f %1.1f %1.1f %1.1f %1.1f", "1.1 2.2 3.3 4.4 5.5")( + stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f, 4.4f, 5.5f) + ) + @Test def floatArgs6(): Unit = + vatest(c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", "1.1 2.2 3.3 4.4 5.5 6.6")( + stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f) + ) + @Test def floatArgs7(): Unit = + vatest( + c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1.1 2.2 3.3 4.4 5.5 6.6 7.7" + )(stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f, 7.7f)) + @Test def floatArgs8(): Unit = + vatest( + c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8" + )(stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f, 7.7f, 8.8f)) + @Test def floatArgs9(): Unit = + vatest( + c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9" + )(stdio.sprintf(_, _, 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 6.6f, 7.7f, 8.8f, 9.9f)) + + @Test def doubleArgs1(): Unit = + vatest(c"%1.1f", "1.1")(stdio.sprintf(_, _, 1.1d)) + @Test def doubleArgs2(): Unit = + vatest(c"%1.1f %1.1f", "1.1 2.2")(stdio.sprintf(_, _, 1.1d, 2.2d)) + @Test def doubleArgs3(): Unit = + vatest(c"%1.1f %1.1f %1.1f", "1.1 2.2 3.3")( + stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d) + ) + @Test def doubleArgs4(): Unit = + vatest(c"%1.1f %1.1f %1.1f %1.1f", "1.1 2.2 3.3 4.4")( + stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d, 4.4d) + ) + @Test def doubleArgs5(): Unit = + vatest(c"%1.1f %1.1f %1.1f %1.1f %1.1f", "1.1 2.2 3.3 4.4 5.5")( + stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d) + ) + @Test def doubleArgs6(): Unit = + vatest(c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", "1.1 2.2 3.3 4.4 5.5 6.6")( + stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d, 6.6d) + ) + @Test def doubleArgs7(): Unit = + vatest( + c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1.1 2.2 3.3 4.4 5.5 6.6 7.7" + )(stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d, 6.6d, 7.7d)) + @Test def doubleArgs8(): Unit = + vatest( + c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8" + )(stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d, 6.6d, 7.7d, 8.8d)) + @Test def doubleArgs9(): Unit = + vatest( + c"%1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9" + )(stdio.sprintf(_, _, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d, 6.6d, 7.7d, 8.8d, 9.9d)) + + @Test def mixArgs1(): Unit = + vatest(c"%d %1.1f", "1 1.1")(stdio.sprintf(_, _, 1, 1.1d)) + @Test def mixArgs2(): Unit = + vatest(c"%d %d %1.1f %1.1f", "1 2 1.1 2.2")( + stdio.sprintf(_, _, 1, 2, 1.1d, 2.2d) + ) + @Test def mixArgs3(): Unit = + vatest(c"%d %d %d %1.1f %1.1f %1.1f", "1 2 3 1.1 2.2 3.3")( + stdio.sprintf(_, _, 1, 2, 3, 1.1d, 2.2d, 3.3d) + ) + @Test def mixArgs4(): Unit = + vatest(c"%d %d %d %d %1.1f %1.1f %1.1f %1.1f", "1 2 3 4 1.1 2.2 3.3 4.4")( + stdio.sprintf(_, _, 1, 2, 3, 4, 1.1d, 2.2d, 3.3d, 4.4d) + ) + @Test def mixArgs5(): Unit = + vatest( + c"%d %d %d %d %d %1.1f %1.1f %1.1f %1.1f %1.1f", + "1 2 3 4 5 1.1 2.2 3.3 4.4 5.5" + )(stdio.sprintf(_, _, 1, 2, 3, 4, 5, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d)) + @Test def mixArgs6(): Unit = + vatest( + c"%d %d %d %d %d %d %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1 2 3 4 5 6 1.1 2.2 3.3 4.4 5.5 6.6" + )(stdio.sprintf(_, _, 1, 2, 3, 4, 5, 6, 1.1d, 2.2d, 3.3d, 4.4d, 5.5d, 6.6d)) + @Test def mixArgs7(): Unit = + vatest( + c"%d %d %d %d %d %d %d %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1 2 3 4 5 6 7 1.1 2.2 3.3 4.4 5.5 6.6 7.7" + )( + stdio.sprintf( + _, + _, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 1.1d, + 2.2d, + 3.3d, + 4.4d, + 5.5d, + 6.6d, + 7.7d + ) + ) + @Test def mixArgs8(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1 2 3 4 5 6 7 8 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8" + )( + stdio.sprintf( + _, + _, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 1.1d, + 2.2d, + 3.3d, + 4.4d, + 5.5d, + 6.6d, + 7.7d, + 8.8d + ) + ) + @Test def mixArgs9(): Unit = + vatest( + c"%d %d %d %d %d %d %d %d %d %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f %1.1f", + "1 2 3 4 5 6 7 8 9 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9" + )( + stdio.sprintf( + _, + _, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 1.1d, + 2.2d, + 3.3d, + 4.4d, + 5.5d, + 6.6d, + 7.7d, + 8.8d, + 9.9d + ) + ) +} From cece7fe9e3791f74d49d4b697b4d6e3fb887a97c Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 9 Mar 2023 21:26:31 +0100 Subject: [PATCH 33/61] Allow for materialization of `Tag[Ptr[_]]` or `Tag[Ptr]` taking abstract type parameter (#3207) Loading `Ptr[Ptr[_]` is always a safe operation, because the result type is a `Ptr[_]`. A try to load `Ptr[_]` would not compile because we are not able to generate a Tag for wildcard type * Adds 2 new materializers allowing to produce `Tag[Ptr[_]]` and `Tag[Ptr[X]]` where `type X` is an abstract type. * Adds `NotGiven[_]` implicit search capability to `Tag` based on built-in Scala3 support or Scala 2 workaround * Fixes erroneous type definitions using `Void` instead of `Unit` is return type of `CFuncPtr` --- .../scala/scalanative/runtime/zlib.scala | 2 +- .../scala/scala/scalanative/unsafe/Tag.scala | 29 +++++++++++++++++++ .../scala/scalanative/unsafe/Tag.scala.gyb | 29 +++++++++++++++++++ .../nscplugin/NirDefinitions.scala | 4 +++ .../scalanative/nscplugin/NirGenUtil.scala | 26 +++++++++-------- .../nscplugin/NirDefinitions.scala | 3 ++ .../scalanative/nscplugin/NirGenUtil.scala | 4 ++- .../scala/scalanative/posix/dirent.scala | 2 +- .../scala/scala/scalanative/IssuesTest.scala | 11 +++++++ .../scala/scalanative/unsafe/TagTest.scala | 14 +++++++++ 10 files changed, 109 insertions(+), 15 deletions(-) diff --git a/nativelib/src/main/scala/scala/scalanative/runtime/zlib.scala b/nativelib/src/main/scala/scala/scalanative/runtime/zlib.scala index e6af2d5c4e..415025490a 100644 --- a/nativelib/src/main/scala/scala/scalanative/runtime/zlib.scala +++ b/nativelib/src/main/scala/scala/scalanative/runtime/zlib.scala @@ -15,7 +15,7 @@ object zlib { type uLong = CUnsignedLong type uLongf = CUnsignedLong type alloc_func = CFuncPtr3[voidpf, uInt, uInt, voidpf] - type free_func = CFuncPtr2[voidpf, voidpf, Void] + type free_func = CFuncPtr2[voidpf, voidpf, Unit] type Bytef = Byte type z_size_t = CUnsignedLong type z_off_t = CLong diff --git a/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala b/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala index 9caf9ef465..b1c1ebf064 100644 --- a/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala +++ b/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala @@ -4434,4 +4434,33 @@ object Tag { } } } + + // Scala 3 defines scala.util.NotGiven, but it has a special handling in the compiler + // For Scala 2 we can use well known hack to get implicit negation (via ambigious defs) + type NotGivenCompat[+T] = NotGivenCompatDef.Proxy.NotGivenCompat[T] + object NotGivenCompatDef{ + import MockImpl._ + object Proxy { + import scala.util._ + type NotGivenCompat[+T] = NotGiven[T] + val NotGivenCompat = NotGiven + } + + object MockImpl { + final class NotGiven[+T] private () + sealed trait LowPriorityNotGiven { + implicit def default[T]: NotGiven[T] = NotGiven.value + } + object NotGiven extends LowPriorityNotGiven { + def value: NotGiven[Nothing] = new NotGiven[Nothing]() + + implicit def amb1[T](implicit ev: T): NotGiven[T] = ??? + implicit def amb2[T](implicit ev: T): NotGiven[T] = ??? + } + } + } + + private def TagOfPtrAnyClass = Tag.Ptr(Tag.Class(classOf[AnyRef])) + implicit def materializePtrWildcard: Tag[unsafe.Ptr[_]] = TagOfPtrAnyClass.asInstanceOf[Tag[unsafe.Ptr[_]]] + implicit def materializePtrClassNotGivenClassTag[T](implicit ev: NotGivenCompat[ClassTag[T]]): Tag[unsafe.Ptr[T]] = TagOfPtrAnyClass.asInstanceOf[Tag[unsafe.Ptr[T]]] } diff --git a/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala.gyb b/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala.gyb index 5eaaa4f45c..17390a9240 100644 --- a/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala.gyb +++ b/nativelib/src/main/scala/scala/scalanative/unsafe/Tag.scala.gyb @@ -269,4 +269,33 @@ object Tag { } } % end + + // Scala 3 defines scala.util.NotGiven, but it has a special handling in the compiler + // For Scala 2 we can use well known hack to get implicit negation (via ambigious defs) + type NotGivenCompat[+T] = NotGivenCompatDef.Proxy.NotGivenCompat[T] + object NotGivenCompatDef{ + import MockImpl._ + object Proxy { + import scala.util._ + type NotGivenCompat[+T] = NotGiven[T] + val NotGivenCompat = NotGiven + } + + object MockImpl { + final class NotGiven[+T] private () + sealed trait LowPriorityNotGiven { + implicit def default[T]: NotGiven[T] = NotGiven.value + } + object NotGiven extends LowPriorityNotGiven { + def value: NotGiven[Nothing] = new NotGiven[Nothing]() + + implicit def amb1[T](implicit ev: T): NotGiven[T] = ??? + implicit def amb2[T](implicit ev: T): NotGiven[T] = ??? + } + } + } + + private def TagOfPtrAnyClass = Tag.Ptr(Tag.Class(classOf[AnyRef])) + implicit def materializePtrWildcard: Tag[unsafe.Ptr[_]] = TagOfPtrAnyClass.asInstanceOf[Tag[unsafe.Ptr[_]]] + implicit def materializePtrClassNotGivenClassTag[T](implicit ev: NotGivenCompat[ClassTag[T]]): Tag[unsafe.Ptr[T]] = TagOfPtrAnyClass.asInstanceOf[Tag[unsafe.Ptr[T]]] } diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirDefinitions.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirDefinitions.scala index 8a9631958d..40a259307b 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirDefinitions.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirDefinitions.scala @@ -99,6 +99,10 @@ trait NirDefinitions { lazy val DoubleTagMethod = getDecl(TagModule, TermName("materializeDoubleTag")) lazy val PtrTagMethod = getDecl(TagModule, TermName("materializePtrTag")) + lazy val PtrWildcardTagMethod = + getDecl(TagModule, TermName("materializePtrWildcard")) + lazy val PtrClassNotGivenClassTagMethod = + getDecl(TagModule, TermName("materializePtrClassNotGivenClassTag")) lazy val ClassTagMethod = getDecl(TagModule, TermName("materializeClassTag")) lazy val NatBaseTagMethod = (0 to 9).map { n => diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenUtil.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenUtil.scala index 5fa35695dd..f647b4775f 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenUtil.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenUtil.scala @@ -58,21 +58,23 @@ trait NirGenUtil[G <: Global with Singleton] { self: NirGenPhase[G] => def wrap(sym: Symbol) = allsts.map(SimpleType(sym, _)) ref.symbol match { - case UnitTagMethod => just(UnitClass) - case BooleanTagMethod => just(BooleanClass) - case CharTagMethod => just(CharClass) - case ByteTagMethod => just(ByteClass) - case UByteTagMethod => just(UByteClass) - case ShortTagMethod => just(ShortClass) - case UShortTagMethod => just(UShortClass) - case IntTagMethod => just(IntClass) - case UIntTagMethod => just(UIntClass) - case LongTagMethod => just(LongClass) - case ULongTagMethod => just(ULongClass) + case UnitTagMethod => just(UnitClass) + case BooleanTagMethod => just(BooleanClass) + case CharTagMethod => just(CharClass) + case ByteTagMethod => just(ByteClass) + case UByteTagMethod => just(UByteClass) + case ShortTagMethod => just(ShortClass) + case UShortTagMethod => just(UShortClass) + case IntTagMethod => just(IntClass) + case UIntTagMethod => just(UIntClass) + case LongTagMethod => just(LongClass) + case ULongTagMethod => just(ULongClass) case FloatTagMethod => just(FloatClass) case DoubleTagMethod => just(DoubleClass) case PtrTagMethod => just(PtrClass) - case ClassTagMethod => just(unwrapClassTagOption(args.head).get) + case PtrWildcardTagMethod => just(PtrClass) + case PtrClassNotGivenClassTagMethod => just(PtrClass) + case ClassTagMethod => just(unwrapClassTagOption(args.head).get) case sym if CStructTagMethod.contains(sym) => wrap(CStructClass(args.length)) case CArrayTagMethod => diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirDefinitions.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirDefinitions.scala index dbb50f5333..4e85fcadfa 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirDefinitions.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirDefinitions.scala @@ -139,6 +139,9 @@ final class NirDefinitions()(using ctx: Context) { def UnsafeTag_materializeNatBaseTags(using Context) = UnsafeTag_materializeNatBaseTagsR.map(_.symbol) def UnsafeTag_materializeNatDigitTags(using Context) = UnsafeTag_materializeNatDigitTagsR.map(_.symbol) def UnsafeTag_materializeCStructTags(using Context) = UnsafeTag_materializeCStructTagsR.map(_.symbol) + @tu lazy val UnsafeTag_materializePtrWildcardTag = TagModule.requiredMethod("materializePtrWildcard") + @tu lazy val UnsafeTag_materializePtrClassNotGivenClassTag = + TagModule.requiredMethod("materializePtrClassNotGivenClassTag") // Native runtime package @tu lazy val RuntimePackageVal = requiredModuleRef("scala.scalanative.runtime.package") diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenUtil.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenUtil.scala index c1cca2f3d9..0f4a4361d8 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenUtil.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenUtil.scala @@ -45,7 +45,9 @@ trait NirGenUtil(using Context) { self: NirCodeGen => defnNir.UnsafeTag_materializeUShortTag -> defnNir.UShortClass, defnNir.UnsafeTag_materializeUIntTag -> defnNir.UIntClass, defnNir.UnsafeTag_materializeULongTag -> defnNir.ULongClass, - defnNir.UnsafeTag_materializePtrTag -> defnNir.PtrClass + defnNir.UnsafeTag_materializePtrTag -> defnNir.PtrClass, + defnNir.UnsafeTag_materializePtrWildcardTag -> defnNir.PtrClass, + defnNir.UnsafeTag_materializePtrClassNotGivenClassTag -> defnNir.PtrClass ) protected def desugarTree(tree: Tree): Tree = { diff --git a/posixlib/src/main/scala/scala/scalanative/posix/dirent.scala b/posixlib/src/main/scala/scala/scalanative/posix/dirent.scala index cf07986fe1..76b61963ba 100644 --- a/posixlib/src/main/scala/scala/scalanative/posix/dirent.scala +++ b/posixlib/src/main/scala/scala/scalanative/posix/dirent.scala @@ -7,7 +7,7 @@ import scala.scalanative.unsafe._, Nat._ object dirent { type _256 = Digit3[_2, _5, _6] - type DIR = Void + type DIR = CStruct0 type dirent = CStruct3[CUnsignedLongLong, CArray[CChar, _256], CShort] diff --git a/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala b/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala index e454555198..7609184415 100644 --- a/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala +++ b/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala @@ -610,6 +610,17 @@ class IssuesTest { } } + @Test def issue3196(): Unit = { + object ctx { + type Foo + } + val ptr1 = stackalloc[Ptr[ctx.Foo]]() + println(!ptr1) // segfault + + val ptr2 = stackalloc[Ptr[_]]() + println(!ptr2) // segfault + } + } package issue1090 { diff --git a/unit-tests/native/src/test/scala/scala/scalanative/unsafe/TagTest.scala b/unit-tests/native/src/test/scala/scala/scalanative/unsafe/TagTest.scala index 43ddf387d3..96eeed145e 100644 --- a/unit-tests/native/src/test/scala/scala/scalanative/unsafe/TagTest.scala +++ b/unit-tests/native/src/test/scala/scala/scalanative/unsafe/TagTest.scala @@ -232,4 +232,18 @@ class TagTest { assertTrue(tagof[sockaddr_in6].size == sizeof[sockaddr_in6]) assertTrue(tagof[ipv6_mreq].size == sizeof[ipv6_mreq]) } + + @Test def abstractTypeTag(): Unit = { + // https://github.com/scala-native/scala-native/issues/3196 + val PtrAnyClassTag = Tag.Ptr(Tag.Class(classOf[AnyRef])) + object abstractTagWrapper { + type Foo + } + assertEquals(PtrAnyClassTag, tagof[Ptr[abstractTagWrapper.Foo]]) + assertEquals(PtrAnyClassTag, tagof[Ptr[_]]) + assertEquals( + Tag.Ptr(PtrAnyClassTag), + tagof[Ptr[Ptr[abstractTagWrapper.Foo]]] + ) + } } From 652fdce7aad16ca93b5e30fd4c1386ffd23f385f Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Fri, 10 Mar 2023 00:30:24 +0100 Subject: [PATCH 34/61] Fix failing release-full build due to usage of void in function signature Ensure duplicate functions use BoxedUnit instead of Unit as a temporal workaround for failing compilation in release-full mode --- .../src/main/scala/scala/scalanative/interflow/Visit.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/src/main/scala/scala/scalanative/interflow/Visit.scala b/tools/src/main/scala/scala/scalanative/interflow/Visit.scala index 46090d8b5f..0b255cc26e 100644 --- a/tools/src/main/scala/scala/scalanative/interflow/Visit.scala +++ b/tools/src/main/scala/scala/scalanative/interflow/Visit.scala @@ -160,7 +160,11 @@ trait Visit { self: Interflow => case (argty, origty) => // Duplicate argument type should not be // less specific than the original declare type. - if (!Sub.is(argty, origty)) origty else argty + val tpe = if (!Sub.is(argty, origty)) origty else argty + // Lift Unit to BoxedUnit, only in that form it can be passed as a function argument + // It would be better to eliminate void arguments, but currently generates lots of problmes + if (tpe == nir.Type.Unit) Rt.BoxedUnit + else tpe } val Global.Member(top, sig) = orig: @unchecked Global.Member(top, Sig.Duplicate(sig, dupargtys)) From e7a877a8dffa5f66cc91ceeb4ad9d4a2a561fb09 Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Fri, 10 Mar 2023 14:57:46 +0100 Subject: [PATCH 35/61] Fix #3206: posixlib unistd and monetary use new varargs support (#3209) --- .../scala/scalanative/posix/monetary.scala | 87 ++----------------- .../scala/scalanative/posix/unistd.scala | 12 ++- 2 files changed, 16 insertions(+), 83 deletions(-) diff --git a/posixlib/src/main/scala/scala/scalanative/posix/monetary.scala b/posixlib/src/main/scala/scala/scalanative/posix/monetary.scala index 5a941d8603..733d7165c9 100644 --- a/posixlib/src/main/scala/scala/scalanative/posix/monetary.scala +++ b/posixlib/src/main/scala/scala/scalanative/posix/monetary.scala @@ -3,107 +3,30 @@ package posix import scalanative.unsafe._ -import scalanative.posix.sys.types._ +import scalanative.posix.sys.types.{ssize_t, size_t} /** POSIX monetary.h for Scala * * The Open Group Base Specifications * [[https://pubs.opengroup.org/onlinepubs/9699919799 Issue 7, 2018]] edition. - * - * POSIX defines strfmon() and strfmon_l() using the "..." form of C variable - * arguments. Scala Native "supports native interoperability with C’s variadic - * argument list type (i.e. va_list), but not ... varargs". - * - * This implementation supports up to 10 items in the variable arguments to - * strfmon() and strfmon_1(). */ +@extern object monetary { type locale_t = locale.locale_t - private final val maxOutArgs = 10 def strfmon( str: CString, max: size_t, format: CString, - argsIn: Double* - ): ssize_t = Zone { implicit z => - val argsOut = new Array[Double](maxOutArgs) - val limit = Math.min(argsIn.size, maxOutArgs) - - for (j <- 0 until limit) - argsOut(j) = argsIn(j) - - // format: off - val nFormatted = monetaryExtern.strfmon_10(str, max, format, - argsOut(0), argsOut(1), - argsOut(2), argsOut(3), - argsOut(4), argsOut(5), - argsOut(6), argsOut(7), - argsOut(8), argsOut(9) - ) - // format: on - - nFormatted - } + vargs: Any* + ): ssize_t = extern def strfmon_l( str: CString, max: size_t, locale: locale_t, format: CString, - argsIn: Double* - ): ssize_t = Zone { implicit z => - val argsOut = new Array[Double](maxOutArgs) - val limit = Math.min(argsIn.size, maxOutArgs) - - for (j <- 0 until limit) - argsOut(j) = argsIn(j) - - // format: off - val nFormatted = monetaryExtern.strfmon_l_10(str, max, locale, format, - argsOut(0), argsOut(1), - argsOut(2), argsOut(3), - argsOut(4), argsOut(5), - argsOut(6), argsOut(7), - argsOut(8), argsOut(9) - ) - // format: on - - nFormatted - } - -} - -@extern -object monetaryExtern { - - // format: off - @name("scalanative_strfmon_10") - def strfmon_10( - str: CString, - max: size_t, - format: CString, - arg0: Double, arg1: Double, - arg2: Double, arg3: Double, - arg4: Double, arg5: Double, - arg6: Double, arg7: Double, - arg8: Double, arg9: Double - ): ssize_t = extern - // format: on - - // format: off - @name("scalanative_strfmon_l_10") - def strfmon_l_10( - str: CString, - max: size_t, - locale: monetary.locale_t, - format: CString, - arg0: Double, arg1: Double, - arg2: Double, arg3: Double, - arg4: Double, arg5: Double, - arg6: Double, arg7: Double, - arg8: Double, arg9: Double + vargs: Any* ): ssize_t = extern - // format: on } diff --git a/posixlib/src/main/scala/scala/scalanative/posix/unistd.scala b/posixlib/src/main/scala/scala/scalanative/posix/unistd.scala index efab0f3263..8f0d0fca9e 100644 --- a/posixlib/src/main/scala/scala/scalanative/posix/unistd.scala +++ b/posixlib/src/main/scala/scala/scalanative/posix/unistd.scala @@ -31,7 +31,17 @@ object unistd { def close(fildes: CInt): CInt = extern def dup(fildes: CInt): CInt = extern def dup2(fildes: CInt, fildesnew: CInt): CInt = extern - def execve(path: CString, argv: Ptr[CString], envp: Ptr[CString]): CInt = + + def _exit(status: CInt): Unit = extern + + // XSI + def encrypt(block: Ptr[Byte], edflag: Int): Unit = extern + + def execl(pathname: CString, arg: CString, vargs: Any*): CInt = extern + def execlp(file: CString, arg: CString, vargs: Any*): CInt = extern + def execle(pathname: CString, arg: CString, vargs: Any*): CInt = extern + def execv(pathname: CString, argv: Ptr[CString]): CInt = extern + def execve(pathname: CString, argv: Ptr[CString], envp: Ptr[CString]): CInt = extern def fork(): CInt = extern def fsync(fildes: CInt): CInt = extern From df53b1d42fa2f18840e68cb82e08c2d3aa53e0fa Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Fri, 10 Mar 2023 16:34:03 +0100 Subject: [PATCH 36/61] Implement limited Java collections spliterator support (#3202) Implement limited javalib spliterator support. The know limitations are listed in the file `Spliterators.scala`. --- .../src/main/scala/java/lang/Iterable.scala | 12 + .../src/main/scala/java/util/ArrayDeque.scala | 2 +- .../src/main/scala/java/util/ArrayList.scala | 7 - javalib/src/main/scala/java/util/Arrays.scala | 106 ++ .../src/main/scala/java/util/Collection.scala | 11 +- .../scala/java/util/PrimitiveIterator.scala | 95 + .../main/scala/java/util/Spliterator.scala | 74 +- .../main/scala/java/util/Spliterators.scala | 702 ++++++++ .../scalanative/nscplugin/NirGenExpr.scala | 12 +- .../scalanative/nscplugin/NirGenType.scala | 2 +- .../scala/scalanative/posix/unistd.scala | 1 - .../lang/IterableSpliteratorTest.scala | 50 + .../javalib/util/ArraysSpliteratorTest.scala | 492 ++++++ .../CollectionDefaultSpliteratorTest.scala | 70 + .../javalib/util/CollectionTest.scala | 39 +- .../javalib/util/SpliteratorsTest.scala | 1571 +++++++++++++++++ 16 files changed, 3224 insertions(+), 22 deletions(-) create mode 100644 javalib/src/main/scala/java/util/PrimitiveIterator.scala create mode 100644 javalib/src/main/scala/java/util/Spliterators.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/lang/IterableSpliteratorTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/ArraysSpliteratorTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionDefaultSpliteratorTest.scala create mode 100644 unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/SpliteratorsTest.scala diff --git a/javalib/src/main/scala/java/lang/Iterable.scala b/javalib/src/main/scala/java/lang/Iterable.scala index f0276e25b6..404f94d220 100644 --- a/javalib/src/main/scala/java/lang/Iterable.scala +++ b/javalib/src/main/scala/java/lang/Iterable.scala @@ -1,9 +1,11 @@ // Ported from Scala.js commit: f9fc1a dated: 2020-03-06 +// default spliterator method added for Scala Native. package java.lang import java.util.Iterator import java.util.function.Consumer +import java.util.{Spliterator, Spliterators} trait Iterable[T] { def iterator(): Iterator[T] @@ -13,4 +15,14 @@ trait Iterable[T] { while (iter.hasNext()) action.accept(iter.next()) } + + /** From the Java 8 documentation: The default implementation should usually + * be overridden. The spliterator returned by the default implementation has + * poor splitting capabilities, is unsized, and does not report any + * spliterator characteristics. Implementing classes can nearly always + * provide a better implementation. + */ + def spliterator(): Spliterator[T] = { + Spliterators.spliteratorUnknownSize[T](this.iterator(), 0) + } } diff --git a/javalib/src/main/scala/java/util/ArrayDeque.scala b/javalib/src/main/scala/java/util/ArrayDeque.scala index 00d5990856..74cf95f7b2 100644 --- a/javalib/src/main/scala/java/util/ArrayDeque.scala +++ b/javalib/src/main/scala/java/util/ArrayDeque.scala @@ -851,7 +851,7 @@ class ArrayDeque[E]( * a {@code Spliterator} over the elements in this deque * @since 1.8 */ - def spliterator(): Spliterator[E] = { + override def spliterator(): Spliterator[E] = { return new DeqSpliterator() } diff --git a/javalib/src/main/scala/java/util/ArrayList.scala b/javalib/src/main/scala/java/util/ArrayList.scala index fbf42e2b40..eebecd57b4 100644 --- a/javalib/src/main/scala/java/util/ArrayList.scala +++ b/javalib/src/main/scala/java/util/ArrayList.scala @@ -178,11 +178,4 @@ class ArrayList[E] private ( } _size = 0 } - - // TODO: JDK 1.8 - // def forEach(action: Consumer[_ >: E]): Unit = - // def spliterator(): Spliterator[E] = - // def removeIf(filter: Predicate[_ >: E]): Boolean = - // def replaceAll(operator: UnaryOperator[E]): Unit = - // def sort(c: Comparator[_ >: E]): Unit = } diff --git a/javalib/src/main/scala/java/util/Arrays.scala b/javalib/src/main/scala/java/util/Arrays.scala index c7f7e03654..2ed8c6f6c9 100644 --- a/javalib/src/main/scala/java/util/Arrays.scala +++ b/javalib/src/main/scala/java/util/Arrays.scala @@ -1,4 +1,5 @@ // Ported from Scala.js commit: ba618ed dated: 2020-10-05 +// Arrays.spliterator() methods added for Scala Native. package java.util @@ -997,4 +998,109 @@ object Arrays { } } } + +// Scala Native additions -------------------------------------------------- + import java.util.{Spliterator, Spliterators} + + private final val standardArraySpliteratorCharacteristics = + Spliterator.SIZED | + Spliterator.SUBSIZED | + Spliterator.ORDERED | + Spliterator.IMMUTABLE + + def spliterator(array: Array[Double]): Spliterator.OfDouble = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + 0, + array.size, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator( + array: Array[Double], + startInclusive: Int, + endExclusive: Int + ): Spliterator.OfDouble = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + startInclusive, + endExclusive, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator(array: Array[Int]): Spliterator.OfInt = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + 0, + array.size, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator( + array: Array[Int], + startInclusive: Int, + endExclusive: Int + ): Spliterator.OfInt = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + startInclusive, + endExclusive, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator(array: Array[Long]): Spliterator.OfLong = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + 0, + array.size, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator( + array: Array[Long], + startInclusive: Int, + endExclusive: Int + ): Spliterator.OfLong = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + startInclusive, + endExclusive, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator[T](array: Array[Object]): Spliterator[T] = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + 0, + array.size, + standardArraySpliteratorCharacteristics + ) + } + + def spliterator[T]( + array: Array[Object], + startInclusive: Int, + endExclusive: Int + ): Spliterator[T] = { + Objects.requireNonNull(array) + Spliterators.spliterator( + array, + startInclusive, + endExclusive, + standardArraySpliteratorCharacteristics + ) + } } diff --git a/javalib/src/main/scala/java/util/Collection.scala b/javalib/src/main/scala/java/util/Collection.scala index c9bfff6382..25d9748698 100644 --- a/javalib/src/main/scala/java/util/Collection.scala +++ b/javalib/src/main/scala/java/util/Collection.scala @@ -1,7 +1,8 @@ // Ported from Scala.js commit: f122aa5 dated: 2019-07-03 - +// Additional Spliterator code implemented for Scala Native package java.util +import java.util.function.Consumer import java.util.function.Predicate trait Collection[E] extends java.lang.Iterable[E] { @@ -33,4 +34,12 @@ trait Collection[E] extends java.lang.Iterable[E] { def clear(): Unit def equals(o: Any): Boolean def hashCode(): Int + + /* From the Java documentation: + * "The default implementation should be overridden by subclasses that + * can return a more efficient spliterator." + */ + override def spliterator(): Spliterator[E] = { + Spliterators.spliterator[E](this, Spliterator.SIZED | Spliterator.SUBSIZED) + } } diff --git a/javalib/src/main/scala/java/util/PrimitiveIterator.scala b/javalib/src/main/scala/java/util/PrimitiveIterator.scala new file mode 100644 index 0000000000..b1aff34597 --- /dev/null +++ b/javalib/src/main/scala/java/util/PrimitiveIterator.scala @@ -0,0 +1,95 @@ +package java.util + +import java.util.function._ + +import Spliterator._ + +object PrimitiveIterator { + trait OfDouble extends PrimitiveIterator[Double, DoubleConsumer] { + override def forEachRemaining(action: Consumer[_ >: Double]): Unit = { + Objects.requireNonNull(action) + + if (action.isInstanceOf[DoubleConsumer]) { + forEachRemaining(action.asInstanceOf[DoubleConsumer]) + } else { + + while (hasNext()) + action.accept(next()) + } + } + + def forEachRemaining(action: DoubleConsumer): Unit = { + Objects.requireNonNull(action) + while (hasNext()) + action.accept(nextDouble()) + } + + /* BEWARE: The Java Doc says that the result from next() should + * be boxed, i.e. new java.lang.Double(nextDouble). + * The Scala Native implementation of Iterator demands + * that this be an unboxed, primitive. The boxed result + * conflicts with Iterator.next() declaration. + * + * Similar consideration exists for OfInt and OfLong. + */ + def next() = nextDouble() // return should be boxed primitive but is not. + + // Throws NoSuchElementException if iterator has no more elements + def nextDouble(): scala.Double // Abstract + } + + trait OfInt extends PrimitiveIterator[Int, IntConsumer] { + override def forEachRemaining(action: Consumer[_ >: Int]): Unit = { + Objects.requireNonNull(action) + + if (action.isInstanceOf[IntConsumer]) { + forEachRemaining(action.asInstanceOf[IntConsumer]) + } else { + + while (hasNext()) + action.accept(next()) + } + } + + def forEachRemaining(action: IntConsumer): Unit = { + Objects.requireNonNull(action) + while (hasNext()) + action.accept(nextInt()) + } + + // See BEWARE above for OfDouble.next() + def next() = nextInt() // return should be boxed primitive but is not. + + // Throws NoSuchElementException if iterator has no more elements + def nextInt(): Int // Abstract + } + + trait OfLong extends PrimitiveIterator[Long, LongConsumer] { + override def forEachRemaining(action: Consumer[_ >: Long]): Unit = { + Objects.requireNonNull(action) + if (action.isInstanceOf[LongConsumer]) { + forEachRemaining(action.asInstanceOf[LongConsumer]) + } else { + + while (hasNext()) + action.accept(next()) + } + } + + def forEachRemaining(action: LongConsumer): Unit = { + Objects.requireNonNull(action) + while (hasNext()) + action.accept(nextLong()) + } + + // See BEWARE above for OfDouble.next() + def next() = nextLong() // return should be boxed primitive but is not. + + // Throws NoSuchElementException if iterator has no more elements + def nextLong(): Long // Abstract + } +} + +trait PrimitiveIterator[T, T_CONS] extends Iterator[T] { + def forEachRemaining(action: T_CONS): Unit +} diff --git a/javalib/src/main/scala/java/util/Spliterator.scala b/javalib/src/main/scala/java/util/Spliterator.scala index 6e90a3dfd1..91bc6d9496 100644 --- a/javalib/src/main/scala/java/util/Spliterator.scala +++ b/javalib/src/main/scala/java/util/Spliterator.scala @@ -1,6 +1,6 @@ package java.util -import java.util.function.Consumer +import java.util.function._ import Spliterator._ @@ -13,6 +13,78 @@ object Spliterator { final val IMMUTABLE = 0x00000400 final val CONCURRENT = 0x00001000 final val SUBSIZED = 0x00004000 + + trait OfPrimitive[ + T, + T_CONS, + T_SPLITR <: Spliterator.OfPrimitive[T, T_CONS, T_SPLITR] + ] extends Spliterator[T] { + override def trySplit(): T_SPLITR + def tryAdvance(action: T_CONS): Boolean + def forEachRemaining(action: T_CONS): Unit = { + while (tryAdvance(action)) () + } + } + + trait OfInt + extends OfPrimitive[java.lang.Integer, IntConsumer, Spliterator.OfInt] { + override def trySplit(): OfInt + override def tryAdvance(action: IntConsumer): Boolean + override def forEachRemaining(action: IntConsumer): Unit = + while (tryAdvance(action)) () + override def tryAdvance(action: Consumer[_ >: Integer]): Boolean = + action match { + case action: IntConsumer => tryAdvance(action: IntConsumer) + case _ => tryAdvance((action.accept(_)): IntConsumer) + } + override def forEachRemaining(action: Consumer[_ >: Integer]): Unit = + action match { + case action: IntConsumer => forEachRemaining(action: IntConsumer) + case _ => forEachRemaining((action.accept(_)): IntConsumer) + } + + } + trait OfLong + extends OfPrimitive[java.lang.Long, LongConsumer, Spliterator.OfLong] { + override def trySplit(): OfLong + override def tryAdvance(action: LongConsumer): Boolean + override def forEachRemaining(action: LongConsumer): Unit = + while (tryAdvance(action)) () + override def tryAdvance(action: Consumer[_ >: java.lang.Long]): Boolean = + action match { + case action: LongConsumer => tryAdvance(action: LongConsumer) + case _ => tryAdvance((action.accept(_)): LongConsumer) + } + override def forEachRemaining(action: Consumer[_ >: java.lang.Long]): Unit = + action match { + case action: LongConsumer => forEachRemaining(action: LongConsumer) + case _ => forEachRemaining((action.accept(_)): LongConsumer) + } + } + trait OfDouble + extends OfPrimitive[ + java.lang.Double, + DoubleConsumer, + Spliterator.OfDouble + ] { + override def trySplit(): OfDouble + override def tryAdvance(action: DoubleConsumer): Boolean + override def forEachRemaining(action: DoubleConsumer): Unit = + while (tryAdvance(action)) () + override def tryAdvance(action: Consumer[_ >: java.lang.Double]): Boolean = + action match { + case action: DoubleConsumer => tryAdvance(action: DoubleConsumer) + case _ => tryAdvance((action.accept(_)): DoubleConsumer) + } + override def forEachRemaining( + action: Consumer[_ >: java.lang.Double] + ): Unit = + action match { + case action: DoubleConsumer => forEachRemaining(action: DoubleConsumer) + case _ => forEachRemaining((action.accept(_)): DoubleConsumer) + } + } + } trait Spliterator[T] { diff --git a/javalib/src/main/scala/java/util/Spliterators.scala b/javalib/src/main/scala/java/util/Spliterators.scala new file mode 100644 index 0000000000..ccc62da579 --- /dev/null +++ b/javalib/src/main/scala/java/util/Spliterators.scala @@ -0,0 +1,702 @@ +package java.util + +import java.util.function._ + +import Spliterator._ + +/** This is a basic, limit implementation of Spliterators. It is a basis for + * further Scala Native development, especially in the java.util.concurrent + * package. + * + * It is most empathically __NOT__ intended for production use. + * + * The limitations of the this implementation may not be as strong as they + * appear at first blush. Many/most classes which extend Spliterator (no s) + * supply more competent and efficient implementations. + * + * The implementation of methods on Spliterators are, to current knowledge, + * robust. Many of these methods return spliterators. Those spliterators have + * some known limitations and may have others. + * + * Future evolutions should, over time, remove these limitations: + * + * - Their trySplit() methods never split, they always return null. + * + * - spliterators specified by Java as late-binding may not be late-binding. + * + * - spliterators never check for concurrent modification. + * + * - A number of spliterator methods have JVM descriptions of what happens + * after iteration starts and one of certain methods, say, trySplit() is + * called. This implementation may not follow the JVM description. Even in + * Java, it is better to never trySplit() after having begun iterating over + * a spliterator. + * + * Also noted: + * + * - Java documents that spliterators need not be thread-safe. This + * implementation follows that guidance. + */ + +/* Developer Notes on evolving trySplit() + * + * 1) A first evolution could implement trySplit() for Spliterators which + * are backed by arrays. + * + * 2) A second evolution could implement trySplit() for Spliterators which + * are backed by a Collection. Collections are SIZED. That should make + * working with the underlying iterator easier. + * + * 3) Later evolutions can address issues with un-SIZED iterators and + * other deficiencies. + */ + +object Spliterators { + + private final val sizedCharacteristicsMask = + Spliterator.SIZED | Spliterator.SUBSIZED + + private def isMaskSet(characteristics: Int, mask: Int): Boolean = + (characteristics & mask) == mask + + private def maskOff(characteristics: Int, mask: Int): Int = + characteristics & ~mask + + private def maskOn(characteristics: Int, mask: Int): Int = + characteristics | mask + + private def maybeSetSizedCharacteristics(characteristics: Int): Int = { + if (isMaskSet(characteristics, Spliterator.CONCURRENT)) characteristics + else maskOn(characteristics, sizedCharacteristicsMask) + } + + abstract class AbstractDoubleSpliterator( + est: Long, + additionalCharacteristics: Int + ) extends Spliterator.OfDouble { + def characteristics(): Int = additionalCharacteristics + + def estimateSize(): Long = est + + def trySplit(): Spliterator.OfDouble = + null.asInstanceOf[Spliterator.OfDouble] + } + + abstract class AbstractIntSpliterator( + est: Long, + additionalCharacteristics: Int + ) extends Spliterator.OfInt { + def characteristics(): Int = additionalCharacteristics + + def estimateSize(): Long = est + + // BEWARE: non-functional, never splits, always returns null + def trySplit(): Spliterator.OfInt = + null.asInstanceOf[Spliterator.OfInt] + } + + abstract class AbstractLongSpliterator( + est: Long, + additionalCharacteristics: Int + ) extends Spliterator.OfLong { + def characteristics(): Int = additionalCharacteristics + + def estimateSize(): Long = est + + // BEWARE: non-functional, never splits, always returns null + def trySplit(): Spliterator.OfLong = + null.asInstanceOf[Spliterator.OfLong] + } + + abstract class AbstractSpliterator[T]( + est: Long, + additionalCharacteristics: Int + ) extends Spliterator[T] { + def characteristics(): Int = additionalCharacteristics + + def estimateSize(): Long = est + + // BEWARE: non-functional, never splits, always returns null + def trySplit(): Spliterator[T] = null.asInstanceOf[Spliterator[T]] + } + + def emptyDoubleSpliterator(): Spliterator.OfDouble = { + new AbstractDoubleSpliterator(0L, sizedCharacteristicsMask) { + def tryAdvance(action: DoubleConsumer): Boolean = false + } + } + + def emptyIntSpliterator(): Spliterator.OfInt = { + new AbstractIntSpliterator(0L, sizedCharacteristicsMask) { + def tryAdvance(action: IntConsumer): Boolean = false + } + } + + def emptyLongSpliterator(): Spliterator.OfLong = { + new AbstractLongSpliterator(0L, sizedCharacteristicsMask) { + def tryAdvance(action: LongConsumer): Boolean = false + } + } + + def emptySpliterator[T](): Spliterator[T] = { + new AbstractSpliterator[T](0, sizedCharacteristicsMask) { + def tryAdvance(action: Consumer[_ >: T]): Boolean = false + } + } + + def iterator( + spliterator: Spliterator.OfDouble + ): PrimitiveIterator.OfDouble = { + Objects.requireNonNull(spliterator) + + new PrimitiveIterator.OfDouble { + // One element lookahead + var cached: Option[scala.Double] = None + + def hasNext(): Boolean = { + if (cached.nonEmpty) true + else { + spliterator.tryAdvance((e: Double) => (cached = Some(e))) + cached.nonEmpty + } + } + + def nextDouble(): scala.Double = { + if (!hasNext()) { + throw new NoSuchElementException() + } else { + val nxt = cached.get + cached = None + nxt + } + } + } + } + + def iterator(spliterator: Spliterator.OfInt): PrimitiveIterator.OfInt = { + Objects.requireNonNull(spliterator) + + new PrimitiveIterator.OfInt { + // One element lookahead + var cached: Option[scala.Int] = None + + def hasNext(): Boolean = { + if (cached.nonEmpty) true + else { + spliterator.tryAdvance((e: Int) => (cached = Some(e))) + cached.nonEmpty + } + } + + def nextInt(): scala.Int = { + if (!hasNext()) { + throw new NoSuchElementException() + } else { + val nxt = cached.get + cached = None + nxt + } + } + } + } + + def iterator(spliterator: Spliterator.OfLong): PrimitiveIterator.OfLong = { + Objects.requireNonNull(spliterator) + + new PrimitiveIterator.OfLong { + // One element lookahead + var cached: Option[scala.Long] = None + + def hasNext(): Boolean = { + if (cached.nonEmpty) true + else { + spliterator.tryAdvance((e: Long) => (cached = Some(e))) + cached.nonEmpty + } + } + + def nextLong(): scala.Long = { + if (!hasNext()) { + throw new NoSuchElementException() + } else { + val nxt = cached.get + cached = None + nxt + } + } + } + } + + def iterator[T](spliterator: Spliterator[_ <: T]): Iterator[T] = { + Objects.requireNonNull(spliterator) + + new Iterator[T] { + // One element lookahead + var cached: Option[T] = None + + def hasNext(): Boolean = { + if (cached.nonEmpty) true + else { + spliterator.tryAdvance((e: T) => (cached = Some(e))) + cached.nonEmpty + } + } + + def next(): T = { + if (!hasNext()) { + throw new NoSuchElementException() + } else { + val nxt = cached.get + cached = None + nxt + } + } + } + } + + def spliterator[T]( + c: Collection[_ <: T], + characteristics: Int + ): Spliterator[T] = { + Objects.requireNonNull(c) + + val harmonized = maybeSetSizedCharacteristics(characteristics) + new AbstractSpliterator[T](c.size(), harmonized) { + lazy val it = c.iterator() + + override def estimateSize(): Long = c.size() // even if CONCURRENT + + def tryAdvance(action: Consumer[_ >: T]): Boolean = { + Objects.requireNonNull(action) + if (!it.hasNext()) false + else { + action.accept(it.next()) + true + } + } + } + } + + private final class SpliteratorFromArrayDouble( + array: Array[Double], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ) extends Spliterator.OfDouble { + // By contract, array == null & bounds have been checked by caller. + + // current index, modified on traverse/split + private var cursor: Int = fromIndex + + // BEWARE: non-functional, never splits, always returns null + def trySplit(): Spliterator.OfDouble = + null.asInstanceOf[Spliterator.OfDouble] + + def tryAdvance(action: DoubleConsumer): Boolean = { + Objects.requireNonNull(action) + if (cursor == toIndex) false + else { + action.accept(array(cursor)) + cursor += 1 + true + } + } + + def estimateSize(): Long = { toIndex - cursor } + + def characteristics(): Int = + maskOn(additionalCharacteristics, sizedCharacteristicsMask) + } + + def spliterator( + array: Array[Double], + additionalCharacteristics: Int + ): Spliterator.OfDouble = { + Objects.requireNonNull(array) + spliterator(array, 0, array.size, additionalCharacteristics) + } + + private def checkArrayBounds( + arraySize: Int, + fromIndex: Int, + toIndex: Int + ): Unit = { + if (fromIndex < 0) + throw new ArrayIndexOutOfBoundsException(fromIndex) + + if (toIndex < fromIndex) { + throw new ArrayIndexOutOfBoundsException( + s"origin(${toIndex}) > fence(${fromIndex})" + ) + } + + if (toIndex > arraySize) { + throw new ArrayIndexOutOfBoundsException( + s"Array index out of range: ${toIndex}" + ) + } + } + + def spliterator( + array: Array[Double], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ): Spliterator.OfDouble = { + Objects.requireNonNull(array) + checkArrayBounds(array.length, fromIndex, toIndex) + + new SpliteratorFromArrayDouble( + array, + fromIndex, + toIndex, + additionalCharacteristics + ) + } + + private final class SpliteratorFromArrayInt( + array: Array[Int], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ) extends Spliterator.OfInt { + // By contract, array == null & bounds have been checked by caller. + + // current index, modified on traverse/split + private var cursor: Int = fromIndex + + // BEWARE: non-functional, never splits, always returns null + def trySplit(): Spliterator.OfInt = + null.asInstanceOf[Spliterator.OfInt] + + def tryAdvance(action: IntConsumer): Boolean = { + Objects.requireNonNull(action) + if (cursor == toIndex) false + else { + action.accept(array(cursor)) + cursor += 1 + true + } + } + + def estimateSize(): Long = { toIndex - cursor } + + def characteristics(): Int = + maskOn(additionalCharacteristics, sizedCharacteristicsMask) + } + + def spliterator( + array: Array[Int], + additionalCharacteristics: Int + ): Spliterator.OfInt = { + Objects.requireNonNull(array) + spliterator(array, 0, array.size, additionalCharacteristics) + } + + def spliterator( + array: Array[Int], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ): Spliterator.OfInt = { + Objects.requireNonNull(array) + checkArrayBounds(array.length, fromIndex, toIndex) + + new SpliteratorFromArrayInt( + array, + fromIndex, + toIndex, + additionalCharacteristics + ) + } + + def spliterator[T]( + iterator: Iterator[_ <: T], + size: Long, + characteristics: Int + ): Spliterator[T] = { + Objects.requireNonNull(iterator) + val harmonized = maybeSetSizedCharacteristics(characteristics) + new AbstractSpliterator[T](size, harmonized) { + override def estimateSize(): Long = size // always initial size + + def tryAdvance(action: Consumer[_ >: T]): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.next()) + true + } + } + } + } + + private final class SpliteratorFromArrayLong( + array: Array[Long], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ) extends Spliterator.OfLong { + // By contract, array == null & bounds have been checked by caller. + + // current index, modified on traverse/split + private var cursor: Int = fromIndex + + // BEWARE: non-functional, never splits, always returns null + def trySplit(): Spliterator.OfLong = + null.asInstanceOf[Spliterator.OfLong] + + def tryAdvance(action: LongConsumer): Boolean = { + Objects.requireNonNull(action) + if (cursor == toIndex) false + else { + action.accept(array(cursor)) + cursor += 1 + true + } + } + + def estimateSize(): Long = { toIndex - cursor } + + def characteristics(): Int = + maskOn(additionalCharacteristics, sizedCharacteristicsMask) + } + + def spliterator( + array: Array[Long], + additionalCharacteristics: Int + ): Spliterator.OfLong = { + Objects.requireNonNull(array) + spliterator(array, 0, array.size, additionalCharacteristics) + } + + def spliterator( + array: Array[Long], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ): Spliterator.OfLong = { + Objects.requireNonNull(array) + checkArrayBounds(array.length, fromIndex, toIndex) + + new SpliteratorFromArrayLong( + array, + fromIndex, + toIndex, + additionalCharacteristics + ) + } + + private final class SpliteratorFromArrayObject[T]( + array: Array[Object], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ) extends Spliterator[T] { + // By contract, array == null & bounds have been checked by caller. + + // current index, modified on traverse/split + private var cursor: Int = fromIndex + + // BEWARE: non-functional, never splits, always returns null + def trySplit(): Spliterator[T] = null.asInstanceOf[Spliterator[T]] + + def tryAdvance(action: Consumer[_ >: T]): Boolean = { + Objects.requireNonNull(action) + if (cursor == toIndex) false + else { + action.accept(array(cursor).asInstanceOf[T]) + cursor += 1 + true + } + } + + def estimateSize(): Long = { toIndex - cursor } + + def characteristics(): Int = { + additionalCharacteristics | + sizedCharacteristicsMask + } + } + + def spliterator[T]( + array: Array[Object], + additionalCharacteristics: Int + ): Spliterator[T] = { + Objects.requireNonNull(array) + + new SpliteratorFromArrayObject[T]( + array, + 0, + array.size, + additionalCharacteristics + ) + } + + def spliterator[T]( + array: Array[Object], + fromIndex: Int, + toIndex: Int, + additionalCharacteristics: Int + ): Spliterator[T] = { + Objects.requireNonNull(array) + checkArrayBounds(array.length, fromIndex, toIndex) + + new SpliteratorFromArrayObject[T]( + array, + fromIndex, + toIndex, + additionalCharacteristics + ) + } + + def spliterator( + iterator: PrimitiveIterator.OfDouble, + size: Long, + characteristics: Int + ): Spliterator.OfDouble = { + Objects.requireNonNull(iterator) + + val harmonized = maybeSetSizedCharacteristics(characteristics) + new AbstractDoubleSpliterator(size, harmonized) { + override def estimateSize(): Long = size // always initial size + + def tryAdvance(action: DoubleConsumer): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.nextDouble()) + true + } + } + } + } + + def spliterator( + iterator: PrimitiveIterator.OfInt, + size: Long, + characteristics: Int + ): Spliterator.OfInt = { + Objects.requireNonNull(iterator) + + val harmonized = maybeSetSizedCharacteristics(characteristics) + new AbstractIntSpliterator(size, harmonized) { + override def estimateSize(): Long = size // always initial size + + def tryAdvance(action: IntConsumer): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.nextInt()) + true + } + } + } + } + + def spliterator( + iterator: PrimitiveIterator.OfLong, + size: Long, + characteristics: Int + ): Spliterator.OfLong = { + Objects.requireNonNull(iterator) + + val harmonized = maybeSetSizedCharacteristics(characteristics) + new AbstractLongSpliterator(size, harmonized) { + override def estimateSize(): Long = size // always initial size + + def tryAdvance(action: LongConsumer): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.nextLong()) + true + } + } + } + } + + def spliteratorUnknownSize[T]( + iterator: Iterator[_ <: T], + characteristics: Int + ): Spliterator[T] = { + Objects.requireNonNull(iterator) + + new AbstractSpliterator[T]( + Long.MaxValue, + maskOff(characteristics, sizedCharacteristicsMask) + ) { + def tryAdvance(action: Consumer[_ >: T]): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.next()) + true + } + } + } + } + + def spliteratorUnknownSize( + iterator: PrimitiveIterator.OfDouble, + characteristics: Int + ): Spliterator.OfDouble = { + Objects.requireNonNull(iterator) + + new AbstractDoubleSpliterator( + Long.MaxValue, + maskOff(characteristics, sizedCharacteristicsMask) + ) { + def tryAdvance(action: DoubleConsumer): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.nextDouble()) + true + } + } + } + } + + def spliteratorUnknownSize( + iterator: PrimitiveIterator.OfInt, + characteristics: Int + ): Spliterator.OfInt = { + Objects.requireNonNull(iterator) + + new AbstractIntSpliterator( + Long.MaxValue, + maskOff(characteristics, sizedCharacteristicsMask) + ) { + def tryAdvance(action: IntConsumer): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.nextInt()) + true + } + } + } + } + + def spliteratorUnknownSize( + iterator: PrimitiveIterator.OfLong, + characteristics: Int + ): Spliterator.OfLong = { + Objects.requireNonNull(iterator) + + new AbstractLongSpliterator( + Long.MaxValue, + maskOff(characteristics, sizedCharacteristicsMask) + ) { + def tryAdvance(action: LongConsumer): Boolean = { + Objects.requireNonNull(action) + if (!iterator.hasNext()) false + else { + action.accept(iterator.nextLong()) + true + } + } + } + } +} diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala index 0bde204f8b..ab0b1664a9 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala @@ -1855,7 +1855,8 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => case (_: Type.I, _: Type.RefKind) => Some(nir.Conv.Inttoptr) case (Type.I(w1, _), Type.F(w2)) if w1 == w2 => Some(nir.Conv.Bitcast) case (Type.F(w1), Type.I(w2, _)) if w1 == w2 => Some(nir.Conv.Bitcast) - case _ if fromty == toty => Nonecase (Type.Float, Type.Double) => Some(nir.Conv.Fpext) + case _ if fromty == toty => None + case (Type.Float, Type.Double) => Some(nir.Conv.Fpext) case (Type.Double, Type.Float) => Some(nir.Conv.Fptrunc) case _ => unsupported(s"cast from $fromty to $toty") @@ -2474,18 +2475,11 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => val promotedArg = arg.ty match { case Type.Float => this.genCastOp(Type.Float, Type.Double, arg) - case Type.FixedSizeI(width, _) if width < Type.Int.width => + case Type.I(width, _) if width < Type.Int.width => val conv = if (isUnsigned) nir.Conv.Zext else nir.Conv.Sext buf.conv(conv, Type.Int, arg, unwind) - case Type.Long => - // On 32-bit systems Long needs to be truncated to Int - // Cast it to size to make undependent from architecture - val conv = - if (isUnsigned) nir.Conv.ZSizeCast - else nir.Conv.SSizeCast - buf.conv(conv, Type.Size, arg, unwind) case _ => arg } res += promotedArg diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala index 1822a7ff45..426e8b64a2 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala @@ -188,7 +188,7 @@ trait NirGenType[G <: Global with Singleton] { self: NirGenPhase[G] => isExtern: Boolean ): Seq[nir.Type] = { val params = sym.tpe.params - if (!isExtern && !sym.owner.isExternType) + if (!isExtern && !sym.owner.isExternModule) params.map { p => genType(p.tpe) } else { val wereRepeated = exitingPhase(currentRun.typerPhase) { diff --git a/posixlib/src/main/scala/scala/scalanative/posix/unistd.scala b/posixlib/src/main/scala/scala/scalanative/posix/unistd.scala index 8f0d0fca9e..74fdb97523 100644 --- a/posixlib/src/main/scala/scala/scalanative/posix/unistd.scala +++ b/posixlib/src/main/scala/scala/scalanative/posix/unistd.scala @@ -25,7 +25,6 @@ object unistd { var optopt: CInt = extern // Methods/functions - def _exit(status: CInt): Unit = extern def access(pathname: CString, mode: CInt): CInt = extern def chdir(path: CString): CInt = extern def close(fildes: CInt): CInt = extern diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/lang/IterableSpliteratorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/lang/IterableSpliteratorTest.scala new file mode 100644 index 0000000000..ec6ef6d986 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/lang/IterableSpliteratorTest.scala @@ -0,0 +1,50 @@ +package org.scalanative.testsuite.javalib.lang + +import java.util.Spliterator +import java.nio.file.{Path, Paths} + +import org.junit.Test +import org.junit.Assert._ + +import org.scalanative.testsuite.utils.AssertThrows.assertThrows + +class IterableSpliteratorTest { + /* nio.Path extends Iterable and does not override Iterable's default + * spliterator() method. Use that knowledge to test the default + * implementation of Iterable#spliterator. + * + * Do not use a class in the Collection hierarchy because Collection + * overrides the Iterable#spliterator method under test. + */ + @Test def defaultSpliteratorShouldBeWellFormed(): Unit = { + + // Let compiler check type returned is as expected. + val spliter: Spliterator[Path] = Paths.get(".").spliterator() + assertNotNull("Null coll.spliterator", spliter) + + assertEquals("estimateSize", Long.MaxValue, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, // spliterator is known to not have SIZED characteristic + spliter.getExactSizeIfKnown() + ) + + // Default method always reports NO characteristics set. + assertEquals( + "characteristics", + 0, + spliter.characteristics() + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + // Check that both the count is right and that each element is as expected. + + var count = 0 + + spliter.forEachRemaining((p: Path) => count += 1) + + assertEquals("forEachRemaining size", 1, count) + + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/ArraysSpliteratorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/ArraysSpliteratorTest.scala new file mode 100644 index 0000000000..77962f6275 --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/ArraysSpliteratorTest.scala @@ -0,0 +1,492 @@ +package org.scalanative.testsuite.javalib.util + +import java.util.{Arrays, Spliterator} + +import org.junit.Test +import org.junit.Assert._ + +import org.scalanative.testsuite.utils.AssertThrows.assertThrows + +/* Test Arrays.spliterator() methods. They were added for Scala Native after + * the port from Scala.js. + * + * These Tests transitively exercise associated methods from + * Spliterators and Spliterator. + */ + +class ArraysSpliteratorTest { + + // characteristics returned by all javalib Arrays.spliterator() methods. + val stdRequiredPresentCharacteristics = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.ORDERED, + Spliterator.IMMUTABLE + ) + + // guard getComparator() throw + val stdRequiredAbsentCharacteristics = Seq(Spliterator.SORTED) + + @Test def spliteratorOfDoubleFromArray: Unit = { + type T = Double + val expectedElements = Array( + 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 + ) + + val expectedSize = expectedElements.size + + // Let compiler check returned type is as expected. + val spliter: Spliterator.OfDouble = Arrays.spliterator(expectedElements) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the size & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(0), + e, + 0.0001 + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + } + + @Test def spliteratorOfDoubleFromArrayRange: Unit = { + type T = Double + val expectedElements = Array( + 1.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 5 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + // Let compiler check returned type is as expected. + val spliter: Spliterator.OfDouble = Arrays.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex + ) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(sliceStartIndex), + e, + 0.0001 + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e, + 0.0001 + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSliceSize, count) + } + + @Test def spliteratorOfIntFromArray: Unit = { + type T = Int + val expectedElements = Array( + 0, 1, 2, 3, 4, 5, 6 + ) + + val expectedSize = expectedElements.size + + // Let compiler check returned type is as expected. + val spliter: Spliterator.OfInt = Arrays.spliterator(expectedElements) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the size & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(0), + e + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + } + + @Test def spliteratorOfIntFromArrayRange: Unit = { + type T = Int + val expectedElements = Array( + 1, 11, 22, 33, 44, 55, 66 + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 5 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + // Let compiler check returned type is as expected. + val spliter: Spliterator.OfInt = Arrays.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex + ) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(sliceStartIndex), + e + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSliceSize, count) + } + + @Test def spliteratorOfLongFromArray: Unit = { + type T = Long + val expectedElements = Array( + 0L, 1L, 2L, 3L, 4L, 5L, 6L + ) + + val expectedSize = expectedElements.size + + // Let compiler check returned type is as expected. + val spliter: Spliterator.OfLong = Arrays.spliterator(expectedElements) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the size & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(0), + e + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + } + + @Test def spliteratorOfLongFromArrayRange: Unit = { + type T = Long + val expectedElements = Array( + 0, 11L, 22L, 33L, 44L, 55L, 66L + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 5 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + // Let compiler check returned type is as expected. + val spliter: Spliterator.OfLong = Arrays.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex + ) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(sliceStartIndex), + e + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSliceSize, count) + } + + @Test def spliteratorOfTypeFromArray: Unit = { + type T = String + val expectedElements = Array( + "Bertha von Suttner", + "Jane Addams", + "Emily Greene Balch", + "Betty Williams", + "Mairead Corrigan", + "Alva Myrdal" + ) + + val expectedSize = expectedElements.size + + // Let compiler check returned type is as expected. + val spliter: Spliterator[T] = Arrays.spliterator(expectedElements) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the size & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(0), + e + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + } + + @Test def spliteratorOfTypeFromArrayRange: Unit = { + type T = String + val expectedElements = Array( + "nul'", + "odin", + "dva", + "tri", + "cotiri", + "p'at", + "sist'" + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 5 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + // Let compiler check returned type is as expected. + val spliter: Spliterator[T] = Arrays.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex + ) + assertNotNull("Null array.spliterator", spliter) + + // check that spliterator has required characteristics and no others. + SpliteratorsTest.verifyCharacteristics( + spliter, + stdRequiredPresentCharacteristics, + stdRequiredAbsentCharacteristics + ) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + assertTrue( + "tryAdvance itself failed", + spliter.tryAdvance((e: T) => + assertEquals( + "tryAdvance contents do not match,", + expectedElements(sliceStartIndex), + e + ) + ) + ) + + var count = 1 + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSliceSize, count) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionDefaultSpliteratorTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionDefaultSpliteratorTest.scala new file mode 100644 index 0000000000..1599b34e1e --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionDefaultSpliteratorTest.scala @@ -0,0 +1,70 @@ +package org.scalanative.testsuite.javalib.util + +import java.util.Spliterator + +import org.junit.Test +import org.junit.Assert._ + +import org.scalanative.testsuite.utils.AssertThrows.assertThrows + +class CollectionDefaultSpliteratorTest { + @Test def defaultSpliteratorShouldBeWellFormed(): Unit = { + val expectedElements = Array( + "Aiopis", + "Antheia", + "Donakis", + "Calypso", + "Mermesa", + "Nelisa", + "Tara" + ) + + val expectedSize = expectedElements.size + val foundElements = new Array[String](expectedSize) + + val coll = TrivialImmutableCollection(expectedElements: _*) + assertEquals(expectedSize, coll.size()) + + val spliter = coll.spliterator() + assertNotNull("Null coll.spliterator", spliter) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + val required = Spliterator.SIZED | Spliterator.SUBSIZED + + assertEquals( + "characteristics", + required, + spliter.characteristics() & required + ) + + assertTrue("hasCharacteristics", spliter.hasCharacteristics(required)) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + // Check that both the count is right and that each element is as expected. + + var count = 0 + + // forEachRemaining() exercises tryAdvance() internally. + spliter.forEachRemaining((str: String) => { + foundElements(count) = str + count += 1 + }) + + assertEquals("forEachRemaining size", expectedSize, count) + + // Are contents equal? + for (j <- 0 until expectedSize) + assertEquals( + s"forEachRemaining contents(${j})", + expectedElements(j), + foundElements(j) + ) + } +} diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionTest.scala index e3f4463501..5ae621bc88 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/CollectionTest.scala @@ -1,9 +1,10 @@ // Ported from Scala.js commit: f9fc1ae dated: 2020-03-06 +// Spliterator test added to celebrate Scala Native multithreading. package org.scalanative.testsuite.javalib.util import java.{util => ju, lang => jl} - +import java.util.Spliterator import org.junit.Test import org.junit.Assert._ @@ -15,6 +16,18 @@ import scala.reflect.ClassTag import org.scalanative.testsuite.utils.AssertThrows.assertThrows import Utils._ +/* Design Note: + * Note the "trait" keyword and not the "class" keyword. That + * means that CollectionTest does not run by itself. It is only run + * when other tests which extend this trait are run. + * "sbt tests3/testOnly *.CollectionTest" will fail. If you are lucky + * it will take only a few days of your life to understand the failure. + * If you are even luckier, you will remember the cause when you + * encounter the quirk six months or a year later. + * + * "sbt tests3/testOnly *.AbstractCollectionTest", for one, should work. + */ + trait CollectionTest extends IterableTest { def factory: CollectionFactory @@ -284,6 +297,30 @@ trait CollectionTest extends IterableTest { ) } + @Test def spliteratorShouldExist(): Unit = { + /* CollectionTest is a trait, which get mixed into the tests for + * several Collections. Spliterators() tend to be tailored to the + * individual collection: the whole reason for overriding the default + * implementation. + * + * Trying to account here for some Collections using the default + * Collection.spliterator() and some overriding it quickly leads to + * a tangled mess. + * + * CollectionDefaultSpliteratorTest.scala exercises the default + * Collection.spliterator() method using a collection know to use + * that implementation. Because it is a separate test (and a "class"), + * it is called once, in a known environment. + */ + val coll = + factory.fromElements[String]("Aegle", "Arethusa", "Hesperethusa") + + val expectedSize = 3 + assertEquals(expectedSize, coll.size()) + + val spliter = coll.spliterator() + assertNotNull("Null coll.spliterator", spliter) + } } trait CollectionFactory extends IterableFactory { diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/SpliteratorsTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/SpliteratorsTest.scala new file mode 100644 index 0000000000..5f81072d3d --- /dev/null +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/SpliteratorsTest.scala @@ -0,0 +1,1571 @@ +package org.scalanative.testsuite.javalib.util + +import java.util.{PrimitiveIterator, Spliterator, Spliterators} + +import org.junit.Test +import org.junit.Assert._ + +import org.scalanative.testsuite.utils.AssertThrows.assertThrows + +/* The vigilant observer will note that there is a SpliteratorsTest, with + * and 's', but no independent SpliteratorTest, without an 's'. This is + * because the generation (Spliterators) and verification (Spliterator) + * of a spliterator() closely depend on the specified conditions in + * each other: Tests are best kept close together, in time & space. + */ + +/* Java 8 introduced a number of ways of obtaining a spliterator. + * This file tests the static methods of Spliterator. It passes + * Arrays & Collections to Spliterator methods rather than calling + * the spliterator method of the Array or Collection. + * + * To get a proper overview, it is worth knowing that other files test + * other ways of obtaining spliterators. A partial list: + * javalib.lang.IterableSpliteratorTest + * javalib.util.ArraysSpliteratorTest + * javalib.util.CollectionDefaultSpliteratorTest + * + * Classes which override the default methods above may/should have + * their own Tests. + */ + +object SpliteratorsTest { + private val maskNames = Map( + 0x00000001 -> "DISTINCT", + 0x00000004 -> "SORTED", + 0x00000010 -> "ORDERED", + 0x00000040 -> "SIZED", + 0x00000100 -> "NONNULL", + 0x00000400 -> "IMMUTABLE", + 0x00001000 -> "CONCURRENT", + 0x00004000 -> "SUBSIZED" + ) + + // convert characteristics bit mask to its corresponding name, or else hex. + private def maskToName(mask: Int): String = + maskNames.getOrElse(mask, s"0x${mask.toHexString.toUpperCase}") + + def verifyCharacteristics[T]( + splItr: Spliterator[T], + requiredPresent: Seq[Int], + requiredAbsent: Seq[Int] + ): Unit = { + /* The splItr.hasCharacteristics() and splItr.characteristics() + * sections both seek the same information: Does the spliterator report + * the required characteristics and no other. They ask the question + * in slightly different ways to exercise each of the two Spliterator + * methods. The answers should match, belt & suspenders. + */ + + for (rp <- requiredPresent) { + assertTrue( + s"missing requiredPresent characteristic: ${maskToName(rp)}", + splItr.hasCharacteristics(rp) + ) + } + + for (rp <- requiredAbsent) { + assertFalse( + s"found requiredAbsent characteristic: ${maskToName(rp)}", + splItr.hasCharacteristics(rp) + ) + } + + val sc = splItr.characteristics() + val requiredPresentMask = requiredPresent.fold(0)((x, y) => x | y) + + val unknownBits = sc & ~requiredPresentMask + val unknownBitsMsg = s"0X${unknownBits.toHexString}" + assertEquals( + s"unexpected characteristics, unknown mask: ${unknownBitsMsg}", + 0, + unknownBits + ) + } +} + +class SpliteratorsTest { + import SpliteratorsTest._ + + @Test def emptyDoubleSpliterator(): Unit = { + type T = Double + val expectedSize = 0 + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfDouble = Spliterators.emptyDoubleSpliterator() + assertNotNull("Null coll.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + assertFalse("tryAdvance", spliter.tryAdvance((_: T) => ())) + + var count = 0 + spliter.forEachRemaining((_: T) => { count += 1 }) + assertEquals("forEachRemaining size", expectedSize, count) + } + + @Test def emptyIntSpliterator(): Unit = { + type T = Int + val expectedSize = 0 + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfInt = Spliterators.emptyIntSpliterator() + assertNotNull("Null coll.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + assertFalse("tryAdvance", spliter.tryAdvance((_: T) => ())) + + var count = 0 + spliter.forEachRemaining((_: T) => { count += 1 }) + assertEquals("forEachRemaining size", expectedSize, count) + } + + @Test def emptyLongSpliterator(): Unit = { + type T = Long + val expectedSize = 0 + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfLong = Spliterators.emptyLongSpliterator() + assertNotNull("Null coll.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + assertFalse("tryAdvance", spliter.tryAdvance((_: T) => ())) + + var count = 0 + spliter.forEachRemaining((_: T) => { count += 1 }) + assertEquals("forEachRemaining size", expectedSize, count) + } + + @Test def emptySpliterator(): Unit = { + type T = String + val expectedSize = 0 + + // Let compiler check type returned is expected. + val spliter: Spliterator[T] = Spliterators.emptySpliterator[T]() + assertNotNull("Null coll.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + assertFalse("tryAdvance", spliter.tryAdvance((_: T) => ())) + + var count = 0 + spliter.forEachRemaining((_: T) => { count += 1 }) + assertEquals("forEachRemaining size", expectedSize, count) + } + + @Test def primitiveIteratorFromSpliteratorDouble: Unit = { + val expectedElements = Array( + 0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6 + ) + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfDouble = Spliterators.spliterator( + expectedElements, + Spliterator.SIZED | Spliterator.SUBSIZED + ) + assertNotNull("Null array.spliterator", spliter) + + // Check that iterator() call returns expected type. + val pItrDouble: PrimitiveIterator.OfDouble = Spliterators.iterator(spliter) + assertNotNull("Null PrimitiveIterator.OfDouble", pItrDouble) + + // Now verify the PrimitiveIterator.OfDouble + + assertTrue( + s"unexpected empty PrimitiveIterator", + pItrDouble.hasNext() + ) + + var count = 0 + pItrDouble.forEachRemaining((e: Double) => { + assertEquals(s"failed match", expectedElements(count), e, 0.0001) + count += 1 + }) + } + + @Test def primitiveIteratorFromSpliteratorInt: Unit = { + val expectedElements = Array( + 0, 1, 2, 3, 4, 5, 6 + ) + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfInt = Spliterators.spliterator( + expectedElements, + Spliterator.SIZED | Spliterator.SUBSIZED + ) + assertNotNull("Null array.spliterator", spliter) + + // Check that iterator() call returns expected type. + val pItrInt: PrimitiveIterator.OfInt = Spliterators.iterator(spliter) + assertNotNull("Null PrimitiveIterator.OfInt", pItrInt) + + // Now verify the PrimitiveIterator.OfInt + + assertTrue( + s"unexpected empty PrimitiveIterator", + pItrInt.hasNext() + ) + + var count = 0 + pItrInt.forEachRemaining((e: Int) => { + assertEquals(s"failed match", expectedElements(count), e) + count += 1 + }) + } + + @Test def primitiveIteratorFromSpliteratorLong: Unit = { + val expectedElements = Array(0, 11L, 22L, 33L, 44L, 55L, 66L) + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfLong = Spliterators.spliterator( + expectedElements, + Spliterator.SIZED | Spliterator.SUBSIZED + ) + assertNotNull("Null array.spliterator", spliter) + + // Check that iterator() call returns expected type. + val pItrLong: PrimitiveIterator.OfLong = Spliterators.iterator(spliter) + assertNotNull("Null PrimitiveIterator.OfLong", pItrLong) + + // Now verify the PrimitiveIterator.OfLong + + assertTrue( + s"unexpected empty PrimitiveIterator", + pItrLong.hasNext() + ) + + var count = 0 + pItrLong.forEachRemaining((e: Long) => { + assertEquals(s"failed match", expectedElements(count), e) + count += 1 + }) + } + + @Test def iteratorFromSpliteratorType: Unit = { + type T = String + val expectedElements = Array( + "lliu", + "hwi", + "kre", + "sei", + "mne", + "rhi", + "fve" + ) + + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator[T] = Spliterators.spliterator( + expectedElements.asInstanceOf[Array[Object]], + Spliterator.SIZED | Spliterator.SUBSIZED + ) + assertNotNull("Null array.spliterator", spliter) + + // Check that iterator() call returns expected type. + val itr: java.util.Iterator[T] = Spliterators.iterator(spliter) + assertNotNull("Null Iterator", itr) + + // Now verify the Iterator + + assertTrue( + s"unexpected empty Iterator", + itr.hasNext() + ) + + var count = 0 + itr.forEachRemaining((e: T) => { + assertEquals(s"failed match", expectedElements(count), e) + count += 1 + }) + } + + @Test def spliteratorOfTypeFromCollection: Unit = { + type T = String + val expectedElements = Array( + "Bertha von Suttner", + "Jane Addams", + "Emily Greene Balch", + "Betty Williams", + "Mairead Corrigan", + "Alva Myrdal" + ) + + val expectedSize = expectedElements.size + + val coll = TrivialImmutableCollection(expectedElements: _*) + assertEquals(expectedSize, coll.size()) + + // Example values used at the time of this writing by ArrayBlockingQueue + val requiredPresent = Seq( + Spliterator.ORDERED, + Spliterator.NONNULL, + Spliterator.CONCURRENT + ) + val requiredPresentMask = requiredPresent.fold(0)((x, y) => x | y) + + // Since CONCURRENT is given in requiredPresent, SIZED and SUBSIZED + // should not be turned on by constructor. + val requiredAbsent = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val spliter: Spliterator[T] = + Spliterators.spliterator(coll, requiredPresentMask) + assertNotNull("Null coll.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, // Because CONCURRENT, exact size is not known. + spliter.getExactSizeIfKnown() + ) + + // Check that both count & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize, // on JVM estimateSize always returns initial expectedSize + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining size", expectedSize, count) + // on JVM estimateSize always returns initial expectedSize + assertEquals( + "forEachRemaining estimateSize", + expectedSize, + spliter.estimateSize() + ) + } + + @Test def spliteratorOfDoubleFromArray: Unit = { + type T = Double + val expectedElements = Array( + 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 + ) + + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfDouble = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorOfDoubleFromArrayRange: Unit = { + type T = Double + val expectedElements = Array( + 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 5 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfDouble = Spliterators.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(sliceStartIndex + count), + e, + 0.0001 + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSliceSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e, + 0.0001 + ) + count += 1 + }) + assertEquals("forEachRemaining cursor", expectedSliceSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorOfIntFromArray: Unit = { + type T = Int + val expectedElements = Array( + 0, 1, 2, 3, 4, 5, 6 + ) + + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfInt = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorOfIntFromArrayRange: Unit = { + type T = Int + val expectedElements = Array( + 1, 11, 22, 33, 44, 55, 66 + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 4 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + val coll = TrivialImmutableCollection(expectedElements: _*) + assertEquals(expectedElements.size, coll.size()) + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfInt = Spliterators.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSliceSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining cursor", expectedSliceSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorFromIteratorType: Unit = { + type T = String + val expectedElements = Array( + "Arctic", + "North Atlantic", + "South Atlantic", + "Indian", + "North Pacific", + "South Pacific", + "Antarctic" + ) + + val expectedSize = expectedElements.size + + val coll = TrivialImmutableCollection(expectedElements: _*) + assertEquals(expectedSize, coll.size()) + + /* Test only the "astonishing" case, estimatedSize always return the + * initial size. No need to test CONCURRENT and SIZED separately. + */ + val requiredPresent = Seq(Spliterator.CONCURRENT) + val requiredPresentMask = requiredPresent.fold(0)((x, y) => x | y) + + // Since CONCURRENT specified as required, SIZED and SUBSIZED should be off. + val requiredAbsent = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.SORTED // guard getComparator() throw + ) + + /* Create spliterator specifying SIZED and SUBSIZED then check + * that the spliterator always reports them as absent, as documented. + */ + // Let compiler check type returned is expected. + val spliter: Spliterator[T] = Spliterators.spliterator( + coll.iterator, + expectedSize, + requiredPresentMask + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, + spliter.getExactSizeIfKnown() + ) + + // Check that both the count & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize, // on JVM estimateSize always returns initial expectedSize + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedSize, count) + // on JVM estimateSize always returns initial expectedSize + assertEquals( + "forEachRemaining estimateSize", + expectedSize, + spliter.estimateSize() + ) + } + + @Test def spliteratorOfLongFromArray: Unit = { + type T = Long + val expectedElements = Array( + 0L, 1L, 2L, 3L, 4L, 5L, 6L + ) + + val expectedSize = expectedElements.size + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfLong = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining count", expectedSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorOfLongFromArrayRange: Unit = { + type T = Long + val expectedElements = Array( + 1L, 11L, 22L, 33L, 44L, 55L, 66L + ) + + val sliceStartIndex = 1 + val sliceEndIndex = 4 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfLong = Spliterators.spliterator( + expectedElements, + sliceStartIndex, + sliceEndIndex, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq(Spliterator.SORTED) // guard getComparator() throw + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSliceSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining cursor", expectedSliceSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorOfTypeFromArrayRange: Unit = { + type T = String + val expectedElements = Array( + "nul'", + "odin", + "dva", + "tri", + "cotiri", + "p'at", + "sist'" + ) + + val sliceStartIndex = 2 + val sliceEndIndex = 6 + val expectedSliceSize = sliceEndIndex - sliceStartIndex + + /* InitialPresent are the values used, at the time of this writing, + * by LinkedBlockingQueue. + * + * The spliterator-under-test is expected to always supply SIZED and + * SUBSIZED. Current implementation does not automatically add the + * "possibly more", from the documentation. If that ever changes, + * this test will begin to fail (unexpected bits set). + * + * Yes, having CONCURRENT and SIZED both set is unusual. Done here + * just to test wierd corner cases that are _bound_ to happen in the wild. + */ + + val initialPresent = Seq( + Spliterator.ORDERED, + Spliterator.NONNULL, + Spliterator.CONCURRENT + ) + val initialPresentMask = initialPresent.fold(0)((x, y) => x | y) + + val requiredPresent = initialPresent ++ + Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + + val requiredAbsent = Seq.empty[Int] + + // Let compiler check type returned is expected. + val spliter: Spliterator[T] = Spliterators.spliterator( + expectedElements.asInstanceOf[Array[AnyRef]], + sliceStartIndex, + sliceEndIndex, + initialPresentMask + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSliceSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSliceSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSliceSize - 1, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(sliceStartIndex + count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining cursor", expectedSliceSize, count) + assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) + } + + @Test def spliteratorFromPrimitiveIteratorOfDouble: Unit = { + type T = Double + val expectedElements = Array( + 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 + ) + val expectedSize = expectedElements.size + + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq( + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val siOfDouble: Spliterator.OfDouble = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", siOfDouble) + + // + // Let compiler check type returned is expected. + val piOfDouble: PrimitiveIterator.OfDouble = + Spliterators.iterator(siOfDouble) + assertNotNull("Null array.spliterator", piOfDouble) + + /* Create spliterator with characteristics of 0, then check + * that the spliterator always reports SIZED and SUBSIZED, unless + * CONCURRENT, as documented. + * + * Someday have a similar Test specifying CONCURRENT. + */ + // Let compiler check type returned is expected. + val spliter: Spliterator.OfDouble = Spliterators.spliterator( + piOfDouble, + expectedSize, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize, // on JVM estimateSize always returns initial expectedSize + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + // on JVM estimateSize always returns initial expectedSize + assertEquals( + "forEachRemaining estimateSize", + expectedSize, + spliter.estimateSize() + ) + } + + @Test def spliteratorFromPrimitiveIteratorOfInt: Unit = { + type T = Int + val expectedElements = Array( + 0, 1, 2, 3, 4, 5, 6 + ) + val expectedSize = expectedElements.size + + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq( + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val siOfInt: Spliterator.OfInt = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", siOfInt) + + // Let compiler check type returned is expected. + val piOfInt: PrimitiveIterator.OfInt = Spliterators.iterator(siOfInt) + assertNotNull("Null array.spliterator", piOfInt) + + /* Create spliterator with characteristics of 0, then check + * that the spliterator always reports SIZED and SUBSIZED, unless + * CONCURRENT, as documented. + * + * Someday have a similar Test specifying CONCURRENT. + */ + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfInt = Spliterators.spliterator( + piOfInt, + expectedSize, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize, // on JVM estimateSize always returns initial expectedSize + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + // on JVM estimateSize always returns initial expectedSize + assertEquals( + "forEachRemaining estimateSize", + expectedSize, + spliter.estimateSize() + ) + } + + @Test def spliteratorFromPrimitiveIteratorOfLong: Unit = { + type T = Long + val expectedElements = Array( + 0L, 1L, 2L, 3L, 4L, 5L, 6L + ) + val expectedSize = expectedElements.size + + val requiredPresent = Seq(Spliterator.SIZED, Spliterator.SUBSIZED) + val requiredAbsent = Seq( + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val siOfLong: Spliterator.OfLong = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", siOfLong) + + // Let compiler check type returned is expected. + val piOfLong: PrimitiveIterator.OfLong = Spliterators.iterator(siOfLong) + assertNotNull("Null array.spliterator", piOfLong) + + /* Create spliterator with characteristics of 0, then check + * that the spliterator always reports SIZED and SUBSIZED, unless + * CONCURRENT, as documented. + * + * Someday have a similar Test specifying CONCURRENT. + */ + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfLong = Spliterators.spliterator( + piOfLong, + expectedSize, + 0 + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", expectedSize, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + expectedSize, + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + expectedSize, // on JVM estimateSize always returns initial expectedSize + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + // on JVM estimateSize always returns initial expectedSize + assertEquals( + "forEachRemaining estimateSize", + expectedSize, + spliter.estimateSize() + ) + } + + @Test def spliteratorUnknownSizeFromIteratorType: Unit = { + type T = String + val expectedElements = Array( + "pride", + "greed", + "wrath", + "envy", + "lust", + "gluttony", + "sloth" + ) + + val coll = TrivialImmutableCollection(expectedElements: _*) + assertEquals(expectedElements.size, coll.size()) + + val requiredPresent = Seq.empty[Int] + val requiredAbsent = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.SORTED // guard getComparator() throw + ) + + /* Create spliterator specifying SIZED and SUBSIZED then check + * that the spliterator always reports them as absent, as documented. + */ + + // Let compiler check type returned is expected. + val spliter: Spliterator[T] = Spliterators.spliteratorUnknownSize( + coll.iterator, + requiredAbsent.take(2).fold(0)((x, y) => x | y) + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", Long.MaxValue, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, // By definition, size is Unknown. + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + assertEquals( + "forEachRemaining estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + } +// --- + @Test def spliteratorUnknownSizeFromPrimitiveIteratorOfDouble: Unit = { + type T = Double + val expectedElements = Array( + 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 + ) + + val requiredPresent = Seq.empty[Int] + val requiredAbsent = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val siOfDouble: Spliterator.OfDouble = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", siOfDouble) + + // Let compiler check type returned is expected. + val piOfDouble: PrimitiveIterator.OfDouble = + Spliterators.iterator(siOfDouble) + assertNotNull("Null array.spliterator", piOfDouble) + + /* Create spliterator specifying SIZED and SUBSIZED then check + * that the spliterator always reports them as absent, as documented. + */ + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfDouble = Spliterators.spliteratorUnknownSize( + piOfDouble, + requiredAbsent.take(2).fold(0)((x, y) => x | y) + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", Long.MaxValue, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, // By definition, size is Unknown. + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e, + 0.0001 + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + assertEquals( + "forEachRemaining estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + } + + @Test def spliteratorUnknownSizeFromPrimitiveIteratorOfInt: Unit = { + type T = Int + val expectedElements = Array( + 0, 1, 2, 3, 4, 5, 6 + ) + + val requiredPresent = Seq.empty[Int] + val requiredAbsent = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val siOfInt: Spliterator.OfInt = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", siOfInt) + + // Let compiler check type returned is expected. + val piOfInt: PrimitiveIterator.OfInt = Spliterators.iterator(siOfInt) + assertNotNull("Null array.spliterator", piOfInt) + + /* Create spliterator specifying SIZED and SUBSIZED then check + * that the spliterator always reports them as absent, as documented. + */ + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfInt = Spliterators.spliteratorUnknownSize( + piOfInt, + requiredAbsent.take(2).fold(0)((x, y) => x | y) + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", Long.MaxValue, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, // By definition, size is Unknown. + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + assertEquals( + "forEachRemaining estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + } + + @Test def spliteratorUnknownSizeFromPrimitiveIteratorOfLong: Unit = { + type T = Long + val expectedElements = Array( + 0L, 1L, 2L, 3L, 4L, 5L, 6L + ) + + val requiredPresent = Seq.empty[Int] + val requiredAbsent = Seq( + Spliterator.SIZED, + Spliterator.SUBSIZED, + Spliterator.SORTED // guard getComparator() throw + ) + + // Let compiler check type returned is expected. + val siOfLong: Spliterator.OfLong = Spliterators.spliterator( + expectedElements, + 0 + ) + assertNotNull("Null array.spliterator", siOfLong) + + // Let compiler check type returned is expected. + val piOfLong: PrimitiveIterator.OfLong = + Spliterators.iterator(siOfLong) + assertNotNull("Null array.spliterator", piOfLong) + + /* Create spliterator specifying SIZED and SUBSIZED then check + * that the spliterator always reports them as absent, as documented. + */ + + // Let compiler check type returned is expected. + val spliter: Spliterator.OfLong = Spliterators.spliteratorUnknownSize( + piOfLong, + requiredAbsent.take(2).fold(0)((x, y) => x | y) + ) + assertNotNull("Null array.spliterator", spliter) + + // spliterator should have required characteristics and no others. + verifyCharacteristics(spliter, requiredPresent, requiredAbsent) + + assertThrows(classOf[IllegalStateException], spliter.getComparator()) + + assertEquals("estimateSize", Long.MaxValue, spliter.estimateSize()) + assertEquals( + "getExactSizeIfKnown", + -1, // By definition, size is Unknown. + spliter.getExactSizeIfKnown() + ) + + // Check that both the end index & each element seen are as expected. + + var count = 0 + + spliter.tryAdvance((e: T) => { + assertEquals( + s"tryAdvance contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + + assertEquals( + "tryAdvance estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + + spliter.forEachRemaining((e: T) => { + assertEquals( + s"forEachRemaining contents(${count})", + expectedElements(count), + e + ) + count += 1 + }) + assertEquals("forEachRemaining", expectedElements.size, count) + assertEquals( + "forEachRemaining estimateSize", + Long.MaxValue, + spliter.estimateSize() + ) + } +} From 82dfa37d8e855cca4fa0db37377623ccbe8f0a95 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Fri, 10 Mar 2023 16:35:20 +0100 Subject: [PATCH 37/61] Fix poisnonous new lines escapes in nir.Show (#3208) Our previous logic for escaping new lines in string literal for textual NIR and LLVM IR representation was crashing due to unescaped '$' character, which after inserting in the escaped string was leading to an invalid regex expression --- nir/src/main/scala/scala/scalanative/nir/Show.scala | 5 +++-- .../native/src/test/scala/scala/scalanative/IssuesTest.scala | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/nir/src/main/scala/scala/scalanative/nir/Show.scala b/nir/src/main/scala/scala/scalanative/nir/Show.scala index 915404a88b..afabf43184 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Show.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Show.scala @@ -660,8 +660,9 @@ object Show { """([^\\]|^)\n""".r.replaceAllIn( s, _.matched.toSeq match { - case Seq(sngl) => s"""\\\\n""" - case Seq(fst, snd) => s"""${fst}\\\\n""" + case Seq(sngl) => raw"\\n" + case Seq('$', snd) => raw"\$$\\n" + case Seq(fst, snd) => raw"\${fst}\\n" } ) diff --git a/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala b/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala index 7609184415..1ceb9caab8 100644 --- a/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala +++ b/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala @@ -590,6 +590,10 @@ class IssuesTest { } assertEquals(Foo.fooLiteral, Foo.fooLazy) } + @Test def i3147(): Unit = { + // It's not a runtime, but linktime bug related to nir.Show new lines escapes for string literals + println("$\n") + } @Test def i3195(): Unit = { // Make sure that inlined calls are resetting the stack upon returning From aa414a3367f8cf280336564f29353da2674007dc Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Fri, 10 Mar 2023 17:28:41 +0100 Subject: [PATCH 38/61] Fix issues found in extended CI runs (#3213) * Fix compilation warning in StackTrace.c on MacOS --- .../main/resources/scala-native/gc/immix_commix/StackTrace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c b/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c index 8ff68c8158..a03f11fa7e 100644 --- a/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c +++ b/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c @@ -10,7 +10,7 @@ void StackTrace_PrintStackTrace() { scalanative_unwind_init_local(cursor, context); while (scalanative_unwind_step(cursor) > 0) { - uint64_t offset, pc; + size_t offset, pc; scalanative_unwind_get_reg(cursor, scalanative_unw_reg_ip(), &pc); if (pc == 0) { break; From ebeed98935e6ff18352d1b4074fc65612445d75e Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Mon, 13 Mar 2023 06:32:06 -0400 Subject: [PATCH 39/61] Replace `Spliterator.trySplit()` dummies with working implementation (#3218) * Spliterators trySplit() methods now split * Add three new classes to javalib doc --- docs/lib/javalib.rst | 3 + .../main/scala/java/util/Spliterators.scala | 331 +++++++++++++++--- .../javalib/util/SpliteratorsTest.scala | 42 +-- 3 files changed, 308 insertions(+), 68 deletions(-) diff --git a/docs/lib/javalib.rst b/docs/lib/javalib.rst index f804049147..dbb197b2f4 100644 --- a/docs/lib/javalib.rst +++ b/docs/lib/javalib.rst @@ -423,6 +423,7 @@ java.util * ``NoSuchElementException`` * ``Objects`` * ``Optional`` +* ``PrimitiveIterator`` * ``PriorityQueue`` * ``Properties`` * ``Queue`` @@ -434,6 +435,8 @@ java.util * ``SizeChangeEvent`` * ``SortedMap`` * ``SortedSet`` +* ``Spliterator`` +* ``Spliterators`` * ``StringTokenizer`` * ``TooManyListenersException`` * ``TreeSet`` diff --git a/javalib/src/main/scala/java/util/Spliterators.scala b/javalib/src/main/scala/java/util/Spliterators.scala index ccc62da579..cc5967e4b8 100644 --- a/javalib/src/main/scala/java/util/Spliterators.scala +++ b/javalib/src/main/scala/java/util/Spliterators.scala @@ -20,8 +20,6 @@ import Spliterator._ * * Future evolutions should, over time, remove these limitations: * - * - Their trySplit() methods never split, they always return null. - * * - spliterators specified by Java as late-binding may not be late-binding. * * - spliterators never check for concurrent modification. @@ -29,8 +27,8 @@ import Spliterator._ * - A number of spliterator methods have JVM descriptions of what happens * after iteration starts and one of certain methods, say, trySplit() is * called. This implementation may not follow the JVM description. Even in - * Java, it is better to never trySplit() after having begun iterating over - * a spliterator. + * Java, it is better to never trySplit() after having begun using a + * spliterator to iterate. * * Also noted: * @@ -38,17 +36,20 @@ import Spliterator._ * implementation follows that guidance. */ -/* Developer Notes on evolving trySplit() +/* Developer Notes on evolving Spliterators * - * 1) A first evolution could implement trySplit() for Spliterators which - * are backed by arrays. + * 1) The limitations listed above should be corrected, or at least relaxed. * - * 2) A second evolution could implement trySplit() for Spliterators which - * are backed by a Collection. Collections are SIZED. That should make - * working with the underlying iterator easier. + * 2) Performance, especially with spliterators which have a large, + * say million or US billion elements, should be measured. That + * will probably show that both execution time and memory usage + * need to be reduced. * - * 3) Later evolutions can address issues with un-SIZED iterators and - * other deficiencies. + * For example, an individual development-only Test + * in SpliteratorsTrySplitTest showed an an un-optimized Scala Native + * executable having results matching the same Test on JVM but taking + * approximately 50% longer (a minute or so), possibly due to swapping + * caused by higher memory usage. */ object Spliterators { @@ -70,54 +71,261 @@ object Spliterators { else maskOn(characteristics, sizedCharacteristicsMask) } + /* This implementation of trySplit() is reverse engineered from the + * default JVM algorithm for Iterable and Collection, without having + * looked at the JVM code. + * + * It allows unit-tests to run in either JVM or Scala Native with the + * matching results. + * + * The JVM algorithm switches from a first "count-them-out" iteration + * algorithm to a reasonably efficient array based "bisection" algorithm. + * + * As advised by the Java documentation authors, sub-classes may benefit + * from overriding this implementation with a more efficient one. + * + * Case in Point, JSR-166 implementations, which can be examined, tend to + * use a different algorithm for batch sizing. + */ + + private final val ABSTRACT_TRYSPLIT_BATCH_SIZE = 1024 + + private def getTrySplitBatchSize(multiplier: Long): Int = { + /* To be discovered: + * JVM may have a lower maximum batch size. + * + * JSR-166 LinkedBlockingQueue.scala specifies a MAX_BATCH of + * 1 << 25 (33_554_432), well less than Integer.MAX_VALUE. + */ + val computedSize = multiplier * ABSTRACT_TRYSPLIT_BATCH_SIZE + Math.min(computedSize, Integer.MAX_VALUE).toInt + } + + private def trySplitUsageRatioOK(used: Long, total: Long): Boolean = { + /* This method concentrates the decision of whether trySplit() should take + * the faster and easier route of passing its work buffer directly to + * Spliterators or if it should reduce it to an exact size by copying. + * + * The issue is that the size of the allocated buffer grows after + * repeated splits on the same spliterator. If a buffer is filled, + * there is no need to copy. The opposite is also clear, if there is + * one byte in a megabyte buffer, it makes sense to pay the Array allocation + * and copy in order to free up the unused memory. + * + * Somewhere between the two scenarios is a sweet spot, which probably + * varies by workload and available resources. Configuration is the + * classical solution but it brings complexity. Auto-tuning of buffer size + * or a different, perhaps capped, scale-up buffer size algorithm + * is the other classical solution. Here that would mean no longer + * matching the JVM size progression. + * + * This is a place to make it easier to tune heuristics. The current + * ones are best guesses, without the benefit of configuration. + * + * Life is choices! + */ + if (total < ABSTRACT_TRYSPLIT_BATCH_SIZE) true // avoid copy on first split + else if (used == total) true + else { + val usageRatio = used / total + usageRatio > 0.8 // Allow 20% wastage. + } + } + abstract class AbstractDoubleSpliterator( est: Long, additionalCharacteristics: Int ) extends Spliterator.OfDouble { + private var remaining = est + + // JVM uses an arithmetic progression, incrementing factor with each split. + private var trySplitsMultiplier = 1L // a Long to ease overflow checking + def characteristics(): Int = additionalCharacteristics - def estimateSize(): Long = est + def estimateSize(): Long = remaining + + def trySplit(): Spliterator.OfDouble = { + // Guard ArrayList(size) constructor by avoiding int overflow (to minus). + val batchSize = getTrySplitBatchSize(trySplitsMultiplier) + val buf = new Array[Double](batchSize) + + var count = 0 - def trySplit(): Spliterator.OfDouble = - null.asInstanceOf[Spliterator.OfDouble] + val action: DoubleConsumer = + (e: Double) => { buf(count) = e; count += 1 } + + while ((count < batchSize) && tryAdvance(action)) { /* side-effect */ } + + if (count == 0) null.asInstanceOf[Spliterator.OfDouble] + else { + remaining -= count + trySplitsMultiplier += 1 + + /* Passing an Array down allows the created spliterator to + * traverse and split more efficiently. + * + * Pass accumulating buffer if small or if unused, wasted space is + * tolerable. Otherwise, pay the cost of an allocation and + * potentially large copy. + */ + + val batch = + if (trySplitUsageRatioOK(count, batchSize)) buf + else Arrays.copyOf(buf, count) + + Spliterators.spliterator( + batch, // of AnyVal primitives + 0, + count, + additionalCharacteristics + ) + } + } } abstract class AbstractIntSpliterator( est: Long, additionalCharacteristics: Int ) extends Spliterator.OfInt { + private var remaining = est + + // JVM uses an arithmetic progression, incrementing factor with each split. + private var trySplitsMultiplier = 1L // a Long to ease overflow checking + def characteristics(): Int = additionalCharacteristics - def estimateSize(): Long = est + def estimateSize(): Long = remaining - // BEWARE: non-functional, never splits, always returns null - def trySplit(): Spliterator.OfInt = - null.asInstanceOf[Spliterator.OfInt] + def trySplit(): Spliterator.OfInt = { + // Guard ArrayList(size) constructor by avoiding int overflow (to minus). + val batchSize = getTrySplitBatchSize(trySplitsMultiplier) + val buf = new Array[Int](batchSize) + + var count = 0 + + val action: IntConsumer = + (e: Int) => { buf(count) = e; count += 1 } + + while ((count < batchSize) && tryAdvance(action)) { /* side-effect */ } + + if (count == 0) null.asInstanceOf[Spliterator.OfInt] + else { + remaining -= count + trySplitsMultiplier += 1 + + // See comment in corresponding place in AbstractDoubleSpliterator + val batch = + if (trySplitUsageRatioOK(count, batchSize)) buf + else Arrays.copyOf(buf, count) + + Spliterators.spliterator( + batch, // of AnyVal primitives + 0, + count, + additionalCharacteristics + ) + } + } } abstract class AbstractLongSpliterator( est: Long, additionalCharacteristics: Int ) extends Spliterator.OfLong { + private var remaining = est + + // JVM uses an arithmetic progression, incrementing factor with each split. + private var trySplitsMultiplier = 1L // a Long to ease overflow checking + def characteristics(): Int = additionalCharacteristics - def estimateSize(): Long = est + def estimateSize(): Long = remaining + + def trySplit(): Spliterator.OfLong = { + // Guard ArrayList(size) constructor by avoiding int overflow (to minus). + val batchSize = getTrySplitBatchSize(trySplitsMultiplier) + val buf = new Array[Long](batchSize) + + var count = 0 - // BEWARE: non-functional, never splits, always returns null - def trySplit(): Spliterator.OfLong = - null.asInstanceOf[Spliterator.OfLong] + val action: LongConsumer = + (e: Long) => { buf(count) = e; count += 1 } + + while ((count < batchSize) && tryAdvance(action)) { /* side-effect */ } + + if (count == 0) null.asInstanceOf[Spliterator.OfLong] + else { + remaining -= count + trySplitsMultiplier += 1 + + // See comment in corresponding place in AbstractDoubleSpliterator + val batch = + if (trySplitUsageRatioOK(count, batchSize)) buf + else Arrays.copyOf(buf, count) + + Spliterators.spliterator( + batch, // of AnyVal primitives + 0, + count, + additionalCharacteristics + ) + } + } } abstract class AbstractSpliterator[T]( est: Long, additionalCharacteristics: Int ) extends Spliterator[T] { + private var remaining = est + + // JVM uses an arithmetic progression, incrementing factor with each split. + private var trySplitsMultiplier = 1L // a Long to ease overflow checking + def characteristics(): Int = additionalCharacteristics - def estimateSize(): Long = est + def estimateSize(): Long = remaining + + def trySplit(): Spliterator[T] = { + // Guard ArrayList(size) constructor by avoiding int overflow (to minus). + val batchSize = getTrySplitBatchSize(trySplitsMultiplier) + val buf = new Array[Object](batchSize) + + var count = 0 + + /* Someday it would be nice to get rid of the cost of the runtime cast. + * The current issue is that type T has no upper bound, such as + * Object or AnyRef. With current declarations, an uninformed, + * unwary, unfortunate, or malicious user could specify an AnyVal + * for T, such as "new AbstractSplitertor[scala.Double]". + * + * The Scala Native compiler checks the signature of "action" + * against the JDK, so that signature can not be modified. + */ + val action: Consumer[_ >: T] = + (e: T) => { buf(count) = e.asInstanceOf[Object]; count += 1 } - // BEWARE: non-functional, never splits, always returns null - def trySplit(): Spliterator[T] = null.asInstanceOf[Spliterator[T]] + while ((count < batchSize) && tryAdvance(action)) { /* side-effect */ } + + if (count == 0) null.asInstanceOf[Spliterator[T]] + else { + remaining -= count + trySplitsMultiplier += 1 + + // See comment in corresponding place in AbstractDoubleSpliterator + val batch = + if (trySplitUsageRatioOK(count, batchSize)) buf + else Arrays.copyOf(buf, count) + + Spliterators.spliterator( + batch, // of AnyRef Objects + 0, + count, + additionalCharacteristics + ) + } + } } def emptyDoubleSpliterator(): Spliterator.OfDouble = { @@ -264,8 +472,6 @@ object Spliterators { new AbstractSpliterator[T](c.size(), harmonized) { lazy val it = c.iterator() - override def estimateSize(): Long = c.size() // even if CONCURRENT - def tryAdvance(action: Consumer[_ >: T]): Boolean = { Objects.requireNonNull(action) if (!it.hasNext()) false @@ -288,9 +494,21 @@ object Spliterators { // current index, modified on traverse/split private var cursor: Int = fromIndex - // BEWARE: non-functional, never splits, always returns null - def trySplit(): Spliterator.OfDouble = - null.asInstanceOf[Spliterator.OfDouble] + def trySplit(): Spliterator.OfDouble = { + val hi = toIndex + val lo = cursor + val mid = (lo + hi) >>> 1 + if (lo >= mid) null + else { + cursor = mid + new SpliteratorFromArrayDouble( + array, + lo, + mid, + additionalCharacteristics + ) + } + } def tryAdvance(action: DoubleConsumer): Boolean = { Objects.requireNonNull(action) @@ -365,9 +583,16 @@ object Spliterators { // current index, modified on traverse/split private var cursor: Int = fromIndex - // BEWARE: non-functional, never splits, always returns null - def trySplit(): Spliterator.OfInt = - null.asInstanceOf[Spliterator.OfInt] + def trySplit(): Spliterator.OfInt = { + val hi = toIndex + val lo = cursor + val mid = (lo + hi) >>> 1 + if (lo >= mid) null + else { + cursor = mid + new SpliteratorFromArrayInt(array, lo, mid, additionalCharacteristics) + } + } def tryAdvance(action: IntConsumer): Boolean = { Objects.requireNonNull(action) @@ -418,8 +643,6 @@ object Spliterators { Objects.requireNonNull(iterator) val harmonized = maybeSetSizedCharacteristics(characteristics) new AbstractSpliterator[T](size, harmonized) { - override def estimateSize(): Long = size // always initial size - def tryAdvance(action: Consumer[_ >: T]): Boolean = { Objects.requireNonNull(action) if (!iterator.hasNext()) false @@ -442,9 +665,16 @@ object Spliterators { // current index, modified on traverse/split private var cursor: Int = fromIndex - // BEWARE: non-functional, never splits, always returns null - def trySplit(): Spliterator.OfLong = - null.asInstanceOf[Spliterator.OfLong] + def trySplit(): Spliterator.OfLong = { + val hi = toIndex + val lo = cursor + val mid = (lo + hi) >>> 1 + if (lo >= mid) null + else { + cursor = mid + new SpliteratorFromArrayLong(array, lo, mid, additionalCharacteristics) + } + } def tryAdvance(action: LongConsumer): Boolean = { Objects.requireNonNull(action) @@ -498,8 +728,21 @@ object Spliterators { // current index, modified on traverse/split private var cursor: Int = fromIndex - // BEWARE: non-functional, never splits, always returns null - def trySplit(): Spliterator[T] = null.asInstanceOf[Spliterator[T]] + def trySplit(): Spliterator[T] = { + val hi = toIndex + val lo = cursor + val mid = (lo + hi) >>> 1 + if (lo >= mid) null + else { + cursor = mid + new SpliteratorFromArrayObject[T]( + array, + lo, + mid, + additionalCharacteristics + ) + } + } def tryAdvance(action: Consumer[_ >: T]): Boolean = { Objects.requireNonNull(action) @@ -559,8 +802,6 @@ object Spliterators { val harmonized = maybeSetSizedCharacteristics(characteristics) new AbstractDoubleSpliterator(size, harmonized) { - override def estimateSize(): Long = size // always initial size - def tryAdvance(action: DoubleConsumer): Boolean = { Objects.requireNonNull(action) if (!iterator.hasNext()) false @@ -581,8 +822,6 @@ object Spliterators { val harmonized = maybeSetSizedCharacteristics(characteristics) new AbstractIntSpliterator(size, harmonized) { - override def estimateSize(): Long = size // always initial size - def tryAdvance(action: IntConsumer): Boolean = { Objects.requireNonNull(action) if (!iterator.hasNext()) false @@ -603,8 +842,6 @@ object Spliterators { val harmonized = maybeSetSizedCharacteristics(characteristics) new AbstractLongSpliterator(size, harmonized) { - override def estimateSize(): Long = size // always initial size - def tryAdvance(action: LongConsumer): Boolean = { Objects.requireNonNull(action) if (!iterator.hasNext()) false diff --git a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/SpliteratorsTest.scala b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/SpliteratorsTest.scala index 5f81072d3d..f6531047fd 100644 --- a/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/SpliteratorsTest.scala +++ b/unit-tests/shared/src/test/scala/org/scalanative/testsuite/javalib/util/SpliteratorsTest.scala @@ -203,7 +203,7 @@ class SpliteratorsTest { assertEquals("forEachRemaining size", expectedSize, count) } - @Test def primitiveIteratorFromSpliteratorDouble: Unit = { + @Test def primitiveIteratorFromSpliteratorDouble(): Unit = { val expectedElements = Array( 0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6 ) @@ -234,7 +234,7 @@ class SpliteratorsTest { }) } - @Test def primitiveIteratorFromSpliteratorInt: Unit = { + @Test def primitiveIteratorFromSpliteratorInt(): Unit = { val expectedElements = Array( 0, 1, 2, 3, 4, 5, 6 ) @@ -265,7 +265,7 @@ class SpliteratorsTest { }) } - @Test def primitiveIteratorFromSpliteratorLong: Unit = { + @Test def primitiveIteratorFromSpliteratorLong(): Unit = { val expectedElements = Array(0, 11L, 22L, 33L, 44L, 55L, 66L) val expectedSize = expectedElements.size @@ -294,7 +294,7 @@ class SpliteratorsTest { }) } - @Test def iteratorFromSpliteratorType: Unit = { + @Test def iteratorFromSpliteratorType(): Unit = { type T = String val expectedElements = Array( "lliu", @@ -333,7 +333,7 @@ class SpliteratorsTest { }) } - @Test def spliteratorOfTypeFromCollection: Unit = { + @Test def spliteratorOfTypeFromCollection(): Unit = { type T = String val expectedElements = Array( "Bertha von Suttner", @@ -418,7 +418,7 @@ class SpliteratorsTest { ) } - @Test def spliteratorOfDoubleFromArray: Unit = { + @Test def spliteratorOfDoubleFromArray(): Unit = { type T = Double val expectedElements = Array( 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 @@ -480,7 +480,7 @@ class SpliteratorsTest { assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) } - @Test def spliteratorOfDoubleFromArrayRange: Unit = { + @Test def spliteratorOfDoubleFromArrayRange(): Unit = { type T = Double val expectedElements = Array( 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 @@ -546,7 +546,7 @@ class SpliteratorsTest { assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) } - @Test def spliteratorOfIntFromArray: Unit = { + @Test def spliteratorOfIntFromArray(): Unit = { type T = Int val expectedElements = Array( 0, 1, 2, 3, 4, 5, 6 @@ -606,7 +606,7 @@ class SpliteratorsTest { assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) } - @Test def spliteratorOfIntFromArrayRange: Unit = { + @Test def spliteratorOfIntFromArrayRange(): Unit = { type T = Int val expectedElements = Array( 1, 11, 22, 33, 44, 55, 66 @@ -673,7 +673,7 @@ class SpliteratorsTest { assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) } - @Test def spliteratorFromIteratorType: Unit = { + @Test def spliteratorFromIteratorType(): Unit = { type T = String val expectedElements = Array( "Arctic", @@ -762,7 +762,7 @@ class SpliteratorsTest { ) } - @Test def spliteratorOfLongFromArray: Unit = { + @Test def spliteratorOfLongFromArray(): Unit = { type T = Long val expectedElements = Array( 0L, 1L, 2L, 3L, 4L, 5L, 6L @@ -822,7 +822,7 @@ class SpliteratorsTest { assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) } - @Test def spliteratorOfLongFromArrayRange: Unit = { + @Test def spliteratorOfLongFromArrayRange(): Unit = { type T = Long val expectedElements = Array( 1L, 11L, 22L, 33L, 44L, 55L, 66L @@ -886,7 +886,7 @@ class SpliteratorsTest { assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) } - @Test def spliteratorOfTypeFromArrayRange: Unit = { + @Test def spliteratorOfTypeFromArrayRange(): Unit = { type T = String val expectedElements = Array( "nul'", @@ -978,7 +978,7 @@ class SpliteratorsTest { assertEquals("forEachRemaining estimateSize", 0, spliter.estimateSize()) } - @Test def spliteratorFromPrimitiveIteratorOfDouble: Unit = { + @Test def spliteratorFromPrimitiveIteratorOfDouble(): Unit = { type T = Double val expectedElements = Array( 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 @@ -1067,7 +1067,7 @@ class SpliteratorsTest { ) } - @Test def spliteratorFromPrimitiveIteratorOfInt: Unit = { + @Test def spliteratorFromPrimitiveIteratorOfInt(): Unit = { type T = Int val expectedElements = Array( 0, 1, 2, 3, 4, 5, 6 @@ -1153,7 +1153,7 @@ class SpliteratorsTest { ) } - @Test def spliteratorFromPrimitiveIteratorOfLong: Unit = { + @Test def spliteratorFromPrimitiveIteratorOfLong(): Unit = { type T = Long val expectedElements = Array( 0L, 1L, 2L, 3L, 4L, 5L, 6L @@ -1239,7 +1239,7 @@ class SpliteratorsTest { ) } - @Test def spliteratorUnknownSizeFromIteratorType: Unit = { + @Test def spliteratorUnknownSizeFromIteratorType(): Unit = { type T = String val expectedElements = Array( "pride", @@ -1318,8 +1318,8 @@ class SpliteratorsTest { spliter.estimateSize() ) } -// --- - @Test def spliteratorUnknownSizeFromPrimitiveIteratorOfDouble: Unit = { + + @Test def spliteratorUnknownSizeFromPrimitiveIteratorOfDouble(): Unit = { type T = Double val expectedElements = Array( 0.0, 10.1, 20.2, 30.3, 44.4, 55.5, 66.6 @@ -1404,7 +1404,7 @@ class SpliteratorsTest { ) } - @Test def spliteratorUnknownSizeFromPrimitiveIteratorOfInt: Unit = { + @Test def spliteratorUnknownSizeFromPrimitiveIteratorOfInt(): Unit = { type T = Int val expectedElements = Array( 0, 1, 2, 3, 4, 5, 6 @@ -1486,7 +1486,7 @@ class SpliteratorsTest { ) } - @Test def spliteratorUnknownSizeFromPrimitiveIteratorOfLong: Unit = { + @Test def spliteratorUnknownSizeFromPrimitiveIteratorOfLong(): Unit = { type T = Long val expectedElements = Array( 0L, 1L, 2L, 3L, 4L, 5L, 6L From af89c260df81c02eb4db9a6063b0ac4f83318748 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 14 Mar 2023 13:43:31 +0100 Subject: [PATCH 40/61] Fix compilation error after cherrypicking --- .../resources/scala-native/gc/immix/Marker.c | 2 +- .../scala-native/gc/immix_commix/StackTrace.c | 2 +- .../resources/scala-native/gc/shared/GCTypes.h | 10 ++++++++++ .../scala-native/platform/posix/unwind.c | 3 +-- .../resources/scala-native/platform/unwind.h | 4 ++-- .../scala-native/platform/windows/unwind.c | 5 ++--- .../scala/scalanative/nscplugin/NirGenExpr.scala | 4 ++-- .../scala/scalanative/nscplugin/NirGenUtil.scala | 6 +++--- .../scala/scalanative/nscplugin/NirGenExpr.scala | 16 ++++------------ .../scalanative/codegen/AbstractCodeGen.scala | 4 ++-- .../scala/scala/scalanative/IssuesTest.scala | 6 +++--- .../scala/scalanative/unsafe/CVarArgTest.scala | 6 +----- 12 files changed, 32 insertions(+), 36 deletions(-) diff --git a/nativelib/src/main/resources/scala-native/gc/immix/Marker.c b/nativelib/src/main/resources/scala-native/gc/immix/Marker.c index aaeb295ce3..ca0fcf9616 100644 --- a/nativelib/src/main/resources/scala-native/gc/immix/Marker.c +++ b/nativelib/src/main/resources/scala-native/gc/immix/Marker.c @@ -96,7 +96,7 @@ void Marker_markProgramStack(Heap *heap, Stack *stack) { setjmp(regs); word_t *dummy; - word_t **current = &dummy; + word_t **stackTop = &dummy; word_t **stackBottom = __stack_bottom; Marker_markRange(heap, stack, stackTop, stackBottom); diff --git a/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c b/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c index a03f11fa7e..2618f0bd56 100644 --- a/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c +++ b/nativelib/src/main/resources/scala-native/gc/immix_commix/StackTrace.c @@ -10,7 +10,7 @@ void StackTrace_PrintStackTrace() { scalanative_unwind_init_local(cursor, context); while (scalanative_unwind_step(cursor) > 0) { - size_t offset, pc; + uintptr_t offset, pc; scalanative_unwind_get_reg(cursor, scalanative_unw_reg_ip(), &pc); if (pc == 0) { break; diff --git a/nativelib/src/main/resources/scala-native/gc/shared/GCTypes.h b/nativelib/src/main/resources/scala-native/gc/shared/GCTypes.h index bd4ca2998f..396d69b62a 100644 --- a/nativelib/src/main/resources/scala-native/gc/shared/GCTypes.h +++ b/nativelib/src/main/resources/scala-native/gc/shared/GCTypes.h @@ -6,6 +6,16 @@ #define NOINLINE __attribute__((noinline)) #define INLINE __attribute__((always_inline)) +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define NO_SANITIZE __attribute__((no_sanitize("address"))) +#endif +#endif + +#ifndef NO_SANITIZE +#define NO_SANITIZE +#endif + #define UNLIKELY(b) __builtin_expect((b), 0) #define LIKELY(b) __builtin_expect((b), 1) diff --git a/nativelib/src/main/resources/scala-native/platform/posix/unwind.c b/nativelib/src/main/resources/scala-native/platform/posix/unwind.c index 6d136219ea..10fc90ea48 100644 --- a/nativelib/src/main/resources/scala-native/platform/posix/unwind.c +++ b/nativelib/src/main/resources/scala-native/platform/posix/unwind.c @@ -22,8 +22,7 @@ int scalanative_unwind_get_proc_name(void *cursor, char *buffer, size_t length, (unw_word_t *)offset); } -int scalanative_unwind_get_reg(void *cursor, int regnum, - unsigned long long *valp) { +int scalanative_unwind_get_reg(void *cursor, int regnum, uintptr_t *valp) { return unw_get_reg((unw_cursor_t *)cursor, regnum, (unw_word_t *)valp); } diff --git a/nativelib/src/main/resources/scala-native/platform/unwind.h b/nativelib/src/main/resources/scala-native/platform/unwind.h index 1d5818b553..13f40cddd3 100644 --- a/nativelib/src/main/resources/scala-native/platform/unwind.h +++ b/nativelib/src/main/resources/scala-native/platform/unwind.h @@ -2,14 +2,14 @@ #define UNWIND_H #include +#include int scalanative_unwind_get_context(void *context); int scalanative_unwind_init_local(void *cursor, void *context); int scalanative_unwind_step(void *cursor); int scalanative_unwind_get_proc_name(void *cursor, char *buffer, size_t length, void *offset); -int scalanative_unwind_get_reg(void *cursor, int regnum, - unsigned long long *valp); +int scalanative_unwind_get_reg(void *cursor, int regnum, uintptr_t *valp); int scalanative_unw_reg_ip(); size_t scalanative_unwind_sizeof_context(); size_t scalanative_unwind_sizeof_cursor(); diff --git a/nativelib/src/main/resources/scala-native/platform/windows/unwind.c b/nativelib/src/main/resources/scala-native/platform/windows/unwind.c index 820ffe842d..1cc8fe7ed2 100644 --- a/nativelib/src/main/resources/scala-native/platform/windows/unwind.c +++ b/nativelib/src/main/resources/scala-native/platform/windows/unwind.c @@ -73,10 +73,9 @@ int scalanative_unwind_get_proc_name(void *cursor, char *buffer, size_t length, return 0; } -int scalanative_unwind_get_reg(void *cursor, int regnum, - unsigned long long *valp) { +int scalanative_unwind_get_reg(void *cursor, int regnum, uintptr_t *valp) { UnwindContext *ucontext = *(UnwindContext **)cursor; - *valp = (unsigned long long)(ucontext->stack[ucontext->cursor]); + *valp = (uintptr_t)(ucontext->stack[ucontext->cursor]); return 0; } diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala index ab0b1664a9..a334d5c647 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala @@ -1856,8 +1856,8 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => case (Type.I(w1, _), Type.F(w2)) if w1 == w2 => Some(nir.Conv.Bitcast) case (Type.F(w1), Type.I(w2, _)) if w1 == w2 => Some(nir.Conv.Bitcast) case _ if fromty == toty => None - case (Type.Float, Type.Double) => Some(nir.Conv.Fpext) - case (Type.Double, Type.Float) => Some(nir.Conv.Fptrunc) + case (Type.Float, Type.Double) => Some(nir.Conv.Fpext) + case (Type.Double, Type.Float) => Some(nir.Conv.Fptrunc) case _ => unsupported(s"cast from $fromty to $toty") } diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenUtil.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenUtil.scala index f647b4775f..940ac21f80 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenUtil.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenUtil.scala @@ -69,9 +69,9 @@ trait NirGenUtil[G <: Global with Singleton] { self: NirGenPhase[G] => case UIntTagMethod => just(UIntClass) case LongTagMethod => just(LongClass) case ULongTagMethod => just(ULongClass) - case FloatTagMethod => just(FloatClass) - case DoubleTagMethod => just(DoubleClass) - case PtrTagMethod => just(PtrClass) + case FloatTagMethod => just(FloatClass) + case DoubleTagMethod => just(DoubleClass) + case PtrTagMethod => just(PtrClass) case PtrWildcardTagMethod => just(PtrClass) case PtrClassNotGivenClassTagMethod => just(PtrClass) case ClassTagMethod => just(unwrapClassTagOption(args.head).get) diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala index f4f4040d82..f012da59d9 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenExpr.scala @@ -1512,19 +1512,11 @@ trait NirGenExpr(using Context) { val promotedArg = arg.ty match { case Type.Float => this.genCastOp(Type.Float, Type.Double, arg) - case Type.FixedSizeI(width, _) if width < Type.Int.width => + case Type.I(width, _) if width < Type.Int.width => val conv = if (isUnsigned) nir.Conv.Zext else nir.Conv.Sext buf.conv(conv, Type.Int, arg, unwind) - case Type.Long => - // On 32-bit systems Long needs to be truncated to Int - // Cast it to size to make undependent from architecture - val conv = - if (isUnsigned) nir.Conv.ZSizeCast - else nir.Conv.SSizeCast - buf.conv(conv, Type.Size, arg, unwind) - case _ => arg } res += promotedArg @@ -1797,9 +1789,9 @@ trait NirGenExpr(using Context) { case (Type.I(w1, _), Type.F(w2)) if w1 == w2 => Some(nir.Conv.Bitcast) case (Type.F(w1), Type.I(w2, _)) if w1 == w2 => Some(nir.Conv.Bitcast) case _ if fromty == toty => None - case (Type.Float, Type.Double) => Some(nir.Conv.Fpext) - case (Type.Double, Type.Float) => Some(nir.Conv.Fptrunc) - case_ => unsupported(s"cast from $fromty to $toty") + case (Type.Float, Type.Double) => Some(nir.Conv.Fpext) + case (Type.Double, Type.Float) => Some(nir.Conv.Fptrunc) + case _ => unsupported(s"cast from $fromty to $toty") } /** Boxes a value of the given type before `elimErasedValueType`. diff --git a/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala b/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala index a8601ad030..3cd0878259 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/AbstractCodeGen.scala @@ -369,7 +369,7 @@ private[codegen] abstract class AbstractCodeGen( case Type.Unit => str("void") case _: Type.RefKind | Type.Ptr | Type.Null | Type.Nothing => str(pointerType) - case Type.Bool => str("i1") + case Type.Bool => str("i1") case i: Type.I => str("i"); str(i.width) case Type.Float => str("float") case Type.Double => str("double") @@ -426,7 +426,7 @@ private[codegen] abstract class AbstractCodeGen( case Val.True => str("true") case Val.False => str("false") case Val.Null => str("null") - case Val.Unit => str("void") + case Val.Unit => str("void") case Val.Zero(ty) => str("zeroinitializer") case Val.Byte(v) => str(v) case Val.Char(v) => str(v.toInt) diff --git a/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala b/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala index 1ceb9caab8..37dd2e752a 100644 --- a/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala +++ b/unit-tests/native/src/test/scala/scala/scalanative/IssuesTest.scala @@ -601,14 +601,14 @@ class IssuesTest { @alwaysinline def allocatingFunction(): CSize = { import scala.scalanative.unsafe.{CArray, Nat} import Nat._ - def `64KB` = (64 * 1024).toUSize + def `64KB` = (64 * 1024).toUInt val chunk = stackalloc[Byte](`64KB`) assertNotNull("stackalloc was null", chunk) `64KB` } // 32MB, may more then available stack 1MB on Windows, < 8 MB on Unix - val toAllocate = (32 * 1024 * 1024).toUSize - var allocated = 0.toUSize + val toAllocate = (32 * 1024 * 1024).toULong + var allocated = 0.toULong while (allocated < toAllocate) { allocated += allocatingFunction() } diff --git a/unit-tests/native/src/test/scala/scala/scalanative/unsafe/CVarArgTest.scala b/unit-tests/native/src/test/scala/scala/scalanative/unsafe/CVarArgTest.scala index 6a03cf6451..b9b10f8280 100644 --- a/unit-tests/native/src/test/scala/scala/scalanative/unsafe/CVarArgTest.scala +++ b/unit-tests/native/src/test/scala/scala/scalanative/unsafe/CVarArgTest.scala @@ -15,7 +15,7 @@ class CVarArgTest { def vatest(cstr: CString, output: String)( generator: (CString, Ptr[CChar]) => Unit ): Unit = { - val buff: Ptr[CChar] = stackalloc[CChar](1024.toUSize) + val buff: Ptr[CChar] = stackalloc[CChar](1024.toUInt) generator(buff, cstr) val got = fromCString(buff) assertEquals(got, output) @@ -254,13 +254,11 @@ class CVarArgTest { @Test def longValueMinus1(): Unit = vatest(c"%d", "-1")(stdio.sprintf(_, _, -1L)) @Test def longValueMin(): Unit = { - assumeNot32Bit() vatest(c"%lld", "-9223372036854775808")( stdio.sprintf(_, _, java.lang.Long.MIN_VALUE) ) } @Test def longValueMax(): Unit = { - assumeNot32Bit() vatest(c"%lld", "9223372036854775807")( stdio.sprintf(_, _, java.lang.Long.MAX_VALUE) ) @@ -567,11 +565,9 @@ class CVarArgTest { ) @Test def ulongValueMin(): Unit = { - assumeNot32Bit() vatest(c"%llu", "0")(stdio.sprintf(_, _, ULong.MinValue)) } @Test def ulongValueMax(): Unit = { - assumeNot32Bit() vatest(c"%llu", "18446744073709551615")(stdio.sprintf(_, _, ULong.MaxValue)) } @Test def ulongArgs1(): Unit = From 6519c2af83b8ae0430810ed2bb50a369db229463 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 15 Mar 2023 10:19:41 +0100 Subject: [PATCH 41/61] Fix Inflater{Input,Output}Stream after changes to more strick array alloc --- .../scala/java/util/zip/InflaterInputStream.scala | 14 ++++++++++++-- .../scala/java/util/zip/InflaterOutputStream.scala | 8 +++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/javalib/src/main/scala/java/util/zip/InflaterInputStream.scala b/javalib/src/main/scala/java/util/zip/InflaterInputStream.scala index 464a5d7d16..ebb4481b69 100644 --- a/javalib/src/main/scala/java/util/zip/InflaterInputStream.scala +++ b/javalib/src/main/scala/java/util/zip/InflaterInputStream.scala @@ -9,8 +9,18 @@ class InflaterInputStream private ( protected var inf: Inflater, protected var buf: Array[Byte] ) extends FilterInputStream(in) { - def this(in: InputStream, inf: Inflater, len: Int) = - this(in, inf, new Array[Byte](len)) + def this(in: InputStream, inf: Inflater, len: Int) = { + this( + in, + inf, { + if (len <= 0) { + throw new IllegalArgumentException() + } + new Array[Byte](len) + } + ) + } + def this(in: InputStream, inf: Inflater) = this(in, inf, InflaterInputStream.BUF_SIZE) def this(in: InputStream) = this(in, new Inflater()) diff --git a/javalib/src/main/scala/java/util/zip/InflaterOutputStream.scala b/javalib/src/main/scala/java/util/zip/InflaterOutputStream.scala index 104f29e865..c83118ec91 100644 --- a/javalib/src/main/scala/java/util/zip/InflaterOutputStream.scala +++ b/javalib/src/main/scala/java/util/zip/InflaterOutputStream.scala @@ -16,7 +16,13 @@ class InflaterOutputStream private ( private var closed = false def this(out: OutputStream, inf: Inflater, bufferSize: Int) = { - this(out, inf, new Array[Byte](bufferSize)) + this( + out, + inf, + if (bufferSize <= 0) + throw new IllegalArgumentException("bufferSize <= 0: " + bufferSize) + else new Array[Byte](bufferSize) + ) if (out == null) { throw new NullPointerException("out == null") } else if (inf == null) { From 5ca521fa0aa977df390ee71f6787a12b566972a4 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 15 Mar 2023 12:58:28 +0100 Subject: [PATCH 42/61] Make WeakReferenceTest less strict --- .../javalib/lang/ref/WeakReferenceTest.scala | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/unit-tests/native/src/test/scala/org/scalanative/testsuite/javalib/lang/ref/WeakReferenceTest.scala b/unit-tests/native/src/test/scala/org/scalanative/testsuite/javalib/lang/ref/WeakReferenceTest.scala index 0b04f50634..d3cbe6cc70 100644 --- a/unit-tests/native/src/test/scala/org/scalanative/testsuite/javalib/lang/ref/WeakReferenceTest.scala +++ b/unit-tests/native/src/test/scala/org/scalanative/testsuite/javalib/lang/ref/WeakReferenceTest.scala @@ -46,18 +46,20 @@ class WeakReferenceTest { def assertEventuallyIsCollected( clue: String, ref: WeakReference[_], - retries: Int + deadline: Long ): Unit = { ref.get() match { case null => assertTrue("collected but not enqueued", ref.isEnqueued()) case v => - if (retries > 0) { + if (System.currentTimeMillis() < deadline) { // Give GC something to collect - System.err.println(s"$clue - not yet collected $ref ($retries)") + locally { + val _ = Seq.fill(1000)(new Object {}) + } Thread.sleep(200) GC.collect() - assertEventuallyIsCollected(clue, ref, retries - 1) + assertEventuallyIsCollected(clue, ref, deadline) } else { fail( s"$clue - expected that WeakReference would be collected, but it contains value ${v}" @@ -73,8 +75,9 @@ class WeakReferenceTest { val weakRefList = List(weakRef1, weakRef2) GC.collect() - assertEventuallyIsCollected("weakRef1", weakRef1, retries = 5) - assertEventuallyIsCollected("weakRef2", weakRef2, retries = 5) + def newDeadline() = System.currentTimeMillis() + 60 * 1000 + assertEventuallyIsCollected("weakRef1", weakRef1, deadline = newDeadline()) + assertEventuallyIsCollected("weakRef2", weakRef2, deadline = newDeadline()) assertEquals("weakRef1", null, weakRef1.get()) assertEquals("weakRef2", null, weakRef2.get()) From 09388aaf29ba6766f0f1a8e946a270c7a28fcea7 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 15 Mar 2023 17:40:40 +0100 Subject: [PATCH 43/61] Disabel sporiously failing WeakReferenceTest --- .../testsuite/javalib/lang/ref/WeakReferenceTest.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/unit-tests/native/src/test/scala/org/scalanative/testsuite/javalib/lang/ref/WeakReferenceTest.scala b/unit-tests/native/src/test/scala/org/scalanative/testsuite/javalib/lang/ref/WeakReferenceTest.scala index d3cbe6cc70..89ea1616fa 100644 --- a/unit-tests/native/src/test/scala/org/scalanative/testsuite/javalib/lang/ref/WeakReferenceTest.scala +++ b/unit-tests/native/src/test/scala/org/scalanative/testsuite/javalib/lang/ref/WeakReferenceTest.scala @@ -38,9 +38,8 @@ class WeakReferenceTest { @deprecated @nooptimize @Test def addsToReferenceQueueAfterGC(): Unit = { assumeFalse( - "In the CI Scala 3 sometimes SN fails to clean weak references in some of Windows build configurations", - ScalaNativeBuildInfo.scalaVersion.startsWith("3.") && - Platform.isWindows + "In the CI Scala 3 sometimes SN fails to clean weak references in some build configurations", + ScalaNativeBuildInfo.scalaVersion.startsWith("3.") ) def assertEventuallyIsCollected( From ddbe43f55d94cf6def511e2a5c86937b7f3c5fb7 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 15 Mar 2023 22:26:30 +0100 Subject: [PATCH 44/61] Add draft of release notes --- docs/changelog/0.4.11.md | 178 +++++++++++++++++++++++++++++++++++++++ docs/changelog/index.rst | 3 +- 2 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/0.4.11.md diff --git a/docs/changelog/0.4.11.md b/docs/changelog/0.4.11.md new file mode 100644 index 0000000000..28797ba4f2 --- /dev/null +++ b/docs/changelog/0.4.11.md @@ -0,0 +1,178 @@ + +# 0.4.11 (2023-03-15) + +We're happy to announce the release of Scala Native. It's the next maintenance release for Scala Native 0.4.x. As always it brings bug fixes and minor improvements. + +## Notable changes + +### Extern methods with a variadic number of arguments +For a long time, Scala Native supported C `va_list` using `scalanative.unsafe.CVarArgList`. This allowed for interop with some of the C functions taking the variadic number of arguments. This release makes usage and definition of them easier, by restoring support for idiomatic ways of passing them using Scala variadic arguments lists. + +### Support for LLVM 15 +The latest versions of LLVM added a new internal representation of pointers - their opaque variant replaces typed pointers. This change should not affect the used of the LLVM toolchain, but in some specific builds it could have lead to linking issues. +Now Scala Native will try to detect version of used LLVM toolchain. When using LLVM 15 or newer Scala Native toolchain would always generate opaque pointers in the compiled LLVM IR. +```c +void printf(char* format, ...); +``` + +```scala +@extern def printf(format: CString, args: Any*): Unit = extern + +@main def test() = + val msg = c"MyMessage" + printf("String '%s' is allocated at %p and has %d characters\n", msg, msg, strlen(msg)) +``` + +Scala standard library used by this release is based on the following versions: + + + + + + + + + + + + + + + + + + + +
Scala binary versionScala release
2.122.12.17
2.132.13.10
33.2.2
+ + + + + + + + + + + + + + + + +
Commits since last release43
Merged PRs40
Contributors6
+ +## Contributors + +Big thanks to everybody who contributed to this release or reported an issue! + +``` +$ git shortlog -sn --no-merges v0.4.10..v0.4.11 + 24 Wojciech Mazur + 14 LeeTibbert + 2 Arman Bilge + 1 João Costa + 1 Ondra Pelech + 1 philwalk +``` + +## Merged PRs + +## [](https://github.com/scala-native/scala-native/tree/) (2023-03-16) + +[Full Changelog](https://github.com/scala-native/scala-native/compare/v0.4.10..v0.4.11) + +**Merged pull requests:** + +## Java Standard Library +- Partial Fix #3090: j.nio.MappedByteBuffer no longer causes segmentation fault on FreeBSD64 + [\#3113](https://github.com/scala-native/scala-native/pull/3113) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Port all missing `java.util.function` types + [\#3127](https://github.com/scala-native/scala-native/pull/3127) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Fix #3131: javalib ServerSocket should now be more accepting + [\#3140](https://github.com/scala-native/scala-native/pull/3140) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix #3153: j.nio.fs.FileHelpers uses only java.io.tmp property for temporary files/dirs + [\#3155](https://github.com/scala-native/scala-native/pull/3155) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix #3071, #3135: Implement Java 11 writeString & readString methods and Java 10 transferTo + [\#3159](https://github.com/scala-native/scala-native/pull/3159) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix #2937, 3163: improved j.nio.f.Files default directory idiom handling + [\#3166](https://github.com/scala-native/scala-native/pull/3166) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Partial fix #3165: Port two JSR-166 concurrent interfaces/traits: BlockingDeque, TransferQueue + [\#3188](https://github.com/scala-native/scala-native/pull/3188) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix #3192. #3194: Implement limited java spliterator support + [\#3202](https://github.com/scala-native/scala-native/pull/3202) + ([LeeTibbert](https://github.com/LeeTibbert)) +- javalib Spliterators trySplit() methods now split + [\#3218](https://github.com/scala-native/scala-native/pull/3218) + ([LeeTibbert](https://github.com/LeeTibbert)) + +## POSIX bindings +- posixlib socket.c now compiles on FreeBSD arm64 + [\#3112](https://github.com/scala-native/scala-native/pull/3112) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix #1642: posixlib stdio.scala is now mostly Open Group 2018 compliant + [\#3160](https://github.com/scala-native/scala-native/pull/3160) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix #3206: posixlib unistd and monetary use new CVarArgs support + [\#3209](https://github.com/scala-native/scala-native/pull/3209) + ([LeeTibbert](https://github.com/LeeTibbert)) + +## Compiler plugin +- Fix generation of CFuncPtr extern forwarders using opaque types + [\#3182](https://github.com/scala-native/scala-native/pull/3182) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Don't emit `Inst.Jump`/`Inst.Label` in NIR taking single `Unit` argument + [\#3201](https://github.com/scala-native/scala-native/pull/3201) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Restore support for C VarArgs alongside current CVarArgLists + [\#3204](https://github.com/scala-native/scala-native/pull/3204) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Allow for materialization of `Tag[Ptr[_]]` or taking abstract type + [\#3207](https://github.com/scala-native/scala-native/pull/3207) + ([WojciechMazur](https://github.com/WojciechMazur)) + +## Toolchain +- Define null guards for methods using `this` + [\#3123](https://github.com/scala-native/scala-native/pull/3123) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Fix #3173: Linux executable file .comment section shows build info + [\#3183](https://github.com/scala-native/scala-native/pull/3183) + ([LeeTibbert](https://github.com/LeeTibbert)) +- Fix cygwin and msys build problems + [\#3180](https://github.com/scala-native/scala-native/pull/3180) + ([philwalk](https://github.com/philwalk)) +- Use opaque pointers in generated LLVM IR when possible + [\#3190](https://github.com/scala-native/scala-native/pull/3190) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Don't emit debug logs when skipping embeding source files + [\#3191](https://github.com/scala-native/scala-native/pull/3191) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Emit `Val.Unit`/`Type.Unit` as `void` in LLVM IR instead of ref to `BoxedUnit` + [\#3200](https://github.com/scala-native/scala-native/pull/3200) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Restore stack state after executing inlined function to prevent stack overflows + [\#3199](https://github.com/scala-native/scala-native/pull/3199) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Fix poisonous new lines escapes in `nir.Show` leading to linker failures + [\#3208](https://github.com/scala-native/scala-native/pull/3208) + ([WojciechMazur](https://github.com/WojciechMazur)) + +## Runtime +- Update LLVM libunwind to 15.0.7 (was 12.0.1) + [\#3184](https://github.com/scala-native/scala-native/pull/3184) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Commix GC - fix deadlocks due to missaligned pointers when marking range + [\#3185](https://github.com/scala-native/scala-native/pull/3185) + ([WojciechMazur](https://github.com/WojciechMazur)) + +## sbt plugin +- Port `definedTestNames` override from Scala.js + [\#3203](https://github.com/scala-native/scala-native/pull/3203) + ([armanbilge](https://github.com/armanbilge)) diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst index d2d67953a3..77b0f5877d 100644 --- a/docs/changelog/index.rst +++ b/docs/changelog/index.rst @@ -5,7 +5,8 @@ Changelog .. toctree:: :maxdepth: 1 - + + 0.4.11 0.4.10 0.4.9 0.4.8 From a94b65fa20f5089a2a8664721546bf655741ae38 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 15 Mar 2023 22:27:29 +0100 Subject: [PATCH 45/61] Set version to 0.4.11 and update mima settings --- docs/conf.py | 4 ++-- nir/src/main/scala/scala/scalanative/nir/Versions.scala | 2 +- project/Settings.scala | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 31ac2ffa8b..4f25867275 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -69,9 +69,9 @@ def generateScalaNativeCurrentYear(): # built documents. # # The short X.Y version. -version = u'0.4.10' +version = u'0.4.11' # The full version, including alpha/beta/rc tags. -release = u'0.4.10' +release = u'0.4.11' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/nir/src/main/scala/scala/scalanative/nir/Versions.scala b/nir/src/main/scala/scala/scalanative/nir/Versions.scala index 8d4538b3a5..f8f852023d 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Versions.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Versions.scala @@ -25,7 +25,7 @@ object Versions { final val revision: Int = 9 // a.k.a. MINOR version /* Current public release version of Scala Native. */ - final val current: String = "0.4.10" + final val current: String = "0.4.11" final val currentBinaryVersion: String = binaryVersion(current) private object FullVersion { diff --git a/project/Settings.scala b/project/Settings.scala index 2cc1737b78..b8a9f17169 100644 --- a/project/Settings.scala +++ b/project/Settings.scala @@ -178,7 +178,7 @@ object Settings { ), mimaPreviousArtifacts ++= { // The previous releases of Scala Native with which this version is binary compatible. - val binCompatVersions = (0 to 9).map(v => s"0.4.$v").toSet + val binCompatVersions = (0 to 10).map(v => s"0.4.$v").toSet val toolsProjects = Set("util", "tools", "nir", "test-runner") lazy val neverPublishedProjects040 = Map( "2.11" -> (toolsProjects ++ Set("windowslib", "scala3lib")), From 236e72072570650abc1db9d5abca83b5f6214744 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 16 Mar 2023 09:25:50 +0100 Subject: [PATCH 46/61] Mititgate posix.monetaryExtern mima issues --- project/BinaryIncompatibilities.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/project/BinaryIncompatibilities.scala b/project/BinaryIncompatibilities.scala index 9da61acdfb..120b499983 100644 --- a/project/BinaryIncompatibilities.scala +++ b/project/BinaryIncompatibilities.scala @@ -85,7 +85,8 @@ object BinaryIncompatibilities { "scala.scalanative.posix.limits.PATH_MAX" ), // Moved to javalib, used internally and in scripted-tests - exclude[MissingClassProblem]("scala.scalanative.runtime.SocketHelpers*") + exclude[MissingClassProblem]("scala.scalanative.runtime.SocketHelpers*"), + exclude[Problem]("scala.scalanative.posix.monetaryExtern*") ) final val WindowsLib: Filters = Nil From d47ef62c9113ff390cbe63ef74ba5e55918a48b1 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 16 Mar 2023 09:26:06 +0100 Subject: [PATCH 47/61] Fix typos in release notes --- docs/changelog/0.4.11.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/changelog/0.4.11.md b/docs/changelog/0.4.11.md index 28797ba4f2..6a7ce6d302 100644 --- a/docs/changelog/0.4.11.md +++ b/docs/changelog/0.4.11.md @@ -9,8 +9,8 @@ We're happy to announce the release of Scala Native. It's the next maintenance r For a long time, Scala Native supported C `va_list` using `scalanative.unsafe.CVarArgList`. This allowed for interop with some of the C functions taking the variadic number of arguments. This release makes usage and definition of them easier, by restoring support for idiomatic ways of passing them using Scala variadic arguments lists. ### Support for LLVM 15 -The latest versions of LLVM added a new internal representation of pointers - their opaque variant replaces typed pointers. This change should not affect the used of the LLVM toolchain, but in some specific builds it could have lead to linking issues. -Now Scala Native will try to detect version of used LLVM toolchain. When using LLVM 15 or newer Scala Native toolchain would always generate opaque pointers in the compiled LLVM IR. +The latest versions of LLVM added a new internal representation of pointers - their opaque variant replaces typed pointers. This change should not affect most of the users, but in some specific builds it could have lead to linking issues. +Now Scala Native will try to detect version of LLVM toolchain. When using LLVM 15 or newer Scala Native toolchain would always generate opaque pointers in the compiled LLVM IR. ```c void printf(char* format, ...); ``` @@ -23,7 +23,7 @@ void printf(char* format, ...); printf("String '%s' is allocated at %p and has %d characters\n", msg, msg, strlen(msg)) ``` -Scala standard library used by this release is based on the following versions: +The Scala standard library used by this release is based on the following versions: From ac653d3594454a5ca0d1d0d368a0c1369e280662 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 16 Mar 2023 12:28:21 +0100 Subject: [PATCH 48/61] Cleanup monetary.h bindings (#3225) * Monetary strfmon methods take var args of Doubles instead of Any * Remove unused monetary.c glue code --- .../main/resources/scala-native/monetary.c | 40 ------------------- .../scala/scalanative/posix/monetary.scala | 4 +- 2 files changed, 2 insertions(+), 42 deletions(-) delete mode 100644 posixlib/src/main/resources/scala-native/monetary.c diff --git a/posixlib/src/main/resources/scala-native/monetary.c b/posixlib/src/main/resources/scala-native/monetary.c deleted file mode 100644 index 6d44f3b507..0000000000 --- a/posixlib/src/main/resources/scala-native/monetary.c +++ /dev/null @@ -1,40 +0,0 @@ -#ifdef _WIN32 -// No Windows support -#else -#if !(defined __STDC_VERSION__) || (__STDC_VERSION__ < 201112L) -#ifndef SCALANATIVE_SUPPRESS_STRUCT_CHECK_WARNING -#warning "Size and order of C structures are not checked when -std < c11." -#endif -#else // POSIX -#include -#include - -#include // FIXME - -#ifdef __APPLE__ -#include -#endif // __APPLE__ - -ssize_t scalanative_strfmon_10(char *restrict str, size_t max, - const char *restrict format, double arg0, - double arg1, double arg2, double arg3, - double arg4, double arg5, double arg6, - double arg7, double arg8, double arg9) { - - return strfmon(str, max, format, arg0, arg1, arg2, arg3, arg4, arg5, arg6, - arg7, arg8, arg9); -} - -ssize_t scalanative_strfmon_l_10(char *restrict str, size_t max, - locale_t locale, const char *restrict format, - double arg0, double arg1, double arg2, - double arg3, double arg4, double arg5, - double arg6, double arg7, double arg8, - double arg9) { - - return strfmon_l(str, max, locale, format, arg0, arg1, arg2, arg3, arg4, - arg5, arg6, arg7, arg8, arg9); -} - -#endif // POSIX -#endif // ! _WIN32 diff --git a/posixlib/src/main/scala/scala/scalanative/posix/monetary.scala b/posixlib/src/main/scala/scala/scalanative/posix/monetary.scala index 733d7165c9..c92afb7541 100644 --- a/posixlib/src/main/scala/scala/scalanative/posix/monetary.scala +++ b/posixlib/src/main/scala/scala/scalanative/posix/monetary.scala @@ -19,7 +19,7 @@ object monetary { str: CString, max: size_t, format: CString, - vargs: Any* + vargs: Double* ): ssize_t = extern def strfmon_l( @@ -27,6 +27,6 @@ object monetary { max: size_t, locale: locale_t, format: CString, - vargs: Any* + vargs: Double* ): ssize_t = extern } From a9d0324b2622da78bc9faf73f3d6f1fe17d05170 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 16 Mar 2023 18:15:43 +0100 Subject: [PATCH 49/61] Towards ScalaNative 0.4.12 --- docs/changelog/0.4.11.md | 8 ++++---- docs/conf.py | 4 ++-- nir/src/main/scala/scala/scalanative/nir/Versions.scala | 2 +- project/Settings.scala | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/changelog/0.4.11.md b/docs/changelog/0.4.11.md index 6a7ce6d302..fbdd56b0d2 100644 --- a/docs/changelog/0.4.11.md +++ b/docs/changelog/0.4.11.md @@ -7,10 +7,6 @@ We're happy to announce the release of Scala Native. It's the next maintenance r ### Extern methods with a variadic number of arguments For a long time, Scala Native supported C `va_list` using `scalanative.unsafe.CVarArgList`. This allowed for interop with some of the C functions taking the variadic number of arguments. This release makes usage and definition of them easier, by restoring support for idiomatic ways of passing them using Scala variadic arguments lists. - -### Support for LLVM 15 -The latest versions of LLVM added a new internal representation of pointers - their opaque variant replaces typed pointers. This change should not affect most of the users, but in some specific builds it could have lead to linking issues. -Now Scala Native will try to detect version of LLVM toolchain. When using LLVM 15 or newer Scala Native toolchain would always generate opaque pointers in the compiled LLVM IR. ```c void printf(char* format, ...); ``` @@ -23,6 +19,10 @@ void printf(char* format, ...); printf("String '%s' is allocated at %p and has %d characters\n", msg, msg, strlen(msg)) ``` +### Support for LLVM 15 +The latest versions of LLVM added a new internal representation of pointers - their opaque variant replaces typed pointers. This change should not affect most of the users, but in some specific builds it could have lead to linking issues. +Now Scala Native will try to detect version of LLVM toolchain. When using LLVM 15 or newer Scala Native toolchain would always generate opaque pointers in the compiled LLVM IR. + The Scala standard library used by this release is based on the following versions:
diff --git a/docs/conf.py b/docs/conf.py index 4f25867275..00736a84e0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -69,9 +69,9 @@ def generateScalaNativeCurrentYear(): # built documents. # # The short X.Y version. -version = u'0.4.11' +version = u'0.4.12-SNAPSHOT' # The full version, including alpha/beta/rc tags. -release = u'0.4.11' +release = u'0.4.12-SNAPSHOT' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/nir/src/main/scala/scala/scalanative/nir/Versions.scala b/nir/src/main/scala/scala/scalanative/nir/Versions.scala index f8f852023d..75f553d9e6 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Versions.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Versions.scala @@ -25,7 +25,7 @@ object Versions { final val revision: Int = 9 // a.k.a. MINOR version /* Current public release version of Scala Native. */ - final val current: String = "0.4.11" + final val current: String = "0.4.12-SNAPSHOT" final val currentBinaryVersion: String = binaryVersion(current) private object FullVersion { diff --git a/project/Settings.scala b/project/Settings.scala index b8a9f17169..f032c93c49 100644 --- a/project/Settings.scala +++ b/project/Settings.scala @@ -178,7 +178,7 @@ object Settings { ), mimaPreviousArtifacts ++= { // The previous releases of Scala Native with which this version is binary compatible. - val binCompatVersions = (0 to 10).map(v => s"0.4.$v").toSet + val binCompatVersions = (0 to 11).map(v => s"0.4.$v").toSet val toolsProjects = Set("util", "tools", "nir", "test-runner") lazy val neverPublishedProjects040 = Map( "2.11" -> (toolsProjects ++ Set("windowslib", "scala3lib")), From feaf551bbc3a4de87f229f6c0566283ac59bdb29 Mon Sep 17 00:00:00 2001 From: Eric K Richardson Date: Fri, 17 Mar 2023 01:58:32 -0700 Subject: [PATCH 50/61] Update sbt to 1.5.8 for scripted tests (#3227) * Update sbt to 1.5.8 for scripted tests * Run scalafmt --- docs/user/setup.rst | 2 +- project/Commands.scala | 7 +------ project/ScalaVersions.scala | 3 ++- scripted-tests/run/hello-scala-app/build.sbt | 10 ---------- 4 files changed, 4 insertions(+), 18 deletions(-) diff --git a/docs/user/setup.rst b/docs/user/setup.rst index 7e7ea3eac7..f61addc016 100644 --- a/docs/user/setup.rst +++ b/docs/user/setup.rst @@ -6,7 +6,7 @@ Environment setup Scala Native has the following build dependencies: * Java 8 or newer -* sbt 1.1.6 or newer +* sbt 1.5.8 or newer * LLVM/Clang 6.0 or newer And following completely optional runtime library dependencies: diff --git a/project/Commands.scala b/project/Commands.scala index 9d7dd15f84..1c1baee171 100644 --- a/project/Commands.scala +++ b/project/Commands.scala @@ -81,19 +81,14 @@ object Commands { | "-Dscala.version=$version" :+ | "-Dscala213.version=${ScalaVersions.scala213}" |}""".stripMargin - // Scala 3 is supported since sbt 1.5.0 + // Scala 3 is supported since sbt 1.5.0. 1.5.8 is used. // Older versions set incorrect binary version val isScala3 = version.startsWith("3.") - val overrideSbtVersion = - if (isScala3) - """set sbtScalaNative/sbtVersion := "1.5.0" """ :: Nil - else Nil val scalaVersionTests = if (isScala3) "scala3/*" else "" setScriptedLaunchOpts :: - overrideSbtVersion ::: s"sbtScalaNative/scripted ${scalaVersionTests} run/*" :: state } diff --git a/project/ScalaVersions.scala b/project/ScalaVersions.scala index 5d1445562d..1094487697 100644 --- a/project/ScalaVersions.scala +++ b/project/ScalaVersions.scala @@ -18,7 +18,8 @@ object ScalaVersions { val scala213: String = crossScala213.last val scala3: String = "3.1.3" - val sbt10Version: String = "1.1.6" // minimum version + // minimum version - 1.5 is required for Scala 3 and 1.5.8 has log4j vulnerability fixed + val sbt10Version: String = "1.5.8" val sbt10ScalaVersion: String = scala212 val libCrossScalaVersions: Seq[String] = diff --git a/scripted-tests/run/hello-scala-app/build.sbt b/scripted-tests/run/hello-scala-app/build.sbt index f8e01b94ff..9bd1d8398e 100644 --- a/scripted-tests/run/hello-scala-app/build.sbt +++ b/scripted-tests/run/hello-scala-app/build.sbt @@ -9,13 +9,3 @@ scalaVersion := { ) else scalaVersion } - -// Old versions of sbt (like 1.1.6 which is being used) don't include -// Scala version specific directiories and has problem with finding files in them -Compile / sources ++= { - CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, _)) => - sourceDirectory.value / "main" / "scala-2" / "HelloScalaApp.scala" :: Nil - case _ => Nil - } -} From 14154c3930bcd91d462e8a73183fe167688fbbb8 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Fri, 17 Mar 2023 11:55:26 +0100 Subject: [PATCH 51/61] Improve resolving repeated parameters in Scala3 (#3230) --- .../scala/scalanative/nscplugin/NirGenType.scala | 15 +++------------ .../scala/scala/scalanative/NIRCompilerTest.scala | 11 +++++++++++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala index eddf59b43f..d6c236a7dd 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala @@ -261,27 +261,18 @@ trait NirGenType(using Context) { import core.Phases._ val repeatedParams = if (sym.isExtern) { atPhase(typerPhase) { - sym.paramInfo match { + sym.paramInfo.stripPoly match { // @extern def foo(a: Int): Int case MethodTpe(paramNames, paramTypes, _) => for (name, tpe) <- paramNames zip paramTypes yield name -> tpe.isRepeatedParam - // @extern def foo[T](ptr: Ptr[T]): Int - case PolyType(_, MethodTpe(paramNames, paramTypes, _)) => - for (name, tpe) <- paramNames zip paramTypes - yield name -> tpe.isRepeatedParam - // @extern def foo: Int - case ExprType(_) => Nil - // @extern var foo: Int - case TypeRef(_, _) => Nil - // @extern def foo: Ptr[Int] - case AppliedType(_, _) => Nil - case _ => + case t if t.isVarArgsMethod => report.warning( "Unable to resolve method sig params for symbol, extern VarArgs would not work", sym.srcPos ) Nil + case _ => Nil } }.toMap } else Map.empty diff --git a/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala b/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala index f54fa91e67..9847921834 100644 --- a/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala +++ b/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala @@ -320,4 +320,15 @@ class NIRCompilerTest extends AnyFlatSpec with Matchers with Inspectors { }.getMessage should include(CannotExportField) } + // https://github.com/scala-native/scala-native/issues/3228 + it should "allow to define fields in extern object" in NIRCompiler(_.compile { + """ + |import scala.scalanative.unsafe._ + | + |@extern + |object Foo { + | final val bar = 42 + |}""".stripMargin + }) + } From f608911082a555f5e81ae88a59e20597b2bd1176 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Fri, 17 Mar 2023 16:44:04 +0100 Subject: [PATCH 52/61] Fix regression in handling opaque pointers on Windows (#3226) * Test different LLVM versions on Windows * Try fix regression in Windows opaque pointers code gen * Make LLVM tests executed only in extended builds --- .github/actions/windows-setup-env/action.yml | 5 +++- .github/workflows/run-tests-windows.yml | 29 ++++++++++++++++++- .../codegen/compat/os/WindowsCompat.scala | 7 +++-- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/.github/actions/windows-setup-env/action.yml b/.github/actions/windows-setup-env/action.yml index c0d217a7f3..515b71190d 100644 --- a/.github/actions/windows-setup-env/action.yml +++ b/.github/actions/windows-setup-env/action.yml @@ -7,6 +7,9 @@ inputs: java-version: description: "Java version to use in tests" default: "8" + llvm-version: + description: "LLVM version to use" + default: "11.0.0" outputs: vcpkg-dir: description: "Directory containing installed libraries" @@ -63,7 +66,7 @@ runs: # Choco-Install is GH Actions wrappers around choco, which does retries - name: Install LLVM shell: pwsh - run: Choco-Install -PackageName llvm -ArgumentList "--version=11.0.0", "--allow-downgrade" + run: Choco-Install -PackageName llvm -ArgumentList "--version=${{ inputs.llvm-version }}", "--allow-downgrade", "--force" - name: Add LLVM on Path shell: pwsh diff --git a/.github/workflows/run-tests-windows.yml b/.github/workflows/run-tests-windows.yml index eb3feeaa57..179b3ea894 100644 --- a/.github/workflows/run-tests-windows.yml +++ b/.github/workflows/run-tests-windows.yml @@ -7,7 +7,7 @@ on: - main - 0.4.x concurrency: - group: windows-${{ github.head_ref }} + group: windows-${{ github.head_ref }}-${{ github.event_name }} cancel-in-progress: true jobs: @@ -132,3 +132,30 @@ jobs: tests${{env.project-version}}/test testsExt${{env.project-version}}/test shell: cmd + + test-llvm-versions: + runs-on: windows-2019 + strategy: + fail-fast: false + matrix: + scala: [3.2.2] + llvm: ["13.0.1", "14.0.6", "15.0.7"] # Last 3 stable versions + steps: + - name: Setup git config + run: git config --global core.autocrlf false + - uses: actions/checkout@v3 + - uses: ./.github/actions/windows-setup-env + id: setup + with: + scala-version: ${{matrix.scala}} + llvm-version: ${{ matrix.llvm }} + java-version: 8 + + - name: Run tests + shell: cmd + run: > + set SCALANATIVE_INCLUDE_DIRS=${{steps.setup.outputs.vcpkg-dir}}\include& + set SCALANATIVE_LIB_DIRS=${{steps.setup.outputs.vcpkg-dir}}\lib& + set SCALANATIVE_CI_NO_DEBUG_SYMBOLS=true& + set SCALANATIVE & + sbt ++${{matrix.scala}} "show tests3/nativeConfig" "test-runtime ${{matrix.scala}}" diff --git a/tools/src/main/scala/scala/scalanative/codegen/compat/os/WindowsCompat.scala b/tools/src/main/scala/scala/scalanative/codegen/compat/os/WindowsCompat.scala index deb6df087e..eb6d342c74 100644 --- a/tools/src/main/scala/scala/scalanative/codegen/compat/os/WindowsCompat.scala +++ b/tools/src/main/scala/scala/scalanative/codegen/compat/os/WindowsCompat.scala @@ -30,11 +30,14 @@ private[codegen] class WindowsCompat(protected val codegen: AbstractCodeGen) override def genPrelude()(implicit sb: ShowBuilder): Unit = { import sb._ def PtrRef = if (useOpaquePointers) ptrT else s"$ptrT*" - line(s"declare i32 @llvm.eh.typeid.for($ptrT*)") + line(s"declare i32 @llvm.eh.typeid.for($ptrT)") line(s"declare i32 $osPersonalityType(...)") line(s"$typeDescriptor = type { $PtrRef, $ptrT, [35 x i8] }") line(s"%$stdExceptionData = type { $ptrT, i8 }") - line(s"%$stdExceptionClass = type { i32 (...)**, %$stdExceptionData }") + if (useOpaquePointers) + line(s"%$stdExceptionClass = type { $ptrT, %$stdExceptionData }") + else + line(s"%$stdExceptionClass = type { i32 (...)**, %$stdExceptionData }") line(s"$ehClass = type { %$stdExceptionClass, $ptrT }") line(s"@$typeInfo = external constant $ptrT") line(s"$$$ehWrapperTy = comdat any") From b698c575a36a5231503315ef0a80af88a29e5ce3 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Mon, 20 Mar 2023 21:08:12 +0100 Subject: [PATCH 53/61] Redefine exported extern methods using varargs instead calling original method (#3232) --- .../nscplugin/PrepNativeInterop.scala | 89 +++++++++++++++++-- .../scala/scalnative/IssuesTestScala3.scala | 13 +++ 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/PrepNativeInterop.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/PrepNativeInterop.scala index c910aec8de..a1d65a4773 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/PrepNativeInterop.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/PrepNativeInterop.scala @@ -4,7 +4,7 @@ import dotty.tools.dotc.plugins.PluginPhase import dotty.tools._ import dotc._ import dotc.ast.tpd._ -import dotc.transform.SymUtils.setter +import dotc.transform.SymUtils._ import core.Contexts._ import core.Definitions import core.Names._ @@ -12,8 +12,8 @@ import core.Symbols._ import core.Types._ import core.StdNames._ import core.Constants.Constant +import core.Flags._ import NirGenUtil.ContextCached -import dotty.tools.dotc.core.Flags /** This phase does: * - Rewrite calls to scala.Enumeration.Value (include name string) (Ported @@ -37,12 +37,87 @@ class PrepNativeInterop extends PluginPhase { dd.symbol.isWrappedToplevelDef } + extension (sym: Symbol) + def isTraitOrInterface(using Context): Boolean = + sym.is(Trait) || sym.isAllOf(JavaInterface) + + def isScalaModule(using Context): Boolean = + sym.is(ModuleClass, butNot = Lifted) + + def isExtern(using Context): Boolean = sym.exists && { + sym.owner.isExternType || + sym.hasAnnotation(defnNir.ExternClass) || + (sym.is(Accessor) && sym.field.isExtern) + } + + def isExternType(using Context): Boolean = + (isScalaModule || sym.isTraitOrInterface) && + sym.hasAnnotation(defnNir.ExternClass) + end extension + + private class DealiasTypeMapper(using Context) extends TypeMap { + override def apply(tp: Type): Type = + val sym = tp.typeSymbol + val dealiased = + if sym.isOpaqueAlias then sym.opaqueAlias + else tp + dealiased.widenDealias match + case AppliedType(tycon, args) => + AppliedType(this(tycon), args.map(this)) + case ty => ty + } + + override def transformTypeApply(tree: TypeApply)(using Context): Tree = { + val TypeApply(fun, tArgs) = tree + val defnNir = this.defnNir + def dealiasTypeMapper = DealiasTypeMapper() + + // sizeOf[T] -> sizeOf(classOf[T]) + fun.symbol match + case defnNir.Intrinsics_sizeOfType => + val tpe = dealiasTypeMapper(tArgs.head.tpe) + cpy + .Apply(tree)( + ref(defnNir.Intrinsics_sizeOf), + List(Literal(Constant(tpe))) + ) + .withAttachment(NirDefinitions.NonErasedType, tpe) + + case defnNir.Intrinsics_alignmentOfType => + val tpe = dealiasTypeMapper(tArgs.head.tpe) + cpy + .Apply(tree)( + ref(defnNir.Intrinsics_alignmentOf), + List(Literal(Constant(tpe))) + ) + .withAttachment(NirDefinitions.NonErasedType, tpe) + + case _ => tree + } + override def transformDefDef(dd: DefDef)(using Context): Tree = { + val sym = dd.symbol + lazy val rhsSym = dd.rhs.symbol // Set `@extern` annotation for top-level extern functions - if (isTopLevelExtern(dd) && !dd.symbol.hasAnnotation(defnNir.ExternClass)) { - dd.symbol.addAnnotation(defnNir.ExternClass) + if (isTopLevelExtern(dd) && !sym.hasAnnotation(defnNir.ExternClass)) { + sym.addAnnotation(defnNir.ExternClass) + } + + def usesVariadicArgs = sym.paramInfo.stripPoly match { + case MethodTpe(paramNames, paramTypes, _) => + paramTypes.exists(param => param.isRepeatedParam) + case t => t.isVarArgsMethod } - dd + + if sym.is(Exported) && rhsSym.isExtern && usesVariadicArgs + then + // Externs with varargs need to be called directly, replace proxy + // with redifintion of extern method + // from: def foo(args: Any*): Unit = origin.foo(args) + // into: def foo(args: Any*): Unit = extern + sym.addAnnotation(defnNir.ExternClass) + cpy.DefDef(dd)(rhs = ref(defnNir.UnsafePackage_extern)) + else dd } override def transformValDef(vd: ValDef)(using Context): Tree = { @@ -63,9 +138,7 @@ class PrepNativeInterop extends PluginPhase { if (isTopLevelExtern(vd) && !sym.hasAnnotation(defnNir.ExternClass)) { sym.addAnnotation(defnNir.ExternClass) - if (vd.symbol.is( - Flags.Mutable - )) { + if (vd.symbol.is(Mutable)) { sym.setter.addAnnotation(defnNir.ExternClass) } } diff --git a/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala b/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala index 35a39c2af2..e7ccf802c6 100644 --- a/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala +++ b/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala @@ -23,6 +23,19 @@ class IssuesTestScala3 { // Check links (!ctx).text_width = CFuncPtr2.fromScalaFunction { (_, _) => 0 } } + + @Test def i3231(): Unit = { + @extern object extern_functions: + @name("sprintf") + def test(buffer: CString, format: CString, args: Any*): Unit = extern + + object functions: + export extern_functions.test // should compile + + val buff: Ptr[CChar] = stackalloc[CChar](128.toUSize) + functions.test(buff, c"%d %d %d", -1, 1, 42) + assertEquals("-1 1 42", fromCString(buff)) + } } object issue2485: From 9aecf1eddefde70f5e8545ff4a68ec06f040b1c1 Mon Sep 17 00:00:00 2001 From: LeeTibbert Date: Wed, 22 Mar 2023 04:26:08 -0400 Subject: [PATCH 54/61] Implement posixlib dlfcn (#3234) Open Group dlfcn.h is now implemented in posixlib. The key methods in this file are dlopen and dlsym. These allow dynamically loading symbols from shareable libraries. Given the right kind of symbols, the symbol can then be executed. --- docs/lib/posixlib.rst | 3 +- .../src/main/resources/scala-native/dlfcn.c | 14 ++ .../scala/scala/scalanative/posix/dlfcn.scala | 40 ++++++ .../testsuite/posixlib/DlfcnTest.scala | 123 ++++++++++++++++++ 4 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 posixlib/src/main/resources/scala-native/dlfcn.c create mode 100644 posixlib/src/main/scala/scala/scalanative/posix/dlfcn.scala create mode 100644 unit-tests/native/src/test/scala/org/scalanative/testsuite/posixlib/DlfcnTest.scala diff --git a/docs/lib/posixlib.rst b/docs/lib/posixlib.rst index b1861b5374..198f4e9311 100644 --- a/docs/lib/posixlib.rst +++ b/docs/lib/posixlib.rst @@ -16,7 +16,7 @@ C Header Scala Native Module `cpio.h`_ scala.scalanative.posix.cpio_ `ctype.h`_ scala.scalanative.libc.ctype_ `dirent.h`_ scala.scalanative.posix.dirent_ -`dlfcn.h`_ N/A +`dlfcn.h`_ scala.scalanative.posix.dlfcn_ `errno.h`_ scala.scalanative.posix.errno_ `fcntl.h`_ scala.scalanative.posix.fcntl_ `fenv.h`_ N/A @@ -183,6 +183,7 @@ C Header Scala Native Module .. _scala.scalanative.libc.ctype: https://github.com/scala-native/scala-native/blob/main/clib/src/main/scala/scala/scalanative/libc/ctype.scala .. _scala.scalanative.posix.cpio: https://github.com/scala-native/scala-native/blob/main/posixlib/src/main/scala/scala/scalanative/posix/cpio.scala .. _scala.scalanative.posix.dirent: https://github.com/scala-native/scala-native/blob/main/posixlib/src/main/scala/scala/scalanative/posix/dirent.scala +.. _scala.scalanative.posix.dlfcn: https://github.com/scala-native/scala-native/blob/main/posixlib/src/main/scala/scala/scalanative/posix/dlfcn.scala .. _scala.scalanative.posix.errno: https://github.com/scala-native/scala-native/blob/main/posixlib/src/main/scala/scala/scalanative/posix/errno.scala .. _scala.scalanative.posix.fcntl: https://github.com/scala-native/scala-native/blob/main/posixlib/src/main/scala/scala/scalanative/posix/fcntl.scala .. _scala.scalanative.libc.float: https://github.com/scala-native/scala-native/blob/main/clib/src/main/scala/scala/scalanative/libc/float.scala diff --git a/posixlib/src/main/resources/scala-native/dlfcn.c b/posixlib/src/main/resources/scala-native/dlfcn.c new file mode 100644 index 0000000000..34f34afd32 --- /dev/null +++ b/posixlib/src/main/resources/scala-native/dlfcn.c @@ -0,0 +1,14 @@ +#if defined(__unix__) || defined(__unix) || defined(unix) || \ + (defined(__APPLE__) && defined(__MACH__)) + +#include + +int scalanative_rtld_lazy() { return RTLD_LAZY; }; + +int scalanative_rtld_now() { return RTLD_NOW; }; + +int scalanative_rtld_global() { return RTLD_GLOBAL; }; + +int scalanative_rtld_local() { return RTLD_LOCAL; }; + +#endif // Unix or Mac OS diff --git a/posixlib/src/main/scala/scala/scalanative/posix/dlfcn.scala b/posixlib/src/main/scala/scala/scalanative/posix/dlfcn.scala new file mode 100644 index 0000000000..031f0dd39b --- /dev/null +++ b/posixlib/src/main/scala/scala/scalanative/posix/dlfcn.scala @@ -0,0 +1,40 @@ +package scala.scalanative +package posix + +import scala.scalanative.unsafe._ + +/** POSIX dlfcn.h for Scala + * + * The Open Group Base Specifications + * [[https://pubs.opengroup.org/onlinepubs/9699919799 Issue 7, 2018]] edition. + */ + +@link("dl") +@extern object dlfcn { + +// Symbolic constants + + @name("scalanative_rtld_lazy") + def RTLD_LAZY: CInt = extern + + @name("scalanative_rtld_now") + def RTLD_NOW: CInt = extern + + @name("scalanative_rtld_global") + def RTLD_GLOBAL: CInt = extern + + @name("scalanative_rtld_local") + def RTLD_LOCAL: CInt = extern + +// Methods + + // Convention: A C "void *" is represented in Scala Native as a "Ptr[Byte]". + + def dlclose(handle: Ptr[Byte]): Int = extern + + def dlerror(): CString = extern + + def dlopen(filename: CString, flags: Int): Ptr[Byte] = extern + + def dlsym(handle: Ptr[Byte], symbol: CString): Ptr[Byte] = extern +} diff --git a/unit-tests/native/src/test/scala/org/scalanative/testsuite/posixlib/DlfcnTest.scala b/unit-tests/native/src/test/scala/org/scalanative/testsuite/posixlib/DlfcnTest.scala new file mode 100644 index 0000000000..3f6e7a8ac2 --- /dev/null +++ b/unit-tests/native/src/test/scala/org/scalanative/testsuite/posixlib/DlfcnTest.scala @@ -0,0 +1,123 @@ +package org.scalanative.testsuite.posixlib + +import org.junit.Test +import org.junit.Assert._ +import org.junit.Assume._ + +import scala.scalanative.meta.LinktimeInfo.{isLinux, isMac} +import scala.scalanative.runtime.PlatformExt + +import java.io.File + +import scala.scalanative.unsafe._ + +import scala.scalanative.posix.dlfcn._ + +class DlfcnTest { + + /* Dlfcn is tested on Linux and macOS. + * With some additional work, it could be tested on FreeBSD. + * One would have to find a suitable "known" .so file and, + * possibly, adjust the message returned by dlerror(). + */ + + @Test def dlfcnOpensAndObtainsSymbolAddressLinux(): Unit = { + if (isLinux) Zone { implicit z => + val soFilePrefix = + if (is32BitPlatform) + "/lib/i386-linux-gnu/" + else if (PlatformExt.isArm64) + "/usr/lib/aarch64-linux-gnu" + else + "/lib/x86_64-linux-gnu" + + val soFile = s"${soFilePrefix}/libc.so.6" + + /* Ensure the file exists before trying to "dlopen()" it. + * Someday the ".so.6" suffix is going to change to ".so.7" or such. + * When it does do a "soft failure", rather than failing the entire + * build. + */ + assumeTrue( + s"shared library ${soFile} not found", + (new File(soFile)).exists() + ) + + val handle = dlopen(toCString(soFile), RTLD_LAZY | RTLD_LOCAL) + assertNotNull(s"dlopen of ${soFile} failed", handle) + + try { + val symbol = "strlen" + val symbolC = toCString(symbol) + + val cFunc = dlsym(handle, symbolC) + assertNotNull(s"dlsym lookup of '${symbol}' failed", cFunc) + + // Have symbol, does it function (sic)? + type StringLengthFn = CFuncPtr1[CString, Int] + val func: StringLengthFn = CFuncPtr.fromPtr[StringLengthFn](cFunc) + + assertEquals( + s"executing symbol '${symbol}' failed", + symbol.length(), + func(symbolC) + ) + + val missingSymbol = "NOT_IN_LIBC" + + val func2 = dlsym(handle, toCString(missingSymbol)) + assertNull(s"dlsym lookup of ${symbol} should have failed", func2) + + val msg = fromCString(dlerror()) + // It is always chancy trying to match exact text. Go for suffix here. + assertTrue( + s"dlerror returned msg: |${msg}|", + msg.endsWith(s"undefined symbol: ${missingSymbol}") + ) + } finally { + dlclose(handle) + } + } + } + + @Test def dlfcnOpensAndObtainsSymbolAddressMacOs(): Unit = { + if (isMac) Zone { implicit z => + val soFile = "/usr/lib/libSystem.dylib" + + val handle = dlopen(toCString(soFile), RTLD_LAZY | RTLD_LOCAL) + assertNotNull(s"dlopen of ${soFile} failed", handle) + + try { + val symbol = "strlen" + val symbolC = toCString(symbol) + + val cFunc = dlsym(handle, symbolC) + assertNotNull(s"dlsym lookup of '${symbol}' failed", cFunc) + + // Have symbol, does it function (sic)? + type StringLengthFn = CFuncPtr1[CString, Int] + val func: StringLengthFn = CFuncPtr.fromPtr[StringLengthFn](cFunc) + + assertEquals( + s"executing symbol '${symbol}' failed", + symbol.length(), + func(symbolC) + ) + + val missingSymbol = "NOT_IN_LIBC" + + val func2 = dlsym(handle, toCString(missingSymbol)) + assertNull(s"dlsym lookup of ${symbol} should have failed", func2) + + val msg = fromCString(dlerror()) + // It is always chancy trying to match exact text. Go for suffix here. + assertTrue( + s"dlerror returned msg: |${msg}|", + msg.endsWith(s"${missingSymbol}): symbol not found") + ) + } finally { + dlclose(handle) + } + } + } +} From 8c3f2e0e6010f9d204d7ee813525014384c7ebf3 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Sat, 12 Nov 2022 18:47:37 +0100 Subject: [PATCH 55/61] Allow to compose extern definitions using `@extern trait` (#2988) * Allow to compose extern traits * Define posix errno and signal as extension of libc defintions * Rename isExternModule to isExternType * cleanup * Fix Scala version dependent tests * Fix failing Junit bootstrappers casting * [skip ci] Cleanup --- .../scalanative/nscplugin/NirGenExports.scala | 2 +- .../scalanative/nscplugin/NirGenExpr.scala | 16 +- .../scalanative/nscplugin/NirGenName.scala | 9 +- .../scalanative/nscplugin/NirGenStat.scala | 198 +++++++++++++----- .../scalanative/nscplugin/NirGenType.scala | 7 +- .../scalanative/nscplugin/NirGenStat.scala | 154 ++++++++++---- .../scalanative/nscplugin/NirGenType.scala | 7 +- project/Settings.scala | 14 +- .../scala/scalanative/NIRCompilerTest.scala | 100 +++++++++ .../scala-2.12+/scala/Issues212PlusTest.scala | 0 10 files changed, 391 insertions(+), 116 deletions(-) rename unit-tests/{native => shared}/src/test/scala-2.12+/scala/Issues212PlusTest.scala (100%) diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExports.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExports.scala index 4af74306c6..164d8e4e17 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExports.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExports.scala @@ -28,7 +28,7 @@ trait NirGenExports[G <: nsc.Global with Singleton] { for { member <- owner.info.members if isExported(member) - if !owner.isExternModule + if !owner.isExternType // Externs combined with exports are not allowed, exception is handled in externs exported <- if (owner.isScalaModule) genModuleMember(owner, member) diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala index a334d5c647..552bd4153a 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenExpr.scala @@ -642,7 +642,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => } else { val ty = genType(tree.symbol.tpe) val name = genFieldName(tree.symbol) - if (sym.owner.isExternModule) { + if (sym.owner.isExternType) { val externTy = genExternType(tree.symbol.tpe) genLoadExtern(ty, externTy, tree.symbol) } else { @@ -669,7 +669,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => val qual = genExpr(qualp) val rhs = genExpr(rhsp) val name = genFieldName(sym) - if (sym.owner.isExternModule) { + if (sym.owner.isExternType) { val externTy = genExternType(sym.tpe) genStoreExtern(externTy, sym, rhs) } else { @@ -2300,7 +2300,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => selfp: Tree, argsp: Seq[Tree] )(implicit pos: nir.Position): Val = { - if (sym.owner.isExternModule && sym.isAccessor) { + if (sym.owner.isExternType && sym.isAccessor) { genApplyExternAccessor(sym, argsp) } else if (sym.isStaticMember) { genApplyStaticMethod(sym, selfp, argsp) @@ -2315,7 +2315,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => receiver: Tree, argsp: Seq[Tree] )(implicit pos: nir.Position): Val = { - require(!sym.owner.isExternModule, sym.owner) + require(!sym.owner.isExternType, sym.owner) val name = genStaticMemberName(sym, receiver.symbol) val method = Val.Global(name, nir.Type.Ptr) val sig = genMethodSig(sym) @@ -2340,7 +2340,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => def genLoadExtern(ty: nir.Type, externTy: nir.Type, sym: Symbol)(implicit pos: nir.Position ): Val = { - assert(sym.owner.isExternModule, "loadExtern was not extern") + assert(sym.owner.isExternType, "loadExtern was not extern") val name = Val.Global(genName(sym), Type.Ptr) @@ -2350,7 +2350,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => def genStoreExtern(externTy: nir.Type, sym: Symbol, value: Val)(implicit pos: nir.Position ): Val = { - assert(sym.owner.isExternModule, "storeExtern was not extern") + assert(sym.owner.isExternType, "storeExtern was not extern") val name = Val.Global(genName(sym), Type.Ptr) val externValue = toExtern(externTy, value) @@ -2390,7 +2390,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => val owner = sym.owner val name = genMethodName(sym) val origSig = genMethodSig(sym) - val isExtern = owner.isExternModule + val isExtern = owner.isExternType val sig = if (isExtern) { genExternMethodSig(sym) @@ -2420,7 +2420,7 @@ trait NirGenExpr[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => } def genMethodArgs(sym: Symbol, argsp: Seq[Tree]): Seq[Val] = - if (sym.owner.isExternModule) genExternMethodArgs(sym, argsp) + if (sym.owner.isExternType) genExternMethodArgs(sym, argsp) else genSimpleArgs(argsp) private def genSimpleArgs(argsp: Seq[Tree]): Seq[Val] = argsp.map(genExpr) diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenName.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenName.scala index 69be4fb373..0875e4d4c0 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenName.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenName.scala @@ -75,7 +75,7 @@ trait NirGenName[G <: Global with Singleton] { } owner.member { - if (sym.owner.isExternModule) { + if (sym.owner.isExternType) { nir.Sig.Extern(id) } else { nir.Sig.Field(id, scope) @@ -97,9 +97,12 @@ trait NirGenName[G <: Global with Singleton] { val paramTypes = tpe.params.toSeq.map(p => genType(p.info)) + def isExtern = + sym.owner.isExternType || implClassTarget(sym.owner).isExternType + if (sym == String_+) genMethodName(StringConcatMethod) - else if (sym.owner.isExternModule) + else if (isExtern) owner.member(genExternSigImpl(sym, id)) else if (sym.name == nme.CONSTRUCTOR) owner.member(nir.Sig.Ctor(paramTypes)) @@ -157,7 +160,7 @@ trait NirGenName[G <: Global with Singleton] { private def nativeIdOf(sym: Symbol): String = { sym.getAnnotation(NameClass).flatMap(_.stringArg(0)).getOrElse { val name = sym.javaSimpleName.toString() - val id: String = if (sym.owner.isExternModule) { + val id: String = if (sym.owner.isExternType) { // Don't use encoded names for externs sym.decodedName.trim() } else if (sym.isField) { diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala index 49722c2878..aca947ff7a 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala @@ -88,11 +88,17 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => def nonEmpty = buf.nonEmpty def genClass(cd: ClassDef): Unit = { + val sym = cd.symbol + // ImplClass does not copy annotations from the trait + if (isImplClass(sym) && implClassTarget(sym).isExternType) { + sym.addAnnotation(ExternClass) + } + scoped( - curClassSym := cd.symbol, + curClassSym := sym, curClassFresh := nir.Fresh() ) { - if (cd.symbol.isStruct) genStruct(cd) + if (sym.isStruct) genStruct(cd) else genNormalClass(cd) } } @@ -118,30 +124,35 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => def traits = genClassInterfaces(sym) implicit val pos: nir.Position = cd.pos + + buf += { + if (sym.isScalaModule) Defn.Module(attrs, name, parent, traits) + else if (sym.isTraitOrInterface) Defn.Trait(attrs, name, traits) + else Defn.Class(attrs, name, parent, traits) + } + genReflectiveInstantiation(cd) genClassFields(cd) genMethods(cd) genMirrorClass(cd) + } - buf += { - if (sym.isScalaModule) { - Defn.Module(attrs, name, parent, traits) - } else if (sym.isTraitOrInterface) { - Defn.Trait(attrs, name, traits) - } else { - Defn.Class(attrs, name, parent, traits) - } + def genClassParent(sym: Symbol): Option[nir.Global] = { + if (sym.isExternType && + sym.superClass != ObjectClass && + !isImplClass(sym)) { + reporter.error( + sym.pos, + s"Extern object can only extend extern traits" + ) } - } - def genClassParent(sym: Symbol): Option[nir.Global] = - if (sym == NObjectClass) { - None - } else if (sym.superClass == NoSymbol || sym.superClass == ObjectClass) { + if (sym == NObjectClass) None + else if (sym.superClass == NoSymbol || sym.superClass == ObjectClass) Some(genTypeName(NObjectClass)) - } else { + else Some(genTypeName(sym.superClass)) - } + } def genClassAttrs(cd: ClassDef): Attrs = { val sym = cd.symbol @@ -160,17 +171,32 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => Attrs.fromSeq(annotationAttrs ++ abstractAttr) } - def genClassInterfaces(sym: Symbol) = - for { - parent <- sym.info.parents - psym = parent.typeSymbol if psym.isTraitOrInterface - } yield { - genTypeName(psym) + def genClassInterfaces(sym: Symbol) = { + val isExtern = sym.isExternType + def validate(psym: Symbol) = { + val parentIsExtern = psym.isExternType + if (isExtern && !parentIsExtern) + reporter.error( + sym.pos, + "Extern object can only extend extern traits" + ) + if (!isExtern && parentIsExtern) + reporter.error( + psym.pos, + "Extern traits can be only mixed with extern traits or objects" + ) } + for { + parent <- sym.parentSymbols + psym = parent.info.typeSymbol if psym.isTraitOrInterface + _ = validate(psym) + } yield genTypeName(psym) + } + def genClassFields(cd: ClassDef): Unit = { val sym = cd.symbol - val attrs = nir.Attrs(isExtern = sym.isExternModule) + val attrs = nir.Attrs(isExtern = sym.isExternType) for (f <- sym.info.decls if !f.isMethod && f.isTerm && !f.isModule) { @@ -592,14 +618,14 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => case EmptyTree => Some(Defn.Declare(attrs, name, sig)) - case _ if dd.name == nme.CONSTRUCTOR && owner.isExternModule => + case _ if dd.symbol.isConstructor && owner.isExternType => validateExternCtor(dd.rhs) None case _ if dd.name == nme.CONSTRUCTOR && owner.isStruct => None - case rhs if owner.isExternModule => + case rhs if owner.isExternType => checkExplicitReturnTypeAnnotation(dd, "extern method") genExternMethod(attrs, name, sig, dd) @@ -679,7 +705,27 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => dd: DefDef ): Option[nir.Defn] = { val rhs = dd.rhs - val defaultArgs = dd.symbol.paramss.flatten.filter(_.hasDefault) + def externMethodDecl() = { + val externAttrs = Attrs(isExtern = true) + val externSig = genExternMethodSig(curMethodSym) + val externDefn = Defn.Declare(externAttrs, name, externSig)(rhs.pos) + Some(externDefn) + } + + def isCallingExternMethod(sym: Symbol) = { + val owner = sym.owner match { + case sym if isImplClass(sym) => implClassTarget(sym) + case sym => sym + } + owner.isExternType + } + + def isExternMethodAlias(target: Symbol) = + (name, genName(target)) match { + case (Global.Member(_, lsig), Global.Member(_, rsig)) => lsig == rsig + case _ => false + } + rhs match { case _ if defaultArgs.nonEmpty => reporter.error( @@ -688,16 +734,23 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => ) None case Apply(ref: RefTree, Seq()) if ref.symbol == ExternMethod => - val moduleName = genTypeName(curClassSym) - val externAttrs = Attrs(isExtern = true) - val externSig = genExternMethodSig(curMethodSym) - val externDefn = Defn.Declare(externAttrs, name, externSig)(rhs.pos) - Some(externDefn) + externMethodDecl() case _ if curMethodSym.hasFlag(ACCESSOR) => None - case rhs => - global.reporter.error( + case Apply(target, _) if isCallingExternMethod(target.symbol) => + val sym = target.symbol + if (isExternMethodAlias(sym)) externMethodDecl() + else { + reporter.error( + target.pos, + "Referencing other extern symbols in not supported" + ) + None + } + + case _ => + reporter.error( rhs.pos, "methods in extern objects must have extern body" ) @@ -706,21 +759,61 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => } def validateExternCtor(rhs: Tree): Unit = { - val Block(_ +: init, _) = rhs - val externs = init.map { - case Assign(ref: RefTree, Apply(extern, Seq())) - if extern.symbol == ExternMethod => - ref.symbol - case _ => - unsupported( - "extern objects may only contain extern fields and methods" - ) - }.toSet - for { - f <- curClassSym.info.decls if f.isField - if !externs.contains(f) - } { - unsupported("extern objects may only contain extern fields") + val classSym = curClassSym.get + def isExternCall(tree: Tree): Boolean = tree match { + case Typed(target, _) => isExternCall(target) + case Apply(extern, _) => extern.symbol == ExternMethod + case _ => false + } + + def isCurClassSetter(sym: Symbol) = + sym.isSetter && { + val owner = sym.owner.tpe + owner <:< classSym.tpe || { + isImplClass(classSym) && owner <:< implClassTarget(classSym).tpe + } + } + + rhs match { + case Block(Nil, _) => () // empty mixin constructor + case Block(inits, _) => + val externs = collection.mutable.Set.empty[Symbol] + inits.foreach { + case Assign(ref: RefTree, rhs) if isExternCall(rhs) => + externs += ref.symbol + + case Apply(fun, Seq(arg)) + if isCurClassSetter(fun.symbol) && isExternCall(arg) => + externs += fun.symbol + + case Apply(target, _) if target.symbol.isConstructor => () + + case tree => + reporter.error( + rhs.pos, + "extern objects may only contain extern fields and methods" + ) + } + def isInheritedField(f: Symbol) = { + def hasFieldGetter(cls: Symbol) = f.getterIn(cls) != NoSymbol + def inheritedTraits(cls: Symbol) = + cls.parentSymbols.filter(_.isTraitOrInterface) + def inheritsField(cls: Symbol): Boolean = + hasFieldGetter(cls) || inheritedTraits(cls).exists(inheritsField) + inheritsField(classSym) + } + + // Exclude fields derived from extern trait + for (f <- curClassSym.info.decls) { + if (f.isField && !isInheritedField(f)) { + if (!(externs.contains(f) || externs.contains(f.setter))) { + reporter.error( + f.pos, + "extern objects may only contain extern fields" + ) + } + } + } } } @@ -738,8 +831,9 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => case NoOptimizeClass => Attr.NoOpt case NoSpecializeClass => Attr.NoSpecialize } + val externAttrs = if (sym.owner.isExternType) Seq(Attr.Extern) else Nil - Attrs.fromSeq(inlineAttrs ++ annotatedAttrs) + Attrs.fromSeq(inlineAttrs ++ annotatedAttrs ++ externAttrs) } def genMethodBody( @@ -900,7 +994,7 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => if (module == NoSymbol) Nil else { val moduleClass = module.moduleClass - if (moduleClass.isExternModule) Nil + if (moduleClass.isExternType) Nil else genStaticForwardersFromModuleClass(existingMembers, moduleClass) } } @@ -946,7 +1040,7 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => } m.isDeferred || m.isConstructor || m.hasAccessBoundary || - m.owner.isExternModule || + m.owner.isExternType || isOfJLObject } diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala index 426e8b64a2..3d09a2a648 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala @@ -20,10 +20,11 @@ trait NirGenType[G <: Global with Singleton] { self: NirGenPhase[G] => sym.isModuleClass && !sym.isLifted def isStaticInNIR: Boolean = - sym.owner.isExternModule || sym.isStaticMember + sym.owner.isExternType || sym.isStaticMember - def isExternModule: Boolean = - isScalaModule && sym.annotations.exists(_.symbol == ExternClass) + def isExternType: Boolean = + (isScalaModule || sym.isTraitOrInterface) && + sym.annotations.exists(_.symbol == ExternClass) def isStruct: Boolean = sym.annotations.exists(_.symbol == StructClass) diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenStat.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenStat.scala index 9560587b30..5136bbb0cb 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenStat.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenStat.scala @@ -54,11 +54,7 @@ trait NirGenStat(using Context) { val name = genTypeName(sym) def parent = genClassParent(sym) - def traits = sym.info.parents - .map(_.classSymbol) - .filter(_.isTraitOrInterface) - .map(genTypeName) - + def traits = genClassInterfaces(sym) generatedDefns += { if (sym.isStaticModule) Defn.Module(attrs, name, parent, traits) else if (sym.isTraitOrInterface) Defn.Trait(attrs, name, traits) @@ -85,16 +81,40 @@ trait NirGenStat(using Context) { } private def genClassParent(sym: ClassSymbol): Option[nir.Global] = { - if (sym == defnNir.NObjectClass) - None - else - Some { - val superClass = sym.superClass - if (superClass == NoSymbol || superClass == defn.ObjectClass) - genTypeName(defnNir.NObjectClass) - else - genTypeName(superClass) - } + if sym.isExternType && sym.superClass != defn.ObjectClass then + report.error("Extern object can only extend extern traits", sym.sourcePos) + + Option.unless(sym == defnNir.NObjectClass) { + val superClass = sym.superClass + if superClass == NoSymbol || superClass == defn.ObjectClass + then genTypeName(defnNir.NObjectClass) + else genTypeName(superClass) + } + } + + private def genClassInterfaces(sym: ClassSymbol): Seq[nir.Global] = { + val isExtern = sym.isExternType + def validate(clsSym: ClassSymbol) = { + val parentIsExtern = clsSym.isExternType + if isExtern && !parentIsExtern then + report.error( + "Extern object can only extend extern traits", + clsSym.sourcePos + ) + + if !isExtern && parentIsExtern then + report.error( + "Extern traits can be only mixed with extern traits or objects", + sym.sourcePos + ) + } + + for + sym <- sym.info.parents + clsSym = sym.classSymbol.asClass + if clsSym.isTraitOrInterface + _ = validate(clsSym) + yield genTypeName(clsSym) } private def genClassFields(td: TypeDef): Unit = { @@ -183,7 +203,7 @@ trait NirGenStat(using Context) { dd.rhs match { case EmptyTree => Some(Defn.Declare(attrs, name, sig)) - case _ if sym.isClassConstructor && sym.isExtern => + case _ if sym.isConstructor && sym.isExtern => validateExternCtor(dd.rhs) None @@ -400,6 +420,18 @@ trait NirGenStat(using Context) { val rhs = dd.rhs given nir.Position = rhs.span val defaultArgs = dd.paramss.flatten.filter(_.symbol.is(HasDefault)) + def externMethodDecl() = { + val externAttrs = Attrs(isExtern = true) + val externSig = genExternMethodSig(curMethodSym) + val externDefn = Defn.Declare(externAttrs, name, externSig) + Some(externDefn) + } + + def isExternMethodAlias(target: Symbol) = (name, genName(target)) match { + case (Global.Member(_, lsig), Global.Member(_, rsig)) => lsig == rsig + case _ => false + } + rhs match { case _ if defaultArgs.nonEmpty || dd.name.is( @@ -409,15 +441,29 @@ trait NirGenStat(using Context) { None case Apply(ref: RefTree, Seq()) if ref.symbol == defnNir.UnsafePackage_extern => - val moduleName = genTypeName(curClassSym) - val externAttrs = Attrs(isExtern = true) - val externSig = genExternMethodSig(curMethodSym) - Some(Defn.Declare(externAttrs, name, externSig)) - case _ if curMethodSym.get.isOneOf(Accessor | Synthetic) => - None - case rhs => + externMethodDecl() + + case _ if curMethodSym.get.isOneOf(Accessor | Synthetic) => None + + case Apply(target, args) if target.symbol.isExtern => + val sym = target.symbol + val Global.Member(_, selfSig) = name: @unchecked + def isExternMethodForwarder = + genExternSig(sym) == selfSig && + genExternMethodSig(sym) == origSig + + if isExternMethodForwarder then externMethodDecl() + else { + report.error( + "Referencing other extern symbols in not supported", + dd.sourcePos + ) + None + } + + case _ => report.error( - s"methods in extern objects must have extern body - ${rhs}", + s"methods in extern objects must have extern body", rhs.sourcePos ) None @@ -425,24 +471,52 @@ trait NirGenStat(using Context) { } def validateExternCtor(rhs: Tree): Unit = { - val Block(_ +: init, _) = rhs: @unchecked - val externs = init.map { - case Assign(ref: RefTree, Apply(extern, Seq())) - if extern.symbol == defnNir.UnsafePackage_extern => - ref.symbol - case _ => + val Block(exprs, _) = rhs: @unchecked + val classSym = curClassSym.get + + val externs = collection.mutable.Set.empty[Symbol] + def isExternCall(tree: Tree): Boolean = tree match + case Apply(extern, _) => + extern.symbol == defnNir.UnsafePackage_extern + case _ => false + + def isCurClassSetter(sym: Symbol) = + sym.isSetter && sym.owner.typeRef <:< classSym.typeRef + + exprs.foreach { + case Assign(ref: RefTree, rhs) if isExternCall(rhs) => + externs += ref.symbol + + case Apply(ref: RefTree, Seq(arg)) + if isCurClassSetter(ref.symbol) && isExternCall(arg) => + externs += ref.symbol + + case tree @ Apply(ref, _) if ref.symbol.isConstructor => + () + + case tree => report.error( - "extern objects may only contain extern fields and methods", + s"extern objects may only contain extern fields and methods", rhs.sourcePos ) - }.toSet - for { - f <- curClassSym.get.info.decls.toList if f.isField - if !externs.contains(f) - } report.error( - "extern objects may only contain extern fields", - f.sourcePos - ) + } + + def isInheritedField(f: Symbol) = + classSym.directlyInheritedTraits.exists { + _.info.decls.exists(_ matches f.getter) + } + + for f <- classSym.info.decls + do { + // Exclude fields derived from extern trait + if (f.isField && !isInheritedField(f)) { + if !(externs.contains(f) || externs.contains(f.setter)) then + report.error( + s"extern objects may only contain extern fields", + f.sourcePos + ) + } + } } // Static forwarders ------------------------------------------------------- @@ -499,7 +573,7 @@ trait NirGenStat(using Context) { if (!module.exists) Nil else { val moduleClass = module.moduleClass - if (moduleClass.isExternModule) Nil + if (moduleClass.isExternType) Nil else genStaticForwardersFromModuleClass(existingMembers, moduleClass) } } diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala index d6c236a7dd..d3831e6954 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenType.scala @@ -46,13 +46,14 @@ trait NirGenType(using Context) { sym.is(JavaStatic) || sym.isScalaStatic || sym.isExtern def isExtern: Boolean = sym.exists && { - sym.owner.isExternModule || + sym.owner.isExternType || sym.hasAnnotation(defnNir.ExternClass) || (sym.is(Accessor) && sym.field.isExtern) } - def isExternModule: Boolean = - isScalaModule && sym.hasAnnotation(defnNir.ExternClass) + def isExternType: Boolean = + (isScalaModule || sym.isTraitOrInterface) && + sym.hasAnnotation(defnNir.ExternClass) def isStruct: Boolean = sym.hasAnnotation(defnNir.StructClass) diff --git a/project/Settings.scala b/project/Settings.scala index f032c93c49..0f2bfa8518 100644 --- a/project/Settings.scala +++ b/project/Settings.scala @@ -415,13 +415,15 @@ object Settings { // baseDirectory = project/{native,jvm}/.{binVersion} val testsRootDir = baseDirectory.value.getParentFile.getParentFile val sharedTestsDir = testsRootDir / "shared/src/test" - def sources2_13OrAbove = sharedTestsDir / "scala-2.13+" - def sources3_2 = sharedTestsDir / "scala-3.2" + val `sources 2.12+` = Seq(sharedTestsDir / "scala-2.12+") + val `sources 2.13+` = `sources 2.12+` :+ sharedTestsDir / "scala-2.13+" + val `sources 3.2+` = `sources 2.13+` :+ sharedTestsDir / "scala-3.2" val extraSharedDirectories = - scalaVersionsDependendent(scalaVersion.value)(List.empty[File]) { - case (2, 13) => sources2_13OrAbove :: Nil - case (3, 1) => sources2_13OrAbove :: Nil - case (3, _) => sources2_13OrAbove :: sources3_2 :: Nil + scalaVersionsDependendent(scalaVersion.value)(Seq.empty[File]) { + case (2, 12) => `sources 2.12+` + case (2, 13) => `sources 2.13+` + case (3, 1) => `sources 2.13+` + case (3, _) => `sources 3.2+` } val sharedScalaSources = scalaVersionDirectories(sharedTestsDir, "scala", scalaVersion.value) diff --git a/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala b/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala index 9847921834..89fad8d59b 100644 --- a/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala +++ b/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala @@ -85,6 +85,60 @@ class NIRCompilerTest extends AnyFlatSpec with Matchers with Inspectors { "`extern` cannot be used in val definition" ) } + } + + it should "not allow members of extern object to reference other externs" in { + val code = + """import scala.scalanative.unsafe.extern + | + |@extern object Dummy { + | def foo(): Int = extern + | def bar(): Int = foo() + |} + |""".stripMargin + intercept[CompilationFailedException] { + NIRCompiler(_.compile(code)) + }.getMessage() should include( + "Referencing other extern symbols in not supported" + ) + } + + it should "allow to extend extern traits" in { + val code = + """import scala.scalanative.unsafe.extern + | + |@extern trait Dummy { + | var x: Int = extern + | def foo(): Int = extern + |} + | + |@extern trait Dummy2 extends Dummy { + | def bar(): Int = extern + |} + | + |@extern object Dummy extends Dummy + |@extern object Dummy2 extends Dummy2 + |""".stripMargin + + NIRCompiler(_.compile(code)) + } + + it should "not allow to mix extern object with regular traits" in { + val code = + """ + |import scala.scalanative.unsafe.extern + | + |trait Dummy { + | def foo(): Int = ??? + |} + | + |@extern object Dummy extends Dummy + |""".stripMargin + intercept[CompilationFailedException](NIRCompiler(_.compile(code))) + .getMessage() should include( + "Extern object can only extend extern traits" + ) + } it should "compile extern var definition" in { // given @@ -97,6 +151,52 @@ class NIRCompilerTest extends AnyFlatSpec with Matchers with Inspectors { |}""".stripMargin // when NIRCompiler(_.compile(code)) + it should "not allow to mix extern object with class" in { + val code = + """import scala.scalanative.unsafe.extern + | + |class Dummy { + | def foo(): Int = ??? + |} + | + |@extern object Dummy extends Dummy + |""".stripMargin + intercept[CompilationFailedException](NIRCompiler(_.compile(code))) + .getMessage() should include( + "Extern object can only extend extern traits" + ) + } + + it should "not allow to mix extern traits with regular object" in { + val code = + """import scala.scalanative.unsafe.extern + | + |@extern trait Dummy { + | def foo(): Int = extern + |} + | + |object Dummy extends Dummy + |""".stripMargin + intercept[CompilationFailedException](NIRCompiler(_.compile(code))) + .getMessage() should include( + "Extern traits can be only mixed with extern traits or objects" + ) + } + + it should "not allow to mix extern traits with class" in { + val code = + """import scala.scalanative.unsafe.extern + | + |@extern trait Dummy { + | def foo(): Int = extern + |} + | + |class DummyImpl extends Dummy + |""".stripMargin + intercept[CompilationFailedException](NIRCompiler(_.compile(code))) + .getMessage() should include( + "Extern traits can be only mixed with extern traits or objects" + ) } it should "report error for intrinsic resolving of not existing field" in { diff --git a/unit-tests/native/src/test/scala-2.12+/scala/Issues212PlusTest.scala b/unit-tests/shared/src/test/scala-2.12+/scala/Issues212PlusTest.scala similarity index 100% rename from unit-tests/native/src/test/scala-2.12+/scala/Issues212PlusTest.scala rename to unit-tests/shared/src/test/scala-2.12+/scala/Issues212PlusTest.scala From 1832daa929cb8c2d2e405620301071691026672a Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 16 Nov 2022 17:53:34 +0100 Subject: [PATCH 56/61] Fix incorrect declarations of extern methods defined in `extern trait` (#3001) * Reproduce failing issue leading to compilation errors * Make sure extern declaration produces from trait are using unboxed types --- .../scalanative/nscplugin/NirGenStat.scala | 8 +++- .../scala/scalanative/linker/IssuesSpec.scala | 41 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala index aca947ff7a..883618af4c 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala @@ -616,7 +616,13 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => dd.rhs match { case EmptyTree => - Some(Defn.Declare(attrs, name, sig)) + Some( + Defn.Declare( + attrs, + name, + if (attrs.isExtern) genExternMethodSig(sym) else sig + ) + ) case _ if dd.symbol.isConstructor && owner.isExternType => validateExternCtor(dd.rhs) diff --git a/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala b/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala index 39d4e92af1..8855115f28 100644 --- a/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala +++ b/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala @@ -4,6 +4,7 @@ import scala.scalanative.checker.Check import scala.scalanative.LinkerSpec import org.scalatest.matchers.should._ +import scala.scalanative.nir._ class IssuesSpec extends LinkerSpec with Matchers { private val mainClass = "Test" @@ -57,4 +58,44 @@ class IssuesSpec extends LinkerSpec with Matchers { |""" } + "Extern traits" should "have only primitive type in type signature" in { + testLinked(s""" + |import scala.scalanative.unsafe._ + |import scala.scalanative.unsigned._ + | + |@extern trait string { + | def memset(dest: Ptr[Byte], ch: Int, count: USize): Ptr[Byte] = extern + |} + |@extern object string extends string + | + |object Test { + | def main(args: Array[String]): Unit = { + | val privilegeSetLength = stackalloc[USize]() + | val privilegeSet: Ptr[Byte] = stackalloc[Byte](!privilegeSetLength) + | + | // real case + | string.memset(privilegeSet, 0, !privilegeSetLength) + | + | // possible case + | def str: string = ??? + | str.memset(privilegeSet, 0, !privilegeSetLength) + | } + |}""".stripMargin) { result => + val Memset = Sig.Extern("memset") + val StringMemset = Global.Top("string").member(Memset) + val decls = result.defns + .collectFirst { + case Defn.Declare(attrs, StringMemset, tpe) => + assert(attrs.isExtern) + tpe shouldEqual Type.Function( + Seq(Type.Ptr, Type.Int, Type.Size), + Type.Ptr + ) + } + .orElse { + fail("Not found extern declaration") + } + } + } + } From 5bc1ef46adc6ac310e5684f9a112ef2aefe8c1dc Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 29 Dec 2022 17:00:14 +0100 Subject: [PATCH 57/61] Fix usage of `var` defined in `extern trait` when using Scala 3 (#3059) * Reproduce problems with usage of extern var defined in trait * Fix detection of nir.Defn attributes --- .../scalanative/nscplugin/NirGenStat.scala | 8 +++--- .../scala/scalanative/linker/IssuesSpec.scala | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenStat.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenStat.scala index 5136bbb0cb..716b7f8ce5 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenStat.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/NirGenStat.scala @@ -246,10 +246,10 @@ trait NirGenStat(using Context) { case defnNir.NoOptimizeType => Attr.NoOpt case defnNir.NoSpecializeType => Attr.NoSpecialize case defnNir.StubType => Attr.Stub - case defnNir.ExternType => Attr.Extern } + val externAttrs = if (sym.owner.isExternType) Seq(Attr.Extern) else Nil - Attrs.fromSeq(inlineAttrs ++ annotatedAttrs) + Attrs.fromSeq(inlineAttrs ++ annotatedAttrs ++ externAttrs) } protected val curExprBuffer = ScopedVar[ExprBuffer]() @@ -434,9 +434,7 @@ trait NirGenStat(using Context) { rhs match { case _ - if defaultArgs.nonEmpty || dd.name.is( - NameKinds.DefaultGetterName - ) => + if defaultArgs.nonEmpty || dd.name.is(NameKinds.DefaultGetterName) => report.error("extern method cannot have default argument") None case Apply(ref: RefTree, Seq()) diff --git a/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala b/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala index 8855115f28..4f8cdabba1 100644 --- a/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala +++ b/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala @@ -98,4 +98,31 @@ class IssuesSpec extends LinkerSpec with Matchers { } } + it should "define extern fields with correct attributes" in { + testLinked(s""" + |import scala.scalanative.unsafe._ + | + |@extern trait lib { + | var field: CInt = extern + |} + |@extern object lib extends lib + | + |object Test { + | def main(args: Array[String]): Unit = { + | val read = lib.field + | lib.field = 42 + | } + |}""".stripMargin) { result => + val Field = Sig.Extern("field") + val LibField = Global.Top("lib").member(Field) + val decls = result.defns + .collect { + case defn @ Defn.Declare(attrs, LibField, tpe) => + println(s"got $defn") + assert(attrs.isExtern) + } + if (decls.isEmpty) fail("Not found extern declaration") + } + } + } From 547dc2b48a9458f1b9aeeef13cc88fedace18d38 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 22 Mar 2023 10:49:14 +0100 Subject: [PATCH 58/61] Fix compilation errors after cherry-picks --- .../scalanative/nscplugin/NirGenName.scala | 24 +++++++---- .../scalanative/nscplugin/NirGenStat.scala | 33 +++++---------- .../scalanative/nscplugin/NirGenType.scala | 2 +- .../nscplugin/PrepNativeInterop.scala | 40 ------------------- .../scala/scalanative/NIRCompilerTest.scala | 23 ++++++----- .../scala/scalanative/linker/IssuesSpec.scala | 7 ++-- .../scala/scalnative/IssuesTestScala3.scala | 2 +- .../testsuite/posixlib/DlfcnTest.scala | 4 +- 8 files changed, 43 insertions(+), 92 deletions(-) diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenName.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenName.scala index 0875e4d4c0..4be1e760bd 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenName.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenName.scala @@ -28,12 +28,7 @@ trait NirGenName[G <: Global with Singleton] { def genTypeName(sym: Symbol): nir.Global.Top = { val id = { val fullName = sym.fullName - if (fullName == "java.lang._String") "java.lang.String" - else if (fullName == "java.lang._Object") "java.lang.Object" - else if (fullName == "java.lang._Class") "java.lang.Class" - else if (fullName == "scala.Nothing") "scala.runtime.Nothing$" - else if (fullName == "scala.Null") "scala.runtime.Null$" - else fullName + MappedNames.getOrElse(fullName, fullName) } val name = sym match { case ObjectClass => @@ -97,8 +92,7 @@ trait NirGenName[G <: Global with Singleton] { val paramTypes = tpe.params.toSeq.map(p => genType(p.info)) - def isExtern = - sym.owner.isExternType || implClassTarget(sym.owner).isExternType + def isExtern = sym.owner.isExternType if (sym == String_+) genMethodName(StringConcatMethod) @@ -184,4 +178,18 @@ trait NirGenName[G <: Global with Singleton] { id.replace("\"", "$u0022") } } + + private val MappedNames = Map( + "java.lang._Class" -> "java.lang.Class", + "java.lang._Enum" -> "java.lang.Enum", + "java.lang._Object" -> "java.lang.Object", + "java.lang._String" -> "java.lang.String", + "scala.Nothing" -> "scala.runtime.Nothing$", + "scala.Null" -> "scala.runtime.Null$" + ).flatMap { + case classEntry @ (nativeName, javaName) => + classEntry :: + (nativeName + "$", javaName + "$") :: + Nil + } } diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala index 883618af4c..daa8c551ce 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenStat.scala @@ -89,10 +89,6 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => def genClass(cd: ClassDef): Unit = { val sym = cd.symbol - // ImplClass does not copy annotations from the trait - if (isImplClass(sym) && implClassTarget(sym).isExternType) { - sym.addAnnotation(ExternClass) - } scoped( curClassSym := sym, @@ -139,8 +135,7 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => def genClassParent(sym: Symbol): Option[nir.Global] = { if (sym.isExternType && - sym.superClass != ObjectClass && - !isImplClass(sym)) { + sym.superClass != ObjectClass) { reporter.error( sym.pos, s"Extern object can only extend extern traits" @@ -200,7 +195,7 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => for (f <- sym.info.decls if !f.isMethod && f.isTerm && !f.isModule) { - if (f.owner.isExternModule && !f.isMutable) { + if (f.owner.isExternType && !f.isMutable) { reporter.error(f.pos, "`extern` cannot be used in val definition") } val ty = genType(f.tpe) @@ -712,26 +707,21 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => ): Option[nir.Defn] = { val rhs = dd.rhs def externMethodDecl() = { - val externAttrs = Attrs(isExtern = true) val externSig = genExternMethodSig(curMethodSym) - val externDefn = Defn.Declare(externAttrs, name, externSig)(rhs.pos) + val externDefn = Defn.Declare(attrs, name, externSig)(rhs.pos) + Some(externDefn) } - def isCallingExternMethod(sym: Symbol) = { - val owner = sym.owner match { - case sym if isImplClass(sym) => implClassTarget(sym) - case sym => sym - } - owner.isExternType - } + def isCallingExternMethod(sym: Symbol) = + sym.owner.isExternType def isExternMethodAlias(target: Symbol) = (name, genName(target)) match { case (Global.Member(_, lsig), Global.Member(_, rsig)) => lsig == rsig case _ => false } - + val defaultArgs = dd.symbol.paramss.flatten.filter(_.hasDefault) rhs match { case _ if defaultArgs.nonEmpty => reporter.error( @@ -773,12 +763,7 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => } def isCurClassSetter(sym: Symbol) = - sym.isSetter && { - val owner = sym.owner.tpe - owner <:< classSym.tpe || { - isImplClass(classSym) && owner <:< implClassTarget(classSym).tpe - } - } + sym.isSetter && sym.owner.tpe <:< classSym.tpe rhs match { case Block(Nil, _) => () // empty mixin constructor @@ -851,7 +836,7 @@ trait NirGenStat[G <: nsc.Global with Singleton] { self: NirGenPhase[G] => val isSynchronized = dd.symbol.hasFlag(SYNCHRONIZED) val sym = dd.symbol val isStatic = sym.isStaticInNIR - val isExtern = sym.owner.isExternModule + val isExtern = sym.owner.isExternType implicit val pos: nir.Position = bodyp.pos diff --git a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala index 3d09a2a648..7fe5a894da 100644 --- a/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala +++ b/nscplugin/src/main/scala-2/scala/scalanative/nscplugin/NirGenType.scala @@ -189,7 +189,7 @@ trait NirGenType[G <: Global with Singleton] { self: NirGenPhase[G] => isExtern: Boolean ): Seq[nir.Type] = { val params = sym.tpe.params - if (!isExtern && !sym.owner.isExternModule) + if (!isExtern && !sym.owner.isExternType) params.map { p => genType(p.tpe) } else { val wereRepeated = exitingPhase(currentRun.typerPhase) { diff --git a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/PrepNativeInterop.scala b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/PrepNativeInterop.scala index a1d65a4773..cda0200390 100644 --- a/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/PrepNativeInterop.scala +++ b/nscplugin/src/main/scala-3/scala/scalanative/nscplugin/PrepNativeInterop.scala @@ -55,46 +55,6 @@ class PrepNativeInterop extends PluginPhase { sym.hasAnnotation(defnNir.ExternClass) end extension - private class DealiasTypeMapper(using Context) extends TypeMap { - override def apply(tp: Type): Type = - val sym = tp.typeSymbol - val dealiased = - if sym.isOpaqueAlias then sym.opaqueAlias - else tp - dealiased.widenDealias match - case AppliedType(tycon, args) => - AppliedType(this(tycon), args.map(this)) - case ty => ty - } - - override def transformTypeApply(tree: TypeApply)(using Context): Tree = { - val TypeApply(fun, tArgs) = tree - val defnNir = this.defnNir - def dealiasTypeMapper = DealiasTypeMapper() - - // sizeOf[T] -> sizeOf(classOf[T]) - fun.symbol match - case defnNir.Intrinsics_sizeOfType => - val tpe = dealiasTypeMapper(tArgs.head.tpe) - cpy - .Apply(tree)( - ref(defnNir.Intrinsics_sizeOf), - List(Literal(Constant(tpe))) - ) - .withAttachment(NirDefinitions.NonErasedType, tpe) - - case defnNir.Intrinsics_alignmentOfType => - val tpe = dealiasTypeMapper(tArgs.head.tpe) - cpy - .Apply(tree)( - ref(defnNir.Intrinsics_alignmentOf), - List(Literal(Constant(tpe))) - ) - .withAttachment(NirDefinitions.NonErasedType, tpe) - - case _ => tree - } - override def transformDefDef(dd: DefDef)(using Context): Tree = { val sym = dd.symbol lazy val rhsSym = dd.rhs.symbol diff --git a/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala b/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala index 89fad8d59b..8849662541 100644 --- a/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala +++ b/tools/src/test/scala/scala/scalanative/NIRCompilerTest.scala @@ -85,6 +85,18 @@ class NIRCompilerTest extends AnyFlatSpec with Matchers with Inspectors { "`extern` cannot be used in val definition" ) } + + it should "compile extern var definition" in { + // given + val code = + """import scala.scalanative.unsafe.extern + | + |@extern + |object Dummy { + | var foo: Int = extern + |}""".stripMargin + // when + NIRCompiler(_.compile(code)) } it should "not allow members of extern object to reference other externs" in { @@ -140,17 +152,6 @@ class NIRCompilerTest extends AnyFlatSpec with Matchers with Inspectors { ) } - it should "compile extern var definition" in { - // given - val code = - """import scala.scalanative.unsafe.extern - | - |@extern - |object Dummy { - | var foo: Int = extern - |}""".stripMargin - // when - NIRCompiler(_.compile(code)) it should "not allow to mix extern object with class" in { val code = """import scala.scalanative.unsafe.extern diff --git a/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala b/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala index 4f8cdabba1..91e5ae9ee7 100644 --- a/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala +++ b/tools/src/test/scala/scala/scalanative/linker/IssuesSpec.scala @@ -64,13 +64,13 @@ class IssuesSpec extends LinkerSpec with Matchers { |import scala.scalanative.unsigned._ | |@extern trait string { - | def memset(dest: Ptr[Byte], ch: Int, count: USize): Ptr[Byte] = extern + | def memset(dest: Ptr[Byte], ch: Int, count: ULong): Ptr[Byte] = extern |} |@extern object string extends string | |object Test { | def main(args: Array[String]): Unit = { - | val privilegeSetLength = stackalloc[USize]() + | val privilegeSetLength = stackalloc[ULong]() | val privilegeSet: Ptr[Byte] = stackalloc[Byte](!privilegeSetLength) | | // real case @@ -88,7 +88,7 @@ class IssuesSpec extends LinkerSpec with Matchers { case Defn.Declare(attrs, StringMemset, tpe) => assert(attrs.isExtern) tpe shouldEqual Type.Function( - Seq(Type.Ptr, Type.Int, Type.Size), + Seq(Type.Ptr, Type.Int, Type.Long), Type.Ptr ) } @@ -118,7 +118,6 @@ class IssuesSpec extends LinkerSpec with Matchers { val decls = result.defns .collect { case defn @ Defn.Declare(attrs, LibField, tpe) => - println(s"got $defn") assert(attrs.isExtern) } if (decls.isEmpty) fail("Not found extern declaration") diff --git a/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala b/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala index e7ccf802c6..e1df03c981 100644 --- a/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala +++ b/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala @@ -32,7 +32,7 @@ class IssuesTestScala3 { object functions: export extern_functions.test // should compile - val buff: Ptr[CChar] = stackalloc[CChar](128.toUSize) + val buff: Ptr[CChar] = stackalloc[CChar](128.toULong) functions.test(buff, c"%d %d %d", -1, 1, 42) assertEquals("-1 1 42", fromCString(buff)) } diff --git a/unit-tests/native/src/test/scala/org/scalanative/testsuite/posixlib/DlfcnTest.scala b/unit-tests/native/src/test/scala/org/scalanative/testsuite/posixlib/DlfcnTest.scala index 3f6e7a8ac2..fefce50b87 100644 --- a/unit-tests/native/src/test/scala/org/scalanative/testsuite/posixlib/DlfcnTest.scala +++ b/unit-tests/native/src/test/scala/org/scalanative/testsuite/posixlib/DlfcnTest.scala @@ -24,9 +24,7 @@ class DlfcnTest { @Test def dlfcnOpensAndObtainsSymbolAddressLinux(): Unit = { if (isLinux) Zone { implicit z => val soFilePrefix = - if (is32BitPlatform) - "/lib/i386-linux-gnu/" - else if (PlatformExt.isArm64) + if (PlatformExt.isArm64) "/usr/lib/aarch64-linux-gnu" else "/lib/x86_64-linux-gnu" From 4944b6b6394d9ce11f0aa0ac84003521b3ad9e59 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 22 Mar 2023 11:15:28 +0100 Subject: [PATCH 59/61] Set version to 0.4.12 and generate changelog --- docs/changelog/0.4.12.md | 112 ++++++++++++++++++ docs/changelog/index.rst | 1 + docs/conf.py | 4 +- .../scala/scalanative/nir/Versions.scala | 2 +- 4 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 docs/changelog/0.4.12.md diff --git a/docs/changelog/0.4.12.md b/docs/changelog/0.4.12.md new file mode 100644 index 0000000000..5d0c9471c8 --- /dev/null +++ b/docs/changelog/0.4.12.md @@ -0,0 +1,112 @@ + +# 0.4.12 (2023-03-22) + +We're happy to announce the release of Scala Native. It's the next maintenance release for Scala Native 0.4.x. +This release fixes regressions introduced in previous version and adds some requested features. + + +The Scala standard library used by this release is based on the following versions: +
+ + + + + + + + + + + + + + + + + + +
Scala binary versionScala release
2.122.12.17
2.132.13.10
33.2.2
+ + + + + + + + + + + + + + + + +
Commits since last release10
Merged PRs8
Contributors3
+ +## Notable changes + +### Composable extern definitions using `@extern trait` +Extern defintions can now be composed using traits annotated as `@extern`. Extern objects can now be composed using multiple extern traits allowing for better modeling of foreign APIs. +An example of natural extension of C bindings can be `errno.h` from C standard library and its POSIX extension. +It can now be modeled as following + +```scala +import scala.scalanative.unsafe.* + +@extern trait errnoC { + var errno: CInt = extern + + def EILSEQ: CInt = extern +} + +@extern trait errnoPosix extends errnoC { + def EWOULDBLOCK: CInt = extern + def EINPROGRESS: CInt = extern + def EINTR: CInt = extern +} + +@extern object errno extends errnoC with errnoPosix +``` +The current bindings of POSIX and C standard library are not affected by this change, however new model would be used in Scala Native 0.5.x + +## Contributors + +Big thanks to everybody who contributed to this release or reported an issue! + +``` +$ git shortlog -sn --no-merges v0.4.11..v0.4.12 + 8 Wojciech Mazur + 1 Eric K Richardson + 1 LeeTibbert +``` + +## Merged PRs + +## [](https://github.com/scala-native/scala-native/tree/) (2023-03-22) + +[Full Changelog](https://github.com/scala-native/scala-native/compare/v0.4.11..v0.4.12) + +**Merged pull requests:** + + +## POSIX bindings +- Implement posixlib dlfcn + [\#3234](https://github.com/scala-native/scala-native/pull/3234) + ([LeeTibbert](https://github.com/LeeTibbert)) + +## Compiler plugin +- Improve resolving repeated parameters in Scala3 extern methods + [\#3230](https://github.com/scala-native/scala-native/pull/3230) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Fix exports of extern methods using variadic arguments in Scala3 + [\#3232](https://github.com/scala-native/scala-native/pull/3232) + ([WojciechMazur](https://github.com/WojciechMazur)) +- Allow to compose extern definitions using `@extern trait` + [\#2988](https://github.com/scala-native/scala-native/pull/2988) + ([WojciechMazur](https://github.com/WojciechMazur)) + +## Toolchain +- Fix regression in handling opaque pointers on Windows + [\#3226](https://github.com/scala-native/scala-native/pull/3226) + ([WojciechMazur](https://github.com/WojciechMazur)) diff --git a/docs/changelog/index.rst b/docs/changelog/index.rst index 77b0f5877d..7f8d59ab1a 100644 --- a/docs/changelog/index.rst +++ b/docs/changelog/index.rst @@ -6,6 +6,7 @@ Changelog .. toctree:: :maxdepth: 1 + 0.4.12 0.4.11 0.4.10 0.4.9 diff --git a/docs/conf.py b/docs/conf.py index 00736a84e0..6a18c37867 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -69,9 +69,9 @@ def generateScalaNativeCurrentYear(): # built documents. # # The short X.Y version. -version = u'0.4.12-SNAPSHOT' +version = u'0.4.12' # The full version, including alpha/beta/rc tags. -release = u'0.4.12-SNAPSHOT' +release = u'0.4.12' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/nir/src/main/scala/scala/scalanative/nir/Versions.scala b/nir/src/main/scala/scala/scalanative/nir/Versions.scala index 75f553d9e6..556855d222 100644 --- a/nir/src/main/scala/scala/scalanative/nir/Versions.scala +++ b/nir/src/main/scala/scala/scalanative/nir/Versions.scala @@ -25,7 +25,7 @@ object Versions { final val revision: Int = 9 // a.k.a. MINOR version /* Current public release version of Scala Native. */ - final val current: String = "0.4.12-SNAPSHOT" + final val current: String = "0.4.12" final val currentBinaryVersion: String = binaryVersion(current) private object FullVersion { From 596300180e4fbffc12d4ebe26892473b6ac6014a Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 22 Mar 2023 11:30:32 +0100 Subject: [PATCH 60/61] Fix typo in chanelog --- docs/changelog/0.4.12.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/changelog/0.4.12.md b/docs/changelog/0.4.12.md index 5d0c9471c8..879cc024e4 100644 --- a/docs/changelog/0.4.12.md +++ b/docs/changelog/0.4.12.md @@ -1,8 +1,5 @@ - -# 0.4.12 (2023-03-22) - We're happy to announce the release of Scala Native. It's the next maintenance release for Scala Native 0.4.x. -This release fixes regressions introduced in previous version and adds some requested features. +This release fixes regressions introduced in the previous version and adds some requested features. The Scala standard library used by this release is based on the following versions: @@ -47,8 +44,8 @@ The Scala standard library used by this release is based on the following versio ## Notable changes ### Composable extern definitions using `@extern trait` -Extern defintions can now be composed using traits annotated as `@extern`. Extern objects can now be composed using multiple extern traits allowing for better modeling of foreign APIs. -An example of natural extension of C bindings can be `errno.h` from C standard library and its POSIX extension. +Extern definitions can now be composed using traits annotated as `@extern`. Extern objects can now be composed using multiple extern traits allowing for better modeling of foreign APIs. +A good candidate for modeling C bindings with this approach can be `errno.h` from C standard library and its POSIX extension. It can now be modeled as following ```scala @@ -68,7 +65,7 @@ import scala.scalanative.unsafe.* @extern object errno extends errnoC with errnoPosix ``` -The current bindings of POSIX and C standard library are not affected by this change, however new model would be used in Scala Native 0.5.x +The current bindings of POSIX and C standard library are not affected by this change, however, new model would be used in Scala Native 0.5.x ## Contributors @@ -76,7 +73,7 @@ Big thanks to everybody who contributed to this release or reported an issue! ``` $ git shortlog -sn --no-merges v0.4.11..v0.4.12 - 8 Wojciech Mazur + 8 Wojciech Mazur 1 Eric K Richardson 1 LeeTibbert ``` From 364885493c7be0507fd40e62ad484dde995c352b Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Wed, 22 Mar 2023 11:46:21 +0100 Subject: [PATCH 61/61] Fix signature of sprintf method used in regression tests --- .../test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala b/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala index e1df03c981..71852bddef 100644 --- a/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala +++ b/unit-tests/native/src/test/scala-3/scala/scala/scalnative/IssuesTestScala3.scala @@ -27,7 +27,7 @@ class IssuesTestScala3 { @Test def i3231(): Unit = { @extern object extern_functions: @name("sprintf") - def test(buffer: CString, format: CString, args: Any*): Unit = extern + def test(buffer: CString, format: CString, args: Any*): CInt = extern object functions: export extern_functions.test // should compile