aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--boot/ast.cc20
-rw-r--r--boot/dependency.cc5
-rw-r--r--boot/evaluator.cc563
-rw-r--r--boot/type_check.cc32
-rw-r--r--gcc/Make-lang.in1
-rw-r--r--gcc/gcc/elna-generic.cc68
-rw-r--r--gcc/gcc/elna1.cc26
-rw-r--r--include/elna/boot/ast.h4
-rw-r--r--include/elna/boot/dependency.h5
-rw-r--r--include/elna/boot/evaluator.h100
-rw-r--r--include/elna/boot/type_check.h20
-rw-r--r--include/elna/gcc/elna-generic.h4
-rw-r--r--testsuite/compilable/const_var_chain.elna7
-rw-r--r--testsuite/compilable/traits_size.elna12
-rw-r--r--testsuite/runnable/two_fields_same_type.elna2
15 files changed, 852 insertions, 17 deletions
diff --git a/boot/ast.cc b/boot/ast.cc
index 3454cce..0931a2c 100644
--- a/boot/ast.cc
+++ b/boot/ast.cc
@@ -534,6 +534,16 @@ namespace elna::boot
return nullptr;
}
+ record_constructor_expression *expression::is_record_constructor()
+ {
+ return nullptr;
+ }
+
+ array_constructor_expression *expression::is_array_constructor()
+ {
+ return nullptr;
+ }
+
named_expression *type_expression::is_named()
{
return nullptr;
@@ -721,6 +731,11 @@ namespace elna::boot
visitor->visit(this);
}
+ record_constructor_expression *record_constructor_expression::is_record_constructor()
+ {
+ return this;
+ }
+
array_constructor_expression::array_constructor_expression(const source_position position,
std::uint32_t size, type_expression *element_type,
std::vector<expression *>&& elements)
@@ -733,6 +748,11 @@ namespace elna::boot
visitor->visit(this);
}
+ array_constructor_expression *array_constructor_expression::is_array_constructor()
+ {
+ return this;
+ }
+
array_constructor_expression::~array_constructor_expression()
{
delete m_element_type;
diff --git a/boot/dependency.cc b/boot/dependency.cc
index ada0bac..7bd86fb 100644
--- a/boot/dependency.cc
+++ b/boot/dependency.cc
@@ -56,7 +56,8 @@ namespace elna::boot
return outcome;
}
- error_list analyze_semantics(std::unique_ptr<unit>& tree, symbol_bag bag)
+ error_list analyze_semantics(std::unique_ptr<unit>& tree, symbol_bag bag,
+ const target_info& target)
{
name_analysis_visitor name_analyser(bag);
tree->accept(&name_analyser);
@@ -65,7 +66,7 @@ namespace elna::boot
{
return std::move(name_analyser.errors());
}
- type_analysis_visitor type_analyzer(bag);
+ type_analysis_visitor type_analyzer(bag, target);
tree->accept(&type_analyzer);
if (type_analyzer.has_errors())
diff --git a/boot/evaluator.cc b/boot/evaluator.cc
new file mode 100644
index 0000000..f782dd6
--- /dev/null
+++ b/boot/evaluator.cc
@@ -0,0 +1,563 @@
+/* Constant expression evaluation.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+
+GCC is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#include "elna/boot/evaluator.h"
+#include "elna/boot/ast.h"
+
+#include <numeric>
+#include <algorithm>
+#include <limits>
+
+namespace elna::boot
+{
+ evaluator::evaluator(symbol_bag& bag, const target_info& target,
+ const std::map<std::string, expression*>& evaluated_initializers)
+ : bag(bag), target(target), evaluated_initializers(evaluated_initializers)
+ {
+ }
+
+ std::optional<constant_value> evaluator::evaluate(expression& subject)
+ {
+ if (auto *literal_expression = subject.is_literal())
+ {
+ return evaluate_literal(*literal_expression);
+ }
+ else if (auto *designator = subject.is_designator())
+ {
+ if (auto *named = designator->is_named())
+ {
+ return evaluate_named(*named);
+ }
+ }
+ else if (auto *unary = subject.is_unary())
+ {
+ return evaluate_unary(*unary);
+ }
+ else if (auto *binary = subject.is_binary())
+ {
+ return evaluate_binary(*binary);
+ }
+ else if (auto *cast = subject.is_cast())
+ {
+ return evaluate_cast(*cast);
+ }
+ else if (auto *traits = subject.is_traits())
+ {
+ return evaluate_traits(*traits);
+ }
+ else if (auto *record_constructor = subject.is_record_constructor())
+ {
+ for (const field_initializer& field_initializer : record_constructor->field_initializers)
+ {
+ if (!evaluate(field_initializer.value()))
+ {
+ return std::nullopt;
+ }
+ }
+ return constant_value{ compound_constant{} };
+ }
+ else if (auto *array_constructor = subject.is_array_constructor())
+ {
+ for (expression *element : array_constructor->elements)
+ {
+ if (!evaluate(*element))
+ {
+ return std::nullopt;
+ }
+ }
+ return constant_value{ compound_constant{} };
+ }
+ return std::nullopt;
+ }
+
+ std::optional<constant_value> evaluator::evaluate_literal(literal_expression& subject)
+ {
+ type decoration = subject.type_decoration;
+
+ if (is_primitive_type(decoration, "Int"))
+ {
+ return constant_value{ static_cast<literal<std::int32_t>&>(subject).value };
+ }
+ if (is_primitive_type(decoration, "Word"))
+ {
+ return constant_value{ static_cast<literal<std::uint32_t>&>(subject).value };
+ }
+ if (is_primitive_type(decoration, "Float"))
+ {
+ return constant_value{ static_cast<literal<double>&>(subject).value };
+ }
+ if (is_primitive_type(decoration, "Bool"))
+ {
+ return constant_value{ static_cast<literal<bool>&>(subject).value };
+ }
+ if (is_primitive_type(decoration, "Char"))
+ {
+ return constant_value{ static_cast<literal<unsigned char>&>(subject).value };
+ }
+ if (is_primitive_type(decoration, "Pointer"))
+ {
+ return constant_value{ std::nullptr_t{} };
+ }
+ return std::nullopt;
+ }
+
+ std::optional<constant_value> evaluator::evaluate_named(named_expression& subject)
+ {
+ auto symbol = this->bag.lookup(subject.name);
+
+ if (symbol == nullptr)
+ {
+ return std::nullopt;
+ }
+ auto variable = symbol->is_variable();
+
+ if (variable == nullptr
+ || resolve_aliases(variable->symbol).get<constant_type>() == nullptr)
+ {
+ return std::nullopt;
+ }
+ auto initializer = this->evaluated_initializers.find(subject.name);
+
+ return initializer == this->evaluated_initializers.end()
+ ? std::nullopt
+ : evaluate(*initializer->second);
+ }
+
+ std::optional<constant_value> evaluator::evaluate_unary(unary_expression& subject)
+ {
+ if (subject.operation() == unary_operator::reference)
+ {
+ // The address of a module-level entity is a compile-time
+ // constant. The caller (type analysis) validates the context.
+ return constant_value{ std::nullptr_t{} };
+ }
+ auto operand = evaluate(subject.operand());
+
+ if (!operand)
+ {
+ return std::nullopt;
+ }
+ if (subject.operation() == unary_operator::minus)
+ {
+ return std::visit([](auto&& value) -> std::optional<constant_value> {
+ using T = std::decay_t<decltype(value)>;
+
+ if constexpr (std::is_same_v<T, std::int32_t>)
+ {
+ return value == std::numeric_limits<std::int32_t>::min()
+ ? std::nullopt
+ : std::make_optional<constant_value>(constant_value{ -value });
+ }
+ if constexpr (std::is_same_v<T, double>)
+ {
+ return constant_value{ -value };
+ }
+ return std::nullopt;
+ }, operand.value());
+ }
+ if (subject.operation() == unary_operator::negation)
+ {
+ return std::visit([](auto v) -> std::optional<constant_value> {
+ using T = std::decay_t<decltype(v)>;
+
+ if constexpr (std::is_same_v<T, bool>)
+ {
+ return constant_value{ !v };
+ }
+ else if constexpr (std::is_integral_v<T>)
+ {
+ return constant_value{ ~v };
+ }
+ return std::nullopt;
+ }, operand.value());
+ }
+ return std::nullopt;
+ }
+
+ template<typename T>
+ static std::optional<T> add_overflow(T lhs, T rhs)
+ {
+ if constexpr (std::is_integral_v<T> && !std::is_same_v<T, bool>)
+ {
+ T result;
+ return __builtin_add_overflow(lhs, rhs, &result)
+ ? std::nullopt
+ : std::make_optional<T>(result);
+ }
+ return std::make_optional<T>(lhs + rhs);
+ }
+
+ template<typename T>
+ static std::optional<T> sub_overflow(T lhs, T rhs)
+ {
+ if constexpr (std::is_integral_v<T> && !std::is_same_v<T, bool>)
+ {
+ T result;
+ return __builtin_sub_overflow(lhs, rhs, &result)
+ ? std::nullopt
+ : std::make_optional<T>(result);
+ }
+ return std::make_optional<T>(lhs - rhs);
+ }
+
+ template<typename T>
+ static std::optional<T> mul_overflow(T lhs, T rhs)
+ {
+ if constexpr (std::is_integral_v<T> && !std::is_same_v<T, bool>)
+ {
+ T result;
+ return __builtin_mul_overflow(lhs, rhs, &result)
+ ? std::nullopt
+ : std::make_optional<T>(result);
+ }
+ return std::make_optional<T>(lhs * rhs);
+ }
+
+ template<typename T>
+ static std::optional<constant_value> evaluate_operation(binary_operator op, T lhs, T rhs)
+ {
+ if constexpr (std::is_same_v<T, std::nullptr_t>
+ || std::is_same_v<T, compound_constant>)
+ {
+ switch (op)
+ {
+ case binary_operator::equals:
+ return constant_value{ true };
+ case binary_operator::not_equals:
+ return constant_value{ false };
+ default:
+ return std::nullopt;
+ }
+ }
+ else
+ {
+ switch (op)
+ {
+ case binary_operator::sum:
+ if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
+ {
+ if (auto r = add_overflow(lhs, rhs))
+ {
+ return constant_value{ *r };
+ }
+ }
+ return std::nullopt;
+ case binary_operator::subtraction:
+ if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
+ {
+ if (auto r = sub_overflow(lhs, rhs))
+ {
+ return constant_value{ *r };
+ }
+ }
+ return std::nullopt;
+ case binary_operator::multiplication:
+ if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
+ {
+ if (auto r = mul_overflow(lhs, rhs))
+ {
+ return constant_value{ *r };
+ }
+ }
+ return std::nullopt;
+ case binary_operator::division:
+ if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
+ {
+ if (rhs != static_cast<T>(0))
+ {
+ return constant_value{ lhs / rhs };
+ }
+ }
+ return std::nullopt;
+ case binary_operator::remainder:
+ if constexpr (std::is_integral_v<T> && !std::is_same_v<T, bool>)
+ {
+ if (rhs != static_cast<T>(0))
+ {
+ return constant_value{ lhs % rhs };
+ }
+ }
+ return std::nullopt;
+ case binary_operator::disjunction:
+ if constexpr (std::is_integral_v<T>)
+ {
+ return constant_value{ lhs | rhs };
+ }
+ return std::nullopt;
+ case binary_operator::conjunction:
+ if constexpr (std::is_integral_v<T>)
+ {
+ return constant_value{ lhs & rhs };
+ }
+ return std::nullopt;
+ case binary_operator::exclusive_disjunction:
+ if constexpr (std::is_integral_v<T>)
+ {
+ return constant_value{ lhs ^ rhs };
+ }
+ return std::nullopt;
+ case binary_operator::shift_left:
+ if constexpr (std::is_integral_v<T> && !std::is_same_v<T, bool>)
+ {
+ if (rhs < 0 || static_cast<std::make_unsigned_t<T>>(rhs) >= std::numeric_limits<T>::digits)
+ {
+ return std::nullopt;
+ }
+ return constant_value{ lhs << rhs };
+ }
+ return std::nullopt;
+ case binary_operator::shift_right:
+ if constexpr (std::is_integral_v<T>)
+ {
+ return constant_value{ lhs >> rhs };
+ }
+ return std::nullopt;
+ case binary_operator::equals:
+ return constant_value{ lhs == rhs };
+ case binary_operator::not_equals:
+ return constant_value{ lhs != rhs };
+ case binary_operator::less:
+ if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
+ {
+ return constant_value{ lhs < rhs };
+ }
+ return std::nullopt;
+ case binary_operator::greater:
+ if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
+ {
+ return constant_value{ lhs > rhs };
+ }
+ return std::nullopt;
+ case binary_operator::less_equal:
+ if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
+ {
+ return constant_value{ lhs <= rhs };
+ }
+ return std::nullopt;
+ case binary_operator::greater_equal:
+ if constexpr (std::is_arithmetic_v<T> && !std::is_same_v<T, bool>)
+ {
+ return constant_value{ lhs >= rhs };
+ }
+ return std::nullopt;
+ default:
+ return std::nullopt;
+ }
+ }
+ }
+
+ std::optional<constant_value> evaluator::evaluate_binary(binary_expression& subject)
+ {
+ std::optional<constant_value> lhs = evaluate(subject.lhs());
+ std::optional<constant_value> rhs = evaluate(subject.rhs());
+
+ if (!lhs || !rhs || lhs->index() != rhs->index())
+ {
+ return std::nullopt;
+ }
+ return std::visit([this, &rhs, operation = subject.operation()](auto&& lhs_value) {
+ using T = std::decay_t<decltype(lhs_value)>;
+ auto rhs_value = std::get<T>(rhs.value());
+
+ return evaluate_operation<T>(operation, lhs_value, rhs_value);
+ }, lhs.value());
+ }
+
+ std::optional<constant_value> evaluator::evaluate_cast(cast_expression& subject)
+ {
+ if (auto value = evaluate(subject.value()))
+ {
+ return constant_value{ value.value() };
+ }
+ else
+ {
+ return std::nullopt;
+ }
+ }
+
+ std::optional<std::size_t> evaluator::evaluate_traits_size(const type& subject)
+ {
+ type resolved = resolve_underlying_type(subject);
+
+ if (is_primitive_type(resolved, "Int"))
+ {
+ return target.int_size;
+ }
+ else if (is_primitive_type(resolved, "Word"))
+ {
+ return target.word_size;
+ }
+ else if (is_primitive_type(resolved, "Char"))
+ {
+ return target.char_size;
+ }
+ else if (is_primitive_type(resolved, "Float"))
+ {
+ return target.float_size;
+ }
+ else if (is_primitive_type(resolved, "Pointer"))
+ {
+ return target.pointer_size;
+ }
+ else if (resolved.get<pointer_type>() != nullptr)
+ {
+ return target.pointer_size;
+ }
+ else if (auto array = resolved.get<array_type>())
+ {
+ if (auto element_size = evaluate_traits_size(array->base))
+ {
+ return array->size * element_size.value();
+ }
+ }
+ return std::nullopt;
+ }
+
+ std::optional<std::size_t> evaluator::evaluate_traits_alignment(const type& subject)
+ {
+ type resolved = resolve_underlying_type(subject);
+
+ if (is_primitive_type(resolved, "Int"))
+ {
+ return target.int_alignment;
+ }
+ if (is_primitive_type(resolved, "Word"))
+ {
+ return target.word_alignment;
+ }
+ if (is_primitive_type(resolved, "Char"))
+ {
+ return target.char_alignment;
+ }
+ if (is_primitive_type(resolved, "Float"))
+ {
+ return target.float_alignment;
+ }
+ if (is_primitive_type(resolved, "Pointer"))
+ {
+ return target.pointer_alignment;
+ }
+ if (resolved.get<pointer_type>() != nullptr)
+ {
+ return target.pointer_alignment;
+ }
+ if (auto array = resolved.get<array_type>())
+ {
+ // The alignment of the array equals that of its element.
+ return evaluate_traits_alignment(array->base);
+ }
+ if (auto record = resolved.get<record_type>())
+ {
+ // An empty record has alignment 1.
+ std::optional<std::size_t> by_field_alignment = std::accumulate(
+ std::begin(record->fields), std::end(record->fields), std::make_optional<std::size_t>(1),
+ [this](std::optional<std::size_t> max_alignment, auto& field) {
+ std::optional<std::size_t> field_alignment = evaluate_traits_alignment(field.second);
+
+ return field_alignment.has_value() && max_alignment.has_value()
+ ? std::make_optional(std::max(field_alignment.value(), max_alignment.value()))
+ : std::nullopt;
+ }
+ );
+ if (!by_field_alignment.has_value())
+ {
+ return by_field_alignment;
+ }
+ std::size_t max_alignment = by_field_alignment.value();
+
+ if (!record->base.empty())
+ {
+ if (auto base_alignment = evaluate_traits_alignment(record->base))
+ {
+ max_alignment = std::max(base_alignment.value(), max_alignment);
+ }
+ else
+ {
+ return std::nullopt;
+ }
+ }
+ return max_alignment;
+ }
+ return std::nullopt;
+ }
+
+ std::optional<constant_value> evaluator::evaluate_traits(traits_expression& subject)
+ {
+ if (subject.types.size() != 1)
+ {
+ return std::nullopt;
+ }
+ else if (subject.name.name() == "size")
+ {
+ if (auto size = evaluate_traits_size(subject.types.front()))
+ {
+ return constant_value{ static_cast<std::uint32_t>(size.value()) };
+ }
+ }
+ else if (subject.name.name() == "alignment")
+ {
+ if (auto alignment = evaluate_traits_alignment(subject.types.front()))
+ {
+ return constant_value{ static_cast<std::uint32_t>(alignment.value()) };
+ }
+ }
+ else if (subject.name.name() == "min")
+ {
+ type resolved = resolve_underlying_type(subject.types.front());
+
+ if (is_primitive_type(resolved, "Int"))
+ {
+ return constant_value{ std::numeric_limits<std::int32_t>::min() };
+ }
+ if (is_primitive_type(resolved, "Word"))
+ {
+ return constant_value{ static_cast<std::uint32_t>(0) };
+ }
+ if (is_primitive_type(resolved, "Char"))
+ {
+ return constant_value{ static_cast<unsigned char>(0) };
+ }
+ if (auto enumeration = resolved.get<enumeration_type>())
+ {
+ return constant_value{ 1 };
+ }
+ }
+ else if (subject.name.name() == "max")
+ {
+ type resolved = resolve_underlying_type(subject.types.front());
+
+ if (is_primitive_type(resolved, "Int"))
+ {
+ return constant_value{ std::numeric_limits<std::int32_t>::max() };
+ }
+ if (is_primitive_type(resolved, "Word"))
+ {
+ return constant_value{ std::numeric_limits<std::uint32_t>::max() };
+ }
+ if (is_primitive_type(resolved, "Char"))
+ {
+ return constant_value{ static_cast<unsigned char>(255) };
+ }
+ if (auto enumeration = resolved.get<enumeration_type>())
+ {
+ return constant_value{ static_cast<std::int32_t>(enumeration->members.size()) };
+ }
+ }
+ // #offset(T, field) stays in codegen - needs GCC record layout.
+ return std::nullopt;
+ }
+}
diff --git a/boot/type_check.cc b/boot/type_check.cc
index b42792b..b310e34 100644
--- a/boot/type_check.cc
+++ b/boot/type_check.cc
@@ -21,6 +21,16 @@ along with GCC; see the file COPYING3. If not see
namespace elna::boot
{
+ non_constant_initializer_error::non_constant_initializer_error(const source_position position)
+ : error(position)
+ {
+ }
+
+ std::string non_constant_initializer_error::what() const
+ {
+ return "Variable initializers must be constant expressions";
+ }
+
type_mismatch_error::type_mismatch_error(const source_position position,
type expected, type actual)
: error(position), expected(expected), actual(actual)
@@ -354,8 +364,8 @@ namespace elna::boot
}.run();
}
- type_analysis_visitor::type_analysis_visitor(symbol_bag bag)
- : error_container(), bag(bag)
+ type_analysis_visitor::type_analysis_visitor(symbol_bag bag, const target_info& target)
+ : error_container(), bag(bag), target(target)
{
}
@@ -424,10 +434,26 @@ namespace elna::boot
{
walking_visitor::visit(declaration);
- if (declaration->initializer == nullptr)
+ if (declaration->initializer == nullptr || has_errors())
{
return;
}
+ evaluator constant_evaluator(this->bag, this->target, this->evaluated_initializers);
+ if (!constant_evaluator.evaluate(*declaration->initializer))
+ {
+ add_error<non_constant_initializer_error>(declaration->initializer->position());
+ return;
+ }
+ // Record const variable initializers so later declarations
+ // can chain through them.
+ for (const auto& id : declaration->identifiers)
+ {
+ if (auto var = this->bag.lookup(id.name())->is_variable();
+ resolve_aliases(var->symbol).get<constant_type>() != nullptr)
+ {
+ this->evaluated_initializers[id.name()] = declaration->initializer;
+ }
+ }
for (const identifier_definition& variable_identifier : declaration->identifiers)
{
auto variable_symbol = this->bag.lookup(variable_identifier.name())->is_variable();
diff --git a/gcc/Make-lang.in b/gcc/Make-lang.in
index e6d6f2d..ecb0141 100644
--- a/gcc/Make-lang.in
+++ b/gcc/Make-lang.in
@@ -48,6 +48,7 @@ elna_OBJS = \
elna/elna-tree.o \
elna/elna-builtins.o \
elna/ast.o \
+ elna/evaluator.o \
elna/dependency.o \
elna/driver.o \
elna/lexer.o \
diff --git a/gcc/gcc/elna-generic.cc b/gcc/gcc/elna-generic.cc
index b849715..0516f5a 100644
--- a/gcc/gcc/elna-generic.cc
+++ b/gcc/gcc/elna-generic.cc
@@ -16,7 +16,9 @@ along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
#include <array>
+#include <cstring>
+#include "elna/boot/evaluator.h"
#include "elna/gcc/elna-diagnostic.h"
#include "elna/gcc/elna-generic.h"
#include "elna/gcc/elna1.h"
@@ -35,8 +37,9 @@ along with GCC; see the file COPYING3. If not see
namespace elna::gcc
{
- generic_visitor::generic_visitor(std::shared_ptr<symbol_table> symbol_table, boot::symbol_bag bag)
- : bag(bag), symbols(symbol_table)
+ generic_visitor::generic_visitor(std::shared_ptr<symbol_table> symbol_table,
+ boot::symbol_bag bag, const boot::target_info& target)
+ : bag(bag), symbols(symbol_table), target(target)
{
}
@@ -160,7 +163,7 @@ namespace elna::gcc
void generic_visitor::visit(boot::cast_expression *expression)
{
- tree cast_target = get_inner_alias(expression->type_decoration, this->symbols->scope());
+ tree cast_target = get_inner_alias(expression->type_decoration, this->symbols);
expression->value().accept(this);
tree cast_source = TREE_TYPE(this->current_expression);
@@ -696,6 +699,42 @@ namespace elna::gcc
}
}
+ static tree constant_to_tree(const boot::constant_value& constant_value)
+ {
+ if (std::holds_alternative<std::int32_t>(constant_value))
+ {
+ return build_int_cst(elna_int_type_node, std::get<std::int32_t>(constant_value));
+ }
+ else if (std::holds_alternative<std::uint32_t>(constant_value))
+ {
+ return build_int_cst(elna_word_type_node, std::get<std::uint32_t>(constant_value));
+ }
+
+ else if (std::holds_alternative<double>(constant_value))
+ {
+ auto real_value = std::get<double>(constant_value);
+ REAL_VALUE_TYPE real;
+ HOST_WIDE_INT target_bits[(sizeof(double) + sizeof(HOST_WIDE_INT) - 1)
+ / sizeof(HOST_WIDE_INT)];
+ std::memcpy(target_bits, &real_value, sizeof(real_value));
+ real_from_target(&real, target_bits, REAL_MODE_FORMAT(TYPE_MODE(elna_float_type_node)));
+ return build_real(elna_float_type_node, real);
+ }
+ else if (std::holds_alternative<bool>(constant_value))
+ {
+ return std::get<bool>(constant_value) ? boolean_true_node : boolean_false_node;
+ }
+ else if (std::holds_alternative<unsigned char>(constant_value))
+ {
+ return build_int_cst(elna_char_type_node, std::get<unsigned char>(constant_value));
+ }
+ else if (std::holds_alternative<std::nullptr_t>(constant_value))
+ {
+ return elna_pointer_nil_node;
+ }
+ return NULL_TREE;
+ }
+
void generic_visitor::visit(boot::variable_declaration *declaration)
{
for (const auto& variable_identifier : declaration->identifiers)
@@ -713,7 +752,28 @@ namespace elna::gcc
if (declaration->initializer != nullptr)
{
declaration->initializer->accept(this);
- DECL_INITIAL(declaration_tree) = this->current_expression;
+ tree initializer_tree = this->current_expression;
+
+ std::map<std::string, boot::expression*> evaluated_initializers;
+ boot::evaluator constant_evaluator(this->bag, this->target, evaluated_initializers);
+ if (auto constant_value = constant_evaluator.evaluate(*declaration->initializer))
+ {
+ tree folded = constant_to_tree(constant_value.value());
+ if (folded != NULL_TREE)
+ {
+ initializer_tree = folded;
+ }
+ }
+ // Follow a const variable's DECL_INITIAL when the
+ // evaluator cannot chain (e.g. y := x with x: const Int).
+ if (initializer_tree != NULL_TREE
+ && TREE_CODE(initializer_tree) == VAR_DECL
+ && TREE_READONLY(initializer_tree)
+ && DECL_INITIAL(initializer_tree) != NULL_TREE)
+ {
+ initializer_tree = DECL_INITIAL(initializer_tree);
+ }
+ DECL_INITIAL(declaration_tree) = initializer_tree;
}
else if (!declaration->is_extern && POINTER_TYPE_P(TREE_TYPE(declaration_tree)))
{
diff --git a/gcc/gcc/elna1.cc b/gcc/gcc/elna1.cc
index 0ccee02..e75277e 100644
--- a/gcc/gcc/elna1.cc
+++ b/gcc/gcc/elna1.cc
@@ -52,6 +52,27 @@ union GTY ((desc("TREE_CODE (&%h.generic) == IDENTIFIER_NODE"),
/* Language hooks. */
+static const elna::boot::target_info& get_host_target()
+{
+ static const elna::boot::target_info info = []{
+ elna::boot::target_info target_info;
+
+ target_info.int_size = TYPE_PRECISION(elna_int_type_node) / BITS_PER_UNIT;
+ target_info.int_alignment = TYPE_ALIGN_UNIT(elna_int_type_node);
+ target_info.word_size = TYPE_PRECISION(elna_word_type_node) / BITS_PER_UNIT;
+ target_info.word_alignment = TYPE_ALIGN_UNIT(elna_word_type_node);
+ target_info.pointer_size = TYPE_PRECISION(ptr_type_node) / BITS_PER_UNIT;
+ target_info.pointer_alignment = TYPE_ALIGN_UNIT(ptr_type_node);
+ target_info.char_size = TYPE_PRECISION(elna_char_type_node) / BITS_PER_UNIT;
+ target_info.char_alignment = TYPE_ALIGN_UNIT(elna_char_type_node);
+ target_info.float_size = TYPE_PRECISION(elna_float_type_node) / BITS_PER_UNIT;
+ target_info.float_alignment = TYPE_ALIGN_UNIT(elna_float_type_node);
+
+ return target_info;
+ }();
+ return info;
+}
+
static bool elna_langhook_init(void)
{
build_common_tree_nodes(false);
@@ -125,7 +146,7 @@ static elna::boot::dependency elna_parse_file(dependency_state& state, const cha
outcome_bag.add_import(cached_import->second);
}
}
- outcome.errors() = analyze_semantics(outcome.tree, outcome_bag);
+ outcome.errors() = analyze_semantics(outcome.tree, outcome_bag, get_host_target());
}
if (outcome.has_errors())
{
@@ -148,7 +169,8 @@ static void elna_langhook_parse_file(void)
if (!outcome.has_errors())
{
linemap_add(line_table, LC_ENTER, 0, in_fnames[i], 1);
- elna::gcc::generic_visitor generic_visitor{ state.custom, state.find(in_fnames[i])->second };
+ elna::gcc::generic_visitor generic_visitor{ state.custom,
+ state.find(in_fnames[i])->second, get_host_target() };
outcome.tree->accept(&generic_visitor);
linemap_add(line_table, LC_LEAVE, 0, NULL, 0);
}
diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h
index 42e7666..9d978a3 100644
--- a/include/elna/boot/ast.h
+++ b/include/elna/boot/ast.h
@@ -257,6 +257,8 @@ namespace elna::boot
virtual designator_expression *is_designator();
virtual procedure_call *is_call_expression();
virtual literal_expression *is_literal();
+ virtual record_constructor_expression *is_record_constructor();
+ virtual array_constructor_expression *is_array_constructor();
};
/**
@@ -378,6 +380,7 @@ namespace elna::boot
identifier&& type_name,
std::vector<field_initializer>&& field_initializers);
void accept(parser_visitor *visitor) override;
+ record_constructor_expression *is_record_constructor() override;
};
class array_constructor_expression : public expression
@@ -391,6 +394,7 @@ namespace elna::boot
std::uint32_t size, type_expression *element_type,
std::vector<expression *>&& elements);
void accept(parser_visitor *visitor) override;
+ array_constructor_expression *is_array_constructor() override;
virtual ~array_constructor_expression() override;
};
diff --git a/include/elna/boot/dependency.h b/include/elna/boot/dependency.h
index 5f6eba5..829b669 100644
--- a/include/elna/boot/dependency.h
+++ b/include/elna/boot/dependency.h
@@ -19,6 +19,7 @@ along with GCC; see the file COPYING3. If not see
#include <filesystem>
#include <fstream>
+#include "elna/boot/evaluator.h"
#include "elna/boot/result.h"
#include "elna/boot/ast.h"
#include "elna/boot/symbol.h"
@@ -38,7 +39,8 @@ namespace elna::boot
dependency read_source(std::istream& entry_point);
std::filesystem::path build_path(const std::vector<std::string>& segments);
- error_list analyze_semantics(std::unique_ptr<unit>& tree, symbol_bag bag);
+ error_list analyze_semantics(std::unique_ptr<unit>& tree, symbol_bag bag,
+ const target_info& target);
template<typename T>
class dependency_state
@@ -86,6 +88,5 @@ namespace elna::boot
{
return this->cache.cend();
}
-
};
}
diff --git a/include/elna/boot/evaluator.h b/include/elna/boot/evaluator.h
new file mode 100644
index 0000000..6e82777
--- /dev/null
+++ b/include/elna/boot/evaluator.h
@@ -0,0 +1,100 @@
+/* Constant expression evaluation.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+
+GCC is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#pragma once
+
+#include <cstdint>
+#include <map>
+#include <optional>
+#include <string>
+#include <variant>
+
+#include "elna/boot/symbol.h"
+#include "elna/boot/ast.h"
+
+namespace elna::boot
+{
+ /**
+ * Target machine information, populated by the compiler backend glue layer.
+ */
+ struct target_info
+ {
+ std::size_t int_size{0};
+ std::size_t int_alignment{0};
+ std::size_t word_size{0};
+ std::size_t word_alignment{0};
+ std::size_t pointer_size{0};
+ std::size_t pointer_alignment{0};
+ std::size_t char_size{0};
+ std::size_t char_alignment{0};
+ std::size_t float_size{0};
+ std::size_t float_alignment{0};
+ };
+
+ /**
+ * For aggregate values (arrays, records) signals that every sub-expression
+ * is constant without storing the full aggregate.
+ */
+ struct compound_constant
+ {
+ };
+
+ /**
+ * Typed constant value produced by the constant expression evaluator.
+ */
+ using constant_value = std::variant<
+ std::int32_t,
+ std::uint32_t,
+ double,
+ bool,
+ unsigned char,
+ std::nullptr_t,
+ compound_constant
+ >;
+
+ /**
+ * Called on-demand.
+ */
+ class evaluator
+ {
+ symbol_bag& bag;
+ const target_info& target;
+ const std::map<std::string, expression*>& evaluated_initializers;
+
+ std::optional<constant_value> evaluate_literal(literal_expression& subject);
+ std::optional<constant_value> evaluate_named(named_expression& subject);
+ std::optional<constant_value> evaluate_unary(unary_expression& subject);
+ std::optional<constant_value> evaluate_binary(binary_expression& subject);
+ std::optional<constant_value> evaluate_cast(cast_expression& subject);
+ std::optional<constant_value> evaluate_traits(traits_expression& subject);
+ std::optional<std::size_t> evaluate_traits_size(const type& subject);
+ std::optional<std::size_t> evaluate_traits_alignment(const type& subject);
+
+ public:
+ explicit evaluator(symbol_bag& bag, const target_info& target,
+ const std::map<std::string, expression*>& evaluated_initializers);
+
+ /**
+ * Evaluates an expression at compile time.
+ *
+ * \param expr Expression to evaluate.
+ * \return Expression's constant value or \c std::nullopt if the
+ * expression is not constant.
+ */
+ std::optional<constant_value> evaluate(expression& subject);
+ };
+}
diff --git a/include/elna/boot/type_check.h b/include/elna/boot/type_check.h
index b250f48..5b6ed1c 100644
--- a/include/elna/boot/type_check.h
+++ b/include/elna/boot/type_check.h
@@ -1,4 +1,4 @@
-/* Type analysis.
+/* Type checking.
Copyright (C) 2025 Free Software Foundation, Inc.
GCC is free software; you can redistribute it and/or modify
@@ -22,6 +22,7 @@ along with GCC; see the file COPYING3. If not see
#include <vector>
#include "elna/boot/ast.h"
+#include "elna/boot/evaluator.h"
#include "elna/boot/result.h"
#include "elna/boot/symbol.h"
@@ -163,6 +164,17 @@ namespace elna::boot
};
/**
+ * A module-level variable initializer must be a constant expression.
+ */
+ class non_constant_initializer_error : public error
+ {
+ public:
+ explicit non_constant_initializer_error(const source_position position);
+
+ std::string what() const override;
+ };
+
+ /**
* Chain of responsibility for type compatibility checks.
*
* Populate \c ctx with pre-resolved types, then call \c run().
@@ -199,6 +211,10 @@ namespace elna::boot
{
symbol_bag bag;
std::shared_ptr<procedure_info> current_procedure;
+ const target_info& target;
+
+ // Map from const variable name to its initializer, for chaining.
+ std::map<std::string, expression*> evaluated_initializers;
/*
* Whether an expression of type assignment can be assigned to a variable
@@ -209,7 +225,7 @@ namespace elna::boot
std::vector<std::string>& path);
public:
- explicit type_analysis_visitor(symbol_bag bag);
+ explicit type_analysis_visitor(symbol_bag bag, const target_info& target);
void visit(procedure_declaration *declaration) override;
void visit(unit *unit) override;
diff --git a/include/elna/gcc/elna-generic.h b/include/elna/gcc/elna-generic.h
index dd5c9ef..2a1425c 100644
--- a/include/elna/gcc/elna-generic.h
+++ b/include/elna/gcc/elna-generic.h
@@ -35,6 +35,7 @@ namespace elna::gcc
tree current_expression{ NULL_TREE };
elna::boot::symbol_bag bag;
std::shared_ptr<symbol_table> symbols;
+ const elna::boot::target_info& target;
void enter_scope();
tree leave_scope();
@@ -60,7 +61,8 @@ namespace elna::gcc
bool assert_constant(location_t expression_location);
public:
- generic_visitor(std::shared_ptr<symbol_table> symbol_table, elna::boot::symbol_bag bag);
+ generic_visitor(std::shared_ptr<symbol_table> symbol_table,
+ elna::boot::symbol_bag bag, const elna::boot::target_info& target);
void visit(boot::procedure_declaration *declaration) override;
void visit(boot::procedure_call *call) override;
diff --git a/testsuite/compilable/const_var_chain.elna b/testsuite/compilable/const_var_chain.elna
new file mode 100644
index 0000000..a6db8c8
--- /dev/null
+++ b/testsuite/compilable/const_var_chain.elna
@@ -0,0 +1,7 @@
+var
+ x: const Int := 42
+ y: Int := x
+
+begin
+ assert(y = 42)
+end.
diff --git a/testsuite/compilable/traits_size.elna b/testsuite/compilable/traits_size.elna
new file mode 100644
index 0000000..523045f
--- /dev/null
+++ b/testsuite/compilable/traits_size.elna
@@ -0,0 +1,12 @@
+var
+ i: Int := 1
+ s: Int := cast(#size(Int): Int)
+ a: [10]Int
+ as: Int := cast(#size([10]Int): Int)
+ p: Int := cast(#size(Pointer): Int)
+
+begin
+ assert(s > 0);
+ assert(as = 10 * cast(#size(Int): Int));
+ assert(p > 0)
+end.
diff --git a/testsuite/runnable/two_fields_same_type.elna b/testsuite/runnable/two_fields_same_type.elna
index 0ebcb4e..649dd25 100644
--- a/testsuite/runnable/two_fields_same_type.elna
+++ b/testsuite/runnable/two_fields_same_type.elna
@@ -5,7 +5,7 @@ type
proc f(): Int
var
- r: R := R(3, 2)
+ r: R := R{x: 3, y: 2}
return r.x + r.y