JS: support inlining in new pipeline

This commit is contained in:
Alexey Andreev
2017-02-10 19:31:52 +03:00
parent a414cd64c5
commit 83140bc5f7
15 changed files with 157 additions and 62 deletions
@@ -27,9 +27,7 @@ import org.jetbrains.kotlin.js.inline.util.IdentitySet
import org.jetbrains.kotlin.js.inline.util.isCallInvocation
import org.jetbrains.kotlin.js.parser.parseFunction
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils.getModuleName
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import org.jetbrains.kotlin.resolve.inline.InlineStrategy
@@ -48,7 +46,7 @@ private val JS_IDENTIFIER="[$JS_IDENTIFIER_START][$JS_IDENTIFIER_PART]*"
private val DEFINE_MODULE_PATTERN = ("($JS_IDENTIFIER)\\.defineModule\\(\\s*(['\"])([^'\"]+)\\2\\s*,\\s*(\\w+)\\s*\\)").toRegex().toPattern()
private val DEFINE_MODULE_FIND_PATTERN = ".defineModule("
class FunctionReader(private val context: TranslationContext) {
class FunctionReader(private val config: LibrarySourcesConfig, private val currentModuleName: JsName, fragments: List<JsProgramFragment>) {
/**
* fileContent: .js file content, that contains this module definition.
* One file can contain more than one module definition.
@@ -63,8 +61,12 @@ class FunctionReader(private val context: TranslationContext) {
private val moduleNameToInfo = HashMultimap.create<String, ModuleInfo>()
private val moduleNameMap: Map<String, JsExpression>
init {
val libs = context.config.libraries.map(::File)
val libs = config.libraries.map(::File)
moduleNameMap = buildModuleNameMap(fragments)
JsLibraryUtils.traverseJsLibraries(libs) { fileContent, _ ->
var current = 0
@@ -86,6 +88,13 @@ class FunctionReader(private val context: TranslationContext) {
}
}
// Since we compile each source file in its own context (and we may loose these context when performing incremental compilation)
// we don't use contexts to generate proper names for modules. Instead, we generate all necessary information during
// translation and rely on it here.
private fun buildModuleNameMap(fragments: List<JsProgramFragment>): Map<String, JsExpression> {
return fragments.flatMap { it.inlineModuleMap.entries }.associate { (k, v) -> k to v }
}
private fun rewindToIdentifierStart(text: String, index: Int): Int {
var result = index
while (result > 0 && Character.isJavaIdentifierPart(text[result - 1])) {
@@ -112,7 +121,7 @@ class FunctionReader(private val context: TranslationContext) {
operator fun contains(descriptor: CallableDescriptor): Boolean {
val moduleName = getModuleName(descriptor)
val currentModuleName = context.config.moduleId
val currentModuleName = config.moduleId
return currentModuleName != moduleName && moduleName in moduleNameToInfo.keys()
}
@@ -144,7 +153,7 @@ class FunctionReader(private val context: TranslationContext) {
}
val function = parseFunction(source, offset, ThrowExceptionOnErrorReporter, JsRootScope(JsProgram()))
val moduleReference = context.getModuleExpressionFor(descriptor) ?: getRootPackage()
val moduleReference = moduleNameMap[tag] ?: currentModuleName.makeRef()
val replacements = hashMapOf(info.moduleVariable to moduleReference,
info.kotlinVariable to Namer.kotlinObject())
@@ -152,11 +161,6 @@ class FunctionReader(private val context: TranslationContext) {
function.markInlineArguments(descriptor)
return function
}
private fun getRootPackage(): JsExpression {
val rootName = context.program().rootScope.declareName(Namer.getRootPackageName())
return JsAstUtils.pureFqn(rootName, null)
}
}
private val Char.isWhitespaceOrComma: Boolean
@@ -28,6 +28,8 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticSink;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.js.backend.ast.*;
import org.jetbrains.kotlin.js.backend.ast.metadata.MetadataProperties;
import org.jetbrains.kotlin.js.config.JsConfig;
import org.jetbrains.kotlin.js.config.LibrarySourcesConfig;
import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor;
import org.jetbrains.kotlin.js.inline.clean.RemoveUnusedFunctionDefinitionsKt;
import org.jetbrains.kotlin.js.inline.clean.RemoveUnusedLocalFunctionDeclarationsKt;
@@ -37,7 +39,6 @@ import org.jetbrains.kotlin.js.inline.context.NamingContext;
import org.jetbrains.kotlin.js.inline.util.CollectUtilsKt;
import org.jetbrains.kotlin.js.inline.util.CollectionUtilsKt;
import org.jetbrains.kotlin.js.inline.util.NamingUtilsKt;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.resolve.inline.InlineStrategy;
import java.util.*;
@@ -61,15 +62,34 @@ public class JsInliner extends JsVisitorWithContextImpl {
private final Function1<JsNode, Boolean> canBeExtractedByInliner =
node -> node instanceof JsInvocation && hasToBeInlined((JsInvocation) node);
public static JsProgram process(@NotNull TranslationContext context) {
JsProgram program = context.program();
Map<JsName, JsFunction> functions = CollectUtilsKt.collectNamedFunctions(program);
Map<String, JsFunction> accessors = CollectUtilsKt.collectAccessors(program);
new DummyAccessorInvocationTransformer().accept(program);
JsInliner inliner = new JsInliner(functions, accessors, new FunctionReader(context), context.bindingTrace());
inliner.accept(program);
RemoveUnusedFunctionDefinitionsKt.removeUnusedFunctionDefinitions(program, functions);
return program;
public static void process(
@NotNull JsConfig config,
@NotNull DiagnosticSink trace,
@NotNull JsName currentModuleName,
@NotNull List<JsProgramFragment> fragments,
@NotNull List<JsProgramFragment> fragmentsToProcess
) {
Map<JsName, JsFunction> functions = CollectUtilsKt.collectNamedFunctions(fragments);
Map<String, JsFunction> accessors = CollectUtilsKt.collectAccessors(fragments);
DummyAccessorInvocationTransformer accessorInvocationTransformer = new DummyAccessorInvocationTransformer();
for (JsProgramFragment fragment : fragmentsToProcess) {
accessorInvocationTransformer.accept(fragment.getDeclarationBlock());
accessorInvocationTransformer.accept(fragment.getInitializerBlock());
}
FunctionReader functionReader = new FunctionReader((LibrarySourcesConfig) config, currentModuleName, fragments);
JsInliner inliner = new JsInliner(functions, accessors, functionReader, trace);
for (JsProgramFragment fragment : fragmentsToProcess) {
inliner.inliningContexts.push(inliner.new JsInliningContext(null, fragment.getScope()));
inliner.accept(fragment.getDeclarationBlock());
// There can be inlined function in top-level initializers, we need to optimize them as well
JsFunction fakeInitFunction = new JsFunction(JsDynamicScope.INSTANCE, fragment.getInitializerBlock(), "");
inliner.accept(fakeInitFunction);
inliner.inliningContexts.pop();
JsBlock block = new JsBlock(fragment.getDeclarationBlock(), fragment.getInitializerBlock(), fragment.getExportBlock());
RemoveUnusedFunctionDefinitionsKt.removeUnusedFunctionDefinitions(block, functions);
}
}
private JsInliner(
@@ -86,7 +106,7 @@ public class JsInliner extends JsVisitorWithContextImpl {
@Override
public boolean visit(@NotNull JsFunction function, @NotNull JsContext context) {
inliningContexts.push(new JsInliningContext(function));
inliningContexts.push(new JsInliningContext(function, function.getScope()));
assert !inProcessFunctions.contains(function): "Inliner has revisited function";
inProcessFunctions.add(function);
@@ -266,8 +286,8 @@ public class JsInliner extends JsVisitorWithContextImpl {
private class JsInliningContext implements InliningContext {
private final FunctionContext functionContext;
JsInliningContext(@NotNull JsFunction function) {
functionContext = new FunctionContext(function, functionReader) {
JsInliningContext(@NotNull JsScope scope) {
functionContext = new FunctionContext(scope) {
@Nullable
@Override
protected JsFunction lookUpStaticFunction(@Nullable JsName functionName) {
@@ -24,7 +24,7 @@ import org.jetbrains.kotlin.js.inline.util.*
import org.jetbrains.kotlin.js.translate.context.Namer
abstract class FunctionContext(
private val function: JsFunction,
private val scope: JsScope,
private val functionReader: FunctionReader
) {
protected abstract fun lookUpStaticFunction(functionName: JsName?): JsFunction?
@@ -40,7 +40,7 @@ abstract class FunctionContext(
}
fun getScope(): JsScope {
return function.scope
return scope
}
/**
@@ -137,6 +137,15 @@ fun collectNamedFunctions(scope: JsNode) = collectNamedFunctionsAndMetadata(scop
fun collectNamedFunctionsOrMetadata(scope: JsNode) = collectNamedFunctionsAndMetadata(scope).mapValues { it.value.second }
fun collectNamedFunctions(fragments: List<JsProgramFragment>): Map<JsName, JsFunction> {
val result = mutableMapOf<JsName, JsFunction>()
for (fragment in fragments) {
result += collectNamedFunctions(fragment.declarationBlock)
result += collectNamedFunctions(fragment.initializerBlock)
}
return result
}
fun collectNamedFunctionsAndMetadata(scope: JsNode): Map<JsName, Pair<JsFunction, JsExpression>> {
val namedFunctions = mutableMapOf<JsName, Pair<JsFunction, JsExpression>>()
@@ -200,6 +209,14 @@ fun collectAccessors(scope: JsNode): Map<String, JsFunction> {
return accessors
}
fun collectAccessors(fragments: List<JsProgramFragment>): Map<String, JsFunction> {
val result = mutableMapOf<String, JsFunction>()
for (fragment in fragments) {
result += collectAccessors(fragment.declarationBlock)
}
return result
}
fun <T : JsNode> collectInstances(klass: Class<T>, scope: JsNode): List<T> {
return with(InstanceCollector(klass, visitNestedDeclarations = false)) {
accept(scope)