source: libcfa/src/raii.hfa @ b12e4ad

Last change on this file since b12e4ad was cfbc56ec, checked in by Michael Brooks <mlbrooks@…>, 10 months ago

Enable array RAII and provide uninit(-), a uNoCtor equivalent.

Enable construction/destruction of "new" CFA array elements,
which was previously deactivated to avoid a compiler performance issue.
The enabled RAII steps more carefully around the performance issue.

Provide uninit(-), with tests covering the typical use case:

struct Foo;
void ?{}( Foo & this, int i ) { printf( "ctor at %d\n", i ); }
uninit(Foo) a[10]; no prints
for (i; 10) (a[i]){ i };
prints
array(uninit(Foo), 10) b; no prints
for (i; 10) (b[i]){ i };
prints

  • Property mode set to 100644
File size: 4.2 KB
Line 
1//
2// Cforall Version 1.0.0 Copyright (C) 2023 University of Waterloo
3//
4// The contents of this file are covered under the licence agreement in the
5// file "LICENCE" distributed with Cforall.
6//
7// raii.hfa -- PUBLIC
8// Utilities for advanced RAII (constructor/destructor) patterns
9//
10// Author           : Mike Brooks
11// Created On       : Fri Sep 22 15:00:00 2023
12// Last Modified By :
13// Last Modified On :
14// Update Count     :
15//
16#pragma once
17
18// Provides access to unititialized storage.
19// Intended to make cheap delayed intialization possible.
20// Similar to uC++ uNoCtor.
21// Regardless of what constructors T offers, the declaration
22//   uninit(T) x;
23// makes x:
24//   - assignable to T,
25//   - be, at first, uninitialized, and
26//   - receive a T-destructor call when x goes out of scope.
27// This sitation means the user becomes responsible for making a placement constructor call
28// on x before its first use, even if this first use is the implicit destructor call.
29// This sitation contrasts with that of
30//   T y @= {};
31// in that y does not receive an implied destructor call when it goes out of scope.
32// This sitation contrasts with that of
33//   optional(T) z;
34// in that z receives a T-destructor call conditionally upon the runtime-tracked state,
35// and that z's assignability to T is guarded by the runtime-tracked state.
36//
37// Implementation note: the uninit RAII that follows is a parade of cfa-cpp quirk exploitations.
38//
39forall( T* )
40struct uninit {
41    inline T;
42};
43
44// Parameterless ctor: leaves bits within uninitialized.
45forall( T* )
46void  ?{}( uninit(T) & this ) {
47
48    // Implementation takes advantage of CFA-available unsoundness.
49    // It could be called a bug; if it's fixed, then uninit needs an escape hatch,
50    // or to find a different loophole.
51
52    // Fundamental unsoundness: Here is a constructor for a T, whatever T is.
53    // Sound compiler reaction: We don't know what fields T has,
54    // so the programmer is surely failing to initialize all of T's fields,
55    // for some choice of T.
56    // Current compiler reaction: Ok, it initializes all the fields we know about.
57    void ?{}( T & ) {}
58
59    // Now for some ado about nothing.
60    // We need to call the above constructor on the inline T field.
61    //   Becasue the compiler holds us accountable for intizliing every field of uninit(T).
62    //   We are happy to do so and are not trying to get out of it.
63    // But the compiler doesn't recognize this form as a field initialization
64    //   T & inner = this;
65    //   ( inner ){};
66    // And the compiler doesn't offer this feature
67    //   ( (return T &) this ){};
68    // It does recognize this form...
69
70    ( (T&) this ){};
71
72    // ...though it probably shouldn't.
73    // The problem with this form is that it doesn't actually mean the Plan-9 base field.
74    // It means to reinterpret `this` with type T.
75    // For a plan-9 use in which the base-type field is not first,
76    // this form would send the wrong address to the called ctor.
77    // Fortunately, uninit has the base-type field first.
78    // For an RAII use in which the constructor does something,
79    // getting the wrong address would matter.
80    // Fortunately, ?{}(T&) is a no-op.
81}
82
83// dtor: pass-through
84forall( T* | { void ^?{}( T& ); } )
85void ^?{}( uninit(T) & this) {
86    // an inner dtor call is implied
87
88    // In fact, an autogen'd dtor would have sufficed.
89    // But there is no autogen'd dtor because no T-dtor is asserted on the struct declaration.
90    // Adding assertions to the struct decl would make the intended ctor (implemented above)
91    // a less preferred candidate than the declared, but undefined, (ugh!) autogen ctor.
92}
93
94// Optional explicit inner-ctor invoation helper.
95// Generally optional, because 1 and 2 below are equivalent:
96//   struct Foo;
97//   void ?{}( Foo &, X, Y, Z );
98//   uninit(Foo) uf;
99//   ?( uf ){ x, y, z };      // 1
100//   emplace( uf, x, y, z );  // 2
101// Is necessary for reaching a parameterless constructor
102//   void ?{}( Foo & );
103//   ?( uf ){};               // calls ?{}( uninit(Foo) & ), which does nothing
104//   emplace( uf );           // calls ?{}( Foo & ), probably what you want
105forall( T*, Args... | { void ?{}( T&, Args ); } )
106void emplace( uninit(T) & this, Args a ) {
107    T & inner = this;
108    ( inner ){ a };
109}
Note: See TracBrowser for help on using the repository browser.