5 So as before, it would be much easier to handle aliasing on the stack and such
6 if aliases did not cross barriers of jumps. Basically this would mean that for
7 any target jump point, aliases must be removed and given allocations.
11 So get jump targets as I have done before.
15 Well, how about an alternative means. All operations for the most part take
16 place on the stack if they do anything. Basically, what if I instead
17 completely ignore the stack and locals. Basically instead those are
18 virtualized as stored values somewhere that get loaded and stored as needed.
19 Basically it would be a specific slot but it has a value history which can
20 change over time. When an operation is performed it just uses those value
21 inputs. Then for the most part, I completely ignore register allocations.
22 Variables will be marked for the source of their value. Essentially there
23 would be a timeline of values for each position. This would allow me to just
24 instead of having local variable copies and forced deallocations to just
25 use the timeline of values.
29 Basically, registers are allocated last. I basically just operate purely on
30 these value timelines. This would require a bit more memory for each position
31 but there would be marks to determine how things occur. So if there is a jump
32 back it would check the states for where values originate from. If the value
33 is different then it will split the timeline which forces an actual copy
34 operation to be performed. The same can be done for exception handlers where
35 there are no stack values except what was thrown. Then after all the splitting
36 is known, registers are allocated and extra load/store operations are added
37 as needed. This would require iterations into the past however to determine
38 values. But there could be derivation and references to the actual value.
39 Basically, I need to make splitting cheap. Exception handlers would
40 essentially just be the same as jumps with conditional branches. So if there
41 is an exception ready it will just check if it is not null then check where to
42 go depending on the exception. This could generate a bunch of duplicate code
43 however. But in this case, exceptions are the same as normal code. This means
44 though that exception checks need to be performed after almost every operation
45 for the most part. But only ones which can generate exceptions.
49 With the timeline I do not need to worry about jump backs. Jump forwards can
50 be a problem though. However, the split point is in the future. Logically it
51 is handled the same. So basically for every local and stack entry, I will need
52 a timeline which encompasses the entire code. Initially the timeline would be
53 blank and nothing would have values. However, with the stack map table and
54 such there would be explicit no values and explcit some values. The stack map
55 table is used for all future and exceptional jumps.
59 Or maybe I am just thinking too into this. I suppose what I can do is have a
60 naive compiler which translates the byte code directly to machine code without
61 optimization. That way I can get the interpreter working to execute the native
62 code to see if things work. Basically it would be the ugliest and horribly
63 optimized machine code ever. Methods would be gigantic and there would be lots
64 of wasteful operations. But I can write it in a way where I can replace it
65 with an optimizing variant. I suppose the initial thing would be to parse all
66 of the operations and build some kind of execution graph then go through all
67 of it to optimize it in a future pass. So I basically only have to write the
68 code generator once, but future work is done in an optimization pass. So the
69 first step is always naive. This is less efficient but it is easier to have a
70 naive setup in the first place.
74 For saved and temporary, I would need save operations.
78 Maybe instead, I turn the byte code from stack based to be register based with
79 expanded exception handling and checking. Basically I just change the
80 interpretation of it. It becomes completely flat and there is aliasing of
81 values. This would be the secondary intermediate code. Then I can turn that
82 into actual machine code and perform allocations as needed. I think that route
83 would be the easiest. A basic linear function. So morphing the byte code to
84 another set of instructions. Then I do not need to worry about exceptional
85 jumps because they are just branches as needed. This means that also variables
86 will need extra meta data, such as the length of arrays when they are read.
87 This way, some array length checks can be removed potentially.