[WIP] Web workers
diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt
index 9b67e66..c9e7a09 100644
--- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt
+++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt
@@ -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
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt
index eb25690..bdee0a4 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt
@@ -26,7 +26,6 @@
 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 @@
     // 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 @@
     private fun binOpBool(name: String) = binOp(name, irBuiltIns.bool)
     private fun binOpInt(name: String) = binOp(name, irBuiltIns.int)
 }
+
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt
index c3aaba0..350829a 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt
@@ -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.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 @@
         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 @@
     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 }
 
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt
index 3324a7c..fcfac66 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt
@@ -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 @@
 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 @@
     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 @@
 
     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 @@
     ).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()
 }
\ No newline at end of file
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt
index 583598e..cc02acf 100644
--- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/IrModuleToJsTransformer.kt
@@ -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.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 @@
 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 @@
         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
diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/webWorkers/webWorkersSupport.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/webWorkers/webWorkersSupport.kt
new file mode 100644
index 0000000..9f2f3a3
--- /dev/null
+++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/webWorkers/webWorkersSupport.kt
@@ -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"))
+
diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt
index e0b1d09..5d9a1df 100644
--- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt
+++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/declarations/IrDeclarationOrigin.kt
@@ -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 @@
     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")
diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/PatchDeclarationParents.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/PatchDeclarationParents.kt
index 2e2b497..489048e 100644
--- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/PatchDeclarationParents.kt
+++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/PatchDeclarationParents.kt
@@ -67,6 +67,9 @@
     }
 
     private fun patchParent(declaration: IrDeclaration) {
+        if (declarationParentsStack.isEmpty()) {
+            declaration.parent = declaration.parent
+        }
         declaration.parent = declarationParentsStack.peekFirst()
     }
 }
\ No newline at end of file
diff --git a/compiler/ir/serialization.js/build.gradle.kts b/compiler/ir/serialization.js/build.gradle.kts
index dcaa8d4..c3a8739 100644
--- a/compiler/ir/serialization.js/build.gradle.kts
+++ b/compiler/ir/serialization.js/build.gradle.kts
@@ -125,6 +125,7 @@
                 "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",
diff --git a/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt b/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt
index db3206d..a317c30 100644
--- a/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt
+++ b/js/js.tests/test/org/jetbrains/kotlin/generators/tests/GenerateJsTests.kt
@@ -42,6 +42,10 @@
         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") {
diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt
index 84eb1d9..32b51bb 100644
--- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt
+++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt
@@ -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 @@
 
 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 @@
     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 @@
                 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)
diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/JsTestChecker.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/JsTestChecker.kt
index 53836dd..4d19bc3 100644
--- a/js/js.tests/test/org/jetbrains/kotlin/js/test/JsTestChecker.kt
+++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/JsTestChecker.kt
@@ -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.
  */
 
diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsWebWorkersTestsGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsWebWorkersTestsGenerated.java
new file mode 100644
index 0000000..589443b
--- /dev/null
+++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/IrJsWebWorkersTestsGenerated.java
@@ -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");
+    }
+}
diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/abstractClassesForGeneratedIrTests.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/abstractClassesForGeneratedIrTests.kt
index 1f69a20..d23cdec 100644
--- a/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/abstractClassesForGeneratedIrTests.kt
+++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/ir/semantics/abstractClassesForGeneratedIrTests.kt
@@ -38,4 +38,6 @@
 
 abstract class AbstractIrInlineSuspendTests : BorrowedIrInlineTest("suspend/")
 
-abstract class AbstractIrJsInlineContractsTests : BorrowedIrInlineTest("contracts/")
\ No newline at end of file
+abstract class AbstractIrJsInlineContractsTests : BorrowedIrInlineTest("contracts/")
+
+abstract class AbstractIrJsWebWorkersTests : BasicIrBoxTest(TEST_DATA_DIR_PATH + "webworkers/", "webworkers/", supportWebWorkers = true)
\ No newline at end of file
diff --git a/js/js.translator/testData/package.json b/js/js.translator/testData/package.json
index 6642c07..2b37b1a9d 100644
--- a/js/js.translator/testData/package.json
+++ b/js/js.translator/testData/package.json
@@ -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"
   }
 }
diff --git a/js/js.translator/testData/runWebWorkerInNode.js b/js/js.translator/testData/runWebWorkerInNode.js
new file mode 100644
index 0000000..e376069
--- /dev/null
+++ b/js/js.translator/testData/runWebWorkerInNode.js
@@ -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();
diff --git a/js/js.translator/testData/webworkers/empty.kt b/js/js.translator/testData/webworkers/empty.kt
new file mode 100644
index 0000000..9409cb9
--- /dev/null
+++ b/js/js.translator/testData/webworkers/empty.kt
@@ -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") }
+}
diff --git a/js/js.translator/testData/webworkers/postMessage.kt b/js/js.translator/testData/webworkers/postMessage.kt
new file mode 100644
index 0000000..478f791
--- /dev/null
+++ b/js/js.translator/testData/webworkers/postMessage.kt
@@ -0,0 +1,10 @@
+// KJS_WITH_FULL_RUNTIME
+
+import kotlin.js.worker.*
+import kotlin.js.Promise
+
+fun box(): Promise<String> {
+    return worker {
+        "OK"
+    }.waitForReply()
+}
diff --git a/libraries/stdlib/js/src/kotlin/worker/worker.kt b/libraries/stdlib/js/src/kotlin/worker/worker.kt
new file mode 100644
index 0000000..51217fc
--- /dev/null
+++ b/libraries/stdlib/js/src/kotlin/worker/worker.kt
@@ -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)")
+}