[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
@@ -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