Prototyping DCE tool for JS

This commit is contained in:
Alexey Andreev
2017-03-31 20:03:41 +03:00
parent d617b1d869
commit 9e89213d66
26 changed files with 1445 additions and 22 deletions
+1
View File
@@ -16,5 +16,6 @@
<orderEntry type="module" module-name="backend-common" scope="TEST" />
<orderEntry type="module" module-name="util" />
<orderEntry type="module" module-name="js.serializer" />
<orderEntry type="module" module-name="js.dce" />
</component>
</module>
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.test
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiManager
@@ -35,6 +36,8 @@ import org.jetbrains.kotlin.js.backend.ast.JsProgram
import org.jetbrains.kotlin.js.config.EcmaVersion
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.dce.DeadCodeElimination
import org.jetbrains.kotlin.js.dce.InputFile
import org.jetbrains.kotlin.js.facade.K2JSTranslator
import org.jetbrains.kotlin.js.facade.MainCallParameters
import org.jetbrains.kotlin.js.facade.TranslationResult
@@ -53,6 +56,7 @@ import org.jetbrains.kotlin.test.KotlinTestUtils.TestFileFactory
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.utils.DFS
import org.mozilla.javascript.Context
import java.io.ByteArrayOutputStream
import java.io.Closeable
import java.io.File
@@ -102,8 +106,9 @@ abstract class BasicBoxTest(
generateJavaScriptFile(file.parent, module, outputFileName, dependencies, friends, modules.size > 1,
outputPrefixFile, outputPostfixFile, mainCallParameters)
if (!module.name.endsWith(OLD_MODULE_SUFFIX)) outputFileName else null
if (!module.name.endsWith(OLD_MODULE_SUFFIX)) Pair(outputFileName, module) else null
}
val mainModuleName = if (TEST_MODULE in modules) TEST_MODULE else DEFAULT_MODULE
val mainModule = modules[mainModuleName]!!
@@ -140,7 +145,7 @@ abstract class BasicBoxTest(
additionalFiles += additionalJsFile
}
val allJsFiles = additionalFiles + inputJsFiles + generatedJsFiles + globalCommonFiles + localCommonFiles +
val allJsFiles = additionalFiles + inputJsFiles + generatedJsFiles.map { it.first } + globalCommonFiles + localCommonFiles +
additionalCommonFiles
if (generateNodeJsRunner && !SKIP_NODE_JS.matcher(fileContent).find()) {
@@ -152,7 +157,10 @@ abstract class BasicBoxTest(
runGeneratedCode(allJsFiles, mainModuleName, testFactory.testPackage, TEST_FUNCTION, expectedResult, withModuleSystem)
performAdditionalChecks(generatedJsFiles, outputPrefixFile, outputPostfixFile)
performAdditionalChecks(generatedJsFiles.map { it.first }, outputPrefixFile, outputPostfixFile)
minifyAndRun(File(File(outputDir, "min"), file.nameWithoutExtension), allJsFiles, generatedJsFiles, expectedResult,
mainModuleName, testFactory.testPackage, TEST_FUNCTION, withModuleSystem)
}
}
@@ -397,6 +405,59 @@ abstract class BasicBoxTest(
return JsConfig(project, configuration)
}
private fun minifyAndRun(
workDir: File, allJsFiles: List<String>, generatedJsFiles: List<Pair<String, TestModule>>,
expectedResult: String, testModuleName: String, testPackage: String?, testFunction: String, withModuleSystem: Boolean
) {
val kotlinJsLib = DIST_DIR_JS_PATH + "kotlin.js"
val kotlinTestJsLib = DIST_DIR_JS_PATH + "kotlin-test.js"
val kotlinJsLibOutput = File(workDir, "kotlin.min.js").path
val kotlinTestJsLibOutput = File(workDir, "kotlin-test.min.js").path
val kotlinJsInputFile = InputFile(kotlinJsLib, kotlinJsLibOutput, "kotlin")
val kotlinTestJsInputFile = InputFile(kotlinTestJsLib, kotlinTestJsLibOutput, "kotlin-test")
val filesToMinify = generatedJsFiles.associate { (fileName, module) ->
val inputFileName = File(fileName).nameWithoutExtension
fileName to InputFile(fileName, File(workDir, inputFileName + ".min.js").absolutePath, module.name)
}
val testFunctionFqn = testModuleName + (if (testPackage.isNullOrEmpty()) "" else ".$testPackage") + ".$testFunction"
val additionalReachableNodes = setOf(
testFunctionFqn, "kotlin.kotlin.io.BufferedOutput", "kotlin.kotlin.io.output.flush",
"kotlin.kotlin.io.output.buffer"
)
val allFilesToMinify = filesToMinify.values + kotlinJsInputFile + kotlinTestJsInputFile
val reachableNodes = DeadCodeElimination.run(allFilesToMinify, additionalReachableNodes) { }
if (reachableNodes.size > 1500) println("!!!")
println(reachableNodes.size.toString() + ": " + workDir.path)
val runList = mutableListOf<String>()
runList += kotlinJsLibOutput
runList += kotlinTestJsLibOutput
val context = Context.enter()
context.languageVersion = Context.VERSION_1_8
context.optimizationLevel = -1
val scope = context.initStandardObjects()
fun applyFile(fileToRun: String) {
val code = FileUtil.loadFile(File(fileToRun), CharsetToolkit.UTF8, true)
context.evaluateString(scope, code, fileToRun, 1, null)
}
applyFile(DIST_DIR_JS_PATH + RhinoUtils.RHINO_POLYFILLS_RELATIVE_PATH)
applyFile(kotlinJsLibOutput)
applyFile(kotlinTestJsLibOutput)
context.evaluateString(scope, "function ok() {}", "setup assertions", 0, null)
context.evaluateString(scope, RhinoUtils.SETUP_KOTLIN_OUTPUT, "setup kotlin output", 0, null)
allJsFiles.map { filesToMinify[it]?.outputName ?: it }.forEach(::applyFile)
val checker = RhinoFunctionResultChecker(testModuleName, testPackage, testFunction, expectedResult, withModuleSystem)
checker.runChecks(context, scope)
}
private inner class TestFileFactoryImpl : TestFileFactory<TestModule, TestFile>, Closeable {
var testPackage: String? = null
val tmpDir = KotlinTestUtils.tmpDir("js-tests")
@@ -57,8 +57,8 @@ class NameResolutionTest {
val expectedCode = FileUtil.loadFile(File(expectedName))
val parserScope = JsFunctionScope(JsRootScope(JsProgram()), "<js fun>")
val originalAst = JsGlobalBlock().apply { statements += parse(originalCode, errorReporter, parserScope) }
val expectedAst = JsGlobalBlock().apply { statements += parse(expectedCode, errorReporter, parserScope) }
val originalAst = JsGlobalBlock().apply { statements += parse(originalCode, errorReporter, parserScope, originalName) }
val expectedAst = JsGlobalBlock().apply { statements += parse(expectedCode, errorReporter, parserScope, expectedName) }
originalAst.accept(object : RecursiveJsVisitor() {
val cache = mutableMapOf<JsName, JsName>()
@@ -55,7 +55,7 @@ abstract class BasicOptimizerTest(private var basePath: String) {
private fun checkOptimizer(unoptimizedCode: String, optimizedCode: String) {
val parserScope = JsFunctionScope(JsRootScope(JsProgram()), "<js fun>")
val unoptimizedAst = parse(unoptimizedCode, errorReporter, parserScope)
val unoptimizedAst = parse(unoptimizedCode, errorReporter, parserScope, "<unknown file>")
updateMetadata(unoptimizedCode, unoptimizedAst)
@@ -63,7 +63,7 @@ abstract class BasicOptimizerTest(private var basePath: String) {
process(statement)
}
val optimizedAst = parse(optimizedCode, errorReporter, parserScope)
val optimizedAst = parse(optimizedCode, errorReporter, parserScope, "<unknown file>")
Assert.assertEquals(astToString(optimizedAst), astToString(unoptimizedAst))
}