Nested Functions

Example:

void foo(int x) { int bar(int y) { r e t u r n x + y ; }

...bar(3)... }

Everybody already understands this! Why Nested Functions?

I Basic feature is simple and well understood I Many languages have them (Ada, Pascal, ...) I Existing extensions (GNU , Ch, Blocks, , C++)

I Safe higher-order programming I Safer alternative to existing constructs I Only relatively small changes to standard required Example 1: Local Helper Functions

Example: Local access.

void foo(struct bar f) { #define access(x, y) (f−>a [ x + y ] [ y ] )

i n t i = 0 ; ... access(1, 2) + access(1, ++i) ... // error!

{ struct foo f = ....

... access(1, 3) ... // error! }

#undef a c c e s s } Example 1: Local Helper Functions

Example: Local access.

void foo(struct bar f) { int access(int x, int y) { r e t u r n f−>a [ x + y ] [ y ] ; }

i n t i = 0 ; ... access(1, 2) + access(1, ++i) ... // ok

{ struct foo f = ....

... access(1, 3) ... // ok } }

I Avoids macro problems I Safely abstract away local context I Type safe Example 2: Higher-Order Programming

Example: Higher-order programming.

extern void sort(int , void ∗ , int cmp(void ∗ , int, int), void swp(void ∗ , int, int));

int int cmp ( v o i d ∗ data, int x, int y) { i n t ∗a = data ; return (a[x] > a [ y ] ) − ( a [ x ] < a [ y ] ) ; }

static void int s w p ( v o i d ∗data, int x, int y) { i n t ∗a = data ; int t =a[x]; a[x] =a[y]; a[y] = t; }

{ i n t a [ 1 0 ] = { ... };

sort(10, a, int cmp , i n t s w p ) ; } Example 2: Higher-Order Programming

Example: Higher-order programming.

extern void sort(int, int cmp(int, int), void swp(int, int));

{ i n t a [ 1 0 ] = { ... };

int cmp(int x, int y) { return (a[x] > a [ y ] ) − ( a [ x ] < a [ y ] ) ; }

void swp(int x, int y) { int t =a[x]; a[x] =a[y]; a[y] = t; }

sort(10, cmp, swp); }

I Generic and type-safe (no void pointer!) I Reentrant I Flexible

Note: This requires taking the address of a . Example 3: Nonlocal

Example: Error handling.

void check(int errval , jmp buf j b u f ) { i f ( e r r v a l ) longjmp(jbuf , errval); }

{ jmp buf j b u f ;

if (0 != setjmp(jbuf)) goto errout;

int errval = 0;

... check(errval , jbuf) ...

e r r o u t : } Example 3: Nonlocal Control Flow

Example: Error handling.

{ int errval = 0;

void check(void) { if (errval) goto errout; }

... check() ...

e r r o u t : }

I Regular syntax for contol flow (labels and jump statements) I Explicit (not mediated by state of jump buffer) I No volatile required I No wording change needed. : Variables

Example: Scope.

{ int x=1, y=3;

int bar(void) { int y=3; // old y is hidden return x + y; // x is visible } }

Note: Scope for identifiers (except labels) follows usual rules. Scope: Labels

Example: Labels.

void foo(int x) { f o o : void bar(void) { i n : goto foo; // ? ...... foo: // redeclaration goto out ; }

goto in; // should not be allowed

... bar() ... out : } Suggested rules: I Labels of enclosing functions are visible in nested functions. I Labels must be unique at all times. Note: N2661 hat explicit wording to prevent jumps into a nested function but it was suggested to limit visibility. Nested Functions: Lifetime

Example: address of

i n t ∗yp ;

{ i n t y = 3 ;

f o o (&y ) ;

yp = &y ; }

Example: address of nested function

i n t (∗ bp ) ( i n t x ) ;

{ i n t y = 1 ;

int bar(int x) { r e t u r n x + y ; }

f o o (& bar ) ;

bp = &bar ; }

Note: Passing pointers down the stack is always ok. Passing pointers to variables or nested function up may cause problems. Nested Functions: Lifetime

Problems: Automatic variables, declarations with variably modified type, and jump targets in enclosing blocks.

Example: address of nested function

i n t (∗ bp ) ( i n t x ) ;

{ i n t y = 3 ; typedef float vmtype[y];

int bar(int x) { int z = sizeof(vmtype); // VM typedef name

i f ( x != z ) gotoout; //label

returny; //variable }

bp = &bar ; out : } Nested Functions: Lifetime

Three options: 1. Bound lifetime to enclosing .

2. Bound lifetime to enclosing blocks that are accessed (lifting).

3. Lifetime is unbounded but access of dead strack frames is undefined. (more difficult to implement) 0 int x; static chain float y; char *c_ptr; int x; double *a_ptr; float y;

1 1 static chain

double b; double b; offset char c; char c; double *a_ptr;

2 2 static chain int z; int z; offset double a; double a; Lifetime: Version 1

13a The lifetime of a function that is defined at block scope ends when the enclosing block ends. Lifetime of all other functions is the entire execution of the program. A pointer to a function becomes indeterminate when the lifetime of that function ends.

I Nested function similar to automatic objects. I Passing down the stack is allowed. I Passing up the stack is always illegal (UB but should be diagnosed if possible) I Simple implementation techniques sufficient Lifting 1

Example: returning address of nested function

{ int bar(int y) { r e t u r n 3 + y ; }

r e t u r n bar ; }

I No access to or non-local jump. ⇒ We could treat it as a regular function. Lifting 2

Example: returning address of nested function

{ i n t (∗b ) ( i n t y ) ; i n t x = . . . ; { int bar(int y) { r e t u r n x + y ; } b=bar; //Whynot? }

. . . b ( 3 ) . . . }

I Access to local variable of grand-parent block. ⇒ One could lift it out of the innermost scope.

I Static code transformation I More complicated specification. Lifetime: Version 2 (Lifting)

13b The lifetime of a function defined at block scope ends when any lexically enclosing block ends that contains one of the following: – an object of automatic storage duration that is referenced by name in the function – a declaration with variably modified type that is referenced by name in the function The lifetime of a function defined at block scope also ends when any lexically enclosing function ends that contains a target of a jump statement contained in the function. Lifetime of all other functions is the entire execution of the program. A pointer to a function becomes indeterminate when the lifetime of that function ends.

Note: Revised wording based on comments from the reflector. Lifetime: Nested Function as Objects

Lifetime can be extended by treating nested functions as objects that can capture variables and can be passed around.

Example: Nested functions as objects (lambda expressions).

auto f(void) { i n t x = . . . ;

return ([x](void) −> i n t { r e t u r n x ; } ); }

Example: Nested functions as objects (Blocks).

typedef void (ˆblock t ) ( v o i d ) ; b l o c k t f ( v o i d ) { i n t x = . . . ;

b l o c k t b = ˆ( v o i d ) { r e t u r n x ; };

return Block c o p y ( b ) ; }

Note: Not proposed in N2661. Nested Functions and Lambda Expressions

Lambda expressions are anonymous nested functions.

Example: lambda expression

void foo(int x) { int bar(int y) { r e t u r n x + y ; }

. . . bar . . . }

void foo(int x) { ... ( [&](int y) −> i n t { r e t u r n x + y ; } )... } Nested functions (this proposal): I existing syntax (function definitions) I regular access to variables of enclosing scopes I regular function types (but ABI issues!) I non-local control flow I focus on run-time generic programming

Lambda expressions (proposal by Jens): I new syntax (parsing of statements inside expressions) I capture lists: value capture, lvalue capture, etc. I new unique object-like types I focus on compile-time generic programming (templates, macros)

Both proposals have a different focus. They have overlap and there they are (or can be made) fully compatible! Generic Function Pointers

I Pointers to nested functions need code and data Generic Function Pointers Alternative 1: Wide function pointer types

struct efnptr1 { v o i d (∗ code ) ( v o i d ∗data , . . . ) ; v o i d ∗data ; };

Alternative 2: Pointer to function descriptor

s t r u c t { v o i d (∗ code ) ( v o i d ∗data , . . . ) ; v o i d ∗data ; }∗ e f n p t r 2 ;

Aternative 3: Pointer to

asm { load chain register with data; jump code ; }∗ e f n p t r 3 ;

Note: Pointer to function descriptor can be made backwards compatible using pointer tagging (but at run-time cost). Trampolines require executable stack and instruction cache flushing. Generic Function Pointers

First (hidden) argument is passed in static chain register. ⇒ ABI compatible with regular functions and other languages.

Regular pointers can be converted: (struct efnptr1){ f n p t r , NULL } ;

Alternatively, it possible to use a wrapper: (struct efnptr1){ wrapper, fnptr } ;

Wrappers can be used to call entities from other programming languages: (struct efnptr1){ wrapper, callable −o b j e c t } ; Generic Function Pointers

Example: C++

std :: function x ;

Example: Blocks

int (ˆfptr)(int x);

Example: Borland C++

i n t ( c l o s u r e ∗ fptr)(int x); Generic Function Pointers

C++ has a generic function pointer. I Library type (ABI?) I Lifetime management (copy / move) I Complex (small object optimization) I Overhead (usual 32 bytes on x86-64)

Example:

std :: function x = [&](int x) −> i n t { r e t u r n x ; };

Note: This will do dynamic allocation, initialize it with the lambda expression, and store a pointer to the function pointer object. Questions? Nested Functions and Lambda Expressions

You can use lambda expressions instead of nested functions:

void foo(int x) { std :: function bar = [ & ] ( i n t y ) −> i n t { r e t u r n x + y ; };

. . . bar . . . } With statement expressions (GNU C extension) you can define lambda expressions using nested functions:

void foo(int x) { ...({ int bar(int y) { r e t u r n x + y } bar ; })... } Nested Functions with VM Type Nested functions with VM type depending on automatic variables.

Example:

{ i n t N = . . . ;

void foo(int (∗ a)[N]) // variably −modified argument {

} } Local declarations can already have such VM types:

Example:

extern void foo(int (∗ a ) [ ] ) ;

{ i n t N = . . . ;

extern void foo(int (∗ a)[N]); // variably −modified argument void foo(int (∗ a)[N]); // variably −modified argument }

Note: This all works according to existing rules (size expressions may access automatic variables of enclosing function). Nested Functions with VM Type Nested functions with VM type depending on automatic variables.

Example:

{ i n t N = . . . ;

i n t (∗ foo(void))[N] // variably −modified return type {

} } The following is not accepted by compilers::

Example:

e x t e r n i n t (∗ foo(void ))[];

{ i n t N = . . . ;

e x t e r n i n t (∗ foo(void))[N]; // variably −modified return type i n t (∗ foo(void))[N]; // variably −modified return type }

Note: Return type not considered part of the prototype (although standard text implies otherwise) + external linkage. Nested Functions with VM Type Nested functions with VM type depending on automatic variables.

Example:

{ i n t N = . . . ;

i n t (∗ foo(void))[N] // variably −modified return type {

} } But for pointer types it also works:

Example:

s t a t i c i n t (∗(∗ fp)(void ))[];

{ i n t N = . . . ;

s t a t i c i n t (∗(∗ fp)(void))[N]; // variably −modified return type i n t (∗(∗ gp)(void))[N]; // variably −modified return type }

Note: A function definition also is a declaration. A nested function definition has no linkage (N2661) ⇒ Everything works. Example 3: Nonlocal Control Flow II

Example: Loops.

f o r ( . . . ) {

v o i d c h e c k restart(void) { if (... cond ...) c o n t i n u e ; }

f o r ( . . . ) f o r ( . . . ) . . . c h e c k restart() ... }

I Identical to goto. I No wording change needed.

Note: With statements expressions (GNU C) or lambda expressions continue / break may appear inside controlling expressions. Example 4: Nonlocal Control Flow III

Example: Switch.

{ switch (...) { c a s e 0 : void foo(void) { case 1: // should not be allowd } c a s e 2 : } }

Note: Addressed in N2661 using wording already used for VLAs