Unsequenced functions

Étienne Alepins (Thales Canada) and Jens Gustedt (INRIA France)

org: ISO/IEC JCT1/SC22/WG14 document: N2887
target: IS 9899:2023 version: 4
date: 2021-12-31 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

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 is proposed to add support to all those functions attributes in C23 such that functions that aren’t unsequenced can still benefit some optimisations.

Note that this proposal avoids to use 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 sematically the same as the return value of the function. Thus the following two functions semantically provide the same functionality.

double f(double)                                   [[unsequenced]];
void   g(double[static 1])                         [[unsequenced]];
double h(double const[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. Observe that here pointer parameters will not alias with any global state, because unsequenced functions are not allowed to interfere with such state.

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 [[stateless,effectless]] attributes 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 or lambdas 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 function fun.

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. To ease the transition of code to the standard attribute, we also include these as possible positions.

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 [[unsequenced]](double const x[static 2]) {
    #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]);
}

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.

Similarily, 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 ATTRIBUTE_LIST_AS_VOTED to the list of standard attributes in 6.7.11.1 p2.

Where ATTRIBUTE_LIST_AS_VOTED is unsequenced combined with the subset of stateless, effectless, independent, idempotent, reproducible and noleak, as included into C23.

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

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

Otherwise, the text to be inserted is as given in the following sections. If lambdas would be incorporated into C23, the proposed attributes should also apply to them, so some adjustments would then be in order.

General description

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 and lambda types

Constraints

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

ATTRIBUTE_LIST_AS_VOTED

2 An attribute for function or lambda type shall be applied to a function declarator or a lambda expression,1) to the declaration of a function or pointer to function, to the declaration of an identifier with function type or pointer to function type, or, to a type definition that refers to a pointer to function type. The corresponding attribute is a property of the referred function or lambda type.2) No attribute argument clause shall be present.

Description

3 Each attribute defined in this clause asserts a specific property of functions or lambdas. The main purpose is to provide the translator with information about the access of objects by a function or lambda such that certain properties of function calls can be deduced; these properties distinguish read operations (stateless and independent) and modify operations (effectless and idempotent) or a combination of both (unsequenced). In the following, observable 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 side effects.

4 Although semantically attached to a function type, the attributes described are not part of the prototype of a such annotated function or lambda, and redeclarations and conversions that drop such an attribute are valid and constitute compatible types. Conversely, if a definition or lambda expression that does not have the asserted property is accessed by a function declaration, a lambda value or a function pointer (originating from a function definition or from a function literal expression) with a type that has the attribute, the behavior is undefined.3)

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

6 An evaluation of a function call is effectless if any observable side effect that happens during the call, inclusive calls into other functions or lambdas, is the modification of an object X that has one of the following properties.

  1. X is target of a pointer parameter, value capture or shadow capture of the called function or lambda.
  2. X has automatic storage duration and is a parameter of the called function or lambda, it is an explicit identifier capture of the called lambda, or its definition is reached during the call.

A function pointer value or lambda value f is effectless if any evaluation of a function call that calls f is effectless.4) A function definition or lambda expression is effectless if the derived function pointer value or lambda value is effectless.

7 A function pointer value or lambda value f is independent if for all calls to f and any lvalue conversion of an object X during such a call at least one of the following applies.

  1. X is target of a pointer parameter of the called function or lambda.
  2. X has automatic storage duration and is a parameter, value capture or shadow capture of the called function or lambda or its definition is reached during the call.
  3. X has static storage duration, all calls to f during the whole execution convert X to the same value and any modification of X happens before the function call.
  4. X has thread storage duration, all calls to f during the execution of a specific thread convert X to the same value and any modification of X happens before the function call.
  5. f originates from an closure expression, X is an explicit identifier capture of f and any modification of X happens before the evaluation of the closure expression.

A function definition or lambda expression is independent if the derived function pointer value or lambda value is independent.

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 or lambda value f is idempotent if any evaluation of a function call that calls f is idempotent.4) A function definition or lambda expression is idempotent if the derived function pointer value or lambda value is idempotent.

9 A function or lambda value is unsequenced if it is stateless, effectless, idempotent and independent.5)

10 NOTE 1 The synchronization requirements for the independence of functions and lambdas 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 that requirement is always guaranteed; if it is an object that is conditioned in an initialization phase of the program execution or during thread startup, such a synchronization can for example be achieved by a single call to atomic_signal_fence(memory_order_seq_cst) (for a single threaded program) or atomic_thread_fence(memory_order_seq_cst) (for a multi-threaded program) at the end of the initialization phase. For an identifier capture, it is sufficient to ensure that all modifications of X are sequenced before the lambda expression corresponding to f is evaluated.

11 NOTE 2 In general the functions provided by the <math.h> header are not unsequenced; 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 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 or lambda expression that does not have the corresponding property.

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), 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 or right after the capture clause, if a lambda has no parameter list. If they appear in a function declarator, the function declarator may declare a function or a pointer to function.

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, lambda expression, function pointer object, lambda value, or function pointer value.

3) That is, the fact that a function or lambda 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) 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.

5) A function call of an unsequenced function or lambda can be executed as early as the function pointer value or lambda 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 unsequenced type attribute

Description

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

2 NOTE 1 The unsequenced type attribute asserts strong properties for the such typed function or lambda, 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 or a lambda 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 or a lambda 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.

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;
[[unsequenced]] toto const* toto_zero(void);

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 function distance itself only depends on the argument array.

#include <math.h>
#include <fenv.h>
inline double distance [[unsequenced]](double const x[static 2]) {
    #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]);
}

stateless

The stateless attribute ensures compile-time independence from local state. As such it is not sufficient to enable all the optimizations that are state of the art, but it is a first step that can be taken at compile-time without inspecting any function arguments.

6.7.11.6.2 The stateless type attribute

Description

1 The stateless type attribute asserts that a function, lambda expression or pointed-to function or function literal with that type is stateless.

effectless

The effectless attribute ensures that a function does not affect global state.

6.7.11.6.3 The effectless type attribute

Description

1 The effectless type attribute asserts that a function, lambda expression or pointed-to function or function literal with that type is effectless.

independent

6.7.11.6.4 The independent type attribute

Description

1 The independent type attribute asserts that a function, lambda expression or a pointed-to function or function literal with that type is independent.

Example

2 The attribute in the following function declaration asserts that it doesn’t depend on any observable state of the abstract machine that can be modeled by an lvalue conversion of an object. Calls to the function can be evaluated out of sequence before the return value is needed, but two calls to the function with the same argument may in general result in different pointer return values.

typedef struct toto toto;
toto* toto_new(size_t size) [[independent]];

idempotent

6.7.11.6.5 The idempotent type attribute

Description

1 The idempotent type attribute asserts that a function, lambda expression or pointed-to function or function literal with that type is idempotent.

Example

2 The attribute in the following function declaration asserts that two consecutive calls to the function will result in the same pointer return value. So if no change to the abstract state occurs between two calls the second is redundant and has no additional side effects.

typedef struct toto toto;
toto const* toto_same(void) [[idempotent]];

reproducible

6.7.11.6.6 The reproducible type attribute

Description

1 The reproducible type attribute asserts that a function, lambda expression or pointed-to function or function literal with that type is effectless and idempotent.

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 static objects 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]];

noleak

Having no storage leaks is an property for which we think that users should be able express their expectation (for function declarations) or their intent (for function definitions or lambda expressions). It will be an important feature for other attributes that we describe, but may also ease the work of static or dynamic program analyzers.

6.7.11.6.7 The noleak type attribute

Description

1 A storage leak in a function definition or lambda expression is an allocation and a possible control flow such that the pointer to the allocation is not returned by the function and such that no corresponding deallocation is performed before the end of a function call with that control flow.7) The noleak type attribute asserts that a function, lambda expression or pointed-to function or function literal with that type does not leak any storage.

Recommended Practice

2 It is recommended that for functions with a noleak attribute implementations diagnose if a pointer to newly allocated storage is not deallocated, unless it is the return value of the function or lambda.


7) Thus allocations of objects where the address is not returned or deallocated are considered to be leaks, even if the allocated object may still be accessible by other means, for example because the address has been stored in a variable.


Questions to WG14

The principal features

Do we integrate the new section 6.7.11.6 including the type attribute [[unsequenced]] and changes to 6.2.7 and 6.7.11.1 as proposed in N2887 into C23?

The [[stateless]] type attribute

Do we add the [[stateless]] type attribute to section 6.7.11.6 as proposed in N2887 into C23?

The [[effectless]] type attribute

Do we add the [[effectless]] type attribute to section 6.7.11.6 as proposed in N2887 into C23?

The [[independent]] type attribute

Do we add the [[independent]] type attribute to section 6.7.11.6 as proposed in N2887 into C23?

The [[idempotent]] type attribute

Do we add the [[idempotent]] attribute to section 6.7.11.6 as proposed in N2887 into C23?

The [[reproducible]] type attribute

Do we add the [[reproducible]] type attribute to section 6.7.11.6 as proposed in N2887 into C23?

The [[noleak]] type attribute

Do we add the [[noleak]] type attribute to section 6.7.11.6 as proposed in N2887 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: