!standard C.8(0) 12-06-04 AI12-0026-1/01 !class Amendment 12-06-04 !status work item 12-06-04 !status received 12-02-28 !priority Low !difficulty Medium !subject Reentrant categorization !summary **TBD. !problem There is a need to have a way to specify that the operations of a package are intended to be task-safe. !proposal Ada should supply a Reenterant package categorization. Operations in packages with this categorization are intended to be called from multiple tasks without interference. The language would as much as possible enforce rules to prevent problems (particularly of maintenance inadvertantly eliminating the property, creating hard-to-find-bugs). The rules of the categorization would be: (1) Semantic dependence only on Pure and Reentrant packages. (Same wording of rules as for Pure). (2) No declaration of library-level variables, unless the variable has a task or protected type, or is an atomic variable. !wording ** TBD. !discussion Note that this pragma also would facilitate programming in a functional style. The rules given are not enough to make a package completely task-safe. For instance, protected objects or atomic variables could be used in such a way as to cause "live-lock" or similar problems. The presence of the categorization provides additional documentation of the intent, which is useful by itself. A bunch of additional points [from the editor's fertile imagination]: (1) The original e-mail also discussed "functional programming". That's a different need which would require a different pragma as no global variables of any kind should be allowed. Pragma Pure is fairly close to the required semantics here (it's a bit too restrictive), and it's probably a bad idea to define another categorization that is too similar to an existing one. (2) There might be value to having finer-grained use of such a pragma. Specifically, one could imagine having a pragma (really an aspect) for an individual subprogram that enforced similar requirements on the subprogram. The exact rules would need to be very different, however (no calls on subprograms that are not Pure or Reentrant; no dependence on global variables that are neither atomic nor task nor protected). This might actually be the better idea. (3) Some amazingly complex schemes were proposed in the e-mail. For this to be in the language, it needs to be simple and obviously useful; Ada programs and programmers have done without this aid for decades and it's hard to imagine why a complex mechanism is needed now. (4) Well, there is one reason for a complex mechanism: if it could actually guarantee task-safe behavior. Such a mechanism would have to limit access to globals in order to prevent live-lock and other race conditions. That seems beyond the state of the art. (5) One also could imagine having an aspect for private types which declared that they are implemented in a task-safe fashion (thus allowing them to be used to declare a global variable in a Reentrant package) -- this would work much like Preelaborable_Initialization. !ACATS test ** TBD. !appendix From: Brad Moore Sent: Tuesday, February 28, 2012 8:39 AM !topic pragma Restrictions (No_Global_Objects) !reference Ada 2005 RM H.4 (24) !from /Brad Moore 12-02-28/ !keywords Functional Programming, thread safe !discussion There is a need to be able to apply a restriction on a package that disallows the declaration of global objects. This would facilitate functional programming where all object declarations are stack based, which in turn would help ensure that facilities provided by the package are thread safe. Such a restriction would also improve maintainability of a package, as it would prevent a future maintainer of the package from adding global declarations to either the spec or body of the package, which could cause problems for clients of the package that expect a called subprogram of the package is thread safe. **************************************************************** From: Bob Duff Sent: Tuesday, February 28, 2012 9:16 AM > There is a need to be able to apply a restriction on a package that > disallows the declaration of global objects. There's no reason to restrict global constants. It might be useful to restrict global variables. Is pragma Pure too restrictive for your purposes? By "global", I assume you mean what the RM calls "library level". The RM uses "global" in a relative way, as in "X is global to Q": package body Pack is procedure P is X : T; procedure Q is ... X ... which doesn't stop P from being thread safe. **************************************************************** From: Jean-Pierre Rosen Sent: Tuesday, February 28, 2012 9:38 AM > There is a need to be able to apply a restriction on a package that > disallows the declaration of global objects. Right, and there are zillions of other interesting possible restrictions. The difficult issue is where to draw the line between what is enforced by the compiler and what is enforced by other tools (AdaControl of course had that rule for a very long time). Hmmm... I'd like to see a discussion about "good taste for restrictions" - if we could find a simple enough criterion on what should or should not be a restriction (in the language - implementers are always free to go beyond that line). I don't think we want to receive hundreds of suggestions for possible restrictions in Ada 2020, not questionning their intrinsic value. **************************************************************** From: Brad Moore Sent: Tuesday, February 28, 2012 8:33 AM >> There is a need to be able to apply a restriction on a package that >> disallows the declaration of global objects. > >There's no reason to restrict global constants. Good point. >It might be useful to restrict global variables. >Is pragma Pure too restrictive for your purposes? Yes. I have pragma Elaborate on the packages, but that is as close to Pure that I've been able achieve. > >By "global", I assume you mean what the RM calls "library level". Yes, I modified the subject header of the small to reflect that. **************************************************************** From: Brad Moore Sent: Wednesday, February 29, 2012 8:36 PM >> There is a need to be able to apply a restriction on a package that >> disallows the declaration of global objects. Good point. > There's no reason to restrict global constants. > It might be useful to restrict global variables. > Is pragma Pure too restrictive for your purposes? Yes, the best I could do is pragma Preelaborate with the current source. > > By "global", I assume you mean what the RM calls "library level". > The RM uses "global" in a relative way, as in "X is global to Q": Yes, the Restriction should be No_Library_Level_Variables, instead of No_Global_Objects **************************************************************** From: Brad Moore Sent: Wednesday, February 29, 2012 10:08 PM >> There is a need to be able to apply a restriction on a package that >> disallows the declaration of global objects. >> > Right, and there are zillions of other interesting possible restrictions. > > The difficult issue is where to draw the line between what is enforced > by the compiler and what is enforced by other tools (AdaControl of > course had that rule for a very long time). > > Hmmm... I'd like to see a discussion about "good taste for restrictions" > - if we could find a simple enough criterion on what should or should > not be a restriction (in the language - implementers are always free > to go beyond that line). I agree and think that would be a good to have such a discussion. I can't speak for the zillions of other possibilities, so maybe to begin with, I will mention some criteria that I see being met by a restriction such as, No_Library_Level_Variables. The following fall under what I would call "good taste for restrictions" 1) A restriction that supports or facilitates a significant paradigm of software design. In this case, the restriction would facilitate functional programming, and parallelism. Object oriented programming might be another example of such a paradigm. 2) A restriction that supports a significant trend in hardware. In this case, the trend is multicore processors. Another example of such a hardware platform might be small devices such as smart-phones and ARM devices, or perhaps server farms, etc. 3) A restriction that helps avoid software maintenance hazards. In a other recent Ada-comment post, an example was given involving a call into a nested procedure. A future maintainer of that package might think that it would be better to move the nested variables to a library level declaration, and then provide another facility to the package to access those variables, not realizing that making such a change breaks the thread-safety of the package, which could lead to nasty bugs in some clients that expect the thread-safety. Such a restriction could have been placed on the package which might have caused the maintainer to think twice about making the change. 4) A restriction that asserts certain semantics about the package, that assist someone reading the software, that instructs the reader on some quality of the software that informs how the software is to be used. Relying on a third party tool to enforce this, means the property of the unit is not necessarily visible to the maintainer. The No_Library_Level_Variables restriction for example might be something some would look for, if they were interested in thread-safe programming. 5) A restriction that might be applied selectively to certain units. For example, No_Library_Level_Variables might be a restriction that one might want to apply only to packages where thread-safety is important. Some third-party tools apply a coding standard to all units in a project, and maybe weren't designed to apply restrictions with fine-grained control on a per unit basis. I believe AdaControl does have such very fine grained control, but other tools may not be so flexible. 6) A restriction where it is desired that it be closely tied to the software. Relying on a third-party tool to enforce such a restriction might not be desirable for a general purpose utility, because not all users of the utility are likely to have the same set of third-party tools, or have them configured the same way. 7) A restriction that is useful for third party software developers whose source is intended to be portable, where the restriction might be needed for target platforms that are not supported by the 3rd party software tools. > I don't think we want to receive hundreds of > suggestions for possible restrictions in Ada 2020, not questionning > their intrinsic value. I believe No_Library_Level_Variables would pass the above tests. It would be interesting to see some other possibilities or examples from the set of other possibilities would meet these criteria. Would the remaining list still be in the order of hundreds? I'm sure there are other "good taste" criteria that I have missed also. Some criteria that I think are poor taste for a restriction might include those that are only style related, such as enforcing a coding standard, but otherwise do not provide any other benefits. It would be interesting to hear what others think. **************************************************************** From: Geert Bosch Sent: Thursday, March 1, 2012 10:52 AM >> By "global", I assume you mean what the RM calls "library level". >> The RM uses "global" in a relative way, as in "X is global to Q": > > Yes, the Restriction should be No_Library_Level_Variables, instead of > No_Global_Objects Note that the Ravenscar restrictions require protected objects and tasks to be allocated at the library level. I don't think it makes a lot of sense to restrict those, especially as you'd want to encourage the use of protected objects. **************************************************************** From: Jean-Pierre Rosen Sent: Friday, March 2, 2012 12:32 AM Good point. And maybe you really want to restrict variables in visible parts, but what about bodies? private parts? As soon as you have states, (or memorizing functions!), you need some global variables. Of course, you are free to not put the restriction. But it makes sense to restrict on visible parts only (all manipulations must be done through provided operations), on bodies (no global state, protect against reentrancy), to except some kind of variables (task, protected)... I'd say that as soon as you need fine grain parameterization, you are in the domain of tools. **************************************************************** From: Tucker Taft Sent: Friday, March 2, 2012 8:08 AM pragma Pure addresses the issue of no global state. I would think we might want some kind of pragma Reentrant which would require that there is no updating of non-atomic, non-protected global variables. **************************************************************** From: Bob Duff Sent: Friday, March 2, 2012 2:33 PM > pragma Pure addresses the issue of no global state. Sort of. It's perhaps overly restrictive for the purpose(s?) being discussed here. E.g. you might want to allow "X: constant := Call(...);". We decided that memory allocation is an important "side effect", so impure, but I'm not sure that's what we want here. After all, so-called "pure functional languages" do lots of memory allocation, and and it's considered just as irrelevant as the "side effect" of raising the temperature of the CPU. **************************************************************** From: Riccardo Bernardini Sent: Friday, March 2, 2012 3:22 PM >> pragma Pure addresses the issue of no global state. > > Sort of. It's perhaps overly restrictive for the purpose(s?) being > discussed here. E.g. you might want to allow > "X: constant := Call(...);". I remember faced this type of problem (forcing packages to be multi-task safe) some time ago. I also remember that I tried the "Pure" solution, but it was way too restrictive and in the end I renounced. Alas, I do not remember the details and why I thought that the use of pragma Pure was too restrictive. **************************************************************** From: Brad Moore Sent: Saturday, March 3, 2012 8:14 AM ... > I'd say that as soon as you need fine grain parameterization, you are in > the domain of tools. I agree that if you want such fine grained control, such as restricting only in the visible part, then that should be in the domain of tools. But that definitely is not what I was looking for here. I would want the Reentrant property to unconditionally apply to both the spec and the body, otherwise one could not assume the library unit was reentrant. Note also, that memorizing functions would be allowed, so long as the state information was stored in a protected or task object. **************************************************************** From: Brad Moore Sent: Saturday, March 3, 2012 8:38 AM ... > I would think we might want some kind of pragma Reentrant which would > require that there is no updating of non-atomic, non-protected global > variables. I like your idea of a Reentrant pragma. That seems to nicely capture the desired intent. I am thinking such a pragma would be allowed as a categorization pragma, but ideally also allowed as a configuration pragma. As a categorization pragma, (i.e. applied to a library unit) it would require that the library unit could only "with" other Reentrant or Pure library units. Such a unit would not necessarily be preelaborable however. Since it should allow constant objects to be declared, as Bob Duff mentioned. So a Reentrant pragma would have no relevance for Annex E in remote operations. For remote usage, one of the categoration pragmas in Annex E would also need to be applied. As a configuration pragma, it would allow the property to be applied system wide, or to a partition, which might be useful for functional programming. It may make sense to also allow the pragma to be applied locally, for example, to a particular subprogram declaration in a package. **************************************************************** From: Bob Duff Sent: Saturday, March 3, 2012 9:29 AM > As a configuration pragma, it would allow the property to be applied > system wide, or to a partition, which might be useful for functional > programming. It's hard to see how it would be practical to have Reentrant system wide. I mean, programs that don't do I/O aren't particularly useful. Ada doesn't have monadic I/O, which is probably a good thing. ;-) We could have Reentrant/Nonreentrant which would override each other in the same way Suppress/Unsuppress override each other. Then you could have a system-wide Reentrant pragma, and mark small portions of your program Nonreentrant, for a mostly-functional style program. **************************************************************** From: Brad Moore Sent: Saturday, March 3, 2012 11:09 AM This sounds like a good idea also. But for I/O, since we are saying that tasks would be allowed, couldn't the I/O be managed by a task, allowing Reentrant to still apply? [Editor's unwritten reply: "Sure, but that wouldn't make the *entire* system Reentrant unless *none* of the language-defined I/O is used. That's not a very likely situation and surely not worth a language-defined pragma. In any case, 'Reentrant' alone could not make a routine or package task-safe (unless it was way too complex for the language-definition). So it doesn't seem valuable to use it everywhere."] **************************************************************** From: Brad Moore Sent: Saturday, March 3, 2012 12:48 AM >Note that the Ravenscar restrictions require protected objects and >tasks to be allocated at the library level. I don't think it makes a >lot of sense to restrict those, especially as you'd want to encourage >the use of protected objects. Good point. I agree. **************************************************************** From: Jean-Pierre Rosen Sent: Monday, March 5, 2012 1:19 AM > I remember faced this type of problem (forcing packages to be > multi-task safe) some time ago. I also remember that I tried the > "Pure" solution, but it was way too restrictive and in the end I > renounced. Alas, I do not remember the details and why I thought that > the use of pragma Pure was too restrictive. "Pure" was intended to allow f.e. the removal of multiple calls, I don't think it was ever intended to ensure reentrancy. [Editor's note: I do not believe this remark. I don't think "multiple calls" has much to do with the reason for pragma Pure; and even if it did, it is stronger than the requirements for reentrancy, so it is essentially the same thing. The Ada 95 Rationale only mentions "no state" as the meaning of Pure, and that it has an important effect on elaboration (along with Preelaborate).] ****************************************************************