aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2026-08-21 00:05:03 +0200
committerEugen Wissner <belka@caraus.de>2026-08-21 00:05:03 +0200
commit39269cc68a32f6e1a7524b00da36a1fe4cc7fb8b (patch)
tree9558cd6107d60e22127faeddbdeac5031ee2fdbe
parent4f77ad5d019618893c59a0f8d5bfa6b98e50a3be (diff)
downloadelna-39269cc68a32f6e1a7524b00da36a1fe4cc7fb8b.tar.gz
Implement Single and Double floats
-rw-r--r--.gitignore1
-rw-r--r--boot/ast.cc4
-rw-r--r--boot/evaluator.cc112
-rw-r--r--boot/lexer.ll26
-rw-r--r--boot/materialization.cc35
-rw-r--r--boot/name_analysis.cc6
-rw-r--r--boot/parser.yy13
-rw-r--r--boot/result.cc87
-rw-r--r--boot/symbol.cc39
-rw-r--r--boot/type_check.cc19
-rw-r--r--gcc/dg.exp10
-rw-r--r--gcc/gcc/elna-builtins.cc7
-rw-r--r--gcc/gcc/elna-generic.cc29
-rw-r--r--gcc/gcc/elna-tree.cc23
-rw-r--r--gcc/testlib/elna-dg.exp235
-rw-r--r--include/elna/boot/ast.h8
-rw-r--r--include/elna/boot/materialization.h12
-rw-r--r--include/elna/boot/name_analysis.h2
-rw-r--r--include/elna/boot/result.h207
-rw-r--r--include/elna/boot/symbol.h27
-rw-r--r--include/elna/gcc/elna-generic.h2
-rw-r--r--include/elna/gcc/elna-tree.h2
-rw-r--r--include/elna/gcc/elna1.h2
-rw-r--r--rakelib/gcc.rake86
-rw-r--r--testsuite/compilable/double_literals.elna9
-rw-r--r--testsuite/compilable/float_literals.elna9
-rw-r--r--testsuite/compilable/single_literals.elna9
-rw-r--r--testsuite/fail_compilation/float_remainder.elna8
-rw-r--r--testsuite/runnable/float_arithmetic.elna13
29 files changed, 589 insertions, 453 deletions
diff --git a/.gitignore b/.gitignore
index f9147b3..873923f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
build/
jit/
+.cache/
diff --git a/boot/ast.cc b/boot/ast.cc
index 9b21f56..287b7aa 100644
--- a/boot/ast.cc
+++ b/boot/ast.cc
@@ -186,7 +186,7 @@ namespace elna::boot
__builtin_unreachable();
}
- void empty_visitor::visit(literal<double> *)
+ void empty_visitor::visit(literal<float_literal> *)
{
__builtin_unreachable();
}
@@ -509,7 +509,7 @@ namespace elna::boot
{
}
- void walking_visitor::visit(literal<double> *)
+ void walking_visitor::visit(literal<float_literal> *)
{
}
diff --git a/boot/evaluator.cc b/boot/evaluator.cc
index d43ef11..1d10346 100644
--- a/boot/evaluator.cc
+++ b/boot/evaluator.cc
@@ -237,9 +237,9 @@ namespace elna::boot
{
return constant_value{ integer_subject->value };
}
- else if (auto *double_subject = subject.is_a<double>())
+ else if (auto *float_subject = subject.is_a<float_literal>())
{
- return constant_value{ double_subject->value };
+ return constant_value{ float_subject->value };
}
else if (auto *boolean_subject = subject.is_a<bool>())
{
@@ -395,7 +395,7 @@ namespace elna::boot
}
return std::nullopt;
}
- if constexpr (std::is_same_v<T, double>)
+ if constexpr (std::is_same_v<T, float_literal>)
{
return constant_value{ -value };
}
@@ -433,7 +433,7 @@ namespace elna::boot
template<typename T>
static std::optional<T> add_overflow(T lhs, T rhs)
{
- if constexpr (is_integral<T>)
+ if constexpr (is_integral_v<T>)
{
T result;
return __builtin_add_overflow(lhs, rhs, &result)
@@ -446,7 +446,7 @@ namespace elna::boot
template<typename T>
static std::optional<T> sub_overflow(T lhs, T rhs)
{
- if constexpr (is_integral<T>)
+ if constexpr (is_integral_v<T>)
{
T result;
return __builtin_sub_overflow(lhs, rhs, &result)
@@ -459,7 +459,7 @@ namespace elna::boot
template<typename T>
static std::optional<T> mul_overflow(T lhs, T rhs)
{
- if constexpr (is_integral<T>)
+ if constexpr (is_integral_v<T>)
{
T result;
return __builtin_mul_overflow(lhs, rhs, &result)
@@ -498,7 +498,7 @@ namespace elna::boot
return constant_value{ result.value() };
}
}
- else if constexpr (std::is_floating_point_v<T>)
+ else if constexpr (std::is_same_v<T, float_literal>)
{
if (auto result = add_overflow(lhs, rhs))
{
@@ -514,7 +514,7 @@ namespace elna::boot
return constant_value{ result.value() };
}
}
- else if constexpr (std::is_floating_point_v<T>)
+ else if constexpr (std::is_same_v<T, float_literal>)
{
if (auto result = sub_overflow(lhs, rhs))
{
@@ -530,7 +530,7 @@ namespace elna::boot
return constant_value{ result.value() };
}
}
- else if constexpr (std::is_floating_point_v<T>)
+ else if constexpr (std::is_same_v<T, float_literal>)
{
if (auto result = mul_overflow(lhs, rhs))
{
@@ -546,12 +546,9 @@ namespace elna::boot
return constant_value{ result.value() };
}
}
- else if constexpr (std::is_floating_point_v<T>)
+ else if constexpr (std::is_same_v<T, float_literal>)
{
- if (rhs != static_cast<T>(0))
- {
- return constant_value{ lhs / rhs };
- }
+ return constant_value{ lhs / rhs };
}
return std::nullopt;
case remainder:
@@ -625,25 +622,25 @@ namespace elna::boot
case not_equals:
return constant_value{ lhs != rhs };
case less:
- if constexpr (std::is_floating_point_v<T> || std::is_same_v<T, integer_literal>)
+ if constexpr (std::is_same_v<T, float_literal> || std::is_same_v<T, integer_literal>)
{
return constant_value{ lhs < rhs };
}
return std::nullopt;
case greater:
- if constexpr (std::is_floating_point_v<T> || std::is_same_v<T, integer_literal>)
+ if constexpr (std::is_same_v<T, float_literal> || std::is_same_v<T, integer_literal>)
{
return constant_value{ lhs > rhs };
}
return std::nullopt;
case less_equal:
- if constexpr (std::is_floating_point_v<T> || std::is_same_v<T, integer_literal>)
+ if constexpr (std::is_same_v<T, float_literal> || std::is_same_v<T, integer_literal>)
{
return constant_value{ lhs <= rhs };
}
return std::nullopt;
case greater_equal:
- if constexpr (std::is_floating_point_v<T> || std::is_same_v<T, integer_literal>)
+ if constexpr (std::is_same_v<T, float_literal> || std::is_same_v<T, integer_literal>)
{
return constant_value{ lhs >= rhs };
}
@@ -789,23 +786,23 @@ namespace elna::boot
{
type const resolved = resolve_underlying_type(subject.types.front());
- if (auto resolved_primitive = resolved.get<primitive_type>())
+ if (is_integer_type(resolved))
{
- if (resolved_primitive->identifier.starts_with("Int"))
- {
- const std::size_t bits = resolved_primitive->properties.size * CHAR_BIT;
- const integer_literal one = integer_literal::from<std::ptrdiff_t>(
- resolved_primitive->properties.size, 1U).value();
+ const std::shared_ptr<primitive_type> resolved_primitive = resolved.get<primitive_type>();
+ const std::size_t bits = resolved_primitive->properties.size * CHAR_BIT;
+ const integer_literal one = integer_literal::from<std::ptrdiff_t>(
+ resolved_primitive->properties.size, 1U).value();
- return constant_value{
- one.shl(integer_literal::from<std::size_t>(bits - 1U)).value()
- };
- }
- if (resolved_primitive->identifier.starts_with("Word"))
- {
- return constant_value{ integer_literal::from<std::size_t>(
- resolved_primitive->properties.size, 0U).value() };
- }
+ return constant_value{
+ one.shl(integer_literal::from<std::size_t>(bits - 1U)).value()
+ };
+ }
+ if (is_word_type(resolved))
+ {
+ const std::shared_ptr<primitive_type> resolved_primitive = resolved.get<primitive_type>();
+
+ return constant_value{ integer_literal::from<std::size_t>(
+ resolved_primitive->properties.size, 0U).value() };
}
if (is_primitive_type(resolved, "Char"))
{
@@ -815,9 +812,13 @@ namespace elna::boot
{
return constant_value{ false };
}
- if (is_primitive_type(resolved, "Float"))
+ if (is_primitive_type(resolved, "Single"))
{
- return constant_value{ -std::numeric_limits<double>::max() };
+ return constant_value{ float_literal::from(std::numeric_limits<float>::min()) };
+ }
+ if (is_primitive_type(resolved, "Double"))
+ {
+ return constant_value{ float_literal::from(std::numeric_limits<double>::min()) };
}
if (auto enumeration = resolved.get<enumeration_type>())
{
@@ -828,26 +829,25 @@ namespace elna::boot
{
type const resolved = resolve_underlying_type(subject.types.front());
- if (auto resolved_primitive = resolved.get<primitive_type>())
+ if (is_integer_type(resolved))
{
- if (resolved_primitive->identifier.starts_with("Int"))
- {
- const std::size_t bits = resolved_primitive->properties.size * CHAR_BIT;
- const integer_literal one = integer_literal::from<std::ptrdiff_t>(
- resolved_primitive->properties.size, 1U).value();
- const std::optional<integer_literal> minimum = one.shl(
- integer_literal::from<std::size_t>(bits - 1U));
+ const std::shared_ptr<primitive_type> resolved_primitive = resolved.get<primitive_type>();
+ const std::size_t bits = resolved_primitive->properties.size * CHAR_BIT;
+ const integer_literal one = integer_literal::from<std::ptrdiff_t>(
+ resolved_primitive->properties.size, 1U).value();
+ const std::optional<integer_literal> minimum = one.shl(
+ integer_literal::from<std::size_t>(bits - 1U));
- return constant_value{ ~minimum.value() };
- }
- if (resolved_primitive->identifier.starts_with("Word"))
- {
- // All bits set.
- std::optional<integer_literal> zero = integer_literal::from<std::size_t>(
- resolved_primitive->properties.size, 0U);
+ return constant_value{ ~minimum.value() };
+ }
+ if (is_word_type(resolved))
+ {
+ const std::shared_ptr<primitive_type> resolved_primitive = resolved.get<primitive_type>();
+ // All bits set.
+ std::optional<integer_literal> zero = integer_literal::from<std::size_t>(
+ resolved_primitive->properties.size, 0U);
- return constant_value{ ~zero.value() };
- }
+ return constant_value{ ~zero.value() };
}
if (is_primitive_type(resolved, "Char"))
{
@@ -857,9 +857,13 @@ namespace elna::boot
{
return constant_value{ true };
}
- if (is_primitive_type(resolved, "Float"))
+ if (is_primitive_type(resolved, "Single"))
+ {
+ return constant_value{ float_literal::from(std::numeric_limits<float>::max()) };
+ }
+ if (is_primitive_type(resolved, "Double"))
{
- return constant_value{ std::numeric_limits<double>::max() };
+ return constant_value{ float_literal::from(std::numeric_limits<double>::max()) };
}
if (auto enumeration = resolved.get<enumeration_type>())
{
diff --git a/boot/lexer.ll b/boot/lexer.ll
index 888c17d..eb1bbd3 100644
--- a/boot/lexer.ll
+++ b/boot/lexer.ll
@@ -32,6 +32,7 @@ along with GCC; see the file COPYING3. If not see
} while (0);
#include <sstream>
+#include <cmath>
#include "parser.hh"
#undef YY_DECL
@@ -50,6 +51,7 @@ HIGIT [0-9a-fA-F]
HEXADECIMAL 0[xX]{HIGIT}+
BIGIT [01]
NATURAL [1-9][[:digit:]]*
+EXPONENT [eE][+-]?[[:digit:]]+
%%
%{
@@ -325,31 +327,17 @@ to {
REJECT;
}
}
-[[:digit:]]+\.[[:digit:]]+([eE][+-]?[[:digit:]]+)? {
+[[:digit:]]+(\.[[:digit:]]+{EXPONENT}?|{EXPONENT}) {
errno = 0;
double result = strtod(yytext, NULL);
- if (errno == ERANGE)
- {
- REJECT;
- }
- else
- {
- return yy::parser::make_FLOAT(result, this->location);
- }
+ return yy::parser::make_DOUBLE(result, this->location);
}
-[[:digit:]]+[eE][+-]?[[:digit:]]+ {
+[[:digit:]]+(\.[[:digit:]]+{EXPONENT}?|{EXPONENT})f {
errno = 0;
- double result = strtod(yytext, NULL);
+ float result = strtof(yytext, NULL);
- if (errno == ERANGE)
- {
- REJECT;
- }
- else
- {
- return yy::parser::make_FLOAT(result, this->location);
- }
+ return yy::parser::make_SINGLE(result, this->location);
}
'([^'\\\n]|\\.|\\\n)+' {
std::optional<std::uint8_t> result = parse_byte_literal(yytext);
diff --git a/boot/materialization.cc b/boot/materialization.cc
index 8c436b3..4b58ac2 100644
--- a/boot/materialization.cc
+++ b/boot/materialization.cc
@@ -19,14 +19,22 @@ along with GCC; see the file COPYING3. If not see
namespace elna::boot
{
- materialization_error::materialization_error(const source_position position)
- : diagnostic(position)
+ materialization_error::materialization_error(const source_position position, kind error_kind)
+ : diagnostic(position), m_kind(error_kind)
{
}
std::string materialization_error::what() const
{
- return "Integer literal overflows";
+ switch (this->m_kind)
+ {
+ using enum kind;
+ case integer_overflow:
+ return "Integer literal overflows";
+ case real_overflow:
+ return "Real literal overflows";
+ }
+ __builtin_unreachable();
}
materialization_visitor::materialization_visitor(const target_info& target)
@@ -46,12 +54,14 @@ namespace elna::boot
if (!literal->has_explicit_size
&& !literal->value.fit_into(true, target.int_properties.front().size))
{
- add_error<materialization_error>(literal->position());
+ add_error<materialization_error>(literal->position(),
+ materialization_error::kind::integer_overflow);
}
}
else
{
- add_error<materialization_error>(literal->position());
+ add_error<materialization_error>(literal->position(),
+ materialization_error::kind::integer_overflow);
}
break;
case unmarked:
@@ -62,7 +72,8 @@ namespace elna::boot
if (!literal->value.fit_into(true, target_size))
{
- add_error<materialization_error>(literal->position());
+ add_error<materialization_error>(literal->position(),
+ materialization_error::kind::integer_overflow);
}
break;
}
@@ -70,9 +81,19 @@ namespace elna::boot
if (!literal->has_explicit_size
&& !literal->value.fit_into(false, target.word_properties.front().size))
{
- add_error<materialization_error>(literal->position());
+ add_error<materialization_error>(literal->position(),
+ materialization_error::kind::integer_overflow);
}
break;
}
}
+
+ void materialization_visitor::visit(literal<float_literal> *literal)
+ {
+ if (!literal->value.is_finite())
+ {
+ add_error<materialization_error>(literal->position(),
+ materialization_error::kind::real_overflow);
+ }
+ }
}
diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc
index 4b69f9c..1ad66a8 100644
--- a/boot/name_analysis.cc
+++ b/boot/name_analysis.cc
@@ -912,9 +912,11 @@ namespace elna::boot
this->current_type = type();
}
- void name_analysis_visitor::visit(literal<double> *literal)
+ void name_analysis_visitor::visit(literal<float_literal> *literal)
{
- literal->type_decoration = lookup_primitive_type("Float");
+ literal->type_decoration = literal->value.format() == float_literal::format_kind::binary32
+ ? lookup_primitive_type("Single")
+ : lookup_primitive_type("Double");
this->current_type = type();
}
diff --git a/boot/parser.yy b/boot/parser.yy
index 411922d..c45fdbc 100644
--- a/boot/parser.yy
+++ b/boot/parser.yy
@@ -82,7 +82,8 @@ along with GCC; see the file COPYING3. If not see
%token <std::pair<std::uint8_t, elna::boot::integer_sign>> INTEGER8 WORD8
%token <std::pair<std::uint16_t, elna::boot::integer_sign>> INTEGER16 WORD16
%token <std::pair<std::uint32_t, elna::boot::integer_sign>> INTEGER32 WORD32
-%token <double> FLOAT
+%token <float> SINGLE
+%token <double> DOUBLE
%token <std::uint32_t> CHARACTER
%token <std::string> STRING
%token <bool> BOOLEAN
@@ -298,9 +299,15 @@ literal:
$$ = new boot::literal<boot::integer_literal>(boot::make_position(@$),
boot::integer_literal::from(magnitude), wants_signed);
}
- | FLOAT
+ | SINGLE
{
- $$ = new boot::literal<double>(boot::make_position(@$), $1, boot::integer_sign::unmarked);
+ $$ = new boot::literal<boot::float_literal>(boot::make_position(@$),
+ boot::float_literal::from($1), boot::integer_sign::unmarked);
+ }
+ | DOUBLE
+ {
+ $$ = new boot::literal<boot::float_literal>(boot::make_position(@$),
+ boot::float_literal::from($1), boot::integer_sign::unmarked);
}
| BOOLEAN
{
diff --git a/boot/result.cc b/boot/result.cc
index 3970b1f..d25b85b 100644
--- a/boot/result.cc
+++ b/boot/result.cc
@@ -18,6 +18,8 @@ along with GCC; see the file COPYING3. If not see
#include "elna/boot/result.h"
#include <algorithm>
+#include <charconv>
+#include <cmath>
#include <cstring>
#include <numeric>
@@ -467,6 +469,85 @@ namespace elna::boot
return mpz_sgn(this->raw) < 0;
}
+ float_literal::float_literal(format_kind binary_format, double value)
+ : m_format(binary_format), raw(value)
+ {
+ }
+
+ float_literal float_literal::rounded(format_kind binary_format, double value)
+ {
+ if (binary_format == format_kind::binary32)
+ {
+ return float_literal(format_kind::binary32, static_cast<float>(value));
+ }
+ return float_literal(binary_format, value);
+ }
+
+ float_literal float_literal::operator+(const float_literal& that) const
+ {
+ return rounded(this->m_format, this->raw + that.raw);
+ }
+
+ float_literal float_literal::operator-(const float_literal& that) const
+ {
+ return rounded(this->m_format, this->raw - that.raw);
+ }
+
+ float_literal float_literal::operator*(const float_literal& that) const
+ {
+ return rounded(this->m_format, this->raw * that.raw);
+ }
+
+ float_literal float_literal::operator/(const float_literal& that) const
+ {
+ return rounded(this->m_format, this->raw / that.raw);
+ }
+
+ float_literal float_literal::operator-() const
+ {
+ return rounded(this->m_format, -this->raw);
+ }
+
+ bool float_literal::operator==(const float_literal& that) const
+ {
+ return this->raw == that.raw;
+ }
+
+ std::partial_ordering float_literal::operator<=>(const float_literal& that) const
+ {
+ return this->raw <=> that.raw;
+ }
+
+ float_literal::format_kind float_literal::format() const
+ {
+ return this->m_format;
+ }
+
+ std::string float_literal::to_string() const
+ {
+ constexpr std::size_t shortest_round_trip = 24;
+ std::array<char, shortest_round_trip + 4> buffer;
+
+ auto float_chars = std::to_chars(buffer.begin(), buffer.end(), this->raw);
+
+ return std::string(buffer.begin(), float_chars.ptr);
+ }
+
+ float_literal float_literal::cast_to(format_kind binary_format) const
+ {
+ return rounded(binary_format, this->raw);
+ }
+
+ double float_literal::value() const
+ {
+ return this->raw;
+ }
+
+ bool float_literal::is_finite() const
+ {
+ return std::isfinite(this->raw);
+ }
+
std::size_t constant_value_hash::operator()(const elna::boot::constant_value& value) const noexcept
{
return std::visit([](auto&& alternative) -> std::size_t {
@@ -541,3 +622,9 @@ std::size_t std::hash<elna::boot::integer_literal>::operator()(const elna::boot:
}
return 0;
}
+
+std::size_t std::hash<elna::boot::float_literal>::operator()(const elna::boot::float_literal& key) const noexcept
+{
+ // -0.0 and 0.0 compare equal, so their hashes must agree.
+ return std::hash<double>{}(key == 0.0 ? 0.0 : key.value());
+}
diff --git a/boot/symbol.cc b/boot/symbol.cc
index 05eea31..fdf6a65 100644
--- a/boot/symbol.cc
+++ b/boot/symbol.cc
@@ -326,8 +326,10 @@ namespace elna::boot
const type pointer = type(std::make_shared<primitive_type>("Pointer", target.pointer_properties));
result->enter("Pointer", std::make_shared<type_info>(pointer));
- result->enter("Float",
- std::make_shared<type_info>(type(std::make_shared<primitive_type>("Float", target.float_properties))));
+ result->enter("Single",
+ std::make_shared<type_info>(type(std::make_shared<primitive_type>("Single", target.single_properties))));
+ result->enter("Double",
+ std::make_shared<type_info>(type(std::make_shared<primitive_type>("Double", target.double_properties))));
const type boolean = type(std::make_shared<primitive_type>("Bool", target.bool_properties));
result->enter("Bool", std::make_shared<type_info>(boolean));
@@ -462,22 +464,41 @@ namespace elna::boot
return false;
}
- bool is_numeric_type(const type& checked)
+ bool is_integer_type(const type& checked)
{
- return is_integral_type(checked)
- || is_primitive_type(checked, "Float");
+ if (auto primitive_checked = checked.get<primitive_type>())
+ {
+ return primitive_checked->identifier.starts_with("Int");
+ }
+ return false;
}
- bool is_integral_type(const type& checked)
+ bool is_word_type(const type& checked)
{
if (auto primitive_checked = checked.get<primitive_type>())
{
- return primitive_checked->identifier.starts_with("Int")
- || primitive_checked->identifier.starts_with("Word");
+ return primitive_checked->identifier.starts_with("Word");
}
return false;
}
+ bool is_float_type(const type& checked)
+ {
+ return is_primitive_type(checked, "Single")
+ || is_primitive_type(checked, "Double");
+ }
+
+ bool is_numeric_type(const type& checked)
+ {
+ return is_integral_type(checked)
+ || is_float_type(checked);
+ }
+
+ bool is_integral_type(const type& checked)
+ {
+ return is_integer_type(checked) || is_word_type(checked);
+ }
+
bool is_discrete_type(const type& checked)
{
return is_integral_type(checked)
@@ -495,7 +516,7 @@ namespace elna::boot
bool is_scalar_type(const type& checked)
{
return is_discrete_type(checked)
- || is_primitive_type(checked, "Float")
+ || is_float_type(checked)
|| is_any_pointer_type(checked)
|| checked.get<enumeration_type>() != nullptr;
}
diff --git a/boot/type_check.cc b/boot/type_check.cc
index d830763..2d82579 100644
--- a/boot/type_check.cc
+++ b/boot/type_check.cc
@@ -833,7 +833,7 @@ namespace elna::boot
auto operation = expression->operation();
type const resolved = resolve_underlying_type(expression->operand().type_decoration);
- if (operation == unary_operator::plus)
+ if (operation == unary_operator::plus || operation == unary_operator::minus)
{
if (!is_numeric_type(resolved))
{
@@ -842,17 +842,6 @@ namespace elna::boot
type_mismatch_error::unary{ .operation = operation });
}
}
- else if (operation == unary_operator::minus)
- {
- auto signed_primitive = resolved.get<primitive_type>();
- if ((signed_primitive == nullptr || !signed_primitive->identifier.starts_with("Int"))
- && !is_primitive_type(resolved, "Float"))
- {
- add_error<type_mismatch_error>(expression->position(),
- expression->operand().type_decoration,
- type_mismatch_error::unary{ .operation = operation });
- }
- }
else if (operation == unary_operator::negation)
{
if (is_primitive_type(resolved, "Bool"))
@@ -933,10 +922,12 @@ namespace elna::boot
|| (is_numeric_type(lhs_resolved) && lhs_resolved == rhs_resolved);
break;
case division:
- case remainder:
case multiplication:
valid = is_numeric_type(lhs_resolved) && lhs_resolved == rhs_resolved;
break;
+ case remainder:
+ valid = is_integral_type(lhs_resolved) && lhs_resolved == rhs_resolved;
+ break;
case less:
case greater:
case less_equal:
@@ -1032,7 +1023,7 @@ namespace elna::boot
const type resolved = resolve_underlying_type(trait->type_decoration);
if (resolved.get<enumeration_type>() == nullptr
- && !is_primitive_type(resolved, "Float")
+ && !is_float_type(resolved)
&& !is_discrete_type(resolved))
{
add_error<trait_error>(trait->name.position(), trait->name.name(),
diff --git a/gcc/dg.exp b/gcc/dg.exp
index 71e7105..edbb371 100644
--- a/gcc/dg.exp
+++ b/gcc/dg.exp
@@ -22,11 +22,11 @@ load_lib elna-dg.exp
# Initialize dg.
dg-init
-# Main loop.
-# Single-module .elna files and multi-module directories,
-# both exactly one level below the category directories.
-elna-dg-runtest-single [lsort [glob -nocomplain $srcdir/$subdir/*/*.elna]]
-elna-dg-runtest-multi [lsort [glob -nocomplain -type d $srcdir/$subdir/*/*]]
+# Main loop. Single-module tests are files one level below the category
+# directories, multi-module tests are directories one level below any
+# category directory, with a sut.elna as the primary module.
+dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*/*.elna]] "" ""
+dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*/*/sut.elna]] "" ""
# All done.
dg-finish
diff --git a/gcc/gcc/elna-builtins.cc b/gcc/gcc/elna-builtins.cc
index 95ffb2f..9552abb 100644
--- a/gcc/gcc/elna-builtins.cc
+++ b/gcc/gcc/elna-builtins.cc
@@ -52,7 +52,8 @@ namespace elna::gcc
},
.pointer_properties = get_host_numeric_properties(ptr_type_node),
.char_properties = get_host_numeric_properties(elna_char_type_node),
- .float_properties = get_host_numeric_properties(elna_float_type_node),
+ .single_properties = get_host_numeric_properties(float_type_node),
+ .double_properties = get_host_numeric_properties(double_type_node),
.bool_properties = {
.size = static_cast<std::size_t>(
(TYPE_PRECISION(elna_bool_type_node) + BITS_PER_UNIT - 1) / BITS_PER_UNIT),
@@ -72,7 +73,6 @@ namespace elna::gcc
TYPE_STRING_FLAG(elna_char_type_node) = 1;
elna_pointer_type_node = ptr_type_node;
- elna_float_type_node = double_type_node;
elna_bool_type_node = boolean_type_node;
elna_bool_true_node = boolean_true_node;
@@ -109,7 +109,8 @@ namespace elna::gcc
declare_builtin_type(builtin_table, "Char", elna_char_type_node);
declare_builtin_type(builtin_table, "Bool", elna_bool_type_node);
declare_builtin_type(builtin_table, "Pointer", elna_pointer_type_node);
- declare_builtin_type(builtin_table, "Float", elna_float_type_node);
+ declare_builtin_type(builtin_table, "Single", float_type_node);
+ declare_builtin_type(builtin_table, "Double", double_type_node);
return builtin_table;
}
diff --git a/gcc/gcc/elna-generic.cc b/gcc/gcc/elna-generic.cc
index 323ec09..fb898d5 100644
--- a/gcc/gcc/elna-generic.cc
+++ b/gcc/gcc/elna-generic.cc
@@ -29,7 +29,6 @@ along with GCC; see the file COPYING3. If not see
#include "gimplify.h"
#include "dumpfile.h"
#include "stringpool.h"
-#include "realmpfr.h"
#include "fold-const.h"
#include "langhooks.h"
@@ -490,34 +489,29 @@ namespace elna::gcc
this->symbols, literal_type);
}
- void generic_visitor::visit(boot::literal<double> *literal)
+ void generic_visitor::visit(boot::literal<boot::float_literal> *literal)
{
- REAL_VALUE_TYPE real_value1;
-
- mpfr_t number;
- mpfr_init2(number, SIGNIFICAND_BITS);
- mpfr_set_d(number, literal->value, MPFR_RNDN);
-
- real_from_mpfr(&real_value1, number, double_type_node, MPFR_RNDN);
-
- this->current_expression = build_real(double_type_node, real_value1);
-
- mpfr_clear(number);
+ tree literal_type = get_inner_alias(literal->type_decoration, this->symbols);
+ this->current_expression = constant_to_tree(boot::constant_value{ literal->value },
+ this->symbols, literal_type);
}
void generic_visitor::visit(boot::literal<bool> *boolean)
{
- this->current_expression = constant_to_tree(boot::constant_value{ boolean->value }, this->symbols);
+ this->current_expression = constant_to_tree(boot::constant_value{ boolean->value },
+ this->symbols, elna_bool_type_node);
}
void generic_visitor::visit(boot::literal<std::uint32_t> *character)
{
- this->current_expression = constant_to_tree(boot::constant_value{ character->value }, this->symbols);
+ this->current_expression = constant_to_tree(boot::constant_value{ character->value },
+ this->symbols, elna_char_type_node);
}
void generic_visitor::visit(boot::literal<std::nullptr_t> *)
{
- this->current_expression = constant_to_tree(boot::constant_value{ std::nullptr_t{} }, this->symbols);
+ this->current_expression = constant_to_tree(boot::constant_value{ std::nullptr_t{} },
+ this->symbols, elna_pointer_type_node);
}
void generic_visitor::visit(boot::literal<std::string> *string)
@@ -579,7 +573,8 @@ namespace elna::gcc
break;
case division:
this->current_expression = fold_build2_loc(expression_location,
- TRUNC_DIV_EXPR, left_type, left, right);
+ SCALAR_FLOAT_TYPE_P(left_type) ? RDIV_EXPR : TRUNC_DIV_EXPR,
+ left_type, left, right);
break;
case remainder:
this->current_expression = fold_build2_loc(expression_location,
diff --git a/gcc/gcc/elna-tree.cc b/gcc/gcc/elna-tree.cc
index 5284e42..e4c8a20 100644
--- a/gcc/gcc/elna-tree.cc
+++ b/gcc/gcc/elna-tree.cc
@@ -15,9 +15,6 @@ You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3. If not see
<http://www.gnu.org/licenses/>. */
-#include <array>
-#include <cstring>
-
#include "elna/gcc/elna-diagnostic.h"
#include "elna/gcc/elna-tree.h"
#include "elna/gcc/elna1.h"
@@ -25,6 +22,7 @@ along with GCC; see the file COPYING3. If not see
#include "function.h"
#include "stor-layout.h"
#include "diagnostic-core.h"
+#include "realmpfr.h"
namespace elna::gcc
{
@@ -251,6 +249,8 @@ namespace elna::gcc
tree constant_to_tree(const boot::constant_value& constant_value,
const std::shared_ptr<symbol_table>& symbols, tree type)
{
+ gcc_assert(type != NULL_TREE);
+
if (std::holds_alternative<boot::integer_literal>(constant_value))
{
auto literal_value = std::get<boot::integer_literal>(constant_value);
@@ -272,17 +272,20 @@ namespace elna::gcc
return NULL_TREE;
}
}
- else if (std::holds_alternative<double>(constant_value))
+ else if (std::holds_alternative<boot::float_literal>(constant_value))
{
- auto real_value = std::get<double>(constant_value);
+ auto literal = std::get<boot::float_literal>(constant_value);
+
+ mpfr_t number;
+ mpfr_init2(number, SIGNIFICAND_BITS);
+ mpfr_set_d(number, literal.value(), MPFR_RNDN);
+
REAL_VALUE_TYPE real;
- constexpr std::size_t bits_size = (sizeof(double) + sizeof(long) - 1) / sizeof(long);
- std::array<long, bits_size> target_bits;
+ real_from_mpfr(&real, number, type, MPFR_RNDN);
- std::memcpy(target_bits.data(), &real_value, sizeof(real_value));
- real_from_target(&real, target_bits.data(), REAL_MODE_FORMAT(TYPE_MODE(elna_float_type_node)));
+ mpfr_clear(number);
- return build_real(elna_float_type_node, real);
+ return build_real(type, real);
}
else if (std::holds_alternative<bool>(constant_value))
{
diff --git a/gcc/testlib/elna-dg.exp b/gcc/testlib/elna-dg.exp
index 03620e3..773dd54 100644
--- a/gcc/testlib/elna-dg.exp
+++ b/gcc/testlib/elna-dg.exp
@@ -16,7 +16,9 @@
load_lib gcc-dg.exp
+#
# Define elna callbacks for dg.exp.
+#
proc elna-dg-test { prog do_what extra_tool_flags } {
set result \
@@ -31,236 +33,3 @@ proc elna-dg-test { prog do_what extra_tool_flags } {
proc elna-dg-prune { system text } {
return [gcc-dg-prune $system $text]
}
-
-# Utility routines.
-
-#
-# Escapes a directive argument so that it reaches dejagnu intact.
-#
-# dg.exp evaluates each extracted directive as a Tcl script
-# ("catch $op"), which performs one round of backslash, command and
-# variable substitution inside the double-quoted argument. Quoting the
-# affected characters here means the argument dejagnu receives is
-# exactly what the test author wrote, so @Error messages keep their
-# regular expression semantics: "\[2\]" matches literal brackets and
-# "[2]" stays a character class.
-#
-proc elna-escape-directive { text } {
- return [string map [list \
- "\\" "\\\\" \
- "\[" "\\\[" \
- "\]" "\\\]" \
- "\$" "\\\$" \
- "\"" "\\\""] $text]
-}
-
-#
-# Replaces an elna-native directive in the line with its dg-* equivalent,
-# escaping the directive argument for dejagnu's Tcl evaluation.
-#
-# line_var - name of the variable holding the line in the caller.
-# directive - elna directive name, e.g. @Error.
-# dg_name - dejagnu directive name, e.g. dg-error.
-#
-proc elna-convert-directive { line_var directive dg_name } {
- upvar 1 $line_var line
-
- if { [regexp -indices "\\(\\*\\s*$directive\\s+(.*?)\\s*\\*\\)" $line whole argument] } {
- set escaped [elna-escape-directive \
- [string range $line [lindex $argument 0] [lindex $argument 1]]]
- set prefix [string range $line 0 [expr {[lindex $whole 0] - 1}]]
- set suffix [string range $line [expr {[lindex $whole 1] + 1}] end]
-
- set line "${prefix}(* { $dg_name \"$escaped\" } *)$suffix"
- }
-}
-
-#
-# This procedure copies a test file from the source tree to the
-# build directory while rewriting elna-native directives to dejagnu
-# dg-* equivalents.
-#
-# Directives (inside (* ... *) comments):
-#
-# (* @Error message *) Expected compiler error on this line.
-# (* @Flags args *) Extra compiler flags.
-#
-# base - absolute path to the source directory, e.g. .../testsuite/elna.dg
-# test - relative path from testsuite, e.g. fail_compilation/file_name.elna
-#
-#
-# Creates a directory at $path, handling the case where a regular
-# file already exists at that location (leftover from a prior
-# interrupted run).
-#
-proc elna-ensure-dir { path } {
- if { [file exists $path] && ![file isdirectory $path] } {
- file delete $path
- }
- file mkdir $path
-}
-
-proc elna-convert-test { base test { extra_lines "" } } {
- set type [file dirname $test]
- set target $test
-
- # Opens a file in the build directory for writing and the source
- # file for reading.
- elna-ensure-dir $type
- set fin [open $base/$target r]
- set fout [open $target w]
-
- # Reads the source test file line by line and replace directives with
- # Elna equivalents using a regular expression.
- while { [gets $fin line] >= 0 } {
- elna-convert-directive line @Error dg-error
- elna-convert-directive line @Flags dg-additional-options
- puts $fout $line
- }
-
- close $fin
-
- # Append caller-supplied extra directives (e.g. dg-additional-sources).
- foreach line $extra_lines {
- puts $fout $line
- }
-
- # Suppress excess output so stray diagnostics do not fail the test.
- puts $fout "(* { dg-prune-output .* } *)"
-
- # Emit category-specific expectation.
- set category [lindex [file split $test] 0]
- switch $category {
- compilable {
- # Assert that an output file was produced.
- puts $fout "(* { dg-final { output-exists } } *)"
- }
- fail_compilation {
- # Assert that no output file was produced.
- puts $fout "(* { dg-final { output-exists-not } } *)"
- }
- # Other cases are handled automatically.
- }
-
- close $fout
-
- # Reaches up one stack frame (into the runtest proc) and links the
- # caller's cleanup_extra_files variable to the local name cleanups.
- upvar 1 cleanup_extra_files cleanups
- # This ensures the converted file gets deleted after the test runs.
- lappend cleanups $target
-
- return $target
-}
-
-#
-# Handles a multi-module test directory.
-#
-# Requires sut.elna (system under test) as the primary module;
-# all other .elna files are passed as dg-additional-sources.
-#
-# base - absolute path to the source directory.
-# test - relative path from testsuite, e.g. group/test_name.
-#
-proc elna-handle-multimodule { base test } {
- upvar 1 cleanup_extra_files cleanups
-
- set dir "$base/$test"
-
- # Find all .elna files recursively inside the test directory.
- set modules [lsort [find $dir *.elna]]
- if { [llength $modules] == 0 } {
- return ""
- }
-
- # Allocate sut.elna as the primary module, collect the rest.
- set main_module ""
- set extra_sources ""
- foreach module $modules {
- if { [file tail $module] == "sut.elna" } {
- set main_module $module
- } else {
- lappend extra_sources $module
- }
- }
- if { $main_module == "" } {
- error "multi-module test directory \"$test\" must contain sut.elna"
- }
-
- # Build extra directives: -I for imports and dg-additional-sources.
- set extra_lines ""
- lappend extra_lines "(* { dg-additional-options \"-I$dir\" } *)"
- elna-ensure-dir $test
- foreach src $extra_sources {
- set src_rel [string range $src [expr {[string length $dir] + 1}] end]
- file copy $src $test
- lappend cleanups [file join $test $src_rel]
- lappend extra_lines "(* { dg-additional-sources \"$src_rel\" } *)"
- }
-
- # Convert the primary module, passing the extra directives.
- set main_rel [file join $test [file tail $main_module]]
- return [elna-convert-test $base $main_rel $extra_lines]
-}
-
-#
-# Runs a converted test file through dejagnu and cleans up afterwards.
-#
-proc elna-dg-run-one { target type cleanup_extra_files } {
- global dg-do-what-default
-
- switch $type {
- runnable { set dg-do-what-default "run" }
- default { set dg-do-what-default "compile" }
- }
-
- dg-test $target "" ""
- file delete $target
- foreach srcfile $cleanup_extra_files {
- file delete $srcfile
- }
-}
-
-#
-# Converts and runs single-module .elna test files.
-#
-proc elna-dg-runtest-single { testcases } {
- global dg-do-what-default
- set saved-dg-do-what-default ${dg-do-what-default}
-
- foreach test $testcases {
- set type [file tail [file dirname $test]]
- set name [file tail $test]
- set base [file dirname [file dirname $test]]
- set cleanup_extra_files ""
-
- set target [elna-convert-test $base $type/$name]
- elna-dg-run-one $target $type $cleanup_extra_files
- }
-
- set dg-do-what-default ${saved-dg-do-what-default}
-}
-
-#
-# Converts and runs multi-module test directories.
-#
-proc elna-dg-runtest-multi { testcases } {
- global dg-do-what-default
- set saved-dg-do-what-default ${dg-do-what-default}
-
- foreach test $testcases {
- set type [file tail [file dirname $test]]
- set name [file tail $test]
- set base [file dirname [file dirname $test]]
- set cleanup_extra_files ""
-
- set target [elna-handle-multimodule $base $type/$name]
- if { $target == "" } {
- continue
- }
-
- elna-dg-run-one $target $type $cleanup_extra_files
- }
-
- set dg-do-what-default ${saved-dg-do-what-default}
-}
diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h
index a05233c..8425b54 100644
--- a/include/elna/boot/ast.h
+++ b/include/elna/boot/ast.h
@@ -79,7 +79,7 @@ namespace elna::boot
};
template<>
- struct literal_type_id<double>
+ struct literal_type_id<float_literal>
{
static constexpr int value = 2;
};
@@ -187,7 +187,7 @@ namespace elna::boot
virtual void visit(field_access_expression *) = 0;
virtual void visit(dereference_expression *) = 0;
virtual void visit(literal<integer_literal> *) = 0;
- virtual void visit(literal<double> *) = 0;
+ virtual void visit(literal<float_literal> *) = 0;
virtual void visit(literal<bool> *) = 0;
virtual void visit(literal<std::uint32_t> *) = 0;
virtual void visit(literal<std::nullptr_t> *) = 0;
@@ -236,7 +236,7 @@ namespace elna::boot
[[noreturn]] void visit(field_access_expression *) override;
[[noreturn]] void visit(dereference_expression *) override;
[[noreturn]] void visit(literal<integer_literal> *) override;
- [[noreturn]] void visit(literal<double> *) override;
+ [[noreturn]] void visit(literal<float_literal> *) override;
[[noreturn]] void visit(literal<bool> *) override;
[[noreturn]] void visit(literal<std::uint32_t> *) override;
[[noreturn]] void visit(literal<std::nullptr_t> *) override;
@@ -283,7 +283,7 @@ namespace elna::boot
void visit(field_access_expression *expression) override;
void visit(dereference_expression *expression) override;
void visit(literal<integer_literal> *) override;
- void visit(literal<double> *) override;
+ void visit(literal<float_literal> *) override;
void visit(literal<bool> *) override;
void visit(literal<std::uint32_t> *) override;
void visit(literal<std::nullptr_t> *) override;
diff --git a/include/elna/boot/materialization.h b/include/elna/boot/materialization.h
index 2c571c5..f3c720e 100644
--- a/include/elna/boot/materialization.h
+++ b/include/elna/boot/materialization.h
@@ -24,9 +24,18 @@ namespace elna::boot
class materialization_error final : public diagnostic
{
public:
- materialization_error(const source_position position);
+ enum class kind
+ {
+ integer_overflow,
+ real_overflow
+ };
+
+ materialization_error(const source_position position, kind error_kind);
std::string what() const override;
+
+ private:
+ kind m_kind;
};
class materialization_visitor final : public walking_visitor, public diagnostic_container
@@ -37,5 +46,6 @@ namespace elna::boot
explicit materialization_visitor(const target_info& target);
void visit(literal<integer_literal> *literal) override;
+ void visit(literal<float_literal> *literal) override;
};
}
diff --git a/include/elna/boot/name_analysis.h b/include/elna/boot/name_analysis.h
index 91b669c..50d9fe6 100644
--- a/include/elna/boot/name_analysis.h
+++ b/include/elna/boot/name_analysis.h
@@ -173,7 +173,7 @@ namespace elna::boot
void visit(for_statement *statement) override;
void visit(literal<integer_literal> *literal) override;
- void visit(literal<double> *literal) override;
+ void visit(literal<float_literal> *literal) override;
void visit(literal<bool> *literal) override;
void visit(literal<std::uint32_t> *literal) override;
void visit(literal<std::nullptr_t> *literal) override;
diff --git a/include/elna/boot/result.h b/include/elna/boot/result.h
index ccc95ca..b34c4d4 100644
--- a/include/elna/boot/result.h
+++ b/include/elna/boot/result.h
@@ -273,12 +273,24 @@ namespace elna::boot
/**
* Checks whether \p T is any of std::int*_t or std::uint*_t types.
*
- * Contrary to \c std::is_integral characters and booleans do not count.
+ * Contrary to \c std::is_integral_v characters and booleans do not count.
*
* \tparam T The examined type.
*/
template<typename T>
- inline constexpr bool is_integral = is_signed_v<T> || is_unsigned_v<T>;
+ inline constexpr bool is_integral_v = is_signed_v<T> || is_unsigned_v<T>;
+
+ /**
+ * Checks whether \p T is a \c float or \c double.
+ *
+ * Contrary to \c is_floating_point_v it considers only 32 and 64 bit
+ * floating point numbers.
+ *
+ * \tparam T The examined type.
+ */
+ template<typename T>
+ inline constexpr bool is_floating_point_v = std::is_same_v<T, float>
+ || std::is_same_v<T, double>;
struct integer_literal
{
@@ -337,69 +349,39 @@ namespace elna::boot
*/
std::optional<integer_literal> shr(const integer_literal& that) const;
- /**
- * \param that Operand.
- * \return Bitwise OR of \c this and \p that.
- */
integer_literal operator|(const integer_literal& that) const;
-
- /**
- * \param that Operand.
- * \return Bitwise AND of \c this and \p that.
- */
integer_literal operator&(const integer_literal& that) const;
-
- /**
- * \param that Operand.
- * \return Bitwise XOR of \c this and \p that.
- */
integer_literal operator^(const integer_literal& that) const;
-
- /**
- * \return Bitwise complement of \c this.
- */
integer_literal operator~() const;
- /**
- * \param that Comparand.
- * \return Whether \c this and \p that hold the same value.
- */
bool operator==(const integer_literal& that) const;
- /// \overload
- template<typename U>
- bool operator==(U that) const
- requires(is_unsigned_v<U> && sizeof(U) <= sizeof(unsigned long int))
+ template<typename T>
+ bool operator==(T that) const
+ requires(is_unsigned_v<T> && sizeof(T) <= sizeof(unsigned long int))
{
return mpz_cmp_ui(this->raw, that) == 0;
}
- /// \overload
- template<typename U>
- bool operator==(U that) const
- requires(is_signed_v<U> && sizeof(U) <= sizeof(signed long int))
+ template<typename T>
+ bool operator==(T that) const
+ requires(is_signed_v<T> && sizeof(T) <= sizeof(signed long int))
{
return mpz_cmp_si(this->raw, that) == 0;
}
- /**
- * \param that Comparand.
- * \return Ordering of \c this relative to \p that.
- */
std::weak_ordering operator<=>(const integer_literal& that) const;
- /// \overload
- template<typename U>
- std::weak_ordering operator<=>(U that) const
- requires(is_unsigned_v<U> && sizeof(U) <= sizeof(unsigned long int))
+ template<typename T>
+ std::weak_ordering operator<=>(T that) const
+ requires(is_unsigned_v<T> && sizeof(T) <= sizeof(unsigned long int))
{
return mpz_cmp_ui(this->raw, that) <=> 0;
}
- /// \overload
- template<typename U>
- std::weak_ordering operator<=>(U that) const
- requires(is_signed_v<U> && sizeof(U) <= sizeof(signed long int))
+ template<typename T>
+ std::weak_ordering operator<=>(T that) const
+ requires(is_signed_v<T> && sizeof(T) <= sizeof(signed long int))
{
return mpz_cmp_si(this->raw, that) <=> 0;
}
@@ -433,7 +415,7 @@ namespace elna::boot
*/
template<typename T>
std::optional<T> try_to() const
- requires is_integral<T>
+ requires is_integral_v<T>
{
if constexpr (is_signed_v<T>)
{
@@ -443,10 +425,6 @@ namespace elna::boot
{
return to_unsigned<T>();
}
- else
- {
- static_assert("integer_literal::to expected only integral types");
- }
}
/**
@@ -485,7 +463,7 @@ namespace elna::boot
*/
template<typename T>
static integer_literal from(T initial)
- requires is_integral<T>
+ requires is_integral_v<T>
{
integer_literal result{ std::is_signed_v<T>, sizeof(T) };
@@ -503,7 +481,7 @@ namespace elna::boot
*/
template<typename T>
static std::optional<integer_literal> from(std::size_t size, T initial)
- requires is_integral<T>
+ requires is_integral_v<T>
{
integer_literal result{ std::is_signed_v<T>, size };
@@ -567,6 +545,122 @@ namespace elna::boot
}
};
+ struct float_literal
+ {
+ enum class format_kind
+ {
+ binary32,
+ binary64
+ };
+
+ /**
+ * \tparam T Initializer type.
+ * \param initial Initial literal value.
+ * \return A literal constructed from the host value \p initial.
+ */
+ template<typename T>
+ static float_literal from(T initial)
+ requires is_floating_point_v<T>
+ {
+ if constexpr (std::is_same_v<T, float>)
+ {
+ return float_literal(format_kind::binary32, initial);
+ }
+ else if constexpr (std::is_same_v<T, double>)
+ {
+ return float_literal(format_kind::binary64, initial);
+ }
+ }
+
+ float_literal operator+(const float_literal& that) const;
+ float_literal operator-(const float_literal& that) const;
+ float_literal operator*(const float_literal& that) const;
+ float_literal operator/(const float_literal& that) const;
+ float_literal operator-() const;
+
+ bool operator==(const float_literal& that) const;
+
+ template<typename T>
+ bool operator==(T that) const
+ requires is_floating_point_v<T>
+ {
+ return this->raw == that;
+ }
+
+ std::partial_ordering operator<=>(const float_literal& that) const;
+
+ template<typename T>
+ bool operator<=>(T that) const
+ requires is_floating_point_v<T>
+ {
+ return this->raw <=> that;
+ }
+
+ /// \return Stored floating point number format.
+ format_kind format() const;
+
+ /**
+ * \return The stored value as a host \c double.
+ *
+ * Binary32 values are exactly representable as \c double, so this
+ * accessor is always lossless.
+ */
+ double value() const;
+
+ /**
+ * \return Whether the stored value is finite.
+ */
+ bool is_finite() const;
+
+ /**
+ * \return String representation.
+ */
+ std::string to_string() const;
+
+ /**
+ * Exports the stored value as a host \p T.
+ *
+ * This function tries to shrink or extend the value to fit into the
+ * target type if possible.
+ *
+ * \tparam Target type.
+ * \return The converted value, or \c std::nullopt if not enough precision.
+ */
+ template<typename T>
+ std::optional<T> try_to() const
+ requires is_floating_point_v<T>
+ {
+ if constexpr (std::is_same_v<T, float>)
+ {
+ float single = static_cast<float>(this->raw);
+
+ return static_cast<double>(single) == this->raw
+ ? std::optional(single)
+ : std::nullopt;
+ }
+ else if constexpr (std::is_same_v<T, double>)
+ {
+ return std::optional(static_cast<T>(this->raw));
+ }
+ }
+
+ /**
+ * Cast the value to the given size, reducing precision if needed.
+ *
+ * \param binary_format Result size.
+ * \return Cast result.
+ */
+ float_literal cast_to(format_kind binary_format) const;
+
+ private:
+ float_literal(format_kind binary_format, double value);
+
+ static float_literal rounded(format_kind binary_format, double value);
+
+ format_kind m_format;
+ double raw;
+ };
+
/**
* An associative container that contains key-value pairs with unique keys.
* Keys preserve the insertion order.
@@ -747,7 +841,8 @@ namespace elna::boot
std::array<type_properties, target_integer_count> word_properties;
type_properties pointer_properties;
type_properties char_properties;
- type_properties float_properties;
+ type_properties single_properties;
+ type_properties double_properties;
type_properties bool_properties;
};
@@ -762,7 +857,7 @@ namespace elna::boot
*/
using constant_value = std::variant<
integer_literal,
- double,
+ float_literal,
bool,
std::uint32_t,
std::nullptr_t,
@@ -907,3 +1002,9 @@ struct std::hash<elna::boot::integer_literal>
{
std::size_t operator()(const elna::boot::integer_literal& key) const noexcept;
};
+
+template<>
+struct std::hash<elna::boot::float_literal>
+{
+ std::size_t operator()(const elna::boot::float_literal& key) const noexcept;
+};
diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h
index 5817b38..1caba83 100644
--- a/include/elna/boot/symbol.h
+++ b/include/elna/boot/symbol.h
@@ -540,8 +540,33 @@ namespace elna::boot
bool is_primitive_type(const type& checked, const std::string& name);
/**
+ * Checks whether the given type is a fixed-size integer.
+ *
+ * \param checked The type to check.
+ * \return Whether the type is a fixed-size integer.
+ */
+ bool is_integer_type(const type& checked);
+
+ /**
+ * Checks whether the given type is a fixed-size word.
+ *
+ * \param checked The type to check.
+ * \return Whether the type is a fixed-size word.
+ */
+ bool is_word_type(const type& checked);
+
+ /**
+ * Checks whether the given type is a floating point primitive type
+ * (\c Single or \c Double).
+ *
+ * \param checked The type to check.
+ * \return Whether the type is a floating point primitive.
+ */
+ bool is_float_type(const type& checked);
+
+ /**
* Checks whether the given type is a numeric primitive type
- * (\c Int, \c Word, or \c Float).
+ * (\c Int, \c Word, \c Single, or \c Double).
*
* \param checked The type to check.
* \return Whether the type is a numeric primitive.
diff --git a/include/elna/gcc/elna-generic.h b/include/elna/gcc/elna-generic.h
index 2d65a26..fd54ba5 100644
--- a/include/elna/gcc/elna-generic.h
+++ b/include/elna/gcc/elna-generic.h
@@ -74,7 +74,7 @@ namespace elna::gcc
void visit(boot::procedure_call *call) override;
void visit(boot::cast_expression *expression) override;
void visit(boot::literal<boot::integer_literal> *literal) override;
- void visit(boot::literal<double> *literal) override;
+ void visit(boot::literal<boot::float_literal> *literal) override;
void visit(boot::literal<bool> *boolean) override;
void visit(boot::literal<std::uint32_t> *character) override;
void visit(boot::literal<std::nullptr_t> *) override;
diff --git a/include/elna/gcc/elna-tree.h b/include/elna/gcc/elna-tree.h
index 504b14c..f201e64 100644
--- a/include/elna/gcc/elna-tree.h
+++ b/include/elna/gcc/elna-tree.h
@@ -62,7 +62,7 @@ namespace elna::gcc
tree extract_constant(tree expression);
tree constant_to_tree(const boot::constant_value& value,
- const std::shared_ptr<symbol_table>& symbols, tree type = NULL_TREE);
+ const std::shared_ptr<symbol_table>& symbols, tree type);
template<typename... Args>
tree call_built_in(location_t call_location, const char *name, tree return_type, Args... arguments)
diff --git a/include/elna/gcc/elna1.h b/include/elna/gcc/elna1.h
index 96043f9..44a0c88 100644
--- a/include/elna/gcc/elna1.h
+++ b/include/elna/gcc/elna1.h
@@ -24,7 +24,6 @@ enum elna_tree_index
ELNA_TI_CHAR_TYPE,
ELNA_TI_BOOL_TYPE,
ELNA_TI_POINTER_TYPE,
- ELNA_TI_FLOAT_TYPE,
ELNA_TI_BOOL_TRUE,
ELNA_TI_BOOL_FALSE,
ELNA_TI_WORD_ONE,
@@ -41,7 +40,6 @@ extern std::vector<std::string> elna_include_dirs;
#define elna_char_type_node elna_global_trees[ELNA_TI_CHAR_TYPE]
#define elna_bool_type_node elna_global_trees[ELNA_TI_BOOL_TYPE]
#define elna_pointer_type_node elna_global_trees[ELNA_TI_POINTER_TYPE]
-#define elna_float_type_node elna_global_trees[ELNA_TI_FLOAT_TYPE]
#define elna_bool_true_node elna_global_trees[ELNA_TI_BOOL_TRUE]
#define elna_bool_false_node elna_global_trees[ELNA_TI_BOOL_FALSE]
#define elna_word_one_node elna_global_trees[ELNA_TI_WORD_ONE]
diff --git a/rakelib/gcc.rake b/rakelib/gcc.rake
index 7b6bf44..3d6cf85 100644
--- a/rakelib/gcc.rake
+++ b/rakelib/gcc.rake
@@ -91,10 +91,48 @@ def download_and_pipe(url, target, command)
end
end
+# Makes a link from the source tree in the GCC tree.
def link_frontend(source, destination)
File.symlink Pathname.new(source).relative_path_from(destination), (destination + File.basename(source))
end
+# Replaces an elna-native directive in the line with its dg-* equivalent,
+# escaping the directive argument for dejagnu's Tcl evaluation.
+def convert_directive(line, directive, dg_name)
+ line.sub(/\(\*\s*#{Regexp.escape directive}\s+(.*?)\s*\*\)/) do
+ escaped = Regexp.last_match(1).gsub(/[\\\[\]$"]/) { |character| "\\#{character}" }
+ "(* { #{dg_name} \"#{escaped}\" } *)"
+ end
+end
+
+def convert_test(source, target, category, extra_lines = [])
+ File.open(target, 'wb') do |output|
+ File.foreach(source) do |line|
+ line = convert_directive(line, '@Error', 'dg-error')
+ line = convert_directive(line, '@Flags', 'dg-additional-options')
+
+ output.puts line
+ end
+
+ # Runnable tests compile, link and execute.
+ output.puts '(* { dg-do run } *)' if category == 'runnable'
+
+ extra_lines.each { |line| output.puts line }
+
+ # Suppress excess output so stray diagnostics do not fail the test.
+ output.puts '(* { dg-prune-output .* } *)'
+
+ case category
+ when 'compilable'
+ # Assert that an output file was produced.
+ output.puts '(* { dg-final { output-exists } } *)'
+ when 'fail_compilation'
+ # Assert that no output file was produced.
+ output.puts '(* { dg-final { output-exists-not } } *)'
+ end
+ end
+end
+
namespace :gcc do
# Dependencies.
GCC_VERSION = "16.1.0"
@@ -139,7 +177,7 @@ namespace :gcc do
FileList['boot', 'include', 'COPYING3', 'README.md', 'gcc/gcc', 'gcc/*.in', 'gcc/lang*'].each do |file|
link_frontend file, source_destination
end
- FileList['testsuite/*', 'gcc/dg.exp', 'README.md'].each do |file|
+ FileList['gcc/dg.exp', 'README.md'].each do |file|
link_frontend file, test_destination
end
destination = GCC_TREE + 'gcc/testsuite/lib'
@@ -180,8 +218,52 @@ namespace :gcc do
sh 'make', 'install', chdir: HOST_GCC
end
+ desc 'Convert the testsuite into dejagnu format'
+ task :convert do
+ destination = GCC_TREE + 'gcc/testsuite/elna.dg'
+ destination.mkpath
+ source = Pathname.new 'testsuite'
+
+ # Regenerate the category directories from scratch: this removes
+ # converted tests whose sources are gone and adds new ones.
+ %w[compilable fail_compilation runnable].each do |category|
+ category_destination = destination + category
+ category_source = source + category
+
+ rm_rf category_destination
+ mkdir_p category_destination
+
+ category_source.each_child do |test_path|
+ test_target = category_destination + test_path.basename
+
+ # Multi-module tests: directories containing a sut.elna (system under
+ # test) and any helper files, compiled and linked together.
+ if test_path.directory?
+ main = test_path + 'sut.elna'
+ raise %(multi-module test directory \"#{test_path}\" must contain sut.elna) unless main
+
+ test_target.mkdir
+ modules = test_path.children - [main]
+
+ modules.each do |extra|
+ cp_r extra, test_target
+ end
+ extra_sources = test_target.glob("**/*.elna")
+ .map { |extra| %("#{extra.relative_path_from test_target}") }
+
+ convert_test main, (test_target + 'sut.elna'), category, [
+ "(* { dg-additional-options \"-I#{test_target.expand_path}\" } *)",
+ "(* { dg-additional-sources #{extra_sources * ' '} } *)"
+ ]
+ else
+ convert_test test_path, test_target, category
+ end
+ end
+ end
+ end
+
desc 'Run tests'
- task :check do
+ task check: 'gcc:convert' do
log_file = Pathname.new(HOST_GCC) + 'gcc/testsuite/elna/elna.log'
sh 'make', 'check-elna', chdir: File.join(HOST_GCC, 'gcc')
diff --git a/testsuite/compilable/double_literals.elna b/testsuite/compilable/double_literals.elna
new file mode 100644
index 0000000..9e8a73d
--- /dev/null
+++ b/testsuite/compilable/double_literals.elna
@@ -0,0 +1,9 @@
+proc f()
+var
+ x: const Double := 3.14
+ y: const Double := 1e10
+ z: const Double := 4.567e8
+ t: const Double := 2.5E-3
+return
+
+end.
diff --git a/testsuite/compilable/float_literals.elna b/testsuite/compilable/float_literals.elna
deleted file mode 100644
index 77af6b1..0000000
--- a/testsuite/compilable/float_literals.elna
+++ /dev/null
@@ -1,9 +0,0 @@
-proc f()
-var
- x: const Float := 3.14
- y: const Float := 1e10
- z: const Float := 4.567e8
- t: const Float := 2.5E-3
-return
-
-end.
diff --git a/testsuite/compilable/single_literals.elna b/testsuite/compilable/single_literals.elna
new file mode 100644
index 0000000..ac03623
--- /dev/null
+++ b/testsuite/compilable/single_literals.elna
@@ -0,0 +1,9 @@
+proc f()
+var
+ x: const Single := 3.14f
+ y: const Single := 1e10f
+ z: const Single := 4.567e8f
+ t: const Single := 2.5E-3f
+return
+
+end.
diff --git a/testsuite/fail_compilation/float_remainder.elna b/testsuite/fail_compilation/float_remainder.elna
new file mode 100644
index 0000000..ec6c9e2
--- /dev/null
+++ b/testsuite/fail_compilation/float_remainder.elna
@@ -0,0 +1,8 @@
+proc f()
+var
+ x: Double
+begin
+ x := 5.0 % 2.0 (* @Error Invalid operands of type 'Double' and 'Double' for operator % *)
+return
+
+end.
diff --git a/testsuite/runnable/float_arithmetic.elna b/testsuite/runnable/float_arithmetic.elna
new file mode 100644
index 0000000..73072f2
--- /dev/null
+++ b/testsuite/runnable/float_arithmetic.elna
@@ -0,0 +1,13 @@
+begin
+ assert(6.0 / 2.0 = 3.0);
+ assert(5.0 / 2.0 = 2.5);
+ assert(3.0 * 0.5 = 1.5);
+ assert(-3.0 + 1.0 = -2.0);
+ assert(10.0 - 4.0 = 6.0);
+
+ assert(6.0f / 2.0f = 3.0f);
+ assert(5.0f / 2.0f = 2.5f);
+ assert(3.0f * 0.5f = 1.5f);
+ assert(1.0f / 4.0f = 0.25f);
+ assert(-3.14f < 0.0f)
+end.