[Wasm] Support restricted version of js("code") (KT-56955)
This commit is contained in:
committed by
Space Team
parent
eb8c47343a
commit
71e6b19760
@@ -187,6 +187,12 @@ private val wasmStringSwitchOptimizerLowering = makeWasmModulePhase(
|
|||||||
description = "Replace when with constant string cases to binary search by string hashcodes"
|
description = "Replace when with constant string cases to binary search by string hashcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val jsCodeCallsLowering = makeWasmModulePhase(
|
||||||
|
::JsCodeCallsLowering,
|
||||||
|
name = "JsCodeCallsLowering",
|
||||||
|
description = "Lower calls to js('code') into @JsFun",
|
||||||
|
)
|
||||||
|
|
||||||
private val complexExternalDeclarationsToTopLevelFunctionsLowering = makeWasmModulePhase(
|
private val complexExternalDeclarationsToTopLevelFunctionsLowering = makeWasmModulePhase(
|
||||||
::ComplexExternalDeclarationsToTopLevelFunctionsLowering,
|
::ComplexExternalDeclarationsToTopLevelFunctionsLowering,
|
||||||
name = "ComplexExternalDeclarationsToTopLevelFunctionsLowering",
|
name = "ComplexExternalDeclarationsToTopLevelFunctionsLowering",
|
||||||
@@ -592,6 +598,7 @@ val wasmPhases = SameTypeNamedCompilerPhase(
|
|||||||
name = "IrModuleLowering",
|
name = "IrModuleLowering",
|
||||||
description = "IR module lowering",
|
description = "IR module lowering",
|
||||||
lower = validateIrBeforeLowering then
|
lower = validateIrBeforeLowering then
|
||||||
|
jsCodeCallsLowering then
|
||||||
generateTests then
|
generateTests then
|
||||||
excludeDeclarationsFromCodegenPhase then
|
excludeDeclarationsFromCodegenPhase then
|
||||||
expectDeclarationsRemovingPhase then
|
expectDeclarationsRemovingPhase then
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ class WasmSymbols(
|
|||||||
context.module.getPackage(FqName("kotlin.enums"))
|
context.module.getPackage(FqName("kotlin.enums"))
|
||||||
private val wasmInternalPackage: PackageViewDescriptor =
|
private val wasmInternalPackage: PackageViewDescriptor =
|
||||||
context.module.getPackage(FqName("kotlin.wasm.internal"))
|
context.module.getPackage(FqName("kotlin.wasm.internal"))
|
||||||
|
private val kotlinJsPackage: PackageViewDescriptor =
|
||||||
|
context.module.getPackage(FqName("kotlin.js"))
|
||||||
private val collectionsPackage: PackageViewDescriptor =
|
private val collectionsPackage: PackageViewDescriptor =
|
||||||
context.module.getPackage(StandardNames.COLLECTIONS_PACKAGE_FQ_NAME)
|
context.module.getPackage(StandardNames.COLLECTIONS_PACKAGE_FQ_NAME)
|
||||||
private val builtInsPackage: PackageViewDescriptor =
|
private val builtInsPackage: PackageViewDescriptor =
|
||||||
@@ -329,6 +331,8 @@ class WasmSymbols(
|
|||||||
private val jsFunClass = getIrClass(FqName("kotlin.JsFun"))
|
private val jsFunClass = getIrClass(FqName("kotlin.JsFun"))
|
||||||
val jsFunConstructor by lazy { jsFunClass.constructors.single() }
|
val jsFunConstructor by lazy { jsFunClass.constructors.single() }
|
||||||
|
|
||||||
|
val jsCode = getFunction("js", kotlinJsPackage)
|
||||||
|
|
||||||
private fun findClass(memberScope: MemberScope, name: Name): ClassDescriptor =
|
private fun findClass(memberScope: MemberScope, name: Name): ClassDescriptor =
|
||||||
memberScope.getContributedClassifier(name, NoLookupLocation.FROM_BACKEND) as ClassDescriptor
|
memberScope.getContributedClassifier(name, NoLookupLocation.FROM_BACKEND) as ClassDescriptor
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ class DeclarationGenerator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val wasmImportModule = declaration.getWasmImportDescriptor()
|
val wasmImportModule = declaration.getWasmImportDescriptor()
|
||||||
val jsCode = if (declaration.isExternal) declaration.getJsFunAnnotation() else null
|
val jsCode = declaration.getJsFunAnnotation()
|
||||||
val importedName = when {
|
val importedName = when {
|
||||||
wasmImportModule != null -> {
|
wasmImportModule != null -> {
|
||||||
check(declaration.isExternal) { "Non-external fun with @WasmImport ${declaration.fqNameWhenAvailable}"}
|
check(declaration.isExternal) { "Non-external fun with @WasmImport ${declaration.fqNameWhenAvailable}"}
|
||||||
|
|||||||
+20
-9
@@ -354,15 +354,7 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
|
|||||||
resultType: IrType,
|
resultType: IrType,
|
||||||
jsCode: String,
|
jsCode: String,
|
||||||
): IrSimpleFunction {
|
): IrSimpleFunction {
|
||||||
val res = context.irFactory.buildFun {
|
val res = createExternalJsFunction(context, originalName, suffix, resultType, jsCode)
|
||||||
name = Name.identifier(originalName.asStringStripSpecialMarkers() + suffix)
|
|
||||||
returnType = resultType
|
|
||||||
isExternal = true
|
|
||||||
}
|
|
||||||
val builder = context.createIrBuilder(res.symbol)
|
|
||||||
res.annotations += builder.irCallConstructor(context.wasmSymbols.jsFunConstructor, typeArguments = emptyList()).also {
|
|
||||||
it.putValueArgument(0, builder.irString(jsCode))
|
|
||||||
}
|
|
||||||
res.parent = currentFile
|
res.parent = currentFile
|
||||||
addedDeclarations += res
|
addedDeclarations += res
|
||||||
return res
|
return res
|
||||||
@@ -388,6 +380,25 @@ class ComplexExternalDeclarationsToTopLevelFunctionsLowering(val context: WasmBa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createExternalJsFunction(
|
||||||
|
context: WasmBackendContext,
|
||||||
|
originalName: Name,
|
||||||
|
suffix: String,
|
||||||
|
resultType: IrType,
|
||||||
|
jsCode: String,
|
||||||
|
): IrSimpleFunction {
|
||||||
|
val res = context.irFactory.buildFun {
|
||||||
|
name = Name.identifier(originalName.asStringStripSpecialMarkers() + suffix)
|
||||||
|
returnType = resultType
|
||||||
|
isExternal = true
|
||||||
|
}
|
||||||
|
val builder = context.createIrBuilder(res.symbol)
|
||||||
|
res.annotations += builder.irCallConstructor(context.wasmSymbols.jsFunConstructor, typeArguments = emptyList()).also {
|
||||||
|
it.putValueArgument(0, builder.irString(jsCode))
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect usages of complex declarations to top-level functions
|
* Redirect usages of complex declarations to top-level functions
|
||||||
*/
|
*/
|
||||||
|
|||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.jetbrains.kotlin.backend.wasm.lower
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||||
|
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
|
||||||
|
import org.jetbrains.kotlin.ir.builders.*
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lower calls to `js(code)` into `@JsFun(code) external` functions.
|
||||||
|
*/
|
||||||
|
class JsCodeCallsLowering(val context: WasmBackendContext) : FileLoweringPass {
|
||||||
|
override fun lower(irFile: IrFile) {
|
||||||
|
irFile.transformDeclarationsFlat { declaration ->
|
||||||
|
when (declaration) {
|
||||||
|
is IrSimpleFunction -> transformFunction(declaration)
|
||||||
|
is IrProperty -> transformProperty(declaration)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun transformFunction(function: IrSimpleFunction): List<IrDeclaration>? {
|
||||||
|
val body = function.body ?: return null
|
||||||
|
check(body is IrBlockBody) // Should be lowered to block body
|
||||||
|
val statement = body.statements.singleOrNull() ?: return null
|
||||||
|
|
||||||
|
val isSingleExpressionJsCode: Boolean
|
||||||
|
val jsCode: String
|
||||||
|
when (statement) {
|
||||||
|
is IrReturn -> {
|
||||||
|
jsCode = statement.value.getJsCode() ?: return null
|
||||||
|
isSingleExpressionJsCode = true
|
||||||
|
}
|
||||||
|
is IrCall -> {
|
||||||
|
jsCode = statement.getJsCode() ?: return null
|
||||||
|
isSingleExpressionJsCode = false
|
||||||
|
}
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
val valueParameters = function.valueParameters
|
||||||
|
val jsFunCode = buildString {
|
||||||
|
append('(')
|
||||||
|
append(valueParameters.joinToString { it.name.identifier })
|
||||||
|
append(") => ")
|
||||||
|
if (!isSingleExpressionJsCode) append("{ ")
|
||||||
|
append(jsCode)
|
||||||
|
if (!isSingleExpressionJsCode) append(" }")
|
||||||
|
}
|
||||||
|
val builder = context.createIrBuilder(function.symbol)
|
||||||
|
function.annotations += builder.irCallConstructor(context.wasmSymbols.jsFunConstructor, typeArguments = emptyList()).also {
|
||||||
|
it.putValueArgument(0, builder.irString(jsFunCode))
|
||||||
|
}
|
||||||
|
function.body = null
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun transformProperty(property: IrProperty): List<IrDeclaration>? {
|
||||||
|
val field = property.backingField ?: return null
|
||||||
|
val initializer = field.initializer ?: return null
|
||||||
|
val jsCode = initializer.expression.getJsCode() ?: return null
|
||||||
|
val externalFun = createExternalJsFunction(
|
||||||
|
context,
|
||||||
|
property.name,
|
||||||
|
"_js_code",
|
||||||
|
field.type,
|
||||||
|
jsCode = "() => ($jsCode)",
|
||||||
|
)
|
||||||
|
val builder = context.createIrBuilder(field.symbol)
|
||||||
|
initializer.expression = builder.irCall(externalFun)
|
||||||
|
return listOf(property, externalFun)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrExpression.getJsCode(): String? {
|
||||||
|
val call = this as? IrCall ?: return null
|
||||||
|
if (call.symbol != context.wasmSymbols.jsCode) return null
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return (call.getValueArgument(0) as IrConst<String>).value
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
|
|||||||
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isBuiltInWasmRefType
|
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isBuiltInWasmRefType
|
||||||
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isExported
|
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isExported
|
||||||
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isExternalType
|
import org.jetbrains.kotlin.backend.wasm.ir2wasm.isExternalType
|
||||||
|
import org.jetbrains.kotlin.backend.wasm.utils.getJsFunAnnotation
|
||||||
import org.jetbrains.kotlin.backend.wasm.utils.getWasmImportDescriptor
|
import org.jetbrains.kotlin.backend.wasm.utils.getWasmImportDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
|
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
|
||||||
@@ -49,7 +50,7 @@ class JsInteropFunctionsLowering(val context: WasmBackendContext) : DeclarationT
|
|||||||
if (declaration.isFakeOverride) return null
|
if (declaration.isFakeOverride) return null
|
||||||
if (declaration !is IrSimpleFunction) return null
|
if (declaration !is IrSimpleFunction) return null
|
||||||
val isExported = declaration.isExported()
|
val isExported = declaration.isExported()
|
||||||
val isExternal = declaration.isExternal
|
val isExternal = declaration.isExternal || declaration.getJsFunAnnotation() != null
|
||||||
if (declaration.isPropertyAccessor) return null
|
if (declaration.isPropertyAccessor) return null
|
||||||
if (declaration.parent !is IrPackageFragment) return null
|
if (declaration.parent !is IrPackageFragment) return null
|
||||||
if (!isExported && !isExternal) return null
|
if (!isExported && !isExternal) return null
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fun foo(x: Int, y: Int, z: String): Int = js("x + y + Number(z)")
|
||||||
|
|
||||||
|
val x: String = js("typeof 10")
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val res = foo(10, 20, z = "30")
|
||||||
|
if (res != 60) return "Wrong foo: $res"
|
||||||
|
if (x != "number") return "Wrong x: $x"
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
Generated
+6
@@ -49,6 +49,12 @@ public class FirJsCodegenWasmJsInteropTestGenerated extends AbstractFirJsCodegen
|
|||||||
runTest("compiler/testData/codegen/boxWasmJsInterop/functionTypes.kt");
|
runTest("compiler/testData/codegen/boxWasmJsInterop/functionTypes.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("jsCode.kt")
|
||||||
|
public void testJsCode() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/boxWasmJsInterop/jsCode.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("jsExport.kt")
|
@TestMetadata("jsExport.kt")
|
||||||
public void testJsExport() throws Exception {
|
public void testJsExport() throws Exception {
|
||||||
|
|||||||
Generated
+6
@@ -49,6 +49,12 @@ public class IrCodegenWasmJsInteropJsTestGenerated extends AbstractIrCodegenWasm
|
|||||||
runTest("compiler/testData/codegen/boxWasmJsInterop/functionTypes.kt");
|
runTest("compiler/testData/codegen/boxWasmJsInterop/functionTypes.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("jsCode.kt")
|
||||||
|
public void testJsCode() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/boxWasmJsInterop/jsCode.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("jsExport.kt")
|
@TestMetadata("jsExport.kt")
|
||||||
public void testJsExport() throws Exception {
|
public void testJsExport() throws Exception {
|
||||||
|
|||||||
+34
-1
@@ -34,4 +34,37 @@ import kotlin.wasm.internal.ExcludedFromCodegen
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
@ExcludedFromCodegen
|
@ExcludedFromCodegen
|
||||||
public external val definedExternally: Nothing
|
public external val definedExternally: Nothing
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function allows you to incorporate JavaScript [code] into Kotlin/Wasm codebase.
|
||||||
|
* It is used to implement top-level functions and initialize top-level properties.
|
||||||
|
*
|
||||||
|
* It is important to note, that calls to [js] function should be the only expression
|
||||||
|
* in a function body or a property initializer.
|
||||||
|
*
|
||||||
|
* [code] parameter should be a compile-time constant.
|
||||||
|
*
|
||||||
|
* When used in an expression context, [code] should contain a single JavaScript expression. For example:
|
||||||
|
*
|
||||||
|
* ``` kotlin
|
||||||
|
* val version: String = js("process.version")
|
||||||
|
* fun newEmptyJsArray(): JsValue = js("[]")
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* When used in a function body, [code] is expected to be a list of JavaScript statements. For example:
|
||||||
|
*
|
||||||
|
* ``` kotlin
|
||||||
|
* fun log(message1: String, message2: String) {
|
||||||
|
* js("""
|
||||||
|
* console.log(message1);
|
||||||
|
* console.log(message2);
|
||||||
|
* """)
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* You can use parameters of calling function in JavaScript [code].
|
||||||
|
* However, other Kotlin declarations are not visible inside the [code] block.
|
||||||
|
*/
|
||||||
|
@ExcludedFromCodegen
|
||||||
|
public external fun js(code: String): Nothing
|
||||||
+5
@@ -65,6 +65,11 @@ public class IrCodegenWasmJsInteropWasmTestGenerated extends AbstractIrCodegenWa
|
|||||||
runTest("compiler/testData/codegen/boxWasmJsInterop/imperativeWrapperUninitialised.kt");
|
runTest("compiler/testData/codegen/boxWasmJsInterop/imperativeWrapperUninitialised.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("jsCode.kt")
|
||||||
|
public void testJsCode() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/boxWasmJsInterop/jsCode.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("jsExport.kt")
|
@TestMetadata("jsExport.kt")
|
||||||
public void testJsExport() throws Exception {
|
public void testJsExport() throws Exception {
|
||||||
runTest("compiler/testData/codegen/boxWasmJsInterop/jsExport.kt");
|
runTest("compiler/testData/codegen/boxWasmJsInterop/jsExport.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user