Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions skainet-lang/skainet-lang-core/api/jvm/skainet-lang-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,39 @@ public final class sk/ainet/lang/memory/FormatKt {
public static final fun getFormatOrNull (Lsk/ainet/lang/tensor/Tensor;)Lsk/ainet/lang/memory/Format;
}

public abstract interface class sk/ainet/lang/memory/Owner {
}

public final class sk/ainet/lang/memory/Owner$Alias : sk/ainet/lang/memory/Owner {
public fun <init> (Lsk/ainet/lang/memory/Storage;)V
public final fun getParent ()Lsk/ainet/lang/memory/Storage;
public fun toString ()Ljava/lang/String;
}

public final class sk/ainet/lang/memory/Owner$Borrowed : sk/ainet/lang/memory/Owner {
public fun <init> ()V
public fun <init> (Ljava/lang/Object;)V
public synthetic fun <init> (Ljava/lang/Object;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/Object;
public final fun copy (Ljava/lang/Object;)Lsk/ainet/lang/memory/Owner$Borrowed;
public static synthetic fun copy$default (Lsk/ainet/lang/memory/Owner$Borrowed;Ljava/lang/Object;ILjava/lang/Object;)Lsk/ainet/lang/memory/Owner$Borrowed;
public fun equals (Ljava/lang/Object;)Z
public final fun getExternal ()Ljava/lang/Object;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class sk/ainet/lang/memory/Owner$Owned : sk/ainet/lang/memory/Owner {
public fun <init> (Lsk/ainet/lang/memory/ScopeKind;)V
public final fun component1 ()Lsk/ainet/lang/memory/ScopeKind;
public final fun copy (Lsk/ainet/lang/memory/ScopeKind;)Lsk/ainet/lang/memory/Owner$Owned;
public static synthetic fun copy$default (Lsk/ainet/lang/memory/Owner$Owned;Lsk/ainet/lang/memory/ScopeKind;ILjava/lang/Object;)Lsk/ainet/lang/memory/Owner$Owned;
public fun equals (Ljava/lang/Object;)Z
public final fun getScope ()Lsk/ainet/lang/memory/ScopeKind;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class sk/ainet/lang/memory/ScopeKind : java/lang/Enum {
public static final field AMBIENT Lsk/ainet/lang/memory/ScopeKind;
public static final field FORWARD Lsk/ainet/lang/memory/ScopeKind;
Expand All @@ -714,6 +747,98 @@ public final class sk/ainet/lang/memory/ScopeKind : java/lang/Enum {
public static fun values ()[Lsk/ainet/lang/memory/ScopeKind;
}

public abstract class sk/ainet/lang/memory/Storage : java/lang/AutoCloseable {
public final fun checkAlive ()V
public final fun close ()V
public abstract fun getDebugOrigin ()Lsk/ainet/lang/tensor/TensorId;
public abstract fun getDomain ()Lsk/ainet/lang/tensor/storage/MemoryDomain;
public abstract fun getId-TPZW6QE ()J
public abstract fun getOwner ()Lsk/ainet/lang/memory/Owner;
public final fun getScope ()Lsk/ainet/lang/memory/ScopeKind;
protected abstract fun getSink ()Lsk/ainet/lang/memory/trace/TraceSink;
public abstract fun getSizeBytes ()J
public final fun isAlive ()Z
public abstract fun isMutable ()Z
protected fun onClose ()V
public abstract fun slice (JJ)Lsk/ainet/lang/memory/Storage;
public fun toString ()Ljava/lang/String;
}

public abstract class sk/ainet/lang/memory/Storage$Device : sk/ainet/lang/memory/Storage {
public fun <init> ()V
public fun getDomain ()Lsk/ainet/lang/tensor/storage/MemoryDomain;
}

public final class sk/ainet/lang/memory/Storage$Heap : sk/ainet/lang/memory/Storage {
public static final field Companion Lsk/ainet/lang/memory/Storage$Heap$Companion;
public synthetic fun <init> (J[F[I[BIJLsk/ainet/lang/memory/Owner;Lsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getArrayOffset ()I
public final fun getBytes ()[B
public fun getDebugOrigin ()Lsk/ainet/lang/tensor/TensorId;
public fun getDomain ()Lsk/ainet/lang/tensor/storage/MemoryDomain;
public final fun getElementBytes ()I
public final fun getElementCount ()I
public final fun getFloats ()[F
public fun getId-TPZW6QE ()J
public final fun getInts ()[I
public fun getOwner ()Lsk/ainet/lang/memory/Owner;
public fun getSizeBytes ()J
public fun isMutable ()Z
public fun slice (JJ)Lsk/ainet/lang/memory/Storage$Heap;
public synthetic fun slice (JJ)Lsk/ainet/lang/memory/Storage;
}

public final class sk/ainet/lang/memory/Storage$Heap$Companion {
public final fun bytes (ILsk/ainet/lang/memory/ScopeKind;Lsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;)Lsk/ainet/lang/memory/Storage$Heap;
public static synthetic fun bytes$default (Lsk/ainet/lang/memory/Storage$Heap$Companion;ILsk/ainet/lang/memory/ScopeKind;Lsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;ILjava/lang/Object;)Lsk/ainet/lang/memory/Storage$Heap;
public final fun floats (ILsk/ainet/lang/memory/ScopeKind;Lsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;)Lsk/ainet/lang/memory/Storage$Heap;
public static synthetic fun floats$default (Lsk/ainet/lang/memory/Storage$Heap$Companion;ILsk/ainet/lang/memory/ScopeKind;Lsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;ILjava/lang/Object;)Lsk/ainet/lang/memory/Storage$Heap;
public final fun ints (ILsk/ainet/lang/memory/ScopeKind;Lsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;)Lsk/ainet/lang/memory/Storage$Heap;
public static synthetic fun ints$default (Lsk/ainet/lang/memory/Storage$Heap$Companion;ILsk/ainet/lang/memory/ScopeKind;Lsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;ILjava/lang/Object;)Lsk/ainet/lang/memory/Storage$Heap;
public final fun wrap ([BIIZLsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;)Lsk/ainet/lang/memory/Storage$Heap;
public final fun wrap ([FIIZLsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;)Lsk/ainet/lang/memory/Storage$Heap;
public final fun wrap ([IIIZLsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;)Lsk/ainet/lang/memory/Storage$Heap;
public static synthetic fun wrap$default (Lsk/ainet/lang/memory/Storage$Heap$Companion;[BIIZLsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;ILjava/lang/Object;)Lsk/ainet/lang/memory/Storage$Heap;
public static synthetic fun wrap$default (Lsk/ainet/lang/memory/Storage$Heap$Companion;[FIIZLsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;ILjava/lang/Object;)Lsk/ainet/lang/memory/Storage$Heap;
public static synthetic fun wrap$default (Lsk/ainet/lang/memory/Storage$Heap$Companion;[IIIZLsk/ainet/lang/tensor/TensorId;Lsk/ainet/lang/memory/trace/TraceSink;ILjava/lang/Object;)Lsk/ainet/lang/memory/Storage$Heap;
}

public abstract class sk/ainet/lang/memory/Storage$Mapped : sk/ainet/lang/memory/Storage {
public fun <init> ()V
public fun getDomain ()Lsk/ainet/lang/tensor/storage/MemoryDomain;
}

public abstract class sk/ainet/lang/memory/Storage$OffHeap : sk/ainet/lang/memory/Storage {
public fun <init> ()V
public fun getDomain ()Lsk/ainet/lang/tensor/storage/MemoryDomain;
}

public final class sk/ainet/lang/memory/StorageClosedException : java/lang/IllegalStateException {
public synthetic fun <init> (JLsk/ainet/lang/tensor/TensorId;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (JLsk/ainet/lang/tensor/TensorId;Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getOrigin ()Lsk/ainet/lang/tensor/TensorId;
public final fun getStorageId-TPZW6QE ()J
}

public final class sk/ainet/lang/memory/StorageId {
public static final field Companion Lsk/ainet/lang/memory/StorageId$Companion;
public static final synthetic fun box-impl (J)Lsk/ainet/lang/memory/StorageId;
public static fun constructor-impl (J)J
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (JLjava/lang/Object;)Z
public static final fun equals-impl0 (JJ)Z
public final fun getValue ()J
public fun hashCode ()I
public static fun hashCode-impl (J)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (J)Ljava/lang/String;
public final synthetic fun unbox-impl ()J
}

public final class sk/ainet/lang/memory/StorageId$Companion {
public final fun next-TPZW6QE ()J
}

public final class sk/ainet/lang/memory/plan/Budget {
public static final field Companion Lsk/ainet/lang/memory/plan/Budget$Companion;
public static final field RESERVE_ANDROID_JVM J
Expand Down Expand Up @@ -5910,6 +6035,7 @@ public final class sk/ainet/lang/tensor/storage/LogicalDType$Companion {
public final class sk/ainet/lang/tensor/storage/MemoryDomain : java/lang/Enum {
public static final field DEVICE_LOCAL Lsk/ainet/lang/tensor/storage/MemoryDomain;
public static final field HOST_HEAP Lsk/ainet/lang/tensor/storage/MemoryDomain;
public static final field HOST_OFFHEAP Lsk/ainet/lang/tensor/storage/MemoryDomain;
public static final field HOST_PINNED Lsk/ainet/lang/tensor/storage/MemoryDomain;
public static final field MMAP_FILE Lsk/ainet/lang/tensor/storage/MemoryDomain;
public static final field UNIFIED Lsk/ainet/lang/tensor/storage/MemoryDomain;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
@file:OptIn(kotlin.concurrent.atomics.ExperimentalAtomicApi::class)

package sk.ainet.lang.memory

import sk.ainet.lang.memory.trace.NoopTraceSink
import sk.ainet.lang.memory.trace.TraceEvent
import sk.ainet.lang.memory.trace.TraceSink
import sk.ainet.lang.tensor.TensorId
import sk.ainet.lang.tensor.storage.MemoryDomain
import kotlin.concurrent.atomics.AtomicLong
import kotlin.concurrent.atomics.fetchAndIncrement
import kotlin.jvm.JvmInline

/**
* Monotonic per-process identity of one allocation (SKEEP-003 §0 *StorageId*): what the memory
* debugger keys on. One `TensorId` maps to many storage ids over time (a `Forward` scope is
* recycled every step); one storage may back many `TensorId`s (views, KV ring).
*/
@ExperimentalMemoryApi
@JvmInline
public value class StorageId(public val value: Long) {
override fun toString(): String = "#$value"

public companion object {
private val counter = AtomicLong(1L)
/** The next id; thread-safe. */
public fun next(): StorageId = StorageId(counter.fetchAndIncrement())
}
}

/**
* How a [Storage] came to hold its bytes (SKEEP-003 §0 *Owner*, §4.4). Ownership is a
* constructor argument, then it is enforced: [Borrowed] storage cannot be freed through us,
* [Alias] keeps its parent alive and cannot free or resize, [Owned] storage is freed exactly once,
* by its scope.
*/
@ExperimentalMemoryApi
public sealed interface Owner {
/** We allocated the bytes; the scope of [scope] kind frees them. */
public data class Owned(val scope: ScopeKind) : Owner
/** The caller's array / buffer / segment / mmap; we never free it. [external] identifies the lender for debugging. */
public data class Borrowed(val external: Any? = null) : Owner
/** A view's storage reference: a strong reference to [parent]; mutability delegated. */
public class Alias(public val parent: Storage) : Owner {
override fun toString(): String = "Alias(parent=${parent.id})"
}
}

/** Thrown on any access to a storage after its scope or the storage itself was closed (SKEEP-003 rule 2). */
@ExperimentalMemoryApi
public class StorageClosedException(
public val storageId: StorageId,
public val origin: TensorId?,
message: String = "Storage ${storageId}${origin?.let { " (" + it.canonical + ")" } ?: ""} is closed",
) : IllegalStateException(message)

/**
* The one and only owner of bytes (SKEEP-003 §0, §4.2). `TensorView` interprets a storage, `Tensor`
* is the DSL handle over a view — neither owns bytes. Sealed over the four kinds; [Heap] is final
* and common, [OffHeap] / [Mapped] / [Device] are abstract here and bound per platform
* (`MemorySegment` / `FileChannel.map` on the JVM, `malloc` / `mmap` on Native, heap fallbacks on
* JS/Wasm — slices #1019, #1020).
*
* Rules enforced here: exactly one byte owner; closing invalidates every alias; a borrowed storage
* is released (forgotten) but never freed; every access after close throws
* [StorageClosedException] carrying the id and origin — not a JVM crash, not silent corruption.
*/
@ExperimentalMemoryApi
public sealed class Storage : AutoCloseable {
public abstract val id: StorageId
public abstract val sizeBytes: Long
public abstract val owner: Owner
public abstract val domain: MemoryDomain
/** The `TensorId` these bytes back, for diagnostics; `null` for anonymous storage. */
public abstract val debugOrigin: TensorId?
/** Where trace events about this storage go (allocation, close). */
protected abstract val sink: TraceSink

/** The lifetime class: from [Owner.Owned], else the parent's, else `AMBIENT`. */
public val scope: ScopeKind
get() = when (val o = owner) {
is Owner.Owned -> o.scope
is Owner.Alias -> o.parent.scope
is Owner.Borrowed -> ScopeKind.AMBIENT
}

private var closed: Boolean = false

/** `true` until this storage — or, for an alias, its parent — is closed. */
public val isAlive: Boolean
get() = !closed && ((owner as? Owner.Alias)?.parent?.isAlive ?: true)

/** Whether writes are allowed: owned and borrowed-mutable storage yes; an alias delegates to its parent. */
public abstract val isMutable: Boolean

/** Throws [StorageClosedException] if this storage is no longer alive. Called by every accessor. */
public fun checkAlive() { if (!isAlive) throw StorageClosedException(id, debugOrigin) }

/**
* Close: an [Owner.Owned] storage releases its bytes (exactly once); an [Owner.Borrowed] storage
* is forgotten (the lender's bytes are untouched); an [Owner.Alias] is detached (its parent is
* unaffected). Idempotent.
*/
final override fun close() {
if (closed) return
closed = true
onClose()
if (sink.isEnabled && owner !is Owner.Alias) sink.emit(TraceEvent.Free(id.value, scope, sizeBytes))
}

/** Release platform resources (owned storage only); default nothing. */
protected open fun onClose() {}

/** A zero-copy alias over `[offsetBytes, offsetBytes + lengthBytes)` of this storage. */
public abstract fun slice(offsetBytes: Long, lengthBytes: Long): Storage

override fun toString(): String = "${this::class.simpleName}(${id}, ${sizeBytes} B, $owner, $domain${debugOrigin?.let { ", $it" } ?: ""}${if (isAlive) "" else ", closed"})"

/**
* Heap storage: a Kotlin array on the managed heap — the JIT-friendliest kind, the only kind on
* JS/Wasm, the default for `Ambient` scope. Exactly one of [floats], [ints], [bytes] is non-null;
* [arrayOffset] (in elements of that array) and [sizeBytes] delimit the region.
*
* Kernels unwrap once per call (`floats` / `ints` / `bytes` + [arrayOffset]) — the Phase-2 spike
* showed per-element access through a view is the slow path by design.
*/
public class Heap private constructor(
override val id: StorageId,
public val floats: FloatArray?,
public val ints: IntArray?,
public val bytes: ByteArray?,
public val arrayOffset: Int,
override val sizeBytes: Long,
override val owner: Owner,
override val debugOrigin: TensorId?,
override val sink: TraceSink,
private val mutable: Boolean,
) : Storage() {
override val domain: MemoryDomain get() = MemoryDomain.HOST_HEAP
override val isMutable: Boolean get() = (owner as? Owner.Alias)?.parent?.isMutable ?: mutable

/** Bytes per element of the backing array (4 for floats/ints, 1 for bytes). */
public val elementBytes: Int get() = if (bytes != null) 1 else 4
/** Number of array elements this storage spans. */
public val elementCount: Int get() = (sizeBytes / elementBytes).toInt()

override fun slice(offsetBytes: Long, lengthBytes: Long): Heap {
checkAlive()
require(offsetBytes >= 0 && lengthBytes >= 0 && offsetBytes + lengthBytes <= sizeBytes) { "slice [$offsetBytes, ${offsetBytes + lengthBytes}) outside $sizeBytes bytes" }
require(offsetBytes % elementBytes == 0L && lengthBytes % elementBytes == 0L) { "slice must align to $elementBytes-byte elements" }
return Heap(StorageId.next(), floats, ints, bytes, arrayOffset + (offsetBytes / elementBytes).toInt(), lengthBytes, Owner.Alias(this), debugOrigin, sink, mutable)
}

public companion object {
private fun create(floats: FloatArray?, ints: IntArray?, bytes: ByteArray?, offset: Int, count: Int, owner: Owner, origin: TensorId?, sink: TraceSink, mutable: Boolean): Heap {
val eb = if (bytes != null) 1 else 4
val s = Heap(StorageId.next(), floats, ints, bytes, offset, count.toLong() * eb, owner, origin, sink, mutable)
if (sink.isEnabled && owner is Owner.Owned) sink.emit(TraceEvent.Allocation(s.id.value, owner.scope, s.sizeBytes, origin))
return s
}

/** Allocate [count] zeroed floats on the heap, owned by a scope of kind [scope]. */
public fun floats(count: Int, scope: ScopeKind = ScopeKind.AMBIENT, origin: TensorId? = null, sink: TraceSink = NoopTraceSink): Heap =
create(FloatArray(count), null, null, 0, count, Owner.Owned(scope), origin, sink, true)
public fun ints(count: Int, scope: ScopeKind = ScopeKind.AMBIENT, origin: TensorId? = null, sink: TraceSink = NoopTraceSink): Heap =
create(null, IntArray(count), null, 0, count, Owner.Owned(scope), origin, sink, true)
public fun bytes(count: Int, scope: ScopeKind = ScopeKind.AMBIENT, origin: TensorId? = null, sink: TraceSink = NoopTraceSink): Heap =
create(null, null, ByteArray(count), 0, count, Owner.Owned(scope), origin, sink, true)

/** Wrap the caller's array without copying — never freed by us (the #782 `copyOf` replacement). */
public fun wrap(array: FloatArray, offset: Int = 0, count: Int = array.size - offset, mutable: Boolean = true, origin: TensorId? = null, sink: TraceSink = NoopTraceSink): Heap =
create(array, null, null, offset, count, Owner.Borrowed(array), origin, sink, mutable)
public fun wrap(array: IntArray, offset: Int = 0, count: Int = array.size - offset, mutable: Boolean = true, origin: TensorId? = null, sink: TraceSink = NoopTraceSink): Heap =
create(null, array, null, offset, count, Owner.Borrowed(array), origin, sink, mutable)
public fun wrap(array: ByteArray, offset: Int = 0, count: Int = array.size - offset, mutable: Boolean = true, origin: TensorId? = null, sink: TraceSink = NoopTraceSink): Heap =
create(null, null, array, offset, count, Owner.Borrowed(array), origin, sink, mutable)
}
}

/** Off-heap storage (`MemorySegment` / direct buffer / `malloc`): bound per platform in #1019/#1020. */
public abstract class OffHeap : Storage() { override val domain: MemoryDomain get() = MemoryDomain.HOST_OFFHEAP }

/** A mapped file region (`FileChannel.map` / `mmap`): bound per platform in #1019/#1020. */
public abstract class Mapped : Storage() { override val domain: MemoryDomain get() = MemoryDomain.MMAP_FILE }

/** An accelerator buffer — placeholder until a device backend is scheduled (PRD non-goal). */
public abstract class Device : Storage() { override val domain: MemoryDomain get() = MemoryDomain.DEVICE_LOCAL }
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public enum class DeviceKind {
public enum class MemoryDomain {
/** Standard JVM / native heap allocation. */
HOST_HEAP,
/** Off-heap host memory: `MemorySegment` / direct `ByteBuffer` / `malloc` — not GC-managed, freed by its scope (SKEEP-003). */
HOST_OFFHEAP,
/** Pinned (non-pageable) host memory for fast DMA transfers. */
HOST_PINNED,
/** Memory-mapped file (immutable, OS-paged). */
Expand Down
Loading
Loading