From 6633a9edc03cf57591b9839d64b3b8b4577eec93 Mon Sep 17 00:00:00 2001 From: Anton Bannykh Date: Fri, 4 Sep 2020 13:30:39 +0300 Subject: [PATCH] [JS IR] add a flag which enable safe property access If an accessor is not available at runtime we fall back to the property access. This is useful in cases when JS objects are casted to Kotlin classes implicitly. This pattern did work in the old BE, which lead to a significant amount of code which doesn't work anymore. --- .../common/arguments/K2JSCompilerArguments.kt | 3 +++ .../jetbrains/kotlin/cli/js/K2JsIrCompiler.kt | 2 +- .../org/jetbrains/kotlin/ir/backend/js/Dce.kt | 5 ++++ .../kotlin/ir/backend/js/JsIntrinsics.kt | 4 ++++ .../ir/backend/js/JsIrBackendContext.kt | 1 + .../kotlin/ir/backend/js/compiler.kt | 2 ++ .../js/transformers/irToJs/jsAstUtils.kt | 23 +++++++++++++++++-- .../stdlib/js-ir/runtime/kotlinJsHacks.kt | 11 +++++++++ 8 files changed, 48 insertions(+), 3 deletions(-) diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt index 5b9e203dfc4..60e223d180a 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt @@ -151,6 +151,9 @@ class K2JSCompilerArguments : CommonCompilerArguments() { ) var irModuleName: String? by NullableStringFreezableVar(null) + @Argument(value = "-Xir-legacy-property-access", description = "Force property access via JS properties (requires -Xir-export-all)") + var irLegacyPropertyAccess: Boolean by FreezableVar(false) + @Argument(value = "-Xir-per-module", description = "Splits generated .js per-module") var irPerModule: Boolean by FreezableVar(false) diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index 6f27f4362bb..67534c81b0c 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -273,9 +273,9 @@ class K2JsIrCompiler : CLICompiler() { multiModule = arguments.irPerModule, relativeRequirePath = true, propertyLazyInitialization = arguments.irPropertyLazyInitialization, + legacyPropertyAccess = arguments.irLegacyPropertyAccess, ) - val jsCode = if (arguments.irDce && !arguments.irDceDriven) compiledModule.dceJsCode!! else compiledModule.jsCode!! outputFile.writeText(jsCode.mainModule) jsCode.dependencies.forEach { (name, content) -> diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt index 591b559d809..14197879a7b 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt @@ -94,6 +94,11 @@ private fun buildRoots(modules: Iterable, context: JsIrBackend rootDeclarations += dceRuntimeDiagnostic.unreachableDeclarationMethod(context).owner } + if (context.legacyPropertyAccess) { + rootDeclarations += context.intrinsics.safePropertyGet.owner + rootDeclarations += context.intrinsics.safePropertySet.owner + } + JsMainFunctionDetector.getMainFunctionOrNull(modules.last())?.let { mainFunction -> rootDeclarations += mainFunction if (mainFunction.isSuspend) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index 4327be9078d..caba6e281a5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -315,6 +315,10 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC val readSharedBox = defineReadSharedBox() val writeSharedBox = defineWriteSharedBox() + + val safePropertyGet = getInternalFunction("safePropertyGet") + val safePropertySet = getInternalFunction("safePropertySet") + val jsUndefined = defineJsUndefinedIntrinsic() // Helpers: diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index 863d37e95d8..87d2be490a5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -47,6 +47,7 @@ class JsIrBackendContext( override val es6mode: Boolean = false, val dceRuntimeDiagnostic: DceRuntimeDiagnostic? = null, val propertyLazyInitialization: Boolean = false, + val legacyPropertyAccess: Boolean = false, ) : JsCommonBackendContext { val fileToInitializationFuns: MutableMap = mutableMapOf() val fileToInitializerPureness: MutableMap = mutableMapOf() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt index 7880c35cfe7..985a5323594 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt @@ -52,6 +52,7 @@ fun compile( multiModule: Boolean = false, relativeRequirePath: Boolean = false, propertyLazyInitialization: Boolean, + legacyPropertyAccess: Boolean = false, ): CompilerResult { val (moduleFragment: IrModuleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) = loadIr(project, mainModule, analyzer, configuration, allDependencies, friendDependencies, irFactory) @@ -73,6 +74,7 @@ fun compile( es6mode = es6mode, dceRuntimeDiagnostic = dceRuntimeDiagnostic, propertyLazyInitialization = propertyLazyInitialization, + legacyPropertyAccess = legacyPropertyAccess ) // Load declarations referenced during `context` initialization diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index 723ba2bb4e9..d0947108e68 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -110,7 +110,7 @@ fun translateCall( // @JsName-annotated external property accessors are translated as function calls if (function.getJsName() == null) { val property = function.correspondingPropertySymbol?.owner - if (property != null && property.isEffectivelyExternal()) { + if (property != null && (property.isEffectivelyExternal())) { val nameRef = JsNameRef(context.getNameForProperty(property), jsDispatchReceiver) return when (function) { property.getter -> nameRef @@ -220,7 +220,26 @@ fun translateCall( } } } else { - JsInvocation(ref, listOfNotNull(jsExtensionReceiver) + arguments) + val defaultResult = JsInvocation(ref, listOfNotNull(jsExtensionReceiver) + arguments) + + val alternativeResult = if (jsDispatchReceiver != null && jsExtensionReceiver == null && context.staticContext.backendContext.legacyPropertyAccess) { + val property = function.correspondingPropertySymbol?.owner + if (property != null) { + val propertyName = context.getNameForProperty(property) + val args = mutableListOf(jsDispatchReceiver, JsStringLiteral(symbolName.ident), JsStringLiteral(propertyName.ident)) + val fnName = when (function) { + property.getter -> context.getNameForStaticFunction(context.staticContext.backendContext.intrinsics.safePropertyGet.owner) + property.setter -> { + args += arguments + context.getNameForStaticFunction(context.staticContext.backendContext.intrinsics.safePropertySet.owner) + } + else -> error("Function must be an accessor of corresponding property") + } + JsInvocation(fnName.makeRef(), args) + } else null + } else null + + alternativeResult ?: defaultResult } } diff --git a/libraries/stdlib/js-ir/runtime/kotlinJsHacks.kt b/libraries/stdlib/js-ir/runtime/kotlinJsHacks.kt index 2754a6c4f60..c4d6699ffb6 100644 --- a/libraries/stdlib/js-ir/runtime/kotlinJsHacks.kt +++ b/libraries/stdlib/js-ir/runtime/kotlinJsHacks.kt @@ -13,6 +13,17 @@ internal fun > enumValuesIntrinsic(): Array = internal fun > enumValueOfIntrinsic(@Suppress("UNUSED_PARAMETER") name: String): T = throw IllegalStateException("Should be replaced by compiler") +@PublishedApi +internal fun safePropertyGet(self: dynamic, getterName: String, propName: String): dynamic { + val getter = self[getterName] + return if (getter != null) getter.call(self) else self[propName] +} + +@PublishedApi +internal fun safePropertySet(self: dynamic, setterName: String, propName: String, value: dynamic) { + val setter = self[setterName] + if (setter != null) setter.call(self, value) else self[propName] = value +} /** * Implements annotated function in JavaScript.