Refactor JS translator to generate code for new inliner

This commit is contained in:
Alexey Andreev
2017-06-27 18:55:34 +03:00
parent 29857e48c8
commit 7c421b0b83
37 changed files with 771 additions and 211 deletions
@@ -33,7 +33,7 @@ import java.util.*;
*/
public class JsVisitorWithContextImpl extends JsVisitorWithContext {
private final Stack<JsContext<JsStatement>> statementContexts = new Stack<JsContext<JsStatement>>();
protected final Stack<JsContext<JsStatement>> statementContexts = new Stack<JsContext<JsStatement>>();
public class ListContext<T extends JsNode> extends JsContext<T> {
private List<T> nodes;
@@ -76,7 +76,7 @@ public class JsVisitorWithContextImpl extends JsVisitorWithContext {
return null;
}
protected void traverse(List<T> nodes) {
public void traverse(List<T> nodes) {
assert previous.isEmpty(): "addPrevious() was called before traverse()";
assert next.isEmpty(): "addNext() was called before traverse()";
this.nodes = nodes;
@@ -102,6 +102,9 @@ public class JsVisitorWithContextImpl extends JsVisitorWithContext {
index += next.size();
}
}
previous.clear();
next.clear();
}
}
@@ -29,6 +29,8 @@ var JsName.staticRef: JsNode? by MetadataProperty(default = null)
var JsName.descriptor: DeclarationDescriptor? by MetadataProperty(default = null)
var JsName.localAlias: JsName? by MetadataProperty(default = null)
// TODO: move this to module 'js.inliner' and change dependency on 'frontend' to dependency on 'descriptors'
var JsInvocation.inlineStrategy: InlineStrategy? by MetadataProperty(default = null)
@@ -21,7 +21,7 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
class CoroutineTransformer() : JsVisitorWithContextImpl() {
class CoroutineTransformer : JsVisitorWithContextImpl() {
private val additionalStatementsByNode = mutableMapOf<JsNode, List<JsStatement>>()
override fun endVisit(x: JsExpressionStatement, ctx: JsContext<in JsStatement>) {
@@ -41,7 +41,7 @@ class CoroutineTransformer() : JsVisitorWithContextImpl() {
val assignment = JsAstUtils.decomposeAssignment(expression)
if (assignment != null) {
val (lhs, rhs) = assignment
val function = rhs as? JsFunction ?: InlineMetadata.decompose(rhs)?.function
val function = rhs as? JsFunction ?: InlineMetadata.decompose(rhs)?.function?.function
if (function?.coroutineMetadata != null) {
val name = ((lhs as? JsNameRef)?.name ?: function.name)?.ident
additionalStatementsByNode[x] = CoroutineFunctionTransformer(function, name).transform()
@@ -60,7 +60,7 @@ class CoroutineTransformer() : JsVisitorWithContextImpl() {
override fun visit(x: JsVars.JsVar, ctx: JsContext<*>): Boolean {
val initExpression = x.initExpression
if (initExpression != null) {
val function = initExpression as? JsFunction ?: InlineMetadata.decompose(initExpression)?.function
val function = initExpression as? JsFunction ?: InlineMetadata.decompose(initExpression)?.function?.function
if (function?.coroutineMetadata != null) {
val name = x.name.ident
additionalStatementsByNode[x] = CoroutineFunctionTransformer(function, name).transform()
@@ -26,22 +26,21 @@ import org.jetbrains.kotlin.js.inline.util.*
import org.jetbrains.kotlin.js.inline.util.rewriters.ReturnReplacingVisitor
import org.jetbrains.kotlin.js.translate.context.Namer
class FunctionInlineMutator
private constructor(
class FunctionInlineMutator(
private val call: JsInvocation,
private val inliningContext: InliningContext
private val inliningContext: InliningContext,
function: JsFunction
) {
private val invokedFunction: JsFunction
private val namingContext = inliningContext.newNamingContext()
private val body: JsBlock
private var resultExpr: JsExpression? = null
val namingContext = inliningContext.newNamingContext()
val body: JsBlock
var resultExpr: JsExpression? = null
private var resultName: JsName? = null
private var breakLabel: JsLabel? = null
var breakLabel: JsLabel? = null
private val currentStatement = inliningContext.statementContext.currentNode
init {
val functionContext = inliningContext.functionContext
invokedFunction = uncoverClosure(functionContext.getFunctionDefinition(call).deepCopy())
invokedFunction = uncoverClosure(function.deepCopy())
body = invokedFunction.body
}
@@ -166,9 +165,11 @@ private constructor(
}
companion object {
@JvmStatic fun getInlineableCallReplacement(call: JsInvocation, inliningContext: InliningContext): InlineableResult {
val mutator = FunctionInlineMutator(call, inliningContext)
@JvmStatic fun getInlineableCallReplacement(
call: JsInvocation, function: JsFunction,
inliningContext: InliningContext
): InlineableResult {
val mutator = FunctionInlineMutator(call, inliningContext, function)
mutator.process()
var inlineableBody: JsStatement = mutator.body
@@ -181,6 +182,7 @@ private constructor(
return InlineableResult(inlineableBody, mutator.resultExpr)
}
@JvmStatic
private fun getThisReplacement(call: JsInvocation): JsExpression? {
if (isCallInvocation(call)) {
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.inlineStrategy
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper
import org.jetbrains.kotlin.js.inline.util.IdentitySet
import org.jetbrains.kotlin.js.inline.util.isCallInvocation
import org.jetbrains.kotlin.js.parser.OffsetToSourceMapping
@@ -154,8 +155,8 @@ class FunctionReader(
override fun toString() = text.substring(offset)
}
private val functionCache = object : SLRUCache<CallableDescriptor, JsFunction>(50, 50) {
override fun createValue(descriptor: CallableDescriptor): JsFunction =
private val functionCache = object : SLRUCache<CallableDescriptor, FunctionWithWrapper>(50, 50) {
override fun createValue(descriptor: CallableDescriptor): FunctionWithWrapper =
readFunction(descriptor).sure { "Could not read function: $descriptor" }
}
@@ -165,9 +166,9 @@ class FunctionReader(
return currentModuleName != moduleName && moduleName in moduleNameToInfo.keys()
}
operator fun get(descriptor: CallableDescriptor): JsFunction = functionCache.get(descriptor)
operator fun get(descriptor: CallableDescriptor): FunctionWithWrapper = functionCache.get(descriptor)
private fun readFunction(descriptor: CallableDescriptor): JsFunction? {
private fun readFunction(descriptor: CallableDescriptor): FunctionWithWrapper? {
if (descriptor !in this) return null
val moduleName = getModuleName(descriptor)
@@ -180,7 +181,7 @@ class FunctionReader(
return null
}
private fun readFunctionFromSource(descriptor: CallableDescriptor, info: ModuleInfo): JsFunction? {
private fun readFunctionFromSource(descriptor: CallableDescriptor, info: ModuleInfo): FunctionWithWrapper? {
val source = info.fileContent
val tag = Namer.getFunctionTag(descriptor, config)
val index = source.indexOf(tag)
@@ -206,7 +207,7 @@ class FunctionReader(
info.kotlinVariable to Namer.kotlinObject())
replaceExternalNames(function, replacements)
function.markInlineArguments(descriptor)
return function
return FunctionWithWrapper(function, null)
}
}
@@ -261,4 +262,4 @@ private fun replaceExternalNames(function: JsFunction, externalReplacements: Map
}
visitor.accept(function)
}
}
@@ -26,35 +26,38 @@ 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.JsToStringGenerationVisitor;
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.FunctionPostProcessor;
import org.jetbrains.kotlin.js.inline.clean.RemoveUnusedFunctionDefinitionsKt;
import org.jetbrains.kotlin.js.inline.clean.RemoveUnusedLocalFunctionDeclarationsKt;
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.CollectUtilsKt;
import org.jetbrains.kotlin.js.inline.util.CollectionUtilsKt;
import org.jetbrains.kotlin.js.inline.util.NamingUtilsKt;
import org.jetbrains.kotlin.js.inline.util.*;
import org.jetbrains.kotlin.resolve.inline.InlineStrategy;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.jetbrains.kotlin.js.inline.FunctionInlineMutator.getInlineableCallReplacement;
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, JsFunction> functions;
private final Map<String, JsFunction> accessors;
private final Map<JsName, FunctionWithWrapper> functions;
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<>();
@@ -68,10 +71,12 @@ public class JsInliner extends JsVisitorWithContextImpl {
@NotNull DiagnosticSink trace,
@NotNull JsName currentModuleName,
@NotNull List<JsProgramFragment> fragments,
@NotNull List<JsProgramFragment> fragmentsToProcess
@NotNull List<JsProgramFragment> fragmentsToProcess,
@NotNull List<JsStatement> importStatements
) {
Map<JsName, JsFunction> functions = CollectUtilsKt.collectNamedFunctions(fragments);
Map<String, JsFunction> accessors = CollectUtilsKt.collectAccessors(fragments);
Map<JsName, FunctionWithWrapper> functions = CollectUtilsKt.collectNamedFunctionsAndWrappers(fragments);
Map<String, FunctionWithWrapper> accessors = CollectUtilsKt.collectAccessors(fragments);
DummyAccessorInvocationTransformer accessorInvocationTransformer = new DummyAccessorInvocationTransformer();
for (JsProgramFragment fragment : fragmentsToProcess) {
accessorInvocationTransformer.accept(fragment.getDeclarationBlock());
@@ -79,27 +84,37 @@ public class JsInliner extends JsVisitorWithContextImpl {
}
FunctionReader functionReader = new FunctionReader(reporter, config, currentModuleName, fragments);
JsInliner inliner = new JsInliner(config, functions, accessors, functionReader, trace);
List<JsNode> nodesToPostProcess = new ArrayList<>();
for (JsStatement statement : importStatements) {
inliner.processImportStatement(statement);
}
for (JsProgramFragment fragment : fragmentsToProcess) {
inliner.inliningContexts.push(inliner.new JsInliningContext());
inliner.accept(fragment.getDeclarationBlock());
inliner.inliningContexts.push(inliner.new JsInliningContext(inliner.new ListContext<JsStatement>()));
inliner.acceptStatement(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);
JsGlobalBlock initWrapper = new JsGlobalBlock();
initWrapper.getStatements().add(new JsExpressionStatement(fakeInitFunction));
inliner.accept(initWrapper);
initWrapper.getStatements().remove(initWrapper.getStatements().size() - 1);
inliner.inliningContexts.pop();
JsBlock block = new JsBlock(fragment.getDeclarationBlock(), fragment.getInitializerBlock(), fragment.getExportBlock());
nodesToPostProcess.add(block);
fragment.getInitializerBlock().getStatements().addAll(0, initWrapper.getStatements());
}
RemoveUnusedFunctionDefinitionsKt.removeUnusedFunctionDefinitions(nodesToPostProcess, functions);
for (JsProgramFragment fragment : fragmentsToProcess) {
JsBlock block = new JsBlock(fragment.getDeclarationBlock(), fragment.getInitializerBlock(), fragment.getExportBlock());
RemoveUnusedImportsKt.removeUnusedImports(block);
RemoveUnusedFunctionDefinitionsKt.removeUnusedFunctionDefinitions(block, CollectUtilsKt.collectNamedFunctions(block));
}
}
private JsInliner(
@NotNull JsConfig config,
@NotNull Map<JsName, JsFunction> functions,
@NotNull Map<String, JsFunction> accessors,
@NotNull Map<JsName, FunctionWithWrapper> functions,
@NotNull Map<String, FunctionWithWrapper> accessors,
@NotNull FunctionReader functionReader,
@NotNull DiagnosticSink trace
) {
@@ -108,24 +123,74 @@ public class JsInliner extends JsVisitorWithContextImpl {
this.accessors = accessors;
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());
}
}
}
@Nullable
private static String getImportTag(JsVars jsVars) {
if (jsVars.getVars().size() == 1) {
JsVars.JsVar jsVar = jsVars.getVars().get(0);
if (jsVar.getInitExpression() != null) {
return extractImportTag(jsVar.getInitExpression());
}
}
return null;
}
@Override
public boolean visit(@NotNull JsFunction function, @NotNull JsContext context) {
inliningContexts.push(new JsInliningContext());
assert !inProcessFunctions.contains(function): "Inliner has revisited function";
inProcessFunctions.add(function);
if (functions.containsValue(function)) {
namedFunctionsStack.push(function);
FunctionWithWrapper functionWithWrapper = functionsByFunctionNodes.get(function);
if (functionWithWrapper != null) {
visit(functionWithWrapper);
return false;
}
else {
startFunction(function);
return super.visit(function, context);
}
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) {
JsContext<JsStatement> statementContext = statementContextForInline != null ? statementContextForInline :
getLastStatementLevelContext();
inliningContexts.push(new JsInliningContext(statementContext));
assert !inProcessFunctions.contains(function): "Inliner has revisited function";
inProcessFunctions.add(function);
if (functions.values().stream().anyMatch(namedFunction -> namedFunction.getFunction().equals(function))) {
namedFunctionsStack.push(function);
}
}
private void endFunction(@NotNull JsFunction function) {
NamingUtilsKt.refreshLabelNames(function.getBody(), function.getScope());
RemoveUnusedLocalFunctionDeclarationsKt.removeUnusedLocalFunctionDeclarations(function);
@@ -143,6 +208,61 @@ public class JsInliner extends JsVisitorWithContextImpl {
}
}
@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;
ListContext<JsStatement> innerContext = new ListContext<>();
JsBlock wrapperBody = functionWithWrapper.getWrapperBody();
List<JsStatement> statements = null;
if (wrapperBody != null) {
existingImports = new HashMap<>();
statementContexts.push(innerContext);
statementContextForInline = innerContext;
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 {
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;
}
@Override
public boolean visit(@NotNull JsInvocation call, @NotNull JsContext context) {
if (!hasToBeInlined(call)) return true;
@@ -153,13 +273,13 @@ public class JsInliner extends JsVisitorWithContextImpl {
inlineCallInfos.add(new JsCallInfo(call, containingFunction));
}
JsFunction definition = getFunctionContext().getFunctionDefinition(call);
FunctionWithWrapper definition = getFunctionContext().getFunctionDefinition(call);
if (inProcessFunctions.contains(definition)) {
reportInlineCycle(call, definition);
if (inProcessFunctions.contains(definition.getFunction())) {
reportInlineCycle(call, definition.getFunction());
}
else if (!processedFunctions.contains(definition)) {
accept(definition);
else if (!processedFunctions.contains(definition.getFunction())) {
visit(definition);
}
return true;
@@ -208,7 +328,19 @@ public class JsInliner extends JsVisitorWithContextImpl {
}
JsInliningContext inliningContext = getInliningContext();
InlineableResult inlineableResult = getInlineableCallReplacement(call, inliningContext);
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();
if (functionWithWrapper.getWrapperBody() != null) {
applyWrapper(functionWithWrapper.getWrapperBody(), function, inliningContext);
}
InlineableResult inlineableResult = FunctionInlineMutator.getInlineableCallReplacement(call, function, inliningContext);
JsStatement inlineableBody = inlineableResult.getInlineableBody();
JsExpression resultExpression = inlineableResult.getResultExpression();
@@ -232,6 +364,64 @@ public class JsInliner extends JsVisitorWithContextImpl {
context.replaceMe(resultExpression);
}
private void applyWrapper(
@NotNull JsBlock wrapper, @NotNull JsFunction function,
@NotNull InliningContext inliningContext
) {
Map<JsName, JsExpression> replacements = new HashMap<>();
List<JsStatement> copiedStatements = new ArrayList<>();
for (JsStatement statement : wrapper.getStatements()) {
if (statement instanceof JsReturn) continue;
statement = statement.deepCopy();
if (statement instanceof JsVars) {
JsVars jsVars = (JsVars) statement;
String tag = getImportTag(jsVars);
if (tag != null) {
JsName name = jsVars.getVars().get(0).getName();
JsName existingName = existingImports == null ? MetadataProperties.getLocalAlias(name) : null;
if (existingName == null) {
existingName = existingImports.computeIfAbsent(tag, t -> {
copiedStatements.add(jsVars);
JsName alias = JsScope.declareTemporaryName(name.getIdent());
alias.copyMetadataFrom(name);
replacements.put(name, pureFqn(alias, null));
return alias;
});
}
if (name != existingName) {
JsExpression replacement = pureFqn(existingName, null);
replacements.put(name, replacement);
}
continue;
}
}
copiedStatements.add(statement);
}
Set<JsName> definedNames = copiedStatements.stream()
.flatMap(node -> CollectUtilsKt.collectDefinedNamesInAllScopes(node).stream())
.filter(name -> !replacements.containsKey(name))
.collect(Collectors.toSet());
for (JsName name : definedNames) {
JsName alias = JsScope.declareTemporaryName(name.getIdent());
alias.copyMetadataFrom(name);
JsExpression replacement = pureFqn(alias, null);
replacements.put(name, replacement);
}
for (JsStatement statement : copiedStatements) {
statement = RewriteUtilsKt.replaceNames(statement, replacements);
inliningContext.getStatementContextBeforeCurrentFunction().addPrevious(accept(statement));
}
RewriteUtilsKt.replaceNames(function, replacements);
}
private static boolean isSuspendWithCurrentContinuation(@Nullable DeclarationDescriptor descriptor) {
if (!(descriptor instanceof FunctionDescriptor)) return false;
return CommonCoroutineCodegenUtilKt.isBuiltInSuspendCoroutineOrReturn((FunctionDescriptor) descriptor.getOriginal());
@@ -262,6 +452,40 @@ public class JsInliner extends JsVisitorWithContextImpl {
return namedFunctionsStack.peek();
}
@Nullable
private static String extractImportTag(@NotNull JsExpression expression) {
StringBuilder sb = new StringBuilder();
return extractImportTagImpl(expression, sb) ? sb.toString() : null;
}
private static boolean extractImportTagImpl(@NotNull JsExpression expression, @NotNull StringBuilder sb) {
if (expression instanceof JsNameRef) {
JsNameRef nameRef = (JsNameRef) expression;
if (nameRef.getQualifier() != null) {
if (!extractImportTagImpl(nameRef.getQualifier(), sb)) {
return false;
}
sb.append('.');
}
sb.append(JsToStringGenerationVisitor.javaScriptString(nameRef.getIdent()));
return true;
}
else if (expression instanceof JsArrayAccess) {
JsArrayAccess arrayAccess = (JsArrayAccess) expression;
if (!extractImportTagImpl(arrayAccess.getArrayExpression(), sb)) {
return false;
}
sb.append(".");
if (!extractImportTagImpl(arrayAccess.getIndexExpression(), sb)) {
return false;
}
return true;
}
else {
return false;
}
}
private void reportInlineCycle(@NotNull JsInvocation call, @NotNull JsFunction calledFunction) {
MetadataProperties.setInlineStrategy(call, InlineStrategy.NOT_INLINE);
Iterator<JsCallInfo> it = inlineCallInfos.descendingIterator();
@@ -291,20 +515,24 @@ public class JsInliner extends JsVisitorWithContextImpl {
private class JsInliningContext implements InliningContext {
private final FunctionContext functionContext;
JsInliningContext() {
@NotNull
private final JsContext<JsStatement> statementContextBeforeCurrentFunction;
JsInliningContext(@NotNull JsContext<JsStatement> statementContextBeforeCurrentFunction) {
functionContext = new FunctionContext(functionReader, config) {
@Nullable
@Override
protected JsFunction lookUpStaticFunction(@Nullable JsName functionName) {
protected FunctionWithWrapper lookUpStaticFunction(@Nullable JsName functionName) {
return functions.get(functionName);
}
@Nullable
@Override
protected JsFunction lookUpStaticFunctionByTag(@NotNull String functionTag) {
protected FunctionWithWrapper lookUpStaticFunctionByTag(@NotNull String functionTag) {
return accessors.get(functionTag);
}
};
this.statementContextBeforeCurrentFunction = statementContextBeforeCurrentFunction;
}
@NotNull
@@ -324,6 +552,12 @@ public class JsInliner extends JsVisitorWithContextImpl {
public FunctionContext getFunctionContext() {
return functionContext;
}
@NotNull
@Override
public JsContext<JsStatement> getStatementContextBeforeCurrentFunction() {
return statementContextBeforeCurrentFunction;
}
}
private static class JsCallInfo {
@@ -29,10 +29,10 @@ import org.jetbrains.kotlin.js.inline.util.collectFunctionReferencesInside
* At now, it only removes unused local functions and function literals,
* because named functions can be referenced from another module.
*/
fun removeUnusedFunctionDefinitions(roots: List<JsNode>, functions: Map<JsName, JsFunction>) {
fun removeUnusedFunctionDefinitions(root: JsNode, functions: Map<JsName, JsFunction>) {
val removable = with(UnusedLocalFunctionsCollector(functions)) {
process()
roots.forEach { accept(it) }
accept(root)
removableFunctions
}.toSet()
@@ -45,7 +45,7 @@ fun removeUnusedFunctionDefinitions(roots: List<JsNode>, functions: Map<JsName,
expression is JsFunction && expression in removable
}
roots.forEach { remover.accept(it) }
remover.accept(root)
}
private class UnusedLocalFunctionsCollector(private val functions: Map<JsName, JsFunction>) : JsVisitorWithContextImpl() {
@@ -79,9 +79,7 @@ private class UnusedLocalFunctionsCollector(private val functions: Map<JsName, J
}
}
override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean {
return !(wasProcessed(x))
}
override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean = !wasProcessed(x)
override fun endVisit(x: JsFunction, ctx: JsContext<*>) {
processed.add(x)
@@ -108,7 +108,7 @@ private fun Scope.liftUsedNames(): Scope {
scope.children.forEach { child ->
scope.usedNames += scope.declaredNames
traverse(child)
scope.usedNames += child.usedNames.filter { !it.isTemporary }
scope.usedNames += child.usedNames
}
}
traverse(this)
@@ -25,11 +25,11 @@ import org.jetbrains.kotlin.js.inline.util.*
import org.jetbrains.kotlin.js.translate.context.Namer
abstract class FunctionContext(private val functionReader: FunctionReader, private val config: JsConfig) {
protected abstract fun lookUpStaticFunction(functionName: JsName?): JsFunction?
protected abstract fun lookUpStaticFunction(functionName: JsName?): FunctionWithWrapper?
protected abstract fun lookUpStaticFunctionByTag(functionTag: String): JsFunction?
protected abstract fun lookUpStaticFunctionByTag(functionTag: String): FunctionWithWrapper?
fun getFunctionDefinition(call: JsInvocation): JsFunction {
fun getFunctionDefinition(call: JsInvocation): FunctionWithWrapper {
return getFunctionDefinitionImpl(call)!!
}
@@ -67,7 +67,7 @@ abstract class FunctionContext(private val functionReader: FunctionReader, priva
* 5. Qualifier can be JsNameRef with ref to case [3]
* in case of local function with closure.
*/
private fun getFunctionDefinitionImpl(call: JsInvocation): JsFunction? {
private fun getFunctionDefinitionImpl(call: JsInvocation): FunctionWithWrapper? {
val descriptor = call.descriptor
if (descriptor != null) {
if (descriptor in functionReader) return functionReader[descriptor]
@@ -87,19 +87,19 @@ abstract class FunctionContext(private val functionReader: FunctionReader, priva
return when (qualifier) {
is JsInvocation -> {
tryExtractCallableReference(qualifier) ?: getSimpleName(qualifier)?.let { simpleName ->
lookUpStaticFunction(simpleName)?.let { if (isFunctionCreator(it)) it else null }
lookUpStaticFunction(simpleName)?.let { if (isFunctionCreator(it.function)) it else null }
}
}
is JsNameRef -> lookUpStaticFunction(qualifier.name)
is JsFunction -> qualifier
is JsFunction -> FunctionWithWrapper(qualifier, null)
else -> null
}
}
private fun tryExtractCallableReference(invocation: JsInvocation): JsFunction? {
private fun tryExtractCallableReference(invocation: JsInvocation): FunctionWithWrapper? {
if (invocation.isCallableReference) {
val arg = invocation.arguments[1]
if (arg is JsFunction) return arg
if (arg is JsFunction) return FunctionWithWrapper(arg, null)
}
return null
}
@@ -21,6 +21,9 @@ import org.jetbrains.kotlin.js.backend.ast.JsStatement
interface InliningContext {
val statementContext: JsContext<JsStatement>
val statementContextBeforeCurrentFunction: JsContext<JsStatement>
val functionContext: FunctionContext
fun newNamingContext(): NamingContext
@@ -114,11 +114,30 @@ fun collectDefinedNames(scope: JsNode): Set<JsName> {
return names
}
fun collectDefinedNamesInAllScopes(scope: JsNode): Set<JsName> {
val names = mutableSetOf<JsName>()
object : RecursiveJsVisitor() {
override fun visit(x: JsVars.JsVar) {
super.visit(x)
names += x.name
}
override fun visitFunction(x: JsFunction) {
super.visitFunction(x)
x.name?.let { names += it }
names += x.parameters.map { it.name }
}
}.accept(scope)
return names
}
fun JsFunction.collectFreeVariables() = collectUsedNames(body) - collectDefinedNames(body) - parameters.map { it.name }
fun JsFunction.collectLocalVariables() = collectDefinedNames(body) + parameters.map { it.name }
fun collectNamedFunctions(scope: JsNode) = collectNamedFunctionsAndMetadata(scope).mapValues { it.value.first }
fun collectNamedFunctions(scope: JsNode) = collectNamedFunctionsAndMetadata(scope).mapValues { it.value.first.function }
fun collectNamedFunctionsOrMetadata(scope: JsNode) = collectNamedFunctionsAndMetadata(scope).mapValues { it.value.second }
@@ -131,8 +150,17 @@ fun collectNamedFunctions(fragments: List<JsProgramFragment>): Map<JsName, JsFun
return result
}
fun collectNamedFunctionsAndMetadata(scope: JsNode): Map<JsName, Pair<JsFunction, JsExpression>> {
val namedFunctions = mutableMapOf<JsName, Pair<JsFunction, JsExpression>>()
fun collectNamedFunctionsAndWrappers(fragments: List<JsProgramFragment>): Map<JsName, FunctionWithWrapper> {
val result = mutableMapOf<JsName, FunctionWithWrapper>()
for (fragment in fragments) {
result += collectNamedFunctionsAndMetadata(fragment.declarationBlock).mapValues { it.value.first }
result += collectNamedFunctionsAndMetadata(fragment.initializerBlock).mapValues { it.value.first }
}
return result
}
fun collectNamedFunctionsAndMetadata(scope: JsNode): Map<JsName, Pair<FunctionWithWrapper, JsExpression>> {
val namedFunctions = mutableMapOf<JsName, Pair<FunctionWithWrapper, JsExpression>>()
scope.accept(object : RecursiveJsVisitor() {
override fun visitBinaryExpression(x: JsBinaryOperation) {
@@ -141,9 +169,10 @@ fun collectNamedFunctionsAndMetadata(scope: JsNode): Map<JsName, Pair<JsFunction
val (left, right) = assignment
if (left is JsNameRef) {
val name = left.name
val function = extractFunction(right)
if (function != null && name != null) {
namedFunctions[name] = Pair(function, right)
if (name != null) {
extractFunction(right)?.let { (function, wrapper) ->
namedFunctions[name] = Pair(FunctionWithWrapper(function, wrapper), right)
}
}
}
}
@@ -154,8 +183,7 @@ fun collectNamedFunctionsAndMetadata(scope: JsNode): Map<JsName, Pair<JsFunction
val initializer = x.initExpression
val name = x.name
if (initializer != null && name != null) {
val function = extractFunction(initializer)
if (function != null) {
extractFunction(initializer)?.let { function ->
namedFunctions[name] = Pair(function, initializer)
}
}
@@ -165,22 +193,19 @@ fun collectNamedFunctionsAndMetadata(scope: JsNode): Map<JsName, Pair<JsFunction
override fun visitFunction(x: JsFunction) {
val name = x.name
if (name != null) {
namedFunctions[name] = Pair(x, x)
namedFunctions[name] = Pair(FunctionWithWrapper(x, null), x)
}
super.visitFunction(x)
}
private fun extractFunction(expression: JsExpression) = when (expression) {
is JsFunction -> expression
else -> InlineMetadata.decompose(expression)?.function
}
})
return namedFunctions
}
fun collectAccessors(scope: JsNode): Map<String, JsFunction> {
val accessors = hashMapOf<String, JsFunction>()
data class FunctionWithWrapper(val function: JsFunction, val wrapperBody: JsBlock?)
fun collectAccessors(scope: JsNode): Map<String, FunctionWithWrapper> {
val accessors = hashMapOf<String, FunctionWithWrapper>()
scope.accept(object : RecursiveJsVisitor() {
override fun visitInvocation(invocation: JsInvocation) {
@@ -194,14 +219,19 @@ fun collectAccessors(scope: JsNode): Map<String, JsFunction> {
return accessors
}
fun collectAccessors(fragments: List<JsProgramFragment>): Map<String, JsFunction> {
val result = mutableMapOf<String, JsFunction>()
fun collectAccessors(fragments: List<JsProgramFragment>): Map<String, FunctionWithWrapper> {
val result = mutableMapOf<String, FunctionWithWrapper>()
for (fragment in fragments) {
result += collectAccessors(fragment.declarationBlock)
}
return result
}
fun extractFunction(expression: JsExpression) = when (expression) {
is JsFunction -> FunctionWithWrapper(expression, null)
else -> InlineMetadata.decompose(expression)?.function ?: InlineMetadata.tryExtractFunction(expression)
}
fun <T : JsNode> collectInstances(klass: Class<T>, scope: JsNode): List<T> {
return with(InstanceCollector(klass, visitNestedDeclarations = false)) {
accept(scope)
@@ -21,23 +21,29 @@ import org.jetbrains.kotlin.js.backend.ast.*
class NameReplacingVisitor(private val replaceMap: Map<JsName, JsExpression>) : JsVisitorWithContextImpl() {
override fun endVisit(x: JsNameRef, ctx: JsContext<JsNode>) {
if (x.qualifier != null) return
val replacement = replaceMap[x.name] ?: return
ctx.replaceMe(replacement.deepCopy().source(x.source))
val replacementCopy = accept(replacement.deepCopy().source(x.source))
ctx.replaceMe(replacementCopy)
}
override fun endVisit(x: JsVars.JsVar, ctx: JsContext<JsNode>) {
val replacement = replaceMap[x.name]
if (replacement is HasName) {
val replacementVar = JsVars.JsVar(replacement.name, x.initExpression)
ctx.replaceMe(replacementVar.source(x.source))
}
}
override fun endVisit(x: JsVars.JsVar, ctx: JsContext<*>) = applyToNamedNode(x)
override fun endVisit(x: JsLabel, ctx: JsContext<JsNode>) {
val replacement = replaceMap[x.name]
if (replacement is HasName) {
val replacementLabel = JsLabel(replacement.name, x.statement)
ctx.replaceMe(replacementLabel)
override fun endVisit(x: JsLabel, ctx: JsContext<*>) = applyToNamedNode(x)
override fun endVisit(x: JsFunction, ctx: JsContext<*>) = applyToNamedNode(x)
override fun endVisit(x: JsParameter, ctx: JsContext<*>) = applyToNamedNode(x)
private fun applyToNamedNode(x: HasName) {
while (true) {
val replacement = replaceMap[x.name]
if (replacement is HasName) {
x.name = replacement.name
}
else {
break
}
}
}
}
+10
View File
@@ -25,6 +25,16 @@ Kotlin.defineInlineFunction = function(tag, fun) {
return fun;
};
Kotlin.wrapFunction = function(fun) {
var f = function() {
f = fun();
return f.apply(this, arguments);
};
return function() {
return f.apply(this, arguments);
};
};
Kotlin.isTypeOf = function(type) {
return function (object) {
return typeof object === type;
@@ -3935,6 +3935,18 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
doTest(fileName);
}
@TestMetadata("inlineInInlineWithLambda.kt")
public void testInlineInInlineWithLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inline/inlineInInlineWithLambda.kt");
doTest(fileName);
}
@TestMetadata("inlineInInlineWithLambdaPrivate.kt")
public void testInlineInInlineWithLambdaPrivate() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inline/inlineInInlineWithLambdaPrivate.kt");
doTest(fileName);
}
@TestMetadata("inlineInc.kt")
public void testInlineInc() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inline/inlineInc.kt");
@@ -4724,6 +4736,24 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
doTest(fileName);
}
@TestMetadata("importObjectInstance.kt")
public void testImportObjectInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inlineMultiModule/importObjectInstance.kt");
doTest(fileName);
}
@TestMetadata("inlineInInlineWithLambdaMultiModule.kt")
public void testInlineInInlineWithLambdaMultiModule() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inlineMultiModule/inlineInInlineWithLambdaMultiModule.kt");
doTest(fileName);
}
@TestMetadata("internalNameClash.kt")
public void testInternalNameClash() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inlineMultiModule/internalNameClash.kt");
doTest(fileName);
}
@TestMetadata("keywordAsMemberName.kt")
public void testKeywordAsMemberName() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/inlineMultiModule/keywordAsMemberName.kt");
@@ -135,7 +135,7 @@ public final class K2JSTranslator {
List<JsProgramFragment> allFragments = new ArrayList<>(translationResult.getFragments());
JsInliner.process(reporter, config, analysisResult.getBindingTrace(), translationResult.getInnerModuleName(),
allFragments, newFragments);
allFragments, newFragments, translationResult.getImportStatements());
LabeledBlockToDoWhileTransformation.INSTANCE.apply(newFragments);
@@ -51,7 +51,7 @@ internal class DeclarationExporter(val context: StaticContext) {
val qualifier = when {
container is PackageFragmentDescriptor -> {
getLocalPackageReference(container.fqName)
getLocalPackageName(container.fqName).makeRef()
}
DescriptorUtils.isObject(container) -> {
JsAstUtils.prototypeOf(context.getInnerNameForDescriptor(container).makeRef())
@@ -129,18 +129,18 @@ internal class DeclarationExporter(val context: StaticContext) {
statements += JsAstUtils.defineProperty(qualifier, name, propertyLiteral).exportStatement(declaration)
}
private fun getLocalPackageReference(packageName: FqName): JsExpression {
fun getLocalPackageName(packageName: FqName): JsName {
if (packageName.isRoot) {
return context.fragment.scope.declareName(Namer.getRootPackageName()).makeRef()
return context.fragment.scope.declareName(Namer.getRootPackageName())
}
var name = localPackageNames[packageName]
if (name == null) {
name = JsScope.declareTemporaryName("package$" + packageName.shortName().asString())
localPackageNames[packageName] = name
statements += definePackageAlias(packageName.shortName().asString(), name, packageName.asString(),
getLocalPackageReference(packageName.parent()))
getLocalPackageName(packageName.parent()).makeRef())
}
return name.makeRef()
return name
}
private fun JsExpression.exportStatement(declaration: DeclarationDescriptor) = JsExpressionStatement(this).also {
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.js.translate.context
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.js.backend.ast.JsGlobalBlock
import org.jetbrains.kotlin.js.backend.ast.JsName
class InlineFunctionContext(val descriptor: CallableDescriptor) {
val imports = mutableMapOf<String, JsName>()
val importBlock = JsGlobalBlock()
val prototypeBlock = JsGlobalBlock()
val declarationsBlock = JsGlobalBlock()
}
@@ -94,6 +94,7 @@ public final class Namer {
public static final JsNameRef IS_ARRAY_FUN_REF = new JsNameRef("isArray", "Array");
public static final String DEFINE_INLINE_FUNCTION = "defineInlineFunction";
public static final String WRAP_FUNCTION = "wrapFunction";
public static final String DEFAULT_PARAMETER_IMPLEMENTOR_SUFFIX = "$default";
private static final JsNameRef JS_OBJECT = new JsNameRef("Object");
@@ -355,6 +356,11 @@ public final class Namer {
return pureFqn(DEFINE_INLINE_FUNCTION, kotlinObject());
}
@NotNull
public static JsNameRef wrapFunction() {
return pureFqn(WRAP_FUNCTION, kotlinObject());
}
@NotNull
public static String suggestedModuleName(@NotNull String id) {
if (id.isEmpty()) {
@@ -533,6 +533,9 @@ public final class StaticContext {
private final class InnerNameGenerator extends Generator<JsName> {
public InnerNameGenerator() {
addRule(descriptor -> {
if (descriptor instanceof PackageFragmentDescriptor && DescriptorUtils.getContainingModule(descriptor) == currentModule) {
return exporter.getLocalPackageName(((PackageFragmentDescriptor) descriptor).getFqName());
}
if (descriptor instanceof FunctionDescriptor) {
FunctionDescriptor initialDescriptor = ((FunctionDescriptor) descriptor).getInitialSignatureDescriptor();
if (initialDescriptor != null) {
@@ -40,6 +40,7 @@ import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver;
import org.jetbrains.kotlin.serialization.js.ModuleKind;
@@ -72,6 +73,9 @@ public class TranslationContext {
@Nullable
private final VariableDescriptor continuationParameterDescriptor;
@Nullable
private InlineFunctionContext inlineFunctionContext;
@NotNull
public static TranslationContext rootContext(@NotNull StaticContext staticContext) {
DynamicContext rootDynamicContext = DynamicContext.rootContext(
@@ -104,6 +108,14 @@ public class TranslationContext {
}
continuationParameterDescriptor = calculateContinuationParameter();
inlineFunctionContext = parent != null ? parent.inlineFunctionContext : null;
DeclarationDescriptor parentDescriptor = parent != null ? parent.declarationDescriptor : null;
if (parentDescriptor != declarationDescriptor &&
declarationDescriptor instanceof CallableDescriptor &&
InlineUtil.isInline(declarationDescriptor)) {
inlineFunctionContext = new InlineFunctionContext((CallableDescriptor) declarationDescriptor);
}
}
private VariableDescriptor calculateContinuationParameter() {
@@ -137,6 +149,11 @@ public class TranslationContext {
return dynamicContext;
}
@Nullable
public InlineFunctionContext getInlineFunctionContext() {
return inlineFunctionContext;
}
@NotNull
public TranslationContext contextWithScope(@NotNull JsFunction fun) {
return this.newFunctionBody(fun, aliasingContext, declarationDescriptor);
@@ -323,8 +340,42 @@ public class TranslationContext {
@NotNull
public JsExpression getInnerReference(@NotNull DeclarationDescriptor descriptor) {
JsName name;
if (inlineFunctionContext == null || !isPublicInlineFunction() ||
DescriptorUtils.isAncestor(inlineFunctionContext.getDescriptor(), descriptor, false)) {
name = getInnerNameForDescriptor(descriptor);
}
else {
String tag = staticContext.getTag(descriptor);
name = inlineFunctionContext.getImports().computeIfAbsent(tag, t -> {
JsExpression imported = createInlineLocalImportExpression(descriptor);
if (imported instanceof JsNameRef) {
JsNameRef importedNameRef = (JsNameRef) imported;
if (importedNameRef.getQualifier() == null && importedNameRef.getIdent().equals(Namer.getRootPackageName()) &&
(descriptor instanceof PackageFragmentDescriptor || descriptor instanceof ModuleDescriptor)) {
return importedNameRef.getName();
}
}
JsName result = JsScope.declareTemporaryName(StaticContext.getSuggestedName(descriptor));
if (isFromCurrentModule(descriptor) && !AnnotationsUtils.isNativeObject(descriptor)) {
MetadataProperties.setLocalAlias(result, getInnerNameForDescriptor(descriptor));
}
MetadataProperties.setDescriptor(result, descriptor);
MetadataProperties.setStaticRef(result, imported);
MetadataProperties.setImported(result, true);
inlineFunctionContext.getImportBlock().getStatements().add(JsAstUtils.newVar(result, imported));
return result;
});
}
return pureFqn(name, null);
}
@NotNull
private JsExpression createInlineLocalImportExpression(@NotNull DeclarationDescriptor descriptor) {
JsExpression result = getQualifiedReference(descriptor);
JsName name = getInnerNameForDescriptor(descriptor);
JsExpression result = pureFqn(name, null);
SuggestedName suggested = staticContext.suggestName(descriptor);
if (suggested != null && getConfig().getModuleKind() != ModuleKind.PLAIN && isPublicInlineFunction()) {
@@ -695,7 +746,12 @@ public class TranslationContext {
}
public void addDeclarationStatement(@NotNull JsStatement statement) {
staticContext.getDeclarationStatements().add(statement);
if (inlineFunctionContext != null) {
inlineFunctionContext.getDeclarationsBlock().getStatements().add(statement);
}
else {
staticContext.getDeclarationStatements().add(statement);
}
}
public void addTopLevelStatement(@NotNull JsStatement statement) {
@@ -725,15 +781,10 @@ public class TranslationContext {
}
public boolean isPublicInlineFunction() {
DeclarationDescriptor descriptor = declarationDescriptor;
while (descriptor instanceof FunctionDescriptor) {
FunctionDescriptor function = (FunctionDescriptor) descriptor;
if (function.isInline() && DescriptorUtilsKt.isEffectivelyPublicApi(function)) {
return true;
}
descriptor = descriptor.getContainingDeclaration();
}
return false;
if (inlineFunctionContext == null) return false;
CallableDescriptor function = inlineFunctionContext.getDescriptor();
return function.getVisibility().effectiveVisibility(function, true).getPublicApi();
}
@Nullable
@@ -113,7 +113,7 @@ abstract class AbstractDeclarationVisitor : TranslatorVisitor<Unit>() {
function.body.statements += FunctionBodyTranslator.setDefaultValueForArguments(descriptor, innerContext)
}
innerContext.translateFunction(expression, function)
return innerContext.wrapWithInlineMetadata(function, descriptor, context.config)
return innerContext.wrapWithInlineMetadata(function, descriptor)
}
// used from kotlinx.serialization
@@ -15,15 +15,14 @@
*/
package org.jetbrains.kotlin.js.translate.declaration
import org.jetbrains.kotlin.js.backend.ast.JsExpression
import org.jetbrains.kotlin.js.backend.ast.JsFunction
import org.jetbrains.kotlin.js.backend.ast.JsName
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.js.backend.ast.JsExpression
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.utils.BindingUtils
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils
import org.jetbrains.kotlin.js.translate.utils.addFunctionButNotExport
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.resolve.BindingContext
@@ -47,15 +46,15 @@ class FileDeclarationVisitor(private val context: TranslationContext) : Abstract
override fun addFunction(descriptor: FunctionDescriptor, expression: JsExpression?, psi: KtElement?) {
if (expression == null) return
addFunctionButNotExport(descriptor, expression)
context.addFunctionButNotExport(descriptor, expression)
context.export(descriptor)
}
override fun addProperty(descriptor: PropertyDescriptor, getter: JsExpression, setter: JsExpression?) {
if (!JsDescriptorUtils.isSimpleFinalProperty(descriptor)) {
addFunctionButNotExport(descriptor.getter!!, getter)
context.addFunctionButNotExport(descriptor.getter!!, getter)
if (setter != null) {
addFunctionButNotExport(descriptor.setter!!, setter)
context.addFunctionButNotExport(descriptor.setter!!, setter)
}
}
context.export(descriptor)
@@ -64,18 +63,4 @@ class FileDeclarationVisitor(private val context: TranslationContext) : Abstract
override fun getBackingFieldReference(descriptor: PropertyDescriptor): JsExpression {
return context.getInnerReference(descriptor)
}
private fun addFunctionButNotExport(descriptor: FunctionDescriptor, expression: JsExpression): JsName {
val name = context.getInnerNameForDescriptor(descriptor)
when (expression) {
is JsFunction -> {
expression.name = name
context.addDeclarationStatement(expression.makeStmt())
}
else -> {
context.addDeclarationStatement(JsAstUtils.newVar(name, expression))
}
}
return name
}
}
@@ -19,18 +19,14 @@ package org.jetbrains.kotlin.js.translate.expression
import com.intellij.openapi.vfs.VfsUtilCore
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.PropertyAccessorDescriptorImpl
import org.jetbrains.kotlin.js.backend.ast.JsExpression
import org.jetbrains.kotlin.js.backend.ast.JsFunction
import org.jetbrains.kotlin.js.backend.ast.JsParameter
import org.jetbrains.kotlin.js.backend.ast.JsScope
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor
import org.jetbrains.kotlin.js.backend.ast.metadata.functionDescriptor
import org.jetbrains.kotlin.js.backend.ast.metadata.hasDefaultValue
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.TranslationContext
import org.jetbrains.kotlin.js.translate.reference.CallExpressionTranslator.shouldBeInlined
import org.jetbrains.kotlin.js.translate.utils.BindingUtils
import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.translateFunctionBody
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
@@ -104,9 +100,9 @@ fun TranslationContext.translateFunction(declaration: KtDeclarationWithBody, fun
function.functionDescriptor = descriptor
}
fun TranslationContext.wrapWithInlineMetadata(function: JsFunction, descriptor: FunctionDescriptor, config: JsConfig): JsExpression {
return if (shouldBeInlined(descriptor, this) && descriptor.isEffectivelyPublicApi) {
val metadata = InlineMetadata.compose(function, descriptor, config)
fun TranslationContext.wrapWithInlineMetadata(function: JsFunction, descriptor: FunctionDescriptor): JsExpression {
return if (descriptor.isInline && descriptor.isEffectivelyPublicApi) {
val metadata = InlineMetadata.compose(function, descriptor, this)
val functionWithMetadata = metadata.functionWithMetadata
config.configuration[JSConfigurationKeys.INCREMENTAL_RESULTS_CONSUMER]?.apply {
@@ -129,6 +125,15 @@ fun TranslationContext.wrapWithInlineMetadata(function: JsFunction, descriptor:
functionWithMetadata
}
else {
function
val block = if (descriptor.isInline) {
inlineFunctionContext!!.let {
JsBlock(it.importBlock.statements + it.prototypeBlock.statements + it.declarationsBlock.statements +
JsReturn(function))
}
}
else {
null
}
if (block != null) InlineMetadata.wrapFunction(FunctionWithWrapper(function, block)) else function
}
}
@@ -18,17 +18,21 @@ package org.jetbrains.kotlin.js.translate.expression
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper
import org.jetbrains.kotlin.js.translate.context.Namer
import org.jetbrains.kotlin.js.translate.context.TranslationContext
private val METADATA_PROPERTIES_COUNT = 2
class InlineMetadata(val tag: JsStringLiteral, val function: JsFunction) {
class InlineMetadata(val tag: JsStringLiteral, val function: FunctionWithWrapper) {
companion object {
@JvmStatic
fun compose(function: JsFunction, descriptor: CallableDescriptor, config: JsConfig): InlineMetadata {
val tag = JsStringLiteral(Namer.getFunctionTag(descriptor, config))
return InlineMetadata(tag, function)
fun compose(function: JsFunction, descriptor: CallableDescriptor, context: TranslationContext): InlineMetadata {
val tag = JsStringLiteral(Namer.getFunctionTag(descriptor, context.config))
val inliningContext = context.inlineFunctionContext!!
val block = JsBlock(inliningContext.importBlock.statements + inliningContext.prototypeBlock.statements +
inliningContext.declarationsBlock.statements + JsReturn(function))
return InlineMetadata(tag, FunctionWithWrapper(function, block))
}
@JvmStatic
@@ -45,17 +49,44 @@ class InlineMetadata(val tag: JsStringLiteral, val function: JsFunction) {
val arguments = call.arguments
if (arguments.size != METADATA_PROPERTIES_COUNT) return null
val tag = arguments[0] as? JsStringLiteral
val function = arguments[1] as? JsFunction
if (tag == null || function == null) return null
val tag = arguments[0] as? JsStringLiteral ?: return null
val function = tryExtractFunction(arguments[1]) ?: return null
return InlineMetadata(tag, function)
}
@JvmStatic
fun tryExtractFunction(callExpression: JsExpression): FunctionWithWrapper? {
if (callExpression is JsFunction) return FunctionWithWrapper(callExpression, null)
if (callExpression !is JsInvocation) return null
val qualifier = callExpression.qualifier as? JsNameRef ?: return null
val qualifierQualifier = qualifier.qualifier as? JsNameRef ?: return null
if (qualifierQualifier.qualifier != null || qualifier.ident != Namer.WRAP_FUNCTION) return null
if (callExpression.arguments.size != 1) return null
val argument = callExpression.arguments[0] as? JsFunction ?: return null
return decomposeWrapper(argument)
}
@JvmStatic
fun decomposeWrapper(wrapperFunction: JsFunction): FunctionWithWrapper? {
val returnExpr = wrapperFunction.body.statements.lastOrNull() as? JsReturn ?: return null
val function = returnExpr.expression as? JsFunction ?: return null
return FunctionWithWrapper(function, wrapperFunction.body)
}
@JvmStatic
fun wrapFunction(function: FunctionWithWrapper): JsExpression {
val wrapperBody = function.wrapperBody ?: JsBlock(JsReturn(function.function))
val wrapper = JsFunction(function.function.scope, wrapperBody, "")
return JsInvocation(Namer.wrapFunction(), wrapper)
}
}
val functionWithMetadata: JsExpression
get() {
val propertiesList = listOf(tag, function)
return JsInvocation(Namer.createInlineFunction(), propertiesList)
return JsInvocation(Namer.createInlineFunction(), tag, wrapFunction(function))
}
}
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.*
import org.jetbrains.kotlin.js.descriptorUtils.isCoroutineLambda
import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper
import org.jetbrains.kotlin.js.inline.util.getInnerFunction
import org.jetbrains.kotlin.js.inline.util.rewriters.NameReplacingVisitor
import org.jetbrains.kotlin.js.translate.context.TranslationContext
@@ -33,6 +34,7 @@ import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.setDefault
import org.jetbrains.kotlin.js.translate.utils.FunctionBodyTranslator.translateFunctionBody
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils.simpleReturnFunction
import org.jetbrains.kotlin.js.translate.utils.addFunctionButNotExport
import org.jetbrains.kotlin.js.translate.utils.fillCoroutineMetadata
import org.jetbrains.kotlin.js.translate.utils.finalElement
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
@@ -65,20 +67,19 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
val tracker = functionContext.usageTracker()!!
val name = invokingContext.getInnerNameForDescriptor(descriptor)
if (tracker.hasCapturedExceptContaining()) {
val lambdaCreator = simpleReturnFunction(invokingContext.scope(), lambda.source(declaration))
lambdaCreator.name = invokingContext.getInnerNameForDescriptor(descriptor)
lambdaCreator.isLocal = true
if (descriptor in tracker.capturedDescriptors && !descriptor.isCoroutineLambda) {
lambda.name = tracker.getNameForCapturedDescriptor(descriptor)
}
lambdaCreator.name.staticRef = lambdaCreator
name.staticRef = lambdaCreator
lambdaCreator.fillCoroutineMetadata(invokingContext, descriptor)
lambdaCreator.source = declaration
return lambdaCreator.withCapturedParameters(descriptor, functionContext, invokingContext)
return lambdaCreator.withCapturedParameters(functionContext, name, invokingContext)
}
lambda.name = invokingContext.getInnerNameForDescriptor(descriptor)
if (descriptor in tracker.capturedDescriptors) {
val capturedName = tracker.getNameForCapturedDescriptor(descriptor)!!
val globalName = invokingContext.getInnerNameForDescriptor(descriptor)
@@ -88,10 +89,10 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
lambda.isLocal = true
invokingContext.addDeclarationStatement(lambda.makeStmt())
invokingContext.addFunctionDeclaration(name, lambda)
lambda.fillCoroutineMetadata(invokingContext, descriptor)
lambda.name.staticRef = lambda
return getReferenceToLambda(invokingContext, descriptor, lambda.name)
name.staticRef = lambda
return JsAstUtils.pureFqn(name, null)
}
fun JsFunction.fillCoroutineMetadata(context: TranslationContext, descriptor: FunctionDescriptor) {
@@ -110,13 +111,22 @@ class LiteralFunctionTranslator(context: TranslationContext) : AbstractTranslato
}
}
private fun TranslationContext.addFunctionDeclaration(name: JsName, function: JsFunction) {
addFunctionButNotExport(name, if (isPublicInlineFunction) {
InlineMetadata.wrapFunction(FunctionWithWrapper(function, null))
}
else {
function
})
}
fun JsFunction.withCapturedParameters(
descriptor: CallableMemberDescriptor,
context: TranslationContext,
functionName: JsName,
invokingContext: TranslationContext
): JsExpression {
context.addDeclarationStatement(makeStmt())
val ref = getReferenceToLambda(invokingContext, descriptor, name)
context.addFunctionDeclaration(functionName, this)
val ref = JsAstUtils.pureFqn(functionName, null)
val invocation = JsInvocation(ref).apply { sideEffects = SideEffectKind.PURE }
val invocationArguments = invocation.arguments
@@ -155,19 +165,6 @@ fun JsFunction.withCapturedParameters(
return invocation
}
private fun getReferenceToLambda(context: TranslationContext, descriptor: CallableMemberDescriptor, name: JsName): JsExpression {
return if (context.isPublicInlineFunction) {
val fqn = context.getQualifiedReference(descriptor)
if (fqn is JsNameRef) {
fqn.name?.let { it.staticRef = name.staticRef }
}
fqn
}
else {
JsAstUtils.pureFqn(name, null)
}
}
private data class CapturedArgsParams(val arguments: List<JsExpression> = listOf(), val parameters: List<JsParameter> = listOf())
/**
@@ -17,10 +17,7 @@
package org.jetbrains.kotlin.js.translate.general
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.js.backend.ast.JsImportedModule
import org.jetbrains.kotlin.js.backend.ast.JsName
import org.jetbrains.kotlin.js.backend.ast.JsProgram
import org.jetbrains.kotlin.js.backend.ast.JsProgramFragment
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.psi.KtFile
class AstGenerationResult(
@@ -29,6 +26,7 @@ class AstGenerationResult(
val fragments: List<JsProgramFragment>,
val fragmentMap: Map<KtFile, JsProgramFragment>,
val newFragments: List<JsProgramFragment>,
val importStatements: List<JsStatement>,
val fileMemberScopes: Map<KtFile, List<DeclarationDescriptor>>,
val importedModuleList: List<JsImportedModule>
)
@@ -29,7 +29,7 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
// Maps unique signature (see generateSignature) to names
private val nameTable = mutableMapOf<String, JsName>()
private val importedModuleTable = mutableMapOf<JsImportedModuleKey, JsName>()
private val importBlock = JsGlobalBlock()
val importBlock = JsGlobalBlock()
private val declarationBlock = JsGlobalBlock()
private val initializerBlock = JsGlobalBlock()
private val exportBlock = JsGlobalBlock()
@@ -339,7 +339,7 @@ public final class Translation {
config.getModuleKind()));
return new AstGenerationResult(program, internalModuleName, fragments, fragmentMap, newFragments,
fileMemberScopes, importedModuleList);
merger.getImportBlock().getStatements(), fileMemberScopes, importedModuleList);
}
private static boolean isBuiltinModule(@NotNull List<JsProgramFragment> fragments) {
@@ -55,13 +55,13 @@ public final class ReferenceTranslator {
JsExpression alias = context.getAliasForDescriptor(descriptor);
if (alias != null) return alias;
if (shouldTranslateAsFQN(descriptor, context)) {
if (shouldTranslateAsFQN(descriptor)) {
return context.getQualifiedReference(descriptor);
}
if (descriptor instanceof PropertyDescriptor) {
PropertyDescriptor property = (PropertyDescriptor) descriptor;
if (context.isFromCurrentModule(property)) {
if (isLocallyAvailableDeclaration(context, property)) {
return context.getInnerReference(property);
}
else {
@@ -72,7 +72,7 @@ public final class ReferenceTranslator {
}
if (DescriptorUtils.isObject(descriptor) || DescriptorUtils.isEnumEntry(descriptor)) {
if (!context.isFromCurrentModule(descriptor)) {
if (!isLocallyAvailableDeclaration(context, descriptor)) {
return getLazyReferenceToObject((ClassDescriptor) descriptor, context);
}
else {
@@ -89,16 +89,12 @@ public final class ReferenceTranslator {
if (AnnotationsUtils.isNativeObject(descriptor) || AnnotationsUtils.isLibraryObject(descriptor)) {
return context.getInnerReference(descriptor);
}
if (!shouldTranslateAsFQN(descriptor, context)) {
if (DescriptorUtils.isObject(descriptor) || DescriptorUtils.isEnumEntry(descriptor)) {
if (!context.isFromCurrentModule(descriptor)) {
return getPrototypeIfNecessary(descriptor, getLazyReferenceToObject(descriptor, context));
}
if (DescriptorUtils.isObject(descriptor) || DescriptorUtils.isEnumEntry(descriptor)) {
if (!isLocallyAvailableDeclaration(context, descriptor)) {
return getPrototypeIfNecessary(descriptor, getLazyReferenceToObject(descriptor, context));
}
return context.getInnerReference(descriptor);
}
return getPrototypeIfNecessary(descriptor, context.getQualifiedReference(descriptor));
return context.getInnerReference(descriptor);
}
@NotNull
@@ -112,6 +108,10 @@ public final class ReferenceTranslator {
return reference;
}
private static boolean isLocallyAvailableDeclaration(@NotNull TranslationContext context, @NotNull DeclarationDescriptor descriptor) {
return context.isFromCurrentModule(descriptor) && !context.isPublicInlineFunction();
}
@NotNull
private static JsExpression getLazyReferenceToObject(@NotNull ClassDescriptor descriptor, @NotNull TranslationContext context) {
DeclarationDescriptor container = descriptor.getContainingDeclaration();
@@ -119,8 +119,8 @@ public final class ReferenceTranslator {
return new JsNameRef(context.getNameForDescriptor(descriptor), qualifier);
}
private static boolean shouldTranslateAsFQN(@NotNull DeclarationDescriptor descriptor, @NotNull TranslationContext context) {
return isLocalVarOrFunction(descriptor) || context.isPublicInlineFunction();
private static boolean shouldTranslateAsFQN(@NotNull DeclarationDescriptor descriptor) {
return isLocalVarOrFunction(descriptor);
}
private static boolean isLocalVarOrFunction(DeclarationDescriptor descriptor) {
@@ -61,7 +61,7 @@ public final class TranslationUtils {
@NotNull TranslationContext context) {
JsExpression functionExpression = function;
if (InlineUtil.isInline(descriptor)) {
InlineMetadata metadata = InlineMetadata.compose(function, descriptor, context.getConfig());
InlineMetadata metadata = InlineMetadata.compose(function, descriptor, context);
functionExpression = metadata.getFunctionWithMetadata();
}
@@ -55,7 +55,7 @@ fun setInlineCallMetadata(
override fun visitInvocation(invocation: JsInvocation) {
super.visitInvocation(invocation)
if (invocation.name in candidateNames) {
if (invocation.name in candidateNames || invocation.name?.descriptor?.original == descriptor.original) {
invocation.descriptor = descriptor
invocation.inlineStrategy = InlineStrategy.IN_PLACE
invocation.psiElement = psiElement
@@ -211,4 +211,20 @@ val PsiElement.finalElement: PsiElement
is KtDeclarationWithBody -> (bodyExpression as? KtBlockExpression)?.rBrace ?: bodyExpression ?: this
is KtLambdaExpression -> bodyExpression?.rBrace ?: this
else -> this
}
}
fun TranslationContext.addFunctionButNotExport(descriptor: FunctionDescriptor, expression: JsExpression): JsName =
addFunctionButNotExport(getInnerNameForDescriptor(descriptor), expression)
fun TranslationContext.addFunctionButNotExport(name: JsName, expression: JsExpression): JsName {
when (expression) {
is JsFunction -> {
expression.name = name
addDeclarationStatement(expression.makeStmt())
}
else -> {
addDeclarationStatement(JsAstUtils.newVar(name, expression))
}
}
return name
}
@@ -0,0 +1,28 @@
var global = ""
fun log(message: String) {
global += message + ";"
}
fun baz(x: String) = "($x)"
inline fun foo(): String {
return baz(bar { "OK" })
}
inline fun bar(noinline x: () -> String): String {
return "[" + baz(boo { shouldBeInlined(); x() }) + "]"
}
fun boo(x: () -> String) = x()
inline fun shouldBeInlined() {
log("shouldBeInlined")
}
fun box(): String {
val result = foo()
if (result != "([(OK)])") return "fail1: $result"
if (global != "shouldBeInlined;") return "fail2: $global"
return "OK"
}
@@ -0,0 +1,27 @@
var global = ""
fun log(message: String) {
global += message + ";"
}
fun baz(x: String) = "($x)"
private inline fun foo(): String {
return baz(bar { "OK" })
}
private inline fun bar(noinline x: () -> String): String {
return "[" + baz(boo { shouldBeInlined(); x() }) + "]"
}
fun boo(x: () -> String) = x()
private inline fun shouldBeInlined() {
log("shouldBeInlined")
}
fun box(): String {
val result = foo()
if (result != "([(OK)])") return "fail: $result"
return "OK"
}
@@ -0,0 +1,14 @@
// MODULE: lib
// FILE: lib.kt
object O {
fun bar() = "OK"
}
inline fun foo() = O.bar()
// MODULE: main(lib)
// FILE: main.kt
// CHECK_CONTAINS_NO_CALLS: box except=bar
fun box() = foo()
@@ -0,0 +1,21 @@
// MODULE: lib
// FILE: lib.kt
fun baz(x: String) = "($x)"
inline fun foo(): String {
return baz(bar { "OK" })
}
inline fun bar(noinline x: () -> String): String {
return "[" + baz(boo { x() }) + "]"
}
fun boo(x: () -> String) = x()
// MODULE: main(lib)
// FILE: main.kt
fun box(): String {
val result = foo()
if (result != "([(OK)])") return "fail: $result"
return "OK"
}
@@ -0,0 +1,31 @@
// MODULE: lib1
// FILE: lib1.kt
package lib1
fun foo() = "lib1"
inline fun bar1() = foo()
// MODULE: lib2
// FILE: lib2.kt
package lib2
fun foo() = "lib2"
inline fun bar2() = foo()
// MODULE: main(lib1, lib2)
// FILE: main.kt
import lib1.bar1
import lib2.bar2
fun box(): String {
val a = bar1()
if (a != "lib1") return "fail1: $a"
val b = bar2()
if (b != "lib2") return "fail2: $b"
return "OK"
}