[code-reflection] RFR: Remove bytecode dialect [v14]
Adam Sotona
asotona at openjdk.org
Mon Feb 12 09:13:07 UTC 2024
On Fri, 9 Feb 2024 13:42:22 GMT, Adam Sotona <asotona at openjdk.org> wrote:
>> This patch re-implements `BytecodeLift` to lift from bytecode directly to the core dialect
>> and removes `BytecodeInstructionOps` with the bytecode dialect.
>>
>> Implementation is complete up to the point of passing all current tests.
>>
>> Please review.
>>
>> Thanks,
>> Adam
>
> Adam Sotona has updated the pull request incrementally with one additional commit since the last revision:
>
> Reverted some locals-related changes
Unfortunately, stack maps do not identify all code blocks as we need it. For example try-start or try-end blocks may or may not have attached stack frames. Conditional branch imply only stack frame for the target block, however not for the follow-through code. In general - stack maps do not provide complete information, so using them still requires a secondary mechanism to get full information about all blocks. The flow traversal as secondary mechanism is 100% reliable, so I made it primary and we do not need the stack maps at all.
The flow traversal of javac-generated code is a single pass from bytecode offset 0 down to the end of the bytecode array. At least I'm not aware of any back-jumps, dead bytecode, or catch handlers preceding try blocks generated by javac. So for javac-generated classes we can be fine with a single pass `for` loop analysis.
However it is possible to generate a valid bytecode with dead code blocks, back calls and catch handlers before the relevant try blocks. Flow analysis of such bytecode does not know stack and locals to enter these "ugly" blocks in the first pass. The simplest way is to skip them for the actual pass and mark them to follow later.
Dead code blocks however are never entered, no matter how many passes we use. I've added a simple mechanism to count progress in each pass and so to identify the remaining instructions are certainly dead (there is no entry to them).
Alternative implementation using two step translation would require:
1. out-of-order translation from bytecode to an intermediate form of (not completely configured) blocks + auxiliary structures to store their instructions + global flow representation (identifying also which block is a try start and try end, and what are their nesting order and relations).
2. flow-ordered initialization of block parameters, filling all ops, and blocks interconnections based on the intermediate auxiliary structures.
Single-step flow analysis seems to be safer, as the instructions are the only source of the truth.
Because we construct blocks only when we reach them during flow analysis - the actual stack is the only source of the truth. If we obtain next block from the map (as previously initialized from another entry point) - we currently assume the stack match the block parameters, or we can implement a physically verifyication of the stack match at that point.
Local variables are a bit different beast. Currently we handle them simply and correctly for the most cases. We handle overrides of the local variable slots by assigning a new variable - which works well for the most cases.
We do not handle correctly only one corner case: Twice overridden variable value needs to be merged back to the original variable in some (extreme) cases. I'm thinking about possible solution, however it is really a corner case.
-------------
PR Comment: https://git.openjdk.org/babylon/pull/16#issuecomment-1938281417
More information about the babylon-dev
mailing list