[JS IR] Introduce the GENERATE_INLINE_ANONYMOUS_FUNCTIONS feature flag
This commit is contained in:
+6
@@ -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
|
||||
|
||||
|
||||
+8
-2
@@ -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
@@ -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
|
||||
|
||||
+5
@@ -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
|
||||
|
||||
+4
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-8
@@ -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 {
|
||||
|
||||
+52
-6
@@ -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
|
||||
|
||||
+25
@@ -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
|
||||
}
|
||||
+29
@@ -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
|
||||
}
|
||||
+29
@@ -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"
|
||||
}
|
||||
+16
@@ -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"
|
||||
}
|
||||
+30
@@ -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"
|
||||
}
|
||||
Vendored
+26
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user