diff --git a/Interop/JsRuntime/build.gradle b/Interop/JsRuntime/build.gradle new file mode 100644 index 00000000000..9852e405464 --- /dev/null +++ b/Interop/JsRuntime/build.gradle @@ -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' +} + diff --git a/samples/html5Canvas/src/jsinterop/js/jsinterop.js b/Interop/JsRuntime/src/main/js/jsinterop.js similarity index 100% rename from samples/html5Canvas/src/jsinterop/js/jsinterop.js rename to Interop/JsRuntime/src/main/js/jsinterop.js diff --git a/samples/html5Canvas/src/jsinterop/kotlin/jsinterop.kt b/Interop/JsRuntime/src/main/kotlin/jsinterop.kt similarity index 91% rename from samples/html5Canvas/src/jsinterop/kotlin/jsinterop.kt rename to Interop/JsRuntime/src/main/kotlin/jsinterop.kt index 65c8d0c8476..7edbe943273 100644 --- a/samples/html5Canvas/src/jsinterop/kotlin/jsinterop.kt +++ b/Interop/JsRuntime/src/main/kotlin/jsinterop.kt @@ -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 diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/LibraryUtils.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/LibraryUtils.kt new file mode 100644 index 00000000000..85101b0ebd9 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/LibraryUtils.kt @@ -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, libraryPaths: List): List { + val result = mutableListOf() + 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, libraryPaths: Array) = argsToCompiler(staticLibraries.toList(), libraryPaths.toList()) + +internal fun argsToCompiler(staticLibraries: List, libraryPaths: List) = + resolveLibraries(staticLibraries, libraryPaths) + .map { it -> listOf("-includeBinary", it) } + .flatten() + .toTypedArray() + diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt index bedab280452..50d3e6a0ea7 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/CommandLine.kt @@ -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 = "", description = "One of: jvm, native") + @Argument(value = "-flavor", valueDescription = "", description = "One of: jvm, native or wasm") var flavor: String? = null @Argument(value = "-pkg", valueDescription = "", description = "place generated bindings to the package") @@ -27,6 +27,18 @@ open class CommonInteropArguments : CommonToolArguments() { @Argument(value = "-generated", valueDescription = "", description = "place generated bindings to the directory") var generated: String? = null + + @Argument(value = "-natives", valueDescription = "", description = "where to put the built native files") + var natives: String? = null + + @Argument(value = "-manifest", valueDescription = "", description = "library manifest addend") + var manifest: String? = null + + @Argument(value = "-staticLibrary", valueDescription = "", description = "embed static library to the result") + var staticLibrary: Array = arrayOf() + + @Argument(value = "-libraryPath", valueDescription = "", description = "add a library search path") + var libraryPath: Array = arrayOf() } class CInteropArguments : CommonInteropArguments() { @@ -36,15 +48,9 @@ class CInteropArguments : CommonInteropArguments() { @Argument(value = "-target", valueDescription = "", description = "native target to compile to") var target: String? = null - @Argument(value = "-natives", valueDescription = "", description = "where to put the built native files") - var natives: String? = null - @Argument(value = "-def", valueDescription = "", description = "the library definition file") var def: String? = null - @Argument(value = "-manifest", valueDescription = "", description = "library manifest addend") - var manifest: String? = null - @Argument(value = "-properties", valueDescription = "", 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 = "", description = "use specified linker") + var linker: String? = null - - @Argument(value = "-staticLibrary", valueDescription = "", description = "embed static library to the result") - var staticLibrary: Array = arrayOf() - - @Argument(value = "-libraryPath", valueDescription = "", description = "add a library search path") - var libraryPath: Array = arrayOf() - @Argument(value = "-cstubsname", valueDescription = "", description = "provide a name for the generated c stubs file") var cstubsname: String? = null diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index a763e001362..fd3ace53e62 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -1001,5 +1001,4 @@ class StubGenerator( val mappingBridgeGenerator: MappingBridgeGenerator = MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator) - } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index 377fa0d5e62..9faf5a4f9b4 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -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) = interop(args, null) - -fun interop(args: Array, argsToCompiler: MutableList? = null) { - val arguments = parseCommandLine(args, CInteropArguments()) - processLib(arguments, argsToCompiler) +fun main(args: Array) { + processCLib(args) } +fun interop(flavor: String, args: Array) = 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, libraryPaths: List): List { - val result = mutableListOf() - 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, libraryPaths: List): List { - return resolveLibraries(staticLibraries, libraryPaths) - .map { it -> listOf("-includeBinary", it) } .flatten() -} - private fun parseImports(imports: Array): ImportsImpl { val headerIdToPackage = imports.map { arg -> val (pkg, joinedIds) = arg.split(':') @@ -247,9 +231,9 @@ private fun findFilesByGlobs(roots: List, globs: List): Map?) { +private fun processCLib(args: Array): Array? { + 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) } diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/StubGenerator.kt new file mode 100644 index 00000000000..68b9870f209 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/StubGenerator.kt @@ -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" + 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($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 = 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.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) = + kotlinHeader(pkg) + + interfaces.map { + it.generateKotlin() + }.joinToString("\n") + + if (pkg == "kotlinx.interop.wasm.dom") // TODO: make it a general solution. + "fun setInterval(interval: Int, lambda: KtFunction) = 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): 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): Array { + 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) +} diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/idl/dom.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/idl/dom.kt new file mode 100644 index 00000000000..0d799ce7671 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/idl/dom.kt @@ -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)) + ) +) + + diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/idl/idl.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/idl/idl.kt new file mode 100644 index 00000000000..0ccbd8e8fca --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/idl/idl.kt @@ -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") +} + + diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/idl/idlMath.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/idl/idlMath.kt new file mode 100644 index 00000000000..948059d0784 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/wasm/idl/idlMath.kt @@ -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)) + ) +) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt index e919ffac806..7a3b4138f66 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt @@ -278,13 +278,20 @@ internal open class WasmPlatform(distribution: Distribution) private fun javaScriptLink(jsFiles: List, 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 } } diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 979339a961a..93845201224 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -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" diff --git a/backend.native/tests/jsinterop/math.kt b/backend.native/tests/jsinterop/math.kt new file mode 100644 index 00000000000..b32103860ac --- /dev/null +++ b/backend.native/tests/jsinterop/math.kt @@ -0,0 +1,16 @@ +import kotlinx.interop.wasm.math.* +import kotlinx.wasm.jsinterop.* + +fun main(args: Array) { + + 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") +} + diff --git a/build.gradle b/build.gradle index 4b3b5c6f4c3..a798a900435 100644 --- a/build.gradle +++ b/build.gradle @@ -311,6 +311,9 @@ targetList.each { target -> task("${target}PlatformLibs") { dependsOn ":platformLibs:${target}Install" + if (target == 'wasm32') { + dependsOn 'Interop:JsRuntime:installJsRuntime' + } } task("${target}CrossDist") { diff --git a/cmd/jsinterop b/cmd/jsinterop new file mode 100755 index 00000000000..f550237d48a --- /dev/null +++ b/cmd/jsinterop @@ -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 "$@" diff --git a/cmd/jsinterop.bat b/cmd/jsinterop.bat new file mode 100644 index 00000000000..a83bc2792e6 --- /dev/null +++ b/cmd/jsinterop.bat @@ -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 %* diff --git a/platformLibs/build.gradle b/platformLibs/build.gradle index b4a04727992..e4a212363ab 100644 --- a/platformLibs/build.gradle +++ b/platformLibs/build.gradle @@ -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' diff --git a/runtime/src/launcher/js/launcher.js b/runtime/src/launcher/js/launcher.js index e10f727c03c..de5eb5fc322 100644 --- a/runtime/src/launcher/js/launcher.js +++ b/runtime/src/launcher/js/launcher.js @@ -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); + } } diff --git a/runtime/src/main/cpp/ReturnSlot.cpp b/runtime/src/main/cpp/ReturnSlot.cpp new file mode 100644 index 00000000000..497a7961fb6 --- /dev/null +++ b/runtime/src/main/cpp/ReturnSlot.cpp @@ -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(&::storage); + } + + void ReturnSlot_setDouble(KInt upper, KInt lower) { + reinterpret_cast(&::storage)[0] = lower; + reinterpret_cast(&::storage)[1] = upper; + } +} +#endif diff --git a/runtime/src/main/cpp/ReturnSlot.h b/runtime/src/main/cpp/ReturnSlot.h new file mode 100644 index 00000000000..8a4a1da0631 --- /dev/null +++ b/runtime/src/main/cpp/ReturnSlot.h @@ -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 diff --git a/samples/html5Canvas/build.bat b/samples/html5Canvas/build.bat index 27018e41a7a..056ade9b40a 100644 --- a/samples/html5Canvas/build.bat +++ b/samples/html5Canvas/build.bat @@ -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" diff --git a/samples/html5Canvas/build.sh b/samples/html5Canvas/build.sh index 6a761458639..2145aed3077 100755 --- a/samples/html5Canvas/build.sh +++ b/samples/html5Canvas/build.sh @@ -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" diff --git a/samples/html5Canvas/src/main/kotlin/main.kt b/samples/html5Canvas/src/main/kotlin/main.kt index a0f0acb8770..a4ee388f826 100644 --- a/samples/html5Canvas/src/main/kotlin/main.kt +++ b/samples/html5Canvas/src/main/kotlin/main.kt @@ -1,6 +1,5 @@ -import html5.minimal.* +import kotlinx.interop.wasm.dom.* import kotlinx.wasm.jsinterop.* -import konan.internal.ExportForCppRuntime fun main(args: Array) { diff --git a/settings.gradle b/settings.gradle index 6512e5a585c..522136dfd54 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,6 +16,7 @@ include ':dependencies' include ':Interop:Indexer' +include ':Interop:JsRuntime' include ':Interop:StubGenerator' include ':Interop:Runtime' include ':llvmDebugInfoC' diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/file/File.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/file/File.kt index e2aa8ef40ce..89ca604d8c8 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/file/File.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/file/File.kt @@ -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) } diff --git a/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/InteropCompiler.kt b/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/InteropCompiler.kt new file mode 100644 index 00000000000..6235c25d1d2 --- /dev/null +++ b/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/InteropCompiler.kt @@ -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): Array { + val cinteropArgFilter = listOf(NODEFAULTLIBS, PURGE_USER_LIBS) + + var outputFileName = "nativelib" + var target = "host" + val libraries = mutableListOf() + val repos = mutableListOf() + 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( + "-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() + + 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 +} + + diff --git a/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/main.kt b/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/main.kt index e5eb8a07f55..1c7c8cf34b3 100644 --- a/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/main.kt +++ b/utilities/src/main/kotlin/org/jetbrains/kotlin/cli/utilities/main.kt @@ -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) { - val cinteropArgFilter = listOf(NODEFAULTLIBS, PURGE_USER_LIBS) - - var outputFileName = "nativelib" - var target = "host" - val libraries = mutableListOf() - val repos = mutableListOf() - 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( - "-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() - 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) { 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 ->