/* 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 namespace elna::boot { evaluator::evaluator(symbol_bag& bag, const target_info& target, const std::map& evaluated_initializers) : bag(bag), target(target), evaluated_initializers(evaluated_initializers) { } 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); } } 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()) { for (const field_initializer& field_initializer : record_constructor->field_initializers) { if (!evaluate(field_initializer.value())) { return std::nullopt; } } return constant_value{ compound_constant{} }; } else if (auto *array_constructor = subject.is_array_constructor()) { for (expression *element : array_constructor->elements) { if (!evaluate(*element)) { return std::nullopt; } } return constant_value{ compound_constant{} }; } 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; } auto initializer = this->evaluated_initializers.find(subject.name); return initializer == this->evaluated_initializers.end() ? std::nullopt : evaluate(*initializer->second); } std::optional 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{} }; } 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([](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([](auto value) -> std::optional { using T = std::decay_t; if constexpr (std::is_integral_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, T lhs, T rhs) { if constexpr (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 { 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) { type const resolved = resolve_underlying_type(subject); if (is_primitive_type(resolved, "Int") || resolved.get() != nullptr) { return target.int_size; } else if (is_primitive_type(resolved, "Word")) { return target.word_size; } else if (is_primitive_type(resolved, "Char")) { return target.char_size; } else if (is_primitive_type(resolved, "Float")) { return target.float_size; } else if (is_primitive_type(resolved, "Bool")) { return target.bool_size; } else if (resolved.get() != nullptr) { return target.pointer_size + target.word_size; } else if (is_primitive_type(resolved, "Pointer") || resolved.get() != nullptr) { return target.pointer_size; } else if (auto array = resolved.get()) { if (auto element_size = evaluate_traits_size(array->base)) { return array->size * element_size.value(); } } return std::nullopt; } std::optional evaluator::evaluate_traits_alignment(const type& subject) { type const resolved = resolve_underlying_type(subject); if (is_primitive_type(resolved, "Int")) { return target.int_alignment; } if (is_primitive_type(resolved, "Word")) { return target.word_alignment; } if (is_primitive_type(resolved, "Char")) { return target.char_alignment; } if (is_primitive_type(resolved, "Float")) { return target.float_alignment; } if (is_primitive_type(resolved, "Pointer")) { return target.pointer_alignment; } if (resolved.get() != nullptr) { return target.pointer_alignment; } if (auto array = resolved.get()) { // The alignment of the array equals that of its element. return evaluate_traits_alignment(array->base); } if (auto record = resolved.get()) { // An empty record has alignment 1. std::optional by_field_alignment = std::accumulate( std::begin(record->fields), std::end(record->fields), std::make_optional(1), [this](std::optional max_alignment, auto& field) { std::optional field_alignment = evaluate_traits_alignment(field.second); return field_alignment.has_value() && max_alignment.has_value() ? std::make_optional(std::max(field_alignment.value(), max_alignment.value())) : std::nullopt; } ); if (!by_field_alignment.has_value()) { return by_field_alignment; } std::size_t max_alignment = by_field_alignment.value(); if (!record->base.empty()) { if (auto base_alignment = evaluate_traits_alignment(record->base)) { max_alignment = std::max(base_alignment.value(), max_alignment); } else { return std::nullopt; } } return max_alignment; } return 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()) }; } } // #offset(T, field) stays in codegen - needs GCC record layout. return std::nullopt; } }