Compare commits

...

1 Commits

Author SHA1 Message Date
Ilmir Usmanov
3930a4204b [WIP] Web workers 2019-08-26 17:54:03 +03:00
19 changed files with 493 additions and 36 deletions

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cli.js

View File

@@ -26,7 +26,6 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi2ir.findSingleFunction
import org.jetbrains.kotlin.types.KotlinType
import java.util.*
class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendContext) {
private val externalPackageFragmentSymbol = IrExternalPackageFragmentSymbolImpl(context.internalPackageFragmentDescriptor)
@@ -260,7 +259,8 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
// TODO move CharSequence-related stiff to IntrinsifyCallsLowering
val charSequenceClassSymbol = context.symbolTable.referenceClass(context.getClass(FqName("kotlin.CharSequence")))
val charSequenceLengthPropertyGetterSymbol =
charSequenceClassSymbol.owner.declarations.filterIsInstance<IrProperty>().first { it.name.asString() == "length" }.getter!!.symbol
charSequenceClassSymbol.owner.declarations.filterIsInstance<IrProperty>().find { it.name.asString() == "length" }?.getter?.symbol
?: charSequenceClassSymbol.owner.declarations.filterIsInstance<IrFunction>().single { it.name.asString() == "<get-length>" }.symbol
val charSequenceGetFunctionSymbol =
charSequenceClassSymbol.owner.declarations.filterIsInstance<IrFunction>().single { it.name.asString() == "get" }.symbol
val charSequenceSubSequenceFunctionSymbol =
@@ -339,3 +339,4 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
private fun binOpBool(name: String) = binOp(name, irBuiltIns.bool)
private fun binOpInt(name: String) = binOp(name, irBuiltIns.int)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -29,7 +29,10 @@ import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.symbols.IrEnumEntrySymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.IrDynamicType
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.classifierOrFail
@@ -121,6 +124,8 @@ class JsIrBackendContext(
private val COROUTINE_PACKAGE_FQNAME = COROUTINE_PACKAGE_FQNAME_13
private val COROUTINE_INTRINSICS_PACKAGE_FQNAME = COROUTINE_PACKAGE_FQNAME.child(INTRINSICS_PACKAGE_NAME)
private val WORKER_PACKAGE_FQNAME = FqName.fromSegments(listOf("kotlin", "js", "worker"))
// TODO: due to name clash those weird suffix is required, remove it once `MemberNameGenerator` is implemented
private val COROUTINE_SUSPEND_OR_RETURN_JS_NAME = "suspendCoroutineUninterceptedOrReturnJS"
private val GET_COROUTINE_CONTEXT_NAME = "getCoroutineContext"
@@ -277,6 +282,12 @@ class JsIrBackendContext(
val throwableConstructors by lazy { throwableClass.owner.declarations.filterIsInstance<IrConstructor>().map { it.symbol } }
val defaultThrowableCtor by lazy { throwableConstructors.single { !it.owner.isPrimary && it.owner.valueParameters.size == 0 } }
// web worker specific stuff
private val workerPackage = module.getPackage(WORKER_PACKAGE_FQNAME)
val postMessage = symbolTable.referenceSimpleFunction(getFunctions(FqName("kotlin.js.worker.postMessage")).single())
val terminateMessage = symbolTable.referenceSimpleFunction(getFunctions(FqName("kotlin.js.worker.terminateWorkers")).single())
val workerClass = symbolTable.referenceClass(findClass(workerPackage.memberScope, Name.identifier("WebWorker")))
private fun referenceOperators(): Map<Name, MutableMap<IrClassifierSymbol, IrSimpleFunctionSymbol>> {
val primitiveIrSymbols = irBuiltIns.primitiveIrTypes.map { it.classifierOrFail as IrClassSymbol }

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -8,9 +8,13 @@ package org.jetbrains.kotlin.ir.backend.js
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
import org.jetbrains.kotlin.ir.backend.js.utils.JsMainFunctionDetector
import org.jetbrains.kotlin.ir.backend.js.webWorkers.moveWorkersToSeparateFiles
import org.jetbrains.kotlin.ir.backend.js.webWorkers.prepareFilePrefixForWorkers
import org.jetbrains.kotlin.ir.backend.js.webWorkers.prepareFileSuffixForWorkers
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
@@ -36,7 +40,8 @@ fun compile(
allDependencies: List<KotlinLibrary>,
friendDependencies: List<KotlinLibrary>,
mainArguments: List<String>?,
exportedDeclarations: Set<FqName> = emptySet()
exportedDeclarations: Set<FqName> = emptySet(),
supportWebWorkers: Boolean = false
): String {
val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) =
loadIr(project, files, configuration, allDependencies, friendDependencies)
@@ -45,7 +50,14 @@ fun compile(
val mainFunction = JsMainFunctionDetector.getMainFunctionOrNull(moduleFragment)
val context = JsIrBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, exportedDeclarations, configuration)
val context = JsIrBackendContext(
moduleDescriptor,
irBuiltIns,
symbolTable,
moduleFragment,
if (supportWebWorkers) exportedDeclarations + FqName("kotlin.js.worker.terminateWorkers") else exportedDeclarations,
configuration
)
// Load declarations referenced during `context` initialization
dependencyModules.forEach {
@@ -71,9 +83,16 @@ fun compile(
).generateUnboundSymbolsAsDependencies()
moduleFragment.patchDeclarationParents()
val workerFilesWithIndices = moduleFragment.files.flatMap { moveWorkersToSeparateFiles(it, context) }
moduleFragment.files += workerFilesWithIndices.map { it.irFile }
jsPhases.invokeToplevel(phaseConfig, context, moduleFragment)
val jsProgram =
moduleFragment.accept(IrModuleToJsTransformer(context, mainFunction, mainArguments), null)
return jsProgram.toString()
val jsProgram = moduleFragment.accept(IrModuleToJsTransformer(context, mainFunction, mainArguments, workerFilesWithIndices), null)
return if (supportWebWorkers && workerFilesWithIndices.isNotEmpty())
prepareFilePrefixForWorkers() + jsProgram.toString() + prepareFileSuffixForWorkers(
context.configuration[CommonConfigurationKeys.MODULE_NAME]!!,
workerFilesWithIndices
)
else jsProgram.toString()
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.backend.js.webWorkers.WorkerFileWithIndex
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
@@ -22,7 +23,8 @@ import org.jetbrains.kotlin.utils.addIfNotNull
class IrModuleToJsTransformer(
private val backendContext: JsIrBackendContext,
private val mainFunction: IrSimpleFunction?,
private val mainArguments: List<String>?
private val mainArguments: List<String>?,
private val workerFilesWithIndices: List<WorkerFileWithIndex>
) : BaseIrElementToJsNodeTransformer<JsNode, Nothing?> {
val moduleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!!
@@ -276,6 +278,10 @@ class IrModuleToJsTransformer(
if (fqNameWhenAvailable in backendContext.additionalExportedDeclarations)
return true
if (name.asString() in workerFilesWithIndices.map { it.functionName }) {
return true
}
// Hack to support properties
val correspondingProperty = when {
this is IrField -> correspondingPropertySymbol

View File

@@ -0,0 +1,226 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.webWorkers
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.isTopLevelInPackage
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.SourceManager
import org.jetbrains.kotlin.ir.SourceRangeInfo
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.types.createType
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.visitors.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.utils.sure
private data class WorkerInfo(val call: IrCall, val lambda: IrFunction, val index: Int)
private const val WORKER_FUNCTION_NAME_PREFIX = "__Worker_"
class WorkerFileWithIndex(val irFile: IrFile, val index: Int) {
val functionName = WORKER_FUNCTION_NAME_PREFIX + index
}
fun prepareFilePrefixForWorkers(): String = "var wt = require('worker_threads');\n"
fun prepareFileSuffixForWorkers(moduleName: String, workerFileWithIndices: List<WorkerFileWithIndex>): String {
val res = StringBuffer(
"""
if (!wt.isMainThread) {
switch (wt.workerData.id) {"""
)
for (workerFileWithIndex in workerFileWithIndices) {
res.append(
"""
case ${workerFileWithIndex.index}:
wt.parentPort.on('message', $moduleName.${workerFileWithIndex.functionName});
break;
"""
)
}
res.append(
"""
default:
throw Error("Worker with id: " + id + " not found")
}
}
"""
)
return res.toString()
}
fun moveWorkersToSeparateFiles(irFile: IrFile, context: JsIrBackendContext): List<WorkerFileWithIndex> {
val workerCalls = irFile.filterCalls { it.descriptor.isWorker() }
if (workerCalls.isEmpty()) return emptyList()
val infos = workerCalls.withIndex().map { (index, call) ->
WorkerInfo(
call,
(call.getValueArgument(0) as? IrFunctionExpression ?: error("worker intrinsic accepts only block, but got $call")).function,
index
)
}
val result = infos.map { (_, workerLambda, index) ->
replaceReturnsWithPostMessage(workerLambda, context)
WorkerFileWithIndex(moveToSeparateFile(workerLambda, context, "$WORKER_FUNCTION_NAME_PREFIX$index"), index)
}
replaceWorkerIntrinsicCalls(infos, irFile, context)
return result
}
private fun moveToSeparateFile(
workerLambda: IrFunction,
context: JsIrBackendContext,
fileName: String
): IrFile {
val newFile = IrFileImpl(
object : SourceManager.FileEntry {
override val name = fileName
override val maxOffset = UNDEFINED_OFFSET
override fun getSourceRangeInfo(beginOffset: Int, endOffset: Int) =
SourceRangeInfo(
"",
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
UNDEFINED_OFFSET
)
override fun getLineNumber(offset: Int) = UNDEFINED_OFFSET
override fun getColumnNumber(offset: Int) = UNDEFINED_OFFSET
}, context.workersPackageFragmentDescriptor
)
val function = JsIrBuilder.buildFunction(fileName, context.irBuiltIns.unitType, newFile).also {
it.body = workerLambda.body
it.copyValueParametersAndUpdateGetValues(workerLambda)
}
newFile.declarations.add(function)
return newFile
}
fun IrFunction.copyValueParametersAndUpdateGetValues(original: IrFunction) {
for (param in original.valueParameters) {
addValueParameter(param.name.asString(), param.type)
}
body?.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitGetValue(expression: IrGetValue): IrExpression {
val index = original.valueParameters.single { it.symbol == expression.symbol.owner }.index
return IrGetValueImpl(expression.startOffset, expression.endOffset, valueParameters[index].symbol)
}
})
}
private fun replaceReturnsWithPostMessage(workerLambda: IrFunction, context: JsIrBackendContext) {
workerLambda.body.sure { "worker lambda $workerLambda shall have body" }.transformReturns {
IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.unitType, context.postMessage).apply {
putValueArgument(0, it.value)
}
}
}
private fun replaceWorkerIntrinsicCalls(
infos: List<WorkerInfo>,
irFile: IrFile,
context: JsIrBackendContext
) {
irFile.transformCalls { call ->
val info = infos.find { it.call == call }
if (info != null) {
val constructor = context.workerClass.constructors.first()
IrConstructorCallImpl.fromSymbolDescriptor(
call.startOffset, call.endOffset, context.workerClass.createType(false, emptyList()),
constructor
).apply {
putValueArgument(
0, newCallWithUndefinedOffsets(
symbol = context.jsCodeSymbol,
arguments = listOf(context.string("new wt.Worker(__filename, { workerData: { id: ${info.index} } })"))
)
)
putValueArgument(
1, newCallWithUndefinedOffsets(
symbol = context.jsCodeSymbol,
arguments = listOf(context.string("{}"))
)
)
}
} else call
}
}
fun IrElement.filterCalls(predicate: (IrCall) -> Boolean): List<IrCall> {
val res = arrayListOf<IrCall>()
acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitCall(expression: IrCall) {
if (predicate(expression)) {
res += expression
}
super.visitCall(expression)
}
})
return res
}
fun IrElement.transformCalls(transformer: (IrCall) -> IrExpression) {
transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitCall(expression: IrCall): IrExpression {
val visited = super.visitCall(expression) as IrCall
return transformer(visited)
}
})
}
fun IrElement.transformReturns(transformer: (IrReturn) -> IrExpression) {
transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitReturn(expression: IrReturn): IrExpression {
val visited = super.visitReturn(expression) as IrReturn
return transformer(visited)
}
})
}
fun newCallWithUndefinedOffsets(
symbol: IrFunctionSymbol,
arguments: List<IrExpression?>
) = IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol.owner.returnType, symbol).apply {
for ((index, expression) in arguments.withIndex()) {
putValueArgument(index, expression)
}
}
private val JsIrBackendContext.workersPackageFragmentDescriptor: PackageFragmentDescriptor
get() = EmptyPackageFragmentDescriptor(builtIns.builtInsModule, FqName(""))
private fun FunctionDescriptor.isWorker(): Boolean = isTopLevelInPackage("worker", "kotlin.js.worker")
private fun JsIrBackendContext.string(s: String) = JsIrBuilder.buildString(irBuiltIns.stringType, s)
private val JsIrBackendContext.jsCodeSymbol
get() = symbolTable.referenceSimpleFunction(getJsInternalFunction("js"))

View File

@@ -1,17 +1,6 @@
/*
* Copyright 2010-2016 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.
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.declarations
@@ -42,6 +31,7 @@ interface IrDeclarationOrigin {
object IR_BUILTINS_STUB : IrDeclarationOriginImpl("IR_BUILTINS_STUB")
object BRIDGE : IrDeclarationOriginImpl("BRIDGE", isSynthetic = true)
object BRIDGE_SPECIAL: IrDeclarationOriginImpl("BRIDGE_SPECIAL")
object WORKER: IrDeclarationOriginImpl("WORKER")
object FIELD_FOR_ENUM_ENTRY : IrDeclarationOriginImpl("FIELD_FOR_ENUM_ENTRY")
object FIELD_FOR_ENUM_VALUES : IrDeclarationOriginImpl("FIELD_FOR_ENUM_VALUES")

View File

@@ -67,6 +67,9 @@ class PatchDeclarationParentsVisitor() : IrElementVisitorVoid {
}
private fun patchParent(declaration: IrDeclaration) {
if (declarationParentsStack.isEmpty()) {
declaration.parent = declaration.parent
}
declaration.parent = declarationParentsStack.peekFirst()
}
}

View File

@@ -125,6 +125,7 @@ val reducedRuntimeSources by task<Sync> {
"libraries/stdlib/js/src/kotlin/regexp.kt",
"libraries/stdlib/js/src/kotlin/sequence.kt",
"libraries/stdlib/js/src/kotlin/text/**",
"libraries/stdlib/js/src/kotlin/worker/**",
"libraries/stdlib/src/kotlin/collections/**",
"libraries/stdlib/src/kotlin/experimental/bitwiseOperations.kt",
"libraries/stdlib/src/kotlin/properties/Delegates.kt",

View File

@@ -42,6 +42,10 @@ fun main(args: Array<String>) {
testClass<AbstractJsLineNumberTest> {
model("lineNumbers/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS)
}
testClass<AbstractIrJsWebWorkersTests> {
model("webworkers/", pattern = "^([^_](.+))\\.kt$", targetBackend = TargetBackend.JS_IR)
}
}
testGroup("js/js.tests/test", "compiler/testData", testRunnerMethodName = "runTest0") {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
@@ -7,10 +7,10 @@ package org.jetbrains.kotlin.js.test
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.toPhaseMap
import org.jetbrains.kotlin.ir.backend.js.loadKlib
import org.jetbrains.kotlin.ir.backend.js.compile
import org.jetbrains.kotlin.ir.backend.js.generateKLib
import org.jetbrains.kotlin.ir.backend.js.jsPhases
import org.jetbrains.kotlin.ir.backend.js.loadKlib
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.JsConfig
import org.jetbrains.kotlin.js.facade.MainCallParameters
@@ -28,7 +28,8 @@ abstract class BasicIrBoxTest(
testGroupOutputDirPrefix: String,
pathToRootOutputDir: String = BasicBoxTest.TEST_DATA_DIR_PATH,
generateSourceMap: Boolean = false,
generateNodeJsRunner: Boolean = false
generateNodeJsRunner: Boolean = false,
private val supportWebWorkers: Boolean = false
) : BasicBoxTest(
pathToTestDir,
testGroupOutputDirPrefix,
@@ -110,7 +111,8 @@ abstract class BasicIrBoxTest(
allDependencies = allDependencies,
friendDependencies = emptyList(),
mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null },
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction)))
exportedDeclarations = setOf(FqName.fromSegments(listOfNotNull(testPackage, testFunction))),
supportWebWorkers = supportWebWorkers
)
val wrappedCode = wrapWithModuleEmulationMarkers(jsCode, moduleId = config.moduleId, moduleKind = config.moduleKind)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.js.test.ir.semantics;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("js/js.translator/testData/webworkers")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class IrJsWebWorkersTestsGenerated extends AbstractIrJsWebWorkersTests {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath);
}
public void testAllFilesPresentInWebworkers() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/webworkers"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.JS_IR, true);
}
@TestMetadata("empty.kt")
public void testEmpty() throws Exception {
runTest("js/js.translator/testData/webworkers/empty.kt");
}
@TestMetadata("postMessage.kt")
public void testPostMessage() throws Exception {
runTest("js/js.translator/testData/webworkers/postMessage.kt");
}
}

View File

@@ -38,4 +38,6 @@ abstract class AbstractIrInlineDefaultValuesTests : BorrowedIrInlineTest("defaul
abstract class AbstractIrInlineSuspendTests : BorrowedIrInlineTest("suspend/")
abstract class AbstractIrJsInlineContractsTests : BorrowedIrInlineTest("contracts/")
abstract class AbstractIrJsInlineContractsTests : BorrowedIrInlineTest("contracts/")
abstract class AbstractIrJsWebWorkersTests : BasicIrBoxTest(TEST_DATA_DIR_PATH + "webworkers/", "webworkers/", supportWebWorkers = true)

View File

@@ -1,14 +1,16 @@
{
"dependencies" : {
"dependencies": {
"node": "^12.7.0",
"require-from-string": "1.2.0"
},
"devDependencies" : {
"devDependencies": {
"mocha": "3.2.0",
"mocha-teamcity-reporter": "1.1.1"
},
"scripts": {
"runOnTeamcity": "mocha --reporter mocha-teamcity-reporter",
"test": "mocha",
"runIrTestInNode" : "node $NODE_DEBUG_OPTION runIrTestInNode.js"
"runIrTestInNode": "node $NODE_DEBUG_OPTION runIrTestInNode.js",
"runWebWorkerInNode": "node $NODE_DEBUG_OPTION runWebWorkerInNode.js"
}
}

View File

@@ -0,0 +1,84 @@
// Note right now it works only for IR tests.
// With IDEA 2018.3 or later you can just activate required js and run "run IR test in node.js" configuration.
// Add to this array your path to test files or provide it as argument.
var anotherFiles = [""];
var vm = require('vm');
var fs = require('fs');
var wt = require('worker_threads');
// Change working dir to root of project
var testDataPathFromRoot = "js/js.translator/testData";
var cwd = process.cwd();
if (cwd.endsWith(testDataPathFromRoot)) {
process.chdir(cwd.substr(0, cwd.length - testDataPathFromRoot.length));
}
var filesFromArgs = process.argv.slice(2);
function toAbsolutePath(path) {
if (fs.existsSync(path) && fs.statSync(path).isFile()) {
return fs.realpathSync(path)
}
return "";
}
// TODO autodetect common js files and other js files
// Filter out all except existing js files and transform all paths to absolute
var files = [].concat(filesFromArgs, anotherFiles)
.map(toAbsolutePath)
.filter(function(path) {
return path.endsWith(".js")
});
// Find runtime path
var runtimeHeader = "// RUNTIME: ";
var runtimeFiles = [];
files.forEach(function (path) {
var code = fs.readFileSync(path, 'utf8');
var firstLine = code.substr(0, code.indexOf("\n"));
if (firstLine.startsWith(runtimeHeader)) {
runtimeFiles = JSON.parse(firstLine.slice(runtimeHeader.length))
.map(toAbsolutePath);
}
});
var allFiles = [].concat(runtimeFiles, files);
// Evaluate files and run box function
var sandbox = {
require: require,
__filename: filesFromArgs[0]
};
vm.createContext(sandbox);
allFiles.forEach(function(path) {
var code = fs.readFileSync(path, 'utf8');
vm.runInContext(code, sandbox, {
filename: path
})
});
function sleep(time) {
var start = Date.now();
while (Date.now() < start + time) {
}
}
function runTest() {
vm.runInContext("JS_TESTS.box()", sandbox).then(function (result) {
console.log(result);
try {
vm.runInContext("JS_TESTS.terminateWorkers()", sandbox);
} catch (e) {
console.log(e);
}
}, function (e) { console.log(e) });
}
runTest();

View File

@@ -0,0 +1,11 @@
// KJS_WITH_FULL_RUNTIME
import kotlin.js.worker.*
import kotlin.js.Promise
fun box(): Promise<String> {
worker<Unit> {
// Just a smoke test: no exception should be thrown
}
return Promise<String> { resolve, _ -> resolve("OK") }
}

View File

@@ -0,0 +1,10 @@
// KJS_WITH_FULL_RUNTIME
import kotlin.js.worker.*
import kotlin.js.Promise
fun box(): Promise<String> {
return worker {
"OK"
}.waitForReply()
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlin.js.worker
import kotlin.js.Promise
class WebWorker<T>(private val worker: dynamic, initialMessage: dynamic) {
init {
aliveWorkers.add(WorkerWrapper(worker))
worker.postMessage(initialMessage)
}
fun waitForReply(): Promise<T> {
return Promise { resolve, reject -> worker.on("message") { e -> resolve(e) }}
}
companion object {
private val aliveWorkers = arrayListOf<WorkerWrapper>()
fun terminateWorkers() {
for (worker in aliveWorkers) {
worker.worker.terminate()
}
}
}
}
// TODO: this shall be internal and exported from module
fun terminateWorkers() {
WebWorker.terminateWorkers()
}
private class WorkerWrapper(val worker: dynamic)
fun <T> worker(c: () -> T): WebWorker<T> {
throw UnsupportedOperationException("Implemented as intrinsic")
}
fun postMessage(message: dynamic) {
js("wt.parentPort.postMessage(message)")
}