kotlinx.atomicfu compiler plugin (JS): tracing supported. (#4691)

* kotlinx.atomicfu compiler plugin (JS): tracing supported.

* Erasing trace invocations from both IrBlock and IrBlockBody.
This commit is contained in:
mvicsokolova
2021-12-27 23:20:08 +03:00
committed by GitHub
parent 29f174f074
commit 188d073cc3
3 changed files with 152 additions and 0 deletions
@@ -25,10 +25,14 @@ private const val LOCKS = "locks"
private const val AFU_LOCKS_PKG = "$AFU_PKG.$LOCKS"
private const val ATOMICFU_RUNTIME_FUNCTION_PREDICATE = "atomicfu_"
private const val REENTRANT_LOCK_TYPE = "ReentrantLock"
private const val TRACE_BASE_TYPE = "TraceBase"
private const val GETTER = "atomicfu\$getter"
private const val SETTER = "atomicfu\$setter"
private const val GET = "get"
private const val ATOMIC_VALUE_FACTORY = "atomic"
private const val TRACE = "Trace"
private const val INVOKE = "invoke"
private const val APPEND = "append"
private const val ATOMIC_ARRAY_OF_NULLS_FACTORY = "atomicArrayOfNulls"
private const val REENTRANT_LOCK_FACTORY = "reentrantLock"
@@ -101,6 +105,23 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
return super.visitFunction(declaration, declaration)
}
override fun visitBlockBody(body: IrBlockBody, data: IrFunction?): IrBody {
// Erase messages added by the Trace object from the function body:
// val trace = Trace(size)
// Messages may be added via trace invocation:
// trace { "Doing something" }
// or via multi-append of arguments:
// trace.append(index, "CAS", value)
body.statements.removeIf { it.isTrace() }
return super.visitBlockBody(body, data)
}
override fun visitContainerExpression(expression: IrContainerExpression, data: IrFunction?): IrExpression {
// Erase messages added by the Trace object from blocks.
expression.statements.removeIf { it.isTrace() }
return super.visitContainerExpression(expression, data)
}
override fun visitCall(expression: IrCall, data: IrFunction?): IrElement {
expression.eraseAtomicFactory()?.let { return it.transform(this, data) }
val isInline = expression.symbol.owner.isInline
@@ -276,6 +297,19 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
it.type.isAtomicArrayType() && symbol.owner.name.asString() == GET
} ?: false
private fun IrStatement.isTrace() =
this is IrCall && (isTraceInvoke() || isTraceAppend())
private fun IrCall.isTraceInvoke(): Boolean =
symbol.isKotlinxAtomicfuPackage() &&
symbol.owner.name.asString() == INVOKE &&
symbol.owner.dispatchReceiverParameter?.type?.isTraceBaseType() == true
private fun IrCall.isTraceAppend(): Boolean =
symbol.isKotlinxAtomicfuPackage() &&
symbol.owner.name.asString() == APPEND &&
symbol.owner.dispatchReceiverParameter?.type?.isTraceBaseType() == true
private fun IrCall.getAccessors(): List<IrExpression> {
val isArrayElement = isArrayElementGetter()
val valueType = type.atomicToValueType()
@@ -318,6 +352,7 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
isAtomicFactory() -> getValueArgument(0) ?: error("Atomic factory should take at least one argument: ${this.render()}")
isAtomicArrayFactory() -> buildObjectArray()
isReentrantLockFactory() -> context.buildConstNull()
isTraceFactory() -> context.buildConstNull()
else -> null
}
@@ -344,6 +379,7 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
private fun IrType.isAtomicValueType() = belongsTo(AFU_PKG, ATOMIC_VALUE_TYPES)
private fun IrType.isAtomicArrayType() = belongsTo(AFU_PKG, ATOMIC_ARRAY_TYPES)
private fun IrType.isReentrantLockType() = belongsTo(AFU_LOCKS_PKG, REENTRANT_LOCK_TYPE)
private fun IrType.isTraceBaseType() = belongsTo(AFU_PKG, TRACE_BASE_TYPE)
private fun IrType.belongsTo(packageName: String, typeNames: Set<String>) =
getSignature()?.let { sig ->
@@ -371,6 +407,10 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
symbol.isKotlinxAtomicfuPackage() && symbol.owner.name.asString() == ATOMIC_VALUE_FACTORY &&
type.isAtomicValueType()
private fun IrCall.isTraceFactory(): Boolean =
symbol.isKotlinxAtomicfuPackage() && symbol.owner.name.asString() == TRACE &&
type.isTraceBaseType()
private fun IrCall.isAtomicArrayFactory(): Boolean =
symbol.isKotlinxAtomicfuPackage() && symbol.owner.name.asString() == ATOMIC_ARRAY_OF_NULLS_FACTORY &&
type.isAtomicArrayType()
@@ -145,6 +145,12 @@ public class AtomicfuJsIrTestGenerated extends AbstractAtomicfuJsIrTest {
runTest("plugins/atomicfu/atomicfu-compiler/testData/box/TopLevelTest.kt");
}
@Test
@TestMetadata("TraceTest.kt")
public void testTraceTest() throws Exception {
runTest("plugins/atomicfu/atomicfu-compiler/testData/box/TraceTest.kt");
}
@Test
@TestMetadata("UncheckedCastTest.kt")
public void testUncheckedCastTest() throws Exception {
@@ -0,0 +1,106 @@
import kotlinx.atomicfu.*
import kotlin.test.*
class TraceTest {
private val defaultTrace = Trace()
private val a1 = atomic(5, defaultTrace)
private val traceWithSize = Trace(30)
private val a2 = atomic(1, traceWithSize)
private val traceWithFormat = Trace(format = TraceFormat { i, text -> "[$i: $text]" })
private val a = atomic(0, traceWithFormat)
private val traceWithSizeAndFormat = Trace(30, TraceFormat { id, text -> "$id: $text"})
private val a3 = atomic(2)
private val shortTrace = Trace(4)
private val s = atomic(0, shortTrace.named("s"))
fun testDefaultTrace() {
val oldValue = a1.value
defaultTrace { "before CAS value = $oldValue" }
val res = a1.compareAndSet(oldValue, oldValue * 10)
val newValue = a1.value
defaultTrace { "after CAS value = $newValue" }
}
fun testTraceWithSize() {
val oldValue = a2.value
traceWithSize { "before CAS value = $oldValue" }
assertTrue(a2.compareAndSet(oldValue, oldValue * 10))
traceWithSize { "after CAS value = ${a2.value}" }
traceWithSize { "before getAndDecrement value = ${a2.value}" }
a2.getAndDecrement()
assertEquals(9, a2.value)
traceWithSize { "after getAndDecrement value = ${a2.value}" }
}
fun testTraceWithFormat() {
val oldValue = a3.value
traceWithFormat { "before CAS value = $oldValue" }
assertTrue(a3.compareAndSet(oldValue, oldValue * 10))
traceWithFormat { "after CAS value = ${a3.value}" }
traceWithFormat { "before getAndDecrement value = ${a3.value}" }
a3.getAndDecrement()
assertEquals(19, a3.value)
traceWithFormat { "after getAndDecrement value = ${a3.value}" }
}
fun testNamedTrace() {
s.value = 5
shortTrace { "before CAS value = ${s.value}" }
s.compareAndSet(5, -2)
assertEquals(-2, s.value)
shortTrace { "after CAS value = ${s.value}" }
}
private enum class Status { START, END }
fun testMultipleAppend() {
val i = 1
traceWithFormat.append(i, Status.START)
assertEquals(0, a.value)
a.incrementAndGet()
traceWithFormat.append(i, a.value, "incAndGet")
assertEquals(1, a.value)
a.lazySet(10)
traceWithFormat.append(i, a.value, "lazySet")
assertEquals(10, a.value)
traceWithFormat.append(i, Status.END)
}
fun testTraceInBlock() {
a1.lazySet(5)
if (a1.value == 5) {
defaultTrace { "Value checked" }
if (a1.compareAndSet(5, 10)) {
defaultTrace { "CAS succeeded" }
}
}
assertEquals(10, a1.value)
while (true) {
if (a1.value == 10) {
defaultTrace.append("Value checked", a1.value)
a1.value = 15
break
} else {
defaultTrace.append("Wrong value", a1.value)
}
}
}
fun test() {
testDefaultTrace()
testTraceWithSize()
testTraceWithFormat()
testNamedTrace()
testMultipleAppend()
testTraceInBlock()
}
}
fun box(): String {
TraceTest().test()
return "OK"
}