blob: afa901ab94b4f1a4df696c84ddfc5ab5d8dba362 [file] [log] [blame]
/*
* 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 org.jetbrains.kotlin.generators.builtins.numbers.primitives
import org.jetbrains.kotlin.generators.builtins.PrimitiveType
import java.io.PrintWriter
import java.util.*
class WasmPrimitivesGenerator(writer: PrintWriter) : BasePrimitivesGenerator(writer) {
override fun FileBuilder.modifyGeneratedFile() {
suppress("OVERRIDE_BY_INLINE")
suppress("NOTHING_TO_INLINE")
suppress("unused")
suppress("UNUSED_PARAMETER")
import("kotlin.wasm.internal.*")
}
override fun ClassBuilder.modifyGeneratedClass(thisKind: PrimitiveType) {
annotations += "WasmAutoboxed"
// used here little hack with name extension just to avoid creation of specialized "ConstructorParameterDescription"
constructorParam {
name = "private val value"
type = thisKind.capitalized
}
}
override fun CompanionObjectBuilder.modifyGeneratedCompanionObject(thisKind: PrimitiveType) {
isPublic = true
}
override fun primitiveConstants(type: PrimitiveType): List<Any> {
return when (type) {
PrimitiveType.FLOAT -> listOf(
String.format(Locale.US, "%.17eF", java.lang.Float.MIN_VALUE),
String.format(Locale.US, "%.17eF", java.lang.Float.MAX_VALUE),
"1.0F/0.0F", "-1.0F/0.0F", "-(0.0F/0.0F)"
)
else -> super.primitiveConstants(type)
}
}
override fun PropertyBuilder.modifyGeneratedCompanionObjectProperty(thisKind: PrimitiveType) {
if (this.name in setOf("POSITIVE_INFINITY", "NEGATIVE_INFINITY", "NaN")) {
annotations += "Suppress(\"DIVISION_BY_ZERO\")"
}
}
override fun MethodBuilder.modifyGeneratedCompareTo(thisKind: PrimitiveType, otherKind: PrimitiveType) {
if (thisKind != otherKind || thisKind !in PrimitiveType.floatingPoint) {
modifySignature { isInline = true }
}
if (otherKind == thisKind) {
if (thisKind in PrimitiveType.floatingPoint) {
"""
// if any of values in NaN both comparisons return false
if (this > $parameterName) return 1
if (this < $parameterName) return -1
val thisBits = this.toBits()
val otherBits = $parameterName.toBits()
// Canonical NaN bit representation is higher than any other value's bit representation
return thisBits.compareTo(otherBits)
""".trimIndent().addAsMultiLineBody()
} else {
val body = when (thisKind) {
PrimitiveType.BYTE -> "wasm_i32_compareTo(this.toInt(), $parameterName.toInt())"
PrimitiveType.SHORT -> "this.toInt().compareTo($parameterName.toInt())"
PrimitiveType.INT, PrimitiveType.LONG -> "wasm_${thisKind.prefixLowercase}_compareTo(this, $parameterName)"
else -> throw IllegalArgumentException("Unsupported type $thisKind for generation `compareTo` method")
}
body.addAsSingleLineBody(bodyOnNewLine = true)
}
return
}
val thisCasted = "this" + thisKind.castToIfNecessary(otherKind)
val otherCasted = parameterName + otherKind.castToIfNecessary(thisKind)
when {
thisKind == PrimitiveType.FLOAT && otherKind == PrimitiveType.DOUBLE -> "-${otherCasted}.compareTo(this)"
else -> "$thisCasted.compareTo($otherCasted)"
}.addAsSingleLineBody(bodyOnNewLine = true)
}
override fun MethodBuilder.modifyGeneratedBinaryOperation(thisKind: PrimitiveType, otherKind: PrimitiveType) {
val sign = operatorSign(methodName)
if (thisKind != PrimitiveType.BYTE && thisKind != PrimitiveType.SHORT && thisKind == otherKind) {
val type = thisKind.capitalized
when (methodName) {
"div" -> {
val oneConst = if (thisKind == PrimitiveType.LONG) "-1L" else "-1"
when (thisKind) {
PrimitiveType.INT, PrimitiveType.LONG -> "if (this == $type.MIN_VALUE && $parameterName == $oneConst) $type.MIN_VALUE " +
"else wasm_${thisKind.prefixLowercase}_div_s(this, $parameterName)"
else -> return implementAsIntrinsic(thisKind, methodName)
}
}
"rem" -> when (thisKind) {
in PrimitiveType.floatingPoint -> "this - (wasm_${thisKind.prefixLowercase}_truncate(this / $parameterName) * $parameterName)"
else -> return implementAsIntrinsic(thisKind, methodName)
}
else -> return implementAsIntrinsic(thisKind, methodName)
}.addAsSingleLineBody(bodyOnNewLine = true)
return
}
modifySignature { isInline = true }
val returnTypeAsPrimitive = PrimitiveType.valueOf(returnType.uppercase())
val thisCasted = "this" + thisKind.castToIfNecessary(returnTypeAsPrimitive)
val otherCasted = parameterName + parameterType.toPrimitiveType().castToIfNecessary(returnTypeAsPrimitive)
"$thisCasted $sign $otherCasted".addAsSingleLineBody(bodyOnNewLine = true)
}
override fun MethodBuilder.modifyGeneratedUnaryOperation(thisKind: PrimitiveType) {
if (thisKind == PrimitiveType.INT && methodName == "dec") {
additionalDoc = "TODO: Fix test compiler/testData/codegen/box/functions/invoke/invoke.kt with inline dec"
} else {
modifySignature { isInline = true }
}
if (methodName in setOf("inc", "dec")) {
val sign = if (methodName == "inc") "+" else "-"
when (thisKind) {
PrimitiveType.BYTE, PrimitiveType.SHORT -> "(this $sign 1).to${thisKind.capitalized}()".addAsSingleLineBody(bodyOnNewLine = true)
PrimitiveType.INT -> "this $sign 1".addAsSingleLineBody(bodyOnNewLine = true)
PrimitiveType.LONG -> "this $sign 1L".addAsSingleLineBody(bodyOnNewLine = true)
PrimitiveType.FLOAT -> "this $sign 1.0f".addAsSingleLineBody(bodyOnNewLine = true)
PrimitiveType.DOUBLE -> "this $sign 1.0".addAsSingleLineBody(bodyOnNewLine = true)
else -> Unit
}
}
if (methodName in setOf("unaryMinus", "unaryPlus")) {
if (thisKind in PrimitiveType.floatingPoint && methodName == "unaryMinus") {
return implementAsIntrinsic(thisKind, methodName)
}
val returnTypeAsPrimitive = PrimitiveType.valueOf(returnType.uppercase())
val thisCasted = "this" + thisKind.castToIfNecessary(returnTypeAsPrimitive)
val sign = if (methodName == "unaryMinus") {
when (thisKind) {
PrimitiveType.INT -> "0 - "
PrimitiveType.LONG -> "0L - "
else -> "-"
}
} else ""
"$sign$thisCasted".addAsSingleLineBody(bodyOnNewLine = true)
}
}
override fun MethodBuilder.modifyGeneratedRangeTo(thisKind: PrimitiveType) {
val rangeType = PrimitiveType.valueOf(returnType.replace("Range", "").uppercase())
val thisCasted = "this" + thisKind.castToIfNecessary(rangeType)
val otherCasted = parameterName + parameterType.toPrimitiveType().castToIfNecessary(rangeType)
"return ${returnType}($thisCasted, $otherCasted)".addAsMultiLineBody()
}
override fun MethodBuilder.modifyGeneratedRangeUntil(thisKind: PrimitiveType) {
"this until $parameterName".addAsSingleLineBody(bodyOnNewLine = false)
}
override fun MethodBuilder.modifyGeneratedBitShiftOperators(thisKind: PrimitiveType) {
if (thisKind == PrimitiveType.INT) {
implementAsIntrinsic(thisKind, methodName)
} else if (thisKind == PrimitiveType.LONG) {
modifySignature { isInline = true }
"wasm_i64_${methodName.toWasmOperator().lowercase()}(this, ${parameterName}.toLong())".addAsSingleLineBody(bodyOnNewLine = true)
}
}
override fun MethodBuilder.modifyGeneratedBitwiseOperators(thisKind: PrimitiveType) {
if (methodName == "inv") {
modifySignature { isInline = true }
val oneConst = if (thisKind == PrimitiveType.LONG) "-1L" else "-1"
"this.xor($oneConst)".addAsSingleLineBody(bodyOnNewLine = true)
return
}
implementAsIntrinsic(thisKind, methodName)
}
override fun MethodBuilder.modifyGeneratedConversions(thisKind: PrimitiveType) {
val returnTypeAsPrimitive = PrimitiveType.valueOf(returnType.uppercase())
if (returnTypeAsPrimitive == thisKind) {
modifySignature { isInline = true }
"this".addAsSingleLineBody(bodyOnNewLine = true)
return
}
when (thisKind) {
PrimitiveType.BYTE, PrimitiveType.SHORT -> when (returnTypeAsPrimitive) {
// byte to byte conversion impossible here due to earlier check on type equality
PrimitiveType.BYTE -> "this.toInt().toByte()".also { modifySignature { isInline = true } }
PrimitiveType.CHAR -> "reinterpretAsInt().reinterpretAsChar()"
PrimitiveType.SHORT -> "reinterpretAsInt().reinterpretAsShort()"
PrimitiveType.INT -> "reinterpretAsInt()"
PrimitiveType.LONG -> "wasm_i64_extend_i32_s(this.toInt())"
PrimitiveType.FLOAT -> "wasm_f32_convert_i32_s(this.toInt())"
PrimitiveType.DOUBLE -> "wasm_f64_convert_i32_s(this.toInt())"
else -> throw IllegalArgumentException("Unsupported type $returnTypeAsPrimitive for generation conversion method from type $thisKind")
}
PrimitiveType.INT -> when (returnTypeAsPrimitive) {
PrimitiveType.BYTE -> "((this shl 24) shr 24).reinterpretAsByte()"
PrimitiveType.CHAR -> "(this and 0xFFFF).reinterpretAsChar()"
PrimitiveType.SHORT -> "((this shl 16) shr 16).reinterpretAsShort()"
PrimitiveType.LONG -> "wasm_i64_extend_i32_s(this)"
PrimitiveType.FLOAT -> "wasm_f32_convert_i32_s(this)"
PrimitiveType.DOUBLE -> "wasm_f64_convert_i32_s(this)"
else -> throw IllegalArgumentException("Unsupported type $returnTypeAsPrimitive for generation conversion method from type $thisKind")
}
PrimitiveType.LONG -> when (returnTypeAsPrimitive) {
PrimitiveType.BYTE, PrimitiveType.CHAR, PrimitiveType.SHORT -> "this.toInt().to${returnTypeAsPrimitive.capitalized}()"
.also { modifySignature { isInline = true } }
PrimitiveType.INT -> "wasm_i32_wrap_i64(this)"
PrimitiveType.FLOAT -> "wasm_f32_convert_i64_s(this)"
PrimitiveType.DOUBLE -> "wasm_f64_convert_i64_s(this)"
else -> throw IllegalArgumentException("Unsupported type $returnTypeAsPrimitive for generation conversion method from type $thisKind")
}
in PrimitiveType.floatingPoint -> when (returnTypeAsPrimitive) {
PrimitiveType.BYTE, PrimitiveType.CHAR, PrimitiveType.SHORT -> "this.toInt().to${returnTypeAsPrimitive.capitalized}()"
.also { modifySignature { isInline = true } }
PrimitiveType.INT -> "wasm_i32_trunc_sat_${thisKind.prefixLowercase}_s(this)"
PrimitiveType.LONG -> "wasm_i64_trunc_sat_${thisKind.prefixLowercase}_s(this)"
PrimitiveType.FLOAT -> "wasm_f32_demote_f64(this)"
PrimitiveType.DOUBLE -> "wasm_f64_promote_f32(this)"
else -> throw IllegalArgumentException("Unsupported type $returnTypeAsPrimitive for generation conversion method from type $thisKind")
}
else -> throw IllegalArgumentException("Unsupported type $thisKind to generate conversion methods")
}.addAsSingleLineBody(bodyOnNewLine = false)
}
override fun MethodBuilder.modifyGeneratedEquals(thisKind: PrimitiveType) {
val additionalCheck = when (thisKind) {
PrimitiveType.LONG -> "wasm_i64_eq(this, $parameterName)"
PrimitiveType.FLOAT, PrimitiveType.DOUBLE -> "this.toBits() == other.toBits()"
else -> {
"wasm_i32_eq(this${thisKind.castToIfNecessary(PrimitiveType.INT)}, $parameterName${thisKind.castToIfNecessary(PrimitiveType.INT)})"
}
}
"$parameterName is ${thisKind.capitalized} && $additionalCheck".addAsSingleLineBody(bodyOnNewLine = true)
}
override fun MethodBuilder.modifyGeneratedToString(thisKind: PrimitiveType) {
when (thisKind) {
in PrimitiveType.floatingPoint -> "dtoa(this${thisKind.castToIfNecessary(PrimitiveType.DOUBLE)})"
PrimitiveType.INT, PrimitiveType.LONG -> "itoa${thisKind.bitSize}(this, 10)"
else -> "this.toInt().toString()"
}.addAsSingleLineBody(bodyOnNewLine = true)
}
override fun ClassBuilder.generateAdditionalMethods(thisKind: PrimitiveType) {
generateHashCode(thisKind)
when {
thisKind == PrimitiveType.BYTE || thisKind == PrimitiveType.SHORT -> generateReinterpret(PrimitiveType.INT)
thisKind == PrimitiveType.INT -> {
setOf(PrimitiveType.BOOLEAN, PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR)
.forEach { generateReinterpret(it) }
}
}
}
private fun ClassBuilder.generateHashCode(thisKind: PrimitiveType) {
method {
signature {
isOverride = true
methodName = "hashCode"
returnType = PrimitiveType.INT.capitalized
}
when (thisKind) {
PrimitiveType.LONG -> "((this ushr 32) xor this).toInt()"
PrimitiveType.FLOAT -> "toBits()"
PrimitiveType.DOUBLE -> "toBits().hashCode()"
else -> "this${thisKind.castToIfNecessary(PrimitiveType.INT)}"
}.addAsSingleLineBody()
}
}
private fun ClassBuilder.generateReinterpret(otherKind: PrimitiveType) {
method {
annotations += "WasmNoOpCast"
annotations += "PublishedApi"
signature {
visibility = MethodVisibility.INTERNAL
methodName = "reinterpretAs${otherKind.capitalized}"
returnType = otherKind.capitalized
}
"implementedAsIntrinsic".addAsSingleLineBody(bodyOnNewLine = true)
}
}
companion object {
private fun String.toWasmOperator(): String {
return when (this) {
"plus" -> "ADD"
"minus" -> "SUB"
"times" -> "MUL"
"rem" -> "REM_S"
"unaryMinus" -> "NEG"
"shr" -> "SHR_S"
"ushr" -> "SHR_U"
"equals" -> "EQ"
else -> this.uppercase()
}
}
private fun MethodBuilder.implementAsIntrinsic(thisKind: PrimitiveType, methodName: String) {
modifySignature { isInline = false }
annotations += "WasmOp(WasmOp.${thisKind.prefixUppercase}_${methodName.toWasmOperator()})"
"implementedAsIntrinsic".addAsSingleLineBody(bodyOnNewLine = true)
}
private val PrimitiveType.prefixUppercase: String
get() = when (this) {
PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.INT -> "I32"
PrimitiveType.LONG -> "I64"
PrimitiveType.FLOAT -> "F32"
PrimitiveType.DOUBLE -> "F64"
else -> ""
}
private val PrimitiveType.prefixLowercase: String
get() = prefixUppercase.lowercase()
}
}