Introduced support for javascript Math and Kotlin Double to WASM32.

Implemented jsinterop tool for interaction with js math.
This commit is contained in:
Alexander Gorshenev
2017-12-28 12:33:29 +03:00
committed by alexander-gorshenev
parent 1399ac9777
commit f703877160
28 changed files with 990 additions and 187 deletions
+39
View File
@@ -0,0 +1,39 @@
import org.jetbrains.kotlin.KlibInstall
apply plugin: 'konan'
buildscript {
repositories {
mavenCentral()
maven {
url kotlinCompilerRepo
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin"
}
ext.konanHome = distDir.absolutePath
ext.jvmArgs = project.findProperty("platformLibsJvmArgs") ?: "-Xmx3G"
ext.setProperty("konan.home", konanHome)
ext.setProperty("konan.jvmArgs", jvmArgs)
}
konan.targets = [ 'wasm32' ]
konanArtifacts {
library("jsinterop") {
dependsOn ':wasm32CrossDist' // our plugin runs konanc, so make sure it is already there.
extraOpts '-includeBinary', project.file('src/main/js/jsinterop.js')
}
}
task installJsRuntime(type: KlibInstall) {
dependsOn konanArtifacts.jsinterop.wasm32
klib = konanArtifacts.jsinterop.wasm32.artifact
repo = file("$konanHome/klib/platform/wasm32")
it.target = 'wasm32'
}
@@ -16,6 +16,17 @@ external public fun freeArena(arena: Arena)
@SymbolName("Konan_js_pushIntToArena")
external public fun pushIntToArena(arena: Arena, value: Int)
const val upperWord = 0xffffffff.toLong() shl 32
fun doubleUpper(value: Double): Int =
((value.toBits() and upperWord) ushr 32) .toInt()
fun doubleLower(value: Double): Int =
(value.toBits() and 0x00000000ffffffff) .toInt()
@SymbolName("ReturnSlot_getDouble")
external public fun ReturnSlot_getDouble(): Double
@SymbolName("Kotlin_String_utf16pointer")
external public fun stringPointer(message: String): Pointer
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.konan.file.File
internal fun resolveLibraries(staticLibraries: List<String>, libraryPaths: List<String>): List<String> {
val result = mutableListOf<String>()
staticLibraries.forEach { library ->
val resolution = libraryPaths.map { "$it/$library" }
.find { File(it).exists }
if (resolution != null) {
result.add(resolution)
} else {
error("Could not find '$library' binary in neither of $libraryPaths")
}
}
return result
}
internal fun argsToCompiler(staticLibraries: Array<String>, libraryPaths: Array<String>) = argsToCompiler(staticLibraries.toList(), libraryPaths.toList())
internal fun argsToCompiler(staticLibraries: List<String>, libraryPaths: List<String>) =
resolveLibraries(staticLibraries, libraryPaths)
.map { it -> listOf("-includeBinary", it) }
.flatten()
.toTypedArray()
@@ -19,7 +19,7 @@ package org.jetbrains.kotlin.native.interop.tool
import org.jetbrains.kotlin.cli.common.arguments.*
open class CommonInteropArguments : CommonToolArguments() {
@Argument(value = "-flavor", valueDescription = "<flavor>", description = "One of: jvm, native")
@Argument(value = "-flavor", valueDescription = "<flavor>", description = "One of: jvm, native or wasm")
var flavor: String? = null
@Argument(value = "-pkg", valueDescription = "<fully qualified name>", description = "place generated bindings to the package")
@@ -27,6 +27,18 @@ open class CommonInteropArguments : CommonToolArguments() {
@Argument(value = "-generated", valueDescription = "<dir>", description = "place generated bindings to the directory")
var generated: String? = null
@Argument(value = "-natives", valueDescription = "<directory>", description = "where to put the built native files")
var natives: String? = null
@Argument(value = "-manifest", valueDescription = "<file>", description = "library manifest addend")
var manifest: String? = null
@Argument(value = "-staticLibrary", valueDescription = "<file>", description = "embed static library to the result")
var staticLibrary: Array<String> = arrayOf()
@Argument(value = "-libraryPath", valueDescription = "<dir>", description = "add a library search path")
var libraryPath: Array<String> = arrayOf()
}
class CInteropArguments : CommonInteropArguments() {
@@ -36,15 +48,9 @@ class CInteropArguments : CommonInteropArguments() {
@Argument(value = "-target", valueDescription = "<target>", description = "native target to compile to")
var target: String? = null
@Argument(value = "-natives", valueDescription = "<directory>", description = "where to put the built native files")
var natives: String? = null
@Argument(value = "-def", valueDescription = "<file>", description = "the library definition file")
var def: String? = null
@Argument(value = "-manifest", valueDescription = "<file>", description = "library manifest addend")
var manifest: String? = null
@Argument(value = "-properties", valueDescription = "<file>", description = "an alternative location of konan.properties file")
var properties: String? = null
@@ -66,14 +72,8 @@ class CInteropArguments : CommonInteropArguments() {
var shims: Boolean = false
@Argument(value = "-linker", valueDescription = "<file>", description = "use specified linker")
var linker: String? = null
@Argument(value = "-staticLibrary", valueDescription = "<file>", description = "embed static library to the result")
var staticLibrary: Array<String> = arrayOf()
@Argument(value = "-libraryPath", valueDescription = "<dir>", description = "add a library search path")
var libraryPath: Array<String> = arrayOf()
@Argument(value = "-cstubsname", valueDescription = "<name>", description = "provide a name for the generated c stubs file")
var cstubsname: String? = null
@@ -1001,5 +1001,4 @@ class StubGenerator(
val mappingBridgeGenerator: MappingBridgeGenerator =
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator)
}
@@ -16,23 +16,28 @@
package org.jetbrains.kotlin.native.interop.gen.jvm
import org.jetbrains.kotlin.native.interop.tool.*
import org.jetbrains.kotlin.konan.util.DefFile
import org.jetbrains.kotlin.native.interop.gen.HeadersInclusionPolicyImpl
import org.jetbrains.kotlin.native.interop.gen.ImportsImpl
import org.jetbrains.kotlin.native.interop.gen.argsToCompiler
import org.jetbrains.kotlin.native.interop.gen.wasm.processIdlLib
import org.jetbrains.kotlin.native.interop.indexer.*
import org.jetbrains.kotlin.native.interop.tool.*
import java.io.File
import java.lang.IllegalArgumentException
import java.nio.file.*
import java.util.*
fun main(args: Array<String>) = interop(args, null)
fun interop(args: Array<String>, argsToCompiler: MutableList<String>? = null) {
val arguments = parseCommandLine(args, CInteropArguments())
processLib(arguments, argsToCompiler)
fun main(args: Array<String>) {
processCLib(args)
}
fun interop(flavor: String, args: Array<String>) = when(flavor) {
"jvm", "native" -> processCLib(args)
"wasm" -> processIdlLib(args)
else -> error("Unexpected flavor")
}
// Options, whose values are space-separated and can be escaped.
val escapedOptions = setOf("-compilerOpts", "-linkerOpts")
@@ -162,27 +167,6 @@ private fun selectNativeLanguage(config: DefFile.DefFileConfig): Language {
error("Unexpected language '$language'. Possible values are: ${languages.keys.joinToString { "'$it'" }}")
}
private fun resolveLibraries(staticLibraries: List<String>, libraryPaths: List<String>): List<String> {
val result = mutableListOf<String>()
staticLibraries.forEach { library ->
val resolution = libraryPaths.map { "$it/$library" }
.find { File(it).exists() }
if (resolution != null) {
result.add(resolution)
} else {
error("Could not find '$library' binary in neither of $libraryPaths")
}
}
return result
}
private fun argsToCompiler(staticLibraries: List<String>, libraryPaths: List<String>): List<String> {
return resolveLibraries(staticLibraries, libraryPaths)
.map { it -> listOf("-includeBinary", it) } .flatten()
}
private fun parseImports(imports: Array<String>): ImportsImpl {
val headerIdToPackage = imports.map { arg ->
val (pkg, joinedIds) = arg.split(':')
@@ -247,9 +231,9 @@ private fun findFilesByGlobs(roots: List<Path>, globs: List<String>): Map<Path,
}
private fun processLib(arguments: CInteropArguments,
argsToCompiler: MutableList<String>?) {
private fun processCLib(args: Array<String>): Array<String>? {
val arguments = parseCommandLine(args, CInteropArguments())
val userDir = System.getProperty("user.dir")
val ktGenRoot = arguments.generated ?: userDir
val nativeLibsDir = arguments.natives ?: userDir
@@ -260,7 +244,7 @@ private fun processLib(arguments: CInteropArguments,
if (defFile == null && arguments.pkg == null) {
usage()
return
return null
}
val tool = ToolConfig(
@@ -314,8 +298,6 @@ private fun processLib(arguments: CInteropArguments,
val excludedFunctions = def.config.excludedFunctions.toSet()
val staticLibraries = def.config.staticLibraries + arguments.staticLibrary
val libraryPaths = def.config.libraryPaths + arguments.libraryPath
argsToCompiler ?. let { it.addAll(argsToCompiler(staticLibraries, libraryPaths)) }
val fqParts = (arguments.pkg ?: def.config.packageName)?.let {
it.split('.')
} ?: defFile!!.name.split('.').reversed().drop(1)
@@ -414,4 +396,6 @@ private fun processLib(arguments: CInteropArguments,
if (!arguments.keepcstubs) {
outCFile.delete()
}
return argsToCompiler(staticLibraries, libraryPaths)
}
@@ -0,0 +1,411 @@
package org.jetbrains.kotlin.native.interop.gen.wasm
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.native.interop.gen.argsToCompiler
import org.jetbrains.kotlin.native.interop.gen.wasm.idl.*
import org.jetbrains.kotlin.native.interop.tool.CommonInteropArguments
import org.jetbrains.kotlin.native.interop.tool.parseCommandLine
fun kotlinHeader(packageName: String): String {
return "package $packageName\n" +
"import kotlinx.wasm.jsinterop.*\n"
}
fun Type.toKotlinType(argName: String? = null): String = when (this) {
is idlVoid -> "Unit"
is idlInt -> "Int"
is idlFloat -> "Float"
is idlDouble -> "Double"
is idlString -> "String"
is idlObject -> "JsValue"
is idlFunction -> "KtFunction<R${argName!!}>"
is idlInterfaceRef -> name
else -> error("Unexpected type")
}
fun Arg.wasmMapping(): String = when (type) {
is idlVoid -> error("An arg can not be idlVoid")
is idlInt -> name
is idlFloat -> name
is idlDouble -> "doubleUpper($name), doubleLower($name)"
is idlString -> "stringPointer($name), stringLengthBytes($name)"
is idlObject -> TODO("implement me")
is idlFunction -> "wrapFunction<R$name>($name), ArenaManager.currentArena"
is idlInterfaceRef -> TODO("Implement me")
else -> error("Unexpected type")
}
fun Type.wasmReturnArg(): String =
when (this) {
is idlVoid -> "ArenaManager.currentArena" // TODO: optimize.
is idlInt -> "ArenaManager.currentArena"
is idlFloat -> "ArenaManager.currentArena"
is idlDouble -> "ArenaManager.currentArena"
is idlString -> "ArenaManager.currentArena"
is idlObject -> "ArenaManager.currentArena"
is idlFunction -> "ArenaManager.currentArena"
is idlInterfaceRef -> "ArenaManager.currentArena"
else -> error("Unexpected type")
}
val Operation.wasmReturnArg: String get() = returnType.wasmReturnArg()
val Attribute.wasmReturnArg: String get() = type.wasmReturnArg()
fun Arg.wasmArgNames(): List<String> = when (type) {
is idlVoid -> error("An arg can not be idlVoid")
is idlInt -> listOf(name)
is idlFloat -> listOf(name)
is idlDouble -> listOf("${name}Upper", "${name}Lower")
is idlString -> listOf("${name}Ptr", "${name}Len")
is idlObject -> TODO("implement me (idlObject)")
is idlFunction -> listOf("${name}Index", "${name}ResultArena")
is idlInterfaceRef -> TODO("Implement me (idlInterfaceRef)")
else -> error("Unexpected type")
}
fun Type.wasmReturnMapping(value: String): String = when (this) {
is idlVoid -> ""
is idlInt -> value
is idlFloat -> value
is idlDouble -> value
is idlString -> "TODO(\"Implement me\")"
is idlObject -> "JsValue(ArenaManager.currentArena, $value)"
is idlFunction -> "TODO(\"Implement me\")"
is idlInterfaceRef -> "$name(ArenaManager.currentArena, $value)"
else -> error("Unexpected type")
}
fun wasmFunctionName(functionName: String, interfaceName: String)
= "knjs__${interfaceName}_$functionName"
fun wasmSetterName(propertyName: String, interfaceName: String)
= "knjs_set__${interfaceName}_$propertyName"
fun wasmGetterName(propertyName: String, interfaceName: String)
= "knjs_get__${interfaceName}_$propertyName"
val Operation.kotlinTypeParameters: String get() {
val lambdaRetTypes = args.filter { it.type is idlFunction }
.map { "R${it.name}" }. joinToString(", ")
return if (lambdaRetTypes == "") "" else "<$lambdaRetTypes>"
}
val Interface.wasmReceiverArgs get() =
if (isGlobal) emptyList()
else listOf("this.arena", "this.index")
fun Member.wasmReceiverArgs(parent: Interface) =
if (isStatic) emptyList()
else parent.wasmReceiverArgs
fun Type.generateKotlinCall(name: String, wasmArgList: String) =
"$name($wasmArgList)"
fun Type.generateKotlinCallWithReturn(name: String, wasmArgList: String) =
when(this) {
is idlVoid -> " ${generateKotlinCall(name, wasmArgList)}\n"
is idlDouble -> " ${generateKotlinCall(name, wasmArgList)}\n" +
" val wasmRetVal = ReturnSlot_getDouble()\n"
else -> " val wasmRetVal = ${generateKotlinCall(name, wasmArgList)}\n"
}
fun Operation.generateKotlinCallWithReturn(parent_name: String, wasmArgList: String) =
returnType.generateKotlinCallWithReturn(
wasmFunctionName(name, parent_name),
wasmArgList)
fun Attribute.generateKotlinGetterCallWithReturn(parent_name: String, wasmArgList: String) =
type.generateKotlinCallWithReturn(
wasmGetterName(name, parent_name),
wasmArgList)
fun Operation.generateKotlin(parent: Interface): String {
val argList = args.map {
"${it.name}: ${it.type.toKotlinType(it.name)}"
}.joinToString(", ")
val wasmArgList = (wasmReceiverArgs(parent) + args.map(Arg::wasmMapping) + wasmReturnArg).joinToString(", ")
// TODO: there can be multiple Rs.
return " fun $kotlinTypeParameters $name(" +
argList +
"): ${returnType.toKotlinType()} {\n" +
generateKotlinCallWithReturn(parent.name, wasmArgList) +
" return ${returnType.wasmReturnMapping("wasmRetVal")}\n"+
" }\n\n"
}
fun Attribute.generateKotlinSetter(parent: Interface): String {
val kotlinType = type.toKotlinType(name)
return " set(value: $kotlinType) {\n" +
" ${wasmSetterName(name, parent.name)}(" +
(wasmReceiverArgs(parent) + Arg("value", type).wasmMapping()).joinToString(", ") +
")\n" +
" }\n\n"
}
fun Attribute.generateKotlinGetter(parent: Interface): String {
val wasmArgList = (wasmReceiverArgs(parent) + wasmReturnArg).joinToString(", ")
return " get() {\n" +
generateKotlinGetterCallWithReturn(parent.name, wasmArgList) +
" return ${type.wasmReturnMapping("wasmRetVal")}\n"+
" }\n\n"
}
fun Attribute.generateKotlin(parent: Interface): String {
val kotlinType = type.toKotlinType(name)
val varOrVal = if (readOnly) "val" else "var"
return " $varOrVal $name: $kotlinType\n" +
generateKotlinGetter(parent) +
if (!readOnly) generateKotlinSetter(parent) else ""
}
val Interface.wasmTypedReceiverArgs get() =
if (isGlobal) emptyList()
else listOf("arena: Int", "index: Int")
fun Member.wasmTypedReceiverArgs(parent: Interface) =
if (isStatic) emptyList() else parent.wasmTypedReceiverArgs
fun Operation.generateWasmStub(parent: Interface): String {
val wasmName = wasmFunctionName(this.name, parent.name)
val allArgs = (wasmTypedReceiverArgs(parent) + args.toList().wasmTypedMapping() + wasmTypedReturnMapping).joinToString(", ")
return "@SymbolName(\"$wasmName\")\n" +
"external public fun $wasmName($allArgs): ${returnType.wasmReturnTypeMapping()}\n\n"
}
fun Attribute.generateWasmSetterStub(parent: Interface): String {
val wasmSetter = wasmSetterName(this.name, parent.name)
val allArgs = (wasmTypedReceiverArgs(parent) + Arg("value", this.type).wasmTypedMapping()).joinToString(", ")
return "@SymbolName(\"$wasmSetter\")\n" +
"external public fun $wasmSetter($allArgs): Unit\n\n"
}
fun Attribute.generateWasmGetterStub(parent: Interface): String {
val wasmGetter = wasmGetterName(this.name, parent.name)
val allArgs = (wasmTypedReceiverArgs(parent) + wasmTypedReturnMapping).joinToString(", ")
return "@SymbolName(\"$wasmGetter\")\n" +
"external public fun $wasmGetter($allArgs): Int\n\n"
}
fun Attribute.generateWasmStubs(parent: Interface) =
generateWasmGetterStub(parent) +
if (!readOnly) generateWasmSetterStub(parent) else ""
// TODO: consider using virtual methods
fun Member.generateKotlin(parent: Interface): String = when (this) {
is Operation -> this.generateKotlin(parent)
is Attribute -> this.generateKotlin(parent)
else -> error("Unexpected member")
}
// TODO: consider using virtual methods
fun Member.generateWasmStub(parent: Interface) =
when (this) {
is Operation -> this.generateWasmStub(parent)
is Attribute -> this.generateWasmStubs(parent)
else -> error("Unexpected member")
}
fun Arg.wasmTypedMapping()
= this.wasmArgNames().map { "$it: Int" } .joinToString(", ")
// TODO: Optimize for simple types.
fun Type.wasmTypedReturnMapping(): String = "resultArena: Int"
val Operation.wasmTypedReturnMapping get() = returnType.wasmTypedReturnMapping()
val Attribute.wasmTypedReturnMapping get() = type.wasmTypedReturnMapping()
fun List<Arg>.wasmTypedMapping()
= this.map(Arg::wasmTypedMapping)
// TODO: more complex return types, such as returning a pair of Ints
// will require a more complex approach.
fun Type.wasmReturnTypeMapping()
= if (this == idlVoid) "Unit" else "Int"
fun Interface.generateMemberWasmStubs() =
members.map {
it.generateWasmStub(this)
}.joinToString("")
fun Interface.generateKotlinMembers() =
members.filterNot { it.isStatic } .map {
it.generateKotlin(this)
}.joinToString("")
fun Interface.generateKotlinCompanion() =
" companion object {\n" +
members.filter { it.isStatic } .map {
it.generateKotlin(this)
}.joinToString("") +
" }\n"
fun Interface.generateKotlinClassHeader() =
"open class $name(arena: Int, index: Int): JsValue(arena, index) {\n" +
" constructor(jsValue: JsValue): this(jsValue.arena, jsValue.index)\n"
fun Interface.generateKotlinClassFooter() =
"}\n"
fun Interface.generateKotlinClassConverter() =
"val JsValue.as$name: $name\n" +
" get() {\n" +
" return $name(this.arena, this.index)\n"+
" }\n"
fun Interface.generateKotlin(): String {
fun unlessGlobal(value: () -> String): String {
return if (this.isGlobal) "" else value()
}
return generateMemberWasmStubs() +
unlessGlobal { generateKotlinClassHeader() } +
generateKotlinMembers() +
unlessGlobal {
generateKotlinCompanion() +
generateKotlinClassFooter() +
generateKotlinClassConverter()
}
}
fun generateKotlin(pkg: String, interfaces: List<Interface>) =
kotlinHeader(pkg) +
interfaces.map {
it.generateKotlin()
}.joinToString("\n") +
if (pkg == "kotlinx.interop.wasm.dom") // TODO: make it a general solution.
"fun <R> setInterval(interval: Int, lambda: KtFunction<R>) = setInterval(lambda, interval)\n"
else ""
/////////////////////////////////////////////////////////
fun Arg.composeWasmArgs(): String = when (type) {
is idlVoid -> error("An arg can not be idlVoid")
is idlInt -> ""
is idlFloat -> ""
is idlDouble ->
" var $name = twoIntsToDouble(${name}Upper, ${name}Lower);\n"
is idlString ->
" var $name = toUTF16String(${name}Ptr, ${name}Len);\n"
is idlObject -> TODO("implement me")
is idlFunction ->
" var $name = konan_dependencies.env.Konan_js_wrapLambda(lambdaResultArena, ${name}Index);\n"
is idlInterfaceRef -> TODO("Implement me")
else -> error("Unexpected type")
}
val Interface.receiver get() =
if (isGlobal) "" else "kotlinObject(arena, obj)."
fun Member.receiver(parent: Interface) =
if (isStatic) "${parent.name}." else parent.receiver
val Interface.wasmReceiverArgName get() =
if (isGlobal) emptyList() else listOf("arena", "obj")
fun Member.wasmReceiverArgName(parent: Interface) =
if (isStatic) emptyList() else parent.wasmReceiverArgName
val Operation.wasmReturnArgName get() =
returnType.wasmReturnArgName
val Attribute.wasmReturnArgName get() =
type.wasmReturnArgName
val Type.wasmReturnArgName get() =
when (this) {
is idlVoid -> emptyList()
is idlInt -> emptyList()
is idlFloat -> emptyList()
is idlDouble -> emptyList()
is idlString -> listOf("resultArena")
is idlObject -> listOf("resultArena")
is idlInterfaceRef -> listOf("resultArena")
else -> error("Unexpected type: $this")
}
val Type.wasmReturnExpression get() =
when(this) {
is idlVoid -> ""
is idlInt -> "result"
is idlFloat -> "result" // TODO: can we really pass floats as is?
is idlDouble -> "doubleToReturnSlot(result)"
is idlString -> "toArena(resultArena, result)"
is idlObject -> "toArena(resultArena, result)"
is idlInterfaceRef -> "toArena(resultArena, result)"
else -> error("Unexpected type: $this")
}
fun Operation.generateJs(parent: Interface): String {
val allArgs = wasmReceiverArgName(parent) + args.map { it.wasmArgNames() }.flatten() + wasmReturnArgName
val wasmMapping = allArgs.joinToString(", ")
val argList = args.map { it.name }. joinToString(", ")
val composedArgsList = args.map { it.composeWasmArgs() }. joinToString("")
return "\n ${wasmFunctionName(this.name, parent.name)}: function($wasmMapping) {\n" +
composedArgsList +
" var result = ${receiver(parent)}$name($argList);\n" +
" return ${returnType.wasmReturnExpression};\n" +
" }"
}
fun Attribute.generateJsSetter(parent: Interface): String {
val valueArg = Arg("value", type)
val allArgs = wasmReceiverArgName(parent) + valueArg.wasmArgNames()
val wasmMapping = allArgs.joinToString(", ")
return "\n ${wasmSetterName(name, parent.name)}: function($wasmMapping) {\n" +
valueArg.composeWasmArgs() +
" ${receiver(parent)}$name = value;\n" +
" }"
}
fun Attribute.generateJsGetter(parent: Interface): String {
val allArgs = wasmReceiverArgName(parent) + wasmReturnArgName
val wasmMapping = allArgs.joinToString(", ")
return "\n ${wasmGetterName(name, parent.name)}: function($wasmMapping) {\n" +
" var result = ${receiver(parent)}$name;\n" +
" return ${type.wasmReturnExpression};\n" +
" }"
}
fun Attribute.generateJs(parent: Interface) =
generateJsGetter(parent) +
if (!readOnly) ",\n${generateJsSetter(parent)}" else ""
fun Member.generateJs(parent: Interface): String = when (this) {
is Operation -> this.generateJs(parent)
is Attribute -> this.generateJs(parent)
else -> error("Unexpected member")
}
fun generateJs(interfaces: List<Interface>): String =
"konan.libraries.push ({\n" +
interfaces.map { interf ->
interf.members.map { member ->
member.generateJs(interf)
}
}.flatten() .joinToString(",\n") +
"\n})\n"
const val idlMathPackage = "kotlinx.interop.wasm.math"
const val idlDomPackage = "kotlinx.interop.wasm.dom"
fun processIdlLib(args: Array<String>): Array<String> {
val arguments = parseCommandLine(args, CommonInteropArguments())
// TODO: Refactor me.
val userDir = System.getProperty("user.dir")
val ktGenRoot = File(arguments.generated ?: userDir).mkdirs()
val nativeLibsDir = File(arguments.natives ?: userDir).mkdirs()
val idl = when (arguments.pkg) {
idlMathPackage-> idlMath
idlDomPackage -> idlDom
else -> throw IllegalArgumentException("Please choose either $idlMathPackage or $idlDomPackage for -pkg argument")
}
File(ktGenRoot, "kotlin_stubs.kt").writeText(generateKotlin(arguments.pkg!!, idl))
File(nativeLibsDir, "js_stubs.js").writeText(generateJs(idl))
File(arguments.manifest!!).writeText("") // The manifest is currently unused for wasm.
return argsToCompiler(arguments.staticLibrary, arguments.libraryPath)
}
@@ -0,0 +1,50 @@
package org.jetbrains.kotlin.native.interop.gen.wasm.idl
// This shall be an output of Web IDL parser.
val idlDom = listOf(
Interface("Context",
Attribute("lineWidth", idlInt),
Attribute("fillStyle", idlString),
Attribute("strokeStyle", idlString),
Operation("lineTo", idlVoid, Arg("x", idlInt), Arg("y", idlInt)),
Operation("moveTo", idlVoid, Arg("x", idlInt), Arg("y", idlInt)),
Operation("beginPath", idlVoid),
Operation("stroke", idlVoid),
Operation("fillRect", idlVoid, Arg("x", idlInt), Arg("y", idlInt), Arg("width", idlInt), Arg("height", idlInt)),
Operation("fillText", idlVoid, Arg("test", idlString), Arg("x", idlInt), Arg("y", idlInt), Arg("maxWidth", idlInt)),
Operation("fill", idlVoid),
Operation("closePath", idlVoid)
),
Interface("DOMRect",
Attribute("left", idlInt),
Attribute("right", idlInt),
Attribute("top", idlInt),
Attribute("bottom", idlInt)
),
Interface("Canvas",
Operation("getContext", idlInterfaceRef("Context"), Arg("context", idlString)),
Operation("getBoundingClientRect", idlInterfaceRef("DOMRect"))
),
Interface("Document",
Operation("getElementById", idlObject, Arg("id", idlString))
),
Interface("MouseEvent",
Attribute("clientX", idlInt, readOnly = true),
Attribute("clientY", idlInt, readOnly = true)
),
Interface("Response",
Operation("json", idlObject)
),
Interface("Promise",
Operation("then", idlInterfaceRef("Promise"), Arg("lambda", idlFunction))
),
Interface("__Global",
Attribute("document", idlInterfaceRef("Document"), readOnly = true),
Operation("fetch", idlInterfaceRef("Promise"), Arg("url", idlString)),
Operation("setInterval", idlVoid, Arg("lambda", idlFunction), Arg("interval", idlInt))
)
)
@@ -0,0 +1,37 @@
package org.jetbrains.kotlin.native.interop.gen.wasm.idl
// This is (as of now) a poor man's IDL representation.
interface Type
interface Member {
val isStatic: Boolean get() = false
}
object idlVoid: Type
object idlInt: Type
object idlFloat: Type
object idlDouble: Type
object idlString: Type
object idlObject: Type
object idlFunction: Type
data class Attribute(val name: String, val type: Type,
val readOnly: Boolean = false,
override val isStatic: Boolean = false): Member
data class Arg(val name: String, val type: Type)
class Operation(val name: String, val returnType: Type,
override val isStatic: Boolean = false,
vararg val args: Arg): Member {
constructor(name: String, returnType: Type, vararg args: Arg) :
this(name, returnType, false, *args)
}
data class idlInterfaceRef(val name: String): Type
class Interface(val name: String, vararg val members: Member) {
val isGlobal = (name == "__Global")
}
@@ -0,0 +1,55 @@
package org.jetbrains.kotlin.native.interop.gen.wasm.idl
// There are no WebIDL descriptions of Math,
// so in any case this one will be a part of the project.
// Although, may be in the form of our own WebIDL source.
val idlMath = listOf(
Interface("Math",
Attribute("E", idlDouble, readOnly = true, isStatic = true),
Attribute("LN2", idlDouble, readOnly = true, isStatic = true),
Attribute("LN10", idlDouble, readOnly = true, isStatic = true),
Attribute("LOG2E", idlDouble, readOnly = true, isStatic = true),
Attribute("LOG10E", idlDouble, readOnly = true, isStatic = true),
Attribute("PI", idlDouble, readOnly = true, isStatic = true),
Attribute("SQRT1_2", idlDouble, readOnly = true, isStatic = true),
Attribute("SQRT2", idlDouble, readOnly = true, isStatic = true),
Operation("abs", idlDouble, true, Arg("x", idlDouble)),
Operation("acos", idlDouble, true, Arg("x", idlDouble)),
Operation("acosh", idlDouble, true, Arg("x", idlDouble)),
Operation("asin", idlDouble, true, Arg("x", idlDouble)),
Operation("asinh", idlDouble, true, Arg("x", idlDouble)),
Operation("atan", idlDouble, true, Arg("x", idlDouble)),
Operation("atanh", idlDouble, true, Arg("x", idlDouble)),
Operation("atan2", idlDouble, true, Arg("y", idlDouble), Arg("x", idlDouble)),
Operation("cbrt", idlDouble, true, Arg("x", idlDouble)),
Operation("ceil", idlDouble, true, Arg("x", idlDouble)),
Operation("clz32", idlDouble, true, Arg("x", idlDouble)),
Operation("cos", idlDouble, true, Arg("x", idlDouble)),
Operation("cosh", idlDouble, true, Arg("x", idlDouble)),
Operation("exp", idlDouble, true, Arg("x", idlDouble)),
Operation("expm1", idlDouble, true, Arg("x", idlDouble)),
Operation("floor", idlDouble, true, Arg("x", idlDouble)),
Operation("fround", idlDouble, true, Arg("x", idlDouble)),
//Operation("hypot([x[, y[, …]]]),
//Operation("imul(x, y),
Operation("log", idlDouble, true, Arg("x", idlDouble)),
Operation("log1p", idlDouble, true, Arg("x", idlDouble)),
Operation("log10", idlDouble, true, Arg("x", idlDouble)),
Operation("log2", idlDouble, true, Arg("x", idlDouble)),
//Operation("max([x[, y[, …]]]), // TODO: Support varargs.
//Operation("min([x[, y[, …]]]),
Operation("pow", idlDouble, true, Arg("x", idlDouble), Arg("y", idlDouble)),
Operation("random", idlDouble, true),
Operation("round", idlDouble, true, Arg("x", idlDouble)),
Operation("sign", idlDouble, true, Arg("x", idlDouble)),
Operation("sin", idlDouble, true, Arg("x", idlDouble)),
Operation("sinh", idlDouble, true, Arg("x", idlDouble)),
Operation("sqrt", idlDouble, true, Arg("x", idlDouble)),
Operation("tan", idlDouble, true, Arg("x", idlDouble)),
Operation("tanh", idlDouble, true, Arg("x", idlDouble)),
//Operation("toSource(),
Operation("trunc", idlDouble, true, Arg("x", idlDouble))
)
)
@@ -278,13 +278,20 @@ internal open class WasmPlatform(distribution: Distribution)
private fun javaScriptLink(jsFiles: List<String>, executable: String): String {
val linkedJavaScript = File("$executable.js")
val linkerStub = "var konan = { libraries: [] };\n"
val linkerHeader = "var konan = { libraries: [] };\n"
val linkerFooter = """|if (isBrowser()) {
| konan.moduleEntry([]);
|} else {
| konan.moduleEntry(arguments);
|}""".trimMargin()
linkedJavaScript.writeBytes(linkerStub.toByteArray());
linkedJavaScript.writeBytes(linkerHeader.toByteArray());
jsFiles.forEach {
linkedJavaScript.appendBytes(File(it).readBytes())
}
linkedJavaScript.appendBytes(linkerFooter.toByteArray());
return linkedJavaScript.name
}
}
+12
View File
@@ -2370,6 +2370,18 @@ if (isMac()) {
}
}
task jsinterop_math(type: RunStandaloneKonanTest) {
doFirst {
// TODO: We probably need a NativeInteropPlugin for jsinterop?
"jsinterop -pkg kotlinx.interop.wasm.math -o $buildDir/jsmath -target wasm32".execute()
}
disabled = (project.testTarget != 'wasm32')
goldValue = "e = 2.718281828459045, pi = 3.141592653589793, sin(pi) = 1.2246467991473532E-16, sin(pi/2) = 1.0, ln(1) = 0.0, ln(e) = 1.0\n"
source = "jsinterop/math.kt"
flags = ["-r", "$buildDir", "-l", "jsmath"]
}
task produce_dynamic(type: DynamicKonanTest) {
disabled = (project.testTarget != null && project.testTarget != project.hostName)
source = "produce_dynamic/simple/hello.kt"
+16
View File
@@ -0,0 +1,16 @@
import kotlinx.interop.wasm.math.*
import kotlinx.wasm.jsinterop.*
fun main(args: Array<String>) {
val e = Math.E
val pi = Math.PI
val sin_pi = Math.sin(pi)
val sin_pi_2 = Math.sin(pi/2)
val ln_1 = Math.log(1.0)
val ln_e = Math.log(e)
println("e = $e, pi = $pi, sin(pi) = $sin_pi, sin(pi/2) = $sin_pi_2, ln(1) = $ln_1, ln(e) = $ln_e")
}
+3
View File
@@ -311,6 +311,9 @@ targetList.each { target ->
task("${target}PlatformLibs") {
dependsOn ":platformLibs:${target}Install"
if (target == 'wasm32') {
dependsOn 'Interop:JsRuntime:installJsRuntime'
}
}
task("${target}CrossDist") {
Executable
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Copyright 2010-2017 JetBrains s.r.o.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
DIR="${BASH_SOURCE[0]%/*}"
: ${DIR:="."}
"${DIR}"/run_konan jsinterop "$@"
+17
View File
@@ -0,0 +1,17 @@
@echo off
rem Copyright 2010-2017 JetBrains s.r.o.
rem
rem Licensed under the Apache License, Version 2.0 (the "License");
rem you may not use this file except in compliance with the License.
rem You may obtain a copy of the License at
rem
rem http://www.apache.org/licenses/LICENSE-2.0
rem
rem Unless required by applicable law or agreed to in writing, software
rem distributed under the License is distributed on an "AS IS" BASIS,
rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
rem See the License for the specific language governing permissions and
rem limitations under the License.
call %~dps0run_konan.bat jsinterop %*
-1
View File
@@ -2,7 +2,6 @@ import org.jetbrains.kotlin.KlibInstall
import org.jetbrains.kotlin.konan.target.TargetManager
import org.jetbrains.kotlin.konan.util.DefFile
import static org.jetbrains.kotlin.konan.util.VisibleNamedKt.getVisibleName
apply plugin: 'konan'
+40 -12
View File
@@ -95,6 +95,24 @@ function toUTF16String(pointer, size) {
return string;
}
function twoIntsToDouble(upper, lower) {
var buffer = new ArrayBuffer(8);
var ints = new Int32Array(buffer);
var doubles = new Float64Array(buffer);
ints[1] = upper;
ints[0] = lower;
return doubles[0];
}
function doubleToTwoInts(value) {
var buffer = new ArrayBuffer(8);
var ints = new Int32Array(buffer);
var doubles = new Float64Array(buffer);
doubles[0] = value;
var twoInts = {upper: ints[1], lower: ints[0]};
return twoInts
}
function int32ToHeap(value, pointer) {
heap[pointer] = value & 0xff;
heap[pointer+1] = (value & 0xff00) >>> 8;
@@ -102,6 +120,11 @@ function int32ToHeap(value, pointer) {
heap[pointer+3] = (value & 0xff000000) >>> 24;
}
function doubleToReturnSlot(value) {
var twoInts = doubleToTwoInts(value);
instance.exports.ReturnSlot_setDouble(twoInts.upper, twoInts.lower);
}
function stackTop() {
// Read the value module's `__stack_pointer` is initialized with.
// It is the very first static in .data section.
@@ -239,17 +262,22 @@ function instantiateAndRunSync(arraybuffer, args) {
return invokeModule(instance, args)
}
if (isBrowser()) {
var filename = document.currentScript.getAttribute("wasm");
fetch(filename).then( function(response) {
return response.arrayBuffer();
}).then(function(arraybuffer) {
instantiateAndRun(arraybuffer, [filename]);
});
} else {
// Invoke from d8.
var arrayBuffer = readbuffer(arguments[0]);
var exitStatus = instantiateAndRunSync(arrayBuffer, arguments);
quit(exitStatus);
konan.moduleEntry = function (args) {
if (isBrowser()) {
if (!document.currentScript.hasAttribute("wasm")) {
throw new Error('Could not find the wasm attribute pointing to the WebAssembly binary.') ;
}
var filename = document.currentScript.getAttribute("wasm");
fetch(filename).then( function(response) {
return response.arrayBuffer();
}).then(function(arraybuffer) {
instantiateAndRun(arraybuffer, [filename]);
});
} else {
// Invoke from d8.
var arrayBuffer = readbuffer(args[0]);
var exitStatus = instantiateAndRunSync(arrayBuffer, args);
quit(exitStatus);
}
}
+35
View File
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ReturnSlot.h"
#ifdef KONAN_WASM
namespace {
THREAD_LOCAL_VARIABLE long long storage;
}
extern "C" {
KDouble ReturnSlot_getDouble() {
return *reinterpret_cast<KDouble*>(&::storage);
}
void ReturnSlot_setDouble(KInt upper, KInt lower) {
reinterpret_cast<KInt*>(&::storage)[0] = lower;
reinterpret_cast<KInt*>(&::storage)[1] = upper;
}
}
#endif
+24
View File
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "Types.h"
#ifdef KONAN_WASM
extern "C" {
KDouble ReturnSlot_getDouble();
void ReturnSlot_setDouble(KInt upper, KInt lower);
}
#endif
+3 -16
View File
@@ -12,25 +12,12 @@ if defined KONAN_HOME (
if not exist build\bin\ (mkdir build\bin)
if not exist build\klib\ (mkdir build\klib)
call konanc %DIR%\src\jsinterop\kotlin ^
-includeBinary %DIR%\src\jsinterop\js\jsinterop.js ^
-p library -o %DIR%\build\klib\jsinterop -target wasm32 || exit /b
call konanc %DIR%\src\stubGenerator\kotlin ^
-e org.jetbrains.kotlin.konan.jsinterop.tool.main ^
-o %DIR%\build\bin\generator || exit /b
::: TODO: make a couple of args to name the result, for example.
%DIR%\build\bin\generator.exe || exit /b
::: TODO: use the proper path and names
call konanc kotlin_stubs.kt ^
-includeBinary js_stubs.js ^
-l %DIR%\build\klib\jsinterop ^
-p library -o %DIR%\build\klib\canvas -target wasm32 || exit /b
call jsinterop -pkg kotlinx.interop.wasm.dom ^
-o %DIR%\build\klib\dom -target wasm32 || exit /b
call konanc %DIR%\src\main\kotlin ^
-r %DIR%\build\klib -l jsinterop -l canvas ^
-r %DIR%\build\klib -l dom ^
-o %DIR%\build\bin\html5Canvas -target wasm32 || exit 1
echo "Artifact path is %DIR%\build\bin\html5Canvas.wasm"
+3 -17
View File
@@ -20,25 +20,11 @@ COMPILER_ARGS=${!var} # add -opt for an optimized build.
mkdir -p $DIR/build/bin
mkdir -p $DIR/build/klib
konanc $DIR/src/jsinterop/kotlin \
-includeBinary $DIR/src/jsinterop/js/jsinterop.js \
-p library -o $DIR/build/klib/jsinterop -target wasm32 || exit 1
konanc $DIR/src/stubGenerator/kotlin \
-e org.jetbrains.kotlin.konan.jsinterop.tool.main \
-o $DIR/build/bin/generator || exit 1
# TODO: make a couple of args to name the result, for example.
$DIR/build/bin/generator.kexe || exit 1
# TODO: use the proper path and names
konanc kotlin_stubs.kt \
-includeBinary js_stubs.js \
-l $DIR/build/klib/jsinterop \
-p library -o $DIR/build/klib/canvas -target wasm32 || exit 1
jsinterop -pkg kotlinx.interop.wasm.dom \
-o $DIR/build/klib/dom -target wasm32 || exit 1
konanc $DIR/src/main/kotlin \
-r $DIR/build/klib -l jsinterop -l canvas \
-r $DIR/build/klib -l dom \
-o $DIR/build/bin/html5Canvas -target wasm32 || exit 1
echo "Artifact path is $DIR/build/bin/html5Canvas.wasm"
+1 -2
View File
@@ -1,6 +1,5 @@
import html5.minimal.*
import kotlinx.interop.wasm.dom.*
import kotlinx.wasm.jsinterop.*
import konan.internal.ExportForCppRuntime
fun main(args: Array<String>) {
+1
View File
@@ -16,6 +16,7 @@
include ':dependencies'
include ':Interop:Indexer'
include ':Interop:JsRuntime'
include ':Interop:StubGenerator'
include ':Interop:Runtime'
include ':llvmDebugInfoC'
@@ -118,6 +118,8 @@ data class File constructor(internal val javaPath: Path) {
Files.write(javaPath, lines)
}
fun writeText(text: String): Unit = writeLines(listOf(text))
fun forEachLine(action: (String) -> Unit) {
Files.lines(javaPath).use { lines ->
lines.forEach { action(it) }
@@ -0,0 +1,119 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.utilities
import org.jetbrains.kotlin.backend.konan.library.defaultResolver
import org.jetbrains.kotlin.backend.konan.library.impl.KonanLibrary
import org.jetbrains.kotlin.backend.konan.library.resolveLibrariesRecursive
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.properties.loadProperties
import org.jetbrains.kotlin.konan.target.TargetManager
import org.jetbrains.kotlin.native.interop.gen.jvm.interop
import org.jetbrains.kotlin.utils.addIfNotNull
private val NODEFAULTLIBS = "-nodefaultlibs"
private val PURGE_USER_LIBS = "--purge_user_libs"
// TODO: this function should eventually be eliminated from 'utilities'.
// The interaction of interop and the compler should be streamlined.
fun invokeInterop(flavor: String, args: Array<String>): Array<String> {
val cinteropArgFilter = listOf(NODEFAULTLIBS, PURGE_USER_LIBS)
var outputFileName = "nativelib"
var target = "host"
val libraries = mutableListOf<String>()
val repos = mutableListOf<String>()
var noDefaultLibs = false
var purgeUserLibs = false
for (i in args.indices) {
val arg = args[i]
val nextArg = args.getOrNull(i + 1)
if (arg.startsWith("-o"))
outputFileName = nextArg ?: outputFileName
if (arg == "-target")
target = nextArg ?: target
if (arg == "-library")
libraries.addIfNotNull(nextArg)
if (arg == "-r" || arg == "-repo")
repos.addIfNotNull(nextArg)
if (arg == NODEFAULTLIBS)
noDefaultLibs = true
if (arg == PURGE_USER_LIBS)
purgeUserLibs = true
}
val buildDir = File("$outputFileName-build")
val generatedDir = File(buildDir, "kotlin")
val nativesDir = File(buildDir, "natives")
val cstubsName ="cstubs"
val manifest = File(buildDir, "manifest.properties")
val targetManager = TargetManager(target)
val resolver = defaultResolver(repos, targetManager)
val allLibraries = resolver.resolveLibrariesRecursive(
libraries, targetManager.target, noStdLib = true, noDefaultLibs = noDefaultLibs
)
val importArgs = allLibraries.flatMap {
val library = KonanLibrary(it.libraryFile)
val manifestProperties = library.manifestFile.loadProperties()
// TODO: handle missing properties?
manifestProperties["package"]?.let {
val pkg = it as String
val headerIds = (manifestProperties["includedHeaders"] as String).split(' ')
val arg = "$pkg:${headerIds.joinToString(";")}"
listOf("-import", arg)
} ?: emptyList()
}
val additionalArgs = listOf<String>(
"-generated", generatedDir.path,
"-natives", nativesDir.path,
"-cstubsname", cstubsName,
"-manifest", manifest.path,
"-flavor", flavor
) + importArgs
val cinteropArgs = (additionalArgs + args.filter { it !in cinteropArgFilter }).toTypedArray()
val cinteropArgsToCompiler = interop(flavor, cinteropArgs) ?: emptyArray<String>()
val nativeStubs =
if (flavor == "wasm")
arrayOf("-includeBinary", File(nativesDir, "js_stubs.js").path)
else
arrayOf("-nativelibrary",File(nativesDir, "$cstubsName.bc").path)
val konancArgs = arrayOf(
generatedDir.path,
"-produce", "library",
"-o", outputFileName,
"-target", target,
"-manifest", manifest.path) +
nativeStubs +
cinteropArgsToCompiler +
libraries.flatMap { listOf("-library", it) } +
repos.flatMap { listOf("-repo", it) } +
(if (noDefaultLibs) arrayOf(NODEFAULTLIBS) else emptyArray()) +
(if (purgeUserLibs) arrayOf(PURGE_USER_LIBS) else emptyArray())
return konancArgs
}
@@ -16,105 +16,23 @@
package org.jetbrains.kotlin.cli.utilities
import org.jetbrains.kotlin.backend.konan.library.defaultResolver
import org.jetbrains.kotlin.backend.konan.library.impl.KonanLibrary
import org.jetbrains.kotlin.backend.konan.library.resolveLibrariesRecursive
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.properties.loadProperties
import org.jetbrains.kotlin.konan.target.TargetManager
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.cli.bc.main as konancMain
import org.jetbrains.kotlin.native.interop.gen.jvm.interop
import org.jetbrains.kotlin.cli.klib.main as klibMain
private val NODEFAULTLIBS = "-nodefaultlibs"
private val PURGE_USER_LIBS = "--purge_user_libs"
fun invokeCinterop(args: Array<String>) {
val cinteropArgFilter = listOf(NODEFAULTLIBS, PURGE_USER_LIBS)
var outputFileName = "nativelib"
var target = "host"
val libraries = mutableListOf<String>()
val repos = mutableListOf<String>()
var noDefaultLibs = false
var purgeUserLibs = false
for (i in args.indices) {
val arg = args[i]
val nextArg = args.getOrNull(i + 1)
if (arg.startsWith("-o"))
outputFileName = nextArg ?: outputFileName
if (arg == "-target")
target = nextArg ?: target
if (arg == "-library")
libraries.addIfNotNull(nextArg)
if (arg == "-r" || arg == "-repo")
repos.addIfNotNull(nextArg)
if (arg == NODEFAULTLIBS)
noDefaultLibs = true
if (arg == PURGE_USER_LIBS)
purgeUserLibs = true
}
val buildDir = File("$outputFileName-build")
val generatedDir = File(buildDir, "kotlin")
val nativesDir = File(buildDir, "natives")
val cstubsName ="cstubs"
val manifest = File(buildDir, "manifest.properties")
val targetManager = TargetManager(target)
val resolver = defaultResolver(repos, targetManager)
val allLibraries = resolver.resolveLibrariesRecursive(
libraries, targetManager.target, noStdLib = true, noDefaultLibs = noDefaultLibs
)
val importArgs = allLibraries.flatMap {
val library = KonanLibrary(it.libraryFile)
val manifestProperties = library.manifestFile.loadProperties()
// TODO: handle missing properties?
manifestProperties["package"]?.let {
val pkg = it as String
val headerIds = (manifestProperties["includedHeaders"] as String).split(' ')
val arg = "$pkg:${headerIds.joinToString(";")}"
listOf("-import", arg)
} ?: emptyList()
}
val additionalArgs = listOf<String>(
"-generated", generatedDir.path,
"-natives", nativesDir.path,
"-cstubsname", cstubsName,
"-manifest", manifest.path,
"-flavor", "native"
) + importArgs
val cinteropArgs = (additionalArgs + args.filter { it !in cinteropArgFilter }).toTypedArray()
val cinteropArgsToCompiler = mutableListOf<String>()
interop(cinteropArgs, cinteropArgsToCompiler)
val konancArgs = arrayOf(
generatedDir.path,
"-produce", "library",
"-nativelibrary", File(nativesDir, "$cstubsName.bc").path,
"-o", outputFileName,
"-target", target,
"-manifest", manifest.path
) + cinteropArgsToCompiler + libraries.flatMap { listOf("-library", it) } + repos.flatMap { listOf("-repo", it) } +
(if (noDefaultLibs) arrayOf(NODEFAULTLIBS) else emptyArray()) +
(if (purgeUserLibs) arrayOf(PURGE_USER_LIBS) else emptyArray())
konancMain(konancArgs)
}
fun main(args: Array<String>) {
val utilityName = args[0]
val utilityArgs = args.drop(1).toTypedArray()
when (utilityName) {
"konanc" ->
konancMain(utilityArgs)
"cinterop" ->
invokeCinterop(utilityArgs)
"cinterop" -> {
val konancArgs = invokeInterop("native", utilityArgs)
konancMain(konancArgs)
}
"jsinterop" -> {
val konancArgs = invokeInterop("wasm", utilityArgs)
konancMain(konancArgs)
}
"klib" ->
klibMain(utilityArgs)
else ->