ADA 9X MAPPING VOLUME II MAPPING SPECIFICATION AND RATIONALE (ANNEXES) ABRIDGED Version 4.1 4 March 1992 IR-MA-1250-3 Ada 9X Mapping/Revision Team Intermetrics, Inc. 733 Concord Avenue Cambridge, Massachusetts 02138 (617)661-1840 Published by Intermetrics, Inc. 733 Concord Avenue Cambridge, Massachusetts 02138 Reprinting permitted if accompanied by this statement. Copyright (C) 1992 Intermetrics, Inc. This work is sponsored by the Ada 9X Project Office under contract F08635-90-C-0066. Foreword There are two volumes of the Ada 9X Mapping Document: Volume I, the Mapping Rationale and Volume II, the Mapping Specification. Volume I, the Mapping Rationale, addresses why specific language changes are being recommended to satisfy the Ada 9X requirements (see Ada 9X Requirements, DoD, December 1990). Volume II, the Mapping Specification, contains a detailed description of the key language changes that are being recommended along with a traceability index between proposed changes and requirements. This release of the Mapping Document (March '92) revises the Specialized Needs Annexes (including additional rationale), and provides an update to the rationale. Volume I, the Mapping Rationale, has been revised to reflect public comments received on the previous version, and to reflect the changes to the core language as described in Volume II, the Mapping Specification (December '91). Volume I now contains the section-by-section rationale for Chapters 1 through 14 of the Mapping Specification (Appendix S), and an analysis of Ada 9X upward compatibility/consistency (Appendix U). Volume II, the Mapping Specification, is abridged for this release (March '92). Chapters 1 through Chapters 14 are not included because there were no changes made in these chapters since December '91. Also, the six areas contained in the Specialized Needs Annex now appear as six individual annexes. The rationale for the six annexes is included. These six individual annexes are: - Systems Programming (Annex G) - Real-Time Systems (Annex H) - Distributed Systems (Annex I) - Information Systems (Annex J) - Safety and Security (Annex K) - Numerics (Annex L) The specifications in the Systems Programming, Real-Time Systems, and Distributed Systems annexes are identical to that of Volume II, the Mapping Specification (December '91). There was a minor nomenclature change in the Distribution annex. The term ``datagram'' has been replaced with the term ``asynchronous''. The changes in the Information Systems Annex and the Numerics Annex are a result of further comments from team members and the public. It is expected that all of the annexes will continue to evolve over time. As always, your comments on this draft are welcome and should be received by 1 May 1992 to have the greatest impact. Please follow the instructions for comment submission located on the following page. Christine M. Anderson Ada 9X Project Manager WL/MNAG Eglin AFB, FL 32542-5434 TEL: (904)882-8264 FAX: (904)882-2095 e-mail: anderson@uv4.eglin.af.mil Instructions for Comment Submission Comments should be sent by 1 May 1992 via one of the following methods: U.S.Mail: Ada 9X Mapping/Revision Team Intermetrics, Inc. 733 Concord Avenue Cambridge, MA 02138 Phone: (617) 661-1840 FAX: (617)868-2843 Attn: Ada 9X Mapping/Revision Team E-Mail: ada9x-mrt@inmet.com Please note that the E-mail address for all correspondence to the Ada 9X Mapping/Revision team was changed. Comments should use the following format: !topic Title summarizing comment on Mapping Specification !reference MS-ss.ss(pp); 4.1 !from Author Name yy-mm-dd !keywords keywords related to topic !discussion where ss.ss is the section number of the document, pp is the paragraph number where applicable, and yy-mm-dd is the date the comment was sent. The date is optional if sent via e-mail. References to multiple sections of the document can be made by including additional !reference lines in the comment. As noted above the version of this document is 4.1. If possible please send comments by e-mail. Please send one message per section or comment, to facilitate cross-referencing. If your comments are lengthy and you cannot send them by e-mail, we would appreciate it if you could include a copy in machine-readable form, using either a Macintosh or IBM compatible disk format. All comments sent via E-mail should receive a confirmation message from the mapping team. If a confirmation is not received we ask that the sender try sending the message one more time. In the event that the second E-mail attempt is unsuccessful, the sender is then asked to call Intermetrics at the phone number noted above. Thank you for your help. Acknowledgements This document was prepared by the Ada 9X Mapping/Revision Team based at Intermetrics, Inc. The members of the team are: W. Carlson, Program Manager; T. Taft, Technical Director; C. Garrity; R. Duff (Oak Tree Software); R. Hilliard; O. Pazy; D. Rosenfeld; L. Shafer; W. White. The following consultants to the Ada 9X Project have contributed to the Specialized Needs Annexes: T. Baker (Real-Time/Systems-Programming -- SEI, FSU); B. Brosgol (Information-Systems -- Consultant); K. Dritz (Numerics -- Argonne National Laboratory); A. Gargaro (Distribution -- Computer Sciences); J. Goodenough (Real-Time/Systems-Programming -- SEI); J. McHugh (Safety- Critical -- Consultant); B. Wichmann (Safety-Critical -- NPL: UK). This work is continuously being reviewed by the Ada 9X Distinguished Reviewers: E. Ploedereder, Chairman (Tartan); B. Bardin (Hughes); B. Brett (DEC); B. Brosgol (Consultant); N. Cohen (IBM); R. Dewar (NYU); G. Dismukes (Telesoft); A. Evans (Consultant); A. Gargaro (Computer Sciences); M. Gerhardt (ESL); J. Goodenough (SEI); T. Harrison (ISSI); S. Heilbrunner (University of Salzburg: Austria); P. Hilfinger (UC/Berkeley); J. Ichbiah (Consultant: DR emeritus); M. Kamrad II (Paramax Systems); P. Kruchten (Rational); R. Landwehr (CCI: Germany); C. Lester (Portsmouth Polytechnic: UK); D. Luckham (Stanford); L. Mansson (TELIA Research: Sweden); R. Mathis (Consultant); S. Michell (Multiprocessor Toolsmiths: Canada); M. Mills (US Air Force); D. Pogge (US Navy); K. Power (Boeing); O. Roubine (Verdix: France); W. Taylor (Consultant: UK); E. Vasilescu (Grumman). Other valuable feedback influencing the revision process is also continuously being received from the Ada 9X Language Precision Team (Odyssey Research Associates), the Ada 9X User/Implementor Teams (AETECH, Tartan, Telesoft), the Ada 9X Implementation Analysis Team (New York University) and the Ada community-at-large. The Ada 9X Project is sponsored by the Ada Joint Program Office. Christine M. Anderson at the Air Force Wright Laboratory, Armament Directorate, is the project manager. Specialized Needs Annexes [new] This Annex defines specifications for implementations satisfying certain application-specific, or otherwise specialized, needs. See 1.2 for more information. [Note: The Specialized Needs Annex sections are in preliminary draft form, and have not yet gone through a complete review cycle. This Annex does not contain change bars, because the changes are so extensive.] Sections entitled ``Documentation Requirements'' contain specifications of information that must be provided by implementors in addition to the general requirement for documentation of implementation-defined behaviors. These subsections do not define non-documentation requirements; i.e. if a Documentation-Requirements section requires documenting a certain aspect of a certain functionality, then the functionality itself will be defined in a different section. Subsections entitled ``Metrics'' contain specifications of timing measurements that should be provided by implementors, in addition to the required documentation. These are purely documentation recommendations; for example, if we require that maximum interrupt latency be documented, we are not requiring that the maximum interrupt latency be any particular amount of time. Some imprecision in the metric is expected; implementations should document any assumptions that they believe might affect the interpretation of the metrics. An implementation may satisfy a metric recommendation using data from an implementation-provided benchmark program. Alternatively, the implementor may count clock cycles in the generated code, using hardware-vendor-provided information. [Note: the purpose of the metrics is to enable a user to tell whether the implementation has made a good-faith effort to implement the feature in a form that is useful for real-time or systems programming.] Examples: The metric for interrupt latency should be enough to tell whether the RTS is disabling interrupts for long periods of time. The metric for interrrupt overhead should be enough to tell whether the RTS is using (more or less) direct execution, or queuing the interrupt to be handled via an entry call mechanism. All subsections entitled ``Rationale'' are not normative, and are intended to be separated from the rest of the text when it appears in the reference manual. They are provided here to explain the intent and reasoning behind the normative sections of the document. It may be desirable to preserve these subsections as part of a separate document. G. Systems Programming The Systems Programming Annex specifies additional capabilities to be provided by Ada implementations intended for use in systems programming. These capabilities are also required in many real-time, embedded and information systems. G.1. Access to Machine Operations All machine operations must be accessible through machine code insertions, interface to assembly language, or intrinsic subprograms (see 6.3.1). If access to machine instructions is via machine code insertion, it must be possible to specify a symbolic reference to the address (or offset from register, as appropriate) of any visible storage-occupying entity in the Ada program as the operand of appropriate machine instructions. This includes at least the entry points of subprograms that are not expanded in-line, statement labels within the same machine code subprogram, parameters, variables, and constants. If access is via assembly language, the implementation must provide a mechanism for assembly code to symbolically address any library-level (see 8.2) objects and subprograms in the Ada program. This requirement can be met by supporting the pragma INTERFACE_NAME, if the specified name can then be used to refer to the address of the specified entity in assembly language code (see 13.9). In addition, it is recommended that more direct access be provided for any machine operations that provide special capabilities or efficiency that are not otherwise available through the compilation of ordinary Ada code. Examples of such instructions include - Atomic read and update operations -- e.g., test and set, compare and swap, decrement and test, enqueue/dequeue. - Standard numeric functions -- e.g., sin, log. - String manipulation operations -- e.g., translate and test. - Vector operations -- e.g., compare vector against thresholds. - Direct operations on I/O ports. This access may be provided via intrinsic subprograms. Notes: If the hardware has privileged modes of operation and an Ada program attempts to execute a privileged operation in a non-privileged mode, the effect is implementation-defined. G.1.1. Implementation Requirements - Objects and subprograms whose names are referenced by machine code, or interface names (see 13.9) referenced by assembly code, must be allocated at an addressable storage location and must be retained by the linking process, even if not referenced in any other way. G.1.2. Documentation Requirements - The notation to be used for machine code insertions, or the notation to be used for coordinating symbolic names between the Ada program and interfaced assembly code. - The subprogram calling conventions of the compiler, including register saving, exception propagation, parameter passing, and function value returning. Note: Objects referenced from machine code or assembly language should ordinarily be specified as VOLATILE, since the implementation cannot be relied upon to detect every read and write operation on such an object. G.2. Interrupts An interrupt represents a class of events that are detected by the hardware or system software. An occurrence of an interrupt is separable into generation and delivery. Generation of the occurrence is an event in the underlying hardware or system. Between generation and delivery, the interrupt occurrence is pending. Certain interrupts may be blocked. When an interrupt is blocked, all occurrences are prevented from being delivered. Whether such occurrences remain pending or are lost depends on the hardware. Interrupts may be attached to parameterless procedures of library-level protected records. A procedure that is attached to an interrupt acts as the handler of that interrupt. A particular attachment of a handler to an interrupt is called an interrupt handler binding. While a binding is in force, the handler is invoked once for each delivered occurrence of the interrupt. The handler that is executed for the delivery of an interrupt is the most recent binding for that interrupt at the time the interrupt is delivered. Multiple interrupts may be attached to the same handler. Certain interrupts are reserved. An interrupt is reserved if it is one of an implementation-defined set of interrupts for which user-defined handlers are not supported, or if the interrupt is currently attached to a task entry by means of an address clause (see G.2.3). In a system that supports priorities, the dispatching of an interrupt handler is as if it were called by a task whose base priority is the hardware priority of the interrupt. Depending on the underlying system, this base priority may vary from one generation to another, for the same interrupt. Each interrupt has a default treatment, which determines the system's response to that interrupt when there is no user-defined handler binding in effect. The set of possible default treatments is implementation-defined, as is the method for configuring the default treatments for interrupts. The default treatment might be to keep the interrupt pending, or to deliver it to an implementation-defined handler. Examples of actions that an implementation- defined handler might perform include aborting the program, ignoring (i.e., discarding occurrences of) the interrupt, and queuing one or more occurrences of the interrupt for possible later delivery when a user-defined handler is attached. G.2.1. Specification of the Package INTERRUPT_MANAGEMENT The package INTERRUPT_MANAGEMENT provides the operations needed to handle interrupts. package INTERRUPT_MANAGEMENT is type INTERRUPT_ID is implementation_defined; RESERVED_INTERRUPT: exception; function IS_RESERVED (INTERRUPT : INTERRUPT_ID) return BOOLEAN; type HANDLER_TYPE is access protected procedure; procedure ATTACH_HANDLER (HANDLER : HANDLER_TYPE; INTERRUPT : INTERRUPT_ID); procedure DETACH_HANDLER (INTERRUPT : INTERRUPT_ID); function IS_ATTACHED (INTERRUPT : INTERRUPT_ID) return BOOLEAN; function CURRENT_HANDLER (INTERRUPT : INTERRUPT_ID) return HANDLER_TYPE; end INTERRUPT_MANAGEMENT; The INTERRUPT_ID type is an implementation-defined type that is used to identify interrupts. Assignment and equality operations must be available for this type. The IS_RESERVED operation returns TRUE if and only if the specified interrupt is reserved. The RESERVED_INTERRUPT exception is raised by an attempt to perform certain other operations on a reserved interrupt. The HANDLER_TYPE type is used to designate interrupt handlers. The ATTACH_HANDLER operation installs the specified protected procedure as the handler for the specified interrupt. The protected record object associated with the handler is marked so that the interrupt is blocked during the execution of every protected operation on that object. If a protected procedure is already attached to an interrupt when an attach operation is called on that interrupt, the previously attached procedure is first detached. In a system that supports priorities, PROGRAM_ERROR is raised if the ceiling of the protected record is lower than the priority of the interrupt. CONSTRAINT_ERROR is raised if the value of the HANDLER parameter is null. RESERVED_INTERRUPT is raised if the specified interrupt is reserved. The DETACH_HANDLER operation restores the default treatment for the specified interrupt. RESERVED_INTERRUPT is raised if the specified interrupt is reserved. The IS_ATTACHED operation returns TRUE if and only if there is an application interrupt handler binding in effect for the specified interrupt. RESERVED_INTERRUPT is raised if the specified interrupt is reserved. The CURRENT_HANDLER operation returns a value that designates the currently attached handler procedure for the specified interrupt. The value null is returned if there is no user-defined handler binding in effect. RESERVED_INTERRUPT is raised if the specified interrupt is reserved. The INTERRUPT_NAMES package is a child of INTERRUPT_MANAGEMENT. It contains a constant declaration for each interrupt that can be handled. The names and values of these constants are implementation-defined. An implementation may also provide constants for other interrupts, that cannot be handled by the application. package INTERRUPT_MANAGEMENT.INTERRUPT_NAMES is implementation_defined : constant INTERRUPT_ID := implementation_defined; . . . implementation_defined : constant INTERRUPT_ID := implementation_defined; end INTERRUPT_MANAGEMENT.INTERRUPT_NAMES; G.2.2. Static Binding The ATTACH_HANDLER pragma is provided to indicate a static association between an interrupt and a procedure within a protected record object. The handler is attached as part of the elaboration of the object, and it is detached on exit from the object's scope. pragma ATTACH_HANDLER(protected_procedure_name, interrupt_id); This pragma may only appear in the specification of a library-level protected record declaration. Moreover, any object of a protected record type with this pragma must be declared at the library-level. Elaboration of this pragma is a bounded error if the specified interrupt already has a handler binding in effect. The possible effects of this error are: - If the error is detected at compile time, the compilation is rejected. - Unless run-time system checks are suppressed (see H.11), RESERVED_INTERRUPT is raised at the time the pragma is elaborated. - If run-time system checks are suppressed, elaboration of the pragma overrides the previous handler binding. It is implementation defined whether the interrupt treatment reverts to the previous handler binding, or to the default treatment, on exit from the scope of the protected record object. The INTERRUPT_ID parameter may be an expression that depends on a discriminant of the protected record type. The expression is reevaluated upon elaboration of each object of the type. If the system supports priority locking of protected records, then a check is made during elaboration of the protected record object that the ceiling priority of the protected record is in INTERRUPT_PRIORITY range, and that this ceiling is at least as high as the highest priority at which the interrupt may be delivered. PROGRAM_ERROR is raised if this check fails. (See H.3.) G.2.3. Interrupt Task Entries Implementations may optionally support an address clause for a task entry. The correspondence of addresses to interrupts is implementation-defined. The task entry may be directly attached to an interrupt, or an internal interrupt handler may be provided by the implementation to connect the interrupt with the task entry. The exact mechanism and the imposed limitations are implementation-defined. When an interrupt is attached to a task entry, it is considered a reserved interrupt. RESERVED_INTERRUPT is raised by elaboration of a task object whose entry has an address clause if the interrupt specified by the address clause already has a handler binding in effect or is otherwise reserved. G.2.4. Implementation Requirements - The implementation must provide an implementation-defined mechanism for the application to reserve stack space for interrupt handlers, sufficient to handle as many levels of interrupts as the system permits, without overflow. - The implementation must not allow itself to be interrupted when it is in a state where it is unable to support all the operations not explicitly disallowed for protected operations of protected records (see 9.7). This is subject to the permission for an implementation to disallow the use of the default allocator from an interrupt handler. - If the hardware holds occurrences of an interrupt that are generated while the interrupt is blocked, the implementation must make these occurrences available for later delivery to the user. Notes: See H.3 for additional requirements on interrupt blocking when priority locking is supported. Even though dispatching of an interrupt handler is modeled after a hardware task calling the handler, it is not required that the invocation of an interrupt handler be implemented as a call to the handler from an Ada task. The handler may be directly invoked by the hardware. TASK_IDENTIFICATION.CURRENT_TASK must not be called by an interrupt handler (see G.8). G.2.5. Documentation Requirements - For each interrupt, which interrupts are blocked from delivery when a handler for that interrupt executes. - Any interrupts that cannot be blocked, and the effect of binding a handler to such an interrupt, if this is possible. - Which run-time stack the interrupt handler uses, how to specify which stack to use if more than one option exists, and how to specify how much stack space to reserve for interrupt handlers. - Any implementation- or hardware-specific activity that happens before the user-defined interrupt handler gets control (e.g., reading device registers, acknowledging devices). - Any timing or other limitations imposed on the interrupt handler code to ensure proper behavior of the implementation. - The state (blocked/unblocked) of the user-attachable interrupts when the program starts, and how the program is protected from interrupts before it can attach handlers to them. - Whether an interrupted task may resume execution before the interrupt handler returns. - The treatment of interrupt occurrences that are generated while the interrupt is blocked; i.e., whether one or more occurrences are held for later delivery, or all are lost. G.2.6. Metrics - The worst case interrupt handling overhead, in clock cycles. This is the execution time not directly attributable to the handler procedure or the interrupted execution. It is estimated as C - (A+B), where A is how long it takes to complete a given sequence of instructions without any interrupt, B is how long it takes to complete a normal call to a given protected procedure, and C is how long it takes to complete the same sequence of instructions when it is interrupted by one execution of the same procedure invoked via an interrupt. - The maximum time the run-time system blocks interrupts. If this is different for different interrupt priority levels, it should be specified for each level. Note: If the processor has cache memory and the activity of an interrupt handler can invalidate the contents of cache memory, the instruction sequence and interrupt handler used to measure interrupt handling overhead should be chosen so as to achieve the worst-case execution time lost due to cache misses. G.3. User-Defined Allocators This section specifies mechanisms that enable a user to replace the predefined implementation of new and UNCHECKED_DEALLOCATION by calls to a user-defined storage management system. G.3.1. STORAGE_POOL Representation Clause The basic operations for allocating and deallocating objects designated by an access type may be overridden by associating a storage pool object with the access type. A storage pool object is an object of a type derived from SYSTEM.ROOT_STORAGE_POOL. Each access type T has an attribute, T'STORAGE_POOL, which denotes the storage pool object associated with the access type, viewed as an object of the class-wide type SYSTEM.ROOT_STORAGE_POOL'CLASS. The storage pool object associated with an access type is specified by an attribute definition clause. for access_type_name'STORAGE_POOL use object_name; The access type name must denote the first named subtype of an access type. The object name must denote an object of a type derived from SYSTEM.ROOT_STORAGE_POOL. The SYSTEM.ROOT_STORAGE_POOL type is a tagged limited private type, with the following primitive operations: procedure INITIALIZE( POOL : in out ROOT_STORAGE_POOL); procedure FINALIZE(POOL : in out ROOT_STORAGE_POOL); procedure ALLOCATE( POOL : in out ROOT_STORAGE_POOL; ADDRESS : out SYSTEM.ADDRESS; STORAGE_SIZE : in SYSTEM.STORAGE_COUNT; ALIGNMENT : in SYSTEM.STORAGE_COUNT) is <>; procedure DEALLOCATE( POOL : in out ROOT_STORAGE_POOL; ADDRESS : in SYSTEM.ADDRESS; STORAGE_SIZE : in SYSTEM.STORAGE_COUNT) is <>; A user-defined storage pool type is obtained by extending this type, overriding the primitive operations, and defining an object of the type. The INITIALIZE operation is called for each storage pool object, during elaboration of the object, after any default initializations. Its purpose is to allow more complete initialization of user-defined storage pools. Abortion is deferred during this operation. The default version of this operation simply returns, with no other effect. The FINALIZE operation is called for each storage pool object, at the end of the object's lifetime. Its purpose is to allow recovery of storage associated with user-defined storage pools. The default version of this operation simply returns, with no other effect. The ALLOCATE operation is called by the compiler-generated code for the evaluation of an allocator of the specified access type, to obtain space for the designated object. It must return in the parameter ADDRESS the address of the lowest addressable storage unit of a contiguous block of free storage. That address must be a multiple of ALIGNMENT. The block of free storage must be at least STORAGE_SIZE storage units in length. PROGRAM_ERROR must be raised if the ALIGNMENT or STORAGE_SIZE is larger than is supported by the allocation algorithm. STORAGE_ERROR must be raised if a block of free storage satisfying these requirements cannot be allocated. The DEALLOCATE operation is called by the compiler-generated code for instantiations of UNCHECKED_DEALLOCATION for the specified access type. It should recover the block of storage whose lowest addressable storage unit is specified by the parameter ADDRESS. This procedure may rely on this address being an address returned by ALLOCATE on the specified pool. It should raise PROGRAM_ERROR if it detects that the address is not one returned by ALLOCATE. This procedure may ignore the STORAGE_SIZE parameter, depending on the algorithm. This procedure must not depend on any portion of the content of the allocated block (i.e. the storage units from ADDRESS through ADDRESS+STORAGE_SIZE-1) being preserved between allocation and deallocation. The writer of the user-defined allocation and deallocation procedures, and users of allocators for the associated access type, are responsible for dealing with any interactions with tasking and protected records. In particular: - If the allocators are used in different tasks, they require mutual exclusion. - If they are used inside protected records, they cannot suspend execution. Example of user-defined allocator: To associate an access type with a storage pool object, the user first declares a pool object of some type derived from ROOT_STORAGE_POOL. Then, immediately within the scope where an access type T is defined, the user defines its STORAGE_POOL attribute, as follows: POOL_OBJECT : SOME_STORAGE_POOL_TYPE; type T is access ... for T'STORAGE_POOL use POOL_OBJECT; Another access type may be added to an existing storage pool, via: for T2'STORAGE_POOL use T'STORAGE_POOL; As usual, a derivative of ROOT_STORAGE_POOL may define additional operations, such as MARK or RELEASE. For example, presuming that MARK_RELEASE_POOL_TYPE has two additional operations, MARK and RELEASE, the following is a possible use: type MARK_RELEASE_POOL_TYPE (POOL_SIZE : STORAGE_COUNT; BLOCK_SIZE: STORAGE_COUNT) is new ROOT_STORAGE_POOL with limited private; ... MR_POOL : MARK_RELEASE_POOL_TYPE (POOL_SIZE => 2000, BLOCK_SIZE => 100); type ACC is access ... for ACC'STORAGE_POOL use MR_POOL; ... MARK(MR_POOL); ... RELEASE(MR_POOL); Notes: The primitives ALLOCATE and DEALLOCATE are declared as abstract (see 6.1), and therefore they must always be overridden when a new storage pool type is declared. G.3.2. Interactions with STORAGE_SIZE Attribute Either the STORAGE_POOL or the STORAGE_SIZE attribute, but not both, may be specified for a given access type. If the STORAGE_SIZE is specified for an access type, it specifies the size of the associated anonymous storage pool. That space is reclaimed on exit from the scope of the access type. If STORAGE_POOL is not specified, the default storage pool is an anonymous pool that is associated with the scope of the access type. Whether this space is reclaimed is implementation defined. G.3.3. MAX_STORAGE_SIZE Attribute The attribute T'MAX_STORAGE_SIZE is the maximum value for STORAGE_SIZE that will be requested via ALLOCATE for an object of subtype T. The value of this attribute is of type root_integer'CLASS. Note: If T is unconstrained, T'MAX_STORAGE_SIZE may be very large. G.3.4. Implementation Requirements - Allocators for the access type must obtain all the required storage for an object of the designated type by calling the specified ALLOCATE procedure. (It may be called more than once, if the representation of T is not contiguous.) - Instantiations of UNCHECKED_DEALLOCATION (see 13.10.1) for the access type must only call the specified DEALLOCATE procedure. - The maximum value of the STORAGE_SIZE parameter in calls to the user-defined ALLOCATE procedure associated with an access type that designates T must not exceed T'MAX_STORAGE_SIZE. G.3.5. Documentation Requirements - Any additional restrictions, which may cause the implementation to reject the representation clause. - Any cases in which the implementation allocates heap storage for a purpose other than the evaluation of an allocator. - The range of values that a user-defined ALLOCATE procedure needs to accept for the ALIGNMENT parameter. - How storage is allocated for objects created by allocators of a type for which there is no STORAGE_POOL or STORAGE_SIZE clause, and whether this storage is reclaimed. G.4. User-Defined Finalization Controlled types have associated initialization and finalization operations. A type is controlled if it is derived from the language-defined type FINALIZATION_SUPPORT.CONTROLLED. package FINALIZATION_SUPPORT is type CONTROLLED is tagged limited private; procedure INITIALIZE(OBJ : in out CONTROLLED); procedure FINALIZE(OBJ : in out CONTROLLED); private type CONTROLLED is implementation_defined; end FINALIZATION_SUPPORT; The default implementations of INITIALIZE and FINALIZE have null bodies. Overriding these procedures allows user-defined actions to be associated with the initialization and finalization operations. Elaboration of an object of a controlled type causes its initialization operation to be invoked. Immediately prior to leaving (in the sense of RM 9.4(6)) the scope of an object of a controlled type, its finalization operation is invoked. Similarly, the finalization operation is invoked when UNCHECKED_DEALLOCATION is called on an object of a controlled type. Immediately prior to leaving the scope of a storage pool object (see G.3), the finalization operations of controlled objects allocated in the pool are invoked. This happens before the finalization operation of the pool object itself is invoked. Initialization for an object O of a controlled type C proceeds (recursively) as follows: - First, if C has components of a controlled type, initialization is performed for each such component of O. - Second, the procedure INITIALIZE for type C is invoked with O as the parameter. When leaving a frame, finalization for objects of a controlled type within the frame is performed in the reverse of the order of their initialization. After performing finalization of an object the storage occupied by the object can be reclaimed. For a function returning a local object of a controlled type, the scope of the function is not left until the returned value is no longer accessible (see 7.4.5). Abort of a task or sequence of statements (see 9.12) is deferred during an initialization or finalization operation on an object of a controlled type. Exceptions raised during a finalization operation are not propagated. If an exception is raised and not handled by a finalization operation, that operation is completed. If any finalization operation completes due to an unhandled exception, then after leaving the completed scopes, PROGRAM_ERROR is raised at the point where control would be transferred in the absence of finalization. [Note: These rules are intended to be analogous to the rules for exceptions raised during the activation of a task.] Note: It is a consequence of the language rules that all controlled types must be declared at library-level (see 3.4.1). Example of a type with user-defined finalization: with FINALIZATION_SUPPORT; package DYNAMIC_STRING is type DYN_STRING(INITIAL_SIZE : NATURAL := 0) is new FINALIZATION_SUPPORT.CONTROLLED with limited private; procedure INITIALIZE(STR : in out DYN_STRING); -- Allocates the initial buffer when INITIAL_SIZE > 0 procedure FINALIZE(STR : in out DYN_STRING); -- Frees the current buffer procedure ASSIGN(LEFT : out DYN_STRING; RIGHT : in DYN_STRING); -- Copies the value of the RIGHT into LEFT, -- freeing and reallocating the buffer if necessary function "&"(LEFT, RIGHT : DYN_STRING) return DYN_STRING; -- Catenate LEFT and RIGHT, return combined string function CONSTRUCT(STR : STRING) return DYN_STRING; -- Construct dynamic string from Ada STRING function VALUE(STR : DYN_STRING) return STRING; -- Return current value of STR as Ada STRING function "="(LEFT, RIGHT : DYN_STRING) return BOOLEAN; -- Dynamic string equality operator private type STRING_PTR is access STRING; type DYN_STRING(INITIAL_SIZE : NATURAL := 0) is new FINALIZATION_SUPPORT.CONTROLLED with record LENGTH : NATURAL := 0; BUFFER : STRING_PTR; end record; end DYNAMIC_STRING; with UNCHECKED_DEALLOCATION; package body DYNAMIC_STRING is procedure FREE is new UNCHECKED_DEALLOCATION(STRING, STRING_PTR); procedure INITIALIZE(STR : in out DYN_STRING) is begin if STR.INITIAL_SIZE > 0 then STR.BUFFER := new STRING(1..STR.INITIAL_SIZE); end if; end INITIALIZE; procedure FINALIZE(STR : in out DYN_STRING) is begin FREE(STR.BUFFER); end FINALIZE; -- Etc. end DYNAMIC_STRING; G.5. Elaboration Control The PREELABORATE pragma specifies that the library package is to be preelaborated. pragma PREELABORATE(library_package_name); The package must be preelaboratable, meaning that it has no sequence of statements in its package body and it has no declarations whose elaboration or finalization includes the evaluation of an expression with a variable name or an allocator, or could result in the execution of a program unit body. Further, a preelaboratable package may depend only on other pure (see 10.5.1) or preelaboratable library units. Such a package is elaborated prior to all non-preelaborated packages, in an order consistent with the dependences. Notes: The restriction against execution of a program unit body means that no tasks may be created since they would have executed upon activation. Calling a subprogram declared in a preelaborated package cannot raise PROGRAM_ERROR due to ELABORATION_CHECK. G.5.1. Implementation Requirements - The elaboration of constants declared in a preelaborated package does not use memory write operations (after load time). Notes: It is recommended that preelaborated packages be implemented in such a way that there will be little or no code executed for elaboration at run time. G.5.2. Documentation Requirements - Any circumstances under which elaboration of a preelaboratable or pure package causes code to be executed at run time. - Whether the method used for initialization of preelaborated variables allows a program to be restarted without reloading. G.6. Unsigned Integers This subsection includes the specification for a standard package providing named unsigned integer types, including operations that treat unsigned integer values as sequences of bits. package UNSIGNED_INTEGERS is pragma PURE; type ROOT_UNSIGNED is new root_integer cyclic base range 0..implementation_defined; function "and"(LEFT,RIGHT: ROOT_UNSIGNED) return ROOT_UNSIGNED; function "or"(LEFT,RIGHT: ROOT_UNSIGNED) return ROOT_UNSIGNED; function "xor"(LEFT,RIGHT: ROOT_UNSIGNED) return ROOT_UNSIGNED; function "not"(RIGHT: ROOT_UNSIGNED) return ROOT_UNSIGNED; function SHIFT_RIGHT(VALUE : ROOT_UNSIGNED; AMOUNT : NATURAL) return ROOT_UNSIGNED; function SHIFT_LEFT(VALUE : ROOT_UNSIGNED; AMOUNT : NATURAL) return ROOT_UNSIGNED; function ROTATE_RIGHT(VALUE : ROOT_UNSIGNED; AMOUNT : NATURAL) return ROOT_UNSIGNED; function ROTATE_LEFT(VALUE : ROOT_UNSIGNED; AMOUNT : NATURAL) return ROOT_UNSIGNED; function "=" (LEFT, RIGHT : ROOT_UNSIGNED) return BOOLEAN; function "<" (LEFT, RIGHT : ROOT_UNSIGNED) return BOOLEAN; function "<=" (LEFT, RIGHT : ROOT_UNSIGNED) return BOOLEAN; function ">" (LEFT, RIGHT : ROOT_UNSIGNED) return BOOLEAN; function ">=" (LEFT, RIGHT : ROOT_UNSIGNED) return BOOLEAN; function "+" (RIGHT : ROOT_UNSIGNED) return ROOT_UNSIGNED; function "-" (RIGHT : ROOT_UNSIGNED) return ROOT_UNSIGNED; function "+" (LEFT, RIGHT : ROOT_UNSIGNED) return ROOT_UNSIGNED; function "-" (LEFT, RIGHT : ROOT_UNSIGNED) return ROOT_UNSIGNED; function "*" (LEFT, RIGHT : ROOT_UNSIGNED) return ROOT_UNSIGNED; function "/" (LEFT, RIGHT : ROOT_UNSIGNED) return ROOT_UNSIGNED; function "rem" (LEFT, RIGHT : ROOT_UNSIGNED) return ROOT_UNSIGNED; function "mod" (LEFT, RIGHT : ROOT_UNSIGNED) return ROOT_UNSIGNED; function "**" (LEFT: ROOT_UNSIGNED; RIGHT: INTEGER) return ROOT_UNSIGNED; -- Additional implementation-defined unsigned types follow. -- (These are examples only) type UNSIGNED_32 is new ROOT_UNSIGNED cyclic base range 0..2**32-1; type UNSIGNED_16 is new ROOT_UNSIGNED cyclic base range 0..2**16-1; type UNSIGNED_8 is new ROOT_UNSIGNED cyclic base range 0..2**8-1; pragma CALLING_CONVENTION(INTRINSIC, "and"); pragma CALLING_CONVENTION(INTRINSIC, "or"); -- ...and so on for all subprograms declared in this package. end UNSIGNED_INTEGERS; Notes: Because unsigned (base) types are not symmetric around zero, they cannot be defined using a normal integer type definition. The use of the words cyclic base in the above definitions of the unsigned types is intended to indicate that these are not normal integer types, but instead rely for their definition on a special implementation-defined mechanism. The effect is that the new types are ``cyclic'' types using modular arithmetic operations, and that the base type attributes are being defined as well as the subtype attributes. For example, UNSIGNED_16'BASE'FIRST is 0 and UNSIGNED_16'BASE'LAST is 2**16-1. [Note: Normal integer type derivation cannot change the values of the base type attributes.] G.6.1. Unsigned Integer Types ROOT_UNSIGNED is an unsigned integer type, with a size at least as great as the largest predefined signed integer type. An implementation may provide one or more additional unsigned integer types with a name of the form UNSIGNED_n where n is the size in bits of the type. There should be unsigned types corresponding to the sizes of the predefined signed integer types. In the above package, we illustrate three such types. An unsigned integer type is an implementation-defined integer type. As for all integer base types (see 3.5.4), the value set is the infinite set of mathematical integers. However, the predefined operations on unsigned integer types are performed modulo the cycle of the (base) type, meaning that the size of an unsigned object need only be sufficient to represent the value modulo the cycle. For an unsigned type T, the cycle equals T'BASE'LAST + 1. The cycle for ROOT_UNSIGNED is implementation defined. For the types with names of the form UNSIGNED_n, the cycle is 2**n. For an unsigned type T, when calling T'IMAGE, and when performing type conversion, it is necessary to pick one value from the equivalence class of integer values that are equal modulo the cycle. Firstly, for type conversion to a signed type, if there is only one value in the equivalence class that is within the base range of the signed type, then that value is chosen. For other conversions and for T'IMAGE, the canonical value in the range 0 .. T'BASE'LAST is chosen. For example, when converting a value of an unsigned type T to a signed subtype S where T'BASE'LAST = 2*S'BASE'LAST+1, the type conversion can be performed without changing the binary representation of the value. The target subtype check after the type conversion will raise CONSTRAINT_ERROR if and only if, after conversion, the value is outside S'FIRST .. S'LAST. Converting to an unsigned (base) type never raises CONSTRAINT_ERROR. However, if an additional range constraint applies to the target subtype, then that constraint is checked. Unsigned integer types must not be provided as actual type parameters for a generic formal type specified with ``range <>.'' [Note: This restriction is due to the difficulty of defining and implementing the attributes and operators properly for such types in a generic.] Unsigned integer types are permitted as actual type parameters for a generic formal type derived from ROOT_UNSIGNED. Unsigned integer types are also included within root_integer'CLASS, and hence are usable with integer literals, and as operands to the VAL attribute, etc. G.6.2. Boolean and Shift Operators The Boolean and shift operators are specified in terms of their effects on a binary representation of the (canonical) value of an unsigned integer, viewed as a sequence of bits. In the specification of these operations, a value V of an unsigned integer type T is viewed as a sequence of bits, numbered V , T'SIZE-1 V ..., V . T'SIZE-2 0 i The value of V is the sum over i in 0 .. T'SIZE-1 of V * 2 . i The Boolean operators, and, or, xor, not, return a value whose ith bit is specified as follows (where 0 is false and 1 is true): (L binary_operator R) = L binary_operator R ; i i i (unary_operator R) = unary_operator R . i i The shift operators, SHIFT_RIGHT, SHIFT_LEFT, ROTATE_LEFT, and ROTATE_RIGHT, return a result whose ith bit is specified as follows: SHIFT_RIGHT(V,A) = V , if i+A < T'SIZE i i+A SHIFT_RIGHT(V,A) = 0, if i+A >= T'SIZE i SHIFT_LEFT(V,A) = V , if i-A >= 0 i i-A SHIFT_LEFT(V,A) = 0, if i-A < 0 i ROTATE_RIGHT(V,A) = V i (i+A) mod T'SIZE ROTATE_LEFT(V,A) = V i (i-A) mod T'SIZE G.6.3. Relational and Arithmetic Operators The relational operators of an unsigned integer type T are defined in terms of comparisons of canonical values, which are those in the range 0..T'BASE'LAST. The arithmetic operators of an unsigned integer type T have the conventional meanings for arithmetic modulo T'BASE'LAST+1. G.6.4. Implementation Requirements - UNSIGNED_n'SIZE must be n. - The predefined operators on an unsigned integer type must not raise CONSTRAINT_ERROR, except when the RIGHT operand of "rem", "mod" or "/" is zero. - Unsigned integer types with names of the form UNSIGNED_n should be provided for the natural operand sizes supported by the underlying processor architecture. - Although ROOT_UNSIGNED may have a cycle that reflects a ones-complement architecture, unsigned types with names of the form UNSIGNED_n must have cycles that are a power of 2. G.6.5. Documentation Requirements - The correspondence of the bit positions of an unsigned object to addresses, if the size of the unsigned object exceeds one storage unit. G.7. Shared Variable Control Two pragmas will be provided for controlling the use of shared variables: pragma ATOMIC(object_or_component_name); pragma VOLATILE(object_or_component_name); The pragma ATOMIC when applied to an object or a component indicates that all reads and writes of the object or component must be indivisible. An implementation must reject a program containing such a pragma if it cannot support indivisible reads and writes of the specified object. [Note: This pragma is intended to replace the pragma SHARED of Ada 83. The name was changed to avoid confusion with other kinds of shared variables.] The pragma VOLATILE when applied to an object or a component indicates that all reads and writes of the object, component, or its subcomponents, must be direct to memory. [Note: Further description of these pragmas will be provided at a later date.] G.8. Task Identification This section describes operations and an attribute that can be used to obtain the identity of a task from context, and associate user-defined information with a task. G.8.1. Package Specification of TASK_IDENTIFICATION package TASK_IDENTIFICATION is type TASK_ATTRIBUTE is tagged limited private; type TASK_ATTRIBUTE_PTR is access all TASK_ATTRIBUTE'CLASS; procedure SET_ATTRIBUTE (T : TASK_CLASS; VALUE : TASK_ATTRIBUTE_PTR); function GET_ATTRIBUTE (T : TASK_CLASS) return TASK_ATTRIBUTE_PTR; function CURRENT_TASK return TASK_CLASS; private . . . implementation_defined end TASK_IDENTIFICATION; The SET_ATTRIBUTE operation replaces the current value of the task attribute of T with VALUE. TASKING_ERROR is raised if T is terminated. This operation is indivisible with respect to GET_ATTRIBUTE. The GET_ATTRIBUTE operation returns the current stored value of the task attribute of T. This value is null if no value was previously set. TASKING_ERROR is raised if T is terminated. The CURRENT_TASK function returns a value that identifies the calling task. This function may be used in conjunction with other operations requiring a task as argument, such as the abort statement and DYNAMIC_PRIORITY_SUPPORT.SET_PRIORITY (see H.5). Calling CURRENT_TASK from a hardware interrupt handler or the body of a protected record entry is a bounded error. The possible effects are: - If the error is detected at compile or link time, the compilation unit or linkage is rejected. - If the error is detected at run time, PROGRAM_ERROR is raised. - If the error is not detected at run time, a value of the type TASK_CLASS is returned. If CURRENT_TASK is called from an interrupt handler that preempted a user or implementation task, the value returned may identify the preempted task. If it is called from within an entry body, the value returned may identify the task that is actually executing the entry body. In all other cases, the value is implementation-defined. Notes: These operations are intended for use in writing user-defined task scheduling packages and constructing server tasks. The scope checking rules for general access types (see 3.9.2) ensure that no dangling references will result from usage of task attributes. The fact that TASK_ATTRIBUTE_PTR is a general access type, allows it to point to statically allocated objects. Since TASK_ATTRIBUTE is a tagged type and therefore extensible, the access value can point to many different types representing different information. A reader of a value may ensure that it is of the expected designated type by using a membership test. G.8.2. CALLER Attribute of an Entry The attribute E'CALLER, which applies to an entry name E, returns a value of the type root_task'CLASS that denotes the task whose call is now being serviced. This attribute may only be used inside the entry body or the accept statement named by the attribute prefix. G.8.3. Documentation Requirements - The effect of calling CURRENT_TASK from an interrupt handler or entry body, and the effects of using the returned value in further operations. H. Real-Time Systems This section specifies additional characteristics of Ada implementations intended for real-time systems software. To conform to this Annex, an implementation must also conform to the Systems Programming Annex. H.1. Priorities This subsection specifies the priority model for real-time systems. Priorities provide a basis for resolving competing demands of tasks for processing resources, and determining the order in which queued entry calls are served. The general principle is that whenever tasks compete for processing resources or entry service a task with higher priority should win. H.1.1. Priority Subtypes The full range of task priorities supported by an implementation is specified by the integer subtype SYSTEM.ANY_PRIORITY. This range is implementation- defined, but must contain at least 31 values. The subrange of task priorities that are high enough to require the blocking of one or more interrupts is specified by the subtype SYSTEM.INTERRUPT_PRIORITY, and it must include at least one value. The subrange of task priorities below SYSTEM.INTERRUPT_PRIORITY'FIRST is specified by the subtype SYSTEM.PRIORITY and it must include at least 30 values. The following relationship must always be true: SYSTEM.ANY_PRIORITY'FIRST = SYSTEM.PRIORITY'FIRST SYSTEM.ANY_PRIORITY'LAST = SYSTEM.INTERRUPT_PRIORITY'LAST SYSTEM.PRIORITY'LAST + 1 = SYSTEM.INTERRUPT_PRIORITY'FIRST H.1.2. Base and Active Priorities Each task has a base priority, which is the intrinsic priority of the task. The base priority of a task is specified by default or by means of a pragma (see H.1.3) and can only be changed by a call to DYNAMIC_PRIORITY_SUPPORT.SET_PRIORITY (see H.5). At any instant, each task has an active priority, which is the priority at which the task competes for processing resources, and for entry service if PRIORITY_QUEUING is specified (see H.4.2). The active priority of a task is the maximum of its base priority and all the priorities the task is inheriting at that instant. This Annex specifies priority inheritance under the following conditions: - During activation, a task being activated inherits the active priority of the task that activates it (see RM 9.3). - During rendezvous, the task accepting the entry call inherits the active priority of the caller (see RM 9.5). - During a protected operation of a protected record, a task inherits the ceiling priority of the protected record object (see H.3). In all these cases, the priority ceases to be inherited as soon as the condition calling for the inheritance ceases to be satisfied. An implementation may cause a task to inherit priority under implementation- defined conditions, but only if an implementation-defined configuration pragma (see Chapter 2) applies to the active partition that contains the task. H.1.3. Base Priority Specification Every task has a base priority. The initial base priority of a task is specified by default, or by pragma. By default, the initial base priority of a task is the base priority of the task that activates it, and the initial base priority of the environment task that elaborates an active partition is SYSTEM.DEFAULT_PRIORITY. The value of SYSTEM.DEFAULT_PRIORITY is: (SYSTEM.PRIORITY'FIRST + SYSTEM.PRIORITY'LAST)/2. The default initial base priority of a task is overridden if one of the pragmas pragma PRIORITY(expression); pragma INTERRUPT_PRIORITY [(expression)]; appears in the corresponding task specification. Likewise, the default priority of the environment task that elaborates an active partition is overridden if such a pragma appears in the outermost declarative part of the corresponding main subprogram. If such a pragma appears in a subprogram other than a main subprogram, it has no effect. Only one of these pragmas is permitted for any task type or subprogram. The priority specified by the PRIORITY or INTERRUPT_PRIORITY pragma is the value of the expression in the pragma, if an expression is present. For the PRIORITY pragma, the value of the expression must be in the range SYSTEM.PRIORITY. For the INTERRUPT_PRIORITY pragma, the value of the expression must be in the range SYSTEM.ANY_PRIORITY. If the INTERRUPT_PRIORITY pragma appears without an expression, it specifies INTERRUPT_PRIORITY'LAST. The expressions in the PRIORITY and INTERRUPT_PRIORITY pragmas are evaluated once for each elaboration of an object of the task type, and may depend on discriminants of the type. Notes: The requirements specified for the number of values in SYSTEM.PRIORITY and SYSTEM.INTERRUPT_PRIORITY are only minimal. Implementations are encouraged to provide more values, if this can be supported efficiently by the underlying architecture. H.2. Task Dispatching Model This subsection specifies certain aspects of the dispatching policy. It will provide both the general model for describing policies, definitions, and a specific default Run-Until-Blocked-FIFO-within-priorities policy. Additional policies are allowed. The dispatching policy determines which task is executed, when more than one task is eligible for execution using the same processing resources. H.2.1. The Conceptual Model In order to be executed, two things are required: - the task must be eligible for execution; - the processing resources required by that task must be available. Physical processors and protected records are processing resources. Any other processing resources that a task may require are implementation-defined. Physical processors are preemptible, and are allocated according to task priority. That is, a processor that is executing a task remains available to execute tasks of higher active priority, within the set of tasks that the processor is able to execute. Protected records are not preemptible (see 9.7). Dispatching policies are specified in terms of conceptual dispatching queues. Dispatching queues are ordered lists of tasks, in which the first position is called the head and the last position is called the tail. Each processor has one running task, which is the task currently being executed by that processor. Each processor has one dispatching queue for each priority value. At any instant, each dispatching queue of a processor contains all the tasks of that active priority that are ready on that processor -- i.e. those tasks that are eligible for execution, are not running on any processor, and can be executed using that processor and other available resources. The task selected to run is the one at the head of the highest priority non-empty dispatching queue of that processor. This task is then removed from all dispatching queues to which it may belong. (While a task is running, it is not on any dispatching queue.) A new running task is selected whenever: - The running task is suspended or completes, or the dispatching policy requires it to go back to the dispatching queue. - There is a nonempty dispatching queue with higher priority than the active priority of the running task. In this case, the running task is said to be preempted. Notes: The dispatching queues are purely conceptual; there is no requirement that such lists physically exist in an implementation. In a multiprocessor system, a task may be on the dispatching queues of more than one processor. Conversely, more than one such queues can be viewed as one. This model specifies preemptive dispatching, based on active priority. It does not specify the treatment of tasks with equal priority. However, the default dispatching policy, described in the next section, describes the default treatment. H.2.2. The Default Dispatching Policy This section describes the default dispatching policy. The treatment of tasks with equal active priority is specified in terms of events that cause a task to be added to a dispatching queue. In this policy, modifications to the dispatching queues occur only as follows: - When a task is preempted, it is added at the head of the queue for its active priority. - When a task becomes ready, it is added at the tail of the queue for its active priority. - When the active priority of a ready task changes, it is added at the tail of the queue for its new active priority. (Running tasks are not on any dispatching queue.) - When the base priority of a running or ready task changes, it is added at the tail of the queue for its active priority. Note: The only time a running task yields to a task of equal priority is when its base priority is changed. This is deferred while the task is executing within a protected record. H.2.3. Documentation Requirements - How the user or implementation determines which tasks are eligible to execute on each processor, if there are several processors. H.3. Ceiling Priorities This section specifies the interactions between priority task scheduling and protected record ceilings. This is based on the concept of the ceiling priority of a protected record. Every protected record has a ceiling priority, or just ceiling, for short. The ceiling priority is an upper bound on the active priority a task may have when it calls protected operations of the protected object. PROGRAM_ERROR is raised if a task attempts a protected operation on a protected record object when the task's active priority is higher than the ceiling of the object. By default, the ceiling priority of a protected record object is SYSTEM.PRIORITY'LAST. The default ceiling priority of a protected record object is overridden if one of the PRIORITY or INTERRUPT_PRIORITY pragmas appears in the corresponding protected record specification. Only one of these two pragmas is permitted for any protected record specification. The priority specified by these two pragmas is determined as specified in H.1.3. Notes: While a task executes a protected operation, the task inherits the ceiling of that object (see H.2). Thus, such a task can only be preempted by tasks whose active priority is higher than the ceiling priority of the protected record object. If a protected record object has a ceiling priority in the range INTERRUPT_PRIORITY, certain interrupts will be blocked while protected operations of that object execute. In the extreme, if the ceiling is INTERRUPT_PRIORITY'LAST, all blockable interrupts will be blocked during protected operations of that object. The ceiling priority of a protected record must be in the INTERRUPT_PRIORITY range if one of its procedures is to be used as an interrupt handler (see G.2). When specifying the ceiling of a protected record, one should choose a value that is at least as high as the highest active priority at which tasks may be executing when they attempt to perform protected operations of the protected record. In determining this value the following factors, which may affect active priority, should be considered: DYNAMIC_PRIORITY_SUPPORT.SET_PRIORITY, nested protected operations, entry calls, task activation, and other implementation-defined factors. H.3.1. Implementation Requirements - The ceilings of resources that the implementation may use implicitly (such as in storage allocators) must be at least SYSTEM.PRIORITY'LAST. - There must be no possibility of deadlock involving only protected subprograms (as opposed to entries) unless it is explicit in the application. That is, for there to be a deadlock involving only protected subprograms there must be a cycle of tasks T ..T and 0 n protected records R ..R (not necessarily all distinct) such that 0 n each task T is executing a protected operation of R , and inside i i that operation it executes a protected operation of R . i+1 (mod n) Note: The latter requirement applies to multiprocessor systems. If the implementation is for a single processor, the ceiling priority rules guarantee that there is no possibility of deadlock involving only protected subprograms. H.3.2. Documentation Requirements - The ceilings of implementation-defined resources that may be used implicitly. This is important to a user because of potential ceiling violations. [Note: For example, suppose the implementation uses ceiling priorities to lock access to a storage pool, and the ceiling associated with the default pool is SYSTEM.PRIORITY'LAST. If a task with active priority SYSTEM.INTERRUPT_PRIORITY'FIRST attempts to evaluate an allocator it will be a ceiling violation, resulting in the allocator raising PROGRAM_ERROR.] H.4. Entry Queuing Policies This subsection specifies a mechanism for a user to specify an entry queuing policy, and defines one such policy. An entry queuing policy governs the order in which tasks are queued for entry service, and the order in which different queues are considered for service. This includes entry queuing order, the choice among open alternatives of a selective-accept statement, and the choice among queued entry calls of a protected record when more than one barrier opens. Only one queuing policy is allowed in each active partition. A compilation may specify a queuing policy by mentioning the name of the queuing policy in a QUEUING_POLICY pragma. This is a configuration pragma (see Chapter 2), and so it can only appear at the start of the compilation. The policy applies to every unit of that compilation. pragma QUEUING_POLICY(policy_simple_name); H.4.1. FIFO Queuing The default entry queuing policy is implementation-defined, subject to the requirements in 9.7. The name of this policy is FIFO_QUEUING. H.4.2. Priority Queuing This Annex specifies an alternate queuing policy, PRIORITY_QUEUING. - Calls to an entry are queued (and requeued) in the order of highest active priority first, and in order of arrival for callers of the same active priority. The active priority used is the one that the task has when the entry call is made. Changes to the active or base priority of a task after a call is first queued do not affect the priority that is used to determine the position of that call in entry queues. - When more than one alternative of a selective-accept statement is open and has queued calls, an alternative is selected whose queue has the highest priority caller at its head. If there is more than one such alternative, the one that is first in textual order within the selective-accept statement is selected. - When a protected operation has opened a barrier and there is more than one entry with an open barrier and queued calls, the next entry selected for service is the one whose queue has the highest priority caller at its head. If there is more than one such entry, the one that is first in textual order within the protected record declaration is selected. H.4.3. Other Queuing Policies An implementation may define other entry queuing policies, which specify alternate rules for entry queuing order, the choice among open alternatives of a selective-accept statement, and the choice among open entries of a protected record. Such a policy may introduce new forms of priority inheritance. An implementation-defined queuing policy may only be applied to an active partition if the QUEUING_POLICY pragma with an implementation-defined queuing policy name applies to some unit of that partition. It is recommended that the names of such policies end with ``_QUEUING''. H.5. Dynamic Priorities The DYNAMIC_PRIORITY_SUPPORT package provides support for querying and altering the base priority of a task. This package uses the type TASK_CLASS, which is the predefined class-wide type for the task class (see 3.4.1), to specify operations applicable to any task type. with SYSTEM; package DYNAMIC_PRIORITY_SUPPORT is procedure SET_PRIORITY(T : TASK_CLASS; PRIORITY : SYSTEM.ANY_PRIORITY); function GET_PRIORITY (T : TASK_CLASS) return SYSTEM.ANY_PRIORITY; end DYNAMIC_PRIORITY_SUPPORT; The SET_PRIORITY operation sets the base priority of the specified task to the specified value. The GET_PRIORITY function returns the current base priority of the specified task. TASKING_ERROR is raised if an attempt is made to set or query the priority of a terminated task. Under the default dispatching policy, any SET_PRIORITY causes the task to move to the tail of the dispatching queue corresponding to its active priority (see H.2.2). The effect of SET_PRIORITY on a task is deferred under the same circumstances that abortion of the task is deferred. Subject to this constraint, the change to the base priority (and the move to the tail of the dispatching queue) should take effect as soon as is practical for the implementation. In no case will it be later than the latest point at which abortion of the same task is required to take effect (see H.6). Notes: The rule for when TASKING_ERROR is raised is different from the rule determining when TASKING_ERROR is raised upon an entry call (see RM 9.5(16), MS 9.7). In particular, setting or querying the priority of an unactivated, completed, or abnormal task is allowed, so long as the task is not yet terminated. Changing the priorities of a set of tasks may be performed by a series of calls to SET_PRIORITY for each task separately. For this to work reliably, it should be done within a protected operation that has high enough ceiling to guarantee that the operation completes without being preempted by any of the affected tasks. Example of changing priorities of a set of tasks: type MODE_TYPE is range 0..2; type TASK_NUMBER is range 1..4; TASK_PRIORITY: array ( TASK_NUMBER, MODE_TYPE ) := ... ; protected MODE_CONTROL is procedure SET(MODE: MODE_TYPE); pragma PRIORITY(SYSTEM.PRIORITY'LAST); private record MODE: MODE_TYPE:= 0; end MODE_CONTROL; protected HIGH_PRIO_MODE_CONTROL is procedure SET(MODE: MODE_TYPE); pragma INTERRUPT_PRIORITY; end HIGH_PRIO_MODE_CONTROL; use DYNAMIC_PRIORITY_SUPPORT; protected body MODE_CONTROL is procedure SET(MODE: MODE_TYPE) is begin HIGH_PRIO_MODE_CONTROL.SET(MODE); SET_PRIORITY(T1,TASK_PRIORITY(1,MODE)); SET_PRIORITY(T2,TASK_PRIORITY(2,MODE)); end SET; end MODE_CONTROL; protected body HIGH_PRIO_MODE_CONTROL is procedure SET(MODE: MODE_TYPE) is begin SET_PRIORITY(T3,TASK_PRIORITY(3,MODE)); SET_PRIORITY(T4,TASK_PRIORITY(4,MODE)); end SET; end HIGH_PRIO_MODE_CONTROL; Notes on the example: The table TASK_PRIORITY specifies the priorities that the tasks T1 through T4 should have, for every mode. Here, in order to avoid blocking every task for a long time, the priority changes are done in stages, at two different active priorities, via two protected records. The task doing the priority change starts with a call to the lowest-priority protected record. This calls the next higher level. The priority adjustments of lower priority tasks can be preempted by execution of the higher priority tasks. H.5.1. Documentation Requirements - When the full effect of the priority change is realized. H.5.2. Metrics - The execution time of SET_PRIORITY, for the nonpreempting case, in clock cycles. This is measured for a call that modifies the priority of a ready task other than the calling task. The new base priority of the affected task is lower than the active priority of the calling task. The affected task is not on any entry queues or executing a protected operation. H.6. Immediate Abort This section specifies constraints on the immediacy of the effect of abortion. The effect of abortion of a task or aborting a sequence of statements is defined in 9.12. If the task that causes the abortion and the aborted task are on a single processor, the abortion takes effect no later than the next time the task to be aborted is eligible to execute outside of a region where abortion is deferred. If the task executing the abort statement and the aborted task are on separate processors, the abortion takes effect asynchronously, as soon as is permitted by the communications medium between the implementations on the two processors. Notes: Executing an abort statement cannot cause the task executing that statement to be suspended. Abortion does not change the active or base priority of the aborted task. Abortion cannot be more immediate than is allowed by the rules for deferral of abortion during finalization and in protected regions. The base priority of an aborted task may be changed explicitly, by means of DYNAMIC_PRIORITY_SUPPORT.SET_PRIORITY, even after it has been aborted (see H.5). H.6.1. Documentation Requirements - Any conditions under which abortion can be relied upon to take effect earlier than the latest that is specified by this Annex. - On a multiprocessor, any communication limitations that may cause the effect of abortion to be delayed. H.6.2. Metrics - The execution time of an abort statement, in clock cycles. This is measured in a situation where a task T2 preempts task T1 and aborts T1. T2 should verify that T1 has terminated, by means of the 'TERMINATED attribute. - The execution time of asynchronous transfer of control (ATC). This is measured between a point immediately before a task T1 executes a protected operation PR.CLEAR that opens the barrier of an entry PR.WAIT, and the point where task T2 resumes execution immediately after an entry call to PR.WAIT in a select statement with abortable final part. T1 preempts T2 while T2 is executing the abortable final part, and then suspends itself, so that T2 can execute. The execution time of T1 is measured separately, and subtracted out. H.7. Processor Time Accounting The CPU_TIME_ACCOUNTING package provides for measuring the processor time used by a task, and enforcing a processor time budget. It is based on the concept of charging discrete units of processor time to a task. The precision of time accounting that is provided by this package depends on the capabilities of the underlying system. This package is optional. H.7.1. Semantic Model For each processor, real time is partitioned into a sequence of segments of equal duration; these are the accounting units. Each of these units may be charged to a task, subject to the following minimum requirements: - No more than one task is charged for each unit. - If a task executes for all of a unit, it is charged for it. - If a task is charged for a unit then it executed on the associated processor for some part of that unit. - If a task does not execute for all of unit for which it is charged, then one of the following holds: 1. The charged task resumed execution after the start of the unit. 2. The charged task suspended itself during the unit. 3. The charged task was preempted during the unit. H.7.2. CPU_TIME_ACCOUNTING Package with MONOTONIC; package CPU_TIME_ACCOUNTING is ACCOUNTING_UNIT: constant MONOTONIC.FINE_DURATION:= implementation defined; subtype ACCOUNTING_RANGE is MONOTONIC.FINE_DURATION range 0.0..MONOTONIC.FINE_DURATION'LAST; function VALUE(T: SYSTEM.TASK_CLASS) return ACCOUNTING_RANGE; procedure SET_VALUE (T: SYSTEM.TASK_CLASS; VALUE: ACCOUNTING_RANGE); protected type OVERRUN_SIGNAL is procedure CLEAR; entry WAIT; pragma INTERRUPT_PRIORITY; private record ... -- implementation defined end OVERRUN_SIGNAL; type SIGNAL_PTR is access all OVERRUN_SIGNAL; procedure ATTACH_OVERRUN_SIGNAL (T: SYSTEM.TASK_CLASS; SIGNAL: SIGNAL_PTR); end CPU_TIME_ACCOUNTING; The constant ACCOUNTING_UNIT is the duration of the time accounting unit. It must be exactly representable in MONOTONIC.FINE_DURATION. The OVERRUN_SIGNAL type is used to signal CPU time budget overruns. The WAIT operation causes the caller to be suspended until the the CLEAR operation is called. Note: This protected record is a persistent one in the sense that if a CLEAR is called before WAIT, the caller of WAIT will continue, i.e. there need not be a pending waiter for CLEAR to have effect. The behavior of this package is explained in terms of a conceptual counter associated with each task. The counter represents the remaining CPU time budget of the task, and its value is always nonnegative. The initial value is zero. The function VALUE returns the current value of the CPU time counter associated with the specified task. The SET_VALUE procedure sets the value of the CPU time counter of the task T to the specified value. After the value is set to a positive value, the signal is cleared and the counter is decremented by ACCOUNTING_UNIT at the end of each unit of CPU time charged to the task, until the counter reaches zero. If a task has an OVERRUN_SIGNAL object attached to it, the implementation calls the CLEAR operation of that object whenever the CPU time counter of the task reaches zero, if the task is still running or ready. Otherwise, it calls CLEAR when the task becomes ready again, if the CPU time counter is still zero. The procedure ATTACH_OVERRUN_SIGNAL associates the designated OVERRUN_SIGNAL object with the specified task. If a null value is specified for SIGNAL, the association is broken. Notes: The OVERRUN_SIGNAL.CLEAR operation may be called by a user, though this is not the the primary intent. To cause a task T to be aborted when its processor time charges exceed a predetermined budget, the task T can place a call to SIG.WAIT in a select statement with an abortable final part, where SIG is the OVERRUN_SIGNAL object attached to T. To cause a task T to drop priority when its processor time charges exceed a predetermined budget, another task can call SIG.WAIT. This other task can then do the changing of priorities whenever the entry call completes. One task can monitor several other tasks, by using a selective entry call statement with calls to the SIG.WAIT entries of the overrun signal objects attached to each of the monitored tasks. To determine the processor time used to execute a sequence of statements, the counter may be set to a large number at the start of the sequence of statements, and the value of the counter may be read again at the end. H.7.3. Implementation Requirements - The time accounting unit must be at least as fine as the precision of the implementation of delays. H.7.4. Metrics - Execution time of SET_VALUE function, in clock cycles. This is measured when the value is positive and the calling task has higher priority than the affected task. - Execution time of the VALUE function, in clock cycles. This is measured when the calling task has higher priority than the queried task. H.8. Simple Tasking Model In order to permit the use of extremely efficient implementation techniques, a program may express the intent to abide by certain restrictions on the usage of the Ada tasking model. This intent is expressed via the pragma SIMPLE_TASKING. pragma SIMPLE_TASKING; This is a configuration pragma (see Chapter 2). The pragma is used to indicate the intent to abide by the following restrictions: - Only library packages are allowed to have dependent tasks (and hence, there are no hierarchies of dependent tasks). - Access types and objects of types with user-defined finalization are only declared in library packages. - The maximum number of task objects in the system at any one time is not greater than a user-configurable limit. - The maximum number of entries per task is not greater than a user-configurable limit. - The maximum storage necessary to hold all the actual parameters of an entry call (of tasks or protected records) is not greater than a user-configurable limit. - The maximum number of alternatives in a selective entry call statement is not greater than a user-configurable limit. - The maximum stack space needed by a task is not greater than a user-configurable limit for each priority level. - Potentially suspending operations (see 9.7) are not permitted within a subprogram, a declare block, or within an abortable final part. - The only declarations permitted within task bodies are subprogram declarations. The implementation is directed to take advantage of all these restrictions to produce a more efficient implementation, if possible. The implementation is also directed to detect and reject any violations of these restrictions. Run-time checks should be provided for any violations that cannot be detected at compile time or link time. Such checks should raise PROGRAM_ERROR, if a violation is detected at run time. H.8.1. Implementation Requirements - A method must be provided for the user to configure the limits mentioned above, at or before link time. H.8.2. Documentation Requirements - Whether use of this pragma results in any reduction in executable program size, reduction in storage requirement, or improvement in execution time performance. If possible, quantitative descriptions of such effects should be provided. - The default user-configurable limits, and how to modify them. - The ranges supported for user-configurable limits. - Any implicit usage of ``heap'' storage. Notes: This subsection can be formally supported by an implementation that simply enforces the pragma restrictions, without doing any special optimizations. However, that is contrary to the intent of the pragma except where the implementation is for a development host that is used in preliminary testing of an application that is eventually intended for an embedded target. In the latter case, enforcement of the pragma by the development host will help to identify code that will cause problems arising when the time comes to move the application to the final target. H.9. Monotonic Time This section is incomplete, and is subject to possibly major change. H.9.1. MONOTONIC Package Specification package MONOTONIC is type TIME is private; type FINE_DURATION is delta implementation-defined; function CLOCK return TIME; function "+" (LEFT: TIME; RIGHT: DURATION) return TIME; function "+" (LEFT: DURATION; RIGHT: TIME) return TIME; function "-" (LEFT: TIME; RIGHT: DURATION) return TIME; function "-" (LEFT: TIME; RIGHT: TIME) return DURATION; function "+" (LEFT: TIME; RIGHT: FINE_DURATION) return TIME; function "+" (LEFT: FINE_DURATION; RIGHT: TIME) return TIME; function "-" (LEFT: TIME; RIGHT: FINE_DURATION) return TIME; function "-" (LEFT: TIME; RIGHT: TIME) return FINE_DURATION; function "<" (LEFT, RIGHT: TIME) return BOOLEAN; function "<="(LEFT, RIGHT: TIME) return BOOLEAN; function ">" (LEFT, RIGHT: TIME) return BOOLEAN; function ">="(LEFT, RIGHT: TIME) return BOOLEAN; private implementation-defined end MONOTONIC; The MONOTONIC.TIME type represents real time values, as they are returned by MONOTONIC.CLOCK. FINE_DURATION'SMALL must be less than or equal to 1.0E-6 second. FINE_DURATION'RANGE must be at least -2.0 .. 2.0 seconds. H.10. Delay Accuracy This section is not yet written. What follows are notes on the issues we intend to address. A documentation requirement on the accuracy of delays should be added. This might be presented solely as a definition of performance metrics. The interrupt handler mechanism might be used to provide a model of the interaction of timers and delays. The remaining metrics could then be stated in terms of the standard timer facilities provided by the MONOTONIC or CALENDAR packages. Possible metrics of interest include: (a) longest real-time duration of suspension for delay with non-positive value; (b) minimum requested duration to achieve a delay longer than (a); (c) maximum difference between requested delay duration and real-time duration of suspension, for requested delay durations above (b). H.11. Suppression of All Checks This section specifies additional mechanisms by which a user can suppress undesired run-time checks. The SUPPRESS_ALL pragma gives the implementation permission to omit all run-time checks, including those that do not have names (and therefore cannot be suppressed using the SUPPRESS pragma). The form of this pragma is as follows: pragma SUPPRESS_ALL; This pragma may appear at the beginning of the compilation as a configuration pragma and then it applies to the entire partition containing any of the units in the compilation. Alternatively, SUPPRESS_ALL may be used at places where pragma SUPPRESS is permitted, and then it only applies to that unit. H.11.1. Documentation Requirements - Any checks that will still be performed, when the SUPPRESS_ALL pragma is specified. I. Distributed Systems I.1. Introduction In Ada 9X, a program may be formed from a cooperating set of partitions (see 10.1.2). In section 10.1.2, all that is specified about program partitions is that they elaborate independently, communicate, and then terminate. Each partition has its own environment task to act as the thread of control for library-level elaboration, and to act as the master for library-level tasks. This Annex specifies additional packages, pragmas, and attributes for a semantic model to support a standard, portable approach to executing a distributed system. I.2. Model for Distributing an Ada 9X System The model of a distributed system defined by this Annex consists of one or more active partitions, and zero or more passive partitions. Partitions are independently executable sets of library units. Library unit packages can act as interfaces between such partitions. The target system is assumed to be made of a collection of (logical) processing and common address space modules. These represent, respectively, processing units (with associated storage) and passive, potentially shared storage units with no processing capabilities. Active partitions are independent from each other, except that they may directly perform remote subprogram calls (with at-most-once semantics) into other active partitions, as well as receive such calls. Active partitions are mapped to independent processor modules in the distributed system. Passive partitions provide data (and possibly code) visible to one or more active partitions. Passive partitions, if any, usually map to common address space modules within the distributed system. A library-level package that contains a REMOTE_CALL_INTERFACE pragma (see I.3), is an RCI package. Such a package may contain remotely callable subprograms. An active partition may have any number of such RCI packages to provide an external interface for calls from client units in active partitions. A single active partition can be both a server for calls from outside, and a client by calling subprograms defined in RCI packages of other active partitions. For each subprogram declared within the visible part of an RCI package, there is a corresponding calling stub, which is generated by the implementation. As described further below (see I.9), the stub is responsible for constructing a message containing the corresponding parameters to be delivered to the receiving partition and waiting for the reply. [Note: The use of the term stub in this Annex should not be confused with the term body stub as appears in RM 3.9. The term stub is used here because it is widely-accepted in this domain.] A library-level package that contains a SHARED_PASSIVE pragma (see I.3), is a shared passive package. Such a package may act as an interface to a passive partition. Passive partitions may contain only pure and shared passive packages. A pure package has no state, and must depend only on other pure packages (see 10.5.1). A shared passive package may have state, and it must depend only on pure or shared passive packages. Partitioning is performed at link time. At compile time, only the possible interfaces where partitioning may occur are identified via categorization pragmas (see 10.5.1 and I.3). At link time, RCI and shared passive packages are assigned to partitions by the user, and then the normal ``closure'' is performed to determine what other library units should be elaborated in each partition. RCI and shared passive packages are elaborated in exactly one partition (although calling stubs of RCI subprograms are included with each referencing partition). Other library units are elaborated in each partition from which they are referenced. Partitions are elaborated independently. The means for loading partitions onto the distributed target, and providing for communication among them is not defined by this Annex. However, a standard interface is defined between the stubs and a user-provided communication subsystem (UPCS), which actually delivers the remote-subprogram-call messages. [Note: The pre-defined COMMUNICATION_SUPPORT package (see I.8.1) is part of the UPCS. Its body must be provided by the user.] To summarize: - A distributed system consisting of one or more partitions is mapped to a distributed target. One or more active partitions are mapped onto each (logical) processor module. If passive partitions exist, they are mapped onto common address space modules (one or more per each module). - At compile time, packages are categorized as pure, remote call interface, shared passive, or ``normal.'' - Remote call interface packages act as remotely callable interfaces to active partitions. - Shared passive packages contain data accessible to one or more partitions. - At link time, library units are grouped into partitions. Passive partitions may contain only pure and shared passive packages. - At run time, active partitions elaborate independently, each in its own (logical) processor module; and then may start to communicate. - The implementation provides the stubs for flattening the parameters of a remote subprogram call into a linear message. - The user provides the configuration mechanisms and the communication subsystem for delivering messages and interfacing to the underlying network. I.3. Categorization Pragmas The following additional categorization pragmas (see also 10.5.1) are used to identify library-level packages having certain properties. The presence of such a pragma in a package is used to indicate that certain restrictions must be obeyed regarding the constructs contained in the package. Furthermore, these pragmas cause the implementation to generate special code for remote procedure calls and/or remote access. SHARED_PASSIVE A shared passive package may be part of a passive partition. It must be preelaboratable (see G.5), and may depend only on pure or shared passive packages. A shared passive package must not contain any library-level declarations of tasks or protected records with entry operations. [Note: A shared passive package may contain entry-less protected records.] REMOTE_CALL_INTERFACE The visible part of such a package may not contain any declaration of a limited type without user-specified READ and WRITE attribute subprograms (see 14.7), nor subprograms with parameters of such types. In addition, no object, access type, protected record, task, or generic declarations are allowed. The specification of such a package may only depend on pure, shared passive, or remote call interface packages. No formal access parameters or pragma INLINE are allowed to be specified for procedures in the visible part of such a package. The visible part of the visible child units of a remote call interface package must abide by these same restrictions. I.4. Remote Subprogram Calls Any call on a subprogram in the visible part of an RCI package from outside the active partition containing the package, results in a remote subprogram call. A task performing a remote subprogram call suspends until the remote call is complete. If an exception is propagated by a remotely called subprogram, the corresponding exception is propagated in the calling partition. If a corresponding exception does not exist in the calling partition, an anonymous exception is propagated. The UPCS should raise COMMUNICATION_ERROR if the call cannot be completed due to difficulties in communicating with the remote partition. The following pragma: pragma ASYNCHRONOUS (procedure_name); is provided to support asynchronous communication between partitions. Such a pragma indicates that calls on the specified procedure may return without waiting for the call to complete. All parameters of the specified procedure must be of mode in. Exceptions propagated by such a procedure may be lost. If the calling task is aborted while suspended waiting for a remote call to complete, the remote call is cancelled. [Note: The remote subprogram body is executed by a remote call receiver task within the remote partition, see I.9. The remote call receiver tasks are created and managed by the UPCS. The maximum number of simultaneously active remote call receiver tasks in a given active partition is implementation- defined.] I.5. Post-Compilation Partitioning Each remote call interface (RCI) package and shared passive package that is to be present in a distributed application must be assigned to exactly one partition. Only shared passive packages may be assigned to passive partitions. The attribute PARTITION_ID, which applies to an RCI or shared passive package name P returns a unique value of an integer type that identifies (at run-time) the partition in which P was elaborated. [Note: The attribute may also be passed to an extension of the UPCS in order to query other information about a partition.] Constructing a partition has two steps: first, the user assigns each RCI or shared passive package to a partition using the partition id. Then, the partitions are actually built. Prior to building a partition, all RCI and shared passive packages that are to be elaborated in that partition, or are referenced from units that are elaborated in that partition, must be assigned to a partition. [Note: This rule ensures that prior to building an active partition, the value of the attribute PARTITION_ID of a referenced partition is well-defined.] Then, the main subprogram (if any) and the packages explicitly assigned to the partition, plus any library units on which these packages depend (directly or indirectly), excepting those explicitly assigned to other partitions, are included in the partition. Packages other than RCI or shared passive packages, are elaborated in each partition from which they are referenced. When a single type declaration is elaborated in multiple partitions, the types created all match each other, with respect to the operand matching rules (see 3.3.3). If a normal package has state, then a separate copy of the state will be present in each active partition that elaborates it. The state of each copy of a normal package evolves independently; the copies are not kept in synchrony with one another. [Implementation notes: The value of the PARTITION_ID attribute is used within the implementation-generated stubs to identify the target partition for a remote subprogram call.] I.6. Dynamic Binding Between Partitions This section defines an optional capability that may be supported to provide dynamic binding between partitions. The capability is based on access-to-subprogram and/or access-to-tagged-type types. [Note: The facilities described earlier do not permit two partitions to communicate unless one includes a with clause for an RCI package in the other. The target partition of a remote subprogram call is always known by the time the partition containing the call is built.] The restrictions on remote call interface packages are relaxed as follows: - An access to subprogram type may be declared in such a package. * This access value may only be dereferenced as part of a subprogram call. * Such an access value must not be converted to an access type that is not declared in an RCI package. * A value of such a type may only be produced by taking the ACCESS attribute of a subtype-conforming subprogram declared in some RCI package. * The value of the type may be passed between partitions. * A call through such an access value is a remote call if the partition ID contained in the access value does not identify the calling partition. * The representation of such an access value includes the PARTITION_ID, PACKAGE_ID, and SUBPROGRAM_ID of the remote subprogram. - A general access type designating T'CLASS may be declared in an RCI package, where T is a tagged limited private type declared in a pure package. * This access value may be dereferenced only as part of a call on a dispatching operation of the type T. * Such an access value may only be converted to a general access type declared in an RCI package. * The access value may be passed between partitions. * At run time, in addition to the normal tag check, a partition check is made to ensure that all controlling operands of the dispatching operation identify objects that are in the same partition. * If the check succeeds, a (possibly remote) call is made to the appropriate subprogram in that partition. Otherwise, CONSTRAINT_ERROR is raised. * The representation for such an access value includes the PARTITION_ID for the designated object, an address or unique ID for the object within the partition, and the tag value of the object. I.7. Configuring and Elaborating a Distributed System Once the partitions have been built, they must be loaded into the target environment. The means of mapping partitions to the target environment is not specified by this Annex. [Note: In fact, it is not expected that the support for this will be provided by the vendor of the compiler system.] In particular, it is not specified whether more than one partition may share the same module. The mapping of partitions to the target environment must be consistent with the call and data references that cross partition boundaries, as implied by the with clauses. After loading, an active partition is elaborated by the environment task associated with the partition. (Passive partitions are preelaborated.) Active partitions may be elaborated in parallel. [Note: It is not specified by this Annex whether all partitions must be loaded and elaborated at the same time. Similarly, it is not specified whether additional partitions may be loaded and elaborated after other active partitions have been elaborated.] A partition may become inaccessible. An active partition is inaccessible if it (or its environment task) is aborted. If a partition cannot be loaded and/or elaborated, it is inaccessible. Other events, not specified by this Annex, may also result in a partition becoming inaccessible. Referencing an active partition that is inaccessible results in the predefined exception COMMUNICATION_ERROR. The specification of an RCI or shared passive package must be consistent across all partitions that reference such a package. It is an error to elaborate partitions that reference different versions of the same RCI or shared passive package specification. The effect of the error is implementation-defined but at a minimum, one of the offending partitions must become inaccessible. [Note: To detect this error, partitions may include time-stamps or equivalent for the specification of each RCI and shared passive package they elaborate or reference.] If a remote subprogram call is received by an active partition while it is elaborating, the call is held until it is cancelled by the calling partition, or until the receiving partition completes its elaboration. PROGRAM_ERROR may be raised if a deadlock is detected due to cyclic elaboration dependencies. This exception may be raised in one or both of the partitions. The rules for partition termination are described in 10.1.2. In addition, an active partition is aborted when its environment task is aborted. Also, active partitions may be aborted explicitly from another active partition. This aborts the environment task associated with the partition. COMMUNICATION_ERROR is raised in a calling task that is in the middle of a remote subprogram call with an active partition that is terminated. Note: It is unspecified by this Annex whether terminated partitions may be reloaded and reelaborated, perhaps using different common address space or processing modules of the target environment. It is not defined by the language how a program terminates. At a minimum, a program shall terminate when all its partitions terminate. [Note: Interfaces to abort, reload, and elaborate a partition are yet to be specified.] I.8. User-Provided Communication Subsystem The user-provided communication subsystem (UPCS) provides facilities for supporting communication between the active partitions that make up a distributed application. The UPCS may include application-specific facilities for explicit message passing, broadcasting, etc. At a minimum, it must implement the standard package COMMUNICATION_SUPPORT defined below, which supports remote procedure calls. I.8.1. Package COMMUNICATION_SUPPORT This subsection specifies the package COMMUNICATION_SUPPORT as a standard interface to the UPCS. The body of this package is supplied by the user. An implementation conforming to this Annex must provide a mode of operation in which only the subprograms in COMMUNICATION_SUPPORT are called to implement remote subprogram calls. [Note: We assume that this interface is above the message-passing layer of the network and is implemented in terms of it.] with SYSTEM; with STREAM_SUPPORT; -- see 14.7.5 package COMMUNICATION_SUPPORT is pragma PURE; type PARTITION_ID is range 0 .. implementation_defined; type PACKAGE_ID is range 0 .. implementation_defined; type SUBPROGRAM_ID is range 0 .. implementation_defined; COMMUNICATION_ERROR : exception; type MESSAGE_STREAM_TYPE (INITIAL_SIZE : SYSTEM.STORAGE_COUNT) is new STREAM_SUPPORT.ROOT_STREAM_TYPE with private; procedure READ( STREAM : in out MESSAGE_STREAM_TYPE; ITEM : out SYSTEM.STORAGE_ARRAY; LAST : out SYSTEM.STORAGE_COUNT); procedure WRITE( STREAM : in out MESSAGE_STREAM_TYPE; ITEM : in SYSTEM.STORAGE_ARRAY); procedure ALIGN( STREAM : in out MESSAGE_STREAM_TYPE; ALIGNMENT : in SYSTEM.STORAGE_COUNT); procedure SEND_MESSAGE_WITH_REPLY( TO_PARTITION : in PARTITION_ID; TO_PACKAGE : in PACKAGE_ID; TO_SUBPROGRAM : in SUBPROGRAM_ID; MESSAGE : access MESSAGE_STREAM_TYPE; REPLY : out STREAM_SUPPORT.STREAM_ACCESS); procedure SEND_MESSAGE( TO_PARTITION : in PARTITION_ID; TO_PACKAGE : in PACKAGE_ID; TO_SUBPROGRAM : in SUBPROGRAM_ID; MESSAGE : access MESSAGE_STREAM_TYPE); type MESSAGE_RECEIVER is access procedure ( TO_PACKAGE : in PACKAGE_ID; TO_SUBPROGRAM : in SUBPROGRAM_ID; MESSAGE : in STREAM_SUPPORT.STREAM_ACCESS; REPLY : in STREAM_SUPPORT.STREAM_ACCESS); procedure ESTABLISH_MESSAGE_RECEIVER( RECEIVER : in MESSAGE_RECEIVER); private -- ... implementation defined end COMMUNICATION_SUPPORT; The integer type PARTITION_ID is used to identify active partitions. Each RCI package has a PARTITION_ID attribute that returns this type. The integer type PACKAGE_ID is used to identify an RCI package within a given active partition. The integer type SUBPROGRAM_ID is used to identify a subprogram within an RCI package. The exception COMMUNICATION_ERROR is raised when an error is detected by the UPCS during a remote procedure call. It may also be raised if an access to a passive partition fails. [Note: Only one exception is specified even though many sources of errors might exist, since it is not always possible to distinguish among these errors. In particular, it is often impossible to tell the difference between a failing communication link and a failing node.] Note: The function EXCEPTION_INFORMATION (see 11.2) may be used by an implementation to provide more detailed information about the cause of the exception such as communication errors, missing partitions, etc. The stream type MESSAGE_STREAM_TYPE is used for flattening into a message the parameters or results of a remote subprogram call, for the purposes of sending them between partitions. Note: Child packages of COMMUNICATION_SUPPORT may provide additional interfaces to query the state of some remote partition (given its partition id), or of the UPCS itself, to set timeout and retry parameters, etc. [Note: We are currently considering the specification of a package for simple message-passing support to standardize some of the above and to allow for simple message-based communication.] The procedure SEND_MESSAGE_WITH_REPLY is called by the calling stub after the parameters are flattened into the message. After sending the message to the remote partition, it suspends the calling task, until a reply arrives. The procedure SEND_MESSAGE acts like SEND_MESSAGE_WITH_REPLY except that it returns immediately after sending the message to the remote partition. It is called whenever the pragma ASYNCHRONOUS is specified for the remotely called procedure. ESTABLISH_MESSAGE_RECEIVER is called immediately after elaborating an active partition, but prior to invoking the main subprogram, if any. The RECEIVER parameter designates an implementation-provided procedure that receives a message and dispatches to the appropriate RCI package and subprogram. I.9. The Interaction Between the Stubs and the UPCS [Note: The following section is still being worked on. Since the interface to the UPCS is not finalized yet, what is described here is more of a canonical implementation model. It is presented for illustrative purposes only to show the corresponding responsibilities of the various components in an Ada 9X distributed system.] This section defines the semantic rules with respect to the use of the COMMUNICATION_SUPPORT package by the implementation-generated stubs. It also specifies the requirements from the user-provided UPCS in implementing the package body. The actual mechanism and the implementation details are not specified by this Annex. I.9.1. Remote Subprogram Stub Generation The code for calls to subprograms defined in an RCI package is generated normally, that is, the call-site looks like any other subprogram call. The code for remotely callable subprogram bodies is also generated normally. Subprograms' prologue and epilogue are the same as if placed locally. When compiling the specification of an RCI package, the implementation generates calling stubs for each visible subprogram. When compiling the body of an RCI package, the compiler generates receiving stubs for each remotely visible subprogram, along with tables or code to allow the message receiver to locate the correct receiving stub. At link-time, the linker provides a message receiver and the necessary tables or code to support it. The linker inserts a call on ESTABLISH_MESSAGE_RECEIVER immediately after the elaboration of the library units of the partition, but prior to the start of the main subprogram, if any. [Note: The interface between the UPCS (the COMMUNICATION_SUPPORT package) and the message receiver is defined to be dynamic in order to allow the elaboration sequence to notify the UPCS when all packages have been elaborated and it is safe to start calling the receiving stubs. It cannot be required that the UPCS will always be the last to be elaborated.] I.9.2. Remote Subprogram Calls The calling stub operates as follows: - It allocates (or reuses) a stream of MESSAGE_STREAM_TYPE, and initializes it by repeatedly calling 'WRITE operations for the various in and in out parameters. - Then it calls SEND_MESSAGE for procedures with a pragma ASYNCHRONOUS, or SEND_MESSAGE_WITH_REPLY for other subprograms. The message stream allocated and initialized above is passed as the MESSAGE parameter. - After calling SEND_MESSAGE_WITH_REPLY, the REPLY out parameter points to a stream object. The stub returns after extracting from the REPLY stream, using 'READ operations, the in out and out parameters or the function result. On the receiving side, the MESSAGE_RECEIVER procedure operates as follows: - It is called from the UPCS when a remote-subprogram-call message is received. The call will originate in some ``remote call receiver task'' managed by the UPCS and will execute in its context. - It then uses the linker-provided tables or code, with the TO_PACKAGE and TO_SUBPROGRAM parameters to reach the receiving stub. - The receiving stub extracts the in and in out parameters using 'READ from the stream pointed to by the MESSAGE parameter. - The receiving stub calls the actual subprogram body, and afterwards uses 'WRITE to insert the results into the stream pointed to by the REPLY parameter. - The receiving stub then returns to the message receiver procedure which returns to the UPCS. I.9.3. The Operation of the UPCS At-most-once semantics must be guaranteed by the UPCS for each remotely called subprogram. If the pragma ASYNCHRONOUS is specified for a procedure, the remote call receiver task is finished with the call once it completes, normally or due to an exception. For other remote calls, the remote call receiver task must send the message encoded in the REPLY stream back to the partition from which the message originated. If an exception is propagated by the remotely called subprogram, the identity of this exception must be encoded and returned in the REPLY stream in place of the subprogram results. When a reply is received in the sending partition indicating that the receiving stub propagated an exception, SEND_MESSAGE_WITH_REPLY must propagate to the calling stub the corresponding exception, if it exists in the sending partition. If there is no corresponding exception in the sending partition, an anonymous exception is used. The SEND_MESSAGE_WITH_REPLY procedure must notice when its caller is aborted, and send a cancellation message to the UPCS in the receiving partition. The remote call receiver task must react to the cancellation message by aborting the remote call. J. Information Systems This annex defines standard facilities -- packages, pragmas, representation clauses, and performance constraints -- that are needed for Information Systems (IS) applications. It comprises several sections: - Decimal Arithmetic and Representation - Character Set Definition and Manipulation - String Handling - Interface Facilities One of the distinguishing traits of IS applications is the need to traffic with external systems, files, and databases. Since these are often implemented in or generated from COBOL and C, the IS Annex places a heavy emphasis on smoothing the interface between Ada and these two languages. Summary of changes from the V4.0 IS Section: - Simplification of underlying character type model for decimal I/O. The previous version postulated an implementation-defined CHARACTER_PACKAGE with types CHARACTER and STRING; the current version simply uses CHARACTER and STRING from STANDARD. - Simplification of data interoperability formats. Instead of the package DECIMAL.EXTERNAL, which defined an individual type for each representation, we now treat external data as of type STRING and use an enumeration value from EXTERNAL_REP to guide the conversion to/from internal format. Conversions are obtained via attribute functions T'INPUT and T'OUTPUT. - Elimination of the type ROUNDING_OPTION. Now there are just two possibilities: truncated and rounded. - Selection of fixed point (versus a ``magic'' discriminated private type) as the basis for decimal computation; in particular, the decision to use the delta ... digits syntax to distinguish decimal from other fixed point types. - Replacement of the package DECIMAL.EDITING by an attribute T'EDIT for a decimal type T. The DECIMAL.EDITING approach required the programmer to instantiate a generic for each type needing edited output, an overly heavy notation for a common operation. - Presentation of package EBCDIC as an example rather than as a standard unit, since its applicability is limited to one particular environment. - Deletion of GENERIC_TEXT_IO, since it was not implementable in the form shown. A non-generic WIDE_TEXT_IO will be included in the core language. - Establishment of more flexible compliance requirements, reflecting the differences in needs between the COBOL and C Information Systems communities New features in this version: - Generic procedure GENERIC_DIVIDE delivering quotient and remainder - Definition of attribute functions T'VALID, T'INPUT, T'OUTPUT, T'EDIT - Supplementing of package LOCALE with a mechanism (pragma PICTURE) for the static establishment of PICTURE character conventions - Provision for packages for the Coded Character Sets defined in ISO 8859 Parts 5 through 8. - Extension of interfacing facilities to include pragma LAYOUT J.1. Decimal Arithmetic and Representation This section defines the facilities needed for supporting exact decimal computation and external representations as required in financial applications and as exemplified in COBOL. These facilities comprise a package DECIMAL together with a generic procedure GENERIC_DIVIDE. J.1.1. Decimal Types [Note: The contents of J.1.1 are part of the core language. For presentation purposes they are included in this version of the IS Annex, but except where otherwise noted all Ada 9X implementations will need to support the described features. (This actually means that the new syntax needs to be supported by all compilers, but that only IS-Annex-compliant implementations need to support the decimal-specific operations.) References to sections of the core language Mapping Specification Version 4.0 (December 1991) are denoted by MS.] J.1.1.1. Decimal Types as a Special Kind of Fixed Point A syntactically distinguished form of fixed-point type declaration identifies a decimally scaled type. In MS-3.5.8, replace the syntax rule (4) by the following: fixed_point_definition ::= general_fixed_point_definition | decimal_fixed_point_definition general_fixed_point_definition ::= delta static_simple_expression real_range_specification decimal_fixed_point_definition ::= delta static_simple_expression digits static_simple_expression [real_range_specification] Revise MS-3.5.8(6), 2nd sentence, to read: ``Implementations are allowed, but not required, to support other smalls and thus are allowed, but not required, to support decimal fixed point type definitions.'' A type is said to be a decimal fixed-point type, or simply a decimal type, if its declaration is given by a decimal_fixed_point_definition. In a decimal_fixed_point_definition, the delta simple_expression must be a power of 10, and the value of this expression is used for the type's SMALL. The declaration type T is delta D digits N range L..H; is equivalent to: type T1 is delta D range (-(10**N)+1)*D .. ((10**N)-1)*D; for T1'SMALL use D; subtype T is T1 range T1(L) .. T1(H); where T1 is an anonymous type. If a decimal MACHINE_RADIX attribute definition clause is provided for a decimal type T (see J.1.4), then in the above equivalence the clause is regarded as appearing after the SMALL clause but before the subtype declaration. In the absence of a real_range_specification, the subtype T is declared simply as T1. Notes: It is a consequence of other rules that only a range constraint or a digits constraint can be applied to a decimal subtype to obtain a further-constrained decimal subtype. ``Reduced accuracy subtypes'', with a coarser delta than provided for the type, are not permitted. Standard attributes for fixed-point types and subtypes (AFT, DELTA, FIRST, FORE, LAST, SMALL) can be applied to decimal types and subtypes; the values for these attributes are determined by the equivalence stated above. Examples of decimal fixed-point type declarations: type MONEY is delta 0.01 digits 10; -- -99_999_999.99 .. 99_999_999.99 type WEEKLY_HOURS is delta 0.1 digits 4 range 0.0 .. 168.0; [Note: The section numberings 3.5.8 and 3.5.9 in the Mapping Specification 4.0 are in error, since the content of 3.5.8 (fixed-point types) does not match the section title (Operations of Floating Point Types). Insert the section heading, 3.5.9 Fixed Point Types, immediately before the paragraph currently numbered as 3.5.8(3), and renumber the current 3.5.9 as 3.5.10.] J.1.1.2. Decimal Subtypes A decimal subtype is obtained by applying either a range constraint or a digits constraint. Thus revise the syntax for constraint in MS-3.3.2 as follows: constraint := range_constraint | decimal_digits_constraint | index_constrain | discriminant_constraint Define the syntax for decimal_digits_constraint in MS-3.5.8: decimal_digits_constraint := digits static_simple_expression A subtype indication T decimal_digits_constraint for a decimal subtype T is equivalent to: T range (-(10**N)+1)*T'SMALL .. ((10**N)-1)*T'SMALL where N is the value of the static_simple_expression. A decimal_digits_constraint is only permitted on a decimal subtype (thus not for a subtype of a general fixed-point type). J.1.1.3. Operations of Decimal Types The following are the operations of a decimal subtype T, beyond those operations that otherwise apply to T as a fixed-point subtype. T'DIGITS functions T'TRUNCATE, T'ROUND (see J.1.3.2) functions T'VALID, T'INPUT, T'OUTPUT, T'EDIT (see J.1.5) Support for these operations is required only in IS-compliant implementations. Thus other implementations are permitted to treat a decimal type declaration as the equivalent fixed-point type and subtype declarations (see 1.0.1) and to reject a program containing any of the above operations. Note: T'VALID, T'INPUT, T'OUTPUT, and T'EDIT depend on the package DECIMAL defined in the Information Systems Annex. J.1.1.4. Generic Formal Decimal Types Add an alternative to the productions for generic_type_definition in RM 12.1 to allow formal decimal types: generic_type_definition := delta <> digits <> Revise MS-12.1(1) to read: ``The kinds of generic formal parameters will be extended to include generic formal decimal types, generic formal packages, and generic formal derived types.'' Add the following to MS-12.1.2 (Generic Formal Types) after paragraph 3: (i) Decimal fixed-point types: delta <> digits <> The available operations are those of decimal subtypes. Add the following to MS-12.3.3 (Matching Rules for Formal Scalar Types): A generic formal type defined by delta <> digits <> is matched by any decimal subtype. J.1.2. Package DECIMAL package DECIMAL is MAX_DIGITS : constant := implementation_defined; MIN_DELTA : constant := implementation_defined; MAX_DELTA : constant := implementation_defined; type PICTURE_STRING is new STRING; SIZE_ERROR, DATA_ERROR, PICTURE_ERROR : exception; type EXTERNAL_REP is (BINARY_BIG_ENDIAN, BINARY_LITTLE_ENDIAN, ZONED_UNSIGNED, ZONED_LEADING_SEPARATE, ZONED_TRAILING_SEPARATE, ZONED_LEADING_NONSEPARATE, ZONED_TRAILING_NONSEPARATE, PACKED_SIGNED, PACKED_UNSIGNED, . . . ); end DECIMAL; Notes: MAX_DIGITS is the largest value allowed in a decimal digits constraint. MIN_DELTA is the smallest value allowed in a fixed_accuracy_definition for a decimal type. MAX_DELTA is the largest value allowed in a fixed_accuracy_definition for a decimal type. SIZE_ERROR is raised on conversion from internal representation, when the source of a conversion operation is outside the range of the target. DATA_ERROR is raised upon conversion from external representation, when the item to be converted does not have the proper form, or when it is outside the range of the target. PICTURE_ERROR is raised when a picture string is malformed. J.1.3. Decimal Computation J.1.3.1. Capacity Requirements - DECIMAL.MAX_DIGITS must be greater than or equal to 18 - DECIMAL.MIN_DELTA must be a power of 10 and less than or equal to 10.0**(-18) - DECIMAL.MAX_DELTA must be a power of 10 and greater than or equal to 1.0 J.1.3.2. Control over Truncation and Rounding For any decimal type T, the attribute functions T'TRUNCATE(expr) and T'ROUND(expr) convert the value of expr to type T as follows, where expr is an expression of any numeric type: T'TRUNCATE(expr) truncates (towards 0) T'ROUND(expr) rounds (away from 0, if midway between two model numbers) Note: As with general fixed point types, it is implementation defined whether the conversion T(expr) rounds, truncates, or returns a machine number in the smallest enclosing model interval. Also, the relaxed accuracy requirements for conversions of fixed-point products and quotients to a fixed-point type T, when the SMALLs are incompatible, apply when T is a decimal type. (If all three types are decimal, however, the SMALLs are compatible and thus exactness is required.) J.1.3.3. Generic Procedure GENERIC_DIVIDE generic type DIVIDEND_TYPE is delta <> digits <>; type DIVISOR_TYPE is delta <> digits <>; type QUOTIENT_TYPE is delta <> digits <>; type REMAINDER_TYPE is delta <> digits <>; procedure GENERIC_DIVIDE(DIVIDEND : DIVIDEND_TYPE; DIVISOR : DIVISOR_TYPE; QUOTIENT : out QUOTIENT_TYPE; REMAINDER : out REMAINDER_TYPE; ROUNDED : BOOLEAN := FALSE); Effect when ROUNDED = FALSE: QUOTIENT_TYPE'TRUNCATE(DIVIDEND/DIVISOR) REMAINDER = REMAINDER_TYPE'TRUNCATE(T(DIVIDEND) - T(DIVISOR*QUOTIENT)) where type T is defined so that: T'SMALL = MIN(DIVISOR_TYPE'SMALL * QUOTIENT_TYPE'SMALL, DIVIDEND_TYPE'SMALL) T'DIGITS = MAX(DIVISOR_TYPE'DIGITS + QUOTIENT_TYPE'DIGITS, DIVIDEND_TYPE'DIGITS, SYSTEM.MAX_DECIMAL_DIGITS) Effect when ROUNDED = TRUE: Same as above, except apply REMAINDER_TYPE'ROUND. J.1.4. Internal Decimal Representation The MACHINE_RADIX attribute definition clause (see Numerics Annex) can be applied to a decimal type T; in particular: for T'MACHINE_RADIX use 10; -- Forces a base type with a decimal range for T'MACHINE_RADIX use 2; -- Forces a base type with a binary range In the absence of one of these clauses, the choice is implementation defined. Note: An implementation is permitted to use packed decimal as an internal representation for objects of type T when T'MACHINE_RADIX = 10. J.1.5. External Decimal Representation The type EXTERNAL_REP enumerates the set of external representations supported by the implementation. Several common formats are included in the predefined type, and the implementation is allowed to add further elements. Since external data are treated as type STRING, the values from type EXTERNAL_REP guide the conversion between external and internal formats. For each decimal type T, an IS-compliant implementation needs to supply the following attributes: function T'VALID(ITEM : STRING; FORMAT : EXTERNAL_REP) return BOOLEAN; function T'INPUT(ITEM : STRING; FORMAT : EXTERNAL_REP) return T; function T'OUTPUT(ITEM : T; FORMAT : EXTERNAL_REP) return STRING; function T'EDIT(ITEM : STRING; FORMAT : EXTERNAL_REP; PICTURE : PICTURE_STRING; ROUNDED : BOOLEAN := FALSE) return STRING; There is also a version of T'EDIT to convert from internal representation to an edited output format: function T'EDIT(ITEM : T; PICTURE : PICTURE_STRING; ROUNDED : BOOLEAN := FALSE) return STRING; Figure J-1 shows the conversions that apply among the external, internal, and edited output representations for a decimal type T. Figure J-1: Relationships Among Decimal Representations J.1.5.1. T'VALID The function T'VALID(ITEM, FORMAT) returns TRUE if ITEM is properly formed according to FORMAT, and returns FALSE otherwise. Specifically: T'VALID(ITEM, BINARY_BIG_ENDIAN) returns TRUE if the value of ITEM when viewed as a signed 2's complement ``big endian'' binary value is in T'BASE'RANGE. A similar rule holds for T'VALID(ITEM, BINARY_LITTLE_ENDIAN). Table J.1.5.1 summarizes the effect of T'VALID for the other predefined elements of EXTERNAL_REP, using the following abbreviations: spaces ::= space_character { space_character } digits ::= digit { digit } sign ::= '+' | '-' signed_digit ::= character corresponding to a digit with a sign The symbols space_character and digit are defined in RM 2.1. The signed_digit values are implementation-defined. FORMAT T'VALID returns TRUE if ITEM'LENGTH is: and ITEM comprises: ZONED_UNSIGNED T'DIGITS [spaces] digits [spaces] ZONED_LEADING_SEPARATE T'DIGITS+1 [spaces] sign digits [spaces] ZONED_TRAILING_SEPARATE T'DIGITS+1 [spaces] digits sign [spaces] ZONED_LEADING_NONSEPARATE T'DIGITS [spaces] signed_digit [digits] [ ZONED_TRAILING_NONSEPARATE T'DIGITS [spaces] [digits] signed_digit [ Table J-1: Definition of T'VALID PACKED_SIGNED, PACKED_UNSIGNED: (To Be Done.) Support for the PACKED_ representations is required only in those environments for which these formats are relevant. J.1.5.2. T'INPUT A precondition for T'INPUT (ITEM, FORMAT, ROUNDED) is that T'VALID (ITEM, FORMAT) yield TRUE; DATA_ERROR is raised if this condition is not met. The effect of T'INPUT is to produce a value of subtype T from the input value ITEM as interpreted according to the representation given by FORMAT. If the value produced is outside T'RANGE, then DATA_ERROR is raised. J.1.5.3. T'OUTPUT The effect of T'OUTPUT (ITEM, FORMAT) is to produce a result STRING S such that T'INPUT (S, FORMAT) = ITEM. Details are as follows: FORMAT Value of ITEM: 0 D1...Dn*T'SMALL -D1...Dn*T'SM (D1 /= 0) (D1 /= 0) ZONED_UNSIGNED m-1 spaces m-n spaces followed by followed by '0' D1...Dn ZONED_LEADING_SEPARATE m-1 spaces m-n spaces m-n spaces followed by followed by followed by "+0" '+'D1...Dn '-'D1...Dn ZONED_TRAILING_SEPARATE m-1 spaces m-n spaces m-n spaces followed by followed by followed by "0+" D1...Dn'+' D1...Dn'-' ZONED_LEADING_NONSEPARATE m-1 spaces m-n spaces m-n spaces followed by followed by followed by '{' D1'D2...Dn D1'D2...Dn where D1' is where D1' i the +D1 digit the -D1 dig ZONED_TRAILING_NONSEPARATE m-1 spaces m-n spaces m-n spaces followed by followed by followed by '{' D1...Dn-1Dn' D1...Dn-1Dn where Dn' is where Dn' i the +Dn digit the -Dn dig Table J-2: Rules for T'OUTPUT of ZONED Representations ZONED Representations Table J.1.5.3 shows the result of T'OUTPUT(ITEM, FORMAT) where FORMAT has one of the ZONED_ values. Assume that T'DIGITS = n. BINARY_BIG_ENDIAN and BINARY_LITTLE_ENDIAN Representations If ITEM is positive, assume that its value in base-256 notation is B ...B *T'SMALL where n = ceiling((log (T'BASE'LAST) + 1)/8) 1 n 2 (thus n is the number of bytes neeed to represent objects of 8n type T in binary). If ITEM is negative, assume that 2 + ITEM is expressed in the above fashion. If FORMAT = BINARY_BIG_ENDIAN then the result of T'OUTPUT(ITEM, FORMAT) is B ...B . If FORMAT = BINARY_LITTLE_ENDIAN then the 1 n result of T'OUTPUT(ITEM, FORMAT) is B ...B . n 1 Note: An implementation may add to the EXTERNAL_REP enumeration type the enumeration values BINARY_BIG_ENDIAN_1 and BINARY_LITTLE_ENDIAN_1 that interpret negative values for ITEM as 1's versus 2's complement. PACKED_ Representations To Be Done. Support for the PACKED_ representations is required only in those environments for which such formats are relevant. J.1.5.4. T'EDIT Two attribute functions T'EDIT are provided: function T'EDIT(ITEM : STRING; FORMAT : EXTERNAL_REP; PICTURE : PICTURE_STRING; ROUNDED : BOOLEAN := FALSE) return STRING; function T'EDIT(ITEM : T; PICTURE : PICTURE_STRING; ROUNDED : BOOLEAN := FALSE) return STRING; The effect of each is to convert ITEM to a result STRING laid out in human readable form (including, for example, a currency symbol, sign, digits group separation, and decimal point) under control of a PICTURE string and with rounding versus truncation governed by ROUNDED. The result of T'EDIT is known as an edited output string. The length of the result string is the same as that of the PICTURE string. A precondition for the T'EDIT function taking ITEM as a STRING parameter is that T'VALID(ITEM, FORMAT) return TRUE. The exception DECIMAL.DATA_ERROR is raised if the condition is FALSE. This exception is likewise raised if T'INPUT(ITEM, FORMAT) is outside the range that is convertible based on PICTURE. For the T'EDIT function taking ITEM as a parameter of type T, the exception DECIMAL.SIZE_ERROR is raised if ITEM is outside the range that is convertible based on PICTURE. The following characters are allowed in a PICTURE string. Digit Characters: 9 Decimal digit Z Decimal digit, but suppressed if leading 0 * Decimal digit, but asterisk if leading 0 Sliding Characters (when repeated): # Currency symbol or digit + Plus sign, if number is greater than or equal to 0, or digit - Minus sign, if number is less than 0, or digit S Plus or minus sign, or digit Insertion Characters: ^ Decimal point (separator between integer and fraction parts) V Decimal point, suppressed if fraction part is zero _ Separator between digit groups B Blank space CR CR, if number is less than 0 DB DB, if number is less than 0 A single occurrence of a sliding character is treated as an insertion character. Lower case equivalents of the letters in the above set have the same effect as the upper case forms. For the insertion characters comprising the CR and DB symbols, the case used in the picture string will be likewise used in the result string. [Note: The exact rules for PICTURE string formation and interpretation remain to be done.] We refer to the picture characters '#', '^', 'V', and '_' as configurable, since the programmer can control their effects on the contents of the edited output string. By default, the correspondence is: PICTURE Output '#' '$' '^' '.' 'V' '.' '_' ',' The procedures in package LOCALE (J.1.6) may be invoked by the user to change these correspondences at run time. Examples: Assume that T'DIGITS = 8, T'SMALL = 0.01. In the table below, '~' in the output represents the blank space character. ITEM PICTURE T'EDIT(ITEM, PICTURE) 1.23 "999^99" "001.23" 0.0 "ZZZZZVZZ" "~~~~~~~~" -1234.56 "######9^99bDB" "~~$1234.56~DB" 1234.56 "S999^99" SIZE_ERROR is raised In some environments, run-time configurability of the effect of picture characters on output is not required. Instead, a mechanism is needed that (1) informs the compiler that the correspondence between the configurable picture characters and the output characters is not going to change at run time; and (2) lets the programmer use for the configurable characters in the PICTURE string a set of graphic characters that are relevant in the programmer's country. To obtain this effect, the following configuration pragma is defined: pragma PICTURE(CURRENCY => character_literal, SEPARATOR => character_literal, DECIMAL_POINT => character_literal); where: 1. The CURRENCY character_literal may be any graphic character not otherwise allowed in PICTURE strings 2. The SEPARATOR character_literal may be any of the following: ',' '.' '_' 'B' 'b' Each of the literals 'B' and 'b' denotes a blank space in the output string 3. The DECIMAL_POINT character_literal must be different from the SEPARATOR character_literal and may be from the set '.' ',' '^' Each of the literals '.' and ',' corresponds to itself in the output string. The literal '^' corresponds to '.' in the output string. The same PICTURE pragma must apply to each compilation in a program. If a program to which the PICTURE pragma applies calls one of the subprograms in package LOCALE that sets the correspondence for a configurable PICTURE character, the effect of the call is to raise the exception PICTURE_ERROR. Support for the PICTURE pragma is optional. Notes: The SEPARATOR and DECIMAL_POINT characters affect the interpretation of picture strings and the form of the edited output result, but they do not change the lexical rules for formation of real literals. The PICTURE pragma is intended both to allow PICTURE strings to be localizable (and thus more readable), and to facilitate optimization of the EDIT function in certain environments. J.1.6. Locale-Specific Characteristics The package LOCALE contains subprograms that allow the programmer to particularize the contents of certain computed string values based on local conventions. package LOCALE is procedure SET_DECIMAL_POINT (CHAR : CHARACTER := '.'); function DECIMAL_POINT return CHARACTER; -- Decimal point character -- DECIMAL_POINT returns the value most recently set by -- SET_DECIMAL_POINT ('.' initially) procedure SET_SEPARATOR(CHAR : CHARACTER := ','); function SEPARATOR return SEPARATOR_TYPE; -- Separator character for digits groups procedure SET_CURRENCY(ITEM : CHARACTER := '$'); function CURRENCY return CHARACTER; -- Currency symbol ... -- To Be Done end LOCALE; [To be done: package LOCALE needs to be augmented with other subprograms, for example for establishing control over time and date formatting.] J.2. Character Set Definition and Manipulation This subsection provides packages with several purposes: - Processing the predefined types CHARACTER and WIDE_CHARACTER - Defining and processing international character sets J.2.1. CHARACTER and WIDE_CHARACTER Handling Package The package below is based on the C library standard header file. package CHARACTER_HANDLING is -- (1) Functions for CHARACTER -- (1.1) Classification Functions function IS_ALPHABETIC(CHAR : CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is an alphabetic character function IS_ALPHANUMERIC(CHAR : CHARACTER) return BOOLEAN; -- Returns TRUE if IS_ALPHABETIC(CHAR) or IS_DIGIT(CHAR) function IS_CONTROL(CHAR : CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a control character function IS_DIGIT(CHAR : CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a decimal digit function IS_GRAPHIC(CHAR : CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a printable character (except space) function IS_LOWER(CHAR : CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a lower-case letter function IS_UPPER(CHAR : CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is an upper-case letter function IS_PRINTABLE(CHAR : CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a printable character -- (including space) function IS_PUNCTUATION(CHAR : CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a printable character, -- excluding space, letters, and digits function IS_SPACE(CHAR : CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is space, formfeed, newline, tab, -- vertical tab, or carriage return function IS_HEX_DIGIT(CHAR : CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a hexadecimal digit (a digit or -- one of 'A' through 'F' or 'a' through 'f') -- (1.2) Case Conversion functions function TO_LOWER(CHAR : CHARACTER) return CHARACTER; -- Returns the corresponding lower-case value for CHAR if -- IS_UPPER(CHAR), and returns CHAR otherwise function TO_UPPER(CHAR : CHARACTER) return CHARACTER; -- Returns the corresponding upper-case value for CHAR if -- IS_LOWER(CHAR), and returns CHAR otherwise -- (2) Functions for WIDE_CHARACTER -- (2.1) Classification Functions -- Overloadings of (1.1) functions for WIDE_CHARACTER function IS_ALPHABETIC(CHAR : WIDE_CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is an alphabetic character function IS_ALPHANUMERIC (CHAR : WIDE_CHARACTER) return BOOLEAN; -- Returns TRUE if IS_ALPHABETIC(CHAR) or IS_DIGIT(CHAR) function IS_CONTROL(CHAR : WIDE_CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a control character function IS_DIGIT(CHAR : WIDE_CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a decimal digit function IS_GRAPHIC(CHAR : WIDE_CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a printable character (except space) function IS_LOWER(CHAR : WIDE_CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a lower-case letter function IS_UPPER(CHAR : WIDE_CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is an upper-case letter function IS_PRINTABLE(CHAR : WIDE_CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a printable character -- (including space) function IS_PUNCTUATION(CHAR : WIDE_CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a printable character, -- excluding space, letters, and digits function IS_SPACE(CHAR : WIDE_CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is space, formfeed, newline, tab, -- vertical tab, or carriage return function IS_HEX_DIGIT(CHAR : WIDE_CHARACTER) return BOOLEAN; -- Returns TRUE if CHAR is a hexadecimal digit (a digit or -- one of 'A' through 'F' or 'a' through 'f') -- (2.2) Case Conversion Functions -- Overloadings of (1.2) functions for WIDE_CHARACTER function TO_LOWER(CHAR : WIDE_CHARACTER) return WIDE_CHARACTER; -- Returns the corresponding lower-case value for CHAR if -- IS_UPPER(CHAR), and returns CHAR otherwise function TO_UPPER(CHAR : WIDE_CHARACTER) return WIDE_CHARACTER; -- Returns the corresponding upper-case value for CHAR if -- IS_LOWER(CHAR), and returns CHAR otherwise -- (3) Conversions between CHARACTER, WIDE_CHARACTER and -- WIDE_CHARACTER, WIDE_STRING function TO_WIDE_CHARACTER(CHAR : CHARACTER) return WIDE_CHARACTER; function TO_CHARACTER(CHAR : WIDE_CHARACTER) return CHARACTER; -- Raises CONSTRAINT_ERROR if CHAR is outside CHARACTER procedure TO_WIDE_STRING(STR : STRING; W_STR : out WIDE_STRING); procedure TO_STRING(W_STR : WIDE_STRING; STR : out STRING); end CHARACTER_HANDLING; The package body implements these subprograms based on the local interpretation of CHARACTER from the appropriate part of ISO 8859 (e.g., Latin-1, Latin-2, etc.). J.2.2. ISO 8859 Coded Character Set Packages package LATIN_1 is -- ISO 8859 Part 1 -- Names for the characters: ... -- To be done type CHARACTER is (16#00# .. 16#1F# => null, -- C0 control characters ' ', '!', '"', ..., '~', -- 16#20# .. 16#7E# 16#7F# => null, -- Another C0 control character 16#80# .. 16#9F# => null, -- C1 control characters 16#A0# => null, -- Non-breaking space ... ); -- 16#A1# .. 16#FF# (Graphic literals) -- In the above declaration, the ``range => null'' syntax is used -- to represent unnamed enumeration values -- The "..." is ellipsis and is not part of the syntax type STRING is array (POSITIVE range <>) of CHARACTER; pragma PACK(STRING); ... -- Same subprograms as CHARACTER_HANDLING, but with -- different implementations if CHARACTER is not Latin-1 end LATIN_1; There are analogous packages LATIN_2 through LATIN_5, as well as LATIN_CYRILLIC, LATIN_ARABIC, LATIN_GREEK, and LATIN_HEBREW. J.2.3. Other Character Packages An implementation is permitted to define additional packages corresponding to character sets available in the local environment. The following illustrate the package format that is recommended for uniformity; implementation support is optional. For some character sets, the mapping between the implementation-provided character type and STANDARD.CHARACTER might not be unique. In such cases the implementation must provide some means for the user to replace the implementation-defined default. The particular mechanism depends on whether such replacement needs to be provided at run time (as with EBCDIC below) or pre-run time. In the latter case the implementation must provide some method for the user to replace the default version of a program unit with one that overrides the default behavior. J.2.3.1. EBCDIC Package package EBCDIC is type CHARACTER is ( ... ); -- TBD type STRING is array (POSITIVE range <>) of CHARACTER; pragma PACK(STRING); -- (1) Classification Functions -- As in CHARACTER_HANDLING (1.1) -- (2) Case Conversion Functions -- As in CHARACTER_HANDLING (1.2) -- (3) Conversions -- (3.1) EBCDIC.CHARACTER to/from STANDARD.CHARACTER TO_STANDARD_CHAR : array (EBCDIC.CHARACTER) of STANDARD.CHARACTER := ( ... ); -- Default conversion TO_EBCDIC_CHAR : array (STANDARD.CHARACTER) of EBCDIC.CHARACTER := ( ... ); -- Default conversion -- (3.2) EBCDIC.STRING to/from STANDARD.STRING procedure TO_STANDARD_STRING( E_STR : in EBCDIC.STRING; STR : out STANDARD.STRING); procedure TO_EBCDIC_STRING( STR : in STANDARD.STRING; E_STR : out EBCDIC.STRING); end EBCDIC; J.2.3.2. Personal Computer Character Set Package package PC is type CHARACTER is ( ... ); -- TBD type STRING is array (POSITIVE range <>) of CHARACTER; pragma PACK(STRING); -- (1) Classification Functions -- As in CHARACTER_HANDLING (1.1) -- (2) Case Conversion Functions -- As in CHARACTER_HANDLING (1.2) -- (3) Conversion Functions -- (3.1) PC.CHARACTER to/from STANDARD.CHARACTER function TO_STANDARD_CHAR(CHAR : PC.CHARACTER) return STANDARD.CHARACTER; function TO_PC_CHAR(CHAR : STANDARD.CHARACTER) return PC.CHARACTER; -- (3.2) PC.STRING to/from STANDARD.STRING procedure TO_STANDARD_STRING( P_STR : in PC.STRING; STR : out STANDARD.STRING); procedure TO_PC_STRING( STR : in STANDARD.STRING; P_STR : out PC.STRING); end PC; J.3. String Handling The generic package GENERIC_STRING_HANDLING provides two kinds of facilities: - Subprograms to search a given string for various kinds of patterns - Subprograms that simulate assignment of strings of different lengths generic type CHARACTER is (<>); type STRING is array (POSITIVE range <>) of CHARACTER; BLANK : CHARACTER := CHARACTER'VAL(32); -- Blank in Latin-n package GENERIC_STRING_HANDLING is -- (1) Subprograms based on the C library -- header file function FIRST_INDEX(ITEM : STRING; CHAR : CHARACTER) return NATURAL; -- Search for the first occurrence of a character -- If CHAR is in the string ITEM, the function returns the smallest -- index I such that CHAR=ITEM(I) -- If CHAR is not in the string ITEM, INDEX returns 0 -- Example: FIRST_INDEX("Ada Augusta Lovelace", 'a') = 3 -- Corresponding C function: strchr function FIRST_INDEX(ITEM : STRING; SET : STRING) return NATURAL; -- Search for the first occurrence of any of a set of characters -- The function returns the smallest index I such that -- ITEM(I) = SET(J) for some J. -- If none of the characters in SET is in ITEM, -- the function returns 0 -- Example: FIRST_INDEX("A*B*C/D", "+-*/") = 2 -- Corresponding C function: strpbrk function SLICE_INDEX(ITEM : STRING; SLICE : STRING) return NATURAL; -- Search for first occurrence of a substring -- If SLICE is a substring of ITEM then return the smallest index I -- such that ITEM(I..I+SLICE'LENGTH-1) = SLICE, -- otherwise return 0 -- Corresponding C function: strstr function LENGTH_OUT(ITEM : STRING; SET : STRING) return NATURAL; -- Count number of characters not in set -- Returns the length of the prefix of ITEM comprising characters -- outside of SET -- Example: -- LENGTH_OUTSIDE(ITEM => "ABCD32", -- SET => "1234567890") = 4 -- Corresponding C function: strcspn function LENGTH_IN(ITEM : STRING; SET : STRING) return NATURAL; -- Count number of characters in set -- Returns the length of the prefix of ITEM comprising -- characters in SET -- Example: LENGTH_IN(ITEM => "32ABCD", -- SET => "1234567890") = 2 -- Corresponding C function: strspn function LAST_INDEX(ITEM : STRING ; CHAR : CHARACTER) return NATURAL; -- Search for the last occurrence of a character -- If CHAR is in the string ITEM, the function returns the largest -- index I such that CHAR=ITEM(I) -- If CHAR is not in the string ITEM, INDEX returns 0 -- Example: LAST_INDEX("Ada Augusta Lovelace", 'a') = 18 -- Corresponding C function: strrchr procedure GET_TOKEN(ITEM : STRING; SEPARATORS : STRING; START : POSITIVE; FIRST : out POSITIVE; LAST : out NATURAL); -- Returns in FIRST and LAST the indices of the beginning and end -- of the first "token" in ITEM occurring no earlier than at -- index START -- Postcondition: -- If at least one character in ITEM at or after index START -- is in SEPARATORS: -- ITEM(ITEM'FIRST .. FIRST-1) comprises only characters in -- SEPARATORS; -- ITEM(FIRST..LAST) comprise only characters not in SEPARATORS -- Either LAST=ITEM'LAST or else ITEM(LAST+1) is in SEPARATORS -- Otherwise FIRST=ITEM'FIRST and LAST = ITEM'FIRST-1 -- Example: -- GET_TOKEN ("How now, brown cow", ",.;: ", 1, FIRST, LAST) -- assigns 1 to FIRST and 3 to LAST -- Corresponding C function: strtok -- (2) Procedures to simulate "varying length" strings procedure MOVE(FROM : STRING; TO : out STRING; PAD : CHARACTER := BLANK; RIGHT : BOOLEAN := TRUE); -- Copies characters from FROM to TO. -- If FROM is shorter than TO, TO is filled with PAD -- (on the right if RIGHT= TRUE, else on the left) -- If FROM is longer than TO, only the prefix of FROM -- up to the length of TO is copied procedure MOVE(FROM : CHARACTER; TO : out STRING; PAD : CHARACTER := BLANK; RIGHT : BOOLEAN := TRUE); -- Equivalent to MOVE(STRING'(FROM), TO, PAD) function "**"(ITEM : STRING, COUNT : NATURAL) return STRING; function "**"(ITEM : CHARACTER, COUNT : NATURAL) return STRING; -- Catenates ITEM with itself COUNT times -- If COUNT=0, the empty string is returned -- Example: "ABCD"**3 = "ABCDABCDABCD" -- 'x' ** 5 = "xxxxx" end GENERIC_STRING_HANDLING; -- Instantiations for STANDARD types: package STRING_HANDLING is new GENERIC_STRING_HANDLING(CHARACTER, STRING); package WIDE_STRING_HANDLING is new GENERIC_STRING_HANDLING (WIDE_CHARACTER, WIDE_STRING, ' '); J.4. Interface Facilities J.4.1. Pragma LAYOUT The form of this pragma is: pragma LAYOUT(language_name, type_name); Where language_name identifies a particular programming language implementation, and where type_name is an Ada type. The effect of the pragma is to direct the Ada compiler to choose a storage layout for objects of the named type so as to match the representation that the foreign language compiler uses for the corresponding type in that language. The Ada implementation's Appendix F documentation must specify the correspondence between Ada types and foreign language types. Note: Language_name may identify an Ada compiler implementation, allowing the user to interface with code produced by other Ada compilers. J.4.2. C_TYPES Package The following package supplies an Ada interface to the predefined C types: package C_TYPES is type int is ...; type long_int is ...; type unsigned_int is ...; type unsigned_long_int is ...; type char is ...; type float is ...; type double is ...; type char_pointer is ...; ... end C_TYPES; The implementation must define these so that an Ada object of any of the types may be passed as a parameter to a pragma INTERFACE'd C function with the corresponding formal parameter. J.4.3. Compliance Requirements An IS-compliant implementation must support the following features for interfacing with the foreign languages C, COBOL, or both: - Implementation of the pragmas INTERFACE, INTERFACE_NAME, and LAYOUT; - Allowing an Ada subprogram to be called from a foreign main program; - Allowing Ada data objects defined in library packages to be accessed from foreign programs; - Allowing foreign statically-allocated data structures to be accessed from Ada. Support for C requires implementation of the package C_TYPES above. Support for COBOL requires implementation of the ZONED_ elements of the DECIMAL.EXTERNAL_REP enumeration type. J.4.4. Other Interfacing Facilities [Note: This section is a placeholder for features related to interfacing with windowing systems, transaction monitors, and other external systems common to IS applications. Issues related to interfacing an Ada program to external programs, in distributed as well as uniprocessor environments, will be addressed here.] J.4.5. Relationship with other Annexes Many large IS applications are real-time distributed systems with needs such as fault tolerance and dynamic reconfiguration. Thus compilers for these applications will need to support not only the IS Annex but also certain sections of both the Real-Time and Distributed Systems Annexes. (Exact sections to be determined.) K. Safety and Security The Safety and Security Annex addresses issues of concern to the communities who write Safety-Critical and Trusted systems in Ada. For the general objective, see the Ada 9X requirements document. This draft is merely a list of issues of concern to the two communities. At this stage, it is not even clear that each issue can be addressed by the final Annex (indeed, some are marked provisionally as not being addressable). We note that there are three classes of Security-Critical usage. The first, and least demanding is a so-called ``untrusted'' application that runs in a secure environment. While such applications do not have the access privileges necessary to violate a system security policy, the system mission may depend on their proper functioning. This class of security applications requires many of the features required by safety critical applications. The second class of applications consists of those that use Ada for the construction of the Trusted Computing Base (TCB). Typically, applications in this class are operating systems or similar programs that mediate accesses by untrusted applications to resources that are shared according to some security policy. These applications have much in common with systems programming applications, but may require a much more restrictive run time environment. The third class of applications consists of trusted applications programs that must enforce an extension of the security policy of the TCB on which they are hosted. Such applications may impose restrictions on a variety of language and run-time behaviors. K.1. Erroneous Execution Situations In many critical applications, it is important to avoid erroneous situations. This can be eased if such situations are enumerated in the Standard. Resolution: (Provisional) List such situations in Chapter 1 of standard or ensure index covers this. K.2. Bounded Error Situations In many critical applications, it is important to avoid bounded error situations. This can be eased if such situations are enumerated in the Standard. Resolution: (Provisional) List such situations in Chapter 1 of standard or ensure index covers this. K.3. Understandable Object Code For some critical applications, it is necessary to validate the machine code produced by the compiler rather than just the Ada source code. Resolution: (Provisional) Define a pragma TRACEABLE_OBJECT_CODE which, if applied to a unit, results in object code which can be easily analyzed. The relationship between the Ada source code and the machine-code is either obvious or the compiler will provide additional information to make the relationship tracable. (This was discussed at WG9 September 91 and had reasonable support.) K.4. Elaboration Order Of Library Units Tools which analyze Ada source code need to know the actual order of elaboration of library units in order to determine the semantic effect of their elaboration. Resolution: (Provisional) Implementations shall indicate the actual order used. K.5. Mathematical Precise Evaluation Of Numerics In Ada 83, there is substantial implementation freedom to evaluate integer expressions with a greater range than the base type, and evaluate real expressions with greater range and precision (see 11.6). This could be critical in safety applications. Resolution: (Provisional) Assume this is resolved in the Numerics Annex and that vendors can reasonably support both Annexes (at once). K.6. Parameter Passing Mechanism Undefined Given N composite parameters, there are 2**N possible methods of passing the parameters which gives a combinatorial explosion in the analysis of an Ada program unless aliasing can be shown to be absent. In large applications, it is probably infeasible to show the absence of aliasing. Resolution: (Provisional) Require that the pragma TRACEABLE_OBJECT_CODE (see K.3) has the effect that the mechanism actually used is indicated by the compiler. K.7. Ensure use of Validatable Compiler Since vendors continually enhance their compilers, a version actually in use on a project might fail to pass some ACVC tests. Is it within the scope of this Annex to require vendors only to supply validatable compilers to some projects? What is the current practice in this area? In the Security community, requirements exist for maintaining all tools used in building a high assurance system under a strict configuration management system. The net effect is often to freeze the version of the compiler and other tools used for the duration of the project or to allow modifications only in response to problems identified by the project. This could require a vendor to commit to continued support of old versions of a compiler for an indefinite period. Resolution: (Provisional) Raise issue with Ada 9X project office. This is almost certainly outside the scope of the Annex. K.8. `Certified' compilers In the UK, a proposal has been made for `certified' compilers which would operate as follows: If a vendor can be independently be registered to ISO 9001 (Quality Management Systems) and has procedures in place to ensure only validatable compilers are released to customers AND the vendors regression test their compiler, then a certification body would grant the `certified' compiler status. Independently, the revision of the Avionics Standard DO178A also requires that compilers are developed under a Quality Management System. In the US, the National Computer Security Center maintains a list of tools that are "approved" for use on high assurance systems. While the list is currently restricted to tools for formal verification, it might be logical to extend it to compilers, etc. Resolution: (Provisional) Raise issue with Ada 9X project office. This is almost certainly outside the scope of the Annex. K.9. Certified run-time system Highly critical applications need run-time systems which are near to being provably correct. Such systems may only support a subset of Ada, say without concurrency. The second class of security applications (TCBs) defined above may provide many of the features such as concurrency and storage management) usually thought of as a part of a run-time system and may, in turn, require little or no compiler based run-time support. Resolution (Provisional) Not clear that this is within scope of Ada 9X. Consult Ada 9X project office. K.10. Defined Storage Allocation Scheme For some applications, it may be necessary to show that the system cannot raise STORAGE_ERROR. To perform the static analysis necessary to show the absence of this exception, information is needed on the amount of storage allocated and how this is undertaken. Resolution: (Provisional) Require documentation from vendors. K.11. Storage Reuse Secure applications that provide or extend a TCB must ensure that reused storage is erased to prevent information written by one application from being read by another. Resolution: (Provisional) This should be an issue only if a single run-time is shared among several untrusted programs or if multilevel programs are used. This feature is provided by operating system TCBs and is an issue when TCBs are written in Ada. K.12. Execution Timing Some safety-critical applications are required to meet timing deadlines which therefore need details of the maximum time taken by certain constructs. Should compilation be required to provide such timing information? Resolution: (Provisional) It is difficult to provide timing information since this will depend upon the actual processor chip being used. Also, one can argue that `understandable object code' provides the essential information anyway. Hence conclusion is no action required. K.13. Allow User To Write Assertions The Green language allowed users to provide assertions based upon side-effect free functions. Since Ada 9X also provides pure functions, assertions can be re-introduced. Such a language-based facility allows the executable code to be switched in or out by a linker option or other method. One problem with assertions is the desirability of using non-program data in assertions (AIRCRAFT_ON_GROUND) or a more powerful assertion language (There exists X, such that ...). Resolution: (Provisional) Provide a pragma ASSERT having a Boolean expression as a parameter which can only assess pure functions. (This was discussed at WG9 September 91 and had reasonable support.) K.14. Define Provable Subset For constructing programs which can be formally verified, it is clearly necessary to write in a subset of Ada that can be handled by the tools used for the formal verification. An agreed subset for this purpose would assist the development of appropriate tools. Resolution (Provisional) The definition of such a subset would be a major undertaking which could not be completed within the time-scale of the 9X project. Hence no action is proposed at this stage. Consult Ada 9X project office on the longer term issue. L. Numerics Area This section is not yet in final form. Several issues remain undecided at this writing and are identified as such. In addition, alternatives to some of the proposed features have been investigated and are discussed in the Mapping Rationale. An implementation claiming conformance to the Numerics Area must implement all the features of this section except those identified as being optional. [Note: This allows implementations not claiming conformance to the Numerics Area to substitute different rules for selecting the representations of user-declared real types, different accuracy requirements for the predefined operations of real types, and different attributes of real types. As an alternative, we are considering separating out these features in a separate section of the Special Needs Annex, or in a separate annex, and making them mandatory for all implementations.] L.1. Semantics of Floating-Point Arithmetic L.1.1. Floating-Point Machine Numbers Associated with each floating-point type is a finite set of machine numbers. The machine numbers of a type are those capable of being represented, without loss of accuracy or the raising of an exception, in the storage representation of the type. The machine numbers of a derived type are those of the parent type; the machine numbers of a subtype are those of the base type. L.1.2. Attributes of Floating-Point Machine Numbers Attributes related to the machine numbers of a floating-point type T (i.e., to its storage representation) are defined as follows. T'MACHINE_RADIX yields the radix of the hardware representation of T. T'MACHINE_MANTISSA yields the largest integer value of p, and T'MACHINE_EMIN and T'MACHINE_EMAX respectively the most negative and most positive integer values of exponent, such that every number expressible in the ``canonical form'' sign * mantissa * (radix ** exponent) where - sign = +1 or -1; - radix = T'MACHINE_RADIX; and - mantissa is a p-digit fraction in the number base radix, the first digit of which is nonzero is a machine number of T, i.e., representable in the storage representation of T without loss of accuracy or the raising of an exception. If, in addition, every number expressible in the canonical form, but where - sign = +1 or -1; - radix = T'MACHINE_RADIX; - exponent = T'MACHINE_EMIN; and - mantissa is a T'MACHINE_MANTISSA-digit nonzero fraction in the number base radix, the first digit of which is zero is also a machine number of T (called in IEEE Std. 754 a denormalized number), then the attribute T'DENORM yields the value TRUE; otherwise, it yields FALSE. T'DENORM is a new representation attribute of the type T. An implementation (i.e., one employing ``radix-complement'' representation) may furthermore include -T'MACHINE_RADIX ** T'MACHINE_EMAX and possibly T'MACHINE_RADIX ** (T'MACHINE_EMIN - 2) in the set of machine numbers of T; if so, they must be documented in Appendix F. Of course, zero is also a machine number of T. An implementation may have two distinct representations for floating-point zeros, with positive and negative sign respectively, having the properties given in IEEE Std. 754 or 854. The attribute T'SIGNED_ZEROS yields TRUE in this case, and FALSE otherwise. T'SIGNED_ZEROS is a new representation attribute of the type T. Note: Even if T'SIGNED_ZEROS is TRUE, the predefined equality operator yields TRUE given two operands of zero; this is a consequence of the IEEE standards cited above. Some of the elementary and primitive functions (see L.3 and L.4, respectively) yield results, given operands of zero, that depend on the value of T'SIGNED_ZEROS. The representation attributes T'MACHINE_ROUNDS and T'MACHINE_OVERFLOWS are retained. The meaning of T'MACHINE_OVERFLOWS is clarified (see L.1.5). The attributes T'MACHINE_RADIX, T'MACHINE_MANTISSA, T'MACHINE_EMIN, and T'MACHINE_EMAX return results of the type root_integer'CLASS. The attributes T'SIGNED_ZEROS and T'DENORM return results of type BOOLEAN. L.1.3. Floating-Point Model Numbers Associated with each floating-point type is an infinite set of model numbers. The model numbers of a type are used to define the accuracy requirements that must be satisfied by certain predefined operations of the type (see L.1.5); through certain attributes of the model numbers, they are also used to explain the meaning of a user-declared floating-point type declaration (see L.1.6). The model numbers of a derived type are those of the parent type; the model numbers of a subtype are those of the base type. The model numbers of a floating-point type T are zero and all the numbers expressible in the canonical form, where - sign = +1 or -1; - radix = T'MACHINE_RADIX; - exponent is an integer >= T'MODEL_EMIN; and - mantissa is a T'MODEL_MANTISSA-digit fraction in the number base radix, the first digit of which is nonzero. L.1.4. Attributes of Floating-Point Model Numbers Attributes related to the model numbers of a floating-point type T are defined as follows. The attributes T'MODEL_MANTISSA and T'MODEL_EMIN used to define the model numbers, and the attribute T'MODEL_EMAX, are determined by the accuracy delivered by certain predefined operations of the type T and by their ability to avoid overflow. More precisely, T'MODEL_MANTISSA, T'MODEL_EMIN, and T'MODEL_EMAX yield, respectively, the largest integer <= T'MACHINE_MANTISSA, the most negative integer >= T'MACHINE_EMIN, and the most positive integer <= T'MACHINE_EMAX such that certain predefined operations of the type T satisfy the accuracy requirements given in L.1.5, expressed in terms of the model numbers of the type T and in terms of the attribute T'MODEL_LARGE, which is defined as follows: T'MODEL_LARGE = T'MACHINE_RADIX ** T'MODEL_EMAX * (1.0 - T'MACHINE_RADIX ** (-T'MODEL_MANTISSA)) Two additional attributes of the model numbers are defined for convenience, as follows: - T'MODEL_EPSILON = T'MACHINE_RADIX ** (1 - T'MODEL_MANTISSA). This attribute gives the absolute value of the difference between the model number 1.0 and the next higher model number of the type T. - T'MODEL_SMALL = T'MACHINE_RADIX ** (T'MODEL_EMIN - 1). This attribute gives the value of the smallest positive (nonzero) model number of the type T. The attributes T'MODEL_LARGE, T'MODEL_SMALL, and T'MODEL_EPSILON return results of the type root_real'CLASS. The attributes T'MODEL_MANTISSA, T'MODEL_EMAX, and T'MODEL_EMIN return results of the type root_integer'CLASS. L.1.5. Accuracy of Floating-Point Operations The accuracy requirements for the evaluation of certain predefined operations of floating-point types are stated as follows. Note: We present here a tentative version of the entire rewrite of RM 4.5.7 anticipated for Ada 9X. This section does not cover the accuracy of any operation preserving staticness; such operations are required to yield exact results (see 4.9). It also does not cover the accuracy of the predefined attributes of a floating-point type that yield a value of the type or its base type; such operations also yield exact results (see L.4 and elsewhere). Finally, it should be remembered that values outside the range T'FIRST .. T'LAST can be assigned to variables, passed to parameters, and returned from functions whose type T is a numeric base type (because range checking is no longer performed in those contexts when the type of the variable, formal parameter, or function is a numeric base type), and that fetching, in any context, the value denoted by a name or function_call whose type T is a numeric base type can, but need not, raise CONSTRAINT_ERROR when the value is outside the range T'FIRST .. T'LAST; thus no special provision is made in this section for the possible raising of CONSTRAINT_ERROR when the value denoted by a name or a function_call is used as the operand of a predefined operation. A model interval of a floating-point type is any interval whose bounds are model numbers of the type. The model interval of a type T associated with a value V is the smallest model interval of T that includes V. (The model interval associated with a model number of a type consists of that number only.) An operand interval is the model interval, of the type specified for the operand of an operation, associated with the value of the operand. If the absolute value of either bound of a model interval of T exceeds T'MODEL_LARGE, the model interval is said to be out of bounds; otherwise, it is said to be in bounds. For any predefined arithmetic operation that yields a result of a floating-point type T, the required bounds on the result are given by a model interval of T (called the ``result interval'') defined in terms of the operand values as follows: The result interval is the smallest model interval of T that includes the minimum and the maximum of all the values obtained by applying the (exact) mathematical operation to values arbitrarily selected from the respective operand intervals. The result interval of an exponentiation is obtained by applying the above rule to the sequence of multiplications defined by the exponent, assuming arbitrary association of the factors, and to the final division in the case of a negative exponent. The result interval of a conversion of a numeric value to a floating-point type T is the model interval of T associated with the operand value, except when the source expression has a fixed-point type or is a fixed-point multiplication or division; in the latter case, the result interval is implementation defined. Note: A conversion to a constrained subtype of a type is a conversion to the type followed by a check the result of the conversion belongs to the subtype, as in Ada 83. For any of the foregoing operations, the implementation must deliver a value that belongs to the result interval when the result interval is in bounds; otherwise (i.e., when the result interval is out of bounds), - if T'MACHINE_OVERFLOWS is TRUE, the implementation must either deliver a value that belongs to the result interval or raise CONSTRAINT_ERROR; - if T'MACHINE_OVERFLOWS is FALSE, the result is implementation defined. For any predefined relation on operands of a floating-point type T, the implementation may deliver any value (i.e., either TRUE or FALSE) obtained by applying the (exact) mathematical comparison to values arbitrarily chosen from the respective operand intervals. The result of a membership test is defined in terms of comparisons of the operand value with the lower and upper bounds of the given range or type mark (the usual rules apply to these comparisons). L.1.6. Floating-Point Type Declarations A floating-point type declaration of one of the two forms (that is, with or without the optional range constraint indicated by the square brackets): type T is digits D [range L .. R]; is, by definition, equivalent to the following declarations: type floating_point_type is new P; subtype T is floating_point_type [range floating_point_type(L) .. floating_point_type(R)]; where floating_point_type is an anonymous type, and where P is a predefined floating-point type implicitly selected by the implementation so that it satisfies the following requirements: - If P'MACHINE_RADIX = 10, then P'MODEL_MANTISSA >= D; otherwise, P'MODEL_MANTISSA >= ceiling(D * log(10)/log(P'MACHINE_RADIX) + 1). - If a range L .. R is specified, then P'MODEL_LARGE >= max(abs(L), abs(R)). The floating-point type declaration is illegal if none of the predefined floating-point types available for implicit selection as a parent type in a floating-point type definition satisfies these requirements. Note: Implementations may provide other predefined numeric types that are not available for implicit selection in a numeric type definition. L.2. Semantics of Fixed-Point Arithmetic Note: This section is presented in outline form. The ideas contained herein have been only recently advanced and have not yet been developed to their final conclusion, and work on this section is continuing. The language features for, and especially the model of, fixed-point arithmetic are being greatly simplified to eliminate some of the surprises that users of fixed-point types experience in Ada 83 and to foster wider implementation of the features. The concept of small, as distinct from delta, is eliminated; the values of a fixed-point type T are the integer multiples of its delta in the range T'FIRST .. T'LAST, these values being determined by the rules given below. Implementations are required to support deltas that are powers of two; they may, but are not required to, support other deltas. L.2.1. Fixed-Point Type Declarations In a fixed-point type declaration type T is delta D range L .. R; L and R must be integer multiples of D. The declaration is, by definition, equivalent to the following declarations: type fixed_point_type is new P; subtype T is fixed_point_type range L .. R; where fixed_point_type is an anonymous type, and where P is a predefined fixed-point type implicitly selected by the implementation so that it satisfies the following requirements: - unless abs(R) is a power of two, P'LAST >= R; if abs(R) is a power of two, P'LAST >= R - D; - unless abs(L) is a power of two, P'FIRST <= L; if abs(L) is a power of two, P'FIRST <= L + D. The fixed-point type declaration is illegal if none of the predefined fixed-point types available for implicit selection as a parent type in a fixed-point type definition satisfies these requirements. Note: Implementations may provide other predefined numeric types that are not available for implicit selection in a numeric type definition. The range of the subtype T declared by the preceding fixed-point type declaration is determined as follows: - T'LAST = min(P'LAST, R); - T'FIRST = max(P'FIRST, L). [Note: The elimination of the concept of small as distinct from delta changes the meaning of programs that declare a fixed-point type with a delta that is not a power of two, when such programs are accepted by the implementation, and that, in Ada 83, relied on the default determination of small as a power of two. Implementations of Ada 9X that support such deltas should, during the transition period, continue to allow attribute definition clauses for SMALL and - produce a warning when there is no attribute definition clause for SMALL; - produce no warning when a ``confirmatory'' attribute definition clause for SMALL is used, i.e., one that establishes a small equal to the delta; - produce a warning when a ``non-confirmatory'' attribute definition clause for SMALL is used. This implies that the attribute T'SMALL (for fixed-point types) would also be retained during the transition period. It could conceivably yield the same value as T'DELTA, since the implementation would produce a warning in just those cases in which T'SMALL differs from T'DELTA in Ada 83.] L.2.2. Accuracy of Fixed-Point Operations The accuracy requirements for the predefined fixed-point arithmetic operations and conversions, and the results of relations on fixed-point operands, are given below. These apply only to operations whose operands are not all static expressions; a predefined arithmetic operation applied to static operands preserves staticness and produces an exact result (see 4.9). As in Ada 83, the operands of the fixed-point adding operators, absolute value, and comparisons must have identical types. They are required to yield exact results, since no implementation difficulties are posed by this requirement. Overflow considerations are discussed later. Multiplications and divisions are allowed between operands of any two fixed-point types. The result must be immediately converted explicitly to some numeric type, as in Ada 83. Although this can be viewed as the multiplication or division of two fixed-point operands to yield an infinitely precise result of a special type, followed by its conversion to the target type (see 4.5.5(5)), for purposes of defining the accuracy rules we treat this instead as an operation involving three types (those of the operands and the result). When the result type is a floating-point type, the accuracy is implementation defined (see L.1.5); this case is not further discussed here. For some combinations of the operand and result types in the remaining cases, the result is required to belong to a small set of results called the ``perfect result set''; for other combinations, it is required merely to belong to a generally larger and implementation-defined ``close result set.'' When one operand of a fixed-point multiplication is a static expression (for example, a real literal, named number, or attribute) yielding a value of the type root_real'CLASS, a case allowed in Ada 9X but not allowed in Ada 83 (see 4.5.5(3)), that operand is not implicitly converted in the usual sense, since the context does not determine a unique target type, but the accuracy of the result of the multiplication (i.e., whether the result must belong to the perfect result set or merely the close result set) depends on the value of the operand of type root_real'CLASS and on the types of the other operand and of the result. We need not consider here the multiplication of two such operands, since in that case they are both static and the result must be exact. Either or both operands of a fixed-point multiplication may be non-static expressions of type root_real'CLASS, in which case the accuracy is implementation defined (see below). For a fixed-point multiplication or division whose (exact) mathematical result is V, and for the conversion of a value V to a fixed-point type, the ``perfect result set'' and ``close result set'' are defined as follows: - If the result type is a fixed-point type with a delta of D, * if V is an integer multiple of D, then the perfect result set contains only the value V; * otherwise, it contains the two integer multiples of D just below and just above V. The close result set is an implementation-defined set of consecutive integer multiples of D containing the perfect result set as a subset. - If the result type is an integer type, * if V is an integer, then the perfect result set contains only the value V; * otherwise, it contains the integer nearest to the value V (if V lies equally distant from two consecutive integers, the perfect result set contains both). The close result set is an implementation-defined set of consecutive integers containing the perfect result set as a subset. The result of a fixed-point multiplication or division must belong either to the perfect result set or to the close result set, as described below, if overflow does not occur. (Overflow is discussed later.) In the following cases, if the result type is a fixed-point type, let D its delta; otherwise, i.e. when the result type is an integer type, let D be 1.0. - Let L and R be the deltas of the left and right operands of a fixed-point division, or of a fixed-point multiplication neither of whose operands is of type root_real'CLASS. For a multiplication, if (L * R) / D is an integer or the reciprocal of an integer, the result must belong to the perfect result set; otherwise, it belongs to the close result set. For a division, if L / (R * D) is an integer or the reciprocal of an integer, the result must belong to the perfect result set; otherwise, it belongs to the close result set. - For a multiplication with a static operand of type root_real'CLASS and value V, let R be the delta of the other operand. If there exist integers m and n such that V = (m / n) * (R / D), the result must belong to the perfect result set; otherwise, it belongs to the close result set. An implementation is allowed to restrict the range of V that it will accept. - For a multiplication with one or two non-static operands of type root_real'CLASS, the result must belong to the close result set. A multiplication P * Q of an operand of a fixed-point type F by an operand of an integer type I, or vice-versa, and a division P / Q of an operand of a fixed-point type F by an operand of an integer type I, are also allowed, as in Ada 83. In these cases, the result has a type of F and need not be converted. The accuracy required in these cases is the same as that required for a multiplication F(P * Q) or a division F(P / Q) obtained by interpreting the operand of the integer type to have a fixed-point type with a delta of 1.0. The accuracy of the result of a conversion from an integer or fixed-point type to a fixed-point type, or from a fixed-point type to an integer or fixed-point type, is the same as that of a fixed-point multiplication of the source value by a fixed-point operand having a delta of 1.0 and a value of 1.0, as given by the foregoing rules. The result of a conversion from a floating-point type to a fixed-point type must belong to the close result set. The possibility of overflow in the result of a predefined arithmetic operation or conversion yielding a result of a fixed-point type T is analogous to that for floating-point types. If all of the permitted results belong to the range T'BASE'FIRST .. T'BASE'LAST, then the implementation must deliver one of the permitted results; otherwise, - if T'MACHINE_OVERFLOWS is TRUE, the implementation must either deliver one of the permitted results or raise CONSTRAINT_ERROR; - if T'MACHINE_OVERFLOWS is FALSE, the result is implementation defined. L.2.3. Attributes of Fixed-Point Numbers Because the model of fixed-point arithmetic is no longer expressed in terms of model numbers and model intervals, no attributes related to the Ada 83 model are required (except T'DELTA, T'FIRST, and T'LAST). In particular, the attributes T'MANTISSA, T'SMALL, T'LARGE, T'SAFE_SMALL, and T'SAFE_LARGE (of a fixed-point type T) are eliminated and not replaced by other attributes, except that T'SMALL may be retained during the transition period, as discussed earlier. T'FORE and T'AFT are retained because of their connection with I/O. L.3. Elementary Functions Implementations conforming to the Numerics Area shall provide a predefined generic package called GENERIC_ELEMENTARY_FUNCTIONS and an accompanying predefined package called ELEMENTARY_FUNCTIONS_EXCEPTIONS having the following specifications: package ELEMENTARY_FUNCTIONS_EXCEPTIONS is ARGUMENT_ERROR : exception; end ELEMENTARY_FUNCTIONS_EXCEPTIONS; with ELEMENTARY_FUNCTIONS_EXCEPTIONS; generic type FLOAT_TYPE is digits <>; package GENERIC_ELEMENTARY_FUNCTIONS is subtype FLOAT_BASE is FLOAT_TYPE'BASE; function SQRT (X : FLOAT_BASE) return FLOAT_BASE; function LOG (X : FLOAT_BASE) return FLOAT_BASE; function LOG (X, BASE : FLOAT_BASE) return FLOAT_BASE; function EXP (X : FLOAT_BASE) return FLOAT_BASE; function "**" (X, Y : FLOAT_BASE) return FLOAT_BASE; function SIN (X : FLOAT_BASE) return FLOAT_BASE; function SIN (X, CYCLE : FLOAT_BASE) return FLOAT_BASE; function COS (X : FLOAT_BASE) return FLOAT_BASE; function COS (X, CYCLE : FLOAT_BASE) return FLOAT_BASE; function TAN (X : FLOAT_BASE) return FLOAT_BASE; function TAN (X, CYCLE : FLOAT_BASE) return FLOAT_BASE; function COT (X : FLOAT_BASE) return FLOAT_BASE; function COT (X, CYCLE : FLOAT_BASE) return FLOAT_BASE; function ARCSIN (X : FLOAT_BASE) return FLOAT_BASE; function ARCSIN (X, CYCLE : FLOAT_BASE) return FLOAT_BASE; function ARCCOS (X : FLOAT_BASE) return FLOAT_BASE; function ARCCOS (X, CYCLE : FLOAT_BASE) return FLOAT_BASE; function ARCTAN (Y : FLOAT_BASE; X : FLOAT_BASE := 1.0) return FLOAT_BASE; function ARCTAN (Y : FLOAT_BASE; X : FLOAT_BASE := 1.0; CYCLE : FLOAT_BASE) return FLOAT_BASE; function ARCCOT (X : FLOAT_BASE; Y : FLOAT_BASE := 1.0) return FLOAT_BASE; function ARCCOT (X : FLOAT_BASE; Y : FLOAT_BASE := 1.0; CYCLE : FLOAT_BASE) return FLOAT_BASE; function SINH (X : FLOAT_BASE) return FLOAT_BASE; function COSH (X : FLOAT_BASE) return FLOAT_BASE; function TANH (X : FLOAT_BASE) return FLOAT_BASE; function COTH (X : FLOAT_BASE) return FLOAT_BASE; function ARCSINH (X : FLOAT_BASE) return FLOAT_BASE; function ARCCOSH (X : FLOAT_BASE) return FLOAT_BASE; function ARCTANH (X : FLOAT_BASE) return FLOAT_BASE; function ARCCOTH (X : FLOAT_BASE) return FLOAT_BASE; ARGUMENT_ERROR : exception renames ELEMENTARY_FUNCTIONS_EXCEPTIONS.ARGUMENT_ERROR; end GENERIC_ELEMENTARY_FUNCTIONS; The specifications above are identical to the proposed separate ISO standard for the elementary functions except that the formal parameters and results of the elementary functions are of the base type of the generic formal type, rather than the type itself. It is intended that implementations of the GENERIC_ELEMENTARY_FUNCTIONS conform to the various semantic requirements (regarding domains, ranges, exception handling, accuracy, prescribed results, etc.) presented in the proposed separate ISO standard and not repeated here, except that implementations conforming to the Numerics Area must allow GENERIC_ELEMENTARY_FUNCTIONS to be instantiated with a range-constrained floating-point subtype, and the body must be immune to potential effects of the range constraint; in other words, implementations are not allowed to impose a restriction (allowed by the proposed ISO standard) that the generic actual type in an instantiation must be a base type. In Ada 9X, the accuracy requirements are expressed in terms of FLOAT_TYPE'EPSILON, since EPSILON of a subtype is now that of the base type. In the proposed separate ISO standard, the accuracy requirements are expressed in terms of FLOAT_TYPE'BASE'EPSILON. The ARCTAN and ARCCOT functions must exploit signed zeros, if present in the implementation (as indicated by the value of FLOAT_TYPE'SIGNED_ZEROS). In particular, when X is negative and Y is zero: - if FLOAT_TYPE'SIGNED_ZEROS is TRUE, ARCTAN(Y, X, CYCLE) and ARCCOT(X, Y, CYCLE) must deliver -CYCLE/2.0 when Y is a negative zero and +CYCLE/2.0 when Y is a positive zero; - if FLOAT_TYPE'SIGNED_ZEROS is FALSE, ARCTAN(Y, X, CYCLE) and ARCCOT(X, Y, CYCLE) deliver CYCLE/2.0. The behavior of the versions of ARCTAN and ARCCOT without a CYCLE parameter is similar in the above case (i.e., when X is negative and Y is zero), except that the result is then an appropriate approximation of plus or minus pi. In addition, the zero delivered by SIN, ARCSIN, SINH, ARCSINH, TAN, TANH, and ARCTANH when X is zero must have the same sign as X when FLOAT_TYPE'SIGNED_ZEROS is TRUE; similarly, the zero delivered by ARCTAN when X is positive and Y is zero must have the same sign as Y when FLOAT_TYPE'SIGNED_ZEROS is TRUE. (This requirement goes beyond the proposed ISO standard, which did not specify the sign of the result in these cases.) The extent of the exploitation of signed zeros is left implementation defined in the many other contexts in which an elementary function can return a zero result. L.4. Primitive Functions Implementations conforming to the Numerics Area shall provide the following additional attributes: T'EXPONENT(X) T'FRACTION(X) T'COMPOSE(FRACTION, EXPONENT) T'SCALE(X, EXPONENT_ADJUSTMENT) T'FLOOR(X) T'CEILING(X) T'ROUNDING(X) T'TRUNCATION(X) T'REMAINDER(X, Y) T'ADJACENT(X, TOWARDS) T'COPY_SIGN(VALUE, SIGN) T'LEADING_PART(X, RADIX_DIGITS) T'MIN(X, Y) T'MAX(X, Y) T'MODEL(X) T'MACHINE(X) In the case of MIN and MAX, the prefix may denote any scalar type or subtype; for the other attributes, the prefix must denote a floating-point type or subtype. Implementations conforming to the Numerics Area shall also extend the attributes T'SUCC(X) and T'PRED(X) to apply when T is a floating-point type or subtype. Note: We are considering moving MIN and MAX to the core. All of the above attributes except MIN, MAX, MODEL, and MACHINE correspond directly to functions in the GENERIC_PRIMITIVE_FUNCTIONS generic package proposed as a separate ISO standard for Ada 83. The ROUNDING and TRUNCATION attributes correspond to the ROUND and TRUNCATE functions in GENERIC_PRIMITIVE_FUNCTIONS; the latter names are proposed in the Information Systems area of the Special Needs Annex for entirely different attributes (see J). The EXPONENT_ADJUSTMENT parameter of the SCALE attribute corresponds to the EXPONENT parameter of the SCALE function of GENERIC_PRIMITIVE_FUNCTIONS. The SUCCESSOR and PREDECESSOR functions of GENERIC_PRIMITIVE_FUNCTIONS are provided by the extension of the existing SUCC and PRED attributes. The functionality of the DECOMPOSE procedure of GENERIC_PRIMITIVE_FUNCTIONS is not provided. MIN, MAX, MODEL, and MACHINE are new (not taken from GENERIC_PRIMITIVE_FUNCTIONS). The type of the result yielded by all of the ``primitive function'' attributes except EXPONENT is the base type of T; EXPONENT yields a result of type root_integer'CLASS. The type of actual parameters corresponding to X, Y, FRACTION, TOWARDS, VALUE, and SIGN must be the base type of T. Actual parameters corresponding to EXPONENT, EXPONENT_ADJUSTMENT, and RADIX_DIGITS may be of any integer type (i.e., the formal parameter has type root_integer'CLASS). The value of an actual parameter corresponding to RADIX_DIGITS must be positive. All the attributes preserve staticness. These attributes deliver results that are accurate to the level of machine numbers. Like T'FIRST and T'LAST, which also must deliver fully accurate results, they are not among the predefined operations covered by the replacement for RM 4.5.7 (see L.1.5). Note: A decision has not yet been made about whether extra accuracy can be passed in to a primitive function, and whether that implies that the extra accuracy must be maintained during the operation and must affect the result. It is anticipated that the attributes corresponding to functions in GENERIC_PRIMITIVE_FUNCTIONS will be defined as they were there, subject to modifications when a decision is made on the role of extra precision. Their definitions are not repeated here. The definitions depend, in some cases, on the presence or absence of denormalized numbers and signed zeros, as reflected in the values of T'DENORM and T'SIGNED_ZEROS, respectively. The other attributes are defined below. T'MACHINE(X) returns the value of X rounded or truncated to a neighboring machine number (see L.1.1) of the type T; i.e., extra precision beyond T'MACHINE_MANTISSA radix digits is discarded, and CONSTRAINT_ERROR may be raised if the value of X is sufficiently outside the range T'BASE'FIRST .. T'BASE'LAST that rounding or truncating it to the precision of the machine numbers cannot yield a result in this range (i.e., cannot yield the appropriate bound of this range). T'MODEL(X) is defined as follows: - if X is a model number of the type T (see L.1.3) in the range -T'MODEL_LARGE .. T'MODEL_LARGE, X is returned; - if X lies between two consecutive model numbers of the type T in that range, one of those surrounding model numbers is returned; and - if X lies outside that range, CONSTRAINT_ERROR is raised. T'MIN(X, Y) and T'MAX(X, Y) return the minimum and the maximum of their two arguments, respectively. S. Rationale S.G. Systems Programming Area S.G.1. Access to Machine Operations In systems programming and embedded computer applications, it is necessary to write software that interfaces directly to hardware devices. This might not be possible if the Ada language implementation does not permit access to the full instruction set of the underlying machine. Since such code is inherently non-portable, there is no requirement that the language standard specify exactly how access to machine operations must be provided. A need to access specific machine instructions arises sometimes from other considerations as well. Examples include instructions that perform compound operations atomically on shared memory, such as test-and-set and compare-and-swap, and instructions that provide high-level operations, such as translate-and-test and vector arithmetic. All the mechanisms specified in this annex for access to machine instructions are certainly already allowed in Ada 83. It is a matter of interpretation whether this annex actually imposes any new requirement, since some individuals believe that Ada 83 already requires support of MACHINE_CODE. However, there are validated Ada implementations that do not support machine code inserts, and there are some that support it only in a form that is inadequate to fully meet the needs of systems programming and real-time applications. This annex specifies that conformance to it will require support for at least one of these mechanisms. In addition, minimum capabilities for interaction of machine-level code with ordinary Ada code are specified. To be useful, a mechanism for access to machine code must permit the flow of data and control between machine operations and the rest of the Ada program. There is not much value in being able to generate a machine code instruction if there is no way to apply it to operands in the Ada program. An implementation that only permits the insertion of machine code as numeric literal data would not satisfy this requirement, since there would be no way for machine code operations to read or write values in variables of the Ada program, or to invoke Ada procedures. However, the requirement can be entirely satisfied by a primitive form of machine-code insertion, which allows an instruction sequence to be specified as a sequence of data values, so long as symbolic references to Ada entities are allowed in such a data sequence. For convenience, it is desirable that certain instructions that are used frequently in systems programming, such as test-and-set and primitive I/O operations, be accessible as intrinsic subprograms (see 6.3.1). However, it is not clear that it is practical for an implementation to provide access to all machine instructions in this form. The specification concerning use of INTERFACE_NAME and the note about VOLATILE reflect experience with problems caused by compiler and linker optimizations with machine code inserts and interface to assembly language. Machine or assembly code may store an address, and later use it as a pointer or subprogram entry point -- as with an interrupt handler. In general, the compiler cannot detect how the variable or subprogram address is used. When machine code is used in this way, it is the programmer's responsibility to tell the compiler about these usages. It is the language's responsibility to specify a way for the programmer to convey this information to the compiler. One concern is optimization that might cause the data object or subprogram code to be deleted. There is an assumption here that INTERFACE_NAME will prevent the linker from deleting the associated data or code. Similarly, loads or stores referencing an object may be suppressed if the compiler is not aware of accesses through machine code. There is an assumption here that VOLATILE will prevent such optimizations. The specifications in this section are not likely to be enforceable by standard validation tests, but it should be possible to check for the existence of the required documentation and interfaces by examination of vendor supplied documentation, and spot checks with particular machine instructions. S.G.2. Interrupts The ability to write handlers for interrupts is essential in systems programming and in programming real-time embedded computer applications. The intent of this part of the Annex is to provide such a capability. The model of interrupts and interrupt handling specified here is intended to capture the common elements of most hardware interrupt schemes, as well as the software interrupt schemes used by some application interfaces to operating systems -- notably POSIX. The notion of blocking an interrupt is an abstraction for various mechanisms that may be used to prevent delivery of an interrupt. These include operations that ``mask'' off a set of interrupts, raise the hardware priority of the processor, or ``disable'' the processor interrupt mechanism. The reason for distinguishing ``treatments'' from handlers is that executing a handler is only one of the possible treatments. In particular, executing a handler constitutes delivery of the interrupt. The default treatment for an interrupt may be to keep the interrupt pending, or to discard it without delivery. On some processor architectures, the priority of an interrupt is determined by the device sending the interrupt, independent of the identity of the interrupt. For this reason, it is necessary to allow for an interrupt being generated at different priorities at different times. This can be modeled by hypothesizing several hardware tasks, at different priorities, which may all call the interrupt handler. Checking that the ceiling priority of an interrupt handler is not lower than the maximum priority at which the interrupt may be delivered might require information that is specific to the installation. In particular, for hardware where the priority at which an interrupt might be delivered is variable, the maximum priority at which an interrupt might be delivered is determined by the peripheral hardware rather than the processor architecture. An implementation for such an architecture could either assume that all interrupts can be delivered at the maximum priority, or provide a way for the installer to specify the maximum priority at which each interrupt can be delivered. The specification is intended to allow an implementation to arrange for a protected procedure to be invoked directly by the hardware, for efficiency. That is, protected procedures should satisfy the requirement for fast interrupt handlers. It is expected that the restrictions on protected procedures will be sufficient to eliminate the need for any further restrictions on the form or content of an interrupt handler. Ordinarily, there should be no need for implementation-defined restrictions on protected procedure interrupt handlers, such as those imposed by Ada 83 on tasks with fast interrupt handler entries. One consequence of allowing direct hardware invocation of interrupt handlers is that one cannot speak meaningfully of the ``currently executing task'' within an interrupt handler (see G.8). The alternative, of requiring the implementation to create the complete illusion of an Ada task as context for the execution of the handler, is bound to add execution time overhead to the interrupt handling. Since interrupts may occur very frequently, and require fast response, any such unnecessary overhead is intolerable. On many hardware architectures it is not practical to allow suspension of an interrupt handler. Trying to support suspension of interrupt handlers results in extra overhead, at best. It can also lead to deadlock or stack overflow. Therefore, interrupt handlers are not allowed to suspend. To enable programmers to avoid unintentional suspension, implementations are not allowed to force a task to suspend unless it performs an operation that the language specifies as potentially suspending (see 9.7). With interrupt handlers, it is important that there be a way to implement protected record locking that does not require suspension. Two basic techniques can be applied. One of these provides mutual exclusion between tasks executing on a single processor. The other provides mutual exclusion between tasks executing on different processors, with shared memory. Within a single processor, a non-suspending implementation of protected record locking can be provided by limiting preemption. The basic prerequisite is that while a protected record is locked, other tasks that might lock that protected record are not allowed to preempt. This imposes a constraint on the dispatching policy, which can be modeled abstractly in terms of locking sets. The locking set of a protected record object is the set of tasks and protected operations that may execute protected operations on that object. More precisely, the locking set of a protected record R may include: - Tasks that call protected operations of R directly. - Protected procedures and entries whose bodies call a protected operation of R. - The locking set of Q, if Q is a protected record with a procedure or entry whose body contains a call to an operation in the locking set of R. While a protected record is held locked by a task or interrupt handler, the implementation must prevent the other tasks and interrupt handlers in the locking set from executing on the same processor. This can be enforced conservatively, by preventing a larger set of tasks and interrupt handlers from executing. At one extreme, it may be enforced by blocking all interrupts, and disabling the dispatching of any other task. The priority-ceiling locking scheme described in H.3 approximates the locking set a little less conservatively, by locking out all tasks and interrupt handlers with lower or equal priority than the one that is holding the protected record lock. The interrupt handler overhead and latency metrics are provided to allow a programmer to determine the feasibility of using a given implementation for a particular application. There is clearly imprecision in the definition of such a metric. The measurements involved require the use of a hardware test instrument, such as a logic analyzer; the inclusion of instructions to trip such a device may alter the execution times slightly. The validity of the test depends on the choice of reference code sequence and handler procedure. It also relies on the fact that a compiler will not attempt in-line optimization of normal procedure calls to a protected procedure that is attached to an interrupt. However, there is no requirement for the measurement to be absolutely precise. If a user needs more precise information on timing, the user can do the testing. The only purpose of the metrics here is to allow the user to determine whether the implementation is close enough to the user's requirements to be worth consideration. For this purpose, the accuracy of a metric might be off by a factor of two and it could still be useful. It is not safe to write interrupt handlers without some means of reserving sufficient stack space for them to execute. Implementations will differ in whether such handlers borrow stack space from the task they interrupt, or whether they execute on a separate stack. In either case, with dynamic binding of interrupt handlers, the application needs to inform the implementation of its maximum interrupt stack depth requirement. This could be done via a pragma or a link-time command. Where hardware permits an interrupt to be handled but not to be blocked (while in the handler), it might not be possible for an implementation to support the protected record locking semantics for such an interrupt. Some interrupts may come from more than one device, so that the interrupt handler needs to perform additional tests to decide which device the interrupt came from. For example, there might be several timers that all generate the same interrupt (and one of these timers might be used by the Ada run-time system to implement delays). In such a case, the implementation may define multiple logical interrupt IDs for each such physical interrupt. S.G.2.1. Specification of the Package INTERRUPT_MANAGEMENT The operations defined in this package are intended to be a minimum set needed to associate handlers with interrupts. The type INTERRUPT_ID is implementation defined, to allow the most natural choice of the type for the underlying computer architecture. It is not required to be private, so that if the architecture permits it to be an integer, a non-portable application may take advantage of this information to construct arrays and loops indexed by INTERRUPT_ID. The concept of reserved interrupts is introduced to reflect the need of the Ada run-time system to install handlers for certain interrupts, including interrupts used to implement time delays or various constraint checks. The same concept is used to reflect interrupts that have already been claimed by another part of the application program, through static binding of handlers. Finally, interrupts that are attached to task entries are considered reserved as well. Consideration was given to providing an atomic operation that would attach a handler and return the previous handler. This is not provided, partly because such an atomic operation can be implemented by the user as a protected procedure with interrupt priority (H.3), and partly because the need for this capability was not believed to be common in systems programming outside of the real-time area. Operations for blocking and unblocking interrupts are intentionally not provided. One reason is that the priority model provides a way to lock out interrupts, using either the ceiling-priority of a protected record or the SET_PRIORITY operation (see H.5). Providing any other mechanism here would raise problems of interactions and conflicts with the priority model. Another reason is that the capabilities for blocking interrupts differ enough from one machine to another that any more specific control over interrupts would not be applicable to all machines. S.G.2.2. Static Binding Both a pragma and a procedure are provided for attaching a handler to an interrupt. The procedure form is needed to satisfy the often-heard requirement for dynamic binding, but the pragma form is also required, to provide bindings where they are required during the protected record object elaboration. S.G.2.3. Interrupt Task Entries Binding interrupts to task entries is specified as optional. This is because support for this feature in Ada 83 was never required. This annex already requires support for protected procedures as interrupt handlers. Requiring every implementation of this annex to also support binding interrupts to task entries was considered to be an unnecessarily heavy burden. S.G.3. User-Defined Allocators User-defined allocators provide the application programmer with control over the management of dynamically allocated storage. Examples of critical details that a user might need to control include the placement of objects in pages of different kinds of memory, control over fragmentation, and trade-offs between speed of allocation and deallocation versus economy of storage space. An alternative to a language-defined interface, such as the one specified here, is for the user to avoid the use of allocator expressions and UNCHECKED_DEALLOCATION, and perform all allocation and deallocation directly through user-defined procedures. There are limitations to this alternative. Some of these are: - A compiler might represent certain types of data in a way that permits the storage to be non-contiguous. That is, there might be implicit indirect references (i.e. pointers) within the representation of the data, from one part to another. There is no way for a user-defined allocation procedure to determine whether this is required, and to initialize the pointers properly. With the present specification, the compiler-generated code can do this, after the address of the storage is obtained from the user-defined allocator. - A compiler might need to allocate extra storage for implementation data with certain types of objects (examples include the index bounds of an array and storage for the discriminants of a record). In some contexts explicit representation of such implementation data might not be necessary, since the constraints of the object are statically determinable. Determining whether extra storage is required, how much space it occupies, and how it needs to be initialized, presents a problem for a user-defined storage allocation scheme, similar to the problem with non-contiguous representation. In fact, this problem is likely to co-occur with the previous one, since the implementation data might be stored discontiguously from the rest of the object. - A compiler might assume special alignment (e.g. even-numbered addresses) for certain types of data. Without some special interface such as is provided here, the user would be limited to defining storage allocators for types where the compiler does not assume any special alignment. - To be completely equivalent, the user-defined allocation procedure would also need to perform any default initialization required for the object. This would require complete visibility of the object type declaration, and any operations that need to be performed in the course of the initialization, which is not possible for limited and private types. S.G.3.1. STORAGE_POOL Representation Clause The proposed association of an access type with a storage pool using the STORAGE_POOL attribute, allows the construction of user-defined allocators. In Ada, the new statement provides two complementary features: dynamic allocation of storage for data structures, and the initialization of these objects based on the type information. With complex types (especially those that involve unconstrained arrays and variant records), this initialization may be quite complex. In these latter cases, it is often difficult for the user to mimic the compiler work of initialization, if it chooses to provide the allocation routines explicitly. On the other hand, there are many different storage allocation algorithms. A large number of trade-offs are involved (e.g. whether an allocation request should be temporarily held when storage is not available, or immediately raise STORAGE_ERROR), and the right choice often depends on the specific system architecture and application needs. The separation of duties provided by user-defined allocators allows the compiler to concentrate on using its knowledge of the type structure to perform the correct and most efficient initialization, while providing a hook for the user to choose and implement his favorite allocation technique without worrying about initialization chores. (This of course is optional; the default behavior is as in Ada 83 where the compiler/RTS do both tasks.) The model provided by the user-defined allocator matches the Ada model regarding the life-time of access types and their corresponding collections. Collections are not required to be deallocated on scope exit (but in Ada 9X, they do if the STORAGE_SIZE attribute is specified). Since a pool object may serve multiple collections (or access types), the objects of a specific designated type are not deallocated from the pool object when the scope of the corresponding access type is exited. A user can achieve this effect by using either finalization or UNCHECKED_DEALLOCATION. The entire pool object is, of course, deallocated when it goes out of scope, but the rules ensure that no access type that uses that pool can still be active at that point; thus there is no danger of dangling references. A pool object is similar to the default heap in most Ada 83 implementations: multiple access types can share it, but objects of the designated types are not automatically deallocated. Allowing multiple access types to share a pool object provides the user with the ability to share allocation algorithms for multiple types. It also prevents unnecessary pre-allocation of storage (usually up to the maximum), when it is known that ``groups'' of objects have disjoint life-times and thus they can time-share a memory resource. The ALIGNMENT parameter of the ALLOCATE procedure facilitates pool sharing by types of different alignment requirements. Of course, such different alignment requests might not be supported by a user-defined allocation routine. The pool object concept encapsulates the allocation/deallocation routines with the actual memory resource. The type ROOT_STORAGE_POOL is intentionally similar to the CONTROLLED type. It is our intention to consider, in later versions, a change that will make it a derived type of CONTROLLED because of this inherent similarity. It is envisioned that the ALLOCATE/DEALLOCATE operations will use mutual exclusion mechanisms such as protected records to ensure their correct behavior in a multi-tasking program. Usually, if only one access type is associated with the same pool object, these two should be in the same scope. It is illegal for the pool's scope to be any ``deeper'' than the access type scope, since then dangling references would be possible. On the other hand, having the the access type scope be deeper will leave the pool object alive longer than is required, and its storage will be held unnecessarily. If all the access types that are using a given pool are declared in subprograms that are more deeply nested than that of the pool, then it is impossible to have dangling references for the same argument as above. However, since objects of the designated types may occupy the pool alternately (see example below), there will be a trade-off between the frequency of entering and exiting the access type scope and the overhead associated with allocating and initializing the pool object itself. (Sometimes, the initialization of a pool object may require splitting sub-blocks, setting bit-vectors, pointers arrays, etc.) If the access types are in different scopes (deeper than that of the object pool itself), their storage will be held longer than necessary. If this is a problem, then sub-pool objects may be created at the scope of each access type, and point to the ``master'' pool. When the object representing the sub-pool is no longer in scope (i.e., its associated access type is also out of scope), the user-defined finalization routine can deallocate it, thus freeing space in the master pool. -- This example shows how a single pool object may be used -- alternately by two access types living in sibling scopes. procedure EXAMPLE is -- Declare a pool object of some size to be shared by -- multiple types type BLOCK_LENGTH_TYPE is SYSTEM.STORAGE_COUNT (1..1000); type MY_POOL_TYPE is new SYSTEM.ROOT_STORAGE_POOL with record is BLOCK : BLOCK_LENGTH_TYPE; end record; POOL_OBJECT : MY_POOL_TYPE; -- Objects designated by T will be allocated in POOL_OBJECT type T is access SOME_TYPE;; for T'STORAGE_POOL use POOL_OBJECT; PTR : T; procedure PROC_1; -- Objects designated by T1 will be allocated in POOL_OBJECT type T1 is access SOME_TYPE_1; for T1'STORAGE_POOL use T'STORAGE_POOL; PTR_1 : T1; begin -- Perform various allocations using PTR_1 and T1 end PROC_1; procedure PROC_2; -- Objects designated by T2 will all be allocated in -- POOL_OBJECT type T2 is access SOME_TYPE_2; for T2'STORAGE_POOL use T'STORAGE_POOL; PTR_2 : T2; begin -- Perform various allocations using PTR_2 and T2 end PROC_2; begin for I in 1..N loop PTR := new SOME_TYPE; -- (*1) PROC_1; -- (*2) PROC_2; end loop; end EXAMPLE; Notes on the example: 1. The memory occupied by these allocations (unless being unchecked deallocated) will live until PROC is exited and POOL_OBJECT is reclaimed. 2. Here, objects of SOME_TYPE_1 no longer exist. However, their storage is still being held in POOL_OBJECT, unless explicitly deallocated. There are several trade-offs that need to be balanced by the user here: The frequency and size of allocations vs the pool object size; the cost of allocation/deallocation of specific objects; the number of iterations and the relative length of the other code, etc. Based on this trade-offs, the user can decide when and how often to deallocate objects, and how to sub-divide the pool object. We have considered other alternatives for supporting this functionality. One alternative would simply associate two operations, ALLOCATE and DEALLOCATE, with an access type. These operations would be called in the same way as in the current proposal, but would have no implicit association with the pool object. In fact, the concept of a pool object would not exist; it would be represented as a hidden state of the operations (or the package in which they are included). The heap used by these types will be finalized when the access type goes out of scope. But since there are no INITIALIZE/FINALIZE routines, which are called automatically by the generated code, this would all have to be dealt with explicitly by the user. Furthermore, the code in these operations would have no information about the types that use them, so the checks that avoid invoking, for example, an allocation routine after the implied pool object has gone, would be close to impossible to implement. The issues of initialization and finalization of the user-defined ``heap'' would then have to be dealt with explicitly, and be much more error-prone, or an additional set of rules would have to be specified. Therefore, the issues of dangling references or heaps that live longer than necessary will have to be addressed by the user in a way that is less safe and convenient. Thus, we determined that this approach does not provide the desired functionality and does not encapsulate well the service. Another approach that was suggested was to specify the user-defined pool object in the new construct itself. This would require modifying the source code in every place where such an allocation takes place (and the possible occasional omission of some). In the current proposal, everything is specified in one place and applies to all allocations, deallocations, and finalizations of the designated types. S.G.3.2. Interactions with STORAGE_SIZE Attribute The possibility of allowing STORAGE_SIZE to be specified for a type with user-defined storage allocation was considered. The reasonable interpretation of this combination is for the compiler to pass the user-specified storage size to the user-defined storage manager upon initialization of the pool object. This possibility was rejected, on the grounds that first, this information is already present as part of the pool object declaration, and providing it in two different ways would be redundant. Second, the information provided by the STORAGE_SIZE clause could not be used effectively by a user-defined storage manager. The user-defined pool object must be declared and elaborated prior to the type to which it is bound. If the user-defined pool is to be allocated on the stack, the size must be fixed by that point. The problem is that the expression for STORAGE_SIZE need not be static, and so may not be evaluated that early. By the time the information from the STORAGE_SIZE clause is available, it is too late to use it. This problem of obtaining the size information from the representation clause early enough to be used in sizing the user-defined storage pool is aggravated if the user wants to use a single storage pool for several access types. S.G.3.3. MAX_STORAGE_SIZE Attribute There is a well-defined requirement for user control over storage allocation and recovery. A very important special case is where the user can achieve constant-time allocation and deallocation operations, by using a pool of same-sized storage blocks. The MAX_STORAGE_SIZE attribute is intended to tell the user the size of block that would be required in this case. Note that the SIZE attribute of a type does not meet this requirement, since it yields the minimum number of bits needed to hold an object of this type. S.G.4. User-Defined Finalization In Ada 83, a record type may have a user-defined default initialization through the provision of defaults for its components. However, there is no way to define actions to be performed when an object of a type is ``finalized.'' It is essential to be able to define finalization actions associated with a type, because a scope in Ada may be exited in so many ways -- exception, abort, transfer of control, in addition to a normal completion. Furthermore, for an object declared in a library package, there is no convenient place to insert an explicit call on a user-defined finalization procedure, to ensure that every program which includes the package always performs the desired object finalization. Finally, in some cases the need for finalization is an implementation detail of the abstraction rather than a part of its visible interface. Users of an abstract data type should not need to know or care whether a given limited type requires finalization. In Ada 9X, we introduce controlled types. A controlled type is a type derived from the type CONTROLLED which is declared in the package FINALIZATION_SUPPORT of the Systems Programming Annex. Controlled types have initialization and finalization actions associated with them. The elaboration of an object of a controlled type invokes its initialization operation (after the default initialization completes). Initialization proceeds ``inside-out'' -- if a controlled object has any components of a controlled type, these components are initialized first, then the object itself. (Note however, that storage allocation, when present, still proceeds in the normal ``outside-in'' direction.) This is so defined in order to allow for the enclosing object's initialization routine to rely on meaningful values of its components. Conversely, components cannot see their enclosing object. The finalization action for an object must be performed for declared objects or their subcomponents at the end of their scope. For objects created by an allocator, or their subcomponents, the finalization actions must be performed prior to reclaiming the storage of the object. Finalization is also invoked when the object is explicitly deallocated using an instantiation of UNCHECKED_DEALLOCATION. Finalization proceeds ``outside-in'' -- first the object itself is finalized, then any controlled components. This ensures that the subcomponents still have a meaningful value when performing the finalization action on the enclosing object. After performing any finalization actions for an object, the implementation may reclaim the storage for the object, either by returning it to the heap as part of UNCHECKED_DEALLOCATION, or by cutting back the stack. Note that one of the finalization actions may deallocate a heap object designated by an access value contained within the object. However, such a finalization action will never reclaim storage for the object that is itself being finalized. Storage reclamation, when present, always follows finalization actions. The type CONTROLLED is a tagged limited type with two primitive operations: INITIALIZE and FINALIZE. These operations have by default, null bodies. User-defined initialization and finalization actions for a type may be defined by: 1. Deriving from a controlled type; either the type CONTROLLED in FINALIZATION_SUPPORT, or one of its derivatives, and then 2. either inheriting or overriding the primitive operations INITIALIZE and FINALIZE Since CONTROLLED is a tagged type, users may extend the type with components appropriate to the application. For example: with FINALIZATION_SUPPORT; package DYNAMIC_STRING is type DYN_STRING(INITIAL_SIZE : NATURAL := 0) is new FINALIZATION_SUPPORT.CONTROLLED with limited private; procedure INITIALIZE(STR : in out DYN_STRING); -- Allocates the initial buffer when INITIAL_SIZE > 0 procedure FINALIZE(STR : in out DYN_STRING); -- Frees the current buffer procedure ASSIGN(LEFT : out DYN_STRING; RIGHT : in DYN_STRING); -- Copies the value of the RIGHT into LEFT, -- freeing and reallocating the buffer if necessary function "&"(LEFT, RIGHT : DYN_STRING) return DYN_STRING; -- Catenate LEFT and RIGHT, return combined string function CONSTRUCT(STR : STRING) return DYN_STRING; -- Construct dynamic string from Ada STRING function VALUE(STR : DYN_STRING) return STRING; -- Return current value of STR as Ada STRING function "="(LEFT, RIGHT : DYN_STRING) return BOOLEAN; -- Dynamic string equality operator private type STRING_PTR is access STRING; type DYN_STRING(INITIAL_SIZE : NATURAL := 0) is new FINALIZATION_SUPPORT.CONTROLLED with record LENGTH : NATURAL := 0; BUFFER : STRING_PTR; end record; end DYNAMIC_STRING; with UNCHECKED_DEALLOCATION; package body DYNAMIC_STRING is procedure FREE is new UNCHECKED_DEALLOCATION(STRING, STRING_PTR); procedure INITIALIZE(STR : in out DYN_STRING) is begin if STR.INITIAL_SIZE > 0 then STR.BUFFER := new STRING(1..STR.INITIAL_SIZE); end if; end INITIALIZE; procedure FINALIZE(STR : in out DYN_STRING) is begin FREE(STR.BUFFER); end FINALIZE; -- Etc. end DYNAMIC_STRING; Being limited, objects of a controlled type must be passed and returned by reference. In other words, the address of such an object must be passed or returned, rather than a copy of the object. If such an object were copied, the temporary created might also need finalization. Furthermore, if the original object were updated, then the temporary might be left referencing a deallocated heap object. Previous versions of the mapping specification proposed a user-defined copy operation and the finalization of temporaries. Instead, the current version, proposes to simply defer all finalization actions of a function with a limited result type. (For a complete discussion of these issues, see MR-S.7.4.5.) In an earlier version, exit handlers could be associated with a package body to permit finalization of that package. Although this feature has been eliminated to simplify the mapping, a similar effect can be achieved with controlled types, as follows: with FINALIZATION_SUPPORT; package PACKAGE_FINALIZATION_SUPPORT is type FINALIZATION_PROC is access procedure; type CLEAN_UP(USING : FINALIZATION_PROC) is new FINALIZATION_SUPPORT.CONTROLLED; procedure FINALIZE(OBJECT : in out CLEAN_UP); end PACKAGE_FINALIZATION_SUPPORT; package body PACKAGE_FINALIZATION_SUPPORT is procedure FINALIZE(OBJECT : in out CLEAN_UP) is begin OBJECT.USING.all; end FINALIZE; end PACKAGE_FINALIZATION_SUPPORT; The package defines a controlled type with a discriminant, an access-to-procedure which is called when any object of the type goes out of scope. It could be used as follows: with PACKAGE_FINALIZATION_SUPPORT; package body TERMINAL_MANAGER is use PACKAGE_FINALIZATION_SUPPORT; procedure RESET_TERMINAL is begin . . . end RESET_TERMINAL; AT_END : CLEAN_UP(USING => RESET_TERMINAL'ACCESS); -- When the package goes out of scope, so does AT_END, -- and the FINALIZE routine is called on it begin . . . end TERMINAL_MANAGER; Finalization with controlled types has been designed to make possible implementations relatively straightforward. Here is a possible private part for FINALIZATION_SUPPORT: private type CONTROLLED_PTR is access all CONTROLLED'CLASS; type CONTROLLED is tagged record NEXT_CONTROLLED_OBJECT : CONTROLLED_PTR; -- Pointer to next object to be finalized STORAGE_POOL_FOLLOWER : CONTROLLED_PTR; -- Additional pointer used for objects in the heap -- to simplify removal upon UNCHECKED_DEALLOCATION end record; end FINALIZATION_SUPPORT; Each task would maintain a singly-linked list of objects to be finalized. Similarly, each storage pool containing controlled objects (including components), would maintain a doubly-linked list of objects to be finalized. Upon entering a frame with controlled objects (or components), a pointer to the head of the per-task finalization list would be saved. As new objects are initialized, they would be added to the head of the list. On any exit from the frame, the list would be walked back, calling FINALIZE on each object, until the saved point. Each controlled subcomponent appears on the list separately. Components are initialized and placed on the list before the enclosing object, implying that they will be finalized after the enclosing object. When allocating a controlled object, or one which contains controlled subcomponents, the controlled objects would also be added to the per-storage-pool finalization list. UNCHECKED_DEALLOCATION would first finalize all the controlled subcomponents and then the object as a whole. After each object (or subcomponent) is finalized, it is removed from the list. Finally, the object will be deallocated. When the storage pool as a whole is about to be deallocated, this list would be walked, calling FINALIZE on each remaining object. S.G.5. Elaboration Control The primary requirement addressed here is the fast starting (and possibly restarting) of programs. We have also addressed the reduction of the run-time memory requirement of programs, by removing some of the elaboration code. There is a spectrum of techniques that can be used to reduce or eliminate elaboration code. One technique that has been suggested is to run the entire program up to a certain point, then take a snap-shot of the memory image, which is copied out and transformed into a file that can be reloaded. Program start-up would then consist of loading this check-point file and resuming execution. This core-dump approach is suitable for some applications and it does not require special support from the language. However, it is not really what has been known as preelaboration, nor does it address all the associated requirements. This core-dump approach to accelerating program start-up suffers from several defects. It is error-prone and awkward to use or maintain. It requires the entire active writable memory of the application to be dumped. This can take a large amount of storage, and a proportionately long load time. It is also troublesome to apply this technique to constant tables that are to be mapped to read-only memory; if the compiler generates elaboration code to initialize such tables, writable memory must be provided in the development target during the elaboration, and replaced by read-only memory after the core-dump has been produced; the core-dump must then be edited to separate out the portions that need to be programmed into read-only memory from those that are loaded in the normal way. This technique presumes the existence of an external load device, which may not be available on smaller real-time embedded systems. Finally, effective use of this method requires more precise control over elaboration order than Ada currently provides, to ensure that the desired packages, and only those packages, are elaborated prior to the core-dump. Given that the compiler does the work of preelaboration, some trade-offs need to be made between the generality of constructs to which preelaboration applies, the degree of reduction in run-time elaboration code, the complexity of the compiler, and the degree to which the concerns of the run-time system can be separated from those of the compiler. Though stated differently, the definition of a preelaboratable package here is very close to that of a pure package. The main difference is that a preelaboratable package can have state (i.e. variables). It is hoped that the restrictions on the kinds of variable declarations permitted will allow such a preelaborated package to have little or no elaboration code. They should also allow the implementation to omit checks for access before elaboration. Implementations are required to reject (not just ignore) instances of this pragma for units that do not comply with the restrictions specified for preelaboratable packages. As a result, preelaboratable packages are the same on all conforming implementations. Rejection of incorrect usage of this pragma is also useful, for example, on implementations that are not targeted to real-time systems, but which might be used to check out software that will eventually be ported to a real-time system. Tasks and allocators are not included among the things specified as preelaboratable, because the initialization of run-time system data structures for tasks and the dynamic allocation of storage would ordinarily require code to be executed at run time. While it might be technically possible to preelaborate tasks and allocators under sufficiently restrictive conditions, this is considered too difficult to be required of every implementation that supports preelaboration, and would make the compiler very dependent on details of the run-time system. The latter is generally considered to be undesirable by real-time systems developers, who often express the need to customize the Ada run-time environment. It is also considered undesirable by compiler vendors, since it aggravates their configuration management and maintenance problem. (Partial preelaboration of tasks might be more practical for the simple tasking model, described in the Real-Time annex, see H.8.) The requirement not to use write operations in the elaboration of preelaborated constants is intended to ensure that the implementation does not prevent such constants and the code that references them from being loaded into read-only memory. As it presently stands, the PREELABORATE pragma requests a compiler optimization that is always safe. The compiler is not required to perform this optimization, and it is very likely that a compiler that is capable of performing this optimization will do so always, regardless of whether the pragma is specified. Thus, the only tangible service performed by the pragma is to instruct the compiler to reject violations of the associated semantic constraints. The utility of this pragma might be increased if considerations about restarting programs were eliminated. Then, it could be interpreted as giving permission to the implementation to initialize library-level variables via the loader. This optimization, which is wrong if the program might be restarted, could significantly reduce start-up time and run-time code size in applications that make extensive use of initial values for variables. Without this permissive interpretation, preelaboration of variables with initial values may be performed by block-copying from read-only storage, or by conventional elaboration code. Preelaboration is in the Systems Programming Annex (rather than the Real-Time Annex) because the functionality is not limited to real-time applications. It is also required for support of distribution. S.G.6. Unsigned Integers Sequences of bits are interpretable as signed or unsigned quantities. Unsigned integers have utility to a number of applications such as checksums and cyclic redundancy checks, bit vectors and sets, hash coding, and pseudo-random numbers. They are also useful for interfaces to other languages supporting unsigned integers, interfaces to specialized hardware, and to make full and efficient use of the underlying hardware arithmetic for unsigned representations found on many machines. The latter comes up frequently in computations involving hardware addresses.[Arithmetic on ADDRESS has been provided separately (see MS-13.7).] Although Ada 83 provides extensive support for signed quantities, its support for unsigned is problematic, leading to the following requirement: Requirement 6.1-A(1) -- Unsigned Integer Operations: Ada 9X shall provide efficient support for operations on unsigned integer data. In particular, unsigned arithmetic, logical operations, and shifting operations shall be supported. [DoD 90] Any solution for unsigned integers should address the following [see RR-0460]: 1. Integer types with non-negative range which fully exploit those available on the underlying hardware 2. Numeric literals for those types in arbitrary bases 3. Efficient modular arithmetic operations for those types 4. Efficient bit-wise operations There are well-known problems with achieving these goals in Ada 83. For example, to have numeric literals, a type should be derived from a predefined integer type, thus obtaining its literal from universal_integer. However, this is precluded, since for integer types in Ada 83, it is required that the base type have a range which is symmetric around zero. Predefined operators and attributes for unsigned integers should support modular operations, effectively using operations which the hardware makes available. Users may define their own operators (although not attributes) for a derived type, but such operations are not efficient, and the predefined (``signed'') operations reemerge in generic units, and other unexpected places. To manipulate sequences of bits in Ada 83, and to define logical boolean or shift operations, one may define packed arrays of BOOLEAN. However, this does not address the arithmetic needs (numeric literals, and modular arithmetic). Furthermore, support for packed BOOLEAN arrays is implementation-dependent. Ada 9X defines a standard package, UNSIGNED_INTEGERS, in the Systems Programming Annex. An implementation may provide one or more unsigned types within the package, which specifies standard names for these types, as well as arithmetic, relational, logical, and shift operations on them. It is intended that implementations provide unsigned integer types only for those representations which are readily supported by the hardware. S.G.6.1. Unsigned Integer Types The package UNSIGNED_INTEGERS declares a predefined integer type ROOT_UNSIGNED. Its range is from zero to some implementation-defined upper bound whose value is ROOT_UNSIGNED'SIZE**2-1. ROOT_UNSIGNED may not be declared as an ordinary integer type due to the symmetric range problem cited above; its declaration suggests this through the use of cyclic base preceding the range. An implementation may define additional unsigned types of the form: type UNSIGNED_n is new ROOT_UNSIGNED range 0..2**n-1; Where n is the size of the type in bits. Unsigned integer types are included in root_integer'CLASS, from which they obtain their literals. The full complement of integer attributes are available on unsigned types, with the expected, modular semantics. For example: ROOT_UNSIGNED'SUCC(ROOT_UNSIGNED'LAST) = ROOT_UNSIGNED'FIRST = 0 The predefined Boolean and shift operations on unsigned types are defined in terms of their canonical binary representation as a sequence of bits. The operations provided here represent a consensus of the bit-level operations found on most computers, completed with a full set of relational and arithmetic operations. It is expected that on most hardware these operations will map directly to machine instructions. There remains the possibility that for certain hardware some of these operations might not be implementable without software emulation. Given that the operations provided by this package are frequently needed, requiring implementations for some unusual architectures to provide such operations via software emulation seems preferable to omitting them. Bit-indexing and bit-slice operations on unsigned integers were also considered, but not included. Where such operations are required, UNCHECKED_CONVERSION to a packed BOOLEAN array can be used. The pragma CALLING_CONVENTION (see MS-13.9) declared with each unsigned operation, specifies that the calling convention is intrinsic, so that objects of access-to-subprogram types cannot designate these subprograms. The bodies of the subprograms could still be given in the body of the package, or not at all if pragma INTERFACE is also specified. S.G.7. Shared Variable Control This section will be provided at a later date. S.G.8. Task Identification In order to do user-defined task scheduling, and write server tasks that serve requests in an order different from entry service order, it is necessary to be able to associate user-defined entities with tasks (e.g. deadline, criticality). This package provides this capability. S.G.8.1. Package Specification of TASK_IDENTIFICATION This package is provided to meet the requirements of user-defined task schedulers and the construction of servers. These include the need to be able to identify the task calling a scheduled service, associate information with the client, and retrieve it later. Several alternatives were considered for the association of a user-defined attribute with tasks. The interface defined in the present package permits the most efficient implementation. All that is required is for the run-time system to reserve one pointer in the TCB, and to allow the user to read and write this value. A less efficient, minimal alternative, would be a ``key'' that a user can apply to look up tasks in tables (e.g via indexing, hashing, or tree search). One possibility that was considered is the ADDRESS attribute of a task. This would require that T1'ADDRESS and T2'ADDRESS be guaranteed to be distinct if and only if T1 and T2 are names of distinct tasks. It would result in slower updates and retrieval, and possibly require extra storage. The reason for raising TASKING_ERROR if a reference is made to an attribute of a terminated task is to allow the implementation not to keep the entire TCB around after the task terminates. The CURRENT_TASK function is needed to allow a user to perform certain operations on the calling task, in contexts where the calling task is not known. These operations include aborting the environment task and changing its base priority (see H.5). The latter capability is essential for a subprogram or a protected record entry (e.g. as part of a scheduling package) to modify the priority of the calling task. CURRENT_TASK is also the only way of obtaining the identity of the environment task, which executes the main subprogram of an active partition, since this task is anonymous. There are two situations in which it is not meaningful to speak of the ``currently executing task''. One is within an interrupt handler, which may be invoked directly by the hardware. The other is within a protected record entry body, which may be executed by one task on behalf of a call from another task (the CALLER attribute should be used in the latter case). For efficiency, the implementation is not required to preserve the illusion of there being an interrupt handler task, or of each execution of an entry body being done by the task that makes the call. Instead, calling CURRENT_TASK in these situations is defined to be an error. The values that may be returned if the error is not detected are based on the assumption that the implementation ordinarily keeps track of the currently executing task, but might not take the time to update this information when an interrupt handler is invoked or a task executes an entry body on behalf of a call from another task. In this model, the value returned by CURRENT_TASK would identify the last value of ``current task'' recorded by the implementation. In the case of an interrupt handler, this might be the task that the interrupt handler preempted, or an implementation task that executes when the processor would otherwise be idle. If CURRENT_TASK could return a value that identifies an implementation task, it might be unsafe to allow a user to abort it or change its priority. However, the likelihood of this occurring is too small to justify checking, especially in an implementation that is sufficiently concerned with efficiency not to have caught the error earlier. The documentation requirements provide a way for the user to find out whether this possibility exists. S.G.8.2. CALLER Attribute of an Entry The need for the CALLER entry attribute arises from the requirement to facilitate user-defined real-time scheduling and other server algorithms. These approaches involve servers and clients, where the server usually needs to know the identity of its caller. No such capability currently exists. The reason for choosing an attribute and not a function is to allow compile-time detection of incorrect placement of this construct. S.H. Real-Time Systems S.H.1. Priorities In a real-time application, it is necessary to schedule the use of processing resources to meet timing constraints. Priority-based scheduling is the approach to meeting this requirement and is specified in this annex. Priority scheduling has a well developed theory, and is adequate for a wide range of real-time applications. However, it is important that the Ada language does not forbid other scheduling models. There are many appropriate scheduling techniques, and more are continually being discovered. Even within the domain of priority scheduling, there are many variations. No one scheduling model is accepted as adequate for all real-time applications. Therefore, the Ada language should not constrain the range of scheduling options open to a real-time application. It is also important to permit Ada tasks to take advantage of the concurrent programming support of commercial real-time operating systems or executives. This is becoming increasingly important with the growing acceptance of the ``open systems'' approach to software architecture, and the development of standards for application program interfaces to operating system services, like POSIX. Ada should not impose any requirements on the language implementation that conflict with the scheduling model of an underlying operating system. For these reasons, the Ada 83 priority scheduling model has been removed from the core of the language. However, this leaves a gap. The Ada 83 model has been criticized -- by some people for being too restrictive, and by others for not being precisely enough defined. Still, some users have found it useful and even have used it to develop portable applications. It is clearly essential to continue to support those users. This argues for the inclusion of a priority scheduling model in this annex, one that is compatible with Ada 83. A second reason for specifying a standard scheduling model in this annex is economy. Even though there may be no single scheduling model that can satisfy the requirements of all Ada users, it seems that a large number of real-time users would be satisfied with priority scheduling, if the model were fleshed out just a little more. If this is true, agreement on such a model in the language standard will benefit users and implementors. The priority model specified in this annex is intended to subsume the Ada 83 model and provide some improvements. The specific improvements, which are discussed in more detail below, include support for dynamic priorities, solutions to the problem of priority inversion, and a unified model of the interactions of task priorities with protected records and interrupts. S.H.1.1. Priority Subtypes The range of possible task priorities is extended so that it can overlap with interrupt priorities because these ranges do overlap on some hardware architectures. A new range SYSTEM.ANY_PRIORITY is introduced (rather than simply allowing SYSTEM.PRIORITY to include interrupt priorities) because existing Ada 83 programs may assume that SYSTEM.PRIORITY'LAST is below interrupt priority. Moreover, since giving a task a priority that blocks interrupts is sufficiently dangerous that it should be very visible in the source code, the subrange SYSTEM.INTERRUPT_PRIORITY is introduced. A minimum number of levels of priority is specified, in the interest of promoting portability of applications and to ensure that an implementation of this Annex actually supports priority scheduling in a useful form. Moreover, research in Rate Monotonic scheduling has shown that approximately 32 levels of priority is the minimum needed to ensure adequate schedulability in systems with 32 or more tasks. (This is assuming that if there are more than 32 tasks, groups of tasks will be assigned the same priority.) Subject to this observation, the required number of levels of priority is small, in order that it not be too burdensome for implementors. In particular, it is desirable that where hardware provides support for priority scheduling, it should be possible to use such support. Certain hardware architectures are reported to support only 32 levels of priority, including interrupt priority levels. Therefore the combined number of priority levels is not required to be higher than 32. In order to permit the use of efficient bit-vector operations on 32-bit machines, where one bit may need to be reserved, the actual requirement is reduced to 31. S.H.1.2. Base and Active Priorities The distinction between base and active priority is required to explain the effect of priority inheritance. The base priority of a task is the priority the task would have in the absence of priority inheritance. Priority inheritance is already present in Ada 83, during rendezvous. It is extended here, to bound priority inversion during protected operations. Priority inheritance is limited to the forms specified, in order to permit more efficient implementation. The key attribute of these forms of priority inheritance is that they do not permit the active priority of a task to change asynchronously. Inheritance only happens as a direct result of the execution of the affected task, when the task is being released from suspension, or before the task has ever executed. If inheritance is via protected record operations, the priority is raised at the start of the operation and lowered at the end. If inheritance is via rendezvous, the priority is raised at the beginning of rendezvous (either by the accepting task itself, or by the caller before the acceptor is resumed) and then lowered at the end of the rendezvous (by the acceptor). The case of activation is slightly different, since if the active priority of the task is raised, it is raised by the creator. However, this change is synchronous with the affected task, since the task has not yet started to execute; the lowering of the priority is done at the end of activation by the action of the activated task. Priority inheritance via queued entry calls, via abortion, and via a task master waiting for dependents to terminate is intentionally not specified, mainly because the effects are asynchronous with respect to the affected task, which would make implementation significantly more difficult. An additional reason for not doing inheritance through task masters waiting for dependents is that it would be one-to-many inheritance, which would also introduce extra implementation difficulty. Additional reasons for not doing inheritance via abortion are stated in S.H.6. S.H.1.3. Base Priority Specification Initial specification of the base priority of a task is by means of the pragma PRIORITY. This is upward-compatible with Ada 83. A new pragma is provided for specifying a base priority that may be at interrupt level. The pragma is different in order to make it very visible in the source code wherever a base priority is being assigned that might have the side-effect of blocking interrupts. The INTERRUPT_PRIORITY pragma is also allowed to specify priorities below interrupt level, so that it is possible to write reusable code modules containing priority specifications, where the actual priority is a parameter. The special rule for elaboration of these pragmas allows tasks of the same type to have different priorities. The expression specifying the priority is elaborated separately for each task (and each protected record) object. This means that it is possible, for example, to define an array or an access collection of tasks of different priorities, by specifying the priority as a call to a function that steps through the desired sequence of priority values. S.H.2. Task Dispatching Model S.H.2.1. The Conceptual Model The use of conceptual queues in the specification of the dispatching model is derived from the draft POSIX 1003.4 (Realtime Extension) and 1003.4a (Threads Extension) standards. This model represents a hard-won consensus of the POSIX group, which found it to be acceptable to both vendors and users. The specific policy specified here is intentionally very similar to the POSIX SCHED_FIFO policy. A separate queue for each processor is specified in the model, in order to allow for models of multiprocessor scheduling in which certain tasks may be restricted to execute only on certain processors. If the implementation allows all tasks to run on any processor, then the conceptual dispatching queues of all processors will be identical. Since this is only a conceptual model, the implementation is free to implement the queues as a single physical queue in shared memory. The model thus accommodates a full range of task-to-processor assignment policies, including the extremes of a single dispatching queue and a separate queue per processor. Though it is not specified here, it is desirable for delay queues to be ordered by priority, within sets of tasks with the same wake-up time. This can reduce priority inversion when several tasks wake up at once. Ideally, run-time system processing for wake-ups of lower priority tasks should also be postponed, while a high-priority task is executing. This behavior is allowed by the model, but it is not required, since the implementation cost may be high. S.H.2.2. The Default Dispatching Policy Though we hope that the default scheduling model will be adequate for most real-time applications, it is inevitable that there will be a demand for implementation-defined variations. To see how the dispatching model admits implementation-defined scheduling policies, consider Earliest-Deadline-First (EDF) scheduling. The EDF scheduling algorithm is known to be optimal for systems of independent tasks on a single processor. The EDF priority of a task is the number of ready tasks with later (absolute) deadlines. In general, this value may need to be adjusted for every ready task that is eligible for execution, whenever there is a change in the set of tasks that are eligible for execution. Since there is no mechanism by which a user-defined scheduler can be notified to make such changes, the DYNAMIC_PRIORITY_SUPPORT package is insufficient for a user to implement EDF scheduling. However, an implementation is free to provide EDF scheduling via an implementation-defined package. The implementation could dynamically adjust base priorities to reflect EDF task ordering, in which case the semantics could be defined in terms of the run-time system calling DYNAMIC_PRIORITY_SUPPORT.SET_PRIORITY to effect the changes. Alternatively, an implementation could model EDF scheduling by means of ``priority inheritance'', where tasks inherit priority dynamically from some implementation-defined abstraction. For this to work well, the base priorities of all tasks would need to be ANY_PRIORITY'FIRST since the active priority would need to be lowered dynamically, as well as raised. Another expected application requirement is for time slicing. Implementation- defined time-slicing schemes may conform to this specification by modifying the active or base priority of a task, in a fashion similar to that outlined for EDF scheduling. S.H.3. Ceiling Priorities By specifying that the task executing a protected operation inherits the priority of the protected record, we permit the duration of priority inversion due to enforcement of mutual exclusion between operations on protected records to be bounded. A priority inversion is a deviation from the ideal model of preemptive priority scheduling; that is, a situation where a higher (base) priority task is waiting for a processing resource that is being used by a lower (base) priority task. Priority inversion is undesirable in a priority-based scheduling system, since it represents a failure to honor the intent of the user, as expressed by the task priorities. Bounding priority inversion is important in schedulability analysis. In particular, if priority inversion can be bounded, Rate Monotone Analysis can be used to predict whether a set of Ada tasks will be able to meet deadlines [Sha 90a]. There is a substantial amount of literature on the subject of Rate Monotone Analysis. The technique has been successfully applied to several hard-real-time systems written in Ada. The ceiling locking scheme specified in this Annex is similar to what has been termed ``priority ceiling emulation'' in [Sha 90b], and is similar to the ``stack resource protocol'' described in [Baker 91]. On a single processor, these schemes have the property that, once a task starts to run, it cannot suffer priority inversion until it suspends itself. Thus, the only points at which a task can suffer priority inversion are where the task has awakened from suspension (e.g. delay or rendezvous) and is waiting to resume execution. At these points, it may need to wait for one task with lower base priority (but a higher inherited priority) to complete the execution of a protected operation. With priority inheritance through protected record ceilings, the duration of priority inversion encountered by a task T that has awakened from suspension will be no longer than the longest execution time of any one protected operation, over all the protected records with ceilings higher than or equal to the base priority of T. In figuring this bound, the worst case execution time of each operation must be used, including the epilogue code. For a protected record with entries, this time bound must include the case where the maximum number of queued entry calls are served. (In any case, this number is bounded by the number of tasks that share access to the protected record.) Note that the Annex does not require that protected records be implemented in any specific way. However, it is intended that the model be implementable via an efficient non-suspending mutual exclusion mechanism, based on priorities. Such mechanisms are well understood for static priority systems where the only priority inheritance is through locks, but the inclusion of dynamic base priorities and other forms of priority inheritance complicates the picture. We will argue the adequacy of the specifications in this Annex to permit an efficient non-suspending mutual exclusion mechanism based on priority, under certain assumptions. In this discussion it is assumed that priority inheritance only occurs via the mechanisms specified in this Annex, and the only processing resources that can be required by a task are a processor and protected records locks. Here, a lock is an abstraction for having mutually exclusive access to a protected record object. The operations on locks are to seize a lock, and to release a lock. Locks are not transferable; once seized, a lock is not allowed to be seized by another task until it is released by the last task that seized it. It is assumed that protected records can be implemented using locks. It is also assumed here that when the base priority of a task is lowered, it yields its processor to any task of equal active priority, in particular to one that is holding a protected record lock with that priority as its ceiling, if such task exists. The cases of a single processor and a multiprocessor will be considered separately. Suppose there is only one processor. Assume that the implementation of the seize operation is not able to suspend the task. We will argue that mutual exclusion is still enforced, by the scheduling policy. In particular, suppose a task, T1, is holding a lock on a protected record, R1. Suppose T2 is another task, and T2 attempts to seize R1 while T1 is holding it. We will show that this leads to a contradiction. Let C(R) denote the ceiling of a protected record R, BP(T) denote the base priority of a task T, and AP(T) denote the active priority of a task T. If AP(T2) > C(R1), T2 would not be allowed to attempt to lock R1. (This rule is enforced by run-time check.) Therefore, AP(T2) <= C(R1). T1 must run in order to seize R1, but it cannot be running when T2 attempts to seize R1. So long as T1 is holding R1, it cannot be suspended. (This rule can be enforced statically, or by a run-time check.) Thus T1 must be preempted, after it seizes R1 but before T2 attempts to seize R1. When T1 is preempted, it goes to the head of the dispatching queue for its active priority, where it stays until it runs again. Note that the active priority of T1 cannot be changed until T1 runs again, according to the reasoning in H.1: changes to base priority are deferred while T1 is holding the lock of R1, and T1 cannot inherit higher priority since it is not suspended and must already have started activation. For T2 to attempt to seize R1 while T1 is on the dispatching queue, T2 must have higher active priority than T1, or have been inserted at the head of T1's queue after T1 was preempted. The latter case can be eliminated: for T2 to be inserted at the head of T1's dispatching queue, T2 must be preempted after T1; to be preempted after T1, T2 must be running after T1 is preempted; to be running after T1 is preempted, T2 must be at the head of the highest priority non-empty queue; this queue must have higher priority than AP(T1), since T1 is at the head of its own queue. Thus, in either case, T2 must be executing with higher active priority than AP(T1), some time after T1 is preempted and while T1 is still on the same priority queue. That is AP(T1) < AP(T2). Since T1 is holding R1, it follows that C(R1) <= AP(T1) < AP(T2) at the first point where T2 runs after T1 is preempted, and while T1 is still on the same dispatching queue. Before T2 attempts to seize R1, the active priority of T2 must drop to a value no greater than C(R1). (This is enforced by a run-time check.) The active priority of T2 cannot drop below AP(T1), or T1 would preempt. This leaves the possibility that the active priority of T2 drops to exactly AP(T1). We are assuming that in this case, the implementation causes T2 to yield to T1, as part of the operation that changes the base priority of T2. Thus, T2 cannot execute and so cannot attempt to lock R1. In conclusion, for a single processor, the scheduling policy guarantees that there is no way a task can execute to attempt to seize a lock that is held by another task. On a multiprocessor, it is clear that priorities alone will not be sufficient to enforce mutual exclusion. Some form of interprocessor locking is required. Suppose this is accomplished by means of a busy-wait loop, using an atomic read-modify-write operation such as test-and-set. That is, a processor attempting to seize a protected record lock ``spins'' until it is able to set some variable in shared memory, which indicates that the protected record is locked. Thus, there is no danger of loss of mutual exclusion. The new problem is deadlock. A necessary (but not sufficient) condition for deadlock is a cycle of ``wait-for'' relationships between pairs of tasks. In this case, there are two kinds of wait-for relationships. The obvious kind is where task T is spinning for a lock R held by task T'. The less obvious kind is where T is waiting for a processor that is being held by the spinning task T'. The priority locking scheme does not prevent a direct deadlock situation of the obvious kind, in which task T1 is spinning waiting for a lock held by task T2, and task T2 is spinning (on another processor) waiting for a lock held by task T1. Fortunately, the user can prevent this kind of a deadlock, by not using nested protected operation calls, or by imposing a fixed ordering on nested protected operation calls. The serious problem is the other kind of deadlock, involving a task waiting for a processor. A simple example of this kind of deadlock is where task T2 seizes R, T2 is preempted by T1, and then T1 starts spinning in an attempt to seize R. This results in a deadlock if T1 is spinning on the only processor where T2 can execute at this time. (This example generalizes to more processors and tasks.) This kind of deadlock is serious, since it is hidden inside the implementation, where the user cannot prevent it. Fortunately, this latter kind of deadlock is prevented by the ceiling locking scheme. Suppose there is a cycle of wait-for relationships. If T is waiting for T', we have either: 1. T is spinning for a lock L held by T', so AP(T) = C(L) <= AP(T'). (Note that we may have C(L) < AP(T') if T' performs a nested protected operation with higher ceiling, while it is still holding the lock L.) 2. T is waiting for a processor held by T', which is spinning for some lock L', so AP(T) < AP(T') = C(L). If the second relationship holds for at least one pair of tasks in the cycle, we have a contradiction. An implementation is allowed to use locking based on ceilings for internal purposes, such as to protect dynamic storage allocation and deallocation. If it does this, an application may get into trouble by executing an operation that uses such an implicit locking mechanism while the task has a higher active priority than the ceiling of the lock. (This problem might occur, for example, if the user tries to evaluate a storage allocator within an interrupt handler.) If this happens, the implementation might raise PROGRAM_ERROR for the ceiling violation. To reduce the likelihood of this problem arising, the implementation is required to make the ceilings of any such implicitly-used locks at least as high as SYSTEM.PRIORITY'LAST. In addition, the implementation is required to document any operations that may involve such locking. S.H.4. Entry Queuing Policies There is strong feeling that any existing Ada applications that rely on FIFO entry queuing order should continue to work with Ada 9X. For this reason, the default entry queuing policy specifies FIFO queuing order. FIFO service order for entries has been widely criticized for being a source of priority inversion, which can cause a significant loss of schedulable processor utilization. The same issue has been raised regarding the choice between open alternatives of a selective accept statement, which is unspecified by Ada 83, but can be a cause of priority inversion. Therefore, a mechanism is provided to allow a user to override the default (FIFO) entry queuing policy. The mechanism chosen is a pragma. The entry queuing policy pragma is specified as applying to a compilation. It is possible that the existence of such a pragma may cause different code to be generated. Thus, it must be provided no later than the point where each affected unit is compiled. Further, certain compilation units (including packages that are intended to be reusable) may depend for correctness on a particular policy. It is therefore important for the compiler or linker to be able to detect inconsistencies in such dependences. Finer-grained specification was considered, but rejected on the grounds that it would impose more implementation burden and the need for it had not been demonstrated. The entry queuing policy does not affect the order in which alternatives of a selective entry call statement are considered. There, the order is specified as being textual. The reason is that there is no clear way to apply priority rules for that case, since all the calls are being made by a single task. S.H.4.1. FIFO Queuing FIFO_QUEUING is provided for upward compatibility with Ada 83. An application whose correctness relies on FIFO entry queuing can thus specify it explicitly, via the QUEUING_POLICY pragma. Nothing is specified about the rule for choosing between open alternatives of a selective accept statement, since there is no consensus among existing Ada compilers or Ada users as to how this choice should be resolved, in the presence of FIFO entry queuing. Leaving the rule unspecified provides upward compatibility. Also, in the absence of priority-queuing, specifying the order does not seem to gain much. S.H.4.2. Priority Queuing This annex specifies a priority scheduling model. Substantial consensus seems to have evolved that priority scheduling requires priority-ordered entry service. Priority-ordered entry service eliminates a source of unnecessary priority inversion and more consistently expedites the execution of higher priority tasks. Therefore, the PRIORITY_QUEUING policy is specified as a user-selectable option that must be supported by all real-time implementations. Under the PRIORITY_QUEUING policy, the decision to specify that entry queues are not reordered on priority changes is based on the goal of implementation uniformity. The decision to resolve this in favor of not reordering queues for priority changes is based on implementation efficiency. Otherwise, every time the active priority of a task changes, it must be deleted and reinserted in all the entry queues on which it resides. This can be especially expensive for the selective entry call, and the select statement with abortable final part. These both allow a task to be on any number of entry queues at once. The latter form of select further allows a task to execute while it is on entry queues. This means that prologue and epilogue operations on protected records would require reshuffling of queues. Note also that since the active priority of a task is raised while it is executing a protected operation, its active priority while the implementation is actually enqueuing the call (or requeuing in the case that it is requeuing itself) on an entry queue is already raised to the ceiling of the protected record. Thus, the priority at which the task is enqueued is not necessarily the current active priority of the task at that time (otherwise, all calls would be queued at the same priority, which would be the ceiling of the protected record). The desired semantics can be implemented by recording the calling priority as an implicit parameter associated with the queued call. The idea of requiring that the active priority of a task cannot change while it is on an entry queue was considered. This has several complicated consequences. Most serious of these seems to be that a task could not lock a protected record while it is on an entry queue and executing the abortable part of a select statement. Other limitations would also need to be imposed, including extension of the deferral of base priority changes to cover the case where a task is on an entry queue. This would in turn increase the overhead of entry calls. Priority inheritance through queued entry calls was intentionally omitted from the PRIORITY_QUEUING policy. Several models for priority inheritance through queued calls have been proposed in the public literature. However, there is no hard analytical data to support choosing one of these priority inheritance models over another. The basic need for providing access to shared data without unbounded priority inversion is already supported by the inheritance feature of priority-based protected records. The implementation overhead of more complex forms of priority inheritance is sufficiently high that requiring it is not sensible, if only one standard entry queuing and priority inheritance policy is specified. (See also H.1.2.) The decision to require priority-order selection between open alternatives of selective accept statements, and between open entries of a protected record is based on avoiding unnecessary priority inversion. It is understood that some additional implementation overhead is implied, but this overhead is believed to be overcome by the potential gain in schedulability. S.H.4.3. Other Queuing Policies The possibility of specifying other standard entry queuing policies, including some with priority inheritance, was also considered. The decision not to specify such alternate policies in the Annex was based on a general policy of avoiding multiple solutions for a single problem. Providing multiple solutions to a single problem would be contrary to the intent of the Annex to encourage uniformity among implementations and portability among applications. Moreover, supporting each alternate policy would involve significant implementation cost. Therefore, requiring every implementation of the Real-Time Annex to support several alternate policies would not be sensible. The intent is that there be one policy that all Annex implementations would be required to support; this is the PRIORITY_QUEUING. (For applications that require upward compatibility with Ada 83, FIFO_QUEUING is also named.) Of course, implementations are permitted to define alternatives, but portable applications should rely only on the PRIORITY_QUEUING or FIFO_QUEUING policy. S.H.5. Dynamic Priorities Versions of the GET_PRIORITY and SET_PRIORITY operations with no explicit task parameter, that would apply implicitly to the calling task, are unnecessary, since this capability is provided by the CURRENT_TASK function (see G.8). A function for querying the active priority of a task was intentionally omitted. This is partly because the active priority can be very dynamic, making the result unreliable. Another reason is that it may be difficult to implement such a function on some systems. Moreover, requiring this value to be available would rule out at least one efficient technique for priority inheritance, in which inheritance relationships are represented only by links from donor to inheritor, and the implementation does not need to explicitly compute the active priority of a task. The running task is required to go to the tail of the dispatching queue for its active priority, when its base priority is set, for several reasons. First, this is what is specified in the POSIX SCHED_FIFO policy, after which the default dispatching policy is modeled. Second, this is needed to prevent priority changes from violating mutual exclusion, if priority inheritance is used to enforce mutual exclusion. For example, suppose task T1 is executing a protected operation of PR1, and task T2 preempts. Suppose T2 then lowers its own base priority to the ceiling of PR1. T2 is required to go to the tail of the dispatching queue at this point. This ensures that there is no danger of T2 trying to perform a protected operation on PR1. (Allowing T1 to preempt under these circumstances might also be desirable from the point of view of expediting the release of PR1.) The effect of SET_PRIORITY is deferred during protected operations, for several reasons. One reason is to prevent SET_PRIORITY from forcing a task that is executing in a protected record to give up the processor to a task of the same active priority. Another reason is to permit more efficient implementation of priority inheritance and priority changes. In particular, when entering a protected record body, or starting a rendezvous, it is permissible to push the old active priority on a stack, from which the old active priority value is popped when the protected body is left, or the rendezvous ends. Note that there need be no additional execution time overhead for implementing this deferral, over that already imposed by deferring abortion, in the case that no priority change is attempted during the time the protected record operation is executed. For simplicity of understanding and implementation, priority changes are also deferred in the other places where abortion is deferred. In particular, changes to the base priority of a task are deferred while the task is executing a protected operation, while it is the calling task in a rendezvous, and while it is executing a user-defined finalization procedure. Taken from a user's point of view, deferring a change to the base priority of a task during protected operations should make no difference, if the change is in the downward direction, since this would not affect the active priority of the task. If the change is in the upward direction, the difference could be noticeable, but no requirement for immediate upward change of base priority during protected operations has been demonstrated. There may be a requirement for a temporary change to active priority, but this is possible by calling an operation of a protected record with high enough ceiling. Deferring base priority changes also eliminates some semantic questions. One of these is whether the base priority of a task should be allowed to be raised higher than the ceiling priority of a protected record in which the task is currently executing. Allowing this would constitute a retroactive violation of the rule that a task cannot call a protected operation of a protected record while its active priority is higher than the protected record ceiling. However, this violation does not seem to have any adverse effect. An alternative, of just allowing (but not requiring) an implementation to defer changes to base priority, was considered. Requiring more uniform behavior across implementations was considered to be preferable, given that there seems to be no extra implementation overhead. If an implementation-defined extension needs to vary the base priority of a task, the conceptual model is that the implementation uses DYNAMIC_PRIORITY_SUPPORT.SET_PRIORITY to achieve the necessary priority changes. The implementation need not actually call this procedure, but the effect must be semantically consistent with the effect of SET_PRIORITY. For example, implementation-defined base priority changes must be deferred where abortion is deferred. To avoid confusion, and perhaps simplify implementation, an implementation may reject the explicit use of the DYNAMIC_PRIORITY_SUPPORT package in conjunction with the use of extensions that vary the base priority. The purpose of the metric for SET_PRIORITY is to give the user an idea of the cost of this operation, compared to other operations, for a case where it should be about as low as can be expected. Of course, complicating factors may make it worse in other cases. Example of changing priorities of a set of tasks: type MODE_TYPE is range 0..2; type TASK_NUMBER is range 1..4; TASK_PRIORITY: array ( TASK_NUMBER, MODE_TYPE ) := ... ; protected MODE_CONTROL is procedure SET(MODE: MODE_TYPE); pragma PRIORITY(SYSTEM.PRIORITY'LAST); private record MODE: MODE_TYPE:= 0; end MODE_CONTROL; protected HIGH_PRIO_MODE_CONTROL is procedure SET(MODE: MODE_TYPE); pragma INTERRUPT_PRIORITY; end HIGH_PRIO_MODE_CONTROL; use DYNAMIC_PRIORITY_SUPPORT; protected body MODE_CONTROL is procedure SET(MODE: MODE_TYPE) is begin HIGH_PRIO_MODE_CONTROL.SET(MODE); SET_PRIORITY(T1,TASK_PRIORITY(1,MODE)); SET_PRIORITY(T2,TASK_PRIORITY(2,MODE)); end SET; end MODE_CONTROL; protected body HIGH_PRIO_MODE_CONTROL is procedure SET(MODE: MODE_TYPE) is begin SET_PRIORITY(T3,TASK_PRIORITY(3,MODE)); SET_PRIORITY(T4,TASK_PRIORITY(4,MODE)); end SET; end HIGH_PRIO_MODE_CONTROL; Notes on the example: The table TASK_PRIORITY specifies the priorities that the tasks T1 through T4 should have, for every mode. Here, in order to avoid blocking every task for a long time, the priority changes are done in stages, at two different active priorities, via two protected records. The task doing the priority change starts with a call to the lowest-priority protected record. This calls the next higher level. The priority adjustments of lower priority tasks can be preempted by execution of the higher priority tasks. S.H.6. Immediate Abort There are at least two reasons for an application wanting the effect of task abortion to be ``immediate'': 1. To stop the task from what it is doing before it can ``contaminate'' the application further, possibly with dangerous consequences. A task can contaminate the application by changing the system state or wasting processing resources. 2. To be certain that the task has stopped executing, so that the aborter can proceed without fear of interference from the aborted task (e.g. I/O, rendezvous, writing on shared variables). There are several possible meanings of ``immediate'' in this context: - Before the abort statement completes. This is easy to define and implement, but it may take a long or indeterminate time to complete, and so might require suspending the task that executes the abort. It satisfies requirement (2) only. - Before the affected task(s) are allowed to execute further. This is easy on a single processor, but may be too costly or impossible on a multiprocessor. It satisfies requirements (1) and (2). - As soon as the implementation permits it. This would be the same as the meaning above, if on a single processor, but would allow some delay on a multiprocessor. Whether it satisfies any of the requirements depends on the implementation. The third meaning of ``immediate'' seems like the best compromise. This is the basis for the specifications in this section. On the surface of things, there may be a good reason for a task executing an abort statement to be suspended until all the aborted tasks have completed their finalization, and for those tasks to inherit the suspended task's priority while it is suspended. This would be a change from Ada 83, where it is only necessary to wait for the aborted tasks to become ``abnormal'', but Ada 83 did not have user-defined finalization. Certainly, one of the reasons for aborting a task may be to release resources that it is holding. The actual release of such resources may be done during task finalization. However, waiting for finalization is not always possible, since a task may abort itself (perhaps indirectly, by aborting some other task on which it indirectly depends). In this case, it is not possible for the task calling for the abortion to wait for all the aborted tasks (including itself) to complete their finalization. Another problem is where the abort statement is inside an accept statement, and the task being aborted is the caller in the rendezvous. In this case, forcing the aborter to wait for the aborted task to complete finalization would result in deadlock. The problem with self-abortion could be resolved by releasing the aborter to perform finalization. The problem with rendezvous does not seem to be so easily resolved, though one option might be to raise TASKING_ERROR in the aborter, if the deadlock is detected. The requirement to wait for a collection of tasks to complete finalization is partially satisfied by two other mechanisms. One of these is the rule that requires suspension of a completed task master to await termination of dependent tasks. If the tasks being aborted are not dependent, another partial solution is to use the delay statement and the 'TERMINATED attribute to poll the aborted tasks until they have all terminated. However, none of these mechanisms fully accomplishes the objective. Not allowing abortion to cause suspension has several benefits. In real-time applications, there are situations where the task executing the abort statement does not wish to wait for the aborted task to complete; in this case it could also be said that requiring the task to suspend is not ``immediate'' abortion. If the task executing the abort statement were to be suspended, unbounded priority inversion would be possible unless the the tasks being aborted inherit the priority of the suspended task. This form of inheritance is undesirable for reasons explained further below. A final benefit is that the treatment of abortion of a task via the abort statement is more similar to the abortion of a sequence of statements via a change to a barrier caused by a protected operation, since executing the body of a protected operation can never involve suspension of the calling task. Outside of the decision not to suspend the task executing the abort statement, there are other reasons for not requiring that aborted tasks executing finalization code inherit the priority of the task executing the abort. First, this would introduce a new form of one-to-many inheritance, and thereby additional implementation complexity. Second, if the aborted task is a low-priority task, and the aborter has high priority, the aborter may not want to be held up waiting for the aborted task to finalize. Third, if the active priority of the aborted task could be raised by abortion, it would be necessary to take into account all abort statements, as well as task dependency hierarchies, in determining protected record ceiling priorities; otherwise, the active priority of a task might violate the ceiling of a protected record during finalization code. A further reason is the desire to keep the implementation of active priorities simple. S.H.7. Processor Time Accounting Any real-time scheduling paradigm that guarantees schedulability (see R5.1-C(1), R5.2-A(1), R5.2-A(2)) relies on some mechanism for ensuring a limit on the amount of CPU time used by a task. More specifically, real-time applications have the following time accounting requirements: - measurement of the execution time of sections of code; - time-slicing critical periodic tasks with long execution times, so that they can be assigned higher priority than less critical tasks with shorter periods, without loss of schedulability (a ``period transformation''); - enforcing processor time budgets for certain phases of a task's computation; - budgeting processor time for sporadic tasks (i.e. tasks that are not periodic but have hard deadlines), in such a way that they receive good average response time without loss of schedulability of other tasks. These requirements cannot be met well without support from the run-time system. While processor time accounting is absolutely required for many real-time applications, other applications may be unable to tolerate the run-time overhead. Therefore, support for this feature is specified as optional. S.H.7.1. Semantic Model The semantic model for processor time accounting is intended to allow the implementation freedom to do time accounting by either of the two well known implementation techniques sketched below. The more precise techniques make use of an interval timer, which can be programmed to interrupt after a specified (variable) interval of time. This timer can be used to enforce budgets, by interrupting an executing task when its budget is exhausted. The accounting work is done at task-switch points, to update the remaining processor time budget of the task that is switched out and to set the timer for the budget of the task that is switched in. (The timer also needs to be read when the VALUE function is called.) The precision of this technique -- i.e. the time accounting unit -- can be as fine as the granularity of the timer. The other implementation techniques make use of a periodic timer, which interrupts at regular intervals. Accounting work is done at each timer interrupt. This includes decrementing the budget of the current task, and checking whether there is any budget left. The work performed at each interrupt may be simpler than with an interval timer, but the precision is limited by the period of the timer. Thus, there is a trade-off between the fineness of the time accounting unit and the execution time overhead due to the interrupts. S.H.7.2. CPU_TIME_ACCOUNTING Package The package CPU_TIME_ACCOUNTING is intended to provide a bare minimum of features that are adequate to meet the time accounting requirements listed above. Following, is an example of how this package can be used to program an approximate emulation of a sporadic server (see [Brinkley 89]). The full sporadic server model is more complicated, and would require more specialized run-time support, but this kind of approximation is sufficient to obtain most of the schedulability benefit. type INDEX is range 0..BUFFER_SIZE; type REQUESTS_ARRAY is array (INDEX) of REQUEST; protected REQUESTOR is procedure HANDLER; entry PULL(R : out REQUEST); pragma ATTACH_HANDLER(HANDLER, SOME_INTERRUPT_ID); private record NEXT_IN, NEXT_OUT : INDEX := 0; end REQUESTOR; REQUESTOR is a protected record that catches and buffers asynchronous requests for service, whose arrivals are signaled by a hardware interrupt. The entry REQUESTOR.PULL pulls one request from the buffer, waiting if the buffer is empty. Suppose the expected interarrival time and service time of the requests are known, and we have estimated that they can be handled with acceptable response time, without preventing higher priority tasks from achieving their deadlines, given a processor budget allotment of 10 accounting units per millisecond. That is, the sporadic server PERIOD is 0.001 second, and processor QUOTA per period is 10 units. QUOTA : constant MONOTONIC.FINE_DURATION := 0.0001; PERIOD : constant MONOTONIC.FINE_DURATION := 0.001; The invariant of the sporadic server model, which is the basis for its schedulability analysis, is that the server never uses more than QUOTA processor time units in every time interval of length PERIOD, no matter where such an interval may be chosen on the time line. The solution below enforces this invariant by allocating the server QUOTA units of processor time, waiting until it has used these up, and then delaying PERIOD - QUOTA seconds before replenishing the server budget. When the SERVER task has used up its budget allocation, the overrun handler lowers the server's priority enough that it will not interfere with the scheduling of other hard-deadline tasks. Replenishment is done by the REPLENISHER task, based on a fixed delay from the point where the server's budget was last exhausted. There is one more detail. Suppose all requests take at least 30 microseconds. Then, if the budget ever falls below 30 microseconds, the server will not be able to complete a request without waiting PERIOD time units for a budget replenishment. Therefore, to hasten the arrival of the next budget replenishment, whenever the server has an ``odd lot'' of less than 30 microseconds of processor time, it gives this up voluntarily, and starts the replenishment timer ticking immediately. THRESHOLD: constant CPU_TIME_ACCOUNTING.UNITS:= 0.000_030; task SERVER is pragma PRIORITY(MEDIUM_PRIORITY); end SERVER; task REPLENISHER is pragma PRIORITY(HIGH_PRIORITY); -- higher than server end REPLENISHER; protected SERVER_CONTROL is procedure CONDITIONALLY_EXHAUST(T: TASK_CLASS); -- lower SERVER priority to background, if budget is below threshold procedure EXHAUST(T: TASK_CLASS); -- lower SERVER priority to background, unconditionally procedure REPLENISH(T: TASK_CLASS); -- raise SERVER priority pragma PRIORITY(HIGH_PRIORITY); end SERVER_CONTROL; protected body SERVER_CONTROL is procedure CONDITIONALLY_EXHAUST(T: TASK_CLASS) is V: constant CPU_TIME_ACCOUNTING.ACCOUNTING_RANGE := CPU_TIME_ACCOUNTING.VALUE(TASK); begin if V <= THRESHOLD and then V > 0 then CPU_TIME_ACCOUNTING.SET_VALUE(T,0.0); SET_PRIORITY(T, LOW_PRIORITY); end if; end CONDITIONALLY_EXHAUST; procedure EXHAUST(T: TASK_CLASS) is begin SET_PRIORITY(T, LOW_PRIORITY); end EXHAUST; procedure REPLENISH(T: TASK_CLASS) is begin CPU_TIME_ACCOUNTING.SET_VALUE(T,QUOTA); SET_PRIORITY(T,MEDIUM_PRIORITY); end REPLENISH; end SERVER_CONTROL; task body SERVER is CURRENT_REQUEST : REQUEST; begin loop REQUESTOR.PULL(CURRENT_REQUEST); -- get next request to serve SERVE(CURRENT_REQUEST); SERVER_CONTROL.CONDITIONALLY_EXHAUST(SERVER); end loop; end SERVER; SERVER_OVERRUN: CPU_TIME_ACCOUNTING.OVERRUN_SIGNAL; task body REPLENISHER is begin ATTACH_OVERRUN_SIGNAL(SERVER,SERVER_OVERRUN'ACCESS); SERVER_CONTROL.REPLENISH(SERVER); loop SERVER_OVERRUN.WAIT; SERVER_CONTROL.EXHAUST(SERVER); delay PERIOD - QUOTA; SERVER_CONTROL.REPLENISH(SERVER); end loop; end REPLENISHER; The CPU_TIME_ACCOUNTING package can also be used to measure the execution time of a task or to achieve simple time-slicing. Note that the SET_VALUE operation can be complex. For high precision time accounting without excessive overhead, a programmable interval timer will need to be used to signal CPU time overruns. This timer will need to be reset, as part of the SET_VALUE operation. The main problem is the race between resetting the timer and arrival of a timer interrupt from the previous setting. Assuming the interrupt is blocked just before and during the hardware reset operation, it might still be generated. It is important to be able to clear such an interrupt, so that it does not masquerade as expiration of the new budget. With a primitive timer, this may require: blocking the timer interrupt; stopping the timer; unblocking the timer interrupt; handling any pending interrupt; blocking the timer interrupt again; resetting the timer; unblocking the timer interrupt. Several alternate solutions have been considered. One of these is to have the Ada run-time system do all the work of implementing the sporadic server model. In this case, instead of a handler, the user would need to specify the desired replenishment interval, the replenishment amount, and perhaps the priority to which the task should be lowered when its budget is exhausted. This would require the run-time system to do more work, and provide less flexibility to the user. However, it would be more convenient for the user, allow the replenishment mechanism to be implemented more efficiently, and support multiple servers. Another alternative considered was to cast the interface in terms of ``logical processor interrupts''. If each task is viewed as executing on its own logical processor, then one can imagine each logical processor as having timers and interrupts that are directed to that specific logical processor. A logical processor interrupt handler would be associated with a particular task, could only execute by preempting that task, and would affect the state of that task. Time budget expirations (and other asynchronous events) could be handled by logical processor interrupt handlers. The interrupt handlers could interact with the task on the logical processor, by changing the task's priority, suspending the task, or propagating an exception to the task. In the case of CPU time accounting, an interface based on this model could eliminate the need for a separate replenisher task, by having the per-task interrupt handlers both lower and then raise the priority of the task. The argument against this approach is that it requires a new abstraction, which overlaps with the existing asynchronous transfer of control mechanism. S.H.8. Simple Tasking Model Some builders of real-time systems with very demanding timing constraints have argued that the full Ada tasking model is more elaborate than they require, and imposes more overhead than they can afford. The existence of very light-weight executives for other tasking models suggests that significant performance improvements are possible for Ada tasking implementations, if some usage restrictions are imposed. Certainly, any Ada implementor can define a restricted tasking model and provide a run-time system that is optimized for this model. However, the likelihood of different implementors and users independently converging on the same set of restrictions is very small. Thus there are potential benefits to defining a standard simple tasking model. This provides a common focus for implementors and users, improving the chances for reusability of components and application architectures across implementations, with higher expectation of good performance. A simple tasking model should permit simple and useful multi-tasking applications to be expressed, but simplify the implementation problem enough that the size and execution time overhead of the run-time system (RTS) need be no greater than with traditional real-time executives. The application domain of the restricted tasking model defined here is intended to include many of the real-time embedded applications that have rejected Ada tasking as being too heavy-weight. These applications include some that consist of a fixed set of cyclic tasks, with periodic and aperiodic timing constraints. This has traditionally been a stronghold of the cyclic executive and rate-monotonic scheduling models. The intended scope of applications also includes event-driven applications. These applications have traditionally used real-time executives that can dynamically create and schedule extremely light weight tasks. In a generic event-driven model, the system is organized into groups of very simple tasks. A task group is created in response to an event, executes for a while, and goes away. There may be precedence relations and delays between tasks within a group, but an individual task never suspends execution to wait for another task. Each task within a group is very simple: it may be preempted, but otherwise it runs to completion without suspending. The events that can trigger the invocation of task groups include interrupts (e.g., timer-driven) and actions performed by other tasks. This is a very well established model of software architecture, which has been used for more than a decade in guidance and control systems, including radar and sonar tracking systems, and ground-control software for anti-missile systems. This annex only specifies one set of restrictions, and this set is intentionally rather tight. To realize the maximum benefit from the restrictions, it is expected that an Ada language implementation will need to provide a special RTS, and perhaps conditional code within the compiler. This represents a big enough effort that it seems unrealistic for an implementation to do it more than once, as would be needed to recognize and support several distinct sets of restrictions. The hope is that the restrictions are tight enough that the potential performance improvement justifies producing a second RTS. Restrictions were only considered if they satisfied all of the following requirements: - The restriction allows a faster or smaller run-time system. - The advantage is distributed. If it is a speed improvement, it applies to operations other than those ruled out by the restriction. If it is a size reduction, it is not due simply to deletion of unused run-time system components, such as might be done automatically by a linker. - Taking advantage of the restriction does not require a major difference in compilation strategy for programs that follow the restriction from those that do not. - A sample of real-time embedded systems developers felt that the feature is not essential for a significant portion of their applications. The specific implementation benefits, and the specific restrictions on which they depend, are as follows: - Fixed RTS and task storage size. The amount of storage required by the run-time system for its own data structures and task workspace should be determinable statically, no later than load time. * The maximum number of task objects that will be required to be in the system at any one time is fixed, and small. * The maximum number of entries per task is fixed, and small. - Fixed TCB size. If each task can be represented in the RTS by a fixed-size task control block, the complexity of the RTS is reduced. A list of free TCBs may be allocated at load time, speeding up task creation. * The maximum number of entries per task is fixed, and small. * The maximum number of alternatives in a selective entry call statement is fixed, and small. * There are no other select statements nested within the abortable final part of a select statement. (This is covered by the restriction on suspension, further below.) - No overhead for task masters. There should be no overhead due to code executed to keep track of potential task masters, or to check for unterminated dependent tasks. Likewise, there should be no storage overhead for data structures to keep track of task master nesting. Abortion and task termination should not be complicated by the need to support hierarchies. * Only library packages are allowed to have dependent tasks. - Fast abortion. If there is never any code that needs to be executed before an aborted task can be terminated, abortion of a sequence of statements, and exception propagation, can be implemented as a direct transfer of control. Likewise, abortion of a task can be implemented as immediate termination. * Access types can only be declared in a library package. * Only library packages are allowed to have dependent tasks. * Objects of types with user-defined finalization procedures are only declared in library packages. - No stack storage for suspended tasks. If a task is not permitted to be suspended while it is holding stack storage, a much larger number of tasks can be supported, since only one stack storage area is required for each priority level (only the TCB of a suspended task needs to be saved). Traditional real time systems are designed to make this possible. Taking advantage of this storage savings in Ada requires: * Except for the potentially suspending operations (see 9.7) involved in task activation and completion, operations that may cause a task to suspend are only permitted immediately within the sequence of statements of a task. That is, potentially suspending operations are not permitted within a procedure, declare block, or the abortable final part of a select statement. * Tasks do not have local declarations that require persistent storage. This can be achieved by allowing only subprogram declarations. * The entry parameters of every task or protected record can be stored in a fixed-size block. (Since the other rules prevent a task from having more than a fixed number of pending entry calls at a time, this allows for allocation of enough space to implement one select statement, in a fixed-size task control block.) Certain other restrictions were considered, but were not included, for the following reasons: - No races between conditions causing wake-ups. If a task that is suspended can be waiting for one and only one event, the execution time overhead and complexity of run-time system code can be reduced. One example is the treatment of delay expiration while a rendezvous is under way. This could be achieved by forbidding select statements with delay, or then abort alternatives. The select with abortable final part was considered too important to real-time applications to forbid, and by comparison the other forms of select statements are not significantly harder to implement. - Access types. This capability is retained, even though heap allocation may be undesirable. The 'ACCESS and 'UNCHECKED_ACCESS attributes make access types useful for statically allocated objects. - Allocators. This restriction could allow deleting run-time support for dynamic storage management. Given the ability for a user to define storage allocators and deallocators, the reason for concern of real-time users about unsuitability of allocators seems to be reduced. Also, if there are no calls to the run-time storage allocation procedures, it might be possible for a linker to delete them, in which case the advantage is not distributed. - Unchecked deallocation. Some people believe that providing for (a nontrivial implementation of) unchecked deallocation may make heap allocation slower and more complex. The opposing view is that allocation can be implemented in such a way that no performance penalty is incurred unless unchecked deallocation is actually used, and all other storage has been exhausted. In this case, the advantage is not distributed. - Mutual exclusion on dynamic storage management. The speed of storage allocation and recovery operations can be improved if they do not require mutual exclusion between tasks. It could be ensured that each storage pool is only accessed by a single task, if allocators are only allowed for access types declared in a main subprogram or task. (Assuming there are no nested tasks, these types are only visible to one task.) Taking advantage of this would require the compiler to always allocate a separate pool for each access type. It would be difficult to determine the size of such a pool, lacking a STORAGE_SIZE clause. The benefit from this restriction seems limited, given the possibility of user-defined allocators. A user that is concerned with the speed of allocation can omit locking, and achieve higher speed, by the latter mechanism. - Access to tasks. The designated type of an access type is allowed to be a task or object containing a task. Disallowing this might permit preelaboration of task control blocks. On the other hand, supporting preelaboration of tasks would create a dependency between the code generator and the run-time support system. Also, there seemed to be a demand for a very lightweight form of dynamic tasking for real-time embedded applications. - Abortion. Asynchronous transfer of control and task abortion are permitted, since most of the features that would make these operations slow have been eliminated. In particular, there are no dependent tasks and no nested objects of an access type which requires finalization. - Exceptions. Forbidding exception handlers could reduce run-time system size and complexity. This restriction would probably not be onerous from the user's point of view. Some developers of small embedded systems already have a policy of avoiding using Ada exception handling. Some are primarily concerned about the explosive effect on the number of potential control paths through a program, which makes verification difficult. Others are more concerned about execution time of the code that performs exception propagation, that it may be too long or too unpredictable to be tolerated when a run-time error is detected. They avoid using exception handlers by writing the program so that it handles all anticipated conditions in the normal flow of control. If an unanticipated situation occurs, one that calls for an exception to be raised, it must be due to a flaw in the program. The only appropriate response to such an error is catastrophic: the entire system is shut down in a safe manner, perhaps after logging the failure, or the system may be restarted. In this case, the failure is trapped globally by the run-time system, which takes care of the shut-down or restart. The decision not to forbid exceptions was based on the limited pay-off, and the difficulty of obtaining it. Obtaining a significant reduction in distributed overhead is likely to require a major change in compilation strategy, such as a change to subprogram activation record layout and calling conventions. - Delay statements. Forbidding delay statements could reduce run-time system size and complexity. It could also remove the requirement for the language implementation to use a timer. This would free the available timer for use by the application. The run-time system is simplified by the elimination of potential for time-outs in operations, and the possibility for a task to be on more than one queue at a time. However, this restriction was not imposed, because there seemed to be a significant requirement for delays in real-time applications. - Predefined I/O and CALENDAR packages. These are not likely to be needed in embedded real-time applications With protected records, and the ability to use protected procedures as interrupt handlers, the application can write its own timer facility and its own CALENDAR package. However, use of these packages is allowed, based on the assumption that they need not be linked if they are not used. - Task entries. Eliminating rendezvous could reduce the size of the run-time system code, and can reduce the complexity of abortion slightly. Protected records provide a lighter-weight mechanism that is more suitable than rendezvous for data and control synchronization in small real-time embedded systems. However, given the other restrictions, it seems likely that the reduction in run-time system size if rendezvous is not used could be done by selective linking. - Terminate alternative. The terminate alternative is generally considered to be hard to implement. Both the complexity of this feature and its utility are mostly connected with the existence of task hierarchies. Since task hierarchies have been ruled out here already, it is less useful, but also easier to implement. A simple approach could work as follows: It is sufficient to keep just one active-task-count. This count is decremented whenever a task suspends on a select with terminate alternative, and incremented when the task wakes up. It is also decremented when a task completes. No other action is required, unless the count reaches zero. In that case, all tasks in that active partition must be terminated. - Implicit use of the heap. Restricting the compiler from using the heap implicitly (i.e. not as a direct result of using an allocator) can improve the deterministic behavior of the memory in the system, by making all memory usage visible and user-invoked. However, this restriction is not required since it goes beyond the scope of this section. Here, we talk about certain restrictions that the compiler must check (and possibly benefit from). The ``no implicit heap usage'' does not fall into this category and might imply a totally different compilation strategy in general (not just because some constructs are disallowed). At the same cost, for vendors, we could require this for all compilers supporting this annex. Nothing in the user program (abiding to the restrictions) makes it any easier for the compiler to avoid using the heap. Specifying a mechanism for configuring the run-time size limits, such as the number of tasks and number of entries per task, was considered. It is left implementation-defined, because the practical mechanisms are expected to be outside the scope of the language. For example, one method is for the implementor to provide source code to a few run-time system packages, which contain configuration constants. The user could edit these, recompile them, and link them in with the rest of the run-time system and the user's application. Another method is to provide a configuration tool that edits the run-time system object code, inserting constants for the required dimensions at the required points. This same function might be performed by a linker or loader. The objective of allowing the stack space of a task to be deallocated when the task suspends, and retaining the dynamic allocation of (simple) tasks, is to efficiently accommodate programming paradigms based on tasks that run to completion without suspending. Such models have been used successfully in real-time systems for more than 10 years, on systems including the Safeguard ABM and the Patriot SAM. This is also the task model of classical scheduling theories (e.g. see [Coffman 73]). The implementation model is that there is a fixed pool of stack spaces that are shared by all tasks, or all the tasks at a priority level. On each processor, not more than one stack space will be needed for each priority level. The stack space for a given level must be configured (by the user) large enough to meet the largest stack requirement of any task that executes at that priority level. A task releases its stack area when it suspends, and is allocated a stack area when it wakes up again. Depending how dispatching queue(s) are structured, allocation of a stack area might be done at the point where the task wakes up, or as a special case in the dispatcher when it gets ready to do a context switch to a stack-less task. In any case, the implementation can go to a linked list of stack spaces, peel one off, and initialize it with a minimum context. This could be kept simple -- maybe just setting the saved stack pointer value. For example, on a machine with register windows, the implementation could keep one register window stored in the TCB. When allocating a stack area, it would write the new stack pointer (base) into this saved register window. Then, when the task is resumed, the implementation would load registers from the TCB and the task would be running with the new stack. The intention is that all requirements for non-volatile storage associated with a task be met by the task control block (or in a fixed-size extension of it). This includes storage for the implementation of select statements and entry parameters. Local variables within a task are not permitted, because they would need to be retained while the task is suspended. This means non-volatile data used by a task must be declared in library-level packages or passed to the task by means of access values. The size of the task control block is intended to be determinable no later than link time, so that a fixed-size pool of identical task control blocks can be pre-allocated at system initialization time. S.H.9. Monotonic Time Real-time applications have several time-measurement requirements that are not met by the package CALENDAR. A partial list of these requirements includes: 1. A monotonically increasing time reference that is consistent with the duration of relative delays and the wake-up times of absolute delays. This time reference must be adequate for specifying precise periodic task execution; thus it cannot be allowed to jump forward or backward. In particular, this means that the clock cannot be affected by seasonal time changes. Note that the implementation of delays is likely to be based on some form of timer, with discrete ticks. For consistency with the duration of delays, the progression of time on the associated clock should correspond tick-for-tick with this timer. 2. Finer granularity in time measurement than is practical for STANDARD.DURATION. In Ada 83, the type DURATION is required to allow representation of positive and negative durations of up to one day. Requiring the representation of DURATION to simultaneously satisfy this minimum range requirement and the finest granularity requirements of real-time applications imposes too high an overhead on arithmetic operations, which are performed frequently under tight time constraints. Thus, a duration type with finer granularity and shorter range is required. This type must be supported for use with time delays. 3. A time reference that is synchronized with a physical time, external to the local computer system. Examples of standard physical times are TAI or UTC (informally known as GMT). In situations where synchronization opportunities are infrequent relative to the local clock drift, the requirement for synchronization may conflict with the requirements for fine granularity and monotonicity. The package MONOTONIC attempts to satisfy the first two of these requirements. Supporting these by a standard interface is justified by the need for coordination with the implementation of delay statements. Some implementations of this package may also be able to satisfy the third requirement, but this is too much to require of all implementations of this annex. The decision to supply a new package, rather than to simply modify the requirements for CALENDAR and DURATION, is based mainly on concerns for upward compatibility. The perception is that much existing software depends on the range of DURATION, and that some existing software depends on CALENDAR.CLOCK returning local political time, including seasonal changes. Of course, there is no requirement for an implementation to have multiple clocks internally. The implementation may simply provide two package interfaces to a single underlying (monotonic) clock. The capability of supporting clock adjustments and seasonal time changes for CALENDAR.CLOCK is not required by the Ada language; moreover, where this is required by the application, it may be accomplished by adding a variable offset to the monotonic time to obtain the value returned by CALENDAR.CLOCK. S.H.9.1. MONOTONIC Package Specification The required minimum range and precision of FINE_DURATION represent a compromise, given the assumption that the value should be representable in 32 bits. Some users would prefer a SMALL as little as one nanosecond (for example, POSIX 1003.4 specifies nanosecond precision for timers). This allows a range of just slightly more than -2.0..2.0 seconds. Other users would prefer a larger range. They might be satisfied with a SMALL as large as one microsecond, which allows a range of about 35 minutes. The present specifications allow implementations some latitude for the choice between finer precision and greater range, based on specific user requirements, but are restrictive enough to ensure that most real-time applications can be satisfied by every implementation of this annex. An alternative considered was to replace both TIME and FINE_DURATION by a single (64-bit) fixed-point type. This would have simplified the interface and allowed a full range of user needs to be met. However, supporting fixed point arithmetic on 64 bits was considered to be more than could be expected of all real-time implementations. Moreover, users who do not require extreme range or precision would suffer from the overhead of arithmetic operations on objects of such a type. Several issues regarding this package have not yet been resolved. These include: - What minimum range should be specified for MONOTONIC.TIME? The most natural choice would be a range equivalent to that of CALENDAR.TIME. - Should SPLIT and TIME_OF functions be provided for MONOTONIC.TIME? If time is measured from system start-up, the units into which time is split must be different from those for CALENDAR.TIME. One possible split is the one used by the POSIX 1003.4 interface, in which time is split into a count of whole seconds and a count of nanoseconds. Unfortunately, there are many other possible splits, and no clear rule for choosing between them. - How should delays on MONOTONIC.FINE_DURATION be supported? Without this capability, one cannot get the full timer precision out of relative delays. Absolute delays do not eliminate the need for relative delays. There are situations where a task is required to delay from its last execution. The sporadic server example illustrates this. To solve this problem, the delay statement might be overloaded on STANDARD.DURATION and MONOTONIC.FINE_DURATION. There would be a possibility of ambiguity, but the ambiguity could be resolved in favor of STANDARD.DURATION for literal expressions, and type qualification could be used to resolve any remaining ambiguities. - Should the fixed-point type FINE_DURATION be replaced by an integer count of time units? This would break the pattern established by standard DURATION, but there are several potential advantages. First, making this a fixed-point type imposes an implementation requirement to support much greater precision in multiplication and division than is actually required for uses of duration in real-time systems. Making it fixed-point also requires attention to the value of SMALL, so that there is no unnecessary loss of accuracy in calculations, due to mismatch with the granularity of the underlying time reference. Finally, making it an integer type would allow overloading delay statements without any danger of ambiguity. S.H.10. Delay Accuracy This section will be supplied at a later date. S.H.11. Suppression of All Checks For efficiency, some systems programming and real-time applications need to suppress run-time checks. The set of checks defined in Chapter 11 does not cover all the conditions where the run-time system is required to raise an exception. Particular checks of interest include those that raise TASKING_ERROR, and the checks that raise PROGRAM_ERROR for suspension and ceiling violations associated with protected record operations. Simply defining more names for checks would not be a good solution, since the present SUPPRESS pragma permits control at too fine a grain to be helpful. The checks of interest are often performed in the run-time system. To obtain any improvement in performance from eliminating such a check, the run-time system code might need to be changed. This cannot be done at a fine grain. Therefore, a system-wide directive is needed, to specify that a check-free run-time system is desired. S.I. Distributed Systems S.I.1. Introduction The Ada 9X model for programming distributed systems specifies a partition as the unit of distribution. Partitions comprise aggregations of library units and may execute using a distributed target execution environment. Typically, each partition corresponds to a single execution site and all its constituent units occupy the same logical address space. The principal interface between partitions are one or more package specifications. The proposed semantic model specifies rules for partition composition, elaboration, execution, interpartition communication, and termination. Support for the configuration of partitions to the target execution environment and its associated communication connectivity is not explicitly specified in the model. The rationale for this model derives from the Ada 9X Requirements for Distributed Processing (see R8.1-A(1) and R8.2-A(1)). Namely, that the language shall facilitate the distribution and dynamic reconfiguration of Ada applications across a homogeneous distributed architecture. These requirements are satisfied by a blend of implementor and user provided capabilities. In addition, the following properties are considered essential to specifying a model for distributed program execution: - The differences between developing a distributed system and non-distributed system should be minimal. In particular, the same rules for type safety and interface consistency for a non-distributed system should apply to a distributed system. - The implementation should be straightforward. In particular, the run-time systems of partitions must be autonomous; i.e., the run-time system for a single partition or multiple partitions are identical. In this way, robust type-safe distributed systems may be implemented using off-the-shelf Ada compilers that support the model, rather than having to depend on custom adaptations of a compiler to a specific distributed environment. - The model should be adaptable to multifarious distributed target environments and should facilitate programming fault-tolerant applications. In particular, the partitioning to support contemporary distributed architectures should not inhibit more advanced architectures as they become available. The requirements and properties are satisfied by specifying a consistent and systematic approach towards composing distributed systems. Partitions are specified subsequent to the compilation of their constituent library units. Programming cooperation among partitions is achieved by library level packages defined to allow access to data and subprograms in different partitions to which these packages may be assigned. These library packages are identified at compile-time by categorization pragmas. In this way, strong typing and unit consistency is maintained across a distributed system, while at the same time avoiding the complexity of a distributed run-time system. Finally, a dichotomy of implementor and user responsibility is allowed by specifying a common interface to a user-provided communication subsystem (UPCS) that performs all target execution environment communication among partitions. The UPCS is responsible for all routing decisions, low-level message protocols, etc. By separating the work, an implementation need not be aware of the specific networking mechanisms supporting the distribution, while the communication subsystem need not be aware of the types of data being communicated. S.I.2. Model for Distributing an Ada 9X System In Ada 9X the role of an Ada 83 main program is subsumed by a partition. Section 10.1.2 defines a program as one or more partitions. This definition allows an equivalence between a partition and an Ada 83 main program when a partition includes a main subprogram; i.e, a subprogram whose context clause names the library units of the partition. In addition, the description in 10.1.2 is kept purposefully non-specific to allow for many different approaches to dynamic and distributed program construction. In this Annex, certain capabilities are specified to enhance portability of distributed systems across implementations that conform to these specifications. Similar to a main program, each partition is associated with an environment task that elaborates the library units comprising the partition. This environment task calls the main subprogram, if present, for execution and then awaits the termination of all tasks that depend upon the library units of the partition. Therefore, unless an implementation elects to support the Distributed Systems Annex, there is no substantive difference between partitions and main programs, except that a main subprogram remains optional for a partition. This Annex develops the partitioning model for distributed systems in terms of active and passive partitions. The library units comprising an active partition reside and execute upon the same processing module. In contrast, library units comprising a passive partition reside at a common address space module that is accessible to the processing modules of different active partitions that reference them. Consequently, the library units comprising a passive partition are restricted to ensure that there are no dependencies upon the Ada run-time system, since the site at which the passive partition is located does not require processing capabilities. A passive partition provides a straightforward abstraction for representing an address space that is shared among different execution sites. It is implementation-defined whether or not more than one partition may be associated with a processing or common address space module, since the characteristics of these module are target dependent and are outside the scope of the proposed model. Partitions are identified as either active or passive by the post-compilation (link-time) aggregation of library units. If a library package assigned to a partition was compiled with the categorization pragma SHARED_PASSIVE (i.e., it is a shared passive package), and all other library units included in the partition are categorized as either shared passive or pure packages, then the resulting aggregation forms a passive partition. All other aggregations form an active partition. An active partition may name a package from a passive partition in the closure of its library units, thus enabling it to reference packages that are common throughout the distributed system. Different active partitions may safely share protected data or call subprograms in such packages. In addition, active partitions may execute subprograms in other active partitions. Calls to subprograms in a different active partition are allowed only if a library package enclosing the called subprogram was compiled with the categorization pragma REMOTE_CALL_INTERFACE. Each active partition calling the subprogram must name the corresponding remote call interface package in the closure of its library units. When an active partition calls such a subprogram, the call is termed a remote subprogram call. The functionality provided by this Annex does not go beyond the minimal requirements of supporting remote procedure calls. By doing this, the coordination required between the run-time systems of the various active partitions is minimized. The categorization of packages within an Ada library establishes the interfaces through which the partitions of a distributed system cooperate. In a distributed system where no remote subprogram calls or common packages are required, e.g., all inter-partition data is exchanged through external media, package categorization is unnecessary. The different partitions identified at link-time provide equivalent capability to that achieved by specifying separate Ada 83 main programs. The package categorization and link-time identification of partitions provide a flexible and straightforward approach for partitioning the library units of an Ada program library. Library units may be aggregated to form partitions commensurate with the target execution environment for the distributed system, with the single stipulation that shared passive and remote call interface packages may be assigned to only one partition. Different distributed configurations of the target execution environment may then be supported by a single version of an Ada library. Furthermore, the specification of a main subprogram at compilation-time becomes unnecessary. Library units are elaborated and executed within the context of the environment task associated with the partition, and until they communicate with another partition, their execution proceeds independently. Specifying the allocation of partitions to the target execution environment is not included in the Annex; similarly, the replication of partitions is not explicitly specified. Both capabilities are deemed beyond the scope of the requirements. However, because partition replication is essential towards programming fault-tolerant applications, the partition identities used to implement a remote call may be changed. Implementations that support replication of partitions may allow a failed partition to be replaced transparently to other partitions. In addition, the two proposed forms of dynamic binding may be used to facilitate user-controlled replication and reconfiguration. In summary, the above model allows for flexible, link-time partitioning, with compile-time type safety. The model separates categorization and partitioning from configuration and communication to promote compiler/linker independence of the target execution environment. The objective is to maintain the properties of a single Ada program for distributed execution with minimal additional semantic and implementation complexity. S.I.3. Categorization Pragmas Two package categorization pragmas are introduced in the Annex. These pragmas identify packages that may be used to support accessing data and subprograms in different partitions. In other words, the packages that are associated with categorization pragmas provide a visible interface to the partitions to which they are assigned. (These pragmas place specific restrictions upon the declarations that may appear in the associated packages and the library units that they may ``with''.) In addition, such packages must be elaborated in a single partition only. Restricting the kinds of declarations that may be present in such a package simplifies the semantic model and reduces the need for additional checking when the package is named in the closure of another library unit. For example, by disallowing task declarations the interaction among the run-time systems of different partitions to support entry calls across partitions is avoided. For remote call interface (RCI) packages the restrictions ensure that no remote access need be supported, other than that required for remote subprogram calls. Furthermore, the types of all formal parameters are specified so that they may be converted to and from a message stream type using the WRITE and READ attributes respectively (see 14.7). This conversion to and from a message stream type simplifies the interface to the UPCS. For shared passive packages, the restrictions ensure that calling a subprogram of the package from another partition cannot result in a remote call either directly or indirectly. Moreover, the restrictions eliminate a requirement for a run-time system (e.g. to support scheduling or real-time clocks) to be associated with a passive partition. This allows passive partitions to correspond to a logical address space that is common to all partitions that reference the constituent packages. Pure packages (see 10.5.1) may be named in the context clauses of packages which are used as an interface to partitions. For example, a pure package may contain type declarations that are used in the formal parameter specifications of subprograms in remote call interface packages. When no categorization pragma is associated with a library unit, that unit may be included in different active partitions without any restrictions. S.I.4. Remote Subprogram Calls The support of RCI packages facilitates the specification and development of distributed systems based upon extending the well-known remote procedure call (RPC) paradigm to operate between partitions. Consistent with the in-progress recommendations of the OSI RPC WG, the Annex presents an informal mapping to the proposed Interaction Model and Communications Model of the WG report. RCI packages allow normal calls to subprograms that may execute in a different partition from that of the caller. Unless the pragma ASYNCHRONOUS is associated with a procedure in an RCI package, the semantics of a call to a remote subprogram are similar to the semantics of the same subprogram called locally. Remote calls are executed with at-most-once semantics (i.e., the called subprogram is executed at most once; if a successful response is returned, the called subprogram is executed exactly once). Consequently, the user may develop a distributed system such that a subprogram call and the called subprogram may be in the same, or in different partitions. An exception COMMUNICATION_ERROR may be raised by a remote call. This exception allows the caller to provide a handler for conditions that cause a remote call to fail when these conditions are not a result of executing the body of the remote subprogram. For example, if the partition containing the remote subprogram has become inaccessible or has terminated. The need for a more asynchronous form of interaction among partitions, for example a simple broadcast capability, is provided by associating the pragma ASYNCHRONOUS with a procedure in an RCI package. This extends the utility of the remote procedure call paradigm to exploit the underlying asynchronism that may be available through the UPCS. As a consequence, synchronous and asynchronous interactions among partitions are maintained at a consistent level of abstraction by not introducing an agent task to wait for the completion of a remote call if asynchronism is desired. When this pragma is present, a procedure call may return without awaiting the completion of the remote subprogram. When the call and called procedure are in the same partition, the normal call semantics apply. Such remote procedure calls are necessarily restricted to have parameters of mode in only. S.I.5. Post-Compilation Partitioning The aggregation of library units to construct partitions of a distributed system is accomplished subsequent to compiling the units into the Ada library. This post-compilation approach specifies rules for constructing active and passive partitions. These rules ensure that a distributed system is semantically consistent with a non-distributed system that uses the same library units. Moreover, the required implementation is not beyond the state of the practice for contemporary post-compilation tools. Therefore, in order to allow the use of existing tools and to avoid constraining future tools, the Annex omits specifying a standard interface for constructing partitions. Each RCI package may only be assigned to a single active partition. Similarly, each shared passive package may only be assigned to a single passive partition. Following the assignment of a package to a partition, a value for the package attribute PARTITION_ID is available that identifies the partition after it is elaborated. The type of this value is named PARTITION_ID and is declared in the COMMUNICATION_SUPPORT package, thereby allowing the UPCS to provide additional facilities to query partition information. In order to construct a partition, all RCI and shared passive packages referenced by the partition must be assigned to a partition. Consequently, when a partition is elaborated, the PARTITION_ID attribute for each RCI or shared passive package, referenced by this partition, has a known value. The construction is completed by including in a partition all the units named in the aggregation, and their corresponding closures, except the RCI and shared passive packages that are assigned to other partitions. A library unit that is neither an RCI nor shared passive package may be included in more than one partition. A type declaration within a unit elaborated in multiple partitions yields matching types. However, unlike a non-distributed system, a normal package does not have a consistent state across all partitions. For example, the package CALENDAR does not synchronize the value returned by the CLOCK function among all partitions that include the package. There is no requirement for an active partition to include a main subprogram. A partition that provides a set of services for the distributed system requires no main subprogram; the partition only executes as a result of a remote subprogram call for a particular service. The following packages are an example of a distributed system that illustrate the post-compilation partitioning approach. In this particular example, the system uses mailboxes to exchange data among its partitions and each partition may call upon the services of another partition through an RCI package. The mailboxes for each partition are allocated in a shared passive package which is assigned to a passive partition. Consequently, no remote access is required to exchange data. However, to use the services of another partition, a remote subprogram call is required. package MBX_PKG is pragma PURE; type MSG_TYPE is ... type MSG_ARRAY is array(POSITIVE range <>) of MSG_TYPE; subtype KEY_TYPE is INT_CLASS; protected type SAFE_MBX(LOCK : KEY_TYPE; SIZE : POSITIVE) is procedure POST (NOTE : in MSG_TYPE); -- Post a note in the mailbox procedure READ (LOCK : in KEY_TYPE; NOTE : out MSG_TYPE); -- Read a note from the mailbox if caller has key record KEY : constant KEY_TYPE := LOCK; MBX : MSG_ARRAY(1..SIZE); end SAFE_MBX; end MBX_PKG; with MBX_PKG; package PTR_MBX_PKG is pragma SHARED_PASSIVE; type PTR_SAFE_MBX is access all MBX_PKG.SAFE_MBX; -- All mailboxes are allocated in a passive partition and -- therefore remote access is not required. end PTR_MBX_PKG; with PTR_MBX_PKG; generic MBX_SIZE : POSITIVE; PTN_LOCK : INT_CLASS; package GEN_MBX_PKG is -- This package creates a mailbox and makes the -- access value designating it available through -- a remote subprogram call. pragma REMOTE_CALL_INTERFACE; function USE_MBX return PTR_MBX_PKG.PTR_SAFE_MBX; end GEN_MBX_PKG; with MBX_PKG; package body GEN_MBX_PKG is NEW_MBX : PTR_MBX_PKG.PTR_SAFE_MBX := new MBX_PKG.SAFE_MBX (PTN_LOCK, MBX_SIZE); -- A mailbox is created in the passive partition. -- The key to read from the mailbox is the elaborating -- partition's identity. function USE_MBX return PTR_MBX_PKG.PTR_SAFE_MBX is -- The access value designating the created mailbox is -- made available to the calling unit. begin return NEW_MBX; end USE_MBX; end GEN_MBX_PKG; with PTR_MBX_PKG, GEN_MBX_PKG; package USE_CLOSURE_1 is -- This package is the interface to a set of packages that -- is conveniently identified by the library unit CLOSURE_1. pragma REMOTE_CALL_INTERFACE; package USE_MBX_PKG is new GEN_MBX_PKG (1_000, USE_CLOSURE_1'PARTITION_ID); function USE_MBX return PTR_MBX_PKG.PTR_SAFE_MBX renames USE_MBX_PKG.USE_MBX; -- All partitions include this remote subprogram in -- their interface. procedure GIZMO ...; -- The specific remote subprograms offered by the partition -- to which this package is assigned. ... end USE_CLOSURE_1; with PTR_MBX_PKG, GEN_MBX_PKG; package USE_CLOSURE_2 is -- This package is the interface to a set of packages that -- is conveniently identified by the library unit CLOSURE_2. pragma REMOTE_CALL_INTERFACE; ... end USE_CLOSURE_2; with PTR_MBX_PKG, GEN_MBX_PKG; package USE_CLOSURE_3 is -- This package is the interface to a set of packages that -- is conveniently identified by the library unit CLOSURE_3. pragma REMOTE_CALL_INTERFACE; ... end USE_CLOSURE_3; with CLOSURE_1; -- Names packages that execute locally. with USE_CLOSURE_2, USE_CLOSURE_3; -- Names RCI packages for interfacing to other -- closures executing at different sites. package body USE_CLOSURE_1 is ... -- Obtain access values to other partition mailboxes. -- For example: MBX_2 := USE_CLOSURE_2.USE_MBX; MBX_3 := USE_CLOSURE_3.USE_MBX; ... end USE_CLOSURE_1; with CLOSURE_2; -- Names packages that execute locally. with USE_CLOSURE_1, USE_CLOSURE_3; -- Names RCI packages for interfacing to other -- closures executing at different sites. package body USE_CLOSURE_2 is ... -- Obtain access values to other partition mailboxes. -- For example: MBX_1 := USE_CLOSURE_1.USE_MBX; MBX_3 := USE_CLOSURE_3.USE_MBX; ... end USE_CLOSURE_2; with CLOSURE_3; -- Names packages that execute locally. with USE_CLOSURE_1, USE_CLOSURE_2; -- Names RCI packages for interfacing to other -- closures executing at different sites. package body USE_CLOSURE_3 is ... -- Obtain access values to other partition mailboxes. -- For example: MBX_1 := USE_CLOSURE_1.USE_MBX; MBX_2 := USE_CLOSURE_2.USE_MBX; ... end USE_CLOSURE_3; The following post-compilation partitioning support is user-provided; the syntax is only illustrative. Several possible combinations for partitioning are presented. In each combination, the first partition specified is a passive partition where the mailboxes are allocated. This partition is accessible to other partitions by simply calling the protected operations of the mailbox. The minimally distributed partitioning comprises two partitions; a passive partition and one active partition. All remote call interface packages in the application closures are assigned to a single active partition. There would be no remote calls executed as a result of this partitioning. PARTITION (PTN => 0, ASSIGN => (PTR_MBX_PKG)) PARTITION (PTN => 1, ASSIGN => (USE_CLOSURE_1, USE_CLOSURE_2, USE_CLOSURE_3)) A more distributed partitioning comprises three partitions. The remote call interface packages in the application closures are assigned to two active partitions. PARTITION (PTN => 0, ASSIGN => (PTR_MBX_PKG)) PARTITION (PTN => 1, ASSIGN => (USE_CLOSURE_1)) PARTITION (PTN => 2, ASSIGN => (USE_CLOSURE_2, USE_CLOSURE_3)) A fully distributed partitioning comprises four partitions. The remote call interface packages in the application closures are assigned to three active partitions. PARTITION (PTN => 0, ASSIGN => (PTR_MBX_PKG)) PARTITION (PTN => 1, ASSIGN => (USE_CLOSURE_1)) PARTITION (PTN => 2, ASSIGN => (USE_CLOSURE_2)) PARTITION (PTN => 3, ASSIGN => (USE_CLOSURE_3)) S.I.6. Dynamic Binding Between Partitions To support distributed systems that are fault-tolerant requires a capability to reconfigure partitions. This has motivated two forms of dynamic binding consistent with capabilities available for a non-distributed system. One form is based upon access-to-subprogram types, another is based upon objects of access-to-tagged types. Using these forms of dynamic binding relaxes the restriction that for a partition to call a subprogram of an RCI package assigned in a different partition, the containing RCI package must be referenced by the calling partition. A remote access value designating a subprogram allows naming the subprogram indirectly when executing a remote subprogram call. This relaxation provides the necessary flexibility for a distributed system to adapt to changes in communications topology and partition failures. The calling partition need only include the RCI package containing the access-to-subprogram type in its closure to call conforming subprograms in RCI packages assigned to different partitions. By restricting the remote access value to point to subprograms declared in an RCI package, the appropriate stubs for the designated subprogram are guaranteed to exist already. The remote access-to-class-wide-type provides a more sophisticated dynamic binding capability. Using objects of access-to-tagged-types facilitates the encapsulation of complete dynamic services (data and operations). By restricting dereference of such access values to occur as part of a dispatching operation, there is no need to deal with remote addresses elsewhere. The existing model for dispatching operations corresponds quite closely to the dispatching model proposed for the linker-provided ``message receiver'' procedure suggested in I.9. It is therefore relatively straightforward to substitute tag-based dispatching in place of package/subprogram-id dispatching for remote calls. These dynamic binding capabilities are enhanced further when combined with a name server partition. Typically, the name server partition provides a central repository of remote access values. When a remote access value is made available to a client partition, the value can be dereferenced to execute a remote subprogram call. This avoids a link-time dependency on the requested service and achieves the dynamic binding typical of a client/server paradigm. The access-to-tagged type binding extends the dispatching model semantics to include remote calls. Remote primitive operations are executed by dereferencing a controlling operand of an access-to-tagged type, declared in an RCI package, as a consequence of calling a dispatching operation. Similar to access-to-subprogram type binding, the calling partition need only include the RCI package containing the access-to-tagged type in its closure to dispatch to subprograms (primitive operations) in RCI packages assigned to different partitions. The following library units illustrate the use of access-to-tagged types to implement a simple distributed system. The system comprises multiple client partitions, instantiations of CLIENT_PTN, a mailbox server partition named MBOX_SERVER_PTN, and two partitions to access local and wide-area network mailboxes named LAN_MBOX_PTN and WAN_MBOX_PTN respectively. A client partition may communicate with other partitions in the distributed system through a mailbox that it is assigned by the mailbox server. It may post a message to its mailbox for delivery to another partition (based on the address in the message), or wait for a message to be delivered to its mailbox. A client may be connected either to the LAN or the WAN, but this is transparent to the application. package MBOX_PKG is pragma PURE; type MAIL_TYPE is ... type MBOX_TYPE is tagged limited private; procedure POST (MAIL : in MAIL_TYPE; MBOX : access MBOX_TYPE) is <>; procedure WAIT (MAIL : out MAIL_TYPE; MBOX : access MBOX_TYPE) is <>; private type MBOX_TYPE is tagged record null; end record; end MBOX_PKG; with MBOX_PKG; use MBOX_PKG; package MBOX_SERVER_PTN is pragma REMOTE_CALL_INTERFACE; type PTR_MBOX_TYPE is access all MBOX_TYPE'CLASS; function RMT_MBOX return PTR_MBOX_TYPE; end MBOX_SERVER_PTN; with MBOX_SERVER_PTN; package LAN_MBOX_PTN is pragma REMOTE_CALL_INTERFACE; function NEW_MBOX return MBOX_SERVER_PTN.PTR_MBOX_TYPE; end LAN_MBOX_PTN; with MBOX_SERVER_PTN; package WAN_MBOX_PTN is pragma REMOTE_CALL_INTERFACE; function NEW_MBOX return MBOX_SERVER_PTN.PTR_MBOX_TYPE; end WAN_MBOX_PTN; with MBOX_PKG; package body LAN_MBOX_PTN is type LAN_MBOX_TYPE is new MBOX_PKG.MBOX_TYPE with ...; type PTR_LAN_MBOX_TYPE is access LAN_MBOX_TYPE; procedure POST (MAIL : in MAIL_TYPE; MBOX : access LAN_MBOX_TYPE) is separate; procedure WAIT (MAIL : out MAIL_TYPE; MBOX : access LAN_MBOX_TYPE) is separate; function NEW_MBOX return PTR_MBOX_TYPE is begin return new LAN_MBOX_TYPE; end NEW_MBOX; end LAN_MBOX_PTN; with MBOX_PKG; package body WAN_MBOX_PTN is ... with LAN_MBOX_PTN, WAN_MBOX_PTN; package body MBOX_SERVER_PTN is function RMT_MBOX return PTR_MBOX_TYPE is begin if ... then return LAN_MBOX_PTN.NEW_MBOX; elsif ... then return WAN_MBOX_PTN.NEW_MBOX; else return null; end if; end RMT_MBOX; end MBOX_SERVER_PTN; -- The client partitions do not need to with the specific -- LAN/WAN mbox interface packages. with MBOX_PKG, MBOX_SERVER_PTN, ... use MBOX_PKG, MBOX_SERVER_PTN, ... generic ... procedure CLIENT_PTN is SOME_MAIL : MAIL_TYPE; THIS_MBOX : PTR_MBOX_TYPE := RMT_MBOX; -- Get a mailbox pointer for this partition begin ... POST(SOME_MAIL, THIS_MBOX); -- Dereferencing controlling operand THIS_MBOX -- causes remote call as part of POST's dispatching ... WAIT(SOME_MAIL, THIS_MBOX); ... end CLIENT_PTN; procedure CLIENT_PTN_1 is new CLIENT_PTN .. ... procedure CLIENT_PTN_N is new CLIENT_PTN .. -- Post-compilation partitioning PARTITION (PTN => 0, ASSIGN => (MBOX_SERVER_PTN)) PARTITION (PTN => 1, ASSIGN => (LAN_MBOX_PTN)) PARTITION (PTN => 2, ASSIGN => (WAN_MBOX_PTN)) PARTITION (PTN => 3, ASSIGN => (CLIENT_PTN_1)) ... PARTITION (PTN => N, ASSIGN => (CLIENT_PTN_N)) Here is an example illustrating the use of dynamic binding between partitions. This example is inspired by a comment by Brian Dobbing of Alsys, UK. In this example, there is one controlling partition, and some number of worker partitions, in a pipeline configuration. The controller sends a job out to a worker partition, and the worker chooses either to perform the job, or if too busy, to pass it on to another worker partition. The results are returned back through the same chain of workers through which the original job was passed. Here is a diagram for the flow of messages: JOB JOB JOB JOB CONTROLLER ----> W1 ---> W2 ---> W3 ---> W4 ... <--- <--- <--- <--- RSLT RSLT RSLT RSLT During the elaboration of each worker, it registers itself with the controller, and determines the worker (if any) to which it is to hand off work when too busy to handle it itself. When it receives work from some other worker, it also receives a "return" address where it should return results. The workers are defined as instances of a generic RCI package. package CONTROLLER is pragma REMOTE_CALL_INTERFACE; type JOB_TYPE is ...; -- Representation of Job to be done type RESULT_TYPE is ...; -- Representation of results type RETURN_ADDRESS is access procedure(RSLT : RESULT_TYPE); -- Return address for sending back results type WORKER_PTR is access procedure(JOB : JOB_TYPE; RET : RETURN_ADDRESS); -- Pointer to next worker in chain procedure REGISTER_WORKER(PTR : WORKER_PTR; NEXT : out WORKER_PTR); -- This procedure is called during elaboration -- to register a worker. It receives back -- a pointer to the next worker in the chain. procedure GIVE_RESULTS(RSLT : RESULT_TYPE); -- This is the controller procedure which ultimately -- receives the result from a worker. end CONTROLLER; with CONTROLLER; use CONTROLLER; generic -- Instantiated once for each worker package WORKER is pragma REMOTE_CALL_INTERFACE; procedure RECEIVE_WORK(JOB : JOB_TYPE; RET : RETURN_ADDRESS); -- This procedure receives work from the controller or -- some other worker in the chain procedure PASS_BACK_RESULTS(RSLT : RESULT_TYPE); -- This procedure passes results back to the worker in the -- chain from which the most recent job was received. end WORKER; package body WORKER is NEXT_WORKER : WORKER_PTR; -- Pointer to next worker in chain, if any PREVIOUS_WORKER : RETURN_ADDRESS; -- Pointer to worker/controller who sent us a job most recently procedure RECEIVE_WORK(JOB : JOB_TYPE; RET : RETURN_ADDRESS) is -- This procedure receives work from the controller or -- some other worker in the chain begin PREVIOUS_WORKER := RET; -- Record return address for returning results if This Worker Too Busy and then NEXT_WORKER /= null then -- Forward job to next worker, if any, if -- this worker is too busy NEXT_WORKER(JOB, PASS_BACK_RESULTS'ACCESS); -- Include this worker's pass-back-results procedure -- as the return address else declare RSLT : RESULT_TYPE; -- The results to be produced begin Do The Work(JOB, RSLT); PREVIOUS_WORKER(RSLT); end; end if; end RECEIVE_WORK; procedure PASS_BACK_RESULTS(RSLT : RESULT_TYPE) is -- This procedure passes results back to the worker in the -- chain from which the most recent job was received. begin -- Just pass the results on... PREVIOUS_WORKER(RSLT); end PASS_BACK_RESULTS; begin -- Register this worker with the controller -- and obtain pointer to next worker in chain, if any CONTROLLER.REGISTER_WORKER( RECEIVE_WORK'ACCESS, NEXT_WORKER); end WORKER; -- Create multiple worker packages package W1_RCI is new WORKER; ... package W9_RCI is new WORKER; -- Post-Compilation Partitioning -- Create multiple worker partitions PARTITION (PTN => 1, ASSIGN => (W1_RCI)) ... PARTITION (PTN => 9, ASSIGN => (W9_RCI)) -- create controller partition PARTITION (PTN => 0, ASSIGN => (CONTROLLER)) The second solution uses (remote) access-to-tagged types to provide the dynamic binding between partitions. A root tagged type is declared in a pure package COMMON. Two derivatives of this are created, one to represent the controller (CONTROLLER_TYPE), and one to represent a worker (REAL_WORKER). One object of CONTROLLER_TYPE is created, which will be designated by the return address sent to the first worker with a job. An object for each worker of the REAL_WORKER type is created, via a generic instantiation of the ONE_WORKER generic. All of the global data associated with a single worker is encapsulated in the REAL_WORKER type. The dispatching operations DO_JOB and PASS_BACK_RESULTS use the pointer to the REAL_WORKER (the formal parameter W) to gain access to this worker-specific ``global'' data. The access type WORKER_PTR is used to designate a worker or a controller, and can be passed between partitions because it is a remote access type. Normal access types cannot be passed between partitions, since they generally contain partition-relative addresses. package COMMON is -- This pure package defines the root tagged type -- used to represent a worker (and a controller) pragma PURE; type JOB_TYPE is ...; -- Representation of Job to be done type RESULT_TYPE is ...; -- Representation of results type WORKER_TYPE is tagged limited private; -- Representation of a worker, or the controller procedure DO_JOB(W : access WORKER_TYPE; JOB : JOB_TYPE; RET : access WORKER_TYPE'CLASS) is <>; -- Dispatching operation to do a job -- RET may point to the controller procedure PASS_BACK_RESULTS(W : access WORKER_TYPE; RSLT : RESULT_TYPE) is <>; -- Dispatching operation to pass back results end COMMON; with COMMON; use COMMON; package CONTROLLER is pragma REMOTE_CALL_INTERFACE; type WORKER_PTR is access all COMMON.WORKER_TYPE; -- Remote access to a worker procedure REGISTER_WORKER(PTR : WORKER_PTR; NEXT : out WORKER_PTR); -- This procedure is called during elaboration -- to register a worker. It receives back -- a pointer to the next worker in the chain. end CONTROLLER; package body CONTROLLER is FIRST_WORKER : WORKER_PTR := null; -- Current first worker in chain type CONTROLLER_TYPE is new COMMON.WORKER_TYPE; -- A controller is a special kind of worker. -- It can receive results, but is never given a job THE_CONTROLLER : CONTROLLER_TYPE; -- The tagged object representing the controller CONTROLLER_IS_NOT_A_WORKER : exception; procedure DO_JOB(W : access CONTROLLER_TYPE; JOB : JOB_TYPE; RET : access WORKER_TYPE'CLASS) is -- Dispatching operation to do a job begin raise CONTROLLER_IS_NOT_A_WORKER; -- Controller never works end DO_JOB; procedure PASS_BACK_RESULTS(W : access CONTROLLER_TYPE; RSLT : RESULT_TYPE) is -- Dispatching operation to receive results begin Do Something With Result(RSLT); end PASS_BACK_RESULTS; procedure REGISTER_WORKER(PTR : WORKER_PTR; NEXT : out WORKER_PTR) is -- This procedure is called during elaboration -- to register a worker. It receives back -- a pointer to the next worker in the chain. begin -- Link this worker into front of chain NEXT := FIRST_WORKER; FIRST_WORKER := PTR; end REGISTER_WORKER; end CONTROLLER; with COMMON; use COMMON; with CONTROLLER; user CONTROLLER; package WORKER_PKG is -- This package defines the REAL_WORKER type -- whose dispatching operations do all the -- "real" work of the system. -- Note: This package has no global data; -- All data is encapsulated in the REAL_WORKER type. type REAL_WORKER is new COMMON.WORKER_TYPE with record NEXT : WORKER_PTR; -- Pointer to next worker in chain, if any PREVIOUS : WORKER_PTR; -- Pointer to worker/controller who sent -- us a job most recently . . . -- other data associated with a worker end record; procedure DO_JOB(W : access REAL_WORKER; JOB : JOB_TYPE; RET : access WORKER_TYPE'CLASS); -- Dispatching operation to do a job procedure PASS_BACK_RESULTS(W : access REAL_WORKER; RSLT : RESULT_TYPE); -- Dispatching operation to pass back results end WORKER; package body WORKER_PKG is procedure DO_JOB(W : access REAL_WORKER; JOB : JOB_TYPE; RET : access WORKER_TYPE'CLASS) is -- Dispatching operation to do a job. -- This procedure receives work from the controller or -- some other worker in the chain. begin W.PREVIOUS := WORKER_PTR(RET); -- Record return address for returning results if W.This_Worker_Too_Busy and then W.NEXT /= null then -- Forward job to next worker, if any, if -- this worker is too busy COMMON.DO_JOB(W.NEXT, JOB, W); -- Include a pointer to this worker -- as the return address. else declare RSLT : RESULT_TYPE; -- The results to be produced begin Do The Work(JOB, RSLT); COMMON.PASS_BACK_RESULTS(W.PREVIOUS, RSLT); end; end if; end RECEIVE_WORK; procedure PASS_BACK_RESULTS(W : access REAL_WORKER; RSLT : RESULT_TYPE) is -- Dispatching operation to pass back results -- This procedure passes results back to the worker in the -- chain from which the most recent job was received. begin -- Pass the results to previous worker COMMON.PASS_BACK_RESULTS(W.PREVIOUS, RSLT); end PASS_BACK_RESULTS; end WORKER; generic -- Instantiated once for each worker package ONE_WORKER is pragma REMOTE_CALL_INTERFACE; end ONE_WORKER; with WORKER_PKG; with CONTROLLER: package body ONE_WORKER is THE_WORKER : WORKER_PKG.REAL_WORKER; -- The actual worker begin -- Register this worker "object" CONTROLLER.REGISTER_WORKER( THE_WORKER'ACCESS, THE_WORKER.NEXT); end ONE_WORKER; -- Create multiple worker packages package W1_RCI is new ONE_WORKER; ... package W9_RCI is new ONE_WORKER; -- Post-Compilation Partitioning -- Create multiple worker partitions PARTITION (PTN => 1, ASSIGN => (W1_RCI)) ... PARTITION (PTN => 9, ASSIGN => (W9_RCI)) -- create controller partition PARTITION (PTN => 0, ASSIGN => (CONTROLLER)) S.I.7. Configuring and Elaborating a Distributed System In the previous examples, post-partitioning has been illustrated in terms of the library units that comprise a partition. However, the configuration of partitions to modules has been omitted since this is beyond the scope of the Annex. For example, whether partitions may share the same module is implementation-defined. The capability for a passive partition to share a module with multiple active partitions would allow a distributed system to be configured into a standard, time-sharing, multiprogramming system, but this may not be practical for all environments. The mapping of partitions to the target environment must be consistent with the call and data references to RCI and shared passive packages respectively. This requires that the target environment only supports the necessary communication connectivity among the modules; it does not guarantee that active partitions are elaborated in a particular order required by the calls and references. To allow partitions to elaborate independently, a remote subprogram call is held until the receiving partition has completed its elaboration. If cyclic elaboration dependencies result in a deadlock condition as a result of remote subprogram calls, the exception PROGRAM_ERROR may be raised in one or both partitions upon detection of the deadlock. The predefined exception COMMUNICATION_ERROR is provided to allow calling partitions to implement a means for continuing execution whenever a receiving partition becomes inaccessible. For example, when the receiving partition fails to elaborate, this exception is raised in all partitions that have outstanding remote calls to this partition. To maintain interface consistency within a distributed system, the same version of an RCI or a shared passive package specification must be used in all elaborations of partitions that reference the same package. The consistency check cannot happen before the configuration step. (The detection of unit inconsistency, achievable when linking a single Ada program, cannot be guaranteed for the case of a distributed system.) It is implementation-defined how this check is accomplished, although the Annex requires that at least one partition, where the inconsistency occurs, becomes inaccessible. In addition to the partition termination rules, the Annex permits an implementation-defined capability for one partition to explicitly terminate (abort) another partition; the PARTITION_ID value may be used to identify the partition to be aborted. If a partition is aborted while executing a subprogram called by another partition, COMMUNICATION_ERROR is raised in the calling partition since the receiving partition is no longer accessible. S.I.8. User-Provided Communication Subsystem The user-provided communication subsystem (UPCS) is notionally compatible with the proposed Communications Model specified by the in-progress recommendations of the OSI RPC WG. The Annex requires that, as a minimum capability, the UPCS must implement the standard package COMMUNICATION_SUPPORT to support remote subprogram calls. Standardizing the interface between the generated stubs for remote subprogram calls and the message-passing layer of the target communications software (the COMMUNICATION_SUPPORT package) facilitates a balanced approach for separating the implementation responsibilities of supporting distributed systems across different target environments. S.I.8.1. Package COMMUNICATION_SUPPORT This package specifies the standard interface necessary to implement stubs at both the calling and receiving partitions for a remote subprogram call. The interface specifies both the actual operations and the semantic conditions under which they are to be used by the stubs. It is also adaptable to different target environments. Additional non-standard interfaces may be specified by the UPCS as child packages of COMMUNICATION_SUPPORT. For example, a simple message passing capability, PARTITION_IO, may be specified for exchanging objects of type MESSAGE_STREAM_TYPE. The package specifies the primitive operations for ROOT_STREAM_TYPE to marshal and unmarshal message data by using the attributes READ and WRITE within the stubs. This allows an implementation to define the format of messages to be compatible with whatever message-passing capability between partitions is available from the target communication software. The routing of parameters to a remote subprogram is supported by three implementation-defined types that identify the partition, package, and subprogram. These are integer types and they may be used to construct remote access types. The attribute PARTITION_ID of each RCI package returns an integer value which identifies the partition to which the package is assigned by the post-compilation partitioning. Similarly, PACKAGE_ID identifies an RCI package, and the SUBPROGRAM_ID identifies a subprogram within such an RCI package. Two procedures are specified to support the generation of stubs for the synchronous and asynchronous forms of remote subprogram call. Each procedure specifies the partition, package, and subprogram that is the target of the call and the appropriate message data to be delivered. For the synchronous form, the result data to be received upon the completion the call is specified. As a consequence, the task originating the remote subprogram call is suspended to await the receipt of this data. In contrast, the asynchronous form does not suspend the originating task to await the receipt of the result data. To facilitate the routing of remote calls in the receiving partition, a procedure is specified to establish the interface for receiving a message and for dispatching to the appropriate RCI package and subprogram. The interface is standardized through the parameters specified for an access-to-subprogram type that designates an implementation-provided message receiver procedure. In this way, post-compilation support can link the necessary units and data to the message receiver procedure. Once the message receiver procedure has been elaborated, it may be called by the UPCS. A single exception COMMUNICATION_ERROR is specified to report error conditions detected by the UPCS. Detailed information on the precise condition may be provided through the function EXCEPTION_INFORMATION. These conditions are necessarily implementation-defined, and therefore, inappropriate for inclusion in the specification as exception names. S.I.9. The Interaction Between the Stubs and the UPCS The execution environment of the UPCS is defined as an Ada environment. This is done in order to provide a full Ada environment to serving partitions. (However, the exact semantics of certain operations which depend on the CURRENT_TASK, see G.8 are not specified here, but rather by the UPCS (which is assumed to be user code). In the calling and receiving partitions, the canonical implementation relies upon the Ada concurrency model to service stubs. For example, in the calling partition, cancellation of a synchronous remote call, when the calling task has been aborted, requires that the UPCS interrupt the send operation to execute the cancellation. In the receiving partition, the stub for a remote subprogram is assumed to be called by the message receiver procedure executing by a task dependent upon the UPCS. S.J. Information Systems The Information Systems (IS) Annex was designed in response to Section 10 of the Ada 9X Requirements [DoD 90] (Requirements for Information Systems), which is included for ease of reference in section S.J.5 below. These requirements dictated the principal areas of concern of the IS Annex: - Decimal Arithmetic and Representation - Character Set Definition and Manipulation - String Handling - Interface Facilities The current version of the Rationale discusses the main alternatives considered for the decimal area and the reasons for the design decisions taken. The other topics will be addressed in a subsequent version. The Information Systems Annex Rationale consists of two parts. The first part is organized in parallel with the sections of the IS Annex itself and describes the reasons for specific decisions. The second part describes the general set of issues relating to decimal arithmetic, the principal alternatives considered and their tradeoffs, and the reasons for the choice of decimal model. Part One: Section-by-Section Rationale S.J.1. Decimal Arithmetic and Representation S.J.1.1. Decimal Types There are two principal techniques for realizing decimal arithmetic. One is through a package, possibly generic, that provides appropriate type(s) and operations. The simplest package solution declares a private discriminated type, where the discriminants correspond to precision (number of decimal digits) and scale (number of digits to the right of the decimal point). The second approach is through Ada's numeric type facility; in particular, fixed point types with smalls that are powers of 10. Financial systems using Ada 83 have adopted the package approach rather than fixed point. To be usable for decimal computation in an IS application, fixed point requires 64-bit integer arithmetic, control over truncation versus rounding on a per-operation basis, and support for SMALL representation clauses for powers of 10. Existing Ada 83 implementations, however, have not met these requirements; hence a package approach has been necessary. The principal benefits of the package solution are: - Easy separation from the rest of the language (thus easy encapsulation into an Annex) - Consistency with SQL semantics and terminology - Availability of I/O subprograms without the need for generic instantiations On the other hand there are several problems with the package approach: semantically, to obtain literals and other convenient operations intrinsic to numeric types; pragmatically, to obtain acceptable run-time performance; and stylistically, to exploit some of Ada's principal software engineering advantages (strong typing, logical range specifications). Fixed-point illustrates the opposite tradeoffs. As a category of numeric type it immediately offers literals and other required operations; herculean optimizations are not needed to obtain acceptable performance; and type differentiation is a natural style. However, fixed-point is not an especially simple part of the language, especially for programmers new to Ada. The need to perform per-type generic instantiations for common operations such as I/O is a rather heavy style and may lead to large executables in the absence of code sharing. Moreover, the original Ada 83 design saw fixed point more as a substitute for floating point in spartan target environments than as a mechanism for realizing COBOL-style arithmetic. The choice between the two alternatives was based on a refinement of the decimal requirements, the preparation and comparison of specific solutions in the two areas, and an analysis of programming examples using the two techniques. The result was a decision to use the fixed-point approach as the basis for decimal arithmetic. Details on the refined set of requirements, specific elements of the alternatives considered, and an evaluation of the approaches with respect to the requirements, appear in Part Two below. These issues are also addressed in [Brosgol 92], [Dewar 90], [Emery 92], [Wichmann 91a] and [Wichmann 91b]. S.J.1.2. Package DECIMAL Package DECIMAL comprises several implementation-defined constants as well as some types and exceptions needed for IS applications. Values of MAX_DELTA greater than 1.0 correspond to COBOL's ``assumed'' scaling ('P' in picture strings), allowing the programmer to represent large quantities without reserving storage for trailing 0's. Similarly, values of MIN_DELTA less than 10.0**(-MAX_DIGITS) allow the representation of quantities with small magnitude without reserving space for leading fractional 0's. Declaring PICTURE_STRING as a derived type (versus as a subtype of STRING) provides increased type protection and also may facilitate certain optimizations based on compile-time evaluation of PICTURE_STRINGs. The enumeration type EXTERNAL_REP is open-ended. An implementation providing an interface to a specific database system, for example, should add appropriate elements to the type so that users can write Ada programs processing the database contents. S.J.1.3. Decimal Computation -18 The capacity requirements -- MAX_DIGITS at least 18, MIN_DELTA at most 10 , and MAX_DELTA at least 1.0 -- are sufficient for IS applications (and satisfy in particular the demands of large financial systems). The attributes T'TRUNCATE(expr) and T'ROUND(expr) provide the needed control over truncation versus rounding. We also considered defining a non-biased rounding function (that is, one that rounds a midpoint value towards the closest even) but decided against this in the interest of simplicity. An implementation is permitted to provide a non-biased rounding operation, for example through a generic unit. Another alternative we considered was to define the conversion operation T(expr) for decimal types so that it performs truncation, and then just to provide a rounding function T'ROUND(expr). Benefits would include: deterministic behavior for conversion, upward compatibility, and consistency with the default that COBOL users would expect. We rejected this approach, however. Requiring T(expr) to truncate for any fixed point type T places a burden on implementations; on the other hand, requiring this behavior for decimal types only while allowing T(expr) for a (non-decimal) fixed point type to be non-deterministic is an unwelcome difference in the default behavior for the conversion. GENERIC_DIVIDE is in response to the requirement for a division operation that delivers both a quotient and a remainder. An alternative approach was also considered; namely, to define an attribute T'DIVIDE for the decimal type T serving as dividend, with this attribute defined as a procedure with parameters of arbitrary decimal type for the divisor and (as out parameters) for quotient and remainder. We rejected this in favor of the generic for several reasons. First, a type attribute used as a procedure would be a new facility in the language. Second, division with remainder is needed sometimes but not often. Obtaining it through explicit generic instantiation versus having it available implicitly is acceptable style and is easier to implement. S.J.1.4. Internal Decimal Representation The programmer can either let the compiler choose an internal representation for a decimal type or provide guidance in the form of an attribute definition for T'MACHINE_RADIX. Note that even where a MACHINE_RADIX is specified, the compiler has some flexibility in choosing exactly how data will be represented internally. This is consistent with the Ada design philosophy, since, in contrast with external representations, the choice of internal representations is in the compiler's rather than the programmer's domain. S.J.1.5. External Decimal Representation A difficult consideration in the design for decimal arithmetic is to select a method for modeling external data representations and conversions to/from internal computational format. Several approaches and variations were considered. The principal alternatives, with associated advantages and disadvantages: 1. Encapsulating external representations as types in a package (as done in V4.0), corresponding to COBOL ``display'' formats and other representations that need to be handled Advantages - Gives explicit support for these data formats - Establishes the interpretation of a data item (zoned leading separate, e.g.) as part of the item's declaration - Data validation function easily provided for each type Disadvantages - Complexity - Needs special optimizations of discriminant storage for efficiency (if types are private), or yields anomalies on assignment (if array types are used) - Needs generic instantiation to convert from external representation to computational type, or else a named type for root_decimal'CLASS 2. Supplying representation clauses corresponding to the various external representations; the programmer derives a new type from a decimal type T and applies the relevant representation clause to the derived type, and can use type conversion to convert between internal and external formats Advantages - Easy for programmer, since conversion is performed automatically and without need for generic instantiation - Automatically obtains arithmetic for derived type with display representation, similar to COBOL - EDIT for edited output of external data is automatically available Disadvantages - Difficult to implement; worse, it adds complexity to the machine-dependent part of the compiler and not just to the front end - Implementation needs to allow a type with such a representation clause to be passed as an actual to a generic that takes a decimal or fixed-point type - It provides functionality (arithmetic on external representations) that goes beyond what is required 3. Treating external data as arrays of bytes (or perhaps as arrays of 4- bit ``nibbles'' in the case of packed decimal); the programmer specifies the format as a parameter to conversion functions T'INPUT and T'OUTPUT that translate between external and internal representation, and performs data validity checking through a function T'VALID Advantages - Simplifies the IS Annex by avoiding the need for a complicated package - Readable programming style - T'VALID is a convenient way to obtain data validity checking Disadvantages - Allows potentially error-prone flexibility, since the same external data item (typically a record field) can be interpreted as having different formats - Generality of formats as run-time parameters needs special treatment for optimized code generation - Implicitly provided attribute functions depend on package DECIMAL The decision to adopt alternative (3) was based on several considerations. It is simpler to implement than (2), which is important since we want to encourage compilers to support a wide variety of external data formats. And in comparison with (1), alternative (3) is simpler to present and to use. Edited Output. Several design issues relate to the topic of edited output. One is the way in which the programmer obtains this capability for a decimal type: the principal alternatives are to have this occur automatically (through the implicit provision of an attribute function) or to require an explicit generic instantiation. As was observed earlier, the choice to realize edited output through attributes was based on the need for a straightforward and lightweight notation. Two versions of T'EDIT are defined: one that converts from internal representation, and another that converts from external representation. In the latter case, the particular format is specified as a parameter. The reason for providing the version that converts from external format is that it is often necessary to produce edited output for input data that will not be used computationally. It would be clumsy and inefficient to have to convert first from external to internal format, then convert from internal to edited output format. Another issue is the relationship between the contents of the PICTURE string and the edited output result. The complication is that there is the need for: 1. Programmer control over the contents of the result string (for example, choice of currency symbol and selection of decimal point and thousands separation characters) 2. Intuitive conventions for PICTURE string contents 3. Optimizability of the code sequences for edited output These goals conflict. An approach with maximum flexibility is to allow the programmer to choose conventions for ``configurable'' symbols (currency, decimal point, digits group separator) via a run-time procedure call, and for the choice to be reflected both in the interpretation of the PICTURE string and the contents of the result string. This achieves (1) and (2) but will require run-time interpretation of PICTURE strings. An alternative approach is to have the programmer establish these conventions at compile time through a pragma. It makes no sense for different compilation units in the same program to have different conventions, hence a configuration pragma is the appropriate mechanism (see MS-2). This satisfies all three goals, but at a sacrifice in flexibility (the output string conventions can't be modified at run time) and also at the potential complication of mixing run-time concerns (output string conventions) with compile-time issues (validity of picture strings). We have decided to provide both alternatives, one required and the other optional. The required support from IS-compliant implementations is to handle the dynamic case, through subprograms declared in package LOCALE. These subprograms only affect the output string contents. The PICTURE string characters are intended to be uniform, with the Annex defining standard ``vanilla'' values for the currency symbol ('#'), decimal point ('^'), and digits-group separator ('_'), along with the default correspondence between these characters and the edited output result. The IS Annex also supplies a configuration pragma PICTURE for optimization purposes and for locale-wide setting of this correspondence. Support for this pragma is optional. S.J.1.6. Locale-Specific Characteristics To be discussed in a future version of the Rationale. S.J.2. Character Set Definition and Manipulation S.J.2.1. CHARACTER and WIDE_CHARACTER Handling Package To be discussed in a future version of the Rationale. S.J.2.2. ISO 8859 Coded Character Set Packages To be discussed in a future version of the Rationale. S.J.2.3. Other Character Packages To be discussed in a future version of the Rationale. S.J.3. String Handling To be discussed in a future version of the Rationale. S.J.4. Interface Facilities S.J.4.1. Pragma LAYOUT To be discussed in a future version of the Rationale. S.J.4.2. C_TYPES Package To be discussed in a future version of the Rationale. S.J.4.3. Compliance Requirements To be discussed in a future version of the Rationale. S.J.4.4. Other Interfacing Facilities To be discussed in a future version of the Rationale. S.J.4.5. Relationship with other Annexes To be discussed in a future version of the Rationale. Part Two: Rationale for the Choice of Models for Decimal Arithmetic S.J.5. Ada 9X Requirements for Information Systems Section 10 of the Ada 9X Requirements document [DoD 90] specifies the following user needs, requirements, and study topics for Information Systems: 10.1 Handling Currency Quantities for Information Systems User Need U10.1-A: Support for Currency Quantities. Information systems programs that deal with currency amounts need to perform exact arithmetic, with control over rounding. At least 18 digits of decimal precision are needed. Requirement R10.1-A(1) - Decimal-Based Types: Ada 9X shall support fixed point types whose value of small is a power of ten. Such types shall have at least 18 decimal digits of precision. It shall be possible to ensure that intermediate results for expressions of such types are not computed or stored with extra precision, and that rounding is under programmer control. Study Topic S10.1-A(2) - Specification of Decimal Representation: Ada 9X information systems implementations shall provide a mechanism for specifying decimal representations for decimal-based fixed point types. 10.2 Compatibility with Other Character Sets User Need U10.2-A: Alternate Character Set Support: Many information systems applications must interface smoothly with applications based on character sets other than ASCII, particularly EBCDIC. Study Topic S10.2-A(1) - Alternate Character Set Support: Ada 9X information-system implementations shall provide for the extension of the set of graphic symbols to obtain input/output facilities and IMAGE attributes for alternate character sets (specifically, for EBCDIC) comparable in both functionality and performance with the features provided for the built-in character set. 10.3 Interfacing with Data Base Systems User Need U10.3-A: Interfacing Ada Programs to DBMSs: Information systems programs written in Ada must often interface with Data Base Management Systems (DBMSs). Such DBMSs are typically designed to interface with COBOL programs. Study Topic S10.3-A(1) - Interfacing with Data Base Systems: It should be relatively easy to write Ada 9X programs that interface smoothly with DBMSs. 10.4 Common Functions User Need U10.4-A: Standard Data Manipulation: The manipulation of varying-length strings and ``picture-based'' editing of strings is commonly required in information systems applications. Study Topic S10.4-A(1) - Varying-Length String Package: Ada 9X shall provide a standard varying-length string package. Study Topic S10.4-A(2) - String Manipulation Functions: Ada 9X shall provide a standard package of general string manipulation functions such as a function for finding a pattern in a string, a conversion function, and functions for creating formatted data in the style of ``picture'' formats as specified in languages such as COBOL and PL/I. S.J.6. Detailed Requirements for Decimal Arithmetic and Representation In order to provide criteria for selecting among design alternatives, we have refined Requirement R10.1-A(1) and Study Topic S10.1-A(2) into the following set of specific requirements: S.J.6.1. Computation Literals. Real literals and, more generally, named numbers of type root_real'CLASS, need to be available for decimal types. If decimal types include binary arithmetic operators "+", "-", "*", and "/", then for each of these a value of type root_real'CLASS must be usable (without conversion) as one operand when the other operand is of a specific decimal type. Explicit Rescaling. Some means is needed for explicitly converting a decimal value to an explicit new scale and/or precision. If the new scale is smaller than the old one, the programmer must be able to control rounding versus truncation. If the value is too large to be represented with the new precision and scale, then a suitable exception is raised. If decimal is realized via a single discriminated type, then a rescaling conversion is performed during assignment, and also when passing a parameter to a formal that is constrained. Conversion to/from Integer, Floating-Point, and Fixed-Point Types. Conversions are needed in both directions. When converting to decimal, programmer control over rounding versus truncation is needed. When converting from decimal, the same rules apply as when converting from fixed point. Arithmetic Operations. Arithmetic may be obtained through operator forms or subprogram calls (or both); the requirements in this section are phrased without prejudice towards a particular approach. Unary operations: Negation and absolute value are needed. Addition and subtraction: It is necessary at least to be able to allow decimal values of the same precision and scale, the result having the same precision and scale as the parameters. More generality is allowed in COBOL through the ADD and SUBTRACT verbs, since variation in precision and/or scale is permitted. This is not essential for an Ada 9X solution. That is, we need to at least be able to add DOLLARS and DOLLARS to get a DOLLARS value. It is not as important to be able automatically to add PENNIES and MILS; an explicit rescaling conversion is an acceptable approach. Note that there is no issue of truncation or rounding of the result of "+" or "-", since the operand scales are the same. Multiplication and division: It is necessary to be able to multiply and divide decimal values that differ in scale or precision. A division operation is needed that returns a remainder as well as a quotient. The programmer must be able to specify whether the result of a multiplication or division is to be truncated versus rounded. Intermediate results: Intermediate results need to be computed with extra range but no extra accuracy. That is, if there are extra digits then supply them on the left, not on the right. Relationals and Comparisons. Equality, inequality, less than, less than or equal, greater than, and greater than or equal functions are needed. A BOOLEAN-returning operation is needed that checks if a value is within a specified range. S.J.6.2. Style Strong Typing. Exploitation of Ada's strong typing is important. That is, the user should be able to declare different types that have the same precision and scale, in order to catch at compile time an error such as attempting to assign a percentage value to a price variable. Expressions. The use of Ada's expression syntax for arithmetic expressions is preferred over COBOL-like procedure invocations. Range Constraints. The ability to associate range constraints with declared variables is highly desirable. Convenient Availability of I/O. Since I/O on decimal data will be the normal case rather than the exception for IS programs, accessing the I/O operations should not require heavy notation. That is, it is a disadvantage if the programmer needs to perform numerous generic instantiations in order to obtain I/O operations. S.J.6.3. Performance and Capacity Run-Time Efficiency. It should be possible for a good compiler to obtain run-time efficiency (both time and space) comparable to COBOL, without requiring research breakthroughs in optimization technology. Compile-Time Efficiency. Although not as critical as run-time performance, compilation performance should be reasonable. Precision. Support is needed for decimal types with at least 18 digits of declared precision. S.J.6.4. Representation Data Interoperability Formats. Ada 9X IS programs must be able to deal with files created externally. A specific requirement is to support common DISPLAY formats defined by COBOL; viz. both unsigned and signed, and, for signed, both trailing and leading, and both separate and non-separate. It is also useful to be able to control whether binary data from external files are to be interpreted in ``Big-Endian'' versus ``Little-Endian'' fashion. Packed Decimal. In many mainframe environments, support for packed decimal is needed: both as an external representation found in files and as an internal computational format used by hardware instructions. Human-Readable Reports. COBOL-style report output (so-called ``edited output'') is needed, with control over currency symbol choice and placement, suppression of leading zeroes, and so on. S.J.6.5. Other Requirements Consistency with Ada Design Philosophy. The approach to decimal arithmetic should fit cleanly with the other elements of the Ada type model (both Ada 83 and Ada 9X). Ease of Use. The transition to Ada 9X from Ada 83 should be smooth, and application programmers who are familiar with COBOL and SQL should be able to learn and use the Ada 9X decimal arithmetic approach without becoming Ada language lawyers. No Burden on Non-IS Implementations. Only IS-compliant implementations should have to pay a price for decimal features. S.J.7. Decimal Computation through a Private Discriminated Type Ada 83's fixed-point facility was designed much more as a ``poor man's floating point'' for embedded-systems applications than as a solution for exact decimal financial computation. Thus instead of trying to retrofit fixed-point to meet such needs, it is appropriate to see if an alternative mechanism using Ada's definitional features (packages, private types) will serve the purpose. Here is one possible approach. S.J.7.1. Package DECIMAL_SUPPORT The following package, a combination of the DECIMAL package from the December 1991 Mapping Specification Section G.4.1.1 (Page G-68) and a fleshed-out version of the DECIMAL_SUPPORT package outlined in G.4.1.3 (Page G-85), illustrates how decimal arithmetic can be obtained through a discriminated type: package DECIMAL_SUPPORT is MAX_PRECISION : constant := implementation-defined; -- Must be at least 18 subtype PRECISION_RANGE is POSITIVE range 1..MAX_PRECISION; MIN_SCALE : constant := implementation_defined; -- Must be at most 0 MAX_SCALE : constant := implementation_defined; -- Must be at least 18 subtype SCALE_RANGE is INTEGER range MIN_SCALE..MAX_SCALE; type DECIMAL(PRECISION : PRECISION_RANGE; SCALE : SCALE_RANGE) is private; SIZE_ERROR : exception; function "+"(RIGHT : DECIMAL) return DECIMAL; function "-"(RIGHT : DECIMAL) return DECIMAL; function "abs"(RIGHT : DECIMAL) return DECIMAL; function "+"(LEFT, RIGHT : DECIMAL) return DECIMAL; function "-"(LEFT, RIGHT : DECIMAL) return DECIMAL; function "*"(LEFT, RIGHT : DECIMAL'CLASS) return DECIMAL'CLASS; function "/"(LEFT, RIGHT : DECIMAL'CLASS) return DECIMAL'CLASS; function "*"(LEFT: DECIMAL'CLASS; RIGHT : SYSTEM.INT_CLASS) return DECIMAL'CLASS; function "*"(LEFT: SYSTEM.INT_CLASS; RIGHT : DECIMAL'CLASS) return DECIMAL'CLASS; function "/"(LEFT: DECIMAL'CLASS; RIGHT : INT_CLASS) return DECIMAL'CLASS; function "<" (LEFT, RIGHT : DECIMAL) return BOOLEAN; function "<="(LEFT, RIGHT : DECIMAL) return BOOLEAN; function ">" (LEFT, RIGHT : DECIMAL) return BOOLEAN; function ">="(LEFT, RIGHT : DECIMAL) return BOOLEAN; function IN_RANGE(ITEM, LOW, HIGH : DECIMAL'CLASS) return BOOLEAN; function NOT_IN_RANGE(ITEM, LOW, HIGH : DECIMAL'CLASS) return BOOLEAN; procedure ADD(LEFT, RIGHT : in DECIMAL; GIVING : out DECIMAL); procedure ADD_ROUNDED(LEFT, RIGHT : in DECIMAL; GIVING : out DECIMAL); procedure SUBTRACT(LEFT, RIGHT : in DECIMAL; GIVING : out DECIMAL); procedure SUBTRACT_ROUNDED(LEFT, RIGHT : in DECIMAL; GIVING : out DECIMAL); procedure MULTIPLY(LEFT, RIGHT : in DECIMAL'CLASS; GIVING : out DECIMAL); procedure MULTIPLY_ROUNDED(LEFT, RIGHT : in DECIMAL'CLASS; GIVING : out DECIMAL ); procedure DIVIDE(LEFT, RIGHT : in DECIMAL'CLASS; GIVING : out DECIMAL); procedure DIVIDE_ROUNDED(LEFT, RIGHT : in DECIMAL'CLASS; GIVING : out DECIMAL'CLASS); procedure DIVIDE(LEFT, RIGHT : in DECIMAL'CLASS; GIVING : out DECIMAL; REMAINDER : out DECIMAL'CLASS); procedure EXPONENTIATE(LEFT : in DECIMAL'CLASS; RIGHT : in INTEGER; GIVING : out DECIMAL); procedure EXPONENTIATE_ROUNDED (LEFT : in DECIMAL'CLASS; RIGHT : in INTEGER; GIVING : out DECIMAL); -- Conversions to/from STRING function IMAGE(ITEM : DECIMAL; PICTURE : STRING := ""; ROUNDED : BOOLEAN := FALSE) return STRING; -- Raises PICTURE_ERROR if PICTURE not properly formed function VALUE(ITEM : STRING) return DECIMAL; -- Raises DATA_ERROR if ITEM does not spell out a legitimate value private ... end DECIMAL_SUPPORT; Note that binary "+" and "-" have parameters and result of type DECIMAL, whereas for "*" and "/" we use DECIMAL'CLASS. The reason for this distinction is to obtain the appropriate set of operations when new types are derived from DECIMAL. For example, if type MONEY is derived from DECIMAL then adding or subtracting two MONEY values yields a MONEY result. If we want to add a MONEY value to another value from either DECIMAL or a type derived from DECIMAL, then we need to perform an explicit conversion. On the other hand, multiplication and division between values from DECIMAL or any of its derivatives is needed without explicit conversion; hence the use of DECIMAL'CLASS in "*" and "/". S.J.7.2. Semantics of Operations The type DECIMAL requires special rules in order to obtain the appropriate behavior for literals, conversions, assignment, and equality. (An earlier version of the Ada 9X mapping included a definitional facility that would have allowed users to provide these operations for private types, thus avoiding such special case rules, but this feature was removed in the interest of simplicity.) Literals. DECIMAL needs to be treated as a real numeric type with respect to literals. Thus a value of type root_real'CLASS will match DECIMAL. It will be represented at run time with up to MAX_PRECISION digits, and with an appropriate scale based on the value. Type Conversions. DECIMAL needs to be treated as a real numeric type with respect to type conversions. There is thus the question of the effect of a conversion from another numeric type when the value to be converted lies properly between two consecutive DECIMAL values. The options are: truncation, rounding, unbiased rounding (i.e., to the nearest even if the value is at the midpoint), undefined and implementation dependent, and undefined but implementation defined. A viable approach is the following: - Define the effect of the bare conversion DECIMAL(expr, precision, scale) to be implementation defined - Provide attribute functions DECIMAL'TRUNCATE(expr, precision, scale) and DECIMAL'ROUND(expr, precision, scale) for explicit truncation and rounding. - For simplicity, ignore unbiased rounding. Assignment, Comparison, and Relationals. Assignment performs rescaling (with truncation) when source and target differ in scale or precision, rather than raising CONSTRAINT_ERROR as would ordinarily be dictated because of the discriminant mismatch. To obtain rounding on assignment, the programmer will need to invoke the DECIMAL'ROUND function on the source expression. Equality, inequality, and the relationals implicitly rescale one or both operands if necessary, in order to compute an arithmetically correct result. Range Constraints. It would be desirable to be able to associate range constraints with decimal objects; however, this has unpleasant interactions with the treatment of private types as composite and the associated need for by-reference semantics in some contexts (which would require identity of ranges). The range-check functions IN_RANGE and NOT_IN_RANGE can be called explicitly for range constraint checking. Implicit rescaling of one or more of the operands may be needed for these functions. Result Scale and Precision for Arithmetic Operators. Defining overloadings for the various binary operators means that something must be said about the result scale and precision. This has been a long-standing thorny issue in programming languages, and perhaps the least-bad solution is to use the same rules as PL/I; viz.: For unary operators: result has same precision and scale as operand. For the binary operators: let P , S , P , and S be the precision left left right right and scale for the left operand and for the right operand, respectively. Let P and S be the result precision and scale. result result Let P be the maximum precision supported for intermediate results. max For binary "+" and "-": P = min(P , max(P ,P ) + 1); result max left right S = max(S , S ). result left right For "*": P = min(P , P +P ); result max left right S = S + S . result left right For "/": P = P ; result max S = P -P +S -S . result max left left right SIZE_ERROR is raised if overflow occurs in any of these operations. There is no operator form for exponentiation, since there is no way for the language rules to define result precision and scale in a meaningful way independent of the run-time value of the exponent. Generics. Although the availability of DECIMAL'CLASS as a formal parameter will make it less necessary to use generics, it is possible to pass DECIMAL to a generic with a formal parameter that is either private or new DECIMAL. S.J.7.3. Evaluation of Private Discriminated Type Approach to Decimal Computation. The requirements for literals, explicit rescaling, conversion with numeric types and arithmetic operations are all satisfied. Style. To obtain the effect of strong typing (i.e., to avoid declaring everything to be of type DECIMAL), the programmer can derive from DECIMAL. The availability of the CLASS attribute for untagged types is key to the success of this approach; otherwise the mixed-type overloadings of "*", "/", and several other subprograms would not be obtained automatically. The overloadings of the operator symbols make available the syntax for expressions, but the programmer will need to be sensitive to the rules for result scales and precisions. Range constraints are not available; the programmer will need to invoke the functions IN_RANGE or NOT_IN_RANGE explicitly. I/O can be defined in a different package, to work on DECIMAL'CLASS. This avoids the need for generic instantiations. Performance and Capacity. Compilers will have to perform several kinds of optimizations to get acceptable performance. The discriminants for DECIMAL objects need to be stored separate from the value, and the compiler will need to perform inline expansion of DECIMAL subprograms to avoid the run-time propagation of precision and scale. These optimizations are not impossible, but they do go against the grain of the natural implementation for discriminated records. By-reference passage is allowed for DECIMAL, which may be an efficiency gain in certain environments. Since generic units will probably not be heavily used with DECIMAL, compile-time performance should be better than with a fixed-point approach. The necessary precision for declared DECIMAL values is obtained. Representation. The DECIMAL_SUPPORT package is independent of the design for external representations. It is possible to define the necessary conversions between DECIMAL and the set of supported external representations. The IMAGE function handles COBOL-style edited output. Other Requirements. Although the DECIMAL_SUPPORT package exploits Ada 9X's untagged class-wide types (using the CLASS attribute), on the whole the package does not fit into the mainstream of the language design philosophy. Special rules are needed to obtain the desired arithmetic operations, but even with these rules we do not get user-specifiable and automatically-checked range constraints. On the other hand, programmers familiar with SQL or COBOL will probably be comfortable with the concept of a single DECIMAL type with per-object variability of PRECISION and SCALE. Non-IS compilers can ignore DECIMAL_SUPPORT completely. S.J.8. Using Fixed Point for Decimal Arithmetic S.J.8.1. Summary of Main Issues On the surface, the Ada 83 SMALL representation clause for fixed point seems to present a solution to the decimal arithmetic problem. That is, if we need a type corresponding to, say, the COBOL picture clause S9V99 then the following would seem to work: type EXAMPLE_TYPE is delta 0.01 range -9.99 .. 9.99; for EXAMPLE_TYPE'SMALL use 0.01; However, this is not sufficient, for several reasons. - A programmer needs to be able to control truncation versus rounding on converting to EXAMPLE_TYPE from other numeric types. Whether the Ada 83 conversion EXAMPLE_TYPE(expr) truncates versus rounds is implementation dependent. Although it is theoretically possible to have generic ROUNDING functions, with the source and target types formal generic parameters, this would be clumsy in practice. - Even with a SMALL that is a power of 10, the Ada 83 rules are biased towards binary ranges. That is, according to RM 3.5.9(6) the model numbers of EXAMPLE_TYPE are -10.23, ..., 10.23 and not just -9.99, ..., 9.99. This is significant in a case such as the evaluation of an expression (E1+E2)-E3 where E1, E2, and E3 are objects of EXAMPLE_TYPE with values 9.98, 0.03, and 0.02, respectively. By Ada 83 rules, this expression must deliver the value 9.99 with no overflow: addition is performed using the operation from EXAMPLE_TYPE'BASE, and this base type's model numbers must include those of EXAMPLE_TYPE. This means that the compiler is not free to use the obvious efficient packed decimal representation for EXAMPLE_TYPE, since with a 2-byte packed decimal the evaluation of E1+E2 will overflow. - The above situation was a problem because the type declaration imposed a wider range (of model numbers) than was desired. The opposite situation can also arise: type TENS_OF_KBYTES is delta 10.0 range 0.0 .. 640.0; for TENS_OF_KBYTES'SMALL use 10.0; The model numbers for TENS_OF_KBYTES are -630.0, -620.0, ..., 630.0. The endpoint 640.0 is specifically excluded, leading to CONSTRAINT_ERROR in the elaboration of this declaration: MEMORY_INSTALLED : TENS_OF_KBYTES := 640.0; - The fixed-point rules require conversions of literals (and named numbers) that appear as factors in multiplication or division, even though the product or quotient itself includes a conversion to a specific target type. Thus in the absence of a declaration of an applicable "*" operator, it would be illegal to write ITEM := ITEM_TYPE(1.05 * ITEM); where ITEM_TYPE is a fixed-point type. Instead, something like the following circumlocution is required ITEM := ITEM_TYPE(SOME_TYPE(1.05) * ITEM); but this still has the problem mentioned above, that the conversions are nondeterministic with respect to truncation versus rounding. - The notation is rather heavy-handed. Requiring a fixed-point type declaration and a representation clause in order to obtain a type with decimal scaling, and further requiring per-type generic instantiations to obtain common operations such as edited output, make decimal arithmetic appear like an afterthought rather than a planned capability in the language. - There is also the practical problem that very few implementations of Ada 83 support non-binary SMALLs. One of the reasons is probably the interaction of two rules: * The requirement to allow products and quotients of any two fixed-point values, including when they are from types with incompatible smalls; and * The need for a conversion of such a result to a fixed or floating point type T to be within T's smallest enclosing model interval (and, even more stringently, the need for a conversion to an integer type to perform accurate rounding). Froggatt [Froggatt 87] showed that triple precision is sufficient to handle these cases, and Hilfinger [Hilfinger 90] was able to reduce this bound to double precision plus 2 bits, but the necessary algorithms are complicated. - The division-with-remainder operation needed for financial computation is not automatically available for a fixed-point type. S.J.8.2. Alternative Fixed-Point Approaches We can solve these problems either by making adjustments to the general fixed-point model that unify decimal and non-decimal, or by introducing a new kind of fixed-point type for decimal arithmetic that is syntactically distinguished and that has specialized operations. An example of a possible revision to the fixed-point model (the ``unified fixed point approach'') is the following: - The programmer-specified delta is taken as SMALL - Through a MACHINE_RADIX attribute definition, the programmer can control the radix (and thus whether the base type's range is binary versus decimal) - Each range bound must be an exact multiple of SMALL, and the endpoints are included as model numbers - As a notational convenience, digits D is allowed as an alternative syntax for specifying a range, both for types and subtypes, and is equivalent to range (-10**D + 1)*SMALL .. (10**D - 1)*SMALL This approach would unify decimal and non-decimal fixed-point under a common syntactic and semantic framework. Since fixed-point seems not to be a heavily used part of Ada 83, and since the Ada 83 semantics that establishes a fixed-point type's default SMALL as a power of 2 is counter-intuitive, there is some justification for such a radical change. However, this would certainly not be upward-compatible. Moreover, unification of decimal and non-decimal fixed point is not necessarily desirable. The two are intended for rather different environments (financial computation versus applications needing types such as DURATION or surrogate floating point) and decimal includes specialized operations that would be burdensome to impose on all fixed-point types. As a result, we have adopted an approach that achieves decimal through a syntactically distinguished form of fixed-point type. The principal features of this solution, referred to as the ``decimal fixed point approach'' are: - Syntactically, the declaration of a decimal type includes both scale (in terms of a delta that must be a power of 10) and precision (as a number of digits)(The digits syntax was first suggested by D. Emery [Emery 91]), and optionally a range constraint. All three must be static. * The small for a decimal type is always its specified delta. * The number of digits D implicitly establishes the maximum range of values for any object of the type as -((10**D)+1)*delta .. ((10**D)-1)*delta, with both endpoints included. * The range constraint, if present, further restricts the range. - A decimal subtype indication may be obtained only through the application of a range constraint or a digits constraint (thus delta constraints are disallowed). A digits constraint retains the small from the base type and restricts the range. - A ``box'' form of decimal type can be used as a generic formal type and can be matched only by an actual that is a decimal subtype. On the other hand, an actual that is a decimal subtype will also match a generic formal fixed point type. - The implementation must allow the declaration of decimal types with at least 18 digits of declared precision. Maximum values for declared and intermediate precisions are implementation defined. - The programmer can use the T'MACHINE_RADIX attribute definition to establish whether the base type for a decimal type T has a binary versus decimal range - For a decimal type T, the function T'TRUNCATE(expr) truncates towards 0, and the function T'ROUND(expr) performs rounding (away from 0 if midway), where expr is of any numeric type including root_real'CLASS and precise_fixed. If expr is a product or quotient of fixed-point values from decimal types, then T'TRUNCATE(expr) and T'ROUND(expr) must be computed exactly. - Several other operations are defined for decimal types but not for fixed-point types in general: the attribute T'DIGITS, and the attribute functions T'VALID, T'INPUT, T'OUTPUT, and T'EDIT. S.J.8.3. Evaluation of Decimal Fixed Point Approach Computation. All of the requirements are met, a consequence of defining decimal types as specialized fixed-point types. Thus real literals and named numbers are available; rescaling between decimal types as well as conversion from other numeric types are obtained through a type conversion (for truncation) or the ROUNDED attribute; and the arithmetic operators, comparisons, relationals, and membership (in) operations are automatically available. The semantics of ``digits'' in a decimal type definition, coupled with the ability to specify 10 as MACHINE_RADIX, solve the end point anomalies mentioned in S.J.8.1 above. Both endpoints of the interval determined by the values of delta and digits are model numbers of the (base) type. And the specification of a decimal radix removes the bias towards binary ranges. Style. Since each decimal type is a new fixed point type, strong typing is directly obtained. It is also possible, in those situations where new types are not needed, to define constrained subtypes of a decimal type through range constraints. Thus the programmer can choose a style appropriate to the application. Expressions are available, with the proviso that "*", "/", and "**" require explicit conversion of the result. Range constraints are available as part of the declaration of a type, subtype, or object. I/O is not directly available; as with other numeric types, a generic instantiation per decimal type is needed. However, the availability of EDIT for each decimal type means that generic instantiation is not required for edited output. Similarly, conversion between external and internal representations is eased by the automatic availability of INPUT and OUTPUT functions. A possible drawback to the delta ... digits... syntax is that it seems to suggest a combination of fixed and floating point. Moreover, supplying a digits constraint on a subtype could potentially be confusing as to whether the range is being constrained or the accuracy is being reduced (made less fine). Programmers will need to remember that digits in a decimal type definition or as a decimal constraint always is equivalent to a range constraint. Performance and Capacity. Since the digits clause in a decimal fixed-point type definition establishes an exact range, a range check is needed on an assignment like X := Y+Z; (as opposed to having an overflow generated for an arithmetic operation) when a binary radix is used. This can be avoided (with some sacrifice in portability) by declaring variables to have the base type T'BASE rather than just T, where T is a decimal type. Subtype space optimizations will be useful and in fact necessary. For example, a decimal type MONEY may allow 18 digits, but particular variables of type MONEY may be declared with smaller ranges and thus the implementation should exploit this in choosing a representation. In particular, when packed decimal is used, the space reserved for an object should be only what is required for its declared range. Inline expansions of arithmetic operations should be performed. These optimizations are within the realm of current compiler technology. Compilation performance will be affected principally by the number of generic instantiations. The compiler will be slower under the decimal type approach than with a single discriminated type DECIMAL. However, the difference might not be noticeable for large programs since there does not seem to be the need for many different decimal types even in sizable applications. The requirement for sufficient declared precision is satisfied by fiat. Representations. The provision of decimal fixed-point types responds to the computational requirements for exact decimal arithmetic and is independent of the issue of external representations. Edited output is obtained conveniently through the EDIT attribute. This avoids generic instantiations, but there is at least one issue worth mentioning. Every decimal type will have EDIT declared, whether or not it is needed. Thus an application that does not require edited output for a particular decimal type may still incur a compilation overhead and perhaps even a code space overhead for this feature. Other Requirements. Decimal fixed point types fit fairly smoothly into Ada's overall type model. Those computational requirements for decimal arithmetic not already satisfied by Ada 83's fixed point facilities can be met through relatively minor and encapsulatable enhancements. Ease of use is a subjective issue; programmers will find decimal types as easy or difficult as other fixed point types. This means that IS Ada programmers will have to learn a part of the language that can be ignored for most other applications, a potential drawback since few Ada 83 textbooks cover fixed point to any depth. Also, unlike the situation with other numeric types where a novice programmer can simply use predefined types like INTEGER and FLOAT, with fixed point there is no good way to avoid user-declared types. Effective training specialized for COBOL professionals will probably be needed. The effect on non-IS implementations is intended to be small, but since at least the new syntax for decimal types and formal generic parameters needs to be supported there will be some price to pay. S.J.8.4. Summary of Decimal Fixed Point Advantages and Disadvantages Among the benefits of this approach, compared with the unified fixed point approach: - It is independent of which changes, if any, are made to the general fixed-point model in terms of the relationship between delta and small. - The syntactic distinction between decimal and general fixed-point types will be helpful both to the user (who will immediately see the intent of a decimal type declaration) and to the compiler (for performing certain kinds of optimizations). - The special form for generics with formal decimal type parameters will be useful for developing associated standards for IS components (for example, a generic package that models some common COBOL verbs) - The delta ... digits ... syntax is equivalent to the concepts of scale and precision found in other languages (such as SQL). In particular, scale = log (1/delta), and precision = digits. 10 - The equivalence of the delta ... digits ... syntax to a general fixed-point declaration and a SMALL representation clause (except for the absence of decimal operations when the explicit representation clause is used) may be exploited by a core language compiler to minimize the impact on the implementation Some potential drawbacks (and responses): - The operations specific to decimal fixed-point types are attributes, and several are functions that are rather complicated. For some implementations, this model may present difficulties. A simpler approach to implement would be to define the decimal operations as generic units. However, it is not good human engineering to require a heavy notation for such an extremely common operation. There is a decided risk that the need to instantiate a generic to obtain operations for I/O conversion and edited output would present an impediment to the successful transition to Ada from IS environments using other languages. - A programmer declaring a decimal type to obtain a power-of-10 SMALL but not needing all the decimal operations may incur a cost at compile time and also in execution code space. On the other hand, if this is an issue then the programmer can always use the Ada 83 style of a general fixed-point type followed by an explicit SMALL representation clause. - Since the syntax for decimal fixed-point and generic formal types must be in the core language, some support for decimal will be needed even for non-IS implementations. However, the major effort required for support of decimal -- the special representations and attributes -- is only required for IS-compliant implementations. Moreover, the alternatives to requiring minimal support -- either moving the syntax to the Annex, or generalizing the syntax to unify decimal with general fixed point -- would have worse consequences. In summary, the decimal fixed point approach seems the most appropriate solution for decimal computation. S.K. Safety and Security This section will be provided at a later date. S.L. Numerics Annex This section provides the rationale for features proposed in the corresponding section of Version 4.1 of the Mapping Specification. The treatment of numerics is simplified by - retaining, for floating-point types, only one of the concepts of model numbers and safe numbers; - eliminating, for fixed-point types, the concept of small (as distinct from delta); - eliminating attributes that are no longer needed. The simplification provides the following direct benefits: - real types become somewhat easier to describe; - at least one common misapprehension (that the safe numbers and model numbers of a floating-point type differ only in range, i.e., that they have the same precision) loses its basis; - fixed-point types become much more intuitive, with no loss of functionality. Conceptually, for floating-point types it is the model numbers that are being eliminated and the safe numbers that are being kept. However, in the process of doing so, the properties of the latter are being changed slightly, and they are being called model numbers instead of safe numbers. One may prefer to think that the model numbers have been retained (with some changes) and the safe numbers eliminated, but the surviving concept is much closer to that of Ada 83's safe numbers. The name change is motivated by the broad connection of the resulting concept to the semantics of floating-point arithmetic, in contrast to the much more limited connotations of ``safe numbers.'' If, with the advent of Ada 9X, one only talks about ``model numbers'' in the context of their definition in Ada 9X, no confusion should arise. The changes in the surviving concept provide these secondary benefits: - the model of floating-point arithmetic becomes more useful to numerical analysts because, as a descriptive tool, it reflects the properties of the underlying hardware more closely; - the ``4*B Rule'' is eliminated; - implementations of floating point on decimal hardware become practical; - a few anomalies are eliminated. In general, the changes will have little impact on implementations; in particular, currently generated floating-point code should, in the main, remain valid. Although an Ada 83 fixed-point type with a non-binary delta and a default (therefore binary) small will change meaning or even validity in Ada 9X, such a type can provoke a warning in Ada 9X compilers and can be rewritten so as to recover its Ada 83 meaning (see S.L.2). S.L.1. Semantics of Floating-Point Arithmetic Floating-point semantics in Ada 83 are tied to the concepts of model numbers and safe numbers. Effectively, the safe numbers define, for a given implementation, the accuracy required of the predefined arithmetic operators and the conditions under which overflow is and is not possible. Numerical analysts have used characteristics of the safe numbers to make claims about the actual performance of their programs in the underlying environment, and they have used attributes of the safe numbers to tailor the behavior of their programs to the numerical properties of the underlying environment. The model numbers, in contrast, can be said to represent the worst-case properties of the safe numbers over all conceivable conforming implementations, and therefore the worst acceptable numerical performance of an Ada 83 program. Numerical analysts have generally not exploited the model numbers or their attributes for any purpose, because they prefer to focus on the actual performance of a program in the underlying environment. The attributes of the safe numbers permit one to reason about that performance in a uniform, symbolic way over all implementations. Since the model numbers of Ada 83 have generally not been put to practical use, they are eliminated in Ada 9X. The concept of safe numbers as the determinant of the actual numeric quality in the underlying environment survives, but in their incarnation in Ada 9X the former Ada 83 safe numbers are called model numbers. At the same time, their definition has been modified slightly to allow them to fit more closely to the actual numeric characteristics of the environment, making them more useful for the purpose for which they were intended. In their new role, they correspond exactly to what Brown in [Brown 81], which is the basis of Ada's model of floating-point arithmetic, called model numbers. The changes in the floating-point model are in line with those addressed by Study Topic S11.1-B(1). S.L.1.1. Floating-Point Machine Numbers Ada 83 includes a characterization of the underlying machine representation of a floating-point type, based on an interpretation of the canonical form [RM 3.5.7(4)] in which the constraints on mantissa, radix, and exponent are those dictated by certain representation attributes [RM 13.7.3(5-9)]. This amounts to the definition of a set of numbers which we are calling, in Ada 9X, the machine numbers of a floating-point type. We define the machine numbers of a type T to be those capable of being represented in the storage representation of T without loss of accuracy or the raising of an exception. Some machines have ``extended registers'' with a wider range and greater precision than the corresponding (or, sometimes, any) storage format. Thus, in the course of computation with a type T, values having wider range or greater precision than the machine numbers of T can be generated, as allowed by the model of floating-point arithmetic. They can also be assigned to variables of T in Ada 9X (if T is a base type), since a variable may be temporarily, or even permanently, held in a register. There is no guarantee, however, that such extended range or precision can be exploited, and consequently no attempt is made to characterize it. Note: In this connection, Ada 83 allows values of extended precision, but not extended range, to be assigned to variables. The benefits of keeping variables in extended registers are partially negated in Ada 83 by the need to perform range checks on assignment, even when the variable is of a base type (having, therefore, an implementation-defined range). These benefits are fully realizable in Ada 9X due to the fact that range checks are no longer performed on assignment to a variable of a numeric base type, on the passing of an argument to a formal parameter of such a type, and on returning a value of such a type from a function. Overflow may still be detected in such contexts, i.e. when the expression on the right-hand side of an assignment statement, in an actual parameter, or in a return statement performs an operation whose result exceeds the hardware's overflow threshold, but that is a separate semantic issue. This is discussed further in S.L.1.5. Consideration was given to eliminating the characterization of machine numbers and retaining only that of the model numbers, thereby simplifying the discussion of floating-point matters even further. However, the characteristics of the machine numbers (that is, the storage representation of a floating-point type) are needed to define the meaning of certain attributes, viz. the ``primitive functions'' (see S.L.4), as well as the meaning of UNCHECKED_CONVERSION when its source or target type is a floating-point type. In addition, occasionally it is appropriate to design a numerical algorithm so as to exploit the characteristics of the machine representation as much as possible, even though in some contexts the hardware might not allow the full benefit of such an attempt to be achieved. S.L.1.2. Attributes of Floating-Point Machine Numbers The Ada 83 representation attributes of floating-point types (MACHINE_EMIN, MACHINE_EMAX, MACHINE_MANTISSA, MACHINE_RADIX, MACHINE_ROUNDS, and MACHINE_OVERFLOWS) have been retained in Ada 9X, and two new attributes (DENORM and SIGNED_ZEROS) have been defined. Those that returned values of the type universal_integer in Ada 83 (MACHINE_EMIN, MACHINE_EMAX, MACHINE_MANTISSA, and MACHINE_RADIX) return values of the analogous type, root_integer'CLASS, in Ada 9X. It has never been particularly clear whether and how the Ada 83 representation attributes accommodate denormalized numbers, if the implementation happens to have them. This situation is improved in Ada 9X in two ways. Implementations that generate and use denormalized numbers for a floating-point type T, as defined in [IEEE 85], will be distinguished by having T'DENORM = TRUE; otherwise, T'DENORM = FALSE. (Besides being useful to programmers, this new attribute plays a role in the definitions of some of the primitive-function attributes.) In addition, denormalized numbers are accommodated as machine numbers by clarifying the meaning of T'MACHINE_EMIN and relaxing the requirement that the leading digit of mantissa, in the canonical form of machine numbers, always be nonzero. The clarification is that T'MACHINE_EMIN gives the smallest value of exponent (in the canonical form) for which every combination of sign, exponent, and mantissa yields a machine number, i.e., a value capable of being represented in the storage representation of T without loss of accuracy or the raising of an exception. This effectively means that T'MACHINE_EMIN is the exponent of the smallest normalized machine number whose negation is also a machine number (which has relevance to implementations featuring ``radix-complement'' representation) and that, in implementations for which T'DENORM is TRUE, it is also the exponent of all of the denormalized numbers. A similar clarification for T'MACHINE_EMAX means that it is the exponent of the largest machine number whose negation is also a machine number; it is not the exponent of the most negative number on radix-complement machines. An alternative clarification of T'MACHINE_EMIN and T'MACHINE_EMAX was considered, namely, that they yield the minimum and maximum values of exponent for which some combination of sign, exponent, and mantissa yields a machine number. This would have allowed denormalized numbers to be accommodated without relaxing the requirement that the leading digit of mantissa be nonzero, and it would allow us to omit an observation, which we expect to include when we write the complete definition for the primitive function T'EXPONENT(X), as currently proposed, that this function can yield a result less than T'MACHINE_EMIN or greater than T'MACHINE_EMAX. Despite the apparent desirability of this alternative, it was judged to be too much of a departure from current practice and therefore too likely to cause compatibility problems. The new attribute T'SIGNED_ZEROS is provided to indicate whether the hardware distinguishes the sign of floating-point zeros, as described by [IEEE 85]. This attribute, together with some of the primitive functions (see S.L.4), allows the numerical programmer to extend the treatment of signed zeros to the higher-level abstractions he or she creates, much in the manner of the elementary functions ARCTAN and ARCCOT (see S.L.3). It is expected that implementations that distinguish the sign of zeros will do so in a way consistent with relevant external standards (e.g., [IEEE 85]) to the extent that such standards apply to operations of Ada, and in appropriate and consistent (but implementation-defined) ways otherwise; thus, no attempt is made in Ada 9X to prescribe the sign of every possible zero result, or the behavior of every operation receiving an operand of zero. The two new attributes T'DENORM and T'SIGNED_ZEROS describe properties that an implementation may exhibit independently of any other support for IEEE arithmetic. Some implementations of Ada 83 do feature denormalized numbers and signed zeros, but no other features of IEEE arithmetic. S.L.1.3. Floating-Point Model Numbers The primary changes that distinguish Ada 9X model numbers from Ada 83 safe numbers are these: 1. the length of the mantissa (in the canonical form) is no longer ``quantized,'' but is as large as possible consistent with satisfaction of the accuracy requirements; 2. the radix (in the canonical form) is no longer always two, but is the same (for a type T) as T'MACHINE_RADIX; 3. the model numbers form an infinite set; 4. the maximum non-overflowing exponent is no longer bounded below by a function of the mantissa length; 5. the minimum exponent is no longer required to be the negation of the maximum non-overflowing exponent, but is given (for a type T) by an independent attribute. The Ada 83 safe numbers have mantissa lengths that are a function of the DIGITS attribute of the underlying predefined type, giving them a quantized length chosen from the list (5, 8, 11, 15, 18, 21, 25, ...). Thus, on binary hardware having T'MACHINE_MANTISSA = 24, which is a common mantissa length of the single-precision floating-point hardware type, the last three bits of the machine representation exceed the precision of the safe numbers; as a consequence, even when the machine arithmetic is fully accurate (at the machine-number level), one cannot deduce that Ada arithmetic operations deliver full machine-number accuracy. With the first change enumerated above, on many machines tighter accuracy claims will be provable. As an additional consequence of this change, in Ada 9X the two types declared as follows type T1 is digits D; type T2 is digits D range T1'FIRST .. T1'LAST; will, as they intuitively should, have the same hardware representation when hardware characteristics do not require parameter penalties; in Ada 83, their hardware representations almost always differ, with T2'BASE'DIGITS > T1'BASE'DIGITS, for reasons having nothing to do with hardware considerations. The second change enumerated above has two effects: - it permits practical implementations on hardware with a decimal radix (which, though not not currently of commercial significance for mainstream computers, is permitted by IEEE Std. 854 [IEEE 87]; is appealing for embedded computers in consumer electronics; and is used in at least one such application, an HP calculator); - on hexadecimal hardware, it allows more machine numbers to be classed as model numbers (and therefore to be proven to possess special properties, such as being exactly representable, contributing no error in certain arithmetic operations, etc.). As an example of the latter effect, note that T'LAST will become a model number on most hexadecimal machines. Also, on hexadecimal hardware, a 64-bit double-precision type having 14 hexadecimal (or 56 binary) digits in the hardware mantissa, as on many IBM machines, has safe numbers with a mantissa length of 51 binary bits in Ada 83, and thus no machine number of this type with more than 51 bits of significance is a safe number; in Ada 9X, such a type would have a mantissa length of 14 hexadecimal digits, with the consequence that every machine number with 53 bits of significance is now a model number, as are some with even more. (Why does the type under discussion not have Ada 83 safe numbers with 55 bits in the mantissa, the next possible quantized length and a length that is less than that of the machine mantissa? Because some machine numbers with 54 or 55 bits of significance do not yield exact results when divided by two and cannot therefore be safe numbers. This is a consequence of their hexadecimal normalization, and it gives rise to the phenomenon known as ``wobbling precision.'') The third change enumerated above is intended to fill a gap in Ada 83 wherein the results of arithmetic operations are not formally defined when they exceed the modeled overflow threshold but an exception is not raised. Some of the reasons why this can happen are as follows: - the quantization of mantissa lengths may force the modeled overflow threshold to be lower than the actual hardware threshold; - arithmetic anomalies of one operation may require the attributes of model and safe numbers to be conservative, with the result that other operations exceed the minimum guaranteed performance; - the provision and use of extended registers in some machines moves the overflow threshold of the registers used to hold arithmetic results well away from that of the storage representation; - the positive and negative actual overflow thresholds may be different, as on radix-complement machines. The extension of the model numbers to an infinite range fills a similar gap in Ada 83 wherein no result is formally defined for an operation receiving an operand exceeding the modeled overflow threshold, when an exception was not raised during its prior computation. The change means, of course, that one can no longer say that the model numbers of a type are a subset of the machine numbers of the type; one may say instead that the model numbers of a type T in the range -T'MODEL_LARGE .. T'MODEL_LARGE are a subset of the machine numbers of T. The fourth change enumerated above is equivalent to the dropping of the ``4*B Rule.'' The rule was necessary in Ada 83 in order to define the model numbers of a type entirely as a function of a single parameter (the requested precision). By its nature, the rule potentially prevents the implementation of Ada in some (hypothetical) environments, as if to say that such environments are not suitable for the language or applications written in it; such matters ought to be left to the judgment of the marketplace. The particular minimum range required in Ada 83 (as a function of precision) is furthermore about twice that deemed minimally necessary for numeric applications [Brown 81]. One real consequence of the Ada 83 4*B Rule is that no type requesting more than nine digits can use VAX D-format hardware representation, even though the format supplies about 16 decimal digits of precision; equivalently, any predefined floating-point type provided as a realization of VAX D-format must accept a severe penalty on the purported precision of the type. With the relaxation of the rule in Ada 9X, such a predefined type may honestly characterize the precision and range provided, and it will be a candidate for the implicit selection of the underlying representation of a user-declared type requesting more than nine digits of precision and a sufficiently small range. Note: Dropping the 4*B Rule creates potential upwards-compatibility problems, in that some declarations will admit the selection of a hardware type with a narrower range than the Ada 83 rules allowed, and an overflow could occur where it did not in Ada 83. Whether this is serious depends on whether one believes that programmers really rely on the 4*B rule in Ada 83. If there is strong objection to dropping the 4*B Rule, it will be restored. Restoring it will mean changing a clause in the ``equivalence rule'' for floating-point declarations to read If a range L .. R is specified, then P'MODEL_LARGE >= max(abs(L), abs(R)); otherwise, P'MODEL_LARGE >= 10.0 ** (4*D). The fifth change enumerated above removes another compromise made necessary by the desire, in Ada 83, to define the model numbers of a type in terms of a single parameter. The minimum exponent of the model or safe numbers of a type in Ada 83 is required to be the negation of the maximum exponent (thereby tying it to the precision implicitly). One consequence of this is that the maximum exponent may need to be reduced simply to avoid having the smallest positive safe number lie inside the implementation's actual underflow threshold; if it is needed, such a reduction provides another way to obtain values in excess of the modeled overflow threshold without raising an exception. Another is that the smallest positive safe number may have a value unnecessarily greater the actual underflow threshold. With the fifth change, as with some of the others, more of the machine numbers will be recognized as numbers having special properties, i.e., as model numbers. Consideration was given to eliminating the model numbers and retaining only the machine numbers. While this would simplify the semantics of floating-point arithmetic further, it would not eliminate the interval orientation of the accuracy requirements (see S.L.1.5) if variations in rounding mode from one implementation to another, and if the use of extended registers, are to be tolerated. It would simply substitute the machine numbers and intervals of machine numbers for the model numbers and intervals of model numbers in those requirements, but their qualitative form would remain the same. However, rephrasing the accuracy requirements in terms of machine numbers and intervals thereof cannot be realistically considered, since many platforms on which Ada has been implemented and might be implemented in the future could not conform to such stringent requirements. If an implementation has appropriate characteristics, its model numbers up to the modeled overflow threshold will in fact coincide with its machine numbers, and an analysis of a program's behavior in terms of the model numbers will not only have the same qualitative form as it would have if the accuracy requirements were expressed in terms of machine numbers, but it will have the same quantitative implications as well. On the other hand, if an implementation lacks guard digits, employs radix-complement representation, or has genuine anomalies, its model numbers up to the modeled overflow threshold will be a subset of its machine numbers having less precision, a narrower exponent range, or both. S.L.1.4. Attributes of Floating-Point Model Numbers Although some of the attributes of model numbers in Ada 9X are closely related to those of the safe numbers in Ada 83, they all bear new names of the form MODEL_xxx. Certainly this is necessary for Ada 83's T'MANTISSA; the new version, T'MODEL_MANTISSA, is conceptually equivalent to T'BASE'MANTISSA in Ada 83 but is now interpreted as the number of radix-digits in the mantissa. Thus, at a minimum the value of this attribute will be roughly quartered on hexadecimal machines, even if there is no reason to take advantage of the other freedoms now permitted. A new name is certainly also necessary for T'SAFE_EMAX; the new version, T'MODEL_EMAX, is now interpreted as a power of the hardware radix, and not necessarily as a power of two. For hexadecimal machines, the value of this attribute will be quartered, all other things being equal. T'MODEL_EMIN is a new attribute. T'MODEL_LARGE is conceptually equivalent to Ada 83's T'SAFE_LARGE. It is defined in terms of more fundamental attributes, as was true of T'LARGE in Ada 83, with the result that the changes in the radix of the model numbers ``cancel out'' in the definition of this attribute; its value will change little, if at all, and then only to reflect the unquantization of mantissa lengths of model numbers. The same can be said about T'MODEL_SMALL, which is conceptually equivalent to Ada 83's T'SAFE_SMALL, and about T'MODEL_EPSILON, which is conceptually equivalent to Ada 83's T'BASE'EPSILON. The values of these attributes will be determined by how well the implementation can satisfy the accuracy requirements, with the primary determinant being the quality of the hardware's arithmetic. On ``clean'' machines, for which the model numbers up to the modeled overflow threshold coincide with the machine numbers, T'MODEL_MANTISSA, T'MODEL_EMAX, and T'MODEL_EMIN will yield the same values as T'MACHINE_MANTISSA, T'MACHINE_EMAX, and T'MACHINE_EMIN, respectively, though in general T'MODEL_MANTISSA and T'MODEL_EMAX may be smaller than their machine counterparts, and T'MODEL_EMIN may be larger. The set of attributes having both MODEL_xxx and MACHINE_xxx versions could conceivably be enlarged to add further intuitive strength and uniformity to the naming convention, but we have resisted adding attributes which meet no identifiable need. The attributes whose Ada 83 counterparts returned results of the type universal_integer now yield values of the type root_integer'CLASS; these are T'MODEL_MANTISSA and T'MODEL_EMAX. The new attribute T'MODEL_EMIN also yields a value of this type. The attributes whose Ada 83 counterparts returned results of the type universal_real now yield values of the type root_real'CLASS; these are T'MODEL_LARGE, T'MODEL_SMALL, and T'MODEL_EPSILON. Although there is no particular reason why T'MODEL_LARGE and T'MODEL_SMALL cannot return values of the base type of T, neither is there a compelling reason to make what would be a gratuitous change. It is our plan to establish a catalog of the fundamental model parameters for all known implementations of Ada at some future time. Any compatibility problems that arise from the renaming and, in some cases, elimination of model attributes will have an impact on an extremely limited and specialized community; if an implementation needs to assist that community in coping with the change, it can continue to offer the previous Ada 83 model attributes as implementation-defined attributes (see RM 4.1.4(4)) during the transition period. S.L.1.5. Accuracy of Floating-Point Operations The accuracy requirements for certain predefined operations (arithmetic operators, relational operators, and the basic operation of conversion, all of which are referred to in this section simply as ``predefined arithmetic operations'') of floating-point types other than root_real are still expressed in terms of model intervals, for reasons explained earlier. It is clarified that they do not apply to all such operations, however. For example, they do not apply to any attribute that yields a result of a specific floating-point type; such an attribute yields a machine number, which must be exact. The accuracy requirements for exponentiation are relaxed in accord with AI-00868. The weaker rules no longer require that exponentiation be implemented as repeated multiplication; special cases can be recognized and implemented more efficiently by, for example, repeated squaring, even when accuracy is sacrificed by doing so. The implementation model for exponentiation by a negative exponent continues to be exponentiation by its absolute value, followed by reciprocation. Thus, the rule continues to allow for the possibility of overflow in this case, despite the counterintuitive nature of such an overflow. The WG9 Numerics Rapporteur Group recommended this treatment, so as to allow for the most efficient implementations, recognizing, of course, that the user who is concerned with the possibility of overflow can express the desired computation differently and thereby avoid it. AI-00868 did not address the possibility of overflow in the intermediate results, for negative exponents. One of the goals for Ada 9X is to allow for and legitimize the typical kinds of optimizations that increase execution efficiency or numerical quality. One of these is the use, for the results of the predefined arithmetic operations of a type, of hardware representations having higher precision or greater range than those of the storage representation of the type. On some machines, this is not an option; arithmetic is performed in ``extended registers,'' there being no registers having exactly the precision or range of the storage cells used for variables of the type. Thus, we must allow the results of arithmetic operations to exceed the precision and range of the underlying type; avoiding that is intolerably expensive on some machines. A second common optimization is the retention of a variable's value in a register after its assignment, with subsequent references to the variable being fulfilled by using the register. This avoids load operations (i.e., the cost of memory references); it may, in many cases, even avoid the store into the storage location for the variable that would normally be generated for the assignment operation. One implication of legitimizing the use of extended registers is the need to define the result of an operation that could overflow but doesn't, as well as the result of a subsequent arithmetic operation that uses such a value. This is the motivation for the extension of the model numbers to an infinite range and for the rewrite of RM 4.5.7(7). The new rules describe behavior that is consistent with the assumption that an operation of a type T that successfully delivers or uses results beyond the modeled overflow threshold of the type T is actually performed by an operation corresponding to a type with higher precision and/or wider range than that of the type T, whose overflow threshold is not exceeded, and whose accuracy is no worse than that of the original operation of the type T. Ada 83 does not permit a value outside the range T'FIRST .. T'LAST to be propagated beyond the point where it is assigned to a variable of a type T, or is passed as an actual parameter to a formal parameter of type T, or is returned from a function of type T; a range check is required in these contexts to prevent it. Nothing prevents the carrying of excess precision beyond such a point, however. Thus, keeping a value in an extended register beyond such a point is permitted in Ada 83, whether or not it is also stored, provided that the range check is satisfied. The range check may be performed by a pair of comparisons of the source value to the bounds of the range, when those bounds are arbitrary; but in the case of a floating-point base type, the check may be a free by-product of the store. For example, on hardware conforming to IEEE arithmetic [IEEE 85], storing an extended register into a shorter storage format will signal an overflow if the source value exceeds the range of the destination format. If the propagation has no need for an actual store, because the value is to be propagated in the register, then a store into a throw-away temporary, just to see if overflow occurs, may be the cheapest way to perform the range check. If the check succeeds, all subsequent uses of the value in the extended register are valid and safe, including any potential need to store it into storage, such as when the value is about to be passed as an actual parameter and the implementation prefers to pass parameters in storage, or even merely because of the need for register spilling at an arbitrary place not connected with a use of the entity currently in the register. The loss of precision that occurs at that point does not matter, because it is consistent with the perturbations allowed when the value, had it not been shortened, is subsequently used as the operand of an operation. For an assignment to a variable of a numeric base type, actual code to perform the range check is not always needed; it can be omitted if the implementation can deduce that the check must succeed. The generation of code to perform a range check is necessary only when extended registers are being used, and then only when the source expression is other than just a primary, that is, contains at the top level a predefined arithmetic operation that can give rise to a value outside the range. As an example, consider X := Y * Z; in which X, Y, and Z are assumed to be of some floating-point base type T. If there are no parameter penalties, T'MODEL_LARGE = T'LAST = -T'FIRST. If extended registers are not being used, then the multiplication cannot generate a value outside the range T'FIRST .. T'LAST (since the attempt to do so would overflow) and the range check can therefore be omitted. On the other hand, if extended registers are being used, a value exceeding T'MODEL_LARGE can be produced in the register, because the multiplication may no longer overflow, and a range check will be needed to preclude the propagation of a value outside T'FIRST .. T'LAST. When the source expression is simply the value of a variable, a formal parameter, or the result of a function call, as in X := Y; no actual range check is necessary, since the value (of Y, in the example) can be presumed to have passed an earlier range check in the first propagation away from the point where it was generated. When the source expression does contain a predefined arithmetic operation at the top level, the formal definition immediately precedes the range check on assignment by an overflow check (on the multiplication, in the first example above) that is at least as stringent (it is more stringent if there are parameter penalties causing T'MODEL_LARGE to be less than T'LAST). Because it is at least as stringent as the range check, the overflow check ought to subsume the range check, but in practice it does not since the actual overflow threshold, when extended registers are used, is even higher. It is unfortunate that the availability and use of extended registers sometimes require extra code to be generated for assignments in Ada 83. We discuss next a possible way to improve on this situation in Ada 9X. It is not what we have actually done, but it motivates the latter, which is described afterwards. We could proceed by making the range check optional at any propagation point (assignment statement, parameter passing, return statement) when the target type is a numeric base type. This would allow an out-of-range value to be propagated when no actual store is needed, and it would also permit an exception to be raised for an out-of-range value when the implementation does require that the propagation be performed by an actual store (for example, some implementations might never pass by-copy parameters or function results in registers). In general, it would permit an out-of-range value to survive in an extended register through an arbitrary number of propagations (in particular, those that don't require stores), only to give rise to an exception when a propagation point requiring a store is reached. We would also have to clarify that passing an actual parameter to an attribute that is a function, and returning a value from such an attribute, are considered propagations, since the attribute may be implemented in the same way as a function and may require its arguments or result to be passed in storage. Thus, a range check would optionally be performed at those places when the parameter or result is of a numeric base type and the source value can be out of range. Finally, we would also have to clarify that presenting an operand to a predefined arithmetic operation, and that returning a result from a predefined arithmetic operation, are also considered propagations, since some implementations may implement some such operations in the same way as function calls, requiring operands and results to be passed in storage. Thus, a range check would optionally be performed at those places, too, when the type of an operand or that of the operation's result is a numeric base type. Actually, this goes a bit too far: there is no need for the range check on the result of a predefined operation, since the more stringent overflow check already there subsumes it and accounts for any necessary raising of CONSTRAINT_ERROR at that point. That approach comes close to accomplishing what we need. The only problem with it is that it leaves untouched many contexts in which an out-of-range value in an extended register could be used without an opportunity for raising CONSTRAINT_ERROR, as might be required by the particular context. For example, simple variables used as the bounds of ranges, as discriminants, as subscripts, as specifiers of various sorts in declarations, as generic actual parameters, as case expressions, in delay statements, as choices in numerous constructs, and undoubtedly in other contexts would not be subject to a range check, because these are not propagation contexts. Implementations would have to be prepared to deal in these contexts with values having the range implied by the extended registers that are available, rather than the range implied by the base type associated with the context at hand. What we have actually done, instead of including the optional range check in the semantics of propagation, is to include it in the semantics of read-references for certain categories of primary, specifically name and function_call. (This does not appear in the Mapping Specification for the Numerics Area, except in the form of a Note, since it is assumed to be a feature of the core.) The range check in the three main propagation contexts of Ada 83 (assignment statement, by-copy parameter passing, and return statement) is entirely eliminated when the target type is a numeric base type. We shall now show that even in its absence there is an opportunity to raise CONSTRAINT_ERROR in those propagation contexts, when the target type is a numeric base type and the source value exceeds the type's range, and in all other contexts in which such a value might be used. Indeed, the propagation contexts are just a subset of the general contexts, so they need not be considered separately. Every value used in a read (fetch) context, including those in propagation contexts, is denoted by the category expression or one of its descendants. Those expressions, or constituents of expressions, involving predefined arithmetic operations (including the implicit conversions inherent in references to numeric literals) already provide an opportunity to raise CONSTRAINT_ERROR when they yield a value of a numeric base type that is outside the range of the type, because the operations perform an overflow check on the result that, being more stringent than the desired range check, subsumes it. The opportunity to raise a CONSTRAINT_ERROR for a parenthesized expression or a qualified expression, as an expression or a component thereof, is provided by the evaluation of the expression that is its immediate component. This leaves only names and function calls. Therefore, all the necessary opportunities for raising the desired CONSTRAINT_ERROR are covered by adding an optional range check to the semantics of read-references for names and function calls (i.e., after the return), when the type denoted by the name or function call is a numeric base type. Note that only names denoting non-static objects are affected, since the evaluation of static expressions is both exact and not limited as to range. We stress that all of the new range checks we are introducing are optional; that is, either the check is optionally performed and raises CONSTRAINT_ERROR if it fails, or it is always performed and optionally raises CONSTRAINT_ERROR if it fails. Thus, the checks will not require any code to be generated unless an actual shortening (storing of an extended register) does need to occur at one of these places. Furthermore, as we indicated earlier, even then the range check may come for free (as on IEEE hardware). A simple example will illustrate what can be gained. Consider this typical inner product: SUM := 0.0; for I in A'RANGE loop SUM := SUM + A(I) * B(I); end loop; F(SUM); Assume that SUM is a variable of a numeric base type. We would like to keep SUM in an extended register during the loop, and in fact not even store the register into SUM during the loop. In Ada 83, we are formally obligated to perform a range check upon the assignment inside the loop, to prevent the propagation of a value outside SUM's range; in IEEE systems, the cheapest way to do this would be by storing into SUM after all, or into a throw-away temporary. Thus, a store (or some other means of checking) is executed each time through the loop. In Ada 9X, on the other hand, no range check is performed on that assignment, allowing an out-of-range value to be propagated, and justifying the complete omission of stores of the extended register containing SUM within the loop. The CONSTRAINT_ERROR that Ada 83 would have raised on some assignment in the loop might instead occur during the passing of SUM to F. It is allowed by the new optional range check in the semantics of the variable reference inherent in the parameter association, and whether or not it occurs there depends on whether parameters are passed in storage or in registers and, in the former case, whether the value of SUM is out of range at that point. With this change, it is true that exceptions can occur in places where they did not occur in Ada 83. However, whenever this happens, one can point to a different place in the program where the exception would have occurred, earlier, in its interpretation according to Ada 83 semantics. It may also be that the exception never occurs in the Ada 9X interpretation; in the example above, it may be that SUM remains forever in an extended register and is never stored, or it may be that its value has been brought back within range by the time it is stored. We should probably have noted much earlier that this treatment of numeric base types applies to all of them, not just floating-point base types. It allows integer and fixed-point values to be held in, for example, 32-bit general registers, in which integer arithmetic is performed, even when the storage format of the base types involved has only 16 or 8 bits. Also, although omitting certain range checks appears to conflict with the safety goals of range checking, it must be remembered that the bounds of numeric base types are implementation dependent anyway, so that whether a particular source value can be assigned to a target of a numeric base type already depends (i.e., in Ada 83) on properties of the implementation. Furthermore, all declared integer and fixed-point types necessarily involve range constraints and will therefore be subject to range checking; only floating-point types declared without a range constraint will escape it. Of course, all predefined numeric types are base types and will escape range checking (which is consistent with their implementation-dependent ranges). Other than by using floating-point types declared without a range constraint, or by using predefined types, or by going out of one's way to use T'BASE as a type mark, one will not escape range checking. As was explained above, even Ada 83 allowed and explained the loss of precision that can occur in shortening, when the propagation of a value held in an extended register requires it. Actually, there is one exception to this: if shortening is allowed to take place on a value being passed to an instantiation of UNCHECKED_CONVERSION (and it certainly seems that shortening is expected in that context), then nothing in the definition of UNCHECKED_CONVERSION, or anywhere else, currently allows or explains the shortening, in regard to the contrast between the possibly extra-precise value going in and the presumably shortened value coming out. In Ada 9X, the primitive functions (see S.L.4) introduce several additional contexts in which shortening can occur and yet the accompanying loss of precision is potentially unexplained. We need to introduce some rules that explain the possibility of loss of accuracy in those contexts where it is not currently explained. (The primitive functions, being attributes, are not operations to which 4.5.7 applies.) It seems likely that the latter will be specific to the contexts involved, though we have not yet resolved how best to accomplish that. The accuracy requirements for floating-point arithmetic operations are, for the time being, expressed separately from those for fixed-point operations, since the latter do not need the full generality of the interval-based model appropriate for floating-point operations (see S.L.2). Nevertheless, the rules may ultimately be recombined into a uniform set of rules for all real types for purely presentation purposes; if so, it would be made clear that some of the freedoms permitted in the floating-point case do not apply in the fixed-point case. S.L.1.6. Floating-Point Type Declarations The restatement of the ``equivalence rule'' for user-declared floating-point type declarations, which explains how an implementation selects a predefined floating-point type on which to base the representation of the declared type, is a natural extension of its form in Ada 83 that accommodates the changes described earlier. This rule is the basis for the intuitive (and informal) observation that floating-point types provide for approximate computations with real numbers so as to guarantee that the relative error of an operation that yields a result of type T is bounded by 10.0 ** (-T'DIGITS). [Note: A more formally complete version of this observation can be obtained from a theorem of [Brown 81].] One way in which our changes do not go quite as far as possible in reflecting the actual properties of the machine has to do with the use of T'MODEL_LARGE in describing when overflow can occur and when it cannot. On radix-complement machines, the negative overflow threshold does not coincide in magnitude with the positive overflow threshold, but this is not reflected in T'MODEL_LARGE, which is conservative (that is, it characterizes the less extreme threshold). While this is not of any particular consequence as far as the rewrite of 4.5.7 goes (after all, there are many reasons why a value exceeding T'MODEL_LARGE in magnitude might not overflow), it does interact in an undesirable way with the equivalence rule for floating-point type declarations. If a floating-point type declaration specifies a lower bound exactly coinciding with the most negative floating-point number of some base type P, as can happen when P'FIRST is used as the lower bound of the requested range, then P will be ineligible as the representation of the type being declared (on radix-complement machines, and even when no other arithmetic anomalies are present). This suggests that T'MODEL_LARGE ought to be abandoned in favor of two attributes, say T'MODEL_FIRST and T'MODEL_LAST, that characterize the positive and negative ``safe'' (i.e., overflow-free) limits separately. The way that T'MODEL_EMAX is defined would have to change; presumably it could be the maximum of the exponents of T'MODEL_FIRST and T'MODEL_LAST in the canonical form. A T'MODEL_FIRST and T'MODEL_LAST could then be defined for all numeric types (for integer and fixed-point types they would be equal to T'BASE'FIRST and T'BASE'LAST, respectively), and they could be used in a type-independent statement of when overflow can and cannot occur (allowing its removal from 4.5.7). This is an attractive idea and may be explored in the future; the idea was suggested too late to be considered in this version of the MS and MR. S.L.2. Semantics of Fixed-Point Arithmetic Various problems have been identified with fixed-point types in Ada 83: - They can be counterintuitive. The values of a fixed-point type are not always integer multiples of the declared delta (they are instead integer multiples of the small, which may be specified or defaulted, and which in either case need not be the same as, or even a submultiple of, the delta), and they do not always exhaust the declared range, even when the bounds of the declared range are integer multiples of the small (we are thinking about the case where a bound of the range is a power of two). These surprises are responsible for some of the confusion with fixed-point types (although some programmers do understand and correctly exploit the fact that the high bound need not be representable). - The model used to define the accuracy requirements for operations of fixed-point types is much more complicated than it needs to be, and many of its freedoms have never, in fact, been exploited. The accuracy achieved by operations of fixed-point types in a given implementation is ultimately determined, in Ada 83, by the safe numbers of the type, just as for floating-point types, and indeed the safe numbers can, and sometimes do, have more precision than the model numbers. However, the model in Ada 83 allows the values of a real type (either fixed or float) to have arbitrarily greater precision than the safe numbers, i.e., to lie between safe numbers on the real number axis; implementations of fixed point typically do not exploit this freedom. Thus, the opportunity to perturb an operand value within its operand interval, although allowed, does not arise in the case of fixed point, since the operands are safe numbers to begin with. In a similar way, the opportunity to select any result within the result interval is not exploited by current implementations, which we believe always produce a safe number; furthermore, in many cases (i.e., for some operations) the result interval contains just a single safe number anyway, given that the operands are safe numbers, and it ought to be more readily apparent that the result is exact in these cases. - Support for fixed-point types is spotty, due to the difficulty of dealing accurately with multiplications and divisions having ``incompatible deltas'' as well as fixed-point multiplications, divisions, and conversions yielding a result of an integer or floating-point type. Algorithms have been published in [Hilfinger 90], but these are somewhat complicated and do not quite cover all cases, leading to implementations that do not support representation clauses for SMALL and that, therefore, only support binary smalls. These problems are partly the result of trying to make fixed-point types serve several needs and several application areas, none of which are served perfectly and all of which are compromised somewhat, as discussed below. - One of the intended applications for fixed-point types is sensor-based applications, where the representations of scaled physical quantities are transmitted over ports as binary integers. Digital signal processing is a related application area with a similar focus on manipulating scaled binary integers. These needs are met fairly well, because either no representation clauses for SMALL are used (the delta already being a power of two) or representation clauses for universally accepted values of SMALL are used. - Fixed-point types are intended, or at least they have been considered, for applications in the Information Systems area, i.e. to represent financial quantities that are typically integer multiples of decimal fractions of some monetary denomination. This need is not met well, since extra precision is generally intolerable in such applications, rounding needs to be controlled, and there is no guarantee that decimal scaling factors are supported (because they require the use of representation clauses). Many fixed-point implementations limit ranges to the equivalent of about ten decimal digits, which is inadequate for some IS applications. In addition, IS applications often need multiple representations of decimal data, e.g. for computation versus display. The fixed-point model in Ada 83 is heavily biased towards an internal representation of fixed-point data as a binary integer, and this bias strongly affects the range of the base type. Specifying a range as, for example, -9_999_999.99 .. 9_999_999.99 is considered cumbersome; in any case, it does not guarantee protection against exceeding the range in computations of the base type. - Finally, fixed-point types are often embraced as a kind of cheap floating point, suitable on hardware lacking true floating point when the application manipulates values from a severely restricted range. This need may be met well, in the sense that efficient performance may be expected when the small is allowed to default and the user holds no expectations that multiples of the delta are exactly represented, but it has influenced the design of the facility too heavily and it compromises the quality of what can be offered in the other application areas. It is not clear that this application of fixed point is much used or needed. Our solution to these problems is to remove some of the unneeded generality that underlies the counterintuitive behavior of fixed point, to remove some of the freedoms of the interval-based accuracy requirements that have never been exploited, and to relax the accuracy requirements so as to encourage wider support for fixed point. Applications that use binary scaling and/or carefully matched (``compatible'') scale factors in multiplications and divisions, which is typical of sensor-based and other embedded applications, will see no loss of accuracy or efficiency. It is not our intention to meet the special needs of IS applications; they are addressed by the new decimal fixed point types defined in the Information Systems area of the Special Needs Annex (see J), although undemanding applications in this area may coincidentally be served marginally better by ordinary fixed-point types than they were in Ada 83. The revamped fixed-point facility substitutes intuitive behavior for the surprises of the past. In Ada 9X, the values of a fixed-point type are always integer multiples of the type's delta, and never multiples of something else (possibly unseen). Effectively, it is as if an Ada 83 representation clause for SMALL, confirming the delta, is always included. The fact that extra precision is never used is exploited to simplify the statement of the accuracy requirements. Some programs will change their behavior as a result of this change. In particular, a program that declares a type with a delta that is not a power of two, and that uses no representation clause for SMALL, will shift from an Ada 83 implementation of the type as representing integer multiples of a power of two to an Ada 9X implementation that represents the type as integer multiples of the specified delta. We argue that this will affect very few programs, that is, that very few programs rely on the default rule for small. Most such programs, we believe, represent applications of fixed point arithmetic best characterized as poor-man's floating point in the sense that close approximations to the mathematically correct result, with a certain guaranteed minimum precision over the range, are all that is desired. For example, an application that declares a type as type PIN_EXTENSION is delta .000001 range 0.6 .. 0.7; probably is saying that values of FLOW should have the equivalent of at least six decimal digits of accuracy over the limited range. Extra accuracy is tolerable but is not needed. Nevertheless, because of the potential for incompatible change, and because such programs will, in general, experience degraded performance (scaling will be performed by multiplications and divisions rather than by binary shifting), we have recommended that a warning message be produced during the transition period. (Changing the declaration to one that specifies a power-of-two delta will recover the Ada 83 performance.) Sensor-based and other embedded applications are not expected to experience any incompatibilities as a result of the change, since they may be assumed to have power-of-two deltas, perhaps with a confirmatory representation clause for SMALL. In the rare case that such an application has a declaration with a delta that is not a power of two and establishes a power-of-two small by means of a representation clause, a warning will be produced (if our recommendation is followed), and the declaration can be changed to achieve the same effect without a representation clause. Note that our simplification of the accuracy requirements removes the need for the attributes of model and safe numbers of fixed-point types. Were those concepts to be retained, we would expect the new names for the attributes discussed in connection with the changes in the floating-point model (see S.L.1.3) to be used for the similar attributes of fixed-point types. Thus, in particular, SMALL would change to MODEL_SMALL, which is potentially a serious incompatibility problem considering the extent to which representation clauses for SMALL are written. Whether or not the concepts of model and safe numbers are retained, implementations can follow our recommendations by providing SMALL as an implementation-defined attribute of fixed-point types, during the transition period. If it is ultimately decided that abandoning the concept of small as distinct from delta is undesirable, we can, with very little impact on the rest of our proposal, restore that concept. Wherever we have referred to integer multiples of delta, we would substitute integer multiples of small. This retrenchment would not necessitate the reintroduction of the model-interval based accuracy requirements. We had hoped to go so far as to remove, in support of Requirement R2.2-B(1), the potential surprise when a range bound that is a power of two is not included within the range of a fixed-point type, by including all the integer multiples of the delta in the declared bounds within the range of the type, arguing that declarations that change their meaning as a result can be rewritten to achieve the desired effect. But we could not argue, as we did above for other changes, that few programs would be affected by this change; in other words, even though this property of Ada 83 has the potential for surprise with some programs, it is used correctly by far more. The feature remains, but it is reflected by a different mechanism now that the concepts of model numbers and their mantissas have been dropped from the fixed-point description. Some of the accuracy requirements, i.e. those for the adding operators and comparisons, now simply say that the result is exact. This was always the case in Ada 83, assuming operands are always safe numbers there, and yet it is not clear from the model-interval form of the accuracy requirements that comparison of fixed-point quantities is, in practice, deterministic and need not be otherwise. Other accuracy requirements are now expressed in terms of small sets of allowable results, called ``perfect result sets'' or ``close result sets'' depending on the amount of accuracy that it is practical to require; these sets always contain consecutive integer multiples of the result type's delta (or of a ``virtual'' delta of 1.0 in the case of multiplication or division with an integer result type). In some cases, the sets are seen to contain a single such multiple or a pair of consecutive multiples; this clearly translates into a requirement that the result be exact, if possible, but never off by more that one rounding error or truncation error. The cases in which this occurs are the fixed-point multiplications and divisions in which the operand and result deltas are ``compatible,'' meaning that the product or quotient of the operand deltas (depending on whether the operation is a multiplication or a division) is either an integer multiple of the result delta, or vice versa. (These cases cover much of the careful matching of types typically exhibited by sensor-based and other embedded applications, which are intended to produce exact results for multiplications and at-most-one-rounding- error results for divisions, with no extra code for scaling; they can produce the same results in Ada 9X, and with the same efficient implementation. Our definition of ``compatible'' is more general than required just to cover those cases of careful matching of operand and result types, permitting some multiplications that require scaling of the result by at worst a single integer division, with an error no worse than one rounding error.) For other cases (when the deltas are ``incompatible''), the accuracy requirements are relaxed, in support of Requirement R2.2-A(1); in fact, they are left implementation defined. Implementations need not go so far as to use the Hilfinger algorithms [Hilfinger 90], though they may of course do so. An Ada 9X implementation could, for instance, perform all necessary scaling on the result of a multiplication or division by a single integer multiplication or division (or shifting). That is, the efficiency for the cases of incompatible deltas need not be less than that for the cases of compatible deltas. This relaxation of the requirements is intended to encourage support for a wider range of deltas. Indeed, we considered making support for all deltas mandatory on the grounds that the relaxed requirements removed all barriers to practical support for arbitrary deltas, but we rejected it because it would make many existing implementations instantly nonconforming. Ada 9X now allows an operand of fixed-point multiplication to be a real literal, named number, or attribute. If the value of that operand is recognizable as an integer multiple of a compatible delta, the operation must be performed with no more than one rounding error and will cost no more than one integer multiplication or division for scaling. (The actual rule we used involves not literally the recognition of the operand value as an integer multiple of a compatible delta, but is equivalent to it.) The relaxed accuracy permitted in other cases allows implementations that, in those cases, still perform scaling by no more than a single integer multiplication or division; or, the operand can be combined with a scale factor and represented only approximately, with degraded accuracy. The accuracy requirements for fixed-point multiplication, division, and conversion to a floating-point target are left implementation defined because the implementation techniques described in [Hilfinger 90] rely on the availability of several extra bits in typical floating-point representations beyond those belonging to the Ada 83 safe numbers; with the revision of the floating-point model, in particular the elimination of the quantization of the mantissa lengths of model numbers, those bits are now likely gone. Requiring model-number accuracy for these operations would demand implementation techniques that are more exacting, expensive, and complicated than those in [Hilfinger 90], or it would result in penalizing the mantissa length of the model numbers of a floating-point type just to recover those bits for this one relatively unimportant operation. With the accuracy requirements for this case left implementation defined, an implementation may use the simple techniques in [Hilfinger 90] for fixed-point multiplication, division, and conversion to a floating-point target; the accuracy achieved will be exactly as in Ada 83, but will simply not be categorizable as model-number accuracy. We have abandoned an idea we first put forth in Version 4.0 of the Mapping Specification, namely, that of allowing the radix of the representation to be specified as ten, by a representation clause (attribute definition clause in Ada 9X); the current bias towards a radix of two would persist as the default. We abandoned this idea because it benefits primarily IS applications, which are now addressed by separate features. This feature would integrate well with the rest of our proposal, were it to be restored. Its primary semantic effect would be to exclude range bounds that are powers of ten from necessarily being included within the bounds of the type. It would permit bounds like 999_999_999.99 in declarations to be written instead as 1_000_000_000.00 or even as 0.01E+12, which is close to the ``digits 11'' shorthand provided by J. Since it would be of no particular benefit outside of IS applications, and since it is only a minuscule part of the totality of additional support required in the IS area, it is not worth adding to general fixed point. A suggestion for a new representation attribute, T'MACHINE_SATURATES, was made as this version of the MS and MR were being finalized. Some digital signal processors do not signal a fault or wrap around upon overflow but instead saturate at the most positive or negative value of the base type. It could be useful to detect and describe that behavior by means of the suggested attribute. We leave this as a subject for future exploration, perhaps in conjunction with similar refinements of T'MACHINE_OVERFLOWS suggested (for floating-point types) by IEEE arithmetic. S.L.3. Elementary Functions For a general rationale for the elementary functions, the reader is referred to [Dritz 91a]. These functions are critical to a wide variety of scientific and engineering applications written in Ada. They have been widely provided in the past as vendor extensions with no standardized interface and with no guarantee of accuracy. These impediments to portability and to analysis of programs are removed by their inclusion in the Numerics Area features of Ada 9X, in support of Requirement R11.1-A(1). The elementary functions are provided in Ada 9X by a new predefined generic package, GENERIC_ELEMENTARY_FUNCTIONS, which is a very slight variation of that proposed in ISO DIS 11430, ``Proposed Standard for a Generic Package of Elementary Functions for Ada.'' The Ada 9X version capitalizes on a feature of Ada 9X (use of T'BASE as a type mark in declarations) not available in the environment (Ada 83) to which the DIS is targeted. The feature has been used here to declare the formal parameter types and result types of the elementary functions to be the base type of the generic formal type, eliminating the possibility of range violations at the interface. The same feature can be used for local variables in the body of GENERIC_ELEMENTARY_FUNCTIONS (if it is programmed in Ada) to avoid spurious exceptions caused by range violations on assignments to local variables of the generic formal type. Thus, there is no longer a need to allow implementations to impose the restriction that the generic actual type in an instantiation must be a base type; implementations must allow a range-constrained subtype as the generic actual type, and they must be immune to the potential effects of the range constraint. An implementation that accommodates signed zeros (i.e., one for which FLOAT_TYPE'SIGNED_ZEROS is TRUE) is required to exploit them in several important contexts, in particular the signs of the zero results from the ``odd'' functions SIN, TAN, and their inverses and hyperbolic analogs, at the origin, and the sign of the half-cycle result from ARCTAN and ARCCOT; this follows a recommendation, in [Kahan 87], that provides important benefits for complex elementary functions built upon the real elementary functions, and for applications in conformal mapping. Exploitation of signed zeros at the many other places where elementary functions can return zero results is left implementation defined, since no obvious guidelines exist for these cases. S.L.4. Primitive Functions For a general rationale for the primitive functions, the reader is referred to [Dritz 91b]. They are required for high-quality, portable, efficient mathematical software such as is provided in libraries of special-function routines, and some are of value even for more mundane uses, like I/O conversions and software testing. The primitive functions are provided in support of Requirement R11.1-A(1). The casting of the primitive functions as attributes, rather than as functions in a generic package (e.g., GENERIC_PRIMITIVE_FUNCTIONS, proposed as a separate ISO standard for Ada 83), befits their primitive nature and allows them to be used as components of static expressions, when the arguments are static. MAX and MIN are particularly useful in this regard, since they are sometimes needed in expressions in numeric type declarations, for example to ensure that a requested precision is limited to the maximum allowed. The functionality of SUCCESSOR and PREDECESSOR, from the proposed GENERIC_PRIMITIVE_FUNCTIONS standard, is provided by extending the existing attributes SUCC and PRED to floating-point types. Note that T'SUCC(0.0) returns the smallest positive number, which is a denormalized number if T'DENORM is TRUE and a normalized number of T'DENORM is FALSE; this is equivalent to the ``fmin'' derived constant of LCAS [ISO/IEC 91]. Most of the other constants and operations of LCAS are provided either as primitive functions or other attributes in Ada 9X; those that are absent can be reliably defined in terms of existing attributes. The proposed separate standard for GENERIC_PRIMITIVE_FUNCTIONS stated that the primitive functions accept and deliver machine numbers, which implies that they never receive arguments in extended registers. Conceptually, that requirement could be removed in Ada 9X, though we are by no means certain that it is wise to do so, and we are still investigating the issue. If the primitive functions always receive machine numbers, then, for example, the result of T'EXPONENT(X) can be assumed to be in the range T'MIN(T'EXPONENT(T'PRED(0.0)), T'EXPONENT(T'SUCC(0.0))) .. T'MAX(T'EXPONENT(T'BASE'FIRST), T'EXPONENT(T'BASE'LAST)) and an integer type with that range can be declared to hold any value that can be returned by T'EXPONENT(X). (These bounds accommodate the fact that T'EXPONENT of a denormalized number returns a value less than T'MACHINE_EMIN, and they also accommodate implementations that may use radix-complement representation.) However, if we define the primitive functions so that they must accept the range of arguments that they might receive in extended registers, then we cannot bound the results of T'EXPONENT(X) by properties of the implementation, since the range of extended registers is nowhere reflected in such properties. In that case, one would be advised to use STANDARD.INT_CLASS for the type of a variable used to hold values delivered by the EXPONENT attribute. If extended range and precision are allowed in the arguments of the primitive functions, T'SUCC, T'PRED, and T'ADJACENT will, nevertheless, deliver machine numbers of the type T. One primitive function that will be allowed to receive an argument in an extended register is T'MACHINE(X), an attribute that was not represented by a function in GENERIC_PRIMITIVE_FUNCTIONS. This attribute exists specifically to give the programmer a way to discard excess precision if the implementation happens to be using it, and if the details of an algorithm are sensitive to its use. It also has the side effect of guaranteeing that a value outside the range T'BASE'FIRST .. T'BASE'LAST is not propagated. The attribute is a no-op in implementations that do not use extended registers. Its definition allows efficient implementations on representative hardware. Thus, on IEEE hardware, it may be implemented simply by storing an extended register into the shorter storage format of the target type T; on implementations having types with extra precision but not extra exponent range, it may be implemented by storing the high-order part of a register pair into storage. Overflow may occur in the former case but cannot occur in the latter; in both cases, values slightly outside the range T'BASE'FIRST .. T'BASE'LAST can escape overflow by being rounded to an endpoint of the range. (This actually happens on IEEE hardware.) The related primitive function T'MODEL(X) also accepts its argument in an extended register and shortens the result to a machine number. In this case, however, the loss of low-order digits is potentially more severe. The result is guaranteed to be a model number within the range -T'MODEL_LARGE .. T'MODEL_LARGE. This function returns its floating-point argument perturbed to a nearby model number (if it is not already a model number) in the same way that is allowed for operands and results of the predefined arithmetic operations (see S.L.1.5), so it introduces no more error than what is already allowed. By forcing a quantity to a nearby model number, it guarantees that subsequent arithmetic operations and comparisons with the number will experience no further perturbation and will therefore produce predictable and consistent results. For example, suppose we have a situation like if X > 1.0 then ... -- several references to X ... end if; in which X can be extremely close to 1.0. If X is in the first model interval above 1.0, the semantics of floating-point arithmetic allow the references to X inside the if statement to behave as if they had the value 1.0, seemingly contradicting the condition that allows entry there, and multiple references could behave as if they yielded slightly different values. If this is intolerable, then one can write Y := T'MODEL(X); if Y > 1.0 then ... -- several references to Y ... end if; The value of Y can be no worse than some value already allowed for the result of the operation that produced X. If the if statement is entered, we are guaranteed that Y exceeds 1.0 and that all references to it yield the same value. If X has a value slightly exceeding 1.0, the if statement might not be entered, but that was also true in the earlier example. In implementations in which the model numbers coincide with the machine numbers, T'MODEL reduces to T'MACHINE, and if in that case extended registers are not being used, both are no-ops. S.L.5. Possible Future Additions It has been suggested that Ada 9X should be positioned to compete better with certain kinds of numeric applications written in Fortran 90, and even in C, by adding at least rudimentary facilities for random number generation and for complex arithmetic. Any such facilities that we may propose in the near future, if the Ada community concurs that they are needed and should be provided, will take the form of optional predefined packages or generic packages. For random number generation, we would probably propose only a uniform random number capability; perhaps the ability to have multiple generators; subprograms for saving and setting the seed(s) and for initializing the generator (or a generator) to a repeatable (but implementation-dependent), or a random (time-dependent), state without the use of seeds; and subprograms to fill a whole array with random numbers in one call. Neither algorithms nor statistical tests would be prescribed. For complex arithmetic, we would probably propose only a generic package exporting a visible Cartesian complex type (whose real and imaginary parts are parameterized by an imported floating-point type), the appropriate arithmetic operators for the complex type, and a small set of complex elementary functions (e.g., those in Fortran 90). This is a small subset of the capabilities on which the SIGAda Numerics Working Group has been working for several years. It is unlikely that the proposal would include accuracy requirements or requirements for freedom from spurious exceptions (those that might be produced by relatively ``naive'' implementations when an intermediate result overflows but the components of the final result do not), since practical requirements in these areas have not yet been determined and agreed upon by researchers. References [Baker 91] T. Baker. Stack-Based Scheduling of Realtime Processes. The Real-Time Systems Journal 3(1):67-100, March, 1991. [Brinkley 89] B. Sprunt, L. Sha, and J. Lehoczky. Aperiodic Task Scheduling for Hard-Real-Time-Systems. Real-Time Systems 1(1):27-60, June, 1989. [Brosgol 92] B. M. Brosgol, D. E. Emery and R. I. Eachus. Decimal Arithmetic in Ada. In Proceedings of the Ada Europe International Conference, Amsterdam, June 1-5, 1992. 1992. [Brown 81] W. S. Brown. A simple but realistic model of floating-point computation. TOMS 7(4):445-480, December, 1981. [Coffman 73] Coffman and Denning. Operating Systems Theory. Prentice-Hall, Englewood Cliffs, New Jersey. 1973. [Dewar 90] R. B. K. Dewar. The Fixed-Point Facility in Ada. Technical Report Special Report SEI-90-SR-2, Software Engineering Institute, Pittsburgh, PA, February, 1990. [DoD 90] Ada 9X Requirements Office of the Under Secretary of Defense for Acquisition, Washington, D.C., 1990. [Dritz 91a] K. W. Dritz. Rationale for the Proposed Standard for a Generic Package of Elementary Functions for Ada. Ada Letters XI(7):47-65, Fall, 1991. [Dritz 91b] K. W. Dritz. Rationale for the Proposed Standard for a Generic Package of Primitive Functions for Ada. Ada Letters XI(7):82-90, Fall, 1991. [Emery 91] D. E. Emery. Electronic mail message `Why do we need scale?', 6 Feb 91. 1991 [Emery 92] David E. Emery. MITRE Project 6890 - Ada Decimal Arithmetic: Interim Report. Technical Report, MITRE Corporation, Bedford, MA, January, 1992. [Froggatt 87] T. Froggatt. Fixed-point Conversion, Multiplication, & Division, in Ada(R). Ada Letters 7(1), January-February, 1987. [Hilfinger 90] P. N. Hilfinger. Implementing Ada Fixed-point Types Having Arbitrary Scales. Technical Report Report No. UCB/CSD 90/#582, University of California, Berkeley, CA, June, 1990. [IEEE 85] Standard for Binary Floating-Point Arithmetic ANSI/IEEE Std. 754-1985; ISO/IEC 559:1989 edition, IEEE, 1985. [IEEE 87] Standard for Radix-Independent Floating-Point Arithmetic ANSI/IEEE Std. 854-1987 edition, 1987. [ISO/IEC 91] Information technology -- Programming languages -- Language compatible arithmetic DIS 10967 edition, ISO/IEC, 1991. [Kahan 87] W. Kahan. Branch Cuts for Complex Elementary Functions, or Much Ado About Nothing's Sign Bit. The State of the Art in Numerical Analysis. Clarendon Press, 1987, Chapter 7. [Sha 90a] L. Sha, and J. B. Goodenough. Real-Time Scheduling Theory and Ada. Computer 23(4):53-62, April, 1990. [Sha 90b] L. Sha, R. Rajkumar, and J. P. Lehoczky. Priority Inheritance Protocols -- An Approach to Real-Time Synchronization. IEEE Transactions on Computers C-39(9), September, 1990. [Wichmann 91a] B. A. Wichmann. Notes on Ada Numerics for Ada 9X. Technical Report NPL Technical Memorandum DITC 53/91, National Physical Laboratory, July, 1991. [Wichmann 91b] B. A. Wichmann and T. Froggatt. Fixed Point and Decimal in Ada 9X. October, 1991 Index Abort deferred during initialization or finalization G-4 immediate H-4 Active priority (of a task) H-1 Allocator user-defined G-3 ANY_PRIORITY (new predefined type) H-1 ASYNCHRONOUS (new predefined pragma) I-2 ATOMIC (new predefined pragma) G-5 ATTACH_HANDLER (new predefined pragma) G-2 Base priority (of a task) H-1 CALLER (new predefined attribute) G-6 Calling stub I-2 Canonical form definition L-1 of denormalized floating-point machine numbers L-1 of floating-point model numbers L-1 of normalized floating-point machine numbers L-1 Ceiling priority (of a protected record) H-2 Checks suppression of all H-5 COMMUNICATION_ERROR (new predefined exception) I-2 COMMUNICATION_SUPPORT (new predefined package) I-2 Configuration of distributed Ada system I-3 Controlled type (supporting initialization and finalization) G-4 DENORM (new predefined attribute) L-1 Denormalized numbers L-1 Dispatching policy (for tasks) H-2 Distribution model for Ada programs I-1 DYNAMIC_PRIORITY_SUPPORT (new predefined package) H-3 Elaboration preelaboration of library packages G-4 Elementary functions L-3 ELEMENTARY_FUNCTIONS_EXCEPTIONS (predefined package) L-3 Entry queuing policy H-3 Finalization G-4 invoked on UNCHECKED_DEALLOCATION G-4 Fixed point L-2 Fixed-point accuracy requirements L-2 arithmetic model L-2 attributes of model numbers eliminated L-3 model numbers eliminated L-3 type declarations L-2 Floating-point L-1 accuracy requirements L-1 arithmetic model L-1 attributes of machine numbers L-1 attributes of model numbers L-1 denormalized machine numbers L-1 machine numbers L-1 model numbers L-1 type declarations L-2 GENERIC_ELEMENTARY_FUNCTIONS (predefined generic package) L-3 Immediate abort H-4 Integer type unsigned G-4 Interrupt G-1 binding to protected procedures G-1 binding to task entries G-2 Interrupt delivery G-1 Interrupt generation G-1 INTERRUPT_MANAGEMENT (new predefined package) G-1 INTERRUPT_PRIORITY (new predefined type) H-1 Leaving a scope G-4 Machine code insertions G-1 Model numbers L-1 Operand matching rules apply to types elaborated in multiple partitions I-2 Partition active I-1 dynamic binding of I-2 elaboration of I-2 passive I-1 PARTITION_ID (new predefined attribute) I-2 Per-task data See also task attributes PREELABORATE (new predefined pragma) G-4 Primitive functions (new predefined floating-point attributes) L-4 Priority H-1 active H-1 base H-1 ceiling H-2 model for real-time systems H-1 QUEUING_POLICY (new predefined pragma) H-3 Remote subprogram call I-1 REMOTE_CALL_INTERFACE (new predefined pragma) I-2 RESERVED_INTERRUPT (new predefined exception) G-1 ROOT_STORAGE_POOL (new predefined type) G-3 Scope leaving a G-4 Shared variables user control over G-5 SHARED_PASSIVE (new predefined pragma) I-2 Signed zeros L-1, L-3 SIGNED_ZEROS (new predefined attribute) L-1 SIMPLE_TASKING (new predefined pragma) H-5 STORAGE_POOL (new predefined attribute) G-3 SUPPRESS_ALL (new predefined pragma) H-5 Task attributes G-6 Task dispatching policy H-2 TASK_IDENTIFICATION (new predefined package) G-6 Time type H-5 UNSIGNED_INTEGERS (new predefined package) G-4 User-defined allocator G-3 User-defined finalization G-4 User-defined initialization G-4 User-provided communication subsystem I-2 VOLATILE (new predefined pragma) G-5 Table of Contents Specialized Needs Annexes [new] G-1 G. Systems Programming G-1 G.1. Access to Machine Operations G-1 G.1.1. Implementation Requirements G-1 G.1.2. Documentation Requirements G-1 G.2. Interrupts G-1 G.2.1. Specification of the Package INTERRUPT_MANAGEMENT G-1 G.2.2. Static Binding G-2 G.2.3. Interrupt Task Entries G-2 G.2.4. Implementation Requirements G-2 G.2.5. Documentation Requirements G-2 G.2.6. Metrics G-2 G.3. User-Defined Allocators G-3 G.3.1. STORAGE_POOL Representation Clause G-3 G.3.2. Interactions with STORAGE_SIZE Attribute G-3 G.3.3. MAX_STORAGE_SIZE Attribute G-3 G.3.4. Implementation Requirements G-3 G.3.5. Documentation Requirements G-3 G.4. User-Defined Finalization G-4 G.5. Elaboration Control G-4 G.5.1. Implementation Requirements G-4 G.5.2. Documentation Requirements G-4 G.6. Unsigned Integers G-4 G.6.1. Unsigned Integer Types G-5 G.6.2. Boolean and Shift Operators G-5 G.6.3. Relational and Arithmetic Operators G-5 G.6.4. Implementation Requirements G-5 G.6.5. Documentation Requirements G-5 G.7. Shared Variable Control G-5 G.8. Task Identification H-1 G.8.1. Package Specification of TASK_IDENTIFICATION H-1 G.8.2. CALLER Attribute of an Entry H-1 G.8.3. Documentation Requirements H-1 H. Real-Time Systems H-1 H.1. Priorities H-1 H.1.1. Priority Subtypes H-1 H.1.2. Base and Active Priorities H-1 H.1.3. Base Priority Specification H-1 H.2. Task Dispatching Model H-2 H.2.1. The Conceptual Model H-2 H.2.2. The Default Dispatching Policy H-2 H.2.3. Documentation Requirements H-2 H.3. Ceiling Priorities H-2 H.3.1. Implementation Requirements H-2 H.3.2. Documentation Requirements H-3 H.4. Entry Queuing Policies H-3 H.4.1. FIFO Queuing H-3 H.4.2. Priority Queuing H-3 H.4.3. Other Queuing Policies H-3 H.5. Dynamic Priorities H-3 H.5.1. Documentation Requirements H-3 H.5.2. Metrics H-3 H.6. Immediate Abort H-4 H.6.1. Documentation Requirements H-4 H.6.2. Metrics H-4 H.7. Processor Time Accounting H-4 H.7.1. Semantic Model H-4 H.7.2. CPU_TIME_ACCOUNTING Package H-4 H.7.3. Implementation Requirements H-4 H.7.4. Metrics H-4 H.8. Simple Tasking Model I-1 H.8.1. Implementation Requirements I-1 H.8.2. Documentation Requirements I-1 H.9. Monotonic Time I-1 H.9.1. MONOTONIC Package Specification I-1 H.10. Delay Accuracy I-1 H.11. Suppression of All Checks I-1 H.11.1. Documentation Requirements I-1 I. Distributed Systems I-1 I.1. Introduction I-1 I.2. Model for Distributing an Ada 9X System I-1 I.3. Categorization Pragmas I-2 I.4. Remote Subprogram Calls I-2 I.5. Post-Compilation Partitioning I-2 I.6. Dynamic Binding Between Partitions I-2 I.7. Configuring and Elaborating a Distributed System I-3 I.8. User-Provided Communication Subsystem I-3 I.8.1. Package COMMUNICATION_SUPPORT I-3 I.9. The Interaction Between the Stubs and the UPCS J-1 I.9.1. Remote Subprogram Stub Generation J-1 I.9.2. Remote Subprogram Calls J-1 I.9.3. The Operation of the UPCS J-1 J. Information Systems J-1 J.1. Decimal Arithmetic and Representation J-2 J.1.1. Decimal Types J-2 J.1.1.1. Decimal Types as a Special Kind of Fixed Point J-2 J.1.1.2. Decimal Subtypes J-2 J.1.1.3. Operations of Decimal Types J-2 J.1.1.4. Generic Formal Decimal Types J-2 J.1.2. Package DECIMAL J-2 J.1.3. Decimal Computation J-3 J.1.3.1. Capacity Requirements J-3 J.1.3.2. Control over Truncation and Rounding J-3 J.1.3.3. Generic Procedure GENERIC_DIVIDE J-3 J.1.4. Internal Decimal Representation J-3 J.1.5. External Decimal Representation J-3 J.1.5.1. T'VALID J-3 J.1.5.2. T'INPUT J-4 J.1.5.3. T'OUTPUT J-4 J.1.5.4. T'EDIT J-4 J.1.6. Locale-Specific Characteristics J-5 J.2. Character Set Definition and Manipulation J-5 J.2.1. CHARACTER and WIDE_CHARACTER Handling Package J-5 J.2.2. ISO 8859 Coded Character Set Packages J-6 J.2.3. Other Character Packages J-6 J.2.3.1. EBCDIC Package J-6 J.2.3.2. Personal Computer Character Set Package J-6 J.3. String Handling J-6 J.4. Interface Facilities J-7 J.4.1. Pragma LAYOUT J-7 J.4.2. C_TYPES Package J-7 J.4.3. Compliance Requirements J-7 J.4.4. Other Interfacing Facilities J-7 J.4.5. Relationship with other Annexes K-1 K. Safety and Security K-1 K.1. Erroneous Execution Situations K-1 K.2. Bounded Error Situations K-1 K.3. Understandable Object Code K-1 K.4. Elaboration Order Of Library Units K-1 K.5. Mathematical Precise Evaluation Of Numerics K-1 K.6. Parameter Passing Mechanism Undefined K-1 K.7. Ensure use of Validatable Compiler K-1 K.8. `Certified' compilers K-1 K.9. Certified run-time system K-1 K.10. Defined Storage Allocation Scheme K-1 K.11. Storage Reuse K-1 K.12. Execution Timing K-1 K.13. Allow User To Write Assertions L-1 K.14. Define Provable Subset L-1 L. Numerics Area L-1 L.1. Semantics of Floating-Point Arithmetic L-1 L.1.1. Floating-Point Machine Numbers L-1 L.1.2. Attributes of Floating-Point Machine Numbers L-1 L.1.3. Floating-Point Model Numbers L-1 L.1.4. Attributes of Floating-Point Model Numbers L-1 L.1.5. Accuracy of Floating-Point Operations L-1 L.1.6. Floating-Point Type Declarations L-2 L.2. Semantics of Fixed-Point Arithmetic L-2 L.2.1. Fixed-Point Type Declarations L-2 L.2.2. Accuracy of Fixed-Point Operations L-2 L.2.3. Attributes of Fixed-Point Numbers L-3 L.3. Elementary Functions L-3 L.4. Primitive Functions S-1 S. Rationale S-1 S.G. Systems Programming Area S-1 S.G.1. Access to Machine Operations S-1 S.G.2. Interrupts S-1 S.G.2.1. Specification of the Package INTERRUPT_MANAGEMENT S-2 S.G.2.2. Static Binding S-2 S.G.2.3. Interrupt Task Entries S-2 S.G.3. User-Defined Allocators S-2 S.G.3.1. STORAGE_POOL Representation Clause S-3 S.G.3.2. Interactions with STORAGE_SIZE Attribute S-3 S.G.3.3. MAX_STORAGE_SIZE Attribute S-4 S.G.4. User-Defined Finalization S-4 S.G.5. Elaboration Control S-5 S.G.6. Unsigned Integers S-5 S.G.6.1. Unsigned Integer Types S-5 S.G.7. Shared Variable Control S-6 S.G.8. Task Identification S-6 S.G.8.1. Package Specification of TASK_IDENTIFICATION S-6 S.G.8.2. CALLER Attribute of an Entry S-6 S.H. Real-Time Systems S-6 S.H.1. Priorities S-6 S.H.1.1. Priority Subtypes S-6 S.H.1.2. Base and Active Priorities S-7 S.H.1.3. Base Priority Specification S-7 S.H.2. Task Dispatching Model S-7 S.H.2.1. The Conceptual Model S-7 S.H.2.2. The Default Dispatching Policy S-7 S.H.3. Ceiling Priorities S-7 S.H.4. Entry Queuing Policies S-8 S.H.4.1. FIFO Queuing S-8 S.H.4.2. Priority Queuing S-8 S.H.4.3. Other Queuing Policies S-8 S.H.5. Dynamic Priorities S-9 S.H.6. Immediate Abort S-9 S.H.7. Processor Time Accounting S-10 S.H.7.1. Semantic Model S-10 S.H.7.2. CPU_TIME_ACCOUNTING Package S-10 S.H.8. Simple Tasking Model S-11 S.H.9. Monotonic Time S-13 S.H.9.1. MONOTONIC Package Specification S-13 S.H.10. Delay Accuracy S-13 S.H.11. Suppression of All Checks S-13 S.I. Distributed Systems S-13 S.I.1. Introduction S-13 S.I.2. Model for Distributing an Ada 9X System S-14 S.I.3. Categorization Pragmas S-14 S.I.4. Remote Subprogram Calls S-14 S.I.5. Post-Compilation Partitioning S-14 S.I.6. Dynamic Binding Between Partitions S-16 S.I.7. Configuring and Elaborating a Distributed System S-18 S.I.8. User-Provided Communication Subsystem S-18 S.I.8.1. Package COMMUNICATION_SUPPORT S-18 S.I.9. The Interaction Between the Stubs and the UPCS S-19 S.J. Information Systems S-19 S.J.1. Decimal Arithmetic and Representation S-20 S.J.1.1. Decimal Types S-20 S.J.1.2. Package DECIMAL S-20 S.J.1.3. Decimal Computation S-20 S.J.1.4. Internal Decimal Representation S-20 S.J.1.5. External Decimal Representation S-20 S.J.1.6. Locale-Specific Characteristics S-21 S.J.2. Character Set Definition and Manipulation S-21 S.J.2.1. CHARACTER and WIDE_CHARACTER Handling Package S-21 S.J.2.2. ISO 8859 Coded Character Set Packages S-21 S.J.2.3. Other Character Packages S-21 S.J.3. String Handling S-21 S.J.4. Interface Facilities S-21 S.J.4.1. Pragma LAYOUT S-21 S.J.4.2. C_TYPES Package S-21 S.J.4.3. Compliance Requirements S-21 S.J.4.4. Other Interfacing Facilities S-21 S.J.4.5. Relationship with other Annexes S-21 S.J.5. Ada 9X Requirements for Information Systems S-22 S.J.6. Detailed Requirements for Decimal Arithmetic and S-22 Representation S.J.6.1. Computation S-22 S.J.6.2. Style S-22 S.J.6.3. Performance and Capacity S-22 S.J.6.4. Representation S-22 S.J.6.5. Other Requirements S-22 S.J.7. Decimal Computation through a Private Discriminated Type S-23 S.J.7.1. Package DECIMAL_SUPPORT S-23 S.J.7.2. Semantics of Operations S-23 S.J.7.3. Evaluation of Private Discriminated Type Approach to S-24 Decimal S.J.8. Using Fixed Point for Decimal Arithmetic S-24 S.J.8.1. Summary of Main Issues S-24 S.J.8.2. Alternative Fixed-Point Approaches S-24 S.J.8.3. Evaluation of Decimal Fixed Point Approach S-25 S.J.8.4. Summary of Decimal Fixed Point Advantages and S-25 Disadvantages S.K. Safety and Security S-26 S.L. Numerics Annex S-26 S.L.1. Semantics of Floating-Point Arithmetic S-26 S.L.1.1. Floating-Point Machine Numbers S-26 S.L.1.2. Attributes of Floating-Point Machine Numbers S-26 S.L.1.3. Floating-Point Model Numbers S-27 S.L.1.4. Attributes of Floating-Point Model Numbers S-28 S.L.1.5. Accuracy of Floating-Point Operations S-28 S.L.1.6. Floating-Point Type Declarations S-29 S.L.2. Semantics of Fixed-Point Arithmetic S-29 S.L.3. Elementary Functions S-31 S.L.4. Primitive Functions S-31 S.L.5. Possible Future Additions -1 References -1 Index I-1 List of Figures Figure J-1: Relationships Among Decimal Representations J-3 List of Tables Table J-1: Definition of T'VALID J-3 Table J-2: Rules for T'OUTPUT of ZONED Representations J-4