/* Constant expression evaluation. Copyright (C) 2025 Free Software Foundation, Inc. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ #include "elna/boot/evaluator.h" #include "elna/boot/ast.h" #include #include #include #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); if (is_primitive_type(resolved, "Int") || resolved.get() != nullptr) { return target.int_properties; } else if (is_primitive_type(resolved, "Word")) { return target.word_properties; } else if (is_primitive_type(resolved, "Char")) { return target.char_properties; } else if (is_primitive_type(resolved, "Float")) { return target.float_properties; } else if (is_primitive_type(resolved, "Bool")) { return target.bool_properties; } else if (resolved.get() != nullptr) { return type_properties{ .size = target.pointer_properties.size + target.word_properties.size, .alignment = target.pointer_properties.alignment }; } else if (is_any_pointer_type(resolved)) { return target.pointer_properties; } else if (auto array = resolved.get()) { if (auto element = get_type_properties(array->base, target)) { return type_properties{ .size = array->size * element->size, .alignment = element->alignment }; } } else if (auto record = resolved.get()) { auto record_layout = layout_record(record, target); if (record_layout.has_value()) { return type_properties{ .size = record_layout.value().size, .alignment = record_layout.value().alignment }; } } return std::nullopt; } std::optional layout_record(const std::shared_ptr& subject, const target_info& target) { std::size_t current_offset{ 0 }; std::size_t size{ 0 }; std::size_t alignment{ 1 }; ordered_map result_map; std::vector chain; for (auto current_record = subject; current_record != nullptr;) { chain.push_back(current_record.get()); if (current_record->base.empty()) { break; } current_record = resolve_underlying_type(current_record->base).get(); } for (auto const *current_record : std::views::reverse(chain)) { for (auto const& field : current_record->fields) { auto props = get_type_properties(field.second, target); if (!props.has_value()) { return std::nullopt; } size = (size + props->alignment - 1) & ~(props->alignment - 1); size += props->size; alignment = std::max(alignment, props->alignment); current_offset = (current_offset + props->alignment - 1) & ~(props->alignment - 1); result_map.insert(field.first, current_offset); current_offset += props->size; } } return record_properties{ .offset_map = std::move(result_map), .size = (size + alignment - 1) & ~(alignment - 1), .alignment = alignment }; } evaluator::evaluator(symbol_bag& bag, const target_info& target) : bag(bag), target(target) { } std::optional evaluator::evaluate(expression& subject) { if (auto *literal_expression = subject.is_literal()) { return evaluate_literal(*literal_expression); } else if (auto *designator = subject.is_designator()) { if (auto *named = designator->is_named()) { return evaluate_named(*named); } 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()) { return evaluate_unary(*unary); } else if (auto *binary = subject.is_binary()) { return evaluate_binary(*binary); } else if (auto *cast = subject.is_cast()) { return evaluate_cast(*cast); } else if (auto *traits = subject.is_traits()) { return evaluate_traits(*traits); } else if (auto *record_constructor = subject.is_record_constructor()) { ordered_map aggregate; for (const field_initializer& field_initializer : record_constructor->field_initializers) { auto value = evaluate(field_initializer.value()); if (!value) { return std::nullopt; } aggregate.insert(field_initializer.name(), std::move(*value)); } return constant_value{ constant_aggregate{ std::move(aggregate) } }; } else if (auto *array_constructor = subject.is_array_constructor()) { std::vector elements; for (expression *element : array_constructor->elements) { auto value = evaluate(*element); if (!value) { return std::nullopt; } elements.push_back(std::move(*value)); } return constant_value{ constant_aggregate{ std::move(elements) } }; } return std::nullopt; } std::optional evaluator::evaluate_literal(literal_expression& subject) { type const decoration = subject.type_decoration; if (is_primitive_type(decoration, "Int")) { return constant_value{ static_cast&>(subject).value }; } if (is_primitive_type(decoration, "Word")) { return constant_value{ static_cast&>(subject).value }; } if (is_primitive_type(decoration, "Float")) { return constant_value{ static_cast&>(subject).value }; } if (is_primitive_type(decoration, "Bool")) { return constant_value{ static_cast&>(subject).value }; } if (is_primitive_type(decoration, "Char")) { return constant_value{ static_cast&>(subject).value }; } if (is_primitive_type(decoration, "Pointer")) { return constant_value{ std::nullptr_t{} }; } return std::nullopt; } std::optional evaluator::evaluate_named(named_expression& subject) { auto symbol = this->bag.lookup(subject.name); if (symbol == nullptr) { return std::nullopt; } auto variable = symbol->is_variable(); if (variable == nullptr || resolve_aliases(variable->symbol).get() == nullptr) { return std::nullopt; } return variable->value; } std::optional 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>(&base.value()); if (array == nullptr) { return std::nullopt; } std::size_t position; if (auto *int_index = std::get_if(&index.value())) { if (*int_index < 0) { return std::nullopt; } position = static_cast(*int_index); } else if (auto *word_index = std::get_if(&index.value())) { position = *word_index; } else { return std::nullopt; } if (position >= (*array)->size()) { return std::nullopt; } return (*array)->at(position); } std::optional 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>(&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; } std::optional 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>(&base.value()); auto *start_idx = std::get_if(&start.value()); auto *end_idx = std::get_if(&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(*start_idx); auto end_pos = static_cast(*end_idx); if (start_pos > end_pos || end_pos > (*array)->size()) { return std::nullopt; } auto slice_begin = std::next((*array)->begin(), static_cast(start_pos)); auto slice_end = std::next((*array)->begin(), static_cast(end_pos)); return constant_value{ constant_aggregate{ std::vector(slice_begin, slice_end) } }; } std::optional evaluator::evaluate_unary(unary_expression& subject) { if (subject.operation() == unary_operator::reference) { 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()); if (!operand) { return std::nullopt; } if (subject.operation() == unary_operator::minus) { return std::visit([](auto&& value) -> std::optional { using T = std::decay_t; if constexpr (std::is_same_v) { return value == std::numeric_limits::min() ? std::nullopt : std::make_optional(constant_value{ -value }); } if constexpr (std::is_same_v) { return constant_value{ -value }; } return std::nullopt; }, operand.value()); } if (subject.operation() == unary_operator::logical_negation) { return std::visit([](const auto& value) -> std::optional { using T = std::decay_t; if constexpr (std::is_same_v) { return constant_value{ !value }; } return std::nullopt; }, operand.value()); } if (subject.operation() == unary_operator::bitwise_negation) { return std::visit([](const auto& value) -> std::optional { using T = std::decay_t; if constexpr (std::is_integral_v && !std::is_same_v) { return constant_value{ ~value }; } return std::nullopt; }, operand.value()); } if (subject.operation() == unary_operator::plus) { return operand; } return std::nullopt; } template static std::optional add_overflow(T lhs, T rhs) { if constexpr (std::is_integral_v && !std::is_same_v) { T result; return __builtin_add_overflow(lhs, rhs, &result) ? std::nullopt : std::make_optional(result); } return std::make_optional(lhs + rhs); } template static std::optional sub_overflow(T lhs, T rhs) { if constexpr (std::is_integral_v && !std::is_same_v) { T result; return __builtin_sub_overflow(lhs, rhs, &result) ? std::nullopt : std::make_optional(result); } return std::make_optional(lhs - rhs); } template static std::optional mul_overflow(T lhs, T rhs) { if constexpr (std::is_integral_v && !std::is_same_v) { T result; return __builtin_mul_overflow(lhs, rhs, &result) ? std::nullopt : std::make_optional(result); } return std::make_optional(lhs * rhs); } template static std::optional evaluate_operation(binary_operator operation, const T& lhs, const T& rhs) { if constexpr (std::is_same_v || std::is_same_v> || std::is_same_v>) { switch (operation) { using enum binary_operator; case equals: return constant_value{ true }; case not_equals: return constant_value{ false }; default: return std::nullopt; } } else if constexpr (std::is_same_v) { 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) { using enum binary_operator; case sum: if constexpr (std::is_arithmetic_v && !std::is_same_v) { if (auto result = add_overflow(lhs, rhs)) { return constant_value{ *result }; } } return std::nullopt; case subtraction: if constexpr (std::is_arithmetic_v && !std::is_same_v) { if (auto result = sub_overflow(lhs, rhs)) { return constant_value{ *result }; } } return std::nullopt; case multiplication: if constexpr (std::is_arithmetic_v && !std::is_same_v) { if (auto result = mul_overflow(lhs, rhs)) { return constant_value{ *result }; } } return std::nullopt; case division: if constexpr (std::is_arithmetic_v && !std::is_same_v) { if (rhs != static_cast(0)) { return constant_value{ lhs / rhs }; } } return std::nullopt; case remainder: if constexpr (std::is_integral_v && !std::is_same_v) { if (rhs != static_cast(0)) { return constant_value{ lhs % rhs }; } } return std::nullopt; case disjunction: case bitwise_disjunction: if constexpr (std::is_integral_v && !std::is_same_v) { return constant_value{ lhs | rhs }; } return std::nullopt; case conjunction: case bitwise_conjunction: if constexpr (std::is_integral_v && !std::is_same_v) { return constant_value{ lhs & rhs }; } return std::nullopt; case exclusive_disjunction: case bitwise_exclusive_disjunction: if constexpr (std::is_integral_v && !std::is_same_v) { return constant_value{ lhs ^ rhs }; } return std::nullopt; case logical_conjunction: if constexpr (std::is_same_v) { return constant_value{ lhs && rhs }; } return std::nullopt; case logical_disjunction: if constexpr (std::is_same_v) { return constant_value{ lhs || rhs }; } return std::nullopt; case logical_exclusive_disjunction: if constexpr (std::is_same_v) { return constant_value{ lhs != rhs }; } return std::nullopt; case shift_left: if constexpr (std::is_integral_v && !std::is_same_v) { if (rhs < 0 || static_cast>(rhs) >= std::numeric_limits::digits) { return std::nullopt; } return constant_value{ lhs << rhs }; } return std::nullopt; case shift_right: if constexpr (std::is_integral_v) { return constant_value{ lhs >> rhs }; } return std::nullopt; case equals: return constant_value{ lhs == rhs }; case not_equals: return constant_value{ lhs != rhs }; case less: if constexpr (std::is_arithmetic_v && !std::is_same_v) { return constant_value{ lhs < rhs }; } return std::nullopt; case greater: if constexpr (std::is_arithmetic_v && !std::is_same_v) { return constant_value{ lhs > rhs }; } return std::nullopt; case less_equal: if constexpr (std::is_arithmetic_v && !std::is_same_v) { return constant_value{ lhs <= rhs }; } return std::nullopt; case greater_equal: if constexpr (std::is_arithmetic_v && !std::is_same_v) { return constant_value{ lhs >= rhs }; } return std::nullopt; default: return std::nullopt; } } } std::optional evaluator::evaluate_binary(binary_expression& subject) { std::optional lhs = evaluate(subject.lhs()); std::optional rhs = evaluate(subject.rhs()); if (!lhs || !rhs || lhs->index() != rhs->index()) { return std::nullopt; } return std::visit([&rhs, operation = subject.operation()](auto&& lhs_value) { using T = std::decay_t; auto rhs_value = std::get(rhs.value()); return evaluate_operation(operation, lhs_value, rhs_value); }, lhs.value()); } std::optional evaluator::evaluate_cast(cast_expression& subject) { if (auto value = evaluate(subject.value())) { return constant_value{ value.value() }; } else { return std::nullopt; } } std::optional evaluator::evaluate_traits_size(const type& subject) { auto props = get_type_properties(subject, this->target); return props ? std::optional{ props->size } : std::nullopt; } std::optional evaluator::evaluate_traits_alignment(const type& subject) { auto props = get_type_properties(subject, this->target); return props ? std::optional{ props->alignment } : std::nullopt; } std::optional evaluator::evaluate_traits(traits_expression& subject) { if (subject.types.size() != 1) { return std::nullopt; } else if (subject.name.name() == "size") { if (auto size = evaluate_traits_size(subject.types.front())) { return constant_value{ static_cast(size.value()) }; } } else if (subject.name.name() == "alignment") { if (auto alignment = evaluate_traits_alignment(subject.types.front())) { return constant_value{ static_cast(alignment.value()) }; } } else if (subject.name.name() == "min") { type const resolved = resolve_underlying_type(subject.types.front()); if (is_primitive_type(resolved, "Int")) { return constant_value{ std::numeric_limits::min() }; } if (is_primitive_type(resolved, "Word")) { return constant_value{ static_cast(0) }; } if (is_primitive_type(resolved, "Char")) { return constant_value{ static_cast(0) }; } if (is_primitive_type(resolved, "Bool")) { return constant_value{ false }; } if (is_primitive_type(resolved, "Float")) { return constant_value{ -std::numeric_limits::max() }; } if (auto enumeration = resolved.get()) { return constant_value{ 1 }; } } else if (subject.name.name() == "max") { type const resolved = resolve_underlying_type(subject.types.front()); if (is_primitive_type(resolved, "Int")) { return constant_value{ std::numeric_limits::max() }; } if (is_primitive_type(resolved, "Word")) { return constant_value{ std::numeric_limits::max() }; } if (is_primitive_type(resolved, "Char")) { return constant_value{ std::numeric_limits::max() }; } if (is_primitive_type(resolved, "Bool")) { return constant_value{ true }; } if (is_primitive_type(resolved, "Float")) { return constant_value{ std::numeric_limits::max() }; } if (auto enumeration = resolved.get()) { return constant_value{ static_cast(enumeration->members.size()) }; } } else if (subject.name.name() == "offset") { auto *field = subject.arguments.at(1)->is_named(); auto record = resolve_underlying_type(subject.types.front()).get(); if (field == nullptr || record == nullptr) { return std::nullopt; } if (auto record_layout = layout_record(record, this->target)) { auto field_search = record_layout.value().offset_map.find(field->name); if (field_search != std::cend(record_layout.value().offset_map)) { return constant_value{ static_cast(field_search->second) }; } } } 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; } }