!standard E.2.1 (08) 99-09-26 AI95-00058/08 !class binding interpretation 99-09-27 !status work item 95-06-25 !status received 95-06-25 !priority High !difficulty Hard !subject Accessibility Rules for Shared_Passive Packages !summary Consider a library unit P1, and a shared passive library unit P2. If the declaration of P2 depends semantically on the declaration of P1, then the accessibility level of P2 is known (at all places) not to be statically deeper than that of P1. Now consider a place within the declarative region of P2. If this place is within the scope of a with clause that mentions P1, then the accessibility level of P2 is known (at that place) not to be statically deeper than that of P1. Otherwise, the accessibility level of P1 is considered to be statically deeper than that of P2. Compile-time accessibility rules are enforced accordingly. The same definition applies to "deeper than" as for "statically deeper than" above. For a run-time Accessibility_Check that involves accessibility levels defined in terms of a shared passive library unit and one that is not shared passive, then the check is done as usual -- Program_Error is raised if the check fails. However, if the accessibility levels to be checked (at run time) are defined in terms of accessibility of two shared passive library units, then an implementation need not perform the check. If the implementation ensures that all shared passive library units have the same lifetime as the program as a whole, and are universally addressable, then omitting such checks cannot cause dangling pointers. On the other hand, if the implementation allows shared passive partitions to come and go during execution of the program as a whole, or if some shared passive partitions are not addressable from others, then the undetected failure of such a check can cause dangling pointers; in that case, the program's execution is defined to be erroneous. !question E.2.1(8-8.b) says: 8 {accessibility [from shared passive library units]} {notwithstanding} Notwithstanding the definition of accessibility given in 3.10.2, the declaration of a library unit P1 is not accessible from within the declarative region of a shared passive library unit P2, unless the shared passive library unit P2 depends semantically on P1. 8.a Discussion: We considered a more complex rule, but dropped it. This is the simplest rule that recognizes that a shared passive package may outlive some other library package, unless it depends semantically on that package. In a nondistributed program, all library packages are presumed to have the same lifetime. 8.b Implementations may define additional pragmas that force two library packages to be in the same partition, or to have the same lifetime, which would allow this rule to be relaxed in the presence of such pragmas. But 3.10.2 uses the notions of "accessibility levels" that are "the same as" or "deeper than" other accessibility levels. So what does E.2.1(8) mean by "accessible from"? !recommentation (See summary.) !wording Replace paragraph E.2.1(8) with this paragraph: Notwithstanding the definition of the accessibility rules given in 3.10.2, if the declaration of a shared passive library unit P1 depends semantically on a library unit P2 and if a declaration immediately within the declaration region of P1 is also within the scope of the with clause that specifies P2 then that declaration has an accessibility level no deeper than the accessibility level of P2. An Accessibility_Check that involves the accessibility levels of a non-shared passive library unit and a shared library unit is performed and Program_Error is raised if the check fails. Add the following heading and paragraph after E.2.1(11): Implementation Permissions If the Accessibility_Check to be performed involves the accessibility levels of two shared passive library units, then an implementation need not perform the check. If the implementation ensures that all shared passive library units have the same lifetimes as the program lifetime and are accessible from all partitions, then omitting such an Accessibility_Check cannot cause dangling references. On the other hand, if the implementation allows passive partitions to be loaded and elaborated dynamically throughout the lifetime of the program or if some passive partitions are not accessible from other partitions, the undetected failure of the Accessibility_Check can cause dangling references; in that case, the execution of the program is defined be erroneous. !discussion Within a single partition, it is possible to have an access value that designates a library level object in any library package. This model assumes that library-level data is addressable from pretty much anywhere, and that all library units have the same lifetime. However, in a multi-partition program, these properties do not hold. Data in an active partition is not addressable from any other partition. Data in a shared passive partition may or may not be addressable, depending on the with_clauses. Furthermore, partitions can come and go at arbitrary times. The goal of E.2.1(8) is to prevent dangling pointer problems that could be caused by pointers from shared passive partitions to other (active or shared passive) partitions. By "dangling pointer", we really mean two situations here: (1) dereferencing a pointer to some data in a different partition that is not addressable from this partition; (2) dereferencing a pointer to some data in a different partition, when that partition no longer exists. Note that Annex E assumes the following properties: - If a normal package with's a shared passive package, then the shared passive partition containing the shared passive package lives at least as long as the active partition containing the normal package. - If a shared passive package with's another shared passive package, then the shared passive partition containing the second package lives at least as long as the shared passive partition containing the first package. In other words, a with_clause of A upon (shared passive) B constitutes a statement that the partition containing A will (at all times) be able to address the partition containing B. Example 1: package P1 is X: aliased Integer; end P1; package P2 is pragma Shared_Passive(P2); type A is access all Integer; Y: A; end P2; with P1, P2; procedure Main is begin P2.Y := P1.X'Access; -- Illegal. end Main; The intent of E.2.1(8) is that the above assignment be illegal, and the above summary achieves that. P1 is part of the active partition. If the above were legal, we would have a pointer from the shared passive partition to the active partition. A different active partition could attempt to dereference P2.Y, when the variable P1.X is not even addressable by that partition. The active partition containing P1.X might not even exist anymore. Note that the above summary implies that two library units can both be deeper than each other: Example 2: package P1 is pragma Shared_Passive(P1); type A is access all Integer; Y: A; X: aliased Integer; end P1; package P2 is pragma Shared_Passive(P2); type A is access all Integer; Y: A; X: aliased Integer; end P2; with P1, P2; procedure Main is begin P2.Y := P1.X'Access; -- Illegal. P1.Y := P2.X'Access; -- Illegal. end Main; P1 and P2 are both statically deeper than each other. Therefore, both assignments are illegal. This prevents problems caused by one shared passive partition living longer than the other. It also prevents problems of pointers across address spaces -- if another active partition had visibility upon P1, it could follow the invalid pointer into P2, which is not addressable. If P2 were to say "with P1;" then we would know that P1 lives at least as long as P2, so the "P2.Y := P1.X'Access;" assignment would be legal. Note that the wording of the summary makes it clear that the with_clause has to be on the declaration of P2, not the body. (It corrects the wording of E.2.1(8), which says "unless the shared passive library unit P2 depends semantically on P1", which makes no sense, since semantic dependence is between pairs of library_items, not library units.) The compile-time accessibility rules must be checked with this potentially-circular notion of deepness in mind. However, implementation of the corresponding run-time checks would be a serious implementation burden, and would be inefficient. Consider: Example 3: package P1 is pragma Shared_Passive(P1); X: aliased Integer; end P1; package P2 is pragma Shared_Passive(P2); type A is access all Integer; Y: A; end P2; procedure P3(Z: access Integer); with P2; procedure P3(Z: access Integer) is begin P2.Y := P2.A(Z); -- Possibly erroneous. P2.Y := Z.all'Access; -- Possibly erroneous. end P3; with P1, P3; procedure Main is begin P3(Z => P1.X'Access); end Main; In this case, it is not known at compile time of P3, whether Z designates an object in P1. Furthermore, given the potential circularities in deepness, it is not easy to check this at run time. Therefore, we define the above situation to be erroneous on any implementation where P3 cannot address P1. The accessibility rules in question are those on 'Access (where the result type is a general access type, or an access-to-subprogram type), type_conversion (where the target type is a general access type, or an access-to-subprogram type), and type extension. Note that the accessibility rule in 9.5.4(6), which has to do with requeues, is irrelevant to this discussion. However, there is one case in which the run-time error can be easily and efficiently detected: An implementation can prevent the creation of pointers from shared passive partitions to active partitions by assigning an accessibility level of -1 to shared passive library units, and doing the run-time checks accordingly (presuming normal library units are at level 0). This is likely to be a common case. We therefore require this particular case to be detected. Note that this technique is not general, since it does not prevent the creation of pointers from one shared passive partition to another shared passive partition. If we were to remove the "pragma Shared_Passive(P1);", then P1 would be part of the active partition, and the lines marked "-- Possibly erroneous." would instead raise Program_Error. Example 4 illustrates various consequences of the rules given in this AI: package P1 is pragma Shared_Passive(P1); type A is access all Integer; Y: A; X: aliased Integer; type T1 is tagged null record; protected type Protected_Type is ... end; -- A return-by-ref type. Protected_Object: Protected_Type; end P1; package P2 is pragma Elaborate_Body(P2); -- So we can have a body. pragma Shared_Passive(P2); type A is access all Integer; Y: A; X: aliased Integer; end P2; with P1; package body P2 is type T2 is new P1.T1 with null record; -- Illegal. -- This is illegal, even though we know that we're going to -- live as long as P1. We don't know that on our spec. function F return P1.Protected_Type is begin return P1.Protected_Object; -- Possibly erroneous. end F; procedure Proc is begin Y := P1.X'Access; -- Illegal. end Proc; end P2; generic package G is pragma Shared_Passive(G); procedure Proc; end G; with P1; package body G is function F return P1.Protected_Type is begin return P1.Protected_Object; -- Possibly erroneous. end F; procedure Proc is begin ... F ... end Proc; end G; package P3 is pragma Elaborate_Body(P3); -- So we can have a body. pragma Shared_Passive(P3); type A is access all Integer; Y: A; X: aliased Integer; end P3; with G; package body P3 is package G_Instance is new G; begin G_Instance.Proc; -- Can cause erroneous execution. end P3; Example 5 illustrates why the definition in the summary depends on the place: package P1 is pragma Shared_Passive(P1); X: aliased Integer; end P1; package P2 is pragma Elaborate_Body; -- so we can have a body. pragma Shared_Passive(P2); type A is access all Integer; Y: A; end P2; with P1, P2; procedure Main is begin P2.Y := P1.X'Access; -- Illegal. end Main; with P1; package body P2 is begin Y := P1.X'Access; -- OK. end P2; In the body of P2, we are within the scope of "with P1;", so the accessibility level of P2 is known not to be statically deeper than that of P1. Therefore, the statement marked "-- OK." is legal. However, in Main, we don't know about the "with P1;" on the body of P2, so the same statement at that place is illegal. !appendix !section E.2.1(08) !subject Vestigial reference to old accessibility rules !reference RM9X-E.2.1(8);5.95 !from Norman Cohen !reference as: 94-5035.c Norman H. Cohen 94-12-19>> !discussion This paragraph still talks of "accessibility from" a declarative region rather than accessibility levels. Oh well. A clarification in the AARM would be in order. **************************************************************** !section E.2.1(08) !subject Vestigial reference to old accessibility rules !reference RM9X-E.2.1(8);5.95 !reference 94-5035.c Norman H. Cohen 94-12-19 !from Bob Duff !reference as: 94-5037.c Bob Duff 94-12-19>> !discussion > This paragraph still talks of "accessibility from" a declarative region > rather than accessibility levels. Oh well. A clarification in the AARM > would be in order. Caught red handed! I knew about this bug, and decided it wasn't worth fixing. It's not clear how to word it using "levels", since these things don't have a particular order -- the core language doesn't have any similar notion, where things are inaccessible from each other. However, I think the intent is clear. We'll see if the compiler writers agree... **************************************************************** !section E.2.1(08) !subject request for clarification !reference RM95-E.2.1(8) !from Tom Burger (tom@ixi.saic.com) 95-06-12 !keywords accessible !reference as: 95-5154.a Tom Burger 95-6-13>> !discussion While developing ACVC tests for the Distribution Annex I found the meaning of this paragraph to be quite elusive. Further explanation or an example would be very helpful. Thanks. **************************************************************** !section E.2.1(08) !subject request for clarification !reference RM95-E.2.1(8) !from Tom Burger (tom@ixi.saic.com) 95-06-12 !keywords accessible !reference 95-5154.a Tom Burger 95-6-13 !reference as: 95-5156.a Robert A Duff 95-6-14>> !discussion > While developing ACVC tests for the Distribution Annex I found > the meaning of this paragraph to be quite elusive. Further > explanation or an example would be very helpful. Thanks. package P1 is X: aliased Integer; end P1; package P2 is type A is access Integer; Y: A; end P2; Is "Y := X'Access;" legal? According to 3.10.2, this is legal. If we make P2 Shared_Passive, it's illegal, by E.2.1(8), unless we add "with P1;" to P2. Normally, all library packages are in the same address space, and live "forever", so you can freely create pointers from one to the other. But if a P2 is shared passive, it can live longer than P1, which might be part of a partition that executes and then disappears. So we don't allow pointers from P2 to P1. OTOH, if P2 says "with P1;", then P1 will live at least as long as P2, so E.2.1(8) doesn't forbid the 'Access in that case. The "Y := X'Access;" statement is in some active partition, I'm assuming, and it says "with" of P1 and P2. The "accesible from" wording is somewhat of a cheat, I admit, since 3.10.2 is written in terms of "levels" and "deeper than". In E.2.1, it's not a question of being deeper, it's just that this thing can't point to that thing. - Bob **************************************************************** !section E.2.1(08) !section E.2.1(8) !subject request for clarification !reference RM95-E.2.1(8) !reference AARM-E.2.1(8a) !reference 95-5154.a Tom Burger 95-6-13 !from Anthony Gargaro 95-06-14 !keywords accessible, shared passive packages !reference as: 95-5157.a Anthony Gargaro 95-6-14>> !discussion > While developing ACVC tests for the Distribution Annex I found > the meaning of this paragraph to be quite elusive. Further > explanation or an example would be very helpful. Thanks. The purpose of this paragraph is to ensure that values accessible through a shared passive package always designate valid objects. For example, unless this rule is present, consider the consequences of compiling and partitioning the following library units: package SP is pragma Shared_Passive; type Ref_F is access function return Integer; F : Ref_F; end SP; package Abnormal is pragma Pure; function F return Integer; end Abnormal; package body Abnormal is function F return Integer is begin return 100; end F; end Abnormal; with SP, Abnormal, Text_IO; procedure Ptn_1 is begin SP.F := Abnormal.F'Access; Text_IO.Put_Line("Value is" & Integer'Image(SP.F.all)); end Ptn_1; with SP, Text_IO; procedure Ptn_2 is begin Text_IO.Put_Line("Value is" & Integer'Image(SP.F.all)); end Ptn_2; There is no guarantee that Ptn_1 and Ptn_2 will be assigned to the same partition; thus, the assignment to SP.F by Ptn_1 may result in Ptn_2 dereferencing an invalid access value in another partition. By requiring that SP semantically depends upon Abnormal protects against such an invalid reference. **************************************************************** !section E.2.1(00) !subject General access types should be disallowed in Shared_Passive !reference RM95-E.2.1 !from Laurent Guerby 96-08-23 !keywords distribution, Shared_Passive, general access type !reference 96-5645.a Laurent Guerby 96-8-23>> !discussion E.2.1 Shared Passive Library Units 1 [A shared passive library unit is used for managing global data shared between active partitions. The restrictions on shared passive library units prevent the data or tasks of one active partition from being accessible to another active partition through references implicit in objects declared in the shared passive library unit.] >> Sources showing the issue. -- On some storage node. package SP is pragma Shared_Passive; type A is access all Integer; -- General access type. P : A; end SP; -- On some processing node with SP; package Local_PN is I : aliased Integer := 2; end Local_PN; with SP, Local_PN; procedure Main_PN is begin SP.P := Local_PN.I'access; -- SP.P points back to PN address space. end Main_PN; -- Laurent Guerby , Team Ada. "Use the Source, Luke. The Source will be with you, always (GPL)." **************************************************************** !section E.2.1(00) !subject General access types should be disallowed in Shared_Passive !reference RM95-E.2.1 !keywords distribution, Shared_Passive, general access type !reference as: 96-5645.a Laurent Guerby 96-8-23 !from Anthony Gargaro 96-8-24 !reference 96-5648.a Anthony Gargaro 96-8-24>> !discussion >> Sources showing the issue. > ... >with SP, Local_PN; >procedure Main_PN is >begin > SP.P := Local_PN.I'access; > -- SP.P points back to PN address space. >end Main_PN; It is my understanding that this example is illegal since SP does not depend upon Local_PN as required by E.2.1(8). **************************************************************** !section E.2.1(00) !subject General access types should be disallowed in Shared_Passive !reference RM95-E.2.1 !keywords distribution, Shared_Passive, general access type !reference 96-5645.a Laurent Guerby 96-8-23 !from Anthony Gargaro 96-8-24 !reference 96-5648.a Anthony Gargaro 96-8-24 !from Bob Duff !reference 96-5653.a Robert A Duff 96-8-27>> !discussion > >> Sources showing the issue. > > > ... > > >with SP, Local_PN; > >procedure Main_PN is > >begin > > > SP.P := Local_PN.I'access; > > -- SP.P points back to PN address space. > > >end Main_PN; > > It is my understanding that this example is illegal since > SP does not depend upon Local_PN as required by E.2.1(8). That is certainly the intent. This is the subject of AI-58, which has never been considered by the ARG, nor have I written a draft version of the AI. Clearly, the above should be illegal. However, it is not entirely clear to me what the rule is in the case of dynamic accessibility checks -- that is, imagine passing Local_PN.I'access as an access parameter. - Bob **************************************************************** !section E.2.1(08) !subject AI-58: Editorial !reference AI95-00058/04 !from Offer Pazy 96-11-22 !reference 96-5767.a Offer Pazy 96-11-22>> !discussion I believe that there is a small error in the discussion of Example 2: > ... > with P1, P2; > procedure Main is > begin > P2.Y := P1.X'Access; --Illegal. > P1.Y := P2.X'Access; --Illegal. > end Main; > > . . . > > If P2 were to say "with P1;" then we would know that > P2 lives at least as long as P1, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > so the "P2.Y := P1.X'Access;" assignment would be legal. I think that this is backwards: If P2 withes P1, then we know that P1 lives as least as long as P2, not the other way arround. The assignment is still (or "therefore" :-) legal. Offer Pazy 48 Be'eri St. Tel-Aviv 64233 Israel 972-3-695-1923 pazy@world.std.com **************************************************************** !section E.2.1(08) !subject AI-58: Drop the support for hierarchical memory configurations !reference RM95-E.2.1(8) !reference AI95-00058/04 !from Offer Pazy 96-11-22 !reference 96-5768.a Offer Pazy 96-11-22>> !discussion I would like to start by arguing that resorting to erroneous execution in this case is very bad news. The features discussed and the interactions among them are not considered unsafe, they cannot be easily localized and the erroneous usage is likely to result from actions of programmers on the one hand and system builders on the other. Also, note that it is very likely that the affected applications are (very) large programs. It is not clear to me what guidelines one could give to his group to avoid such cases without significantly limiting the usefulness of Annex E. Errors resulting from this can be extremely subtle and very hard to find (let alone predict in advance) even for a more experienced user. Most other cases where we have resorted to erroneousness are either obscure or use unsafe features and hence are usually marked by a pragma or a certain with clause. This is not the case here, where the constructs are "mainstream: and their usage may appear in many places in the code's body. So this is my main problem and I would like the ARG to consider this AI with this in mind, this is a very serious erroneousness case. Based on this general objective, I would like to propose another approach to address the real problem that is expressed in the AI, and that is to abandon the (very ambitious) goal of supporting in the standard the idea of hierarchical memory configurations (HMCs) (those that allow passive partitions to come and go). From the outset, we knew that the support of this is at best partial; we leave all issues of explicit partition abort, reload, and restart outside the ARM, features that must be provided in some form by the implementation to support HMCs. Furthermore, the partitioning and configuration steps are treated by the ARM as conceptual only, we give very few hints on what they should do. This was done intentionally since we understood that we cannot achieve more standardization here. Essentially, what has happened at an early stage of the mapping, was that we saw this "nice feature" and thought that we could get this interesting model in the annex, almost free. Later revisions, and this AI proved us wrong. In order to really support HMCs, much more definition work in needed in the language to do it right, and it's not easy. Furthermore, while some may argue, I do believe that there are still many applications for the flat memory model without supporting HMCs; it's nice to have, but not critical. If the objective of supporting HMCs causes us to make straight-forward distributed programs erroneous in a serious manner, then I think we are making the wrong decision. So basically, my suggestion is to give up on the standard support of HMCs. We should leave this entire model as impl-defined (as is most of it now anyway) and fix whatever is necessary in the accessibility rules for the flat model (the AI does it already as is discussed under the third example). The flat model will be the default one and the only model required by the annex. Implementations will then be free to support HMCs fully and to add whatever pragmas or rules to address this specific problem. I think that the majority of users will gain by this!!! Offer Pazy 48 Be'eri St. Tel-Aviv 64233 Israel 972-3-695-1923 pazy@world.std.com **************************************************************** From: Anthony Gargaro Sent: Thursday, March 25, 1999 6:03 AM Two minor comments on this version. [Version 07, Ed.] The wording shared passive partitions should be changed to simply passive partitions. In example 5, a pragma Shared_Passive should apply to P1, since P2 semantically depends upon P1. Anthony. ****************************************************************