[JS IR] Introduce the GENERATE_INLINE_ANONYMOUS_FUNCTIONS feature flag

This commit is contained in:
Sergej Jaskiewicz
2022-04-20 14:00:18 +03:00
committed by Space
parent c10af22b27
commit e03747ea7d
18 changed files with 245 additions and 17 deletions
@@ -178,6 +178,12 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
@Argument(value = "-Xir-new-ir2js", description = "New fragment-based ir2js")
var irNewIr2Js: Boolean by FreezableVar(true)
@Argument(
value = "-Xir-generate-inline-anonymous-functions",
description = "Lambda expressions that capture values are translated into in-line anonymous JavaScript functions"
)
var irGenerateInlineAnonymousFunctions: Boolean by FreezableVar(false)
@Argument(
value = "-Xinclude",
valueDescription = "<path>",
@@ -163,6 +163,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
configurationJs.put(CLIConfigurationKeys.ALLOW_KOTLIN_PACKAGE, arguments.allowKotlinPackage)
configurationJs.put(CLIConfigurationKeys.RENDER_DIAGNOSTIC_INTERNAL_NAME, arguments.renderInternalDiagnosticNames)
configurationJs.put(JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION, arguments.irPropertyLazyInitialization)
configurationJs.put(JSConfigurationKeys.GENERATE_INLINE_ANONYMOUS_FUNCTIONS, arguments.irGenerateInlineAnonymousFunctions)
if (!checkKotlinPackageUsage(environmentForJS.configuration, sourcesFiles)) return ExitCode.COMPILATION_ERROR
@@ -28,12 +28,16 @@ import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.*
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLoweringPass {
val generateInlineAnonymousFunctions: Boolean
get() = context.configuration.getBoolean(JSConfigurationKeys.GENERATE_INLINE_ANONYMOUS_FUNCTIONS)
/**
* A factory that creates [IrFunctionExpression] for lambdas being constructed.
*
@@ -121,7 +125,8 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
val closureUsageAnalyser = ClosureUsageAnalyser()
irFile.acceptChildrenVoid(closureUsageAnalyser) // TODO: Only do this if lambda inlining is enabled with a feature flag!
if (generateInlineAnonymousFunctions)
irFile.acceptChildrenVoid(closureUsageAnalyser)
val callableReferenceClassTransformer = CallableReferenceClassTransformer(
ctorToFactoryMap,
@@ -317,8 +322,9 @@ class InteropCallableReferenceLowering(val context: JsIrBackendContext) : BodyLo
// This allows us to avoid allocating a new object each time the lambda is created.
liftLambda(ctorToFreeFunctionMap, lambdaInfo)
} else if (
generateInlineAnonymousFunctions
// If the lambda constructor is called from more than one place, don't inline.
closureUsageAnalyser.getLambdaConstructorCalls(lambdaClass.primaryConstructor!!.symbol).size == 1
&& closureUsageAnalyser.getLambdaConstructorCalls(lambdaClass.primaryConstructor!!.symbol).size == 1
// In-line anonymous functions that capture variables declared in loops are dangerous.
// See https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example
&& !closureUsageAnalyser.lambdaCapturesVariablesDeclaredInLoops(lambdaClass)
+2
View File
@@ -17,6 +17,8 @@ where advanced options include:
Print declarations' reachability info to stdout during performing DCE
-Xir-dce-runtime-diagnostic={log|exception}
Enable runtime diagnostics when performing DCE instead of removing declarations
-Xir-generate-inline-anonymous-functions
Lambda expressions that capture values are translated into in-line anonymous JavaScript functions
-Xir-minimized-member-names Perform minimization for names of members
-Xir-module-name=<name> Specify a compilation module name for IR backend
-Xir-new-ir2js New fragment-based ir2js
@@ -112,6 +112,11 @@ object JsEnvironmentConfigurationDirectives : SimpleDirectivesContainer() {
applicability = DirectiveApplicability.Global
)
val GENERATE_INLINE_ANONYMOUS_FUNCTIONS by directive(
description = "translate lambdas into in-line anonymous functions",
applicability = DirectiveApplicability.Global
)
val SAFE_EXTERNAL_BOOLEAN by directive(
description = "",
applicability = DirectiveApplicability.Global
@@ -120,6 +120,10 @@ fun createCompilerConfiguration(module: TestModule, configurators: List<Abstract
configuration.put(JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION, true)
}
if (JsEnvironmentConfigurationDirectives.GENERATE_INLINE_ANONYMOUS_FUNCTIONS in module.directives) {
configuration.put(JSConfigurationKeys.GENERATE_INLINE_ANONYMOUS_FUNCTIONS, true)
}
if (module.frontendKind == FrontendKinds.FIR) {
configuration[CommonConfigurationKeys.USE_FIR] = true
}
@@ -100,6 +100,9 @@ public class JSConfigurationKeys {
public static final CompilerConfigurationKey<Boolean> PROPERTY_LAZY_INITIALIZATION =
CompilerConfigurationKey.create("perform lazy initialization for properties");
public static final CompilerConfigurationKey<Boolean> GENERATE_INLINE_ANONYMOUS_FUNCTIONS =
CompilerConfigurationKey.create("translate lambdas into in-line anonymous functions");
public static final CompilerConfigurationKey<Boolean> WASM_ENABLE_ARRAY_RANGE_CHECKS =
CompilerConfigurationKey.create("enable array range checks");
@@ -79,7 +79,7 @@ fun main(args: Array<String>) {
generateTestGroupSuiteWithJUnit5(args) {
testGroup("js/js.tests/tests-gen", "js/js.translator/testData", testRunnerMethodName = "runTest0") {
testClass<AbstractBoxJsTest> {
model("box/", pattern = "^([^_](.+))\\.kt$")
model("box/", pattern = "^([^_](.+))\\.kt$", excludeDirs = listOf("closure/inlineAnonymousFunctions"))
}
testClass<AbstractSourceMapGenerationSmokeTest> {
@@ -65,6 +65,12 @@ abstract class AbstractJsIrTest(
::JsIrRecompiledArtifactsIdentityHandler,
)
}
forTestsMatching("${JsEnvironmentConfigurator.TEST_DATA_DIR_PATH}/box/closure/inlineAnonymousFunctions/*") {
defaultDirectives {
+JsEnvironmentConfigurationDirectives.GENERATE_INLINE_ANONYMOUS_FUNCTIONS
}
}
}
}
}
@@ -22,7 +22,7 @@ import java.util.regex.Pattern;
public class BoxJsTestGenerated extends AbstractBoxJsTest {
@Test
public void testAllFilesPresentInBox() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true);
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true, "closure/inlineAnonymousFunctions");
}
@Nested
@@ -347,7 +347,7 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
public class Closure {
@Test
public void testAllFilesPresentInClosure() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/closure"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true);
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/closure"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true, "inlineAnonymousFunctions");
}
@Test
@@ -356,12 +356,6 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
runTest("js/js.translator/testData/box/closure/closureArrayListInstance.kt");
}
@Test
@TestMetadata("closureCodeSize.kt")
public void testClosureCodeSize() throws Exception {
runTest("js/js.translator/testData/box/closure/closureCodeSize.kt");
}
@Test
@TestMetadata("closureFunctionAsArgument.kt")
public void testClosureFunctionAsArgument() throws Exception {
@@ -356,12 +356,6 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
runTest("js/js.translator/testData/box/closure/closureArrayListInstance.kt");
}
@Test
@TestMetadata("closureCodeSize.kt")
public void testClosureCodeSize() throws Exception {
runTest("js/js.translator/testData/box/closure/closureCodeSize.kt");
}
@Test
@TestMetadata("closureFunctionAsArgument.kt")
public void testClosureFunctionAsArgument() throws Exception {
@@ -631,6 +625,58 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
public void testWrappedVariableInExtensionFun() throws Exception {
runTest("js/js.translator/testData/box/closure/wrappedVariableInExtensionFun.kt");
}
@Nested
@TestMetadata("js/js.translator/testData/box/closure/inlineAnonymousFunctions")
@TestDataPath("$PROJECT_ROOT")
public class InlineAnonymousFunctions {
@Test
public void testAllFilesPresentInInlineAnonymousFunctions() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/closure/inlineAnonymousFunctions"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
}
@Test
@TestMetadata("closureCodeSize.kt")
public void testClosureCodeSize() throws Exception {
runTest("js/js.translator/testData/box/closure/inlineAnonymousFunctions/closureCodeSize.kt");
}
@Test
@TestMetadata("closureInWithInsideWith.kt")
public void testClosureInWithInsideWith() throws Exception {
runTest("js/js.translator/testData/box/closure/inlineAnonymousFunctions/closureInWithInsideWith.kt");
}
@Test
@TestMetadata("inlineChain.kt")
public void testInlineChain() throws Exception {
runTest("js/js.translator/testData/box/closure/inlineAnonymousFunctions/inlineChain.kt");
}
@Test
@TestMetadata("lambdaChain.kt")
public void testLambdaChain() throws Exception {
runTest("js/js.translator/testData/box/closure/inlineAnonymousFunctions/lambdaChain.kt");
}
@Test
@TestMetadata("lambdaParameters.kt")
public void testLambdaParameters() throws Exception {
runTest("js/js.translator/testData/box/closure/inlineAnonymousFunctions/lambdaParameters.kt");
}
@Test
@TestMetadata("localParameterInCallback.kt")
public void testLocalParameterInCallback() throws Exception {
runTest("js/js.translator/testData/box/closure/inlineAnonymousFunctions/localParameterInCallback.kt");
}
@Test
@TestMetadata("twiceRegeneratedAnonymousObject.kt")
public void testTwiceRegeneratedAnonymousObject() throws Exception {
runTest("js/js.translator/testData/box/closure/inlineAnonymousFunctions/twiceRegeneratedAnonymousObject.kt");
}
}
}
@Nested
@@ -0,0 +1,25 @@
// KT-4237 With in with
package foo
class A {
val ok = "OK"
}
class B
fun <T> with(o: T, body: T.() -> Unit) {
o.body()
}
fun box(): String {
var o = ""
with(A()) {
with(B()) {
o = ok
}
}
return o
}
@@ -0,0 +1,29 @@
// FILE: 1.kt
package test
interface A {
fun run()
}
inline fun testNested(crossinline f: (String) -> Unit) {
object : A {
override fun run() {
f("OK")
}
}.run()
}
inline fun test(crossinline f: (String) -> Unit) {
testNested { it -> { f(it) }()}
}
// FILE: 2.kt
import test.*
fun box(): String {
var result = "fail"
test { it -> result = it }
return result
}
@@ -0,0 +1,29 @@
// FILE: 1.kt
package test
inline fun <T> inlineFun(arg: T, f: (T) -> Unit) {
f(arg)
}
// FILE: 2.kt
import test.*
fun box(): String {
val param = "start"
var result = "fail"
inlineFun("1") { c ->
{
inlineFun("2") { a ->
{
{
result = param + c + a
}()
}()
}
}()
}
return if (result == "start12") "OK" else "fail: $result"
}
@@ -0,0 +1,16 @@
fun <T> bar(x: T, y: (T) -> Boolean): Boolean = y(x) && jsTypeOf(x.asDynamic()) != "number"
fun typeOf(x: dynamic) = js("typeof x")
fun box(): String {
val f = { x: Char ->
val a: Char = x
val b: Any = x
typeOf(a) == "number" && typeOf(b) == "object"
}
if (!f('Q')) return "fail1"
if (!bar('W', f)) return "fail2"
return "OK"
}
@@ -0,0 +1,30 @@
// KJS_WITH_FULL_RUNTIME
package foo
fun box(): String {
val oneTwo = Array(2) {
it + 1
}
val a = ArrayList<() -> Int>()
for (i in oneTwo) {
for (l in 1..2) {
val j = l
a.add({
var res = 0
for (t in 0..2) {
res += i * j
}
res
})
}
}
var sum = 0
for (f in a) {
sum += f()
}
if (sum != 27) return "fail: $sum"
return "OK"
}
@@ -0,0 +1,26 @@
// WITH_STDLIB
// WITH_COROUTINES
// FILE: 1.kt
package test
inline fun foo(crossinline x: () -> Unit) = suspend {
try { } finally {
// This object is regenerated twice (normal return & "catch Throwable, execute finally, and rethrow")
// It doesn't *need* to be, but this should work regardless.
{ x() }()
}
}
// FILE: 2.kt
import helpers.*
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
import test.*
var result = "fail"
fun box(): String {
suspend {
foo { result = "OK" }()
}.startCoroutine(EmptyContinuation)
return result
}