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:
+40
@@ -25,10 +25,14 @@ private const val LOCKS = "locks"
|
|||||||
private const val AFU_LOCKS_PKG = "$AFU_PKG.$LOCKS"
|
private const val AFU_LOCKS_PKG = "$AFU_PKG.$LOCKS"
|
||||||
private const val ATOMICFU_RUNTIME_FUNCTION_PREDICATE = "atomicfu_"
|
private const val ATOMICFU_RUNTIME_FUNCTION_PREDICATE = "atomicfu_"
|
||||||
private const val REENTRANT_LOCK_TYPE = "ReentrantLock"
|
private const val REENTRANT_LOCK_TYPE = "ReentrantLock"
|
||||||
|
private const val TRACE_BASE_TYPE = "TraceBase"
|
||||||
private const val GETTER = "atomicfu\$getter"
|
private const val GETTER = "atomicfu\$getter"
|
||||||
private const val SETTER = "atomicfu\$setter"
|
private const val SETTER = "atomicfu\$setter"
|
||||||
private const val GET = "get"
|
private const val GET = "get"
|
||||||
private const val ATOMIC_VALUE_FACTORY = "atomic"
|
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 ATOMIC_ARRAY_OF_NULLS_FACTORY = "atomicArrayOfNulls"
|
||||||
private const val REENTRANT_LOCK_FACTORY = "reentrantLock"
|
private const val REENTRANT_LOCK_FACTORY = "reentrantLock"
|
||||||
|
|
||||||
@@ -101,6 +105,23 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
|
|||||||
return super.visitFunction(declaration, declaration)
|
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 {
|
override fun visitCall(expression: IrCall, data: IrFunction?): IrElement {
|
||||||
expression.eraseAtomicFactory()?.let { return it.transform(this, data) }
|
expression.eraseAtomicFactory()?.let { return it.transform(this, data) }
|
||||||
val isInline = expression.symbol.owner.isInline
|
val isInline = expression.symbol.owner.isInline
|
||||||
@@ -276,6 +297,19 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
|
|||||||
it.type.isAtomicArrayType() && symbol.owner.name.asString() == GET
|
it.type.isAtomicArrayType() && symbol.owner.name.asString() == GET
|
||||||
} ?: false
|
} ?: 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> {
|
private fun IrCall.getAccessors(): List<IrExpression> {
|
||||||
val isArrayElement = isArrayElementGetter()
|
val isArrayElement = isArrayElementGetter()
|
||||||
val valueType = type.atomicToValueType()
|
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()}")
|
isAtomicFactory() -> getValueArgument(0) ?: error("Atomic factory should take at least one argument: ${this.render()}")
|
||||||
isAtomicArrayFactory() -> buildObjectArray()
|
isAtomicArrayFactory() -> buildObjectArray()
|
||||||
isReentrantLockFactory() -> context.buildConstNull()
|
isReentrantLockFactory() -> context.buildConstNull()
|
||||||
|
isTraceFactory() -> context.buildConstNull()
|
||||||
else -> null
|
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.isAtomicValueType() = belongsTo(AFU_PKG, ATOMIC_VALUE_TYPES)
|
||||||
private fun IrType.isAtomicArrayType() = belongsTo(AFU_PKG, ATOMIC_ARRAY_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.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>) =
|
private fun IrType.belongsTo(packageName: String, typeNames: Set<String>) =
|
||||||
getSignature()?.let { sig ->
|
getSignature()?.let { sig ->
|
||||||
@@ -371,6 +407,10 @@ class AtomicfuTransformer(private val context: IrPluginContext) {
|
|||||||
symbol.isKotlinxAtomicfuPackage() && symbol.owner.name.asString() == ATOMIC_VALUE_FACTORY &&
|
symbol.isKotlinxAtomicfuPackage() && symbol.owner.name.asString() == ATOMIC_VALUE_FACTORY &&
|
||||||
type.isAtomicValueType()
|
type.isAtomicValueType()
|
||||||
|
|
||||||
|
private fun IrCall.isTraceFactory(): Boolean =
|
||||||
|
symbol.isKotlinxAtomicfuPackage() && symbol.owner.name.asString() == TRACE &&
|
||||||
|
type.isTraceBaseType()
|
||||||
|
|
||||||
private fun IrCall.isAtomicArrayFactory(): Boolean =
|
private fun IrCall.isAtomicArrayFactory(): Boolean =
|
||||||
symbol.isKotlinxAtomicfuPackage() && symbol.owner.name.asString() == ATOMIC_ARRAY_OF_NULLS_FACTORY &&
|
symbol.isKotlinxAtomicfuPackage() && symbol.owner.name.asString() == ATOMIC_ARRAY_OF_NULLS_FACTORY &&
|
||||||
type.isAtomicArrayType()
|
type.isAtomicArrayType()
|
||||||
|
|||||||
+6
@@ -145,6 +145,12 @@ public class AtomicfuJsIrTestGenerated extends AbstractAtomicfuJsIrTest {
|
|||||||
runTest("plugins/atomicfu/atomicfu-compiler/testData/box/TopLevelTest.kt");
|
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
|
@Test
|
||||||
@TestMetadata("UncheckedCastTest.kt")
|
@TestMetadata("UncheckedCastTest.kt")
|
||||||
public void testUncheckedCastTest() throws Exception {
|
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"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user