For some simplicity the code parser can handle generation of operation data, one that provides stack operations and opcode arguments.
Actually for image downscaling with weights, say a region to downscale has a bunch of colors in it. For example it is mostly white. My previous plan was if that there were even a single pixel of the border color then it would be border colored. However this may make things a bit ugly especially when it comes to borders and such.
NCPOp to work better, I will need to know the potential verification
state of the operation. However, if I ignore this then I can have the states
be a bit implicit so to speak. When I go through the actual decoded operations
I can determine if the stack states are correct and such. By then I would know
the start locations of all basic blocks for example. However the verification
state could still be indicated in the operation, using a similar means of
setting the data like I have done before.
Going to look at the statistics for this repository.
I must learn how to draw my own mascot.
The question is however, do all instructions require basic blocks to be
created for them? The basic blocks only cause stack entries to potentially be
cached. So instructions like
new need it since that instruction can
initialize classes. Also
new would likely be implemented as a method call to
an allocator. Instructions such as array store would not need them, but
potentially array loads would. However if an array is stored then it just
consumes temporary stack items. If an exception is thrown before the actual
store (out of bounds or
null) then nothing is done. The same happens for load
also. However other operations such as put of static and get of static should
be in their own basic blocks since they could initialize classes for example.
Thinking about it, a large number of instructions do not need explicit basic blocks at all. I can pretty much keep it limited to instructions after method invocations and such and the targets of jumps.
Exception handlers only care about local variables and the stack items are all temporary anyway. When a method is called, arguments are popped from the stack. If I start a basic block on the invocation then all stack items are saved and then they are saved again before the method call. So to think about it, are basic blocks even needed at all? All stack items are essentially temporary. If I calculate the jump targets, arguments, and verification info for operations, I should then be able to just directly create executable code. As stated before, locals would be written to immedietly for simplicity while stack items would remain temporary. When a method call is performed, I would just store the local variables and any stack items which were not popped and move the values over so the calling convention is statisfied. Provided I verify that the states are correct for the instructions things can get a bit simplified. However, thinking about that. I do need basic blocks because of jump targets. I only need to start a new basic block when execution flows to another basic block, which saves the existing stack items. Doing it this way would reduce the complexity of requiring SSA magic going backwards and setting up phi functions for locals and stack items. So essentially it would just be a simple register storage mechanism. Each basic block could then have its own register mapping and stack mappings. So say for example a basic block jumps to another and the registers/stack entries which are associated with the stack are not exactly the same they would be moved around. Then if I follow the write local immedietly rule, then that means I do not have to have a new basic block after a method call or similar.
Then as before, operations such as push and pop would just have a basic "copy". If the end of a basic block is reached then the copy is stored on the stack as needed. The copy would not actually be set in a temporary register unless a basic block boundary was reached. Then once flow passes to a new basic block, all temporary register assignments are reset and such. For simplicity with exception handlers, local variables would be bound to the same locations.
So perhaps instead of a code parser, I would instead have a byte code
representation. Setup an immutable class which is given the code attribute and
can setup operations for everything immutably. This representation would
ONLY concern itself with byte code, it does not execute it, it just
describes how it works. Then there is a code generator which takes this
representation and runs the code so that it creates a
NRProgram with its
basic blocks and such. The byte code representation would still have to save
on memory by caching things and reading in things as needed however.
Then the byte code could be used for debugging such as
javap or if a JAR file
exists and there is an exception and a debugger wants to be attached or
similar. Rather than just looking at native code it can actually see the
byte code that it is mapped to potentially.
This should in general result in much cleaner code.