diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-09-08 10:46:13 +0200 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-09-08 10:46:13 +0200 |
| commit | 72f0f9b9629013e83fc4346a35113e9eecae45b5 (patch) | |
| tree | 47779eda823f936e1c8d3e99dfc153901497c34f | |
| parent | 72f5e82196d95008ed12c12de6bdccf24361e106 (diff) | |
| download | elna-72f0f9b9629013e83fc4346a35113e9eecae45b5.tar.gz | |
Allow compile-time casts
| -rw-r--r-- | boot/evaluator.cc | 279 | ||||
| -rw-r--r-- | gcc/gcc/elna-generic.cc | 25 | ||||
| -rw-r--r-- | include/elna/boot/evaluator.h | 11 | ||||
| -rw-r--r-- | testsuite/runnable/float_cast.elna | 12 |
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. |
