384 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
			
		
		
	
	
			384 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			ReStructuredText
		
	
	
	
| ====================
 | |
| Constant Interpreter
 | |
| ====================
 | |
| 
 | |
| .. contents::
 | |
|    :local:
 | |
| 
 | |
| Introduction
 | |
| ============
 | |
| 
 | |
| The constexpr interpreter aims to replace the existing tree evaluator in
 | |
| clang, improving performance on constructs which are executed inefficiently
 | |
| by the evaluator. The interpreter is activated using the following flags:
 | |
| 
 | |
| * ``-fexperimental-new-constant-interpreter`` enables the interpreter,
 | |
|   emitting an error if an unsupported feature is encountered
 | |
| 
 | |
| Bytecode Compilation
 | |
| ====================
 | |
| 
 | |
| Bytecode compilation is handled in ``ByteCodeStmtGen.h`` for statements
 | |
| and ``ByteCodeExprGen.h`` for expressions. The compiler has two different
 | |
| backends: one to generate bytecode for functions (``ByteCodeEmitter``) and
 | |
| one to directly evaluate expressions as they are compiled, without
 | |
| generating bytecode (``EvalEmitter``). All functions are compiled to
 | |
| bytecode, while toplevel expressions used in constant contexts are directly
 | |
| evaluated since the bytecode would never be reused. This mechanism aims to
 | |
| pave the way towards replacing the evaluator, improving its performance on
 | |
| functions and loops, while being just as fast on single-use toplevel
 | |
| expressions.
 | |
| 
 | |
| The interpreter relies on stack-based, strongly-typed opcodes. The glue
 | |
| logic between the code generator, along with the enumeration and
 | |
| description of opcodes, can be found in ``Opcodes.td``. The opcodes are
 | |
| implemented as generic template methods in ``Interp.h`` and instantiated
 | |
| with the relevant primitive types by the interpreter loop or by the
 | |
| evaluating emitter.
 | |
| 
 | |
| Primitive Types
 | |
| ---------------
 | |
| 
 | |
| * ``PT_{U|S}int{8|16|32|64}``
 | |
| 
 | |
|   Signed or unsigned integers of a specific bit width, implemented using
 | |
|   the ```Integral``` type.
 | |
| 
 | |
| * ``PT_{U|S}intFP``
 | |
| 
 | |
|   Signed or unsigned integers of an arbitrary, but fixed width used to
 | |
|   implement integral types which are required by the target, but are not
 | |
|   supported by the host. Under the hood, they rely on APValue. The
 | |
|   ``Integral`` specialisation for these types is required by opcodes to
 | |
|   share an implementation with fixed integrals.
 | |
| 
 | |
| * ``PT_Bool``
 | |
| 
 | |
|   Representation for boolean types, essentially a 1-bit unsigned
 | |
|   ``Integral``.
 | |
| 
 | |
| * ``PT_RealFP``
 | |
| 
 | |
|   Arbitrary, but fixed precision floating point numbers. Could be
 | |
|   specialised in the future similarly to integers in order to improve
 | |
|   floating point performance.
 | |
| 
 | |
| * ``PT_Ptr``
 | |
| 
 | |
|   Pointer type, defined in ``"Pointer.h"``. A pointer can be either null,
 | |
|   reference interpreter-allocated memory (``BlockPointer``) or point to an
 | |
|   address which can be derived, but not accessed (``ExternPointer``).
 | |
| 
 | |
| * ``PT_FnPtr``
 | |
| 
 | |
|   Function pointer type, can also be a null function pointer. Defined
 | |
|   in ``"FnPointer.h"``.
 | |
| 
 | |
| * ``PT_MemPtr``
 | |
| 
 | |
|   Member pointer type, can also be a null member pointer. Defined
 | |
|   in ``"MemberPointer.h"``
 | |
| 
 | |
| * ``PT_VoidPtr``
 | |
| 
 | |
|   Void pointer type, can be used for rount-trip casts. Represented as
 | |
|   the union of all pointers which can be cast to void.
 | |
|   Defined in ``"VoidPointer.h"``.
 | |
| 
 | |
| * ``PT_ObjCBlockPtr``
 | |
| 
 | |
|   Pointer type for ObjC blocks. Defined in ``"ObjCBlockPointer.h"``.
 | |
| 
 | |
| Composite types
 | |
| ---------------
 | |
| 
 | |
| The interpreter distinguishes two kinds of composite types: arrays and
 | |
| records (structs and classes). Unions are represented as records, except
 | |
| at most a single field can be marked as active. The contents of inactive
 | |
| fields are kept until they are reactivated and overwritten.
 | |
| Complex numbers (``_Complex``) and vectors
 | |
| (``__attribute((vector_size(16)))``) are treated as arrays.
 | |
| 
 | |
| 
 | |
| Bytecode Execution
 | |
| ==================
 | |
| 
 | |
| Bytecode is executed using a stack-based interpreter. The execution
 | |
| context consists of an ``InterpStack``, along with a chain of
 | |
| ``InterpFrame`` objects storing the call frames. Frames are built by
 | |
| call instructions and destroyed by return instructions. They perform
 | |
| one allocation to reserve space for all locals in a single block.
 | |
| These objects store all the required information to emit stack traces
 | |
| whenever evaluation fails.
 | |
| 
 | |
| Memory Organisation
 | |
| ===================
 | |
| 
 | |
| Memory management in the interpreter relies on 3 data structures: ``Block``
 | |
| objects which store the data and associated inline metadata, ``Pointer``
 | |
| objects which refer to or into blocks, and ``Descriptor`` structures which
 | |
| describe blocks and subobjects nested inside blocks.
 | |
| 
 | |
| Blocks
 | |
| ------
 | |
| 
 | |
| Blocks contain data interleaved with metadata. They are allocated either
 | |
| statically in the code generator (globals, static members, dummy parameter
 | |
| values etc.) or dynamically in the interpreter, when creating the frame
 | |
| containing the local variables of a function. Blocks are associated with a
 | |
| descriptor that characterises the entire allocation, along with a few
 | |
| additional attributes:
 | |
| 
 | |
| * ``IsStatic`` indicates whether the block has static duration in the
 | |
|   interpreter, i.e. it is not a local in a frame.
 | |
| 
 | |
| * ``DeclID`` identifies each global declaration (it is set to an invalid
 | |
|   and irrelevant value for locals) in order to prevent illegal writes and
 | |
|   reads involving globals and temporaries with static storage duration.
 | |
| 
 | |
| Static blocks are never deallocated, but local ones might be deallocated
 | |
| even when there are live pointers to them. Pointers are only valid as
 | |
| long as the blocks they point to are valid, so a block with pointers to
 | |
| it whose lifetime ends is kept alive until all pointers to it go out of
 | |
| scope. Since the frame is destroyed on function exit, such blocks are
 | |
| turned into a ``DeadBlock`` and copied to storage managed by the
 | |
| interpreter itself, not the frame. Reads and writes to these blocks are
 | |
| illegal and cause an appropriate diagnostic to be emitted. When the last
 | |
| pointer goes out of scope, dead blocks are also deallocated.
 | |
| 
 | |
| The lifetime of blocks is managed through 3 methods stored in the
 | |
| descriptor of the block:
 | |
| 
 | |
| * **CtorFn**: initializes the metadata which is store in the block,
 | |
|   alongside actual data. Invokes the default constructors of objects
 | |
|   which are not trivial (``Pointer``, ``RealFP``, etc.)
 | |
| 
 | |
| * **DtorFn**: invokes the destructors of non-trivial objects.
 | |
| 
 | |
| * **MoveFn**: moves a block to dead storage.
 | |
| 
 | |
| Non-static blocks track all the pointers into them through an intrusive
 | |
| doubly-linked list, required to adjust and invalidate all pointers when
 | |
| transforming a block into a dead block. If the lifetime of an object ends,
 | |
| all pointers to it are invalidated, emitting the appropriate diagnostics when
 | |
| dereferenced.
 | |
| 
 | |
| The interpreter distinguishes 3 different kinds of blocks:
 | |
| 
 | |
| * **Primitives**
 | |
| 
 | |
|   A block containing a single primitive with no additional metadata.
 | |
| 
 | |
| * **Arrays of primitives**
 | |
| 
 | |
|   An array of primitives contains a pointer to an ``InitMap`` storage as its
 | |
|   first field: the initialisation map is a bit map indicating all elements of
 | |
|   the array which were initialised. If the pointer is null, no elements were
 | |
|   initialised, while a value of ``(InitMap*)-1`` indicates that the object was
 | |
|   fully initialised. When all fields are initialised, the map is deallocated
 | |
|   and replaced with that token.
 | |
| 
 | |
|   Array elements are stored sequentially, without padding, after the pointer
 | |
|   to the map.
 | |
| 
 | |
| * **Arrays of composites and records**
 | |
| 
 | |
|   Each element in an array of composites is preceded by an ``InlineDescriptor``
 | |
|   which stores the attributes specific to the field and not the whole
 | |
|   allocation site. Descriptors and elements are stored sequentially in the
 | |
|   block.
 | |
|   Records are laid out identically to arrays of composites: each field and base
 | |
|   class is preceded by an inline descriptor. The ``InlineDescriptor``
 | |
|   has the following fields:
 | |
| 
 | |
|    * **Offset**: byte offset into the array or record, used to step back to the
 | |
|      parent array or record.
 | |
|    * **IsConst**: flag indicating if the field is const-qualified.
 | |
|    * **IsInitialized**: flag indicating whether the field or element was
 | |
|      initialized. For non-primitive fields, this is only relevant to determine
 | |
|      the dynamic type of objects during construction.
 | |
|    * **IsBase**: flag indicating whether the record is a base class. In that
 | |
|      case, the offset can be used to identify the derived class.
 | |
|    * **IsActive**: indicates if the field is the active field of a union.
 | |
|    * **IsMutable**: indicates if the field is marked as mutable.
 | |
| 
 | |
| Inline descriptors are filled in by the `CtorFn` of blocks, which leaves storage
 | |
| in an uninitialised, but valid state.
 | |
| 
 | |
| Descriptors
 | |
| -----------
 | |
| 
 | |
| Descriptors are generated at bytecode compilation time and contain information
 | |
| required to determine if a particular memory access is allowed in constexpr.
 | |
| They also carry all the information required to emit a diagnostic involving
 | |
| a memory access, such as the declaration which originates the block.
 | |
| Currently there is a single kind of descriptor encoding information for all
 | |
| block types.
 | |
| 
 | |
| Pointers
 | |
| --------
 | |
| 
 | |
| Pointers, implemented in ``Pointer.h`` are represented as a tagged union.
 | |
| Some of these may not yet be available in upstream ``clang``.
 | |
| 
 | |
|  * **BlockPointer**: used to reference memory allocated and managed by the
 | |
|    interpreter, being the only pointer kind which allows dereferencing in the
 | |
|    interpreter
 | |
|  * **ExternPointer**: points to memory which can be addressed, but not read by
 | |
|    the interpreter. It is equivalent to APValue, tracking a declaration and a path
 | |
|    of fields and indices into that allocation.
 | |
|  * **TargetPointer**: represents a target address derived from a base address
 | |
|    through pointer arithmetic, such as ``((int *)0x100)[20]``. Null pointers are
 | |
|    target pointers with a zero offset.
 | |
|  * **TypeInfoPointer**: tracks information for the opaque type returned by
 | |
|    ``typeid``
 | |
|  * **InvalidPointer**: is dummy pointer created by an invalid operation which
 | |
|    allows the interpreter to continue execution. Does not allow pointer
 | |
|    arithmetic or dereferencing.
 | |
| 
 | |
| Besides the previously mentioned union, a number of other pointer-like types
 | |
| have their own type:
 | |
| 
 | |
|  * **ObjCBlockPointer** tracks Objective-C blocks
 | |
|  * **FnPointer** tracks functions and lazily caches their compiled version
 | |
|  * **MemberPointer** tracks C++ object members
 | |
| 
 | |
| Void pointers, which can be built by casting any of the aforementioned
 | |
| pointers, are implemented as a union of all pointer types. The ``BitCast``
 | |
| opcode is responsible for performing all legal conversions between these
 | |
| types and primitive integers.
 | |
| 
 | |
| BlockPointer
 | |
| ~~~~~~~~~~~~
 | |
| 
 | |
| Block pointers track a ``Pointee``, the block to which they point, along
 | |
| with a ``Base`` and an ``Offset``. The base identifies the innermost field,
 | |
| while the offset points to an array element relative to the base (including
 | |
| one-past-end pointers). The offset identifies the array element or field
 | |
| which is referenced, while the base points to the outer object or array which
 | |
| contains the field. These two fields allow all pointers to be uniquely
 | |
| identified, disambiguated and characterised.
 | |
| 
 | |
| As an example, consider the following structure:
 | |
| 
 | |
| .. code-block:: c
 | |
| 
 | |
|     struct A {
 | |
|         struct B {
 | |
|             int x;
 | |
|             int y;
 | |
|         } b;
 | |
|         struct C {
 | |
|             int a;
 | |
|             int b;
 | |
|         } c[2];
 | |
|         int z;
 | |
|     };
 | |
|     constexpr A a;
 | |
| 
 | |
| On the target, ``&a`` and ``&a.b.x`` are equal. So are ``&a.c[0]`` and
 | |
| ``&a.c[0].a``. In the interpreter, all these pointers must be
 | |
| distinguished since the are all allowed to address distinct range of
 | |
| memory.
 | |
| 
 | |
| In the interpreter, the object would require 240 bytes of storage and
 | |
| would have its field interleaved with metadata. The pointers which can
 | |
| be derived to the object are illustrated in the following diagram:
 | |
| 
 | |
| ::
 | |
| 
 | |
|       0   16  32  40  56  64  80  96  112 120 136 144 160 176 184 200 208 224 240
 | |
|   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
 | |
|   + B | D | D | x | D | y | D | D | D | a | D | b | D | D | a | D | b | D | z |
 | |
|   +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
 | |
|       ^   ^   ^       ^       ^   ^   ^       ^       ^   ^       ^       ^
 | |
|       |   |   |       |       |   |   |   &a.c[0].b   |   |   &a.c[1].b   |
 | |
|       a   |&a.b.x   &a.y    &a.c  |&a.c[0].a          |&a.c[1].a          |
 | |
|         &a.b                   &a.c[0]            &a.c[1]               &a.z
 | |
| 
 | |
| The ``Base`` offset of all pointers points to the start of a field or
 | |
| an array and is preceded by an inline descriptor (unless ``Base`` is
 | |
| zero, pointing to the root). All the relevant attributes can be read
 | |
| from either the inline descriptor or the descriptor of the block.
 | |
| 
 | |
| 
 | |
| Array elements are identified by the ``Offset`` field of pointers,
 | |
| pointing to past the inline descriptors for composites and before
 | |
| the actual data in the case of primitive arrays. The ``Offset``
 | |
| points to the offset where primitives can be read from. As an example,
 | |
| ``a.c + 1`` would have the same base as ``a.c`` since it is an element
 | |
| of ``a.c``, but its offset would point to ``&a.c[1]``. The
 | |
| array-to-pointer decay operation adjusts a pointer to an array (where
 | |
| the offset is equal to the base) to a pointer to the first element.
 | |
| 
 | |
| ExternPointer
 | |
| ~~~~~~~~~~~~~
 | |
| 
 | |
| Extern pointers can be derived, pointing into symbols which are not
 | |
| readable from constexpr. An external pointer consists of a base
 | |
| declaration, along with a path designating a subobject, similar to
 | |
| the ``LValuePath`` of an APValue. Extern pointers can be converted
 | |
| to block pointers if the underlying variable is defined after the
 | |
| pointer is created, as is the case in the following example:
 | |
| 
 | |
| .. code-block:: c
 | |
| 
 | |
|   extern const int a;
 | |
|   constexpr const int *p = &a;
 | |
|   const int a = 5;
 | |
|   static_assert(*p == 5, "x");
 | |
| 
 | |
| TargetPointer
 | |
| ~~~~~~~~~~~~~
 | |
| 
 | |
| While null pointer arithmetic or integer-to-pointer conversion is
 | |
| banned in constexpr, some expressions on target offsets must be folded,
 | |
| replicating the behaviour of the ``offsetof`` builtin. Target pointers
 | |
| are characterised by 3 offsets: a field offset, an array offset and a
 | |
| base offset, along with a descriptor specifying the type the pointer is
 | |
| supposed to refer to. Array indexing adjusts the array offset, while the
 | |
| field offset is adjusted when a pointer to a member is created. Casting
 | |
| an integer to a pointer sets the value of the base offset. As a special
 | |
| case, null pointers are target pointers with all offsets set to 0.
 | |
| 
 | |
| TypeInfoPointer
 | |
| ~~~~~~~~~~~~~~~
 | |
| 
 | |
| ``TypeInfoPointer`` tracks two types: the type assigned to
 | |
| ``std::type_info`` and the type which was passed to ``typeinfo``.
 | |
| 
 | |
| InvalidPointer
 | |
| ~~~~~~~~~~~~~~
 | |
| 
 | |
| Such pointers are built by operations which cannot generate valid
 | |
| pointers, allowing the interpreter to continue execution after emitting
 | |
| a warning. Inspecting such a pointer stops execution.
 | |
| 
 | |
| TODO
 | |
| ====
 | |
| 
 | |
| Missing Language Features
 | |
| -------------------------
 | |
| 
 | |
| * Changing the active field of unions
 | |
| * ``volatile``
 | |
| * ``__builtin_constant_p``
 | |
| * ``dynamic_cast``
 | |
| * ``new`` and ``delete``
 | |
| * Fixed Point numbers and arithmetic on Complex numbers
 | |
| * Several builtin methods, including string operations and
 | |
|   ``__builtin_bit_cast``
 | |
| * Continue-after-failure: a form of exception handling at the bytecode
 | |
|   level should be implemented to allow execution to resume. As an example,
 | |
|   argument evaluation should resume after the computation of an argument fails.
 | |
| * Pointer-to-Integer conversions
 | |
| * Lazy descriptors: the interpreter creates a ``Record`` and ``Descriptor``
 | |
|   when it encounters a type: ones which are not yet defined should be lazily
 | |
|   created when required
 | |
| 
 | |
| Known Bugs
 | |
| ----------
 | |
| 
 | |
| * If execution fails, memory storing APInts and APFloats is leaked when the
 | |
|   stack is cleared
 |