diff options
28 files changed, 957 insertions, 247 deletions
diff --git a/boot/dependency.cc b/boot/dependency.cc index 9ab5141..bd1251a 100644 --- a/boot/dependency.cc +++ b/boot/dependency.cc @@ -18,6 +18,7 @@ along with GCC; see the file COPYING3. If not see #include "elna/boot/dependency.h" #include "elna/boot/driver.h" +#include "elna/boot/materialization.h" #include "elna/boot/name_analysis.h" #include "elna/boot/type_check.h" #include "elna/boot/validation.h" @@ -25,7 +26,7 @@ along with GCC; see the file COPYING3. If not see namespace elna::boot { - dependency read_source(std::istream& entry_point) + dependency read_source(std::istream& entry_point, const target_info& target) { driver parse_driver; lexer tokenizer(entry_point); @@ -41,10 +42,18 @@ namespace elna::boot { std::swap(outcome.tree, parse_driver.tree); } - declaration_visitor declaration_visitor; + materialization_visitor materialization_visitor(target); + outcome.tree->accept(&materialization_visitor); + + if (materialization_visitor.has_errors()) + { + std::swap(outcome.errors(), materialization_visitor.errors()); + return outcome; + } + declaration_visitor declaration_visitor{}; outcome.tree->accept(&declaration_visitor); - if (!declaration_visitor.errors().empty()) + if (declaration_visitor.has_errors()) { std::swap(outcome.errors(), declaration_visitor.errors()); } diff --git a/boot/evaluator.cc b/boot/evaluator.cc index 14ca889..d8d9ddc 100644 --- a/boot/evaluator.cc +++ b/boot/evaluator.cc @@ -73,31 +73,18 @@ namespace elna::boot { auto resolved = resolve_underlying_type(subject); - if (is_primitive_type(resolved, "Int") - || resolved.get<enumeration_type>() != nullptr) + if (resolved.get<enumeration_type>() != nullptr) { - return target.int_properties; + return target.word_properties.front(); } - else if (is_primitive_type(resolved, "Word")) - { - return target.word_properties; - } - else if (is_primitive_type(resolved, "Char")) + else if (auto resolved_primitive = resolved.get<primitive_type>()) { - return target.char_properties; - } - else if (is_primitive_type(resolved, "Float")) - { - return target.float_properties; - } - else if (is_primitive_type(resolved, "Bool")) - { - return target.bool_properties; + return resolved_primitive->properties; } else if (resolved.get<slice_type>() != nullptr) { return type_properties{ - .size = target.pointer_properties.size + target.word_properties.size, + .size = target.pointer_properties.size + target.word_properties.front().size, .alignment = target.pointer_properties.alignment }; } @@ -812,13 +799,23 @@ namespace elna::boot { type const resolved = resolve_underlying_type(subject.types.front()); - if (is_primitive_type(resolved, "Int")) - { - return constant_value{ integer_literal::from(std::numeric_limits<std::ptrdiff_t>::min()) }; - } - if (is_primitive_type(resolved, "Word")) + if (auto resolved_primitive = resolved.get<primitive_type>()) { - return constant_value{ integer_literal::from<std::size_t>(0) }; + 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(); + + 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() }; + } } if (is_primitive_type(resolved, "Char")) { @@ -841,13 +838,26 @@ namespace elna::boot { type const resolved = resolve_underlying_type(subject.types.front()); - if (is_primitive_type(resolved, "Int")) + if (auto resolved_primitive = resolved.get<primitive_type>()) { - return constant_value{ integer_literal::from(std::numeric_limits<std::ptrdiff_t>::max()) }; - } - if (is_primitive_type(resolved, "Word")) - { - return constant_value{ integer_literal::from(std::numeric_limits<std::size_t>::max()) }; + 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)); + + 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{ ~zero.value() }; + } } if (is_primitive_type(resolved, "Char")) { diff --git a/boot/lexer.ll b/boot/lexer.ll index e944560..691034a 100644 --- a/boot/lexer.ll +++ b/boot/lexer.ll @@ -47,7 +47,9 @@ along with GCC; see the file COPYING3. If not see ID1 [A-Za-z_] ID2 [A-Za-z0-9_] HIGIT [0-9a-fA-F] +HEXADECIMAL 0[xX]{HIGIT}+ BIGIT [01] +NATURAL [1-9][[:digit:]]* %% %{ @@ -173,55 +175,158 @@ to { #{ID1}{ID2}* { return yy::parser::make_TRAIT(yytext + 1, this->location); } -[[:digit:]]+u { - std::uint64_t result = strtoull(yytext, NULL, 10); - - if (errno == ERANGE) +(0|{NATURAL})u|{HEXADECIMAL} { + if (auto result = parse_integer<std::uint64_t>(yytext, 0)) + { + return yy::parser::make_WORD(result.value(), this->location); + } + else { REJECT; } +} +(0|{NATURAL}|{HEXADECIMAL})u8 { + if (auto result = parse_integer<std::uint8_t>(yytext, 0)) + { + return yy::parser::make_WORD8(result.value(), this->location); + } else { - return yy::parser::make_WORD(result, this->location); + REJECT; } } -[[:digit:]]+ { - std::int64_t result = strtoll(yytext, NULL, 10); - - if (errno == ERANGE) +(0|{NATURAL}|{HEXADECIMAL})u16 { + if (auto result = parse_integer<std::uint16_t>(yytext, 0)) + { + return yy::parser::make_WORD16(result.value(), this->location); + } + else { REJECT; } +} +(0|{NATURAL}|{HEXADECIMAL})u32 { + if (auto result = parse_integer<std::uint32_t>(yytext, 0)) + { + return yy::parser::make_WORD32(result.value(), this->location); + } else { - return yy::parser::make_INTEGER(result, this->location); + REJECT; } } -0[x|X]{HIGIT}+ { - std::uint64_t result = strtoull(yytext, NULL, 16); - - if (errno == ERANGE) +(0|{NATURAL}|{HEXADECIMAL})u64 { + if (auto result = parse_integer<std::uint64_t>(yytext, 0)) + { + return yy::parser::make_WORD64(result.value(), this->location); + } + else { REJECT; } +} +0|{NATURAL} { + if (auto result = parse_integer<std::int64_t>(yytext, 10)) + { + return yy::parser::make_INTEGER(result.value(), this->location); + } else { - return yy::parser::make_WORD(result, this->location); + REJECT; } } -0[b|B]{BIGIT}+ { - std::uint64_t result = strtoull(yytext + 2, NULL, 2); - - if (errno == ERANGE) +(0|{NATURAL})i8 { + if (auto result = parse_integer<std::int8_t>(yytext, 10)) + { + return yy::parser::make_INTEGER8(result.value(), this->location); + } + else { REJECT; } +} +(0|{NATURAL})i16 { + if (auto result = parse_integer<std::int16_t>(yytext, 10)) + { + return yy::parser::make_INTEGER16(result.value(), this->location); + } + else + { + REJECT; + } +} +(0|{NATURAL})i32 { + if (auto result = parse_integer<std::int32_t>(yytext, 10)) + { + return yy::parser::make_INTEGER32(result.value(), this->location); + } else { - return yy::parser::make_WORD(result, this->location); + REJECT; + } +} +(0|{NATURAL})i64 { + if (auto result = parse_integer<std::int64_t>(yytext, 10)) + { + return yy::parser::make_INTEGER64(result.value(), this->location); + } + else + { + REJECT; + } +} +0[bB]{BIGIT}+ { + if (auto result = parse_integer<std::uint64_t>(yytext + 2, 2)) + { + return yy::parser::make_WORD(result.value(), this->location); + } + else + { + REJECT; + } +} +0[bB]{BIGIT}+u8 { + if (auto result = parse_integer<std::uint8_t>(yytext + 2, 2)) + { + return yy::parser::make_WORD8(result.value(), this->location); + } + else + { + REJECT; + } +} +0[bB]{BIGIT}+u16 { + if (auto result = parse_integer<std::uint16_t>(yytext + 2, 2)) + { + return yy::parser::make_WORD16(result.value(), this->location); + } + else + { + REJECT; + } +} +0[bB]{BIGIT}+u32 { + if (auto result = parse_integer<std::uint32_t>(yytext + 2, 2)) + { + return yy::parser::make_WORD32(result.value(), this->location); + } + else + { + REJECT; + } +} +0[bB]{BIGIT}+u64 { + if (auto result = parse_integer<std::uint64_t>(yytext + 2, 2)) + { + return yy::parser::make_WORD64(result.value(), this->location); + } + else + { + REJECT; } } [[:digit:]]+\.[[:digit:]]+([eE][+-]?[[:digit:]]+)? { + errno = 0; double result = strtod(yytext, NULL); if (errno == ERANGE) @@ -234,6 +339,7 @@ to { } } [[:digit:]]+[eE][+-]?[[:digit:]]+ { + errno = 0; double result = strtod(yytext, NULL); if (errno == ERANGE) diff --git a/boot/materialization.cc b/boot/materialization.cc new file mode 100644 index 0000000..e055dfd --- /dev/null +++ b/boot/materialization.cc @@ -0,0 +1,62 @@ +/* Literal sign folding. + 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 +<http://www.gnu.org/licenses/>. */ + +#include "elna/boot/materialization.h" + +namespace elna::boot +{ + materialization_error::materialization_error(const source_position position) + : error(position) + { + } + + std::string materialization_error::what() const + { + return "Integer literal overflows"; + } + + materialization_visitor::materialization_visitor(const target_info& target) + : target(target) + { + } + + void materialization_visitor::visit(literal<integer_literal> *literal) + { + switch (literal->wants_signed) + { + using enum integer_sign; + case negative: + if (auto negated_literal = literal->value.negate()) + { + literal->value = negated_literal.value(); + } + else + { + add_error<materialization_error>(literal->position()); + } + break; + case unmarked: + if (!literal->value.fit_into(true, literal->value.size())) + { + add_error<materialization_error>(literal->position()); + } + break; + case _unsigned: + break; + } + } +} diff --git a/boot/name_analysis.cc b/boot/name_analysis.cc index 56ea399..ae6810d 100644 --- a/boot/name_analysis.cc +++ b/boot/name_analysis.cc @@ -69,23 +69,53 @@ namespace elna::boot return std::nullopt; } - const_qualifier_error::const_qualifier_error(const source_position position, kind error_kind) - : error(position), error_kind(error_kind) + name_analysis_error::name_analysis_error(const source_position position, payload_type payload) + : error(position), payload(std::move(payload)) { } - std::string const_qualifier_error::what() const + std::string name_analysis_error::what() const { - switch (error_kind) - { - using enum kind; - case array_position: - return "const must be written before the array size, not after"; - case duplicate: - return "Duplicate 'const' qualifier is not allowed"; - default: - __builtin_unreachable(); - } + return std::visit([](const auto& payload) -> std::string { + using T = std::decay_t<decltype(payload)>; + + if constexpr (std::is_same_v<T, not_initialized>) + { + return "All constants should be initialized"; + } + else if constexpr (std::is_same_v<T, kind>) + { + switch (payload) + { + using enum kind; + case array_position: + return "const must be written before the array size, not after"; + case duplicate: + return "Duplicate 'const' qualifier is not allowed"; + default: + __builtin_unreachable(); + } + } + }, this->payload); + } + + std::optional<std::pair<std::string, source_position>> name_analysis_error::note() const + { + return std::visit([](const auto& payload) -> std::optional<std::pair<std::string, source_position>> { + using T = std::decay_t<decltype(payload)>; + + if constexpr (std::is_same_v<T, not_initialized>) + { + auto position_span = source_position(payload.identifiers.front().position().start(), + payload.identifiers.back().position().end()); + + return std::make_optional(std::make_pair(join(payload.identifiers), position_span)); + } + else + { + return std::nullopt; + } + }, this->payload); } member_error::member_error(const source_position position, const std::string& name, @@ -164,23 +194,6 @@ namespace elna::boot return std::nullopt; } - not_initialized_error::not_initialized_error(const source_position position, std::vector<identifier> identifiers) - : error(position), identifiers(std::move(identifiers)) - { - } - - std::string not_initialized_error::what() const - { - return "All constants should be initialized"; - } - - std::optional<std::pair<std::string, source_position>> not_initialized_error::note() const - { - auto position_span = source_position(this->identifiers.front().position().start(), - this->identifiers.back().position().end()); - return std::make_pair(join(this->identifiers), position_span); - } - // Members of a constant aggregate are constant themselves. static type qualify_member_type(const type& element, const type& aggregate) { @@ -304,8 +317,8 @@ namespace elna::boot if (this->current_type.get<constant_type>() != nullptr) { - add_error<const_qualifier_error>(expression->position(), - const_qualifier_error::kind::duplicate); + add_error<name_analysis_error>(expression->position(), + name_analysis_error::kind::duplicate); } this->current_type = type(std::make_shared<constant_type>(this->current_type)); } @@ -317,8 +330,8 @@ namespace elna::boot if (array_base.get<constant_type>() != nullptr) { - add_error<const_qualifier_error>(expression->position(), - const_qualifier_error::kind::array_position); + add_error<name_analysis_error>(expression->position(), + name_analysis_error::kind::array_position); } expression->dimensions().accept(this); const auto size_constant = this->constant_evaluator.evaluate_index(expression->dimensions()); @@ -567,8 +580,8 @@ namespace elna::boot { auto position_span = source_position(declaration->identifiers.front().id().position().start(), declaration->identifiers.back().id().position().end()); - add_error<not_initialized_error>(position_span, - extract_identifiers(declaration->identifiers)); + add_error<name_analysis_error>(position_span, + name_analysis_error::not_initialized{ extract_identifiers(declaration->identifiers) }); } for (const identifier_definition& variable_identifier : declaration->identifiers) { @@ -881,11 +894,27 @@ namespace elna::boot { if (literal->value.is_signed()) { - literal->type_decoration = lookup_primitive_type("Int"); + if (literal->has_explicit_size) + { + literal->type_decoration = lookup_primitive_type( + "Int" + std::to_string(literal->value.size() * CHAR_BIT)); + } + else + { + literal->type_decoration = lookup_primitive_type("Int"); + } } else { - literal->type_decoration = lookup_primitive_type("Word"); + if (literal->has_explicit_size) + { + literal->type_decoration = lookup_primitive_type( + "Word" + std::to_string(literal->value.size() * CHAR_BIT)); + } + else + { + literal->type_decoration = lookup_primitive_type("Word"); + } } this->current_type = type(); } @@ -921,20 +950,8 @@ namespace elna::boot this->current_type = type(); } - declaration_visitor::declaration_visitor() - { - } - - void declaration_visitor::visit(import_declaration *) - { - } - void declaration_visitor::visit(unit *unit) { - for (import_declaration *const _import : unit->imports) - { - _import->accept(this); - } for (type_declaration *const type : unit->types) { type->accept(this); diff --git a/boot/parser.yy b/boot/parser.yy index a28b6cb..b48510b 100644 --- a/boot/parser.yy +++ b/boot/parser.yy @@ -78,8 +78,10 @@ along with GCC; see the file COPYING3. If not see %token <std::string> IDENTIFIER %token <std::string> TRAIT -%token <std::int64_t> INTEGER -%token <std::uint64_t> WORD +%token <std::pair<std::uint64_t, elna::boot::integer_sign>> INTEGER INTEGER64 WORD WORD64 +%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 <std::string> CHARACTER %token <std::string> STRING @@ -130,8 +132,7 @@ along with GCC; see the file COPYING3. If not see %type <std::vector<elna::boot::variable_declaration *>> variable_declarations variable_part; %type <elna::boot::type_expression *> type_expression; %type <std::vector<elna::boot::type_expression *>> type_expressions; -%type <elna::boot::expression *> expression operand simple_expression procedure_return; -%type <elna::boot::unary_expression *> unary_expression; +%type <elna::boot::expression *> expression operand simple_expression procedure_return unary_expression; %type <elna::boot::binary_expression *> binary_expression; %type <std::vector<elna::boot::expression *>> expressions actual_parameter_list; %type <elna::boot::designator_expression *> designator_expression; @@ -237,13 +238,86 @@ procedure_return: "return" expression { $$ = $2; } | "return" { $$ = nullptr; } literal: - INTEGER { $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), boot::integer_literal::from($1)); } - | WORD { $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), boot::integer_literal::from($1)); } - | FLOAT { $$ = new boot::literal<double>(boot::make_position(@$), $1); } - | BOOLEAN { $$ = new boot::literal<bool>(boot::make_position(@$), $1); } - | CHARACTER { $$ = new boot::literal<unsigned char>(boot::make_position(@$), $1.at(0)); } - | "nil" { $$ = new boot::literal<std::nullptr_t>(boot::make_position(@$), nullptr); } - | STRING { $$ = new boot::literal<std::string>(boot::make_position(@$), $1); } + INTEGER + { + auto [magnitude, wants_signed] = $1; + $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), + boot::integer_literal::from(magnitude), wants_signed, false); + } + | INTEGER8 + { + auto [magnitude, wants_signed] = $1; + $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), + boot::integer_literal::from(magnitude), wants_signed); + } + | INTEGER16 + { + auto [magnitude, wants_signed] = $1; + $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), + boot::integer_literal::from(magnitude), wants_signed); + } + | INTEGER32 + { + auto [magnitude, wants_signed] = $1; + $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), + boot::integer_literal::from(magnitude), wants_signed); + } + | INTEGER64 + { + auto [magnitude, wants_signed] = $1; + $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), + boot::integer_literal::from(magnitude), wants_signed); + } + | WORD + { + auto [magnitude, wants_signed] = $1; + $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), + boot::integer_literal::from(magnitude), wants_signed, false); + } + | WORD8 + { + auto [magnitude, wants_signed] = $1; + $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), + boot::integer_literal::from(magnitude), wants_signed); + } + | WORD16 + { + auto [magnitude, wants_signed] = $1; + $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), + boot::integer_literal::from(magnitude), wants_signed); + } + | WORD32 + { + auto [magnitude, wants_signed] = $1; + $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), + boot::integer_literal::from(magnitude), wants_signed); + } + | WORD64 + { + auto [magnitude, wants_signed] = $1; + $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), + boot::integer_literal::from(magnitude), wants_signed); + } + | FLOAT + { + $$ = new boot::literal<double>(boot::make_position(@$), $1, boot::integer_sign::unmarked); + } + | BOOLEAN + { + $$ = new boot::literal<bool>(boot::make_position(@$), $1, boot::integer_sign::_unsigned); + } + | CHARACTER + { + $$ = new boot::literal<unsigned char>(boot::make_position(@$), $1.at(0), boot::integer_sign::_unsigned); + } + | "nil" + { + $$ = new boot::literal<std::nullptr_t>(boot::make_position(@$), nullptr, boot::integer_sign::_unsigned); + } + | STRING + { + $$ = new boot::literal<std::string>(boot::make_position(@$), $1, boot::integer_sign::_unsigned); + } simple_expression: literal { $$ = $1; } | designator_expression { $$ = $1; } @@ -348,7 +422,25 @@ unary_expression: } | "-" operand { - $$ = new boot::unary_expression(boot::make_position(@$), $2, boot::unary_operator::minus); + auto operand = $2; + auto integer_operand = operand->is_literal() == nullptr + ? nullptr + : operand->is_literal()->is_a<boot::integer_literal>(); + + if (integer_operand != nullptr + && integer_operand->wants_signed == boot::integer_sign::unmarked) + { + // Rebuild the literal with a position spanning the minus and the + // integer token. + $$ = new boot::literal<boot::integer_literal>(boot::make_position(@$), + integer_operand->value, boot::integer_sign::negative, + integer_operand->has_explicit_size); + delete integer_operand; + } + else + { + $$ = new boot::unary_expression(boot::make_position(@$), operand, boot::unary_operator::minus); + } } | "+" operand { diff --git a/boot/result.cc b/boot/result.cc index 30b4576..15fe8b4 100644 --- a/boot/result.cc +++ b/boot/result.cc @@ -226,7 +226,7 @@ namespace elna::boot std::optional<integer_literal> integer_literal::neg() const { - if (!is_signed() || is_negative_minimum()) + if (!is_signed() || is_negative_minimum(bits())) { return std::nullopt; } @@ -237,32 +237,44 @@ namespace elna::boot return result; } + std::optional<integer_literal> 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> integer_literal::shl(const integer_literal& that) const { if (that >= bits()) { return std::nullopt; } - integer_literal result = *this; - - mpz_mul_2exp(result.raw, this->raw, static_cast<mp_bitcnt_t>(mpz_get_ui(that.raw))); + else + { + integer_literal result = *this; - return std::move(result).check(); + mpz_mul_2exp(result.raw, this->raw, static_cast<mp_bitcnt_t>(mpz_get_ui(that.raw))); + return std::make_optional(std::move(result).cast_to(is_signed(), size())); + } } std::optional<integer_literal> integer_literal::shr(const integer_literal& that) const { - integer_literal result = *this; - if (that >= bits()) { - mpz_set_si(result.raw, *this > 0 ? 0 : -1); + return std::nullopt; } else { + integer_literal result = *this; + mpz_fdiv_q_2exp(result.raw, this->raw, static_cast<mp_bitcnt_t>(mpz_get_ui(that.raw))); + return std::make_optional(std::move(result)); } - return std::make_optional(std::move(result)); } integer_literal integer_literal::operator|(const integer_literal& that) const @@ -298,7 +310,7 @@ namespace elna::boot mpz_com(result.raw, this->raw); - return result; + return std::move(result).cast_to(is_signed(), size()); } bool integer_literal::operator==(const integer_literal& that) const @@ -328,16 +340,49 @@ namespace elna::boot return *this; } - bool integer_literal::fit_into(const std::size_t target_size) + bool integer_literal::fit_into(bool target_signed, const std::size_t target_size) { - if (fits_in(target_size * CHAR_BIT)) + 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; @@ -367,30 +412,27 @@ namespace elna::boot std::swap(lhs.m_size, rhs.m_size); } - bool integer_literal::fits_in(const std::size_t bits) const + 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 (!is_negative() || !is_negative_minimum(bits)) + if (target_signed && !is_negative_minimum(bits)) { ++required_bits; // Add one bit for the sign. } - return required_bits <= bits && (!is_negative() || is_signed()); + return required_bits <= bits && (!is_negative() || target_signed); } std::optional<integer_literal> integer_literal::check() && { - return fits_in(bits()) ? std::make_optional(std::move(*this)) : std::nullopt; + 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 mpz_scan1(this->raw, 0) == bits - 1; - } - - bool integer_literal::is_negative_minimum() const - { - return is_negative() && is_negative_minimum(bits()); + return is_negative() && mpz_scan1(this->raw, 0) == bits - 1; } std::size_t integer_literal::bits() const diff --git a/boot/symbol.cc b/boot/symbol.cc index 1cc652c..9854acc 100644 --- a/boot/symbol.cc +++ b/boot/symbol.cc @@ -184,8 +184,8 @@ namespace elna::boot }, payload); } - alias_type::alias_type(const std::string& name) - : name(name) + alias_type::alias_type(const std::string& name, type referent) + : name(name), referent(std::move(referent)) { } @@ -209,8 +209,8 @@ namespace elna::boot { } - primitive_type::primitive_type(const std::string& identifier) - : identifier(identifier) + primitive_type::primitive_type(const std::string& identifier, const type_properties& properties) + : identifier(identifier), properties(properties) { } @@ -282,16 +282,42 @@ namespace elna::boot return std::static_pointer_cast<variable_info>(shared_from_this()); } - std::shared_ptr<symbol_table> builtin_symbol_table() + static void builtin_integers(const std::shared_ptr<symbol_table>& symbols, + const std::array<type_properties, target_integer_count>& properties, + const std::string& integer_name) + { + for (std::size_t i = 1; i < properties.size(); ++i) + { + const std::size_t bit_size = properties[i].size * CHAR_BIT; + const std::string type_name = integer_name + std::to_string(bit_size); + const type variant_type = type(std::make_shared<primitive_type>(type_name, properties[i])); + + symbols->enter(type_name, std::make_shared<type_info>(variant_type)); + } + if (!symbols->contains(integer_name)) + { + auto variant_type = type(std::make_shared<primitive_type>(integer_name, properties.front())); + + symbols->enter(integer_name, std::make_shared<type_info>(variant_type)); + } + } + + std::shared_ptr<symbol_table> builtin_symbol_table(const target_info& target) { auto result = std::make_shared<symbol_table>(); - result->enter("Int", std::make_shared<type_info>(type(std::make_shared<primitive_type>("Int")))); - result->enter("Word", std::make_shared<type_info>(type(std::make_shared<primitive_type>("Word")))); - result->enter("Char", std::make_shared<type_info>(type(std::make_shared<primitive_type>("Char")))); - result->enter("Pointer", std::make_shared<type_info>(type(std::make_shared<primitive_type>("Pointer")))); - result->enter("Float", std::make_shared<type_info>(type(std::make_shared<primitive_type>("Float")))); - type const boolean = type(std::make_shared<primitive_type>("Bool")); + builtin_integers(result, target.int_properties, "Int"); + builtin_integers(result, target.word_properties, "Word"); + + result->enter("Char", + std::make_shared<type_info>(type(std::make_shared<primitive_type>("Char", target.char_properties)))); + + 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)))); + + const type boolean = type(std::make_shared<primitive_type>("Bool", target.bool_properties)); result->enter("Bool", std::make_shared<type_info>(boolean)); procedure_type assert_symbol{ procedure_type::return_t() }; @@ -426,15 +452,18 @@ namespace elna::boot bool is_numeric_type(const type& checked) { - return is_primitive_type(checked, "Int") - || is_primitive_type(checked, "Word") + return is_integral_type(checked) || is_primitive_type(checked, "Float"); } bool is_integral_type(const type& checked) { - return is_primitive_type(checked, "Int") - || is_primitive_type(checked, "Word"); + if (auto primitive_checked = checked.get<primitive_type>()) + { + return primitive_checked->identifier.starts_with("Int") + || primitive_checked->identifier.starts_with("Word"); + } + return false; } bool is_discrete_type(const type& checked) diff --git a/boot/type_check.cc b/boot/type_check.cc index 78e975a..b1739d9 100644 --- a/boot/type_check.cc +++ b/boot/type_check.cc @@ -1038,11 +1038,11 @@ namespace elna::boot bool narrowed{ false }; if (expression->value.is_signed()) { - narrowed = expression->value.fit_into(target.int_properties.size); + narrowed = expression->value.fit_into(target.int_properties.front().size); } else { - narrowed = expression->value.fit_into(target.word_properties.size); + narrowed = expression->value.fit_into(target.word_properties.front().size); } if (!narrowed) { diff --git a/boot/validation.cc b/boot/validation.cc index b29381a..0c03fd0 100644 --- a/boot/validation.cc +++ b/boot/validation.cc @@ -55,6 +55,38 @@ namespace elna::boot { } + void validation_visitor::visit(assign_statement *) + { + } + + void validation_visitor::visit(if_statement *) + { + } + + void validation_visitor::visit(while_statement *) + { + } + + void validation_visitor::visit(repeat_statement *) + { + } + + void validation_visitor::visit(for_statement *) + { + } + + void validation_visitor::visit(defer_statement *) + { + } + + void validation_visitor::visit(empty_statement *) + { + } + + void validation_visitor::visit(procedure_call *) + { + } + void validation_visitor::visit(unit *unit) { for (procedure_declaration *procedure : unit->procedures) @@ -73,17 +105,31 @@ namespace elna::boot { auto procedure = this->bag.lookup(declaration->identifier.name())->is_procedure(); this->bag.enter(procedure->scope); - } - walking_visitor::visit(declaration); - if (declaration->body.has_value()) - { + for (auto *statement : declaration->body.value().entry_point) + { + statement->accept(this); + } this->bag.leave(); } } void validation_visitor::visit(case_statement *statement) { - walking_visitor::visit(statement); + for (const switch_case& case_block : statement->cases) + { + for (auto *block_statement : case_block.statements) + { + block_statement->accept(this); + } + } + if (statement->alternative != nullptr) + { + for (auto *block_statement : *statement->alternative) + { + block_statement->accept(this); + } + } + std::unordered_map<constant_value, source_position, constant_value_hash> seen; for (const auto& case_block : statement->cases) { diff --git a/gcc/Make-lang.in b/gcc/Make-lang.in index 690d047..bf1893d 100644 --- a/gcc/Make-lang.in +++ b/gcc/Make-lang.in @@ -58,6 +58,7 @@ elna_OBJS = \ elna/symbol.o \ elna/result.o \ elna/validation.o \ + elna/materialization.o \ $(END) elna1$(exeext): attribs.o $(elna_OBJS) $(BACKEND) $(LIBDEPS) diff --git a/gcc/gcc/elna-builtins.cc b/gcc/gcc/elna-builtins.cc index 7c4545e..4f3db65 100644 --- a/gcc/gcc/elna-builtins.cc +++ b/gcc/gcc/elna-builtins.cc @@ -25,6 +25,43 @@ along with GCC; see the file COPYING3. If not see namespace elna::gcc { + static constexpr boot::type_properties get_host_numeric_properties(tree node) + { + return boot::type_properties{ + .size = static_cast<std::size_t>(TYPE_PRECISION(node) / BITS_PER_UNIT), + .alignment = TYPE_ALIGN_UNIT(node) + }; + } + + const boot::target_info& get_host_target() + { + static const boot::target_info info = boot::target_info{ + .int_properties = { + get_host_numeric_properties(elna_int_type_node), + get_host_numeric_properties(intQI_type_node), + get_host_numeric_properties(intHI_type_node), + get_host_numeric_properties(intSI_type_node), + get_host_numeric_properties(intDI_type_node) + }, + .word_properties = { + get_host_numeric_properties(elna_word_type_node), + get_host_numeric_properties(unsigned_intQI_type_node), + get_host_numeric_properties(unsigned_intHI_type_node), + get_host_numeric_properties(unsigned_intSI_type_node), + get_host_numeric_properties(unsigned_intDI_type_node) + }, + .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), + .bool_properties = { + .size = static_cast<std::size_t>( + (TYPE_PRECISION(elna_bool_type_node) + BITS_PER_UNIT - 1) / BITS_PER_UNIT), + .alignment = TYPE_ALIGN_UNIT(elna_bool_type_node) + } + }; + return info; + } + void init_ttree() { elna_int_type_node = ptrdiff_type_node; @@ -56,7 +93,15 @@ namespace elna::gcc auto builtin_table = std::make_shared<symbol_table>(); declare_builtin_type(builtin_table, "Int", elna_int_type_node); + declare_builtin_type(builtin_table, "Int8", intQI_type_node); + declare_builtin_type(builtin_table, "Int16", intHI_type_node); + declare_builtin_type(builtin_table, "Int32", intSI_type_node); + declare_builtin_type(builtin_table, "Int64", intDI_type_node); declare_builtin_type(builtin_table, "Word", elna_word_type_node); + declare_builtin_type(builtin_table, "Word8", unsigned_intQI_type_node); + declare_builtin_type(builtin_table, "Word16", unsigned_intHI_type_node); + declare_builtin_type(builtin_table, "Word32", unsigned_intSI_type_node); + declare_builtin_type(builtin_table, "Word64", unsigned_intDI_type_node); 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); diff --git a/gcc/gcc/elna-tree.cc b/gcc/gcc/elna-tree.cc index 05690d6..688fc4a 100644 --- a/gcc/gcc/elna-tree.cc +++ b/gcc/gcc/elna-tree.cc @@ -28,29 +28,6 @@ along with GCC; see the file COPYING3. If not see namespace elna::gcc { - const elna::boot::target_info& get_host_target() - { - static const elna::boot::target_info info = []{ - elna::boot::target_info target_info; - - target_info.int_properties.size = TYPE_PRECISION(elna_int_type_node) / BITS_PER_UNIT; - target_info.int_properties.alignment = TYPE_ALIGN_UNIT(elna_int_type_node); - target_info.word_properties.size = TYPE_PRECISION(elna_word_type_node) / BITS_PER_UNIT; - target_info.word_properties.alignment = TYPE_ALIGN_UNIT(elna_word_type_node); - target_info.pointer_properties.size = TYPE_PRECISION(ptr_type_node) / BITS_PER_UNIT; - target_info.pointer_properties.alignment = TYPE_ALIGN_UNIT(ptr_type_node); - target_info.char_properties.size = TYPE_PRECISION(elna_char_type_node) / BITS_PER_UNIT; - target_info.char_properties.alignment = TYPE_ALIGN_UNIT(elna_char_type_node); - target_info.float_properties.size = TYPE_PRECISION(elna_float_type_node) / BITS_PER_UNIT; - target_info.float_properties.alignment = TYPE_ALIGN_UNIT(elna_float_type_node); - target_info.bool_properties.size = (TYPE_PRECISION(elna_bool_type_node) + BITS_PER_UNIT - 1) / BITS_PER_UNIT; - target_info.bool_properties.alignment = TYPE_ALIGN_UNIT(elna_bool_type_node); - - return target_info; - }(); - return info; - } - bool is_integral_type(tree type) { gcc_assert(TYPE_P(type)); diff --git a/gcc/gcc/elna1.cc b/gcc/gcc/elna1.cc index 4eb01cc..180c839 100644 --- a/gcc/gcc/elna1.cc +++ b/gcc/gcc/elna1.cc @@ -102,7 +102,8 @@ static elna::boot::dependency elna_parse_file(dependency_state& state, const cha fatal_error(UNKNOWN_LOCATION, "Cannot open filename %s: %m", filename); } const elna::gcc::linemap_guard guard(filename); - elna::boot::dependency outcome = elna::boot::read_source(entry_point); + elna::boot::dependency outcome = elna::boot::read_source(entry_point, + elna::gcc::get_host_target()); elna::boot::symbol_bag outcome_bag{ std::move(outcome.unresolved), state.globals }; @@ -139,7 +140,8 @@ static elna::boot::dependency elna_parse_file(dependency_state& state, const cha static void elna_langhook_parse_file() { - dependency_state state{ elna::gcc::builtin_symbol_table() }; + elna::boot::target_info target = elna::gcc::get_host_target(); + dependency_state state{ elna::gcc::builtin_symbol_table(), target }; for (unsigned int i = 0; i < num_in_fnames; i++) { @@ -149,7 +151,7 @@ static void elna_langhook_parse_file() { linemap_add(line_table, LC_ENTER, 0, in_fnames[i], 1); elna::gcc::generic_visitor generic_visitor{ state.custom, - state.find(in_fnames[i])->second, elna::gcc::get_host_target() }; + state.find(in_fnames[i])->second, target }; outcome.tree->accept(&generic_visitor); linemap_add(line_table, LC_LEAVE, 0, nullptr, 0); } diff --git a/include/elna/boot/ast.h b/include/elna/boot/ast.h index 1ea17fb..2645026 100644 --- a/include/elna/boot/ast.h +++ b/include/elna/boot/ast.h @@ -62,6 +62,55 @@ namespace elna::boot plus }; + enum class integer_sign + { + _unsigned, + unmarked, + negative + }; + + template<typename T> + struct literal_type_id; + + template<> + struct literal_type_id<integer_literal> + { + static constexpr int value = 1; + }; + + template<> + struct literal_type_id<double> + { + static constexpr int value = 2; + }; + + template<> + struct literal_type_id<bool> + { + static constexpr int value = 3; + }; + + template<> + struct literal_type_id<unsigned char> + { + static constexpr int value = 4; + }; + + template<> + struct literal_type_id<std::nullptr_t> + { + static constexpr int value = 5; + }; + + template<> + struct literal_type_id<std::string> + { + static constexpr int value = 6; + }; + + template<typename T> + concept literal_type = requires { literal_type_id<T>::value; }; + class variable_declaration; class procedure_declaration; class type_declaration; @@ -95,7 +144,7 @@ namespace elna::boot class dereference_expression; class designator_expression; class literal_expression; - template<typename T> + template<literal_type T> class literal; class defer_statement; class empty_statement; @@ -488,6 +537,16 @@ namespace elna::boot { public: literal_expression *is_literal() override; + + virtual int tag() const = 0; + + template<literal_type T> + literal<T> *is_a() + { + return tag() == literal_type_id<T>::value + ? static_cast<literal<T > *>(this) + : nullptr; + } }; /** @@ -893,14 +952,17 @@ namespace elna::boot ~unit() override; }; - template<typename T> + template<literal_type T> class literal : public literal_expression { public: T value; + const integer_sign wants_signed; + const bool has_explicit_size; - literal(const source_position position, const T& value) - : node(position), value(value) + literal(const source_position position, const T& value, const integer_sign wants_signed, + const bool has_explicit_size = true) + : node(position), value(value), wants_signed(wants_signed), has_explicit_size(has_explicit_size) { } @@ -908,6 +970,11 @@ namespace elna::boot { visitor->visit(this); } + + int tag() const override + { + return literal_type_id<T>::value; + } }; class defer_statement : public statement diff --git a/include/elna/boot/dependency.h b/include/elna/boot/dependency.h index b3c1155..854c661 100644 --- a/include/elna/boot/dependency.h +++ b/include/elna/boot/dependency.h @@ -37,7 +37,7 @@ namespace elna::boot dependency() = default; }; - dependency read_source(std::istream& entry_point); + dependency read_source(std::istream& entry_point, const target_info& target); std::filesystem::path build_path(const std::vector<std::string>& segments); error_list analyze_semantics(std::unique_ptr<unit>& tree, symbol_bag& bag, const target_info& target); @@ -54,8 +54,8 @@ namespace elna::boot using iterator = std::unordered_map<std::filesystem::path, symbol_bag>::iterator; using const_iterator = std::unordered_map<std::filesystem::path, symbol_bag>::const_iterator; - explicit dependency_state(T custom) - : globals(builtin_symbol_table()), custom(custom) + explicit dependency_state(T custom, const target_info& target) + : globals(builtin_symbol_table(target)), custom(custom) { } diff --git a/include/elna/boot/driver.h b/include/elna/boot/driver.h index 60d40fb..2503e39 100644 --- a/include/elna/boot/driver.h +++ b/include/elna/boot/driver.h @@ -54,4 +54,20 @@ namespace elna::boot */ void normalize_newlines(std::string& string); std::optional<std::string> escape_string(const char *escape); + + template<typename T> + std::optional<std::pair<std::make_unsigned_t<T>, integer_sign>> + parse_integer(const char *text, const int base = 10) + { + using unsigned_t = std::make_unsigned_t<T>; + errno = 0; + std::uint64_t result = strtoull(text, nullptr, base); + + if (errno == ERANGE || result > std::numeric_limits<unsigned_t>::max()) + { + return std::nullopt; + } + return std::make_pair(static_cast<unsigned_t>(result), + std::is_signed_v<T> ? integer_sign::unmarked : integer_sign::_unsigned); + } } diff --git a/include/elna/boot/materialization.h b/include/elna/boot/materialization.h new file mode 100644 index 0000000..21a217d --- /dev/null +++ b/include/elna/boot/materialization.h @@ -0,0 +1,41 @@ +/* Literal sign folding. + 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 +<http://www.gnu.org/licenses/>. */ + +#pragma once + +#include "elna/boot/ast.h" + +namespace elna::boot +{ + class materialization_error final : public error + { + public: + materialization_error(const source_position position); + + std::string what() const override; + }; + + class materialization_visitor final : public walking_visitor, public error_container + { + const target_info& target; + + public: + explicit materialization_visitor(const target_info& target); + + void visit(literal<integer_literal> *literal) override; + }; +} diff --git a/include/elna/boot/name_analysis.h b/include/elna/boot/name_analysis.h index f6663ea..9e10fab 100644 --- a/include/elna/boot/name_analysis.h +++ b/include/elna/boot/name_analysis.h @@ -36,7 +36,13 @@ namespace elna::boot class declaration_error final : public error { public: - enum class kind { undeclared_type, undeclared_trait, undeclared_symbol, local_export }; + enum class kind + { + undeclared_type, + undeclared_trait, + undeclared_symbol, + local_export + }; struct redefinition { std::optional<source_position> original; @@ -57,17 +63,27 @@ namespace elna::boot * \c const qualifier used incorrectly — wrong position or * duplicate. */ - class const_qualifier_error final : public error + class name_analysis_error final : public error { public: - enum class kind { array_position, duplicate }; + enum class kind + { + array_position, + duplicate + }; + struct not_initialized + { + std::vector<identifier> identifiers; + }; + using payload_type = std::variant<not_initialized, kind>; - const_qualifier_error(const source_position position, kind error_kind); + name_analysis_error(const source_position position, payload_type payload); + std::optional<std::pair<std::string, source_position>> note() const override; std::string what() const override; private: - kind error_kind; + payload_type payload; }; /** @@ -95,17 +111,6 @@ namespace elna::boot payload_type payload; }; - class not_initialized_error final : public error - { - std::vector<identifier> identifiers; - - public: - not_initialized_error(const source_position position, std::vector<identifier> identifiers); - - std::string what() const override; - std::optional<std::pair<std::string, source_position>> note() const override; - }; - /** * Origin of a field in a composite type. */ @@ -183,9 +188,6 @@ namespace elna::boot public: forward_table unresolved; - explicit declaration_visitor(); - - void visit(import_declaration *) override; void visit(unit *unit) override; void visit(type_declaration *declaration) override; void visit(procedure_declaration *declaration) override; diff --git a/include/elna/boot/result.h b/include/elna/boot/result.h index ee73f33..03d589e 100644 --- a/include/elna/boot/result.h +++ b/include/elna/boot/result.h @@ -273,40 +273,117 @@ namespace elna::boot integer_literal(const integer_literal& that); ~integer_literal(); + /** + * \param that Summand. + * \return Sum of \c this and \p that, or \c nullopt on overflow. + */ std::optional<integer_literal> add(const integer_literal& that) const; + + /** + * \param that Subtrahend. + * \return Difference of \c this and \p that, or \c nullopt on overflow. + */ std::optional<integer_literal> sub(const integer_literal& that) const; + + /** + * \return Product of \c this and \p that, or \c nullopt on overflow. + */ std::optional<integer_literal> mul(const integer_literal& that) const; + + /** + * \param that Factor. + * \return Quotient of \c this and \p that, or \c nullopt if \p that is zero. + */ std::optional<integer_literal> div(const integer_literal& that) const; + + /** + * \param that Divisor. + * \return Remainder of \c this divided by \p that, or \c nullopt if \p that is zero. + */ std::optional<integer_literal> mod(const integer_literal& that) const; + + /** + * \return Arithmetic negation, or \c nullopt if unsigned or overflowing. + */ std::optional<integer_literal> neg() const; + + /** + * \return Signed negation of an unsigned magnitude, or \c nullopt on overflow. + */ + std::optional<integer_literal> negate() const; + + /** + * \param that Bit count. + * \return \c this shifted left by \p that bits, wrapping within the current size. + */ std::optional<integer_literal> shl(const integer_literal& that) const; + + /** + * \param that Bit count. + * \return \c this shifted right by \p that bits, or \c nullopt if the shift is out of range. + */ 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; - std::weak_ordering 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)) { 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)) { 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)) { 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)) @@ -317,10 +394,21 @@ namespace elna::boot integer_literal& operator=(integer_literal&& that) noexcept; integer_literal& operator=(const integer_literal& that); + /// \return Whether the literal is signed. bool is_signed() const; + + /// \return Storage size in bytes. std::size_t size() const; + + /** + * \param base Base. + * \return String representation in the given \p base. + */ std::string to_string(const std::uint8_t base = 10U) const; + /// \return Whether the stored value is negative. + bool is_negative() const; + /** * Exports the stored value as a host \p T. * @@ -359,10 +447,29 @@ namespace elna::boot * returned. * * \param target_size Size of the target type in bytes. + * \param target_signed Signedness of the target type. * \return Whether the value fits and has been changed. */ + bool fit_into(bool target_signed, const std::size_t target_size); + + /// \overload bool fit_into(const std::size_t target_size); + /** + * Cast the value to the given size and signedness, discarding bits if + * needed and reinterpreting bits on sign change. + * + * \param target_signed Result signedness. + * \param target_size Result size. + * \return Cast result. + */ + integer_literal cast_to(bool target_signed, std::size_t target_size) const; + + /** + * \tparam T Initializer type. + * \param initial Initial literal value. + * \return A literal constructed from the host value \p initial. + */ template<typename T> static integer_literal from(T initial) requires is_integral<T> @@ -374,6 +481,29 @@ namespace elna::boot return result; } + /** + * Constructs a literal of \p size bytes from \p initial inheriting its + * signedness. + * + * \tparam T Initializer type. + * \return A literal of \p size bytes constructed from \p initial, or \c nullopt on overflow. + */ + template<typename T> + static std::optional<integer_literal> from(std::size_t size, T initial) + requires is_integral<T> + { + integer_literal result{ std::is_signed_v<T>, size }; + + mpz_import(result.raw, 1, 1, sizeof(T), 0, 0, &initial); + + return std::move(result).check(); + } + + /** + * \param lhs Left hand side. + * \param lhs Right hand side. + * Swaps the contents of \p lhs and \p rhs. + */ friend void swap(integer_literal& lhs, integer_literal& rhs) noexcept; private: @@ -382,11 +512,9 @@ namespace elna::boot mpz_t raw; integer_literal(bool is_signed, std::size_t size); - bool fits_in(const std::size_t bits) const; + bool fits_in(bool starget_signed, const std::size_t bits) const; std::optional<integer_literal> check() &&; - bool is_negative() const; bool is_negative_minimum(const std::size_t bits) const; - bool is_negative_minimum() const; std::size_t bits() const; template<typename T> @@ -474,9 +602,7 @@ namespace elna::boot } } - /** - * \overload - */ + /// \overload const_iterator find(const key_type& key) const { auto search_result = this->index_map.find(key); @@ -509,9 +635,7 @@ namespace elna::boot return { this->payload.begin() + insert_result.first->second, insert_result.second }; } - /** - * \overload - */ + /// \overload std::pair<iterator, bool> insert(const key_type& key, mapped_type&& value) { auto insert_result = this->index_map.emplace(key, this->payload.size()); @@ -532,9 +656,7 @@ namespace elna::boot return this->payload.begin(); } - /** - * \overload - */ + /// \overload const_iterator begin() const { return this->payload.cbegin(); @@ -550,9 +672,7 @@ namespace elna::boot return this->payload.end(); } - /** - * \overload - */ + /// \overload const_iterator end() const { return this->payload.cend(); @@ -577,9 +697,7 @@ namespace elna::boot return this->payload[this->index_map.find(key)->second].second; } - /** - * \overload - */ + /// \overload const mapped_type& operator[](const key_type& key) const { return this->payload[this->index_map.find(key)->second].second; @@ -605,13 +723,15 @@ namespace elna::boot std::size_t alignment; }; + constexpr std::size_t target_integer_count = 5; + /** * Target machine information, populated by the compiler backend glue layer. */ struct target_info { - type_properties int_properties; - type_properties word_properties; + std::array<type_properties, target_integer_count> int_properties; + std::array<type_properties, target_integer_count> word_properties; type_properties pointer_properties; type_properties char_properties; type_properties float_properties; diff --git a/include/elna/boot/symbol.h b/include/elna/boot/symbol.h index bd07a7e..f048016 100644 --- a/include/elna/boot/symbol.h +++ b/include/elna/boot/symbol.h @@ -123,7 +123,7 @@ namespace elna::boot const std::string name; type referent; - explicit alias_type(const std::string& name); + explicit alias_type(const std::string& name, type referent = type()); }; struct pointer_type @@ -158,8 +158,9 @@ namespace elna::boot struct primitive_type { const std::string identifier; + const type_properties properties; - explicit primitive_type(const std::string& identifier); + primitive_type(const std::string& identifier, const type_properties& properties); }; struct record_type @@ -243,9 +244,7 @@ namespace elna::boot return this->entries.begin(); } - /** - * \overload - */ + /// \overload const_iterator begin() const { return this->entries.cbegin(); @@ -261,9 +260,7 @@ namespace elna::boot return this->entries.end(); } - /** - * \overload - */ + /// \overload const_iterator end() const { return this->entries.cend(); @@ -405,7 +402,7 @@ namespace elna::boot std::shared_ptr<variable_info> is_variable() override; }; - std::shared_ptr<symbol_table> builtin_symbol_table(); + std::shared_ptr<symbol_table> builtin_symbol_table(const target_info& target); /** * Symbol bag contains: @@ -520,9 +517,7 @@ namespace elna::boot */ type resolve_underlying_type(const type& alias); - /** - * \overload - */ + /// \overload type resolve_underlying_type(const std::shared_ptr<alias_type>& alias); /** diff --git a/include/elna/boot/validation.h b/include/elna/boot/validation.h index 4a256fa..171ac1d 100644 --- a/include/elna/boot/validation.h +++ b/include/elna/boot/validation.h @@ -49,7 +49,7 @@ namespace elna::boot * Validates: * - case label uniqueness */ - class validation_visitor final : public walking_visitor, public error_container + class validation_visitor final : public empty_visitor, public error_container { symbol_bag& bag; evaluator constant_evaluator; @@ -60,5 +60,14 @@ namespace elna::boot void visit(unit *unit) override; void visit(procedure_declaration *declaration) override; void visit(case_statement *statement) override; + + void visit(assign_statement *) override; + void visit(if_statement *) override; + void visit(while_statement *) override; + void visit(repeat_statement *) override; + void visit(for_statement *) override; + void visit(defer_statement *) override; + void visit(empty_statement *) override; + void visit(procedure_call *) override; }; } diff --git a/include/elna/gcc/elna-builtins.h b/include/elna/gcc/elna-builtins.h index 846a8db..9f24b00 100644 --- a/include/elna/gcc/elna-builtins.h +++ b/include/elna/gcc/elna-builtins.h @@ -27,6 +27,8 @@ along with GCC; see the file COPYING3. If not see namespace elna::gcc { + const elna::boot::target_info& get_host_target(); + void init_ttree(); std::shared_ptr<symbol_table> builtin_symbol_table(); diff --git a/include/elna/gcc/elna-tree.h b/include/elna/gcc/elna-tree.h index 8f7e306..504b14c 100644 --- a/include/elna/gcc/elna-tree.h +++ b/include/elna/gcc/elna-tree.h @@ -60,8 +60,6 @@ namespace elna::gcc tree build_slice(tree slice_type, tree ptr, tree length); tree build_enumeration_type(const std::vector<std::string>& members); - const elna::boot::target_info& get_host_target(); - 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); diff --git a/testsuite/fail_compilation/negative_integer_literal_overflow.elna b/testsuite/fail_compilation/negative_integer_literal_overflow.elna new file mode 100644 index 0000000..7db4888 --- /dev/null +++ b/testsuite/fail_compilation/negative_integer_literal_overflow.elna @@ -0,0 +1,4 @@ +var + a: Int8 := -200i8 (* @Error Integer literal overflows *) + +end. diff --git a/testsuite/runnable/fixed_int_max.elna b/testsuite/runnable/fixed_int_max.elna new file mode 100644 index 0000000..0564dd6 --- /dev/null +++ b/testsuite/runnable/fixed_int_max.elna @@ -0,0 +1,6 @@ +begin + assert(#max(Int8) = 127i8); + assert(#max(Int16) = 32767i16); + assert(#max(Int32) = 2147483647i32); + assert(#max(Int64) = 9223372036854775807i64) +end. diff --git a/testsuite/runnable/fixed_int_min.elna b/testsuite/runnable/fixed_int_min.elna new file mode 100644 index 0000000..de83d1a --- /dev/null +++ b/testsuite/runnable/fixed_int_min.elna @@ -0,0 +1,6 @@ +begin + assert(#min(Int8) = -128i8); + assert(#min(Int16) = -32768i16); + assert(#min(Int32) = -2147483648i32); + assert(#min(Int64) = -9223372036854775808i64) +end. diff --git a/testsuite/runnable/fixed_word_max.elna b/testsuite/runnable/fixed_word_max.elna new file mode 100644 index 0000000..76026a2 --- /dev/null +++ b/testsuite/runnable/fixed_word_max.elna @@ -0,0 +1,6 @@ +begin + assert(#max(Word8) = 255u8); + assert(#max(Word16) = 65535u16); + assert(#max(Word32) = 4294967295u32); + assert(#max(Word64) = 18446744073709551615u64) +end. |
