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 --- 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 + 9 files changed, 272 insertions(+), 81 deletions(-) create mode 100644 include/elna/boot/validation.h (limited to 'include') 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) -- cgit v1.2.3