From b4660d03a1cd23d4c6d777aa25687c6719f70971 Mon Sep 17 00:00:00 2001 From: Alexander Gorshenev Date: Fri, 14 Jul 2017 17:08:55 +0300 Subject: [PATCH] Initial .wasm file generation for the wasm32 target. Some basic launcher.js and runtime. --- .../kotlin/backend/konan/LinkStage.kt | 72 +++++-- backend.native/konan.properties | 10 +- backend.native/tests/build.gradle | 74 ++++++- build.gradle | 5 + .../org/jetbrains/kotlin/KonanTest.groovy | 65 +++++-- runtime/build.gradle | 2 - runtime/src/launcher/cpp/launcher.cpp | 16 ++ runtime/src/launcher/js/launcher.js | 137 +++++++++++++ runtime/src/main/cpp/Exceptions.cpp | 5 +- runtime/src/main/cpp/Exceptions.h | 2 + runtime/src/main/cpp/Natives.cpp | 4 +- runtime/src/main/cpp/Porting.cpp | 183 +++++++++++++++++- runtime/src/main/cpp/ToString.cpp | 8 +- runtime/src/main/cpp/dlmalloc/malloc.cpp | 10 +- runtime/src/main/cpp/dtoa/dblparse.cpp | 4 +- runtime/src/main/cpp/dtoa/fltparse.cpp | 4 +- .../cpp/snprintf/{snprintf.c => snprintf.cpp} | 29 ++- .../kotlin/konan/internal/RuntimeUtils.kt | 7 +- .../kotlin/konan/target/ClangArgs.kt | 4 +- .../kotlin/konan/target/KonanProperties.kt | 1 + .../kotlin/konan/target/KonanTarget.kt | 2 +- 21 files changed, 576 insertions(+), 68 deletions(-) create mode 100644 runtime/src/launcher/js/launcher.js rename runtime/src/main/cpp/snprintf/{snprintf.c => snprintf.cpp} (98%) 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 e9fed37a151..7520a94df26 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 @@ -37,17 +37,17 @@ internal abstract class PlatformFlags(val properties: KonanProperties) { val linkerKonanFlags = properties.linkerKonanFlags val linkerDebugFlags = properties.linkerDebugFlags val llvmDebugOptFlags = properties.llvmDebugOptFlags + val s2wasmFlags = properties.s2wasmFlags + val targetToolchain = properties.absoluteTargetToolchain + val targetSysRoot = properties.absoluteTargetSysRoot + + val targetLibffi = properties.libffiDir ?.let { listOf("${properties.absoluteLibffiDir}/lib/libffi.a") } ?: emptyList() open val useCompilerDriverAsLinker: Boolean get() = false // TODO: refactor. - abstract fun linkCommand(objectFiles: List, + abstract fun linkCommand(objectFiles: List, executable: ExecutableFile, optimize: Boolean, debug: Boolean): List - val targetLibffiDir = properties.absoluteLibffiDir - val targetLibffi = "$targetLibffiDir/lib/libffi.a" - val targetToolchain = properties.absoluteTargetToolchain - val targetSysRoot = properties.absoluteTargetSysRoot - open fun linkCommandSuffix(): List = emptyList() protected fun propertyTargetString(name: String) @@ -178,19 +178,19 @@ internal open class WasmPlatform(distribution: Distribution) override fun linkCommand(objectFiles: List, executable: ExecutableFile, optimize: Boolean, debug: Boolean): List { - return mutableListOf(clang).apply{ - throw Error("Implement me!") - } + //No link stage for WASM yet, just give '.wasm' as output. + return mutableListOf("/bin/cp", objectFiles.single(), executable) } } internal class LinkStage(val context: Context) { val config = context.config.configuration + val target = context.config.targetManager.target private val distribution = context.config.distribution - private val platform = when (context.config.targetManager.target) { + private val platform = when (target) { KonanTarget.LINUX, KonanTarget.RASPBERRYPI -> LinuxBasedPlatform(distribution) KonanTarget.MACBOOK, KonanTarget.IPHONE, KonanTarget.IPHONE_SIM -> @@ -202,7 +202,7 @@ internal class LinkStage(val context: Context) { KonanTarget.WASM32 -> WasmPlatform(distribution) else -> - error("Unexpected target platform: ${context.config.targetManager.target}") + error("Unexpected target platform: ${target}") } private val optimize = config.get(KonanConfigKeys.OPTIMIZATION) ?: false @@ -216,9 +216,7 @@ internal class LinkStage(val context: Context) { } private fun llvmLto(files: List): ObjectFile { - val tmpCombined = createTempFile("combined", ".o") - tmpCombined.deleteOnExit() - val combined = tmpCombined.absolutePath + val combined = temporary("combined", ".o") val tool = distribution.llvmLto val command = mutableListOf(tool, "-o", combined) @@ -234,6 +232,41 @@ internal class LinkStage(val context: Context) { return combined } + private fun temporary(name: String, suffix: String): String { + val temporaryFile = createTempFile(name, suffix) + temporaryFile.deleteOnExit() + return temporaryFile.absolutePath + } + + private fun targetTool(tool: String, vararg arg: String) { + val absoluteToolName = "${platform.targetToolchain}/bin/$tool" + runTool(absoluteToolName, *arg) + } + + private fun hostLlvmTool(tool: String, args: List) { + val absoluteToolName = "${distribution.llvmBin}/$tool" + val command = listOf(absoluteToolName) + args + runTool(*command.toTypedArray()) + } + + private fun bitcodeToWasm(bitcodeFiles: List): String { + val combinedBc = temporary("combined", ".bc") + hostLlvmTool("llvm-link", bitcodeFiles + listOf("-o", combinedBc)) + + val combinedS = temporary("combined", ".s") + targetTool("llc", combinedBc, "-o", combinedS) + + val s2wasmFlags = platform.s2wasmFlags.toTypedArray() + val combinedWast = temporary( "combined", ".wast") + targetTool("s2wasm", combinedS, "-o", combinedWast, *s2wasmFlags) + + val combinedWasm = temporary( "combined", ".wasm") + val combinedSmap = temporary( "combined", ".smap") + targetTool("wasm-as", combinedWast, "-o", combinedWasm, "-g", "-s", combinedSmap) + + return combinedWasm + } + private fun asLinkerArgs(args: List): List { if (platform.useCompilerDriverAsLinker) { return args @@ -261,8 +294,12 @@ internal class LinkStage(val context: Context) { private val entryPointSelector: List get() = if (nomain) emptyList() else platform.entrySelector + private val libffi + get() = platform.targetLibffi ?.let { listOf(it) } ?: emptyList() + private fun link(objectFiles: List, libraryProvidedLinkerFlags: List): ExecutableFile { val executable = context.config.outputFile + val linkCommand = platform.linkCommand(objectFiles, executable, optimize, debug) + platform.targetLibffi + asLinkerArgs(config.getNotNull(KonanConfigKeys.LINKER_ARGS)) + @@ -333,7 +370,12 @@ internal class LinkStage(val context: Context) { val phaser = PhaseManager(context) phaser.phase(KonanPhase.OBJECT_FILES) { - objectFiles = listOf( llvmLto(bitcodeFiles ) ) + objectFiles = listOf( + if (target == KonanTarget.WASM32) + bitcodeToWasm(bitcodeFiles) + else + llvmLto(bitcodeFiles) + ) } phaser.phase(KonanPhase.LINKER) { link(objectFiles, libraryProvidedLinkerFlags) diff --git a/backend.native/konan.properties b/backend.native/konan.properties index 1e782c6f875..f85ad5fcaa2 100644 --- a/backend.native/konan.properties +++ b/backend.native/konan.properties @@ -218,14 +218,6 @@ dependencies.osx-wasm32 = \ target-toolchain-1-osx-wasm quadruple.wasm32 = wasm32 -entrySelector.wasm32 = -Wl,--defsym -Wl,main=Konan_main llvmLtoFlags.wasm32 = targetSysRoot.wasm32 = target-sysroot-1-wasm -clangFlags.wasm32 = -O1 -fno-rtti -fno-exceptions \ - -D_LIBCPP_ABI_VERSION=2 -DKONAN_NO_FFI=1 -DKONAN_NO_THREADS=1 -DKONAN_NO_EXCEPTIONS=1 \ - -DKONAN_NO_DEBUG_API=1 \ - -nostdinc -Xclang -nobuiltininc -Xclang -nostdsysteminc \ - -Xclang -isystem/include/libcxx -Xclang -isystem/lib/libcxxabi/include \ - -Xclang -isystem/include/compat -Xclang -isystem/include/libc -linkerKonanFlags.wasm32 = -lc -linkerDebugFlags.wasm32 = -Wl,-S \ No newline at end of file +s2wasmFlags.wasm32 = --allocate-stack 1024 --import-memory diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index c034d2b15a6..1fa0b6206e9 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -328,6 +328,7 @@ task codegen_controlflow_for_loops_overflow(type: RunKonanTest) { } task codegen_controlflow_for_loops_errors(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // Uses exceptions. source = "codegen/controlflow/for_loops_errors.kt" goldValue = "OK\n" } @@ -349,6 +350,7 @@ task codegen_controlflow_for_loops_nested(type: RunKonanTest) { } task codegen_controlflow_for_loops_coroutines(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' source = "codegen/controlflow/for_loops_coroutines.kt" goldValue = "before: 0 after: 0\n" + "before: 2 after: 2\n" + @@ -376,6 +378,7 @@ task cast_simple(type: RunKonanTest) { } task cast_null(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. goldValue = "Ok\n" source = "codegen/basics/cast_null.kt" } @@ -392,6 +395,7 @@ task unchecked_cast2(type: RunKonanTest) { } task unchecked_cast3(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "Ok\n" source = "codegen/basics/unchecked_cast3.kt" } @@ -411,13 +415,13 @@ task hello0(type: RunKonanTest) { task hello1(type: RunKonanTest) { goldValue = "Hello World" - testData = "Hello World" + testData = "Hello World\n" source = "runtime/basic/hello1.kt" } task hello2(type: RunKonanTest) { goldValue = "you entered 'Hello World'" - testData = "Hello World" + testData = "Hello World\n" source = "runtime/basic/hello2.kt" } @@ -481,16 +485,19 @@ task empty_substring(type: RunKonanTest) { } task worker0(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "Got Input processed\nOK\n" source = "runtime/workers/worker0.kt" } task worker1(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "OK\n" source = "runtime/workers/worker1.kt" } task worker2(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "OK\n" source = "runtime/workers/worker2.kt" } @@ -813,77 +820,92 @@ task classDelegation_withBridge(type: RunKonanTest) { } task delegatedProperty_simpleVal(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails: not global_ctros support. goldValue = "x\n42\n" source = "codegen/delegatedProperty/simpleVal.kt" } task delegatedProperty_simpleVar(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "get x\n42\nset x\nget x\n117\n" source = "codegen/delegatedProperty/simpleVar.kt" } task delegatedProperty_packageLevel(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "x\n42\n" source = "codegen/delegatedProperty/packageLevel.kt" } task delegatedProperty_local(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "x\n42\n" source = "codegen/delegatedProperty/local.kt" } task delegatedProperty_delegatedOverride(type: LinkKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "156\nx\n117\n42\n" source = "codegen/delegatedProperty/delegatedOverride_main.kt" lib = "codegen/delegatedProperty/delegatedOverride_lib.kt" } task delegatedProperty_lazy(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "computed!\nHello\nHello\n" source = "codegen/delegatedProperty/lazy.kt" } task delegatedProperty_observable(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = " -> first\nfirst -> second\n" source = "codegen/delegatedProperty/observable.kt" } task delegatedProperty_map(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "John Doe\n25\n" source = "codegen/delegatedProperty/map.kt" } task propertyCallableReference_valClass(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "42\n117\n" source = "codegen/propertyCallableReference/valClass.kt" } task propertyCallableReference_valModule(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "42\n" source = "codegen/propertyCallableReference/valModule.kt" } task propertyCallableReference_varClass(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // RuntimeError: memory access out of bounds goldValue = "117\n117\n42\n42\n" source = "codegen/propertyCallableReference/varClass.kt" } task propertyCallableReference_varModule(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "117\n117\n" source = "codegen/propertyCallableReference/varModule.kt" } task propertyCallableReference_valExtension(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "42\n117\n" source = "codegen/propertyCallableReference/valExtension.kt" } task propertyCallableReference_varExtension(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "117\n117\n42\n42\n" source = "codegen/propertyCallableReference/varExtension.kt" } task propertyCallableReference_linkTest(type: LinkKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "42\n117\n" source = "codegen/propertyCallableReference/linkTest_main.kt" lib = "codegen/propertyCallableReference/linkTest_lib.kt" @@ -900,6 +922,7 @@ task lateinit_initialized(type: RunKonanTest) { } task lateinit_notInitialized(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails. goldValue = "OK\n" source = "codegen/lateinit/notInitialized.kt" } @@ -910,116 +933,139 @@ task lateinit_inBaseClass(type: RunKonanTest) { } task coroutines_simple(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "42\n" source = "codegen/coroutines/simple.kt" } task coroutines_withReceiver(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "42\n" source = "codegen/coroutines/withReceiver.kt" } task coroutines_correctOrder1(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "f1\ns1\nf2\n160\n" source = "codegen/coroutines/correctOrder1.kt" } task coroutines_controlFlow_if1(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "f1\ns1\nf3\n84\n" source = "codegen/coroutines/controlFlow_if1.kt" } task coroutines_controlFlow_if2(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "f1\ns1\nf2\n43\n" source = "codegen/coroutines/controlFlow_if2.kt" } task coroutines_controlFlow_finally1(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "f1\ns1\n117\n" source = "codegen/coroutines/controlFlow_finally1.kt" } task coroutines_controlFlow_finally2(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "f1\ns1\n117\n" source = "codegen/coroutines/controlFlow_finally2.kt" } task coroutines_controlFlow_finally3(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "f1\ns1\n117\n" source = "codegen/coroutines/controlFlow_finally3.kt" } task coroutines_controlFlow_finally4(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "s1\nfinally\n42\n" source = "codegen/coroutines/controlFlow_finally4.kt" } task coroutines_controlFlow_finally5(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "s1\nf2\nfinally\n1\n" source = "codegen/coroutines/controlFlow_finally5.kt" } task coroutines_controlFlow_finally6(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "s1\nfinally\nerror\n0\n" source = "codegen/coroutines/controlFlow_finally6.kt" } task coroutines_controlFlow_finally7(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "s1\nf2\nfinally1\ns2\nfinally2\n42\n" source = "codegen/coroutines/controlFlow_finally7.kt" } task coroutines_controlFlow_inline1(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "42\n" source = "codegen/coroutines/controlFlow_inline1.kt" } task coroutines_controlFlow_inline2(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "s1\n42\n" source = "codegen/coroutines/controlFlow_inline2.kt" } task coroutines_controlFlow_inline3(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "f1\ns1\n42\n" source = "codegen/coroutines/controlFlow_inline3.kt" } task coroutines_controlFlow_tryCatch1(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "s1\n42\n" source = "codegen/coroutines/controlFlow_tryCatch1.kt" } task coroutines_controlFlow_tryCatch2(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "s1\n42\n" source = "codegen/coroutines/controlFlow_tryCatch2.kt" } task coroutines_controlFlow_tryCatch3(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "s2\nf2\n1\n" source = "codegen/coroutines/controlFlow_tryCatch3.kt" } task coroutines_controlFlow_tryCatch4(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "s2\nf2\n1\n" source = "codegen/coroutines/controlFlow_tryCatch4.kt" } task coroutines_controlFlow_tryCatch5(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "s2\ns1\nError\n42\n" source = "codegen/coroutines/controlFlow_tryCatch5.kt" } task coroutines_controlFlow_while1(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "s3\ns3\ns3\ns3\n3\n" source = "codegen/coroutines/controlFlow_while1.kt" } task coroutines_controlFlow_while2(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "s3\ns3\ns3\n3\n" source = "codegen/coroutines/controlFlow_while2.kt" } task coroutines_returnsUnit1(type: RunKonanTest) { + disabled = (project.testTarget == 'wasm32') // llvm: 'WebAssembly hasn't implemented computed gotos' goldValue = "117\ns1\n0\n" source = "codegen/coroutines/returnsUnit1.kt" } @@ -1030,6 +1076,7 @@ task AbstractMutableCollection(type: RunKonanTest) { } task BitSet(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. expectedExitStatus = 0 source = "runtime/collections/BitSet.kt" } @@ -1223,6 +1270,7 @@ task boxing6(type: RunKonanTest) { } task boxing7(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. goldValue = "1\n0\n" source = "codegen/boxing/boxing7.kt" } @@ -1283,6 +1331,7 @@ task array_list1(type: RunKonanTest) { } task hash_map0(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails on wasm32 (remainder by zero) goldValue = "OK\n" source = "runtime/collections/hash_map0.kt" } @@ -1315,6 +1364,7 @@ task moderately_large_array1(type: RunKonanTest) { } task string_builder0(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails on wasm32. goldValue = "OK\n" source = "runtime/text/string_builder0.kt" } @@ -1344,21 +1394,25 @@ task to_string0(type: RunKonanTest) { } task chars0(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. source = "runtime/text/chars0.kt" expectedExitStatus = 0 } task catch1(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. goldValue = "Before\nCaught Throwable\nDone\n" source = "runtime/exceptions/catch1.kt" } task catch2(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. goldValue = "Before\nCaught Error\nDone\n" source = "runtime/exceptions/catch2.kt" } task catch7(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. goldValue = "Error happens\n" source = "runtime/exceptions/catch7.kt" } @@ -1379,11 +1433,13 @@ task catch4(type: RunKonanTest) { } task catch5(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. goldValue = "Before\nCaught Error\nDone\n" source = "codegen/try/catch5.kt" } task catch6(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. goldValue = "Before\nCaught Error\nDone\n" source = "codegen/try/catch6.kt" } @@ -1646,6 +1702,7 @@ task objectExpression3(type: RunKonanTest) { } task initializers2(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails on wasm32. goldValue = "init globalValue2\n" + "init globalValue3\n" + "1\n" + @@ -1656,11 +1713,13 @@ task initializers2(type: RunKonanTest) { } task initializers3(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails on wasm32. goldValue = "42\n" source = "runtime/basic/initializers3.kt" } task initializers4(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails on wasm32. goldValue = "1073741824\ntrue\n" source = "runtime/basic/initializers4.kt" } @@ -1671,6 +1730,7 @@ task initializers5(type: RunKonanTest) { } task expression_as_statement(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // The test fails on wasm32. goldValue = "Ok\n" source = "codegen/basics/expression_as_statement.kt" } @@ -1692,6 +1752,7 @@ task memory_var4(type: RunKonanTest) { } task memory_throw_cleanup(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. goldValue = "Ok\n" source = "runtime/memory/throw_cleanup.kt" } @@ -1947,18 +2008,21 @@ kotlinNativeInterop { } task interop0(type: RunInteropKonanTest) { + disabled = (project.testTarget == 'wasm32') // No interop for wasm yet. goldValue = "0\n0\n" source = "interop/basics/0.kt" interop = 'sysstat' } task interop1(type: RunInteropKonanTest) { + disabled = (project.testTarget == 'wasm32') // No interop for wasm yet. goldValue = "257\n-1\n-2\n" source = "interop/basics/1.kt" interop = 'cstdlib' } task interop2(type: RunInteropKonanTest) { + disabled = (project.testTarget == 'wasm32') // No interop for wasm yet. goldValue = "Hello\n" source = "interop/basics/2.kt" interop = 'cstdio' @@ -1970,19 +2034,22 @@ task interop3(type: RunInteropKonanTest) { // should be different depending on the bitness of the target. // There are plans to provide a solution and turn this // test back on. - disabled = (project.testTarget == 'raspberrypi') + disabled = (project.testTarget == 'raspberrypi') || + (project.testTarget == 'wasm32') // No interop for wasm yet. goldValue = "8 9 12 13 14 \n" source = "interop/basics/3.kt" interop = 'cstdlib' } task interop4(type: RunInteropKonanTest) { + disabled = (project.testTarget == 'wasm32') // No interop for wasm yet. goldValue = "a b -1 2 3 9223372036854775807 0.1 0.2\n1 42\n" source = "interop/basics/4.kt" interop = 'cstdio' } task interop_echo_server(type: RunInteropKonanTest) { + disabled = (project.testTarget == 'wasm32') // No interop for wasm yet. if (!isMac()) { // Not supported or not tested. disabled = true @@ -1994,6 +2061,7 @@ task interop_echo_server(type: RunInteropKonanTest) { if (isMac()) { task interop_opengl_teapot(type: RunInteropKonanTest) { + disabled = (project.testTarget == 'wasm32') // No interop for wasm yet. run = false source = "interop/basics/opengl_teapot.kt" interop = 'opengl' diff --git a/build.gradle b/build.gradle index e3d6768496a..ec7bdaf805c 100644 --- a/build.gradle +++ b/build.gradle @@ -254,6 +254,11 @@ targetList.each { target -> rename("${target}Start.bc", 'start.bc') into("klib/stdlib/targets/$target/native") } + if (target == 'wasm32') { + from(project(':runtime').file('src/launcher/js')) { + into('konan/nativelib') + } + } } } diff --git a/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy b/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy index 890f4887b16..c0a44db40e6 100644 --- a/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy +++ b/buildSrc/plugins/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy @@ -23,17 +23,16 @@ import org.gradle.api.tasks.ParallelizableTask import org.gradle.api.tasks.TaskAction import org.gradle.process.ExecResult import org.jetbrains.kotlin.konan.target.* +import org.jetbrains.kotlin.konan.properties.* abstract class KonanTest extends JavaExec { protected String source + def targetManager = new TargetManager(project.testTarget) + def target = targetManager.target def backendNative = project.project(":backend.native") def runtimeProject = project.project(":runtime") def dist = project.rootProject.file("dist") - def dependenciesDir = project.findProject(":dependencies").file("all") - def runtimeBc = new File("${dist.canonicalPath}/lib/runtime.bc").absolutePath - def launcherBc = new File("${dist.canonicalPath}/lib/launcher.bc").absolutePath - def startKtBc = new File("${dist.canonicalPath}/lib/start.kt.bc").absolutePath - def stdlibKtBc = new File("${dist.canonicalPath}/lib/stdlib.kt.bc").absolutePath + def dependenciesDir = project.rootProject.dependenciesDir def konancDriver = project.isWindows() ? "konanc.bat" : "konanc" def konanc = new File("${dist.canonicalPath}/bin/$konancDriver").absolutePath def mainC = 'main.c' @@ -47,12 +46,17 @@ abstract class KonanTest extends JavaExec { List flags = null boolean enabled = true + boolean expectedFail = false boolean run = true void setDisabled(boolean value) { this.enabled = !value } + void setExpectedFail(boolean value) { + this.expectedFail = value + } + // Uses directory defined in $outputSourceSetName source set. // If such source set doesn't exist, uses temporary directory. void createOutputDirectory() { @@ -102,7 +106,7 @@ abstract class KonanTest extends JavaExec { *moreArgs, *project.globalTestArgs] if (project.testTarget) { - args "-target", project.testTarget + args "-target", target.userName } if (enableKonanAssertions) { args "-ea" @@ -212,7 +216,6 @@ fun handleExceptionContinuation(x: (Throwable) -> Unit): Continuation = ob void executeTest() { createOutputDirectory() def program = buildExePath() - def targetManager = new TargetManager(project.testTarget) def suffix = targetManager.programSuffix def exe = "$program$suffix" @@ -227,7 +230,9 @@ fun handleExceptionContinuation(x: (Throwable) -> Unit): Continuation = ob def out = new ByteArrayOutputStream() //TODO Add test timeout ExecResult execResult = project.execRemote { - commandLine exe + + commandLine executionCommandLine(exe) + if (arguments != null) { args arguments } @@ -239,15 +244,43 @@ fun handleExceptionContinuation(x: (Throwable) -> Unit): Continuation = ob ignoreExitValue = true } def result = out.toString("UTF-8") + println(result) - if (execResult.exitValue != expectedExitStatus) { - throw new TestFailedException( - "Test failed. Expected exit status: $expectedExitStatus, actual: ${execResult.exitValue}") - } + + def exitCodeMismatch = execResult.exitValue != expectedExitStatus + if (exitCodeMismatch) { + def message = "Expected exit status: $expectedExitStatus, actual: ${execResult.exitValue}" + if (this.expectedFail) { + println("Expected failure. $message") + } else { + throw new TestFailedException("Test failed. $message") + } + } + + def goldValueMismatch = goldValue != null && goldValue != result.replace(System.lineSeparator(), "\n") + if (goldValueMismatch) { + def message = "Expected output: $goldValue, actual output: $result" + if (this.expectedFail) { + println("Expected failure. $message") + } else { + throw new TestFailedException("Test failed. $message") + } + } - if (goldValue != null && goldValue != result.replace(System.lineSeparator(), "\n")) { - throw new TestFailedException("Test failed. Expected output: $goldValue, actual output: $result") + if (!exitCodeMismatch && !goldValueMismatch && this.expectedFail) println("Unexpected pass") + } + + List executionCommandLine(String exe) { + if (target == KonanTarget.WASM32) { + def properties = project.rootProject.konanProperties + def targetToolchain = TargetPropertiesKt.hostTargetString(properties, "targetToolchain", target) + def absoluteTargetToolchain = "$dependenciesDir/$targetToolchain" + def d8 = "$absoluteTargetToolchain/bin/d8" + def launcherJs = "${dist.absolutePath}/konan/nativelib/launcher.js" + return [d8, '--expose-wasm', launcherJs, '--', exe] + } else { + return [exe] } } } @@ -289,7 +322,7 @@ class RunDriverKonanTest extends KonanTest { *moreArgs, *project.globalTestArgs] if (project.testTarget) { - args "-target", project.testTarget + args "-target", target.userName } if (enableKonanAssertions) { args "-ea" @@ -312,7 +345,7 @@ class RunInteropKonanTest extends KonanTest { void setInterop(String value) { this.interop = value this.interopConf = project.kotlinNativeInterop[value] - this.interopConf.target = project.testTarget + this.interopConf.target = target.userName this.dependsOn(this.interopConf.genTask) } diff --git a/runtime/build.gradle b/runtime/build.gradle index 48bb4408c95..7c913f66c25 100644 --- a/runtime/build.gradle +++ b/runtime/build.gradle @@ -25,7 +25,6 @@ targetList.each { targetName -> dependsOn ":common:${targetName}Hash" dependsOn "${targetName}Launcher" target targetName - compilerArgs '-g' compilerArgs '-I' + project.file('../common/src/hash/headers') if (rootProject.hasProperty("${targetName}LibffiDir")) compilerArgs '-I' + project.file(rootProject.ext.get("${targetName}LibffiDir") + "/include") @@ -38,7 +37,6 @@ targetList.each { targetName -> name "launcher" srcRoot file('src/launcher') target targetName - compilerArgs '-g' compilerArgs '-I' + project.file('../common/src/hash/headers') compilerArgs '-I' + project.file('src/main/cpp') } diff --git a/runtime/src/launcher/cpp/launcher.cpp b/runtime/src/launcher/cpp/launcher.cpp index cfb630e7ade..929a3557bc9 100644 --- a/runtime/src/launcher/cpp/launcher.cpp +++ b/runtime/src/launcher/cpp/launcher.cpp @@ -57,4 +57,20 @@ extern "C" RUNTIME_USED int Konan_main(int argc, const char** argv) { return exitStatus; } +#ifdef KONAN_WASM +// Before we pass control to Konan_main, we need to obtain argv elements +// from the javascript world. +extern "C" int Konan_js_arg_size(int index); +extern "C" int Konan_js_fetch_arg(int index, char* ptr); + +extern "C" int Konan_js_main(int argc) { + char** argv = (char**)konan::calloc(1, argc); + for (int i = 0; i< argc; ++i) { + argv[i] = (char*)konan::calloc(1, Konan_js_arg_size(i)); + Konan_js_fetch_arg(i, argv[i]); + } + return Konan_main(argc, (const char**)argv); +} +#endif + #endif diff --git a/runtime/src/launcher/js/launcher.js b/runtime/src/launcher/js/launcher.js new file mode 100644 index 00000000000..20c4a61c005 --- /dev/null +++ b/runtime/src/launcher/js/launcher.js @@ -0,0 +1,137 @@ +/* + * 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. + */ + +var module; +var instance; +var heap; +var memory; +var global_arguments = arguments; +var exit_status = 0; +var globalBase = 0; // Is there any way to obtain global_base from JavaScript? + +function print_usage() { + // TODO: any reliable way to obtain the current script name? + print('Usage: d8 --expose-wasm launcher.js -- ...') + quit(1); // TODO: this is d8 specific +} + +function utf8encode(s) { + return unescape(encodeURIComponent(s)); +} + +function utf8decode(s) { + return decodeURIComponent(escape(s)); +} + +function fromString(string, pointer) { + for (i = 0; i < string.length; i++) { + heap[pointer + i] = string.charCodeAt(i); + } + heap[pointer + string.length] = 0; +} + +function toString(pointer) { + var string = ''; + for (var i = pointer; heap[i] != 0; i++) { + string += String.fromCharCode(heap[i]); + } + return string; +} + +function int32ToHeap(value, pointer) { + heap[pointer] = value & 0xff; + heap[pointer+1] = (value & 0xff00) >>> 8; + heap[pointer+2] = (value & 0xff0000) >>> 16; + heap[pointer+3] = (value & 0xff000000) >>> 24; +} + +function stackTop() { + // Read the value module's `__stack_pointer` is initialized with. + // It is the very first static in .data section. + var addr = (globalBase == 0 ? 4 : globalBase); + var fourBytes = heap.buffer.slice(addr, addr+4); + return new Uint32Array(fourBytes)[0]; +} + +var konan_dependencies = { + env: { + abort: function() { + throw new Error("abort()"); + }, + // TODO: Account for file and size. + fgets: function(str, size, file) { + fromString(utf8encode(readline()), str); + return str; + }, + Konan_heap_upper: function() { + return memory.buffer.byteLength; + }, + Konan_heap_lower: function() { + return konanStackTop; + }, + Konan_abort: function(pointer) { + throw new Error("Konan_abort(" + utf8decode(toString(pointer)) + ")"); + }, + Konan_js_arg_size: function(index) { + if (index >= global_arguments.length) return -1; + return global_arguments[index].length + 1; // + 1 for trailing zero. + }, + Konan_js_fetch_arg: function(index, ptr) { + var arg = utf8encode(global_arguments[index]); + fromString(arg, ptr); + }, + Konan_date_now: function(pointer) { + var now = Date.now(); + var high = Math.floor(now / 0xffffffff); + var low = Math.floor(now % 0xffffffff); + int32ToHeap(low, pointer); + int32ToHeap(high, pointer+4); + }, + stdin: 0, // This is for fgets(,,stdin) to resolve. It is ignored. + // TODO: Account for fd and size. + write: function(fd, str, size) { + if (fd != 1 && fd != 2) throw ("write(" + fd + ", ...)"); + // TODO: There is no writeErr() in d8. + // Approximate it with write() to stdout for now. + write(utf8decode(toString(str))); // TODO: write() d8 specific. + }, + memory: new WebAssembly.Memory({ initial: 256 }) + } +}; + +if (arguments.length < 1) print_usage(); + +module = new WebAssembly.Module(new Uint8Array(readbuffer(arguments[0]))); +module.env = {}; +module.env.memoryBase = 0; +module.env.tablebase = 0; + +instance = new WebAssembly.Instance(module, konan_dependencies); +memory = konan_dependencies.env.memory +heap = new Uint8Array(konan_dependencies.env.memory.buffer); +heap32 = new Uint32Array(konan_dependencies.env.memory.buffer); +konanStackTop = stackTop(); + +try { + exit_status = instance.exports.Konan_js_main(arguments.length); +} catch (e) { + print("Exception executing Konan_js_main: " + e); + print(e.stack); + exit_status = 1; +} + +quit(exit_status); // TODO: d8 specific. + diff --git a/runtime/src/main/cpp/Exceptions.cpp b/runtime/src/main/cpp/Exceptions.cpp index 20878f84164..6603bf3ba06 100644 --- a/runtime/src/main/cpp/Exceptions.cpp +++ b/runtime/src/main/cpp/Exceptions.cpp @@ -50,7 +50,7 @@ class AutoFree { AutoFree(void* mem): mem_(mem) {} ~AutoFree() { - free(mem_); + konan::free(mem_); } }; @@ -105,7 +105,7 @@ _Unwind_Reason_Code unwindCallback( } char line[512]; - snprintf(line, sizeof(line) - 1, "%s (%p)", + konan::snprintf(line, sizeof(line) - 1, "%s (%p)", symbol, (void*)(intptr_t)address); backtrace->setNextElement(line); return _URC_NO_REASON; @@ -162,6 +162,7 @@ void ThrowException(KRef exception) { RuntimeAssert(exception != nullptr && IsInstance(exception, theThrowableTypeInfo), "Throwing something non-throwable"); #if KONAN_NO_EXCEPTIONS + PrintThrowable(exception); RuntimeAssert(false, "Exceptions unsupported"); #else #if (__MINGW32__ || __MINGW64__) diff --git a/runtime/src/main/cpp/Exceptions.h b/runtime/src/main/cpp/Exceptions.h index effb8bf9018..3b38f130c1e 100644 --- a/runtime/src/main/cpp/Exceptions.h +++ b/runtime/src/main/cpp/Exceptions.h @@ -44,6 +44,8 @@ void ThrowArithmeticException(); void ThrowNumberFormatException(); // Throws out of memory error. void ThrowOutOfMemoryError(); +// Prints out mesage of Throwable. +void PrintThrowable(KRef); #ifdef __cplusplus } // extern "C" diff --git a/runtime/src/main/cpp/Natives.cpp b/runtime/src/main/cpp/Natives.cpp index f9ffea8f0a8..2615abd770d 100644 --- a/runtime/src/main/cpp/Natives.cpp +++ b/runtime/src/main/cpp/Natives.cpp @@ -53,7 +53,7 @@ void* Kotlin_interop_malloc(KLong size, KInt align) { return nullptr; } - void* result = malloc(size); + void* result = konan::calloc(1, size); if ((reinterpret_cast(result) & (align - 1)) != 0) { // Unaligned! RuntimeAssert(false, "unsupported alignment"); @@ -63,7 +63,7 @@ void* Kotlin_interop_malloc(KLong size, KInt align) { } void Kotlin_interop_free(void* ptr) { - free(ptr); + konan::free(ptr); } } // extern "C" diff --git a/runtime/src/main/cpp/Porting.cpp b/runtime/src/main/cpp/Porting.cpp index 57def457693..5e97be1df06 100644 --- a/runtime/src/main/cpp/Porting.cpp +++ b/runtime/src/main/cpp/Porting.cpp @@ -76,7 +76,7 @@ void abort() { // memcpy/memmove are not here intentionally, as frequently implemented/optimized // by C compiler. void* memmem(const void *big, size_t bigLen, const void *little, size_t littleLen) { -#if KONAN_WINDOWS +#if KONAN_WINDOWS || KONAN_WASM for (size_t i = 0; i + littleLen <= bigLen; ++i) { void* pos = ((char*)big) + i; if (::memcmp(little, pos, littleLen) == 0) return pos; @@ -88,10 +88,19 @@ void* memmem(const void *big, size_t bigLen, const void *little, size_t littleLe } +// The sprintf family. +#if KONAN_INTERNAL_SNPRINTF +extern "C" int rpl_vsnprintf(char *, size_t, const char *, va_list); +#endif + int snprintf(char* buffer, size_t size, const char* format, ...) { va_list args; va_start(args, format); +#if KONAN_INTERNAL_SNPRINTF + int rv = rpl_vsnprintf(buffer, size, format, args); +#else int rv = ::vsnprintf(buffer, size, format, args); +#endif va_end(args); return rv; } @@ -122,6 +131,22 @@ void free(void* pointer) { #endif } +#if KONAN_INTERNAL_NOW + +extern "C" void Konan_date_now(uint64_t*); + +uint64_t getTimeMillis() { + uint64_t now; + Konan_date_now(&now); + return now; +} +uint64_t getTimeMicros() { + return getTimeMillis() * 1000ULL; +} +uint64_t getTimeNanos() { + return getTimeMillis() * 1000000ULL; +} +#else // Time operations. using namespace std::chrono; @@ -136,12 +161,166 @@ uint64_t getTimeNanos() { uint64_t getTimeMicros() { return duration_cast(high_resolution_clock::now().time_since_epoch()).count(); } +#endif #if KONAN_INTERNAL_DLMALLOC // This function is being called when memory allocator needs more RAM. + +#ifdef KONAN_WASM + +// This one is an interface to query module.env.memory.buffer.byteLength +extern "C" unsigned long Konan_heap_upper(); +extern "C" unsigned long Konan_heap_lower(); + +#define MFAIL ((void*) ~(size_t)0) +#define WASM_PAGESIZE 65536U +#define WASM_PAGEMASK ((WASM_PAGESIZE-(size_t)1)) +#define PAGE_ALIGN(value) ((value + WASM_PAGEMASK) & ~(WASM_PAGEMASK)) + void* moreCore(int size) { - return sbrk(size); + static int initialized = 0; + static void* sbrk_top = MFAIL; + static void* upperHeapLimit = MFAIL; + + if (!initialized) { + sbrk_top = (void*)PAGE_ALIGN(Konan_heap_lower()); + upperHeapLimit = (void*)Konan_heap_upper(); + initialized = 1; + } + + if (size == 0) { + return sbrk_top; + } else if (size < 0) { + return MFAIL; + } + + size = PAGE_ALIGN(size); + + void* old_sbrk_top = sbrk_top; + sbrk_top = (char*)sbrk_top + size; + + if (((char*)sbrk_top - (char*)upperHeapLimit) >= 0) { + // TODO: Consider using grow() and .maximum Memory settings. + abort(); + } + return old_sbrk_top; } + +// dlmalloc wants to know the page size. +long getpagesize() { + return WASM_PAGESIZE; +} + +#else +void* moreCore(int size) { + return sbrk(size); +} + +long getpagesize() { + return sysconf(_SC_PAGESIZE); +} +#endif #endif } // namespace konan + +extern "C" { +#ifdef KONAN_WASM + extern void Konan_abort(const char*); + + // TODO: get rid of these. + void _ZNKSt3__220__vector_base_commonILb1EE20__throw_length_errorEv(void) { + Konan_abort("TODO: throw_length_error not implemented."); + } + void _ZNKSt3__221__basic_string_commonILb1EE20__throw_length_errorEv(void) { + Konan_abort("TODO: throw_length_error not implemented."); + } + int _ZNSt3__212__next_primeEm(unsigned long n) { + static unsigned long primes[] = { + 11UL, + 101UL, + 1009UL, + 10007UL, + 100003UL, + 1000003UL, + 10000019UL, + 100000007UL, + 1000000007UL + }; + int table_length = sizeof(primes)/sizeof(unsigned long); + + if (n > primes[table_length - 1]) konan::abort(); + + unsigned long prime = primes[0]; + for (unsigned long i=0; i< table_length; i++) { + prime = primes[i]; + if (prime >= n) break; + } + return prime; + } + void __assert_fail(const char * assertion, const char * file, unsigned int line, const char * function) { + char buf[1024]; + konan::snprintf(buf, sizeof(buf), "%s:%d in %s: runtime assert: %s\n", file, line, function, assertion); + Konan_abort(buf); + } + int* __errno_location() { + static int theErrno = 0; + return &theErrno; + } + + // Some math.h functions. + + double pow(double x, double y) { + return __builtin_pow(x, y); + } + + // Some string.h functions. + + void *memcpy(void *dst, const void *src, size_t n) { + for (long i = 0; i != n; ++i) + *((char*)dst + i) = *((char*)src + i); + return dst; + } + + void *memmove(void *dst, const void *src, size_t len) { + if (src < dst) { + for (long i = len; i != 0; --i) { + *((char*)dst + i - 1) = *((char*)src + i - 1); + } + } else { + memcpy(dst, src, len); + } + return dst; + } + + int memcmp(const void *s1, const void *s2, size_t n) { + for (long i = 0; i != n; ++i) { + if (*((char*)s1 + i) != *((char*)s2 + i)) { + return *((char*)s1 + i) - *((char*)s2 + i); + } + } + return 0; + } + + void *memset(void *b, int c, size_t len) { + for (long i = 0; i != len; ++i) { + *((char*)b + i) = c; + } + return b; + } + + size_t strlen(const char *s) { + for (long i = 0;; ++i) { + if (s[i] == 0) return i; + } + } + + size_t strnlen(const char *s, size_t maxlen) { + for (long i = 0; i<=maxlen; ++i) { + if (s[i] == 0) return i; + } + return maxlen; + } +#endif + +} diff --git a/runtime/src/main/cpp/ToString.cpp b/runtime/src/main/cpp/ToString.cpp index 063369870a0..d96cbbbe46c 100644 --- a/runtime/src/main/cpp/ToString.cpp +++ b/runtime/src/main/cpp/ToString.cpp @@ -82,7 +82,7 @@ OBJ_GETTER(Kotlin_Any_toString, KConstRef thiz) { OBJ_GETTER(Kotlin_Byte_toString, KByte value) { char cstring[8]; - snprintf(cstring, sizeof(cstring), "%d", value); + konan::snprintf(cstring, sizeof(cstring), "%d", value); RETURN_RESULT_OF(CreateStringFromCString, cstring); } @@ -95,13 +95,13 @@ OBJ_GETTER(Kotlin_Char_toString, KChar value) { OBJ_GETTER(Kotlin_Short_toString, KShort value) { char cstring[8]; - snprintf(cstring, sizeof(cstring), "%d", value); + konan::snprintf(cstring, sizeof(cstring), "%d", value); RETURN_RESULT_OF(CreateStringFromCString, cstring); } OBJ_GETTER(Kotlin_Int_toString, KInt value) { char cstring[16]; - snprintf(cstring, sizeof(cstring), "%d", value); + konan::snprintf(cstring, sizeof(cstring), "%d", value); RETURN_RESULT_OF(CreateStringFromCString, cstring); } @@ -111,7 +111,7 @@ OBJ_GETTER(Kotlin_Int_toStringRadix, KInt value, KInt radix) { OBJ_GETTER(Kotlin_Long_toString, KLong value) { char cstring[32]; - snprintf(cstring, sizeof(cstring), "%lld", static_cast(value)); + konan::snprintf(cstring, sizeof(cstring), "%lld", static_cast(value)); RETURN_RESULT_OF(CreateStringFromCString, cstring); } diff --git a/runtime/src/main/cpp/dlmalloc/malloc.cpp b/runtime/src/main/cpp/dlmalloc/malloc.cpp index 3862f73adf5..28c73c7efe3 100644 --- a/runtime/src/main/cpp/dlmalloc/malloc.cpp +++ b/runtime/src/main/cpp/dlmalloc/malloc.cpp @@ -525,14 +525,22 @@ MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP /**** Start of Konan-specific dlmalloc configuration. ****/ #define USE_DL_PREFIX 1 -#if !KONAN_WASM +#if KONAN_WASM +#define USE_LOCKS 0 +#define LACKS_TIME_H 1 +#define NO_MALLOC_STATS 1 +#define HAVE_MMAP 0 // don't try to allocate large chunks of memory using mmap(). + // It will go to malloc->calloc->sbrk->morecore chain anyways. +#else #define USE_LOCKS 1 #endif #define DLMALLOC_EXPORT extern "C" #define HAVE_MORECORE 1 #define MORECORE konan::moreCore +#define malloc_getpagesize konan::getpagesize() namespace konan { extern void* moreCore(int size); +extern long getpagesize(); } // namespace konan /**** End of Konan-specific dlmalloc configuration. ****/ diff --git a/runtime/src/main/cpp/dtoa/dblparse.cpp b/runtime/src/main/cpp/dtoa/dblparse.cpp index a5b081ad710..315ea3d513f 100644 --- a/runtime/src/main/cpp/dtoa/dblparse.cpp +++ b/runtime/src/main/cpp/dtoa/dblparse.cpp @@ -173,8 +173,8 @@ static const KDouble tens[] = { } #define ERROR_OCCURED(x) (HIGH_I32_FROM_VAR(x) < 0) -#define allocateU64(x, n) if (!((x) = (U_64*) malloc((n) * sizeof(U_64)))) goto OutOfMemory; -#define release(r) if ((r)) free((r)); +#define allocateU64(x, n) if (!((x) = (U_64*) konan::calloc(1, (n) * sizeof(U_64)))) goto OutOfMemory; +#define release(r) if ((r)) konan::free((r)); /*NB the Number converter methods are synchronized so it is possible to *have global data for use by bigIntDigitGenerator */ diff --git a/runtime/src/main/cpp/dtoa/fltparse.cpp b/runtime/src/main/cpp/dtoa/fltparse.cpp index 34f81518ee5..26eb4d52a45 100644 --- a/runtime/src/main/cpp/dtoa/fltparse.cpp +++ b/runtime/src/main/cpp/dtoa/fltparse.cpp @@ -121,8 +121,8 @@ static const U_32 tens[] = { } \ } -#define allocateU64(x, n) if (!((x) = (U_64*) malloc((n) * sizeof(U_64)))) goto OutOfMemory; -#define release(r) if ((r)) free((r)); +#define allocateU64(x, n) if (!((x) = (U_64*) konan::calloc(1, (n) * sizeof(U_64)))) goto OutOfMemory; +#define release(r) if ((r)) konan::free((r)); KFloat createFloat (const char *s, KInt e) diff --git a/runtime/src/main/cpp/snprintf/snprintf.c b/runtime/src/main/cpp/snprintf/snprintf.cpp similarity index 98% rename from runtime/src/main/cpp/snprintf/snprintf.c rename to runtime/src/main/cpp/snprintf/snprintf.cpp index a3f70319eb1..59f309d21a0 100644 --- a/runtime/src/main/cpp/snprintf/snprintf.c +++ b/runtime/src/main/cpp/snprintf/snprintf.cpp @@ -163,6 +163,26 @@ * . */ +#if KONAN_INTERNAL_SNPRINTF + +/**** Start of Konan-specific c99-snprintf configuration. ****/ + +#define HAVE_FLOAT_H 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VASPRINTF 1 +#define HAVE_ASPRINTF 1 +#define HAVE_LONG_LONG_INT 1 +#define HAVE_UNSIGNED_LONG_LONG_INT 1 +#include +#include +extern "C" int rpl_vsnprintf(char *, size_t, const char *, va_list); +#define malloc(size) konan:calloc(1, size) + +/**** End of Konan-specific c99-snprintf configuration. ****/ + #if HAVE_CONFIG_H #include #endif /* HAVE_CONFIG_H */ @@ -831,7 +851,7 @@ rpl_vsnprintf(char *str, size_t size, const char *format, va_list args) * characters, in an implementation-defined * manner." (C99: 7.19.6.1, 8) */ - if ((strvalue = va_arg(args, void *)) == NULL) + if ((strvalue = (char*)va_arg(args, void *)) == NULL) /* * We use the glibc format. BSD prints * "0x0", SysV "0". @@ -1472,8 +1492,8 @@ mypow10(int exponent) void * mymemcpy(void *dst, void *src, size_t len) { - const char *from = src; - char *to = dst; + const char *from = (char*)src; + char *to = (char*)dst; /* No need for optimization, we use this only to replace va_copy(3). */ while (len-- > 0) @@ -1492,7 +1512,7 @@ rpl_vasprintf(char **ret, const char *format, va_list ap) VA_COPY(aq, ap); len = vsnprintf(NULL, 0, format, aq); VA_END_COPY(aq); - if (len < 0 || (*ret = malloc(size = len + 1)) == NULL) + if (len < 0 || (*ret = (char*)malloc(size = len + 1)) == NULL) return -1; return vsnprintf(*ret, size, format, ap); } @@ -2092,4 +2112,5 @@ do { \ } #endif /* TEST_SNPRINTF */ +#endif // KONAN_INTERNAL_SNPRINTF /* vim: set joinspaces noexpandtab textwidth=80 cinoptions=(4,u0: */ diff --git a/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt b/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt index a59bce9a82f..de87f832398 100644 --- a/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt +++ b/runtime/src/main/kotlin/konan/internal/RuntimeUtils.kt @@ -60,6 +60,11 @@ fun ThrowUninitializedPropertyAccessException(): Nothing { throw UninitializedPropertyAccessException() } +@ExportForCppRuntime +fun PrintThrowable(throwable: Throwable) { + println(throwable) +} + @ExportForCppRuntime internal fun TheEmptyString() = "" @@ -95,4 +100,4 @@ fun getProgressionLast(start: Char, end: Char, step: Int): Char = getProgressionLast(start.toInt(), end.toInt(), step).toChar() fun getProgressionLast(start: Int, end: Int, step: Int): Int = getProgressionLastElement(start, end, step) -fun getProgressionLast(start: Long, end: Long, step: Long): Long = getProgressionLastElement(start, end, step) \ No newline at end of file +fun getProgressionLast(start: Long, end: Long, step: Long): Long = getProgressionLastElement(start, end, step) diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt index fccedab1cec..b30d18be3d8 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/ClangArgs.kt @@ -69,8 +69,8 @@ class ClangTarget(val target: KonanTarget, konanProperties: KonanProperties) { KonanTarget.WASM32 -> listOf("-target", targetArg!!, "-O1", "-fno-rtti", "-fno-exceptions", "-DKONAN_WASM=1", - "-D_LIBCPP_ABI_VERSION=2", "-DKONAN_NO_FFI=1", "-DKONAN_NO_THREADS=1", "-DKONAN_NO_EXCEPTIONS=1", - "-DKONAN_INTERNAL_DLMALLOC=1", + "-D_LIBCPP_ABI_VERSION=2", "-D_LIBCPP_NO_EXCEPTIONS=1", "-DKONAN_NO_FFI=1", "-DKONAN_NO_THREADS=1", "-DKONAN_NO_EXCEPTIONS=1", + "-DKONAN_INTERNAL_DLMALLOC=1", "-DKONAN_INTERNAL_SNPRINTF=1", "-DKONAN_INTERNAL_NOW=1", "-nostdinc", "-Xclang", "-nobuiltininc", "-Xclang", "-nostdsysteminc", "-Xclang", "-isystem$sysRoot/include/libcxx", "-Xclang", "-isystem$sysRoot/lib/libcxxabi/include", "-Xclang", "-isystem$sysRoot/include/compat", "-Xclang", "-isystem$sysRoot/include/libc") diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanProperties.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanProperties.kt index d8bd17baedb..a7e9d21f9b4 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanProperties.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanProperties.kt @@ -43,6 +43,7 @@ class KonanProperties(val target: KonanTarget, val properties: Properties, val b val linkerKonanFlags get() = targetList("linkerKonanFlags") val linkerDebugFlags get() = targetList("linkerDebugFlags") val llvmDebugOptFlags get() = targetList("llvmDebugOptFlags") + val s2wasmFlags get() = targetList("s2wasmFlags") val targetSysRoot get() = targetString("targetSysRoot") val libffiDir get() = targetString("libffiDir") diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTarget.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTarget.kt index d0226ab65fb..f6bda9606e8 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTarget.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTarget.kt @@ -25,7 +25,7 @@ enum class KonanTarget(val targetSuffix: String, val programSuffix: String, var MINGW("mingw", "exe"), MACBOOK("osx", "kexe"), RASPBERRYPI("raspberrypi", "kexe"), - WASM32("wasm32", "kexe"); + WASM32("wasm32", "wasm"); val userName get() = name.toLowerCase() }