Correct spelling.
[SquirrelJME.git] / assets / developer-notes / stephanie-gawroriski / 2016 / 03 / 23.mkd
blob47d0c364da12cabf2c384c462797e83a392cdd7a
1 # 2016/03/23
3 ## 00:03
5 For a single pass system with no allocations for temporary byte code that is
6 read, one problem I will encounter during translation is exceptions. One thing
7 I do know about exceptions though, is that the stack is wiped away yet the
8 locals are kept the same. The exception handler can never be the first
9 operation. On entry of the method, there are just the locals and no stack.
10 Exception handlers get a single stack element containing the exception which
11 was thrown. Also, each instruction can only have a single stack state also.
12 Thus if some byte code starts modifying an object on the stack, then it is
13 likely an exception handler. The only thing to determine though are future
14 `goto` operations going to these locations with matching stacks.
16 ## 00:09
18 So with the first instruction never being an exception is a rule. I would
19 just flow normally after that and make sure elements are valid and such. There
20 can also be a heuristic to guess code which is likely an exception handler.
21 If it is alone for example. If code is reached which is never reachable by
22 normal flow of execution then it is likely an exception handler. The only
23 case of it not being so would be if a goto were just tossed to a future
24 address, which then jumps back to the given point. So for unknown states I
25 will have to allocate some buffers to store byte code temporarily and defer it
26 to until the exception handler table is read. If a method is very simple then
27 this will never occur. Unknown states would only be for code outside the
28 starting program flow.
30 ## 10:54
32 Ok, so the goal with byte code parsing is that it needs to be light and simple.
33 There are many operations and a large number of them are calculated similarly
34 such as when stack operations are performed. So what I am thinking of is having
35 the operations stored in a simple text file with their stack opertions and
36 such. However, alternatively I could just make a gigantic switch statement.
37 The switch would be more inline so to speak. I could use reflection and cache
38 the classes, then each byte code operation would be in its own class and such.
39 I can do this because I can call `Class.forName()` and call `newInstance()`.
40 However, they would need to be interfaces and I would also need a bridging
41 object. The opcode handlers would be single instance, however they would also
42 be a lookup table that can be static so to speak. Doing it that way I do not
43 have to clutter the class with details, or have gigantic enumerations which
44 are pre-exist in memory. The only time I need an operation is when I am
45 handling it.
47 ## 12:52
49 I should set references to `null` when I am done with them to indicate that I
50 no longer care for them and they can be freed as such. Although the compiler
51 can determine this and this is likely a micro premature optmization. On
52 second thought, in my code generation if a local or stack element ever stops
53 being a references, I can just null it in the code.
55 ## 13:11
57 However, thinking about it, having 255 classes would be a bit messy, I could
58 just instead have 255 methods instead. However, since I cannot have function
59 pointers essentially with method handles, I need a giant switch statement.
60 The `tableswitch` instruction can be turned into a binary tree since it is
61 sorted. That would reduce the total number of comparisons needed. That however
62 is if code cannot internally use a table. If the target architecture can for
63 example use pointers, then it just essentially would become a local table
64 lookup followed by a jump based on an offset. Thinking about it however, the
65 NARF code would complicate handling of `tableswitch` due to how the
66 instructions are laid out. So perhaps instead I should have a linear generation
67 pass directly to native code. Essentially it would be close to the Java byte
68 code except be in native form. It would not have the best optimization
69 however due to translation between a stack machine and a register one. However
70 for that I would need to load in the byte code, read the exception table, and
71 then verify stack map locations. But using the known rules I know about earlier
72 I could ignore some things. I could do a basic linear parsing, with notes so
73 to speak.
75 ## 13:24
77 So what the engine needs is a way to be told if it is interpreting or if it is
78 performing native compilation. Basically a code operation handler of sorts.
80 ## 13:26
82 Well, I would want an interpreter which is optimized as interpreting Java
83 byte code can be really slow due to potentially wasted stack operations and
84 needing the constant pool to work correctly. So the interpreter engine should
85 be the core of the code generation engine since it already has all of the code
86 and logic to do such things.
88 ## 14:33
90 Considering that the code parser if it gets all the methods, the file will
91 essentially be probably a 5000 line behemoth. So I suppose the seprated class
92 would be a good thing because all that code would be elsewhere instead.
94 ## 15:40
96 `__ChunkedInputStream__` must change to provide byte offsets and then it can
97 be used for the code parser to determine the address of the current operation
98 being handled.
100 ## 18:17
102 I need a class which can handle the state that things are on the locals and
103 stack. The basic requirement is that the types exist (can use bit fields for
104 that). The more advanced requirement for any optimization is I need to know
105 how new or stale a value is, or if it gets mutated. Basically after a value is
106 set, how many times has it been updated before it was erased.
108 ## 18:34
110 If I use the register types, I only need a single bit that I can use for
111 something else. That is if I keep the alignment of types at 4 bits. From the
112 looks of it the NARF package is going to go away and will likely be merged
113 into the interpreter code.
115 ## 18:37
117 One thing I can actually do for low memory systems is have hollow arrays. So
118 for example if you only have 32KiB of RAM and you allocate an integer array
119 with 2 billion elements, the allocation will succeed. Once you start writing
120 enough into it (where you cannot fit any more) there will be an out of memory
121 error thrown during the write. This does have a speed cost though, so it should
122 only be used when it is needed. There can also be a cost of extra memory
123 also. But the complexity cost of it might not be worth it. I am definitely
124 going to use handles so that I can perform compaction (move everything down)
125 without worrying too much about pointer reassignment. With any classes and
126 their data structures in memory, I will need to be efficient with the stuff
127 which I use in the heap. One idea I can do is actual merging of values. For
128 example if a class defines 8 non-final boolean fields, they can be combined
129 into a single byte. However if one is volatile then they all become volatile.
130 This would work, however there would need to be some kind of lock because if
131 multiple threads are changing the values then other booleans could lose their
132 state. So if an architecture has an atomic clear and set of a bit it could
133 be used. Otherwise I would fall back to wasting 7-bits. However, I can perform
134 this optimization on final fields no problem.
136 ## 23:22
138 Something I can have in the stack data, would be to have a unique value of
139 sorts. That unique value can be synced between the local variables and the
140 stack and could be used for optimization and such. So if I load a local
141 variable onto the stack, they both have the same unique ID. That ID though
142 will just have to be managed by the program so things are not out of sync
143 when lots of code changes them.
145 ## 23:27
147 I can also use a kind of directly loaded setup and register allocation kind of
148 thing too. For example instead of generating code for a push to the stack from
149 a local, I can have an alias which is indicated to by the unique ID and
150 another marker that says that it is currently allocated to a given register.