JS: convert JsInliner to Kotlin
This commit is contained in:
@@ -1,757 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.inline;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import kotlin.jvm.functions.Function1;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.backend.common.CommonCoroutineCodegenUtilKt;
|
||||
import org.jetbrains.kotlin.config.CommonConfigurationKeysKt;
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings;
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor;
|
||||
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.inline.clean.*;
|
||||
import org.jetbrains.kotlin.js.inline.context.FunctionContext;
|
||||
import org.jetbrains.kotlin.js.inline.context.InliningContext;
|
||||
import org.jetbrains.kotlin.js.inline.context.NamingContext;
|
||||
import org.jetbrains.kotlin.js.inline.util.*;
|
||||
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata;
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineStrategy;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jetbrains.kotlin.js.inline.util.CollectUtilsKt.getImportTag;
|
||||
import static org.jetbrains.kotlin.js.inline.util.CollectionUtilsKt.IdentitySet;
|
||||
import static org.jetbrains.kotlin.js.translate.declaration.InlineCoroutineUtilKt.transformSpecialFunctionsToCoroutineMetadata;
|
||||
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.flattenStatement;
|
||||
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn;
|
||||
|
||||
public class JsInliner extends JsVisitorWithContextImpl {
|
||||
|
||||
private final JsConfig config;
|
||||
private final Map<JsName, FunctionWithWrapper> functions;
|
||||
private final Set<JsFunction> namedFunctionsSet;
|
||||
private final Map<String, FunctionWithWrapper> accessors;
|
||||
private final Stack<JsInliningContext> inliningContexts = new Stack<>();
|
||||
private final Set<JsFunction> processedFunctions = CollectionUtilsKt.IdentitySet();
|
||||
private final Set<JsFunction> inProcessFunctions = CollectionUtilsKt.IdentitySet();
|
||||
private final FunctionReader functionReader;
|
||||
private final DiagnosticSink trace;
|
||||
private Map<String, JsName> existingImports = new HashMap<>();
|
||||
private JsContext<JsStatement> statementContextForInline;
|
||||
private final Map<JsBlock, FunctionWithWrapper> functionsByWrapperNodes = new HashMap<>();
|
||||
private final Map<JsFunction, FunctionWithWrapper> functionsByFunctionNodes = new HashMap<>();
|
||||
|
||||
// these are needed for error reporting, when inliner detects cycle
|
||||
private final Stack<JsFunction> namedFunctionsStack = new Stack<>();
|
||||
private final LinkedList<JsCallInfo> inlineCallInfos = new LinkedList<>();
|
||||
private final Function1<JsNode, Boolean> canBeExtractedByInliner =
|
||||
node -> node instanceof JsInvocation && hasToBeInlined((JsInvocation) node);
|
||||
private int inlineFunctionDepth;
|
||||
|
||||
private final Map<JsWrapperKey, Map<JsName, JsNameRef>> replacementsInducedByWrappers = new HashMap<>();
|
||||
|
||||
private final Map<JsName, String> inverseNameBindings;
|
||||
|
||||
private Map<JsName, String> existingNameBindings = new HashMap<>();
|
||||
|
||||
private final List<JsNameBinding> additionalNameBindings = new ArrayList<>();
|
||||
|
||||
private final Set<JsName> inlinedModuleAliases = new HashSet<>();
|
||||
|
||||
public static void process(
|
||||
@NotNull JsConfig.Reporter reporter,
|
||||
@NotNull JsConfig config,
|
||||
@NotNull DiagnosticSink trace,
|
||||
@NotNull JsName currentModuleName,
|
||||
@NotNull List<JsProgramFragment> fragments,
|
||||
@NotNull List<JsProgramFragment> fragmentsToProcess,
|
||||
@NotNull List<JsStatement> importStatements
|
||||
) {
|
||||
Map<JsName, FunctionWithWrapper> functions = CollectUtilsKt.collectNamedFunctionsAndWrappers(fragments);
|
||||
Map<String, FunctionWithWrapper> accessors = CollectUtilsKt.collectAccessors(fragments);
|
||||
Map<JsName, String> inverseNameBindings = CollectUtilsKt.collectNameBindings(fragments);
|
||||
|
||||
DummyAccessorInvocationTransformer accessorInvocationTransformer = new DummyAccessorInvocationTransformer();
|
||||
for (JsProgramFragment fragment : fragmentsToProcess) {
|
||||
accessorInvocationTransformer.accept(fragment.getDeclarationBlock());
|
||||
accessorInvocationTransformer.accept(fragment.getInitializerBlock());
|
||||
}
|
||||
FunctionReader functionReader = new FunctionReader(reporter, config, currentModuleName, fragments);
|
||||
JsInliner inliner = new JsInliner(config, functions, accessors, inverseNameBindings, functionReader, trace);
|
||||
|
||||
for (JsStatement statement : importStatements) {
|
||||
inliner.processImportStatement(statement);
|
||||
}
|
||||
|
||||
Map<JsName, JsImportedModule> moduleMap = fillModuleMap(buildModuleMap(fragments), fragmentsToProcess);
|
||||
|
||||
for (JsProgramFragment fragment : fragmentsToProcess) {
|
||||
inliner.existingImports.clear();
|
||||
inliner.additionalNameBindings.clear();
|
||||
inliner.inlinedModuleAliases.clear();
|
||||
inliner.existingNameBindings = CollectUtilsKt.collectNameBindings(Collections.singletonList(fragment));
|
||||
|
||||
inliner.acceptStatement(fragment.getDeclarationBlock());
|
||||
// Mostly for the sake of post-processor
|
||||
// TODO are inline function marked with @Test possible?
|
||||
if (fragment.getTests() != null) {
|
||||
inliner.acceptStatement(fragment.getTests());
|
||||
}
|
||||
|
||||
// There can be inlined function in top-level initializers, we need to optimize them as well
|
||||
JsFunction fakeInitFunction = new JsFunction(JsDynamicScope.INSTANCE, fragment.getInitializerBlock(), "");
|
||||
JsGlobalBlock initWrapper = new JsGlobalBlock();
|
||||
initWrapper.getStatements().add(new JsExpressionStatement(fakeInitFunction));
|
||||
inliner.accept(initWrapper);
|
||||
initWrapper.getStatements().remove(initWrapper.getStatements().size() - 1);
|
||||
|
||||
fragment.getInitializerBlock().getStatements().addAll(0, initWrapper.getStatements());
|
||||
fragment.getNameBindings().addAll(inliner.additionalNameBindings);
|
||||
inliner.addInlinedModules(fragment, moduleMap);
|
||||
}
|
||||
|
||||
for (JsProgramFragment fragment : fragmentsToProcess) {
|
||||
JsBlock block = new JsBlock(fragment.getDeclarationBlock(), fragment.getInitializerBlock(), fragment.getExportBlock());
|
||||
RemoveUnusedImportsKt.removeUnusedImports(block);
|
||||
SimplifyWrappedFunctionsKt.simplifyWrappedFunctions(block);
|
||||
RemoveUnusedFunctionDefinitionsKt.removeUnusedFunctionDefinitions(block, CollectUtilsKt.collectNamedFunctions(block));
|
||||
}
|
||||
}
|
||||
|
||||
private void addInlinedModules(JsProgramFragment fragment, Map<JsName, JsImportedModule> moduleMap) {
|
||||
Set<JsName> localMap = buildModuleMap(Collections.singletonList(fragment)).keySet();
|
||||
for (JsName inlinedModuleName : inlinedModuleAliases) {
|
||||
if (!localMap.contains(inlinedModuleName)) {
|
||||
fragment.getImportedModules().add(moduleMap.get(inlinedModuleName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<JsName, JsImportedModule> buildModuleMap(List<JsProgramFragment> fragments) {
|
||||
return fillModuleMap(new HashMap<>(), fragments);
|
||||
}
|
||||
|
||||
private static Map<JsName, JsImportedModule> fillModuleMap(Map<JsName, JsImportedModule> map, List<JsProgramFragment> fragments) {
|
||||
for (JsProgramFragment fragment : fragments) {
|
||||
for (JsImportedModule module : fragment.getImportedModules()) {
|
||||
map.put(module.getInternalName(), module);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
private JsInliner(
|
||||
@NotNull JsConfig config,
|
||||
@NotNull Map<JsName, FunctionWithWrapper> functions,
|
||||
@NotNull Map<String, FunctionWithWrapper> accessors,
|
||||
@NotNull Map<JsName, String> inverseNameBindings,
|
||||
@NotNull FunctionReader functionReader,
|
||||
@NotNull DiagnosticSink trace
|
||||
) {
|
||||
this.config = config;
|
||||
this.functions = functions;
|
||||
this.namedFunctionsSet = IdentitySet();
|
||||
for (FunctionWithWrapper functionWithWrapper : functions.values()) {
|
||||
namedFunctionsSet.add(functionWithWrapper.getFunction());
|
||||
}
|
||||
this.accessors = accessors;
|
||||
this.inverseNameBindings = inverseNameBindings;
|
||||
this.functionReader = functionReader;
|
||||
this.trace = trace;
|
||||
|
||||
Stream.concat(functions.values().stream(), accessors.values().stream())
|
||||
.forEach(f -> {
|
||||
functionsByFunctionNodes.put(f.getFunction(), f);
|
||||
if (f.getWrapperBody() != null) {
|
||||
functionsByWrapperNodes.put(f.getWrapperBody(), f);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void processImportStatement(JsStatement statement) {
|
||||
if (statement instanceof JsVars) {
|
||||
JsVars jsVars = (JsVars) statement;
|
||||
String tag = getImportTag(jsVars);
|
||||
if (tag != null) {
|
||||
existingImports.put(tag, jsVars.getVars().get(0).getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(@NotNull JsFunction function, @NotNull JsContext context) {
|
||||
FunctionWithWrapper functionWithWrapper = functionsByFunctionNodes.get(function);
|
||||
if (functionWithWrapper != null) {
|
||||
visit(functionWithWrapper);
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
if (statementContextForInline == null) {
|
||||
statementContextForInline = getLastStatementLevelContext();
|
||||
startFunction(function);
|
||||
boolean result = super.visit(function, context);
|
||||
statementContextForInline = null;
|
||||
return result;
|
||||
} else {
|
||||
startFunction(function);
|
||||
return super.visit(function, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(@NotNull JsFunction function, @NotNull JsContext context) {
|
||||
super.endVisit(function, context);
|
||||
if (!functionsByFunctionNodes.containsKey(function)) {
|
||||
endFunction(function);
|
||||
}
|
||||
}
|
||||
|
||||
private void startFunction(@NotNull JsFunction function) {
|
||||
inliningContexts.push(new JsInliningContext(statementContextForInline));
|
||||
|
||||
assert !inProcessFunctions.contains(function): "Inliner has revisited function";
|
||||
inProcessFunctions.add(function);
|
||||
|
||||
if (namedFunctionsSet.contains(function)) {
|
||||
namedFunctionsStack.push(function);
|
||||
}
|
||||
}
|
||||
|
||||
private void endFunction(@NotNull JsFunction function) {
|
||||
NamingUtilsKt.refreshLabelNames(function.getBody(), function.getScope());
|
||||
|
||||
RemoveUnusedLocalFunctionDeclarationsKt.removeUnusedLocalFunctionDeclarations(function);
|
||||
processedFunctions.add(function);
|
||||
|
||||
new FunctionPostProcessor(function).apply();
|
||||
|
||||
assert inProcessFunctions.contains(function);
|
||||
inProcessFunctions.remove(function);
|
||||
|
||||
inliningContexts.pop();
|
||||
|
||||
if (!namedFunctionsStack.empty() && namedFunctionsStack.peek() == function) {
|
||||
namedFunctionsStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(@NotNull JsBlock x, @NotNull JsContext ctx) {
|
||||
FunctionWithWrapper functionWithWrapper = functionsByWrapperNodes.get(x);
|
||||
if (functionWithWrapper != null) {
|
||||
visit(functionWithWrapper);
|
||||
return false;
|
||||
}
|
||||
return super.visit(x, ctx);
|
||||
}
|
||||
|
||||
private void visit(@NotNull FunctionWithWrapper functionWithWrapper) {
|
||||
JsContext<JsStatement> oldContextForInline = statementContextForInline;
|
||||
Map<String, JsName> oldExistingImports = existingImports;
|
||||
int oldInlineFunctionDepth = inlineFunctionDepth;
|
||||
|
||||
ListContext<JsStatement> innerContext = new ListContext<>();
|
||||
|
||||
JsBlock wrapperBody = functionWithWrapper.getWrapperBody();
|
||||
List<JsStatement> statements = null;
|
||||
if (wrapperBody != null) {
|
||||
existingImports = new HashMap<>();
|
||||
statementContexts.push(innerContext);
|
||||
statementContextForInline = innerContext;
|
||||
inlineFunctionDepth++;
|
||||
|
||||
for (JsStatement statement : wrapperBody.getStatements()) {
|
||||
processImportStatement(statement);
|
||||
}
|
||||
assert functionWithWrapper.getWrapperBody() != null;
|
||||
statements = functionWithWrapper.getWrapperBody().getStatements();
|
||||
if (!statements.isEmpty() && statements.get(statements.size() - 1) instanceof JsReturn) {
|
||||
statements = statements.subList(0, statements.size() - 1);
|
||||
}
|
||||
|
||||
innerContext.traverse(statements);
|
||||
statementContexts.pop();
|
||||
} else {
|
||||
if (statementContextForInline == null) statementContextForInline = getLastStatementLevelContext();
|
||||
}
|
||||
|
||||
startFunction(functionWithWrapper.getFunction());
|
||||
|
||||
JsBlock block = new JsBlock(functionWithWrapper.getFunction().getBody());
|
||||
innerContext.traverse(block.getStatements());
|
||||
functionWithWrapper.getFunction().getBody().traverse(this, innerContext);
|
||||
|
||||
endFunction(functionWithWrapper.getFunction());
|
||||
|
||||
if (statements != null) {
|
||||
statements.addAll(block.getStatements().subList(0, block.getStatements().size() - 1));
|
||||
}
|
||||
|
||||
statementContextForInline = oldContextForInline;
|
||||
existingImports = oldExistingImports;
|
||||
inlineFunctionDepth = oldInlineFunctionDepth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visit(@NotNull JsInvocation call, @NotNull JsContext context) {
|
||||
if (!hasToBeInlined(call)) return true;
|
||||
|
||||
JsFunction containingFunction = getCurrentNamedFunction();
|
||||
|
||||
if (containingFunction != null) {
|
||||
inlineCallInfos.add(new JsCallInfo(call, containingFunction));
|
||||
}
|
||||
|
||||
FunctionWithWrapper definition = getFunctionContext().getFunctionDefinition(call);
|
||||
|
||||
if (inProcessFunctions.contains(definition.getFunction())) {
|
||||
reportInlineCycle(call, definition.getFunction());
|
||||
}
|
||||
else if (!processedFunctions.contains(definition.getFunction())) {
|
||||
for (int i = 0; i < call.getArguments().size(); ++i) {
|
||||
JsExpression argument = call.getArguments().get(i);
|
||||
call.getArguments().set(i, accept(argument));
|
||||
}
|
||||
visit(definition);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(@NotNull JsInvocation x, @NotNull JsContext ctx) {
|
||||
if (hasToBeInlined(x)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
JsContext<JsNode> context = (JsContext) ctx;
|
||||
inline(x, context);
|
||||
}
|
||||
|
||||
JsCallInfo lastCallInfo = null;
|
||||
|
||||
if (!inlineCallInfos.isEmpty()) {
|
||||
lastCallInfo = inlineCallInfos.getLast();
|
||||
}
|
||||
|
||||
if (lastCallInfo != null && lastCallInfo.call == x) {
|
||||
inlineCallInfos.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(@NotNull JsExpressionStatement x, @NotNull JsContext ctx) {
|
||||
JsExpression e = x.getExpression();
|
||||
if (e instanceof JsBinaryOperation) {
|
||||
JsBinaryOperation binOp = (JsBinaryOperation) e;
|
||||
if (binOp.getOperator() == JsBinaryOperator.ASG) {
|
||||
JsExpression argument2 = binOp.getArg2();
|
||||
if (argument2 != null) {
|
||||
JsFunction splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(argument2);
|
||||
if (splitSuspendInlineFunction != null) {
|
||||
binOp.setArg2(splitSuspendInlineFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.endVisit(x, ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(@NotNull JsVars.JsVar x, @NotNull JsContext ctx) {
|
||||
JsExpression initExpression = x.getInitExpression();
|
||||
if (initExpression != null) {
|
||||
JsFunction splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(initExpression);
|
||||
if (splitSuspendInlineFunction != null) {
|
||||
x.setInitExpression(splitSuspendInlineFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JsFunction splitExportedSuspendInlineFunctionDeclarations(@NotNull JsExpression expression) {
|
||||
InlineMetadata inlineMetadata = InlineMetadata.decompose(expression);
|
||||
if (inlineMetadata != null) {
|
||||
FunctionWithWrapper functionWithWrapper = inlineMetadata.getFunction();
|
||||
JsFunction originalFunction = functionWithWrapper.getFunction();
|
||||
if (MetadataProperties.getCoroutineMetadata(originalFunction) != null) {
|
||||
JsContext<JsStatement> statementContext = getLastStatementLevelContext();
|
||||
|
||||
// This function will be exported to JS
|
||||
JsFunction function = originalFunction.deepCopy();
|
||||
|
||||
// Original function should be not be transformed into a state machine
|
||||
originalFunction.setName(null);
|
||||
MetadataProperties.setCoroutineMetadata(originalFunction, null);
|
||||
MetadataProperties.setInlineableCoroutineBody(originalFunction, true);
|
||||
if (functionWithWrapper.getWrapperBody() != null) {
|
||||
// Extract local declarations
|
||||
applyWrapper(functionWithWrapper.getWrapperBody(), function, originalFunction, new JsInliningContext(statementContext));
|
||||
}
|
||||
|
||||
// Keep the `defineInlineFunction` for the inliner to find
|
||||
statementContext.addNext(expression.makeStmt());
|
||||
|
||||
// Return the function body to be used without inlining.
|
||||
return function;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doAcceptStatementList(List<JsStatement> statements) {
|
||||
// at top level of js ast, contexts stack can be empty,
|
||||
// but there is no inline calls anyway
|
||||
if(!inliningContexts.isEmpty()) {
|
||||
int i = 0;
|
||||
|
||||
while (i < statements.size()) {
|
||||
List<JsStatement> additionalStatements =
|
||||
ExpressionDecomposer.preserveEvaluationOrder(statements.get(i), canBeExtractedByInliner);
|
||||
statements.addAll(i, additionalStatements);
|
||||
i += additionalStatements.size() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
super.doAcceptStatementList(statements);
|
||||
}
|
||||
|
||||
private void inline(@NotNull JsInvocation call, @NotNull JsContext<JsNode> context) {
|
||||
DeclarationDescriptor callDescriptor = MetadataProperties.getDescriptor(call);
|
||||
if (isSuspendWithCurrentContinuation(callDescriptor,
|
||||
CommonConfigurationKeysKt.getLanguageVersionSettings(config.getConfiguration()))) {
|
||||
inlineSuspendWithCurrentContinuation(call, context);
|
||||
return;
|
||||
}
|
||||
|
||||
JsInliningContext inliningContext = getInliningContext();
|
||||
FunctionWithWrapper functionWithWrapper = inliningContext.getFunctionContext().getFunctionDefinition(call);
|
||||
|
||||
// Since we could get functionWithWrapper as a simple function directly from staticRef (which always points on implementation)
|
||||
// we should check if we have a known wrapper for it
|
||||
if (functionsByFunctionNodes.containsKey(functionWithWrapper.getFunction())) {
|
||||
functionWithWrapper = functionsByFunctionNodes.get(functionWithWrapper.getFunction());
|
||||
}
|
||||
|
||||
JsFunction function = functionWithWrapper.getFunction().deepCopy();
|
||||
function.setBody(transformSpecialFunctionsToCoroutineMetadata(function.getBody()));
|
||||
if (functionWithWrapper.getWrapperBody() != null) {
|
||||
applyWrapper(functionWithWrapper.getWrapperBody(), function, functionWithWrapper.getFunction(), inliningContext);
|
||||
}
|
||||
InlineableResult inlineableResult = FunctionInlineMutator.getInlineableCallReplacement(call, function, inliningContext);
|
||||
|
||||
JsStatement inlineableBody = inlineableResult.getInlineableBody();
|
||||
JsExpression resultExpression = inlineableResult.getResultExpression();
|
||||
JsContext<JsStatement> statementContext = inliningContext.getStatementContext();
|
||||
// body of inline function can contain call to lambdas that need to be inlined
|
||||
JsStatement inlineableBodyWithLambdasInlined = accept(inlineableBody);
|
||||
assert inlineableBody == inlineableBodyWithLambdasInlined;
|
||||
|
||||
// Support non-local return from secondary constructor
|
||||
// Returns from secondary constructors should return `$this` object.
|
||||
JsFunction currentFunction = getCurrentNamedFunction();
|
||||
if (currentFunction != null) {
|
||||
JsName returnVariable = MetadataProperties.getForcedReturnVariable(currentFunction);
|
||||
if (returnVariable != null) {
|
||||
inlineableBody.accept(new RecursiveJsVisitor() {
|
||||
@Override
|
||||
public void visitReturn(@NotNull JsReturn x) {
|
||||
x.setExpression(returnVariable.makeRef());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
statementContext.addPrevious(flattenStatement(inlineableBody));
|
||||
|
||||
/*
|
||||
* Assumes, that resultExpression == null, when result is not needed.
|
||||
* @see FunctionInlineMutator.isResultNeeded()
|
||||
*/
|
||||
if (resultExpression == null) {
|
||||
statementContext.removeMe();
|
||||
return;
|
||||
}
|
||||
|
||||
resultExpression = accept(resultExpression);
|
||||
MetadataProperties.setSynthetic(resultExpression, true);
|
||||
context.replaceMe(resultExpression);
|
||||
}
|
||||
|
||||
private void applyWrapper(
|
||||
@NotNull JsBlock wrapper, @NotNull JsFunction function, @NotNull JsFunction originalFunction,
|
||||
@NotNull InliningContext inliningContext
|
||||
) {
|
||||
// Apparently we should avoid this trick when we implement fair support for crossinline
|
||||
Function<JsWrapperKey, Map<JsName, JsNameRef>> replacementGen = k -> {
|
||||
JsContext<JsStatement> ctx = k.context;
|
||||
|
||||
Map<JsName, JsNameRef> newReplacements = new HashMap<>();
|
||||
|
||||
List<JsStatement> copiedStatements = new ArrayList<>();
|
||||
for (JsStatement statement : wrapper.getStatements()) {
|
||||
if (statement instanceof JsReturn) continue;
|
||||
|
||||
statement = statement.deepCopy();
|
||||
if (inlineFunctionDepth == 0) {
|
||||
replaceExpressionsWithLocalAliases(statement);
|
||||
}
|
||||
|
||||
if (statement instanceof JsVars) {
|
||||
JsVars jsVars = (JsVars) statement;
|
||||
String tag = getImportTag(jsVars);
|
||||
if (tag != null) {
|
||||
JsName name = jsVars.getVars().get(0).getName();
|
||||
JsName existingName = inlineFunctionDepth == 0 ? MetadataProperties.getLocalAlias(name) : null;
|
||||
if (existingName == null) {
|
||||
existingName = existingImports.computeIfAbsent(tag, t -> {
|
||||
copiedStatements.add(jsVars);
|
||||
JsName alias = JsScope.declareTemporaryName(name.getIdent());
|
||||
alias.copyMetadataFrom(name);
|
||||
newReplacements.put(name, pureFqn(alias, null));
|
||||
return alias;
|
||||
});
|
||||
}
|
||||
|
||||
if (name != existingName) {
|
||||
JsNameRef replacement = pureFqn(existingName, null);
|
||||
newReplacements.put(name, replacement);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
copiedStatements.add(statement);
|
||||
}
|
||||
|
||||
Set<JsName> definedNames = copiedStatements.stream()
|
||||
.flatMap(node -> CollectUtilsKt.collectDefinedNamesInAllScopes(node).stream())
|
||||
.filter(name -> !newReplacements.containsKey(name))
|
||||
.collect(Collectors.toSet());
|
||||
for (JsName name : definedNames) {
|
||||
JsName alias = JsScope.declareTemporaryName(name.getIdent());
|
||||
alias.copyMetadataFrom(name);
|
||||
JsNameRef replacement = pureFqn(alias, null);
|
||||
newReplacements.put(name, replacement);
|
||||
}
|
||||
|
||||
for (JsStatement statement : copiedStatements) {
|
||||
statement = RewriteUtilsKt.replaceNames(statement, newReplacements);
|
||||
ctx.addPrevious(accept(statement));
|
||||
}
|
||||
|
||||
for (Map.Entry<JsName, JsFunction> entry : CollectUtilsKt.collectNamedFunctions(new JsBlock(copiedStatements)).entrySet()) {
|
||||
if (MetadataProperties.getStaticRef(entry.getKey()) instanceof JsFunction) {
|
||||
MetadataProperties.setStaticRef(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return newReplacements;
|
||||
};
|
||||
|
||||
JsWrapperKey key = new JsWrapperKey(inliningContext.getStatementContextBeforeCurrentFunction(), originalFunction);
|
||||
Map<JsName, JsNameRef> replacements = replacementsInducedByWrappers.computeIfAbsent(key, replacementGen);
|
||||
|
||||
RewriteUtilsKt.replaceNames(function, replacements);
|
||||
|
||||
// Copy nameBinding's for inlined localAlias'es
|
||||
for (JsNameRef nameRef : replacements.values()) {
|
||||
JsName name = nameRef.getName();
|
||||
if (name != null && !existingNameBindings.containsKey(name)) {
|
||||
String tag = inverseNameBindings.get(name);
|
||||
if (tag != null) {
|
||||
existingNameBindings.put(name, tag);
|
||||
additionalNameBindings.add(new JsNameBinding(tag, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void replaceExpressionsWithLocalAliases(@NotNull JsStatement statement) {
|
||||
new JsVisitorWithContextImpl() {
|
||||
@Override
|
||||
public void endVisit(@NotNull JsNameRef x, @NotNull JsContext ctx) {
|
||||
replaceIfNecessary(x, ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endVisit(@NotNull JsArrayAccess x, @NotNull JsContext ctx) {
|
||||
replaceIfNecessary(x, ctx);
|
||||
}
|
||||
|
||||
private void replaceIfNecessary(@NotNull JsExpression expression, @NotNull JsContext ctx) {
|
||||
JsName alias = MetadataProperties.getLocalAlias(expression);
|
||||
if (alias != null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
JsContext<JsNode> context = (JsContext) ctx;
|
||||
context.replaceMe(alias.makeRef());
|
||||
inlinedModuleAliases.add(alias);
|
||||
}
|
||||
}
|
||||
|
||||
}.accept(statement);
|
||||
}
|
||||
|
||||
private static boolean isSuspendWithCurrentContinuation(
|
||||
@Nullable DeclarationDescriptor descriptor,
|
||||
@NotNull LanguageVersionSettings languageVersionSettings
|
||||
) {
|
||||
if (!(descriptor instanceof FunctionDescriptor)) return false;
|
||||
return CommonCoroutineCodegenUtilKt.isBuiltInSuspendCoroutineUninterceptedOrReturn(
|
||||
(FunctionDescriptor) descriptor.getOriginal(), languageVersionSettings
|
||||
);
|
||||
}
|
||||
|
||||
private void inlineSuspendWithCurrentContinuation(@NotNull JsInvocation call, @NotNull JsContext<JsNode> context) {
|
||||
JsExpression lambda = call.getArguments().get(0);
|
||||
JsExpression continuationArg = call.getArguments().get(call.getArguments().size() - 1);
|
||||
|
||||
JsInvocation invocation = new JsInvocation(lambda, continuationArg);
|
||||
MetadataProperties.setSuspend(invocation, true);
|
||||
context.replaceMe(accept(invocation));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private JsInliningContext getInliningContext() {
|
||||
return inliningContexts.peek();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private FunctionContext getFunctionContext() {
|
||||
return getInliningContext().getFunctionContext();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JsFunction getCurrentNamedFunction() {
|
||||
if (namedFunctionsStack.empty()) return null;
|
||||
return namedFunctionsStack.peek();
|
||||
}
|
||||
|
||||
private void reportInlineCycle(@NotNull JsInvocation call, @NotNull JsFunction calledFunction) {
|
||||
MetadataProperties.setInlineStrategy(call, InlineStrategy.NOT_INLINE);
|
||||
Iterator<JsCallInfo> it = inlineCallInfos.descendingIterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
JsCallInfo callInfo = it.next();
|
||||
PsiElement psiElement = MetadataProperties.getPsiElement(callInfo.call);
|
||||
|
||||
CallableDescriptor descriptor = MetadataProperties.getDescriptor(callInfo.call);
|
||||
if (psiElement != null && descriptor != null) {
|
||||
trace.report(Errors.INLINE_CALL_CYCLE.on(psiElement, descriptor));
|
||||
}
|
||||
|
||||
if (callInfo.containingFunction == calledFunction) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasToBeInlined(@NotNull JsInvocation call) {
|
||||
InlineStrategy strategy = MetadataProperties.getInlineStrategy(call);
|
||||
if (strategy == null || !strategy.isInline()) return false;
|
||||
|
||||
return getFunctionContext().hasFunctionDefinition(call);
|
||||
}
|
||||
|
||||
private class JsInliningContext implements InliningContext {
|
||||
private final FunctionContext functionContext;
|
||||
|
||||
@NotNull
|
||||
private final JsContext<JsStatement> statementContextBeforeCurrentFunction;
|
||||
|
||||
JsInliningContext(@NotNull JsContext<JsStatement> statementContextBeforeCurrentFunction) {
|
||||
functionContext = new FunctionContext(functionReader, config) {
|
||||
@Nullable
|
||||
@Override
|
||||
protected FunctionWithWrapper lookUpStaticFunction(@Nullable JsName functionName) {
|
||||
return functions.get(functionName);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected FunctionWithWrapper lookUpStaticFunctionByTag(@NotNull String functionTag) {
|
||||
return accessors.get(functionTag);
|
||||
}
|
||||
};
|
||||
this.statementContextBeforeCurrentFunction = statementContextBeforeCurrentFunction;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public NamingContext newNamingContext() {
|
||||
return new NamingContext(getStatementContext());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JsContext<JsStatement> getStatementContext() {
|
||||
return getLastStatementLevelContext();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public FunctionContext getFunctionContext() {
|
||||
return functionContext;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JsContext<JsStatement> getStatementContextBeforeCurrentFunction() {
|
||||
return statementContextBeforeCurrentFunction;
|
||||
}
|
||||
}
|
||||
|
||||
private static class JsCallInfo {
|
||||
@NotNull
|
||||
public final JsInvocation call;
|
||||
|
||||
@NotNull
|
||||
public final JsFunction containingFunction;
|
||||
|
||||
private JsCallInfo(@NotNull JsInvocation call, @NotNull JsFunction function) {
|
||||
this.call = call;
|
||||
containingFunction = function;
|
||||
}
|
||||
}
|
||||
|
||||
static class JsWrapperKey {
|
||||
final JsContext<JsStatement> context;
|
||||
private final JsFunction function;
|
||||
|
||||
public JsWrapperKey(@NotNull JsContext<JsStatement> context, @NotNull JsFunction function) {
|
||||
this.context = context;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
JsWrapperKey key = (JsWrapperKey) o;
|
||||
return Objects.equals(context, key.context) && Objects.equals(function, key.function);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(context, function);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,653 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.inline
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.*
|
||||
import org.jetbrains.kotlin.config.*
|
||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
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.*
|
||||
import org.jetbrains.kotlin.js.config.JsConfig
|
||||
import org.jetbrains.kotlin.js.inline.clean.*
|
||||
import org.jetbrains.kotlin.js.inline.context.FunctionContext
|
||||
import org.jetbrains.kotlin.js.inline.context.InliningContext
|
||||
import org.jetbrains.kotlin.js.inline.context.NamingContext
|
||||
import org.jetbrains.kotlin.js.inline.util.*
|
||||
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineStrategy
|
||||
|
||||
import java.util.*
|
||||
|
||||
import org.jetbrains.kotlin.js.inline.util.getImportTag
|
||||
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
||||
import org.jetbrains.kotlin.js.translate.declaration.transformSpecialFunctionsToCoroutineMetadata
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.flattenStatement
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.pureFqn
|
||||
|
||||
class JsInliner private constructor(
|
||||
private val config: JsConfig,
|
||||
private val functions: Map<JsName, FunctionWithWrapper>,
|
||||
private val accessors: Map<String, FunctionWithWrapper>,
|
||||
private val inverseNameBindings: Map<JsName, String>,
|
||||
private val functionReader: FunctionReader,
|
||||
private val trace: DiagnosticSink
|
||||
) : JsVisitorWithContextImpl() {
|
||||
private val namedFunctionsSet: MutableSet<JsFunction> = functions.values.mapTo(IdentitySet()) { it.function }
|
||||
private val inliningContexts = Stack<JsInliningContext>()
|
||||
private val processedFunctions = IdentitySet<JsFunction>()
|
||||
private val inProcessFunctions = IdentitySet<JsFunction>()
|
||||
private var existingImports: MutableMap<String, JsName> = HashMap()
|
||||
private var statementContextForInline: JsContext<JsStatement>? = null
|
||||
|
||||
private val functionsByWrapperNodes = HashMap<JsBlock, FunctionWithWrapper>()
|
||||
private val functionsByFunctionNodes = HashMap<JsFunction, FunctionWithWrapper>()
|
||||
init {
|
||||
(functions.values.asSequence() + accessors.values.asSequence()).forEach { f ->
|
||||
functionsByFunctionNodes[f.function] = f
|
||||
if (f.wrapperBody != null) {
|
||||
functionsByWrapperNodes[f.wrapperBody] = f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// these are needed for error reporting, when inliner detects cycle
|
||||
private val namedFunctionsStack = Stack<JsFunction>()
|
||||
private val inlineCallInfos = LinkedList<JsCallInfo>()
|
||||
private val canBeExtractedByInliner: (JsNode) -> Boolean = { node -> node is JsInvocation && hasToBeInlined(node) }
|
||||
private var inlineFunctionDepth: Int = 0
|
||||
|
||||
private val replacementsInducedByWrappers = HashMap<JsWrapperKey, Map<JsName, JsNameRef>>()
|
||||
|
||||
private var existingNameBindings: MutableMap<JsName, String> = HashMap()
|
||||
|
||||
private val additionalNameBindings = ArrayList<JsNameBinding>()
|
||||
|
||||
private val inlinedModuleAliases = HashSet<JsName>()
|
||||
|
||||
private val inliningContext: JsInliningContext
|
||||
get() = inliningContexts.peek()
|
||||
|
||||
private val functionContext: FunctionContext
|
||||
get() = inliningContext.functionContext
|
||||
|
||||
private val currentNamedFunction: JsFunction?
|
||||
get() = if (namedFunctionsStack.empty()) null else namedFunctionsStack.peek()
|
||||
|
||||
private fun addInlinedModules(fragment: JsProgramFragment, moduleMap: Map<JsName, JsImportedModule>) {
|
||||
val localMap = buildModuleMap(listOf(fragment)).keys
|
||||
for (inlinedModuleName in inlinedModuleAliases) {
|
||||
if (!localMap.contains(inlinedModuleName)) {
|
||||
fragment.importedModules.add(moduleMap[inlinedModuleName]!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processImportStatement(statement: JsStatement) {
|
||||
if (statement is JsVars) {
|
||||
val tag = getImportTag(statement)
|
||||
if (tag != null) {
|
||||
existingImports[tag] = statement.vars[0].name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(function: JsFunction, context: JsContext<*>): Boolean {
|
||||
val functionWithWrapper = functionsByFunctionNodes[function]
|
||||
if (functionWithWrapper != null) {
|
||||
visit(functionWithWrapper)
|
||||
return false
|
||||
} else {
|
||||
if (statementContextForInline == null) {
|
||||
statementContextForInline = lastStatementLevelContext
|
||||
startFunction(function)
|
||||
val result = super.visit(function, context)
|
||||
statementContextForInline = null
|
||||
return result
|
||||
} else {
|
||||
startFunction(function)
|
||||
return super.visit(function, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun endVisit(function: JsFunction, context: JsContext<*>) {
|
||||
super.endVisit(function, context)
|
||||
if (!functionsByFunctionNodes.containsKey(function)) {
|
||||
endFunction(function)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startFunction(function: JsFunction) {
|
||||
inliningContexts.push(JsInliningContext(statementContextForInline!!))
|
||||
|
||||
assert(!inProcessFunctions.contains(function)) { "Inliner has revisited function" }
|
||||
inProcessFunctions.add(function)
|
||||
|
||||
if (namedFunctionsSet.contains(function)) {
|
||||
namedFunctionsStack.push(function)
|
||||
}
|
||||
}
|
||||
|
||||
private fun endFunction(function: JsFunction) {
|
||||
refreshLabelNames(function.body, function.scope)
|
||||
|
||||
removeUnusedLocalFunctionDeclarations(function)
|
||||
processedFunctions.add(function)
|
||||
|
||||
FunctionPostProcessor(function).apply()
|
||||
|
||||
assert(inProcessFunctions.contains(function))
|
||||
inProcessFunctions.remove(function)
|
||||
|
||||
inliningContexts.pop()
|
||||
|
||||
if (!namedFunctionsStack.empty() && namedFunctionsStack.peek() == function) {
|
||||
namedFunctionsStack.pop()
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(x: JsBlock, ctx: JsContext<*>): Boolean {
|
||||
val functionWithWrapper = functionsByWrapperNodes[x]
|
||||
if (functionWithWrapper != null) {
|
||||
visit(functionWithWrapper)
|
||||
return false
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
private fun visit(functionWithWrapper: FunctionWithWrapper) {
|
||||
val oldContextForInline = statementContextForInline
|
||||
val oldExistingImports = existingImports
|
||||
val oldInlineFunctionDepth = inlineFunctionDepth
|
||||
|
||||
val innerContext = ListContext<JsStatement>()
|
||||
|
||||
val wrapperBody = functionWithWrapper.wrapperBody
|
||||
var statements: MutableList<JsStatement>? = null
|
||||
if (wrapperBody != null) {
|
||||
existingImports = HashMap()
|
||||
statementContexts.push(innerContext)
|
||||
statementContextForInline = innerContext
|
||||
inlineFunctionDepth++
|
||||
|
||||
for (statement in wrapperBody.statements) {
|
||||
processImportStatement(statement)
|
||||
}
|
||||
statements = wrapperBody.statements
|
||||
if (!statements.isEmpty() && statements[statements.size - 1] is JsReturn) {
|
||||
statements = statements.subList(0, statements.size - 1)
|
||||
}
|
||||
|
||||
innerContext.traverse(statements)
|
||||
statementContexts.pop()
|
||||
} else {
|
||||
if (statementContextForInline == null) statementContextForInline = lastStatementLevelContext
|
||||
}
|
||||
|
||||
startFunction(functionWithWrapper.function)
|
||||
|
||||
val block = JsBlock(functionWithWrapper.function.body)
|
||||
innerContext.traverse(block.statements)
|
||||
functionWithWrapper.function.body.traverse(this, innerContext)
|
||||
|
||||
endFunction(functionWithWrapper.function)
|
||||
|
||||
statements?.addAll(block.statements.subList(0, block.statements.size - 1))
|
||||
|
||||
statementContextForInline = oldContextForInline
|
||||
existingImports = oldExistingImports
|
||||
inlineFunctionDepth = oldInlineFunctionDepth
|
||||
}
|
||||
|
||||
override fun visit(call: JsInvocation, context: JsContext<*>): Boolean {
|
||||
if (!hasToBeInlined(call)) return true
|
||||
|
||||
val containingFunction = currentNamedFunction
|
||||
|
||||
if (containingFunction != null) {
|
||||
inlineCallInfos.add(JsCallInfo(call, containingFunction))
|
||||
}
|
||||
|
||||
val definition = functionContext.getFunctionDefinition(call)
|
||||
|
||||
if (inProcessFunctions.contains(definition.function)) {
|
||||
reportInlineCycle(call, definition.function)
|
||||
} else if (!processedFunctions.contains(definition.function)) {
|
||||
for (i in 0 until call.arguments.size) {
|
||||
val argument = call.arguments[i]
|
||||
call.arguments[i] = accept(argument)
|
||||
}
|
||||
visit(definition)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun endVisit(x: JsInvocation, ctx: JsContext<JsNode>) {
|
||||
if (hasToBeInlined(x)) {
|
||||
inline(x, ctx)
|
||||
}
|
||||
|
||||
var lastCallInfo: JsCallInfo? = null
|
||||
|
||||
if (!inlineCallInfos.isEmpty()) {
|
||||
lastCallInfo = inlineCallInfos.last
|
||||
}
|
||||
|
||||
if (lastCallInfo != null && lastCallInfo.call == x) {
|
||||
inlineCallInfos.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
override fun endVisit(x: JsExpressionStatement, ctx: JsContext<*>) {
|
||||
val e = x.expression
|
||||
if (e is JsBinaryOperation) {
|
||||
if (e.operator == JsBinaryOperator.ASG) {
|
||||
e.arg2?.let { argument2 ->
|
||||
val splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(argument2)
|
||||
if (splitSuspendInlineFunction != null) {
|
||||
e.arg2 = splitSuspendInlineFunction
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.endVisit(x, ctx)
|
||||
}
|
||||
|
||||
override fun endVisit(x: JsVars.JsVar, ctx: JsContext<*>) {
|
||||
val initExpression = x.initExpression ?: return
|
||||
|
||||
val splitSuspendInlineFunction = splitExportedSuspendInlineFunctionDeclarations(initExpression)
|
||||
if (splitSuspendInlineFunction != null) {
|
||||
x.initExpression = splitSuspendInlineFunction
|
||||
}
|
||||
}
|
||||
|
||||
private fun splitExportedSuspendInlineFunctionDeclarations(expression: JsExpression): JsFunction? {
|
||||
val inlineMetadata = InlineMetadata.decompose(expression)
|
||||
if (inlineMetadata != null) {
|
||||
val (originalFunction, wrapperBody) = inlineMetadata.function
|
||||
if (originalFunction.coroutineMetadata != null) {
|
||||
val statementContext = lastStatementLevelContext
|
||||
|
||||
// This function will be exported to JS
|
||||
val function = originalFunction.deepCopy()
|
||||
|
||||
// Original function should be not be transformed into a state machine
|
||||
originalFunction.setName(null)
|
||||
originalFunction.coroutineMetadata = null
|
||||
originalFunction.isInlineableCoroutineBody = true
|
||||
if (wrapperBody != null) {
|
||||
// Extract local declarations
|
||||
applyWrapper(wrapperBody, function, originalFunction, JsInliningContext(statementContext))
|
||||
}
|
||||
|
||||
// Keep the `defineInlineFunction` for the inliner to find
|
||||
statementContext.addNext(expression.makeStmt())
|
||||
|
||||
// Return the function body to be used without inlining.
|
||||
return function
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun doAcceptStatementList(statements: MutableList<JsStatement>) {
|
||||
// at top level of js ast, contexts stack can be empty,
|
||||
// but there is no inline calls anyway
|
||||
if (!inliningContexts.isEmpty()) {
|
||||
var i = 0
|
||||
|
||||
while (i < statements.size) {
|
||||
val additionalStatements = ExpressionDecomposer.preserveEvaluationOrder(statements[i], canBeExtractedByInliner)
|
||||
statements.addAll(i, additionalStatements)
|
||||
i += additionalStatements.size + 1
|
||||
}
|
||||
}
|
||||
|
||||
super.doAcceptStatementList(statements)
|
||||
}
|
||||
|
||||
private fun inline(call: JsInvocation, context: JsContext<JsNode>) {
|
||||
val callDescriptor = call.descriptor
|
||||
if (isSuspendWithCurrentContinuation(
|
||||
callDescriptor,
|
||||
config.configuration.languageVersionSettings
|
||||
)
|
||||
) {
|
||||
inlineSuspendWithCurrentContinuation(call, context)
|
||||
return
|
||||
}
|
||||
|
||||
val inliningContext = inliningContext
|
||||
var functionWithWrapper = inliningContext.functionContext.getFunctionDefinition(call)
|
||||
|
||||
// Since we could get functionWithWrapper as a simple function directly from staticRef (which always points on implementation)
|
||||
// we should check if we have a known wrapper for it
|
||||
functionsByFunctionNodes[functionWithWrapper.function]?.let {
|
||||
functionWithWrapper = it
|
||||
}
|
||||
|
||||
val function = functionWithWrapper.function.deepCopy()
|
||||
function.body = transformSpecialFunctionsToCoroutineMetadata(function.body)
|
||||
if (functionWithWrapper.wrapperBody != null) {
|
||||
applyWrapper(functionWithWrapper.wrapperBody!!, function, functionWithWrapper.function, inliningContext)
|
||||
}
|
||||
val inlineableResult = FunctionInlineMutator.getInlineableCallReplacement(call, function, inliningContext)
|
||||
|
||||
val inlineableBody = inlineableResult.inlineableBody
|
||||
var resultExpression = inlineableResult.resultExpression
|
||||
val statementContext = inliningContext.statementContext
|
||||
// body of inline function can contain call to lambdas that need to be inlined
|
||||
val inlineableBodyWithLambdasInlined = accept(inlineableBody)
|
||||
assert(inlineableBody === inlineableBodyWithLambdasInlined)
|
||||
|
||||
// Support non-local return from secondary constructor
|
||||
// Returns from secondary constructors should return `$this` object.
|
||||
val currentFunction = currentNamedFunction
|
||||
if (currentFunction != null) {
|
||||
val returnVariable = currentFunction.forcedReturnVariable
|
||||
if (returnVariable != null) {
|
||||
inlineableBody.accept(object : RecursiveJsVisitor() {
|
||||
override fun visitReturn(x: JsReturn) {
|
||||
x.expression = returnVariable.makeRef()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
statementContext.addPrevious(flattenStatement(inlineableBody))
|
||||
|
||||
/*
|
||||
* Assumes, that resultExpression == null, when result is not needed.
|
||||
* @see FunctionInlineMutator.isResultNeeded()
|
||||
*/
|
||||
if (resultExpression == null) {
|
||||
statementContext.removeMe()
|
||||
return
|
||||
}
|
||||
|
||||
resultExpression = accept(resultExpression)
|
||||
resultExpression.synthetic = true
|
||||
context.replaceMe(resultExpression)
|
||||
}
|
||||
|
||||
private fun applyWrapper(
|
||||
wrapper: JsBlock, function: JsFunction, originalFunction: JsFunction,
|
||||
inliningContext: InliningContext
|
||||
) {
|
||||
val key = JsWrapperKey(inliningContext.statementContextBeforeCurrentFunction, originalFunction)
|
||||
|
||||
// Apparently we should avoid this trick when we implement fair support for crossinline
|
||||
val replacements = replacementsInducedByWrappers.computeIfAbsent(key) { k ->
|
||||
val ctx = k.context
|
||||
|
||||
val newReplacements = HashMap<JsName, JsNameRef>()
|
||||
|
||||
val copiedStatements = ArrayList<JsStatement>()
|
||||
wrapper.statements.asSequence()
|
||||
.filterNot { it is JsReturn }
|
||||
.map { it.deepCopy() }
|
||||
.forEach { statement ->
|
||||
if (inlineFunctionDepth == 0) {
|
||||
replaceExpressionsWithLocalAliases(statement)
|
||||
}
|
||||
|
||||
if (statement is JsVars) {
|
||||
val tag = getImportTag(statement)
|
||||
if (tag != null) {
|
||||
val name = statement.vars[0].name
|
||||
var existingName: JsName? = if (inlineFunctionDepth == 0) name.localAlias else null
|
||||
if (existingName == null) {
|
||||
existingName = existingImports.computeIfAbsent(tag) {
|
||||
copiedStatements.add(statement)
|
||||
val alias = JsScope.declareTemporaryName(name.ident)
|
||||
alias.copyMetadataFrom(name)
|
||||
newReplacements[name] = pureFqn(alias, null)
|
||||
alias
|
||||
}
|
||||
}
|
||||
|
||||
if (name !== existingName) {
|
||||
val replacement = pureFqn(existingName, null)
|
||||
newReplacements[name] = replacement
|
||||
}
|
||||
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
|
||||
copiedStatements.add(statement)
|
||||
}
|
||||
|
||||
val definedNames = copiedStatements.asSequence()
|
||||
.flatMap { node -> collectDefinedNamesInAllScopes(node).asSequence() }
|
||||
.filter { name -> !newReplacements.containsKey(name) }
|
||||
.toSet()
|
||||
for (name in definedNames) {
|
||||
val alias = JsScope.declareTemporaryName(name.ident)
|
||||
alias.copyMetadataFrom(name)
|
||||
val replacement = pureFqn(alias, null)
|
||||
newReplacements[name] = replacement
|
||||
}
|
||||
|
||||
for (statement in copiedStatements) {
|
||||
ctx.addPrevious(accept(replaceNames(statement, newReplacements)))
|
||||
}
|
||||
|
||||
for ((key, value) in collectNamedFunctions(JsBlock(copiedStatements))) {
|
||||
if (key.staticRef is JsFunction) {
|
||||
key.staticRef = value
|
||||
}
|
||||
}
|
||||
|
||||
newReplacements
|
||||
}
|
||||
|
||||
replaceNames(function, replacements)
|
||||
|
||||
// Copy nameBinding's for inlined localAlias'es
|
||||
for (nameRef in replacements.values) {
|
||||
val name = nameRef.name
|
||||
if (name != null && !existingNameBindings.containsKey(name)) {
|
||||
val tag = inverseNameBindings[name]
|
||||
if (tag != null) {
|
||||
existingNameBindings[name] = tag
|
||||
additionalNameBindings.add(JsNameBinding(tag, name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun replaceExpressionsWithLocalAliases(statement: JsStatement) {
|
||||
object : JsVisitorWithContextImpl() {
|
||||
override fun endVisit(x: JsNameRef, ctx: JsContext<JsNode>) {
|
||||
replaceIfNecessary(x, ctx)
|
||||
}
|
||||
|
||||
override fun endVisit(x: JsArrayAccess, ctx: JsContext<JsNode>) {
|
||||
replaceIfNecessary(x, ctx)
|
||||
}
|
||||
|
||||
private fun replaceIfNecessary(expression: JsExpression, ctx: JsContext<JsNode>) {
|
||||
val alias = expression.localAlias
|
||||
if (alias != null) {
|
||||
ctx.replaceMe(alias.makeRef())
|
||||
inlinedModuleAliases.add(alias)
|
||||
}
|
||||
}
|
||||
|
||||
}.accept(statement)
|
||||
}
|
||||
|
||||
private fun inlineSuspendWithCurrentContinuation(call: JsInvocation, context: JsContext<JsNode>) {
|
||||
val lambda = call.arguments[0]
|
||||
val continuationArg = call.arguments[call.arguments.size - 1]
|
||||
|
||||
val invocation = JsInvocation(lambda, continuationArg)
|
||||
invocation.isSuspend = true
|
||||
context.replaceMe(accept(invocation))
|
||||
}
|
||||
|
||||
private fun reportInlineCycle(call: JsInvocation, calledFunction: JsFunction) {
|
||||
call.inlineStrategy = InlineStrategy.NOT_INLINE
|
||||
val it = inlineCallInfos.descendingIterator()
|
||||
|
||||
while (it.hasNext()) {
|
||||
val callInfo = it.next()
|
||||
val psiElement = callInfo.call.psiElement
|
||||
|
||||
val descriptor = callInfo.call.descriptor
|
||||
if (psiElement != null && descriptor != null) {
|
||||
trace.report(Errors.INLINE_CALL_CYCLE.on(psiElement, descriptor))
|
||||
}
|
||||
|
||||
if (callInfo.containingFunction == calledFunction) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasToBeInlined(call: JsInvocation): Boolean {
|
||||
val strategy = call.inlineStrategy
|
||||
return if (strategy == null || !strategy.isInline) false else functionContext.hasFunctionDefinition(call)
|
||||
|
||||
}
|
||||
|
||||
private inner class JsInliningContext internal constructor(override val statementContextBeforeCurrentFunction: JsContext<JsStatement>) :
|
||||
InliningContext {
|
||||
override val functionContext: FunctionContext
|
||||
|
||||
override val statementContext: JsContext<JsStatement>
|
||||
get() = lastStatementLevelContext
|
||||
|
||||
init {
|
||||
functionContext = object : FunctionContext(functionReader, config) {
|
||||
override fun lookUpStaticFunction(functionName: JsName?): FunctionWithWrapper? {
|
||||
return functions[functionName]
|
||||
}
|
||||
|
||||
override fun lookUpStaticFunctionByTag(functionTag: String): FunctionWithWrapper? {
|
||||
return accessors[functionTag]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun newNamingContext(): NamingContext {
|
||||
return NamingContext(statementContext)
|
||||
}
|
||||
}
|
||||
|
||||
private class JsCallInfo(val call: JsInvocation, val containingFunction: JsFunction)
|
||||
|
||||
internal class JsWrapperKey(val context: JsContext<JsStatement>, private val function: JsFunction) {
|
||||
|
||||
override fun equals(o: Any?): Boolean {
|
||||
if (this === o) return true
|
||||
if (o == null || javaClass != o.javaClass) return false
|
||||
val key = o as JsWrapperKey?
|
||||
return context == key!!.context && function == key.function
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Objects.hash(context, function)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun process(
|
||||
reporter: JsConfig.Reporter,
|
||||
config: JsConfig,
|
||||
trace: DiagnosticSink,
|
||||
currentModuleName: JsName,
|
||||
fragments: List<JsProgramFragment>,
|
||||
fragmentsToProcess: List<JsProgramFragment>,
|
||||
importStatements: List<JsStatement>
|
||||
) {
|
||||
val functions = collectNamedFunctionsAndWrappers(fragments)
|
||||
val accessors = collectAccessors(fragments)
|
||||
val inverseNameBindings = collectNameBindings(fragments)
|
||||
|
||||
val accessorInvocationTransformer = DummyAccessorInvocationTransformer()
|
||||
for (fragment in fragmentsToProcess) {
|
||||
accessorInvocationTransformer.accept<JsGlobalBlock>(fragment.declarationBlock)
|
||||
accessorInvocationTransformer.accept<JsGlobalBlock>(fragment.initializerBlock)
|
||||
}
|
||||
val functionReader = FunctionReader(reporter, config, currentModuleName, fragments)
|
||||
val inliner = JsInliner(config, functions, accessors, inverseNameBindings, functionReader, trace)
|
||||
|
||||
for (statement in importStatements) {
|
||||
inliner.processImportStatement(statement)
|
||||
}
|
||||
|
||||
val moduleMap = fillModuleMap(buildModuleMap(fragments), fragmentsToProcess)
|
||||
|
||||
for (fragment in fragmentsToProcess) {
|
||||
inliner.existingImports.clear()
|
||||
inliner.additionalNameBindings.clear()
|
||||
inliner.inlinedModuleAliases.clear()
|
||||
inliner.existingNameBindings = collectNameBindings(listOf(fragment))
|
||||
|
||||
inliner.acceptStatement<JsGlobalBlock>(fragment.declarationBlock)
|
||||
// Mostly for the sake of post-processor
|
||||
// TODO are inline function marked with @Test possible?
|
||||
if (fragment.tests != null) {
|
||||
inliner.acceptStatement(fragment.tests)
|
||||
}
|
||||
|
||||
// There can be inlined function in top-level initializers, we need to optimize them as well
|
||||
val fakeInitFunction = JsFunction(JsDynamicScope, fragment.initializerBlock, "")
|
||||
val initWrapper = JsGlobalBlock()
|
||||
initWrapper.statements.add(JsExpressionStatement(fakeInitFunction))
|
||||
inliner.accept(initWrapper)
|
||||
initWrapper.statements.removeAt(initWrapper.statements.size - 1)
|
||||
|
||||
fragment.initializerBlock.getStatements().addAll(0, initWrapper.statements)
|
||||
fragment.nameBindings.addAll(inliner.additionalNameBindings)
|
||||
inliner.addInlinedModules(fragment, moduleMap)
|
||||
}
|
||||
|
||||
for (fragment in fragmentsToProcess) {
|
||||
val block = JsBlock(fragment.declarationBlock, fragment.initializerBlock, fragment.exportBlock)
|
||||
removeUnusedImports(block)
|
||||
simplifyWrappedFunctions(block)
|
||||
removeUnusedFunctionDefinitions(block, collectNamedFunctions(block))
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildModuleMap(fragments: List<JsProgramFragment>): MutableMap<JsName, JsImportedModule> {
|
||||
return fillModuleMap(HashMap(), fragments)
|
||||
}
|
||||
|
||||
private fun fillModuleMap(
|
||||
map: MutableMap<JsName, JsImportedModule>,
|
||||
fragments: List<JsProgramFragment>
|
||||
): MutableMap<JsName, JsImportedModule> {
|
||||
for (fragment in fragments) {
|
||||
for (module in fragment.importedModules) {
|
||||
map[module.internalName] = module
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
private fun isSuspendWithCurrentContinuation(
|
||||
descriptor: DeclarationDescriptor?,
|
||||
languageVersionSettings: LanguageVersionSettings
|
||||
): Boolean {
|
||||
return (descriptor as? FunctionDescriptor)?.original?.isBuiltInSuspendCoroutineUninterceptedOrReturn(
|
||||
languageVersionSettings
|
||||
) ?: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +230,7 @@ fun collectAccessors(fragments: List<JsProgramFragment>): Map<String, FunctionWi
|
||||
return result
|
||||
}
|
||||
|
||||
fun collectNameBindings(fragments: List<JsProgramFragment>): Map<JsName, String> {
|
||||
fun collectNameBindings(fragments: List<JsProgramFragment>): MutableMap<JsName, String> {
|
||||
val result = mutableMapOf<JsName, String>()
|
||||
for (fragment in fragments) {
|
||||
for (binding in fragment.nameBindings) {
|
||||
|
||||
Reference in New Issue
Block a user