/* 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 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::negation)
{
return std::visit([](auto v) -> std::optional {
using T = std::decay_t;
if constexpr (std::is_same_v)
{
return constant_value{ !v };
}
else if constexpr (std::is_integral_v)
{
return constant_value{ ~v };
}
return std::nullopt;
}, operand.value());
}
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 op, T lhs, T rhs)
{
if constexpr (std::is_same_v
|| std::is_same_v)
{
switch (op)
{
case binary_operator::equals:
return constant_value{ true };
case binary_operator::not_equals:
return constant_value{ false };
default:
return std::nullopt;
}
}
else
{
switch (op)
{
case binary_operator::sum:
if constexpr (std::is_arithmetic_v && !std::is_same_v)
{
if (auto r = add_overflow(lhs, rhs))
{
return constant_value{ *r };
}
}
return std::nullopt;
case binary_operator::subtraction:
if constexpr (std::is_arithmetic_v && !std::is_same_v)
{
if (auto r = sub_overflow(lhs, rhs))
{
return constant_value{ *r };
}
}
return std::nullopt;
case binary_operator::multiplication:
if constexpr (std::is_arithmetic_v && !std::is_same_v)
{
if (auto r = mul_overflow(lhs, rhs))
{
return constant_value{ *r };
}
}
return std::nullopt;
case binary_operator::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 binary_operator::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 binary_operator::disjunction:
if constexpr (std::is_integral_v)
{
return constant_value{ lhs | rhs };
}
return std::nullopt;
case binary_operator::conjunction:
if constexpr (std::is_integral_v)
{
return constant_value{ lhs & rhs };
}
return std::nullopt;
case binary_operator::exclusive_disjunction:
if constexpr (std::is_integral_v)
{
return constant_value{ lhs ^ rhs };
}
return std::nullopt;
case binary_operator::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 binary_operator::shift_right:
if constexpr (std::is_integral_v)
{
return constant_value{ lhs >> rhs };
}
return std::nullopt;
case binary_operator::equals:
return constant_value{ lhs == rhs };
case binary_operator::not_equals:
return constant_value{ lhs != rhs };
case binary_operator::less:
if constexpr (std::is_arithmetic_v && !std::is_same_v)
{
return constant_value{ lhs < rhs };
}
return std::nullopt;
case binary_operator::greater:
if constexpr (std::is_arithmetic_v && !std::is_same_v)
{
return constant_value{ lhs > rhs };
}
return std::nullopt;
case binary_operator::less_equal:
if constexpr (std::is_arithmetic_v && !std::is_same_v)
{
return constant_value{ lhs <= rhs };
}
return std::nullopt;
case binary_operator::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([this, &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 resolved = resolve_underlying_type(subject);
if (is_primitive_type(resolved, "Int"))
{
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, "Pointer"))
{
return target.pointer_size;
}
else if (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 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 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 (auto enumeration = resolved.get())
{
return constant_value{ 1 };
}
}
else if (subject.name.name() == "max")
{
type 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{ static_cast(255) };
}
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;
}
}