Unsequenced functions

Étienne Alepins (Thales Canada)

Jens Gustedt (INRIA France)

org: ISO/IEC JCT1/SC22/WG14 document: N2956
target: IS 9899:2023 version: 5
date: 2022-04-08 license: CC BY

Revision history

Paper number Title Changes
N2477 Const functions Initial version
N2539 Unsequenced functions Supersedes N2477 WG14 poll: 15-0-1
new wording (extracted from N2522)
no application to the C library
N2825 Unsequenced functions v3 Supersedes N2539
no attribute verification imposed
support for function pointers
optional text for inclusion of lambdas
N2887 Unsequenced functions v4 Supersedes N2825 refactoring of the properties
regroup properties in general text
attach properties to evaluations instead of syntax
add a sentence to the wording for composite types
editorial adjustments are collected in a note to the editors at the end
emphasize on the relationship with existing implementations
withdraw the special treatment of call_once
N2956 Unsequenced functions v5 Supersedes N2887 restrict attributes to type attributes (1/14/7)
only provide [[unsequenced]] and [[reproducible]] (10/2/10 and 10/3/9)
remove provisions for lambdas
talk about lifetime and synchronization, instead of storage duration
use the based on relation (6.7.3.1) for pointer parameters
restrict composability of type attributes to standard attributes

Introduction

Multiple compilers on the market support the concept of const and pure functions (so coined by GCC), also sometimes called no-side-effects functions, for C and other programming languages. In that terminology, a const function is a function that does not depend on or modify the global context of the program: it only uses its input parameters to modify its return value (GCC doesn’t allow a const function to have output parameters). To avoid ambiguities with the properties of the const qualifier, the new attribute unsequenced is proposed which defines that a call to such a function can be executed unsequenced with respect to unrelated evaluations, namely as soon as its input is available and as late as its result is used by another statement. Although widely supported, this concept is currently absent from the C standard. Unsequenced property can be gradually built from sub-properties:

The following two attributes then combine these to approximate the GCC attributes pure and const

It was proposed to add support to all those functions attributes in C23 such that functions that aren’t unsequenced can still benefit some optimizations, but WG14 was not in favor of this.

Note that this proposal avoids using the term pure for the features, because that term has a quite confusing history in programming languages and designates several closely related concepts. A good overview can be found in a failed attempt for C++ to unify that terminology and to introduce a [[pure]] attribute for them.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0078r0.pdf

Use cases

Unsequenced functions can be leveraged in multiple ways to enhance performance and reduce code size. Optimization is key in a compiler: it allows adding more features to a given system or conversely selecting lower-power CPUs to perform the same tasks.

Global variables reload

Model-based systems define software using a series of graphical blocks such as confirmators, delays, digital filters, etc. Code generators are often used to produce source code. Some of these generators use global variables to implement the data flow between models. Each block is implemented by a call to a library function. Let’s take a model with two confirmator blocks:

extern bool bInput1, bInput2, bInit, bInitState1, bInitState2;

extern int uConfTime1, uConfTime2;

void foo() {

    static int uCounter1, uCounter2;

    ...

    bOutput1 = CONF(bInput1, uConfTime1, bInit, bInitState1, &uCounter1);

    bOutput2 = CONF(bInput2, uConfTime2, bInit, bInitState2, &uCounter2);

    ...

}

where all these are global variables. bInit represents the initialization state of the whole system (for CONF, it resets the counter output parameter to zero). Since the compiler must assume CONF may be modifying bInit (through a direct access to this global variable), it is forced to load it’s value once before the first CONF call and a second time before the second CONF call. The second load can be avoided if the compiler is told CONF is an unsequenced function. It will then re-use bInit value from a register or stack, thus reducing the number of assembly instructions, i.e. potentially saving code size and performance. Code generators are not always flexible enough to put bInit in a local variable before calling blocks in order to work around compilers not supporting unsequenced functions.

In large model-based systems, there can be hundreds of such optimization opportunities, thus having a major overall positive impact.

Function call merge

Multiple use cases of potentially unsequenced function exist in the standard library, taking for example the cos <math.h> function:

if ((cos(angle1) > cos(angle2)) || (cos(angle1) > cos(angle3))) {
    ...
}

In this case, the compiler will invoke four times the cos function, with three different parameters. If cos would be declared unsequenced, the compiler could have merged the first and the third calls, reusing the result of the first call for the third, thus reducing CPU throughput and code size.

Provided that the rounding mode does not change, a large portion of the <math.h> functions fit the unsequenced objective for most of their input domain. However, in case of errors, these functions may also modify errno or the floating-point environment, which contradicts the strict unsequenced property. Special care has to be taken to allow the functions to be declared unsequenced and in a manner where additional constraints can be spelled out. These properties depend on implementation properties, in particular on the behavior of the functions under different rounding modes, and this paper does not propose any attribute addition to standard library functions, yet.

Nevertheless, we provide an example (Example 3 in the proposed text) how calls of such a library function (here sqrt) can be constrained such that the unsequenced attribute can be applied.

Prior art

GCC

int square(int) __attribute__ ((const));
int hash(char*) __attribute__ ((pure));

The const attribute is implemented since GCC 2.5 and pure since GCC 2.96.

GCC distinguishes const from pure:

Below, we will propose features that extend these specifications, namely unsequenced and reproducible. These extensions are done in a way that the implementations of the GCC attributes are valid implementations, in the sense that they don’t check for validation of the requested properties, anyhow, and that the wider permissions that we give still make the targeted optimizations valid.

The first important difference to the GCC features is that our proposed attributes allow that the functions have pointer parameters that are not const qualified. Such pointed-to parameters are accounted semantically the same as the return value of the function. Thus the following functions semantically may provide the same functionality.

double f(double)                                   [[unsequenced]];
void   g(double[restrict static 1])                [[unsequenced]];
double h(double const[restrict static 1])          [[unsequenced]];

Where f and h communicate the result of a computation as a return value, g may store it in the first element of the mutable pointed-to array; f receives the input as a value parameter, g and h use the input value of the first element of the pointed-to array of the parameter. The restrict qualification for g enforces that only a pointed-to object can be used by a call that is not be accessed by g through other means than the parameter; for h it enforces that the object must be such that it is possibly read but never modified. If the implementation can guarantee that no global objects of type double are temporarily changed during g or h through other means (even if restored at the end), the restrict qualification can be omitted from the interface.

The proposed [[unsequenced]] attribute is the same as GCC const attribute, provided the function has no pointer or array parameters.

GCC’ pure attribute poses more difficulties, because there is no unanimous notion of what constitutes a pure function, because the documentation of the feature is poor, and their interface relies on undefined behavior (for non-const qualified pointer targets) where it shouldn’t. We model an extension of the GCC attribute by [[reproducible]] which combines the properties of being idempotent and effectless.

The proposed [[reproducible]] attribute is the same as GCC pure attribute, provided all pointer or array parameters have const qualified target types.

LLVM Clang

Clang supports all GCC attributes.

WindRiver Diab 5.x

#pragma pure_function function({global | n}, ...),  ...
#pragma no_side_effects function({global | n}, ...), ...

These pragma exist in Diab compiler since at least version 4.4. Diab uses pure_function similar to GCC const and no_side_effects similar to GCC pure.

In addition to these basic features, Diab allows functions to still access some specially marked global variable. This is achieved by passing parameters to the pragma. It is the only compiler to our knowledge that goes beyond the GCC const/pure concept. As proposed in N2644, this could be used to solve the errno and floating-point environment challenge of standard library functions. Nevertheless, we think that the general idempotent and unsequenced properties are currently more widely supported and so we concentrate this paper on these.

Diab 5.x compiler is a WindRiver proprietary compiler, not based on GCC or LLVM. It is in particular used in the highly popular WindRiver VxWorks RTOS for aerospace, automotive and industrial. It supports C99 (C11 experimental) and C++14 (except thread_local, some Unicode types and some library functions related to atomic, chron/signal, thread, filesystem and localization).

Not to be confused with Diab 7.x which is a branch based on LLVM.

GreenHills MULTI

MULTI supports GCC const and pure attributes since at least version 4.0.

Ada language – GNAT

pragma Pure_Function ([Entity =>] function_LOCAL_NAME);

AdaCore GNAT Ada uses Pure_Function for unsequenced functions, i.e. functions where “the compiler can assume that there are no side effects, and in particular that two calls with identical arguments produce the same result.”

SPARK language also has the “Global” aspects which is similar. Efforts are in progress to incorporate const/pure support in the next revision of Ada language.

Fortran

Fortran allows a function to be declared PURE, meaning that the function has no internal state and produces no side effect. That is not the equivalent of GCC pure or const; it is not the same as pure because a pure function is allowed to have internal state and it is not the same as const because a PURE function may depend on global state.

The proposed combination of the stateless and effectless properties is the same as Fortran’s PURE functions, provided all pointer or array parameters have const qualified target types.

Note that such a function (stateless, effectless and no mutable pointed-to parameters) is also idempotent because the only possible effect then is the return value which deterministically depends on the input state.

D

D’s concept of functions that are marked as pure coincides with our notion of unsequenced, only that the language syntactically enforces the property by requiring that pure functions only call other pure functions.

Standardization and design choices

Const/pure compiler-specific features should be promoted to language features in order to:

Attribute syntax

Unsequenced and its sub-properties fit well into the realm of the new attribute feature of C23, because conforming code that has these attributes would remain conforming if the attribute is removed or ignored. The use of the attribute feature is clearly advantageous over pragmas (attributes have well-defined applicability) and the introduction of new keywords which suggest a semantic change.

Therefore this paper proposes to use the new attribute system to implement the properties and this is in line with prior art as GCC attributes. The names const and pure are not used because their definition is split into several sub-properties, because “unsequenced” has not the exact same meaning as GCC const and because pure has been used in the industry for different semantics (Fortran, Ada, D, Diab compiler).

Attribute enforcement (or not)

GCC 8.3.0 does not seem to raise warnings when const functions modify global variables.

AdaCore GNAT Ada allows pure/const functions to modify global variables. This is to support cases like instrumentation (e.g. call counter), table-caching implementations, debug traces, etc.

WindRiver Diab const/pure attribute allows to specify exceptions, i.e. global variables that a const/pure function might still read/write. That may be used to work around above mentioned GNAT use case. However, it is proposed not to introduce this for now.

Modifying global context includes calling non-idempotent functions. Reading global context includes calling non-unsequenced functions.

Having compiler errors or warnings when code violate its properties would avoid source code bugs. Indeed, wrongly declaring as unsequenced a function might produce functionally incorrect executable code. As other programming languages such as Fortran and D, a previous version of our paper required compiler enforcement of these properties. However, as pointed out by WG14 on August 2020 meeting, making that propagated type check might increase drastically compilation time and memory usage. Also, it would require attribute addition to existing code, some of which might be hard to change (e.g. OS headers). For these reasons, this paper proposes an “user assertion” flavor of the attributes meaning that:

Hence, this puts the verification burden on the developer. However, depending on the quality of implementation, compiler diagnostics could reduce that burden and increase safety, at least when function definitions are visible to the translation unit. Static analysis tools might also be used to cover that outside of the compilation process.

For the same reasons, this paper does not ask for attribute inference, i.e. that the compiler automatically tags a function with these properties by looking at its code.

Attributes attached to types

The attributes that this paper describes are properties of functions in context that should not get lost when a function pointer is formed. In particular, in C function calls that use function designators are specified to first convert the designator to a pointer. With an attribute that applies to a function designator, the following two function calls are semantically equivalent.

[[magpie]] double fun(double);
double (*const funp)(double) = fun;
fun(5.0);
funp(5.0);

Thus there is no possibility to transfer the knowledge of the fictive attribute [[magpie]] from the function declaration to the call in all cases. That attribute could also be applied to function pointers as in

[[magpie]] double (*const funp)(double) = fun;
double (*const [[magpie]] funp)(double) = fun;

but the description of the mechanics would become relatively complicated and counter-intuitive when for example applied to arrays of function pointers.

It has been an explicit demand from within WG14 that the unsequenced attribute be a property that can be transferred to function pointers. An application of the attribute to the type of the function, much as a qualifier to an object, seems much more appropriate and solve the problem of the transfer of the property to function pointers much more elegantly.

double fun(double) [[unsequenced]];
double (*const funp)(double) [[unsequenced]] = fun;
fun(5.0);
funp(5.0);

This clearly indicates that the property transfers with conversion to a function pointer. Also, for funp it clearly distinguishes the unsequenced property as a property of the pointed-to function from the const qualifier which is a property of the pointer funp, and not of the entity to which it points, the function fun in that case.

For the GCC const attribute, this position of the attribute after the parameter list is already supported for function declarations and function pointers. For function definitions this is novel and we introduce it for simplicity and consistency, here.

The GCC attribute can also be applied to identifiers of function type or function pointer type, and to typedef of function pointer type. WG14 voted against including these as possible positions.

Nevertheless we allow the attribute to be applied to a type specifier.

typedef double f_t [[reproducible]] (double);    // invalid, applies to identifier f_t
typedef double g_t(double) [[reproducible]];     // valid, applies to type
extern g_t f [[unsequenced]];                    // invalid, applies to identifier f
extern typeof(double(double)) [[unsequenced]] g; // valid, applies to type specifier
extern g_t [[unsequenced]] g;                    // valid, applies to type specifier

Composite types

If two declarations of the same function, say, are visible that differ with respect to a function type attribute, we want that the strongest assumptions about the properties of that function can be made by the translator. Therefore we add a new rule for composite types that make type attributes additive.

This rules allows for example to augment the knowledge about a function in a certain context, if a property such as being unsequenced can be guaranteed, there. The following example, which is also added to the proposed text, explores this possibility.

#include <math.h>
#include <fenv.h>
inline double distance (double const x[static 2]) [[reproducible]] {
    #pragma FP_CONTRACT OFF
    #pragma FENV_ROUND  FE_TONEAREST
    // We assert that sqrt will not be called with invalid arguments
    // and the result only depends on the argument value.
    extern double sqrt(double) [[unsequenced]];
    return sqrt(x[0]*x[0] + x[1]*x[1]);
}

Access and synchronization

The proposed attributes deal with relaxing sequencing properties of function calls, so we must properly name the conditions that allow these relaxations. The main property that we have to ensure is that all data flow into and out of such a function must be “through” the start and termination of the function, respectively.

Therefore we say that an object and a function call synchronize if all accesses to the object happen before or after the function call, or if they are sequenced during the function call. Here, the distinction between “happens before” and “sequenced” is important. We allow reads and writes from other threads before or after the call (provided they synchronize), but during the call only the active thread that processes the call may access the object.

Objects that naturally synchronize with a call are all objects that have a lifetime that is included in the call, for example the parameters of the function, other automatic variables, or compound literals, as long as no pointer of them escapes to another thread. Additionally, this also includes

Also note that (independent of the definitions above), if a local static variable X is changed during a function call to f, all other calls to f that access X must synchronize, otherwise they’d have a race condition. For local thread-local variables that don’t escape to other threads the situation is simpler, because all calls that are issued by the same thread are indeterminately sequenced, anyhow.

On top of this notion of synchronization between objects and function calls we then plug the notions of observed and observable stores. A store operation is observed by a call if the target is an object X that synchronizes with the call and the store operation is the last such operation that happens before the call. A store operation during a call is observable if it is the last such operation before the termination of the call and if the value that is stored is different from the value that was observed by the call.

WG21 C++ liaison

These properties seem compatible with C++. It would be beneficial for the whole C and C++ communities that they be implemented the same way in both languages. Discussions for this harmonization have started in the joint WG14/WG21 SG.

An observation has been that the emphasis of a previous version of this proposal on call_once is very C specific, this C library function is not even integrated in C++. This part has henceforth been withdrawn from the proposal but may be revived for later standardization.

Link-time optimization can achieve some of the optimization benefits these properties bring. However, in addition to lacking manually-enforced constraints addition capabilities, it is not always possible to enable LTO. For example, multiple products in safety-critical markets such as avionics, nuclear, automotive and railway explicitly disable LTO in order to keep testing credit obtained by testing individual object files. Moreover, not all linkers offer LTO.

Some differences with GCC const and pure

GCC forbids (but does not enforce) usage of pointer arguments to const functions. There are two reasons why this paper wants to support that:

The requirements for programs are less restricted for [[unsequenced]] than for GCC ((__attribute__(const))). Thus, besides the syntax, GCC’s implementation is a valid implementation of [[unsequenced]], only that it may perhaps not be able to do all optimizations that would be possible.

Programs that are specified with GCC const, can easily be switched to use [[unsequenced]], instead, and remain valid. Additional standard attributes could then be added for functions that would not fit under the GCC model.

Similarly, GCC’s pure is less restricted than the new standard attribute [[reproducible]], and an implementation of the GCC feature is, syntax aside, an implementation of the new standard attribute.

Proposed Wording

All proposed wording are additions. Because the main change is the insertion of a whole clause, the proposed changes can mostly be based on any intermediate version of ISO/IEC 9899:2023; a note to the editors at the end lists possible precautions that have to be taken to integrate the text with other possible changes in C23. The only changes that effectively would modify existing text is the following:

Add the new attributes unsequenced and reproducible to the list of standard attributes in 6.7.11.1 p2.

Add the following as a – item in 6.2.7 p2 (Compatible and composite types) to add standard type attributes:

– If one of the types has a standard attribute, the composite type also has that attribute.

Otherwise, the text to be inserted is as given in the following sections.

The new clause for function attributes

This introduces the general framework of these new function attributes. Changes are merely straightforward for the syntax feature itself and its impact on the type system.

6.7.11.6 Standard attributes for function types

Constraints

1 The identifier in a standard function type attribute shall be one of:

unsequenced reproducible

2 An attribute for a function type shall be applied to a function declarator1) or to a type specifier that has a function type. The corresponding attribute is a property of the referred function type.2) No attribute argument clause shall be present.

Description

3 The main purpose of the function type properties and attributes defined in this clause is to provide the translator with information about the access of objects by a function such that certain properties of function calls can be deduced; the properties distinguish read operations (stateless and independent) and write operations (effectless, idempotent and reproducible) or a combination of both (unsequenced). Although semantically attached to a function type, the attributes described are not part of the prototype of a such annotated function, and redeclarations and conversions that drop such an attribute are valid and constitute compatible types. Conversely, if a definition that does not have the asserted property is accessed by a function declaration or a function pointer with a type that has the attribute, the behavior is undefined.3)

4 To allow reordering of calls to functions as they are described here, possible access to objects with a lifetime that starts before or ends after a call has to be restricted; effects on all objects that are accessed during a function call are restricted to the same thread as the call and the based-on relation between pointer parameters and lvalues (6.7.3.1) models the fact that objects do not change inadvertently during the call. In the following, an operation is said to be sequenced during a function call if it is sequenced after the start of the function call4) and before the call terminates. An object definition of an object X in a function f escapes if an access to X happens while no call to f is active. An object is local to a call to a function f if its lifetime starts and ends during the call or if it is defined by f but does not escape. A function call and an object X synchronize if all accesses to X that are not sequenced during the call happen before or after the call. Execution state that is described in the library clause, such as the floating-point environment, conversion state, locale, input/output streams, external files or errno account as objects; operations that allow to query this state, even indirectly, account as lvalue conversions, and operations that allow to change this state account as store operations.

5 A function definition f is stateless if any definition of an object of static or thread storage duration in f or in a function that is called by f is const but not volatile qualified.

6 An object X is observed by a function call if both synchronize, if X is not local to the call, if X has a lifetime that starts before the function call and if an access of X is sequenced during the call; the last value of X, if any, that is stored before the call is said to be the value of X that is observed by the call. A function pointer value f is independent if for any object X that is observed by some call to f through an lvalue that is not based on a parameter of the call, then all accesses to X in all calls to f during the same program execution observe the same value; otherwise if the access is based on a pointer parameter, there shall be a unique such pointer parameter P such that any access to X shall be to an lvalue that is based on P. A function definition is independent if the derived function pointer value is independent.

7 A store operation to an object X that is sequenced during a function call such that both synchronize is said to be observable if X is not local to the call, if the lifetime of X ends after the call, if the stored value is different from the value observed by the call, if any, and if it is the last value written before the termination of the call. An evaluation of a function call5) is effectless if any store operation that is sequenced during the call is the modification of an object that synchronizes with the call; if additionally the operation is observable, there shall be a unique pointer parameter P of the function such that any access to X shall be to an lvalue that is based on P. A function pointer value f is effectless if any evaluation of a function call that calls f is effectless. A function definition is effectless if the derived function pointer value is effectless.

8 An evaluation E is idempotent if a second evaluation of E can be sequenced immediately after the original one without changing the resulting value, if any, or the observable state of the execution. A function pointer value f is idempotent if any evaluation of a function call5) that calls f is idempotent. A function definition is idempotent if the derived function pointer value is idempotent.

9 A function is reproducible if it is effectless and idempotent; it is unsequenced if it is stateless, effectless, idempotent and independent.6)

10 NOTE 1 The synchronization requirements with respect to any accessed object X for the independence of functions provide boundaries up to which a function call may safely be reordered without changing the semantics of the program. If X is const but not volatile qualified the reordering is unconstrained. If it is an object that is conditioned in an initialization phase, for a single threaded program a synchronization is provided by the sequenced before relation and the reordering may, in principle, move the call just after the initialization. For a multi-threaded program, synchronization guarantees can be given by calls to synchronizing functions of the <threads.h> header or by an appropriate call to atomic_thread_fence at the end of the initialization phase. If a function is known to be independent or effectless, adding restrict qualifications to the declarations of all pointer parameters does not change the semantics of any call. Similarly, changing the memory order to memory_order_relaxed for all atomic operations during a call to such a function preserves semantics.

11 NOTE 2 In general the functions provided by the <math.h> header do not have the properties that are defined above; many of them change the floating-point state or errno when they encounter an error (so they have observable side effects) and the results of most of them depend on execution wide state such as the rounding direction mode (so they are not independent). Whether a particular C library function is reproducible or unsequenced additionally often depends on properties of the implementation, such as implementation-defined behavior for certain error conditions.

Recommended Practice

12 If possible, it is recommended that implementations diagnose if an attribute of this clause is applied to a function definition that does not have the corresponding property. It is recommended that applications that assert the independent or effectless properties for functions qualify pointer parameters with restrict.

Forward references: errors <errno.h> (7.5), floating-point environment <fenv.h> (7.6), localization <locale.h> (7.11), mathematics <math.h> (7.12), fences (7.17.4), input/output <stdio.h> (7.21), threads <threads.h> (7.27), extended multibyte and wide character utilities <wchar.h> (7.29).


1) That is, they appear in the attributes right after the closing parenthesis of the parameter list, independently if the function type is, for example, used directly to declare a function or if it is used in a pointer to function type.

2) If several declarations of the same function or function pointer are visible, regardless whether an attribute is present at several or just one of the declarators, it is attached to the type of the corresponding function definition, function pointer object, or function pointer value.

3) That is, the fact that a function has one of these properties is in general not determined by the specification of the translation unit in which it is found; other translation units and specific run time conditions also condition the possible assertion of the properties.

4) The initializations of the parameters is sequenced during the function call.

5) This considers the evaluation of the function call itself, not the evaluation of a full function call expression. Such an evaluation is sequenced after all evaluations that determine f and the call arguments, if any, have been performed.

6) A function call of an unsequenced function can be executed as early as the function pointer value, the values of the arguments and all objects that are accessible through them, and all values of globally accessible state have been determined, and it can be executed as late as the arguments and the objects they possibly target are unchanged and as any of its return value or modified pointed-to arguments are accessed.


6.7.11.6.1 The reproducible type attribute

Description

1 The reproducible type attribute asserts that a function or pointed-to function with that type is reproducible.

Example

2 The attribute in the following function declaration asserts that two consecutive calls to the function will result in the same return value. Changes to the abstract state during the call are possible as long as they are not observable, but no other side effects will occur. Thus the function definition may for example use local objects of static or thread storage duration to keep track of the arguments for which the function has been called and cache their computed return values.

size_t hash(char const[static 32]) [[reproducible]];

6.7.11.6.2 The unsequenced type attribute

Description

1 The unsequenced type attribute asserts that a function or pointed-to function with that type is unsequenced.

2 NOTE 1 The unsequenced type attribute asserts strong properties for the such typed function, in particular that certain sequencing requirements for function calls can be relaxed without affecting the state of the abstract machine. Thereby, calls to such functions are natural candidates for optimization techniques such as common subexpression elimination, local memoization or lazy evaluation.

3 NOTE 2 A proof of validity of the annotation of a function type with the unsequenced attribute may depend on the property if a derived function pointer escapes the translation unit or not. For a function with internal linkage where no function pointer escapes the translation unit, all calling contexts are known and it is possible, in principle, to prove that no control flow exists such that a library function is called with arguments that trigger an exceptional condition. For a function with external linkage such a proof may not be possible and the use of such a function then has to ensure that no exceptional condition results from the provided arguments.

4 NOTE 3 The unsequenced property does not necessarily imply that the function is reentrant or that calls can be executed concurrently. This is because an unsequenced function can read from and write to objects of static storage duration, as long as no change is observable after a call terminates.

Example 1

4 The attribute in the following function declaration asserts that it doesn’t depend on any modifiable state of the abstract machine. Calls to the function can be executed out of sequence before the return value is needed and two calls to the function with the same argument value will result in the same return value.

bool tendency(signed char) [[unsequenced]];

Therefore such a call for a given argument value needs only to be executed once and the returned value can be reused when appropriate. For example, calls for all possible argument values can be executed during program startup and tabulated.

Example 2

5 The attribute in the following function declaration asserts that it doesn’t depend on any modifiable state of the abstract machine. Within the same thread, calls to the function can be executed out of sequence before the return value is needed and two calls to the function will result in the same pointer return value. Therefore such a call needs only to be executed once in a given thread and the returned pointer value can be reused when appropriate. For example, a single call can be executed during thread startup and the return value p and the value of the object *p of type toto const can be cached.

typedef struct toto toto;
toto const* toto_zero(void) [[unsequenced]];

Example 3

6 The unsequenced property of a function f can be locally asserted within a function g that uses it. For example the library function sqrt is in generally not unsequenced because a negative argument will raise a domain error and because the result may depend on the rounding mode. Nevertheless in contexts similar to the following function a user can prove that it will not be called with invalid arguments, and, that the floating point environment has the same value for all calls.

#include <math.h>
#include <fenv.h>
inline double distance (double const x[static 2]) [[reproducible]] {
    #pragma FP_CONTRACT OFF
    #pragma FENV_ROUND  FE_TONEAREST
    // We assert that sqrt will not be called with invalid arguments
    // and the result only depends on the argument value.
    extern typeof(sqrt) [[unsequenced]] sqrt;
    return sqrt(x[0]*x[0] + x[1]*x[1]);
}

The function distance potentially has the side effect of changing the floating point environment. Nevertheless the floating environment is thread local, thus a change to that state outside the function is sequenced with the change within and additionally the observed value is restored when the function returns. Thus this side effect is not observable for a caller. Overall the function distance is stateless, effectless and idempotent and in particular it is reproducible as the attribute indicates. Because the function can be called in a context where the floating point environment has different state, distance is not independent and thus it is also not unsequenced. Nevertheless, adding an unsequenced attribute where this is justified may introduce optimization opportunities.

double g (double y[static 1], double const x[static 2]) {
    // We assert that distance will not see different states of the floating
    // point environment.
    extern double distance (double const x[static 2]) [[unsequenced]];
    y[0] = distance(x);
    ...
    return distance(x);   // replacement by y[0] is valid
}

Question to WG14

Does WG14 want to integrate N2956 into C23?

Note to the editors

Integrating this proposal into C23 will necessitate some adjustments, depending on how other proposals a received. The possible additions concern the following: