JS_IR: DCE

Could be enabled by toggling `-Xir-dce`

Box test output in js/js.translator/testData/out-min
This commit is contained in:
Anton Bannykh
2019-11-19 15:36:27 +03:00
parent faf4c05fc8
commit 81699299f5
23 changed files with 553 additions and 173 deletions
@@ -122,6 +122,9 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
@Argument(value = "-Xir-produce-js", description = "Generates JS file using IR backend. Also disables pre-IR backend") @Argument(value = "-Xir-produce-js", description = "Generates JS file using IR backend. Also disables pre-IR backend")
var irProduceJs: Boolean by FreezableVar(false) var irProduceJs: Boolean by FreezableVar(false)
@Argument(value = "-Xir-dce", description = "Perform experimental dead code elimination")
var irDce: Boolean by FreezableVar(false)
@Argument(value = "-Xir-only", description = "Disables pre-IR backend") @Argument(value = "-Xir-only", description = "Disables pre-IR backend")
var irOnly: Boolean by FreezableVar(false) var irOnly: Boolean by FreezableVar(false)
@@ -205,10 +205,13 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
phaseConfig, phaseConfig,
allDependencies = resolvedLibraries, allDependencies = resolvedLibraries,
friendDependencies = friendDependencies, friendDependencies = friendDependencies,
mainArguments = mainCallArguments mainArguments = mainCallArguments,
generateFullJs = !arguments.irDce,
generateDceJs = arguments.irDce
) )
outputFile.writeText(compiledModule.jsCode) val jsCode = if (arguments.irDce) compiledModule.dceJsCode!! else compiledModule.jsCode!!
outputFile.writeText(jsCode)
if (arguments.generateDts) { if (arguments.generateDts) {
val dtsFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "d.ts")!! val dtsFile = outputFile.withReplacedExtensionOrNull(outputFile.extension, "d.ts")!!
dtsFile.writeText(compiledModule.tsDefinitions ?: error("No ts definitions")) dtsFile.writeText(compiledModule.tsDefinitions ?: error("No ts definitions"))
@@ -0,0 +1,287 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.ir.backend.js
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.utils.getJsName
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.types.classOrNull
import org.jetbrains.kotlin.ir.types.classifierOrFail
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.*
import java.util.*
fun eliminateDeadDeclarations(
module: IrModuleFragment,
context: JsIrBackendContext,
mainFunction: IrSimpleFunction?
) {
val allRoots = buildRoots(module, context, mainFunction)
val usefulDeclarations = usefulDeclarations(allRoots, context)
removeUselessDeclarations(module, usefulDeclarations)
}
private fun buildRoots(module: IrModuleFragment, context: JsIrBackendContext, mainFunction: IrSimpleFunction?): Iterable<IrDeclaration> {
val rootDeclarations =
(module.files + context.packageLevelJsModules + context.externalPackageFragment.values).flatMapTo(mutableListOf()) { file ->
file.declarations.filter {
it is IrField && it.initializer != null && it.fqNameWhenAvailable?.asString()?.startsWith("kotlin") != true
|| it.isExported(context)
|| it.isEffectivelyExternal()
|| it is IrField && it.correspondingPropertySymbol?.owner?.isExported(context) == true
|| it is IrSimpleFunction && it.correspondingPropertySymbol?.owner?.isExported(context) == true
}
}
if (context.hasTests) rootDeclarations += context.testContainer
if (mainFunction != null) {
rootDeclarations += mainFunction
if (mainFunction.isSuspend) {
rootDeclarations += context.coroutineEmptyContinuation.owner
}
}
return rootDeclarations
}
private fun removeUselessDeclarations(module: IrModuleFragment, usefulDeclarations: Set<IrDeclaration>) {
module.files.forEach {
it.acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitFile(declaration: IrFile) {
process(declaration)
}
override fun visitClass(declaration: IrClass) {
process(declaration)
}
override fun visitConstructor(declaration: IrConstructor) {
if (declaration !in usefulDeclarations) {
// Keep the constructor declaration without body in order to declare the JS constructor function
declaration.body = IrBlockBodyImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, emptyList())
}
}
private fun process(container: IrDeclarationContainer) {
container.declarations.transformFlat { member ->
if (member !in usefulDeclarations && member !is IrConstructor) {
emptyList()
} else {
member.acceptVoid(this)
null
}
}
}
})
}
}
private fun Iterable<IrDeclaration>.withNested(): Iterable<IrDeclaration> {
val result = mutableListOf<IrDeclaration>()
this.forEach {
it.acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitBody(body: IrBody) {
// Skip
}
override fun visitDeclaration(declaration: IrDeclaration) {
super.visitDeclaration(declaration)
result += declaration
}
})
}
return result
}
fun usefulDeclarations(roots: Iterable<IrDeclaration>, context: JsIrBackendContext): Set<IrDeclaration> {
val queue = ArrayDeque<IrDeclaration>()
val result = hashSetOf<IrDeclaration>()
val constructedClasses = hashSetOf<IrClass>()
fun IrDeclaration.enqueue() {
if (this !in result) {
result.add(this)
queue.addLast(this)
}
}
// Add roots, including nested declarations
roots.withNested().forEach { it.enqueue() }
val toStringMethod =
context.irBuiltIns.anyClass.owner.declarations.filterIsInstance<IrFunction>().single { it.name.asString() == "toString" }
val equalsMethod =
context.irBuiltIns.anyClass.owner.declarations.filterIsInstance<IrFunction>().single { it.name.asString() == "equals" }
val hashCodeMethod =
context.irBuiltIns.anyClass.owner.declarations.filterIsInstance<IrFunction>().single { it.name.asString() == "hashCode" }
while (queue.isNotEmpty()) {
while (queue.isNotEmpty()) {
val declaration = queue.pollFirst()
if (declaration is IrClass) {
declaration.superTypes.forEach {
(it.classifierOrNull as? IrClassSymbol)?.owner?.enqueue()
}
// Special hack for `IntrinsicsJs.kt` support
if (declaration.superTypes.any { it.isSuspendFunctionTypeOrSubtype() }) {
declaration.declarations.forEach {
if (it is IrSimpleFunction && it.name.asString().startsWith("invoke")) {
it.enqueue()
}
}
}
// TODO find out how `doResume` gets removed
if (declaration.symbol == context.ir.symbols.coroutineImpl) {
declaration.declarations.forEach {
if (it is IrSimpleFunction && it.name.asString() == "doResume") {
it.enqueue()
}
}
}
}
if (declaration is IrSimpleFunction) {
declaration.resolveFakeOverride()?.enqueue()
}
// Collect instantiated classes.
if (declaration is IrConstructor) {
declaration.constructedClass.let {
it.enqueue()
constructedClasses += it
}
}
val body = when (declaration) {
is IrFunction -> declaration.body
is IrField -> declaration.initializer
is IrVariable -> declaration.initializer
else -> null
}
body?.acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitFunctionAccess(expression: IrFunctionAccessExpression) {
super.visitFunctionAccess(expression)
expression.symbol.owner.enqueue()
}
override fun visitVariableAccess(expression: IrValueAccessExpression) {
super.visitVariableAccess(expression)
expression.symbol.owner.enqueue()
}
override fun visitFieldAccess(expression: IrFieldAccessExpression) {
super.visitFieldAccess(expression)
expression.symbol.owner.enqueue()
}
override fun visitCall(expression: IrCall) {
super.visitCall(expression)
when (expression.symbol) {
context.intrinsics.jsBoxIntrinsic -> {
val inlineClass = expression.getTypeArgument(0)!!.getInlinedClass()!!
val constructor = inlineClass.declarations.filterIsInstance<IrConstructor>().single { it.isPrimary }
constructor.enqueue()
}
context.intrinsics.jsClass -> {
(expression.getTypeArgument(0)!!.classifierOrFail.owner as IrDeclaration).enqueue()
}
context.intrinsics.jsObjectCreate.symbol -> {
val classToCreate = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrClass
classToCreate.enqueue()
constructedClasses += classToCreate
}
context.intrinsics.jsEquals -> {
equalsMethod.enqueue()
}
context.intrinsics.jsToString -> {
toStringMethod.enqueue()
}
context.intrinsics.jsHashCode -> {
hashCodeMethod.enqueue()
}
context.intrinsics.jsPlus -> {
if (expression.getValueArgument(0)?.type?.classOrNull == context.irBuiltIns.stringClass) {
toStringMethod.enqueue()
}
}
}
}
override fun visitStringConcatenation(expression: IrStringConcatenation) {
super.visitStringConcatenation(expression)
toStringMethod.enqueue()
}
})
}
fun IrOverridableDeclaration<*>.overridesUsefulFunction(): Boolean {
return this.overriddenSymbols.any {
(it.owner as? IrOverridableDeclaration<*>)?.let {
it in result || it.overridesUsefulFunction()
} ?: false
}
}
for (klass in constructedClasses) {
for (declaration in klass.declarations) {
if (declaration in result) continue
if (declaration is IrOverridableDeclaration<*> && declaration.overridesUsefulFunction()) {
declaration.enqueue()
}
if (declaration is IrSimpleFunction && declaration.getJsNameOrKotlinName().asString() == "valueOf") {
declaration.enqueue()
}
// A hack to support `toJson` and other js-specific members
if (declaration.getJsName() != null ||
declaration is IrField && declaration.correspondingPropertySymbol?.owner?.getJsName() != null ||
declaration is IrSimpleFunction && declaration.correspondingPropertySymbol?.owner?.getJsName() != null
) {
declaration.enqueue()
}
}
}
}
return result
}
@@ -9,12 +9,19 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.lower.moveBodilessDeclarationsToSeparatePlace import org.jetbrains.kotlin.ir.backend.js.lower.moveBodilessDeclarationsToSeparatePlace
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
import org.jetbrains.kotlin.ir.backend.js.utils.JsMainFunctionDetector import org.jetbrains.kotlin.ir.backend.js.utils.JsMainFunctionDetector
import org.jetbrains.kotlin.ir.backend.js.utils.NameTables import org.jetbrains.kotlin.ir.backend.js.utils.NameTables
import org.jetbrains.kotlin.ir.backend.js.utils.isJsExport
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.ir.util.patchDeclarationParents import org.jetbrains.kotlin.ir.util.patchDeclarationParents
import org.jetbrains.kotlin.library.KotlinLibrary import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.resolver.KotlinLibraryResolveResult import org.jetbrains.kotlin.library.resolver.KotlinLibraryResolveResult
@@ -32,7 +39,8 @@ fun sortDependencies(dependencies: Collection<IrModuleFragment>): Collection<IrM
} }
class CompilerResult( class CompilerResult(
val jsCode: String, val jsCode: String?,
val dceJsCode: String?,
val tsDefinitions: String? = null val tsDefinitions: String? = null
) )
@@ -44,7 +52,9 @@ fun compile(
allDependencies: KotlinLibraryResolveResult, allDependencies: KotlinLibraryResolveResult,
friendDependencies: List<KotlinLibrary>, friendDependencies: List<KotlinLibrary>,
mainArguments: List<String>?, mainArguments: List<String>?,
exportedDeclarations: Set<FqName> = emptySet() exportedDeclarations: Set<FqName> = emptySet(),
generateFullJs: Boolean = true,
generateDceJs: Boolean = false
): CompilerResult { ): CompilerResult {
val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) = val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) =
loadIr(project, files, configuration, allDependencies, friendDependencies) loadIr(project, files, configuration, allDependencies, friendDependencies)
@@ -74,7 +84,7 @@ fun compile(
jsPhases.invokeToplevel(phaseConfig, context, moduleFragment) jsPhases.invokeToplevel(phaseConfig, context, moduleFragment)
val transformer = IrModuleToJsTransformer(context, mainFunction, mainArguments) val transformer = IrModuleToJsTransformer(context, mainFunction, mainArguments)
return transformer.generateModule(moduleFragment) return transformer.generateModule(moduleFragment, generateFullJs, generateDceJs)
} }
fun generateJsCode( fun generateJsCode(
@@ -86,5 +96,5 @@ fun generateJsCode(
jsPhases.invokeToplevel(PhaseConfig(jsPhases), context, moduleFragment) jsPhases.invokeToplevel(PhaseConfig(jsPhases), context, moduleFragment)
val transformer = IrModuleToJsTransformer(context, null, null, true, nameTables) val transformer = IrModuleToJsTransformer(context, null, null, true, nameTables)
return transformer.generateModule(moduleFragment).jsCode return transformer.generateModule(moduleFragment).jsCode!!
} }
@@ -45,35 +45,9 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
} }
) )
private fun getExportCandidate(declaration: IrDeclaration): IrDeclarationWithName? {
// Only actual public declarations with name can be exported
if (declaration !is IrDeclarationWithVisibility ||
declaration !is IrDeclarationWithName ||
declaration.visibility != Visibilities.PUBLIC ||
declaration.isExpect
) {
return null
}
// Workaround to get property declarations instead of its lowered accessors.
if (declaration is IrSimpleFunction) {
val property = declaration.correspondingPropertySymbol?.owner
if (property != null) {
// Return property for getter accessors only to prevent
// returning it twice (for getter and setter) in the same scope
return if (property.getter == declaration)
property
else
null
}
}
return declaration
}
private fun exportDeclaration(declaration: IrDeclaration): ExportedDeclaration? { private fun exportDeclaration(declaration: IrDeclaration): ExportedDeclaration? {
val candidate = getExportCandidate(declaration) ?: return null val candidate = getExportCandidate(declaration) ?: return null
if (!shouldDeclarationBeExported(candidate)) return null if (!shouldDeclarationBeExported(candidate, context)) return null
return when (candidate) { return when (candidate) {
is IrSimpleFunction -> exportFunction(candidate) is IrSimpleFunction -> exportFunction(candidate)
@@ -173,7 +147,7 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
for (declaration in klass.declarations) { for (declaration in klass.declarations) {
val candidate = getExportCandidate(declaration) ?: continue val candidate = getExportCandidate(declaration) ?: continue
if (!shouldDeclarationBeExported(candidate)) continue if (!shouldDeclarationBeExported(candidate, context)) continue
when (candidate) { when (candidate) {
is IrSimpleFunction -> is IrSimpleFunction ->
@@ -302,20 +276,6 @@ class ExportModelGenerator(val context: JsIrBackendContext) {
else identifier else identifier
} }
private fun shouldDeclarationBeExported(declaration: IrDeclarationWithName): Boolean {
if (declaration.fqNameWhenAvailable in context.additionalExportedDeclarations)
return true
if (declaration.isJsExport())
return true
return when (val parent = declaration.parent) {
is IrDeclarationWithName -> shouldDeclarationBeExported(parent)
is IrAnnotationContainer -> parent.isJsExport()
else -> false
}
}
private fun functionExportability(function: IrSimpleFunction): Exportability { private fun functionExportability(function: IrSimpleFunction): Exportability {
if (function.isInline && function.typeParameters.any { it.isReified }) if (function.isInline && function.typeParameters.any { it.isReified })
return Exportability.Prohibited("Inline reified function") return Exportability.Prohibited("Inline reified function")
@@ -353,6 +313,51 @@ sealed class Exportability {
private val IrClassifierSymbol.isInterface private val IrClassifierSymbol.isInterface
get() = (owner as? IrClass)?.isInterface == true get() = (owner as? IrClass)?.isInterface == true
private fun getExportCandidate(declaration: IrDeclaration): IrDeclarationWithName? {
// Only actual public declarations with name can be exported
if (declaration !is IrDeclarationWithVisibility ||
declaration !is IrDeclarationWithName ||
declaration.visibility != Visibilities.PUBLIC ||
declaration.isExpect
) {
return null
}
// Workaround to get property declarations instead of its lowered accessors.
if (declaration is IrSimpleFunction) {
val property = declaration.correspondingPropertySymbol?.owner
if (property != null) {
// Return property for getter accessors only to prevent
// returning it twice (for getter and setter) in the same scope
return if (property.getter == declaration)
property
else
null
}
}
return declaration
}
private fun shouldDeclarationBeExported(declaration: IrDeclarationWithName, context: JsIrBackendContext): Boolean {
if (declaration.fqNameWhenAvailable in context.additionalExportedDeclarations)
return true
if (declaration.isJsExport())
return true
return when (val parent = declaration.parent) {
is IrDeclarationWithName -> shouldDeclarationBeExported(parent, context)
is IrAnnotationContainer -> parent.isJsExport()
else -> false
}
}
fun IrDeclaration.isExported(context: JsIrBackendContext): Boolean {
val candidate = getExportCandidate(this) ?: return false
return shouldDeclarationBeExported(candidate, context)
}
private val reservedWords = setOf( private val reservedWords = setOf(
"break", "break",
"case", "case",
@@ -138,6 +138,7 @@ class BridgesConstruction(val context: CommonBackendContext) : ClassLoweringPass
valueParameters += bridge.valueParameters.map { p -> p.copyTo(this) } valueParameters += bridge.valueParameters.map { p -> p.copyTo(this) }
annotations += bridge.annotations annotations += bridge.annotations
overriddenSymbols.addAll(delegateTo.overriddenSymbols) overriddenSymbols.addAll(delegateTo.overriddenSymbols)
overriddenSymbols.add(bridge.symbol)
} }
context.createIrBuilder(irFunction.symbol).irBlockBody(irFunction) { context.createIrBuilder(irFunction.symbol).irBlockBody(irFunction) {
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.lower.calls
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
@@ -172,57 +173,70 @@ class NumberOperatorCallsTransformer(context: JsIrBackendContext) : CallsTransfo
else -> e else -> e
} }
private fun withLongCoercion(default: (IrFunctionAccessExpression) -> IrExpression): (IrFunctionAccessExpression) -> IrExpression = { call -> private fun withLongCoercion(default: (IrFunctionAccessExpression) -> IrExpression): (IrFunctionAccessExpression) -> IrExpression =
assert(call.valueArgumentsCount == 1) { call ->
val arg = call.getValueArgument(0)!! assert(call.valueArgumentsCount == 1)
val arg = call.getValueArgument(0)!!
if (arg.type.isLong()) { var actualCall = call
val receiverType = call.dispatchReceiver!!.type
when { if (arg.type.isLong()) {
// Double OP Long => Double OP Long.toDouble() val receiverType = call.dispatchReceiver!!.type
receiverType.isDouble() -> {
call.putValueArgument(0, IrCallImpl( when {
call.startOffset, // Double OP Long => Double OP Long.toDouble()
call.endOffset, receiverType.isDouble() -> {
intrinsics.longToDouble.owner.returnType, call.putValueArgument(0, IrCallImpl(
intrinsics.longToDouble call.startOffset,
).apply { call.endOffset,
dispatchReceiver = arg intrinsics.longToDouble.owner.returnType,
}) intrinsics.longToDouble
} ).apply {
// Float OP Long => Float OP Long.toFloat() dispatchReceiver = arg
receiverType.isFloat() -> { })
call.putValueArgument(0, IrCallImpl( }
call.startOffset, // Float OP Long => Float OP Long.toFloat()
call.endOffset, receiverType.isFloat() -> {
intrinsics.longToFloat.owner.returnType, call.putValueArgument(0, IrCallImpl(
intrinsics.longToFloat call.startOffset,
).apply { call.endOffset,
dispatchReceiver = arg intrinsics.longToFloat.owner.returnType,
}) intrinsics.longToFloat
} ).apply {
// {Byte, Short, Int} OP Long => {Byte, Sort, Int}.toLong() OP Long dispatchReceiver = arg
!receiverType.isLong() -> { })
call.dispatchReceiver = IrCallImpl( }
call.startOffset, // {Byte, Short, Int} OP Long => {Byte, Sort, Int}.toLong() OP Long
call.endOffset, !receiverType.isLong() -> {
intrinsics.jsNumberToLong.owner.returnType, call.dispatchReceiver = IrCallImpl(
intrinsics.jsNumberToLong call.startOffset,
).apply { call.endOffset,
putValueArgument(0, call.dispatchReceiver) intrinsics.jsNumberToLong.owner.returnType,
intrinsics.jsNumberToLong
).apply {
putValueArgument(0, call.dispatchReceiver)
}
// Replace {Byte, Short, Int}.OP with corresponding Long.OP
val declaration = call.symbol.owner as IrSimpleFunction
val replacement = intrinsics.longClassSymbol.owner.declarations.filterIsInstance<IrSimpleFunction>()
.single { member ->
member.name.asString() == declaration.name.asString() &&
member.valueParameters.size == declaration.valueParameters.size &&
member.valueParameters.zip(declaration.valueParameters).all { (a, b) -> a.type == b.type }
}.symbol
actualCall = irCall(call, replacement)
} }
} }
} }
}
if (call.dispatchReceiver!!.type.isLong()) { if (actualCall.dispatchReceiver!!.type.isLong()) {
// LHS is Long => use as is actualCall
call } else {
} else { default(actualCall)
default(call) }
} }
}
fun IrFunctionSymbol.call(vararg arguments: IrExpression) = fun IrFunctionSymbol.call(vararg arguments: IrExpression) =
JsIrBuilder.buildCall(this, owner.returnType).apply { JsIrBuilder.buildCall(this, owner.returnType).apply {
@@ -8,8 +8,10 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.config.CommonConfigurationKeys import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.ir.backend.js.CompilerResult import org.jetbrains.kotlin.ir.backend.js.CompilerResult
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.eliminateDeadDeclarations
import org.jetbrains.kotlin.ir.backend.js.export.ExportModelGenerator import org.jetbrains.kotlin.ir.backend.js.export.ExportModelGenerator
import org.jetbrains.kotlin.ir.backend.js.export.ExportModelToJsStatements import org.jetbrains.kotlin.ir.backend.js.export.ExportModelToJsStatements
import org.jetbrains.kotlin.ir.backend.js.export.ExportedModule
import org.jetbrains.kotlin.ir.backend.js.export.toTypeScript import org.jetbrains.kotlin.ir.backend.js.export.toTypeScript
import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering
import org.jetbrains.kotlin.ir.backend.js.utils.* import org.jetbrains.kotlin.ir.backend.js.utils.*
@@ -33,63 +35,7 @@ class IrModuleToJsTransformer(
private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!! private val moduleKind = backendContext.configuration[JSConfigurationKeys.MODULE_KIND]!!
private val generateRegionComments = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_REGION_COMMENTS) private val generateRegionComments = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_REGION_COMMENTS)
private fun generateModuleBody(module: IrModuleFragment, context: JsGenerationContext): List<JsStatement> { fun generateModule(module: IrModuleFragment, fullJs: Boolean = true, dceJs: Boolean = false): CompilerResult {
val statements = mutableListOf<JsStatement>().also {
if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt()
}
val preDeclarationBlock = JsGlobalBlock()
val postDeclarationBlock = JsGlobalBlock()
statements.addWithComment("block: pre-declaration", preDeclarationBlock)
val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH)
val pathPrefixMap = backendContext.configuration.getMap(JSConfigurationKeys.FILE_PATHS_PREFIX_MAP)
module.files.forEach {
val fileStatements = it.accept(IrFileToJsTransformer(), context).statements
if (fileStatements.isNotEmpty()) {
var startComment = ""
if (generateRegionComments) {
startComment = "region "
}
if (generateRegionComments || generateFilePaths) {
val originalPath = it.path
val path = pathPrefixMap.entries
.find { (k, _) -> originalPath.startsWith(k) }
?.let { (k, v) -> v + originalPath.substring(k.length) }
?: originalPath
startComment += "file: $path"
}
if (startComment.isNotEmpty()) {
statements.add(JsSingleLineComment(startComment))
}
statements.addAll(fileStatements)
statements.endRegion()
}
}
// sort member forwarding code
processClassModels(context.staticContext.classModels, preDeclarationBlock, postDeclarationBlock)
statements.addWithComment("block: post-declaration", postDeclarationBlock.statements)
statements.addWithComment("block: init", context.staticContext.initializerBlock.statements)
if (backendContext.hasTests) {
statements.startRegion("block: tests")
statements += JsInvocation(context.getNameForStaticFunction(backendContext.testContainer).makeRef()).makeStmt()
statements.endRegion()
}
return statements
}
fun generateModule(module: IrModuleFragment): CompilerResult {
val additionalPackages = with(backendContext) { val additionalPackages = with(backendContext) {
externalPackageFragment.values + listOf( externalPackageFragment.values + listOf(
bodilessBuiltInsPackageFragment, bodilessBuiltInsPackageFragment,
@@ -104,6 +50,17 @@ class IrModuleToJsTransformer(
namer.merge(module.files, additionalPackages) namer.merge(module.files, additionalPackages)
val jsCode = if (fullJs) generateWrappedModuleBody(module, exportedModule) else null
val dceJsCode = if (dceJs) {
eliminateDeadDeclarations(module, backendContext, mainFunction)
generateWrappedModuleBody(module, exportedModule)
} else null
return CompilerResult(jsCode, dceJsCode, dts)
}
private fun generateWrappedModuleBody(module: IrModuleFragment, exportedModule: ExportedModule): String {
val program = JsProgram() val program = JsProgram()
val nameGenerator = IrNamerImpl( val nameGenerator = IrNamerImpl(
@@ -159,7 +116,63 @@ class IrModuleToJsTransformer(
) )
} }
return CompilerResult(program.toString(), dts) return program.toString()
}
private fun generateModuleBody(module: IrModuleFragment, context: JsGenerationContext): List<JsStatement> {
val statements = mutableListOf<JsStatement>().also {
if (!generateScriptModule) it += JsStringLiteral("use strict").makeStmt()
}
val preDeclarationBlock = JsGlobalBlock()
val postDeclarationBlock = JsGlobalBlock()
statements.addWithComment("block: pre-declaration", preDeclarationBlock)
val generateFilePaths = backendContext.configuration.getBoolean(JSConfigurationKeys.GENERATE_COMMENTS_WITH_FILE_PATH)
val pathPrefixMap = backendContext.configuration.getMap(JSConfigurationKeys.FILE_PATHS_PREFIX_MAP)
module.files.forEach {
val fileStatements = it.accept(IrFileToJsTransformer(), context).statements
if (fileStatements.isNotEmpty()) {
var startComment = ""
if (generateRegionComments) {
startComment = "region "
}
if (generateRegionComments || generateFilePaths) {
val originalPath = it.path
val path = pathPrefixMap.entries
.find { (k, _) -> originalPath.startsWith(k) }
?.let { (k, v) -> v + originalPath.substring(k.length) }
?: originalPath
startComment += "file: $path"
}
if (startComment.isNotEmpty()) {
statements.add(JsSingleLineComment(startComment))
}
statements.addAll(fileStatements)
statements.endRegion()
}
}
// sort member forwarding code
processClassModels(context.staticContext.classModels, preDeclarationBlock, postDeclarationBlock)
statements.addWithComment("block: post-declaration", postDeclarationBlock.statements)
statements.addWithComment("block: init", context.staticContext.initializerBlock.statements)
if (backendContext.hasTests) {
statements.startRegion("block: tests")
statements += JsInvocation(context.getNameForStaticFunction(backendContext.testContainer).makeRef()).makeStmt()
statements.endRegion()
}
return statements
} }
private fun generateMainArguments(mainFunction: IrSimpleFunction, rootContext: JsGenerationContext): List<JsExpression> { private fun generateMainArguments(mainFunction: IrSimpleFunction, rootContext: JsGenerationContext): List<JsExpression> {
@@ -6,10 +6,8 @@
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.utils.Namer import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
@@ -84,22 +82,33 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
) )
} }
val getterRef = property.getter?.accessorRef() if (irClass.isExported(context.staticContext.backendContext) ||
val setterRef = property.setter?.accessorRef() property.getter?.overridesExternal() == true ||
classBlock.statements += JsExpressionStatement( property.getJsName() != null
defineProperty( ) {
classPrototypeRef, val getterRef = property.getter?.accessorRef()
context.getNameForProperty(property).ident, val setterRef = property.setter?.accessorRef()
getter = getterRef, classBlock.statements += JsExpressionStatement(
setter = setterRef defineProperty(
classPrototypeRef,
context.getNameForProperty(property).ident,
getter = getterRef,
setter = setterRef
)
) )
) }
} }
} }
context.staticContext.classModels[irClass.symbol] = classModel context.staticContext.classModels[irClass.symbol] = classModel
return classBlock return classBlock
} }
private fun IrSimpleFunction.overridesExternal(): Boolean {
if (this.isEffectivelyExternal()) return true
return this.overriddenSymbols.any { it.owner.overridesExternal() }
}
private fun generateMemberFunction(declaration: IrSimpleFunction): JsStatement? { private fun generateMemberFunction(declaration: IrSimpleFunction): JsStatement? {
val translatedFunction = declaration.run { if (isReal) accept(IrFunctionToJsTransformer(), context) else null } val translatedFunction = declaration.run { if (isReal) accept(IrFunctionToJsTransformer(), context) else null }
+1
View File
@@ -4,6 +4,7 @@ where advanced options include:
-Xfriend-modules=<path> Paths to friend modules -Xfriend-modules=<path> Paths to friend modules
-Xfriend-modules-disabled Disable internal declaration export -Xfriend-modules-disabled Disable internal declaration export
-Xgenerate-dts Generate TypeScript declarations .d.ts file alongside JS file. Available in IR backend only. -Xgenerate-dts Generate TypeScript declarations .d.ts file alongside JS file. Available in IR backend only.
-Xir-dce Perform experimental dead code elimination
-Xir-only Disables pre-IR backend -Xir-only Disables pre-IR backend
-Xir-produce-js Generates JS file using IR backend. Also disables pre-IR backend -Xir-produce-js Generates JS file using IR backend. Also disables pre-IR backend
-Xir-produce-klib-dir Generate unpacked KLIB into parent directory of output JS file. -Xir-produce-klib-dir Generate unpacked KLIB into parent directory of output JS file.
@@ -87,6 +87,7 @@ abstract class BasicBoxTest(
protected open val runMinifierByDefault: Boolean = false protected open val runMinifierByDefault: Boolean = false
protected open val skipMinification = getBoolean("kotlin.js.skipMinificationTest") protected open val skipMinification = getBoolean("kotlin.js.skipMinificationTest")
protected open val runIrDce: Boolean = false
protected open val incrementalCompilationChecksEnabled = true protected open val incrementalCompilationChecksEnabled = true
@@ -103,6 +104,7 @@ abstract class BasicBoxTest(
open fun doTest(filePath: String, expectedResult: String, mainCallParameters: MainCallParameters, coroutinesPackage: String = "") { open fun doTest(filePath: String, expectedResult: String, mainCallParameters: MainCallParameters, coroutinesPackage: String = "") {
val file = File(filePath) val file = File(filePath)
val outputDir = getOutputDir(file) val outputDir = getOutputDir(file)
val dceOutputDir = getOutputDir(file, testGroupOutputDirForMinification)
var fileContent = KotlinTestUtils.doLoadFile(file) var fileContent = KotlinTestUtils.doLoadFile(file)
if (coroutinesPackage.isNotEmpty()) { if (coroutinesPackage.isNotEmpty()) {
fileContent = fileContent.replace("COROUTINES_PACKAGE", coroutinesPackage) fileContent = fileContent.replace("COROUTINES_PACKAGE", coroutinesPackage)
@@ -154,10 +156,11 @@ abstract class BasicBoxTest(
val friends = module.friends.map { modules[it]?.outputFileName(outputDir) + ".meta.js" } val friends = module.friends.map { modules[it]?.outputFileName(outputDir) + ".meta.js" }
val outputFileName = module.outputFileName(outputDir) + ".js" val outputFileName = module.outputFileName(outputDir) + ".js"
val dceOutputFileName = module.outputFileName(dceOutputDir) + ".js"
val isMainModule = mainModuleName == module.name val isMainModule = mainModuleName == module.name
generateJavaScriptFile( generateJavaScriptFile(
testFactory.tmpDir, testFactory.tmpDir,
file.parent, module, outputFileName, dependencies, allDependencies, friends, modules.size > 1, file.parent, module, outputFileName, dceOutputFileName, dependencies, allDependencies, friends, modules.size > 1,
!SKIP_SOURCEMAP_REMAPPING.matcher(fileContent).find(), outputPrefixFile, outputPostfixFile, !SKIP_SOURCEMAP_REMAPPING.matcher(fileContent).find(), outputPrefixFile, outputPostfixFile,
actualMainCallParameters, testPackage, testFunction, needsFullIrRuntime, isMainModule actualMainCallParameters, testPackage, testFunction, needsFullIrRuntime, isMainModule
) )
@@ -212,6 +215,9 @@ abstract class BasicBoxTest(
val allJsFiles = additionalFiles + inputJsFiles + generatedJsFiles.map { it.first } + globalCommonFiles + localCommonFiles + val allJsFiles = additionalFiles + inputJsFiles + generatedJsFiles.map { it.first } + globalCommonFiles + localCommonFiles +
additionalCommonFiles + additionalMainFiles additionalCommonFiles + additionalMainFiles
val dceAllJsFiles = additionalFiles + inputJsFiles + generatedJsFiles.map { it.first.replace(outputDir.absolutePath, dceOutputDir.absolutePath) } +
globalCommonFiles + localCommonFiles + additionalCommonFiles + additionalMainFiles
val dontRunGeneratedCode = InTextDirectivesUtils.dontRunGeneratedCode(targetBackend, file) val dontRunGeneratedCode = InTextDirectivesUtils.dontRunGeneratedCode(targetBackend, file)
if (!dontRunGeneratedCode && generateNodeJsRunner && !SKIP_NODE_JS.matcher(fileContent).find()) { if (!dontRunGeneratedCode && generateNodeJsRunner && !SKIP_NODE_JS.matcher(fileContent).find()) {
@@ -223,6 +229,10 @@ abstract class BasicBoxTest(
if (!dontRunGeneratedCode) { if (!dontRunGeneratedCode) {
runGeneratedCode(allJsFiles, testModuleName, testPackage, testFunction, expectedResult, withModuleSystem) runGeneratedCode(allJsFiles, testModuleName, testPackage, testFunction, expectedResult, withModuleSystem)
if (runIrDce) {
runGeneratedCode(dceAllJsFiles, testModuleName, testPackage, testFunction, expectedResult, withModuleSystem)
}
} }
performAdditionalChecks(generatedJsFiles.map { it.first }, outputPrefixFile, outputPostfixFile) performAdditionalChecks(generatedJsFiles.map { it.first }, outputPrefixFile, outputPostfixFile)
@@ -340,6 +350,7 @@ abstract class BasicBoxTest(
directory: String, directory: String,
module: TestModule, module: TestModule,
outputFileName: String, outputFileName: String,
dceOutputFileName: String,
dependencies: List<String>, dependencies: List<String>,
allDependencies: List<String>, allDependencies: List<String>,
friends: List<String>, friends: List<String>,
@@ -369,10 +380,11 @@ abstract class BasicBoxTest(
val sourceDirs = (testFiles + additionalFiles).map { File(it).parent }.distinct() val sourceDirs = (testFiles + additionalFiles).map { File(it).parent }.distinct()
val config = createConfig(sourceDirs, module, dependencies, allDependencies, friends, multiModule, tmpDir, incrementalData = null) val config = createConfig(sourceDirs, module, dependencies, allDependencies, friends, multiModule, tmpDir, incrementalData = null)
val outputFile = File(outputFileName) val outputFile = File(outputFileName)
val dceOutputFile = File(dceOutputFileName)
val incrementalData = IncrementalData() val incrementalData = IncrementalData()
translateFiles( translateFiles(
psiFiles.map(TranslationUnit::SourceFile), outputFile, config, outputPrefixFile, outputPostfixFile, psiFiles.map(TranslationUnit::SourceFile), outputFile, dceOutputFile, config, outputPrefixFile, outputPostfixFile,
mainCallParameters, incrementalData, remap, testPackage, testFunction, needsFullIrRuntime, isMainModule mainCallParameters, incrementalData, remap, testPackage, testFunction, needsFullIrRuntime, isMainModule
) )
@@ -423,7 +435,7 @@ abstract class BasicBoxTest(
val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js") val recompiledOutputFile = File(outputFile.parentFile, outputFile.nameWithoutExtension + "-recompiled.js")
translateFiles( translateFiles(
translationUnits, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile, translationUnits, recompiledOutputFile, recompiledOutputFile, recompiledConfig, outputPrefixFile, outputPostfixFile,
mainCallParameters, incrementalData, remap, testPackage, testFunction, needsFullIrRuntime, false mainCallParameters, incrementalData, remap, testPackage, testFunction, needsFullIrRuntime, false
) )
@@ -485,6 +497,7 @@ abstract class BasicBoxTest(
protected open fun translateFiles( protected open fun translateFiles(
units: List<TranslationUnit>, units: List<TranslationUnit>,
outputFile: File, outputFile: File,
dceOutputFile: File,
config: JsConfig, config: JsConfig,
outputPrefixFile: File?, outputPrefixFile: File?,
outputPostfixFile: File?, outputPostfixFile: File?,
@@ -46,6 +46,8 @@ abstract class BasicIrBoxTest(
override val skipMinification = true override val skipMinification = true
override val runIrDce: Boolean = true
// TODO Design incremental compilation for IR and add test support // TODO Design incremental compilation for IR and add test support
override val incrementalCompilationChecksEnabled = false override val incrementalCompilationChecksEnabled = false
@@ -62,6 +64,7 @@ abstract class BasicIrBoxTest(
override fun translateFiles( override fun translateFiles(
units: List<TranslationUnit>, units: List<TranslationUnit>,
outputFile: File, outputFile: File,
dceOutputFile: File,
config: JsConfig, config: JsConfig,
outputPrefixFile: File?, outputPrefixFile: File?,
outputPostfixFile: File?, outputPostfixFile: File?,
@@ -118,12 +121,19 @@ abstract class BasicIrBoxTest(
allDependencies = resolvedLibraries, allDependencies = resolvedLibraries,
friendDependencies = emptyList(), friendDependencies = emptyList(),
mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null }, mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null },
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))) exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))),
generateFullJs = true,
generateDceJs = runIrDce
) )
val wrappedCode = wrapWithModuleEmulationMarkers(compiledModule.jsCode, moduleId = config.moduleId, moduleKind = config.moduleKind) val wrappedCode = wrapWithModuleEmulationMarkers(compiledModule.jsCode!!, moduleId = config.moduleId, moduleKind = config.moduleKind)
outputFile.write(wrappedCode) outputFile.write(wrappedCode)
compiledModule.dceJsCode?.let { dceJsCode ->
val dceWrappedCode = wrapWithModuleEmulationMarkers(dceJsCode, moduleId = config.moduleId, moduleKind = config.moduleKind)
dceOutputFile.write(dceWrappedCode)
}
if (generateDts) { if (generateDts) {
val dtsFile = outputFile.withReplacedExtensionOrNull("_v5.js", ".d.ts") val dtsFile = outputFile.withReplacedExtensionOrNull("_v5.js", ".d.ts")
dtsFile?.write(compiledModule.tsDefinitions ?: error("No ts definitions")) dtsFile?.write(compiledModule.tsDefinitions ?: error("No ts definitions"))
+1 -1
View File
@@ -3,5 +3,5 @@ package kotlin
// see StdLibTestBase.removeAdHocAssertions // see StdLibTestBase.removeAdHocAssertions
fun fail(message: String? = null): Nothing { fun fail(message: String? = null): Nothing {
throw Exception(message) throw Throwable(message)
} }
@@ -1,5 +1,7 @@
// EXPECTED_REACHABLE_NODES: 1276 // EXPECTED_REACHABLE_NODES: 1276
// IGNORE_BACKEND: JS_IR // IGNORE_BACKEND: JS_IR
@JsExport
class A(val x: Char) class A(val x: Char)
fun typeOf(x: dynamic): String = js("typeof x") fun typeOf(x: dynamic): String = js("typeof x")
@@ -4,6 +4,7 @@ interface I {
val a: Char val a: Char
} }
@JsExport
object X : I { object X : I {
override var a = '#' override var a = '#'
} }
@@ -1,5 +1,7 @@
// EXPECTED_REACHABLE_NODES: 1289 // EXPECTED_REACHABLE_NODES: 1289
// IGNORE_BACKEND: JS_IR // IGNORE_BACKEND: JS_IR
@JsExport
open class A { open class A {
val foo: Char val foo: Char
get() = 'X' get() = 'X'
@@ -3,6 +3,7 @@ package foo
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
@JsExport
class A { class A {
@JsName("xx") val x: Int by B(23) @JsName("xx") val x: Int by B(23)
@@ -1,5 +1,6 @@
// EXPECTED_REACHABLE_NODES: 1290 // EXPECTED_REACHABLE_NODES: 1290
@JsExport
object A { object A {
@JsName("js_method") fun f() = "method" @JsName("js_method") fun f() = "method"
+1
View File
@@ -1,5 +1,6 @@
// EXPECTED_REACHABLE_NODES: 1291 // EXPECTED_REACHABLE_NODES: 1291
@JsExport
object A { object A {
@JsName("js_f") fun f(x: Int) = "f($x)" @JsName("js_f") fun f(x: Int) = "f($x)"
+1 -1
View File
@@ -13,7 +13,7 @@ fun box(): String {
assertEquals(a.foo, undefined) assertEquals(a.foo, undefined)
assertNotEquals(a.toString, undefined) assertNotEquals(a.toString, undefined)
val b: dynamic = object {val bar = ""} val b: dynamic = object {@JsName("bar") val bar = ""}
assertEquals(b.foo, undefined) assertEquals(b.foo, undefined)
assertNotEquals(b.bar, undefined) assertNotEquals(b.bar, undefined)
@@ -7,6 +7,7 @@ fun deinitialize(foo: dynamic) {
} }
class Foo { class Foo {
@JsName("bar")
lateinit var bar: String lateinit var bar: String
fun test(): String { fun test(): String {
@@ -55,7 +55,7 @@ fun hashCode(obj: dynamic): Int {
} }
} }
private var POW_2_32 = 4294967296 private var POW_2_32 = 4294967296.0
private var OBJECT_HASH_CODE_PROPERTY_NAME = "kotlinHashCodeValue$" private var OBJECT_HASH_CODE_PROPERTY_NAME = "kotlinHashCodeValue$"
fun getObjectHashCode(obj: dynamic): Int { fun getObjectHashCode(obj: dynamic): Int {
@@ -83,7 +83,9 @@ fun identityHashCode(obj: Any?): Int = getObjectHashCode(obj)
internal fun captureStack(instance: Throwable) { internal fun captureStack(instance: Throwable) {
if (js("Error").captureStackTrace != null) { if (js("Error").captureStackTrace != null) {
js("Error").captureStackTrace(instance, instance::class.js) // TODO Why we generated get kclass for throwable in original code?
js("Error").captureStackTrace(instance, instance.asDynamic().constructor)
// js("Error").captureStackTrace(instance, instance::class.js)
} else { } else {
instance.asDynamic().stack = js("new Error()").stack instance.asDynamic().stack = js("new Error()").stack
} }
+1 -1
View File
@@ -65,7 +65,7 @@ internal open class BufferedOutput : BaseOutput() {
internal class BufferedOutputToConsoleLog : BufferedOutput() { internal class BufferedOutputToConsoleLog : BufferedOutput() {
override fun print(message: Any?) { override fun print(message: Any?) {
var s = String(message) var s = String(message)
val i = s.lastIndexOf('\n') val i = s.nativeLastIndexOf("\n", 0)
if (i >= 0) { if (i >= 0) {
buffer += s.substring(0, i) buffer += s.substring(0, i)
flush() flush()