reference, declarationdefinition
definition → references, declarations, derived classes, virtual overrides
reference to multiple definitions → definitions
unreferenced
    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
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
From: Chris Lattner <[email protected]>
To: "Vikram S. Adve" <[email protected]>
Subject: Re: LLVM Feedback

I've included your feedback in the /home/vadve/lattner/llvm/docs directory
so that it will live in CVS eventually with the rest of LLVM.  I've
significantly updated the documentation to reflect the changes you
suggested, as specified below:

> We should consider eliminating the type annotation in cases where it is
> essentially obvious from the instruction type:
>        br bool <cond>, label <iftrue>, label <iffalse>
> I think your point was that making all types explicit improves clarity
> and readability.  I agree to some extent, but it also comes at the
> cost of verbosity.  And when the types are obvious from people's
> experience (e.g., in the br instruction), it doesn't seem to help as
> much.

Very true.  We should discuss this more, but my reasoning is more of a
consistency argument.  There are VERY few instructions that can have all
of the types eliminated, and doing so when available unnecessarily makes
the language more difficult to handle.  Especially when you see 'int
%this' and 'bool %that' all over the place, I think it would be
disorienting to see:

  br %predicate, %iftrue, %iffalse

for branches.  Even just typing that once gives me the creeps. ;)  Like I
said, we should probably discuss this further in person...

> On reflection, I really like your idea of having the two different
> switch types (even though they encode implementation techniques rather
> than semantics).  It should simplify building the CFG and my guess is it
> could enable some significant optimizations, though we should think
> about which.

Great.  I added a note to the switch section commenting on how the VM
should just use the instruction type as a hint, and that the
implementation may choose altermate representations (such as predicated
branches).

> In the lookup-indirect form of the switch, is there a reason not to
> make the val-type uint?

No.  This was something I was debating for a while, and didn't really feel
strongly about either way.  It is common to switch on other types in HLL's
(for example signed int's are particularly common), but in this case, all
that will be added is an additional 'cast' instruction.  I removed that
from the spec.

> I agree with your comment that we don't need 'neg'

Removed.

> There's a trade-off with the cast instruction:
>  +  it avoids having to define all the upcasts and downcasts that are
>     valid for the operands of each instruction  (you probably have
>     thought of other benefits also)
>  -  it could make the bytecode significantly larger because there could
>     be a lot of cast operations

 + You NEED casts to represent things like:
    void foo(float);
    ...
    int x;
    ...
    foo(x);
   in a language like C.  Even in a Java like language, you need upcasts
   and some way to implement dynamic downcasts.
 + Not all forms of instructions take every type (for example you can't
   shift by a floating point number of bits), thus SOME programs will need
   implicit casts.

To be efficient and to avoid your '-' point above, we just have to be
careful to specify that the instructions shall operate on all common
types, therefore casting should be relatively uncommon.  For example all
of the arithmetic operations work on almost all data types.

> Making the second arg. to 'shl' a ubyte seems good enough to me.
> 255 positions seems adequate for several generations of machines

Okay, that comment is removed.

> and is more compact than uint.

No, it isn't.  Remember that the bytecode encoding saves value slots into
the bytecode instructions themselves, not constant values.  This is
another case where we may introduce more cast instructions (but we will
also reduce the number of opcode variants that must be supported by a
virtual machine).  Because most shifts are by constant values, I don't
think that we'll have to cast many shifts.  :)

> I still have some major concerns about including malloc and free in the
> language (either as builtin functions or instructions).

Agreed.  How about this proposal:

malloc/free are either built in functions or actual opcodes.  They provide
all of the type safety that the document would indicate, blah blah
blah. :)

Now, because of all of the excellent points that you raised, an
implementation may want to override the default malloc/free behavior of
the program.  To do this, they simply implement a "malloc" and
"free" function.  The virtual machine will then be defined to use the user
defined malloc/free function (which return/take void*'s, not type'd
pointers like the builtin function would) if one is available, otherwise
fall back on a system malloc/free.

Does this sound like a good compromise?  It would give us all of the
typesafety/elegance in the language while still allowing the user to do
all the cool stuff they want to...

>  'alloca' on the other hand sounds like a good idea, and the
>  implementation seems fairly language-independent so it doesn't have the
>  problems with malloc listed above.

Okay, once we get the above stuff figured out, I'll put it all in the
spec.

>  About indirect call:
>  Your option #2 sounded good to me.  I'm not sure I understand your
>  concern about an explicit 'icall' instruction?

I worry too much.  :)  The other alternative has been removed. 'icall' is
now up in the instruction list next to 'call'.

> I believe tail calls are relatively easy to identify; do you know why
> .NET has a tailcall instruction?

Although I am just guessing, I believe it probably has to do with the fact
that they want languages like Haskell and lisp to be efficiently runnable
on their VM.  Of course this means that the VM MUST implement tail calls
'correctly', or else life will suck.  :)  I would put this into a future
feature bin, because it could be pretty handy...

>  A pair of important synchronization instr'ns to think about:
>    load-linked
>    store-conditional

What is 'load-linked'?  I think that (at least for now) I should add these
to the 'possible extensions' section, because they are not immediately
needed...

> Other classes of instructions that are valuable for pipeline
> performance:
>    conditional-move            
>    predicated instructions

Conditional move is effectly a special case of a predicated
instruction... and I think that all predicated instructions can possibly
be implemented later in LLVM.  It would significantly change things, and
it doesn't seem to be very necessary right now.  It would seem to
complicate flow control analysis a LOT in the virtual machine.  I would
tend to prefer that a predicated architecture like IA64 convert from a
"basic block" representation to a predicated rep as part of it's dynamic
complication phase.  Also, if a basic block contains ONLY a move, then
that can be trivally translated into a conditional move...

> I agree that we need a static data space.  Otherwise, emulating global
> data gets unnecessarily complex.

Definitely.  Also a later item though.  :)

> We once talked about adding a symbolic thread-id field to each
> ..
> Instead, it could a great topic for a separate study.

Agreed.  :)

> What is the semantics of the IA64 stop bit?

Basically, the IA64 writes instructions like this:
mov ...
add ...
sub ...
op xxx
op xxx
;;
mov ...
add ...
sub ...
op xxx
op xxx
;;

Where the ;; delimits a group of instruction with no dependencies between
them, which can all be executed concurrently (to the limits of the
available functional units).  The ;; gets translated into a bit set in one
of the opcodes.

The advantages of this representation is that you don't have to do some
kind of 'thread id scheduling' pass by having to specify ahead of time how
many threads to use, and the representation doesn't have a per instruction
overhead...

> And finally, another thought about the syntax for arrays :-)
>  Although this syntax:
>         array <dimension-list> of <type>
>  is verbose, it will be used only in the human-readable assembly code so
>  size should not matter.  I think we should consider it because I find it
>  to be the clearest syntax.  It could even make arrays of function
>  pointers somewhat readable.

My only comment will be to give you an example of why this is a bad
idea.  :)

Here is an example of using the switch statement (with my recommended
syntax):

switch uint %val, label %otherwise, 
       [%3 x {uint, label}] [ { uint %57, label %l1 }, 
                              { uint %20, label %l2 }, 
                              { uint %14, label %l3 } ]

Here it is with the syntax you are proposing:

switch uint %val, label %otherwise, 
       array %3 of {uint, label} 
              array of {uint, label}
                              { uint %57, label %l1 },
                              { uint %20, label %l2 },
                              { uint %14, label %l3 }

Which is ambiguous and very verbose. It would be possible to specify
constants with [] brackets as in my syntax, which would look like this:

switch uint %val, label %otherwise,
       array %3 of {uint, label}  [ { uint %57, label %l1 },
                                    { uint %20, label %l2 },
                                    { uint %14, label %l3 } ]

But then the syntax is inconsistent between type definition and constant
definition (why do []'s enclose the constants but not the types??).  

Anyways, I'm sure that there is much debate still to be had over
this... :)

-Chris

http://www.nondot.org/~sabre/os/
http://www.nondot.org/MagicStats/
http://korbit.sourceforge.net/