[Wasm][Stdlib] Add public APIs for linear memory access
Needed for interop with APIs that use linear memory.
This commit is contained in:
committed by
Space Team
parent
fbf06b5495
commit
9bc6b420a9
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.wasm.unsafe
|
||||
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
|
||||
/**
|
||||
* This annotation marks APIs for working with unmanaged WebAssembly linear memory.
|
||||
*
|
||||
* Any usage of a declaration annotated with `@UnsafeWasmMemoryApi` must be accepted either by
|
||||
* annotating that usage with the [OptIn] annotation, e.g. `@OptIn(UnsafeWasmMemoryApi::class)`,
|
||||
* or by using the compiler argument `-opt-in=kotlin.wasm.unsafe.UnsafeWasmMemoryApi`.
|
||||
*/
|
||||
@RequiresOptIn("Unsafe APIs to access to WebAssembly linear memory")
|
||||
public annotation class UnsafeWasmMemoryApi
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.wasm.unsafe
|
||||
|
||||
import kotlin.wasm.internal.WasmOp
|
||||
import kotlin.wasm.internal.implementedAsIntrinsic
|
||||
|
||||
/**
|
||||
* Linear memory pointer type.
|
||||
* Corresponds to `i32` type on 32-bit Wasm architecture.
|
||||
*/
|
||||
@UnsafeWasmMemoryApi
|
||||
public value class Pointer public constructor(public val address: UInt) {
|
||||
|
||||
/** Adds an [Int] to the address of this [Pointer] */
|
||||
public operator fun plus(other: Int): Pointer =
|
||||
Pointer(address + other.toUInt())
|
||||
|
||||
/** Subtracts an [Int] from the address of this [Pointer] */
|
||||
public operator fun minus(other: Int): Pointer =
|
||||
Pointer(address - other.toUInt())
|
||||
|
||||
/** Adds an [UInt] to the address of this [Pointer] */
|
||||
public operator fun plus(other: UInt): Pointer =
|
||||
Pointer(address + other)
|
||||
|
||||
/** Subtracts an [UInt] from the address of this [Pointer] */
|
||||
public operator fun minus(other: UInt): Pointer =
|
||||
Pointer(address - other)
|
||||
|
||||
/** Load a Byte (8 bit) value */
|
||||
@WasmOp(WasmOp.I32_LOAD8_S)
|
||||
public fun loadByte(): Byte =
|
||||
implementedAsIntrinsic
|
||||
|
||||
/** Load a Short (16 bit) value */
|
||||
@WasmOp(WasmOp.I32_LOAD16_S)
|
||||
public fun loadShort(): Short =
|
||||
implementedAsIntrinsic
|
||||
|
||||
/** Load an Int (32 bit) value */
|
||||
@WasmOp(WasmOp.I32_LOAD)
|
||||
public fun loadInt(): Int =
|
||||
implementedAsIntrinsic
|
||||
|
||||
/** Load a Long (64 bit) value */
|
||||
@WasmOp(WasmOp.I64_LOAD)
|
||||
public fun loadLong(): Long =
|
||||
implementedAsIntrinsic
|
||||
|
||||
/** Store a Byte (8 bit) [value] */
|
||||
@WasmOp(WasmOp.I32_STORE8)
|
||||
public fun storeByte(value: Byte): Unit =
|
||||
implementedAsIntrinsic
|
||||
|
||||
/** Store a Short (16 bit) [value] */
|
||||
@WasmOp(WasmOp.I32_STORE16)
|
||||
public fun storeShort(value: Short): Unit =
|
||||
implementedAsIntrinsic
|
||||
|
||||
/** Store an Int (32 bit) [value] */
|
||||
@WasmOp(WasmOp.I32_STORE)
|
||||
public fun storeInt(value: Int): Unit =
|
||||
implementedAsIntrinsic
|
||||
|
||||
/** Store a Long (64 bit) [value] */
|
||||
@WasmOp(WasmOp.I64_STORE)
|
||||
public fun storeLong(value: Long): Unit =
|
||||
implementedAsIntrinsic
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package kotlin.wasm.unsafe
|
||||
|
||||
import kotlin.wasm.internal.WasmOp
|
||||
import kotlin.wasm.internal.implementedAsIntrinsic
|
||||
import kotlin.wasm.internal.unsafeGetScratchRawMemory
|
||||
import kotlin.contracts.*
|
||||
|
||||
/**
|
||||
* WebAssembly linear memory allocator.
|
||||
*/
|
||||
@UnsafeWasmMemoryApi
|
||||
public abstract class MemoryAllocator {
|
||||
/**
|
||||
* Allocates a block of uninitialized linear memory of the given [size] in bytes.
|
||||
*
|
||||
* @return an address of allocated memory. It is guaranteed to be a multiple of 8.
|
||||
*/
|
||||
public abstract fun allocate(size: Int): Pointer
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the [block] of code, providing it a temporary [MemoryAllocator] as an argument, and returns the result of this block.
|
||||
*
|
||||
* Frees all memory allocated with the provided allocator after running the [block].
|
||||
*
|
||||
* This function is intened to facilitate the exchange of values with outside world through linear memory.
|
||||
* For example:
|
||||
*
|
||||
* ```
|
||||
* val buffer_size = ...
|
||||
* withScopedMemoryAllocator { allocator ->
|
||||
* val buffer_address = allocator.allocate(buffer_size)
|
||||
* importedWasmFunctionThatWritesToBuffer(buffer_address, buffer_size)
|
||||
* return readDataFromBufferIntoManagedKotlinMemory(buffer_address, buffer_size)
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* WARNING! Addresses allocated inside the [block] function become invalid after exiting the function.
|
||||
*
|
||||
* WARNING! A nested call to [withScopedMemoryAllocator] will temporarily disable the allocator from the outer scope
|
||||
* for the duration of the call. Calling [MemoryAllocator.allocate] on a disabled allocator
|
||||
* will throw [IllegalStateException].
|
||||
*
|
||||
* WARNING! Accessing the allocator outside of the [block] scope will throw [IllegalStateException].
|
||||
*/
|
||||
@UnsafeWasmMemoryApi
|
||||
public inline fun <T> withScopedMemoryAllocator(
|
||||
block: (allocator: MemoryAllocator) -> T
|
||||
): T {
|
||||
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
|
||||
val allocator = createAllocatorInTheNewScope()
|
||||
val result = try {
|
||||
block(allocator)
|
||||
} finally {
|
||||
allocator.destroy()
|
||||
currentAllocator = allocator.parent
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
@UnsafeWasmMemoryApi
|
||||
internal fun createAllocatorInTheNewScope(): ScopedMemoryAllocator {
|
||||
val allocator = currentAllocator?.createChild() ?:
|
||||
ScopedMemoryAllocator(unsafeGetScratchRawMemory(), parent = null)
|
||||
currentAllocator = allocator
|
||||
return allocator
|
||||
}
|
||||
|
||||
|
||||
@PublishedApi
|
||||
@UnsafeWasmMemoryApi
|
||||
internal var currentAllocator: ScopedMemoryAllocator? = null
|
||||
|
||||
@PublishedApi
|
||||
@UnsafeWasmMemoryApi
|
||||
internal class ScopedMemoryAllocator(
|
||||
startAddress: Int,
|
||||
// Allocator from parent scope or null for top-level scope.
|
||||
@PublishedApi
|
||||
internal var parent: ScopedMemoryAllocator?,
|
||||
) : MemoryAllocator() {
|
||||
// true if allocator is out of scope
|
||||
private var destroyed = false
|
||||
// true if child allocator is active
|
||||
private var suspended = false
|
||||
// all memory is available starting from this address
|
||||
private var availableAddress: ULong = startAddress.toULong()
|
||||
|
||||
override fun allocate(size: Int): Pointer {
|
||||
check(!destroyed) { "ScopedMemoryAllocator is destroyed when out of scope" }
|
||||
check(!suspended) { "ScopedMemoryAllocator is suspended when nested allocators are used" }
|
||||
|
||||
// Pad available address to align it to 8
|
||||
// 8 is a max alignment number currently needed for Wasm component model canonical ABI
|
||||
val align = 8uL
|
||||
val result = (availableAddress + align - 1uL) and (align - 1uL).inv()
|
||||
check(result % 8uL == 0uL)
|
||||
|
||||
availableAddress = result + size.toULong()
|
||||
|
||||
if (availableAddress > UInt.MAX_VALUE.toULong()) {
|
||||
error("Out of linear memory. All available address space (4gb) is used.")
|
||||
}
|
||||
|
||||
val currentMaxSize = wasmMemorySize().toULong() * WASM_PAGE_SIZE_IN_BYTES.toULong()
|
||||
if (availableAddress >= currentMaxSize) {
|
||||
|
||||
val numPagesToGrow =
|
||||
(availableAddress - currentMaxSize) / WASM_PAGE_SIZE_IN_BYTES.toULong() + 2uL
|
||||
|
||||
if (wasmMemoryGrow(numPagesToGrow.toInt()) == -1) {
|
||||
error("Out of linear memory. memory.grow returned -1")
|
||||
}
|
||||
}
|
||||
|
||||
check(availableAddress < wasmMemorySize().toULong() * WASM_PAGE_SIZE_IN_BYTES.toULong())
|
||||
|
||||
return Pointer(result.toUInt())
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun createChild(): ScopedMemoryAllocator {
|
||||
val child = ScopedMemoryAllocator(availableAddress.toInt(), parent = this)
|
||||
suspended = true
|
||||
return child
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun destroy() {
|
||||
destroyed = true
|
||||
parent?.suspended = false
|
||||
}
|
||||
}
|
||||
|
||||
private const val WASM_PAGE_SIZE_IN_BYTES = 65_536 // 64 KiB
|
||||
|
||||
/**
|
||||
* Current linear memory size in pages
|
||||
*/
|
||||
@WasmOp(WasmOp.MEMORY_SIZE)
|
||||
internal fun wasmMemorySize(): Int =
|
||||
implementedAsIntrinsic
|
||||
|
||||
/**
|
||||
* Grow memory by a given delta (in pages).
|
||||
* Return the previous size, or -1 if enough memory cannot be allocated.
|
||||
*/
|
||||
@WasmOp(WasmOp.MEMORY_GROW)
|
||||
internal fun wasmMemoryGrow(delta: Int): Int =
|
||||
implementedAsIntrinsic
|
||||
Reference in New Issue
Block a user