From: Chris Lattner <sabre@nondot.org> To: "Vikram S. Adve" <vadve@cs.uiuc.edu> 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 unnecesarily 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 particually 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. Definately. 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/