$(DDOC $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE ) $(SPEC_S Live Functions, $(DDOC_BLANKLINE ) $(HEADERNAV_TOC $(HEADERNAV_ITEM ownership, Ownership) $(HEADERNAV_ITEM ob, Borrowing) $(HEADERNAV_SUBITEMS live, @live attribute, $(HEADERNAV_ITEM tracked, Tracked Pointers) $(HEADERNAV_ITEM state, Pointer States) $(HEADERNAV_ITEM lifetime, Lifetimes) $(HEADERNAV_ITEM transition, Pointer State Transitions) $(HEADERNAV_ITEM borrower, Borrowers can be Owners) $(HEADERNAV_ITEM exception, Exceptions) $(HEADERNAV_ITEM lazy, Lazy Parameters) $(HEADERNAV_ITEM mempool, Mixing Memory Pools) $(HEADERNAV_ITEM vargs, Variadic Function Arguments) ) $(HEADERNAV_ITEM references, References) ) $(DDOC_BLANKLINE ) $(B Experimental, Subject to Change, use -preview=dip1021 to activate) $(DDOC_BLANKLINE )

$(LNAME2 ownership, Ownership)

$(DDOC_BLANKLINE ) $(P If a memory object has only one pointer to it, that pointer is the $(I owner) of the memory object. With the single owner, it becomes straightforward to manage the memory for the object. It also becomes trivial to synchronize access to that memory object among multiple threads, because it can only be accessed by the thread that controls that single pointer. ) $(DDOC_BLANKLINE ) $(P This can be generalized to a graph of memory objects interconnected by pointers, where only a single pointer connects to that graph from elsewhere. That single pointer becomes the $(I owner) of all the memory objects in that graph. ) $(DDOC_BLANKLINE ) $(P When the owner of the graph is no longer needed, then the graph of memory objects it points to is no longer needed and can be safely disposed of. If the owner itself is no longer in use (i.e. is no longer $(I live)) and the owned memory objects are not disposed of, an error can be diagnosed. ) $(DDOC_BLANKLINE ) $(P Hence, the following errors can be statically detected: ) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD int)* allocate(); $(D_COMMENT // allocate a memory object )$(D_KEYWORD void) release($(D_KEYWORD int)*); $(D_COMMENT // deallocate a memory object ) @live $(D_KEYWORD void) test() { $(D_KEYWORD auto) p = allocate(); } $(D_COMMENT // error: p is not disposed of ) @live $(D_KEYWORD void) test() { $(D_KEYWORD auto) p = allocate(); release(p); release(p); $(D_COMMENT // error: p was already disposed of )} @live $(D_KEYWORD void) test() { $(D_KEYWORD int)* p = $(D_KEYWORD void); release(p); $(D_COMMENT // error, p does not have a defined value )} @live $(D_KEYWORD void) test() { $(D_KEYWORD auto) p = allocate(); p = allocate(); $(D_COMMENT // error: p was not disposed of ) release(p); } ) $(DDOC_BLANKLINE ) $(P Functions with the @live attribute enable diagnosing these sorts of errors by tracking the status of owner pointers. ) $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE )

$(LNAME2 ob, Borrowing)

$(DDOC_BLANKLINE ) $(P Tracking the ownership status of a pointer can be safely extended by adding the capability of temporarilly $(I borrowing) ownership of a pointer from the $(I owner). The $(I owner) can no longer use the pointer as long as the $(I borrower) is still using the pointer value (i.e. it is $(I live)). Once the $(I borrower) is no longer $(I live), the $(I owner) can resume using it. Only one $(I borrower) can be live at any point. ) $(DDOC_BLANKLINE ) $(P Multiple $(I borrower) pointers can simultaneously exist if all of them are pointers to read only (const or immutable) data, i.e. none of them can modify the memory object(s) pointed to. ) $(DDOC_BLANKLINE ) $(P This is collectively called an $(I Ownership/Borrowing) system. It can be stated as: ) $(DDOC_BLANKLINE )
At any point in the program, for each memory object, there is exactly one live mutable pointer to it or all the live pointers to it are read-only.
$(DDOC_BLANKLINE ) $(DDOC_BLANKLINE )

$(LNAME2 live, @live attribute)

$(DDOC_BLANKLINE ) $(P Function declarations annotated with the @live attribute are checked for compliance with the Ownership/Borrowing rules. The checks are run after other semantic processing is complete. The checking does not influence code generation. ) $(DDOC_BLANKLINE ) $(P Whether a pointer is allocated memory using the GC or some other storage allocator is immaterial to OB, they are not distinguished and are handled identically. ) $(DDOC_BLANKLINE ) $(P Class references are assumed to be allocated using either the GC or are allocated on the stack as scope classes, and are not tracked. ) $(DDOC_BLANKLINE ) $(P If @live functions call non-@live functions, those called functions are expected to present an @live compatible interface, although it is not checked. if non-@live functions call @live functions, arguments passed are expected to follow @live conventions. ) $(DDOC_BLANKLINE ) $(P It will not detect attempts to dereference null pointers or possibly null pointers. This is unworkable because there is no current method of annotating a type as a non-null pointer. ) $(DDOC_BLANKLINE )

$(LNAME2 tracked, Tracked Pointers)

$(DDOC_BLANKLINE ) $(P The only pointers that are tracked are those declared in the @live function as this, function parameters or local variables. Variables from other functions are not tracked, even @live ones, as the analysis of interactions with other functions depends entirely on that function signature, not its internals. Parameters that are const are not tracked. ) $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE )

$(LNAME2 state, Pointer States)

$(DDOC_BLANKLINE ) $(P Each tracked pointer is in one of the following states: ) $(DDOC_BLANKLINE ) $(DL $(DT Undefined) $(DDOC_BLANKLINE ) $(DD The pointer is in an invalid state. Dereferencing such a pointer is an error.) $(DDOC_BLANKLINE ) $(DT Owner) $(DDOC_BLANKLINE ) $(DD The owner is the sole pointer to a memory object graph. An Owner pointer normally does not have a scope attribute. If a pointer with the scope attribute is initialized with an expression not derived from a tracked pointer, it is an Owner. $(DDOC_BLANKLINE ) If an Owner pointer is assigned to another Owner pointer, the former enters the Undefined state. $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD void) consume($(D_KEYWORD int)* o); $(D_COMMENT // o is owner ) @live $(D_KEYWORD int)* f($(D_KEYWORD int)* p) $(D_COMMENT // p is owner ){ writeln(*p); $(D_COMMENT // transfer ownership to `consume` ) consume(p); $(D_COMMENT // p is now undefined ) $(D_COMMENT //writeln$(LPAREN)*p$(RPAREN ); // error ) $(D_KEYWORD int)* q = $(D_KEYWORD new) $(D_KEYWORD int); $(D_COMMENT // q is owner ) writeln(*q); p = q; $(D_COMMENT // transfer ownership ) $(D_COMMENT // q is now undefined ) $(D_COMMENT //writeln$(LPAREN)*q$(RPAREN ); // error ) writeln(*p); $(D_KEYWORD return) p; } ) ) $(DDOC_BLANKLINE ) $(DT Borrowed) $(DDOC_BLANKLINE ) $(DD A Borrowed pointer is one that temporarily becomes the sole live pointer to a memory object graph. It enters that state via assignment from an owner pointer or another borrowed pointer, and stays in that state until its last use. $(DDOC_BLANKLINE ) A Borrowed pointer must be scope and must be a pointer to mutable. A mutable scope pointer function parameter is a Borrowed pointer. $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD void) consume($(D_KEYWORD int)* o); $(D_COMMENT // o is owner )$(D_KEYWORD void) borrow($(D_KEYWORD scope) $(D_KEYWORD int)* b); $(D_COMMENT // b is borrowed ) @live $(D_KEYWORD void) g($(D_KEYWORD scope) $(D_KEYWORD int)* p) $(D_COMMENT // p is borrowed ){ $(D_COMMENT //consume$(LPAREN)p$(RPAREN ); // error, p is not owner ) borrow(p); $(D_COMMENT // lend p to q ) $(D_KEYWORD int)* q = p; $(D_COMMENT // q is inferred as scope ) $(D_COMMENT // <-- using p here would end q's lifetime ) writeln(*q); $(D_COMMENT // lifetime of q ends before p is used ) writeln(*p); $(D_COMMENT // OK )} ) ) $(DDOC_BLANKLINE ) $(DT Readonly) $(DDOC_BLANKLINE ) $(DD A Readonly pointer acquires its value from an Owner or Borrowed pointer. While the Readonly pointer is live, only Readonly pointers can be acquired from that Owner. A Readonly pointer must be scope and also must not be a pointer to mutable. $(DDOC_BLANKLINE ) $(D_CODE @live $(D_KEYWORD void) h($(D_KEYWORD scope) $(D_KEYWORD int)* p) { $(D_COMMENT // acquire 2 read only pointers ) $(D_KEYWORD const) q = p; $(D_KEYWORD const) r = q; $(D_COMMENT // <-- borrowing or using p here would end q and r's lifetime ) $(D_COMMENT // both q and r are live ) writeln(*q); writeln(*r); $(D_COMMENT // using p ends all its read only pointer lifetimes ) writeln(*p); $(D_COMMENT //writeln$(LPAREN)*q$(RPAREN ); // error )} ) ) ) $(DDOC_BLANKLINE )

$(LNAME2 lifetime, Lifetimes)

$(DDOC_BLANKLINE ) $(P The lifetime of a Borrowed or Readonly pointer value starts when it is assigned a value from an Owner or another Borrowed pointer, and ends at the last read of that value. ) $(DDOC_BLANKLINE ) $(P This is also known as $(I Non-Lexical Lifetimes). ) $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE )

$(LNAME2 transition, Pointer State Transitions)

$(DDOC_BLANKLINE ) $(P A pointer changes its state when one of these operations is done to it: ) $(DDOC_BLANKLINE ) $(UL $(LI storage is allocated for it (such as a local variable on the stack), which places the pointer in the Undefined state) $(DDOC_BLANKLINE ) $(LI initialization (treated as assignment)) $(DDOC_BLANKLINE ) $(LI assignment - the source and target pointers change state based on what states they are in and their types and storage classes) $(DDOC_BLANKLINE ) $(LI passed to an out function parameter (changes state after the function returns), treated the same as initialization) $(DDOC_BLANKLINE ) $(LI passed by ref to a function parameter, treated as an assignment to a Borrow or a Readonly depending on the storage class and type of the parameter) $(DDOC_BLANKLINE ) $(LI returned from a function) $(DDOC_BLANKLINE ) $(LI it is passed by value to a function parameter, which is treated as an assignment to that parameter.) $(DDOC_BLANKLINE ) $(LI it is implicitly passed by ref as a closure variable to a nested function) $(DDOC_BLANKLINE ) $(LI the address of the pointer is taken, which is treated as assignment to whoever receives the address) $(DDOC_BLANKLINE ) $(LI the address of any part of the memory object graph is taken, which is treated as assignment to whoever receives that address) $(DDOC_BLANKLINE ) $(LI a pointer value is read from any part of the memory object graph, which is treated as assignment to whoever receives that pointer) $(DDOC_BLANKLINE ) $(LI merging of control flow reconciles the state of each variable based on the states they have from each edge) ) $(DDOC_BLANKLINE )

$(LNAME2 borrower, Borrowers can be Owners)

$(DDOC_BLANKLINE ) $(P Borrowers are considered Owners if they are initialized from other than a pointer. ) $(DDOC_BLANKLINE ) $(D_CODE @live $(D_KEYWORD void) uhoh() { $(D_KEYWORD scope) p = malloc(); $(D_COMMENT // p is considered an Owner ) $(D_KEYWORD scope) $(D_KEYWORD const) pc = malloc(); $(D_COMMENT // pc is not considered an Owner )} $(D_COMMENT // dangling pointer pc is not detected on exit ) ) $(DDOC_BLANKLINE )

$(LNAME2 exception, Exceptions)

$(DDOC_BLANKLINE ) $(P The analysis assumes no exceptions are thrown. ) $(DDOC_BLANKLINE ) $(D_CODE @live $(D_KEYWORD void) leaky() { $(D_KEYWORD auto) p = malloc(); pitcher(); $(D_COMMENT // throws exception, p leaks ) free(p); } ) $(DDOC_BLANKLINE ) $(P One solution is to use scope(exit): ) $(DDOC_BLANKLINE ) $(D_CODE @live $(D_KEYWORD void) waterTight() { $(D_KEYWORD auto) p = malloc(); $(D_KEYWORD scope)(exit) free(p); pitcher(); } ) $(DDOC_BLANKLINE ) $(P or use RAII objects or call only nothrow functions. ) $(DDOC_BLANKLINE )

$(LNAME2 lazy, Lazy Parameters)

$(DDOC_BLANKLINE ) $(P Lazy parameters are not considered. ) $(DDOC_BLANKLINE )

$(LNAME2 mempool, Mixing Memory Pools)

$(DDOC_BLANKLINE ) $(P Conflation of different memory pools: ) $(DDOC_BLANKLINE ) $(D_CODE $(D_KEYWORD void)* xmalloc(size_t); $(D_KEYWORD void) xfree($(D_KEYWORD void)*); $(D_KEYWORD void)* ymalloc(size_t); $(D_KEYWORD void) yfree($(D_KEYWORD void)*); $(D_KEYWORD auto) p = xmalloc(20); yfree(p); $(D_COMMENT // should call xfree$(LPAREN)$(RPAREN ) instead )) $(P is not detected. ) $(DDOC_BLANKLINE ) $(P This can be mitigated by using type-specific pools: ) $(DDOC_BLANKLINE ) $(D_CODE U* umalloc(); $(D_KEYWORD void) ufree(U*); V* vmalloc(); $(D_KEYWORD void) vfree(V*); $(D_KEYWORD auto) p = umalloc(); vfree(p); $(D_COMMENT // type mismatch )) $(DDOC_BLANKLINE ) $(P and perhaps disabling implicit conversions to void* in @live functions. ) $(DDOC_BLANKLINE )

$(LNAME2 vargs, Variadic Function Arguments)

$(DDOC_BLANKLINE ) $(P Arguments to variadic functions (such as printf) are considered to be consumed. ) $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE )

$(LNAME2 references, References)

$(DDOC_BLANKLINE ) $(OL $(LI $(LINK2 https://bartoszmilewski.com/2009/06/02/race-free-multithreading-ownership, Race-free Multithreading: Ownership)) ) $(DDOC_BLANKLINE ) $(DDOC_BLANKLINE ) $(SPEC_SUBNAV_PREV_NEXT importc, ImportC, windows, Windows Programming) ) )