/* 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
#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)
: 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 type_to_check = subject.base().type_decoration;
if (type_to_check.empty())
{
type_to_check = subject.type_decoration;
}
auto resolved_base = resolve_underlying_type(type_to_check);
if (auto enumeration = resolved_base.get())
{
auto member_iterator = std::ranges::find(enumeration->members, subject.field().name());
if (member_iterator != enumeration->members.end())
{
return constant_value{
static_cast(std::distance(enumeration->members.begin(), member_iterator) + 1)
};
}
return std::nullopt;
}
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;
}
}