Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gazprea/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
146 changes: 146 additions & 0 deletions gazprea/spec/constexpr.rst
Original file line number Diff line number Diff line change
@@ -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 <ssec:array>`.

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<integer> 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
20 changes: 14 additions & 6 deletions gazprea/spec/globals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <sec:constexpr>`. 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``.


19 changes: 14 additions & 5 deletions gazprea/spec/typedef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <sec:constexpr>`. 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

13 changes: 8 additions & 5 deletions gazprea/spec/types/array.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <sec:constexpr>`. 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 <ssec:vector>`.

::

Expand Down Expand Up @@ -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 <ssec:vector>`.

.. _sssec:array_constr:

Expand Down