diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-07-26 19:47:59 +0200 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-07-26 19:47:59 +0200 |
| commit | 5cdaceb77af6a1d98145f8afb34ce6884e21a3d6 (patch) | |
| tree | 1f75fa1aeee67a72017dbaa2fb006e93c943700d | |
| parent | dea1c177cd3592cc24fd15ad446a676da2e4da28 (diff) | |
| download | elna-5cdaceb77af6a1d98145f8afb34ce6884e21a3d6.tar.gz | |
Type check traits properly
| -rw-r--r-- | boot/ast.cc | 53 | ||||
| -rw-r--r-- | boot/dependency.cc | 9 | ||||
| -rw-r--r-- | boot/evaluator.cc | 340 | ||||
| -rw-r--r-- | boot/type_check.cc | 68 | ||||
| -rw-r--r-- | gcc/gcc/elna-generic.cc | 85 | ||||
| -rw-r--r-- | gcc/gcc/elna-spec.cc | 2 | ||||
| -rw-r--r-- | gcc/gcc/elna1.cc | 10 | ||||
| -rw-r--r-- | include/elna/boot/ast.h | 21 | ||||
| -rw-r--r-- | include/elna/boot/dependency.h | 2 | ||||
| -rw-r--r-- | include/elna/boot/evaluator.h | 137 | ||||
| -rw-r--r-- | include/elna/boot/result.h | 103 | ||||
| -rw-r--r-- | include/elna/boot/symbol.h | 8 | ||||
| -rw-r--r-- | include/elna/boot/type_check.h | 31 | ||||
| -rw-r--r-- | include/elna/gcc/elna-generic.h | 5 | ||||
| -rw-r--r-- | testsuite/runnable/compile_time_address_not_equal.elna | 7 |
15 files changed, 643 insertions, 238 deletions
diff --git a/boot/ast.cc b/boot/ast.cc index 69d72fb..a2f108a 100644 --- a/boot/ast.cc +++ b/boot/ast.cc @@ -800,6 +800,15 @@ namespace elna::boot return *this->m_value; } + void field_initializer::value(expression& value) + { + if (this->m_value != &value) + { + delete this->m_value; + this->m_value = &value; + } + } + record_constructor_expression::record_constructor_expression(const source_position position, identifier&& type_name, std::vector<field_initializer>&& field_initializers) @@ -1255,16 +1264,34 @@ namespace elna::boot return this; } - expression& binary_expression::lhs() + expression& binary_expression::lhs() const { return *m_lhs; } - expression& binary_expression::rhs() + void binary_expression::lhs(expression& lhs) + { + if (this->m_lhs != &lhs) + { + delete this->m_lhs; + this->m_lhs = &lhs; + } + } + + expression& binary_expression::rhs() const { return *m_rhs; } + void binary_expression::rhs(expression& rhs) + { + if (this->m_rhs != &rhs) + { + delete this->m_rhs; + this->m_rhs = &rhs; + } + } + binary_operator binary_expression::operation() const { return m_operator; @@ -1297,11 +1324,20 @@ namespace elna::boot return this; } - expression& unary_expression::operand() + expression& unary_expression::operand() const { return *m_operand; } + void unary_expression::operand(expression& operand) + { + if (this->m_operand != &operand) + { + delete this->m_operand; + this->m_operand = &operand; + } + } + unary_operator unary_expression::operation() const { return this->m_operator; @@ -1367,11 +1403,20 @@ namespace elna::boot return *m_target; } - expression& cast_expression::value() + expression& cast_expression::value() const { return *m_value; } + void cast_expression::value(expression& value) + { + if (this->m_value != &value) + { + delete this->m_value; + this->m_value = &value; + } + } + cast_expression::~cast_expression() { delete m_target; diff --git a/boot/dependency.cc b/boot/dependency.cc index dc827fa..63d3952 100644 --- a/boot/dependency.cc +++ b/boot/dependency.cc @@ -56,7 +56,7 @@ namespace elna::boot return outcome; } - error_list analyze_semantics(std::unique_ptr<unit>& tree, const 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); @@ -73,6 +73,13 @@ namespace elna::boot { return std::move(type_analyzer.errors()); } + constant_folder folder(bag, target); + tree->accept(&folder); + + if (folder.has_errors()) + { + return std::move(folder.errors()); + } return error_list{}; } diff --git a/boot/evaluator.cc b/boot/evaluator.cc index bb50bd3..6d3153e 100644 --- a/boot/evaluator.cc +++ b/boot/evaluator.cc @@ -19,11 +19,22 @@ along with GCC; see the file COPYING3. If not see #include "elna/boot/ast.h" #include <algorithm> +#include <cstddef> #include <limits> #include <ranges> 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<type_properties> get_type_properties(const type& subject, const target_info& target) { auto resolved = resolve_underlying_type(subject); @@ -127,9 +138,8 @@ 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) + evaluator::evaluator(symbol_bag& bag, const target_info& target) + : bag(bag), target(target) { } @@ -145,6 +155,18 @@ namespace elna::boot { return evaluate_named(*named); } + if (auto *array_access = designator->is_array_access()) + { + return evaluate_array_access(*array_access); + } + if (auto *field_access = designator->is_field_access()) + { + return evaluate_field_access(*field_access); + } + if (auto *slicing = designator->is_slicing()) + { + return evaluate_slicing(*slicing); + } } else if (auto *unary = subject.is_unary()) { @@ -240,20 +262,109 @@ namespace elna::boot { return std::nullopt; } - auto initializer = this->evaluated_initializers.find(subject.name); + return variable->value; + } + + std::optional<constant_value> evaluator::evaluate_array_access(array_access_expression& subject) + { + auto base = evaluate(subject.base()); + auto index = evaluate(subject.index()); + if (!base.has_value() || !index.has_value()) + { + return std::nullopt; + } + auto *array = std::get_if<constant_aggregate<std::vector>>(&base.value()); + if (array == nullptr) + { + return std::nullopt; + } + std::size_t position; + if (auto *int_index = std::get_if<std::int32_t>(&index.value())) + { + if (*int_index < 0) + { + return std::nullopt; + } + position = static_cast<std::size_t>(*int_index); + } + else if (auto *word_index = std::get_if<std::uint32_t>(&index.value())) + { + position = *word_index; + } + else + { + return std::nullopt; + } + if (position >= (*array)->size()) + { + return std::nullopt; + } + return (*array)->at(position); + } + + std::optional<constant_value> evaluator::evaluate_field_access(field_access_expression& subject) + { + auto base = evaluate(subject.base()); + if (!base.has_value()) + { + return std::nullopt; + } + auto *record = std::get_if<constant_aggregate<ordered_map>>(&base.value()); + if (record == nullptr) + { + return std::nullopt; + } + auto pos = (*record)->find(subject.field().name()); + if (pos == (*record)->end()) + { + return std::nullopt; + } + return pos->second; + } - return initializer == this->evaluated_initializers.end() - ? std::nullopt - : evaluate(*initializer->second); + std::optional<constant_value> evaluator::evaluate_slicing(slicing_expression& subject) + { + auto base = evaluate(subject.base()); + auto start = evaluate(subject.start()); + auto end = evaluate(subject.end()); + if (!base.has_value() || !start.has_value() || !end.has_value()) + { + return std::nullopt; + } + auto *array = std::get_if<constant_aggregate<std::vector>>(&base.value()); + auto *start_idx = std::get_if<std::int32_t>(&start.value()); + auto *end_idx = std::get_if<std::int32_t>(&end.value()); + if (array == nullptr || start_idx == nullptr || end_idx == nullptr + || *start_idx < 0 || *end_idx < 0) + { + return std::nullopt; + } + auto start_pos = static_cast<std::size_t>(*start_idx); + auto end_pos = static_cast<std::size_t>(*end_idx); + if (start_pos > end_pos || end_pos > (*array)->size()) + { + return std::nullopt; + } + auto slice_begin = std::next((*array)->begin(), static_cast<std::ptrdiff_t>(start_pos)); + auto slice_end = std::next((*array)->begin(), static_cast<std::ptrdiff_t>(end_pos)); + + return constant_value{ + constant_aggregate<std::vector>{ std::vector<constant_value>(slice_begin, slice_end) } + }; } 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{} }; + if (auto *designator = subject.operand().is_designator()) + { + if (auto *named = designator->is_named()) + { + return constant_value{ global_address{ .name = named->name } }; + } + } + return std::nullopt; } auto operand = evaluate(subject.operand()); @@ -367,6 +478,19 @@ namespace elna::boot return std::nullopt; } } + else if constexpr (std::is_same_v<T, global_address>) + { + switch (operation) + { + using enum binary_operator; + case equals: + return constant_value{ lhs == rhs }; + case not_equals: + return constant_value{ lhs != rhs }; + default: + return std::nullopt; + } + } else { switch (operation) @@ -645,4 +769,200 @@ 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<decltype(value)>; + + if constexpr (std::is_same_v<T, std::int32_t>) + { + auto *lit = new literal<std::int32_t>(position, value); + lit->type_decoration = decoration; + return lit; + } + else if constexpr (std::is_same_v<T, std::uint32_t>) + { + auto *lit = new literal<std::uint32_t>(position, value); + lit->type_decoration = decoration; + return lit; + } + else if constexpr (std::is_same_v<T, double>) + { + auto *lit = new literal<double>(position, value); + lit->type_decoration = decoration; + return lit; + } + else if constexpr (std::is_same_v<T, bool>) + { + auto *lit = new literal<bool>(position, value); + lit->type_decoration = decoration; + return lit; + } + else if constexpr (std::is_same_v<T, unsigned char>) + { + auto *lit = new literal<unsigned char>(position, value); + lit->type_decoration = decoration; + return lit; + } + else if constexpr (std::is_same_v<T, std::nullptr_t> + || std::is_same_v<T, global_address>) + { + auto *lit = new literal<std::nullptr_t>(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<non_constant_initializer_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<constant_type>() != 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/type_check.cc b/boot/type_check.cc index c88a7d1..2bb0ddd 100644 --- a/boot/type_check.cc +++ b/boot/type_check.cc @@ -22,14 +22,30 @@ 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) + trait_error::trait_error(const source_position position, const std::string& trait_name, + payload_type payload) + : error(position), trait_name(trait_name), m_payload(payload) { } - std::string non_constant_initializer_error::what() const + std::string trait_error::what() const { - return "Variable initializers must be constant expressions"; + return std::visit([this](auto&& payload) -> std::string + { + using T = std::decay_t<decltype(payload)>; + + if constexpr (std::is_same_v<T, argument_count>) + { + return "Trait #" + this->trait_name + " expects " + + std::to_string(payload.expected) + " argument" + + (payload.expected != 1 ? "s" : "") + ", got " + + std::to_string(payload.actual); + } + else + { + return "The second argument to the #" + this->trait_name + " trait must be a field name"; + } + }, this->m_payload); } type_mismatch_error::type_mismatch_error(const source_position position, @@ -453,22 +469,6 @@ namespace elna::boot { 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& identifier : declaration->identifiers) - { - if (auto var = this->bag.lookup(identifier.name())->is_variable(); - resolve_aliases(var->symbol).get<constant_type>() != nullptr) - { - this->evaluated_initializers[identifier.name()] = declaration->initializer; - } - } for (const identifier_definition& variable_identifier : declaration->identifiers) { auto variable_symbol = this->bag.lookup(variable_identifier.name())->is_variable(); @@ -864,4 +864,32 @@ namespace elna::boot expression->lhs().type_decoration, expression->rhs().type_decoration, operation); } } + + void type_analysis_visitor::visit(traits_expression *trait) + { + walking_visitor::visit(trait); + + if (trait->name == "size" || trait->name == "alignment" + || trait->name == "min" || trait->name == "max") + { + if (trait->arguments.size() != 1) + { + add_error<trait_error>(trait->position(), trait->name.name(), + trait_error::argument_count{ .expected = 1, .actual = trait->arguments.size() }); + } + } + else if (trait->name == "offset") + { + if (trait->arguments.size() != 2) + { + add_error<trait_error>(trait->position(), trait->name.name(), + trait_error::argument_count{ .expected = 2, .actual = trait->arguments.size() }); + } + else if (trait->arguments.at(1)->is_named() == nullptr) + { + add_error<trait_error>(trait->arguments.at(1)->position(), trait->name.name(), + trait_error::offset_not_field_name{}); + } + } + } } diff --git a/gcc/gcc/elna-generic.cc b/gcc/gcc/elna-generic.cc index 7491372..00581df 100644 --- a/gcc/gcc/elna-generic.cc +++ b/gcc/gcc/elna-generic.cc @@ -39,8 +39,6 @@ namespace elna::gcc generic_visitor::generic_visitor(const std::shared_ptr<symbol_table>& symbol_table, boot::symbol_bag bag, const boot::target_info& target) : bag(std::move(bag)), symbols(symbol_table), target(target) - , const_evaluator(std::make_unique<boot::evaluator>(this->bag, this->target, - this->evaluated_initializers)) { } @@ -802,32 +800,12 @@ namespace elna::gcc declaration_tree = declare_variable(variable_identifier.name(), *variable_symbol, this->symbols); } - // Set initializer if given. + // Set initializer if given. The constant_folder pass has + // already folded the initializer into literals at this point. if (declaration->initializer != nullptr) { declaration->initializer->accept(this); - 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(), TREE_TYPE(declaration_tree)); - 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; + DECL_INITIAL(declaration_tree) = this->current_expression; } else if (!declaration->is_extern && POINTER_TYPE_P(TREE_TYPE(declaration_tree))) { @@ -918,63 +896,6 @@ namespace elna::gcc } } - void generic_visitor::visit(boot::traits_expression *trait) - { - location_t trait_location = get_location(&trait->position()); - - if (trait->name == "size" || trait->name == "alignment" - || trait->name == "min" || trait->name == "max") - { - if (trait->arguments.size() != 1) - { - error_at(trait_location, "Trait '%s' expects 1 argument, got %lu", - trait->name.name().c_str(), trait->arguments.size()); - this->current_expression = error_mark_node; - return; - } - if (auto value = this->const_evaluator->evaluate_traits(*trait)) - { - this->current_expression = constant_to_tree(*value); - } - else - { - this->current_expression = error_mark_node; - } - } - else if (trait->name == "offset") - { - if (trait->arguments.size() != 2) - { - error_at(trait_location, "Trait '%s' expects 2 arguments, got %lu", - trait->name.name().c_str(), trait->arguments.size()); - this->current_expression = error_mark_node; - return; - } - auto *field_type = trait->arguments.at(1)->is_named(); - - if (field_type == nullptr) - { - error_at(trait_location, - "The second argument to the offset trait is expected to be a field name," - "got a type expression"); - this->current_expression = error_mark_node; - return; - } - if (auto value = this->const_evaluator->evaluate_traits(*trait)) - { - this->current_expression = constant_to_tree(*value); - } - else - { - this->current_expression = error_mark_node; - } - } - else - { - this->current_expression = error_mark_node; - } - } - void generic_visitor::visit(boot::field_access_expression *expression) { expression->base().accept(this); diff --git a/gcc/gcc/elna-spec.cc b/gcc/gcc/elna-spec.cc index 5d1ace1..5b3b1c2 100644 --- a/gcc/gcc/elna-spec.cc +++ b/gcc/gcc/elna-spec.cc @@ -22,7 +22,7 @@ void lang_specific_driver(struct cl_decoded_option ** /* in_decoded_options */, } /* Called before linking. Returns 0 on success and -1 on failure. */ -int lang_specific_pre_link(void) +int lang_specific_pre_link() { return 0; } diff --git a/gcc/gcc/elna1.cc b/gcc/gcc/elna1.cc index 8eb103b..0602b99 100644 --- a/gcc/gcc/elna1.cc +++ b/gcc/gcc/elna1.cc @@ -52,7 +52,7 @@ union GTY ((desc("TREE_CODE (&%h.generic) == IDENTIFIER_NODE"), /* Language hooks. */ -static bool elna_langhook_init(void) +static bool elna_langhook_init() { build_common_tree_nodes(false); @@ -115,7 +115,7 @@ static elna::boot::dependency elna_parse_file(dependency_state& state, const cha if (cached_import == std::cend(state)) { - auto filename_pointer = ggc_strdup(sub_path.native().c_str()); + const char *filename_pointer = ggc_strdup(sub_path.native().c_str()); elna_parse_file(state, filename_pointer); cached_import = state.find(sub_path); @@ -137,7 +137,7 @@ static elna::boot::dependency elna_parse_file(dependency_state& state, const cha return outcome; } -static void elna_langhook_parse_file(void) +static void elna_langhook_parse_file() { dependency_state state{ elna::gcc::builtin_symbol_table() }; @@ -223,7 +223,7 @@ static tree elna_langhook_type_for_mode(enum machine_mode mode, int unsignedp) return nullptr; } -static bool global_bindings_p(void) +static bool global_bindings_p() { return current_function_decl == NULL_TREE; } @@ -239,7 +239,7 @@ static tree elna_langhook_builtin_function(tree decl) return decl; } -static unsigned int elna_langhook_option_lang_mask(void) +static unsigned int elna_langhook_option_lang_mask() { return CL_Elna; } diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h index d6804df..8ee0e8b 100644 --- a/include/elna/boot/ast.h +++ b/include/elna/boot/ast.h @@ -407,6 +407,7 @@ namespace elna::boot const std::string& name() const; const identifier& id() const; expression& value() const; + void value(expression& value); private: identifier m_name; @@ -417,7 +418,7 @@ namespace elna::boot { public: const identifier type_name; - const std::vector<field_initializer> field_initializers; + std::vector<field_initializer> field_initializers; record_constructor_expression(const source_position position, identifier&& type_name, @@ -431,7 +432,7 @@ namespace elna::boot public: const std::uint32_t size; type_expression *const m_element_type; - const std::vector<expression *> elements; + std::vector<expression *> elements; array_constructor_expression(const source_position position, std::uint32_t size, type_expression *element_type, @@ -478,7 +479,7 @@ namespace elna::boot const std::vector<identifier_definition> identifiers; type_expression& variable_type(); - expression *const initializer{ nullptr }; + expression *initializer{ nullptr }; const bool is_extern{ false }; }; @@ -579,7 +580,8 @@ namespace elna::boot cast_expression *is_cast() override; type_expression& target(); - expression& value(); + expression& value() const; + void value(expression& value); ~cast_expression() override; }; @@ -744,7 +746,7 @@ namespace elna::boot designator_expression *m_callable; public: - const std::vector<expression *> arguments; + std::vector<expression *> arguments; procedure_call(const source_position position, designator_expression *callable, std::vector<expression *>&& arguments); @@ -941,8 +943,10 @@ namespace elna::boot void accept(parser_visitor *visitor) override; binary_expression *is_binary() override; - expression& lhs(); - expression& rhs(); + expression& lhs() const; + void lhs(expression& lhs); + expression& rhs() const; + void rhs(expression& rhs); binary_operator operation() const; void operation(binary_operator operation); @@ -961,7 +965,8 @@ namespace elna::boot void accept(parser_visitor *visitor) override; unary_expression *is_unary() override; - expression& operand(); + expression& operand() const; + void operand(expression& operand); unary_operator operation() const; void operation(unary_operator operation); diff --git a/include/elna/boot/dependency.h b/include/elna/boot/dependency.h index e6cfb36..6a3a412 100644 --- a/include/elna/boot/dependency.h +++ b/include/elna/boot/dependency.h @@ -39,7 +39,7 @@ 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, const symbol_bag& bag, + error_list analyze_semantics(std::unique_ptr<unit>& tree, symbol_bag& bag, const target_info& target); template<typename T> diff --git a/include/elna/boot/evaluator.h b/include/elna/boot/evaluator.h index 2d832bf..5812ee8 100644 --- a/include/elna/boot/evaluator.h +++ b/include/elna/boot/evaluator.h @@ -18,7 +18,6 @@ along with GCC; see the file COPYING3. If not see #pragma once #include <cstdint> -#include <map> #include <memory> #include <optional> #include <string> @@ -30,93 +29,14 @@ along with GCC; see the file COPYING3. If not see namespace elna::boot { /** - * Size and alignment of a single type on the target machine. + * A module-level variable initializer must be a constant expression. */ - struct type_properties + class non_constant_initializer_error : public error { - std::size_t size{0}; - std::size_t alignment{0}; - }; - - /** - * Size, alignment and offset of each field of a record. - */ - struct record_properties - { - ordered_map<std::size_t> offset_map; - std::size_t size; - std::size_t alignment; - }; - - /** - * Target machine information, populated by the compiler backend glue layer. - */ - struct target_info - { - type_properties int_properties; - type_properties word_properties; - type_properties pointer_properties; - type_properties char_properties; - type_properties float_properties; - type_properties bool_properties; - }; - - template<template<typename, typename> typename C, template<typename> typename Alloc = std::allocator> - class constant_aggregate; - - /** - * Typed constant value produced by the constant expression evaluator. - * - * Record constructors are stored as ordered_map keyed by field name, - * array constructors as a vector of elements. - */ - using constant_value = std::variant< - std::int32_t, - std::uint32_t, - double, - bool, - unsigned char, - std::nullptr_t, - constant_aggregate<ordered_map>, - constant_aggregate<std::vector> - >; - - template<template<typename, typename> typename C, template<typename> typename Alloc> - class constant_aggregate - { - using Container = C<constant_value, Alloc<constant_value>>; - std::shared_ptr<Container> container; - public: - explicit constant_aggregate(const Container& value) - : container(std::make_shared<Container>(value)) - { - } - - explicit constant_aggregate(const Container&& value) - : container(std::make_shared<Container>(std::move(value))) - { - } - - Container& operator*() - { - return *this->container; - } - - const Container& operator*() const - { - return *this->container; - } - - Container *operator->() - { - return this->container.get(); - } - - const Container *operator->() const - { - return this->container.get(); - } + explicit non_constant_initializer_error(const source_position position); + + std::string what() const override; }; std::optional<type_properties> get_type_properties(const type& subject, const target_info& target); @@ -142,20 +62,22 @@ namespace elna::boot { symbol_bag& bag; const target_info& target; - const std::map<std::string, expression*>& evaluated_initializers; static std::optional<constant_value> evaluate_literal(literal_expression& subject); std::optional<constant_value> evaluate_named(named_expression& subject); + std::optional<constant_value> evaluate_array_access(array_access_expression& subject); + std::optional<constant_value> evaluate_field_access(field_access_expression& subject); + std::optional<constant_value> evaluate_slicing(slicing_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<std::size_t> evaluate_traits_size(const type& subject); std::optional<std::size_t> evaluate_traits_alignment(const type& subject); + void fold_aggregate_field(field_initializer& field_init); public: std::optional<constant_value> evaluate_traits(traits_expression& subject); - explicit evaluator(symbol_bag& bag, const target_info& target, - const std::map<std::string, expression*>& evaluated_initializers); + explicit evaluator(symbol_bag& bag, const target_info& target); /** * Evaluates an expression at compile time. @@ -165,5 +87,44 @@ namespace elna::boot * expression is not constant. */ std::optional<constant_value> 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/result.h b/include/elna/boot/result.h index 0719151..89753c1 100644 --- a/include/elna/boot/result.h +++ b/include/elna/boot/result.h @@ -18,6 +18,7 @@ along with GCC; see the file COPYING3. If not see #pragma once #include <cstddef> +#include <cstdint> #include <string> #include <deque> #include <memory> @@ -304,6 +305,108 @@ namespace elna::boot return this->payload.cend(); } }; + + /** + * Size and alignment of a single type on the target machine. + */ + struct type_properties + { + std::size_t size{0}; + std::size_t alignment{0}; + }; + + /** + * Size, alignment and offset of each field of a record. + */ + struct record_properties + { + ordered_map<std::size_t> offset_map; + std::size_t size; + std::size_t alignment; + }; + + /** + * Target machine information, populated by the compiler backend glue layer. + */ + struct target_info + { + type_properties int_properties; + type_properties word_properties; + type_properties pointer_properties; + type_properties char_properties; + type_properties float_properties; + type_properties bool_properties; + }; + + /** + * Address of a module-level variable, tracked by name so that + * different variables have distinct addresses at compile time. + */ + struct global_address + { + std::string name; + + auto operator<=>(const global_address&) const = default; + }; + + template<template<typename, typename> typename C, template<typename> typename Alloc = std::allocator> + class constant_aggregate; + + /** + * Typed constant value produced by the constant expression evaluator. + * + * Record constructors are stored as ordered_map keyed by field name, + * array constructors as a vector of elements. + */ + using constant_value = std::variant< + std::int32_t, + std::uint32_t, + double, + bool, + unsigned char, + std::nullptr_t, + global_address, + constant_aggregate<ordered_map>, + constant_aggregate<std::vector> + >; + + template<template<typename, typename> typename C, template<typename> typename Alloc> + class constant_aggregate + { + using Container = C<constant_value, Alloc<constant_value>>; + std::shared_ptr<Container> container; + + public: + explicit constant_aggregate(const Container& value) + : container(std::make_shared<Container>(value)) + { + } + + explicit constant_aggregate(const Container&& value) + : container(std::make_shared<Container>(std::move(value))) + { + } + + Container& operator*() + { + return *this->container; + } + + const Container& operator*() const + { + return *this->container; + } + + Container *operator->() + { + return this->container.get(); + } + + const Container *operator->() const + { + return this->container.get(); + } + }; } template<> diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h index 5b56904..46d9c2c 100644 --- a/include/elna/boot/symbol.h +++ b/include/elna/boot/symbol.h @@ -20,13 +20,14 @@ along with GCC; see the file COPYING3. If not see #include <cstdint> #include <forward_list> #include <memory> +#include <optional> #include <string> #include <unordered_map> #include <utility> -#include <variant> -#include <vector> #include "elna/boot/result.h" +#include <variant> +#include <vector> namespace elna::boot { @@ -359,6 +360,9 @@ namespace elna::boot /// Whether this is an extern symbol. const bool is_extern; + /// Evaluated constant value, set by the constant folder. + std::optional<constant_value> value; + /** * Constructs a variable symbol information. * diff --git a/include/elna/boot/type_check.h b/include/elna/boot/type_check.h index 3a2b61d..bd32c08 100644 --- a/include/elna/boot/type_check.h +++ b/include/elna/boot/type_check.h @@ -22,7 +22,6 @@ 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" @@ -125,18 +124,30 @@ namespace elna::boot }; /** - * Type passed to a trait like #min or #max does not support - * the trait. + * A trait invocation has invalid arguments. */ - /** - * A module-level variable initializer must be a constant expression. - */ - class non_constant_initializer_error : public error + class trait_error : public error { public: - explicit non_constant_initializer_error(const source_position position); + /// Wrong number of arguments passed to a trait. + struct argument_count + { + std::size_t expected; + std::size_t actual; + }; + /// \c \#offset second argument is not a field name. + struct offset_not_field_name {}; + + using payload_type = std::variant<argument_count, offset_not_field_name>; + + trait_error(const source_position position, const std::string& trait_name, + payload_type payload); std::string what() const override; + + private: + std::string trait_name; + payload_type m_payload; }; /** @@ -217,9 +228,6 @@ namespace elna::boot 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 * of type assignee. @@ -248,6 +256,7 @@ namespace elna::boot void visit(if_statement *statement) override; void visit(record_constructor_expression *expression) override; void visit(array_constructor_expression *expression) override; + void visit(traits_expression *trait) override; void visit(slicing_expression *expression) override; void visit(unary_expression *expression) override; void visit(binary_expression *expression) override; diff --git a/include/elna/gcc/elna-generic.h b/include/elna/gcc/elna-generic.h index 27fdb43..c2f154f 100644 --- a/include/elna/gcc/elna-generic.h +++ b/include/elna/gcc/elna-generic.h @@ -17,11 +17,9 @@ along with GCC; see the file COPYING3. If not see #pragma once -#include <map> #include <memory> #include <string> #include "elna/boot/ast.h" -#include "elna/boot/evaluator.h" #include "elna/boot/symbol.h" #include "elna/gcc/elna-tree.h" @@ -39,8 +37,6 @@ namespace elna::gcc elna::boot::symbol_bag bag; std::shared_ptr<symbol_table> symbols; const elna::boot::target_info& target; - std::map<std::string, boot::expression *> evaluated_initializers; - std::unique_ptr<boot::evaluator> const_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, @@ -76,7 +72,6 @@ namespace elna::gcc void visit(boot::procedure_declaration *declaration) override; void visit(boot::procedure_call *call) override; void visit(boot::cast_expression *expression) override; - void visit(boot::traits_expression *trait) override; void visit(boot::literal<std::int32_t> *literal) override; void visit(boot::literal<std::uint32_t> *literal) override; void visit(boot::literal<double> *literal) override; diff --git a/testsuite/runnable/compile_time_address_not_equal.elna b/testsuite/runnable/compile_time_address_not_equal.elna new file mode 100644 index 0000000..b011227 --- /dev/null +++ b/testsuite/runnable/compile_time_address_not_equal.elna @@ -0,0 +1,7 @@ +var + x, y: const Int + z: const Bool := @x = @y + +begin + assert(~z) +end. |
