lins@apple.com (Chuck Lins) (04/30/91)
Execution units are the enforcement agents of the RISC revolution. Does anyone know how nested procedures affect the ability to pass parameters via registers? If there was no up-level access everything would work fine, but with this facility you get all sorts of problems. Uplevel access would also seem to affect dataflow analysis (the compiler could think that a variable is 'dead' when in reality it's going to get accessed by a nested local procedure. Maybe all the potential hair is why Pascal and Modula-2 compiler writers just pass all parameters via the stack. Are there (nice) solutions to these problems. References welcome. Thanks in advance from, Chuck Lins lins@apple.com [Can one reference a named parameter in an enclosing procedure? If so, I can imagine that would force them onto the stack. -John] -- Send compilers articles to compilers@iecc.cambridge.ma.us or {ima | spdcc | world}!iecc!compilers. Meta-mail to compilers-request.
zlsiial@cms.manchester-computing-centre.ac.uk (A. V. Le Blanc) (04/30/91)
The ETH Pascal 6000 compiler, at least the versions I used, passed the first four arguments to procedures and functions in registers. It was also possible to change this for certain procedures or functions, either increasing or decreasing the number of parameters passed. A. V. Le Blanc Computing Centre University of Manchester ZLSIIAL@uk.ac.mcc.cms -- Send compilers articles to compilers@iecc.cambridge.ma.us or {ima | spdcc | world}!iecc!compilers. Meta-mail to compilers-request.
mike@yalla.tuwien.ac.at (Michael K. Gschwind) (04/30/91)
In article <1991Apr30.022048.4539@iecc.cambridge.ma.us> lins@apple.com (Chuck Lins) writes: >Does anyone know how nested procedures affect the ability to pass parameters >via registers? ... Well, there is an easy solution: just generate a list of all variables used by nested procedures - then you can store these (AND ONLY THESE, they are bound to be very few) in well-known stack locations. All other variables can be kept in registers and pronounced dead. In this set-up, a variable is dead, if *) it is dead in the procedure *) and +) either no nested procedures are called +) or the varaible is dead upon entry in this procedure There are two possibilities to act, if you know a parameter is going to be referenced in nested procedures. Either push it on the stack upon function entry or only if you're calling a nested procedure which uses it (You will need the transitive closure, I guess) - the latter mey be preferable in some circumstances, but the fromer is easier to implement: Just search the nested functions and check if the parameter is ever used. If so, reserve a stack slot and push immediately. PROCEDURE x( a, b, c ,d : integer) ; PROCEDURE y ( e,f,g : real); BEGIN write (a+f); END BEGIN if (d = 0) y(a,b,c); else write (a); END You either do: _x: /* a into well-know location, no need to save b,c,d */ st rA, fp+_a test rD jne @L1 <call y> return @L1: <write> return Or: _x: test rD jne @L1 /* a into well-know loc */ st rA, fp+_a <call y> return @L1: <write> return [Can one reference a named parameter in an enclosing procedure? If so, I can imagine that would force them onto the stack. -John] I dunno, but I guess so. Shouldn't be any complication in any case. Maybe somebody could check out the relevant facts? Register allocation across all nested functions is *) impossible in Pascal (since nested procedures may be transmitted via PROCEDURE parameters to non-related functions) and *) (I guess) not worthwhile in Modula-2 (where only top-level functions may be assigned to procedure-type variables - thus you can have a fall-back strategy for all top-level functions and thus restrict register allocation to a top-level function and all its nested functions) except if used with a global register allocation algorithm. BTW, does anybody know about compilers trying to find 'optimal' register assignment for top-level Modula-2 functions and all nested functions? Register allocation could probably done quite easily for such a block of related functions, as the only entry point from without this block is the top-level function. Thus the compiler knows all the places where the nested functions are called. Michael K. Gschwind, Dept. of VLSI-Design, Vienna University of Technology mike@vlsivie.tuwien.ac.at || mike@vlsivie.uucp Voice: (++43).1.58801 8144 || Fax: (++43).1.569697 -- Send compilers articles to compilers@iecc.cambridge.ma.us or {ima | spdcc | world}!iecc!compilers. Meta-mail to compilers-request.
mike@vlsivie.tuwien.ac.at (Michael K. Gschwind) (04/30/91)
In article <1991Apr30.022048.4539@iecc.cambridge.ma.us> lins@apple.com (Chuck Lins) writes: >Does anyone know how nested procedures affect the ability to pass parameters >via registers? ... Well, there is an easy solution: just generate a list of all variables used by nested procedures - then you can store these (AND ONLY THESE, they are bound to be very few) in well-known stack locations. All other variables can be kept in registers and pronounced dead. In this set-up, a variable is dead, if *) it is dead in the procedure *) and +) either no nested procedures are called +) or the varaible is dead upon entry in this procedure There are two possibilities to act, if you know a parameter is going to be referenced in nested procedures. Either push it on the stack upon function entry or only if you're calling a nested procedure which uses it (You will need the transitive closure, I guess) - the latter mey be preferable in some circumstances, but the fromer is easier to implement: Just search the nested functions and check if the parameter is ever used. If so, reserve a stack slot and push immediately. PROCEDURE x( a, b, c ,d : integer) ; PROCEDURE y ( e,f,g : real); BEGIN write (a+f); END BEGIN if (d = 0) y(a,b,c); else write (a); END You either do: _x: /* a into well-know location, no need to save b,c,d */ st rA, fp+_a test rD jne @L1 <call y> return @L1: <write> return Or: _x: test rD jne @L1 /* a into well-know loc */ st rA, fp+_a <call y> return @L1: <write> return [Can one reference a named parameter in an enclosing procedure? If so, I can imagine that would force them onto the stack. -John] I dunno, but I guess so. Shouldn't be any complication in any case. Maybe somebody could check out the relevant facts? Register allocation across all nested functions is *) impossible in Pascal (since nested procedures may be transmitted via PROCEDURE parameters to non-related functions) and *) (I guess) not worthwhile in Modula-2 (where only top-level functions may be assigned to procedure-type variables - thus you can have a fall-back strategy for all top-level functions and thus restrict register allocation to a top-level function and all its nested functions) except if used with a global register allocation algorithm. BTW, does anybody know about compilers trying to find 'optimal' register assignment for top-level Modula-2 functions and all nested functions? Register allocation could probably done quite easily for such a block of related functions, as the only entry point from without this block is the top-level function. Thus the compiler knows all the places where the nested functions are called. Michael K. Gschwind, Dept. of VLSI-Design, Vienna University of Technology mike@vlsivie.tuwien.ac.at || mike@vlsivie.uucp Voice: (++43).1.58801 8144 || Fax: (++43).1.569697 -- Send compilers articles to compilers@iecc.cambridge.ma.us or {ima | spdcc | world}!iecc!compilers. Meta-mail to compilers-request.
preston@ariel.rice.edu (Preston Briggs) (04/30/91)
Chuck Lins <lins@apple.com> writes: >Does anyone know how nested procedures affect the ability to pass parameters >via registers? If there was no up-level access everything would work fine, >but with this facility you get all sorts of problems. Uplevel access would >also seem to affect dataflow analysis (the compiler could think that a >variable is 'dead' when in reality it's going to get accessed by a nested >local procedure. One approach would be to pass the first few parameters in registers. However, in the called routine, it may be necessary to store the parameters on the stack, in standard locations. Imagine we have 3 procedures: A, B, and C, where C is nested inside B. When A calls B, it can pass (say) the first 4 suitable arguments in registers (where suitable means not a record or array). When C is compiled (it's compiled before B, since it's nested), we record which of B's parameters and variables are referenced. For each uplevel reference, we assume that the values are in some standard place on the stack. When compiling B, we must leave space on the stack for all the parameters. When we see a call to C, we know which variables and parameters might be accessed in C, and must emit code to store any "enregistered" variables into their home locations on the stack. Note that we must also recognize the effects of any procedures nested within C. The above version is "flow-insensitive" analysis, in that we assume that any reference can be reached and we don't bother about noticing whether DEFs come before USEs. It sounds slightly hairy, but really it's just setting a couple of flags per variable in the symbol table, and then following conventions while generating code. A more precise version would be "flow-sensitive". Here we do a little more work to recognize that a variable is always DEF'd before USE, and is therefore dead. This is all pretty informal and will take some effort to fill out the details. We're (probably) saved from the effort of complete call graph construction and interprocedural analysis by the fact that we're considering only lexically scoped procedures, without first-class procedures. Preston Briggs -- Send compilers articles to compilers@iecc.cambridge.ma.us or {ima | spdcc | world}!iecc!compilers. Meta-mail to compilers-request.
mike@taumet.com (Michael S. Ball) (04/30/91)
In article <1991Apr30.022048.4539@iecc.cambridge.ma.us> Chuck Lins <lins@apple.com> writes: >Does anyone know how nested procedures affect the ability to pass parameters >via registers? If there was no up-level access everything would work fine, >but with this facility you get all sorts of problems. Uplevel access would >also seem to affect dataflow analysis (the compiler could think that a >variable is 'dead' when in reality it's going to get accessed by a nested >local procedure. Actually, since you have the bodies of the nested procedures available before compiling the body of the enclosing procedure you have all of the data necessary to do a good job. Any register parameter which is used by uplevel reference is simply stored on the stack, just as it would be in a C function using varargs. I don't know that Pascal and Modula compilers pass parameters on the stack any more than C compilers do. My experience is that they use the calling sequence established by the execution environment. Of course with languages like Pascal which encourage internal linkage, compilers are able to establish their own sequences, and on machines like the VAX, where calls are expensive, they frequently do. You can do the same with C, but static functions seem to be rare. -- Michael S. Ball mike@taumet.com TauMetric Corporation (619)697-7607 -- Send compilers articles to compilers@iecc.cambridge.ma.us or {ima | spdcc | world}!iecc!compilers. Meta-mail to compilers-request.
ram+@cs.cmu.edu (Rob MacLachlan) (05/01/91)
>Does anyone know how nested procedures affect the ability to pass parameters >via registers? If there was no up-level access everything would work fine, >but with this facility you get all sorts of problems. In "mostly functional" langauges like Lisp and ML, the compiler usually only forces out to memory variables that may be side-effected. Variables that are initialized once and then never reassigned don't cause any problems, since the value can be freely copied. >Uplevel access would also seem to affect dataflow analysis (the compiler >could think that a variable is 'dead' when in reality it's going to get >accessed by a nested local procedure. A common approach is to find all variables that may be accessed uplevel by a particular function, and then pass those values as additional implicit arguments. This causes the uplevel copy of the variable to be live until you are done with it. Robert MacLachlan (ram+@cs.cmu.edu) -- Send compilers articles to compilers@iecc.cambridge.ma.us or {ima | spdcc | world}!iecc!compilers. Meta-mail to compilers-request.
mauney@eos.ncsu.edu (Dr. Jon Mauney) (05/02/91)
Most of the discussion has mentioned ways of forcing parameters passed in registers onto the stack for non-local reference. Note that it is not strictly necessary for such parameters to be on the stack. Since the references occur within internal functions, code could be compiled to use the value in the register. You would have to change the calling sequence so as to preserve the register, but you have control of both sides of the call, so that is possible. Conceivably, this could be done even if the procedure is passed as a parameter -- just make sure the register value is part of the closure that you pass. I'm not claiming it's worth the trouble... -- Jon Mauney, parsing partisan Computer Science Dept. N.C. State University -- Send compilers articles to compilers@iecc.cambridge.ma.us or {ima | spdcc | world}!iecc!compilers. Meta-mail to compilers-request.
mario@cs.man.ac.uk (Mario Wolczko) (05/02/91)
In article <1991Apr30.022048.4539@iecc.cambridge.ma.us>, lins@apple.com (Chuck Lins) writes: > Does anyone know how nested procedures affect the ability to pass parameters > via registers? ... This is a particularly gruesome problem in languages that have procedures whose closures are first-class objects, and can outlive their surrounding context. Smalltalk and Scheme are examples. In Smalltalk, for example, you can write: method: arg ... var := [ :x | x + arg]. ^var This returns a "block" (the expression in brackets; similar to a bound lambda-expression in Scheme) that references the argument in its enclosing method, despite the block outliving the method invocation. My Scheme is a little rusty, but I think it would be something like: (define method (arg) (lambda (x) (+ x arg))) The problem here is that the closure for the block/function needs to reference the activation record for "method", even when control has returned from "method". A naive implementation of this is to put every activation in the heap, and garbage collect dead ones [GR83]. Better is to put activation records on the stack, and defer conversion to heap form until absolutely necessary [DS84]. This gets trickier when your activations live in register windows [Ung87], which is where your question comes in. In the Smalltalk compiler I'm working on at the moment, one phase of the compiler attempts to locate variables which are genuinely shared between activations (including dataflow and liveness analysis), and then allocates a value holder for them in the heap. Otherwise they're truly local and can go in registers. I can supply more details if you're interested. It should be a lot simpler in languages with strict LIFO activations. Mario Wolczko [GR83] A. Goldberg, D. Robson, "Smalltalk-80: The Language and its Implementation," Addison-Wesley, 1983. [DS84] L. P. Deutsch, A. M. Schiffman, Efficient Implementation of the Smalltalk-80 System, ACM POPL11, 1984. [Ung87] D. M. Ungar, The Design and Evaluation of a High Performance Smalltalk System, MIT Press, 1987. Dept. of Computer Science Internet: mario@cs.man.ac.uk The University uucp: mcsun!ukc!man.cs!mario Manchester M13 9PL JANET: mario@uk.ac.man.cs U.K. Tel: +44-61-275 6146 (FAX: 6236) -- Send compilers articles to compilers@iecc.cambridge.ma.us or {ima | spdcc | world}!iecc!compilers. Meta-mail to compilers-request.
mike@vlsivie.tuwien.ac.at (Michael K. Gschwind) (05/04/91)
Dr. Jon Mauney writes: >Most of the discussion has mentioned ways of forcing parameters passed in >registers onto the stack for non-local reference. Note that it is not >strictly necessary for such parameters to be on the stack. Since the >references occur within internal functions, code could be compiled to use >the value in the register. You would have to change the calling sequence >so as to preserve the register, but you have control of both sides of the >call, so that is possible. Conceivably, this could be done even if the >procedure is passed as a parameter -- just make sure the register value is >part of the closure that you pass. > >I'm not claiming it's worth the trouble... It IS worth the trouble if you're doing global register allocation or at least register allocation relative to a top-level function and its nested procedures. IT IS (probably) NOT worth it if you're only trying to prevent forcing parameters on the stack because they are used in some nested procedures. You might of course find a simple algorithm which would not cost much, but to be really effective, you'd probably have to do extensive data flow analysis. Parameters used in nested procedures are (probably) too rare warrant such extensive data flow analysis. But if you're doing data flow analysis anyway, you'll probably get it for free. You can then probably apply the strategy adopted for local variables used in nested functions to parameters as well (or vice versa ;-). You write that "this could be done even if the procedure is passed as a parameter". This is not correct in the general case, and the case you line out, namely "just make sure the register value is part of the closure that you pass", is not feasable. Procedures passed as parameters can be called from just about any context, e.g., library functions, separately compiled modules etc., thus you cannot change the calling sequence. In the case of functions passed as parameters/procedure typed variables, you always have to have a standardized calling sequence. You could of course always store the calling conventions of every function in a library or external module in a data base, but what do you do if you are PascalWorkstations, Inc. and your shared library has a bug? You have to keep the same calling conventions for a function 'x' once you have sold a single copy of a shared library containing it, or all programs linked with the new, improved shared library, but compiled with the old one (i.e., all commercial programs sold for your workstation) will no longer work! Modula-2 is much easier here: if you want to do aggressive register allocation, you can optimize all top-level functions + their nested functions, while retaining a consistent calling convention for top-level functions, which are the only functions which may be assigned to procedure-typed variables. bye, mike Michael K. Gschwind, Dept. of VLSI-Design, Vienna University of Technology mike@vlsivie.tuwien.ac.at || mike@vlsivie.uucp Voice: (++43).1.58801 8144 || Fax: (++43).1.569697 -- Send compilers articles to compilers@iecc.cambridge.ma.us or {ima | spdcc | world}!iecc!compilers. Meta-mail to compilers-request.
mauney@eos.ncsu.edu (Dr. Jon Mauney) (05/07/91)
I wrote >> ... Conceivably, this could be done even if the >>procedure is passed as a parameter -- just make sure the register value is >>part of the closure that you pass. >> >>I'm not claiming it's worth the trouble... Michael Gschwind replies >You write that "this could be done even if the procedure is passed as a >parameter". This is not correct in the general case, and the case you line >out, namely "just make sure the register value is part of the closure that >you pass", is not feasable. Procedures passed as parameters can be called >from just about any context, e.g., library functions, separately compiled >modules etc., thus you cannot change the calling sequence. In the case of >functions passed as parameters/procedure typed variables, you always have >to have a standardized calling sequence. The discussion is worth continuing only in that it illustrates a very important principle of compiler implementation: you can make things work just about any way you want as long as you are pigheaded enough to keep at it. "In the world of mules, there are no rules" -- Ogden Nash In this case, the function passed as a parameter will have to help set up its own environment. Essentially you would construct a thunk that sets up the environment and then calls the funciton; the thunk would then get passed instead of the original. You probably have to do this sort of thing anyway, if you are passing Pascal procedures to library functions, since the library is probably going to assume a C style pointer to function, with no environment of any sort. (ah, but where does the thunk get the environment data in this case? Left as an exercise to the reader) Like I said, I continue the discussion not because I think anyone cares about leaving values in registers for use by nested procedures that are passed as callback functions to Xt library routines, but because one often hears the statement "it is impossible for a compiler to accomplish X, or to use technique Y for language Z." Usually such statements are not true. One simply has to think a little harder, change some other assumptions, and sometimes build a huge cumbersome mechanism to do it. And then decide whether it is worth it. -- Jon Mauney, parsing partisan Computer Science Dept. N.C. State University -- Send compilers articles to compilers@iecc.cambridge.ma.us or {ima | spdcc | world}!iecc!compilers. Meta-mail to compilers-request.