Upgrade kotlinx.atomicfu to 0.18.5 am: b943793454 am: 2282b1ddcd am: 2fe11a0356

Original change: https://android-review.googlesource.com/c/platform/external/kotlinx.atomicfu/+/2281219

Change-Id: I9e9ec7b3e99d3b2f34dce2cecbe274987311a09f
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/CHANGES.md b/CHANGES.md
index 1ad8836..1303cb4 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,75 @@
 # Change log for kotlinx.atomicfu
 
+# Version 0.18.5
+
+* Support JVM IR compiler plugin (#246).
+* Update Kotlin to 1.7.20. 
+* Added more tests for atomicfu-gradle-plugin (#255).
+
+# Version 0.18.4
+
+* Fix KGP compatibility bug with freeCompilerArgs modification (#247).
+* Update kotlinx.metadata to 0.5.0 (#245). 
+* Update gradle version to 6.8.3 (#244)
+
+# Version 0.18.3
+
+* Fix for atomicfu-gradle-plugin application to the MPP project (for Kotlin 1.7.20).
+
+# Version 0.18.2
+
+* In Kotlin 1.7.10 the name of `atomicfu-runtime` module was reverted back to `kotlinx-atomicfu-runtime`, 
+  as the renaming was an incompatible change. 
+  Fixed `atomicfu-gradle-plugin` to add `kotlinx-atomicfu-runtime` dependency directly.
+
+# Version 0.18.1
+
+* Fix for the compatibility issue: add `atomicfu-runtime` dependency directly since Kotlin 1.7.10.
+
+# Version 0.18.0
+
+* Update Kotlin to 1.7.0.
+* Fix kotlin 1.7 compatibility (#222).
+* Update JVM target to 1.8 (see KT-45165).
+* Fix for parsing Kotlin version in AtomicfuGradlePlugin.
+
+# Version 0.17.3
+
+* Adding compiler plugin dependency only for projects with KGP >= 1.6.20 (#226).
+* Compiler plugin runtime dependency fixes (#230).
+* Update README badges (#228).
+
+# Version 0.17.2
+
+* Update Kotlin to 1.6.20.
+* IR transformation for Kotlin/JS. (#215).
+* Update ASM to 9.3 for Java 18 support (#223)
+* Update kotlinx.metadata to 0.4.2.
+
+# Version 0.17.1
+
+* Support of `org.jetbrains.kotlin.js` plugin (#218).
+* Fixed configuration cache bug. (#216).
+* Bug fixes for delegated fields support (#179).
+
+# Version 0.17.0
+
+* Update Kotlin to 1.6.0.
+* Update ASM minimal api version to ASM7 (#203).
+* Add explicit module-info for JPMS compatibility (#201).
+
+# Version 0.16.3
+
+* Kotlin is updated to 1.5.30.
+* All references to Bintray are removed from artefacts POMs.
+* Added new Apple Silicon targets for K/N.
+
+# Version 0.16.2
+
+* Update Kotlin to 1.5.20.
+* ASM 9.1 for Java 15+ support (#190).
+* Removing extra atomicfu references from LVT.
+
 # Version 0.16.0
 
 * Update Kotlin to 1.5.0.
diff --git a/METADATA b/METADATA
index c25731d..ef605b9 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,7 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update kotlinx.atomicfu
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+
 name: "kotlinx.atomicfu"
 description: "The idiomatic way to use atomic operations in Kotlin."
 third_party {
@@ -9,11 +13,11 @@
     type: GIT
     value: "https://github.com/Kotlin/kotlinx.atomicfu"
   }
-  version: "0.14.4"
+  version: "0.18.5"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2020
+    year: 2022
     month: 11
-    day: 25
+    day: 1
   }
 }
diff --git a/README.md b/README.md
index 3f86a27..e3dd5b3 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,49 @@
 # AtomicFU 
 
-[![JetBrains incubator project](https://jb.gg/badges/incubator.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
+[![Kotlin Beta](https://kotl.in/badges/beta.svg)](https://kotlinlang.org/docs/components-stability.html)
+[![JetBrains official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
 [![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0)
-[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.atomicfu/images/download.svg) ](https://bintray.com/kotlin/kotlinx/kotlinx.atomicfu/_latestVersion)
+[![Maven Central](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/atomicfu)](https://search.maven.org/artifact/org.jetbrains.kotlinx/atomicfu/0.18.5/pom)
 
-The idiomatic way to use atomic operations in Kotlin. 
+>Note on Beta status: the plugin is in its active development phase and changes from release to release.
+>We do provide a compatibility of atomicfu-transformed artifacts between releases, but we do not provide 
+>strict compatibility guarantees on plugin API and its general stability between Kotlin versions.
 
-* Code it like `AtomicReference/Int/Long`, but run it in production efficiently as `AtomicXxxFieldUpdater` on Kotlin/JVM 
-  and as plain unboxed values on Kotlin/JS. 
-* Use Kotlin-specific extensions (e.g. inline `updateAndGet` and `getAndUpdate` functions).
-* Compile-time dependency only (no runtime dependencies).
-  * Post-compilation bytecode transformer that declares all the relevant field updaters for you on [Kotlin/JVM](#jvm).
-  * Post-compilation JavaScript files transformer on [Kotlin/JS](#js).
-* Multiplatform: 
-  * [Kotlin/Native](#native) is supported.
-  * However, Kotlin/Native works as library dependency at the moment (unlike Kotlin/JVM and Kotlin/JS).
-  * This enables writing [common](#common) Kotlin code with atomics that compiles for JVM, JS, and Native.
-* [Gradle](#gradle-build-setup) for all platforms and [Maven](#maven-build-setup) for JVM are supported.  
-* [Additional features](#additional-features) include:
-  * [JDK9 VarHandle](#varhandles-with-java-9).
-  * [Arrays of atomic values](#arrays-of-atomic-values).
-  * [User-defined extensions on atomics](#user-defined-extensions-on-atomics)
-  * [Locks](#locks)
-  * [Testing of lock-free data structures](#testing-lock-free-data-structures-on-jvm).
-  * [Tracing operations](#tracing-operations)
+**Atomicfu** is a multiplatform library that provides the idiomatic and effective way of using atomic operations in Kotlin.
 
+## Table of contents
+- [Features](#features)
+- [Example](#example)
+- [Quickstart](#quickstart)
+  - [Apply plugin to a project](#apply-plugin)
+    - [Gradle configuration](#gradle-configuration)
+    - [Maven configuration](#maven-configuration)
+- [Usage constraints](#usage-constraints)  
+- [Transformation modes](#transformation-modes)
+  - [Atomicfu compiler plugin](#atomicfu-compiler-plugin)
+- [Options for post-compilation transformation](#options-for-post-compilation-transformation) 
+  - [JVM options](#jvm-options)
+  - [JS options](#js-options)
+- [More features](#more-features)
+  - [Arrays of atomic values](#arrays-of-atomic-values)
+  - [User-defined extensions on atomics](#user-defined-extensions-on-atomics)
+  - [Locks](#locks)
+  - [Tracing operations](#tracing-operations)
+- [Kotlin/Native support](#kotlin-native-support)
+
+
+## Features
+
+* Code it like a boxed value `atomic(0)`, but run it in production efficiently:
+  * as `java.util.concurrent.atomic.AtomicXxxFieldUpdater` on Kotlin/JVM 
+  * as a plain unboxed value on Kotlin/JS
+* Multiplatform: write common Kotlin code with atomics that compiles for Kotlin JVM, JS, and Native backends:
+    * Compile-only dependency for JVM and JS (no runtime dependencies)
+    * Compile and runtime dependency for Kotlin/Native 
+* Use Kotlin-specific extensions (e.g. inline `loop`, `update`, `updateAndGet` functions).
+* Use atomic arrays, user-defined extensions on atomics and locks (see [more features](#more-features)).
+* [Tracing operations](#tracing-operations) for debugging.
+  
 ## Example
 
 Let us declare a `top` variable for a lock-free stack implementation:
@@ -74,107 +93,67 @@
 Integer and long atomics provide all the usual `getAndIncrement`, `incrementAndGet`, `getAndAdd`, `addAndGet`, and etc
 operations. They can be also atomically modified via `+=` and `-=` operators.
 
-## Dos and Don'ts
+## Quickstart
+### Apply plugin
+#### Gradle configuration
 
-* Declare atomic variables as `private val` or `internal val`. You can use just (public) `val`, 
-  but make sure they are not directly accessed outside of your Kotlin module (outside of the source set).
-  Access to the atomic variable itself shall be encapsulated.
-* Only simple operations on atomic variables _directly_ are supported. 
-  * Do not read references on atomic variables into local variables,
-    e.g. `top.compareAndSet(...)` is Ok, while `val tmp = top; tmp...` is not. 
-  * Do not leak references on atomic variables in other way (return, pass as params, etc). 
-* Do not introduce complex data flow in parameters to atomic variable operations, 
-  i.e. `top.value = complex_expression` and `top.compareAndSet(cur, complex_expression)` are not supported 
-  (more specifically, `complex_expression` should not have branches in its compiled representation).
-  Extract `complex_expression` into a variable when needed.
-* Use the following convention if you need to expose the value of atomic property to the public:
+Gradle configuration is supported for all platforms, minimal version is Gradle 6.8.
+
+In top-level build file:
+
+<details open>
+<summary>Kotlin</summary>
 
 ```kotlin
-private val _foo = atomic<T>(initial) // private atomic, convention is to name it with leading underscore
-public var foo: T by _foo            // public delegated property (val/var)
-```  
-
-## Gradle build setup
-
-Building with Gradle is supported for all platforms.
-
-### JVM
-
-You will need Gradle 4.10 or later.
-Add and apply AtomicFU plugin. It adds all the corresponding dependencies
-and transformations automatically. 
-See [additional configuration](#additional-configuration) if that needs tweaking.
-
-```groovy
 buildscript {
-    ext.atomicfu_version = '0.16.0'
+    repositories {
+        mavenCentral()
+    }
 
     dependencies {
-        classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version"
+      classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.18.5")
     }
 }
 
+apply(plugin = "kotlinx-atomicfu")
+```
+</details>
+
+<details>
+<summary>Groovy</summary>
+
+```groovy
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.18.5'
+    }
+}
+  
 apply plugin: 'kotlinx-atomicfu'
 ```
+</details>
 
-### JS 
+#### Maven configuration
 
-Configure add apply plugin just like for [JVM](#jvm). 
+Maven configuration is supported for JVM projects.
 
-### Native
 
-This library is available for Kotlin/Native (`atomicfu-native`).
-Kotlin/Native uses Gradle metadata and needs Gradle version 5.3 or later.
-See [Gradle Metadata 1.0 announcement](https://blog.gradle.org/gradle-metadata-1.0) for more details.
-Apply the corresponding plugin just like for [JVM](#jvm). 
-
-Atomic references for Kotlin/Native are based on 
-[FreezableAtomicReference](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-freezable-atomic-reference/-init-.html)
-and every reference that is stored to the previously 
-[frozen](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/freeze.html) 
-(shared with another thread) atomic is automatically frozen, too.
-
-Since Kotlin/Native does not generally provide binary compatibility between versions, 
-you should use the same version of Kotlin compiler as was used to build AtomicFU. 
-See [gradle.properties](gradle.properties) in AtomicFU project for its `kotlin_version`.
-
-### Common
-
-If you write a common code that should get compiled or different platforms, add `org.jetbrains.kotlinx:atomicfu-common`
-to your common code dependencies or apply `kotlinx-atomicfu` plugin that adds this dependency automatically:
-
-```groovy
-dependencies {
-    compile "org.jetbrains.kotlinx:atomicfu-common:$atomicfu_version"
-}
-```
-
-### Additional configuration
-
-There are the following additional parameters (with their defaults):
-
-```groovy
-atomicfu {
-  dependenciesVersion = '0.16.0' // set to null to turn-off auto dependencies
-  transformJvm = true // set to false to turn off JVM transformation
-  transformJs = true // set to false to turn off JS transformation
-  variant = "FU" // JVM transformation variant: FU,VH, or BOTH 
-  verbose = false // set to true to be more verbose  
-}
-```
-
-## Maven build setup
-
-Declare AtomicFU version:
+<details open>
+<summary>Declare atomicfu version</summary>
 
 ```xml
 <properties>
-     <atomicfu.version>0.16.0</atomicfu.version>
+     <atomicfu.version>0.18.5</atomicfu.version>
 </properties> 
 ```
 
-Declare _provided_ dependency on the AtomicFU library 
-(the users of the resulting artifact will not have a dependency on AtomicFU library):
+</details>
+
+<details>
+<summary>Declare provided dependency on the AtomicFU library</summary>
 
 ```xml
 <dependencies>
@@ -187,71 +166,161 @@
 </dependencies>
 ```
 
+</details>
+
 Configure build steps so that Kotlin compiler puts classes into a different `classes-pre-atomicfu` directory,
 which is then transformed to a regular `classes` directory to be used later by tests and delivery.
 
+<details>
+<summary>Build steps</summary>
+
 ```xml
 <build>
-    <plugins>
-        <!-- compile Kotlin files to staging directory -->
-        <plugin>
-            <groupId>org.jetbrains.kotlin</groupId>
-            <artifactId>kotlin-maven-plugin</artifactId>
-            <version>${kotlin.version}</version>
-            <executions>
-                <execution>
-                    <id>compile</id>
-                    <phase>compile</phase>
-                    <goals>
-                        <goal>compile</goal>
-                    </goals>
-                    <configuration>
-                        <output>${project.build.directory}/classes-pre-atomicfu</output>
-                        <!-- "VH" to use Java 9 VarHandle, "BOTH" to produce multi-version code -->
-                        <variant>FU</variant>  
-                    </configuration>
-                </execution>
-            </executions>
-        </plugin>
-        <!-- transform classes with AtomicFU plugin -->
-        <plugin>
-            <groupId>org.jetbrains.kotlinx</groupId>
-            <artifactId>atomicfu-maven-plugin</artifactId>
-            <version>${atomicfu.version}</version>
-            <executions>
-                <execution>
-                    <goals>
-                        <goal>transform</goal>
-                    </goals>
-                    <configuration>
-                        <input>${project.build.directory}/classes-pre-atomicfu</input>
-                    </configuration>
-                </execution>
-            </executions>
-        </plugin>
-    </plugins>
+  <plugins>
+    <!-- compile Kotlin files to staging directory -->
+    <plugin>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-maven-plugin</artifactId>
+      <version>${kotlin.version}</version>
+      <executions>
+        <execution>
+          <id>compile</id>
+          <phase>compile</phase>
+          <goals>
+            <goal>compile</goal>
+          </goals>
+          <configuration>
+            <output>${project.build.directory}/classes-pre-atomicfu</output>
+          </configuration>
+        </execution>
+      </executions>
+    </plugin>
+    <!-- transform classes with AtomicFU plugin -->
+    <plugin>
+      <groupId>org.jetbrains.kotlinx</groupId>
+      <artifactId>atomicfu-maven-plugin</artifactId>
+      <version>${atomicfu.version}</version>
+      <executions>
+        <execution>
+          <goals>
+            <goal>transform</goal>
+          </goals>
+          <configuration>
+            <input>${project.build.directory}/classes-pre-atomicfu</input>
+            <!-- "VH" to use Java 9 VarHandle, "BOTH" to produce multi-version code -->
+            <variant>FU</variant>
+          </configuration>
+        </execution>
+      </executions>
+    </plugin>
+  </plugins>
 </build>
 ```
 
-## Additional features
+</details>
 
-AtomicFU provides some additional features that you can optionally use.
+## Usage constraints
 
-### VarHandles with Java 9
+* Declare atomic variables as `private val` or `internal val`. You can use just (public) `val`, 
+  but make sure they are not directly accessed outside of your Kotlin module (outside of the source set).
+  Access to the atomic variable itself shall be encapsulated.
+* Only simple operations on atomic variables _directly_ are supported. 
+  * Do not read references on atomic variables into local variables,
+    e.g. `top.compareAndSet(...)` is ok, while `val tmp = top; tmp...` is not. 
+  * Do not leak references on atomic variables in other way (return, pass as params, etc). 
+* Do not introduce complex data flow in parameters to atomic variable operations, 
+  i.e. `top.value = complex_expression` and `top.compareAndSet(cur, complex_expression)` are not supported 
+  (more specifically, `complex_expression` should not have branches in its compiled representation).
+  Extract `complex_expression` into a variable when needed.
+* Use the following convention if you need to expose the value of atomic property to the public:
 
-AtomicFU can produce code that uses Java 9 
-[VarHandle](https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/VarHandle.html)
-instead of `AtomicXxxFieldUpdater`. Configure transformation `variant` in Gradle build file:
- 
+```kotlin
+private val _foo = atomic<T>(initial) // private atomic, convention is to name it with leading underscore
+public var foo: T by _foo            // public delegated property (val/var)
+```
+
+## Transformation modes
+
+Basically, Atomicfu library provides an effective usage of atomic values by performing the transformations of the compiled code.
+For JVM and JS there 2 transformation modes available: 
+* **Post-compilation transformation** that modifies the compiled bytecode or `*.js` files. 
+* **IR transformation** that is performed by the atomicfu compiler plugin.
+
+### Atomicfu compiler plugin
+
+Compiler plugin transformation is less fragile than transformation of the compiled sources 
+as it depends on the compiler IR tree.
+
+To turn on IR transformation set these properties in your `gradle.properties` file:
+
+<details open>
+<summary>For Kotlin >= 1.7.20</summary>
+
+```groovy
+kotlinx.atomicfu.enableJvmIrTransformation=true // for JVM IR transformation
+kotlinx.atomicfu.enableJsIrTransformation=true // for JS IR transformation
+```
+
+</details>
+
+<details>
+
+
+<summary> For Kotlin >= 1.6.20 and Kotlin < 1.7.20</summary>
+
+```groovy
+kotlinx.atomicfu.enableIrTransformation=true // only JS IR transformation is supported
+```
+
+</details>
+
+Also for JS backend make sure that `ir` or `both` compiler mode is set:
+
+```groovy
+kotlin.js.compiler=ir // or both
+```
+
+
+## Options for post-compilation transformation
+
+Some configuration options are available for _post-compilation transform tasks_ on JVM and JS.
+
+To set configuration options you should create `atomicfu` section in a `build.gradle` file, 
+like this:
 ```groovy
 atomicfu {
-    variant = "VH"
+  dependenciesVersion = '0.18.5'
 }
-``` 
- 
-It can also create [JEP 238](https://openjdk.java.net/jeps/238) multi-release jar file with both
-`AtomicXxxFieldUpdater` for JDK<=8 and `VarHandle` for for JDK9+ if you 
-set `variant` to `"BOTH"`.
+```
+
+### JVM options
+
+To turn off transformation for Kotlin/JVM set option `transformJvm` to `false`.
+
+Configuration option `jvmVariant` defines the Java class that replaces atomics during bytecode transformation.
+Here are the valid options:
+- `FU` – atomics are replaced with [AtomicXxxFieldUpdater](https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.html).
+- `VH` – atomics are replaced with [VarHandle](https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/VarHandle.html), 
+  this option is supported for JDK 9+.
+- `BOTH` – [multi-release jar file](https://openjdk.java.net/jeps/238) will be created with both `AtomicXxxFieldUpdater` for JDK <= 8 and `VarHandle` for JDK 9+.
+
+### JS options
+
+To turn off transformation for Kotlin/JS set option `transformJs` to `false`.
+
+Here are all available configuration options (with their defaults):
+```groovy
+atomicfu {
+  dependenciesVersion = '0.18.5' // set to null to turn-off auto dependencies
+  transformJvm = true // set to false to turn off JVM transformation
+  jvmVariant = "FU" // JVM transformation variant: FU,VH, or BOTH
+  transformJs = true // set to false to turn off JVM transformation
+}
+```
+
+## More features
+
+AtomicFU provides some additional features that you can use.
 
 ### Arrays of atomic values
 
@@ -297,46 +366,9 @@
 [jucl.ReentrantLock](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html)
 is used on JVM. On JVM it is a typealias to the later class, erased on JS.   
 
-Condition variables (`notify`/`wait` and `signal`/`await`) are not supported.
-
-### Testing lock-free data structures on JVM
-
-You can optionally test lock-freedomness of lock-free data structures using
-[`LockFreedomTestEnvironment`](atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/LockFreedomTestEnvironment.kt) class.
-See example in [`LockFreeQueueLFTest`](atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/LockFreeQueueLFTest.kt).
-Testing is performed by pausing one (random) thread before or after a random state-update operation and
-making sure that all other threads can still make progress.
-
-In order to make those test to actually perform lock-freedomness testing you need to configure an additional
-execution of tests with the original (non-transformed) classes for Maven:
-
-```xml
-<build>
-    <plugins>
-        <!-- additional test execution with surefire on non-transformed files -->
-        <plugin>
-            <artifactId>maven-surefire-plugin</artifactId>
-            <executions>
-                <execution>
-                    <id>lockfree-test</id>
-                    <phase>test</phase>
-                    <goals>
-                        <goal>test</goal>
-                    </goals>
-                    <configuration>
-                        <classesDirectory>${project.build.directory}/classes-pre-atomicfu</classesDirectory>
-                        <includes>
-                            <include>**/*LFTest.*</include>
-                        </includes>
-                    </configuration>
-                </execution>
-            </executions>
-        </plugin>
-    </plugins>
-</build>
-```
-
-For Gradle there is nothing else to add. Tests are always run using original (non-transformed) classes.
+> Note that package `kotlinx.atomicfu.locks` is experimental explicitly even while atomicfu is experimental itself,
+> meaning that no ABI guarantees are provided whatsoever. API from this package is not recommended to use in libraries
+> that other projects depend on.
 
 ### Tracing operations
 
@@ -369,3 +401,15 @@
 
 `trace` is only seen before transformation and completely erased after on Kotlin/JVM and Kotlin/JS.
 
+## Kotlin Native support
+
+Atomic references for Kotlin/Native are based on
+[FreezableAtomicReference](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-freezable-atomic-reference/-init-.html)
+and every reference that is stored to the previously
+[frozen](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/freeze.html)
+(shared with another thread) atomic is automatically frozen, too.
+
+Since Kotlin/Native does not generally provide binary compatibility between versions,
+you should use the same version of Kotlin compiler as was used to build AtomicFU.
+See [gradle.properties](gradle.properties) in AtomicFU project for its `kotlin_version`.
+
diff --git a/RELEASE.md b/RELEASE.md
index 6f5e71d..d603b5d 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -51,7 +51,7 @@
     * Create a release named `<version>`. 
     * Cut & paste lines from [`CHANGES.md`](CHANGES.md) into description.
    
-13. In [Sonatype](oss.sonatype.org/#stagingRepositories) admin interface:
+13. In [Nexus](https://oss.sonatype.org/#stagingRepositories) admin interface:
     * Close the repository and wait for it to verify.
     * Release it.
 
diff --git a/atomicfu-gradle-plugin/build.gradle b/atomicfu-gradle-plugin/build.gradle
index 28091b6..5312551 100644
--- a/atomicfu-gradle-plugin/build.gradle
+++ b/atomicfu-gradle-plugin/build.gradle
@@ -14,26 +14,41 @@
 // Gradle plugin must be compiled targeting the same Kotlin version as used by Gradle
 kotlin.sourceSets.all {
     languageSettings {
-        apiVersion = "1.3"
-        languageVersion = "1.3"
+        apiVersion = "1.4"
+        languageVersion = "1.4"
     }
 }
 
 dependencies {
-    compile gradleApi()
-    compile project(":atomicfu-transformer")
-    compile 'org.jetbrains.kotlin:kotlin-stdlib'
+    implementation(project(":atomicfu-transformer")) {
+        exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib'
+    }
 
+    compileOnly gradleApi()
+    compileOnly 'org.jetbrains.kotlin:kotlin-stdlib'
     compileOnly "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    // atomicfu compiler plugin dependency will be loaded to kotlinCompilerPluginClasspath
+    implementation "org.jetbrains.kotlin:atomicfu:$kotlin_version"
 
-    testCompile gradleTestKit()
-    testCompile 'org.jetbrains.kotlin:kotlin-test'
-    testCompile 'org.jetbrains.kotlin:kotlin-test-junit'
-    testCompile 'junit:junit:4.12'
+    testImplementation gradleTestKit()
+    testImplementation 'org.jetbrains.kotlin:kotlin-test'
+    testImplementation 'org.jetbrains.kotlin:kotlin-test-junit'
+    testImplementation 'junit:junit:4.12'
 }
 
 configurations {
-    testPluginClasspath
+    testPluginClasspath {
+        attributes {
+            attribute(
+                    Usage.USAGE_ATTRIBUTE,
+                    project.objects.named(Usage.class, Usage.JAVA_RUNTIME)
+            )
+            attribute(
+                    Category.CATEGORY_ATTRIBUTE,
+                    project.objects.named(Category.class, Category.LIBRARY)
+            )
+        }
+    }
 }
 
 dependencies {
diff --git a/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt b/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt
index bb619a5..b77e95b 100644
--- a/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt
+++ b/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt
@@ -14,22 +14,33 @@
 import org.gradle.api.tasks.testing.*
 import org.gradle.jvm.tasks.*
 import org.jetbrains.kotlin.gradle.dsl.*
+import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
 import org.jetbrains.kotlin.gradle.plugin.*
 import java.io.*
 import java.util.*
 import java.util.concurrent.*
+import org.jetbrains.kotlin.gradle.targets.js.*
+import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
+import org.jetbrains.kotlin.gradle.tasks.*
+import org.jetbrains.kotlinx.atomicfu.gradle.*
 
 private const val EXTENSION_NAME = "atomicfu"
 private const val ORIGINAL_DIR_NAME = "originalClassesDir"
 private const val COMPILE_ONLY_CONFIGURATION = "compileOnly"
 private const val IMPLEMENTATION_CONFIGURATION = "implementation"
 private const val TEST_IMPLEMENTATION_CONFIGURATION = "testImplementation"
+// If the project uses KGP <= 1.6.20, only JS IR compiler plugin is available, and it is turned on via setting this property.
+// The property is supported for backwards compatibility.
+private const val ENABLE_JS_IR_TRANSFORMATION_LEGACY = "kotlinx.atomicfu.enableIrTransformation"
+private const val ENABLE_JS_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJsIrTransformation"
+private const val ENABLE_JVM_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJvmIrTransformation"
 
 open class AtomicFUGradlePlugin : Plugin<Project> {
     override fun apply(project: Project) = project.run {
         val pluginVersion = rootProject.buildscript.configurations.findByName("classpath")
             ?.allDependencies?.find { it.name == "atomicfu-gradle-plugin" }?.version
         extensions.add(EXTENSION_NAME, AtomicFUPluginExtension(pluginVersion))
+        applyAtomicfuCompilerPlugin()
         configureDependencies()
         configureTasks()
     }
@@ -43,12 +54,13 @@
         )
         dependencies.add(TEST_IMPLEMENTATION_CONFIGURATION, getAtomicfuDependencyNotation(Platform.JVM, version))
     }
-    withPluginWhenEvaluatedDependencies("kotlin2js") { version ->
+    withPluginWhenEvaluatedDependencies("org.jetbrains.kotlin.js") { version ->
         dependencies.add(
             if (config.transformJs) COMPILE_ONLY_CONFIGURATION else IMPLEMENTATION_CONFIGURATION,
             getAtomicfuDependencyNotation(Platform.JS, version)
         )
         dependencies.add(TEST_IMPLEMENTATION_CONFIGURATION, getAtomicfuDependencyNotation(Platform.JS, version))
+        addCompilerPluginDependency()
     }
     withPluginWhenEvaluatedDependencies("kotlin-multiplatform") { version ->
         configureMultiplatformPluginDependencies(version)
@@ -59,7 +71,9 @@
     val config = config
     withPluginWhenEvaluated("kotlin") {
         if (config.transformJvm) {
-            configureTransformTasks("compileTestKotlin") { sourceSet, transformedDir, originalDir ->
+            // skip transformation task if ir transformation is enabled
+            if (rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)) return@withPluginWhenEvaluated
+            configureJvmTransformation("compileTestKotlin") { sourceSet, transformedDir, originalDir ->
                 createJvmTransformTask(sourceSet).configureJvmTask(
                     sourceSet.compileClasspath,
                     sourceSet.classesTaskName,
@@ -70,20 +84,88 @@
             }
         }
     }
-    withPluginWhenEvaluated("kotlin2js") {
-        if (config.transformJs) {
-            configureTransformTasks("compileTestKotlin2Js") { sourceSet, transformedDir, originalDir ->
-                createJsTransformTask(sourceSet).configureJsTask(
-                    sourceSet.classesTaskName,
-                    transformedDir,
-                    originalDir,
-                    config
-                )
+    withPluginWhenEvaluated("org.jetbrains.kotlin.js") {
+        if (config.transformJs) configureJsTransformation()
+    }
+    withPluginWhenEvaluated("kotlin-multiplatform") {
+        configureMultiplatformTransformation()
+    }
+}
+
+private data class KotlinVersion(val major: Int, val minor: Int, val patch: Int)
+
+private fun Project.getKotlinVersion(): KotlinVersion {
+    val kotlinVersion = getKotlinPluginVersion()
+    val (major, minor) = kotlinVersion
+        .split('.')
+        .take(2)
+        .map { it.toInt() }
+    val patch = kotlinVersion.substringAfterLast('.').substringBefore('-').toInt()
+    return KotlinVersion(major, minor, patch)
+}
+
+private fun KotlinVersion.atLeast(major: Int, minor: Int, patch: Int) =
+    this.major == major && (this.minor == minor && this.patch >= patch || this.minor > minor) || this.major > major
+
+// kotlinx-atomicfu compiler plugin is available for KGP >= 1.6.20
+private fun Project.isCompilerPluginAvailable() = getKotlinVersion().atLeast(1, 6, 20)
+
+private fun Project.applyAtomicfuCompilerPlugin() {
+    val kotlinVersion = getKotlinVersion()
+    // for KGP >= 1.7.20:
+    // compiler plugin for JS IR is applied via the property `kotlinx.atomicfu.enableJsIrTransformation`
+    // compiler plugin for JVM IR is applied via the property `kotlinx.atomicfu.enableJvmIrTransformation`
+    if (kotlinVersion.atLeast(1, 7, 20)) {
+        plugins.apply(AtomicfuKotlinGradleSubplugin::class.java)
+        extensions.getByType(AtomicfuKotlinGradleSubplugin.AtomicfuKotlinGradleExtension::class.java).apply {
+            isJsIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION)
+            isJvmIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)
+        }
+    } else {
+        // for KGP >= 1.6.20 && KGP <= 1.7.20:
+        // compiler plugin for JS IR is applied via the property `kotlinx.atomicfu.enableIrTransformation`
+        // compiler plugin for JVM IR is not supported yet
+        if (kotlinVersion.atLeast(1, 6, 20)) {
+            if (rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION_LEGACY)) {
+                plugins.apply(AtomicfuKotlinGradleSubplugin::class.java)
             }
         }
     }
-    withPluginWhenEvaluated("kotlin-multiplatform") {
-        configureMultiplatformPluginTasks()
+}
+
+private fun Project.getBooleanProperty(name: String) =
+    rootProject.findProperty(name)?.toString()?.toBooleanStrict() ?: false
+
+private fun String.toBooleanStrict(): Boolean = when (this) {
+    "true" -> true
+    "false" -> false
+    else -> throw IllegalArgumentException("The string doesn't represent a boolean value: $this")
+}
+
+private fun Project.needsJsIrTransformation(target: KotlinTarget): Boolean =
+    (rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION) || rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION_LEGACY))
+            && target.isJsIrTarget()
+
+private fun KotlinTarget.isJsIrTarget() = (this is KotlinJsTarget && this.irTarget != null) || this is KotlinJsIrTarget
+
+private fun Project.addCompilerPluginDependency() {
+    if (isCompilerPluginAvailable()) {
+        withKotlinTargets { target ->
+            if (needsJsIrTransformation(target)) {
+                target.compilations.forEach { kotlinCompilation ->
+                    kotlinCompilation.dependencies {
+                        if (getKotlinVersion().atLeast(1, 7, 10)) {
+                            // since Kotlin 1.7.10 we can add `atomicfu-runtime` dependency directly
+                            implementation("org.jetbrains.kotlin:kotlinx-atomicfu-runtime:${getKotlinPluginVersion()}")
+                        } else {
+                            // add atomicfu compiler plugin dependency
+                            // to provide the `atomicfu-runtime` library used during compiler plugin transformation
+                            implementation("org.jetbrains.kotlin:atomicfu:${getKotlinPluginVersion()}")
+                        }
+                    }
+                }
+            }
+        }
     }
 }
 
@@ -135,88 +217,112 @@
 }
 
 fun Project.withKotlinTargets(fn: (KotlinTarget) -> Unit) {
-    extensions.findByType(KotlinProjectExtension::class.java)?.let { kotlinExtension ->
-        val targetsExtension = (kotlinExtension as? ExtensionAware)?.extensions?.findByName("targets")
-        @Suppress("UNCHECKED_CAST")
-        val targets = targetsExtension as NamedDomainObjectContainer<KotlinTarget>
+    extensions.findByType(KotlinTargetsContainer::class.java)?.let { kotlinExtension ->
         // find all compilations given sourceSet belongs to
-        targets.all { target -> fn(target) }
+        kotlinExtension.targets
+            .all { target -> fn(target) }
     }
 }
 
-private fun KotlinCommonOptions.addFriendPaths(friendPathsFileCollection: FileCollection) {
-    val argName = when (this) {
-        is KotlinJvmOptions -> "-Xfriend-paths"
-        is KotlinJsOptions -> "-Xfriend-modules"
-        else -> return
+private fun KotlinCompile<*>.setFriendPaths(friendPathsFileCollection: FileCollection) {
+    val (majorVersion, minorVersion) = project.getKotlinPluginVersion()
+        .split('.')
+        .take(2)
+        .map { it.toInt() }
+    if (majorVersion == 1 && minorVersion < 7) {
+        (this as? AbstractKotlinCompile<*>)?.friendPaths?.from(friendPathsFileCollection)
+    } else {
+        // See KT-KT-54167 (works only for KGP 1.7.0+)
+        (this as BaseKotlinCompile).friendPaths.from(friendPathsFileCollection)
     }
-    freeCompilerArgs = freeCompilerArgs + "$argName=${friendPathsFileCollection.joinToString(",")}"
 }
 
-fun Project.configureMultiplatformPluginTasks() {
-    val originalDirsByCompilation = hashMapOf<KotlinCompilation<*>, FileCollection>()
-    val config = config
+fun Project.configureJsTransformation() =
+    configureTransformationForTarget((kotlinExtension as KotlinJsProjectExtension).js())
+
+fun Project.configureMultiplatformTransformation() =
     withKotlinTargets { target ->
         if (target.platformType == KotlinPlatformType.common || target.platformType == KotlinPlatformType.native) {
             return@withKotlinTargets // skip the common & native targets -- no transformation for them
         }
-        target.compilations.all compilations@{ compilation ->
-            val compilationType = compilation.name.compilationNameToType()
-                ?: return@compilations // skip unknown compilations
-            val classesDirs = compilation.output.classesDirs
-            // make copy of original classes directory
-            val originalClassesDirs: FileCollection =
-                project.files(classesDirs.from.toTypedArray()).filter { it.exists() }
-            originalDirsByCompilation[compilation] = originalClassesDirs
-            val transformedClassesDir =
-                project.buildDir.resolve("classes/atomicfu/${target.name}/${compilation.name}")
-            val transformTask = when (target.platformType) {
-                KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> {
-                    if (!config.transformJvm) return@compilations // skip when transformation is turned off
-                    project.createJvmTransformTask(compilation).configureJvmTask(
-                        compilation.compileDependencyFiles,
-                        compilation.compileAllTaskName,
-                        transformedClassesDir,
-                        originalClassesDirs,
-                        config
-                    )
-                }
-                KotlinPlatformType.js -> {
-                    if (!config.transformJs) return@compilations // skip when transformation is turned off
-                    project.createJsTransformTask(compilation).configureJsTask(
-                        compilation.compileAllTaskName,
-                        transformedClassesDir,
-                        originalClassesDirs,
-                        config
-                    )
-                }
-                else -> error("Unsupported transformation platform '${target.platformType}'")
-            }
-            //now transformTask is responsible for compiling this source set into the classes directory
-            classesDirs.setFrom(transformedClassesDir)
-            classesDirs.builtBy(transformTask)
-            (tasks.findByName(target.artifactsTaskName) as? Jar)?.apply {
-                setupJarManifest(multiRelease = config.variant.toVariant() == Variant.BOTH)
-            }
-            // test should compile and run against original production binaries
-            if (compilationType == CompilationType.TEST) {
-                val mainCompilation =
-                    compilation.target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME)
-                val originalMainClassesDirs = project.files(
-                    // use Callable because there is no guarantee that main is configured before test
-                    Callable { originalDirsByCompilation[mainCompilation]!! }
-                )
+        configureTransformationForTarget(target)
+    }
 
+private fun Project.configureTransformationForTarget(target: KotlinTarget) {
+    val originalDirsByCompilation = hashMapOf<KotlinCompilation<*>, FileCollection>()
+    val config = config
+    target.compilations.all compilations@{ compilation ->
+        val compilationType = compilation.name.compilationNameToType()
+            ?: return@compilations // skip unknown compilations
+        val classesDirs = compilation.output.classesDirs
+        // make copy of original classes directory
+        val originalClassesDirs: FileCollection =
+            project.files(classesDirs.from.toTypedArray()).filter { it.exists() }
+        originalDirsByCompilation[compilation] = originalClassesDirs
+        val transformedClassesDir =
+            project.buildDir.resolve("classes/atomicfu/${target.name}/${compilation.name}")
+        val transformTask = when (target.platformType) {
+            KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> {
+                // skip transformation task if transformation is turned off or ir transformation is enabled
+                if (!config.transformJvm || rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)) return@compilations
+                project.createJvmTransformTask(compilation).configureJvmTask(
+                    compilation.compileDependencyFiles,
+                    compilation.compileAllTaskName,
+                    transformedClassesDir,
+                    originalClassesDirs,
+                    config
+                )
+            }
+            KotlinPlatformType.js -> {
+                // skip when js transformation is not needed or when IR is transformed
+                if (!config.transformJs || (needsJsIrTransformation(target))) {
+                    return@compilations
+                }
+                project.createJsTransformTask(compilation).configureJsTask(
+                    compilation.compileAllTaskName,
+                    transformedClassesDir,
+                    originalClassesDirs,
+                    config
+                )
+            }
+            else -> error("Unsupported transformation platform '${target.platformType}'")
+        }
+        //now transformTask is responsible for compiling this source set into the classes directory
+        classesDirs.setFrom(transformedClassesDir)
+        classesDirs.builtBy(transformTask)
+        (tasks.findByName(target.artifactsTaskName) as? Jar)?.apply {
+            setupJarManifest(multiRelease = config.jvmVariant.toJvmVariant() == JvmVariant.BOTH)
+        }
+        // test should compile and run against original production binaries
+        if (compilationType == CompilationType.TEST) {
+            val mainCompilation =
+                compilation.target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME)
+            val originalMainClassesDirs = project.files(
+                // use Callable because there is no guarantee that main is configured before test
+                Callable { originalDirsByCompilation[mainCompilation]!! }
+            )
+
+            // KGP >= 1.7.0 has breaking changes in task hierarchy:
+            // https://youtrack.jetbrains.com/issue/KT-32805#focus=Comments-27-5915479.0-0
+            val (majorVersion, minorVersion) = getKotlinPluginVersion()
+                .split('.')
+                .take(2)
+                .map { it.toInt() }
+            if (majorVersion == 1 && minorVersion < 7) {
                 (tasks.findByName(compilation.compileKotlinTaskName) as? AbstractCompile)?.classpath =
                     originalMainClassesDirs + compilation.compileDependencyFiles - mainCompilation.output.classesDirs
-
-                (tasks.findByName("${target.name}${compilation.name.capitalize()}") as? Test)?.classpath =
-                    originalMainClassesDirs + (compilation as KotlinCompilationToRunnableFiles).runtimeDependencyFiles - mainCompilation.output.classesDirs
-
-                compilation.compileKotlinTask.doFirst {
-                    compilation.kotlinOptions.addFriendPaths(originalMainClassesDirs)
-                }
+            } else {
+                (tasks.findByName(compilation.compileKotlinTaskName) as? AbstractKotlinCompileTool<*>)
+                    ?.libraries
+                    ?.setFrom(
+                        originalMainClassesDirs + compilation.compileDependencyFiles - mainCompilation.output.classesDirs
+                    )
             }
+
+            (tasks.findByName("${target.name}${compilation.name.capitalize()}") as? Test)?.classpath =
+                originalMainClassesDirs + (compilation as KotlinCompilationToRunnableFiles).runtimeDependencyFiles - mainCompilation.output.classesDirs
+
+            compilation.compileKotlinTask.setFriendPaths(originalMainClassesDirs)
         }
     }
 }
@@ -234,15 +340,16 @@
 }
 
 fun Project.configureMultiplatformPluginDependencies(version: String) {
-    if (rootProject.findProperty("kotlin.mpp.enableGranularSourceSetsMetadata").toString().toBoolean()) {
+    if (rootProject.getBooleanProperty("kotlin.mpp.enableGranularSourceSetsMetadata")) {
+        addCompilerPluginDependency()
         val mainConfigurationName = project.extensions.getByType(KotlinMultiplatformExtension::class.java).sourceSets
-                .getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME)
-                .compileOnlyConfigurationName
+            .getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME)
+            .compileOnlyConfigurationName
         dependencies.add(mainConfigurationName, getAtomicfuDependencyNotation(Platform.MULTIPLATFORM, version))
 
         val testConfigurationName = project.extensions.getByType(KotlinMultiplatformExtension::class.java).sourceSets
-                .getByName(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME)
-                .implementationConfigurationName
+            .getByName(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME)
+            .implementationConfigurationName
         dependencies.add(testConfigurationName, getAtomicfuDependencyNotation(Platform.MULTIPLATFORM, version))
 
         // For each source set that is only used in Native compilations, add an implementation dependency so that it
@@ -258,20 +365,21 @@
         }
     } else {
         sourceSetsByCompilation().forEach { (sourceSet, compilations) ->
+            addCompilerPluginDependency()
             val platformTypes = compilations.map { it.platformType }.toSet()
             val compilationNames = compilations.map { it.compilationName }.toSet()
             if (compilationNames.size != 1)
                 error("Source set '${sourceSet.name}' of project '$name' is part of several compilations $compilationNames")
             val compilationType = compilationNames.single().compilationNameToType()
-                    ?: return@forEach // skip unknown compilations
+                ?: return@forEach // skip unknown compilations
             val platform =
-                    if (platformTypes.size > 1) Platform.MULTIPLATFORM else // mix of platform types -> "common"
-                        when (platformTypes.single()) {
-                            KotlinPlatformType.common -> Platform.MULTIPLATFORM
-                            KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> Platform.JVM
-                            KotlinPlatformType.js -> Platform.JS
-                            KotlinPlatformType.native -> Platform.NATIVE
-                        }
+                if (platformTypes.size > 1) Platform.MULTIPLATFORM else // mix of platform types -> "common"
+                    when (platformTypes.single()) {
+                        KotlinPlatformType.common -> Platform.MULTIPLATFORM
+                        KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> Platform.JVM
+                        KotlinPlatformType.js -> Platform.JS
+                        KotlinPlatformType.native, KotlinPlatformType.wasm -> Platform.NATIVE
+                    }
             val configurationName = when {
                 // impl dependency for native (there is no transformation)
                 platform == Platform.NATIVE -> sourceSet.implementationConfigurationName
@@ -285,7 +393,7 @@
     }
 }
 
-fun Project.configureTransformTasks(
+fun Project.configureJvmTransformation(
     testTaskName: String,
     createTransformTask: (sourceSet: SourceSet, transformedDir: File, originalDir: FileCollection) -> Task
 ) {
@@ -305,7 +413,7 @@
         //now transformTask is responsible for compiling this source set into the classes directory
         sourceSet.compiledBy(transformTask)
         (tasks.findByName(sourceSet.jarTaskName) as? Jar)?.apply {
-            setupJarManifest(multiRelease = config.variant.toVariant() == Variant.BOTH)
+            setupJarManifest(multiRelease = config.jvmVariant.toJvmVariant() == JvmVariant.BOTH)
         }
         // test should compile and run against original production binaries
         if (compilationType == CompilationType.TEST) {
@@ -319,9 +427,7 @@
                 classpath =
                     originalMainClassesDirs + sourceSet.compileClasspath - mainSourceSet.output.classesDirs
 
-                (this as? KotlinCompile<*>)?.doFirst {
-                    kotlinOptions.addFriendPaths(originalMainClassesDirs)
-                }
+                (this as? KotlinCompile<*>)?.setFriendPaths(originalMainClassesDirs)
             }
 
             // todo: fix test runtime classpath for JS?
@@ -331,7 +437,7 @@
     }
 }
 
-fun String.toVariant(): Variant = enumValueOf(toUpperCase(Locale.US))
+fun String.toJvmVariant(): JvmVariant = enumValueOf(toUpperCase(Locale.US))
 
 fun Project.createJvmTransformTask(compilation: KotlinCompilation<*>): AtomicFUTransformTask =
     tasks.create(
@@ -348,9 +454,6 @@
 fun Project.createJvmTransformTask(sourceSet: SourceSet): AtomicFUTransformTask =
     tasks.create(sourceSet.getTaskName("transform", "atomicfuClasses"), AtomicFUTransformTask::class.java)
 
-fun Project.createJsTransformTask(sourceSet: SourceSet): AtomicFUTransformJsTask =
-    tasks.create(sourceSet.getTaskName("transform", "atomicfuJsFiles"), AtomicFUTransformJsTask::class.java)
-
 fun AtomicFUTransformTask.configureJvmTask(
     classpath: FileCollection,
     classesTaskName: String,
@@ -363,7 +466,7 @@
         classPath = classpath
         inputFiles = originalClassesDir
         outputDir = transformedClassesDir
-        variant = config.variant
+        jvmVariant = config.jvmVariant
         verbose = config.verbose
     }
 
@@ -395,7 +498,7 @@
     var dependenciesVersion = pluginVersion
     var transformJvm = true
     var transformJs = true
-    var variant: String = "FU"
+    var jvmVariant: String = "FU"
     var verbose: Boolean = false
 }
 
@@ -413,7 +516,8 @@
     lateinit var classPath: FileCollection
 
     @Input
-    var variant = "FU"
+    var jvmVariant = "FU"
+
     @Input
     var verbose = false
 
@@ -422,7 +526,7 @@
         val cp = classPath.files.map { it.absolutePath }
         inputFiles.files.forEach { inputDir ->
             AtomicFUTransformer(cp, inputDir, outputDir).let { t ->
-                t.variant = variant.toVariant()
+                t.jvmVariant = jvmVariant.toJvmVariant()
                 t.verbose = verbose
                 t.transform()
             }
@@ -438,6 +542,7 @@
 
     @OutputDirectory
     lateinit var outputDir: File
+
     @Input
     var verbose = false
 
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/BaseKotlinGradleTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/BaseKotlinGradleTest.kt
deleted file mode 100644
index b81a0c9..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/BaseKotlinGradleTest.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu.plugin.gradle
-
-import org.gradle.internal.impldep.com.google.common.io.Files
-import org.junit.After
-import org.junit.Before
-import java.io.File
-
-abstract class BaseKotlinGradleTest {
-    private lateinit var workingDir: File
-
-    fun project(name: String, suffix: String = "", fn: Project.() -> Unit) {
-        workingDir = File("build${File.separator}test-$name$suffix").absoluteFile
-        workingDir.deleteRecursively()
-        workingDir.mkdirs()
-        val testResources = File("src/test/resources")
-        val originalProjectDir = testResources.resolve("projects/$name").apply { checkExists() }
-        val projectDir = workingDir.resolve(name).apply { mkdirs() }
-        originalProjectDir.listFiles().forEach { it.copyRecursively(projectDir.resolve(it.name)) }
-
-        // Add an empty setting.gradle
-        projectDir.resolve("settings.gradle").writeText("// this file is intentionally left empty")
-
-        Project(projectDir = projectDir).fn()
-    }
-}
\ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/EmptyProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/EmptyProjectTest.kt
deleted file mode 100644
index b542f2b..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/EmptyProjectTest.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu.plugin.gradle
-
-import org.junit.Test
-
-class EmptyProjectTest : BaseKotlinGradleTest() {
-    @Test
-    fun testEmpty() = project("empty") {
-        build("build") {}
-    }
-}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JsProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JsProjectTest.kt
deleted file mode 100644
index 2201199..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JsProjectTest.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu.plugin.gradle
-
-import org.gradle.testkit.runner.TaskOutcome
-import org.junit.Test
-import java.io.File
-
-class JsProjectTest : BaseKotlinGradleTest() {
-    @Test
-    fun testKotlin2JsPlugin() = project("js-simple") {
-        val tasksToCheck = arrayOf(
-            ":compileKotlin2Js",
-            ":compileTestKotlin2Js",
-            ":transformAtomicfuJsFiles",
-            ":transformTestAtomicfuJsFiles"
-        )
-
-        build("build") {
-            checkOutcomes(TaskOutcome.SUCCESS, *tasksToCheck)
-
-            val testCompileClasspathFiles = projectDir.resolve("build/test_compile_classpath.txt")
-                .readLines().asSequence().flatMap { File(it).walk().filter(File::isFile) }.toHashSet()
-
-            projectDir.resolve("build/classes/kotlin/main/js-simple.js").let {
-                it.checkExists()
-                check(it in testCompileClasspathFiles) { "Original '$it' is missing from test compile classpath" }
-                // todo: check test runtime classpath when js test tasks are supported in plugin
-            }
-
-            projectDir.resolve("build/classes/atomicfu/main/js-simple.js").let {
-                it.checkExists()
-                check(it !in testCompileClasspathFiles) { "Transformed '$it' is present in test compile classpath" }
-            }
-        }
-
-        build("build") {
-            checkOutcomes(TaskOutcome.UP_TO_DATE, *tasksToCheck)
-        }
-    }
-}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JvmProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JvmProjectTest.kt
deleted file mode 100644
index e017f05..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JvmProjectTest.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu.plugin.gradle
-
-import org.gradle.testkit.runner.TaskOutcome
-import org.junit.Test
-import java.io.File
-
-class JvmProjectTest : BaseKotlinGradleTest() {
-    @Test
-    fun testKotlinPlugin() =
-        project("jvm-simple") {
-            doSimpleTest()
-        }
-
-    @Test
-    fun testKotlinPlatformJvmPlugin() =
-        project("jvm-simple", "-platform") {
-            projectDir.resolve("build.gradle").modify {
-                it.checkedReplace("apply plugin: 'kotlin'", "apply plugin: 'kotlin-platform-jvm'")
-            }
-            doSimpleTest()
-        }
-
-    private fun Project.doSimpleTest() {
-        val tasksToCheck = arrayOf(
-            ":compileKotlin",
-            ":compileTestKotlin",
-            ":transformAtomicfuClasses",
-            ":transformTestAtomicfuClasses"
-        )
-
-        build("build") {
-            checkOutcomes(TaskOutcome.SUCCESS, *tasksToCheck)
-
-            val testCompileClasspathFiles = filesFrom("build/test_compile_classpath.txt")
-            val testRuntimeClasspathFiles = filesFrom("build/test_runtime_classpath.txt")
-
-            projectDir.resolve("build/classes/kotlin/main/IntArithmetic.class").let {
-                it.checkExists()
-                check(it in testCompileClasspathFiles) { "Original '$it' is missing from test compile classpath" }
-                check(it in testRuntimeClasspathFiles) { "Original '$it' is missing from test runtime classpath" }
-            }
-
-            projectDir.resolve("build/classes/atomicfu/main/IntArithmetic.class").let {
-                it.checkExists()
-                check(it !in testCompileClasspathFiles) { "Transformed '$it' is present in test compile classpath" }
-                check(it !in testRuntimeClasspathFiles) { "Transformed '$it' is present in test runtime classpath" }
-            }
-        }
-
-        build("build") {
-            checkOutcomes(TaskOutcome.UP_TO_DATE, *tasksToCheck)
-        }
-    }
-
-    private fun Project.filesFrom(name: String) = projectDir.resolve(name)
-        .readLines().asSequence().flatMap { listFiles(it) }.toHashSet()
-
-    private fun listFiles(dir: String): Sequence<File> = File(dir).walk().filter { it.isFile }
-}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/MppProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/MppProjectTest.kt
deleted file mode 100644
index 6dd7fa1..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/MppProjectTest.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu.plugin.gradle
-
-import org.gradle.testkit.runner.TaskOutcome
-import org.junit.Test
-import java.io.File
-
-class MppProjectTest : BaseKotlinGradleTest() {
-    @Test
-    fun testKotlinMultiplatformPlugin() = project("mpp-simple") {
-        val tasksToCheck = arrayOf(
-            ":compileKotlinJvm",
-            ":compileTestKotlinJvm",
-            ":transformJvmMainAtomicfu",
-            ":transformJvmTestAtomicfu",
-            ":compileKotlinJs",
-            ":transformJsMainAtomicfu"
-        )
-
-        build("build") {
-            checkOutcomes(TaskOutcome.SUCCESS, *tasksToCheck)
-
-            fun checkPlatform(platform: String, fileInMainName: String) {
-                val isJs = platform == "js"
-                val testCompileClasspathFiles = projectDir.resolve("build/classpath/$platform/test_compile.txt")
-                    .readLines().asSequence().flatMapTo(HashSet()) { File(it).walk().filter(File::isFile) }
-                val testRuntimeClasspathFiles = if (isJs) emptySet<File>() else projectDir.resolve("build/classpath/$platform/test_runtime.txt")
-                    .readLines().asSequence().flatMapTo(HashSet()) { File(it).walk().filter(File::isFile) }
-
-                projectDir.resolve("build/classes/kotlin/$platform/main/$fileInMainName").let {
-                    it.checkExists()
-                    check(it in testCompileClasspathFiles) { "Original '$it' is missing from $platform test compile classpath" }
-                    if (!isJs) check(it in testRuntimeClasspathFiles) { "Original '$it' is missing from $platform test runtime classpath" }
-                }
-
-                projectDir.resolve("build/classes/atomicfu/jvm/main/IntArithmetic.class").let {
-                    it.checkExists()
-                    check(it !in testCompileClasspathFiles) { "Transformed '$it' is present in $platform test compile classpath" }
-                    if (!isJs) check(it !in testRuntimeClasspathFiles) { "Transformed '$it' is present in $platform test runtime classpath" }
-                }
-
-            }
-
-            checkPlatform("jvm", "IntArithmetic.class")
-            checkPlatform("js", "mpp-simple.js")
-        }
-
-        build("build") {
-            checkOutcomes(TaskOutcome.UP_TO_DATE, *tasksToCheck)
-        }
-    }
-}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/Project.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/Project.kt
deleted file mode 100644
index f3f49e1..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/Project.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu.plugin.gradle
-
-import org.gradle.testkit.runner.BuildResult
-import org.gradle.testkit.runner.GradleRunner
-import java.io.File
-
-class Project(val projectDir: File) {
-    init {
-        projectDir.resolve("build.gradle").modify {
-            buildScript + "\n\n" + it
-        }
-    }
-
-    private var isDebug = false
-    private var printStdout = false
-
-    @Deprecated("Should be used for debug only!")
-    @Suppress("unused")
-    fun debug() {
-        isDebug = true
-    }
-
-    /**
-     * Redirects Gradle runner output to stdout. Useful for debugging.
-     */
-    @Deprecated("Should be used for debug only!")
-    @Suppress("unused")
-    fun printStdout() {
-        printStdout = true
-    }
-
-    fun gradle(vararg tasks: String): GradleRunner =
-            GradleRunner.create()
-                .withDebug(isDebug)
-                .withProjectDir(projectDir)
-                .withArguments(*(defaultArguments() + tasks))
-                .run {
-                    if (printStdout) {
-                        forwardStdOutput(System.out.bufferedWriter())
-                    } else {
-                        this
-                    }
-                }
-
-    fun build(vararg tasks: String, fn: BuildResult.() -> Unit = {}) {
-        val gradle = gradle(*tasks)
-        val buildResult = gradle.build()
-        buildResult.fn()
-    }
-
-    @Suppress("unused")
-    fun buildAndFail(vararg tasks: String, fn: BuildResult.() -> Unit = {}) {
-        val gradle = gradle(*tasks)
-        val buildResult = gradle.buildAndFail()
-        buildResult.fn()
-    }
-
-    private fun defaultArguments(): Array<String> =
-        arrayOf("--stacktrace")
-
-    companion object {
-        private fun readFileList(fileName: String): String {
-            val resource = Project::class.java.classLoader.getResource(fileName)
-                    ?: throw IllegalStateException("Could not find resource '$fileName'")
-            val files = File(resource.toURI())
-                    .readLines()
-                    .map { File(it).absolutePath.replace("\\", "\\\\") } // escape backslashes in Windows paths
-            return files.joinToString(", ") { "'$it'" }
-        }
-
-        private val buildScript = run {
-            """
-                buildscript {
-                    dependencies {
-                        classpath files(${readFileList("plugin-classpath.txt")})
-                    }
-                }
-
-                repositories {
-                    jcenter()
-                    mavenCentral()
-                    maven { url 'https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev' }
-                    maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
-                    maven { url 'https://dl.bintray.com/kotlin/kotlin-dev' }
-                    maven { url 'https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev' }
-                    maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
-                }
-
-                def atomicfuJvm = files(${readFileList("atomicfu-jvm.txt")})
-                def atomicfuJs = files(${readFileList("atomicfu-js.txt")})
-                def atomicfuMetadata = files(${readFileList("atomicfu-metadata.txt")})
-            """.trimIndent()
-        }
-    }
-}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/Assert.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/Assert.kt
new file mode 100644
index 0000000..f55e38a
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/Assert.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o.
+ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
+ */
+
+package kotlinx.atomicfu.plugin.gradle.internal
+
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.TaskOutcome
+import kotlin.test.assertEquals
+
+/**
+ * Helper `fun` for asserting a [TaskOutcome] to be equal to [TaskOutcome.SUCCESS]
+ */
+internal fun BuildResult.assertTaskSuccess(task: String) {
+    assertTaskOutcome(TaskOutcome.SUCCESS, task)
+}
+
+/**
+ * Helper `fun` for asserting a [TaskOutcome] to be equal to [TaskOutcome.FAILED]
+ */
+internal fun BuildResult.assertTaskFailure(task: String) {
+    assertTaskOutcome(TaskOutcome.FAILED, task)
+}
+
+internal fun BuildResult.assertTaskUpToDate(task: String) {
+    assertTaskOutcome(TaskOutcome.UP_TO_DATE, task)
+}
+
+private fun BuildResult.assertTaskOutcome(taskOutcome: TaskOutcome, taskName: String) {
+    assertEquals(taskOutcome, task(taskName)?.outcome)
+}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/TestDsl.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/TestDsl.kt
new file mode 100644
index 0000000..2541b41
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/TestDsl.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o.
+ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
+ */
+
+package kotlinx.atomicfu.plugin.gradle.internal
+
+import kotlinx.atomicfu.plugin.gradle.test.*
+import org.gradle.testkit.runner.*
+import java.io.*
+
+internal fun BaseKotlinGradleTest.test(fn: BaseKotlinScope.() -> Unit): GradleRunner {
+    val baseKotlinScope = BaseKotlinScope()
+    fn(baseKotlinScope)
+
+    baseKotlinScope.files.forEach { scope ->
+        val fileWriteTo = rootProjectDir.resolve(scope.filePath)
+            .apply {
+                parentFile.mkdirs()
+                createNewFile()
+            }
+
+        scope.files.forEach {
+            val fileContent = readFileList(it)
+            fileWriteTo.appendText(fileContent)
+        }
+    }
+
+    return GradleRunner.create()
+        .withProjectDir(rootProjectDir)
+        .withArguments(baseKotlinScope.runner.arguments)
+        .withPluginClasspath()
+        .addPluginTestRuntimeClasspath()
+}
+
+/**
+ * same as [file][FileContainer.file], but prepends "src/${sourceSet}/kotlin" before given `classFileName`
+ */
+internal fun FileContainer.kotlin(classFileName: String, sourceSet: String = "main", fn: AppendableScope.() -> Unit) {
+    require(classFileName.endsWith(".kt")) {
+        "ClassFileName must end with '.kt'"
+    }
+
+    val fileName = "src/${sourceSet}/kotlin/$classFileName"
+    file(fileName, fn)
+}
+
+/**
+ * Shortcut for creating a `build.gradle.kts` by using [file][FileContainer.file]
+ */
+internal fun FileContainer.buildGradleKts(fn: AppendableScope.() -> Unit) {
+    val fileName = "build.gradle.kts"
+    file(fileName, fn)
+}
+
+/**
+ * Shortcut for creating a `settings.gradle.kts` by using [file][FileContainer.file]
+ */
+internal fun FileContainer.settingsGradleKts(fn: AppendableScope.() -> Unit) {
+    val fileName = "settings.gradle.kts"
+    file(fileName, fn)
+}
+
+/**
+ * Shortcut for creating a `gradle.properties` by using [file][FileContainer.file]
+ */
+internal fun FileContainer.gradleProperties(fn: AppendableScope.() -> Unit) {
+    val fileName = "gradle.properties"
+    file(fileName, fn)
+}
+
+/**
+ * Declares a directory with the given [dirName] inside the current container.
+ * All calls creating files within this scope will create the files nested in this directory.
+ *
+ * Note that it is valid to call this method multiple times at the same level with the same [dirName].
+ * Files declared within 2 independent calls to [dir] will be added to the same directory.
+ */
+internal fun FileContainer.dir(dirName: String, fn: DirectoryScope.() -> Unit) {
+    DirectoryScope(dirName, this).fn()
+}
+
+internal fun BaseKotlinScope.runner(fn: Runner.() -> Unit) {
+    val runner = Runner()
+    fn(runner)
+
+    this.runner = runner
+}
+
+internal fun AppendableScope.resolve(fileName: String) {
+    this.files.add(fileName)
+}
+
+internal interface FileContainer {
+    fun file(fileName: String, fn: AppendableScope.() -> Unit)
+}
+
+internal class BaseKotlinScope : FileContainer {
+    var files: MutableList<AppendableScope> = mutableListOf()
+    var runner: Runner = Runner()
+
+    override fun file(fileName: String, fn: AppendableScope.() -> Unit) {
+        val appendableScope = AppendableScope(fileName)
+        fn(appendableScope)
+        files.add(appendableScope)
+    }
+}
+
+internal class DirectoryScope(
+    val dirPath: String,
+    val parent: FileContainer
+): FileContainer {
+
+    override fun file(fileName: String, fn: AppendableScope.() -> Unit) {
+        parent.file("$dirPath/$fileName", fn)
+    }
+}
+
+internal class AppendableScope(val filePath: String) {
+    val files: MutableList<String> = mutableListOf()
+}
+
+internal class Runner {
+    val arguments: MutableList<String> = mutableListOf()
+}
+
+internal fun readFileList(fileName: String): String =
+    getFile(fileName).readText()
+
+internal fun getFile(fileName: String): File {
+    val resource = BaseKotlinGradleTest::class.java.classLoader.getResource(fileName)
+        ?: throw IllegalStateException("Could not find resource '$fileName'")
+    return File(resource.toURI())
+}
+
+internal fun GradleRunner.addPluginTestRuntimeClasspath() = apply {
+    val pluginClasspath = getFile("plugin-classpath.txt").readLines().map { File(it) }
+    withPluginClasspath(pluginClasspath)
+}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/utils.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/utils.kt
new file mode 100644
index 0000000..49856f2
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/utils.kt
@@ -0,0 +1,13 @@
+package kotlinx.atomicfu.plugin.gradle.internal
+
+import java.io.*
+import kotlin.test.*
+
+fun File.checkExists() {
+    assertTrue(exists(), "File does not exist: $canonicalPath")
+}
+
+fun File.filesFrom(relative: String) = resolve(relative)
+    .readLines().asSequence().flatMap { listFiles(it) }.toHashSet()
+
+fun listFiles(dir: String): Sequence<File> = File(dir).walk().filter { it.isFile }
\ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/BaseKotlinGradleTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/BaseKotlinGradleTest.kt
new file mode 100644
index 0000000..e9ff7bb
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/BaseKotlinGradleTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.atomicfu.plugin.gradle.test
+
+import kotlinx.atomicfu.plugin.gradle.internal.*
+import org.objectweb.asm.*
+import java.io.File
+import kotlin.test.*
+
+abstract class BaseKotlinGradleTest(private val projectName: String) {
+    internal val rootProjectDir: File
+    private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray()
+    private val KOTLIN_METADATA_DESC = "Lkotlin/Metadata;"
+
+    init {
+        rootProjectDir = File("build${File.separator}test-$projectName").absoluteFile
+        rootProjectDir.deleteRecursively()
+        rootProjectDir.mkdirs()
+    }
+
+    internal abstract fun BaseKotlinScope.createProject()
+
+    val runner = test {
+        createProject()
+        runner {
+            arguments.add(":build")
+        }
+    }
+
+    fun checkTaskOutcomes(executedTasks: List<String>, excludedTasks: List<String>) {
+        runner.build().apply {
+            val tasks = tasks.map { it.path }
+            excludedTasks.forEach {
+                check(it !in tasks) { "Post-compilation transformation task $it was added in the compiler plugin mode" }
+            }
+            executedTasks.forEach {
+                assertTaskSuccess(it)
+            }
+        }
+        // check that task outcomes are cached for the second build
+        runner.build().apply {
+            executedTasks.forEach {
+                assertTaskUpToDate(it)
+            }
+        }
+    }
+
+    fun checkJvmCompilationClasspath(originalClassFile: String, transformedClassFile: String) {
+        // check that test compile and runtime classpath does not contain original non-transformed classes
+        val testCompileClasspathFiles = rootProjectDir.filesFrom("build/test_compile_jvm_classpath.txt")
+        val testRuntimeClasspathFiles = rootProjectDir.filesFrom("build/test_runtime_jvm_classpath.txt")
+
+        rootProjectDir.resolve(transformedClassFile).let {
+            it.checkExists()
+            check(it in testCompileClasspathFiles) { "Transformed '$it' is missing from test compile classpath" }
+            check(it in testRuntimeClasspathFiles) { "Transformed '$it' is missing from test runtime classpath" }
+        }
+
+        rootProjectDir.resolve(originalClassFile).let {
+            it.checkExists()
+            check(it !in testCompileClasspathFiles) { "Original '$it' is present in test compile classpath" }
+            check(it !in testRuntimeClasspathFiles) { "Original '$it' is present in test runtime classpath" }
+        }
+    }
+
+    fun checkJsCompilationClasspath() {
+        // check that test compilation depends on transformed main sources
+        val testCompileClasspathFiles = rootProjectDir.filesFrom("build/test_compile_js_classpath.txt")
+
+        rootProjectDir.resolve("build/classes/atomicfu/js/main/$projectName.js").let {
+            it.checkExists()
+            check(it in testCompileClasspathFiles) { "Transformed '$it' is missing from test compile classpath" }
+        }
+    }
+
+    fun checkBytecode(classFilePath: String) {
+        rootProjectDir.resolve(classFilePath).let {
+            it.checkExists()
+            assertFalse(it.readBytes().findAtomicfuRef(), "Found 'Lkotlinx/atomicfu/' reference in $it" )
+        }
+    }
+
+    private fun ByteArray.findAtomicfuRef(): Boolean {
+        val bytes = this.eraseMetadata()
+        loop@for (i in 0 until bytes.size - ATOMIC_FU_REF.size) {
+            for (j in 0 until ATOMIC_FU_REF.size) {
+                if (bytes[i + j] != ATOMIC_FU_REF[j]) continue@loop
+            }
+            return true
+        }
+        return false
+    }
+
+    // The atomicfu compiler plugin does not remove atomic properties from metadata,
+    // so for now we check that there are no ATOMIC_FU_REF left in the class bytecode excluding metadata.
+    // This may be reverted after the fix in the compiler plugin transformer (See #254).
+    private fun ByteArray.eraseMetadata(): ByteArray {
+        val cw = ClassWriter(ClassWriter.COMPUTE_MAXS or ClassWriter.COMPUTE_FRAMES)
+        ClassReader(this).accept(object : ClassVisitor(Opcodes.ASM9, cw) {
+            override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
+                return if (descriptor == KOTLIN_METADATA_DESC) null else super.visitAnnotation(descriptor, visible)
+            }
+        }, ClassReader.SKIP_FRAMES)
+        return cw.toByteArray()
+    }
+}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JsProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JsProjectTest.kt
new file mode 100644
index 0000000..0b34955
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JsProjectTest.kt
@@ -0,0 +1,49 @@
+package kotlinx.atomicfu.plugin.gradle.test
+
+import kotlinx.atomicfu.plugin.gradle.internal.*
+import kotlinx.atomicfu.plugin.gradle.internal.BaseKotlinScope
+import org.junit.Test
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the JS project:
+ * - post-compilation js transformation tasks are created
+ *   (legacy transformation is tested here, compiler plugin is not applied).
+ * - original non-transformed classes are not left in compile/runtime classpath.
+ */
+class JsLegacyTransformationTest : BaseKotlinGradleTest("js-simple") {
+
+    override fun BaseKotlinScope.createProject() {
+        buildGradleKts {
+            resolve("projects/js-simple/js-simple.gradle.kts")
+        }
+        settingsGradleKts {
+            resolve("projects/js-simple/settings.gradle.kts")
+        }
+        dir("src/main/kotlin") {}
+        kotlin("IntArithmetic.kt", "main") {
+            resolve("projects/js-simple/src/main/kotlin/IntArithmetic.kt")
+        }
+        dir("src/test/kotlin") {}
+        kotlin("ArithmeticTest.kt", "test") {
+            resolve("projects/js-simple/src/test/kotlin/ArithmeticTest.kt")
+        }
+    }
+
+    @Test
+    fun testPluginApplication() =
+        checkTaskOutcomes(
+            executedTasks = listOf(
+                ":compileKotlinJs",
+                ":transformJsMainAtomicfu",
+                ":compileTestKotlinJs",
+                ":transformJsTestAtomicfu"
+            ),
+            excludedTasks = emptyList()
+        )
+
+    @Test
+    fun testClasspath() {
+        runner.build()
+        checkJsCompilationClasspath()
+    }
+}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JvmProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JvmProjectTest.kt
new file mode 100644
index 0000000..2545e0e
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JvmProjectTest.kt
@@ -0,0 +1,108 @@
+package kotlinx.atomicfu.plugin.gradle.test
+
+import kotlinx.atomicfu.plugin.gradle.internal.*
+import kotlinx.atomicfu.plugin.gradle.internal.BaseKotlinScope
+import org.junit.Test
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the JVM project:
+ * - post-compilation bytecode transformation tasks are created
+ *   (legacy transformation is tested here, compiler plugin is not applied).
+ * - original non-transformed classes are not left in compile/runtime classpath.
+ * - no `kotlinx/atomicfu` references are left in the transformed bytecode.
+ */
+class JvmLegacyTransformationTest : BaseKotlinGradleTest("jvm-simple") {
+
+    override fun BaseKotlinScope.createProject() {
+        buildGradleKts {
+            resolve("projects/jvm-simple/jvm-simple.gradle.kts")
+        }
+        settingsGradleKts {
+            resolve("projects/jvm-simple/settings.gradle.kts")
+        }
+        dir("src/main/kotlin") {}
+        kotlin("IntArithmetic.kt", "main") {
+            resolve("projects/jvm-simple/src/main/kotlin/IntArithmetic.kt")
+        }
+        dir("src/test/kotlin") {}
+        kotlin("ArithmeticTest.kt", "test") {
+            resolve("projects/jvm-simple/src/test/kotlin/ArithmeticTest.kt")
+        }
+    }
+
+    @Test
+    fun testPluginApplication() =
+        checkTaskOutcomes(
+            executedTasks = listOf(
+                ":compileKotlin",
+                ":transformAtomicfuClasses",
+                ":compileTestKotlin",
+                ":transformTestAtomicfuClasses"
+            ),
+            excludedTasks = emptyList()
+        )
+
+    @Test
+    fun testClasspath() {
+        runner.build()
+        checkJvmCompilationClasspath(
+            originalClassFile = "build/classes/kotlin/main/IntArithmetic.class",
+            transformedClassFile = "build/classes/atomicfu/main/IntArithmetic.class"
+        )
+    }
+
+    @Test
+    fun testAtomicfuReferences() {
+        runner.build()
+        checkBytecode("build/classes/atomicfu/main/IntArithmetic.class")
+    }
+}
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the JVM project,
+ * - JVM IR compiler plugin transformation (kotlinx.atomicfu.enableJvmIrTransformation=true)
+ * - no post-compilation bytecode transforming tasks created
+ * - no `kotlinx/atomicfu` references are left in the resulting bytecode after IR transformation.
+ */
+class JvmIrTransformationTest : BaseKotlinGradleTest("jvm-simple") {
+
+    override fun BaseKotlinScope.createProject() {
+        buildGradleKts {
+            resolve("projects/jvm-simple/jvm-simple.gradle.kts")
+        }
+        settingsGradleKts {
+            resolve("projects/jvm-simple/settings.gradle.kts")
+        }
+        // set kotlinx.atomicfu.enableJvmIrTransformation=true to apply compiler plugin
+        gradleProperties {
+            resolve("projects/jvm-simple/gradle.properties")
+        }
+        dir("src/main/kotlin") {}
+        kotlin("IntArithmetic.kt", "main") {
+            resolve("projects/jvm-simple/src/main/kotlin/IntArithmetic.kt")
+        }
+        dir("src/test/kotlin") {}
+        kotlin("ArithmeticTest.kt", "test") {
+            resolve("projects/jvm-simple/src/test/kotlin/ArithmeticTest.kt")
+        }
+    }
+
+    @Test
+    fun testPluginApplication() =
+        checkTaskOutcomes(
+            executedTasks = listOf(
+                ":compileKotlin",
+                ":compileTestKotlin"
+            ),
+            excludedTasks = listOf(
+                ":transformAtomicfuClasses",
+                ":transformTestAtomicfuClasses"
+            )
+        )
+
+    @Test
+    fun testAtomicfuReferences() {
+        runner.build()
+        checkBytecode("build/classes/kotlin/main/IntArithmetic.class")
+    }
+}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/MppProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/MppProjectTest.kt
new file mode 100644
index 0000000..e95b091
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/MppProjectTest.kt
@@ -0,0 +1,219 @@
+package kotlinx.atomicfu.plugin.gradle.test
+
+import kotlinx.atomicfu.plugin.gradle.internal.*
+import org.junit.*
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the MPP project:
+ * - post-compilation bytecode transformation tasks are created
+ *   (legacy transformation is tested here, compiler plugin is not applied).
+ * - original non-transformed classes are not left in compile/runtime classpath.
+ * - no `kotlinx/atomicfu` references are left in the transformed bytecode.
+ */
+class MppLegacyTransformationTest : BaseKotlinGradleTest("mpp-simple") {
+
+    override fun BaseKotlinScope.createProject() {
+        buildGradleKts {
+            resolve("projects/mpp-simple/mpp-simple.gradle.kts")
+        }
+        settingsGradleKts {
+            resolve("projects/mpp-simple/settings.gradle.kts")
+        }
+        dir("src/commonMain/kotlin") {}
+        kotlin("IntArithmetic.kt", "commonMain") {
+            resolve("projects/mpp-simple/src/commonMain/kotlin/IntArithmetic.kt")
+        }
+        dir("src/commonTest/kotlin") {}
+        kotlin("ArithmeticTest.kt", "commonTest") {
+            resolve("projects/mpp-simple/src/commonTest/kotlin/ArithmeticTest.kt")
+        }
+    }
+
+    @Test
+    fun testPluginApplication() =
+        checkTaskOutcomes(
+            executedTasks = listOf(
+                ":compileKotlinJvm",
+                ":compileTestKotlinJvm",
+                ":transformJvmMainAtomicfu",
+                ":transformJvmTestAtomicfu",
+                ":compileKotlinJs",
+                ":transformJsMainAtomicfu"
+            ),
+            excludedTasks = emptyList()
+        )
+
+    @Test
+    fun testClasspath() {
+        runner.build()
+        checkJvmCompilationClasspath(
+            originalClassFile = "build/classes/kotlin/jvm/main/IntArithmetic.class",
+            transformedClassFile = "build/classes/atomicfu/jvm/main/IntArithmetic.class"
+        )
+        checkJsCompilationClasspath()
+    }
+
+    @Test
+    fun testAtomicfuReferences() {
+        runner.build()
+        checkBytecode("build/classes/atomicfu/jvm/main/IntArithmetic.class")
+    }
+}
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the MPP project,
+ * - JVM IR compiler plugin transformation (kotlinx.atomicfu.enableJvmIrTransformation=true)
+ * - no post-compilation bytecode transformation tasks are created
+ * - post-compilation js file transformation task created (as only JVM IR transformation applied, js is transformed in legacy mode)
+ * - no `kotlinx/atomicfu` references are left in the transformed bytecode.
+ */
+class MppJvmIrTransformationTest : BaseKotlinGradleTest("mpp-simple") {
+
+    override fun BaseKotlinScope.createProject() {
+        buildGradleKts {
+            resolve("projects/mpp-simple/mpp-simple.gradle.kts")
+        }
+        settingsGradleKts {
+            resolve("projects/mpp-simple/settings.gradle.kts")
+        }
+        gradleProperties {
+            resolve("projects/mpp-simple/gradle.properties_jvm")
+        }
+        dir("src/commonMain/kotlin") {}
+        kotlin("IntArithmetic.kt", "commonMain") {
+            resolve("projects/mpp-simple/src/commonMain/kotlin/IntArithmetic.kt")
+        }
+        dir("src/commonTest/kotlin") {}
+        kotlin("ArithmeticTest.kt", "commonTest") {
+            resolve("projects/mpp-simple/src/commonTest/kotlin/ArithmeticTest.kt")
+        }
+    }
+
+    @Test
+    fun testPluginApplication() =
+        checkTaskOutcomes(
+            executedTasks = listOf(
+                ":compileKotlinJvm",
+                ":compileTestKotlinJvm",
+                ":compileKotlinJs",
+                // legacy JS transformation
+                ":transformJsMainAtomicfu",
+                ":transformJsTestAtomicfu"
+            ),
+            excludedTasks = listOf(
+                ":transformJvmMainAtomicfu",
+                ":transformJvmTestAtomicfu"
+            )
+        )
+
+    @Test
+    fun testAtomicfuReferences() {
+        runner.build()
+        checkBytecode("build/classes/kotlin/jvm/main/IntArithmetic.class")
+    }
+}
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the MPP project,
+ * - JS IR compiler plugin transformation (kotlinx.atomicfu.enableJsIrTransformation=true)
+ * - post-compilation bytecode transformation tasks are created (only JS IR transformation is applied, jvm is transformed in legacy mode)
+ * - no post-compilation js file transformation tasks are created
+ * - no `kotlinx/atomicfu` references are left in the transformed bytecode.
+ */
+class MppJsIrTransformationTest : BaseKotlinGradleTest("mpp-simple") {
+
+    override fun BaseKotlinScope.createProject() {
+        buildGradleKts {
+            resolve("projects/mpp-simple/mpp-simple.gradle.kts")
+        }
+        settingsGradleKts {
+            resolve("projects/mpp-simple/settings.gradle.kts")
+        }
+        gradleProperties {
+            resolve("projects/mpp-simple/gradle.properties_js")
+        }
+        dir("src/commonMain/kotlin") {}
+        kotlin("IntArithmetic.kt", "commonMain") {
+            resolve("projects/mpp-simple/src/commonMain/kotlin/IntArithmetic.kt")
+        }
+        dir("src/commonTest/kotlin") {}
+        kotlin("ArithmeticTest.kt", "commonTest") {
+            resolve("projects/mpp-simple/src/commonTest/kotlin/ArithmeticTest.kt")
+        }
+    }
+
+    @Test
+    fun testPluginApplication() =
+        checkTaskOutcomes(
+            executedTasks = listOf(
+                ":compileKotlinJvm",
+                ":compileTestKotlinJvm",
+                ":compileKotlinJs",
+                // legacy JVM transformation
+                ":transformJvmMainAtomicfu",
+                ":transformJvmTestAtomicfu"
+            ),
+            excludedTasks = listOf(
+                ":transformJsMainAtomicfu",
+                ":transformJsTestAtomicfu"
+            )
+        )
+
+    @Test
+    fun testAtomicfuReferences() {
+        runner.build()
+        checkBytecode("build/classes/atomicfu/jvm/main/IntArithmetic.class")
+    }
+}
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the MPP project,
+ * - JS IR and JVM IR compiler plugin transformation
+ * - no post-compilation bytecode transformation tasks are created (only JS IR transformation is applied, jvm is transformed in legacy mode)
+ * - no post-compilation js file transformation tasks are created
+ * - no `kotlinx/atomicfu` references are left in the transformed bytecode.
+ */
+class MppBothIrTransformationTest : BaseKotlinGradleTest("mpp-simple") {
+
+    override fun BaseKotlinScope.createProject() {
+        buildGradleKts {
+            resolve("projects/mpp-simple/mpp-simple.gradle.kts")
+        }
+        settingsGradleKts {
+            resolve("projects/mpp-simple/settings.gradle.kts")
+        }
+        gradleProperties {
+            resolve("projects/mpp-simple/gradle.properties_both")
+        }
+        dir("src/commonMain/kotlin") {}
+        kotlin("IntArithmetic.kt", "commonMain") {
+            resolve("projects/mpp-simple/src/commonMain/kotlin/IntArithmetic.kt")
+        }
+        dir("src/commonTest/kotlin") {}
+        kotlin("ArithmeticTest.kt", "commonTest") {
+            resolve("projects/mpp-simple/src/commonTest/kotlin/ArithmeticTest.kt")
+        }
+    }
+
+    @Test
+    fun testPluginApplication() =
+        checkTaskOutcomes(
+            executedTasks = listOf(
+                ":compileKotlinJvm",
+                ":compileTestKotlinJvm",
+                ":compileKotlinJs"
+            ),
+            excludedTasks = listOf(
+                ":transformJvmMainAtomicfu",
+                ":transformJvmTestAtomicfu",
+                ":transformJsMainAtomicfu",
+                ":transformJsTestAtomicfu"
+            )
+        )
+
+    @Test
+    fun testAtomicfuReferences() {
+        runner.build()
+        checkBytecode("build/classes/kotlin/jvm/main/IntArithmetic.class")
+    }
+}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/utils.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/utils.kt
deleted file mode 100644
index 2a8d0f7..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/utils.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu.plugin.gradle
-
-import org.gradle.testkit.runner.BuildResult
-import org.gradle.testkit.runner.TaskOutcome
-import java.io.File
-import kotlin.test.assertTrue
-
-fun BuildResult.checkOutcomes(expected: TaskOutcome, vararg tasks: String) {
-    val unexpectedOutcomes = tasks
-            .map { it to task(it)?.outcome }
-            .filter { (_, outcome) -> outcome != expected }
-    if (unexpectedOutcomes.isNotEmpty()) {
-        throw AssertionError("Unexpected outcomes for tasks." +
-                "\nExpected: $expected." +
-                "\nGot:" +
-                "\n${unexpectedOutcomes.joinToString("\n") { (task, outcome) -> "* $task -> $outcome" }}")
-
-    }
-}
-
-fun File.checkExists() {
-    assertTrue(exists(), "File does not exist: $canonicalPath")
-}
-
-fun File.modify(fn: (String) -> String) {
-    writeText(fn(readText()))
-}
-
-fun String.checkedReplace(oldValue: String, newValue: String, ignoreCase: Boolean = false): String {
-    check(contains(oldValue, ignoreCase)) { "String must contain '$oldValue'" }
-    return replace(oldValue, newValue, ignoreCase)
-}
\ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/empty/build.gradle b/atomicfu-gradle-plugin/src/test/resources/projects/empty/build.gradle
deleted file mode 100644
index f4add41..0000000
--- a/atomicfu-gradle-plugin/src/test/resources/projects/empty/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-apply plugin: 'kotlinx-atomicfu'
-apply plugin: 'base'
-
-repositories {
-    maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
-}
\ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/build.gradle b/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/build.gradle
deleted file mode 100644
index 31dbec6..0000000
--- a/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-apply plugin: 'kotlinx-atomicfu'
-apply plugin: 'kotlin2js'
-
-dependencies {
-    compileOnly atomicfuJs
-    testRuntime atomicfuJs
-
-    compile 'org.jetbrains.kotlin:kotlin-stdlib-js'
-    testCompile 'org.jetbrains.kotlin:kotlin-test-js'
-}
-
-compileTestKotlin2Js.doLast {
-    file("$buildDir/test_compile_classpath.txt").text = classpath.join("\n")
-}
\ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/js-simple.gradle.kts b/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/js-simple.gradle.kts
new file mode 100644
index 0000000..37a41e5
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/js-simple.gradle.kts
@@ -0,0 +1,38 @@
+import kotlinx.atomicfu.plugin.gradle.*
+
+buildscript {
+    dependencies {
+        classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.17.0")
+    }
+}
+
+plugins {
+    kotlin("js")
+}
+
+apply(plugin = "kotlinx-atomicfu")
+
+repositories {
+    mavenLocal()
+    mavenCentral()
+}
+
+dependencies {
+    implementation(kotlin("stdlib-js"))
+    implementation(kotlin("test-junit"))
+    implementation("org.jetbrains.kotlin:kotlin-test-js")
+}
+
+kotlin {
+    js {
+        nodejs()
+    }
+
+    tasks.named("compileTestKotlinJs") {
+        doLast {
+            file("$buildDir/test_compile_js_classpath.txt").writeText(
+                target.compilations["test"].compileDependencyFiles.joinToString("\n")
+            )
+        }
+    }
+}
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/settings.gradle.kts b/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/settings.gradle.kts
new file mode 100644
index 0000000..bd39e74
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "js-simple"
\ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/build.gradle b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/build.gradle
deleted file mode 100644
index 7e21215..0000000
--- a/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/build.gradle
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-apply plugin: 'kotlinx-atomicfu'
-apply plugin: 'kotlin'
-
-// This flag is enabled to be able using JVM IR compiled dependencies (when build is ran with -Penable_jvm_ir)
-kotlin.target.compilations.all {
-    kotlinOptions.freeCompilerArgs += '-Xallow-jvm-ir-dependencies'
-}
-
-dependencies {
-    compileOnly atomicfuJvm
-    testRuntime atomicfuJvm
-
-    compile 'org.jetbrains.kotlin:kotlin-stdlib'
-
-    testCompile 'org.jetbrains.kotlin:kotlin-test'
-    testCompile 'org.jetbrains.kotlin:kotlin-test-junit'
-    testCompile 'junit:junit:4.12'
-}
-
-compileTestKotlin.doLast {
-    file("$buildDir/test_compile_classpath.txt").text = classpath.join("\n")
-}
-
-test.doLast {
-    file("$buildDir/test_runtime_classpath.txt").text = classpath.join("\n")
-}
\ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/gradle.properties b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/gradle.properties
new file mode 100644
index 0000000..fa37a2c
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/gradle.properties
@@ -0,0 +1 @@
+kotlinx.atomicfu.enableJvmIrTransformation=true
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/jvm-simple.gradle.kts b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/jvm-simple.gradle.kts
new file mode 100644
index 0000000..db644ef
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/jvm-simple.gradle.kts
@@ -0,0 +1,41 @@
+import org.gradle.api.tasks.compile.*
+import org.jetbrains.kotlin.gradle.plugin.*
+
+buildscript {
+    dependencies {
+        classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.17.0")
+    }
+}
+
+plugins {
+    kotlin("jvm")
+}
+
+apply(plugin = "kotlinx-atomicfu")
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    implementation(kotlin("stdlib"))
+    implementation(kotlin("test-junit"))
+}
+
+kotlin {
+    tasks.compileTestKotlin {
+        doLast {
+            file("$buildDir/test_compile_jvm_classpath.txt").writeText(
+                target.compilations["test"].compileDependencyFiles.joinToString("\n")
+            )
+        }
+    }
+
+    tasks.test {
+        doLast {
+            file("$buildDir/test_runtime_jvm_classpath.txt").writeText(
+                (target.compilations["test"] as KotlinCompilationToRunnableFiles<*>).runtimeDependencyFiles.joinToString("\n")
+            )
+        }
+    }
+}
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/settings.gradle.kts b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/settings.gradle.kts
new file mode 100644
index 0000000..2f5327f
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "jvm-simple"
\ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/build.gradle b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/build.gradle
deleted file mode 100644
index fc95366..0000000
--- a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/build.gradle
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-apply plugin: 'kotlinx-atomicfu'
-apply plugin: 'kotlin-multiplatform'
-
-kotlin {
-    // This flag is enabled to be able using JVM IR compiled dependencies (when build is ran with -Penable_jvm_ir)
-    jvm() {
-        compilations.all {
-            kotlinOptions.freeCompilerArgs += '-Xallow-jvm-ir-dependencies'
-        }
-    }
-    js()
-
-    sourceSets {
-        commonMain.dependencies {
-            implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
-            compileOnly atomicfuMetadata
-
-        }
-        commonTest.dependencies {
-            implementation 'org.jetbrains.kotlin:kotlin-test-common'
-            implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common'
-            runtimeOnly atomicfuMetadata
-
-        }
-        jsMain.dependencies {
-            implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
-            compileOnly atomicfuJs
-
-        }
-        jsTest.dependencies {
-            implementation 'org.jetbrains.kotlin:kotlin-test-js'
-            runtimeOnly atomicfuJs
-        }
-        jvmMain.dependencies {
-            implementation 'org.jetbrains.kotlin:kotlin-stdlib'
-            compileOnly atomicfuJvm
-        }
-        jvmTest.dependencies {
-            implementation 'org.jetbrains.kotlin:kotlin-test'
-            implementation 'org.jetbrains.kotlin:kotlin-test-junit'
-            implementation "junit:junit:4.12"
-            runtimeOnly atomicfuJvm
-        }
-    }
-}
-
-def File classpathFile(String platform, String fileName) {
-    def dir = file("$buildDir/classpath/$platform")
-    dir.mkdirs()
-    return file("$dir/$fileName")
-}
-
-
-compileTestKotlinJvm.doLast {
-    classpathFile("jvm", "test_compile.txt").text = classpath.files.join("\n")
-}
-
-jvmTest.doLast {
-    classpathFile("jvm", "test_runtime.txt").text = classpath.files.join("\n")
-}
-
-
-compileTestKotlinJs.doLast {
-    classpathFile("js", "test_compile.txt").text = classpath.files.join("\n")
-}
-
-jsTest.dependsOn(":compileTestKotlinJs")
-jsTest.dependsOn(":transformJsTestAtomicfu")
-check.dependsOn(":jsTest")
\ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_both b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_both
new file mode 100644
index 0000000..5d28ccd
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_both
@@ -0,0 +1,3 @@
+kotlin.js.compiler=ir
+kotlinx.atomicfu.enableJvmIrTransformation=true
+kotlinx.atomicfu.enableJsIrTransformation=true
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_js b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_js
new file mode 100644
index 0000000..b57875b
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_js
@@ -0,0 +1,2 @@
+kotlin.js.compiler=ir
+kotlinx.atomicfu.enableJsIrTransformation=true
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_jvm b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_jvm
new file mode 100644
index 0000000..fa37a2c
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_jvm
@@ -0,0 +1 @@
+kotlinx.atomicfu.enableJvmIrTransformation=true
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/mpp-simple.gradle.kts b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/mpp-simple.gradle.kts
new file mode 100644
index 0000000..ed15d3d
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/mpp-simple.gradle.kts
@@ -0,0 +1,90 @@
+import org.jetbrains.kotlin.gradle.plugin.*
+
+buildscript {
+    dependencies {
+        classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.17.0")
+    }
+}
+
+plugins {
+    kotlin("multiplatform")
+}
+
+apply(plugin = "kotlinx-atomicfu")
+
+repositories {
+    mavenCentral()
+}
+
+kotlin {
+    targets {
+        jvm {
+            compilations.all {
+                kotlinOptions.jvmTarget = "1.8"
+            }
+            testRuns["test"].executionTask.configure {
+                useJUnit()
+            }
+        }
+        js {
+            nodejs()
+        }
+    }
+    sourceSets {
+        val commonMain by getting {
+            dependencies {
+                implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
+            }
+        }
+        val commonTest by getting {
+            dependencies {
+                implementation(kotlin("test-common"))
+                implementation(kotlin("test-annotations-common"))
+            }
+        }
+        val jvmMain by getting {
+            dependencies {
+                implementation(kotlin("stdlib"))
+            }
+        }
+        val jvmTest by getting {
+            dependencies {
+                implementation(kotlin("test-junit"))
+            }
+        }
+        val jsMain by getting {
+            dependencies {
+                implementation("org.jetbrains.kotlin:kotlin-stdlib-js")
+            }
+        }
+        val jsTest by getting {
+            dependencies {
+                implementation("org.jetbrains.kotlin:kotlin-test-js")
+            }
+        }
+    }
+
+    tasks.named("compileTestKotlinJvm") {
+        doLast {
+            file("$buildDir/test_compile_jvm_classpath.txt").writeText(
+                targets["jvm"].compilations["test"].compileDependencyFiles.joinToString("\n")
+            )
+        }
+    }
+
+    tasks.named("jvmTest") {
+        doLast {
+            file("$buildDir/test_runtime_jvm_classpath.txt").writeText(
+                (targets["jvm"].compilations["test"] as KotlinCompilationToRunnableFiles).runtimeDependencyFiles.joinToString("\n")
+            )
+        }
+    }
+
+    tasks.named("compileTestKotlinJs") {
+        doLast {
+            file("$buildDir/test_compile_js_classpath.txt").writeText(
+                targets["js"].compilations["test"].compileDependencyFiles.joinToString("\n")
+            )
+        }
+    }
+}
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/settings.gradle.kts b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/settings.gradle.kts
new file mode 100644
index 0000000..5a4e5ab
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "mpp-simple"
\ No newline at end of file
diff --git a/atomicfu-maven-plugin/build.gradle b/atomicfu-maven-plugin/build.gradle
index 8929be9..a165769 100644
--- a/atomicfu-maven-plugin/build.gradle
+++ b/atomicfu-maven-plugin/build.gradle
@@ -18,7 +18,7 @@
 }
 
 def pomFile = file("$buildDir/pom.xml")
-def outputDir = compileKotlin.destinationDir
+def outputDir = compileKotlin.destinationDirectory
 def buildSnapshots = rootProject.properties['build_snapshot_train'] != null
 
 evaluationDependsOn(':atomicfu-transformer')
@@ -37,32 +37,17 @@
                 asNode().with {
                     appendNode('build').with {
                         appendNode('directory', buildDir)
-                        appendNode('outputDirectory', outputDir)
+                        appendNode('outputDirectory', outputDir.get().getAsFile())
                     }
                     appendNode('properties').with {
                         appendNode('project.build.sourceEncoding', 'UTF-8')
                     }
                     appendNode('repositories').with {
                         appendNode('repository').with {
-                            appendNode('id', 'kotlin-eap')
-                            appendNode('url', 'https://kotlin.bintray.com/kotlin-eap')
-                        }
-
-                        appendNode('repository').with {
-                            appendNode('id', 'kotlin-dev')
-                            appendNode('url', 'https://kotlin.bintray.com/kotlin-dev')
-                        }
-
-                        appendNode('repository').with {
                             appendNode('id', 'dev')
                             appendNode('url', 'https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev')
                         }
 
-                        appendNode('repository').with {
-                            appendNode('id', 'kotlinx')
-                            appendNode('url', 'https://kotlin.bintray.com/kotlinx')
-                        }
-
                         if (buildSnapshots) {
                             appendNode('repository').with {
                                 appendNode('id', 'kotlin-snapshots')
@@ -84,7 +69,7 @@
 
 // runs the plugin description generator
 task generatePluginDescriptor(type: Exec, dependsOn: generatePomFile) {
-    def pluginDescriptorFile = new File(outputDir, 'META-INF/maven/plugin.xml')
+    def pluginDescriptorFile = outputDir.file('META-INF/maven/plugin.xml')
 
     workingDir projectDir
     boolean isWindows = System.getProperty("os.name").toLowerCase().indexOf("windows") >= 0
@@ -100,8 +85,9 @@
     ])
     commandLine args
     doLast {
-        assert pluginDescriptorFile.file, "$pluginDescriptorFile: was not generated"
-        logger.info("Plugin descriptor is generated in $pluginDescriptorFile")
+        def descriptorFile = pluginDescriptorFile.get().getAsFile()
+        assert descriptorFile, "$descriptorFile: was not generated"
+        logger.info("Plugin descriptor is generated in $descriptorFile")
     }
 }
 
diff --git a/atomicfu-maven-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/TransformMojo.kt b/atomicfu-maven-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/TransformMojo.kt
index 28580d8..5f45f3e 100644
--- a/atomicfu-maven-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/TransformMojo.kt
+++ b/atomicfu-maven-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/TransformMojo.kt
@@ -17,7 +17,7 @@
 package kotlinx.atomicfu.plugin
 
 import kotlinx.atomicfu.transformer.AtomicFUTransformer
-import kotlinx.atomicfu.transformer.Variant
+import kotlinx.atomicfu.transformer.JvmVariant
 import org.apache.maven.plugin.AbstractMojo
 import org.apache.maven.plugins.annotations.LifecyclePhase
 import org.apache.maven.plugins.annotations.Mojo
@@ -50,8 +50,8 @@
     /**
      * Transformation variant: "FU", "VH", or "BOTH".
      */
-    @Parameter(defaultValue = "FU", property = "variant", required = true)
-    lateinit var variant: Variant
+    @Parameter(defaultValue = "FU", property = "jvmVariant", required = true)
+    lateinit var jvmVariant: JvmVariant
 
     /**
      * Verbose debug info.
@@ -60,7 +60,7 @@
     var verbose: Boolean = false
 
     override fun execute() {
-        val t = AtomicFUTransformer(classpath, input, output, variant)
+        val t = AtomicFUTransformer(classpath, input, output, jvmVariant)
         t.verbose = verbose
         t.transform()
     }
diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt
index ba08fbc..0687f38 100644
--- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt
+++ b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt
@@ -4,7 +4,9 @@
 
 package kotlinx.atomicfu.transformer
 
+import org.objectweb.asm.*
 import org.objectweb.asm.Opcodes.*
+import org.objectweb.asm.Type.*
 import org.objectweb.asm.tree.*
 import org.objectweb.asm.util.*
 
@@ -69,12 +71,23 @@
 fun AbstractInsnNode.isGetStatic(owner: String) =
     this is FieldInsnNode && this.opcode == GETSTATIC && this.owner == owner
 
+fun AbstractInsnNode.isGetFieldOrGetStatic() =
+    this is FieldInsnNode && (this.opcode == GETFIELD || this.opcode == GETSTATIC)
+
 fun AbstractInsnNode.isAreturn() =
     this.opcode == ARETURN
 
 fun AbstractInsnNode.isReturn() =
     this.opcode == RETURN
 
+fun AbstractInsnNode.isTypeReturn(type: Type) =
+    opcode == when (type) {
+        INT_TYPE -> IRETURN
+        LONG_TYPE -> LRETURN
+        BOOLEAN_TYPE -> IRETURN
+        else -> ARETURN
+    }
+
 fun AbstractInsnNode.isInvokeVirtual() =
         this.opcode == INVOKEVIRTUAL
 
diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
index c7c262c..a138422 100644
--- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
+++ b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
@@ -91,7 +91,6 @@
 private val TRACE_APPEND_4 = MethodId(TRACE_BASE_CLS, "append", getMethodDescriptor(VOID_TYPE, OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE), INVOKEVIRTUAL)
 private val TRACE_DEFAULT_ARGS = "I${OBJECT_TYPE.descriptor}"
 private const val DEFAULT = "\$default"
-private const val DELEGATE = "\$delegate"
 
 private val TRACE_FACTORY = MethodId(TRACE_KT, TRACE, "(IL$AFU_PKG/$TRACE_FORMAT;)L$AFU_PKG/$TRACE_BASE;", INVOKESTATIC)
 private val TRACE_PARTIAL_ARGS_FACTORY = MethodId(TRACE_KT, "$TRACE$DEFAULT", "(IL$AFU_PKG/$TRACE_FORMAT;$TRACE_DEFAULT_ARGS)L$AFU_PKG/$TRACE_BASE;", INVOKESTATIC)
@@ -126,7 +125,7 @@
 }
 
 private inline fun insns(block: InstructionAdapter.() -> Unit): InsnList {
-    val node = MethodNode(ASM5)
+    val node = MethodNode(ASM9)
     block(InstructionAdapter(node))
     return node.instructions
 }
@@ -169,13 +168,13 @@
     override fun toString(): String = "${owner.prettyStr()}::$name"
 }
 
-enum class Variant { FU, VH, BOTH }
+enum class JvmVariant { FU, VH, BOTH }
 
 class AtomicFUTransformer(
     classpath: List<String>,
     inputDir: File,
     outputDir: File = inputDir,
-    var variant: Variant = Variant.FU
+    var jvmVariant: JvmVariant = JvmVariant.FU
 ) : AtomicFUTransformerBase(inputDir, outputDir) {
 
     private val classPathLoader = URLClassLoader(
@@ -188,6 +187,7 @@
     private val traceFields = mutableSetOf<FieldId>()
     private val traceAccessors = mutableSetOf<MethodId>()
     private val fieldDelegates = mutableMapOf<FieldId, FieldInfo>()
+    private val delegatedPropertiesAccessors = mutableMapOf<FieldId, MethodId>()
     private val removeMethods = mutableSetOf<MethodId>()
 
     override fun transform() {
@@ -195,7 +195,7 @@
         val files = inputDir.walk().filter { it.isFile }.toList()
         val needTransform = analyzeFilesForFields(files)
         if (needTransform || outputDir == inputDir) {
-            val vh = variant == Variant.VH
+            val vh = jvmVariant == JvmVariant.VH
             // visit method bodies for external references to fields, runs all logic, fails if anything is wrong
             val needsTransform = analyzeFilesForRefs(files, vh)
             // perform transformation
@@ -205,7 +205,7 @@
                 val outBytes = if (file.isClassFile() && file in needsTransform) transformFile(file, bytes, vh) else bytes
                 val outFile = file.toOutputFile()
                 outFile.mkdirsAndWrite(outBytes)
-                if (variant == Variant.BOTH && outBytes !== bytes) {
+                if (jvmVariant == JvmVariant.BOTH && outBytes !== bytes) {
                     val vhBytes = transformFile(file, bytes, true)
                     val vhFile = outputDir / "META-INF" / "versions" / "9" / file.relativeTo(inputDir).toString()
                     vhFile.mkdirsAndWrite(vhBytes)
@@ -280,7 +280,7 @@
         return cw.toByteArray() // write transformed bytes
     }
 
-    private abstract inner class CV(cv: ClassVisitor?) : ClassVisitor(ASM5, cv) {
+    private abstract inner class CV(cv: ClassVisitor?) : ClassVisitor(ASM9, cv) {
         lateinit var className: String
 
         override fun visit(
@@ -341,6 +341,10 @@
                 // check for copying atomic values into delegate fields and register potential delegate fields
                 return DelegateFieldsCollectorMV(access, name, desc, signature, exceptions)
             }
+            // collect accessors of potential delegated properties
+            if (methodType.argumentTypes.isEmpty()) {
+                return DelegatedFieldAccessorCollectorMV(className, methodType.returnType, access, name, desc, signature, exceptions)
+            }
             return null
         }
     }
@@ -348,7 +352,7 @@
     private inner class AccessorCollectorMV(
         private val className: String,
         access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
-    ) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
+    ) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
         override fun visitEnd() {
             val insns = instructions.listUseful(4)
             if (insns.size == 3 &&
@@ -396,10 +400,47 @@
         }
     }
 
-    private inner class DelegateFieldsCollectorMV(
+    private inner class DelegatedFieldAccessorCollectorMV(
+            private val className: String, private val returnType: Type,
             access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
     ) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
         override fun visitEnd() {
+            // check for pattern of a delegated property getter
+            // getfield/getstatic a$delegate: Atomic*
+            // astore_i ...
+            // aload_i
+            // invokevirtual Atomic*.getValue()
+            // ireturn
+            var cur = instructions.first
+            while (cur != null && !(cur.isGetFieldOrGetStatic() && getType((cur as FieldInsnNode).desc) in AFU_TYPES)) {
+                cur = cur.next
+            }
+            if (cur != null && cur.next.opcode == ASTORE) {
+                val fi = cur as FieldInsnNode
+                val fieldDelegate = FieldId(className, fi.name, fi.desc)
+                val atomicType = getType(fi.desc)
+                val v = (cur.next as VarInsnNode).`var`
+                while (!(cur is VarInsnNode && cur.opcode == ALOAD && cur.`var` == v)) {
+                    cur = cur.next
+                }
+                val invokeVirtual = cur.next
+                if (invokeVirtual.opcode == INVOKEVIRTUAL && (invokeVirtual as MethodInsnNode).name == GET_VALUE && invokeVirtual.owner == atomicType.internalName) {
+                    // followed by RETURN operation
+                    val next = invokeVirtual.nextUseful
+                    val ret = if (next?.opcode == CHECKCAST) next.nextUseful else next
+                    if (ret != null && ret.isTypeReturn(returnType)) {
+                        // register delegated property accessor
+                        delegatedPropertiesAccessors[fieldDelegate] = MethodId(className, name, desc, accessToInvokeOpcode(access))
+                    }
+                }
+            }
+        }
+    }
+
+    private inner class DelegateFieldsCollectorMV(
+            access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
+    ) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
+        override fun visitEnd() {
             // register delegate field and the corresponding original atomic field
             // getfield a: *Atomic
             // putfield a$delegate: *Atomic
@@ -408,7 +449,7 @@
                     insn.checkGetFieldOrGetStatic()?.let { getfieldId ->
                         val next = insn.next
                         (next as? FieldInsnNode)?.checkPutFieldOrPutStatic()?.let { delegateFieldId ->
-                            if (delegateFieldId.name.endsWith(DELEGATE)) {
+                            if (getfieldId in fields && delegateFieldId in fields) {
                                 // original atomic value is copied to the synthetic delegate atomic field <delegated field name>$delegate
                                 val originalField = fields[getfieldId]!!
                                 fieldDelegates[delegateFieldId] = originalField
@@ -420,11 +461,12 @@
                     val methodId = MethodId(insn.owner, insn.name, insn.desc, insn.opcode)
                     if (methodId in FACTORIES) {
                         (insn.nextUseful as? FieldInsnNode)?.checkPutFieldOrPutStatic()?.let { delegateFieldId ->
-                            if (delegateFieldId.name.endsWith(DELEGATE)) {
+                            val fieldType = getType(insn.desc).returnType
+                            if (fieldType in AFU_TYPES) {
+                                val isStatic = insn.nextUseful!!.opcode == PUTSTATIC
                                 // delegate field is initialized by a factory invocation
-                                val fieldType = getType(insn.desc).returnType
                                 // for volatile delegated properties store FieldInfo of the delegate field itself
-                                fieldDelegates[delegateFieldId] = FieldInfo(delegateFieldId, fieldType)
+                                fieldDelegates[delegateFieldId] = FieldInfo(delegateFieldId, fieldType, isStatic)
                             }
                         }
                     }
@@ -447,6 +489,8 @@
         return if (fieldId in fields) fieldId else null
     }
 
+    private fun FieldId.isFieldDelegate() = this in fieldDelegates && delegatedPropertiesAccessors.contains(this)
+
     private inner class TransformerCV(
         cv: ClassVisitor?,
         private val vh: Boolean,
@@ -460,7 +504,7 @@
         private var originalClinit: MethodNode? = null
         private var newClinit: MethodNode? = null
 
-        private fun newClinit() = MethodNode(ASM5, ACC_STATIC, "<clinit>", "()V", null, null)
+        private fun newClinit() = MethodNode(ASM9, ACC_STATIC, "<clinit>", "()V", null, null)
         fun getOrCreateNewClinit(): MethodNode = newClinit ?: newClinit().also { newClinit = it }
 
         override fun visitSource(source: String?, debug: String?) {
@@ -478,8 +522,8 @@
             val fieldType = getType(desc)
             if (fieldType.sort == OBJECT && fieldType.internalName in AFU_CLASSES) {
                 val fieldId = FieldId(className, name, desc)
-                // skip delegate field
-                if (fieldId in fieldDelegates && (fieldId != fieldDelegates[fieldId]!!.fieldId)) {
+                // skip field delegates except volatile delegated properties (e.g. val a: Int by atomic(0))
+                if (fieldId.isFieldDelegate() && (fieldId != fieldDelegates[fieldId]!!.fieldId)) {
                     transformed = true
                     return null
                 }
@@ -607,7 +651,7 @@
             val superMV = if (name == "<clinit>" && desc == "()V") {
                 if (access and ACC_STATIC == 0) abort("<clinit> method not marked as static")
                 // defer writing class initialization method
-                val node = MethodNode(ASM5, access, name, desc, signature, exceptions)
+                val node = MethodNode(ASM9, access, name, desc, signature, exceptions)
                 if (originalClinit != null) abort("Multiple <clinit> methods found")
                 originalClinit = node
                 node
@@ -673,7 +717,7 @@
         private val packageName: String,
         private val vh: Boolean,
         private val analyzePhase2: Boolean // true in Phase 2 when we are analyzing file for refs (not transforming yet)
-    ) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
+    ) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
         init {
             this.mv = mv
         }
@@ -715,14 +759,19 @@
                     i = i.next
                     hasErrors = true
                 }
+            // make sure all kotlinx/atomicfu references removed
+            removeAtomicReferencesFromLVT()
             // save transformed method if not in analysis phase
             if (!hasErrors && !analyzePhase2)
                 accept(mv)
         }
 
+        private fun removeAtomicReferencesFromLVT() =
+            localVariables?.removeIf { getType(it.desc) in AFU_TYPES }
+
         private fun FieldInsnNode.checkCopyToDelegate(): AbstractInsnNode? {
             val fieldId = FieldId(owner, name, desc)
-            if (fieldId in fieldDelegates) {
+            if (fieldId.isFieldDelegate()) {
                 // original atomic value is copied to the synthetic delegate atomic field <delegated field name>$delegate
                 val originalField = fieldDelegates[fieldId]!!
                 val getField = previous as FieldInsnNode
@@ -748,51 +797,29 @@
             if (iv.name == GET_VALUE || iv.name == SET_VALUE) {
                 check(!f.isArray || onArrayElement) { "getValue/setValue can only be called on elements of arrays" }
                 val setInsn = iv.name == SET_VALUE
-                if (!onArrayElement) {
-                    val primitiveType = f.getPrimitiveType(vh)
-                    val owner = if (!vh && f.isStatic) f.refVolatileClassName else f.owner
-                    if (!vh && f.isStatic) {
-                        val getOwnerClass = FieldInsnNode(
-                            GETSTATIC,
-                            f.owner,
-                            f.staticRefVolatileField,
-                            getObjectType(owner).descriptor
-                        )
-                        instructions.insert(ld, getOwnerClass)
-                    }
-                    instructions.remove(ld) // drop getstatic (we don't need field updater)
-                    val j = FieldInsnNode(
-                        when {
-                            iv.name == GET_VALUE -> if (f.isStatic && vh) GETSTATIC else GETFIELD
-                            else -> if (f.isStatic && vh) PUTSTATIC else PUTFIELD
-                        }, owner, f.name, primitiveType.descriptor
-                    )
-                    instructions.set(iv, j) // replace invokevirtual with get/setfield
-                    return j.next
-                } else {
-                    var methodType = getMethodType(iv.desc)
-                    if (f.typeInfo.originalType != f.typeInfo.transformedType && !vh) {
-                        val ret = f.typeInfo.transformedType.elementType
-                        iv.desc = if (setInsn) getMethodDescriptor(methodType.returnType, ret) else getMethodDescriptor(ret, *methodType.argumentTypes)
-                        methodType = getMethodType(iv.desc)
-                    }
-                    iv.name = iv.name.substring(0, 3)
-                    if (!vh) {
-                        // map to j.u.c.a.Atomic*Array get or set
-                        iv.owner = descToName(f.fuType.descriptor)
-                        iv.desc = getMethodDescriptor(methodType.returnType, INT_TYPE, *methodType.argumentTypes)
-                    } else {
-                        // map to VarHandle get or set
-                        iv.owner = descToName(VH_TYPE.descriptor)
-                        iv.desc = getMethodDescriptor(
-                            methodType.returnType,
-                            f.getPrimitiveType(vh),
-                            INT_TYPE,
-                            *methodType.argumentTypes
-                        )
-                    }
-                    return iv
+                if (!onArrayElement) return getPureTypeField(ld, f, iv)
+                var methodType = getMethodType(iv.desc)
+                if (f.typeInfo.originalType != f.typeInfo.transformedType && !vh) {
+                    val ret = f.typeInfo.transformedType.elementType
+                    iv.desc = if (setInsn) getMethodDescriptor(methodType.returnType, ret) else getMethodDescriptor(ret, *methodType.argumentTypes)
+                    methodType = getMethodType(iv.desc)
                 }
+                iv.name = iv.name.substring(0, 3)
+                if (!vh) {
+                    // map to j.u.c.a.Atomic*Array get or set
+                    iv.owner = descToName(f.fuType.descriptor)
+                    iv.desc = getMethodDescriptor(methodType.returnType, INT_TYPE, *methodType.argumentTypes)
+                } else {
+                    // map to VarHandle get or set
+                    iv.owner = descToName(VH_TYPE.descriptor)
+                    iv.desc = getMethodDescriptor(
+                        methodType.returnType,
+                        f.getPrimitiveType(vh),
+                        INT_TYPE,
+                        *methodType.argumentTypes
+                    )
+                }
+                return iv
             }
             if (f.isArray && iv.name == GET_SIZE) {
                 if (!vh) {
@@ -855,6 +882,29 @@
             return iv.next
         }
 
+        private fun getPureTypeField(ld: FieldInsnNode, f: FieldInfo, iv: MethodInsnNode): AbstractInsnNode? {
+            val primitiveType = f.getPrimitiveType(vh)
+            val owner = if (!vh && f.isStatic) f.refVolatileClassName else f.owner
+            if (!vh && f.isStatic) {
+                val getOwnerClass = FieldInsnNode(
+                        GETSTATIC,
+                        f.owner,
+                        f.staticRefVolatileField,
+                        getObjectType(owner).descriptor
+                )
+                instructions.insert(ld, getOwnerClass)
+            }
+            instructions.remove(ld) // drop getfield/getstatic of the atomic field
+            val j = FieldInsnNode(
+                    when {
+                        iv.name == GET_VALUE -> if (f.isStatic && vh) GETSTATIC else GETFIELD
+                        else -> if (f.isStatic && vh) PUTSTATIC else PUTFIELD
+                    }, owner, f.name, primitiveType.descriptor
+            )
+            instructions.set(iv, j) // replace invokevirtual with get/setfield
+            return j.next
+        }
+
         private fun vhOperation(iv: MethodInsnNode, typeInfo: TypeInfo, f: FieldInfo) {
             val methodType = getMethodType(iv.desc)
             val args = methodType.argumentTypes
@@ -1305,7 +1355,7 @@
                 is FieldInsnNode -> {
                     val fieldId = FieldId(i.owner, i.name, i.desc)
                     if ((i.opcode == GETFIELD || i.opcode == GETSTATIC) && fieldId in fields) {
-                        if (fieldId in fieldDelegates && i.next.opcode == ASTORE) {
+                        if (fieldId.isFieldDelegate() && i.next.opcode == ASTORE) {
                             return transformDelegatedFieldAccessor(i, fieldId)
                         }
                         (i.next as? FieldInsnNode)?.checkCopyToDelegate()?.let { return it } // atomic field is copied to delegate field
@@ -1338,29 +1388,25 @@
             return i.next
         }
 
-        private fun transformDelegatedFieldAccessor(i: FieldInsnNode, fieldId: FieldId): AbstractInsnNode {
+        private fun transformDelegatedFieldAccessor(i: FieldInsnNode, fieldId: FieldId): AbstractInsnNode? {
             val f = fieldDelegates[fieldId]!!
-            val astore = (i.next as? VarInsnNode) ?: abort("Method $name does not match the pattern of a delegated field accessor")
-            val v = astore.`var`
-            var cur: AbstractInsnNode = i
+            val v = (i.next as VarInsnNode).`var`
+            // remove instructions [astore_v .. aload_v]
+            var cur: AbstractInsnNode = i.next
             while (!(cur is VarInsnNode && cur.opcode == ALOAD && cur.`var` == v)) {
                 val next = cur.next
                 instructions.remove(cur)
                 cur = next
             }
-            val invokeVirtual = FlowAnalyzer(cur.next).execute()
-            instructions.remove(cur)
-            check(invokeVirtual.isAtomicGetValueOrSetValue()) { "Aload of the field delegate $f should be followed with Atomic*.getValue()/setValue() invocation" }
-            val accessorName = (invokeVirtual as MethodInsnNode).name.substring(0, 3)
-            val isGetter = accessorName == "get"
-            val primitiveType = f.getPrimitiveType(vh)
-            val j = FieldInsnNode(if (isGetter) GETFIELD else PUTFIELD, f.owner, f.name, primitiveType.descriptor)
-            instructions.set(invokeVirtual, j)
+            val iv = FlowAnalyzer(cur.next).execute()
+            check(iv.isAtomicGetValueOrSetValue()) { "Aload of the field delegate $f should be followed with Atomic*.getValue()/setValue() invocation" }
+            val isGetter = (iv as MethodInsnNode).name == GET_VALUE
+            instructions.remove(cur) // remove aload_v
             localVariables.removeIf {
                 !(getType(it.desc).internalName == f.owner ||
                         (!isGetter && getType(it.desc) == getType(desc).argumentTypes.first() && it.name == "<set-?>"))
             }
-            return j.next
+            return getPureTypeField(i, f, iv)
         }
 
         private fun AbstractInsnNode.isAtomicGetFieldOrGetStatic() =
@@ -1466,7 +1512,7 @@
     }
     val t = AtomicFUTransformer(emptyList(), File(args[0]))
     if (args.size > 1) t.outputDir = File(args[1])
-    if (args.size > 2) t.variant = enumValueOf(args[2].toUpperCase(Locale.US))
+    if (args.size > 2) t.jvmVariant = enumValueOf(args[2].toUpperCase(Locale.US))
     t.verbose = true
     t.transform()
 }
diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt
index 91f3037..de8f94a 100644
--- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt
+++ b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt
@@ -52,8 +52,8 @@
     outputDir: File
 ) : AtomicFUTransformerBase(inputDir, outputDir) {
     private val atomicConstructors = mutableSetOf<String>()
-    private val fieldDelegates = mutableMapOf<String, String>()
-    private val delegatedProperties = mutableMapOf<String, String>()
+    private val delegateToOriginalAtomicField = mutableMapOf<String, Name>()
+    private val topLevelDelegatedFieldAccessorToOriginalField = mutableMapOf<String, Name>()
     private val atomicArrayConstructors = mutableMapOf<String, String?>()
     private val traceConstructors = mutableSetOf<String>()
     private val traceFormatObjects = mutableSetOf<String>()
@@ -81,6 +81,7 @@
         root.visit(AtomicConstructorDetector())
         root.visit(FieldDelegatesVisitor())
         root.visit(DelegatedPropertyAccessorsVisitor())
+        root.visit(TopLevelDelegatedFieldsAccessorVisitor())
         root.visit(TransformVisitor())
         root.visit(AtomicOperationsInliner())
         return root.eraseGetValue().toByteArray()
@@ -283,12 +284,17 @@
                             if (stmt is ExpressionStatement) {
                                 if (stmt.expression is Assignment) {
                                     val delegateAssignment = stmt.expression as Assignment
-                                    if (delegateAssignment.right is PropertyGet) {
-                                        val initializer = delegateAssignment.right as PropertyGet
-                                        if (initializer.toSource() == atomicField.toSource()) {
-                                            // register field delegate and the original atomic field
-                                            fieldDelegates[(delegateAssignment.left as PropertyGet).property.toSource()] =
-                                                    (atomicField as PropertyGet).property.toSource()
+                                    val initializer = delegateAssignment.right
+                                    if (initializer.toSource() == atomicField.toSource()) {
+                                        if (delegateAssignment.right is PropertyGet) { // initialization of a class field
+                                            // delegate${owner_class} to original atomic field
+                                            val delegateFieldName = (delegateAssignment.left as PropertyGet).property.toSource()
+                                            val ownerClassName = constructorBlock.enclosingFunction.functionName.identifier
+                                            delegateToOriginalAtomicField["$delegateFieldName\$$ownerClassName"] =
+                                                    (atomicField as PropertyGet).property
+                                        } else { // top-level delegated fields
+                                            val delegateFieldName = delegateAssignment.left.toSource()
+                                            delegateToOriginalAtomicField[delegateFieldName] = atomicField as Name
                                         }
                                     }
                                 }
@@ -303,22 +309,74 @@
 
     inner class DelegatedPropertyAccessorsVisitor : NodeVisitor {
         override fun visit(node: AstNode?): Boolean {
-            if (node is PropertyGet) {
-                if (node.target is PropertyGet) {
-                    if ((node.target as PropertyGet).property.toSource() in fieldDelegates && node.property.toSource() == MANGLED_VALUE_PROP) {
-                        if (node.parent is ReturnStatement) {
-                            val getter = ((((node.parent.parent as? Block)?.parent as? FunctionNode)?.parent as? ObjectProperty)?.parent as? ObjectLiteral)
-                                    ?: abort("Incorrect tree structure of the accessor for the property delegated " +
-                                            "to the atomic field ${fieldDelegates[node.target.toSource()]}")
-                            val definePropertyCall = getter.parent as FunctionCall
-                            val stringLiteral = definePropertyCall.arguments[1] as? StringLiteral
-                                    ?: abort ("Object.defineProperty invocation should take a property name as the second argument")
-                            val delegatedProperty = stringLiteral.value.toString()
-                            delegatedProperties[delegatedProperty] = (node.target as PropertyGet).property.toSource()
+            // find ObjectLiteral with accessors of the delegated field (get: FunctionNode, set: FunctionNode)
+            // redirect getter/setter from generated delegate field to the original atomic field
+            if (node is ObjectLiteral && node.parent is FunctionCall &&
+                    ((node.elements.size == 2 && node.elements[1].left.toSource() == "get") ||
+                            (node.elements.size == 3 && node.elements[1].left.toSource() == "get" && node.elements[2].left.toSource() == "set"))) {
+                // check that these are accessors of the atomic delegate field (check only getter)
+                if (node.elements[1].right is FunctionNode) {
+                    val getter = node.elements[1].right as FunctionNode
+                    if (getter.body.hasChildren() && getter.body.firstChild is ReturnStatement) {
+                        val returnStmt = getter.body.firstChild as ReturnStatement
+                        if (returnStmt.returnValue is PropertyGet && (returnStmt.returnValue as PropertyGet).property.toSource() == MANGLED_VALUE_PROP) {
+                            val delegateField = ((returnStmt.returnValue as PropertyGet).target as PropertyGet).property.toSource()
+                            val ownerClassName = ((node.parent as FunctionCall).arguments[0] as PropertyGet).target.toSource()
+                            val key = "$delegateField\$$ownerClassName"
+                            delegateToOriginalAtomicField[key]?.let { atomicField ->
+                                // get() = a$delegate.value -> _a.value
+                                getter.replaceAccessedField(true, atomicField)
+                                if (node.elements.size == 3) {
+                                    // set(v: T) { a$delegate.value = v } -> { _a.value = v }
+                                    val setter = node.elements[2].right as FunctionNode
+                                    setter.replaceAccessedField(false, atomicField)
+                                }
+                            }
                         }
                     }
                 }
+            }
+            if (node is ObjectLiteral && node.parent is FunctionCall && ((node.elements.size == 1 && node.elements[0].left.toSource() == "get") ||
+                            node.elements.size == 2 && node.elements[0].left.toSource() == "get" && node.elements[1].left.toSource() == "set")) {
+                val parent = node.parent as FunctionCall
+                if (parent.arguments.size == 3 && parent.arguments[1] is StringLiteral) {
+                    val topLevelDelegatedFieldName = (parent.arguments[1] as StringLiteral).value
+                    if (topLevelDelegatedFieldName in delegateToOriginalAtomicField) {
+                        val originalAtomicFieldName = delegateToOriginalAtomicField[topLevelDelegatedFieldName]!!
+                        val getterName = node.elements[0].right.toSource()
+                        topLevelDelegatedFieldAccessorToOriginalField[getterName] = originalAtomicFieldName
+                        if (node.elements.size == 2) {
+                            val setterName = node.elements[1].right.toSource()
+                            topLevelDelegatedFieldAccessorToOriginalField[setterName] = originalAtomicFieldName
+                        }
+                    }
+                }
+            }
+            return true
+        }
+    }
 
+    private fun FunctionNode.replaceAccessedField(isGetter: Boolean, newField: Name) {
+        val propertyGet = if (isGetter) {
+            (body.firstChild as ReturnStatement).returnValue as PropertyGet
+        } else {
+            ((body.firstChild as ExpressionStatement).expression as Assignment).left as PropertyGet
+        }
+        if (propertyGet.target is PropertyGet) { // class member
+            (propertyGet.target as PropertyGet).property = newField
+        } else { // top-level field
+            propertyGet.target = newField
+        }
+    }
+
+    inner class TopLevelDelegatedFieldsAccessorVisitor : NodeVisitor {
+        override fun visit(node: AstNode?): Boolean {
+            if (node is FunctionNode && node.name.toString() in topLevelDelegatedFieldAccessorToOriginalField) {
+                val accessorName = node.name.toString()
+                val atomicField = topLevelDelegatedFieldAccessorToOriginalField[accessorName]!!
+                // function get_topLevelDelegatedField() = a.value  -> _a.value
+                // function set_topLevelDelegatedField(v: T) { a.value = v }  -> { _a.value = v }
+                node.replaceAccessedField(accessorName.startsWith("get"), atomicField)
             }
             return true
         }
@@ -388,12 +446,6 @@
                         rr.receiver?.let { node.target = it }
                     }
                 }
-                if (node.property.toSource() in delegatedProperties) {
-                    // replace delegated property name with the name of the original atomic field
-                    val fieldDelegate = delegatedProperties[node.property.toSource()]
-                    val originalField = fieldDelegates[fieldDelegate]!!
-                    node.property = Name().apply { identifier = originalField }
-                }
                 // replace Atomic*Array.size call with `length` property on the pure type js array
                 if (node.property.toSource() == ARRAY_SIZE) {
                     node.property = Name().also { it.identifier = LENGTH }
diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/MetadataTransformer.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/MetadataTransformer.kt
index f8bcaf1..13c5366 100644
--- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/MetadataTransformer.kt
+++ b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/MetadataTransformer.kt
@@ -26,7 +26,6 @@
         val hdr = KotlinClassHeader(
             kind = map["k"] as Int?,
             metadataVersion = (map["mv"] as? List<Int>)?.toIntArray(),
-            bytecodeVersion = (map["bv"] as? List<Int>)?.toIntArray(),
             data1 = (map["d1"] as? List<String>)?.toTypedArray(),
             data2 = (map["d2"] as? List<String>)?.toTypedArray(),
             extraString = map["xs"] as String?,
@@ -229,4 +228,4 @@
         }
     }
     error("Annotation key '$key' is not found")
-}
\ No newline at end of file
+}
diff --git a/atomicfu/build.gradle b/atomicfu/build.gradle
index 66cfcb8..063c9a3 100644
--- a/atomicfu/build.gradle
+++ b/atomicfu/build.gradle
@@ -18,12 +18,15 @@
 }
 
 kotlin {
-    targets.metaClass.addTarget = { preset ->
-        addNative(delegate.fromPreset(preset, preset.name))
+    targets {
+        delegate.metaClass.addTarget = { preset ->
+            addNative(delegate.fromPreset(preset, preset.name))
+        }
     }
 
     // JS -- always
     js {
+        moduleName = "kotlinx-atomicfu"
         // TODO: Commented out because browser tests do not work on TeamCity
         // browser()
         nodejs()
@@ -89,6 +92,11 @@
                 addTarget(presets.watchosArm64)
                 addTarget(presets.watchosX86)
                 addTarget(presets.watchosX64)
+
+                addTarget(presets.iosSimulatorArm64)
+                addTarget(presets.watchosSimulatorArm64)
+                addTarget(presets.tvosSimulatorArm64)
+                addTarget(presets.macosArm64)
             }
         }
 
@@ -159,14 +167,6 @@
     }
 }
 
-compileJsLegacy.configure {
-    kotlinOptions {
-        // NOTE: Module base-name must be equal to the package name declared in package.json
-        def baseName = "kotlinx-atomicfu"
-        outputFile = new File(outputFile.parent, baseName + ".js")
-    }
-}
-
 apply from: file("$rootProject.projectDir/gradle/node-js.gradle")
 apply from: file("$rootProject.projectDir/gradle/publish-npm-js.gradle")
 
@@ -188,7 +188,7 @@
     kotlinOptions {
         // NOTE: Module base-name must be equal to the package name declared in package.json
         def baseName = "kotlinx-atomicfu"
-        outputFile = new File(outputFile.parent, baseName + ".js")
+        outputFile = new File(new File(outputFile).parent, baseName + ".js")
     }
 }
 def originalJsFile = compileTestJsLegacy.kotlinOptions.outputFile
@@ -203,8 +203,14 @@
 
 if (project.tasks.findByName('jsLegacyNodeTest')) {
     jsLegacyNodeTest.dependsOn transformJS
+    jsLegacyNodeTest.configure {
+        inputFileProperty.set(new File(transformedJsFile))
+    }
 } else {
     jsNodeTest.dependsOn transformJS
+    jsNodeTest.configure {
+        inputFileProperty.set(new File(transformedJsFile))
+    }
 }
 
 // ==== CONFIGURE JVM =====
@@ -216,7 +222,7 @@
 
 tasks.withType(compileTestKotlinJvm.getClass()) {
     kotlinOptions {
-        jvmTarget = "1.6"
+        jvmTarget = "1.8"
     }
 }
 
@@ -244,24 +250,6 @@
     outputs.dir(classesPostTransformVH)
 }
 
-task checkJdk16() {
-    doLast {
-        if (!System.env.JDK_16) {
-            throw new GradleException("JDK_16 environment variable is not defined. " +
-                    "Can't build against JDK 1.6 runtime and run JDK 1.6 compatibility tests. " +
-                    "Please ensure JDK 1.6 is installed and that JDK_16 points to it.")
-        }
-    }
-}
-
-task transformedTestFU_6(type: Test, dependsOn: [checkJdk16, transformFU]) {
-    executable = "$System.env.JDK_16/bin/java"
-    classpath = configurations.jvmTestRuntimeClasspath + project.files(classesPostTransformFU)
-    testClassesDirs = project.files(classesPostTransformFU)
-    exclude '**/*LFTest.*', '**/TraceToStringTest.*', '**/AtomicfuReferenceJsTest.*'
-    filter { setFailOnNoMatchingTests(false) } // to run excluded tests in Idea
-}
-
 task transformedTestFU_current(type: Test, dependsOn: transformFU) {
     classpath = files(configurations.jvmTestRuntimeClasspath, classesPostTransformFU)
     testClassesDirs = project.files(classesPostTransformFU)
@@ -269,14 +257,6 @@
     filter { setFailOnNoMatchingTests(false) }
 }
 
-task transformedTestBOTH_6(type: Test, dependsOn: [checkJdk16, transformBOTH]) {
-    executable = "$System.env.JDK_16/bin/java"
-    classpath = files(configurations.jvmTestRuntimeClasspath, classesPostTransformBOTH)
-    testClassesDirs = project.files(classesPostTransformBOTH)
-    exclude '**/*LFTest.*', '**/TraceToStringTest.*', '**/TopLevelGeneratedDeclarationsReflectionTest.*', '**/SyntheticFUFieldsTest.*', '**/AtomicfuReferenceJsTest.*'
-    filter { setFailOnNoMatchingTests(false) }
-}
-
 task transformedTestBOTH_current(type: Test, dependsOn: transformBOTH) {
     classpath = files(configurations.jvmTestRuntimeClasspath, classesPostTransformBOTH)
     testClassesDirs = project.files(classesPostTransformBOTH)
@@ -305,9 +285,7 @@
 }
 
 task jvmTestAll(dependsOn: [
-        transformedTestFU_6,
         transformedTestFU_current,
-        transformedTestBOTH_6,
         transformedTestBOTH_current,
         transformedTestVH,
         testAtomicfuReferenceJs])
@@ -319,6 +297,72 @@
     }
 }
 
+task compileJavaModuleInfo(type: JavaCompile) {
+    def moduleName = "kotlinx.atomicfu" // this module's name
+    def compileKotlinJvm = kotlin.targets["jvm"].compilations["main"].compileKotlinTask
+    def sourceDir = file("src/jvmMain/java9/")
+    def targetDir = compileKotlinJvm.destinationDirectory.map { it.dir("../java9/") }
+
+    // Use a Java 11 compiler for the module info.
+    javaCompiler.set(project.javaToolchains.compilerFor { languageVersion.set(JavaLanguageVersion.of(11)) })
+
+    // Always compile kotlin classes before the module descriptor.
+    dependsOn(compileKotlinJvm)
+
+    // Add the module-info source file.
+    source(sourceDir)
+
+    // Also add the module-info.java source file to the Kotlin compile task.
+    // The Kotlin compiler will parse and check module dependencies,
+    // but it currently won't compile to a module-info.class file.
+    // Note that module checking only works on JDK 9+,
+    // because the JDK built-in base modules are not available in earlier versions.
+    def javaVersion = compileKotlinJvm.kotlinJavaToolchain.javaVersion.getOrNull()
+    if (javaVersion?.isJava9Compatible() == true) {
+        logger.info("Module-info checking is enabled; $compileKotlinJvm is compiled using Java $javaVersion")
+        compileKotlinJvm.source(sourceDir)
+    } else {
+        logger.info("Module-info checking is disabled")
+    }
+
+    // Set the task outputs and destination dir
+    outputs.dir(targetDir)
+    destinationDirectory.set(targetDir)
+
+    // Configure JVM compatibility
+    sourceCompatibility = JavaVersion.VERSION_1_9.toString()
+    targetCompatibility = JavaVersion.VERSION_1_9.toString()
+
+    // Set the Java release version.
+    options.release.set(9)
+
+    // Ignore warnings about using 'requires transitive' on automatic modules.
+    // not needed when compiling with recent JDKs, e.g. 17
+    options.compilerArgs.add("-Xlint:-requires-transitive-automatic")
+
+    // Patch the compileKotlinJvm output classes into the compilation so exporting packages works correctly.
+    options.compilerArgs.addAll(["--patch-module", "$moduleName=${compileKotlinJvm.destinationDirectory.get().getAsFile()}"])
+
+    // Use the classpath of the compileKotlinJvm task.
+    // Also ensure that the module path is used instead of classpath.
+    classpath = compileKotlinJvm.classpath
+    modularity.inferModulePath.set(true)
+
+    doFirst {
+        logger.warn("Task destination directory: ${destinationDirectory.get().getAsFile()}")
+    }
+}
+
+// Configure the JAR task so that it will include the compiled module-info class file.
+tasks.named("jvmJar") {
+    manifest {
+        attributes(["Multi-Release": true])
+    }
+    from(compileJavaModuleInfo) {
+        into("META-INF/versions/9/")
+    }
+}
+
 jvmTest {
     exclude "**/AtomicfuBytecodeTest*", "**/AtomicfuReferenceJsTest*", '**/TopLevelGeneratedDeclarationsReflectionTest.*', '**/SyntheticFUFieldsTest.*' // run them only for transformed code
 }
@@ -337,3 +381,4 @@
 tasks.matching { it.name == "generatePomFileForKotlinMultiplatformPublication" }.configureEach {
     dependsOn(tasks["generatePomFileForJvmPublication"])
 }
+
diff --git a/atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt b/atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt
index 1b24e6d..4521c09 100644
--- a/atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt
+++ b/atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt
@@ -5,8 +5,27 @@
 import kotlinx.atomicfu.atomic
 import kotlin.test.*
 
-class DelegatedProperties {
+private val topLevelIntOriginalAtomic = atomic(77)
+var topLevelIntDelegatedProperty: Int by topLevelIntOriginalAtomic
 
+private val _topLevelLong = atomic(55555555555)
+var topLevelDelegatedPropertyLong: Long by _topLevelLong
+
+private val _topLevelBoolean = atomic(false)
+var topLevelDelegatedPropertyBoolean: Boolean by _topLevelBoolean
+
+private val _topLevelRef = atomic(listOf("a", "b"))
+var topLevelDelegatedPropertyRef: List<String> by _topLevelRef
+
+var vTopLevelInt by atomic(77)
+
+var vTopLevelLong by atomic(777777777)
+
+var vTopLevelBoolean by atomic(false)
+
+var vTopLevelRef by atomic(listOf("a", "b"))
+
+class DelegatedProperties {
     private val _a = atomic(42)
     var a: Int by _a
 
@@ -99,6 +118,76 @@
         assertEquals(99, vRef.b.n)
     }
 
+    @Test
+    fun testTopLevelDelegatedPropertiesInt() {
+        assertEquals(77, topLevelIntDelegatedProperty)
+        topLevelIntOriginalAtomic.compareAndSet(77, 56)
+        assertEquals(56, topLevelIntDelegatedProperty)
+        topLevelIntDelegatedProperty = 88
+        topLevelIntOriginalAtomic.compareAndSet(88,  66)
+        assertEquals(66, topLevelIntOriginalAtomic.value)
+        assertEquals(66, topLevelIntDelegatedProperty)
+    }
+
+    @Test
+    fun testTopLevelDelegatedPropertiesLong() {
+        assertEquals(55555555555, topLevelDelegatedPropertyLong)
+        _topLevelLong.getAndIncrement()
+        assertEquals(55555555556, topLevelDelegatedPropertyLong)
+        topLevelDelegatedPropertyLong = 7777777777777
+        assertTrue(_topLevelLong.compareAndSet(7777777777777, 66666666666))
+        assertEquals(66666666666, _topLevelLong.value)
+        assertEquals(66666666666, topLevelDelegatedPropertyLong)
+    }
+
+    @Test
+    fun testTopLevelDelegatedPropertiesBoolean() {
+        assertEquals(false, topLevelDelegatedPropertyBoolean)
+        _topLevelBoolean.lazySet(true)
+        assertEquals(true, topLevelDelegatedPropertyBoolean)
+        topLevelDelegatedPropertyBoolean = false
+        assertTrue(_topLevelBoolean.compareAndSet(false, true))
+        assertEquals(true, _topLevelBoolean.value)
+        assertEquals(true, topLevelDelegatedPropertyBoolean)
+    }
+
+    @Test
+    fun testTopLevelDelegatedPropertiesRef() {
+        assertEquals("b", topLevelDelegatedPropertyRef[1])
+        _topLevelRef.lazySet(listOf("c"))
+        assertEquals("c", topLevelDelegatedPropertyRef[0])
+        topLevelDelegatedPropertyRef = listOf("d", "e")
+        assertEquals("e", _topLevelRef.value[1])
+    }
+
+    @Test
+    fun testVolatileTopLevelInt() {
+        assertEquals(77, vTopLevelInt)
+        vTopLevelInt = 55
+        assertEquals(110, vTopLevelInt * 2)
+    }
+
+    @Test
+    fun testVolatileTopLevelLong() {
+        assertEquals(777777777, vTopLevelLong)
+        vTopLevelLong = 55
+        assertEquals(55, vTopLevelLong)
+    }
+
+    @Test
+    fun testVolatileTopLevelBoolean() {
+        assertEquals(false, vTopLevelBoolean)
+        vTopLevelBoolean = true
+        assertEquals(true, vTopLevelBoolean)
+    }
+
+    @Test
+    fun testVolatileTopLevelRef() {
+        assertEquals("a", vTopLevelRef[0])
+        vTopLevelRef = listOf("c")
+        assertEquals("c", vTopLevelRef[0])
+    }
+
     class A (val b: B)
     class B (val n: Int)
 }
@@ -144,4 +233,25 @@
         cl.vInt = 99
         assertEquals(99, cl.vInt)
     }
+}
+
+class ClashedNamesTest {
+    private class A1 {
+        val _a = atomic(0)
+        val a: Int by _a
+    }
+
+    private class A2 {
+        val _a = atomic(0)
+        val a: Int by _a
+    }
+
+    @Test
+    fun testClashedDelegatedPropertiesNames() {
+        val a1Class = A1()
+        val a2Class = A2()
+        a1Class._a.compareAndSet(0, 77)
+        assertEquals(77, a1Class.a)
+        assertEquals(0, a2Class.a)
+    }
 }
\ No newline at end of file
diff --git a/atomicfu/src/jvmMain/java9/module-info.java b/atomicfu/src/jvmMain/java9/module-info.java
new file mode 100644
index 0000000..e68d750
--- /dev/null
+++ b/atomicfu/src/jvmMain/java9/module-info.java
@@ -0,0 +1,6 @@
+module kotlinx.atomicfu {
+    requires transitive kotlin.stdlib;
+
+    exports kotlinx.atomicfu;
+    exports kotlinx.atomicfu.locks;
+}
diff --git a/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/AtomicFU.kt b/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/AtomicFU.kt
index 20dc5f2..ddaf1dc 100644
--- a/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/AtomicFU.kt
+++ b/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/AtomicFU.kt
@@ -80,10 +80,8 @@
     @Volatile
     public actual var value: T = value
         set(value) {
-            interceptor.beforeUpdate(this)
             field = value
-            if (trace !== TraceBase.None) trace { "set($value)" }
-            interceptor.afterSet(this, value)
+            if (trace !== None) trace { "set($value)" }
         }
 
     public actual inline operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
@@ -94,22 +92,16 @@
      * Maps to [AtomicReferenceFieldUpdater.lazySet].
      */
     public actual fun lazySet(value: T) {
-        interceptor.beforeUpdate(this)
         FU.lazySet(this, value)
-        if (trace !== TraceBase.None) trace { "lazySet($value)" }
-        interceptor.afterSet(this, value)
+        if (trace !== None) trace { "lazySet($value)" }
     }
 
     /**
      * Maps to [AtomicReferenceFieldUpdater.compareAndSet].
      */
     public actual fun compareAndSet(expect: T, update: T): Boolean {
-        interceptor.beforeUpdate(this)
         val result = FU.compareAndSet(this, expect, update)
-        if (result) {
-            if (trace !== TraceBase.None) trace { "CAS($expect, $update)" }
-            interceptor.afterRMW(this, expect, update)
-        }
+        if (result && trace !== None) trace { "CAS($expect, $update)" }
         return result
     }
 
@@ -117,10 +109,8 @@
      * Maps to [AtomicReferenceFieldUpdater.getAndSet].
      */
     public actual fun getAndSet(value: T): T {
-        interceptor.beforeUpdate(this)
         val oldValue = FU.getAndSet(this, value) as T
-        if (trace !== TraceBase.None) trace { "getAndSet($value):$oldValue" }
-        interceptor.afterRMW(this, oldValue, value)
+        if (trace !== None) trace { "getAndSet($value):$oldValue" }
         return oldValue
     }
 
@@ -155,35 +145,27 @@
     public actual var value: Boolean
         get() = _value != 0
         set(value) {
-            interceptor.beforeUpdate(this)
             _value = if (value) 1 else 0
-            if (trace !== TraceBase.None) trace { "set($value)" }
-            interceptor.afterSet(this, value)
+            if (trace !== None) trace { "set($value)" }
         }
 
     /**
      * Maps to [AtomicIntegerFieldUpdater.lazySet].
      */
     public actual fun lazySet(value: Boolean) {
-        interceptor.beforeUpdate(this)
         val v = if (value) 1 else 0
         FU.lazySet(this, v)
-        if (trace !== TraceBase.None) trace { "lazySet($value)" }
-        interceptor.afterSet(this, value)
+        if (trace !== None) trace { "lazySet($value)" }
     }
 
     /**
      * Maps to [AtomicIntegerFieldUpdater.compareAndSet].
      */
     public actual fun compareAndSet(expect: Boolean, update: Boolean): Boolean {
-        interceptor.beforeUpdate(this)
         val e = if (expect) 1 else 0
         val u = if (update) 1 else 0
         val result = FU.compareAndSet(this, e, u)
-        if (result) {
-            if (trace !== TraceBase.None) trace { "CAS($expect, $update)" }
-            interceptor.afterRMW(this, expect, update)
-        }
+        if (result && trace !== None) trace { "CAS($expect, $update)" }
         return result
     }
 
@@ -191,11 +173,9 @@
      * Maps to [AtomicIntegerFieldUpdater.getAndSet].
      */
     public actual fun getAndSet(value: Boolean): Boolean {
-        interceptor.beforeUpdate(this)
         val v = if (value) 1 else 0
         val oldValue = FU.getAndSet(this, v)
-        if (trace !== TraceBase.None) trace { "getAndSet($value):$oldValue" }
-        interceptor.afterRMW(this, (oldValue == 1), value)
+        if (trace !== None) trace { "getAndSet($value):$oldValue" }
         return oldValue == 1
     }
 
@@ -220,10 +200,8 @@
     @Volatile
     public actual var value: Int = value
         set(value) {
-            interceptor.beforeUpdate(this)
             field = value
-            if (trace !== TraceBase.None) trace { "set($value)" }
-            interceptor.afterSet(this, value)
+            if (trace !== None) trace { "set($value)" }
         }
 
     public actual inline operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = value
@@ -234,22 +212,16 @@
      * Maps to [AtomicIntegerFieldUpdater.lazySet].
      */
     public actual fun lazySet(value: Int) {
-        interceptor.beforeUpdate(this)
         FU.lazySet(this, value)
-        if (trace !== TraceBase.None) trace { "lazySet($value)" }
-        interceptor.afterSet(this, value)
+        if (trace !== None) trace { "lazySet($value)" }
     }
 
     /**
      * Maps to [AtomicIntegerFieldUpdater.compareAndSet].
      */
     public actual fun compareAndSet(expect: Int, update: Int): Boolean {
-        interceptor.beforeUpdate(this)
         val result = FU.compareAndSet(this, expect, update)
-        if (result) {
-            if (trace !== TraceBase.None) trace { "CAS($expect, $update)" }
-            interceptor.afterRMW(this, expect, update)
-        }
+        if (result && trace !== None) trace { "CAS($expect, $update)" }
         return result
     }
 
@@ -257,10 +229,8 @@
      * Maps to [AtomicIntegerFieldUpdater.getAndSet].
      */
     public actual fun getAndSet(value: Int): Int {
-        interceptor.beforeUpdate(this)
         val oldValue = FU.getAndSet(this, value)
-        if (trace !== TraceBase.None) trace { "getAndSet($value):$oldValue" }
-        interceptor.afterRMW(this, oldValue, value)
+        if (trace !== None) trace { "getAndSet($value):$oldValue" }
         return oldValue
     }
 
@@ -268,10 +238,8 @@
      * Maps to [AtomicIntegerFieldUpdater.getAndIncrement].
      */
     public actual fun getAndIncrement(): Int {
-        interceptor.beforeUpdate(this)
         val oldValue = FU.getAndIncrement(this)
-        if (trace !== TraceBase.None) trace { "getAndInc():$oldValue" }
-        interceptor.afterRMW(this, oldValue, oldValue + 1)
+        if (trace !== None) trace { "getAndInc():$oldValue" }
         return oldValue
     }
 
@@ -279,10 +247,8 @@
      * Maps to [AtomicIntegerFieldUpdater.getAndDecrement].
      */
     public actual fun getAndDecrement(): Int {
-        interceptor.beforeUpdate(this)
         val oldValue = FU.getAndDecrement(this)
-        if (trace !== TraceBase.None) trace { "getAndDec():$oldValue" }
-        interceptor.afterRMW(this, oldValue, oldValue - 1)
+        if (trace !== None) trace { "getAndDec():$oldValue" }
         return oldValue
     }
 
@@ -290,10 +256,8 @@
      * Maps to [AtomicIntegerFieldUpdater.getAndAdd].
      */
     public actual fun getAndAdd(delta: Int): Int {
-        interceptor.beforeUpdate(this)
         val oldValue = FU.getAndAdd(this, delta)
-        if (trace !== TraceBase.None) trace { "getAndAdd($delta):$oldValue" }
-        interceptor.afterRMW(this, oldValue, oldValue + delta)
+        if (trace !== None) trace { "getAndAdd($delta):$oldValue" }
         return oldValue
     }
 
@@ -301,10 +265,8 @@
      * Maps to [AtomicIntegerFieldUpdater.addAndGet].
      */
     public actual fun addAndGet(delta: Int): Int {
-        interceptor.beforeUpdate(this)
         val newValue = FU.addAndGet(this, delta)
-        if (trace !== TraceBase.None) trace { "addAndGet($delta):$newValue" }
-        interceptor.afterRMW(this, newValue - delta, newValue)
+        if (trace !== None) trace { "addAndGet($delta):$newValue" }
         return newValue
     }
 
@@ -312,10 +274,8 @@
      * Maps to [AtomicIntegerFieldUpdater.incrementAndGet].
      */
     public actual fun incrementAndGet(): Int {
-        interceptor.beforeUpdate(this)
         val newValue = FU.incrementAndGet(this)
-        if (trace !== TraceBase.None) trace { "incAndGet():$newValue" }
-        interceptor.afterRMW(this, newValue - 1, newValue)
+        if (trace !== None) trace { "incAndGet():$newValue" }
         return newValue
     }
 
@@ -323,10 +283,8 @@
      * Maps to [AtomicIntegerFieldUpdater.decrementAndGet].
      */
     public actual fun decrementAndGet(): Int {
-        interceptor.beforeUpdate(this)
         val newValue = FU.decrementAndGet(this)
-        if (trace !== TraceBase.None) trace { "decAndGet():$newValue" }
-        interceptor.afterRMW(this, newValue + 1, newValue)
+        if (trace !== None) trace { "decAndGet():$newValue" }
         return newValue
     }
 
@@ -365,10 +323,8 @@
     @Volatile
     public actual var value: Long = value
         set(value) {
-            interceptor.beforeUpdate(this)
             field = value
-            if (trace !== TraceBase.None) trace { "set($value)" }
-            interceptor.afterSet(this, value)
+            if (trace !== None) trace { "set($value)" }
         }
 
     public actual inline operator fun getValue(thisRef: Any?, property: KProperty<*>): Long = value
@@ -379,22 +335,16 @@
      * Maps to [AtomicLongFieldUpdater.lazySet].
      */
     public actual fun lazySet(value: Long) {
-        interceptor.beforeUpdate(this)
         FU.lazySet(this, value)
-        if (trace !== TraceBase.None) trace { "lazySet($value)" }
-        interceptor.afterSet(this, value)
+        if (trace !== None) trace { "lazySet($value)" }
     }
 
     /**
      * Maps to [AtomicLongFieldUpdater.compareAndSet].
      */
     public actual fun compareAndSet(expect: Long, update: Long): Boolean {
-        interceptor.beforeUpdate(this)
         val result = FU.compareAndSet(this, expect, update)
-        if (result) {
-            if (trace !== TraceBase.None) trace { "CAS($expect, $update)" }
-            interceptor.afterRMW(this, expect, update)
-        }
+        if (result && trace !== None) trace { "CAS($expect, $update)" }
         return result
     }
 
@@ -402,10 +352,8 @@
      * Maps to [AtomicLongFieldUpdater.getAndSet].
      */
     public actual fun getAndSet(value: Long): Long {
-        interceptor.beforeUpdate(this)
         val oldValue = FU.getAndSet(this, value)
-        if (trace !== TraceBase.None) trace { "getAndSet($value):$oldValue" }
-        interceptor.afterRMW(this, oldValue, value)
+        if (trace !== None) trace { "getAndSet($value):$oldValue" }
         return oldValue
     }
 
@@ -413,10 +361,8 @@
      * Maps to [AtomicLongFieldUpdater.getAndIncrement].
      */
     public actual fun getAndIncrement(): Long {
-        interceptor.beforeUpdate(this)
         val oldValue = FU.getAndIncrement(this)
-        if (trace !== TraceBase.None) trace { "getAndInc():$oldValue" }
-        interceptor.afterRMW(this, oldValue, oldValue + 1)
+        if (trace !== None) trace { "getAndInc():$oldValue" }
         return oldValue
     }
 
@@ -424,10 +370,8 @@
      * Maps to [AtomicLongFieldUpdater.getAndDecrement].
      */
     public actual fun getAndDecrement(): Long {
-        interceptor.beforeUpdate(this)
         val oldValue = FU.getAndDecrement(this)
-        if (trace !== TraceBase.None) trace { "getAndDec():$oldValue" }
-        interceptor.afterRMW(this, oldValue, oldValue - 1)
+        if (trace !== None) trace { "getAndDec():$oldValue" }
         return oldValue
     }
 
@@ -435,10 +379,8 @@
      * Maps to [AtomicLongFieldUpdater.getAndAdd].
      */
     public actual fun getAndAdd(delta: Long): Long {
-        interceptor.beforeUpdate(this)
         val oldValue = FU.getAndAdd(this, delta)
-        if (trace !== TraceBase.None) trace { "getAndAdd($delta):$oldValue" }
-        interceptor.afterRMW(this, oldValue, oldValue + delta)
+        if (trace !== None) trace { "getAndAdd($delta):$oldValue" }
         return oldValue
     }
 
@@ -446,10 +388,8 @@
      * Maps to [AtomicLongFieldUpdater.addAndGet].
      */
     public actual fun addAndGet(delta: Long): Long {
-        interceptor.beforeUpdate(this)
         val newValue = FU.addAndGet(this, delta)
-        if (trace !== TraceBase.None) trace { "addAndGet($delta):$newValue" }
-        interceptor.afterRMW(this, newValue - delta, newValue)
+        if (trace !== None) trace { "addAndGet($delta):$newValue" }
         return newValue
     }
 
@@ -457,10 +397,8 @@
      * Maps to [AtomicLongFieldUpdater.incrementAndGet].
      */
     public actual fun incrementAndGet(): Long {
-        interceptor.beforeUpdate(this)
         val newValue = FU.incrementAndGet(this)
-        if (trace !== TraceBase.None) trace { "incAndGet():$newValue" }
-        interceptor.afterRMW(this, newValue - 1, newValue)
+        if (trace !== None) trace { "incAndGet():$newValue" }
         return newValue
     }
 
@@ -468,10 +406,8 @@
      * Maps to [AtomicLongFieldUpdater.decrementAndGet].
      */
     public actual fun decrementAndGet(): Long {
-        interceptor.beforeUpdate(this)
         val newValue = FU.decrementAndGet(this)
-        if (trace !== TraceBase.None) trace { "decAndGet():$newValue" }
-        interceptor.afterRMW(this, newValue + 1, newValue)
+        if (trace !== None) trace { "decAndGet():$newValue" }
         return newValue
     }
 
@@ -494,4 +430,4 @@
     private companion object {
         private val FU = AtomicLongFieldUpdater.newUpdater(AtomicLong::class.java, "value")
     }
-}
\ No newline at end of file
+}
diff --git a/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/Interceptor.kt b/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/Interceptor.kt
deleted file mode 100644
index 28d989b..0000000
--- a/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/Interceptor.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu
-
-import java.util.concurrent.locks.ReentrantLock
-
-internal var interceptor: AtomicOperationInterceptor = DefaultInterceptor
-    private set
-private val interceptorLock = ReentrantLock()
-
-internal fun lockAndSetInterceptor(impl: AtomicOperationInterceptor) {
-    if (!interceptorLock.tryLock() || interceptor !== DefaultInterceptor) {
-        error("Interceptor is locked by another test: $interceptor")
-    }
-    interceptor = impl
-}
-
-internal fun unlockAndResetInterceptor(impl: AtomicOperationInterceptor) {
-    check(interceptor === impl) { "Unexpected interceptor found: $interceptor" }
-    interceptor = DefaultInterceptor
-    interceptorLock.unlock()
-}
-
-/**
- * Interceptor for modifications of atomic variables.
- */
-internal open class AtomicOperationInterceptor {
-    open fun <T> beforeUpdate(ref: AtomicRef<T>) {}
-    open fun beforeUpdate(ref: AtomicInt) {}
-    open fun beforeUpdate(ref: AtomicLong) {}
-    open fun beforeUpdate(ref: AtomicBoolean){}
-    open fun <T> afterSet(ref: AtomicRef<T>, newValue: T) {}
-    open fun afterSet(ref: AtomicInt, newValue: Int) {}
-    open fun afterSet(ref: AtomicLong, newValue: Long) {}
-    open fun afterSet(ref: AtomicBoolean, newValue: Boolean) {}
-    open fun <T> afterRMW(ref: AtomicRef<T>, oldValue: T, newValue: T) {}
-    open fun afterRMW(ref: AtomicInt, oldValue: Int, newValue: Int) {}
-    open fun afterRMW(ref: AtomicLong, oldValue: Long, newValue: Long) {}
-    open fun afterRMW(ref: AtomicBoolean, oldValue: Boolean, newValue: Boolean) {}
-}
-
-private object DefaultInterceptor : AtomicOperationInterceptor() {
-    override fun toString(): String = "DefaultInterceptor"
-}
diff --git a/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/LockFreedomTestEnvironment.kt b/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/LockFreedomTestEnvironment.kt
deleted file mode 100644
index 0208feb..0000000
--- a/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/LockFreedomTestEnvironment.kt
+++ /dev/null
@@ -1,482 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-@file:Suppress("RedundantVisibilityModifier")
-
-package kotlinx.atomicfu
-
-import java.util.*
-import java.util.concurrent.atomic.*
-import java.util.concurrent.locks.*
-import kotlin.coroutines.*
-import kotlin.coroutines.intrinsics.*
-
-private const val PAUSE_EVERY_N_STEPS = 1000
-private const val STALL_LIMIT_MS = 15_000L // 15s
-private const val SHUTDOWN_CHECK_MS = 10L // 10ms
-
-private const val STATUS_DONE = Int.MAX_VALUE
-
-private const val MAX_PARK_NANOS = 1_000_000L // part for at most 1ms just in case of loosing unpark signal
-
-/**
- * Environment for performing lock-freedom tests for lock-free data structures
- * that are written with [atomic] variables.
- */
-public open class LockFreedomTestEnvironment(
-    private val name: String,
-    private val allowSuspendedThreads: Int = 0
-) {
-    private val interceptor = Interceptor()
-    private val threads = mutableListOf<TestThread>()
-    private val performedOps = LongAdder()
-    private val uncaughtException = AtomicReference<Throwable?>()
-    private var started = false
-    private var performedResumes = 0
-
-    @Volatile
-    private var completed = false
-    private val onCompletion = mutableListOf<() -> Unit>()
-
-    private val ueh = Thread.UncaughtExceptionHandler { t, e ->
-        synchronized(System.out) {
-            println("Uncaught exception in thread $t")
-            e.printStackTrace(System.out)
-            uncaughtException.compareAndSet(null, e)
-        }
-    }
-
-    // status < 0             - inv paused thread id
-    // status >= 0            - no. of performed resumes so far (==last epoch)
-    // status == STATUS_DONE - done working
-    private val status = AtomicInteger()
-    private val globalPauseProgress = AtomicInteger()
-    private val suspendedThreads = ArrayList<TestThread>()
-
-    @Volatile
-    private var isActive = true
-
-    // ---------- API ----------
-
-    /**
-     * Starts lock-freedom test for a given duration in seconds,
-     * invoking [progress] every second (it will be invoked `seconds + 1` times).
-     */
-    public fun performTest(seconds: Int, progress: () -> Unit = {}) {
-        check(isActive) { "Can perform test at most once on this instance" }
-        println("=== $name")
-        val minThreads = 2 + allowSuspendedThreads
-        check(threads.size >= minThreads) { "Must define at least $minThreads test threads" }
-        lockAndSetInterceptor(interceptor)
-        started = true
-        var nextTime = System.currentTimeMillis()
-        threads.forEach { thread ->
-            thread.setUncaughtExceptionHandler(ueh)
-            thread.lastOpTime = nextTime
-            thread.start()
-        }
-        try {
-            var second = 0
-            while (uncaughtException.get() == null) {
-                waitUntil(nextTime)
-                println("--- $second: Performed ${performedOps.sum()} operations${resumeStr()}")
-                progress()
-                checkStalled()
-                if (++second > seconds) break
-                nextTime += 1000L
-            }
-        } finally {
-            complete()
-        }
-        println("------ Done with ${performedOps.sum()} operations${resumeStr()}")
-        progress()
-    }
-
-    private fun complete() {
-        val activeNonPausedThreads: MutableMap<TestThread, Array<StackTraceElement>> = mutableMapOf()
-        val shutdownDeadline = System.currentTimeMillis() + STALL_LIMIT_MS
-        try {
-            completed = true
-            // perform custom completion blocks. For testing of things like channels, these custom completion
-            // blocks close all the channels, so that all suspended coroutines shall get resumed.
-            onCompletion.forEach { it() }
-            // signal shutdown to all threads (non-paused threads will terminate)
-            isActive = false
-            // wait for threads to terminate
-            while (System.currentTimeMillis() < shutdownDeadline) {
-                // Check all threads while shutting down:
-                // All terminated threads are considered to make progress for the purpose of resuming stalled ones
-                activeNonPausedThreads.clear()
-                for (t in threads) {
-                    when {
-                        !t.isAlive -> t.makeProgress(getPausedEpoch()) // not alive - makes progress
-                        t.index.inv() == status.get() -> {} // active, paused -- skip
-                        else -> {
-                            val stackTrace = t.stackTrace
-                            if (t.isAlive) activeNonPausedThreads[t] = stackTrace
-                        }
-                    }
-                }
-                if (activeNonPausedThreads.isEmpty()) break
-                checkStalled()
-                Thread.sleep(SHUTDOWN_CHECK_MS)
-            }
-            activeNonPausedThreads.forEach { (t, stackTrack) ->
-                println("=== $t had failed to shutdown in time")
-                stackTrack.forEach { println("\tat $it") }
-            }
-        } finally {
-            shutdown(shutdownDeadline)
-        }
-        // if no other exception was throws & we had threads that did not shut down -- still fails
-        if (activeNonPausedThreads.isNotEmpty()) error("Some threads had failed to shutdown in time")
-    }
-
-    private fun shutdown(shutdownDeadline: Long) {
-        // forcefully unpause paused threads to shut them down (if any left)
-        val curStatus = status.getAndSet(STATUS_DONE)
-        if (curStatus < 0) LockSupport.unpark(threads[curStatus.inv()])
-        threads.forEach {
-            val remaining = shutdownDeadline - System.currentTimeMillis()
-            if (remaining > 0) it.join(remaining)
-        }
-        // abort waiting threads (if still any left)
-        threads.forEach { it.abortWait() }
-        // cleanup & be done
-        unlockAndResetInterceptor(interceptor)
-        uncaughtException.get()?.let { throw it }
-        threads.find { it.isAlive }?.let { dumpThreadsError("A thread is still alive: $it")}
-    }
-
-    private fun checkStalled() {
-        val stallLimit = System.currentTimeMillis() - STALL_LIMIT_MS
-        val stalled = threads.filter { it.lastOpTime < stallLimit }
-        if (stalled.isNotEmpty()) dumpThreadsError("Progress stalled in threads ${stalled.map { it.name }}")
-    }
-
-    private fun resumeStr(): String {
-        val resumes = performedResumes
-        return if (resumes == 0) "" else " (pause/resumes $resumes)"
-    }
-
-    private fun waitUntil(nextTime: Long) {
-        while (true) {
-            val curTime = System.currentTimeMillis()
-            if (curTime >= nextTime) break
-            Thread.sleep(nextTime - curTime)
-        }
-    }
-
-    private fun dumpThreadsError(message: String) : Nothing {
-        val traces = threads.associate { it to it.stackTrace }
-        println("!!! $message")
-        println("=== Dumping live thread stack traces")
-        for ((thread, trace) in traces) {
-            if (trace.isEmpty()) continue
-            println("Thread \"${thread.name}\" ${thread.state}")
-            for (t in trace) println("\tat ${t.className}.${t.methodName}(${t.fileName}:${t.lineNumber})")
-            println()
-        }
-        println("===")
-        error(message)
-    }
-
-    /**
-     * Returns true when test was completed.
-     * Sets to true before calling [onCompletion] blocks.
-     */
-    public val isCompleted: Boolean get() = completed
-
-    /**
-     * Performs a given block of code on test's completion
-     */
-    public fun onCompletion(block: () -> Unit) {
-        onCompletion += block
-    }
-
-    /**
-     * Creates a new test thread in this environment that is executes a given lock-free [operation]
-     * in a loop while this environment [isActive].
-     */
-    public fun testThread(name: String? = null, operation: suspend TestThread.() -> Unit): TestThread =
-        TestThread(name, operation)
-
-    /**
-     * Test thread.
-     */
-    @Suppress("LeakingThis")
-    public inner class TestThread internal constructor(
-        name: String?,
-        private val operation: suspend TestThread.() -> Unit
-    ) : Thread(composeThreadName(name)) {
-        internal val index: Int
-
-        internal @Volatile var lastOpTime = 0L
-        internal @Volatile var pausedEpoch = -1
-
-        private val random = Random()
-
-        // thread-local stuff
-        private var operationEpoch = -1
-        private var progressEpoch = -1
-        private var sink = 0
-
-        init {
-            check(!started)
-            index = threads.size
-            threads += this
-        }
-
-        public override fun run() {
-            while (isActive) {
-                callOperation()
-            }
-        }
-
-        /**
-         * Use it to insert an arbitrary intermission between lock-free operations.
-         */
-        public inline fun <T> intermission(block: () -> T): T {
-            afterLockFreeOperation()
-            return try { block() }
-                finally { beforeLockFreeOperation() }
-        }
-
-        @PublishedApi
-        internal fun beforeLockFreeOperation() {
-            operationEpoch = getPausedEpoch()
-        }
-
-        @PublishedApi
-        internal fun afterLockFreeOperation() {
-            makeProgress(operationEpoch)
-            lastOpTime = System.currentTimeMillis()
-            performedOps.add(1)
-        }
-
-        internal fun makeProgress(epoch: Int) {
-            if (epoch <= progressEpoch) return
-            progressEpoch = epoch
-            val total = globalPauseProgress.incrementAndGet()
-            if (total >= threads.size - 1) {
-                check(total == threads.size - 1)
-                check(globalPauseProgress.compareAndSet(threads.size - 1, 0))
-                resumeImpl()
-            }
-        }
-
-        /**
-         * Inserts random spin wait between multiple lock-free operations in [operation].
-         */
-        public fun randomSpinWaitIntermission() {
-            intermission {
-                if (random.nextInt(100) < 95) return // be quick, no wait 95% of time
-                do {
-                    val x = random.nextInt(100)
-                    repeat(x) { sink += it }
-                } while (x >= 90)
-            }
-        }
-
-        internal fun stepImpl() {
-            if (random.nextInt(PAUSE_EVERY_N_STEPS) == 0) pauseImpl()
-        }
-
-        internal fun pauseImpl() {
-            while (true) {
-                val curStatus = status.get()
-                if (curStatus < 0 || curStatus == STATUS_DONE) return // some other thread paused or done
-                pausedEpoch = curStatus + 1
-                val newStatus = index.inv()
-                if (status.compareAndSet(curStatus, newStatus)) {
-                    while (status.get() == newStatus) LockSupport.parkNanos(MAX_PARK_NANOS) // wait
-                    return
-                }
-            }
-        }
-
-        // ----- Lightweight support for suspending operations -----
-
-        private fun callOperation() {
-            beforeLockFreeOperation()
-            beginRunningOperation()
-            val result = operation.startCoroutineUninterceptedOrReturn(this, completion)
-            when {
-                result === Unit -> afterLockFreeOperation() // operation completed w/o suspension -- done
-                result === COROUTINE_SUSPENDED -> waitUntilCompletion() // operation had suspended
-                else -> error("Unexpected result of operation: $result")
-            }
-            try {
-                doneRunningOperation()
-            } catch(e: IllegalStateException) {
-                throw IllegalStateException("${e.message}; original start result=$result", e)
-            }
-        }
-
-        private var runningOperation = false
-        private var result: Result<Any?>? = null
-        private var continuation: Continuation<Any?>? = null
-
-        private fun waitUntilCompletion() {
-            try {
-                while (true) {
-                    afterLockFreeOperation()
-                    val result: Result<Any?> = waitForResult()
-                    val continuation = takeContinuation()
-                    if (continuation == null) { // done
-                        check(result.getOrThrow() === Unit)
-                        return
-                    }
-                    removeSuspended(this)
-                    beforeLockFreeOperation()
-                    continuation.resumeWith(result)
-                }
-            } finally {
-                removeSuspended(this)
-            }
-        }
-
-        private fun beginRunningOperation() {
-            runningOperation = true
-            result = null
-            continuation = null
-        }
-
-        @Synchronized
-        private fun doneRunningOperation() {
-            check(runningOperation) { "Should be running operation" }
-            check(result == null && continuation == null) {
-                "Callback invoked with result=$result, continuation=$continuation"
-            }
-            runningOperation = false
-        }
-
-        @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-        @Synchronized
-        private fun resumeWith(result: Result<Any?>, continuation: Continuation<Any?>?) {
-            check(runningOperation) { "Should be running operation" }
-            check(this.result == null && this.continuation == null) {
-                "Resumed again with result=$result, continuation=$continuation, when this: result=${this.result}, continuation=${this.continuation}"
-            }
-            this.result = result
-            this.continuation = continuation
-            (this as Object).notifyAll()
-        }
-
-        @Suppress("RESULT_CLASS_IN_RETURN_TYPE", "PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-        @Synchronized
-        private fun waitForResult(): Result<Any?> {
-            while (true) {
-                val result = this.result
-                if (result != null) return result
-                val index = addSuspended(this)
-                if (index < allowSuspendedThreads) {
-                    // This suspension was permitted, so assume progress is happening while it is suspended
-                    makeProgress(getPausedEpoch())
-                }
-                (this as Object).wait(10) // at most 10 ms
-            }
-        }
-
-        @Synchronized
-        private fun takeContinuation(): Continuation<Any?>? =
-            continuation.also {
-                this.result = null
-                this.continuation = null
-            }
-
-        @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
-        @Synchronized
-        fun abortWait() {
-            this.result = Result.failure(IllegalStateException("Aborted at the end of test"))
-            (this as Object).notifyAll()
-        }
-
-        private val interceptor: CoroutineContext = object : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
-            override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
-                Continuation<T>(this) {
-                    @Suppress("UNCHECKED_CAST")
-                    resumeWith(it, continuation as Continuation<Any?>)
-                }
-        }
-
-        private val completion = Continuation<Unit>(interceptor) {
-            resumeWith(it, null)
-        }
-    }
-
-    // ---------- Implementation ----------
-
-    @Synchronized
-    private fun addSuspended(thread: TestThread): Int {
-        val index = suspendedThreads.indexOf(thread)
-        if (index >= 0) return index
-        suspendedThreads.add(thread)
-        return suspendedThreads.size - 1
-    }
-
-    @Synchronized
-    private fun removeSuspended(thread: TestThread) {
-        suspendedThreads.remove(thread)
-    }
-
-    private fun getPausedEpoch(): Int {
-        while (true) {
-            val curStatus = status.get()
-            if (curStatus >= 0) return -1 // not paused
-            val thread = threads[curStatus.inv()]
-            val pausedEpoch = thread.pausedEpoch
-            if (curStatus == status.get()) return pausedEpoch
-        }
-    }
-
-    internal fun step() {
-        val thread = Thread.currentThread() as? TestThread ?: return
-        thread.stepImpl()
-    }
-
-    private fun resumeImpl() {
-        while (true) {
-            val curStatus = status.get()
-            if (curStatus == STATUS_DONE) return // done
-            check(curStatus < 0)
-            val thread = threads[curStatus.inv()]
-            performedResumes = thread.pausedEpoch
-            if (status.compareAndSet(curStatus, thread.pausedEpoch)) {
-                LockSupport.unpark(thread)
-                return
-            }
-        }
-    }
-
-    private fun composeThreadName(threadName: String?): String {
-        if (threadName != null) return "$name-$threadName"
-        return name + "-${threads.size + 1}"
-    }
-
-    private inner class Interceptor : AtomicOperationInterceptor() {
-        override fun <T> beforeUpdate(ref: AtomicRef<T>) = step()
-        override fun beforeUpdate(ref: AtomicInt) = step()
-        override fun beforeUpdate(ref: AtomicLong) = step()
-        override fun <T> afterSet(ref: AtomicRef<T>, newValue: T) = step()
-        override fun afterSet(ref: AtomicInt, newValue: Int) = step()
-        override fun afterSet(ref: AtomicLong, newValue: Long) = step()
-        override fun <T> afterRMW(ref: AtomicRef<T>, oldValue: T, newValue: T) = step()
-        override fun afterRMW(ref: AtomicInt, oldValue: Int, newValue: Int) = step()
-        override fun afterRMW(ref: AtomicLong, oldValue: Long, newValue: Long) = step()
-        override fun toString(): String = "LockFreedomTestEnvironment($name)"
-    }
-}
-
-/**
- * Manual pause for on-going lock-free operation in a specified piece of code.
- * Use it for targeted debugging of specific places in code. It does nothing
- * when invoked outside of test thread.
- *
- * **Don't use it in production code.**
- */
-public fun pauseLockFreeOp() {
-    val thread = Thread.currentThread() as? LockFreedomTestEnvironment.TestThread ?: return
-    thread.pauseImpl()
-}
\ No newline at end of file
diff --git a/atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/LockFreeQueueLFTest.kt b/atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/LockFreeQueueLFTest.kt
deleted file mode 100644
index 5179638..0000000
--- a/atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/LockFreeQueueLFTest.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu.test
-
-import kotlinx.atomicfu.LockFreedomTestEnvironment
-import org.junit.Test
-import java.util.*
-
-class LockFreeQueueLFTest : LockFreedomTestEnvironment("LockFreeQueueLFTest") {
-    val nEnqueuers = 2
-    val nDequeuers = 2
-    val nSeconds = 5
-
-    val queue = LockFreeQueue()
-
-    @Test
-    fun testLockFreedom() {
-        repeat(nEnqueuers) { id ->
-            val rnd = Random()
-            testThread("Enqueue-$id") {
-                queue.enqueue(rnd.nextInt(1000))
-            }
-        }
-        repeat(nDequeuers) { id ->
-            testThread("Dequeue-$id") {
-                queue.dequeue()
-            }
-        }
-        performTest(nSeconds)
-    }
-}
\ No newline at end of file
diff --git a/atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/TraceLFTest.kt b/atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/TraceLFTest.kt
deleted file mode 100644
index 32ea8d8..0000000
--- a/atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/TraceLFTest.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu.test
-
-import kotlinx.atomicfu.*
-import kotlin.test.Test
-
-class Counter {
-    private val t = Trace(64, TraceFormat { index, text ->
-        "$index: [${Thread.currentThread().name}] $text"
-    })
-    private val a = atomic(0, t)
-
-    fun inc(): Int {
-        t { "inc() invoked" }
-        val x = a.incrementAndGet()
-        t { "inc() = $x" }
-        return x
-    }
-
-    internal fun get() = a.value
-}
-
-class CounterDefaultAtomic {
-    private val a = atomic(0)
-    private val trace = Trace(64)
-
-    fun inc(): Int {
-        trace { "inc() invoked" }
-        val x = a.incrementAndGet()
-        trace { "inc() = $x" }
-        return x
-    }
-
-    internal fun get() = a.value
-}
-
-class CounterLFTest : LockFreedomTestEnvironment("CounterLFTest") {
-    private val c = Counter()
-    private val c1 = CounterDefaultAtomic()
-
-    @Test
-    fun testCounterDefault() {
-        repeat(10) { id ->
-            testThread ("Inc-$id")  {
-                c1.inc()
-            }
-        }
-        repeat(2) { id ->
-            testThread("Get-$id") {
-                c1.get()
-            }
-        }
-        performTest(10)
-        println(c1.get())
-    }
-
-    @Test
-    fun testLockFreedom() {
-        repeat(10) { id ->
-            testThread("Inc-$id") {
-                c.inc()
-            }
-        }
-        repeat(2) { id ->
-            testThread("Get-$id") {
-                c.get()
-            }
-        }
-        performTest(10)
-        println(c.get())
-    }
-}
-
diff --git a/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/AtomicFU.kt b/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/AtomicFU.kt
index 55ed452..b369540 100644
--- a/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/AtomicFU.kt
+++ b/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/AtomicFU.kt
@@ -25,8 +25,8 @@
 
 // ==================================== AtomicRef ====================================
 
-@Suppress("ACTUAL_WITHOUT_EXPECT", "EXPERIMENTAL_FEATURE_WARNING", "NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
-public actual inline class AtomicRef<T> internal constructor(@PublishedApi internal val a: KAtomicRef<T>) {
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual value class AtomicRef<T> internal constructor(@PublishedApi internal val a: KAtomicRef<T>) {
     public actual inline var value: T
         get() = a.value
         set(value) {
@@ -62,8 +62,8 @@
 
 // ==================================== AtomicBoolean ====================================
 
-@Suppress("ACTUAL_WITHOUT_EXPECT", "EXPERIMENTAL_FEATURE_WARNING", "NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
-public actual inline class AtomicBoolean internal constructor(@PublishedApi internal val a: KAtomicInt) {
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual value class AtomicBoolean internal constructor(@PublishedApi internal val a: KAtomicInt) {
     public actual inline var value: Boolean
         get() = a.value != 0
         set(value) { a.value = if (value) 1 else 0 }
@@ -94,8 +94,8 @@
 
 // ==================================== AtomicInt ====================================
 
-@Suppress("ACTUAL_WITHOUT_EXPECT", "EXPERIMENTAL_FEATURE_WARNING", "NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
-public actual inline class AtomicInt internal constructor(@PublishedApi internal val a: KAtomicInt) {
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual value class AtomicInt internal constructor(@PublishedApi internal val a: KAtomicInt) {
     public actual inline var value: Int
         get() = a.value
         set(value) { a.value = value }
@@ -132,8 +132,8 @@
 
 // ==================================== AtomicLong ====================================
 
-@Suppress("ACTUAL_WITHOUT_EXPECT", "EXPERIMENTAL_FEATURE_WARNING", "NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
-public actual inline class AtomicLong internal constructor(@PublishedApi internal val a: KAtomicLong) {
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual value class AtomicLong internal constructor(@PublishedApi internal val a: KAtomicLong) {
     public actual inline var value: Long
         get() = a.value
         set(value) { a.value = value }
diff --git a/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/locks/Synchronized.kt b/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/locks/Synchronized.kt
index ff81a5a..76e5d7a 100644
--- a/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/locks/Synchronized.kt
+++ b/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/locks/Synchronized.kt
@@ -190,7 +190,7 @@
 
     init {
         for (i in 0 until capacity) {
-            release(interpretCPointer<mutex_node_t>(mutexes.rawValue.plus(i * mutex_node_t.size))!!)
+            release(interpretCPointer<mutex_node_t>(mutexes.rawValue.plus(i * sizeOf<mutex_node_t>()))!!)
         }
     }
 
diff --git a/build.gradle b/build.gradle
index 12f531d..09a62fe 100644
--- a/build.gradle
+++ b/build.gradle
@@ -34,15 +34,6 @@
         maven { url "https://plugins.gradle.org/m2/" }
         // Future replacement for kotlin-dev, with cache redirector
         maven { url "https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
-        maven {
-            url "https://kotlin.bintray.com/kotlin-dev"
-            credentials {
-                username = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') ?: ""
-                password = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') ?: ""
-            }
-        }
-        maven { url "https://kotlin.bintray.com/kotlin-eap" }
-        maven { url "https://jetbrains.bintray.com/kotlin-native-dependencies" }
         maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
     }
     
@@ -66,17 +57,8 @@
     println "Using Kotlin $kotlin_version for project $it"
     repositories {
         jcenter()
-        maven { url "https://kotlin.bintray.com/kotlin-eap" }
         // Future replacement for kotlin-dev, with cache redirector
         maven { url "https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
-        maven {
-            url "https://kotlin.bintray.com/kotlin-dev"
-            credentials {
-                username = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') ?: ""
-                password = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') ?: ""
-            }
-        }
-        maven { url "https://kotlin.bintray.com/kotlinx" }
         maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
     }
 
diff --git a/buildSrc/src/main/kotlin/Publishing.kt b/buildSrc/src/main/kotlin/Publishing.kt
index 6844ad7..3db911f 100644
--- a/buildSrc/src/main/kotlin/Publishing.kt
+++ b/buildSrc/src/main/kotlin/Publishing.kt
@@ -44,20 +44,6 @@
     }
 }
 
-fun configureBintrayPublication(rh: RepositoryHandler, project: Project) {
-    rh.maven {
-        val user = "kotlin"
-        val repo = "kotlinx"
-        val name = "kotlinx.atomicfu"
-        url = URI("https://api.bintray.com/maven/$user/$repo/$name/;publish=0;override=0")
-
-        credentials {
-            username = project.findProperty("bintrayUser") as? String ?: System.getenv("BINTRAY_USER")
-            password = project.findProperty("bintrayApiKey") as? String ?: System.getenv("BINTRAY_API_KEY")
-        }
-    }
-}
-
 fun mavenRepositoryUri(): URI {
     // TODO -SNAPSHOT detection can be made here as well
     val repositoryId: String? = System.getenv("libs.repository.id")
diff --git a/gradle.properties b/gradle.properties
index ce4f84c..903d60d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,14 +2,14 @@
 # Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 #
 
-version=0.16.0-SNAPSHOT
+version=0.18.5-SNAPSHOT
 group=org.jetbrains.kotlinx
 
-kotlin_version=1.5.0
-asm_version=7.2
+kotlin_version=1.7.20
+asm_version=9.3
 slf4j_version=1.8.0-alpha2
 junit_version=4.12
-kotlinx_metadata_version=0.2.0
+kotlinx_metadata_version=0.5.0
 
 maven_version=3.5.3
 
diff --git a/gradle/compile-options.gradle b/gradle/compile-options.gradle
index e6b4432..b6ce532 100644
--- a/gradle/compile-options.gradle
+++ b/gradle/compile-options.gradle
@@ -21,8 +21,6 @@
         languageSettings {
             apiVersion = "1.4"
             languageVersion = "1.4"
-            useExperimentalAnnotation("kotlin.Experimental")
-            useExperimentalAnnotation("kotlin.ExperimentalStdlibApi")
         }
     }
 }
diff --git a/gradle/interop-as-source-set-klib.gradle b/gradle/interop-as-source-set-klib.gradle
index 62f2b77..25cb0c2 100644
--- a/gradle/interop-as-source-set-klib.gradle
+++ b/gradle/interop-as-source-set-klib.gradle
@@ -7,7 +7,7 @@
         def cinteropTask = tasks.named(interop.interopProcessingTaskName)
         def cinteropKlib = cinteropTask.map { it.outputFile }
         def fakeCinteropCompilation = kotlin.targets["metadata"].compilations[sourceSet.name]
-        def destination = fakeCinteropCompilation.compileKotlinTask.destinationDir
+        def destination = fakeCinteropCompilation.compileKotlinTask.destinationDirectory
 
         def tempDir = "$buildDir/tmp/${sourceSet.name}UnpackedInteropKlib"
 
diff --git a/gradle/publish-npm-js.gradle b/gradle/publish-npm-js.gradle
index e6760a7..b7e7264 100644
--- a/gradle/publish-npm-js.gradle
+++ b/gradle/publish-npm-js.gradle
@@ -29,7 +29,7 @@
     from(npmTemplateDir) {
         expand (project.properties + [kotlinDependency: "\"kotlin\": \"$kotlin_version\""])
     }
-    from(compileJsLegacy.destinationDir)
+    from(compileJsLegacy.destinationDirectory)
     into npmDeployDir
 }
 
diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle
index 0ca4788..e9e4372 100644
--- a/gradle/publishing.gradle
+++ b/gradle/publishing.gradle
@@ -27,15 +27,9 @@
     archiveClassifier = 'javadoc'
 }
 
-def bintrayUpload = System.getenv("libs.bintray.upload") != null
-
 publishing {
     repositories { // this: closure
-        if (bintrayUpload) {
-            PublishingKt.configureBintrayPublication(delegate, project)
-        } else {
-            PublishingKt.configureMavenPublication(delegate, project)
-        }
+        PublishingKt.configureMavenPublication(delegate, project)
     }
     
     if (!isMultiplatform) {
@@ -61,9 +55,7 @@
 
     publications.all {
         PublishingKt.configureMavenCentralMetadata(pom, project)
-        if (!bintrayUpload) {
-            PublishingKt.signPublicationIfKeyPresent(project, it)
-        }
+        PublishingKt.signPublicationIfKeyPresent(project, it)
 
         // add empty javadocs
         if (it.name != "kotlinMultiplatform") { // The root module gets the JVM's javadoc JAR
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 4c83d2a..0904b9b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip