Skip to content

Commit

Permalink
Overhaul of scala.scalanative.unsafe.{stackalloc,alloc} (#3411)
Browse files Browse the repository at this point in the history
* Restore `stackalloc[T](Int)` and `alloc[T](Int)` methods. The now have special handling for literals integers and detect zero and negative literals at compile time. Non-literal values now have a runtime check to ensure that provide integer is positive integer. Unsigned variants are still present and have the same sementics as before. 
* `Intrinsic.stackalloc`signature has changed, methods take now generic `T` type used to resolve size of required memory. The `size: RawSize` argument no longer describes size of the allocated memory, instead it describe number of elements of sizeOf[T] to allocate. This change allows to skip some arithmetic operations at runtime/optimizer and allows to use the `nir.Type` argument of `nir.Op.Stackalloc` (previously always Byte). This work is based on #3260 
* 0-initialization of stackallocated memory is now moved to `Lowering` stage. Thanks to having a full information about allocated type, we can calculate exactly the required amount of memory at linktime after optimizer run, and allows to add optimizer runs designed to removal of zero-initialization of manually initialized memory. 
* All special intrinsic operations used as intermiediete step of compilation were moved to `Intrinsics.internal` object
* Removed most of no longer required `toUnsigned` conversion
  • Loading branch information
WojciechMazur committed Aug 4, 2023
1 parent 9f6b9b8 commit af36d31
Show file tree
Hide file tree
Showing 48 changed files with 658 additions and 180 deletions.
2 changes: 1 addition & 1 deletion javalib/src/main/scala/java/io/File.scala
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ object File {
}
fromCWideString(buff, StandardCharsets.UTF_16LE)
} else {
val buff: CString = alloc[CChar](4096.toUInt)
val buff: CString = alloc[CChar](4096)
if (getcwd(buff, 4095.toUInt) == 0.toUInt) {
val errMsg = fromCString(string.strerror(errno.errno))
throw new IOException(
Expand Down
2 changes: 1 addition & 1 deletion javalib/src/main/scala/java/lang/IEEE754Helpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private[java] object IEEE754Helpers {
if (nChars == 0)
throw new NumberFormatException(exceptionMsg(s))

val cStr: CString = stackalloc[scala.Byte]((nChars + 1).toUInt)
val cStr: CString = stackalloc[scala.Byte](nChars + 1)

if (_numericCharSeqToCString(s, nChars, cStr) == false) {
throw new NumberFormatException(exceptionMsg(s))
Expand Down
4 changes: 2 additions & 2 deletions javalib/src/main/scala/java/lang/StackTraceElement.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ private[lang] object StackTraceElement {
object Fail extends scala.util.control.NoStackTrace

def fromSymbol(sym: CString): StackTraceElement = {
val className: Ptr[CChar] = stackalloc[CChar](1024.toUSize)
val methodName: Ptr[CChar] = stackalloc[CChar](1024.toUSize)
val className: Ptr[CChar] = stackalloc[CChar](1024)
val methodName: Ptr[CChar] = stackalloc[CChar](1024)
SymbolFormatter.asyncSafeFromSymbol(sym, className, methodName)

new StackTraceElement(
Expand Down
2 changes: 1 addition & 1 deletion javalib/src/main/scala/java/lang/Throwables.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ private[lang] object StackTrace {
cursor: Ptr[scala.Byte]
)(implicit zone: Zone): StackTraceElement = {
val nameMax = 1024
val name = alloc[CChar](nameMax.toUSize)
val name = alloc[CChar](nameMax)
val offset = stackalloc[scala.Long]()

unwind.get_proc_name(cursor, name, nameMax.toUSize, offset)
Expand Down
2 changes: 1 addition & 1 deletion javalib/src/main/scala/java/lang/impl/PosixThread.scala
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ private[java] class PosixThread(val thread: Thread, stackSize: Long)
import scala.scalanative.posix.pollEvents._

type PipeFDs = CArray[CInt, Nat._2]
val pipefd = stackalloc[PipeFDs](1.toUInt)
val pipefd = stackalloc[PipeFDs](1)
checkStatus("create sleep interrupt event") {
pipe(pipefd.at(0))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,10 @@ object UnixProcessGen1 {
ProcessMonitor.waitForPid(pid, ts, res)

def apply(builder: ProcessBuilder): Process = Zone { implicit z =>
val infds: Ptr[CInt] = stackalloc[CInt](2.toUInt)
val outfds: Ptr[CInt] = stackalloc[CInt](2.toUInt)
val infds: Ptr[CInt] = stackalloc[CInt](2)
val outfds: Ptr[CInt] = stackalloc[CInt](2)
val errfds =
if (builder.redirectErrorStream()) outfds else stackalloc[CInt](2.toUInt)
if (builder.redirectErrorStream()) outfds else stackalloc[CInt](2)

throwOnError(unistd.pipe(infds), s"Couldn't create pipe.")
throwOnError(unistd.pipe(outfds), s"Couldn't create pipe.")
Expand Down Expand Up @@ -260,7 +260,7 @@ object UnixProcessGen1 {
@inline private def nullTerminate(
list: java.util.List[String]
)(implicit z: Zone) = {
val res: Ptr[CString] = alloc[CString]((list.size() + 1).toUInt)
val res: Ptr[CString] = alloc[CString]((list.size() + 1))
val li = list.listIterator()
while (li.hasNext()) {
!(res + li.nextIndex()) = toCString(li.next())
Expand Down
16 changes: 8 additions & 8 deletions javalib/src/main/scala/java/lang/process/UnixProcessGen2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ private[lang] class UnixProcessGen2 private (
throw new IOException(msg)
}

val fds = stackalloc[struct_pollfd](1.toUInt)
val fds = stackalloc[struct_pollfd](1)
(fds + 0).fd = pidFd
(fds + 0).events = (pollEvents.POLLIN | pollEvents.POLLRDNORM).toShort

Expand Down Expand Up @@ -373,11 +373,11 @@ object UnixProcessGen2 {
}

private def forkChild(builder: ProcessBuilder)(implicit z: Zone): Process = {
val infds: Ptr[CInt] = stackalloc[CInt](2.toUInt)
val outfds: Ptr[CInt] = stackalloc[CInt](2.toUInt)
val infds: Ptr[CInt] = stackalloc[CInt](2)
val outfds: Ptr[CInt] = stackalloc[CInt](2)
val errfds =
if (builder.redirectErrorStream()) outfds
else stackalloc[CInt](2.toUInt)
else stackalloc[CInt](2)

throwOnError(unistd.pipe(infds), s"Couldn't create infds pipe.")
throwOnError(unistd.pipe(outfds), s"Couldn't create outfds pipe.")
Expand Down Expand Up @@ -476,11 +476,11 @@ object UnixProcessGen2 {
)(implicit z: Zone): Process = {
val pidPtr = stackalloc[pid_t]()

val infds: Ptr[CInt] = stackalloc[CInt](2.toUInt)
val outfds: Ptr[CInt] = stackalloc[CInt](2.toUInt)
val infds: Ptr[CInt] = stackalloc[CInt](2)
val outfds: Ptr[CInt] = stackalloc[CInt](2)
val errfds =
if (builder.redirectErrorStream()) outfds
else stackalloc[CInt](2.toUInt)
else stackalloc[CInt](2)

throwOnError(unistd.pipe(infds), s"Couldn't create infds pipe.")
throwOnError(unistd.pipe(outfds), s"Couldn't create outfds pipe.")
Expand Down Expand Up @@ -661,7 +661,7 @@ object UnixProcessGen2 {
private def nullTerminate(
list: java.util.List[String]
)(implicit z: Zone) = {
val res: Ptr[CString] = alloc[CString]((list.size() + 1).toUInt)
val res: Ptr[CString] = alloc[CString]((list.size() + 1))
val li = list.listIterator()
while (li.hasNext()) {
!(res + li.nextIndex()) = toCString(li.next())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ private[net] abstract class AbstractPlainSocketImpl extends SocketImpl {
}
val family =
storage.asInstanceOf[Ptr[socket.sockaddr_storage]].ss_family.toInt
val ipstr: Ptr[CChar] = stackalloc[CChar](in.INET6_ADDRSTRLEN.toUSize)
val ipstr: Ptr[CChar] = stackalloc[CChar](in.INET6_ADDRSTRLEN)

val (srcPtr, port) = if (family == socket.AF_INET) {
val sa = storage.asInstanceOf[Ptr[in.sockaddr_in]]
Expand Down
2 changes: 1 addition & 1 deletion javalib/src/main/scala/java/net/Inet6Address.scala
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ object Inet6Address {
val suffix =
try {
val ifIndex = Integer.parseInt(zi)
val ifName = stackalloc[Byte](IF_NAMESIZE.toUSize)
val ifName = stackalloc[Byte](IF_NAMESIZE)
if (if_indextoname(ifIndex.toUInt, ifName) == stddef.NULL) zi
else fromCString(ifName)
} catch {
Expand Down
2 changes: 1 addition & 1 deletion javalib/src/main/scala/java/net/InetAddress.scala
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ object InetAddress {
private def formatIn4Addr(pb: Ptr[Byte]): String = {
// By contract, pb isInstanceOf[Ptr[in_addr]]
val dstSize = INET_ADDRSTRLEN
val dst = stackalloc[Byte](dstSize.toUSize)
val dst = stackalloc[Byte](dstSize)

val result = inet_ntop(AF_INET, pb, dst, dstSize.toUInt)

Expand Down
2 changes: 1 addition & 1 deletion javalib/src/main/scala/java/net/NetworkInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ object NetworkInterface {
}

private def unixGetByIndex(index: Int): NetworkInterface = {
val buf = stackalloc[Byte](IF_NAMESIZE.toUInt)
val buf = stackalloc[Byte](IF_NAMESIZE)

val ret = if_indextoname(index.toUInt, buf)

Expand Down
2 changes: 1 addition & 1 deletion javalib/src/main/scala/java/nio/file/Files.scala
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@ object Files {
fromCWideString(buffer, StandardCharsets.UTF_16LE)
}
} else {
val buf: CString = alloc[Byte](limits.PATH_MAX.toUInt)
val buf: CString = alloc[Byte](limits.PATH_MAX)
if (unistd.readlink(
toCString(link.toString),
buf,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class UnixFileSystemProvider extends GenericFileSystemProvider {
}

private def getUserDir(): String = {
val buff: Ptr[CChar] = stackalloc[CChar](4096.toUInt)
val buff: Ptr[CChar] = stackalloc[CChar](4096)
val res = unistd.getcwd(buff, 4095.toUInt)
if (res == null)
throw UnixException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,23 @@ private[scalanative] trait UnsafePackageCompat { self =>
/** Heap allocate and zero-initialize n values using current implicit
* allocator.
*/
def alloc[T](n: CSize)(implicit z: Zone): Ptr[T] = macro MacroImpl.allocN[T]
def alloc[T](n: Int)(implicit z: Zone): Ptr[T] = macro MacroImpl.allocN[T]

/** Heap allocate and zero-initialize n values using current implicit
* allocator.
*/
def alloc[T](n: CSize)(implicit z: Zone): Ptr[T] =
macro MacroImpl.allocNUnsigned[T]

/** Stack allocate and zero-initialize 1 value of given type */
def stackalloc[T](): Ptr[T] = macro MacroImpl.stackallocSingle[T]

/** Stack allocate and zero-initialize n values of given type */
def stackalloc[T](n: CSize): Ptr[T] = macro MacroImpl.stackallocN[T]
def stackalloc[T](n: Int): Ptr[T] = macro MacroImpl.stackallocN[T]

/** Stack allocate and zero-initialize n values of given type */
def stackalloc[T](n: CSize): Ptr[T] = macro MacroImpl.stackallocNUnsigned[T]

}

private object MacroImpl {
Expand Down Expand Up @@ -96,6 +106,28 @@ private object MacroImpl {

val T = weakTypeOf[T]

val elemSize, size, ptr = TermName(c.freshName())

val runtime = q"_root_.scala.scalanative.runtime"
val unsignedOf = q"$runtime.Intrinsics.unsignedOf"

q"""{
val $elemSize = $runtime.Intrinsics.sizeOf[$T]
val $size =
$unsignedOf($elemSize) * $unsignedOf(${validateSize(c)(n)})
val $ptr = $z.alloc($size)
$runtime.libc.memset($ptr, 0, $size)
$ptr.asInstanceOf[Ptr[$T]]
}"""
}

def allocNUnsigned[T: c.WeakTypeTag](
c: Context
)(n: c.Tree)(z: c.Tree): c.Tree = {
import c.universe._

val T = weakTypeOf[T]

val size, ptr, rawSize = TermName(c.freshName())

val runtime = q"_root_.scala.scalanative.runtime"
Expand All @@ -114,32 +146,71 @@ private object MacroImpl {

val T = weakTypeOf[T]

val size, rawptr, rawSize = TermName(c.freshName())
val rawptr = TermName(c.freshName())

val runtime = q"_root_.scala.scalanative.runtime"

q"""{
val $rawSize = $runtime.Intrinsics.sizeOf[$T]
val $rawptr = $runtime.Intrinsics.stackalloc($rawSize)
$runtime.libc.memset($rawptr, 0, $rawSize)
val $rawptr = $runtime.Intrinsics.stackalloc[$T]()
$runtime.fromRawPtr[$T]($rawptr)
}"""
}

private def validateSize(c: Context)(size: c.Tree): c.Tree = {
import c.universe._
size match {
case lit @ Literal(Constant(size: Int)) =>
if (size == 0)
c.error(c.enclosingPosition, "Allocation of size 0 is fruitless")
else if (size < 0)
c.error(
c.enclosingPosition,
"Cannot allocate memory of negative size"
)
lit
case expr =>
val size = TermName(c.freshName())
q"""{
val $size = $expr
if($size < 0) throw new java.lang.IllegalArgumentException("Cannot allocate memory of negative size")
$size
}
"""
}
}

def stackallocN[T: c.WeakTypeTag](c: Context)(n: c.Tree): c.Tree = {
import c.universe._

val T = weakTypeOf[T]

val size, rawptr, rawSize = TermName(c.freshName())
val elements, rawptr = TermName(c.freshName())

val runtime = q"_root_.scala.scalanative.runtime"
val toRawSize = q"$runtime.Intrinsics.castIntToRawSizeUnsigned"

q"""{
val $rawSize = $runtime.Intrinsics.sizeOf[$T]
val $size = $runtime.fromRawUSize($rawSize) * $n
val $rawptr = $runtime.Intrinsics.stackalloc($size)
$runtime.libc.memset($rawptr, 0, $runtime.toRawSize($size))
val $rawptr = $runtime.Intrinsics.stackalloc[$T](
$toRawSize(${validateSize(c)(n)})
)
$runtime.fromRawPtr[$T]($rawptr)
}"""
}

def stackallocNUnsigned[T: c.WeakTypeTag](c: Context)(n: c.Tree): c.Tree = {
import c.universe._

val T = weakTypeOf[T]

val elements, rawptr = TermName(c.freshName())

val runtime = q"_root_.scala.scalanative.runtime"
val toRawSize = q"$runtime.Intrinsics.castIntToRawSizeUnsigned"
val toInt = q"$runtime.Intrinsics.castRawSizeToInt"

q"""{
val $elements = $runtime.toRawSize($n)
val $rawptr = $runtime.Intrinsics.stackalloc[$T]($elements)
$runtime.fromRawPtr[$T]($rawptr)
}"""
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ private object LazyVals {
val n = (e & mask) | (v.toLong << (ord * BITS_PER_LAZY_VAL))
if (isMultithreadingEnabled) {
// multi-threaded
val expected = stackalloc(sizeOf[Long])
val expected = stackalloc[Long]()
storeLong(expected, e)
atomic_compare_exchange_llong(bitmap, expected, n)
} else {
Expand All @@ -56,7 +56,7 @@ private object LazyVals {
def objCAS(objPtr: RawPtr, exp: Object, n: Object): Boolean = {
if (isMultithreadingEnabled) {
// multi-threaded
val expected = stackalloc(sizeOf[RawPtr])
val expected = stackalloc[RawPtr]()
storeObject(expected, exp)
atomic_compare_exchange_intptr(objPtr, expected, castObjectToRawPtr(n))
} else {
Expand Down

0 comments on commit af36d31

Please sign in to comment.