aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2026-07-26 19:47:59 +0200
committerEugen Wissner <belka@caraus.de>2026-07-26 19:47:59 +0200
commit5cdaceb77af6a1d98145f8afb34ce6884e21a3d6 (patch)
tree1f75fa1aeee67a72017dbaa2fb006e93c943700d
parentdea1c177cd3592cc24fd15ad446a676da2e4da28 (diff)
downloadelna-5cdaceb77af6a1d98145f8afb34ce6884e21a3d6.tar.gz
Type check traits properly
-rw-r--r--boot/ast.cc53
-rw-r--r--boot/dependency.cc9
-rw-r--r--boot/evaluator.cc340
-rw-r--r--boot/type_check.cc68
-rw-r--r--gcc/gcc/elna-generic.cc85
-rw-r--r--gcc/gcc/elna-spec.cc2
-rw-r--r--gcc/gcc/elna1.cc10
-rw-r--r--include/elna/boot/ast.h21
-rw-r--r--include/elna/boot/dependency.h2
-rw-r--r--include/elna/boot/evaluator.h137
-rw-r--r--include/elna/boot/result.h103
-rw-r--r--include/elna/boot/symbol.h8
-rw-r--r--include/elna/boot/type_check.h31
-rw-r--r--include/elna/gcc/elna-generic.h5
-rw-r--r--testsuite/runnable/compile_time_address_not_equal.elna7
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.