diff --git a/gazprea/index.rst b/gazprea/index.rst index 5eda096..347686a 100644 --- a/gazprea/index.rst +++ b/gazprea/index.rst @@ -21,6 +21,7 @@ Hardware Acceleration Laboratory in Markham, ON. spec/namespaces spec/comments spec/declarations + spec/constexpr spec/type_qualifiers spec/types spec/type_inference diff --git a/gazprea/spec/constexpr.rst b/gazprea/spec/constexpr.rst new file mode 100644 index 0000000..49fcb4d --- /dev/null +++ b/gazprea/spec/constexpr.rst @@ -0,0 +1,146 @@ +.. _sec:constexpr: + +Constant Expressions +==================== + +A constant expression (sometimes called a constexpr) is an expression that can +be fully evaluated by the compiler at compile time. This feature is primarily +for specifying the size of +:ref:`statically-sized arrays `. + +In *Gazprea*, a ``constexpr`` is not a keyword, but a property of a ``const`` +variable. A ``const`` variable is considered a ``constexpr`` if and only if its +initializer expression meets a strict set of criteria: + +.. _ssec:constexpr_rules: + +Rules for Constant Expressions +------------------------------ + +An expression is a valid ``constexpr`` if it is composed exclusively of: + +1. Literals of base types (``boolean``, ``integer``, ``real``, ``character``). +2. The operators ``+``, ``-``, ``*``, ``/``, ``not``, ``and``, ``or``, + between two or more ``constexpr``\ s. +3. Constructors for aggregate types, provided that the aggregate is const and + all members are ``constexpr``\ s. +4. Index or field access on ``constexpr`` aggregate types. +5. Other variables that are themselves valid ``constexpr``\ s. + +An expression is **not** a ``constexpr`` if it contains: + +1. References to ``var`` variables. +2. Function or procedure calls. +3. Any I/O operations (``<-``). + +The compiler must perform this validation recursively. When checking if a +variable is a ``constexpr``, the compiler must trace its entire dependency +chain. If the chain ever depends on a runtime value, the check fails. + +The only expressions that *must* be ``constexpr`` are global constants. Other +constexprs arising from constants inside function scope may also be constexprs +but the implementation does not need to enforce or necessarily identify this. +Students should also note that MLIR has a constant propagation pass built in, +so doing constant folding yourself may not be necessary depending on your +implementation. + +**Examples:** + +**Note**: we will annotate the scope explicitly in these examples. Some +'illegal' examples here would be legal within a non-global scope. + +:: + + // ---------------------------- + // in global scope + // ---------------------------- + + // Legal Global Constant Expressions + const A = 10; + const B = A * 2; // Depends on another constexpr + const C = B + 5; // C is 25 + + // Illegal Global Constant Expressions + var x = 10; + const Y = x + 5; // Not a constexpr: depends on a 'var' + + function get_val() returns integer { return 100; } + const Z = get_val(); // Not a constexpr: depends on a function call + +.. _ssec:constexpr_aggregates: + +Constant Expressions with Aggregate Types +----------------------------------------- + +Arrays and tuples can also be ``constexpr``\ s if they meet specific criteria, +allowing them to be used to define other constants. + +#. Arrays + + A ``const`` array is a ``constexpr`` if: + + 1. Its size is a valid ``constexpr``. + 2. All of its element initializers are valid ``constexpr``\ s. + + A ``vector`` (the dynamically-sized type) can never be a ``constexpr`` + aggregate, since its size is determined at runtime. An inferred-size array + such as ``integer[*] X = [1, 2, 3]`` must be a ``constexpr``, meaning its + initializer is itself a ``constexpr``: ``[*]`` denotes an inferred size, not + a dynamic one. + + :: + + // ---------------------------- + // in global scope + // ---------------------------- + + const WIDTH = 5; + const integer[WIDTH] LOOKUP_TABLE = [10, 20, 30, 40, 50]; // Legal constexpr array + + const ELEMENT = LOOKUP_TABLE[3]; // Legal: ELEMENT is a constexpr with value 30 + integer[ELEMENT] my_array = 0; // Legal: static array of size 30, zero-filled + + const integer[2] BAD_TABLE = [10, get_val()]; // Illegal: initializer is not a constexpr + // also illegal if a procedure since + // procedure calls are not allowed + // within declarations + + A ``constexpr`` can appear anywhere a ``const`` declaration is legal, + including inside functions, procedures, and control-flow blocks. However, + **not every** ``const`` variable is a ``constexpr``. ``const`` means only + that the variable is immutable within its scope; ``constexpr`` is the + stronger property that the value is fully known at compile time. For + example: + + :: + + // ---------------------------------- + // in local/function/non-global scope + // ---------------------------------- + var integer x; + x <- std_input; + const integer y = x; // Legal: y is immutable, but NOT a constexpr + // because its value depends on runtime input. + integer[y] arr; // Illegal: an explicit array size must be a + // constexpr, and y is not a constexpr. + vector v; // Legal: a vector is the dynamically-sized type; + // use it when the size is only known at runtime. + + The compiler propagates the constexpr property through local scopes + normally; there is no restriction on where in a block the declaration + appears, as long as its entire dependency chain satisfies the rules above. + +#. Tuples + + A ``const`` tuple is a ``constexpr`` if all of its fields are initialized + with valid constant expressions. + + :: + + // ---------------------------- + // in global scope + // ---------------------------- + const CONFIG = (true, 10 * 2); // Legal constexpr tuple + + const IS_ENABLED = CONFIG.1; // Legal: IS_ENABLED is a constexpr with value 'true' + const VALUE = CONFIG.2; // Legal: VALUE is a constexpr with value 20 diff --git a/gazprea/spec/globals.rst b/gazprea/spec/globals.rst index 453676f..1c647d6 100644 --- a/gazprea/spec/globals.rst +++ b/gazprea/spec/globals.rst @@ -24,11 +24,19 @@ place since mutable global variables would ruin functional purity. If functions have access to mutable global state then we can not guarantee their purity. -Globals must be initialized, but the initialization expressions may only contain -a single _scalar_ literal. That means that functions and even previously defined globals may not -appear on the RHS of a global declaration. The reason is because it is very difficult to -evaluate variables and functions at compile time. Global expression evaluation could -be deferred to runtime, but that has the disadvantage of changing errors from compile -time to run time. +Globals must be initialized with a valid +:ref:`constant expression `. A global initializer may therefore +reference other globals and use arithmetic and constexpr aggregates, but it must +be fully evaluable by the compiler before the program runs. This preserves +functional purity and enables compile-time optimizations. As a consequence: + +* Functions, procedures, and I/O operations may not appear in a global's + initializer. +* A global may not have a ``vector`` type (the dynamically-sized type), + because a vector's size is determined at runtime. An inferred-size array + such as ``const integer[*] X = [1, 2, 3]`` *is* permitted: ``[*]`` denotes + an inferred size that is fixed by its ``constexpr`` initializer at compile + time. +* All globals are implicitly ``constexpr``. diff --git a/gazprea/spec/typedef.rst b/gazprea/spec/typedef.rst index a533600..e4724d6 100644 --- a/gazprea/spec/typedef.rst +++ b/gazprea/spec/typedef.rst @@ -61,18 +61,27 @@ Duplicate alias names should raise a `SymbolError` typealias integer ty; typealias character ty; -Some type aliases may be parameterized with an expression, such as with arrays, -such expressions are restricted to be composed exclusively from arithmetic -operations on scalar literals. Practically speaking, this requires constant -folding but *not* constant propagation. +Some type aliases may be parameterized with an expression, such as the size of +an array. Such size expressions must be valid +:ref:`constant expressions `. This permits not only constant +folding of scalar literals but also constant propagation through other +``constexpr`` values, such as global constants. :: typealias integer[1 + 3 - 2] vec_of_two; procedure main() returns integer { - vec_of_two v = 1..3; + vec_of_two v = 1..3; } Should raise a ``SizeError`` on line 3 since the ``vec_of_two`` type has a size of 2 and an array of size 3 is being assigned. +Because the size may be any ``constexpr``, it can reference other constant +expressions rather than being limited to literals: + +:: + + const WIDTH = 4; + typealias integer[WIDTH] row; // legal: WIDTH is a constexpr + diff --git a/gazprea/spec/types/array.rst b/gazprea/spec/types/array.rst index 329d831..6cdcc61 100644 --- a/gazprea/spec/types/array.rst +++ b/gazprea/spec/types/array.rst @@ -26,9 +26,11 @@ array instead of a ``real`` array. #. Explicit Size Declarations - When an array is declared it may be explicitly given a size. This - size can be given as any integer expression, thus the size of the - array may not be known until runtime. + When an array is declared it may be explicitly given a size. This size + must be a :ref:`constant expression `. Every array, whether + explicitly or implicitly sized, has a size that is known at compile time; a + collection whose size is only known at runtime requires a + :ref:`vector `. :: @@ -77,8 +79,9 @@ array instead of a ``real`` array. In this example the compiler can infer both the size and the type of - ``w`` from ``v``. The size may not always be known at compile time, so this - may need to be handled during runtime. + ``w`` from ``v``. As with any array, this inferred size is known at compile + time; a collection whose size is only known at runtime must be a + :ref:`vector `. .. _sssec:array_constr: