CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-scala-native--nativelib-native0-5-3

Native interoperability library for Scala Native providing unsafe operations, C-style data types, and memory management primitives for direct interaction with native code

Pending
Overview
Eval results
Files

arrays-runtime.mddocs/

Native Arrays and Runtime

Scala Native provides high-performance native array types optimized for native code generation, along with runtime utilities for garbage collection, memory intrinsics, and system integration.

Native Array Types

Base Array Class

sealed abstract class Array[T] {
  def length: Int
  def stride: Int
  def at(i: Int): Ptr[T]
  def atUnsafe(i: Int): Ptr[T]
  def apply(i: Int): T
  def update(i: Int, value: T): Unit
  def clone(): Array[T]
}

Array methods:

  • length - Number of elements in the array
  • stride - Size in bytes of each element
  • at(i) - Get pointer to element at index i (bounds checked)
  • atUnsafe(i) - Get pointer to element at index i (no bounds checking)
  • apply(i) - Read element at index i
  • update(i, value) - Write element at index i
  • clone() - Create a copy of the array

Typed Array Classes

final class BooleanArray extends Array[Boolean]
final class CharArray extends Array[Char]
final class ByteArray extends Array[Byte]  
final class ShortArray extends Array[Short]
final class IntArray extends Array[Int]
final class LongArray extends Array[Long]
final class FloatArray extends Array[Float]
final class DoubleArray extends Array[Double]
final class ObjectArray[T] extends Array[T]

Array Companion Objects

Each array type provides allocation methods:

object BooleanArray {
  def alloc(size: Int): BooleanArray
  def snapshot(values: scala.Array[Boolean]): BooleanArray
}

object CharArray {
  def alloc(size: Int): CharArray
  def snapshot(values: scala.Array[Char]): CharArray
}

object ByteArray {
  def alloc(size: Int): ByteArray
  def snapshot(values: scala.Array[Byte]): ByteArray
}

object ShortArray {
  def alloc(size: Int): ShortArray
  def snapshot(values: scala.Array[Short]): ShortArray
}

object IntArray {
  def alloc(size: Int): IntArray
  def snapshot(values: scala.Array[Int]): IntArray
}

object LongArray {
  def alloc(size: Int): LongArray
  def snapshot(values: scala.Array[Long]): LongArray
}

object FloatArray {
  def alloc(size: Int): FloatArray
  def snapshot(values: scala.Array[Float]): FloatArray
}

object DoubleArray {
  def alloc(size: Int): DoubleArray
  def snapshot(values: scala.Array[Double]): DoubleArray
}

object ObjectArray {
  def alloc[T](size: Int): ObjectArray[T]
  def snapshot[T](values: scala.Array[T]): ObjectArray[T]
}

Allocation methods:

  • alloc(size) - Allocate new array of given size
  • snapshot(values) - Create native array from Scala array

BlobArray

Special array type for conservative garbage collection:

final class BlobArray extends Array[Byte] {
  def scannableLimit: Int
  def withScannableLimit(limit: Int): BlobArray
}

object BlobArray {
  def alloc(size: Int): BlobArray
  def snapshot(values: scala.Array[Byte]): BlobArray
}

BlobArray methods:

  • scannableLimit - Get limit for GC scanning (conservative collection)
  • withScannableLimit(limit) - Set scanning limit for this array

Runtime Services

Garbage Collector Interface

Complete interface to the Boehm conservative garbage collector:

object GC {
  // Heap statistics
  def getInitHeapSize(): CSize
  def getMaxHeapSize(): CSize
  def getUsedHeapSize(): CSize
  
  // Collection statistics
  def getStatsCollectionTotal(): CSize
  def getStatsCollectionDurationTotal(): CSize
  
  // Manual collection
  def collect(): Unit
  
  // Thread registration and management
  type ThreadStartRoutine = CFuncPtr1[ThreadRoutineArg, CVoidPtr]
  type ThreadRoutineArg = CVoidPtr
  
  // POSIX thread creation proxy (registers thread with GC)
  def pthread_create(
    thread: Ptr[CUnsignedLongInt],
    attr: Ptr[CUnsignedLongLong], 
    startroutine: ThreadStartRoutine,
    args: ThreadRoutineArg
  ): CInt
  
  // Windows thread creation proxy (registers thread with GC)
  def CreateThread(
    threadAttributes: Ptr[CStruct3[CUnsignedInt, CVoidPtr, Boolean]],
    stackSize: CSize,
    startRoutine: ThreadStartRoutine, 
    routineArg: ThreadRoutineArg,
    creationFlags: CUnsignedInt,
    threadId: Ptr[CUnsignedInt]
  ): CVoidPtr
  
  // Thread state management (cooperative GC)
  object MutatorThreadState {
    def Managed: CInt    // Thread executing Scala Native code
    def Unmanaged: CInt  // Thread executing foreign/blocking code
  }
  
  // GC cooperation and synchronization
  def setMutatorThreadState(newState: CInt): Unit
  def `yield`(): Unit
  def yieldPointTrap: RawPtr
  
  // Root set management for external memory
  def addRoots(addressLow: CVoidPtr, addressHigh: CVoidPtr): Unit
  def removeRoots(addressLow: CVoidPtr, addressHigh: CVoidPtr): Unit
  
  // Weak references callback support
  type WeakReferencesCollectedCallback = CFuncPtr0[Unit]
  def setWeakReferencesCollectedCallback(callback: WeakReferencesCollectedCallback): Unit
}

GC heap statistics:

  • getInitHeapSize() - Initial heap size in bytes
  • getMaxHeapSize() - Maximum heap size in bytes
  • getUsedHeapSize() - Currently used heap size in bytes

GC collection statistics:

  • getStatsCollectionTotal() - Total number of GC collections
  • getStatsCollectionDurationTotal() - Total time spent in GC (nanoseconds)

GC control:

  • collect() - Force garbage collection
  • setWeakReferencesCollectedCallback - Set callback for weak reference cleanup

Thread registration:

  • pthread_create - POSIX thread creation with GC registration
  • CreateThread - Windows thread creation with GC registration
  • Both proxies ensure threads are properly registered with garbage collector

Thread cooperation:

  • MutatorThreadState.Managed - Thread executing managed Scala Native code
  • MutatorThreadState.Unmanaged - Thread executing foreign/blocking operations
  • setMutatorThreadState - Notify GC of thread state changes
  • yield - Cooperative yield point for stop-the-world collection
  • yieldPointTrap - Low-overhead trap-based yield point mechanism

Root set management:

  • addRoots - Register external memory range for GC scanning
  • removeRoots - Unregister external memory range from GC scanning
  • Used for malloc'd memory containing GC pointers

Runtime Information

Application and system information available at runtime:

// Package object scala.scalanative.runtime
def filename: String
def startTime: Long  
def uptime: Long

// Pointer and size conversions
def fromRawPtr[T](rawptr: RawPtr): Ptr[T]
def toRawPtr[T](ptr: Ptr[T]): RawPtr
def fromRawSize(rawSize: RawSize): Size
def fromRawUSize(rawSize: RawSize): USize
def toRawSize(size: Size): RawSize
def toRawSize(size: USize): RawSize

// Monitor and synchronization support
def getMonitor(obj: Object): Monitor
def enterMonitor(obj: Object): Unit
def exitMonitor(obj: Object): Unit

// Stack overflow protection
object StackOverflowGuards {
  def size: Int
  def setup(isMainThread: Boolean): Unit
  def reset(): Unit
  def close(): Unit
}

// Runtime exception handling
def throwDivisionByZero(): Nothing
def throwClassCast(from: RawPtr, to: RawPtr): Nothing
def throwNullPointer(): Nothing
def throwUndefined(): Nothing
def throwOutOfBounds(i: Int, length: Int): Nothing
def throwNoSuchMethod(sig: String): Nothing

Application information:

  • filename - Executable filename
  • startTime - Application start time (milliseconds since epoch)
  • uptime - Application uptime (milliseconds)

Memory conversions:

  • fromRawPtr / toRawPtr - Convert between raw and typed pointers
  • fromRawSize / toRawSize - Convert between raw and sized types
  • fromRawUSize - Convert raw size to unsigned size

Thread synchronization:

  • getMonitor - Get monitor object for synchronization
  • enterMonitor / exitMonitor - Monitor entry/exit operations
  • Available only in multithreading mode

Stack overflow protection:

  • StackOverflowGuards.size - Size of stack guard region
  • StackOverflowGuards.setup - Initialize stack overflow detection
  • StackOverflowGuards.reset - Reset after stack overflow recovery
  • StackOverflowGuards.close - Cleanup stack guards

Runtime exception helpers:

  • throwDivisionByZero - Division by zero exception
  • throwClassCast - Class cast exception with type information
  • throwNullPointer - Null pointer exception
  • throwUndefined - Undefined behavior error
  • throwOutOfBounds - Array bounds exception
  • throwNoSuchMethod - Reflection method not found

Memory Intrinsics

Low-level memory operations (from Intrinsics object):

object Intrinsics {
  // Stack allocation
  def stackalloc[T](implicit tag: Tag[T]): Ptr[T]
  def stackalloc[T](count: Int)(implicit tag: Tag[T]): Ptr[T]
  def stackalloc[T](count: UInt)(implicit tag: Tag[T]): Ptr[T]
  def stackalloc[T](count: ULong)(implicit tag: Tag[T]): Ptr[T]
  def stackalloc[T](count: CSize)(implicit tag: Tag[T]): Ptr[T]
  
  // Memory load operations
  def loadBoolean(ptr: Ptr[Byte]): Boolean
  def loadChar(ptr: Ptr[Byte]): Char
  def loadByte(ptr: Ptr[Byte]): Byte
  def loadShort(ptr: Ptr[Byte]): Short
  def loadInt(ptr: Ptr[Byte]): Int
  def loadLong(ptr: Ptr[Byte]): Long
  def loadFloat(ptr: Ptr[Byte]): Float
  def loadDouble(ptr: Ptr[Byte]): Double
  def loadObject(ptr: Ptr[Byte]): Object
  def loadRawPtr(ptr: Ptr[Byte]): RawPtr
  def loadRawSize(ptr: Ptr[Byte]): RawSize
  
  // Memory store operations  
  def storeBoolean(ptr: Ptr[Byte], value: Boolean): Unit
  def storeChar(ptr: Ptr[Byte], value: Char): Unit
  def storeByte(ptr: Ptr[Byte], value: Byte): Unit
  def storeShort(ptr: Ptr[Byte], value: Short): Unit
  def storeInt(ptr: Ptr[Byte], value: Int): Unit
  def storeLong(ptr: Ptr[Byte], value: Long): Unit
  def storeFloat(ptr: Ptr[Byte], value: Float): Unit
  def storeDouble(ptr: Ptr[Byte], value: Double): Unit
  def storeObject(ptr: Ptr[Byte], value: Object): Unit
  def storeRawPtr(ptr: Ptr[Byte], value: RawPtr): Unit
  def storeRawSize(ptr: Ptr[Byte], value: RawSize): Unit
  
  // Unsigned arithmetic
  def divUInt(l: UInt, r: UInt): UInt
  def remUInt(l: UInt, r: UInt): UInt
  def divULong(l: ULong, r: ULong): ULong  
  def remULong(l: ULong, r: ULong): ULong
  
  // Type conversions
  def byteToUInt(value: Byte): Int
  def byteToULong(value: Byte): Long
  def shortToUInt(value: Short): Int
  def shortToULong(value: Short): Long
  def intToULong(value: Int): Long
  
  // Pointer arithmetic
  def elemRawPtr(ptr: RawPtr, offset: RawSize): RawPtr
  
  // Type casting
  def castIntToFloat(value: Int): Float
  def castFloatToInt(value: Float): Int
  def castLongToDouble(value: Long): Double
  def castDoubleToLong(value: Double): Long
  def castRawPtrToObject(value: RawPtr): Object
  def castObjectToRawPtr(value: Object): RawPtr
  def castRawPtrToLong(value: RawPtr): Long
  def castRawPtrToInt(value: RawPtr): Int
  def castIntToRawPtr(value: Int): RawPtr
  def castLongToRawPtr(value: Long): RawPtr
  def castIntToRawSize(value: Int): RawSize
  def castIntToRawSizeUnsigned(value: Int): RawSize
  def castLongToRawSize(value: Long): RawSize
}

Boxing Utilities

object Boxes {
  // Unsigned type boxing
  def boxToUByte(value: Byte): UByte
  def boxToUShort(value: Short): UShort
  def boxToUInt(value: Int): UInt
  def boxToULong(value: Long): ULong
  def boxToUSize(value: RawSize): USize
  
  def unboxToUByte(value: UByte): Byte
  def unboxToUShort(value: UShort): Short
  def unboxToUInt(value: UInt): Int
  def unboxToULong(value: ULong): Long
  def unboxToUSize(value: USize): RawSize
  
  // Pointer boxing
  def boxToPtr[T](value: RawPtr): Ptr[T]
  def unboxToPtr[T](value: Ptr[T]): RawPtr
  
  // Size boxing
  def boxToSize(value: RawSize): Size
  def unboxToSize(value: Size): RawSize
}

Usage Examples

Native Array Usage

import scala.scalanative.runtime._

// Allocate arrays
val intArray = IntArray.alloc(100)
val doubleArray = DoubleArray.alloc(50)

// Initialize with values
for (i <- 0 until intArray.length) {
  intArray(i) = i * i
}

// Access elements
val value = intArray(25)  // Get element at index 25
intArray(30) = 900       // Set element at index 30

// Get pointers to elements
val ptr = intArray.at(10)  // Pointer to element 10
val unsafePtr = intArray.atUnsafe(10)  // No bounds checking

// Clone array
val copy = intArray.clone()

Creating Arrays from Scala Arrays

import scala.scalanative.runtime._

// Convert Scala array to native array
val scalaArray = Array(1, 2, 3, 4, 5)
val nativeArray = IntArray.snapshot(scalaArray)

// Work with native array
nativeArray(0) = 10
val firstElement = nativeArray(0)  // 10

// String array example
val strings = Array("hello", "world", "native")
val nativeStrings = ObjectArray.snapshot(strings)
val greeting = nativeStrings(0)  // "hello"

GC Integration

import scala.scalanative.runtime._

// Check heap status
val usedHeap = GC.getUsedHeapSize()
val maxHeap = GC.getMaxHeapSize()

println(s"Heap usage: ${usedHeap}/${maxHeap} bytes")

// Force garbage collection
GC.collect()

// Check collection statistics  
val totalCollections = GC.getStatsCollectionTotal()
val totalDuration = GC.getStatsCollectionDurationTotal()
println(s"GC: ${totalCollections} collections, ${totalDuration} ns total")

BlobArray for Conservative GC

import scala.scalanative.runtime._

// Allocate blob array (conservative GC scanning)
val blob = BlobArray.alloc(1024)

// Set scanning limit (only first 512 bytes scanned by GC)
val limitedBlob = blob.withScannableLimit(512)

// Use as regular byte array
blob(0) = 0x42.toByte
blob(1) = 0x24.toByte

val scannableLimit = limitedBlob.scannableLimit  // 512

Low-level Memory Operations

import scala.scalanative.runtime.Intrinsics._
import scala.scalanative.unsafe._

// Stack allocation (automatic cleanup)
def processData(): Unit = {
  val buffer = stackalloc[CInt](256)
  
  // Initialize buffer
  var i = 0
  while (i < 256) {
    storeInt(buffer.asInstanceOf[Ptr[Byte]] + (i * 4), i)
    i += 1
  }
  
  // Read values back
  val value = loadInt(buffer.asInstanceOf[Ptr[Byte]] + (100 * 4))  // Get 100th element
  
  // buffer automatically freed on function exit
}

// Pointer arithmetic
Zone.acquire { implicit z =>
  val array = alloc[CInt](10)
  val basePtr = toRawPtr(array)
  
  // Move to 5th element (5 * sizeof(CInt))
  val fifthPtr = elemRawPtr(basePtr, castIntToRawSize(5 * 4))
  val typedPtr = fromRawPtr[CInt](fifthPtr)
  
  !typedPtr = 42  // Set 5th element
}

Runtime Information

import scala.scalanative.runtime._

// Get application info
val executableName = filename
val applicationStartTime = startTime
val runningTime = uptime

println(s"Running $executableName for ${runningTime}ms")

// Convert between pointer types
Zone.acquire { implicit z =>
  val ptr = alloc[CInt]
  val rawPtr = toRawPtr(ptr)
  val backToTyped = fromRawPtr[CInt](rawPtr)
  
  // Size conversions
  val size = Size.valueOf(1024)
  val rawSize = toRawSize(size)
  val backToSize = fromRawSize(rawSize)
}

Performance Considerations

  1. Use native arrays for performance-critical code - they avoid boxing overhead
  2. Prefer atUnsafe when bounds are already checked - eliminates redundant checks
  3. Stack allocation is faster than heap allocation for temporary buffers
  4. BlobArray scanning limits can improve GC performance for large binary data
  5. Manual GC control can help with predictable latency requirements
  6. Clone sparingly - creates full copies, can be expensive for large arrays

Best Practices

  1. Choose appropriate array types - use primitive arrays when possible
  2. Handle GC pressure - monitor heap usage in allocation-heavy code
  3. Use snapshot carefully - creates copies, consider sharing when possible
  4. Stack allocate temporary data - automatic cleanup and better performance
  5. Set blob limits appropriately - balance GC scanning cost vs precision
  6. Profile GC behavior - use statistics to tune collection strategy

Install with Tessl CLI

npx tessl i tessl/maven-org-scala-native--nativelib-native0-5-3

docs

annotations.md

arrays-runtime.md

c-interop.md

index.md

memory-management.md

unsigned-types.md

tile.json