From: Arthur Cohen <arthur.co...@embecosm.com> gcc/rust/ChangeLog:
* Make-lang.in: Compile the offset_of handler. * lang.opt: Add -frust-assume-builtin-offset-of option. * ast/rust-ast.h: Add has_str() for const_TokenPtr. * expand/rust-macro-builtins.cc: Map offset_of as builtin. * expand/rust-macro-builtins.h: Declare it. * expand/rust-macro-expand.cc (MacroExpander::expand_invoc): Add hack for calling builtin offset_of!(). * resolve/rust-early-name-resolver-2.0.cc (Early::visit): Likewise. * expand/rust-macro-builtins-offset-of.cc: New file. gcc/testsuite/ChangeLog: * rust/compile/offset_of1.rs: New test. --- gcc/rust/Make-lang.in | 1 + gcc/rust/ast/rust-ast.h | 1 + .../expand/rust-macro-builtins-offset-of.cc | 78 +++++++++++++++++++ gcc/rust/expand/rust-macro-builtins.cc | 3 + gcc/rust/expand/rust-macro-builtins.h | 4 + gcc/rust/expand/rust-macro-expand.cc | 21 +++++ gcc/rust/lang.opt | 4 + .../resolve/rust-early-name-resolver-2.0.cc | 15 +++- gcc/testsuite/rust/compile/offset_of1.rs | 11 +++ 9 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 gcc/rust/expand/rust-macro-builtins-offset-of.cc create mode 100644 gcc/testsuite/rust/compile/offset_of1.rs diff --git a/gcc/rust/Make-lang.in b/gcc/rust/Make-lang.in index 12c9a427143..98590d20b8c 100644 --- a/gcc/rust/Make-lang.in +++ b/gcc/rust/Make-lang.in @@ -115,6 +115,7 @@ GRS_OBJS = \ rust/rust-macro-builtins-log-debug.o \ rust/rust-macro-builtins-test-bench.o \ rust/rust-macro-builtins-format-args.o \ + rust/rust-macro-builtins-offset-of.o \ rust/rust-macro-builtins-location.o \ rust/rust-macro-builtins-include.o \ rust/rust-token-tree-desugar.o \ diff --git a/gcc/rust/ast/rust-ast.h b/gcc/rust/ast/rust-ast.h index 93d4ff132e5..a367c5dd0c3 100644 --- a/gcc/rust/ast/rust-ast.h +++ b/gcc/rust/ast/rust-ast.h @@ -283,6 +283,7 @@ public: std::vector<std::unique_ptr<Token>> to_token_stream () const override; TokenId get_id () const { return tok_ref->get_id (); } + bool has_str () const { return tok_ref->has_str (); } const std::string &get_str () const { return tok_ref->get_str (); } location_t get_locus () const { return tok_ref->get_locus (); } diff --git a/gcc/rust/expand/rust-macro-builtins-offset-of.cc b/gcc/rust/expand/rust-macro-builtins-offset-of.cc new file mode 100644 index 00000000000..53efe74b228 --- /dev/null +++ b/gcc/rust/expand/rust-macro-builtins-offset-of.cc @@ -0,0 +1,78 @@ +// Copyright (C) 2020-2025 Free Software Foundation, Inc. + +// This file is part of GCC. + +// 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 "optional.h" +#include "rust-ast-fragment.h" +#include "rust-ast.h" +#include "rust-builtin-ast-nodes.h" +#include "rust-diagnostics.h" +#include "rust-macro-builtins-helpers.h" +#include "rust-macro-builtins.h" +#include "rust-macro-invoc-lexer.h" +#include "rust-parse.h" + +namespace Rust { + +tl::optional<AST::Fragment> +MacroBuiltin::offset_of_handler (location_t invoc_locus, + AST::MacroInvocData &invoc, + AST::InvocKind semicolon) +{ + MacroInvocLexer lex (invoc.get_delim_tok_tree ().to_token_stream ()); + Parser<MacroInvocLexer> parser (lex); + + auto last_token = macro_end_token (invoc.get_delim_tok_tree (), parser); + + auto type = parser.parse_type (); + + // if we don't see a type, there might be an eager macro expansion missing + // FIXME: handle that + if (!type) + { + rust_error_at (invoc_locus, "could not parse type argument for %qs", + "offset_of"); + + // we skip so we can still parse the field arg and check if it is correct + while (parser.peek_current_token ()->get_id () != COMMA + && parser.peek_current_token ()->get_id () != last_token) + parser.skip_token (); + } + + parser.skip_token (COMMA); + + auto field_tok = parser.parse_identifier_or_keyword_token (); + auto invalid_field = !field_tok || !field_tok->has_str (); + + if (invalid_field) + rust_error_at (invoc_locus, "could not parse field argument for %qs", + "offset_of"); + + if (!type || invalid_field) + return tl::nullopt; + + auto field = Identifier (field_tok->get_str ()); + + // FIXME: Do we need to do anything to handle the optional comma at the end? + parser.maybe_skip_token (COMMA); + + return AST::Fragment ({AST::SingleASTNode (std::make_unique<AST::OffsetOf> ( + std::move (type), field, invoc_locus))}, + invoc.get_delim_tok_tree ().to_token_stream ()); +} + +} // namespace Rust diff --git a/gcc/rust/expand/rust-macro-builtins.cc b/gcc/rust/expand/rust-macro-builtins.cc index b58ed71ba4e..a7ae2207985 100644 --- a/gcc/rust/expand/rust-macro-builtins.cc +++ b/gcc/rust/expand/rust-macro-builtins.cc @@ -162,6 +162,9 @@ std::unordered_map<std::string, AST::MacroTranscriberFunc> {"Ord", MacroBuiltin::proc_macro_builtin}, {"PartialOrd", MacroBuiltin::proc_macro_builtin}, {"Hash", MacroBuiltin::proc_macro_builtin}, + /* offset_of is not declared in Rust 1.49 but still needed for + Rust-for-Linux, so we still create a transcriber and warn the user */ + {"offset_of", MacroBuiltin::offset_of_handler}, }; tl::optional<BuiltinMacro> diff --git a/gcc/rust/expand/rust-macro-builtins.h b/gcc/rust/expand/rust-macro-builtins.h index 541e95636b1..b6c29074ad9 100644 --- a/gcc/rust/expand/rust-macro-builtins.h +++ b/gcc/rust/expand/rust-macro-builtins.h @@ -19,6 +19,7 @@ #ifndef RUST_MACRO_BUILTINS_H #define RUST_MACRO_BUILTINS_H +#include "optional.h" #include "rust-ast.h" #include "rust-builtin-ast-nodes.h" #include "rust-ast-fragment.h" @@ -188,6 +189,9 @@ public: format_args_handler (location_t invoc_locus, AST::MacroInvocData &invoc, AST::InvocKind semicolon, AST::FormatArgs::Newline nl); + static tl::optional<AST::Fragment> + offset_of_handler (location_t, AST::MacroInvocData &, AST::InvocKind); + static tl::optional<AST::Fragment> sorry (location_t invoc_locus, AST::MacroInvocData &invoc, AST::InvocKind semicolon); diff --git a/gcc/rust/expand/rust-macro-expand.cc b/gcc/rust/expand/rust-macro-expand.cc index 475ad56a364..4c54ceff98a 100644 --- a/gcc/rust/expand/rust-macro-expand.cc +++ b/gcc/rust/expand/rust-macro-expand.cc @@ -19,6 +19,7 @@ #include "rust-macro-expand.h" #include "optional.h" #include "rust-ast-fragment.h" +#include "rust-macro-builtins.h" #include "rust-macro-substitute-ctx.h" #include "rust-ast-full.h" #include "rust-ast-visitor.h" @@ -287,6 +288,26 @@ MacroExpander::expand_invoc (AST::MacroInvocation &invoc, // lookup the rules auto rules_def = mappings.lookup_macro_invocation (invoc); + // We special case the `offset_of!()` macro if the flag is here and manually + // resolve to the builtin transcriber we have specified + auto assume_builtin_offset_of + = flag_assume_builtin_offset_of + && (invoc.get_invoc_data ().get_path ().as_string () == "offset_of") + && !rules_def; + + // TODO: This is *massive hack* which should be removed as we progress to + // Rust 1.71 when offset_of gets added to core + if (assume_builtin_offset_of) + { + fragment = MacroBuiltin::offset_of_handler (invoc.get_locus (), + invoc_data, semicolon) + .value_or (AST::Fragment::create_empty ()); + + set_expanded_fragment (std::move (fragment)); + + return; + } + // If there's no rule associated with the invocation, we can simply return // early. The early name resolver will have already emitted an error. if (!rules_def) diff --git a/gcc/rust/lang.opt b/gcc/rust/lang.opt index 4c48816affa..d9824f1a5ac 100644 --- a/gcc/rust/lang.opt +++ b/gcc/rust/lang.opt @@ -229,4 +229,8 @@ frust-overflow-checks Rust Var(flag_overflow_checks) Init(1) Enable the overflow checks in code generation +frust-assume-builtin-offset-of +Rust Var(flag_assume_builtin_offset_of) +Define a built-in offset_of macro in the compiler and assume it is present + ; This comment is to ensure we retain the blank line above. diff --git a/gcc/rust/resolve/rust-early-name-resolver-2.0.cc b/gcc/rust/resolve/rust-early-name-resolver-2.0.cc index bb93d95fe8e..4fd1dd265b6 100644 --- a/gcc/rust/resolve/rust-early-name-resolver-2.0.cc +++ b/gcc/rust/resolve/rust-early-name-resolver-2.0.cc @@ -256,6 +256,11 @@ Early::visit (AST::MacroInvocation &invoc) { auto &path = invoc.get_invoc_data ().get_path (); + // We special case the `offset_of!()` macro if the flag is here, otherwise + // we accept whatever `offset_of!()` definition we resolved to. + auto resolve_offset_of + = flag_assume_builtin_offset_of && (path.as_string () == "offset_of"); + if (invoc.get_kind () == AST::MacroInvocation::InvocKind::Builtin) for (auto &pending_invoc : invoc.get_pending_eager_invocations ()) pending_invoc->accept_vis (*this); @@ -279,12 +284,14 @@ Early::visit (AST::MacroInvocation &invoc) if (!definition.has_value ()) definition = ctx.resolve_path (path, Namespace::Macros); - // if the definition still does not have a value, then it's an error + // if the definition still does not have a value, then it's an error - unless + // we should automatically resolve offset_of!() calls if (!definition.has_value ()) { - collect_error (Error (invoc.get_locus (), ErrorCode::E0433, - "could not resolve macro invocation %qs", - path.as_string ().c_str ())); + if (!resolve_offset_of) + collect_error (Error (invoc.get_locus (), ErrorCode::E0433, + "could not resolve macro invocation %qs", + path.as_string ().c_str ())); return; } diff --git a/gcc/testsuite/rust/compile/offset_of1.rs b/gcc/testsuite/rust/compile/offset_of1.rs new file mode 100644 index 00000000000..5b796991039 --- /dev/null +++ b/gcc/testsuite/rust/compile/offset_of1.rs @@ -0,0 +1,11 @@ +// { dg-additional-options "-frust-compile-until=lowering -frust-assume-builtin-offset-of" } + +pub struct Foo { + a: i32, +} + +fn main() { + let _ = offset_of!(Foo, a); // valid + let _ = offset_of!("bloop", a); // { dg-error "could not parse type" } + let _ = offset_of!(Foo, 15); // { dg-error "could not parse field" } +} -- 2.49.0