[Wasm] Default parameter values for external functions
+ import top-level external functions wrapped in lambdas
This commit is contained in:
committed by
Space
parent
f8262a2549
commit
f833fc4bcb
+1
-1
@@ -300,7 +300,7 @@ private val addMainFunctionCallsLowering = makeCustomWasmModulePhase(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private val defaultArgumentStubGeneratorPhase = makeWasmModulePhase(
|
private val defaultArgumentStubGeneratorPhase = makeWasmModulePhase(
|
||||||
::DefaultArgumentStubGenerator,
|
{ context -> DefaultArgumentStubGenerator(context, skipExternalMethods = true) },
|
||||||
name = "DefaultArgumentStubGenerator",
|
name = "DefaultArgumentStubGenerator",
|
||||||
description = "Generate synthetic stubs for functions with default parameter values"
|
description = "Generate synthetic stubs for functions with default parameter values"
|
||||||
)
|
)
|
||||||
|
|||||||
+8
-2
@@ -269,13 +269,19 @@ class BodyGenerator(val context: WasmFunctionCodegenContext) : IrElementVisitorV
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val function: IrFunction = call.symbol.owner.realOverrideTarget
|
||||||
|
|
||||||
call.dispatchReceiver?.let { generateExpression(it) }
|
call.dispatchReceiver?.let { generateExpression(it) }
|
||||||
call.extensionReceiver?.let { generateExpression(it) }
|
call.extensionReceiver?.let { generateExpression(it) }
|
||||||
for (i in 0 until call.valueArgumentsCount) {
|
for (i in 0 until call.valueArgumentsCount) {
|
||||||
generateExpression(call.getValueArgument(i)!!)
|
val valueArgument = call.getValueArgument(i)
|
||||||
|
if (valueArgument == null) {
|
||||||
|
generateDefaultInitializerForType(context.transformType(function.valueParameters[i].type), body)
|
||||||
|
} else {
|
||||||
|
generateExpression(valueArgument)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val function: IrFunction = call.symbol.owner.realOverrideTarget
|
|
||||||
|
|
||||||
if (tryToGenerateIntrinsicCall(call, function)) {
|
if (tryToGenerateIntrinsicCall(call, function)) {
|
||||||
if (function.returnType == irBuiltIns.unitType)
|
if (function.returnType == irBuiltIns.unitType)
|
||||||
|
|||||||
+102
-35
@@ -11,6 +11,8 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
|||||||
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
|
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
|
import org.jetbrains.kotlin.backend.wasm.utils.getJsFunAnnotation
|
||||||
|
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
|
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
|
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
||||||
@@ -20,6 +22,7 @@ import org.jetbrains.kotlin.ir.builders.irString
|
|||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
||||||
import org.jetbrains.kotlin.ir.types.IrType
|
import org.jetbrains.kotlin.ir.types.IrType
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
import org.jetbrains.kotlin.ir.visitors.*
|
import org.jetbrains.kotlin.ir.visitors.*
|
||||||
@@ -159,53 +162,94 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
|
|||||||
|
|
||||||
fun processExternalConstructor(constructor: IrConstructor) {
|
fun processExternalConstructor(constructor: IrConstructor) {
|
||||||
val klass = constructor.constructedClass
|
val klass = constructor.constructedClass
|
||||||
val jsCode = buildString {
|
processFunctionOrConstructor(
|
||||||
append("(")
|
function = constructor,
|
||||||
appendParameterList(constructor.valueParameters.size)
|
name = klass.name,
|
||||||
append(") => new ")
|
returnType = klass.defaultType,
|
||||||
appendExternalClassReference(klass)
|
isConstructor = true,
|
||||||
append("(")
|
jsFunctionReference = buildString { appendExternalClassReference(klass) }
|
||||||
appendParameterList(constructor.valueParameters.size)
|
|
||||||
append(")")
|
|
||||||
}
|
|
||||||
|
|
||||||
val res = createExternalJsFunction(
|
|
||||||
klass.name,
|
|
||||||
"_\$external_constructor",
|
|
||||||
resultType = klass.defaultType,
|
|
||||||
jsCode = jsCode
|
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor.valueParameters.forEach { res.addValueParameter(it.name, it.type) }
|
|
||||||
externalFunToTopLevelMapping[constructor] = res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun processExternalSimpleFunction(function: IrSimpleFunction) {
|
fun processExternalSimpleFunction(function: IrSimpleFunction) {
|
||||||
if (function.parent is IrPackageFragment) return
|
val jsFun = function.getJsFunAnnotation()
|
||||||
val jsName = function.getJsNameOrKotlinName()
|
// Wrap external functions without @JsFun to lambdas `foo` -> `(a, b) => foo(a, b)`.
|
||||||
|
// This way we wouldn't fail if we don't call them.
|
||||||
|
if (jsFun != null && function.valueParameters.all { it.defaultValue == null })
|
||||||
|
return
|
||||||
|
processFunctionOrConstructor(
|
||||||
|
function = function,
|
||||||
|
name = function.name,
|
||||||
|
returnType = function.returnType,
|
||||||
|
isConstructor = false,
|
||||||
|
jsFunctionReference = jsFun?.let { "($it)" } ?: function.getJsNameOrKotlinName().identifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun processFunctionOrConstructor(
|
||||||
|
function: IrFunction,
|
||||||
|
name: Name,
|
||||||
|
returnType: IrType,
|
||||||
|
isConstructor: Boolean,
|
||||||
|
jsFunctionReference: String
|
||||||
|
) {
|
||||||
val dispatchReceiver = function.dispatchReceiverParameter
|
val dispatchReceiver = function.dispatchReceiverParameter
|
||||||
require(dispatchReceiver != null) {
|
val numValueParameters = function.valueParameters.size
|
||||||
"Non top-level external function w/o dispatchReceiverParameter: ${function.fqNameWhenAvailable}"
|
|
||||||
}
|
|
||||||
|
val numDefaultParameters =
|
||||||
|
numDefaultParametersForExternalFunction(function)
|
||||||
|
|
||||||
val jsCode = buildString {
|
val jsCode = buildString {
|
||||||
append("(_this, ")
|
|
||||||
appendParameterList(function.valueParameters.size)
|
|
||||||
append(") => _this.")
|
|
||||||
append(jsName)
|
|
||||||
append("(")
|
append("(")
|
||||||
appendParameterList(function.valueParameters.size)
|
if (dispatchReceiver != null) {
|
||||||
|
append("_this, ")
|
||||||
|
}
|
||||||
|
appendParameterList(numValueParameters, isEnd = numDefaultParameters == 0)
|
||||||
|
|
||||||
|
// Parameters with default values are handled via adding additional flags to indicate that parameter is default .
|
||||||
|
//
|
||||||
|
// external fun foo(x: Int, y: Int = definedExternally, z: Int = definedExternally)
|
||||||
|
//
|
||||||
|
// =>
|
||||||
|
//
|
||||||
|
// @JsCode("""(x, y, z, isDefault1, isDefault2) =>
|
||||||
|
// foo(
|
||||||
|
// x,
|
||||||
|
// isDefault1 ? undefined : y,
|
||||||
|
// isDefault2 ? undefined : z
|
||||||
|
// )
|
||||||
|
// """)
|
||||||
|
// external fun foo(x: Int, y: Int, z: Int, isDefault1: Int, isDefault: Int)
|
||||||
|
|
||||||
|
appendParameterList(numDefaultParameters, "isDefault", isEnd = true)
|
||||||
|
append(") => ")
|
||||||
|
if (isConstructor) {
|
||||||
|
append("new ")
|
||||||
|
}
|
||||||
|
if (dispatchReceiver != null) {
|
||||||
|
append("_this.")
|
||||||
|
}
|
||||||
|
append(jsFunctionReference)
|
||||||
|
append("(")
|
||||||
|
appendParameterList(numValueParameters - numDefaultParameters, isEnd = numDefaultParameters == 0)
|
||||||
|
repeat(numDefaultParameters) {
|
||||||
|
append("isDefault$it ? undefined : p${numValueParameters - numDefaultParameters + it}, ")
|
||||||
|
}
|
||||||
append(")")
|
append(")")
|
||||||
}
|
}
|
||||||
val res = createExternalJsFunction(
|
val res = createExternalJsFunction(
|
||||||
function.name,
|
name,
|
||||||
"_\$external_member_fun",
|
"_\$external_fun",
|
||||||
resultType = function.returnType,
|
resultType = returnType,
|
||||||
jsCode = jsCode
|
jsCode = jsCode
|
||||||
)
|
)
|
||||||
res.addValueParameter("_this", dispatchReceiver.type)
|
if (dispatchReceiver != null) {
|
||||||
|
res.addValueParameter("_this", dispatchReceiver.type)
|
||||||
|
}
|
||||||
function.valueParameters.forEach { res.addValueParameter(it.name, it.type) }
|
function.valueParameters.forEach { res.addValueParameter(it.name, it.type) }
|
||||||
|
// Using Int type with 0 and 1 values to prevent overhead of converting Boolean to true and false
|
||||||
|
repeat(numDefaultParameters) { res.addValueParameter("isDefault$it", context.irBuiltIns.intType) }
|
||||||
externalFunToTopLevelMapping[function] = res
|
externalFunToTopLevelMapping[function] = res
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,11 +333,26 @@ class ComplexExternalDeclarationsUsageLowering(val context: WasmBackendContext)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun transformCall(call: IrFunctionAccessExpression): IrExpression {
|
fun transformCall(call: IrFunctionAccessExpression): IrExpression {
|
||||||
|
val oldFun = call.symbol.owner.realOverrideTarget
|
||||||
val newFun: IrSimpleFunction? =
|
val newFun: IrSimpleFunction? =
|
||||||
context.mapping.wasmNestedExternalToNewTopLevelFunction[call.symbol.owner.realOverrideTarget]
|
context.mapping.wasmNestedExternalToNewTopLevelFunction[oldFun]
|
||||||
|
|
||||||
return if (newFun != null) {
|
return if (newFun != null) {
|
||||||
irCall(call, newFun, receiversAsArguments = true)
|
val newCall = irCall(call, newFun, receiversAsArguments = true)
|
||||||
|
|
||||||
|
// Add default arguments flags if needed
|
||||||
|
val numDefaultParameters = numDefaultParametersForExternalFunction(oldFun)
|
||||||
|
val firstDefaultFlagArgumentIdx = newFun.valueParameters.size - numDefaultParameters
|
||||||
|
val firstOldDefaultArgumentIdx = call.valueArgumentsCount - numDefaultParameters
|
||||||
|
repeat(numDefaultParameters) {
|
||||||
|
val value = if (call.getValueArgument(firstOldDefaultArgumentIdx + it) == null) 1 else 0
|
||||||
|
newCall.putValueArgument(
|
||||||
|
firstDefaultFlagArgumentIdx + it,
|
||||||
|
IrConstImpl.int(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.intType, value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
newCall
|
||||||
} else {
|
} else {
|
||||||
call
|
call
|
||||||
}
|
}
|
||||||
@@ -302,4 +361,12 @@ class ComplexExternalDeclarationsUsageLowering(val context: WasmBackendContext)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun numDefaultParametersForExternalFunction(function: IrFunction): Int {
|
||||||
|
val firstDefaultParameterIndex: Int? =
|
||||||
|
function.valueParameters.firstOrNull { it.defaultValue != null }?.index
|
||||||
|
|
||||||
|
return if (firstDefaultParameterIndex == null)
|
||||||
|
0
|
||||||
|
else
|
||||||
|
function.valueParameters.size - firstDefaultParameterIndex
|
||||||
|
}
|
||||||
|
|||||||
+8
-3
@@ -81,6 +81,11 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
* fun foo__externalAdapter(x: KotlinType): KotlinType = adaptResult(foo(adaptParameter(x)));
|
* fun foo__externalAdapter(x: KotlinType): KotlinType = adaptResult(foo(adaptParameter(x)));
|
||||||
*/
|
*/
|
||||||
fun transformExternalFunction(function: IrSimpleFunction): List<IrDeclaration>? {
|
fun transformExternalFunction(function: IrSimpleFunction): List<IrDeclaration>? {
|
||||||
|
// External functions with default parameter values are already processed by
|
||||||
|
// [ComplexExternalDeclarationsToTopLevelFunctionsLowering]
|
||||||
|
if (function.valueParameters.any { it.defaultValue != null })
|
||||||
|
return null
|
||||||
|
|
||||||
val valueParametersAdapters = function.valueParameters.map {
|
val valueParametersAdapters = function.valueParameters.map {
|
||||||
it.type.kotlinToJsAdapterIfNeeded(isReturn = false)
|
it.type.kotlinToJsAdapterIfNeeded(isReturn = false)
|
||||||
}
|
}
|
||||||
@@ -600,11 +605,11 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun StringBuilder.appendParameterList(size: Int) =
|
internal fun StringBuilder.appendParameterList(size: Int, name: String = "p", isEnd: Boolean = true) =
|
||||||
repeat(size) {
|
repeat(size) {
|
||||||
append("p")
|
append(name)
|
||||||
append(it)
|
append(it)
|
||||||
if (it + 1 < size)
|
if (!isEnd || it + 1 < size)
|
||||||
append(", ")
|
append(", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// FILE: defaultValues.js
|
||||||
|
function foo(x1 = "d1", x2 = "d2", x3 = "d3", x4 = "d4", x5 = "d5") {
|
||||||
|
return `${x1} ${x2} ${x3} ${x4} ${x5}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const foo2 = foo;
|
||||||
|
|
||||||
|
class C {
|
||||||
|
constructor(x1 = 100, x2 = 200) {
|
||||||
|
this.x1 = x1;
|
||||||
|
this.x2 = x2;
|
||||||
|
}
|
||||||
|
|
||||||
|
foo(x3 = 300, x4 = 400) {
|
||||||
|
return `${this.x1} ${this.x2} ${x3} ${x4}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
bar(x5 = new C(10, 20), x6 = new C(1, 2)) {
|
||||||
|
return `${x5.foo()} ${x6.foo()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: defaultValues.kt
|
||||||
|
|
||||||
|
external fun foo(
|
||||||
|
x1: String = definedExternally,
|
||||||
|
x2: String = definedExternally,
|
||||||
|
x3: String = definedExternally,
|
||||||
|
x4: String = definedExternally,
|
||||||
|
x5: String = definedExternally,
|
||||||
|
): String
|
||||||
|
|
||||||
|
external fun foo2(
|
||||||
|
x1: String,
|
||||||
|
x2: String,
|
||||||
|
x3: String = definedExternally,
|
||||||
|
x4: String,
|
||||||
|
x5: String = definedExternally,
|
||||||
|
): String
|
||||||
|
|
||||||
|
external class C {
|
||||||
|
constructor(x1: Int = definedExternally, x2: Int = definedExternally)
|
||||||
|
val x1: Int
|
||||||
|
val x2: Int
|
||||||
|
fun foo(x3: Int = definedExternally, x4: Int = definedExternally): String
|
||||||
|
fun bar(x5: C = definedExternally, x6: Any = definedExternally) : String
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
if (foo() != "d1 d2 d3 d4 d5") return "Fail 1"
|
||||||
|
if (foo(x1 = "x1", x3 = "x3", x5 = "x5") != "x1 d2 x3 d4 x5") return "Fail 2"
|
||||||
|
if (foo("x1", "x2", "x3", "x4", "x5") != "x1 x2 x3 x4 x5") return "Fail 3"
|
||||||
|
if (foo2("x1", "x2", x4 = "x4") != "x1 x2 d3 x4 d5") return "Fail 4"
|
||||||
|
if (foo2("x1", "x2", "x3", "x4", "x5") != "x1 x2 x3 x4 x5") return "Fail 5"
|
||||||
|
|
||||||
|
if (C(1, 2).foo(3, 4) != "1 2 3 4") return "Fail 6"
|
||||||
|
if (C(1).foo(3) != "1 200 3 400") return "Fail 7"
|
||||||
|
if (C(x2 = 2).foo(x4 = 4) != "100 2 300 4") return "Fail 8"
|
||||||
|
if (C().foo() != "100 200 300 400") return "Fail 9"
|
||||||
|
|
||||||
|
if (C().bar(C(), C()) != "100 200 300 400 100 200 300 400") return "Fail 10"
|
||||||
|
if (C().bar() != "10 20 300 400 1 2 300 400") return "Fail 11"
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
Generated
+6
@@ -25,6 +25,12 @@ public class IrCodegenWasmJsInteropJsTestGenerated extends AbstractIrCodegenWasm
|
|||||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmJsInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true);
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmJsInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("defaultValues.kt")
|
||||||
|
public void testDefaultValues() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/boxWasmJsInterop/defaultValues.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("externalTypeOperators.kt")
|
@TestMetadata("externalTypeOperators.kt")
|
||||||
public void testExternalTypeOperators() throws Exception {
|
public void testExternalTypeOperators() throws Exception {
|
||||||
|
|||||||
+5
@@ -30,6 +30,11 @@ public class IrCodegenWasmJsInteropWasmTestGenerated extends AbstractIrCodegenWa
|
|||||||
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmJsInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.WASM, true);
|
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmJsInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.WASM, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("defaultValues.kt")
|
||||||
|
public void testDefaultValues() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/boxWasmJsInterop/defaultValues.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("externalTypeOperators.kt")
|
@TestMetadata("externalTypeOperators.kt")
|
||||||
public void testExternalTypeOperators() throws Exception {
|
public void testExternalTypeOperators() throws Exception {
|
||||||
runTest("compiler/testData/codegen/boxWasmJsInterop/externalTypeOperators.kt");
|
runTest("compiler/testData/codegen/boxWasmJsInterop/externalTypeOperators.kt");
|
||||||
|
|||||||
@@ -164,7 +164,11 @@ internal fun convertJsStringToKotlinString(x: ExternalInterfaceType): String {
|
|||||||
internal external fun jsWriteStringIntoMemory(str: ExternalInterfaceType, addr: Int)
|
internal external fun jsWriteStringIntoMemory(str: ExternalInterfaceType, addr: Int)
|
||||||
|
|
||||||
|
|
||||||
internal fun kotlinToJsStringAdapter(x: String): ExternalInterfaceType {
|
internal fun kotlinToJsStringAdapter(x: String?): ExternalInterfaceType? {
|
||||||
|
// Using nullable String to represent default value
|
||||||
|
// for parameters with default values
|
||||||
|
if (x == null)
|
||||||
|
return null
|
||||||
return importStringFromWasm(exportString(x))
|
return importStringFromWasm(exportString(x))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user