aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2026-09-08 10:46:13 +0200
committerEugen Wissner <belka@caraus.de>2026-09-08 10:46:13 +0200
commit72f0f9b9629013e83fc4346a35113e9eecae45b5 (patch)
tree47779eda823f936e1c8d3e99dfc153901497c34f
parent72f5e82196d95008ed12c12de6bdccf24361e106 (diff)
downloadelna-72f0f9b9629013e83fc4346a35113e9eecae45b5.tar.gz
Allow compile-time casts
-rw-r--r--boot/evaluator.cc279
-rw-r--r--gcc/gcc/elna-generic.cc25
-rw-r--r--include/elna/boot/evaluator.h11
-rw-r--r--testsuite/runnable/float_cast.elna12
4 files changed, 285 insertions, 42 deletions
diff --git a/boot/evaluator.cc b/boot/evaluator.cc
index 6a9fb3b..b1648af 100644
--- a/boot/evaluator.cc
+++ b/boot/evaluator.cc
@@ -19,6 +19,7 @@ along with GCC; see the file COPYING3. If not see
#include "elna/boot/ast.h"
+#include <cmath>
#include <cstddef>
#include <limits>
#include <ranges>
@@ -680,28 +681,130 @@ namespace elna::boot
}, lhs.value());
}
- std::optional<constant_value> evaluator::cast_to_int(const constant_value& source_expression)
+ static std::optional<constant_value> integer_constant(const bool target_signed,
+ const std::size_t target_size, const double value)
{
- return std::visit([](auto&& source_value) -> std::optional<constant_value> {
+ std::optional<integer_literal> result =
+ integer_literal::from(target_size, static_cast<std::size_t>(std::abs(value)));
+
+ if (result.has_value() && value < 0.0)
+ {
+ result = result.value().negate();
+ }
+ if (!result.has_value() || !result.value().fit_into(target_signed, target_size))
+ {
+ return std::nullopt;
+ }
+ return constant_value{ std::move(result).value() };
+ }
+
+ /// The caller converts the result, so the range is checked here rather than
+ /// left to an out of range floating point to integer conversion.
+ static std::optional<double> truncate_into(const float_literal& source_value,
+ const bool target_signed, const std::size_t target_size)
+ {
+ if (!source_value.is_finite())
+ {
+ return std::nullopt;
+ }
+ const double truncated = std::trunc(source_value.value());
+ const int value_bits = static_cast<int>(target_size * CHAR_BIT) - (target_signed ? 1 : 0);
+ const double bound = std::ldexp(1.0, value_bits);
+
+ if (truncated >= bound || truncated < (target_signed ? -bound : 0.0))
+ {
+ return std::nullopt;
+ }
+ return truncated;
+ }
+
+ static std::optional<double> widen_to_double(const integer_literal& source_value)
+ {
+ if (source_value.is_signed())
+ {
+ std::optional<std::ptrdiff_t> signed_value = source_value.try_to<std::ptrdiff_t>();
+
+ return signed_value.has_value()
+ ? std::optional(static_cast<double>(signed_value.value()))
+ : std::nullopt;
+ }
+ std::optional<std::size_t> unsigned_value = source_value.try_to<std::size_t>();
+
+ return unsigned_value.has_value()
+ ? std::optional(static_cast<double>(unsigned_value.value()))
+ : std::nullopt;
+ }
+
+ std::optional<constant_value> evaluator::cast_to_integer(const constant_value& source_expression,
+ const bool target_signed, const std::size_t target_size)
+ {
+ return std::visit([target_signed, target_size](auto&& source_value) -> std::optional<constant_value> {
using T = std::decay_t<decltype(source_value)>;
if constexpr (std::is_same_v<T, integer_literal>)
{
- std::optional<std::ptrdiff_t> signed_value = source_value.template try_to<std::ptrdiff_t>();
+ // Changing the signedness without changing the width
+ // reinterprets the bits and loses nothing.
+ if (source_value.size() == target_size)
+ {
+ return constant_value{ source_value.cast_to(target_signed, target_size) };
+ }
+ integer_literal narrowed = source_value;
+
+ return narrowed.fit_into(target_signed, target_size)
+ ? std::optional(constant_value{ std::move(narrowed) })
+ : std::nullopt;
+ }
+ else if constexpr (std::is_same_v<T, float_literal>)
+ {
+ std::optional<double> truncated = truncate_into(source_value, target_signed, target_size);
+
+ return truncated.has_value()
+ ? integer_constant(target_signed, target_size, truncated.value())
+ : std::nullopt;
+ }
+ else if constexpr (std::is_same_v<T, bool> || std::is_same_v<T, std::uint32_t>)
+ {
+ return integer_constant(target_signed, target_size, static_cast<double>(source_value));
+ }
+ else if constexpr (std::is_same_v<T, std::nullptr_t>)
+ {
+ return integer_constant(target_signed, target_size, 0.0);
+ }
+ else
+ {
+ return std::nullopt;
+ }
+ }, source_expression);
+ }
+
+ std::optional<constant_value> evaluator::cast_to_float(const constant_value& source_expression,
+ const float_literal::format_kind target_format)
+ {
+ return std::visit([target_format](auto&& source_value) -> std::optional<constant_value> {
+ using T = std::decay_t<decltype(source_value)>;
+
+ if constexpr (std::is_same_v<T, float_literal>)
+ {
+ return constant_value{ source_value.cast_to(target_format) };
+ }
+ else if constexpr (std::is_same_v<T, integer_literal>)
+ {
+ std::optional<double> widened = widen_to_double(source_value);
- return signed_value.has_value()
- ? std::optional(integer_literal::from(signed_value.value()))
+ return widened.has_value()
+ ? std::optional(constant_value{ float_literal::from(widened.value()).cast_to(target_format) })
: std::nullopt;
}
- else if constexpr (std::is_same_v<T, double>
- || std::is_same_v<T, bool>
- || std::is_same_v<T, std::uint32_t>)
+ else if constexpr (std::is_same_v<T, bool> || std::is_same_v<T, std::uint32_t>)
{
- return integer_literal::from(static_cast<std::ptrdiff_t>(source_value));
+ const float_literal widened = float_literal::from(static_cast<double>(source_value));
+
+ return constant_value{ widened.cast_to(target_format) };
}
else if constexpr (std::is_same_v<T, std::nullptr_t>)
{
- return integer_literal::from<std::ptrdiff_t>(0);
+ return constant_value{ float_literal::from(0.0).cast_to(target_format) };
}
else
{
@@ -710,28 +813,70 @@ namespace elna::boot
}, source_expression);
}
- std::optional<constant_value> evaluator::cast_to_word(const constant_value& source_expression)
+ std::optional<constant_value> evaluator::cast_to_bool(const constant_value& source_expression)
{
return std::visit([](auto&& source_value) -> std::optional<constant_value> {
using T = std::decay_t<decltype(source_value)>;
- if constexpr (std::is_same_v<T, integer_literal>)
+ if constexpr (std::is_same_v<T, bool>)
+ {
+ return constant_value{ source_value };
+ }
+ else if constexpr (std::is_same_v<T, integer_literal>)
+ {
+ return constant_value{ !(source_value == std::ptrdiff_t{ 0 }) };
+ }
+ else if constexpr (std::is_same_v<T, float_literal>)
+ {
+ return constant_value{ !(source_value == 0.0) };
+ }
+ else if constexpr (std::is_same_v<T, std::uint32_t>)
+ {
+ return constant_value{ source_value != 0U };
+ }
+ else if constexpr (std::is_same_v<T, std::nullptr_t>)
+ {
+ return constant_value{ false };
+ }
+ else
+ {
+ return std::nullopt;
+ }
+ }, source_expression);
+ }
+
+ std::optional<constant_value> evaluator::cast_to_char(const constant_value& source_expression)
+ {
+ return std::visit([](auto&& source_value) -> std::optional<constant_value> {
+ using T = std::decay_t<decltype(source_value)>;
+
+ if constexpr (std::is_same_v<T, std::uint32_t>)
+ {
+ return constant_value{ source_value };
+ }
+ else if constexpr (std::is_same_v<T, integer_literal>)
+ {
+ std::optional<std::uint32_t> code_point = source_value.template try_to<std::uint32_t>();
+
+ return code_point.has_value()
+ ? std::optional(constant_value{ code_point.value() })
+ : std::nullopt;
+ }
+ else if constexpr (std::is_same_v<T, float_literal>)
{
- std::optional<std::size_t> unsigned_value = source_value.template try_to<std::size_t>();
+ std::optional<double> truncated = truncate_into(source_value, false, sizeof(std::uint32_t));
- return unsigned_value.has_value()
- ? std::optional(integer_literal::from(unsigned_value.value()))
+ return truncated.has_value()
+ ? std::optional(constant_value{ static_cast<std::uint32_t>(truncated.value()) })
: std::nullopt;
}
- else if constexpr (std::is_same_v<T, double>
- || std::is_same_v<T, bool>
- || std::is_same_v<T, std::uint32_t>)
+ else if constexpr (std::is_same_v<T, bool>)
{
- return integer_literal::from(static_cast<std::size_t>(source_value));
+ return constant_value{ static_cast<std::uint32_t>(source_value) };
}
else if constexpr (std::is_same_v<T, std::nullptr_t>)
{
- return integer_literal::from<std::ptrdiff_t>(0U);
+ return constant_value{ std::uint32_t{ 0 } };
}
else
{
@@ -740,39 +885,93 @@ namespace elna::boot
}, source_expression);
}
+ std::optional<constant_value> evaluator::cast_to_enumeration(const constant_value& source_expression,
+ const std::size_t member_count, const std::size_t target_size)
+ {
+ std::optional<constant_value> position = cast_to_integer(source_expression, false, target_size);
+
+ if (!position.has_value())
+ {
+ return std::nullopt;
+ }
+ const auto *converted = std::get_if<integer_literal>(&position.value());
+
+ if (converted == nullptr || *converted < std::size_t{ 1 } || *converted > member_count)
+ {
+ return std::nullopt;
+ }
+ return position;
+ }
+
+ std::optional<constant_value> evaluator::cast_to_pointer(const constant_value& source_expression)
+ {
+ if (std::holds_alternative<std::nullptr_t>(source_expression))
+ {
+ return source_expression;
+ }
+ if (const auto *address = std::get_if<integer_literal>(&source_expression))
+ {
+ // Only the null pointer has a constant representation.
+ if (*address == std::ptrdiff_t{ 0 })
+ {
+ return constant_value{ nullptr };
+ }
+ }
+ return std::nullopt;
+ }
+
std::optional<constant_value> evaluator::evaluate_cast(cast_expression& subject)
{
- auto value = evaluate(subject.value());
+ std::optional<constant_value> value = evaluate(subject.value());
if (!value.has_value())
{
return std::nullopt;
}
- // The target type is looked up in the symbol table instead of reading
- // the type decoration set by name analysis.
- if (auto *target = subject.target().is_named())
+ const type source_type = resolve_underlying_type(subject.value().type_decoration);
+ const type target_type = resolve_underlying_type(subject.type_decoration);
+
+ if (source_type == target_type)
+ {
+ return value;
+ }
+ if (const std::shared_ptr<enumeration_type> enumeration = target_type.get<enumeration_type>())
{
- auto symbol = this->bag.lookup(target->name);
+ std::optional<type_properties> properties = get_type_properties(target_type, this->target);
- if (symbol != nullptr)
+ if (!properties.has_value())
{
- if (auto type_symbol = symbol->is_type())
- {
- const type resolved = resolve_underlying_type(type_symbol->symbol);
-
- if (is_primitive_type(resolved, "Int"))
- {
- return cast_to_int(value.value());
- }
- else if (is_primitive_type(resolved, "Word"))
- {
- return cast_to_word(value.value());
- }
- }
+ return std::nullopt;
}
+ return cast_to_enumeration(value.value(), enumeration->members.size(), properties->size);
}
+ if (is_integral_type(target_type))
+ {
+ std::optional<type_properties> properties = get_type_properties(target_type, this->target);
- return constant_value{ value.value() };
+ return properties.has_value()
+ ? cast_to_integer(value.value(), is_integer_type(target_type), properties->size)
+ : std::nullopt;
+ }
+ if (is_float_type(target_type))
+ {
+ return cast_to_float(value.value(), is_primitive_type(target_type, "Single")
+ ? float_literal::format_kind::binary32
+ : float_literal::format_kind::binary64);
+ }
+ if (is_primitive_type(target_type, "Bool"))
+ {
+ return cast_to_bool(value.value());
+ }
+ if (is_primitive_type(target_type, "Char"))
+ {
+ return cast_to_char(value.value());
+ }
+ if (is_any_pointer_type(target_type))
+ {
+ return cast_to_pointer(value.value());
+ }
+ return std::nullopt;
}
std::optional<std::size_t> evaluator::evaluate_traits_size(const type& subject)
diff --git a/gcc/gcc/elna-generic.cc b/gcc/gcc/elna-generic.cc
index 9bb1e89..45d8311 100644
--- a/gcc/gcc/elna-generic.cc
+++ b/gcc/gcc/elna-generic.cc
@@ -30,6 +30,7 @@ along with GCC; see the file COPYING3. If not see
#include "dumpfile.h"
#include "stringpool.h"
#include "fold-const.h"
+#include "convert.h"
#include "langhooks.h"
namespace elna::gcc
@@ -156,6 +157,30 @@ namespace elna::gcc
this->current_expression = build_slice(cast_target, ptr, new_length);
}
+ else if (TREE_CODE(cast_target) == BOOLEAN_TYPE
+ && TREE_CODE(TREE_TYPE(this->current_expression)) != BOOLEAN_TYPE)
+ {
+ // Converting would keep the least significant bit only, but every
+ // non-zero value stands for true.
+ tree zero = build_zero_cst(TREE_TYPE(this->current_expression));
+
+ this->current_expression = build2_loc(cast_location, NE_EXPR, cast_target,
+ this->current_expression, zero);
+ }
+ else if (SCALAR_FLOAT_TYPE_P(TREE_TYPE(this->current_expression))
+ && INTEGRAL_TYPE_P(cast_target))
+ {
+ // fold_convert cannot build the FIX_TRUNC_EXPR this conversion needs
+ // and asserts instead.
+ this->current_expression = convert_to_integer(cast_target, this->current_expression);
+ }
+ else if (SCALAR_FLOAT_TYPE_P(TREE_TYPE(this->current_expression))
+ && POINTER_TYPE_P(cast_target))
+ {
+ tree address = convert_to_integer(elna_word_type_node, this->current_expression);
+
+ this->current_expression = fold_convert_loc(cast_location, cast_target, address);
+ }
else
{
this->current_expression = fold_convert_loc(cast_location, cast_target, this->current_expression);
diff --git a/include/elna/boot/evaluator.h b/include/elna/boot/evaluator.h
index 35cca1a..dc6fd71 100644
--- a/include/elna/boot/evaluator.h
+++ b/include/elna/boot/evaluator.h
@@ -86,8 +86,15 @@ namespace elna::boot
std::optional<std::size_t> evaluate_traits_size(const type& subject);
std::optional<std::size_t> evaluate_traits_alignment(const type& subject);
- static std::optional<constant_value> cast_to_int(const constant_value& source_expression);
- static std::optional<constant_value> cast_to_word(const constant_value& source_expression);
+ static std::optional<constant_value> cast_to_integer(const constant_value& source_expression,
+ bool target_signed, std::size_t target_size);
+ static std::optional<constant_value> cast_to_float(const constant_value& source_expression,
+ float_literal::format_kind target_format);
+ static std::optional<constant_value> cast_to_bool(const constant_value& source_expression);
+ static std::optional<constant_value> cast_to_char(const constant_value& source_expression);
+ static std::optional<constant_value> cast_to_enumeration(const constant_value& source_expression,
+ std::size_t member_count, std::size_t target_size);
+ static std::optional<constant_value> cast_to_pointer(const constant_value& source_expression);
public:
std::optional<std::size_t> evaluate_index(expression& subject);
diff --git a/testsuite/runnable/float_cast.elna b/testsuite/runnable/float_cast.elna
new file mode 100644
index 0000000..08d14c5
--- /dev/null
+++ b/testsuite/runnable/float_cast.elna
@@ -0,0 +1,12 @@
+var
+ folded: Int := cast(2.9: Int)
+ measure: Double := -2.9
+
+program()
+begin
+ assert(folded = 2);
+ assert(cast(measure: Int) = -2);
+ assert(cast(folded: Double) = 2.0)
+return 0u8
+
+end.