[WASM] Initial infrastructure

- New module ":compiler:backend.wasm"
    - Initial compiler infra (driver, phaser, context)
    - Subset of Wasm AST
    - Skeleton of IR -> Wasm AST
    - Wasm AST -> WAT transformer

- Testing infra

- SpiderMonkey jsshell tool
This commit is contained in:
Svyatoslav Kuzmich
2019-08-07 14:27:18 +03:00
parent 812083ee05
commit 6e6ffa12a6
106 changed files with 6418 additions and 584 deletions
+26
View File
@@ -0,0 +1,26 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
compile(project(":compiler:util"))
compile(project(":compiler:frontend"))
compile(project(":compiler:backend-common"))
compile(project(":compiler:ir.tree"))
compile(project(":compiler:ir.psi2ir"))
compile(project(":compiler:ir.backend.common"))
compile(project(":compiler:ir.serialization.common"))
compile(project(":compiler:ir.serialization.js"))
compile(project(":js:js.ast"))
compile(project(":js:js.frontend"))
compile(project(":compiler:backend.js"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
}
sourceSets {
"main" { projectDefault() }
"test" {}
}
@@ -0,0 +1,91 @@
/*
* 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.backend.wasm
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.ir.Ir
import org.jetbrains.kotlin.backend.common.ir.Symbols
import org.jetbrains.kotlin.backend.js.JsDeclarationFactory
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.backend.js.JsSharedVariablesManager
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrExternalPackageFragmentSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrExternalPackageFragmentSymbolImpl
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.name.FqName
class WasmBackendContext(
val module: ModuleDescriptor,
override val irBuiltIns: IrBuiltIns,
symbolTable: SymbolTable,
irModuleFragment: IrModuleFragment,
val additionalExportedDeclarations: Set<FqName>,
override val configuration: CompilerConfiguration
) : CommonBackendContext {
override val builtIns = module.builtIns
override var inVerbosePhase: Boolean = false
// Place to store declarations excluded from code generation
val excludedDeclarations: IrPackageFragment by lazy {
IrExternalPackageFragmentImpl(
DescriptorlessExternalPackageFragmentSymbol(),
FqName("kotlin")
)
}
override val declarationFactory = JsDeclarationFactory()
val objectToGetInstanceFunction = mutableMapOf<IrClassSymbol, IrSimpleFunction>()
override val internalPackageFqn = FqName("kotlin.wasm")
private val internalPackageFragment = IrExternalPackageFragmentImpl(
IrExternalPackageFragmentSymbolImpl(
EmptyPackageFragmentDescriptor(builtIns.builtInsModule, FqName("kotlin.wasm.internal"))
)
)
override val sharedVariablesManager = JsSharedVariablesManager(irBuiltIns, internalPackageFragment)
val wasmSymbols: WasmSymbols = WasmSymbols(this@WasmBackendContext, symbolTable)
override val ir = object : Ir<WasmBackendContext>(this, irModuleFragment) {
override val symbols: Symbols<WasmBackendContext> = wasmSymbols
override fun shouldGenerateHandlerParameterForDefaultBodyFun() = true
}
override fun log(message: () -> String) {
/*TODO*/
if (inVerbosePhase) print(message())
}
override fun report(element: IrElement?, irFile: IrFile?, message: String, isError: Boolean) {
/*TODO*/
print(message)
}
}
class DescriptorlessExternalPackageFragmentSymbol : IrExternalPackageFragmentSymbol {
override val descriptor: PackageFragmentDescriptor
get() = error("Operation is unsupported")
private var _owner: IrExternalPackageFragment? = null
override val owner get() = _owner!!
override val isBound get() = _owner != null
override fun bind(owner: IrExternalPackageFragment) {
_owner = owner
}
}
@@ -0,0 +1,414 @@
/*
* 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.backend.wasm
import org.jetbrains.kotlin.backend.common.*
import org.jetbrains.kotlin.backend.common.lower.*
import org.jetbrains.kotlin.backend.common.lower.inline.FunctionInlining
import org.jetbrains.kotlin.backend.common.phaser.*
import org.jetbrains.kotlin.backend.wasm.lower.BuiltInsLowering
import org.jetbrains.kotlin.backend.wasm.lower.WasmBlockDecomposerLowering
import org.jetbrains.kotlin.backend.wasm.lower.excludeDeclarationsFromCodegen
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.lower.*
import org.jetbrains.kotlin.ir.backend.js.lower.inline.RemoveInlineFunctionsWithReifiedTypeParametersLowering
import org.jetbrains.kotlin.ir.backend.js.lower.inline.ReturnableBlockLowering
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
private fun ClassLoweringPass.runOnFilesPostfix(moduleFragment: IrModuleFragment) = moduleFragment.files.forEach { runOnFilePostfix(it) }
private fun validationCallback(context: WasmBackendContext, module: IrModuleFragment) {
val validatorConfig = IrValidatorConfig(
abortOnError = true,
ensureAllNodesAreDifferent = true,
checkTypes = false,
checkDescriptors = false
)
module.accept(IrValidator(context, validatorConfig), null)
module.accept(CheckDeclarationParentsVisitor, null)
}
val validationAction = makeVerifyAction(::validationCallback)
private fun makeWasmModulePhase(
lowering: (WasmBackendContext) -> FileLoweringPass,
name: String,
description: String,
prerequisite: Set<AnyNamedPhase> = emptySet()
) = makeIrModulePhase<WasmBackendContext>(lowering, name, description, prerequisite, actions = setOf(validationAction, defaultDumper))
private fun makeCustomWasmModulePhase(
op: (WasmBackendContext, IrModuleFragment) -> Unit,
description: String,
name: String,
prerequisite: Set<AnyNamedPhase> = emptySet()
) = namedIrModulePhase(
name,
description,
prerequisite,
actions = setOf(defaultDumper, validationAction),
nlevels = 0,
lower = object : SameTypeCompilerPhase<WasmBackendContext, IrModuleFragment> {
override fun invoke(
phaseConfig: PhaseConfig,
phaserState: PhaserState<IrModuleFragment>,
context: WasmBackendContext,
input: IrModuleFragment
): IrModuleFragment {
op(context, input)
return input
}
}
)
private val validateIrBeforeLowering = makeCustomWasmModulePhase(
{ context, module -> validationCallback(context, module) },
name = "ValidateIrBeforeLowering",
description = "Validate IR before lowering"
)
private val validateIrAfterLowering = makeCustomWasmModulePhase(
{ context, module -> validationCallback(context, module) },
name = "ValidateIrAfterLowering",
description = "Validate IR after lowering"
)
private val expectDeclarationsRemovingPhase = makeWasmModulePhase(
::ExpectDeclarationsRemoveLowering,
name = "ExpectDeclarationsRemoving",
description = "Remove expect declaration from module fragment"
)
private val lateinitLoweringPhase = makeWasmModulePhase(
::LateinitLowering,
name = "LateinitLowering",
description = "Insert checks for lateinit field references"
)
// TODO make all lambda-related stuff work with IrFunctionExpression and drop this phase
private val provisionalFunctionExpressionPhase = makeWasmModulePhase(
{ ProvisionalFunctionExpressionLowering() },
name = "FunctionExpression",
description = "Transform IrFunctionExpression to a local function reference"
)
private val arrayConstructorPhase = makeWasmModulePhase(
::ArrayConstructorLowering,
name = "ArrayConstructor",
description = "Transform `Array(size) { index -> value }` into a loop"
)
private val functionInliningPhase = makeCustomWasmModulePhase(
{ context, module ->
FunctionInlining(context).inline(module)
module.patchDeclarationParents()
},
name = "FunctionInliningPhase",
description = "Perform function inlining",
prerequisite = setOf(expectDeclarationsRemovingPhase)
)
private val removeInlineFunctionsWithReifiedTypeParametersLoweringPhase = makeWasmModulePhase(
{ RemoveInlineFunctionsWithReifiedTypeParametersLowering() },
name = "RemoveInlineFunctionsWithReifiedTypeParametersLowering",
description = "Remove Inline functions with reified parameters from context",
prerequisite = setOf(functionInliningPhase)
)
private val tailrecLoweringPhase = makeWasmModulePhase(
::TailrecLowering,
name = "TailrecLowering",
description = "Replace `tailrec` callsites with equivalent loop"
)
private val enumClassConstructorLoweringPhase = makeWasmModulePhase(
::EnumClassConstructorLowering,
name = "EnumClassConstructorLowering",
description = "Transform Enum Class into regular Class"
)
private val sharedVariablesLoweringPhase = makeWasmModulePhase(
::SharedVariablesLowering,
name = "SharedVariablesLowering",
description = "Box captured mutable variables"
)
private val localDelegatedPropertiesLoweringPhase = makeWasmModulePhase(
{ LocalDelegatedPropertiesLowering() },
name = "LocalDelegatedPropertiesLowering",
description = "Transform Local Delegated properties"
)
private val localDeclarationsLoweringPhase = makeWasmModulePhase(
::LocalDeclarationsLowering,
name = "LocalDeclarationsLowering",
description = "Move local declarations into nearest declaration container",
prerequisite = setOf(sharedVariablesLoweringPhase, localDelegatedPropertiesLoweringPhase)
)
private val localClassExtractionPhase = makeWasmModulePhase(
::LocalClassPopupLowering,
name = "LocalClassExtractionPhase",
description = "Move local declarations into nearest declaration container",
prerequisite = setOf(localDeclarationsLoweringPhase)
)
private val innerClassesLoweringPhase = makeWasmModulePhase(
::InnerClassesLowering,
name = "InnerClassesLowering",
description = "Capture outer this reference to inner class"
)
private val innerClassConstructorCallsLoweringPhase = makeWasmModulePhase(
::InnerClassConstructorCallsLowering,
name = "InnerClassConstructorCallsLowering",
description = "Replace inner class constructor invocation"
)
private val defaultArgumentStubGeneratorPhase = makeWasmModulePhase(
::DefaultArgumentStubGenerator,
name = "DefaultArgumentStubGenerator",
description = "Generate synthetic stubs for functions with default parameter values"
)
private val defaultParameterInjectorPhase = makeWasmModulePhase(
{ context -> DefaultParameterInjector(context, skipExternalMethods = true) },
name = "DefaultParameterInjector",
description = "Replace callsite with default parameters with corresponding stub function",
prerequisite = setOf(innerClassesLoweringPhase)
)
private val defaultParameterCleanerPhase = makeWasmModulePhase(
::DefaultParameterCleaner,
name = "DefaultParameterCleaner",
description = "Clean default parameters up"
)
//private val jsDefaultCallbackGeneratorPhase = makeJsModulePhase(
// ::JsDefaultCallbackGenerator,
// name = "JsDefaultCallbackGenerator",
// description = "Build binding for super calls with default parameters"
//)
//private val varargLoweringPhase = makeJsModulePhase(
// ::VarargLowering,
// name = "VarargLowering",
// description = "Lower vararg arguments"
//)
private val propertiesLoweringPhase = makeWasmModulePhase(
{ context -> PropertiesLowering(context, skipExternalProperties = true, generateAnnotationFields = true) },
name = "PropertiesLowering",
description = "Move fields and accessors out from its property"
)
private val primaryConstructorLoweringPhase = makeWasmModulePhase(
::PrimaryConstructorLowering,
name = "PrimaryConstructorLowering",
description = "Creates primary constructor if it doesn't exist"
)
private val initializersLoweringPhase = makeCustomWasmModulePhase(
{ context, module -> InitializersLowering(context, JsLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER, false).lower(module) },
name = "InitializersLowering",
description = "Merge init block and field initializers into [primary] constructor",
prerequisite = setOf(primaryConstructorLoweringPhase)
)
private val excludeDeclarationsFromCodegenPhase = makeCustomWasmModulePhase(
{ context, module ->
excludeDeclarationsFromCodegen(context, module)
},
name = "ExcludeDeclarationsFromCodegen",
description = "Move excluded declarations to separate place"
)
private val returnableBlockLoweringPhase = makeWasmModulePhase(
::ReturnableBlockLowering,
name = "ReturnableBlockLowering",
description = "Replace returnable block with do-while loop",
prerequisite = setOf(functionInliningPhase)
)
private val bridgesConstructionPhase = makeWasmModulePhase(
::BridgesConstruction,
name = "BridgesConstruction",
description = "Generate bridges"
)
private val inlineClassLoweringPhase = makeCustomWasmModulePhase(
{ context, module ->
InlineClassLowering(context).run {
inlineClassDeclarationLowering.runOnFilesPostfix(module)
inlineClassUsageLowering.lower(module)
}
},
name = "InlineClassLowering",
description = "Handle inline classes"
)
//private val autoboxingTransformerPhase = makeJsModulePhase(
// ::AutoboxingTransformer,
// name = "AutoboxingTransformer",
// description = "Insert box/unbox intrinsics"
//)
private val blockDecomposerLoweringPhase = makeCustomWasmModulePhase(
{ context, module ->
WasmBlockDecomposerLowering(context).lower(module)
module.patchDeclarationParents()
},
name = "BlockDecomposerLowering",
description = "Transform statement-like-expression nodes into pure-statement to make it easily transform into JS"
)
//private val classReferenceLoweringPhase = makeJsModulePhase(
// ::ClassReferenceLowering,
// name = "ClassReferenceLowering",
// description = "Handle class references"
//)
//
//private val primitiveCompanionLoweringPhase = makeJsModulePhase(
// ::PrimitiveCompanionLowering,
// name = "PrimitiveCompanionLowering",
// description = "Replace common companion object access with platform one"
//)
//
//private val constLoweringPhase = makeJsModulePhase(
// ::ConstLowering,
// name = "ConstLowering",
// description = "Wrap Long and Char constants into constructor invocation"
//)
//
//private val callsLoweringPhase = makeJsModulePhase(
// ::CallsLowering,
// name = "CallsLowering",
// description = "Handle intrinsics"
//)
//
//private val testGenerationPhase = makeJsModulePhase(
// ::TestGenerator,
// name = "TestGenerationLowering",
// description = "Generate invocations to kotlin.test suite and test functions"
//)
//
private val staticMembersLoweringPhase = makeWasmModulePhase(
::StaticMembersLowering,
name = "StaticMembersLowering",
description = "Move static member declarations to top-level"
)
private val builtInsLoweringPhase = makeWasmModulePhase(
::BuiltInsLowering,
name = "BuiltInsLowering",
description = "Lower IR buildins"
)
private val objectDeclarationLoweringPhase = makeCustomWasmModulePhase(
{ context, module -> ObjectUsageLowering(context, context.objectToGetInstanceFunction).lower(module) },
name = "ObjectDeclarationLowering",
description = "Create lazy object instance generator functions"
)
private val objectUsageLoweringPhase = makeCustomWasmModulePhase(
{ context, module -> ObjectUsageLowering(context, context.objectToGetInstanceFunction).lower(module) },
name = "ObjectUsageLowering",
description = "Transform IrGetObjectValue into instance generator call"
)
val wasmPhases = namedIrModulePhase<WasmBackendContext>(
name = "IrModuleLowering",
description = "IR module lowering",
lower = validateIrBeforeLowering then
excludeDeclarationsFromCodegenPhase then
expectDeclarationsRemovingPhase then
provisionalFunctionExpressionPhase then
// TODO: Need some helpers from stdlib
// arrayConstructorPhase then
functionInliningPhase then
lateinitLoweringPhase then
tailrecLoweringPhase then
enumClassConstructorLoweringPhase then
sharedVariablesLoweringPhase then
localDelegatedPropertiesLoweringPhase then
localDeclarationsLoweringPhase then
localClassExtractionPhase then
innerClassesLoweringPhase then
innerClassConstructorCallsLoweringPhase then
propertiesLoweringPhase then
primaryConstructorLoweringPhase then
initializersLoweringPhase then
// Common prefix ends
builtInsLoweringPhase then
// TODO: Commonize enumEntryToGetInstanceFunction
// Commonize array literal creation
// Extract external enum lowering to JS part
//
// enumClassLoweringPhase then
// enumUsageLoweringPhase then
// TODO: Requires stdlib
// suspendFunctionsLoweringPhase then
returnableBlockLoweringPhase then
// TODO: Callable reference lowering is too JS specific.
// Should we reuse JVM or Native lowering?
// callableReferenceLoweringPhase then
defaultArgumentStubGeneratorPhase then
defaultParameterInjectorPhase then
defaultParameterCleanerPhase then
// TODO: Investigate
// jsDefaultCallbackGeneratorPhase then
removeInlineFunctionsWithReifiedTypeParametersLoweringPhase then
// TODO: Varargs are too platform-specific. Reimplement.
// varargLoweringPhase then
// TODO: Investigate exception proposal
// multipleCatchesLoweringPhase then
bridgesConstructionPhase then
// TODO: Reimplement
// typeOperatorLoweringPhase then
// TODO: Reimplement
// secondaryConstructorLoweringPhase then
// secondaryFactoryInjectorLoweringPhase then
// TODO: Reimplement
// classReferenceLoweringPhase then
inlineClassLoweringPhase then
// TODO: Commonize box/unbox intrinsics
// autoboxingTransformerPhase then
blockDecomposerLoweringPhase then
// TODO: Reimplement
// constLoweringPhase then
objectDeclarationLoweringPhase then
objectUsageLoweringPhase then
staticMembersLoweringPhase then
validateIrAfterLowering
)
@@ -0,0 +1,121 @@
/*
* 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.backend.wasm
import org.jetbrains.kotlin.backend.common.ir.Symbols
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.util.ReferenceSymbolTable
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.types.SimpleType
class WasmSymbols(
context: WasmBackendContext,
private val symbolTable: ReferenceSymbolTable
) : Symbols<WasmBackendContext>(context, symbolTable) {
override val ThrowNullPointerException
get() = TODO()
override val ThrowNoWhenBranchMatchedException
get() = TODO()
override val ThrowTypeCastException
get() = TODO()
override val ThrowUninitializedPropertyAccessException
get() = TODO()
override val defaultConstructorMarker
get() = TODO()
override val stringBuilder
get() = TODO()
override val copyRangeTo: Map<ClassDescriptor, IrSimpleFunctionSymbol>
get() = TODO()
override val coroutineImpl
get() = TODO()
override val coroutineSuspendedGetter
get() = TODO()
override val getContinuation
get() = TODO()
override val coroutineContextGetter by lazy {
context.excludedDeclarations.addFunction {
name = Name.identifier("coroutineContextGetter\$Stub")
}.symbol
}
override val suspendCoroutineUninterceptedOrReturn
get() = TODO()
override val coroutineGetContext
get() = TODO()
override val returnIfSuspended
get() = TODO()
private val wasmInternalPackage = context.module.getPackage(FqName("kotlin.wasm.internal"))
val equalityFunctions = mapOf(
context.irBuiltIns.booleanType to getInternalFunction("wasm_i32_eq"),
context.irBuiltIns.byteType to getInternalFunction("wasm_i32_eq"),
context.irBuiltIns.shortType to getInternalFunction("wasm_i32_eq"),
context.irBuiltIns.charType to getInternalFunction("wasm_i32_eq"),
context.irBuiltIns.intType to getInternalFunction("wasm_i32_eq"),
context.irBuiltIns.longType to getInternalFunction("wasm_i64_eq"),
context.irBuiltIns.floatType to getInternalFunction("wasm_f32_eq"),
context.irBuiltIns.doubleType to getInternalFunction("wasm_f64_eq")
)
private fun wasmString(simpleType: SimpleType): String = with(context.irBuiltIns) {
when (simpleType) {
bool, byte, short, char, int -> "i32"
float -> "f32"
double -> "f64"
long -> "i64"
else -> error("Unkonow primitive type")
}
}
val irBuiltInsToWasmIntrinsics = context.irBuiltIns.run {
mapOf(
lessFunByOperandType to "lt",
lessOrEqualFunByOperandType to "le",
greaterOrEqualFunByOperandType to "ge",
greaterFunByOperandType to "gt"
).map { (typeToBuiltIn, wasmOp) ->
typeToBuiltIn.map { (type, builtin) ->
val wasmType = wasmString(type)
val markSign = if (wasmType == "i32" || wasmType == "i64") "_s" else ""
builtin to getInternalFunction("wasm_${wasmType}_$wasmOp$markSign")
}
}.flatten().toMap()
}
val stringGetLiteral = getInternalFunction("stringLiteral")
private fun findClass(memberScope: MemberScope, name: Name): ClassDescriptor =
memberScope.getContributedClassifier(name, NoLookupLocation.FROM_BACKEND) as ClassDescriptor
private fun findFunctions(memberScope: MemberScope, name: Name): List<SimpleFunctionDescriptor> =
memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).toList()
private fun findProperty(memberScope: MemberScope, name: Name): List<PropertyDescriptor> =
memberScope.getContributedVariables(name, NoLookupLocation.FROM_BACKEND).toList()
internal fun getClass(fqName: FqName): ClassDescriptor =
findClass(context.module.getPackage(fqName.parent()).memberScope, fqName.shortName())
internal fun getProperty(fqName: FqName): PropertyDescriptor =
findProperty(context.module.getPackage(fqName.parent()).memberScope, fqName.shortName()).single()
internal fun getInternalFunction(name: String): IrSimpleFunctionSymbol {
val tmp = findFunctions(wasmInternalPackage.memberScope, Name.identifier(name)).single()
return symbolTable.referenceSimpleFunction(tmp)
}
private fun getIrClass(fqName: FqName): IrClassSymbol = symbolTable.referenceClass(getClass(fqName))
}
@@ -0,0 +1,64 @@
/*
* 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.backend.wasm.ast
// TODO: Abstract out S-expression part of dumping?
fun WasmInstruction.toWat(ident: String = ""): String =
"$ident($mnemonic${immediate.toWat()}${operands.joinToString("") { " " + it.toWat("") }})"
fun WasmImmediate.toWat(): String = when (this) {
WasmImmediate.None -> ""
is WasmImmediate.DeclarationReference -> " $$name"
// SpiderMonkey jsshell won't parse Uppercase letters in literals
is WasmImmediate.LiteralValue<*> -> " $value".toLowerCase()
}
fun wasmModuleToWat(module: WasmModule): String =
"(module\n${module.fields.joinToString("") { wasmModuleFieldToWat(it) + "\n" }})"
fun wasmFunctionToWat(function: WasmFunction): String {
val watId = "$${function.name}"
val watImport = function.importPair?.let { importPair ->
" (import ${toWasString(importPair.module)} ${toWasString(importPair.name)})"
} ?: ""
val watLocals = function.locals.joinToString("") { " " + wasmLocalToWat(it) + "\n" }
val watParameters = function.parameters.joinToString("") { " " + wasmParameterToWat(it, function.importPair == null) }
val watResult = function.returnType?.let { type -> " (result ${type.mnemonic})" } ?: ""
val watBody = function.instructions.joinToString("") { it.toWat(" ") + "\n" }
return " (func $watId$watImport$watParameters$watResult\n$watLocals$watBody )"
}
fun wasmParameterToWat(parameter: WasmParameter, includeName: Boolean): String {
val name = if (includeName) " $${parameter.name}" else ""
return "(param$name ${parameter.type.mnemonic})"
}
fun wasmLocalToWat(local: WasmLocal): String =
local.run { "(local $$name ${type.mnemonic})" }
fun wasmGlobalToWat(global: WasmGlobal): String {
val watMut = if (global.isMutable) "mut " else ""
val watInit = global.init?.toWat("") ?: ""
return global.run { " (global $$name ($watMut${type.mnemonic}) $watInit)" }
}
fun wasmExportToWat(export: WasmExport): String =
export.run { " (export \"$exportedName\" (${kind.keyword} $$wasmName))" }
fun wasmModuleFieldToWat(moduleField: WasmModuleField): String =
when (moduleField) {
is WasmFunction -> wasmFunctionToWat(moduleField)
is WasmGlobal -> wasmGlobalToWat(moduleField)
is WasmExport -> wasmExportToWat(moduleField)
is WasmModuleFieldList -> moduleField.fields.joinToString("") { wasmModuleFieldToWat(it) + "\n" }
}
fun toWasString(s: String): String {
// TODO: escape characters according to
// https://webassembly.github.io/spec/core/text/values.html#strings
return "\"" + s + "\""
}
@@ -0,0 +1,55 @@
/*
* 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.backend.wasm.ast
import org.jetbrains.kotlin.backend.wasm.utils.WasmImportPair
class WasmModule(
val fields: List<WasmModuleField>
)
sealed class WasmModuleField
class WasmModuleFieldList(
val fields: List<WasmModuleField>
) : WasmModuleField()
class WasmFunction(
val name: String,
val parameters: List<WasmParameter>,
val returnType: WasmValueType?,
val locals: List<WasmLocal>,
val instructions: List<WasmInstruction>,
val importPair: WasmImportPair?
) : WasmModuleField()
class WasmParameter(
val name: String,
val type: WasmValueType
)
class WasmLocal(
val name: String,
val type: WasmValueType
)
class WasmGlobal(
val name: String,
val type: WasmValueType,
val isMutable: Boolean,
val init: WasmInstruction?
) : WasmModuleField()
class WasmExport(
val wasmName: String,
val exportedName: String,
val kind: Kind
) : WasmModuleField() {
enum class Kind(val keyword: String) {
FUNCTION("func"),
GLOBAL("global")
}
}
@@ -0,0 +1,65 @@
/*
* 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.backend.wasm.ast
sealed class WasmImmediate {
object None : WasmImmediate()
class DeclarationReference(val name: String) : WasmImmediate()
class LiteralValue<T : Number>(val value: T) : WasmImmediate()
}
sealed class WasmInstruction(
val mnemonic: String,
val immediate: WasmImmediate = WasmImmediate.None,
val operands: List<WasmInstruction> = emptyList()
)
class WasmSimpleInstruction(mnemonic: String, operands: List<WasmInstruction>) :
WasmInstruction(mnemonic, operands = operands)
class WasmNop : WasmInstruction("nop")
class WasmReturn(values: List<WasmInstruction>) :
WasmInstruction("return", operands = values)
class WasmDrop(instructions: List<WasmInstruction>) :
WasmInstruction("drop", operands = instructions)
class WasmCall(name: String, operands: List<WasmInstruction>) :
WasmInstruction("call", WasmImmediate.DeclarationReference(name), operands)
class WasmGetLocal(name: String) :
WasmInstruction("get_local", WasmImmediate.DeclarationReference(name))
class WasmGetGlobal(name: String) :
WasmInstruction("get_global", WasmImmediate.DeclarationReference(name))
class WasmSetGlobal(name: String, value: WasmInstruction) :
WasmInstruction("set_global", WasmImmediate.DeclarationReference(name), listOf(value))
class WasmSetLocal(name: String, value: WasmInstruction) :
WasmInstruction("set_local", WasmImmediate.DeclarationReference(name), listOf(value))
class WasmIf(condition: WasmInstruction, thenInstructions: WasmThen?, elseInstruction: WasmElse?) :
WasmInstruction("if", operands = listOfNotNull(condition, thenInstructions, elseInstruction))
class WasmThen(inst: WasmInstruction) :
WasmInstruction("then", operands = listOf(inst))
class WasmElse(inst: WasmInstruction) :
WasmInstruction("else", operands = listOf(inst))
class WasmBlock(instructions: List<WasmInstruction>) :
WasmInstruction("block", operands = instructions)
sealed class WasmConst<KotlinType : Number, WasmType : WasmValueType>(value: KotlinType, type: WasmType) :
WasmInstruction(type.mnemonic + ".const", WasmImmediate.LiteralValue<KotlinType>(value))
class WasmI32Const(value: Int) : WasmConst<Int, WasmI32>(value, WasmI32)
class WasmI64Const(value: Long) : WasmConst<Long, WasmI64>(value, WasmI64)
class WasmF32Const(value: Float) : WasmConst<Float, WasmF32>(value, WasmF32)
class WasmF64Const(value: Double) : WasmConst<Double, WasmF64>(value, WasmF64)
@@ -0,0 +1,15 @@
/*
* 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.backend.wasm.ast
sealed class WasmValueType(val mnemonic: String)
object WasmI32 : WasmValueType("i32")
object WasmI64 : WasmValueType("i64")
object WasmF32 : WasmValueType("f32")
object WasmF64 : WasmValueType("f64")
object WasmAnyRef : WasmValueType("anyref")
@@ -0,0 +1,16 @@
/*
* 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.backend.wasm.codegen
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.backend.js.utils.TODO
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
interface BaseTransformer<out R, in D> : IrElementVisitor<R, D> {
override fun visitElement(element: IrElement, data: D): R {
TODO(element)
}
}
@@ -0,0 +1,123 @@
/*
* 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.backend.wasm.codegen
import org.jetbrains.kotlin.backend.wasm.ast.*
import org.jetbrains.kotlin.backend.wasm.utils.getWasmImportAnnotation
import org.jetbrains.kotlin.backend.wasm.utils.getWasmInstructionAnnotation
import org.jetbrains.kotlin.backend.wasm.utils.hasExcludedFromCodegenAnnotation
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.isAnnotationClass
import org.jetbrains.kotlin.ir.util.isFakeOverride
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
class DeclarationTransformer : BaseTransformer<WasmModuleField?, WasmCodegenContext> {
override fun visitSimpleFunction(declaration: IrSimpleFunction, data: WasmCodegenContext): WasmModuleField? {
if (declaration.hasExcludedFromCodegenAnnotation())
return null
if (declaration.getWasmInstructionAnnotation() != null)
return null
if (declaration.isFakeOverride)
return null
// Virtual functions are not supported yet
if (declaration.origin == IrDeclarationOrigin.BRIDGE)
return null
// Collect local variables
val localNames = wasmNameTable<IrValueDeclaration>()
val wasmName = data.getGlobalName(declaration)
val irParameters = declaration.run {
listOfNotNull(dispatchReceiverParameter, extensionReceiverParameter) + valueParameters
}
val wasmParameters = irParameters.map { parameter ->
val name = localNames.declareFreshName(parameter, parameter.name.asString())
WasmParameter(name, data.transformType(parameter.type))
}
val wasmReturnType = when {
declaration.returnType.isUnit() -> null
else -> data.transformType(declaration.returnType)
}
val importedName = declaration.getWasmImportAnnotation()
if (importedName != null) {
data.imports.add(
WasmFunction(
name = wasmName,
parameters = wasmParameters,
returnType = wasmReturnType,
locals = emptyList(),
instructions = emptyList(),
importPair = importedName
)
)
return null
}
val body = declaration.body
?: error("Function ${declaration.fqNameWhenAvailable} without a body")
data.localNames = localNames.names
val locals = mutableListOf<WasmLocal>()
body.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitVariable(declaration: IrVariable) {
val name = localNames.declareFreshName(declaration, declaration.name.asString())
locals += WasmLocal(name, data.transformType(declaration.type))
super.visitVariable(declaration)
}
})
return WasmFunction(
name = wasmName,
parameters = wasmParameters,
returnType = wasmReturnType,
locals = locals,
instructions = bodyToWasmInstructionList(body, data),
importPair = null
)
}
override fun visitConstructor(declaration: IrConstructor, data: WasmCodegenContext): WasmModuleField? {
TODO()
}
override fun visitClass(declaration: IrClass, data: WasmCodegenContext): WasmModuleField? {
if (declaration.isAnnotationClass) return null
if (declaration.hasExcludedFromCodegenAnnotation()) return null
val wasmMembers = declaration.declarations.mapNotNull { member ->
when (member) {
is IrSimpleFunction -> this.visitSimpleFunction(member, data)
else -> null
}
}
return WasmModuleFieldList(wasmMembers)
}
override fun visitField(declaration: IrField, data: WasmCodegenContext): WasmModuleField {
return WasmGlobal(
name = data.getGlobalName(declaration),
type = data.transformType(declaration.type),
isMutable = true,
// TODO: move non-constexpr initializers out
init = declaration.initializer?.let {
expressionToWasmInstruction(it.expression, data)
}
)
}
}
@@ -0,0 +1,206 @@
/*
* 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.backend.wasm.codegen
import org.jetbrains.kotlin.backend.common.ir.isElseBranch
import org.jetbrains.kotlin.backend.wasm.ast.*
import org.jetbrains.kotlin.backend.wasm.utils.getWasmInstructionAnnotation
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.ir.util.dump
class ExpressionTransformer : BaseTransformer<WasmInstruction, WasmCodegenContext> {
override fun visitVararg(expression: IrVararg, data: WasmCodegenContext): WasmInstruction {
TODO("Support arrays")
}
override fun visitExpressionBody(body: IrExpressionBody, data: WasmCodegenContext): WasmInstruction =
body.expression.accept(this, data)
override fun visitFunctionReference(expression: IrFunctionReference, data: WasmCodegenContext): WasmInstruction {
TODO("?")
}
override fun <T> visitConst(expression: IrConst<T>, data: WasmCodegenContext): WasmInstruction {
return when (val kind = expression.kind) {
is IrConstKind.Null -> TODO()
is IrConstKind.String -> {
val value = kind.valueOf(expression)
val index = data.stringLiterals.size
data.stringLiterals.add(value)
val funName = data.getGlobalName(data.backendContext.wasmSymbols.stringGetLiteral.owner)
val operand = WasmI32Const(index)
WasmCall(funName, listOf(operand))
}
is IrConstKind.Boolean -> WasmI32Const(if (kind.valueOf(expression)) 1 else 0)
is IrConstKind.Byte -> WasmI32Const(kind.valueOf(expression).toInt())
is IrConstKind.Short -> WasmI32Const(kind.valueOf(expression).toInt())
is IrConstKind.Int -> WasmI32Const(kind.valueOf(expression))
is IrConstKind.Long -> WasmI64Const(kind.valueOf(expression))
is IrConstKind.Char -> WasmI32Const(kind.valueOf(expression).toInt())
is IrConstKind.Float -> WasmF32Const(kind.valueOf(expression))
is IrConstKind.Double -> WasmF64Const(kind.valueOf(expression))
}
}
override fun visitStringConcatenation(expression: IrStringConcatenation, data: WasmCodegenContext): WasmInstruction {
TODO("Implement kotlin.String")
}
override fun visitGetField(expression: IrGetField, data: WasmCodegenContext): WasmInstruction {
val fieldName = data.getGlobalName(expression.symbol.owner)
if (expression.receiver != null)
TODO("Support member fields")
return WasmGetGlobal(fieldName)
}
override fun visitGetValue(expression: IrGetValue, data: WasmCodegenContext): WasmInstruction =
WasmGetLocal(data.getLocalName(expression.symbol.owner))
override fun visitGetObjectValue(expression: IrGetObjectValue, data: WasmCodegenContext): WasmInstruction {
TODO("IrGetObjectValue")
}
override fun visitSetField(expression: IrSetField, data: WasmCodegenContext): WasmInstruction {
val fieldName = data.getGlobalName(expression.symbol.owner)
if (expression.receiver != null)
TODO("Support member fields")
val value = expression.value.accept(this, data)
return WasmSetGlobal(fieldName, value)
}
override fun visitSetVariable(expression: IrSetVariable, data: WasmCodegenContext): WasmInstruction {
val fieldName = data.getLocalName(expression.symbol.owner)
val value = expression.value.accept(this, data)
return WasmSetLocal(fieldName, value)
}
override fun visitConstructorCall(expression: IrConstructorCall, data: WasmCodegenContext): WasmInstruction {
TODO("IrConstructorCall")
}
override fun visitCall(expression: IrCall, data: WasmCodegenContext): WasmInstruction {
val function = expression.symbol.owner.realOverrideTarget
require(function is IrSimpleFunction) { "Only IrSimpleFunction could be called via IrCall" }
val valueArgs = (0 until expression.valueArgumentsCount).mapNotNull { expression.getValueArgument(it) }
val irArguments = listOfNotNull(expression.dispatchReceiver, expression.extensionReceiver) + valueArgs
val wasmArguments = irArguments.map { expressionToWasmInstruction(it, data) }
val wasmInstruction = function.getWasmInstructionAnnotation()
if (wasmInstruction != null) {
if (wasmInstruction == "nop") {
return wasmArguments.single()
}
return WasmSimpleInstruction(wasmInstruction, wasmArguments)
}
val name = data.getGlobalName(function)
return WasmCall(name, wasmArguments)
}
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: WasmCodegenContext): WasmInstruction {
val wasmArgument = expressionToWasmInstruction(expression.argument, data)
when (expression.operator) {
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> return wasmArgument
}
TODO("IrTypeOperatorCall:\n ${expression.dump()}")
}
override fun visitGetEnumValue(expression: IrGetEnumValue, data: WasmCodegenContext): WasmInstruction {
TODO("IrGetEnumValue")
}
override fun visitBlockBody(body: IrBlockBody, data: WasmCodegenContext): WasmInstruction {
TODO()
}
override fun visitContainerExpression(expression: IrContainerExpression, data: WasmCodegenContext): WasmInstruction {
val expressions = expression.statements.map { it.accept(this, data) }
if (!expression.type.isUnit())
return WasmBlock(expressions + listOf(WasmDrop(emptyList())))
return WasmBlock(expressions)
}
override fun visitExpression(expression: IrExpression, data: WasmCodegenContext): WasmInstruction {
return expressionToWasmInstruction(expression, data)
}
override fun visitBreak(jump: IrBreak, data: WasmCodegenContext): WasmInstruction {
TODO()
}
override fun visitContinue(jump: IrContinue, data: WasmCodegenContext): WasmInstruction {
TODO()
}
override fun visitReturn(expression: IrReturn, data: WasmCodegenContext): WasmInstruction {
if (expression.value.type.isUnit()) return WasmReturn(emptyList())
return WasmReturn(listOf(expressionToWasmInstruction(expression.value, data)))
}
override fun visitThrow(expression: IrThrow, data: WasmCodegenContext): WasmInstruction {
TODO("IrThrow")
}
override fun visitVariable(declaration: IrVariable, data: WasmCodegenContext): WasmInstruction {
val init = declaration.initializer ?: return WasmNop()
val varName = data.getLocalName(declaration)
return WasmSetLocal(varName, expressionToWasmInstruction(init, data))
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall, data: WasmCodegenContext): WasmInstruction {
TODO("IrDelegatingConstructorCall")
}
override fun visitInstanceInitializerCall(expression: IrInstanceInitializerCall, data: WasmCodegenContext): WasmInstruction {
TODO("IrInstanceInitializerCall")
}
override fun visitTry(aTry: IrTry, data: WasmCodegenContext): WasmInstruction {
TODO("IrTry")
}
override fun visitWhen(expression: IrWhen, data: WasmCodegenContext): WasmInstruction {
return expression.branches.foldRight(null) { br: IrBranch, inst: WasmInstruction? ->
val body = expressionToWasmInstruction(br.result, data)
if (isElseBranch(br)) body
else {
val condition = expressionToWasmInstruction(br.condition, data)
WasmIf(condition, WasmThen(body), inst?.let { WasmElse(inst) })
}
}!!
}
override fun visitWhileLoop(loop: IrWhileLoop, data: WasmCodegenContext): WasmInstruction {
TODO("IrWhileLoop")
}
override fun visitDoWhileLoop(loop: IrDoWhileLoop, data: WasmCodegenContext): WasmInstruction {
TODO("IrDoWhileLoop")
}
override fun visitSyntheticBody(body: IrSyntheticBody, data: WasmCodegenContext): WasmInstruction {
TODO("IrSyntheticBody")
}
override fun visitDynamicMemberExpression(expression: IrDynamicMemberExpression, data: WasmCodegenContext): WasmInstruction =
error("Dynamic operators are not supported for WASM target")
override fun visitDynamicOperatorExpression(expression: IrDynamicOperatorExpression, data: WasmCodegenContext): WasmInstruction =
error("Dynamic operators are not supported for WASM target")
}
fun expressionToWasmInstruction(expression: IrExpression, context: WasmCodegenContext): WasmInstruction {
return expression.accept(ExpressionTransformer(), context)
}
@@ -0,0 +1,82 @@
/*
* 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.backend.wasm.codegen
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.backend.wasm.WasmCompilerResult
import org.jetbrains.kotlin.backend.wasm.ast.WasmExport
import org.jetbrains.kotlin.backend.wasm.ast.WasmModule
import org.jetbrains.kotlin.backend.wasm.ast.wasmModuleToWat
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsAssignment
import org.jetbrains.kotlin.ir.backend.js.utils.sanitizeName
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.js.backend.ast.JsArrayLiteral
import org.jetbrains.kotlin.js.backend.ast.JsBlock
import org.jetbrains.kotlin.js.backend.ast.JsNameRef
import org.jetbrains.kotlin.js.backend.ast.JsStringLiteral
import org.jetbrains.kotlin.utils.addIfNotNull
class IrModuleToWasm(private val backendContext: WasmBackendContext) {
fun generateModule(module: IrModuleFragment): WasmCompilerResult {
val nameTable = generateWatTopLevelNames(module.files)
val context = WasmCodegenContext(nameTable, backendContext)
val irDeclarations = module.files.flatMap { it.declarations }
val wasmDeclarations = irDeclarations.mapNotNull { it.accept(DeclarationTransformer(), context) }
val exports = generateExports(module, context)
val wasmModule = WasmModule(context.imports + wasmDeclarations + exports)
val wat = wasmModuleToWat(wasmModule)
return WasmCompilerResult(wat, generateStringLiteralsSupport(context.stringLiterals))
}
private fun generateStringLiteralsSupport(literals: List<String>): String {
return JsBlock(
jsAssignment(
JsNameRef("stringLiterals", "runtime"),
JsArrayLiteral(literals.map { JsStringLiteral(it) })
).makeStmt()
).toString()
}
private fun generateExports(module: IrModuleFragment, context: WasmCodegenContext): List<WasmExport> {
val exports = mutableListOf<WasmExport>()
for (file in module.files) {
for (declaration in file.declarations) {
exports.addIfNotNull(generateExport(declaration, context))
}
}
return exports
}
private fun generateExport(declaration: IrDeclaration, context: WasmCodegenContext): WasmExport? {
if (declaration !is IrDeclarationWithVisibility ||
declaration !is IrDeclarationWithName ||
declaration !is IrSimpleFunction ||
declaration.visibility != Visibilities.PUBLIC
) {
return null
}
if (!declaration.isExported(context))
return null
val internalName = context.getGlobalName(declaration)
val exportedName = sanitizeName(declaration.name.identifier)
return WasmExport(
wasmName = internalName,
exportedName = exportedName,
kind = WasmExport.Kind.FUNCTION
)
}
}
fun IrFunction.isExported(context: WasmCodegenContext): Boolean =
fqNameWhenAvailable in context.backendContext.additionalExportedDeclarations
@@ -0,0 +1,64 @@
/*
* 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.backend.wasm.codegen
import org.jetbrains.kotlin.backend.common.ir.isTopLevel
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
fun <T> wasmNameTable() = NameTable<T>(sanitizer = ::sanitizeWatIdentifier)
fun generateWatTopLevelNames(packages: List<IrPackageFragment>): Map<IrDeclarationWithName, String> {
val names = wasmNameTable<IrDeclarationWithName>()
fun nameTopLevelDecl(declaration: IrDeclarationWithName) {
val suggestedName = declaration.fqNameWhenAvailable?.toString()
?: "fqname???" + declaration.name.asString()
names.declareFreshName(declaration, suggestedName)
}
for (p in packages) {
p.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
nameTopLevelDecl(declaration)
super.visitSimpleFunction(declaration)
}
override fun visitField(declaration: IrField) {
if (declaration.isTopLevel)
nameTopLevelDecl(declaration)
super.visitField(declaration)
}
})
}
return names.names
}
fun sanitizeWatIdentifier(ident: String): String {
if (ident.isEmpty())
return "_"
if (ident.all(::isValidWatIdentifier))
return ident
return ident.map { if (isValidWatIdentifier(it)) it else "_" }.joinToString("")
}
// https://webassembly.github.io/spec/core/text/values.html#text-id
fun isValidWatIdentifier(c: Char): Boolean =
c in '0'..'9' || c in 'A'..'Z' || c in 'a'..'z'
// TODO: SpiderMonkey js shell can't parse some of the
// permitted identifiers: '?', '<'
// || c in "!#$%&*+-./:<=>?@\\^_`|~"
|| c in "$.@_"
@@ -0,0 +1,35 @@
/*
* 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.backend.wasm.codegen
import org.jetbrains.kotlin.backend.wasm.ast.WasmInstruction
import org.jetbrains.kotlin.backend.wasm.ast.WasmNop
import org.jetbrains.kotlin.backend.wasm.ast.WasmSetLocal
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.expressions.*
class StatementTransformer : BaseTransformer<WasmInstruction, WasmCodegenContext> {
override fun visitVariable(declaration: IrVariable, data: WasmCodegenContext): WasmInstruction {
val init = declaration.initializer ?: return WasmNop()
val varName = data.getLocalName(declaration)
return WasmSetLocal(varName, expressionToWasmInstruction(init, data))
}
override fun visitExpression(expression: IrExpression, data: WasmCodegenContext): WasmInstruction {
return expressionToWasmInstruction(expression, data)
}
}
fun statementToWasmInstruction(statement: IrStatement, context: WasmCodegenContext): WasmInstruction {
return statement.accept(StatementTransformer(), context)
}
fun bodyToWasmInstructionList(body: IrBody, context: WasmCodegenContext): List<WasmInstruction> {
if (body is IrBlockBody) {
return body.statements.map { statementToWasmInstruction(it, context) }
} else TODO()
}
@@ -0,0 +1,27 @@
/*
* 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.backend.wasm.codegen
import org.jetbrains.kotlin.backend.wasm.ast.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.render
fun WasmCodegenContext.transformType(irType: IrType): WasmValueType =
when {
irType.isBoolean() -> WasmI32
irType.isByte() -> WasmI32
irType.isShort() -> WasmI32
irType.isInt() -> WasmI32
irType.isLong() -> WasmI64
irType.isChar() -> WasmI32
irType.isFloat() -> WasmF32
irType.isDouble() -> WasmF64
irType.isString() -> WasmAnyRef
irType.isAny() || irType.isNullableAny() -> WasmAnyRef
else ->
TODO("Unsupported type: ${irType.render()}")
}
@@ -0,0 +1,29 @@
/*
* 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.backend.wasm.codegen
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.backend.wasm.ast.WasmModuleField
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
class WasmCodegenContext(
private val topLevelNames: Map<IrDeclarationWithName, String>,
val backendContext: WasmBackendContext
) {
val imports = mutableListOf<WasmModuleField>()
var localNames: Map<IrValueDeclaration, String> = emptyMap()
val stringLiterals = mutableListOf<String>()
fun getGlobalName(declaration: IrDeclarationWithName): String =
topLevelNames[declaration]
?: error("Can't find name for ${declaration.fqNameWhenAvailable}")
fun getLocalName(declaration: IrValueDeclaration): String =
localNames[declaration]
?: error("Can't find local name for ${declaration.fqNameWhenAvailable}")
}
@@ -0,0 +1,65 @@
/*
* 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.backend.wasm
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
import org.jetbrains.kotlin.backend.wasm.codegen.IrModuleToWasm
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.backend.js.loadIr
import org.jetbrains.kotlin.ir.backend.js.sortDependencies
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
data class WasmCompilerResult(val wat: String, val js: String)
fun compileWasm(
project: Project,
files: List<KtFile>,
configuration: CompilerConfiguration,
phaseConfig: PhaseConfig,
allDependencies: List<KotlinLibrary>,
friendDependencies: List<KotlinLibrary>,
exportedDeclarations: Set<FqName> = emptySet()
): WasmCompilerResult {
val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) =
loadIr(project, files, configuration, allDependencies, friendDependencies)
val moduleDescriptor = moduleFragment.descriptor
val context = WasmBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, exportedDeclarations, configuration)
// Load declarations referenced during `context` initialization
dependencyModules.forEach {
ExternalDependenciesGenerator(
it.descriptor,
symbolTable,
irBuiltIns,
deserializer = deserializer
).generateUnboundSymbolsAsDependencies()
}
// Since modules should be initialized in the correct topological order we sort them
val irFiles = sortDependencies(dependencyModules).flatMap { it.files } + moduleFragment.files
moduleFragment.files.clear()
moduleFragment.files += irFiles
// Create stubs
ExternalDependenciesGenerator(
moduleDescriptor = moduleDescriptor,
symbolTable = symbolTable,
irBuiltIns = irBuiltIns
).generateUnboundSymbolsAsDependencies()
moduleFragment.patchDeclarationParents()
wasmPhases.invokeToplevel(phaseConfig, context, moduleFragment)
return IrModuleToWasm(context).generateModule(moduleFragment)
}
@@ -0,0 +1,14 @@
/*
* 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.backend.wasm.lower
import org.jetbrains.kotlin.ir.backend.js.lower.AbstractBlockDecomposerLowering
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.ir.expressions.IrExpression
class WasmBlockDecomposerLowering(val context: WasmBackendContext) : AbstractBlockDecomposerLowering(context) {
override fun unreachableExpression(): IrExpression = TODO()
}
@@ -0,0 +1,48 @@
/*
* 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.backend.wasm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.util.irCall
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
class BuiltInsLowering(val context: WasmBackendContext) : FileLoweringPass {
private val irBuiltins = context.irBuiltIns
private val symbols = context.wasmSymbols
fun transformCall(call: IrCall): IrExpression {
when (val symbol = call.symbol) {
irBuiltins.eqeqSymbol, irBuiltins.eqeqeqSymbol, in irBuiltins.ieee754equalsFunByOperandType.values -> {
val type = call.getValueArgument(0)!!.type
val newSymbol = symbols.equalityFunctions[type]
?: error("Unsupported equality operator with type: ${type.render()}")
return irCall(call, newSymbol)
}
in symbols.irBuiltInsToWasmIntrinsics.keys -> {
val newSymbol = symbols.irBuiltInsToWasmIntrinsics[symbol]!!
return irCall(call, newSymbol)
}
}
return call
}
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitCall(expression: IrCall): IrExpression {
val newExpression = transformCall(expression)
newExpression.transformChildrenVoid(this)
return newExpression
}
})
}
}
@@ -0,0 +1,124 @@
/*
* 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.backend.wasm.lower
import org.jetbrains.kotlin.backend.common.ir.addChild
import org.jetbrains.kotlin.backend.wasm.WasmBackendContext
import org.jetbrains.kotlin.backend.wasm.utils.hasExcludedFromCodegenAnnotation
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.name.FqName
private val BODILESS_BUILTIN_CLASSES = listOf(
"kotlin.Nothing",
"kotlin.Array",
"kotlin.Any",
"kotlin.ByteArray",
"kotlin.CharArray",
"kotlin.ShortArray",
"kotlin.IntArray",
"kotlin.LongArray",
"kotlin.FloatArray",
"kotlin.DoubleArray",
"kotlin.BooleanArray",
"kotlin.Boolean",
"kotlin.Function",
"kotlin.Throwable",
"kotlin.Suppress",
"kotlin.SinceKotlin",
"kotlin.Deprecated",
"kotlin.ReplaceWith",
"kotlin.DeprecationLevel",
"kotlin.UnsafeVariance",
"kotlin.reflect.KType",
"kotlin.reflect.KTypeProjection",
"kotlin.reflect.Companion",
"kotlin.reflect.KTypeParameter",
"kotlin.reflect.KDeclarationContainer",
"kotlin.reflect.KProperty",
"kotlin.reflect.KProperty0",
"kotlin.reflect.KProperty1",
"kotlin.reflect.KProperty2",
"kotlin.reflect.KMutableProperty0",
"kotlin.reflect.KMutableProperty",
"kotlin.reflect.KMutableProperty1",
"kotlin.reflect.KMutableProperty2",
"kotlin.reflect.Accessor",
"kotlin.reflect.Getter",
"kotlin.reflect.KFunction",
"kotlin.reflect.KVariance",
"kotlin.reflect.KVisibility",
"kotlin.reflect.KClass",
"kotlin.reflect.KCallable",
"kotlin.reflect.KClassifier",
"kotlin.reflect.KParameter",
"kotlin.reflect.Kind",
"kotlin.reflect.KAnnotatedElement",
"kotlin.annotation.Target",
"kotlin.annotation.AnnotationTarget",
"kotlin.annotation.Retention",
"kotlin.annotation.AnnotationRetention",
"kotlin.annotation.MustBeDocumented",
"kotlin.Unit",
"kotlin.collections.BooleanIterator",
"kotlin.collections.CharIterator",
"kotlin.collections.ByteIterator",
"kotlin.collections.ShortIterator",
"kotlin.collections.IntIterator",
"kotlin.collections.FloatIterator",
"kotlin.collections.LongIterator",
"kotlin.collections.DoubleIterator",
"kotlin.internal.PlatformDependent",
"kotlin.CharSequence",
"kotlin.Annotation",
"kotlin.Comparable",
"kotlin.collections.Collection",
"kotlin.collections.Iterable",
"kotlin.collections.List",
"kotlin.collections.Map",
"kotlin.collections.Set",
"kotlin.collections.MutableCollection",
"kotlin.collections.MutableIterable",
"kotlin.collections.MutableSet",
"kotlin.collections.MutableList",
"kotlin.collections.MutableMap",
"kotlin.collections.Entry",
"kotlin.collections.MutableEntry",
"kotlin.Number",
"kotlin.Enum",
"kotlin.collections.Iterator",
"kotlin.collections.ListIterator",
"kotlin.collections.MutableIterator",
"kotlin.collections.MutableListIterator"
).map { FqName(it) }.toSet()
fun excludeDeclarationsFromCodegen(context: WasmBackendContext, module: IrModuleFragment) {
fun isExcluded(declaration: IrDeclaration): Boolean {
if (declaration is IrDeclarationWithName && declaration.fqNameWhenAvailable in BODILESS_BUILTIN_CLASSES)
return true
if (declaration.hasExcludedFromCodegenAnnotation())
return true
val parentFile = declaration.parent as? IrFile
if (parentFile?.hasExcludedFromCodegenAnnotation() == true)
return true
return false
}
for (file in module.files) {
val it = file.declarations.iterator()
while (it.hasNext()) {
val d = it.next() as? IrDeclarationWithName ?: continue
if (isExcluded(d)) {
it.remove()
context.excludedDeclarations.addChild(d)
}
}
}
}
@@ -0,0 +1,29 @@
/*
* 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.backend.wasm.utils
import org.jetbrains.kotlin.ir.backend.js.utils.getSingleConstStringArgument
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.util.getAnnotation
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.name.FqName
fun IrAnnotationContainer.hasExcludedFromCodegenAnnotation(): Boolean =
hasAnnotation(FqName("kotlin.wasm.internal.ExcludedFromCodegen"))
fun IrAnnotationContainer.getWasmInstructionAnnotation(): String? =
getAnnotation(FqName("kotlin.wasm.internal.WasmInstruction"))?.getSingleConstStringArgument()
class WasmImportPair(val module: String, val name: String)
@Suppress("UNCHECKED_CAST")
fun IrAnnotationContainer.getWasmImportAnnotation(): WasmImportPair? =
getAnnotation(FqName("kotlin.wasm.internal.WasmImport"))?.let {
WasmImportPair(
(it.getValueArgument(0) as IrConst<String>).value,
(it.getValueArgument(1) as IrConst<String>).value
)
}