Open eupp opened 1 year ago
@ndkoval @alefedor do you remember what parts exactly require bytecode transformation? Do you have any ideas how to isolate them?
Hi @eupp !
Currently, there is no bytecode generation for init and post parts.
They are handled in ParallelThreadsRunner
using Java Reflections.
https://github.com/JetBrains/lincheck/blob/2dcaa9acb11790a4b96d36b810862f6aa78cc837/src/jvm/main/org/jetbrains/kotlinx/lincheck/runner/ParallelThreadsRunner.kt#L233
So the reason why lincheck fails with the thrown exception is that executeActor
does not try to wrap any thrown exception on method.invoke(...)
.
As for TestThreadExecutionGenerator
, it is possibly to re-write most byte-code generation to Kotlin code.
I did it the another project. The idea is to write most of the method in Kotlin code and then replace some "stub" invocations with transformed bytecode. It looked like this
override fun call() {
INITIALIZATION_STUB()
...
val timeSpent = measureNanoTime {
for (operationId in operationIds) {
RUN_OPERATION_STUB(operationId)
}
}
...
FINALIZATION_STUB()
return timeSpent
}
Though you still have to do in the transformed byte-code the following:
One part that may be tricky to implement this way is expection handling, since you can directly catch
expected exceptions only in the transformed code but we want this logic in Kotlin code. This issue can be bypassed if we catch all exceptions in Kotlin code and then check whether they are instances of expected exception classes
Hi @alefedor, thank you for clarification!
I like the approach with stub functions. It allows to implement the class "template" in plain Kotlin code, making it easy to read and maintain.
With respect to that, my question is what parts exactly we need to stub and generate? The part that executes actor? We need to generate custom bytecode for it to avoid boxing arguments and result of primitive types?
As for handling exceptions, wouldn't the #181 solve the issues with them by unifying expected/unexpected exceptions handling logic? (cc @avpotapov00)
@eupp
what parts exactly we need to stub and generate? The part that executes actor?
Yes, it seems that only the part that generates actors.
Then, the transformer replaces i-th call of a stub method with i-th actor in the scenario. Boxing is not an issue for arguments. Primitive types are pushed directly in the bytecode, non-primitive ones are stored in an array during transformation and then loaded in the transformed code. For simplicity, the result should always be wrapped in byte-code to be Lincheck Result
.
wouldn't the https://github.com/JetBrains/lincheck/pull/181 solve the issues with them by unifying expected/unexpected exceptions handling logic
Yes, this pull request addresses the issue by handling Java Reflection invocations.
Boxing is not an issue for arguments.
I meant that boxing is the problem we are trying to solve with bytecode generation, isn't it? Otherwise we could just invoke actors directly in the Kotlin code. What are other reasons why we generate custom bytecode here?
I meant that boxing is the problem we are trying to solve with bytecode generation, isn't it?
That's actually a very good question. Indeed, byte-code generation helps to prevent boxing and overhead of Java Reflections. Since these costs are present in every invocation, we need to get rid of them if they are the bottleneck
Currently, subclasses of
TestThreadExecution
class, which are used to run scenario threads, are generated from bytecode (seeTestThreadExecutionGenerator
class). It makes it hard to maintain and modify the execution scenario running logic (I recently faced this issue in #146). However, it looks like most part of the logic can actually be implemented as a plain Kotlin code. Some examples:Strategy
and performed insideonActorStart
method;Strategy
(we can consider addingonActorEnd
method for this purpose).