From 36a274c9a8bca944234589220def025d3920b3ef Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Tue, 28 Jul 2026 19:19:32 +0200 Subject: Enforce case label uniqueness and constness --- boot/dependency.cc | 13 +- boot/evaluator.cc | 224 ++-------------------- boot/name_analysis.cc | 1 + boot/result.cc | 75 ++++++++ boot/symbol.cc | 40 ---- boot/type_check.cc | 12 +- boot/validation.cc | 155 +++++++++++++++ gcc/Make-lang.in | 1 + gcc/gcc/elna-builtins.cc | 2 + gcc/gcc/elna-generic.cc | 140 +++----------- gcc/gcc/elna-tree.cc | 103 ++++++++++ include/elna/boot/ast.h | 4 +- include/elna/boot/dependency.h | 6 +- include/elna/boot/evaluator.h | 60 +----- include/elna/boot/name_analysis.h | 22 ++- include/elna/boot/result.h | 126 ++++++++++++ include/elna/boot/symbol.h | 55 ++++-- include/elna/boot/validation.h | 76 ++++++++ include/elna/gcc/elna-generic.h | 2 + include/elna/gcc/elna-tree.h | 2 + testsuite/fail_compilation/case_non_constant.elna | 2 +- testsuite/fail_compilation/case_unique_label.elna | 7 + 22 files changed, 678 insertions(+), 450 deletions(-) create mode 100644 boot/validation.cc create mode 100644 include/elna/boot/validation.h create mode 100644 testsuite/fail_compilation/case_unique_label.elna diff --git a/boot/dependency.cc b/boot/dependency.cc index 63d3952..a44b1f5 100644 --- a/boot/dependency.cc +++ b/boot/dependency.cc @@ -17,13 +17,10 @@ along with GCC; see the file COPYING3. If not see #include "elna/boot/dependency.h" -#include -#include -#include - #include "elna/boot/driver.h" #include "elna/boot/name_analysis.h" #include "elna/boot/type_check.h" +#include "elna/boot/validation.h" #include "parser.hh" namespace elna::boot @@ -73,12 +70,12 @@ namespace elna::boot { return std::move(type_analyzer.errors()); } - constant_folder folder(bag, target); - tree->accept(&folder); + validation_visitor validator(bag, target); + tree->accept(&validator); - if (folder.has_errors()) + if (validator.has_errors()) { - return std::move(folder.errors()); + return std::move(validator.errors()); } return error_list{}; } diff --git a/boot/evaluator.cc b/boot/evaluator.cc index 6d3153e..b49a9ec 100644 --- a/boot/evaluator.cc +++ b/boot/evaluator.cc @@ -16,25 +16,17 @@ along with GCC; see the file COPYING3. If not see . */ #include "elna/boot/evaluator.h" + #include "elna/boot/ast.h" #include #include +#include #include #include 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"; - } - std::optional get_type_properties(const type& subject, const target_info& target) { auto resolved = resolve_underlying_type(subject); @@ -304,6 +296,23 @@ namespace elna::boot std::optional evaluator::evaluate_field_access(field_access_expression& subject) { + auto type_to_check = subject.base().type_decoration; + if (type_to_check.empty()) + { + type_to_check = subject.type_decoration; + } + auto resolved_base = resolve_underlying_type(type_to_check); + if (auto enumeration = resolved_base.get()) + { + auto member_iterator = std::ranges::find(enumeration->members, subject.field().name()); + if (member_iterator != enumeration->members.end()) + { + return constant_value{ + static_cast(std::distance(enumeration->members.begin(), member_iterator) + 1) + }; + } + return std::nullopt; + } auto base = evaluate(subject.base()); if (!base.has_value()) { @@ -770,199 +779,4 @@ namespace elna::boot return std::nullopt; } - /** - * Converts a constant_value to an AST literal expression. - * - * \param value Evaluated constant to convert. - * \param position Source position for the new literal node. - * \param decoration Type decoration to apply to the literal. - * \return A new literal expression, or \c nullptr if \p value - * is an aggregate (handled separately by the caller). - */ - static expression *value_to_expression(const constant_value& value, - const source_position& position, const type& decoration) - { - return std::visit([&position, &decoration](auto&& value) -> expression* - { - using T = std::decay_t; - - if constexpr (std::is_same_v) - { - auto *lit = new literal(position, value); - lit->type_decoration = decoration; - return lit; - } - else if constexpr (std::is_same_v) - { - auto *lit = new literal(position, value); - lit->type_decoration = decoration; - return lit; - } - else if constexpr (std::is_same_v) - { - auto *lit = new literal(position, value); - lit->type_decoration = decoration; - return lit; - } - else if constexpr (std::is_same_v) - { - auto *lit = new literal(position, value); - lit->type_decoration = decoration; - return lit; - } - else if constexpr (std::is_same_v) - { - auto *lit = new literal(position, value); - lit->type_decoration = decoration; - return lit; - } - else if constexpr (std::is_same_v - || std::is_same_v) - { - auto *lit = new literal(position, nullptr); - lit->type_decoration = decoration; - return lit; - } - else - { - return nullptr; - } - }, value); - } - - expression *evaluator::fold(expression& original) - { - auto value = evaluate(original); - if (!value.has_value()) - { - return &original; - } - if (expression *literal = value_to_expression(value.value(), original.position(), original.type_decoration)) - { - delete &original; - return literal; - } - if (auto *record = original.is_record_constructor()) - { - for (auto& field_init : record->field_initializers) - { - fold_aggregate_field(field_init); - } - } - else if (auto *array = original.is_array_constructor()) - { - for (auto& element : array->elements) - { - element = fold(*element); - } - } - return &original; - } - - void evaluator::fold_aggregate_field(field_initializer& field_init) - { - auto value = evaluate(field_init.value()); - if (!value.has_value()) - { - return; - } - expression *literal = value_to_expression(value.value(), - field_init.value().position(), field_init.value().type_decoration); - if (literal != nullptr) - { - field_init.value(*literal); - return; - } - expression *folded = fold(field_init.value()); - if (folded != &field_init.value()) - { - field_init.value(*folded); - } - } - - constant_folder::constant_folder(symbol_bag& bag, const target_info& target) - : bag(bag), target(target), constant_evaluator(this->bag, this->target) - { - } - - void constant_folder::visit(variable_declaration* declaration) - { - if (declaration->initializer == nullptr || has_errors()) - { - return; - } - auto computed = this->constant_evaluator.evaluate(*declaration->initializer); - if (!computed) - { - add_error(declaration->initializer->position()); - return; - } - declaration->initializer = this->constant_evaluator.fold(*declaration->initializer); - - for (const auto& identifier : declaration->identifiers) - { - auto symbol = this->bag.lookup(identifier.name()); - if (symbol == nullptr) - { - continue; - } - if (auto var = symbol->is_variable(); - resolve_aliases(var->symbol).get() != nullptr) - { - var->value = computed; - } - } - } - - void constant_folder::visit(cast_expression *expr) - { - expr->target().accept(this); - expr->value().accept(this); - expr->value(fold_trait(expr->value())); - } - - void constant_folder::visit(binary_expression *expr) - { - expr->lhs().accept(this); - expr->lhs(fold_trait(expr->lhs())); - expr->rhs().accept(this); - expr->rhs(fold_trait(expr->rhs())); - } - - void constant_folder::visit(unary_expression *expr) - { - expr->operand().accept(this); - expr->operand(fold_trait(expr->operand())); - } - - void constant_folder::visit(procedure_call *call) - { - call->callable().accept(this); - for (auto& argument : call->arguments) - { - argument->accept(this); - expression& folded = fold_trait(*argument); - if (&folded != argument) - { - delete argument; - argument = &folded; - } - } - } - - expression& constant_folder::fold_trait(expression& expr) - { - if (auto *trait = expr.is_traits()) - { - if (auto value = this->constant_evaluator.evaluate_traits(*trait)) - { - if (auto *literal = value_to_expression(value.value(), trait->position(), trait->type_decoration)) - { - return *literal; - } - } - } - return expr; - } - } diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc index 07f6d31..077b5d6 100644 --- a/boot/name_analysis.cc +++ b/boot/name_analysis.cc @@ -17,6 +17,7 @@ along with GCC; see the file COPYING3. If not see #include "elna/boot/name_analysis.h" +#include #include namespace elna::boot diff --git a/boot/result.cc b/boot/result.cc index 86b0e7a..0eb5c12 100644 --- a/boot/result.cc +++ b/boot/result.cc @@ -17,6 +17,8 @@ along with GCC; see the file COPYING3. If not see #include "elna/boot/result.h" +#include + namespace elna::boot { location::location(const std::size_t line, const std::size_t column) @@ -137,6 +139,49 @@ namespace elna::boot { return this->m_exported; } + + std::size_t constant_value_hash::operator()(const elna::boot::constant_value& value) const noexcept + { + return std::visit([](auto&& alternative) -> std::size_t { + using T = std::decay_t; + + return std::hash{}(alternative); + }, value); + } + + bool constant_value_hash::operator()(const elna::boot::constant_value& lhs, + const elna::boot::constant_value& rhs) const noexcept + { + return std::visit([](auto&& first, auto&& second) -> bool { + using T = std::decay_t; + using U = std::decay_t; + + if constexpr (std::is_same_v) + { + return first == second; + } + else + { + return false; + } + }, lhs, rhs); + } + + hash_accumulator hash_accumulator::operator+(const std::size_t& that) const + { + hash_accumulator result{}; + + result.m_seed ^= that + hash_accumulator::golden_ratio + // NOLINTNEXTLINE(readability-magic-numbers) + + (this->m_seed << 6) + (this->m_seed >> 2); + + return result; + } + + std::size_t hash_accumulator::seed() const + { + return this->m_seed; + } } std::size_t std::hash::operator()( @@ -144,3 +189,33 @@ std::size_t std::hash::operator()( { return std::hash{}(key.name()); } + +std::size_t std::hash::operator()( + const elna::boot::global_address& key) const noexcept +{ + return std::hash{}(key.name); +} + +std::size_t std::hash>::operator()( + const elna::boot::constant_aggregate& key) const noexcept +{ + const elna::boot::constant_value_hash hasher{}; + auto hash = std::accumulate(key->begin(), key->end(), elna::boot::hash_accumulator{}, + [&hasher](const auto& accumulator, const auto& element) { + return accumulator + hasher(element); + }); + + return hash.seed(); +} + +std::size_t std::hash>::operator()( + const elna::boot::constant_aggregate& key) const noexcept +{ + const elna::boot::constant_value_hash hasher{}; + auto hash = std::accumulate(key->begin(), key->end(), elna::boot::hash_accumulator{}, + [&hasher](const auto& accumulator, const auto& element) { + return accumulator + std::hash{}(element.first) + hasher(element.second); + }); + + return hash.seed(); +} diff --git a/boot/symbol.cc b/boot/symbol.cc index 5e303fb..e0a89e6 100644 --- a/boot/symbol.cc +++ b/boot/symbol.cc @@ -26,46 +26,6 @@ namespace elna::boot { } - type::type(std::shared_ptr primitive) - : payload(primitive) - { - } - - type::type(std::shared_ptr record) - : payload(record) - { - } - - type::type(std::shared_ptr pointer) - : payload(pointer) - { - } - - type::type(std::shared_ptr constant) - : payload(constant) - { - } - - type::type(std::shared_ptr array) - : payload(array) - { - } - - type::type(std::shared_ptr slice) - : payload(slice) - { - } - - type::type(std::shared_ptr procedure) - : payload(procedure) - { - } - - type::type(std::shared_ptr enumeration) - : payload(enumeration) - { - } - template std::shared_ptr type::get() const { diff --git a/boot/type_check.cc b/boot/type_check.cc index 2bb0ddd..343b5aa 100644 --- a/boot/type_check.cc +++ b/boot/type_check.cc @@ -198,9 +198,9 @@ namespace elna::boot } else if (auto record = referent.get()) { - for (const type_field& field : record->fields) + for (const auto& [field_name, field_type] : record->fields) { - if (contains_constant_member(field.second)) + if (contains_constant_member(field_type)) { return true; } @@ -660,14 +660,14 @@ namespace elna::boot } for (const field_initializer& initializer : expression->field_initializers) { - for (const type_field& field : record->fields) + for (const auto& [field_name, field_type]: record->fields) { - if (field.first == initializer.name()) + if (field_name == initializer.name()) { - if (!is_assignable_from(field.second, initializer.value().type_decoration)) + if (!is_assignable_from(field_type, initializer.value().type_decoration)) { add_error( - initializer.value().position(), field.second, + initializer.value().position(), field_type, initializer.value().type_decoration); } break; diff --git a/boot/validation.cc b/boot/validation.cc new file mode 100644 index 0000000..ccc8865 --- /dev/null +++ b/boot/validation.cc @@ -0,0 +1,155 @@ +/* Final validation after constant folding. + 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/validation.h" + +#include +#include +#include + +namespace elna::boot +{ + validation_error::validation_error(const source_position position, payload_type payload) + : error(position), payload(std::move(payload)) + { + } + + std::string validation_error::what() const + { + return std::visit([](const auto& payload) -> std::string { + using T = std::decay_t; + + if constexpr (std::is_same_v) + { + return "Variable initializers must be constant expressions"; + } + else if constexpr (std::is_same_v) + { + return "Duplicate case label"; + } + else if constexpr (std::is_same_v) + { + return "Case label must be a constant expression"; + } + }, this->payload); + } + + std::optional> validation_error::note() const + { + return std::visit([](const auto& payload) -> std::optional> { + using T = std::decay_t; + + if constexpr (std::is_same_v) + { + std::string identifier_list = std::accumulate( + std::next(payload.identifiers.begin()), payload.identifiers.end(), + payload.identifiers.front().name(), + [](const std::string& accumulator, const identifier& next) -> std::string { + return accumulator + ", " + next.name(); + }); + auto position_span = source_position(payload.identifiers.front().position().start(), + payload.identifiers.back().position().end()); + return std::make_pair(std::move(identifier_list), position_span); + } + else if constexpr (std::is_same_v) + { + return std::make_pair("Previous label here", payload.first); + } + else if constexpr (std::is_same_v) + { + return std::nullopt; + } + }, this->payload); + } + + validation_error validation_error::non_constant_initializer_error(const source_position position, + const std::vector& identifiers) + { + non_constant_initializer payload; + payload.identifiers.reserve(identifiers.size()); + + std::ranges::transform(identifiers, std::back_inserter(payload.identifiers), + [](const auto& identifier) { return identifier.id(); }); + + return validation_error(position, std::move(payload)); + } + + validation_visitor::validation_visitor(symbol_bag& bag, const target_info& target) + : bag(bag), target(target), constant_evaluator(this->bag, this->target) + { + } + + void validation_visitor::visit(variable_declaration* declaration) + { + if (declaration->initializer == nullptr || has_errors()) + { + return; + } + auto computed = this->constant_evaluator.evaluate(*declaration->initializer); + if (!computed) + { + auto non_constant_initializer_error = validation_error::non_constant_initializer_error( + declaration->initializer->position(), declaration->identifiers); + add_error(non_constant_initializer_error); + return; + } + for (const auto& identifier : declaration->identifiers) + { + auto variable_symbol = this->bag.lookup(identifier.name())->is_variable(); + variable_symbol->value = computed; + } + } + + void validation_visitor::visit(procedure_declaration *declaration) + { + if (declaration->body.has_value()) + { + auto procedure = this->bag.lookup(declaration->identifier.name())->is_procedure(); + this->bag.enter(procedure->scope); + } + walking_visitor::visit(declaration); + if (declaration->body.has_value()) + { + this->bag.leave(); + } + } + + void validation_visitor::visit(case_statement *statement) + { + walking_visitor::visit(statement); + std::unordered_map seen; + for (const auto& case_block : statement->cases) + { + for (auto *label : case_block.labels) + { + auto value = this->constant_evaluator.evaluate(*label); + if (!value.has_value()) + { + add_error(label->position(), + validation_error::non_constant_case_label{}); + continue; + } + auto [case_position, inserted] = seen.try_emplace(value.value(), label->position()); + if (!inserted) + { + add_error(label->position(), + validation_error::duplicate_case{ case_position->second }); + } + } + } + } +} diff --git a/gcc/Make-lang.in b/gcc/Make-lang.in index 5b8b6a8..690d047 100644 --- a/gcc/Make-lang.in +++ b/gcc/Make-lang.in @@ -57,6 +57,7 @@ elna_OBJS = \ elna/type_check.o \ elna/symbol.o \ elna/result.o \ + elna/validation.o \ $(END) elna1$(exeext): attribs.o $(elna_OBJS) $(BACKEND) $(LIBDEPS) diff --git a/gcc/gcc/elna-builtins.cc b/gcc/gcc/elna-builtins.cc index 226de4a..7e47c30 100644 --- a/gcc/gcc/elna-builtins.cc +++ b/gcc/gcc/elna-builtins.cc @@ -19,6 +19,8 @@ along with GCC; see the file COPYING3. If not see #include "elna/gcc/elna1.h" #include "stor-layout.h" #include "stringpool.h" + +#include "elna/boot/evaluator.h" #include "elna/gcc/elna-tree.h" namespace elna::gcc diff --git a/gcc/gcc/elna-generic.cc b/gcc/gcc/elna-generic.cc index 00581df..559d09c 100644 --- a/gcc/gcc/elna-generic.cc +++ b/gcc/gcc/elna-generic.cc @@ -16,10 +16,8 @@ along with GCC; see the file COPYING3. If not see . */ #include -#include #include -#include "elna/boot/evaluator.h" #include "elna/gcc/elna-diagnostic.h" #include "elna/gcc/elna-generic.h" #include "elna/gcc/elna1.h" @@ -38,7 +36,8 @@ namespace elna::gcc { generic_visitor::generic_visitor(const std::shared_ptr& symbol_table, boot::symbol_bag bag, const boot::target_info& target) - : bag(std::move(bag)), symbols(symbol_table), target(target) + : bag(std::move(bag)), symbols(symbol_table), target(target), + constant_evaluator(this->bag, this->target) { } @@ -483,104 +482,14 @@ namespace elna::gcc cgraph_node::finalize_function(fndecl, true); } - static tree constant_to_tree(const boot::constant_value& constant_value, tree type = NULL_TREE) - { - 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_cstu(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; - constexpr std::size_t bits_size = (sizeof(double) + sizeof(HOST_WIDE_INT) - 1) / sizeof(HOST_WIDE_INT); - std::array target_bits; - - std::memcpy(target_bits.data(), &real_value, sizeof(real_value)); - real_from_target(&real, target_bits.data(), 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_cstu(elna_char_type_node, std::get(constant_value)); - } - else if (std::holds_alternative(constant_value)) - { - return null_pointer_node; - } - else if (std::holds_alternative>(constant_value)) - { - // NOLINTNEXTLINE(readability-simplify-boolean-expr) - if (type == NULL_TREE || !RECORD_OR_UNION_TYPE_P(type)) - { - return NULL_TREE; - } - const auto& aggregate = std::get>(constant_value); - const auto& fields = *aggregate; - vec *tree_arguments = nullptr; - - for (const auto& [field_name, field_value] : fields) - { - tree field_decl = find_field_by_name(UNKNOWN_LOCATION, type, field_name); - if (field_decl == error_mark_node) - { - return NULL_TREE; - } - tree value_tree = constant_to_tree(field_value, TREE_TYPE(field_decl)); - if (value_tree == NULL_TREE) - { - return NULL_TREE; - } - CONSTRUCTOR_APPEND_ELT(tree_arguments, field_decl, value_tree); - } - return build_constructor(type, tree_arguments); - } - else if (std::holds_alternative>(constant_value)) - { - if (type == NULL_TREE || TREE_CODE(type) != ARRAY_TYPE) - { - return NULL_TREE; - } - const auto& aggregate = std::get>(constant_value); - const auto& elements = *aggregate; - tree domain = TYPE_DOMAIN(type); - tree index = TYPE_MIN_VALUE(domain); - tree element_type = TREE_TYPE(type); - vec *tree_arguments = nullptr; - - for (const auto& element : elements) - { - tree element_tree = constant_to_tree(element, element_type); - if (element_tree == NULL_TREE) - { - return NULL_TREE; - } - CONSTRUCTOR_APPEND_ELT(tree_arguments, index, element_tree); - index = int_const_binop(PLUS_EXPR, index, elna_word_one_node); - } - return build_constructor(type, tree_arguments); - } - return NULL_TREE; - } - void generic_visitor::visit(boot::literal *literal) { - this->current_expression = constant_to_tree(boot::constant_value{ literal->value }); + this->current_expression = constant_to_tree(boot::constant_value{ literal->value }, this->symbols); } void generic_visitor::visit(boot::literal *literal) { - this->current_expression = constant_to_tree(boot::constant_value{ literal->value }); + this->current_expression = constant_to_tree(boot::constant_value{ literal->value }, this->symbols); } void generic_visitor::visit(boot::literal *literal) @@ -600,17 +509,17 @@ namespace elna::gcc void generic_visitor::visit(boot::literal *boolean) { - this->current_expression = constant_to_tree(boot::constant_value{ boolean->value }); + this->current_expression = constant_to_tree(boot::constant_value{ boolean->value }, this->symbols); } void generic_visitor::visit(boot::literal *character) { - this->current_expression = constant_to_tree(boot::constant_value{ character->value }); + this->current_expression = constant_to_tree(boot::constant_value{ character->value }, this->symbols); } void generic_visitor::visit(boot::literal *) { - this->current_expression = constant_to_tree(boot::constant_value{ std::nullptr_t{} }); + this->current_expression = constant_to_tree(boot::constant_value{ std::nullptr_t{} }, this->symbols); } void generic_visitor::visit(boot::literal *string) @@ -639,6 +548,19 @@ namespace elna::gcc this->current_expression = build_constructor(slice_type, elms); } + void generic_visitor::visit(boot::traits_expression *trait) + { + auto value = this->constant_evaluator.evaluate_traits(*trait); + + if (!value.has_value()) + { + this->current_expression = error_mark_node; + return; + } + tree type = get_inner_alias(trait->type_decoration, this->symbols); + this->current_expression = constant_to_tree(value.value(), this->symbols, type); + } + void generic_visitor::visit(boot::binary_expression *expression) { expression->lhs().accept(this); @@ -794,15 +716,19 @@ namespace elna::gcc location_t declaration_location = get_location(&declaration->position()); tree declaration_tree = this->symbols->lookup(variable_identifier.name()); + auto variable_symbol = this->bag.lookup(variable_identifier.name())->is_variable(); + if (declaration_tree == NULL_TREE) { - auto variable_symbol = this->bag.lookup(variable_identifier.name())->is_variable(); - declaration_tree = declare_variable(variable_identifier.name(), *variable_symbol, this->symbols); } - // Set initializer if given. The constant_folder pass has - // already folded the initializer into literals at this point. - if (declaration->initializer != nullptr) + if (variable_symbol->value.has_value()) + { + tree type = get_inner_alias(variable_symbol->symbol, this->symbols); + DECL_INITIAL(declaration_tree) = constant_to_tree( + variable_symbol->value.value(), this->symbols, type); + } + else if (declaration->initializer != nullptr) { declaration->initializer->accept(this); DECL_INITIAL(declaration_tree) = this->current_expression; @@ -811,13 +737,9 @@ namespace elna::gcc { DECL_INITIAL(declaration_tree) = null_pointer_node; } + if (variable_symbol->symbol.get() != nullptr) { - auto variable_symbol = this->bag.lookup(variable_identifier.name())->is_variable(); - - if (variable_symbol != nullptr && variable_symbol->symbol.get() != nullptr) - { - TREE_READONLY(declaration_tree) = 1; - } + TREE_READONLY(declaration_tree) = 1; } this->current_expression = NULL_TREE; diff --git a/gcc/gcc/elna-tree.cc b/gcc/gcc/elna-tree.cc index e8a7daa..352d413 100644 --- a/gcc/gcc/elna-tree.cc +++ b/gcc/gcc/elna-tree.cc @@ -15,6 +15,9 @@ You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ +#include +#include + #include "elna/gcc/elna-diagnostic.h" #include "elna/gcc/elna-tree.h" #include "elna/gcc/elna1.h" @@ -281,4 +284,104 @@ namespace elna::gcc } return NULL_TREE; } + + tree constant_to_tree(const boot::constant_value& constant_value, + const std::shared_ptr& symbols, tree type) + { + 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_cstu(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; + constexpr std::size_t bits_size = (sizeof(double) + sizeof(HOST_WIDE_INT) - 1) / sizeof(HOST_WIDE_INT); + std::array target_bits; + + std::memcpy(target_bits.data(), &real_value, sizeof(real_value)); + real_from_target(&real, target_bits.data(), 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_cstu(elna_char_type_node, std::get(constant_value)); + } + else if (std::holds_alternative(constant_value)) + { + return null_pointer_node; + } + else if (std::holds_alternative(constant_value)) + { + const auto& address = std::get(constant_value); + tree decl = symbols->lookup(address.name); + if (decl == NULL_TREE) + { + return NULL_TREE; + } + return build1(ADDR_EXPR, build_pointer_type(TREE_TYPE(decl)), decl); + } + else if (std::holds_alternative>(constant_value)) + { + // NOLINTNEXTLINE(readability-simplify-boolean-expr) + if (type == NULL_TREE || !RECORD_OR_UNION_TYPE_P(type)) + { + return NULL_TREE; + } + const auto& aggregate = std::get>(constant_value); + const auto& fields = *aggregate; + vec *tree_arguments = nullptr; + + for (const auto& [field_name, field_value] : fields) + { + tree field_decl = find_field_by_name(UNKNOWN_LOCATION, type, field_name); + if (field_decl == error_mark_node) + { + return NULL_TREE; + } + tree value_tree = constant_to_tree(field_value, symbols, TREE_TYPE(field_decl)); + if (value_tree == NULL_TREE) + { + return NULL_TREE; + } + CONSTRUCTOR_APPEND_ELT(tree_arguments, field_decl, value_tree); + } + return build_constructor(type, tree_arguments); + } + else if (std::holds_alternative>(constant_value)) + { + if (type == NULL_TREE || TREE_CODE(type) != ARRAY_TYPE) + { + return NULL_TREE; + } + const auto& aggregate = std::get>(constant_value); + const auto& elements = *aggregate; + tree domain = TYPE_DOMAIN(type); + tree index = TYPE_MIN_VALUE(domain); + tree element_type = TREE_TYPE(type); + vec *tree_arguments = nullptr; + + for (const auto& element : elements) + { + tree element_tree = constant_to_tree(element, symbols, element_type); + if (element_tree == NULL_TREE) + { + return NULL_TREE; + } + CONSTRUCTOR_APPEND_ELT(tree_arguments, index, element_tree); + index = int_const_binop(PLUS_EXPR, index, elna_word_one_node); + } + return build_constructor(type, tree_arguments); + } + return NULL_TREE; + } } diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h index 8ee0e8b..9697d1c 100644 --- a/include/elna/boot/ast.h +++ b/include/elna/boot/ast.h @@ -214,8 +214,8 @@ namespace elna::boot void visit(variable_declaration *) override; void visit(procedure_declaration *) override; - void visit(assign_statement *) override; - void visit(if_statement *) override; + void visit(assign_statement *statement) override; + void visit(if_statement *statement) override; void visit(import_declaration *) override; void visit(while_statement *statement) override; void visit(repeat_statement *statement) override; diff --git a/include/elna/boot/dependency.h b/include/elna/boot/dependency.h index 6a3a412..b3c1155 100644 --- a/include/elna/boot/dependency.h +++ b/include/elna/boot/dependency.h @@ -17,13 +17,13 @@ along with GCC; see the file COPYING3. If not see #pragma once -#include -#include -#include "elna/boot/evaluator.h" #include "elna/boot/result.h" #include "elna/boot/ast.h" #include "elna/boot/symbol.h" +#include +#include + namespace elna::boot { class dependency : public error_container diff --git a/include/elna/boot/evaluator.h b/include/elna/boot/evaluator.h index 5812ee8..8b36e1b 100644 --- a/include/elna/boot/evaluator.h +++ b/include/elna/boot/evaluator.h @@ -17,28 +17,14 @@ 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" +#include +#include + 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; - }; - std::optional get_type_properties(const type& subject, const target_info& target); /** @@ -73,7 +59,6 @@ namespace elna::boot std::optional evaluate_cast(cast_expression& subject); std::optional evaluate_traits_size(const type& subject); std::optional evaluate_traits_alignment(const type& subject); - void fold_aggregate_field(field_initializer& field_init); public: std::optional evaluate_traits(traits_expression& subject); @@ -87,44 +72,5 @@ namespace elna::boot * expression is not constant. */ std::optional evaluate(expression& subject); - - /** - * Folds an expression into literals. - * - * For scalars, the original expression is deleted and a new - * literal node is returned. For constructors, fields or - * elements are folded recursively and the original node is - * returned. - * - * \param original Expression to fold. - * \return The folded expression (either a new literal or the - * original aggregate with folded children). - */ - expression *fold(expression& original); - }; - - /** - * Folding pass that validates constant variable initializers. - * - * Runs after name analysis and type checking. Walks all variable - * declarations, evaluates constant initializers, and records them - * so later declarations can chain through earlier ones. - */ - class constant_folder final : public walking_visitor, public error_container - { - symbol_bag& bag; - const target_info& target; - evaluator constant_evaluator; - - expression& fold_trait(expression& expr); - - public: - constant_folder(symbol_bag& bag, const target_info& target); - - void visit(variable_declaration* declaration) override; - void visit(cast_expression *expr) override; - void visit(binary_expression *expr) override; - void visit(unary_expression *expr) override; - void visit(procedure_call *call) override; }; } diff --git a/include/elna/boot/name_analysis.h b/include/elna/boot/name_analysis.h index a4f550d..8f53260 100644 --- a/include/elna/boot/name_analysis.h +++ b/include/elna/boot/name_analysis.h @@ -17,16 +17,15 @@ along with GCC; see the file COPYING3. If not see #pragma once +#include "elna/boot/ast.h" +#include "elna/boot/result.h" +#include "elna/boot/symbol.h" + #include #include -#include #include #include -#include "elna/boot/ast.h" -#include "elna/boot/result.h" -#include "elna/boot/symbol.h" - namespace elna::boot { /** @@ -83,8 +82,17 @@ namespace elna::boot class member_error : public error { public: - struct not_found { std::string name; type composite; }; - struct duplicate { std::string name; type aggregate; std::optional original; std::optional base; }; + struct not_found + { + std::string name; + type composite; + }; + struct duplicate { + std::string name; + type aggregate; + std::optional original; + std::optional base; + }; using payload_type = std::variant; member_error(const source_position position, payload_type payload); diff --git a/include/elna/boot/result.h b/include/elna/boot/result.h index 89753c1..42c96f2 100644 --- a/include/elna/boot/result.h +++ b/include/elna/boot/result.h @@ -19,6 +19,7 @@ along with GCC; see the file COPYING3. If not see #include #include +#include #include #include #include @@ -201,6 +202,11 @@ namespace elna::boot using iterator = std::vector::iterator; using const_iterator = std::vector::const_iterator; + auto operator<=>(const ordered_map& that) const + { + return this->payload <=> that.payload; + } + /** * Finds element with specific key. * @@ -304,6 +310,11 @@ namespace elna::boot { return this->payload.cend(); } + + std::size_t size() const + { + return this->payload.size(); + } }; /** @@ -370,6 +381,21 @@ namespace elna::boot constant_aggregate >; + /** + * Hash and equality for \c constant_value, for use with std::unordered_map. + */ + struct constant_value_hash + { + std::size_t operator()(const constant_value& value) const noexcept; + bool operator()(const constant_value& lhs, const constant_value& rhs) const noexcept; + }; + + /** + * Representation of array and record literals. + * + * \tparam C Used container, like \c std::vector or ordered_map. + * \tparam Alloc Allocator passed to the container. + */ template typename C, template typename Alloc> class constant_aggregate { @@ -406,6 +432,88 @@ namespace elna::boot { return this->container.get(); } + + bool operator==(const constant_aggregate& that) const + requires std::is_same_v>> + { + if (this->container->size() != that.container->size()) + { + return false; + } + return std::ranges::equal(*this->container, *that.container, constant_value_hash{}); + } + + bool operator==(const constant_aggregate& that) const + requires std::is_same_v>> + { + if (this->container->size() != that.container->size()) + { + return false; + } + const constant_value_hash comparator{}; + + return std::ranges::equal(*this->container, *that.container, + [comparator](const auto& lhs, const auto& rhs) { + return lhs.first == rhs.first && comparator(lhs.second, rhs.second); + }); + } + + bool operator!=(const constant_aggregate& that) const + { + return !(*this == that); + } + }; + + /** + * Primary template, intentionally left undefined. Attempting to use this trait + * trait with a second template argument that is not a \c std::variant results + * in a compile error, since no matching specialization exists. + * + * \tparam T The type to search for among the variant's alternatives. + * \tparam V A \c std::variant type to search within. + * + * \see is_in_variant_v + */ + template + struct is_in_variant; + + /** + * Partial specialization implementing the check for std::variant. + * The first argument is the tested type, the second is \c std::variant. + * + * \tparam T The type to search for. + * \tparam Ts The variant's alternative types. + * + * \see is_in_variant_v + */ + template + struct is_in_variant> + : std::disjunction...> + { + }; + + /** + * Convenience variable template for is_in_variant. + * + * \tparam T The type to search for among the variant's alternatives. + * \tparam V A \c std::variant type to search within. + * + * \return Whether \p T is one of \p V's alternative types. + * + * \see is_in_variant + */ + template + inline constexpr bool is_in_variant_v = is_in_variant::value; + + struct hash_accumulator + { + hash_accumulator operator+(const std::size_t& that) const; + + std::size_t seed() const; + + private: + static constexpr std::size_t golden_ratio = 0x9e3779b9; + std::size_t m_seed{ 0 }; }; } @@ -414,3 +522,21 @@ struct std::hash { std::size_t operator()(const elna::boot::identifier& key) const noexcept; }; + +template<> +struct std::hash +{ + std::size_t operator()(const elna::boot::global_address& key) const noexcept; +}; + +template<> +struct std::hash> +{ + std::size_t operator()(const elna::boot::constant_aggregate& key) const noexcept; +}; + +template<> +struct std::hash> +{ + std::size_t operator()(const elna::boot::constant_aggregate& key) const noexcept; +}; diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h index 46d9c2c..dd3e8ff 100644 --- a/include/elna/boot/symbol.h +++ b/include/elna/boot/symbol.h @@ -41,9 +41,15 @@ namespace elna::boot struct procedure_type; struct enumeration_type; + /** + * Represents a type stored in the symbol table. + * + * All types are wrapped in \c std::shared_ptr so that copying is cheap. + * There is also an empty type representing an error. + */ class type { - std::variant< + using Payload = std::variant< std::monostate, std::weak_ptr, std::shared_ptr, @@ -54,21 +60,45 @@ namespace elna::boot std::shared_ptr, std::shared_ptr, std::shared_ptr - > payload; + >; + Payload payload; public: + /** + * Constructs an empty, invalid type. + */ type() = default; + /** + * Constructs a type alias. + * + * The specialization is required because the type is internally stored + * as \c std::weak_ptr. + * + * \param alias Stored type. + */ explicit type(std::shared_ptr alias); - explicit type(std::shared_ptr primitive); - explicit type(std::shared_ptr record); - explicit type(std::shared_ptr pointer); - explicit type(std::shared_ptr constant); - explicit type(std::shared_ptr array); - explicit type(std::shared_ptr slice); - explicit type(std::shared_ptr procedure); - explicit type(std::shared_ptr enumeration); + /** + * Constructs a non empty type. + * + * \tparam T Type kind. + * \param value Concrete type. + */ + template + explicit type(std::shared_ptr value) + requires is_in_variant_v, Payload> + : payload(std::move(value)) + { + } + + /** + * Checks whether \p T is currently stored and returns this concrete + * type. + * + * \tparam T Type kind to check. + * \return Concrete type or \c nullptr. + */ template std::shared_ptr get() const; @@ -76,6 +106,9 @@ namespace elna::boot bool operator==(const type& other) const; explicit operator bool() const; + /** + * \return Whether type holds no concrete type. + */ bool empty() const; /** @@ -129,8 +162,6 @@ namespace elna::boot explicit primitive_type(const std::string& identifier); }; - using type_field = std::pair; - struct record_type { ordered_map fields; diff --git a/include/elna/boot/validation.h b/include/elna/boot/validation.h new file mode 100644 index 0000000..c11a652 --- /dev/null +++ b/include/elna/boot/validation.h @@ -0,0 +1,76 @@ +/* Final validation after constant folding. + 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 "elna/boot/ast.h" +#include "elna/boot/evaluator.h" + +#include + +namespace elna::boot +{ + /** + * Validation error. + */ + class validation_error : public error + { + public: + struct non_constant_initializer + { + std::vector identifiers; + }; + struct duplicate_case + { + source_position first; + }; + struct non_constant_case_label + { + }; + using payload_type = std::variant; + + validation_error(const source_position position, payload_type payload); + + std::string what() const override; + std::optional> note() const override; + + static validation_error non_constant_initializer_error(const source_position position, + const std::vector& identifiers); + + private: + payload_type payload; + }; + + /** + * Validates: + * - case label uniqueness + * - Initializer constness. + */ + class validation_visitor final : public walking_visitor, public error_container + { + symbol_bag& bag; + const target_info& target; + evaluator constant_evaluator; + + public: + validation_visitor(symbol_bag& bag, const target_info& target); + + void visit(variable_declaration* declaration) override; + void visit(procedure_declaration* declaration) override; + void visit(case_statement *statement) override; + }; +} diff --git a/include/elna/gcc/elna-generic.h b/include/elna/gcc/elna-generic.h index c2f154f..b5ba5e0 100644 --- a/include/elna/gcc/elna-generic.h +++ b/include/elna/gcc/elna-generic.h @@ -37,6 +37,7 @@ namespace elna::gcc elna::boot::symbol_bag bag; std::shared_ptr symbols; const elna::boot::target_info& target; + elna::boot::evaluator constant_evaluator; static tree build_equality(boot::binary_expression *expression, tree left, tree right); static tree build_equality_comparison(location_t loc, tree left, tree right, @@ -79,6 +80,7 @@ namespace elna::gcc void visit(boot::literal *character) override; void visit(boot::literal *) override; void visit(boot::literal *string) override; + void visit(boot::traits_expression *trait) override; void visit(boot::binary_expression *expression) override; void visit(boot::unary_expression *expression) override; void visit(boot::variable_declaration *declaration) override; diff --git a/include/elna/gcc/elna-tree.h b/include/elna/gcc/elna-tree.h index a989656..71cc13b 100644 --- a/include/elna/gcc/elna-tree.h +++ b/include/elna/gcc/elna-tree.h @@ -68,6 +68,8 @@ namespace elna::gcc const elna::boot::target_info& get_host_target(); tree extract_constant(tree expression); + tree constant_to_tree(const boot::constant_value& value, + const std::shared_ptr& symbols, tree type = NULL_TREE); template tree call_built_in(location_t call_location, const char *name, tree return_type, Args... arguments) diff --git a/testsuite/fail_compilation/case_non_constant.elna b/testsuite/fail_compilation/case_non_constant.elna index 2f19df8..dc69703 100644 --- a/testsuite/fail_compilation/case_non_constant.elna +++ b/testsuite/fail_compilation/case_non_constant.elna @@ -11,6 +11,6 @@ var begin x := r; case x of - r: (* @Error Expected a constant expression *) + r: (* @Error Case label must be a constant expression *) end end. diff --git a/testsuite/fail_compilation/case_unique_label.elna b/testsuite/fail_compilation/case_unique_label.elna new file mode 100644 index 0000000..a97fb37 --- /dev/null +++ b/testsuite/fail_compilation/case_unique_label.elna @@ -0,0 +1,7 @@ +begin + case 3 of + 1: + | 3: + | 1 + 2: (* @Error Duplicate case label *) + end +end. -- cgit v1.2.3