[Wasm] Wasi stdlib implementation
KT-56608
This commit is contained in:
committed by
Zalim Bashorov
parent
090f393f97
commit
8cc0660693
@@ -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
|
||||
Reference in New Issue
Block a user