/* Miscellaneous types used across stage boundaries. 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/result.h" #include #include #include #include #include namespace elna::boot { location::location(const std::size_t line, const std::size_t column) : m_line(line), m_column(column) { } std::size_t location::line() const { return this->m_line; } std::size_t location::column() const { return this->m_column; } bool location::available() const { return this->m_line != 0 || this->m_column != 0; } source_position::source_position(location start, location end) : m_start(start), m_end(end) { } const location& source_position::start() const { return this->m_start; } const location& source_position::end() const { return this->m_end; } bool source_position::is_span() const { return this->m_start != this->m_end; } diagnostic::diagnostic(const source_position position) : position(position) { } std::deque>& diagnostic_container::errors() { return m_errors; } const std::deque>& diagnostic_container::errors() const { return m_errors; } bool diagnostic_container::has_errors() const { return !m_errors.empty(); } identifier::identifier(const std::string& name, const source_position& position) : m_name(name), m_position(position) { } const std::string& identifier::name() const { return this->m_name; } std::string identifier::to_string() const { return name(); } const source_position& identifier::position() const { return this->m_position; } bool identifier::operator==(const identifier& that) const { return this->m_name == that; } bool identifier::operator==(std::string_view that) const { return this->m_name == that; } identifier_definition::identifier_definition(const std::string& name, const source_position& position, const bool exported) : m_identifier(name, position), m_exported(exported) { } const std::string& identifier_definition::name() const { return this->m_identifier.name(); } const identifier& identifier_definition::id() const { return this->m_identifier; } bool identifier_definition::exported() const { return this->m_exported; } std::optional previous_declaration_note( const std::optional& original, std::string_view label, const std::filesystem::path& file) { if (original.has_value() && original.value().start().available()) { return diagnostic_note{ .message = std::string(label), .position = original.value(), .file = file }; } else { return std::nullopt; } } std::optional identifier_list_note( const std::vector& identifiers) { auto position_span = source_position(identifiers.front().position().start(), identifiers.back().position().end()); return diagnostic_note{ .message = join(identifiers), .position = position_span, .file = {} }; } std::vector extract_identifiers(const std::vector& identifiers) { std::vector result; result.reserve(identifiers.size()); std::ranges::transform(identifiers, std::back_inserter(result), [](const auto& identifier) { return identifier.id(); }); return result; } integer_literal::integer_literal(bool is_signed, std::size_t size) : m_signed(is_signed), m_size(size) { mpz_init(this->raw); } integer_literal::integer_literal(integer_literal&& that) noexcept : m_signed(that.is_signed()), m_size(that.size()) { mpz_init(this->raw); mpz_swap(this->raw, that.raw); } integer_literal::integer_literal(const integer_literal& that) : m_signed(that.is_signed()), m_size(that.size()) { mpz_init(this->raw); mpz_set(this->raw, that.raw); } integer_literal::~integer_literal() { mpz_clear(this->raw); } std::optional integer_literal::add(const integer_literal& that) const { integer_literal result = *this; mpz_add(result.raw, this->raw, that.raw); return std::move(result).check(); } std::optional integer_literal::sub(const integer_literal& that) const { integer_literal result = *this; mpz_sub(result.raw, this->raw, that.raw); return std::move(result).check(); } std::optional integer_literal::mul(const integer_literal& that) const { integer_literal result = *this; mpz_mul(result.raw, this->raw, that.raw); return std::move(result).check(); } std::optional integer_literal::div(const integer_literal& that) const { if (mpz_cmp_ui(that.raw, 0U) == 0) { return std::nullopt; } else { integer_literal result = *this; mpz_div(result.raw, this->raw, that.raw); return std::make_optional(std::move(result)); } } std::optional integer_literal::mod(const integer_literal& that) const { if (mpz_cmp_ui(that.raw, 0U) == 0) { return std::nullopt; } else { integer_literal result = *this; mpz_mod(result.raw, this->raw, that.raw); return std::make_optional(std::move(result)); } } std::optional integer_literal::neg() const { if (!is_signed() || is_negative_minimum(bits())) { return std::nullopt; } integer_literal result = *this; mpz_neg(result.raw, this->raw); return result; } std::optional integer_literal::negate() const { integer_literal result{ true, this->m_size }; mpz_set(result.raw, this->raw); mpz_neg(result.raw, result.raw); return std::move(result).check(); } std::optional integer_literal::shl(const integer_literal& that) const { if (that >= bits()) { return std::nullopt; } else { integer_literal result = *this; mpz_mul_2exp(result.raw, this->raw, static_cast(mpz_get_ui(that.raw))); return std::make_optional(std::move(result).cast_to(is_signed(), size())); } } std::optional integer_literal::shr(const integer_literal& that) const { if (that >= bits()) { return std::nullopt; } else { integer_literal result = *this; mpz_fdiv_q_2exp(result.raw, this->raw, static_cast(mpz_get_ui(that.raw))); return std::make_optional(std::move(result)); } } integer_literal integer_literal::operator|(const integer_literal& that) const { integer_literal result = *this; mpz_ior(result.raw, this->raw, that.raw); return result; } integer_literal integer_literal::operator&(const integer_literal& that) const { integer_literal result = *this; mpz_and(result.raw, this->raw, that.raw); return result; } integer_literal integer_literal::operator^(const integer_literal& that) const { integer_literal result = *this; mpz_xor(result.raw, this->raw, that.raw); return result; } integer_literal integer_literal::operator~() const { integer_literal result = *this; mpz_com(result.raw, this->raw); return std::move(result).cast_to(is_signed(), size()); } bool integer_literal::operator==(const integer_literal& that) const { return mpz_cmp(this->raw, that.raw) == 0; } std::weak_ordering integer_literal::operator<=>(const integer_literal& that) const { return mpz_cmp(this->raw, that.raw) <=> 0; } integer_literal& integer_literal::operator=(integer_literal&& that) noexcept { swap(*this, that); return *this; } integer_literal& integer_literal::operator=(const integer_literal& that) { if (this != &that) { integer_literal temp(that); swap(*this, temp); } return *this; } bool integer_literal::fit_into(bool target_signed, const std::size_t target_size) { if (fits_in(target_signed, target_size * CHAR_BIT)) { this->m_signed = target_signed; this->m_size = target_size; return true; } return false; } bool integer_literal::fit_into(const std::size_t target_size) { return fit_into(is_signed(), target_size); } integer_literal integer_literal::cast_to(bool target_signed, std::size_t target_size) const { integer_literal result{ target_signed, target_size }; const std::size_t bits = target_size * CHAR_BIT; // Reduce to the unsigned residue in [0, 2^bits). This is the bit pattern // resulting from truncating or zero-extending in two's complement, // regardless of the source's sign. mpz_fdiv_r_2exp(result.raw, this->raw, bits); // Since GMP doesn't store the value as 2's complement, if the value is // signed it should be converted manually. if (target_signed && mpz_tstbit(result.raw, bits - 1)) { mpz_t modulus; mpz_init(modulus); mpz_set_ui(modulus, 1); mpz_mul_2exp(modulus, modulus, bits); mpz_sub(result.raw, result.raw, modulus); mpz_clear(modulus); } return result; } bool integer_literal::is_signed() const { return this->m_signed; } std::size_t integer_literal::size() const { return this->m_size; } std::string integer_literal::to_string(const std::uint8_t base) const { // +1 sign, +1 null terminator const size_t buffer_size = mpz_sizeinbase(this->raw, static_cast(base)) + 2; std::string result(buffer_size, '\0'); mpz_get_str(result.data(), base, this->raw); result.resize(std::strlen(result.c_str())); return result; } void swap(integer_literal& lhs, integer_literal& rhs) noexcept { mpz_swap(lhs.raw, rhs.raw); std::swap(lhs.m_signed, rhs.m_signed); std::swap(lhs.m_size, rhs.m_size); } bool integer_literal::fits_in(bool target_signed, const std::size_t bits) const { std::size_t required_bits = mpz_sizeinbase(this->raw, 2); if (target_signed && !is_negative_minimum(bits)) { ++required_bits; // Add one bit for the sign. } return required_bits <= bits && (!is_negative() || target_signed); } std::optional integer_literal::check() && { return fits_in(is_signed(), bits()) ? std::make_optional(std::move(*this)) : std::nullopt; } bool integer_literal::is_negative_minimum(const std::size_t bits) const { return is_negative() && mpz_scan1(this->raw, 0) == bits - 1; } std::size_t integer_literal::bits() const { return size() * CHAR_BIT; } bool integer_literal::is_negative() const { 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(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 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 { using T = std::decay_t; return std::hash{}(alternative); }, value); } hash_accumulator hash_accumulator::operator+(const std::size_t& that) const { hash_accumulator result{}; result.m_seed ^= that + golden_ratio + (this->m_seed << mix_shift_left) + (this->m_seed >> mix_shift_right); return result; } std::size_t hash_accumulator::seed() const { return this->m_seed; } } std::size_t std::hash::operator()( const elna::boot::identifier& key) const noexcept { return std::hash{}(key.name()); } std::size_t std::hash>::operator()( const elna::boot::constant_aggregate& key) const noexcept { const elna::boot::constant_value_hash hasher{}; auto hash = std::accumulate(key->begin(), key->end(), elna::boot::hash_accumulator{}, [&hasher](const auto& accumulator, const auto& element) { return accumulator + hasher(element); }); return hash.seed(); } std::size_t std::hash>::operator()( const elna::boot::constant_aggregate& key) const noexcept { const elna::boot::constant_value_hash hasher{}; auto hash = std::accumulate(key->begin(), key->end(), elna::boot::hash_accumulator{}, [&hasher](const auto& accumulator, const auto& element) { return accumulator + std::hash{}(element.first) + hasher(element.second); }); return hash.seed(); } std::size_t std::hash::operator()(const elna::boot::integer_literal& key) const noexcept { if (key.is_signed()) { if (auto converted = key.try_to()) { return std::hash{}(*converted); } } else { if (auto converted = key.try_to()) { return std::hash{}(*converted); } } return 0; } std::size_t std::hash::operator()(const elna::boot::float_literal& key) const noexcept { // -0.0 and 0.0 compare equal, so their hashes must agree. return std::hash{}(key == 0.0 ? 0.0 : key.value()); }