JS IR: custom JS AST serialization
This commit is contained in:
committed by
Space Team
parent
978f937fa9
commit
698be0fddd
+6
-7
@@ -6,17 +6,18 @@
|
|||||||
package org.jetbrains.kotlin.ir.backend.js.ic
|
package org.jetbrains.kotlin.ir.backend.js.ic
|
||||||
|
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.serialization.JsIrAstSerializer
|
import org.jetbrains.kotlin.ir.backend.js.utils.serialization.serializeTo
|
||||||
|
import org.jetbrains.kotlin.konan.file.use
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
internal sealed class SourceFileCacheArtifact(val srcFile: KotlinSourceFile, val binaryAstFile: File) {
|
internal sealed class SourceFileCacheArtifact(val srcFile: KotlinSourceFile, val binaryAstFile: File) {
|
||||||
abstract fun commitMetadata()
|
abstract fun commitMetadata()
|
||||||
|
|
||||||
fun commitBinaryAst(fragment: JsIrProgramFragment, serializer: JsIrAstSerializer) {
|
fun commitBinaryAst(fragment: JsIrProgramFragment) {
|
||||||
binaryAstFile.recreate()
|
binaryAstFile.recreate()
|
||||||
BufferedOutputStream(binaryAstFile.outputStream()).use { bufferedOutStream ->
|
BufferedOutputStream(binaryAstFile.outputStream()).use {
|
||||||
serializer.serialize(fragment, bufferedOutStream)
|
fragment.serializeTo(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +62,10 @@ internal class IncrementalCacheArtifact(
|
|||||||
moduleName: String,
|
moduleName: String,
|
||||||
rebuiltFileFragments: Map<KotlinSourceFile, JsIrProgramFragment>,
|
rebuiltFileFragments: Map<KotlinSourceFile, JsIrProgramFragment>,
|
||||||
): ModuleArtifact {
|
): ModuleArtifact {
|
||||||
val serializer = JsIrAstSerializer()
|
|
||||||
|
|
||||||
val fileArtifacts = srcCacheActions.map { srcFileAction ->
|
val fileArtifacts = srcCacheActions.map { srcFileAction ->
|
||||||
val rebuiltFileFragment = rebuiltFileFragments[srcFileAction.srcFile]
|
val rebuiltFileFragment = rebuiltFileFragments[srcFileAction.srcFile]
|
||||||
if (rebuiltFileFragment != null) {
|
if (rebuiltFileFragment != null) {
|
||||||
srcFileAction.commitBinaryAst(rebuiltFileFragment, serializer)
|
srcFileAction.commitBinaryAst(rebuiltFileFragment)
|
||||||
}
|
}
|
||||||
srcFileAction.commitMetadata()
|
srcFileAction.commitMetadata()
|
||||||
SrcFileArtifact(srcFileAction.srcFile.path, rebuiltFileFragment, srcFileAction.binaryAstFile)
|
SrcFileArtifact(srcFileAction.srcFile.path, rebuiltFileFragment, srcFileAction.binaryAstFile)
|
||||||
|
|||||||
@@ -8,19 +8,16 @@ package org.jetbrains.kotlin.ir.backend.js.ic
|
|||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrModule
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrModule
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeModuleName
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.safeModuleName
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.serialization.JsIrAstDeserializer
|
import org.jetbrains.kotlin.ir.backend.js.utils.serialization.deserializeJsIrProgramFragment
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class SrcFileArtifact(val srcFilePath: String, private val fragment: JsIrProgramFragment?, private val astArtifact: File? = null) {
|
class SrcFileArtifact(val srcFilePath: String, private val fragment: JsIrProgramFragment?, private val astArtifact: File? = null) {
|
||||||
fun loadJsIrFragment(deserializer: JsIrAstDeserializer): JsIrProgramFragment? {
|
fun loadJsIrFragment(): JsIrProgramFragment? {
|
||||||
if (fragment != null) {
|
if (fragment != null) {
|
||||||
return fragment
|
return fragment
|
||||||
}
|
}
|
||||||
return astArtifact?.ifExists { readBytes() }?.let {
|
return astArtifact?.ifExists { readBytes() }?.let {
|
||||||
ByteArrayInputStream(it).use { byteStream ->
|
deserializeJsIrProgramFragment(it)
|
||||||
deserializer.deserialize(byteStream)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,8 +35,7 @@ class ModuleArtifact(
|
|||||||
val moduleExternalName = externalModuleName ?: moduleSafeName
|
val moduleExternalName = externalModuleName ?: moduleSafeName
|
||||||
|
|
||||||
fun loadJsIrModule(): JsIrModule {
|
fun loadJsIrModule(): JsIrModule {
|
||||||
val deserializer = JsIrAstDeserializer()
|
val fragments = fileArtifacts.sortedBy { it.srcFilePath }.mapNotNull { it.loadJsIrFragment() }
|
||||||
val fragments = fileArtifacts.sortedBy { it.srcFilePath }.mapNotNull { it.loadJsIrFragment(deserializer) }
|
|
||||||
return JsIrModule(moduleSafeName, moduleExternalName, fragments)
|
return JsIrModule(moduleSafeName, moduleExternalName, fragments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 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.utils.serialization
|
||||||
|
|
||||||
|
// TODO: is it OK to just use UTF_8?
|
||||||
|
val SerializationCharset = Charsets.UTF_8
|
||||||
|
|
||||||
|
object StatementIds {
|
||||||
|
const val RETURN = 0
|
||||||
|
const val THROW = 1
|
||||||
|
const val BREAK = 2
|
||||||
|
const val CONTINUE = 3
|
||||||
|
const val DEBUGGER = 4
|
||||||
|
const val EXPRESSION = 5
|
||||||
|
const val VARS = 6
|
||||||
|
const val BLOCK = 7
|
||||||
|
const val COMPOSITE_BLOCK = 8
|
||||||
|
const val LABEL = 9
|
||||||
|
const val IF = 10
|
||||||
|
const val SWITCH = 11
|
||||||
|
const val WHILE = 12
|
||||||
|
const val DO_WHILE = 13
|
||||||
|
const val FOR = 14
|
||||||
|
const val FOR_IN = 15
|
||||||
|
const val TRY = 16
|
||||||
|
const val EMPTY = 17
|
||||||
|
const val SINGLE_LINE_COMMENT = 18
|
||||||
|
const val MULTI_LINE_COMMENT = 19
|
||||||
|
}
|
||||||
|
|
||||||
|
object ExpressionIds {
|
||||||
|
const val THIS_REF = 0
|
||||||
|
const val NULL = 1
|
||||||
|
const val TRUE_LITERAL = 2
|
||||||
|
const val FALSE_LITERAL = 3
|
||||||
|
const val STRING_LITERAL = 4
|
||||||
|
const val REG_EXP = 5
|
||||||
|
const val INT_LITERAL = 6
|
||||||
|
const val DOUBLE_LITERAL = 7
|
||||||
|
const val ARRAY_LITERAL = 8
|
||||||
|
const val OBJECT_LITERAL = 9
|
||||||
|
const val FUNCTION = 10
|
||||||
|
const val DOC_COMMENT = 11
|
||||||
|
const val BINARY_OPERATION = 12
|
||||||
|
const val PREFIX_OPERATION = 13
|
||||||
|
const val POSTFIX_OPERATION = 14
|
||||||
|
const val CONDITIONAL = 15
|
||||||
|
const val ARRAY_ACCESS = 16
|
||||||
|
const val NAME_REFERENCE = 17
|
||||||
|
const val SIMPLE_NAME_REFERENCE = 18
|
||||||
|
const val PROPERTY_REFERENCE = 19
|
||||||
|
const val INVOCATION = 20
|
||||||
|
const val NEW = 21
|
||||||
|
}
|
||||||
+449
-92
@@ -11,97 +11,454 @@ import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragmen
|
|||||||
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
|
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsImportedModule
|
import org.jetbrains.kotlin.js.backend.ast.JsImportedModule
|
||||||
import org.jetbrains.kotlin.protobuf.CodedInputStream
|
import org.jetbrains.kotlin.js.backend.ast.metadata.*
|
||||||
import org.jetbrains.kotlin.serialization.js.ast.JsAstDeserializerBase
|
import org.jetbrains.kotlin.js.backend.ast.metadata.LocalAlias
|
||||||
import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.*
|
import org.jetbrains.kotlin.js.backend.ast.metadata.SpecialFunction
|
||||||
import java.io.InputStream
|
import java.util.*
|
||||||
|
import java.util.ArrayDeque
|
||||||
|
|
||||||
class JsIrAstDeserializer : JsAstDeserializerBase() {
|
fun deserializeJsIrProgramFragment(input: ByteArray): JsIrProgramFragment {
|
||||||
override val scope = emptyScope
|
return JsIrAstDeserializer(input).readFragment()
|
||||||
|
}
|
||||||
fun deserialize(input: InputStream): JsIrProgramFragment {
|
|
||||||
return deserialize(Chunk.parseFrom(CodedInputStream.newInstance(input).apply { setRecursionLimit(4096) }))
|
private class JsIrAstDeserializer(private val source: ByteArray) {
|
||||||
}
|
|
||||||
|
private var offset = 0
|
||||||
fun deserialize(proto: Chunk): JsIrProgramFragment {
|
|
||||||
stringTable += proto.stringTable.entryList
|
private val stringTable = readArray { readString() }
|
||||||
nameTable += proto.nameTable.entryList
|
private val nameTable = readArray { readName() }
|
||||||
nameCache += nameTable.map { null }
|
|
||||||
try {
|
private val scope = emptyScope
|
||||||
return deserialize(proto.fragment)
|
private val fileStack: Deque<String> = ArrayDeque()
|
||||||
} finally {
|
|
||||||
stringTable.clear()
|
private fun readByte(): Byte {
|
||||||
nameTable.clear()
|
return source[offset++]
|
||||||
nameCache.clear()
|
}
|
||||||
}
|
|
||||||
}
|
private fun readBoolean(): Boolean {
|
||||||
|
return readByte() != 0.toByte()
|
||||||
private fun deserialize(proto: Fragment): JsIrProgramFragment {
|
}
|
||||||
val fragment = JsIrProgramFragment(proto.packageFqn)
|
|
||||||
|
private fun readInt(): Int {
|
||||||
fragment.importedModules += proto.importedModuleList.map { importedModuleProto ->
|
var result = readByte().toInt() // no need to mask the highest bit
|
||||||
JsImportedModule(
|
result = (result shl 8) or (readByte().toInt() and 0xFF)
|
||||||
deserializeString(importedModuleProto.externalNameId),
|
result = (result shl 8) or (readByte().toInt() and 0xFF)
|
||||||
deserializeName(importedModuleProto.internalNameId),
|
result = (result shl 8) or (readByte().toInt() and 0xFF)
|
||||||
if (importedModuleProto.hasPlainReference()) deserialize(importedModuleProto.plainReference) else null
|
return result
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
private fun readLong(): Long {
|
||||||
proto.importEntryList.associateTo(fragment.imports) { importProto ->
|
var result = readByte().toLong() // no need to mask the highest bit
|
||||||
deserializeString(importProto.signatureId) to deserialize(importProto.expression)
|
result = (result shl 8) or (readByte().toLong() and 0xFF)
|
||||||
}
|
result = (result shl 8) or (readByte().toLong() and 0xFF)
|
||||||
|
result = (result shl 8) or (readByte().toLong() and 0xFF)
|
||||||
if (proto.hasDeclarationBlock()) {
|
result = (result shl 8) or (readByte().toLong() and 0xFF)
|
||||||
fragment.declarations.statements += deserializeCompositeBlock(proto.declarationBlock).statements
|
result = (result shl 8) or (readByte().toLong() and 0xFF)
|
||||||
}
|
result = (result shl 8) or (readByte().toLong() and 0xFF)
|
||||||
if (proto.hasInitializerBlock()) {
|
result = (result shl 8) or (readByte().toLong() and 0xFF)
|
||||||
fragment.initializers.statements += deserializeCompositeBlock(proto.initializerBlock).statements
|
return result
|
||||||
}
|
}
|
||||||
if (proto.hasExportBlock()) {
|
|
||||||
fragment.exports.statements += deserializeCompositeBlock(proto.exportBlock).statements
|
private fun readDouble(): Double {
|
||||||
}
|
return Double.fromBits(readLong())
|
||||||
if (proto.hasPolyfills()) {
|
}
|
||||||
fragment.polyfills.statements += deserializeCompositeBlock(proto.polyfills).statements
|
|
||||||
}
|
private fun readString(): String {
|
||||||
|
val length = readInt()
|
||||||
proto.nameBindingList.associateTo(fragment.nameBindings) { nameBindingProto ->
|
val result = String(source, offset, length, SerializationCharset)
|
||||||
deserializeString(nameBindingProto.signatureId) to deserializeName(nameBindingProto.nameId)
|
offset += length
|
||||||
}
|
return result
|
||||||
|
}
|
||||||
proto.optionalCrossModuleImportsList.mapTo(fragment.optionalCrossModuleImports) { deserializeString(it) }
|
|
||||||
|
private inline fun <reified T> readArray(readElement: () -> T): Array<T> {
|
||||||
proto.irClassModelList.associateTo(fragment.classes) { clsProto -> deserialize(clsProto) }
|
return Array<T>(readInt()) { readElement() }
|
||||||
|
}
|
||||||
if (proto.hasTestsInvocation()) {
|
|
||||||
fragment.testFunInvocation = deserialize(proto.testsInvocation)
|
private inline fun readRepeated(readElement: () -> Unit) {
|
||||||
}
|
var length = readInt()
|
||||||
|
while (length-- > 0) {
|
||||||
if (proto.hasMainInvocation()) {
|
readElement()
|
||||||
fragment.mainFunction = deserialize(proto.mainInvocation)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proto.hasSuiteFunction()) {
|
private inline fun <T> readList(readElement: () -> T): List<T> {
|
||||||
fragment.suiteFn = deserializeName(proto.suiteFunction)
|
val length = readInt()
|
||||||
}
|
val result = ArrayList<T>(length)
|
||||||
|
for (i in 0 until length) {
|
||||||
if (proto.hasDts()) {
|
result.add(readElement())
|
||||||
fragment.dts = TypeScriptFragment(proto.dts)
|
}
|
||||||
}
|
return result
|
||||||
fragment.definitions += proto.definitionsList.map { deserializeString(it) }
|
}
|
||||||
|
|
||||||
return fragment
|
private inline fun <T> ifTrue(then: () -> T): T? {
|
||||||
}
|
return if (readBoolean()) then() else null
|
||||||
|
}
|
||||||
private fun deserialize(proto: IrClassModel): Pair<JsName, JsIrIcClassModel> {
|
|
||||||
return deserializeName(proto.nameId) to JsIrIcClassModel(proto.superClassesList.map { deserializeName(it) }).apply {
|
fun readFragment(): JsIrProgramFragment {
|
||||||
if (proto.hasPreDeclarationBlock()) {
|
return JsIrProgramFragment(readString()).apply {
|
||||||
preDeclarationBlock.statements += deserializeCompositeBlock(proto.preDeclarationBlock).statements
|
readRepeated {
|
||||||
}
|
importedModules += JsImportedModule(
|
||||||
if (proto.hasPostDeclarationBlock()) {
|
externalName = stringTable[readInt()],
|
||||||
postDeclarationBlock.statements += deserializeCompositeBlock(proto.postDeclarationBlock).statements
|
internalName = nameTable[readInt()],
|
||||||
}
|
plainReference = ifTrue { readExpression() }
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun embedSources(deserializedLocation: JsLocation, file: String): JsLocationWithSource? = null
|
readRepeated { imports[stringTable[readInt()]] = readExpression() }
|
||||||
|
|
||||||
|
readRepeated { declarations.statements += readStatement() }
|
||||||
|
readRepeated { initializers.statements += readStatement() }
|
||||||
|
readRepeated { exports.statements += readStatement() }
|
||||||
|
readRepeated { polyfills.statements += readStatement() }
|
||||||
|
|
||||||
|
readRepeated { nameBindings[stringTable[readInt()]] = nameTable[readInt()] }
|
||||||
|
readRepeated { optionalCrossModuleImports.add(stringTable[readInt()]) }
|
||||||
|
readRepeated { classes[nameTable[readInt()]] = readIrIcClassModel() }
|
||||||
|
|
||||||
|
ifTrue { testFunInvocation = readStatement() }
|
||||||
|
ifTrue { mainFunction = readStatement() }
|
||||||
|
ifTrue { dts = TypeScriptFragment(readString()) }
|
||||||
|
ifTrue { suiteFn = nameTable[readInt()] }
|
||||||
|
|
||||||
|
readRepeated { definitions += stringTable[readInt()] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readIrIcClassModel(): JsIrIcClassModel {
|
||||||
|
return JsIrIcClassModel(readList { nameTable[readInt()] }).apply {
|
||||||
|
readRepeated { preDeclarationBlock.statements += readStatement() }
|
||||||
|
readRepeated { postDeclarationBlock.statements += readStatement() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readStatement(): JsStatement {
|
||||||
|
return withComments {
|
||||||
|
withLocation {
|
||||||
|
with(StatementIds) {
|
||||||
|
when (val id = readByte().toInt()) {
|
||||||
|
RETURN -> {
|
||||||
|
JsReturn(ifTrue { readExpression() })
|
||||||
|
}
|
||||||
|
THROW -> {
|
||||||
|
JsThrow(readExpression())
|
||||||
|
}
|
||||||
|
BREAK -> {
|
||||||
|
JsBreak(ifTrue { JsNameRef(nameTable[readInt()]) })
|
||||||
|
}
|
||||||
|
CONTINUE -> {
|
||||||
|
JsContinue(ifTrue { JsNameRef(nameTable[readInt()]) })
|
||||||
|
}
|
||||||
|
DEBUGGER -> {
|
||||||
|
JsDebugger()
|
||||||
|
}
|
||||||
|
EXPRESSION -> {
|
||||||
|
JsExpressionStatement(readExpression()).apply {
|
||||||
|
ifTrue { exportedTag = stringTable[readInt()] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VARS -> {
|
||||||
|
readVars()
|
||||||
|
}
|
||||||
|
BLOCK -> {
|
||||||
|
JsBlock().apply {
|
||||||
|
readRepeated { statements += readStatement() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
COMPOSITE_BLOCK -> {
|
||||||
|
readCompositeBlock()
|
||||||
|
}
|
||||||
|
LABEL -> {
|
||||||
|
JsLabel(nameTable[readInt()], readStatement())
|
||||||
|
}
|
||||||
|
IF -> {
|
||||||
|
JsIf(readExpression(), readStatement(), ifTrue { readStatement() })
|
||||||
|
}
|
||||||
|
SWITCH -> {
|
||||||
|
JsSwitch(
|
||||||
|
readExpression(),
|
||||||
|
readList {
|
||||||
|
withLocation {
|
||||||
|
ifTrue {
|
||||||
|
JsCase().apply { caseExpression = readExpression() }
|
||||||
|
} ?: JsDefault()
|
||||||
|
}.apply {
|
||||||
|
readRepeated {
|
||||||
|
statements += readStatement()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
WHILE -> {
|
||||||
|
JsWhile(readExpression(), readStatement())
|
||||||
|
}
|
||||||
|
DO_WHILE -> {
|
||||||
|
JsDoWhile(readExpression(), readStatement())
|
||||||
|
}
|
||||||
|
FOR -> {
|
||||||
|
val condition = ifTrue { readExpression() }
|
||||||
|
val incrementExpression = ifTrue { readExpression() }
|
||||||
|
val body = ifTrue { readStatement() }
|
||||||
|
|
||||||
|
ifTrue {
|
||||||
|
JsFor(
|
||||||
|
readVars(),
|
||||||
|
condition,
|
||||||
|
incrementExpression,
|
||||||
|
body
|
||||||
|
)
|
||||||
|
} ?: JsFor(
|
||||||
|
ifTrue { readExpression() },
|
||||||
|
condition,
|
||||||
|
incrementExpression,
|
||||||
|
body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
FOR_IN -> {
|
||||||
|
JsForIn(
|
||||||
|
ifTrue { nameTable[readInt()] },
|
||||||
|
ifTrue { readExpression() },
|
||||||
|
readExpression(),
|
||||||
|
readStatement()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
TRY -> {
|
||||||
|
JsTry(
|
||||||
|
readBlock(),
|
||||||
|
readList {
|
||||||
|
JsCatch(nameTable[readInt()]).apply {
|
||||||
|
body = readBlock()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ifTrue { readBlock() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
EMPTY -> {
|
||||||
|
JsEmpty
|
||||||
|
}
|
||||||
|
SINGLE_LINE_COMMENT -> {
|
||||||
|
JsSingleLineComment(readString())
|
||||||
|
}
|
||||||
|
MULTI_LINE_COMMENT -> {
|
||||||
|
JsMultiLineComment(readString())
|
||||||
|
}
|
||||||
|
else -> error("Unknown statement id: $id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.apply {
|
||||||
|
synthetic = readBoolean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val sideEffectKindValues = SideEffectKind.values()
|
||||||
|
private val jsBinaryOperatorValues = JsBinaryOperator.values()
|
||||||
|
private val jsUnaryOperatorValues = JsUnaryOperator.values()
|
||||||
|
|
||||||
|
private fun readExpression(): JsExpression {
|
||||||
|
return withComments {
|
||||||
|
withLocation {
|
||||||
|
with(ExpressionIds) {
|
||||||
|
when (val id = readByte().toInt()) {
|
||||||
|
THIS_REF -> {
|
||||||
|
JsThisRef()
|
||||||
|
}
|
||||||
|
NULL -> {
|
||||||
|
JsNullLiteral()
|
||||||
|
}
|
||||||
|
TRUE_LITERAL -> {
|
||||||
|
JsBooleanLiteral(true)
|
||||||
|
}
|
||||||
|
FALSE_LITERAL -> {
|
||||||
|
JsBooleanLiteral(false)
|
||||||
|
}
|
||||||
|
STRING_LITERAL -> {
|
||||||
|
JsStringLiteral(stringTable[readInt()])
|
||||||
|
}
|
||||||
|
REG_EXP -> {
|
||||||
|
JsRegExp().apply {
|
||||||
|
pattern = stringTable[readInt()]
|
||||||
|
ifTrue { flags = stringTable[readInt()] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
INT_LITERAL -> {
|
||||||
|
JsIntLiteral(readInt())
|
||||||
|
}
|
||||||
|
DOUBLE_LITERAL -> {
|
||||||
|
JsDoubleLiteral(readDouble())
|
||||||
|
}
|
||||||
|
ARRAY_LITERAL -> {
|
||||||
|
JsArrayLiteral(readList { readExpression() })
|
||||||
|
}
|
||||||
|
OBJECT_LITERAL -> {
|
||||||
|
JsObjectLiteral(
|
||||||
|
readList { JsPropertyInitializer(readExpression(), readExpression()) },
|
||||||
|
readBoolean()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
FUNCTION -> {
|
||||||
|
JsFunction(scope, readBlock(), "").apply {
|
||||||
|
readRepeated { parameters += readParameter() }
|
||||||
|
ifTrue { name = nameTable[readInt()] }
|
||||||
|
isLocal = readBoolean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DOC_COMMENT -> {
|
||||||
|
val tags = hashMapOf<String, Any>()
|
||||||
|
readRepeated {
|
||||||
|
tags[stringTable[readInt()]] = ifTrue { readExpression() } ?: stringTable[readInt()]
|
||||||
|
}
|
||||||
|
JsDocComment(tags)
|
||||||
|
}
|
||||||
|
BINARY_OPERATION -> {
|
||||||
|
JsBinaryOperation(
|
||||||
|
jsBinaryOperatorValues[readInt()],
|
||||||
|
readExpression(),
|
||||||
|
readExpression()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
PREFIX_OPERATION -> {
|
||||||
|
JsPrefixOperation(jsUnaryOperatorValues[readInt()], readExpression())
|
||||||
|
}
|
||||||
|
POSTFIX_OPERATION -> {
|
||||||
|
JsPostfixOperation(jsUnaryOperatorValues[readInt()], readExpression())
|
||||||
|
}
|
||||||
|
CONDITIONAL -> {
|
||||||
|
JsConditional(
|
||||||
|
readExpression(),
|
||||||
|
readExpression(),
|
||||||
|
readExpression()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ARRAY_ACCESS -> {
|
||||||
|
JsArrayAccess(readExpression(), readExpression())
|
||||||
|
}
|
||||||
|
NAME_REFERENCE -> {
|
||||||
|
JsNameRef(nameTable[readInt()]).apply {
|
||||||
|
ifTrue { qualifier = readExpression() }
|
||||||
|
ifTrue { isInline = readBoolean() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SIMPLE_NAME_REFERENCE -> {
|
||||||
|
JsNameRef(nameTable[readInt()])
|
||||||
|
}
|
||||||
|
PROPERTY_REFERENCE -> {
|
||||||
|
JsNameRef(stringTable[readInt()]).apply {
|
||||||
|
ifTrue { qualifier = readExpression() }
|
||||||
|
ifTrue { isInline = readBoolean() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
INVOCATION -> {
|
||||||
|
JsInvocation(readExpression(), readList { readExpression() }).apply {
|
||||||
|
ifTrue { isInline = readBoolean() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NEW -> {
|
||||||
|
JsNew(readExpression(), readList { readExpression() })
|
||||||
|
}
|
||||||
|
else -> error("Unknown expression id: $id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.apply {
|
||||||
|
synthetic = readBoolean()
|
||||||
|
sideEffects = sideEffectKindValues[readInt()]
|
||||||
|
ifTrue { localAlias = readJsImportedModule() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readJsImportedModule(): JsImportedModule {
|
||||||
|
return JsImportedModule(
|
||||||
|
stringTable[readInt()],
|
||||||
|
nameTable[readInt()],
|
||||||
|
ifTrue { readExpression() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readParameter(): JsParameter {
|
||||||
|
return JsParameter(nameTable[readInt()]).apply {
|
||||||
|
hasDefaultValue = readBoolean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readCompositeBlock(): JsCompositeBlock {
|
||||||
|
return JsCompositeBlock().apply {
|
||||||
|
readRepeated { statements += readStatement() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readBlock(): JsBlock {
|
||||||
|
return ifTrue { readCompositeBlock() } ?: JsBlock().apply {
|
||||||
|
readRepeated { statements += readStatement() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readVars(): JsVars {
|
||||||
|
return JsVars(readBoolean()).apply {
|
||||||
|
readRepeated {
|
||||||
|
vars += withLocation {
|
||||||
|
JsVars.JsVar(nameTable[readInt()], ifTrue { readExpression() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ifTrue { exportedPackage = stringTable[readInt()] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val specialFunctionValues = SpecialFunction.values()
|
||||||
|
|
||||||
|
private fun readName(): JsName {
|
||||||
|
val identifier = stringTable[readInt()]
|
||||||
|
val name = ifTrue {
|
||||||
|
JsScope.declareTemporaryName(identifier)
|
||||||
|
} ?: JsDynamicScope.declareName(identifier)
|
||||||
|
ifTrue { name.localAlias = readLocalAlias() }
|
||||||
|
ifTrue { name.imported = true }
|
||||||
|
ifTrue { name.specialFunction = specialFunctionValues[readInt()] }
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readLocalAlias(): LocalAlias {
|
||||||
|
return LocalAlias(
|
||||||
|
nameTable[readInt()],
|
||||||
|
ifTrue { stringTable[readInt()] }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readComment(): JsComment {
|
||||||
|
val text = readString()
|
||||||
|
return ifTrue { JsMultiLineComment(text) } ?: JsSingleLineComment(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <T : JsNode> withLocation(action: () -> T): T {
|
||||||
|
return ifTrue {
|
||||||
|
val deserializedFile = ifTrue { stringTable[readInt()] }
|
||||||
|
val file = deserializedFile ?: fileStack.peek()
|
||||||
|
|
||||||
|
val startLine = readInt()
|
||||||
|
val startChar = readInt()
|
||||||
|
val deserializedLocation = file?.let { JsLocation(it, startLine, startChar) }
|
||||||
|
|
||||||
|
val shouldUpdateFile = deserializedFile != null && deserializedFile != fileStack.peek()
|
||||||
|
|
||||||
|
if (shouldUpdateFile) {
|
||||||
|
fileStack.push(deserializedFile)
|
||||||
|
}
|
||||||
|
val node = action()
|
||||||
|
if (deserializedLocation != null) {
|
||||||
|
node.source = deserializedLocation
|
||||||
|
}
|
||||||
|
if (shouldUpdateFile) {
|
||||||
|
fileStack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
node
|
||||||
|
} ?: action()
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <T : JsNode> withComments(action: () -> T): T {
|
||||||
|
return action().apply {
|
||||||
|
ifTrue { this.commentsBeforeNode = readArray { readComment() }.toList() }
|
||||||
|
ifTrue { this.commentsAfterNode = readArray { readComment() }.toList() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+557
-94
@@ -5,114 +5,577 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.ir.backend.js.utils.serialization
|
package org.jetbrains.kotlin.ir.backend.js.utils.serialization
|
||||||
|
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrClassModel
|
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrIcClassModel
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrIcClassModel
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
import org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.*
|
import org.jetbrains.kotlin.js.backend.ast.JsImportedModule
|
||||||
import org.jetbrains.kotlin.serialization.js.ast.JsAstSerializerBase
|
import org.jetbrains.kotlin.js.backend.ast.metadata.*
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
|
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.DataOutputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import java.util.*
|
||||||
|
import java.util.ArrayDeque
|
||||||
|
|
||||||
class JsIrAstSerializer: JsAstSerializerBase() {
|
fun JsIrProgramFragment.serializeTo(output: OutputStream) {
|
||||||
|
JsIrAstSerializer().append(this).saveTo(output)
|
||||||
|
}
|
||||||
|
|
||||||
fun serialize(fragment: JsIrProgramFragment, output: OutputStream) {
|
private class DataWriter {
|
||||||
importedNames.clear()
|
|
||||||
importedNames += fragment.imports.map { fragment.nameBindings[it.key]!! }
|
|
||||||
serialize(fragment).writeTo(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun serialize(fragment: JsIrProgramFragment): Chunk {
|
val data = ByteArrayOutputStream()
|
||||||
try {
|
private val output = DataOutputStream(data)
|
||||||
val chunkBuilder = Chunk.newBuilder()
|
|
||||||
chunkBuilder.fragment = serializeFragment(fragment)
|
fun saveTo(output: DataOutputStream) {
|
||||||
chunkBuilder.nameTable = nameTableBuilder.build()
|
output.let {
|
||||||
chunkBuilder.stringTable = stringTableBuilder.build()
|
data.writeTo(it)
|
||||||
return chunkBuilder.build()
|
|
||||||
} finally {
|
|
||||||
nameTableBuilder.clear()
|
|
||||||
stringTableBuilder.clear()
|
|
||||||
nameMap.clear()
|
|
||||||
stringMap.clear()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun serializeFragment(fragment: JsIrProgramFragment): Fragment {
|
fun writeByte(byte: Int) {
|
||||||
val fragmentBuilder = Fragment.newBuilder()
|
// Limit bytes to positive values to avoid conversion in deserializer
|
||||||
|
if ((byte and 0x7F.inv()) != 0) error("Byte out of bounds: $byte")
|
||||||
fragmentBuilder.packageFqn = fragment.packageFqn
|
output.writeByte(byte)
|
||||||
|
|
||||||
for (importedModule in fragment.importedModules) {
|
|
||||||
val importedModuleBuilder = ImportedModule.newBuilder()
|
|
||||||
importedModuleBuilder.externalNameId = serialize(importedModule.externalName)
|
|
||||||
importedModuleBuilder.internalNameId = serialize(importedModule.internalName)
|
|
||||||
importedModule.plainReference?.let { importedModuleBuilder.plainReference = serialize(it) }
|
|
||||||
fragmentBuilder.addImportedModule(importedModuleBuilder)
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((signature, expression) in fragment.imports) {
|
|
||||||
val importBuilder = Import.newBuilder()
|
|
||||||
importBuilder.signatureId = serialize(signature)
|
|
||||||
importBuilder.expression = serialize(expression)
|
|
||||||
fragmentBuilder.addImportEntry(importBuilder)
|
|
||||||
}
|
|
||||||
|
|
||||||
fragmentBuilder.declarationBlock = serializeBlock(fragment.declarations)
|
|
||||||
fragmentBuilder.initializerBlock = serializeBlock(fragment.initializers)
|
|
||||||
fragmentBuilder.exportBlock = serializeBlock(fragment.exports)
|
|
||||||
fragmentBuilder.polyfills = serializeBlock(fragment.polyfills)
|
|
||||||
|
|
||||||
for ((key, name) in fragment.nameBindings.entries) {
|
|
||||||
val nameBindingBuilder = NameBinding.newBuilder()
|
|
||||||
nameBindingBuilder.signatureId = serialize(key)
|
|
||||||
nameBindingBuilder.nameId = serialize(name)
|
|
||||||
fragmentBuilder.addNameBinding(nameBindingBuilder)
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment.optionalCrossModuleImports.forEach {
|
|
||||||
fragmentBuilder.addOptionalCrossModuleImports(serialize(it))
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment.classes.entries.forEach { (name, model) -> fragmentBuilder.addIrClassModel(serialize(name, model)) }
|
|
||||||
|
|
||||||
fragment.testFunInvocation?.let {
|
|
||||||
fragmentBuilder.setTestsInvocation(serialize(it))
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment.mainFunction?.let {
|
|
||||||
fragmentBuilder.setMainInvocation(serialize(it))
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment.dts?.let {
|
|
||||||
fragmentBuilder.dts = it.raw
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment.suiteFn?.let {
|
|
||||||
fragmentBuilder.setSuiteFunction(serialize(it))
|
|
||||||
}
|
|
||||||
|
|
||||||
fragment.definitions.forEach {
|
|
||||||
fragmentBuilder.addDefinitions(serialize(it))
|
|
||||||
}
|
|
||||||
|
|
||||||
return fragmentBuilder.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun serialize(name: JsName, classModel: JsIrIcClassModel): IrClassModel {
|
fun writeByteArray(byteArray: ByteArray) {
|
||||||
val builder = IrClassModel.newBuilder()
|
output.writeInt(byteArray.size)
|
||||||
builder.nameId = serialize(name)
|
output.write(byteArray)
|
||||||
classModel.superClasses.forEach { builder.addSuperClasses(serialize(it)) }
|
|
||||||
if (classModel.preDeclarationBlock.statements.isNotEmpty()) {
|
|
||||||
builder.preDeclarationBlock = serializeBlock(classModel.preDeclarationBlock)
|
|
||||||
}
|
|
||||||
if (classModel.postDeclarationBlock.statements.isNotEmpty()) {
|
|
||||||
builder.postDeclarationBlock = serializeBlock(classModel.postDeclarationBlock)
|
|
||||||
}
|
|
||||||
return builder.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun extractLocation(node: JsNode): JsLocation? {
|
fun writeString(string: String) {
|
||||||
return node.source.safeAs<JsLocationWithSource>()?.asSimpleLocation()
|
writeByteArray(string.toByteArray(SerializationCharset))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeBoolean(boolean: Boolean) {
|
||||||
|
output.writeBoolean(boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeInt(int: Int) {
|
||||||
|
output.writeInt(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeDouble(double: Double) {
|
||||||
|
output.writeDouble(double)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> writeCollection(collection: Collection<T>, writeItem: (T) -> Unit) {
|
||||||
|
output.writeInt(collection.size)
|
||||||
|
collection.forEach(writeItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <T> ifNotNull(t: T?, write: (T) -> Unit): T? {
|
||||||
|
output.writeBoolean(t != null)
|
||||||
|
if (t != null) {
|
||||||
|
write(t)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun ifTrue(condition: Boolean, write: () -> Unit) {
|
||||||
|
output.writeBoolean(condition)
|
||||||
|
if (condition) {
|
||||||
|
write()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class JsIrAstSerializer {
|
||||||
|
|
||||||
|
private val fragmentSerializer = DataWriter()
|
||||||
|
private val nameSerializer = DataWriter()
|
||||||
|
private val stringSerializer = DataWriter()
|
||||||
|
|
||||||
|
private val nameMap = mutableMapOf<JsName, Int>()
|
||||||
|
private val stringMap = mutableMapOf<String, Int>()
|
||||||
|
private val fileStack: Deque<String> = ArrayDeque()
|
||||||
|
private val importedNames = mutableSetOf<JsName>()
|
||||||
|
|
||||||
|
fun append(fragment: JsIrProgramFragment): JsIrAstSerializer {
|
||||||
|
importedNames += fragment.imports.map { fragment.nameBindings[it.key]!! }
|
||||||
|
fragmentSerializer.writeFragment(fragment)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveTo(rawOutput: OutputStream) {
|
||||||
|
DataOutputStream(rawOutput).use {
|
||||||
|
it.writeInt(stringMap.size)
|
||||||
|
stringSerializer.saveTo(it)
|
||||||
|
|
||||||
|
it.writeInt(nameMap.size)
|
||||||
|
nameSerializer.saveTo(it)
|
||||||
|
|
||||||
|
fragmentSerializer.saveTo(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataWriter.writeFragment(fragment: JsIrProgramFragment) {
|
||||||
|
writeString(fragment.packageFqn)
|
||||||
|
|
||||||
|
writeCollection(fragment.importedModules) {
|
||||||
|
writeInt(internalizeString(it.externalName))
|
||||||
|
writeInt(internalizeName(it.internalName))
|
||||||
|
ifNotNull(it.plainReference) {
|
||||||
|
writeExpression(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCollection(fragment.imports.entries) { (signatureId, expression) ->
|
||||||
|
writeInt(internalizeString(signatureId))
|
||||||
|
writeExpression(expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCompositeBlock(fragment.declarations)
|
||||||
|
writeCompositeBlock(fragment.initializers)
|
||||||
|
writeCompositeBlock(fragment.exports)
|
||||||
|
writeCompositeBlock(fragment.polyfills)
|
||||||
|
|
||||||
|
writeCollection(fragment.nameBindings.entries) { (key, name) ->
|
||||||
|
writeInt(internalizeString(key))
|
||||||
|
writeInt(internalizeName(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCollection(fragment.optionalCrossModuleImports) {
|
||||||
|
writeInt(internalizeString(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCollection(fragment.classes.entries) { (name, model) ->
|
||||||
|
writeInt(internalizeName(name))
|
||||||
|
writeIrIcModel(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
ifNotNull(fragment.testFunInvocation) {
|
||||||
|
writeStatement(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
ifNotNull(fragment.mainFunction) {
|
||||||
|
writeStatement(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
ifNotNull(fragment.dts) {
|
||||||
|
writeString(it.raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
ifNotNull(fragment.suiteFn) {
|
||||||
|
writeInt(internalizeName(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCollection(fragment.definitions) {
|
||||||
|
writeInt(internalizeString(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataWriter.writeIrIcModel(classModel: JsIrIcClassModel) {
|
||||||
|
writeCollection(classModel.superClasses) {
|
||||||
|
writeInt(internalizeName(it))
|
||||||
|
}
|
||||||
|
writeCompositeBlock(classModel.preDeclarationBlock)
|
||||||
|
writeCompositeBlock(classModel.postDeclarationBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataWriter.writeStatement(statement: JsStatement) {
|
||||||
|
val visitor = object : JsVisitor() {
|
||||||
|
override fun visitReturn(x: JsReturn) {
|
||||||
|
writeByte(StatementIds.RETURN)
|
||||||
|
ifNotNull(x.expression) {
|
||||||
|
writeExpression(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitThrow(x: JsThrow) {
|
||||||
|
writeByte(StatementIds.THROW)
|
||||||
|
writeExpression(x.expression)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitBreak(x: JsBreak) {
|
||||||
|
writeByte(StatementIds.BREAK)
|
||||||
|
ifNotNull(x.label) {
|
||||||
|
writeInt(internalizeName(it.name!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitContinue(x: JsContinue) {
|
||||||
|
writeByte(StatementIds.CONTINUE)
|
||||||
|
ifNotNull(x.label) {
|
||||||
|
writeInt(internalizeName(it.name!!))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitDebugger(x: JsDebugger) {
|
||||||
|
writeByte(StatementIds.DEBUGGER)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitExpressionStatement(x: JsExpressionStatement) {
|
||||||
|
writeByte(StatementIds.EXPRESSION)
|
||||||
|
writeExpression(x.expression)
|
||||||
|
ifNotNull(x.exportedTag) { writeInt(internalizeString(it)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitVars(x: JsVars) {
|
||||||
|
writeByte(StatementIds.VARS)
|
||||||
|
writeVars(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitBlock(x: JsBlock) {
|
||||||
|
if (x is JsCompositeBlock) {
|
||||||
|
writeByte(StatementIds.COMPOSITE_BLOCK)
|
||||||
|
writeCompositeBlock(x)
|
||||||
|
} else {
|
||||||
|
writeByte(StatementIds.BLOCK)
|
||||||
|
writeCollection(x.statements) {
|
||||||
|
writeStatement(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitLabel(x: JsLabel) {
|
||||||
|
writeByte(StatementIds.LABEL)
|
||||||
|
writeInt(internalizeName(x.name))
|
||||||
|
writeStatement(x.statement)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitIf(x: JsIf) {
|
||||||
|
writeByte(StatementIds.IF)
|
||||||
|
writeExpression(x.ifExpression)
|
||||||
|
writeStatement(x.thenStatement)
|
||||||
|
ifNotNull(x.elseStatement) { writeStatement(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(x: JsSwitch) {
|
||||||
|
writeByte(StatementIds.SWITCH)
|
||||||
|
writeExpression(x.expression)
|
||||||
|
writeCollection(x.cases) { case ->
|
||||||
|
withLocation(case) {
|
||||||
|
ifNotNull(case as? JsCase) {
|
||||||
|
writeExpression(it.caseExpression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeCollection(case.statements) { part ->
|
||||||
|
writeStatement(part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitWhile(x: JsWhile) {
|
||||||
|
writeByte(StatementIds.WHILE)
|
||||||
|
writeExpression(x.condition)
|
||||||
|
writeStatement(x.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitDoWhile(x: JsDoWhile) {
|
||||||
|
writeByte(StatementIds.DO_WHILE)
|
||||||
|
writeExpression(x.condition)
|
||||||
|
writeStatement(x.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFor(x: JsFor) {
|
||||||
|
writeByte(StatementIds.FOR)
|
||||||
|
ifNotNull(x.condition) { writeExpression(it) }
|
||||||
|
ifNotNull(x.incrementExpression) { writeExpression(it) }
|
||||||
|
ifNotNull(x.body) { writeStatement(it) }
|
||||||
|
|
||||||
|
ifNotNull(x.initVars) {
|
||||||
|
writeVars(it)
|
||||||
|
} ?: ifNotNull(x.initExpression) {
|
||||||
|
writeExpression(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitForIn(x: JsForIn) {
|
||||||
|
writeByte(StatementIds.FOR_IN)
|
||||||
|
ifNotNull(x.iterVarName) {
|
||||||
|
writeInt(internalizeName(it))
|
||||||
|
}
|
||||||
|
ifNotNull(x.iterExpression) { writeExpression(it) }
|
||||||
|
writeExpression(x.objectExpression)
|
||||||
|
writeStatement(x.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitTry(x: JsTry) {
|
||||||
|
writeByte(StatementIds.TRY)
|
||||||
|
writeBlock(x.tryBlock)
|
||||||
|
writeCollection(x.catches) { c ->
|
||||||
|
writeInt(internalizeName(c.parameter.name))
|
||||||
|
writeBlock(c.body)
|
||||||
|
}
|
||||||
|
ifNotNull(x.finallyBlock) { writeBlock(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitEmpty(x: JsEmpty) {
|
||||||
|
writeByte(StatementIds.EMPTY)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitSingleLineComment(comment: JsSingleLineComment) {
|
||||||
|
writeByte(StatementIds.SINGLE_LINE_COMMENT)
|
||||||
|
writeString(comment.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitMultiLineComment(comment: JsMultiLineComment) {
|
||||||
|
writeByte(StatementIds.MULTI_LINE_COMMENT)
|
||||||
|
writeString(comment.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitElement(node: JsNode) {
|
||||||
|
error("Unknown statement type: ${statement::class.qualifiedName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
withComments(statement) {
|
||||||
|
withLocation(statement) {
|
||||||
|
statement.accept(visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeBoolean((statement as HasMetadata).synthetic)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataWriter.writeExpression(expression: JsExpression) {
|
||||||
|
val visitor = object : JsVisitor() {
|
||||||
|
|
||||||
|
override fun visitThis(x: JsThisRef) {
|
||||||
|
writeByte(ExpressionIds.THIS_REF)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitNull(x: JsNullLiteral) {
|
||||||
|
writeByte(ExpressionIds.NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitBoolean(x: JsBooleanLiteral) {
|
||||||
|
if (x.value) {
|
||||||
|
writeByte(ExpressionIds.TRUE_LITERAL)
|
||||||
|
} else {
|
||||||
|
writeByte(ExpressionIds.FALSE_LITERAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitString(x: JsStringLiteral) {
|
||||||
|
writeByte(ExpressionIds.STRING_LITERAL)
|
||||||
|
writeInt(internalizeString(x.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitRegExp(x: JsRegExp) {
|
||||||
|
writeByte(ExpressionIds.REG_EXP)
|
||||||
|
writeInt(internalizeString(x.pattern))
|
||||||
|
ifNotNull(x.flags) { writeInt(internalizeString(it)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitInt(x: JsIntLiteral) {
|
||||||
|
writeByte(ExpressionIds.INT_LITERAL)
|
||||||
|
writeInt(x.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitDouble(x: JsDoubleLiteral) {
|
||||||
|
writeByte(ExpressionIds.DOUBLE_LITERAL)
|
||||||
|
writeDouble(x.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitArray(x: JsArrayLiteral) {
|
||||||
|
writeByte(ExpressionIds.ARRAY_LITERAL)
|
||||||
|
writeCollection(x.expressions) {
|
||||||
|
writeExpression(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitObjectLiteral(x: JsObjectLiteral) {
|
||||||
|
writeByte(ExpressionIds.OBJECT_LITERAL)
|
||||||
|
writeCollection(x.propertyInitializers) {
|
||||||
|
writeExpression(it.labelExpr)
|
||||||
|
writeExpression(it.valueExpr)
|
||||||
|
}
|
||||||
|
writeBoolean(x.isMultiline)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitFunction(x: JsFunction) {
|
||||||
|
writeByte(ExpressionIds.FUNCTION)
|
||||||
|
writeBlock(x.body)
|
||||||
|
writeCollection(x.parameters) { writeParameter(it) }
|
||||||
|
ifNotNull(x.name) {
|
||||||
|
writeInt(internalizeName(it))
|
||||||
|
}
|
||||||
|
writeBoolean(x.isLocal)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitDocComment(comment: JsDocComment) {
|
||||||
|
writeByte(ExpressionIds.DOC_COMMENT)
|
||||||
|
writeCollection(comment.tags.entries) { (name, value) ->
|
||||||
|
writeInt(internalizeString(name))
|
||||||
|
|
||||||
|
ifNotNull(value as? JsNameRef) {
|
||||||
|
writeExpression(it)
|
||||||
|
} ?: if (value is String) {
|
||||||
|
writeInt(internalizeString(value))
|
||||||
|
} else {
|
||||||
|
error("Unsupported tag: ${value::class.qualifiedName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitBinaryExpression(x: JsBinaryOperation) {
|
||||||
|
writeByte(ExpressionIds.BINARY_OPERATION)
|
||||||
|
writeInt(x.operator.ordinal)
|
||||||
|
writeExpression(x.arg1)
|
||||||
|
writeExpression(x.arg2)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitPrefixOperation(x: JsPrefixOperation) {
|
||||||
|
writeByte(ExpressionIds.PREFIX_OPERATION)
|
||||||
|
writeInt(x.operator.ordinal)
|
||||||
|
writeExpression(x.arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitPostfixOperation(x: JsPostfixOperation) {
|
||||||
|
writeByte(ExpressionIds.POSTFIX_OPERATION)
|
||||||
|
writeInt(x.operator.ordinal)
|
||||||
|
writeExpression(x.arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitConditional(x: JsConditional) {
|
||||||
|
writeByte(ExpressionIds.CONDITIONAL)
|
||||||
|
writeExpression(x.testExpression)
|
||||||
|
writeExpression(x.thenExpression)
|
||||||
|
writeExpression(x.elseExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitArrayAccess(x: JsArrayAccess) {
|
||||||
|
writeByte(ExpressionIds.ARRAY_ACCESS)
|
||||||
|
writeExpression(x.arrayExpression)
|
||||||
|
writeExpression(x.indexExpression)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitNameRef(nameRef: JsNameRef) {
|
||||||
|
val name = nameRef.name
|
||||||
|
val qualifier = nameRef.qualifier
|
||||||
|
if (name != null) {
|
||||||
|
if (qualifier != null || nameRef.isInline == true) {
|
||||||
|
writeByte(ExpressionIds.NAME_REFERENCE)
|
||||||
|
writeInt(internalizeName(name))
|
||||||
|
ifNotNull(qualifier) { writeExpression(it) }
|
||||||
|
ifNotNull(nameRef.isInline) { writeBoolean(it) }
|
||||||
|
} else {
|
||||||
|
writeByte(ExpressionIds.SIMPLE_NAME_REFERENCE)
|
||||||
|
writeInt(internalizeName(name))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
writeByte(ExpressionIds.PROPERTY_REFERENCE)
|
||||||
|
writeInt(internalizeString(nameRef.ident))
|
||||||
|
ifNotNull(qualifier) { writeExpression(it) }
|
||||||
|
ifNotNull(nameRef.isInline) { writeBoolean(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitInvocation(invocation: JsInvocation) {
|
||||||
|
writeByte(ExpressionIds.INVOCATION)
|
||||||
|
writeExpression(invocation.qualifier)
|
||||||
|
writeCollection(invocation.arguments) { writeExpression(it) }
|
||||||
|
ifNotNull(invocation.isInline) { writeBoolean(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitNew(x: JsNew) {
|
||||||
|
writeByte(ExpressionIds.NEW)
|
||||||
|
writeExpression(x.constructorExpression)
|
||||||
|
writeCollection(x.arguments) { writeExpression(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
withComments(expression) {
|
||||||
|
withLocation(expression) {
|
||||||
|
expression.accept(visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeBoolean(expression.synthetic)
|
||||||
|
writeInt(expression.sideEffects.ordinal)
|
||||||
|
ifNotNull(expression.localAlias) { writeImportedModule(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataWriter.writeImportedModule(module: JsImportedModule) {
|
||||||
|
writeInt(internalizeString(module.externalName))
|
||||||
|
writeInt(internalizeName(module.internalName))
|
||||||
|
ifNotNull(module.plainReference) { writeExpression(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataWriter.writeParameter(parameter: JsParameter) {
|
||||||
|
writeInt(internalizeName(parameter.name))
|
||||||
|
writeBoolean(parameter.hasDefaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataWriter.writeCompositeBlock(block: JsCompositeBlock) {
|
||||||
|
writeCollection(block.statements) { writeStatement(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataWriter.writeBlock(block: JsBlock) {
|
||||||
|
writeBoolean(block is JsCompositeBlock)
|
||||||
|
writeCollection(block.statements) { writeStatement(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataWriter.writeVars(vars: JsVars) {
|
||||||
|
writeBoolean(vars.isMultiline)
|
||||||
|
writeCollection(vars.vars) { varDecl ->
|
||||||
|
withLocation(varDecl) {
|
||||||
|
writeInt(internalizeName(varDecl.name))
|
||||||
|
ifNotNull(varDecl.initExpression) { writeExpression(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ifNotNull(vars.exportedPackage) { writeInt(internalizeString(it)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun internalizeName(name: JsName): Int = nameMap.getOrPut(name) {
|
||||||
|
// Make sure all dependant JsName's are already serialized.
|
||||||
|
name.localAlias?.let { internalizeName(it.name) }
|
||||||
|
|
||||||
|
nameSerializer.apply {
|
||||||
|
writeInt(internalizeString(name.ident))
|
||||||
|
writeBoolean(name.isTemporary)
|
||||||
|
ifNotNull(name.localAlias) { writeLocalAlias(it) }
|
||||||
|
writeBoolean(name.imported && name !in importedNames)
|
||||||
|
ifNotNull(name.specialFunction) { writeInt(it.ordinal) }
|
||||||
|
}
|
||||||
|
nameMap.size
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataWriter.writeLocalAlias(alias: LocalAlias) {
|
||||||
|
writeInt(internalizeName(alias.name))
|
||||||
|
ifNotNull(alias.tag) { writeInt(internalizeString(it)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun internalizeString(string: String) = stringMap.getOrPut(string) {
|
||||||
|
stringSerializer.writeString(string)
|
||||||
|
stringMap.size
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DataWriter.writeComment(comment: JsComment) {
|
||||||
|
writeString(comment.text)
|
||||||
|
when (comment) {
|
||||||
|
is JsSingleLineComment -> writeBoolean(false)
|
||||||
|
is JsMultiLineComment -> writeBoolean(true)
|
||||||
|
else -> error("Unknown type of comment ${comment.javaClass.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun DataWriter.withLocation(node: JsNode, inner: () -> Unit) {
|
||||||
|
val location = node.source.safeAs<JsLocationWithSource>()?.asSimpleLocation()
|
||||||
|
ifNotNull(location) {
|
||||||
|
val lastFile = fileStack.peek()
|
||||||
|
val newFile = it.file
|
||||||
|
val fileChanged = lastFile != newFile
|
||||||
|
ifTrue(fileChanged) {
|
||||||
|
writeInt(internalizeString(newFile))
|
||||||
|
fileStack.push(it.file)
|
||||||
|
}
|
||||||
|
writeInt(it.startLine)
|
||||||
|
writeInt(it.startChar)
|
||||||
|
|
||||||
|
inner()
|
||||||
|
|
||||||
|
if (fileChanged) {
|
||||||
|
fileStack.pop()
|
||||||
|
}
|
||||||
|
} ?: inner()
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun DataWriter.withComments(node: JsNode, inner: () -> Unit) {
|
||||||
|
inner()
|
||||||
|
ifNotNull(node.commentsBeforeNode) { comments -> writeCollection(comments) { writeComment(it) } }
|
||||||
|
ifNotNull(node.commentsAfterNode) { comments -> writeCollection(comments) { writeComment(it) } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
|
|||||||
import org.jetbrains.kotlin.ir.backend.js.WholeWorldStageController
|
import org.jetbrains.kotlin.ir.backend.js.WholeWorldStageController
|
||||||
import org.jetbrains.kotlin.ir.backend.js.ic.*
|
import org.jetbrains.kotlin.ir.backend.js.ic.*
|
||||||
import org.jetbrains.kotlin.ir.backend.js.moduleName
|
import org.jetbrains.kotlin.ir.backend.js.moduleName
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.serialization.JsIrAstDeserializer
|
import org.jetbrains.kotlin.ir.backend.js.utils.serialization.serializeTo
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.serialization.JsIrAstSerializer
|
import org.jetbrains.kotlin.ir.backend.js.utils.serialization.deserializeJsIrProgramFragment
|
||||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
|
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImplForJsIC
|
||||||
import org.jetbrains.kotlin.js.test.handlers.JsBoxRunner
|
import org.jetbrains.kotlin.js.test.handlers.JsBoxRunner
|
||||||
import org.jetbrains.kotlin.konan.properties.propertyList
|
import org.jetbrains.kotlin.konan.properties.propertyList
|
||||||
@@ -24,13 +24,11 @@ import org.jetbrains.kotlin.test.services.TestServices
|
|||||||
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
|
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
|
||||||
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
|
import org.jetbrains.kotlin.test.services.configuration.JsEnvironmentConfigurator
|
||||||
import org.jetbrains.kotlin.test.services.jsLibraryProvider
|
import org.jetbrains.kotlin.test.services.jsLibraryProvider
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
private class TestArtifactCache(val moduleName: String, val binaryAsts: MutableMap<String, ByteArray> = mutableMapOf()) {
|
private class TestArtifactCache(val moduleName: String, val binaryAsts: MutableMap<String, ByteArray> = mutableMapOf()) {
|
||||||
fun fetchArtifacts(): ModuleArtifact {
|
fun fetchArtifacts(): ModuleArtifact {
|
||||||
val deserializer = JsIrAstDeserializer()
|
|
||||||
return ModuleArtifact(
|
return ModuleArtifact(
|
||||||
moduleName = moduleName,
|
moduleName = moduleName,
|
||||||
fileArtifacts = binaryAsts.entries.map {
|
fileArtifacts = binaryAsts.entries.map {
|
||||||
@@ -39,7 +37,7 @@ private class TestArtifactCache(val moduleName: String, val binaryAsts: MutableM
|
|||||||
// TODO: It will be better to use saved fragments, but it doesn't work
|
// TODO: It will be better to use saved fragments, but it doesn't work
|
||||||
// Merger.merge() + JsNode.resolveTemporaryNames() modify fragments,
|
// Merger.merge() + JsNode.resolveTemporaryNames() modify fragments,
|
||||||
// therefore the sequential calls produce different results
|
// therefore the sequential calls produce different results
|
||||||
fragment = deserializer.deserialize(ByteArrayInputStream(it.value))
|
fragment = deserializeJsIrProgramFragment(it.value)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -149,11 +147,10 @@ class JsIrIncrementalDataProvider(private val testServices: TestServices) : Test
|
|||||||
|
|
||||||
val moduleCache = icCache[canonicalPath] ?: TestArtifactCache(mainModuleIr.name.asString())
|
val moduleCache = icCache[canonicalPath] ?: TestArtifactCache(mainModuleIr.name.asString())
|
||||||
|
|
||||||
val serializer = JsIrAstSerializer()
|
|
||||||
for (rebuiltFile in rebuiltFiles) {
|
for (rebuiltFile in rebuiltFiles) {
|
||||||
if (rebuiltFile.first.module == mainModuleIr) {
|
if (rebuiltFile.first.module == mainModuleIr) {
|
||||||
val output = ByteArrayOutputStream()
|
val output = ByteArrayOutputStream()
|
||||||
serializer.serialize(rebuiltFile.second, output)
|
rebuiltFile.second.serializeTo(output)
|
||||||
moduleCache.binaryAsts[rebuiltFile.first.fileEntry.name] = output.toByteArray()
|
moduleCache.binaryAsts[rebuiltFile.first.fileEntry.name] = output.toByteArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user