Welcome to mirror list, hosted at ThFree Co, Russian Federation.

jit-thoughts « docs - github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 6295c73c38c6e699b4219beb2e30e7c3169e1249 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
Just some thoughts for the JITer:

General issues:
===============

We are designing a JIT compiler, so we have to consider two things:

- the quality of the generated code
- the time needed to generate that code

The current approach is to keep the JITer as simple as possible, and thus as
fast as possible. The generated code quality will suffer from that.

We do not map local variables to registers at the moment, and this makes the
whole JIT much easier, for example we do not need to identify basic block
boundaries or the lifetime of local variables, or select the variables which
are worth to put into a register.

Register allocation is thus done only inside the trees of the forest, and each
tree can use the full set of registers. We simply split a tree if we get out of
registers, for example the following tree:


              add(R0)
             /   \
            /     \
           a(R0)  add(R1)
                 /   \
                /     \
               b(R1)  add(R2)
                     /   \
                    /     \
                   c(R2)   b(R3)

can be transformed to:


       stloc(t1)         add(R0)
         |              /   \
         |             /     \
        add(R0)       a(R0)  add(R1)
       /   \                /   \
      /     \              /     \
     c(R0)   b(R1)        b(R1)  t1(R2)


Please notice that the split trees use less registers than the original
tree. 


Register Allocation:
====================

With lcc you can assign a fixed register to a tree before register
allocation. For example this is needed by call, which return the value always
in EAX on x86. The current implementation works without such system, due to
special forest generation.


X86 Register Allocation:
========================

We can use 8bit or 16bit registers on the x86. If we use that feature we have
more registers to allocate, which maybe prevents some register spills. We
currently ignore that ability and always allocate 32 bit registers, because I
think we would gain very little from that optimisation and it would complicate
the code.

Different Register Sets:
========================

Most processors have more that one register set, at least one for floating
point values, and one for integers. Should we support architectures with more
that two sets? Does someone knows such an architecture?

64bit Integer Values:
=====================

I can imagine two different implementation. On possibility would be to treat
long (64bit) values simply like any other value type. This implies that we
call class methods for ALU operations like add or sub. Sure, this method will
be be a bit inefficient.

The more performant solution is to allocate two 32bit registers for each 64bit
value. We add a new non terminal to the monoburg grammar called long_reg. The
register allocation routines takes care of this non terminal and allocates two
registers for them.


Forest generation:
==================

It seems that trees generated from the CIL language have some special
properties, i.e. the trees already represents basic blocks, so there can be no
branches to the inside of such a tree. All results of those trees are stored to
memory.

One idea was to drive the code generation directly from the CIL code, without
generating an intermediate forest of trees. I think this is not possible,
because you always have to gather some attributes and attach it to the
instruction (for example the register allocation info). So I thing generating a
tree is the right thing and that also works perfectly with monoburg. IMO we
would not get any benefit from trying to feed monoburg directly with CIL
instructions. 

DAG handling:
=============

Monoburg can't handle DAGs, instead we need real trees as input for
the code generator. So we have two problems:

1.) DUP instruction: This one is obvious - we need to store the value
into a temporary variable to solve the problem.

2.) function calls: Chapter 12.8, page 343 of "A retargetable C compiler"
explains that: "because listing a call node will give it a hidden reference
from the code list". I don't understand that (can someone explain that?), but
there is another reason to save return values to temporaries: Consider the
following code:

x = f(y) + g(z); // all functions return integers

We could generate such a tree for this expression: STLOC(ADD(CALL,CALL))

The problem is that both calls returns the value in the same register,
so it is non trivial to generate code for that tree. We must copy one
register into another one, which make register allocation more complex.
The easier solution is store the result of function calls to
temporaries. This leads to the following forest:

STLOC(CALL)
STLOC(CALL)
STLOC(ADD (LDLOC, LDLOC))

This is what lcc is doing, if I understood 12.8, page 342, 343?

Value Types:
============

The only CLI instructions which can handle value types are loads and stores,
either to local variable, to the stack or to array elements. Value types with a
size smaller than sizeof(int) are handled like any other basic type. For other
value types we load the base address and emit block copies to store them.