JS: support inlining in new pipeline
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user