/* 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 { 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, 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()) { 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; } 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([](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 { 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; } }