[Wasm] Wasi stdlib implementation

KT-56608
This commit is contained in:
Igor Yakovlev
2023-07-31 14:27:09 +02:00
committed by Zalim Bashorov
parent 090f393f97
commit 8cc0660693
61 changed files with 817 additions and 103 deletions
+23 -6
View File
@@ -1,4 +1,6 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.targets.js.KotlinWasmTargetAttribute
import org.jetbrains.kotlin.gradle.targets.js.d8.D8RootPlugin
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink
@@ -81,14 +83,20 @@ kotlin {
@Suppress("DEPRECATION")
@OptIn(org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl::class)
wasm("wasm") {
d8()
nodejs()
}
fun KotlinSourceSet.addWasmMainDirs() {
kotlin.srcDirs("builtins", "internal", "runtime", "src", "stubs")
kotlin.srcDirs("$rootDir/libraries/stdlib/native-wasm/src")
kotlin.srcDirs(files(builtInsSources.map { it.destinationDir }))
}
sourceSets {
named("wasmMain") {
kotlin.srcDirs("builtins", "internal", "runtime", "src", "stubs")
kotlin.srcDirs("$rootDir/libraries/stdlib/native-wasm/src")
kotlin.srcDirs(files(builtInsSources.map { it.destinationDir }))
addWasmMainDirs()
// kotlin.srcDirs("js/builtins/kotlin", "js/internal", "js/src/kotlin", "js/src/kotlinx", "js/src/org.w3c")
kotlin.srcDirs("wasi/builtins/kotlin", "wasi/internal", "wasi/src/kotlin", "wasi/src/kotlinx")
}
named("commonMain") {
@@ -153,8 +161,17 @@ val compileTestDevelopmentExecutableKotlinWasm = tasks.named<KotlinJsIrLink>("co
(this as KotlinCompile<*>).kotlinOptions.freeCompilerArgs += listOf("-Xwasm-enable-array-range-checks")
}
val runtimeElements by configurations.creating {}
val apiElements by configurations.creating {}
val runtimeElements by configurations.creating {
attributes {
attributes.attribute(KotlinWasmTargetAttribute.wasmTargetAttribute, KotlinWasmTargetAttribute.js)
}
}
val apiElements by configurations.creating {
attributes {
attributes.attribute(KotlinWasmTargetAttribute.wasmTargetAttribute, KotlinWasmTargetAttribute.js)
}
}
publish(sbom = false) {
pom.packaging = "klib"
@@ -3,50 +3,4 @@
* 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
import kotlin.wasm.internal.ExternalInterfaceType
import kotlin.wasm.internal.getSimpleName
import kotlin.wasm.internal.jsToKotlinStringAdapter
/**
* The base class for all errors and exceptions. Only instances of this class can be thrown or caught.
*
* @param message the detail message string.
* @param cause the cause of this throwable.
*/
public open class Throwable(open val message: String?, open val cause: kotlin.Throwable?) {
constructor(message: String?) : this(message, null)
constructor(cause: Throwable?) : this(cause?.toString(), cause)
constructor() : this(null, null)
internal val jsStack: ExternalInterfaceType = captureStackTrace()
private var _stack: String? = null
internal val stack: String
get() {
var value = _stack
if (value == null) {
value = jsToKotlinStringAdapter(jsStack).removePrefix("Error\n")
_stack = value
}
return value
}
internal var suppressedExceptionsList: MutableList<Throwable>? = null
/**
* Returns the short description of this throwable consisting of the exception class name
* followed by the exception message if it is not null.
*/
public override fun toString(): String {
val s = getSimpleName(this.typeInfo)
return if (message != null) s + ": " + message.toString() else s
}
}
private fun captureStackTrace(): ExternalInterfaceType =
js("new Error().stack")
package kotlin
@@ -0,0 +1,8 @@
/*
* 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.internal
internal var isNotFirstWasmExportCall: Boolean = false
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2021 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.internal
@Suppress("UNUSED_PARAMETER")
@ExcludedFromCodegen
/*
* Compiler generates inplace next code:
* ```
* block (result anyref) {
* local.get 0
* extern.internalize
* br_on_cast_fail 0000_0011b 0 (type any) (type $kotlin.Any)
* return
* }
* ```
*/
internal fun returnArgumentIfItIsKotlinAny(): Unit = implementedAsIntrinsic
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2023 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.
*/
@@ -9,8 +9,6 @@
package kotlin.wasm.internal
import kotlin.wasm.internal.ExternalInterfaceType
@WasmOp(WasmOp.UNREACHABLE)
internal fun wasm_unreachable(): Nothing =
implementedAsIntrinsic
@@ -495,10 +493,4 @@ internal fun wasm_i64_popcnt(a: Long): Long =
@PublishedApi
@WasmOp(WasmOp.I64_CTZ)
internal fun wasm_i64_ctz(a: Long): Long =
implementedAsIntrinsic
// Reference type operators
@WasmOp(WasmOp.REF_IS_NULL)
internal external fun wasm_externref_is_null(a: ExternalInterfaceType?): Boolean =
implementedAsIntrinsic
@@ -0,0 +1,52 @@
/*
* 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
import kotlin.wasm.internal.ExternalInterfaceType
import kotlin.wasm.internal.getSimpleName
import kotlin.wasm.internal.jsToKotlinStringAdapter
/**
* The base class for all errors and exceptions. Only instances of this class can be thrown or caught.
*
* @param message the detail message string.
* @param cause the cause of this throwable.
*/
public open class Throwable(open val message: String?, open val cause: kotlin.Throwable?) {
constructor(message: String?) : this(message, null)
constructor(cause: Throwable?) : this(cause?.toString(), cause)
constructor() : this(null, null)
internal val jsStack: ExternalInterfaceType = captureStackTrace()
private var _stack: String? = null
internal val stack: String
get() {
var value = _stack
if (value == null) {
value = jsToKotlinStringAdapter(jsStack).removePrefix("Error\n")
_stack = value
}
return value
}
internal var suppressedExceptionsList: MutableList<Throwable>? = null
/**
* Returns the short description of this throwable consisting of the exception class name
* followed by the exception message if it is not null.
*/
public override fun toString(): String {
val s = getSimpleName(this.typeInfo)
return if (message != null) s + ": " + message.toString() else s
}
}
private fun captureStackTrace(): ExternalInterfaceType =
js("new Error().stack")
@@ -18,6 +18,4 @@ private fun throwJsError(message: String?, wasmTypeName: String?, stack: Externa
internal fun throwAsJsException(t: Throwable): Nothing {
throwJsError(t.message, getSimpleName(t.typeInfo), t.jsStack)
}
internal var isNotFirstWasmExportCall: Boolean = false
}
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2023 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.
*/
@@ -145,21 +145,6 @@ private fun Any.asWasmExternRef(): ExternalInterfaceType =
internal fun isNullish(ref: ExternalInterfaceType?): Boolean =
js("ref == null")
@Suppress("UNUSED_PARAMETER")
@ExcludedFromCodegen
/*
* Compiler generates inplace next code:
* ```
* block (result anyref) {
* local.get 0
* extern.internalize
* br_on_cast_fail 0000_0011b 0 (type any) (type $kotlin.Any)
* return
* }
* ```
*/
internal fun returnArgumentIfItIsKotlinAny(): Unit = implementedAsIntrinsic
internal fun externRefToAny(ref: ExternalInterfaceType): Any? {
// TODO rewrite it so to get something like:
// block {
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2023 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.
*/
@file:ExcludedFromCodegen
@file:Suppress("unused", "NON_ABSTRACT_FUNCTION_WITH_NO_BODY", "INLINE_CLASS_IN_EXTERNAL_DECLARATION", "UNUSED_PARAMETER")
package kotlin.wasm.internal
import kotlin.wasm.internal.ExternalInterfaceType
// Reference type operators
@WasmOp(WasmOp.REF_IS_NULL)
internal external fun wasm_externref_is_null(a: ExternalInterfaceType?): Boolean
@@ -1,5 +1,5 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2023 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.
*/
@@ -37,6 +37,4 @@ public actual fun print(message: Any?) {
public actual fun readln(): String = throw UnsupportedOperationException("readln is not supported in Kotlin/WASM")
@SinceKotlin("1.6")
public actual fun readlnOrNull(): String? = throw UnsupportedOperationException("readlnOrNull is not supported in Kotlin/WASM")
internal actual interface Serializable
public actual fun readlnOrNull(): String? = throw UnsupportedOperationException("readlnOrNull is not supported in Kotlin/WASM")
@@ -0,0 +1,12 @@
/*
* Copyright 2010-2020 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.random
private fun initialSeed(): Int =
js("((Math.random() * Math.pow(2, 32)) | 0)")
internal actual fun defaultPlatformRandom(): Random =
Random(initialSeed())
@@ -23,7 +23,6 @@ internal actual fun formatToExactDecimals(value: Double, decimals: Int): String
} else {
val pow = (10.0).pow(decimals)
round(abs(value) * pow) / pow * sign(value)
round(abs(value) * pow) / pow * sign(value)
}
return if (abs(rounded) < 1e21) {
// toFixed switches to scientific format after 1e21
@@ -0,0 +1,8 @@
/*
* Copyright 2010-2023 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.io
internal actual interface Serializable
@@ -5,11 +5,5 @@
package kotlin.random
private fun initialSeed(): Int =
js("((Math.random() * Math.pow(2, 32)) | 0)")
internal actual fun defaultPlatformRandom(): Random =
Random(initialSeed())
internal actual fun doubleFromParts(hi26: Int, low27: Int): Double =
(hi26.toLong().shl(27) + low27) / (1L shl 53).toDouble()
@@ -3,9 +3,6 @@ package test.wasm.unsafe
import kotlin.wasm.unsafe.*
import kotlin.test.*
private fun jsConcatStrings(a: String, b: String): String =
js("a + b")
@OptIn(UnsafeWasmMemoryApi::class)
class MemoryAllocationTest {
val pageSize = 65_536
@@ -128,14 +125,6 @@ class MemoryAllocationTest {
assertTrue(max1 < min1_1)
}
@Test
fun testJsIntropInsideAllocations() {
withScopedMemoryAllocator { allocator ->
assertEquals(jsConcatStrings("str1", "str2"), "str1str2")
allocator.allocate(10)
}
}
@Test
fun testNestedAllocatorThrows() {
var leakedAllocator1: MemoryAllocator? = null
@@ -1,3 +1,8 @@
/*
* Copyright 2010-2023 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 test.js
import kotlin.js.*
@@ -0,0 +1,18 @@
package test.wasm.unsafe
import kotlin.wasm.unsafe.*
import kotlin.test.*
private fun jsConcatStrings(a: String, b: String): String =
js("a + b")
@OptIn(UnsafeWasmMemoryApi::class)
class MemoryAllocationJsTest {
@Test
fun testJsIntropInsideAllocations() {
withScopedMemoryAllocator { allocator ->
assertEquals(jsConcatStrings("str1", "str2"), "str1str2")
allocator.allocate(10)
}
}
}
@@ -0,0 +1,36 @@
/*
* 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
import kotlin.wasm.internal.getSimpleName
/**
* The base class for all errors and exceptions. Only instances of this class can be thrown or caught.
*
* @param message the detail message string.
* @param cause the cause of this throwable.
*/
public open class Throwable(open val message: String?, open val cause: kotlin.Throwable?) {
constructor(message: String?) : this(message, null)
constructor(cause: Throwable?) : this(cause?.toString(), cause)
constructor() : this(null, null)
//TODO: Investigate possibility to make WASI stack discoverable (KT-60965)
internal val stack: String get() = ""
internal var suppressedExceptionsList: MutableList<Throwable>? = null
/**
* Returns the short description of this throwable consisting of the exception class name
* followed by the exception message if it is not null.
*/
public override fun toString(): String {
val s = getSimpleName(this.typeInfo)
return if (message != null) s + ": " + message.toString() else s
}
}
@@ -0,0 +1,398 @@
/*
* Copyright 2010-2023 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
/**
* [WASI Error codes for preview1](https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md)
*/
internal enum class WasiErrorCode {
/**
* No error occurred. System call completed successfully.
*/
SUCCESS,
/**
* Argument list too long.
*/
_2BIG,
/**
* Permission denied.
*/
ACCES,
/**
* Address in use.
*/
ADDRINUSE,
/**
* Address not available.
*/
ADDRNOTAVAIL,
/**
* Address family not supported.
*/
AFNOSUPPORT,
/**
* Resource unavailable, or operation would block.
*/
AGAIN,
/**
* Connection already in progress.
*/
ALREADY,
/**
* Bad file descriptor.
*/
BADF,
/**
* Bad message.
*/
BADMSG,
/**
* Device or resource busy.
*/
BUSY,
/**
* Operation canceled.
*/
CANCELED,
/**
* No child processes.
*/
CHILD,
/**
* Connection aborted.
*/
CONNABORTED,
/**
* Connection refused.
*/
CONNREFUSED,
/**
* Connection reset.
*/
CONNRESET,
/**
* Resource deadlock would occur.
*/
DEADLK,
/**
* Destination address required.
*/
DESTADDRREQ,
/**
* Mathematics argument out of domain of function.
*/
DOM,
/**
* Reserved.
*/
DQUOT,
/**
* File exists.
*/
EXIST,
/**
* Bad address.
*/
FAULT,
/**
* File too large.
*/
FBIG,
/**
* Host is unreachable.
*/
HOSTUNREACH,
/**
* Identifier removed.
*/
IDRM,
/**
* Illegal byte sequence.
*/
ILSEQ,
/**
* Operation in progress.
*/
INPROGRESS,
/**
* Interrupted function.
*/
INTR,
/**
* Invalid argument.
*/
INVAL,
/**
* I/O error.
*/
IO,
/**
* Socket is connected.
*/
ISCONN,
/**
* Is a directory.
*/
ISDIR,
/**
* Too many levels of symbolic links.
*/
LOOP,
/**
* File descriptor value too large.
*/
MFILE,
/**
* Too many links.
*/
MLINK,
/**
* Message too large.
*/
MSGSIZE,
/**
* Reserved.
*/
MULTIHOP,
/**
* Filename too long.
*/
NAMETOOLONG,
/**
* Network is down.
*/
NETDOWN,
/**
* Connection aborted by network.
*/
NETRESET,
/**
* Network unreachable.
*/
NETUNREACH,
/**
* Too many files open in system.
*/
NFILE,
/**
* No buffer space available.
*/
NOBUFS,
/**
* No such device.
*/
NODEV,
/**
* No such file or directory.
*/
NOENT,
/**
* Executable file format error.
*/
NOEXEC,
/**
* No locks available.
*/
NOLCK,
/**
* Reserved.
*/
NOLINK,
/**
* Not enough space.
*/
NOMEM,
/**
* No message of the desired type.
*/
NOMSG,
/**
* Protocol not available.
*/
NOPROTOOPT,
/**
* No space left on device.
*/
NOSPC,
/**
* Function not supported.
*/
NOSYS,
/**
* The socket is not connected.
*/
NOTCONN,
/**
* Not a directory or a symbolic link to a directory.
*/
NOTDIR,
/**
* Directory not empty.
*/
NOTEMPTY,
/**
* State not recoverable.
*/
NOTRECOVERABLE,
/**
* Not a socket.
*/
NOTSOCK,
/**
* Not supported, or operation not supported on socket.
*/
NOTSUP,
/**
* Inappropriate I/O control operation.
*/
NOTTY,
/**
* No such device or address.
*/
NXIO,
/**
* Value too large to be stored in data type.
*/
OVERFLOW,
/**
* Previous owner died.
*/
OWNERDEAD,
/**
* Operation not permitted.
*/
PERM,
/**
* Broken pipe.
*/
PIPE,
/**
* Protocol error.
*/
PROTO,
/**
* Protocol not supported.
*/
PROTONOSUPPORT,
/**
* Protocol wrong type for socket.
*/
PROTOTYPE,
/**
* Result too large.
*/
RANGE,
/**
* Read-only file system.
*/
ROFS,
/**
* Invalid seek.
*/
SPIPE,
/**
* No such process.
*/
SRCH,
/**
* Reserved.
*/
STALE,
/**
* Connection timed out.
*/
TIMEDOUT,
/**
* Text file busy.
*/
TXTBSY,
/**
* Cross-device link.
*/
XDEV,
/**
* Extension: Capabilities insufficient.
*/
NOTCAPABLE,
}
internal class WasiError(val error: WasiErrorCode) : Throwable(message = "WASI call failed with $error")
@@ -0,0 +1,98 @@
/*
* Copyright 2010-2020 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.io
import kotlin.wasm.WasiError
import kotlin.wasm.WasiErrorCode
import kotlin.wasm.WasmImport
import kotlin.wasm.unsafe.MemoryAllocator
import kotlin.wasm.unsafe.withScopedMemoryAllocator
private const val STDOUT = 1
private const val STDERR = 2
/**
* Write to a file descriptor. Note: This is similar to `writev` in POSIX.
*/
@WasmImport("wasi_snapshot_preview1", "fd_write")
private external fun wasiRawFdWrite(descriptor: Int, scatterPtr: Int, scatterSize: Int, errorPtr: Int): Int
internal fun wasiPrintImpl(
allocator: MemoryAllocator,
data: ByteArray?,
newLine: Boolean,
useErrorStream: Boolean
) {
val dataSize = data?.size ?: 0
val memorySize = dataSize + (if (newLine) 1 else 0)
if (memorySize == 0) return
val ptr = allocator.allocate(memorySize)
if (data != null) {
var currentPtr = ptr
for (el in data) {
currentPtr.storeByte(el)
currentPtr += 1
}
}
if (newLine) {
(ptr + dataSize).storeByte(0x0A)
}
val scatterPtr = allocator.allocate(8)
(scatterPtr + 0).storeInt(ptr.address.toInt())
(scatterPtr + 4).storeInt(memorySize)
val rp0 = allocator.allocate(4)
val ret =
wasiRawFdWrite(
descriptor = if (useErrorStream) STDERR else STDOUT,
scatterPtr = scatterPtr.address.toInt(),
scatterSize = 1,
errorPtr = rp0.address.toInt()
)
if (ret != 0) {
throw WasiError(WasiErrorCode.values()[ret])
}
}
private fun printImpl(message: String?, useErrorStream: Boolean, newLine: Boolean) {
withScopedMemoryAllocator { allocator ->
wasiPrintImpl(
allocator = allocator,
data = message?.encodeToByteArray(),
newLine = newLine,
useErrorStream = useErrorStream,
)
}
}
internal fun printError(error: String?) {
printImpl(error, useErrorStream = true, newLine = false)
}
/** Prints the line separator to the standard output stream. */
public actual fun println() {
printImpl(null, useErrorStream = false, newLine = true)
}
/** Prints the given [message] and the line separator to the standard output stream. */
public actual fun println(message: Any?) {
printImpl(message?.toString(), useErrorStream = false, newLine = true)
}
/** Prints the given [message] to the standard output stream. */
public actual fun print(message: Any?) {
printImpl(message?.toString(), useErrorStream = false, newLine = false)
}
@SinceKotlin("1.6")
public actual fun readln(): String = TODO("wasi")
@SinceKotlin("1.6")
public actual fun readlnOrNull(): String? = TODO("wasi")
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2020 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.random
import kotlin.wasm.WasiError
import kotlin.wasm.WasiErrorCode
import kotlin.wasm.WasmImport
import kotlin.wasm.unsafe.withScopedMemoryAllocator
/**
* Write high-quality random data into a buffer. This function blocks when the implementation is
* unable to immediately provide sufficient high-quality random data. This function may execute
* slowly, so when large mounts of random data are required, it's advisable to use this function to
* seed a pseudo-random number generator, rather than to provide the random data directly.
*/
@WasmImport("wasi_snapshot_preview1", "random_get")
//TODO: Better to use 64-bit Long as a seed if possible. (KT-60962)
internal external fun wasiRawRandomGet(address: Int, size: Int): Int
private fun wasiRandomGet(): Int {
withScopedMemoryAllocator { allocator ->
val memory = allocator.allocate(Int.SIZE_BYTES)
val ret = wasiRawRandomGet(memory.address.toInt(), Int.SIZE_BYTES)
return if (ret == 0) {
memory.loadInt()
} else {
throw WasiError(WasiErrorCode.values()[ret])
}
}
}
internal actual fun defaultPlatformRandom(): Random =
Random(wasiRandomGet())
@@ -0,0 +1,19 @@
/*
* 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.time
import kotlin.math.fdlibm.__ieee754_pow
internal actual fun formatToExactDecimals(value: Double, decimals: Int): String {
// TODO Make correct implementation (KT-60964)
val pow = __ieee754_pow(10.0, decimals.toDouble())
val round = kotlin.math.fdlibm.rint(value * pow)
return (round / pow).toString()
}
internal actual fun formatUpToDecimals(value: Double, decimals: Int): String = TODO("Wasi Implement")
internal actual inline val durationAssertionsEnabled: Boolean get() = true
@@ -0,0 +1,60 @@
/*
* 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.time
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.TimeSource.Monotonic.ValueTimeMark
import kotlin.wasm.WasiError
import kotlin.wasm.WasiErrorCode
import kotlin.wasm.WasmImport
import kotlin.wasm.unsafe.Pointer
import kotlin.wasm.unsafe.withScopedMemoryAllocator
private const val MONOTONIC = 1
/**
* Return the time value of a clock. Note: This is similar to `clock_gettime` in POSIX.
*/
@WasmImport("wasi_snapshot_preview1", "clock_time_get")
private external fun wasiRawClockTimeGet(clockId: Int, precision: Long, resultPtr: Int): Int
private fun clockTimeGet(): Long = withScopedMemoryAllocator { allocator ->
val rp0 = allocator.allocate(8)
val ret = wasiRawClockTimeGet(
clockId = MONOTONIC,
precision = 1,
resultPtr = rp0.address.toInt()
)
if (ret == 0) {
(Pointer(rp0.address.toInt().toUInt())).loadLong()
} else {
throw WasiError(WasiErrorCode.values()[ret])
}
}
@SinceKotlin("1.3")
//TODO: Try use implementation of K/JVM since it uses a Long nanosecond counter similar to System.nanoTime (KT-60963)
internal actual object MonotonicTimeSource : TimeSource.WithComparableMarks {
actual override fun markNow(): ValueTimeMark =
ValueTimeMark(clockTimeGet())
actual fun elapsedFrom(timeMark: ValueTimeMark): Duration =
(clockTimeGet() - timeMark.reading).nanoseconds
actual fun adjustReading(timeMark: ValueTimeMark, duration: Duration): ValueTimeMark =
ValueTimeMark(timeMark.reading + duration.toLong(DurationUnit.NANOSECONDS))
actual fun differenceBetween(one: ValueTimeMark, another: ValueTimeMark): Duration {
val ms1 = one.reading
val ms2 = another.reading
return if (ms1 == ms2) Duration.ZERO else (ms1 - ms2).nanoseconds
}
override fun toString(): String = "WASI monotonic time source"
}
@Suppress("ACTUAL_WITHOUT_EXPECT") // visibility
internal actual typealias ValueTimeMarkReading = Long