From d78dd67f7c74730f92ed9a3a23842094e1cd7a6b Mon Sep 17 00:00:00 2001 From: Drew Date: Mon, 22 Jun 2026 14:11:23 -0400 Subject: [PATCH 1/5] spec: add constant-expression (constexpr) specification Add spec/constexpr.rst defining a constexpr as a property of a const variable whose initializer is fully evaluable at compile time: the composition rules, the recursive validation requirement, and the aggregate (array and tuple) cases. This is the analysis-derived notion used to size statically-sized arrays and to constrain global initializers. Examples are written against the staging array model (no spread operator). --- gazprea/spec/constexpr.rst | 143 +++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 gazprea/spec/constexpr.rst diff --git a/gazprea/spec/constexpr.rst b/gazprea/spec/constexpr.rst new file mode 100644 index 0000000..96854fa --- /dev/null +++ b/gazprea/spec/constexpr.rst @@ -0,0 +1,143 @@ +.. _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. Operators, including ``+``, ``-``, ``*``, ``/``, ``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`` statically-sized array is a ``constexpr`` if: + + 1. Its size is a valid ``constexpr``. + 2. All of its element initializers are valid ``constexpr``\ s. + + Dynamically-sized arrays (e.g., ``integer[*]``) cannot be ``constexpr`` + aggregates as their size is not known at compile time. + + :: + + // ---------------------------- + // 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; // Legal, but not constexpr: y is not a + // constexpr, so it cannot + // be used as a static array size. arr is + // a dynamic-sized array + + 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 From 28400b1f2f9cd7ffb029c6fafa76f3b5d83523cc Mon Sep 17 00:00:00 2001 From: Drew Date: Mon, 22 Jun 2026 14:11:34 -0400 Subject: [PATCH 2/5] spec: register constexpr page in the toctree Add spec/constexpr to the Language Specification toctree, after declarations, so the new page is included in the rendered docs. --- gazprea/index.rst | 1 + 1 file changed, 1 insertion(+) 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 From 540794023f7b914786acdc222e98d6feac1dfc56 Mon Sep 17 00:00:00 2001 From: Drew Date: Mon, 22 Jun 2026 14:11:48 -0400 Subject: [PATCH 3/5] spec: require global initializers to be constant expressions Replace the old single-scalar-literal restriction on global initializers with the constexpr rule (see sec:constexpr). Globals may now reference other globals and use arithmetic and constexpr aggregates, provided the initializer is fully evaluable at compile time. Spell out the consequences: no function/procedure/IO in initializers, no dynamically-sized array globals, and all globals are implicitly constexpr. The list of legal global statements is unchanged. --- gazprea/spec/globals.rst | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/gazprea/spec/globals.rst b/gazprea/spec/globals.rst index 453676f..f42dff3 100644 --- a/gazprea/spec/globals.rst +++ b/gazprea/spec/globals.rst @@ -24,11 +24,16 @@ 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 dynamically-sized array type (e.g. ``integer[*]``), + because its size would not be known at compile time. +* All globals are implicitly ``constexpr``. From abc8691b195fe48ba157aa77664ebb518ea21e1c Mon Sep 17 00:00:00 2001 From: Agent Date: Tue, 30 Jun 2026 11:12:05 -0400 Subject: [PATCH 4/5] docs(constexpr): clarify array sizes are constexpr, not dynamic Array-alias sizes must be valid constexprs (permitting constant propagation), updating typedef.rst away from the literals-only rule. Disentangle inferred size from dynamic size: integer[*] denotes an inferred, compile-time-known size, while the dynamically-sized type is the vector. Explicit array sizes must be constexprs, so a runtime-sized collection requires a vector. Updates globals.rst, constexpr.rst, and array.rst accordingly. Assisted-by: Agent (claude) --- gazprea/spec/constexpr.rst | 17 ++++++++++------- gazprea/spec/globals.rst | 7 +++++-- gazprea/spec/typedef.rst | 19 ++++++++++++++----- gazprea/spec/types/array.rst | 13 ++++++++----- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/gazprea/spec/constexpr.rst b/gazprea/spec/constexpr.rst index 96854fa..65c81fb 100644 --- a/gazprea/spec/constexpr.rst +++ b/gazprea/spec/constexpr.rst @@ -77,13 +77,16 @@ allowing them to be used to define other constants. #. Arrays - A ``const`` statically-sized array is a ``constexpr`` if: + A ``const`` array is a ``constexpr`` if: 1. Its size is a valid ``constexpr``. 2. All of its element initializers are valid ``constexpr``\ s. - Dynamically-sized arrays (e.g., ``integer[*]``) cannot be ``constexpr`` - aggregates as their size is not known at compile time. + 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]`` *can* be a ``constexpr`` when its + initializer is itself a ``constexpr``: ``[*]`` denotes an inferred size, not + a dynamic one. :: @@ -118,10 +121,10 @@ allowing them to be used to define other constants. 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; // Legal, but not constexpr: y is not a - // constexpr, so it cannot - // be used as a static array size. arr is - // a dynamic-sized array + 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 diff --git a/gazprea/spec/globals.rst b/gazprea/spec/globals.rst index f42dff3..1c647d6 100644 --- a/gazprea/spec/globals.rst +++ b/gazprea/spec/globals.rst @@ -32,8 +32,11 @@ 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 dynamically-sized array type (e.g. ``integer[*]``), - because its size would not be known at compile time. +* 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: From 256b9ad47c96da0f732a6fac68c845a043011b90 Mon Sep 17 00:00:00 2001 From: Ayrton Chilibeck Date: Tue, 30 Jun 2026 19:47:35 -0400 Subject: [PATCH 5/5] docs(constexpr): clarify operators for constexpr --- gazprea/spec/constexpr.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gazprea/spec/constexpr.rst b/gazprea/spec/constexpr.rst index 65c81fb..49fcb4d 100644 --- a/gazprea/spec/constexpr.rst +++ b/gazprea/spec/constexpr.rst @@ -20,7 +20,7 @@ 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. Operators, including ``+``, ``-``, ``*``, ``/``, ``not``, ``and``, ``or``, +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. @@ -84,7 +84,7 @@ allowing them to be used to define other constants. 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]`` *can* be a ``constexpr`` when its + 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.