[JS IR] Add to IR keep possibility similar to legacy-dce one

It helps to:

- keep declarations even if they are not reachable and not exported
- not minify names of not exported declarations

Compiler argument: -Xir-keep=A,B

Can be used for top-level declarations or for member

^KT-54118 fixed
This commit is contained in:
Ilya Goncharov
2022-09-20 16:06:17 +00:00
committed by Space
parent 4863e5d47b
commit d203dc35bf
28 changed files with 460 additions and 13 deletions
@@ -593,7 +593,15 @@ class GenerateIrRuntime {
private fun doBackEnd(
module: IrModuleFragment, symbolTable: SymbolTable, irBuiltIns: IrBuiltIns, jsLinker: JsIrLinker
): CompilerResult {
val context = JsIrBackendContext(module.descriptor, irBuiltIns, symbolTable, module, emptySet(), configuration)
val context = JsIrBackendContext(
module.descriptor,
irBuiltIns,
symbolTable,
module,
additionalExportedDeclarationNames = emptySet(),
keep = emptySet(),
configuration
)
ExternalDependenciesGenerator(symbolTable, listOf(jsLinker)).generateUnboundSymbolsAsDependencies()
@@ -81,6 +81,7 @@ class JsIrBackendFacade(
val splitPerFile = JsEnvironmentConfigurationDirectives.SPLIT_PER_FILE in module.directives
val perModule = JsEnvironmentConfigurationDirectives.PER_MODULE in module.directives
val runNewIr2Js = JsEnvironmentConfigurationDirectives.RUN_NEW_IR_2_JS in module.directives
val keep = module.directives[JsEnvironmentConfigurationDirectives.KEEP].toSet()
val granularity = when {
!firstTimeCompilation -> JsGenerationGranularity.WHOLE_PROGRAM
@@ -145,6 +146,7 @@ class JsIrBackendFacade(
deserializer,
phaseConfig,
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, TEST_FUNCTION))),
keep = keep,
dceRuntimeDiagnostic = null,
es6mode = false,
baseClassIntoMetadata = false,
@@ -166,6 +168,7 @@ class JsIrBackendFacade(
val mainArguments = JsEnvironmentConfigurator.getMainCallParametersForModule(module)
.run { if (shouldBeGenerated()) arguments() else null }
val runIrDce = JsEnvironmentConfigurationDirectives.RUN_IR_DCE in module.directives
val onlyIrDce = JsEnvironmentConfigurationDirectives.ONLY_IR_DCE in module.directives
val esModules = JsEnvironmentConfigurationDirectives.ES_MODULES in module.directives
val runNewIr2Js = JsEnvironmentConfigurationDirectives.RUN_NEW_IR_2_JS in module.directives
val perModuleOnly = JsEnvironmentConfigurationDirectives.SPLIT_PER_MODULE in module.directives
@@ -180,7 +183,7 @@ class JsIrBackendFacade(
// If perModuleOnly then skip whole program
// (it.dce => runIrDce) && (perModuleOnly => it.perModule)
val translationModes = TranslationMode.values()
.filter { (!it.dce || runIrDce) && (!perModuleOnly || it.perModule) }
.filter { (it.dce || !onlyIrDce) && (!it.dce || runIrDce) && (!perModuleOnly || it.perModule) }
.filter { it.dce == it.minimizedMemberNames }
.toSet()
val compilationOut = transformer.generateModule(loweredIr.allModules, translationModes, false)
@@ -33,8 +33,9 @@ class JsMinifierRunner(testServices: TestServices) : AbstractJsArtifactsCollecto
val dontRunGeneratedCode = globalDirectives[JsEnvironmentConfigurationDirectives.DONT_RUN_GENERATED_CODE]
.contains(testServices.defaultsProvider.defaultTargetBackend?.name)
val esModules = JsEnvironmentConfigurationDirectives.ES_MODULES in globalDirectives
val onlyIrDce = JsEnvironmentConfigurationDirectives.ONLY_IR_DCE in globalDirectives
if (dontRunGeneratedCode || esModules) return
if (dontRunGeneratedCode || esModules || onlyIrDce) return
val allJsFiles = getOnlyJsFilesForRunner(testServices, modulesToArtifact)
@@ -22,7 +22,6 @@ import java.io.File
class NodeJsGeneratorHandler(testServices: TestServices) : AbstractJsArtifactsCollector(testServices) {
override fun processAfterAllModules(someAssertionWasFailed: Boolean) {
if (someAssertionWasFailed) return
val allJsFiles = getOnlyJsFilesForRunner(testServices, modulesToArtifact)
val globalDirectives = testServices.moduleStructure.allDirectives
@@ -31,8 +30,11 @@ class NodeJsGeneratorHandler(testServices: TestServices) : AbstractJsArtifactsCo
val generateNodeJsRunner = JsEnvironmentConfigurationDirectives.GENERATE_NODE_JS_RUNNER in globalDirectives
val skipNodeJs = JsEnvironmentConfigurationDirectives.SKIP_NODE_JS in globalDirectives
val esModules = JsEnvironmentConfigurationDirectives.ES_MODULES in globalDirectives
val onlyIrDce = JsEnvironmentConfigurationDirectives.ONLY_IR_DCE in globalDirectives
if (dontRunGeneratedCode || !generateNodeJsRunner || skipNodeJs || esModules) return
if (dontRunGeneratedCode || !generateNodeJsRunner || skipNodeJs || esModules || onlyIrDce) return
val allJsFiles = getOnlyJsFilesForRunner(testServices, modulesToArtifact)
val mainModuleName = getMainModuleName(testServices)
val outputDir = File(JsEnvironmentConfigurator.getJsArtifactsOutputDir(testServices).absolutePath)
@@ -136,10 +136,14 @@ fun getAllFilesForRunner(
val artifactsPaths = modulesToArtifact.values.map { it.outputFile.absolutePath }.filter { !File(it).isDirectory }
val allJsFiles = additionalFiles + inputJsFilesBefore +artifactsPaths + commonFiles + additionalMainFiles + inputJsFilesAfter
val result = mutableMapOf(TranslationMode.FULL to allJsFiles)
val result = mutableMapOf<TranslationMode, List<String>>()
val globalDirectives = testServices.moduleStructure.allDirectives
val runIrDce = JsEnvironmentConfigurationDirectives.RUN_IR_DCE in globalDirectives
val onlyIrDce = JsEnvironmentConfigurationDirectives.ONLY_IR_DCE in globalDirectives
if (!onlyIrDce) {
result[TranslationMode.FULL] = allJsFiles
}
if (runIrDce) {
val dceJsFiles = artifactsPaths.map { it.replace(outputDir.absolutePath, dceOutputDir.absolutePath) }
val dceAllJsFiles = additionalFiles + inputJsFilesBefore + dceJsFiles + commonFiles + additionalMainFiles + inputJsFilesAfter
@@ -6797,6 +6797,16 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/keep")
@TestDataPath("$PROJECT_ROOT")
public class Keep {
@Test
public void testAllFilesPresentInKeep() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/keep"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true);
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/kotlin.test")
@TestDataPath("$PROJECT_ROOT")
@@ -7269,6 +7269,46 @@ public class FirJsTestGenerated extends AbstractFirJsTest {
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/keep")
@TestDataPath("$PROJECT_ROOT")
public class Keep {
@Test
public void testAllFilesPresentInKeep() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/keep"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
}
@Test
@TestMetadata("keepClass.kt")
public void testKeepClass() throws Exception {
runTest("js/js.translator/testData/box/keep/keepClass.kt");
}
@Test
@TestMetadata("keepMethod.kt")
public void testKeepMethod() throws Exception {
runTest("js/js.translator/testData/box/keep/keepMethod.kt");
}
@Test
@TestMetadata("keepNestedClass.kt")
public void testKeepNestedClass() throws Exception {
runTest("js/js.translator/testData/box/keep/keepNestedClass.kt");
}
@Test
@TestMetadata("keepNestedClassIfKeptTopLevelClass.kt")
public void testKeepNestedClassIfKeptTopLevelClass() throws Exception {
runTest("js/js.translator/testData/box/keep/keepNestedClassIfKeptTopLevelClass.kt");
}
@Test
@TestMetadata("keepOverriddenMethod.kt")
public void testKeepOverriddenMethod() throws Exception {
runTest("js/js.translator/testData/box/keep/keepOverriddenMethod.kt");
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/kotlin.test")
@TestDataPath("$PROJECT_ROOT")
@@ -7269,6 +7269,46 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/keep")
@TestDataPath("$PROJECT_ROOT")
public class Keep {
@Test
public void testAllFilesPresentInKeep() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/keep"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true);
}
@Test
@TestMetadata("keepClass.kt")
public void testKeepClass() throws Exception {
runTest("js/js.translator/testData/box/keep/keepClass.kt");
}
@Test
@TestMetadata("keepMethod.kt")
public void testKeepMethod() throws Exception {
runTest("js/js.translator/testData/box/keep/keepMethod.kt");
}
@Test
@TestMetadata("keepNestedClass.kt")
public void testKeepNestedClass() throws Exception {
runTest("js/js.translator/testData/box/keep/keepNestedClass.kt");
}
@Test
@TestMetadata("keepNestedClassIfKeptTopLevelClass.kt")
public void testKeepNestedClassIfKeptTopLevelClass() throws Exception {
runTest("js/js.translator/testData/box/keep/keepNestedClassIfKeptTopLevelClass.kt");
}
@Test
@TestMetadata("keepOverriddenMethod.kt")
public void testKeepOverriddenMethod() throws Exception {
runTest("js/js.translator/testData/box/keep/keepOverriddenMethod.kt");
}
}
@Nested
@TestMetadata("js/js.translator/testData/box/kotlin.test")
@TestDataPath("$PROJECT_ROOT")
+33
View File
@@ -0,0 +1,33 @@
// TARGET_BACKEND: JS_IR
// ONLY_IR_DCE
// RUN_PLAIN_BOX_FUNCTION
// INFER_MAIN_MODULE
// KEEP: A
// MODULE: keep_class
// FILE: lib.kt
class A {
fun foo(): String {
return "foo"
}
fun bar(): String {
return "bar"
}
}
@JsExport
fun bar(): A {
return A()
}
// FILE: test.js
function box() {
var a = this["keep_class"].bar()
if (a.foo_26di_k$() != "foo") return "fail 1"
if (a.bar_232r_k$() != "bar") return "fail 2"
return "OK"
}
+33
View File
@@ -0,0 +1,33 @@
// TARGET_BACKEND: JS_IR
// ONLY_IR_DCE
// RUN_PLAIN_BOX_FUNCTION
// INFER_MAIN_MODULE
// KEEP: A.foo
// MODULE: keep_method
// FILE: lib.kt
class A {
fun foo(): String {
return "foo"
}
fun bar(): String {
return "bar"
}
}
@JsExport
fun bar(): A {
return A()
}
// FILE: test.js
function box() {
var a = this["keep_method"].bar()
if (a.foo_26di_k$() != "foo") return "fail 1"
if (typeof a.bar_232r_k$ !== "undefined") return "fail 2"
return "OK"
}
+44
View File
@@ -0,0 +1,44 @@
// TARGET_BACKEND: JS_IR
// ONLY_IR_DCE
// RUN_PLAIN_BOX_FUNCTION
// INFER_MAIN_MODULE
// KEEP: A.B
// MODULE: keep_nested_class
// FILE: lib.kt
class A {
fun foo(): String {
return "foo"
}
fun bar(): String {
return "bar"
}
class B {
fun baz() = "baz"
}
}
@JsExport
fun barA(): A {
return A()
}
@JsExport
fun bar(): A.B {
return A.B()
}
// FILE: test.js
function box() {
var a = this["keep_nested_class"].barA()
var b = this["keep_nested_class"].bar()
if (typeof a.foo_26di_k$ !== "undefined") return "fail 1"
if (typeof a.bar_232r_k$ !== "undefined") return "fail 2"
if (b.baz_232z_k$() != "baz") return "fail 3"
return "OK"
}
@@ -0,0 +1,44 @@
// TARGET_BACKEND: JS_IR
// ONLY_IR_DCE
// RUN_PLAIN_BOX_FUNCTION
// INFER_MAIN_MODULE
// KEEP: A
// MODULE: keep_nested_class_if_kept_top_level_class
// FILE: lib.kt
class A {
fun foo(): String {
return "foo"
}
fun bar(): String {
return "bar"
}
class B {
fun baz() = "baz"
}
}
@JsExport
fun barA(): A {
return A()
}
@JsExport
fun bar(): A.B {
return A.B()
}
// FILE: test.js
function box() {
var a = this["keep_nested_class_if_kept_top_level_class"].barA()
var b = this["keep_nested_class_if_kept_top_level_class"].bar()
if (a.foo_26di_k$() != "foo") return "fail 1"
if (a.bar_232r_k$() != "bar") return "fail 2"
if (b.baz_232z_k$() != "baz") return "fail 3"
return "OK"
}
@@ -0,0 +1,38 @@
// TARGET_BACKEND: JS_IR
// ONLY_IR_DCE
// RUN_PLAIN_BOX_FUNCTION
// INFER_MAIN_MODULE
// KEEP: A.foo
// MODULE: keep_overridden_method
// FILE: lib.kt
open class A {
open fun foo(): String {
return "foo"
}
open fun bar(): String {
return "bar"
}
}
open class B : A() {
override fun foo(): String {
return super.foo() + "!"
}
}
@JsExport
fun bar(): B {
return B()
}
// FILE: test.js
function box() {
var b = this["keep_overridden_method"].bar()
if (b.foo_26di_k$() != "foo!") return "fail 1"
return "OK"
}