From 2e1e95dc716167853be4a4b6acaaee1688142744 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sun, 19 Jul 2026 02:08:06 +0200 Subject: Implement initializer evaluator --- boot/ast.cc | 20 + boot/dependency.cc | 5 +- boot/evaluator.cc | 563 +++++++++++++++++++++++++++ boot/type_check.cc | 32 +- gcc/Make-lang.in | 1 + gcc/gcc/elna-generic.cc | 68 +++- gcc/gcc/elna1.cc | 26 +- include/elna/boot/ast.h | 4 + include/elna/boot/dependency.h | 5 +- include/elna/boot/evaluator.h | 100 +++++ include/elna/boot/type_check.h | 20 +- include/elna/gcc/elna-generic.h | 4 +- testsuite/compilable/const_var_chain.elna | 7 + testsuite/compilable/traits_size.elna | 12 + testsuite/runnable/two_fields_same_type.elna | 2 +- 15 files changed, 852 insertions(+), 17 deletions(-) create mode 100644 boot/evaluator.cc create mode 100644 include/elna/boot/evaluator.h create mode 100644 testsuite/compilable/const_var_chain.elna create mode 100644 testsuite/compilable/traits_size.elna 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&& 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& tree, symbol_bag bag) + error_list analyze_semantics(std::unique_ptr& 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 +. */ + +#include "elna/boot/evaluator.h" +#include "elna/boot/ast.h" + +#include +#include +#include + +namespace elna::boot +{ + evaluator::evaluator(symbol_bag& bag, const target_info& target, + const std::map& evaluated_initializers) + : bag(bag), target(target), evaluated_initializers(evaluated_initializers) + { + } + + std::optional 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 evaluator::evaluate_literal(literal_expression& subject) + { + type decoration = subject.type_decoration; + + if (is_primitive_type(decoration, "Int")) + { + return constant_value{ static_cast&>(subject).value }; + } + if (is_primitive_type(decoration, "Word")) + { + return constant_value{ static_cast&>(subject).value }; + } + if (is_primitive_type(decoration, "Float")) + { + return constant_value{ static_cast&>(subject).value }; + } + if (is_primitive_type(decoration, "Bool")) + { + return constant_value{ static_cast&>(subject).value }; + } + if (is_primitive_type(decoration, "Char")) + { + return constant_value{ static_cast&>(subject).value }; + } + if (is_primitive_type(decoration, "Pointer")) + { + return constant_value{ std::nullptr_t{} }; + } + return std::nullopt; + } + + std::optional 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() == 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 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 { + using T = std::decay_t; + + if constexpr (std::is_same_v) + { + return value == std::numeric_limits::min() + ? std::nullopt + : std::make_optional(constant_value{ -value }); + } + if constexpr (std::is_same_v) + { + return constant_value{ -value }; + } + return std::nullopt; + }, operand.value()); + } + if (subject.operation() == unary_operator::negation) + { + return std::visit([](auto v) -> std::optional { + using T = std::decay_t; + + if constexpr (std::is_same_v) + { + return constant_value{ !v }; + } + else if constexpr (std::is_integral_v) + { + return constant_value{ ~v }; + } + return std::nullopt; + }, operand.value()); + } + return std::nullopt; + } + + template + static std::optional add_overflow(T lhs, T rhs) + { + if constexpr (std::is_integral_v && !std::is_same_v) + { + T result; + return __builtin_add_overflow(lhs, rhs, &result) + ? std::nullopt + : std::make_optional(result); + } + return std::make_optional(lhs + rhs); + } + + template + static std::optional sub_overflow(T lhs, T rhs) + { + if constexpr (std::is_integral_v && !std::is_same_v) + { + T result; + return __builtin_sub_overflow(lhs, rhs, &result) + ? std::nullopt + : std::make_optional(result); + } + return std::make_optional(lhs - rhs); + } + + template + static std::optional mul_overflow(T lhs, T rhs) + { + if constexpr (std::is_integral_v && !std::is_same_v) + { + T result; + return __builtin_mul_overflow(lhs, rhs, &result) + ? std::nullopt + : std::make_optional(result); + } + return std::make_optional(lhs * rhs); + } + + template + static std::optional evaluate_operation(binary_operator op, T lhs, T rhs) + { + if constexpr (std::is_same_v + || std::is_same_v) + { + 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 && !std::is_same_v) + { + if (auto r = add_overflow(lhs, rhs)) + { + return constant_value{ *r }; + } + } + return std::nullopt; + case binary_operator::subtraction: + if constexpr (std::is_arithmetic_v && !std::is_same_v) + { + if (auto r = sub_overflow(lhs, rhs)) + { + return constant_value{ *r }; + } + } + return std::nullopt; + case binary_operator::multiplication: + if constexpr (std::is_arithmetic_v && !std::is_same_v) + { + if (auto r = mul_overflow(lhs, rhs)) + { + return constant_value{ *r }; + } + } + return std::nullopt; + case binary_operator::division: + if constexpr (std::is_arithmetic_v && !std::is_same_v) + { + if (rhs != static_cast(0)) + { + return constant_value{ lhs / rhs }; + } + } + return std::nullopt; + case binary_operator::remainder: + if constexpr (std::is_integral_v && !std::is_same_v) + { + if (rhs != static_cast(0)) + { + return constant_value{ lhs % rhs }; + } + } + return std::nullopt; + case binary_operator::disjunction: + if constexpr (std::is_integral_v) + { + return constant_value{ lhs | rhs }; + } + return std::nullopt; + case binary_operator::conjunction: + if constexpr (std::is_integral_v) + { + return constant_value{ lhs & rhs }; + } + return std::nullopt; + case binary_operator::exclusive_disjunction: + if constexpr (std::is_integral_v) + { + return constant_value{ lhs ^ rhs }; + } + return std::nullopt; + case binary_operator::shift_left: + if constexpr (std::is_integral_v && !std::is_same_v) + { + if (rhs < 0 || static_cast>(rhs) >= std::numeric_limits::digits) + { + return std::nullopt; + } + return constant_value{ lhs << rhs }; + } + return std::nullopt; + case binary_operator::shift_right: + if constexpr (std::is_integral_v) + { + 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 && !std::is_same_v) + { + return constant_value{ lhs < rhs }; + } + return std::nullopt; + case binary_operator::greater: + if constexpr (std::is_arithmetic_v && !std::is_same_v) + { + return constant_value{ lhs > rhs }; + } + return std::nullopt; + case binary_operator::less_equal: + if constexpr (std::is_arithmetic_v && !std::is_same_v) + { + return constant_value{ lhs <= rhs }; + } + return std::nullopt; + case binary_operator::greater_equal: + if constexpr (std::is_arithmetic_v && !std::is_same_v) + { + return constant_value{ lhs >= rhs }; + } + return std::nullopt; + default: + return std::nullopt; + } + } + } + + std::optional evaluator::evaluate_binary(binary_expression& subject) + { + std::optional lhs = evaluate(subject.lhs()); + std::optional 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; + auto rhs_value = std::get(rhs.value()); + + return evaluate_operation(operation, lhs_value, rhs_value); + }, lhs.value()); + } + + std::optional evaluator::evaluate_cast(cast_expression& subject) + { + if (auto value = evaluate(subject.value())) + { + return constant_value{ value.value() }; + } + else + { + return std::nullopt; + } + } + + std::optional 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() != nullptr) + { + return target.pointer_size; + } + else if (auto array = resolved.get()) + { + if (auto element_size = evaluate_traits_size(array->base)) + { + return array->size * element_size.value(); + } + } + return std::nullopt; + } + + std::optional 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() != nullptr) + { + return target.pointer_alignment; + } + if (auto array = resolved.get()) + { + // The alignment of the array equals that of its element. + return evaluate_traits_alignment(array->base); + } + if (auto record = resolved.get()) + { + // An empty record has alignment 1. + std::optional by_field_alignment = std::accumulate( + std::begin(record->fields), std::end(record->fields), std::make_optional(1), + [this](std::optional max_alignment, auto& field) { + std::optional 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 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(size.value()) }; + } + } + else if (subject.name.name() == "alignment") + { + if (auto alignment = evaluate_traits_alignment(subject.types.front())) + { + return constant_value{ static_cast(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::min() }; + } + if (is_primitive_type(resolved, "Word")) + { + return constant_value{ static_cast(0) }; + } + if (is_primitive_type(resolved, "Char")) + { + return constant_value{ static_cast(0) }; + } + if (auto enumeration = resolved.get()) + { + 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::max() }; + } + if (is_primitive_type(resolved, "Word")) + { + return constant_value{ std::numeric_limits::max() }; + } + if (is_primitive_type(resolved, "Char")) + { + return constant_value{ static_cast(255) }; + } + if (auto enumeration = resolved.get()) + { + return constant_value{ static_cast(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(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() != 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 . */ #include +#include +#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, boot::symbol_bag bag) - : bag(bag), symbols(symbol_table) + generic_visitor::generic_visitor(std::shared_ptr 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(constant_value)) + { + return build_int_cst(elna_int_type_node, std::get(constant_value)); + } + else if (std::holds_alternative(constant_value)) + { + return build_int_cst(elna_word_type_node, std::get(constant_value)); + } + + else if (std::holds_alternative(constant_value)) + { + auto real_value = std::get(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(constant_value)) + { + return std::get(constant_value) ? boolean_true_node : boolean_false_node; + } + else if (std::holds_alternative(constant_value)) + { + return build_int_cst(elna_char_type_node, std::get(constant_value)); + } + else if (std::holds_alternative(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 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_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&& 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 #include +#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& segments); - error_list analyze_semantics(std::unique_ptr& tree, symbol_bag bag); + error_list analyze_semantics(std::unique_ptr& tree, symbol_bag bag, + const target_info& target); template 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 +. */ + +#pragma once + +#include +#include +#include +#include +#include + +#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& evaluated_initializers; + + std::optional evaluate_literal(literal_expression& subject); + std::optional evaluate_named(named_expression& subject); + std::optional evaluate_unary(unary_expression& subject); + std::optional evaluate_binary(binary_expression& subject); + std::optional evaluate_cast(cast_expression& subject); + std::optional evaluate_traits(traits_expression& subject); + std::optional evaluate_traits_size(const type& subject); + std::optional evaluate_traits_alignment(const type& subject); + + public: + explicit evaluator(symbol_bag& bag, const target_info& target, + const std::map& 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 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 #include "elna/boot/ast.h" +#include "elna/boot/evaluator.h" #include "elna/boot/result.h" #include "elna/boot/symbol.h" @@ -162,6 +163,17 @@ namespace elna::boot std::string what() const override; }; + /** + * 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. * @@ -199,6 +211,10 @@ namespace elna::boot { symbol_bag bag; std::shared_ptr current_procedure; + const target_info& target; + + // Map from const variable name to its initializer, for chaining. + std::map evaluated_initializers; /* * Whether an expression of type assignment can be assigned to a variable @@ -209,7 +225,7 @@ namespace elna::boot std::vector& 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 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, elna::boot::symbol_bag bag); + generic_visitor(std::shared_ptr 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 -- cgit v1.2.3