about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_ast/src/ast.rs4
-rw-r--r--compiler/rustc_ast/src/mut_visit.rs6
-rw-r--r--compiler/rustc_ast/src/util/parser.rs4
-rw-r--r--compiler/rustc_ast/src/visit.rs6
-rw-r--r--compiler/rustc_ast_lowering/src/expr.rs7
-rw-r--r--compiler/rustc_ast_lowering/src/lib.rs2
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state/expr.rs21
-rw-r--r--compiler/rustc_borrowck/src/type_check/mod.rs3
-rw-r--r--compiler/rustc_builtin_macros/messages.ftl3
-rw-r--r--compiler/rustc_builtin_macros/src/assert/context.rs1
-rw-r--r--compiler/rustc_builtin_macros/src/lib.rs2
-rw-r--r--compiler/rustc_builtin_macros/src/offset_of.rs99
-rw-r--r--compiler/rustc_codegen_cranelift/src/base.rs5
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/rvalue.rs5
-rw-r--r--compiler/rustc_const_eval/src/interpret/step.rs7
-rw-r--r--compiler/rustc_const_eval/src/transform/check_consts/check.rs2
-rw-r--r--compiler/rustc_const_eval/src/transform/promote_consts.rs1
-rw-r--r--compiler/rustc_const_eval/src/transform/validate.rs53
-rw-r--r--compiler/rustc_hir/src/hir.rs7
-rw-r--r--compiler/rustc_hir/src/intravisit.rs4
-rw-r--r--compiler/rustc_hir_pretty/src/lib.rs18
-rw-r--r--compiler/rustc_hir_typeck/src/expr.rs105
-rw-r--r--compiler/rustc_hir_typeck/src/expr_use_visitor.rs1
-rw-r--r--compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs2
-rw-r--r--compiler/rustc_hir_typeck/src/mem_categorization.rs1
-rw-r--r--compiler/rustc_hir_typeck/src/writeback.rs25
-rw-r--r--compiler/rustc_middle/src/mir/syntax.rs6
-rw-r--r--compiler/rustc_middle/src/mir/tcx.rs4
-rw-r--r--compiler/rustc_middle/src/thir.rs5
-rw-r--r--compiler/rustc_middle/src/thir/visit.rs1
-rw-r--r--compiler/rustc_middle/src/ty/typeck_results.rs12
-rw-r--r--compiler/rustc_mir_build/src/build/expr/as_place.rs1
-rw-r--r--compiler/rustc_mir_build/src/build/expr/as_rvalue.rs4
-rw-r--r--compiler/rustc_mir_build/src/build/expr/category.rs7
-rw-r--r--compiler/rustc_mir_build/src/build/expr/into.rs3
-rw-r--r--compiler/rustc_mir_build/src/check_unsafety.rs1
-rw-r--r--compiler/rustc_mir_build/src/thir/cx/expr.rs8
-rw-r--r--compiler/rustc_mir_build/src/thir/print.rs13
-rw-r--r--compiler/rustc_mir_dataflow/src/move_paths/builder.rs2
-rw-r--r--compiler/rustc_mir_transform/src/separate_const_switch.rs3
-rw-r--r--compiler/rustc_passes/src/dead.rs36
-rw-r--r--compiler/rustc_passes/src/hir_stats.rs5
-rw-r--r--compiler/rustc_passes/src/liveness.rs5
-rw-r--r--compiler/rustc_passes/src/naked_functions.rs1
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_target/src/abi/mod.rs15
-rw-r--r--compiler/rustc_ty_utils/src/consts.rs2
-rw-r--r--library/core/src/mem/mod.rs42
-rw-r--r--library/core/tests/lib.rs1
-rw-r--r--library/core/tests/mem.rs74
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/never_loop.rs3
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/author.rs4
-rw-r--r--src/tools/clippy/clippy_utils/src/eager_or_lazy.rs3
-rw-r--r--src/tools/clippy/clippy_utils/src/hir_utils.rs9
-rw-r--r--src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/sugg.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/visitors.rs1
-rw-r--r--src/tools/rustfmt/src/expr.rs1
-rw-r--r--src/tools/rustfmt/src/utils.rs1
-rw-r--r--tests/mir-opt/const_prop/offset_of.main.ConstProp.diff43
-rw-r--r--tests/mir-opt/const_prop/offset_of.rs25
-rw-r--r--tests/ui/liveness/liveness-offset-of.rs26
-rw-r--r--tests/ui/liveness/liveness-offset-of.stderr33
-rw-r--r--tests/ui/macros/user-defined-macro-rules.rs (renamed from tests/ui/user-defined-macro-rules.rs)0
-rw-r--r--tests/ui/offset-of/offset-of-arg-count.rs9
-rw-r--r--tests/ui/offset-of/offset-of-arg-count.stderr20
-rw-r--r--tests/ui/offset-of/offset-of-dst-field.rs14
-rw-r--r--tests/ui/offset-of/offset-of-dst-field.stderr11
-rw-r--r--tests/ui/offset-of/offset-of-private.rs16
-rw-r--r--tests/ui/offset-of/offset-of-private.stderr9
71 files changed, 841 insertions, 38 deletions
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index ab0409efb3b..c0fec0cf565 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -1271,6 +1271,7 @@ impl Expr {
             ExprKind::Continue(..) => ExprPrecedence::Continue,
             ExprKind::Ret(..) => ExprPrecedence::Ret,
             ExprKind::InlineAsm(..) => ExprPrecedence::InlineAsm,
+            ExprKind::OffsetOf(..) => ExprPrecedence::OffsetOf,
             ExprKind::MacCall(..) => ExprPrecedence::Mac,
             ExprKind::Struct(..) => ExprPrecedence::Struct,
             ExprKind::Repeat(..) => ExprPrecedence::Repeat,
@@ -1469,6 +1470,9 @@ pub enum ExprKind {
     /// Output of the `asm!()` macro.
     InlineAsm(P<InlineAsm>),
 
+    /// Output of the `offset_of!()` macro.
+    OffsetOf(P<Ty>, Vec<Ident>),
+
     /// A macro invocation; pre-expansion.
     MacCall(P<MacCall>),
 
diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs
index 694d31d8f1f..7603bebb178 100644
--- a/compiler/rustc_ast/src/mut_visit.rs
+++ b/compiler/rustc_ast/src/mut_visit.rs
@@ -1456,6 +1456,12 @@ pub fn noop_visit_expr<T: MutVisitor>(
         }
         ExprKind::InlineAsm(asm) => vis.visit_inline_asm(asm),
         ExprKind::FormatArgs(fmt) => vis.visit_format_args(fmt),
+        ExprKind::OffsetOf(container, fields) => {
+            vis.visit_ty(container);
+            for field in fields {
+                vis.visit_ident(field);
+            }
+        }
         ExprKind::MacCall(mac) => vis.visit_mac_call(mac),
         ExprKind::Struct(se) => {
             let StructExpr { qself, path, fields, rest } = se.deref_mut();
diff --git a/compiler/rustc_ast/src/util/parser.rs b/compiler/rustc_ast/src/util/parser.rs
index 3893875e9a4..24b4bd8623f 100644
--- a/compiler/rustc_ast/src/util/parser.rs
+++ b/compiler/rustc_ast/src/util/parser.rs
@@ -269,6 +269,7 @@ pub enum ExprPrecedence {
     Index,
     Try,
     InlineAsm,
+    OffsetOf,
     Mac,
     FormatArgs,
 
@@ -335,7 +336,8 @@ impl ExprPrecedence {
             | ExprPrecedence::Try
             | ExprPrecedence::InlineAsm
             | ExprPrecedence::Mac
-            | ExprPrecedence::FormatArgs => PREC_POSTFIX,
+            | ExprPrecedence::FormatArgs
+            | ExprPrecedence::OffsetOf => PREC_POSTFIX,
 
             // Never need parens
             ExprPrecedence::Array
diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs
index ac9b321b71c..8a6b5d5c905 100644
--- a/compiler/rustc_ast/src/visit.rs
+++ b/compiler/rustc_ast/src/visit.rs
@@ -909,6 +909,12 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) {
         ExprKind::Paren(subexpression) => visitor.visit_expr(subexpression),
         ExprKind::InlineAsm(asm) => visitor.visit_inline_asm(asm),
         ExprKind::FormatArgs(f) => visitor.visit_format_args(f),
+        ExprKind::OffsetOf(container, fields) => {
+            visitor.visit_ty(container);
+            for &field in fields {
+                visitor.visit_ident(field);
+            }
+        }
         ExprKind::Yield(optional_expression) => {
             walk_list!(visitor, visit_expr, optional_expression);
         }
diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs
index 0d212b3e130..6863100d9ba 100644
--- a/compiler/rustc_ast_lowering/src/expr.rs
+++ b/compiler/rustc_ast_lowering/src/expr.rs
@@ -289,6 +289,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
                     hir::ExprKind::InlineAsm(self.lower_inline_asm(e.span, asm))
                 }
                 ExprKind::FormatArgs(fmt) => self.lower_format_args(e.span, fmt),
+                ExprKind::OffsetOf(container, fields) => hir::ExprKind::OffsetOf(
+                    self.lower_ty(
+                        container,
+                        &mut ImplTraitContext::Disallowed(ImplTraitPosition::OffsetOf),
+                    ),
+                    self.arena.alloc_from_iter(fields.iter().map(|&ident| self.lower_ident(ident))),
+                ),
                 ExprKind::Struct(se) => {
                     let rest = match &se.rest {
                         StructRest::Base(e) => Some(self.lower_expr(e)),
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index bfd9956b004..c969d709608 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -283,6 +283,7 @@ enum ImplTraitPosition {
     FieldTy,
     Cast,
     ImplSelf,
+    OffsetOf,
 }
 
 impl std::fmt::Display for ImplTraitPosition {
@@ -313,6 +314,7 @@ impl std::fmt::Display for ImplTraitPosition {
             ImplTraitPosition::FieldTy => "field types",
             ImplTraitPosition::Cast => "cast types",
             ImplTraitPosition::ImplSelf => "impl headers",
+            ImplTraitPosition::OffsetOf => "`offset_of!` params",
         };
 
         write!(f, "{name}")
diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
index 776bf54244e..d31332b3b91 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
@@ -549,6 +549,27 @@ impl<'a> State<'a> {
                 self.end();
                 self.pclose();
             }
+            ast::ExprKind::OffsetOf(container, fields) => {
+                // FIXME: This should have its own syntax, distinct from a macro invocation.
+                self.word("offset_of!");
+                self.popen();
+                self.rbox(0, Inconsistent);
+                self.print_type(container);
+                self.word(",");
+                self.space();
+
+                let (&first, rest) =
+                    fields.split_first().expect("offset_of! should have at least 1 field");
+
+                self.print_ident(first);
+
+                for &field in rest {
+                    self.word(".");
+                    self.print_ident(field);
+                }
+
+                self.end();
+            }
             ast::ExprKind::MacCall(m) => self.print_mac(m),
             ast::ExprKind::Paren(e) => {
                 self.popen();
diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs
index 375eca1b29d..52651aa40c9 100644
--- a/compiler/rustc_borrowck/src/type_check/mod.rs
+++ b/compiler/rustc_borrowck/src/type_check/mod.rs
@@ -2306,7 +2306,8 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
             Rvalue::AddressOf(..)
             | Rvalue::ThreadLocalRef(..)
             | Rvalue::Len(..)
-            | Rvalue::Discriminant(..) => {}
+            | Rvalue::Discriminant(..)
+            | Rvalue::NullaryOp(NullOp::OffsetOf(..), _) => {}
         }
     }
 
diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl
index 83dc1ac50e5..fca6012a408 100644
--- a/compiler/rustc_builtin_macros/messages.ftl
+++ b/compiler/rustc_builtin_macros/messages.ftl
@@ -149,3 +149,6 @@ builtin_macros_format_pos_mismatch = {$n} positional {$n ->
     [one] argument
     *[more] arguments
     } in format string, but {$desc}
+builtin_macros_offset_of_expected_field = expected field
+
+builtin_macros_offset_of_expected_two_args = expected 2 arguments
diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs
index c9e3cd486f8..090e00616fb 100644
--- a/compiler/rustc_builtin_macros/src/assert/context.rs
+++ b/compiler/rustc_builtin_macros/src/assert/context.rs
@@ -301,6 +301,7 @@ impl<'cx, 'a> Context<'cx, 'a> {
             | ExprKind::If(_, _, _)
             | ExprKind::IncludedBytes(..)
             | ExprKind::InlineAsm(_)
+            | ExprKind::OffsetOf(_, _)
             | ExprKind::Let(_, _, _)
             | ExprKind::Lit(_)
             | ExprKind::Loop(_, _, _)
diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs
index 134d64ce9cc..8f86ef44aa3 100644
--- a/compiler/rustc_builtin_macros/src/lib.rs
+++ b/compiler/rustc_builtin_macros/src/lib.rs
@@ -45,6 +45,7 @@ mod format;
 mod format_foreign;
 mod global_allocator;
 mod log_syntax;
+mod offset_of;
 mod source_util;
 mod test;
 mod trace_macros;
@@ -92,6 +93,7 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) {
         line: source_util::expand_line,
         log_syntax: log_syntax::expand_log_syntax,
         module_path: source_util::expand_mod,
+        offset_of: offset_of::expand_offset_of,
         option_env: env::expand_option_env,
         core_panic: edition_panic::expand_panic,
         std_panic: edition_panic::expand_panic,
diff --git a/compiler/rustc_builtin_macros/src/offset_of.rs b/compiler/rustc_builtin_macros/src/offset_of.rs
new file mode 100644
index 00000000000..f5c04015340
--- /dev/null
+++ b/compiler/rustc_builtin_macros/src/offset_of.rs
@@ -0,0 +1,99 @@
+use rustc_ast as ast;
+use rustc_ast::ptr::P;
+use rustc_ast::token;
+use rustc_ast::tokenstream::TokenStream;
+use rustc_errors::PResult;
+use rustc_expand::base::{self, *};
+use rustc_macros::Diagnostic;
+use rustc_parse::parser::Parser;
+use rustc_span::{symbol::Ident, Span};
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_offset_of_expected_field)]
+struct ExpectedField {
+    #[primary_span]
+    span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(builtin_macros_offset_of_expected_two_args)]
+struct ExpectedTwoArgs {
+    #[primary_span]
+    span: Span,
+}
+
+fn parse_field<'a>(cx: &ExtCtxt<'a>, p: &mut Parser<'a>) -> PResult<'a, Ident> {
+    let token = p.token.uninterpolate();
+    let field = match token.kind {
+        token::Ident(name, _) => Ident::new(name, token.span),
+        token::Literal(token::Lit { kind: token::Integer, symbol, suffix: None }) => {
+            Ident::new(symbol, token.span)
+        }
+        _ => return Err(cx.create_err(ExpectedField { span: p.token.span })),
+    };
+
+    p.bump();
+
+    Ok(field)
+}
+
+fn parse_args<'a>(
+    cx: &mut ExtCtxt<'a>,
+    sp: Span,
+    tts: TokenStream,
+) -> PResult<'a, (P<ast::Ty>, Vec<Ident>)> {
+    let mut p = cx.new_parser_from_tts(tts);
+
+    let container = p.parse_ty()?;
+
+    p.expect(&token::Comma)?;
+
+    if p.eat(&token::Eof) {
+        return Err(cx.create_err(ExpectedTwoArgs { span: sp }));
+    }
+
+    let mut fields = Vec::new();
+
+    loop {
+        let field = parse_field(cx, &mut p)?;
+        fields.push(field);
+
+        if p.eat(&token::Dot) {
+            continue;
+        }
+
+        p.eat(&token::Comma);
+
+        if !p.eat(&token::Eof) {
+            return Err(cx.create_err(ExpectedTwoArgs { span: sp }));
+        }
+
+        break;
+    }
+
+    Ok((container, fields))
+}
+
+pub fn expand_offset_of<'cx>(
+    cx: &'cx mut ExtCtxt<'_>,
+    sp: Span,
+    tts: TokenStream,
+) -> Box<dyn base::MacResult + 'cx> {
+    match parse_args(cx, sp, tts) {
+        Ok((container, fields)) => {
+            let expr = P(ast::Expr {
+                id: ast::DUMMY_NODE_ID,
+                kind: ast::ExprKind::OffsetOf(container, fields),
+                span: sp,
+                attrs: ast::AttrVec::new(),
+                tokens: None,
+            });
+
+            MacEager::expr(expr)
+        }
+        Err(mut err) => {
+            err.emit();
+            DummyResult::any(sp)
+        }
+    }
+}
diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs
index f5301f9f7f1..f481290583e 100644
--- a/compiler/rustc_codegen_cranelift/src/base.rs
+++ b/compiler/rustc_codegen_cranelift/src/base.rs
@@ -781,12 +781,15 @@ fn codegen_stmt<'tcx>(
                     let operand = operand.load_scalar(fx);
                     lval.write_cvalue(fx, CValue::by_val(operand, box_layout));
                 }
-                Rvalue::NullaryOp(null_op, ty) => {
+                Rvalue::NullaryOp(ref null_op, ty) => {
                     assert!(lval.layout().ty.is_sized(fx.tcx, ParamEnv::reveal_all()));
                     let layout = fx.layout_of(fx.monomorphize(ty));
                     let val = match null_op {
                         NullOp::SizeOf => layout.size.bytes(),
                         NullOp::AlignOf => layout.align.abi.bytes(),
+                        NullOp::OffsetOf(fields) => {
+                            layout.offset_of_subfield(fx, fields.iter().map(|f| f.index())).bytes()
+                        }
                     };
                     let val = CValue::const_val(fx, fx.layout_of(fx.tcx.types.usize), val.into());
                     lval.write_cvalue(fx, val);
diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
index bd11d47500a..94de19a9c29 100644
--- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs
@@ -666,13 +666,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                 }
             }
 
-            mir::Rvalue::NullaryOp(null_op, ty) => {
+            mir::Rvalue::NullaryOp(ref null_op, ty) => {
                 let ty = self.monomorphize(ty);
                 assert!(bx.cx().type_is_sized(ty));
                 let layout = bx.cx().layout_of(ty);
                 let val = match null_op {
                     mir::NullOp::SizeOf => layout.size.bytes(),
                     mir::NullOp::AlignOf => layout.align.abi.bytes(),
+                    mir::NullOp::OffsetOf(fields) => {
+                        layout.offset_of_subfield(bx.cx(), fields.iter().map(|f| f.index())).bytes()
+                    }
                 };
                 let val = bx.cx().const_usize(val);
                 let tcx = self.cx.tcx();
diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs
index 9a366364e76..4ed83f1db0e 100644
--- a/compiler/rustc_const_eval/src/interpret/step.rs
+++ b/compiler/rustc_const_eval/src/interpret/step.rs
@@ -280,10 +280,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 self.write_immediate(*val, &dest)?;
             }
 
-            NullaryOp(null_op, ty) => {
+            NullaryOp(ref null_op, ty) => {
                 let ty = self.subst_from_current_frame_and_normalize_erasing_regions(ty)?;
                 let layout = self.layout_of(ty)?;
-                if layout.is_unsized() {
+                if let mir::NullOp::SizeOf | mir::NullOp::AlignOf = null_op && layout.is_unsized() {
                     // FIXME: This should be a span_bug (#80742)
                     self.tcx.sess.delay_span_bug(
                         self.frame().current_span(),
@@ -294,6 +294,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
                 let val = match null_op {
                     mir::NullOp::SizeOf => layout.size.bytes(),
                     mir::NullOp::AlignOf => layout.align.abi.bytes(),
+                    mir::NullOp::OffsetOf(fields) => {
+                        layout.offset_of_subfield(self, fields.iter().map(|f| f.index())).bytes()
+                    }
                 };
                 self.write_scalar(Scalar::from_target_usize(val, self), &dest)?;
             }
diff --git a/compiler/rustc_const_eval/src/transform/check_consts/check.rs b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
index 9dad9479053..9d138f176f5 100644
--- a/compiler/rustc_const_eval/src/transform/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/transform/check_consts/check.rs
@@ -558,7 +558,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
 
             Rvalue::Cast(_, _, _) => {}
 
-            Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) => {}
+            Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_), _) => {}
             Rvalue::ShallowInitBox(_, _) => {}
 
             Rvalue::UnaryOp(_, operand) => {
diff --git a/compiler/rustc_const_eval/src/transform/promote_consts.rs b/compiler/rustc_const_eval/src/transform/promote_consts.rs
index 7919aed097a..2ed84bddfa3 100644
--- a/compiler/rustc_const_eval/src/transform/promote_consts.rs
+++ b/compiler/rustc_const_eval/src/transform/promote_consts.rs
@@ -514,6 +514,7 @@ impl<'tcx> Validator<'_, 'tcx> {
             Rvalue::NullaryOp(op, _) => match op {
                 NullOp::SizeOf => {}
                 NullOp::AlignOf => {}
+                NullOp::OffsetOf(_) => {}
             },
 
             Rvalue::ShallowInitBox(_, _) => return Err(Unpromotable),
diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs
index 8aee019e994..119fe9801e4 100644
--- a/compiler/rustc_const_eval/src/transform/validate.rs
+++ b/compiler/rustc_const_eval/src/transform/validate.rs
@@ -8,9 +8,10 @@ use rustc_middle::mir::interpret::Scalar;
 use rustc_middle::mir::visit::{NonUseContext, PlaceContext, Visitor};
 use rustc_middle::mir::{
     traversal, BasicBlock, BinOp, Body, BorrowKind, CastKind, CopyNonOverlapping, Local, Location,
-    MirPass, MirPhase, NonDivergingIntrinsic, Operand, Place, PlaceElem, PlaceRef, ProjectionElem,
-    RetagKind, RuntimePhase, Rvalue, SourceScope, Statement, StatementKind, Terminator,
-    TerminatorKind, UnOp, UnwindAction, VarDebugInfo, VarDebugInfoContents, START_BLOCK,
+    MirPass, MirPhase, NonDivergingIntrinsic, NullOp, Operand, Place, PlaceElem, PlaceRef,
+    ProjectionElem, RetagKind, RuntimePhase, Rvalue, SourceScope, Statement, StatementKind,
+    Terminator, TerminatorKind, UnOp, UnwindAction, VarDebugInfo, VarDebugInfoContents,
+    START_BLOCK,
 };
 use rustc_middle::ty::{self, InstanceDef, ParamEnv, Ty, TyCtxt, TypeVisitableExt};
 use rustc_mir_dataflow::impls::MaybeStorageLive;
@@ -711,10 +712,54 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
                     }
                 }
             }
+            Rvalue::NullaryOp(NullOp::OffsetOf(fields), container) => {
+                let fail_out_of_bounds = |this: &Self, location, field, ty| {
+                    this.fail(location, format!("Out of bounds field {field:?} for {ty:?}"));
+                };
+
+                let mut current_ty = *container;
+
+                for &field in fields {
+                    match current_ty.kind() {
+                        ty::Tuple(fields) => {
+                            let Some(&f_ty) = fields.get(field.as_usize()) else {
+                                fail_out_of_bounds(self, location, field, current_ty);
+                                return;
+                            };
+
+                            current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty);
+                        }
+                        ty::Adt(adt_def, substs) => {
+                            if adt_def.is_enum() {
+                                self.fail(
+                                    location,
+                                    format!("Cannot get field offset from enum {current_ty:?}"),
+                                );
+                                return;
+                            }
+
+                            let Some(field) = adt_def.non_enum_variant().fields.get(field) else {
+                                fail_out_of_bounds(self, location, field, current_ty);
+                                return;
+                            };
+
+                            let f_ty = field.ty(self.tcx, substs);
+                            current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty);
+                        }
+                        _ => {
+                            self.fail(
+                                location,
+                                format!("Cannot get field offset from non-adt type {current_ty:?}"),
+                            );
+                            return;
+                        }
+                    }
+                }
+            }
             Rvalue::Repeat(_, _)
             | Rvalue::ThreadLocalRef(_)
             | Rvalue::AddressOf(_, _)
-            | Rvalue::NullaryOp(_, _)
+            | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _)
             | Rvalue::Discriminant(_) => {}
         }
         self.super_rvalue(rvalue, location);
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index 54b328e78f8..52ed9660256 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -1715,6 +1715,7 @@ impl Expr<'_> {
             ExprKind::Continue(..) => ExprPrecedence::Continue,
             ExprKind::Ret(..) => ExprPrecedence::Ret,
             ExprKind::InlineAsm(..) => ExprPrecedence::InlineAsm,
+            ExprKind::OffsetOf(..) => ExprPrecedence::OffsetOf,
             ExprKind::Struct(..) => ExprPrecedence::Struct,
             ExprKind::Repeat(..) => ExprPrecedence::Repeat,
             ExprKind::Yield(..) => ExprPrecedence::Yield,
@@ -1774,6 +1775,7 @@ impl Expr<'_> {
             | ExprKind::Loop(..)
             | ExprKind::Assign(..)
             | ExprKind::InlineAsm(..)
+            | ExprKind::OffsetOf(..)
             | ExprKind::AssignOp(..)
             | ExprKind::Lit(_)
             | ExprKind::ConstBlock(..)
@@ -1818,7 +1820,7 @@ impl Expr<'_> {
 
     pub fn can_have_side_effects(&self) -> bool {
         match self.peel_drop_temps().kind {
-            ExprKind::Path(_) | ExprKind::Lit(_) => false,
+            ExprKind::Path(_) | ExprKind::Lit(_) | ExprKind::OffsetOf(..) => false,
             ExprKind::Type(base, _)
             | ExprKind::Unary(_, base)
             | ExprKind::Field(base, _)
@@ -2022,6 +2024,9 @@ pub enum ExprKind<'hir> {
     /// Inline assembly (from `asm!`), with its outputs and inputs.
     InlineAsm(&'hir InlineAsm<'hir>),
 
+    /// Field offset (`offset_of!`)
+    OffsetOf(&'hir Ty<'hir>, &'hir [Ident]),
+
     /// A struct or struct-like variant literal expression.
     ///
     /// E.g., `Foo {x: 1, y: 2}`, or `Foo {x: 1, .. base}`,
diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs
index 234256ab553..df0047d82e1 100644
--- a/compiler/rustc_hir/src/intravisit.rs
+++ b/compiler/rustc_hir/src/intravisit.rs
@@ -786,6 +786,10 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>)
         ExprKind::InlineAsm(ref asm) => {
             visitor.visit_inline_asm(asm, expression.hir_id);
         }
+        ExprKind::OffsetOf(ref container, ref fields) => {
+            visitor.visit_ty(container);
+            walk_list!(visitor, visit_ident, fields.iter().copied());
+        }
         ExprKind::Yield(ref subexpression, _) => {
             visitor.visit_expr(subexpression);
         }
diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs
index 74f5b359021..1e05003c19c 100644
--- a/compiler/rustc_hir_pretty/src/lib.rs
+++ b/compiler/rustc_hir_pretty/src/lib.rs
@@ -1551,6 +1551,24 @@ impl<'a> State<'a> {
                 self.word("asm!");
                 self.print_inline_asm(asm);
             }
+            hir::ExprKind::OffsetOf(container, ref fields) => {
+                self.word("offset_of!(");
+                self.print_type(container);
+                self.word(",");
+                self.space();
+
+                let (&first, rest) =
+                    fields.split_first().expect("offset_of! should have at least 1 field");
+
+                self.print_ident(first);
+
+                for &field in rest {
+                    self.word(".");
+                    self.print_ident(field);
+                }
+
+                self.word(")");
+            }
             hir::ExprKind::Yield(expr, _) => {
                 self.word_space("yield");
                 self.print_expr_maybe_paren(expr, parser::PREC_JUMP);
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 0c0a7515d9c..653f2f2886f 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -309,6 +309,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 self.deferred_asm_checks.borrow_mut().push((asm, expr.hir_id));
                 self.check_expr_asm(asm)
             }
+            ExprKind::OffsetOf(container, ref fields) => {
+                self.check_offset_of(container, fields, expr)
+            }
             ExprKind::Break(destination, ref expr_opt) => {
                 self.check_expr_break(destination, expr_opt.as_deref(), expr)
             }
@@ -2450,15 +2453,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         base_did: DefId,
         return_ty: Option<Ty<'tcx>>,
     ) -> ErrorGuaranteed {
-        let struct_path = self.tcx().def_path_str(base_did);
-        let kind_name = self.tcx().def_descr(base_did);
-        let mut err = struct_span_err!(
-            self.tcx().sess,
-            field.span,
-            E0616,
-            "field `{field}` of {kind_name} `{struct_path}` is private",
-        );
-        err.span_label(field.span, "private field");
+        let mut err = self.private_field_err(field, base_did);
+
         // Also check if an accessible method exists, which is often what is meant.
         if self.method_exists(field, expr_t, expr.hir_id, false, return_ty)
             && !self.expr_in_place(expr.hir_id)
@@ -2698,6 +2694,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         err
     }
 
+    fn private_field_err(
+        &self,
+        field: Ident,
+        base_did: DefId,
+    ) -> DiagnosticBuilder<'_, ErrorGuaranteed> {
+        let struct_path = self.tcx().def_path_str(base_did);
+        let kind_name = self.tcx().def_descr(base_did);
+        let mut err = struct_span_err!(
+            self.tcx().sess,
+            field.span,
+            E0616,
+            "field `{field}` of {kind_name} `{struct_path}` is private",
+        );
+        err.span_label(field.span, "private field");
+
+        err
+    }
+
     pub(crate) fn get_field_candidates_considering_privacy(
         &self,
         span: Span,
@@ -3042,4 +3056,77 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             self.tcx.mk_unit()
         }
     }
+
+    fn check_offset_of(
+        &self,
+        container: &'tcx hir::Ty<'tcx>,
+        fields: &[Ident],
+        expr: &'tcx hir::Expr<'tcx>,
+    ) -> Ty<'tcx> {
+        let container = self.to_ty(container).normalized;
+
+        let mut field_indices = Vec::with_capacity(fields.len());
+        let mut current_container = container;
+
+        for &field in fields {
+            let container = self.structurally_resolved_type(expr.span, current_container);
+
+            match container.kind() {
+                ty::Adt(container_def, substs) if !container_def.is_enum() => {
+                    let block = self.tcx.hir().local_def_id_to_hir_id(self.body_id);
+                    let (ident, def_scope) =
+                        self.tcx.adjust_ident_and_get_scope(field, container_def.did(), block);
+
+                    let fields = &container_def.non_enum_variant().fields;
+                    if let Some((index, field)) = fields
+                        .iter_enumerated()
+                        .find(|(_, f)| f.ident(self.tcx).normalize_to_macros_2_0() == ident)
+                    {
+                        let field_ty = self.field_ty(expr.span, field, substs);
+
+                        self.require_type_is_sized(field_ty, expr.span, traits::MiscObligation);
+
+                        if field.vis.is_accessible_from(def_scope, self.tcx) {
+                            self.tcx.check_stability(field.did, Some(expr.hir_id), expr.span, None);
+                        } else {
+                            self.private_field_err(ident, container_def.did()).emit();
+                        }
+
+                        // Save the index of all fields regardless of their visibility in case
+                        // of error recovery.
+                        field_indices.push(index);
+                        current_container = field_ty;
+
+                        continue;
+                    }
+                }
+                ty::Tuple(tys) => {
+                    let fstr = field.as_str();
+
+                    if let Ok(index) = fstr.parse::<usize>() {
+                        if fstr == index.to_string() {
+                            if let Some(&field_ty) = tys.get(index) {
+                                field_indices.push(index.into());
+                                current_container = field_ty;
+
+                                continue;
+                            }
+                        }
+                    }
+                }
+                _ => (),
+            };
+
+            self.no_such_field_err(field, container, expr.hir_id).emit();
+
+            break;
+        }
+
+        self.typeck_results
+            .borrow_mut()
+            .offset_of_data_mut()
+            .insert(expr.hir_id, (container, field_indices));
+
+        self.tcx.types.usize
+    }
 }
diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs
index ee1c6fbfd65..94b6a0f8f47 100644
--- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs
+++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs
@@ -300,6 +300,7 @@ impl<'a, 'tcx> ExprUseVisitor<'a, 'tcx> {
             hir::ExprKind::Continue(..)
             | hir::ExprKind::Lit(..)
             | hir::ExprKind::ConstBlock(..)
+            | hir::ExprKind::OffsetOf(..)
             | hir::ExprKind::Err(_) => {}
 
             hir::ExprKind::Loop(blk, ..) => {
diff --git a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs
index 3e9a9ce1b31..28c44aa5703 100644
--- a/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs
+++ b/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/cfg_build.rs
@@ -215,6 +215,7 @@ impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> {
             | ExprKind::Continue(..)
             | ExprKind::Ret(..)
             | ExprKind::InlineAsm(..)
+            | ExprKind::OffsetOf(..)
             | ExprKind::Struct(..)
             | ExprKind::Repeat(..)
             | ExprKind::Yield(..)
@@ -485,6 +486,7 @@ impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> {
             | ExprKind::Field(..)
             | ExprKind::Index(..)
             | ExprKind::InlineAsm(..)
+            | ExprKind::OffsetOf(..)
             | ExprKind::Let(..)
             | ExprKind::Lit(..)
             | ExprKind::Path(..)
diff --git a/compiler/rustc_hir_typeck/src/mem_categorization.rs b/compiler/rustc_hir_typeck/src/mem_categorization.rs
index 6c861b5930a..f5fca14eca8 100644
--- a/compiler/rustc_hir_typeck/src/mem_categorization.rs
+++ b/compiler/rustc_hir_typeck/src/mem_categorization.rs
@@ -381,6 +381,7 @@ impl<'a, 'tcx> MemCategorizationContext<'a, 'tcx> {
             | hir::ExprKind::Struct(..)
             | hir::ExprKind::Repeat(..)
             | hir::ExprKind::InlineAsm(..)
+            | hir::ExprKind::OffsetOf(..)
             | hir::ExprKind::Err(_) => Ok(self.cat_rvalue(expr.hir_id, expr.span, expr_ty)),
         }
     }
diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs
index e876fa27593..9432a5840b2 100644
--- a/compiler/rustc_hir_typeck/src/writeback.rs
+++ b/compiler/rustc_hir_typeck/src/writeback.rs
@@ -70,6 +70,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         wbcx.visit_user_provided_tys();
         wbcx.visit_user_provided_sigs();
         wbcx.visit_generator_interior_types();
+        wbcx.visit_offset_of_container_types();
 
         wbcx.typeck_results.rvalue_scopes =
             mem::take(&mut self.typeck_results.borrow_mut().rvalue_scopes);
@@ -295,7 +296,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for WritebackCx<'cx, 'tcx> {
                     self.visit_field_id(field.hir_id);
                 }
             }
-            hir::ExprKind::Field(..) => {
+            hir::ExprKind::Field(..) | hir::ExprKind::OffsetOf(..) => {
                 self.visit_field_id(e.hir_id);
             }
             hir::ExprKind::ConstBlock(anon_const) => {
@@ -682,6 +683,28 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> {
         }
     }
 
+    fn visit_offset_of_container_types(&mut self) {
+        let fcx_typeck_results = self.fcx.typeck_results.borrow();
+        assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner);
+        let common_hir_owner = fcx_typeck_results.hir_owner;
+
+        for (local_id, &(container, ref indices)) in
+            fcx_typeck_results.offset_of_data().items_in_stable_order()
+        {
+            let hir_id = hir::HirId { owner: common_hir_owner, local_id };
+
+            if cfg!(debug_assertions) && container.needs_infer() {
+                span_bug!(
+                    hir_id.to_span(self.fcx.tcx),
+                    "writeback: `{:?}` has inference variables",
+                    container
+                );
+            };
+
+            self.typeck_results.offset_of_data_mut().insert(hir_id, (container, indices.clone()));
+        }
+    }
+
     fn resolve<T>(&mut self, x: T, span: &dyn Locatable) -> T
     where
         T: TypeFoldable<TyCtxt<'tcx>>,
diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs
index c38a347809f..6d7316a3262 100644
--- a/compiler/rustc_middle/src/mir/syntax.rs
+++ b/compiler/rustc_middle/src/mir/syntax.rs
@@ -1211,12 +1211,14 @@ pub enum AggregateKind<'tcx> {
     Generator(DefId, SubstsRef<'tcx>, hir::Movability),
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
+#[derive(Clone, Debug, PartialEq, Eq, TyEncodable, TyDecodable, Hash, HashStable)]
 pub enum NullOp {
     /// Returns the size of a value of that type
     SizeOf,
     /// Returns the minimum alignment of a type
     AlignOf,
+    /// Returns the offset of a field
+    OffsetOf(Vec<FieldIdx>),
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -1286,6 +1288,6 @@ mod size_asserts {
     static_assert_size!(Operand<'_>, 24);
     static_assert_size!(Place<'_>, 16);
     static_assert_size!(PlaceElem<'_>, 24);
-    static_assert_size!(Rvalue<'_>, 40);
+    static_assert_size!(Rvalue<'_>, 48);
     // tidy-alphabetical-end
 }
diff --git a/compiler/rustc_middle/src/mir/tcx.rs b/compiler/rustc_middle/src/mir/tcx.rs
index 4f00abf7fab..5ca82413448 100644
--- a/compiler/rustc_middle/src/mir/tcx.rs
+++ b/compiler/rustc_middle/src/mir/tcx.rs
@@ -188,7 +188,9 @@ impl<'tcx> Rvalue<'tcx> {
             }
             Rvalue::UnaryOp(UnOp::Not | UnOp::Neg, ref operand) => operand.ty(local_decls, tcx),
             Rvalue::Discriminant(ref place) => place.ty(local_decls, tcx).ty.discriminant_ty(tcx),
-            Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) => tcx.types.usize,
+            Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {
+                tcx.types.usize
+            }
             Rvalue::Aggregate(ref ak, ref ops) => match **ak {
                 AggregateKind::Array(ty) => tcx.mk_array(ty, ops.len() as u64),
                 AggregateKind::Tuple => {
diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs
index 7d79a13d3fd..084446697cb 100644
--- a/compiler/rustc_middle/src/thir.rs
+++ b/compiler/rustc_middle/src/thir.rs
@@ -481,6 +481,11 @@ pub enum ExprKind<'tcx> {
     },
     /// Inline assembly, i.e. `asm!()`.
     InlineAsm(Box<InlineAsmExpr<'tcx>>),
+    /// Field offset (`offset_of!`)
+    OffsetOf {
+        container: Ty<'tcx>,
+        fields: Vec<FieldIdx>,
+    },
     /// An expression taking a reference to a thread local.
     ThreadLocalRef(DefId),
     /// A `yield` expression.
diff --git a/compiler/rustc_middle/src/thir/visit.rs b/compiler/rustc_middle/src/thir/visit.rs
index 5614528c4cb..5c7ec31cf93 100644
--- a/compiler/rustc_middle/src/thir/visit.rs
+++ b/compiler/rustc_middle/src/thir/visit.rs
@@ -160,6 +160,7 @@ pub fn walk_expr<'a, 'tcx: 'a, V: Visitor<'a, 'tcx>>(visitor: &mut V, expr: &Exp
                 }
             }
         }
+        OffsetOf { container: _, fields: _ } => {}
         ThreadLocalRef(_) => {}
         Yield { value } => visitor.visit_expr(&visitor.thir()[value]),
     }
diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs
index 47943b94c3b..ef8955b1d3a 100644
--- a/compiler/rustc_middle/src/ty/typeck_results.rs
+++ b/compiler/rustc_middle/src/ty/typeck_results.rs
@@ -208,6 +208,9 @@ pub struct TypeckResults<'tcx> {
     /// Contains the data for evaluating the effect of feature `capture_disjoint_fields`
     /// on closure size.
     pub closure_size_eval: FxHashMap<LocalDefId, ClosureSizeProfileData<'tcx>>,
+
+    /// Container types and field indices of `offset_of!` expressions
+    offset_of_data: ItemLocalMap<(Ty<'tcx>, Vec<FieldIdx>)>,
 }
 
 /// Whenever a value may be live across a generator yield, the type of that value winds up in the
@@ -280,6 +283,7 @@ impl<'tcx> TypeckResults<'tcx> {
             generator_interior_predicates: Default::default(),
             treat_byte_string_as_slice: Default::default(),
             closure_size_eval: Default::default(),
+            offset_of_data: Default::default(),
         }
     }
 
@@ -530,6 +534,14 @@ impl<'tcx> TypeckResults<'tcx> {
     pub fn coercion_casts(&self) -> &ItemLocalSet {
         &self.coercion_casts
     }
+
+    pub fn offset_of_data(&self) -> LocalTableInContext<'_, (Ty<'tcx>, Vec<FieldIdx>)> {
+        LocalTableInContext { hir_owner: self.hir_owner, data: &self.offset_of_data }
+    }
+
+    pub fn offset_of_data_mut(&mut self) -> LocalTableInContextMut<'_, (Ty<'tcx>, Vec<FieldIdx>)> {
+        LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.offset_of_data }
+    }
 }
 
 /// Validate that the given HirId (respectively its `local_id` part) can be
diff --git a/compiler/rustc_mir_build/src/build/expr/as_place.rs b/compiler/rustc_mir_build/src/build/expr/as_place.rs
index fb775766c65..7ec57add66b 100644
--- a/compiler/rustc_mir_build/src/build/expr/as_place.rs
+++ b/compiler/rustc_mir_build/src/build/expr/as_place.rs
@@ -557,6 +557,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             | ExprKind::ConstBlock { .. }
             | ExprKind::StaticRef { .. }
             | ExprKind::InlineAsm { .. }
+            | ExprKind::OffsetOf { .. }
             | ExprKind::Yield { .. }
             | ExprKind::ThreadLocalRef(_)
             | ExprKind::Call { .. } => {
diff --git a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
index 8631749a524..a7fdb961248 100644
--- a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
+++ b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs
@@ -481,6 +481,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 }))))
             }
 
+            ExprKind::OffsetOf { container, ref fields } => {
+                block.and(Rvalue::NullaryOp(NullOp::OffsetOf(fields.clone()), container))
+            }
+
             ExprKind::Literal { .. }
             | ExprKind::NamedConst { .. }
             | ExprKind::NonHirLiteral { .. }
diff --git a/compiler/rustc_mir_build/src/build/expr/category.rs b/compiler/rustc_mir_build/src/build/expr/category.rs
index d33401f0764..3cc104ad96a 100644
--- a/compiler/rustc_mir_build/src/build/expr/category.rs
+++ b/compiler/rustc_mir_build/src/build/expr/category.rs
@@ -53,8 +53,7 @@ impl Category {
             | ExprKind::Borrow { .. }
             | ExprKind::AddressOf { .. }
             | ExprKind::Yield { .. }
-            | ExprKind::Call { .. }
-            | ExprKind::InlineAsm { .. } => Some(Category::Rvalue(RvalueFunc::Into)),
+            | ExprKind::Call { .. } => Some(Category::Rvalue(RvalueFunc::Into)),
 
             ExprKind::Array { .. }
             | ExprKind::Tuple { .. }
@@ -67,7 +66,9 @@ impl Category {
             | ExprKind::Repeat { .. }
             | ExprKind::Assign { .. }
             | ExprKind::AssignOp { .. }
-            | ExprKind::ThreadLocalRef(_) => Some(Category::Rvalue(RvalueFunc::AsRvalue)),
+            | ExprKind::ThreadLocalRef(_)
+            | ExprKind::InlineAsm { .. }
+            | ExprKind::OffsetOf { .. } => Some(Category::Rvalue(RvalueFunc::AsRvalue)),
 
             ExprKind::ConstBlock { .. }
             | ExprKind::Literal { .. }
diff --git a/compiler/rustc_mir_build/src/build/expr/into.rs b/compiler/rustc_mir_build/src/build/expr/into.rs
index 9b38ac1cc4c..29ff916d2cc 100644
--- a/compiler/rustc_mir_build/src/build/expr/into.rs
+++ b/compiler/rustc_mir_build/src/build/expr/into.rs
@@ -561,7 +561,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
             | ExprKind::ZstLiteral { .. }
             | ExprKind::ConstParam { .. }
             | ExprKind::ThreadLocalRef(_)
-            | ExprKind::StaticRef { .. } => {
+            | ExprKind::StaticRef { .. }
+            | ExprKind::OffsetOf { .. } => {
                 debug_assert!(match Category::of(&expr.kind).unwrap() {
                     // should be handled above
                     Category::Rvalue(RvalueFunc::Into) => false,
diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs
index 03a7f2d70fa..121ba8166af 100644
--- a/compiler/rustc_mir_build/src/check_unsafety.rs
+++ b/compiler/rustc_mir_build/src/check_unsafety.rs
@@ -323,6 +323,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
             | ExprKind::Box { .. }
             | ExprKind::If { .. }
             | ExprKind::InlineAsm { .. }
+            | ExprKind::OffsetOf { .. }
             | ExprKind::LogicalOp { .. }
             | ExprKind::Use { .. } => {
                 // We don't need to save the old value and restore it
diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs
index 8e2e92e6f6a..5329f6dd9d9 100644
--- a/compiler/rustc_mir_build/src/thir/cx/expr.rs
+++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs
@@ -664,6 +664,14 @@ impl<'tcx> Cx<'tcx> {
                 line_spans: asm.line_spans,
             })),
 
+            hir::ExprKind::OffsetOf(_, _) => {
+                let data = self.typeck_results.offset_of_data();
+                let &(container, ref indices) = data.get(expr.hir_id).unwrap();
+                let fields = indices.iter().copied().collect();
+
+                ExprKind::OffsetOf { container, fields }
+            }
+
             hir::ExprKind::ConstBlock(ref anon_const) => {
                 let ty = self.typeck_results().node_type(anon_const.hir_id);
                 let did = anon_const.def_id.to_def_id();
diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs
index ed61d6ee78b..86d20327f60 100644
--- a/compiler/rustc_mir_build/src/thir/print.rs
+++ b/compiler/rustc_mir_build/src/thir/print.rs
@@ -519,6 +519,19 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> {
                 self.print_inline_asm_expr(&**expr, depth_lvl + 2);
                 print_indented!(self, "}", depth_lvl);
             }
+            OffsetOf { container, fields } => {
+                print_indented!(self, "InlineAsm {", depth_lvl);
+                print_indented!(self, format!("container: {:?}", container), depth_lvl + 1);
+                print_indented!(self, "fields: [", depth_lvl + 1);
+
+                for field in fields.iter() {
+                    print_indented!(self, format!("{:?}", field), depth_lvl + 2);
+                    print_indented!(self, ",", depth_lvl + 1);
+                }
+
+                print_indented!(self, "]", depth_lvl + 1);
+                print_indented!(self, "}", depth_lvl);
+            }
             ThreadLocalRef(def_id) => {
                 print_indented!(self, "ThreadLocalRef {", depth_lvl);
                 print_indented!(self, format!("def_id: {:?}", def_id), depth_lvl + 1);
diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs
index 64ed7a29f6f..736ca62cacc 100644
--- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs
+++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs
@@ -360,7 +360,7 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
             | Rvalue::AddressOf(..)
             | Rvalue::Discriminant(..)
             | Rvalue::Len(..)
-            | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) => {}
+            | Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(..), _) => {}
         }
     }
 
diff --git a/compiler/rustc_mir_transform/src/separate_const_switch.rs b/compiler/rustc_mir_transform/src/separate_const_switch.rs
index ef367faf6a7..2479856b727 100644
--- a/compiler/rustc_mir_transform/src/separate_const_switch.rs
+++ b/compiler/rustc_mir_transform/src/separate_const_switch.rs
@@ -303,8 +303,7 @@ fn find_determining_place<'tcx>(
                     | Rvalue::NullaryOp(_, _)
                     | Rvalue::ShallowInitBox(_, _)
                     | Rvalue::UnaryOp(_, Operand::Constant(_))
-                    | Rvalue::Cast(_, Operand::Constant(_), _)
-                    => return None,
+                    | Rvalue::Cast(_, Operand::Constant(_), _) => return None,
                 }
             }
 
diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs
index 5cfe691df17..82b55df106c 100644
--- a/compiler/rustc_passes/src/dead.rs
+++ b/compiler/rustc_passes/src/dead.rs
@@ -237,6 +237,39 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
         }
     }
 
+    fn handle_offset_of(&mut self, expr: &'tcx hir::Expr<'tcx>) {
+        let data = self.typeck_results().offset_of_data();
+        let &(container, ref indices) =
+            data.get(expr.hir_id).expect("no offset_of_data for offset_of");
+
+        let mut last_did = expr.hir_id.owner.to_def_id();
+        let mut current_ty = container;
+
+        for &index in indices {
+            match current_ty.kind() {
+                ty::Adt(def, subst) => {
+                    let field = &def.non_enum_variant().fields[index];
+
+                    self.insert_def_id(field.did);
+                    let field_ty = field.ty(self.tcx, subst);
+
+                    last_did = field.did;
+                    current_ty =
+                        self.tcx.normalize_erasing_regions(self.tcx.param_env(field.did), field_ty);
+                }
+                // we don't need to mark tuple fields as live,
+                // but we may need to mark subfields
+                ty::Tuple(tys) => {
+                    current_ty = self.tcx.normalize_erasing_regions(
+                        self.tcx.param_env(last_did),
+                        tys[index.as_usize()],
+                    );
+                }
+                _ => span_bug!(expr.span, "named field access on non-ADT"),
+            }
+        }
+    }
+
     fn mark_live_symbols(&mut self) {
         let mut scanned = LocalDefIdSet::default();
         while let Some(id) = self.worklist.pop() {
@@ -405,6 +438,9 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> {
             hir::ExprKind::Closure(cls) => {
                 self.insert_def_id(cls.def_id.to_def_id());
             }
+            hir::ExprKind::OffsetOf(..) => {
+                self.handle_offset_of(expr);
+            }
             _ => (),
         }
 
diff --git a/compiler/rustc_passes/src/hir_stats.rs b/compiler/rustc_passes/src/hir_stats.rs
index 47e032758f2..dc5e454074d 100644
--- a/compiler/rustc_passes/src/hir_stats.rs
+++ b/compiler/rustc_passes/src/hir_stats.rs
@@ -302,7 +302,8 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> {
             [
                 ConstBlock, Array, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type,
                 DropTemps, Let, If, Loop, Match, Closure, Block, Assign, AssignOp, Field, Index,
-                Path, AddrOf, Break, Continue, Ret, InlineAsm, Struct, Repeat, Yield, Err
+                Path, AddrOf, Break, Continue, Ret, InlineAsm, OffsetOf, Struct, Repeat, Yield,
+                Err
             ]
         );
         hir_visit::walk_expr(self, e)
@@ -568,7 +569,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> {
                 Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let,
                 If, While, ForLoop, Loop, Match, Closure, Block, Async, Await, TryBlock, Assign,
                 AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret,
-                InlineAsm, FormatArgs, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, IncludedBytes, Err
+                InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, IncludedBytes, Err
             ]
         );
         ast_visit::walk_expr(self, e)
diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs
index a8471ce3b6f..b39a8c5598f 100644
--- a/compiler/rustc_passes/src/liveness.rs
+++ b/compiler/rustc_passes/src/liveness.rs
@@ -473,6 +473,7 @@ impl<'tcx> Visitor<'tcx> for IrMaps<'tcx> {
             | hir::ExprKind::Struct(..)
             | hir::ExprKind::Repeat(..)
             | hir::ExprKind::InlineAsm(..)
+            | hir::ExprKind::OffsetOf(..)
             | hir::ExprKind::Type(..)
             | hir::ExprKind::Err(_)
             | hir::ExprKind::Path(hir::QPath::TypeRelative(..))
@@ -1129,7 +1130,8 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
             | hir::ExprKind::ConstBlock(..)
             | hir::ExprKind::Err(_)
             | hir::ExprKind::Path(hir::QPath::TypeRelative(..))
-            | hir::ExprKind::Path(hir::QPath::LangItem(..)) => succ,
+            | hir::ExprKind::Path(hir::QPath::LangItem(..))
+            | hir::ExprKind::OffsetOf(..) => succ,
 
             // Note that labels have been resolved, so we don't need to look
             // at the label ident
@@ -1418,6 +1420,7 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) {
         | hir::ExprKind::ConstBlock(..)
         | hir::ExprKind::Block(..)
         | hir::ExprKind::AddrOf(..)
+        | hir::ExprKind::OffsetOf(..)
         | hir::ExprKind::Struct(..)
         | hir::ExprKind::Repeat(..)
         | hir::ExprKind::Closure { .. }
diff --git a/compiler/rustc_passes/src/naked_functions.rs b/compiler/rustc_passes/src/naked_functions.rs
index c398467f03e..cf8d9300a11 100644
--- a/compiler/rustc_passes/src/naked_functions.rs
+++ b/compiler/rustc_passes/src/naked_functions.rs
@@ -203,6 +203,7 @@ impl<'tcx> CheckInlineAssembly<'tcx> {
             | ExprKind::Break(..)
             | ExprKind::Continue(..)
             | ExprKind::Ret(..)
+            | ExprKind::OffsetOf(..)
             | ExprKind::Struct(..)
             | ExprKind::Repeat(..)
             | ExprKind::Yield(..) => {
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index d6ee7ac34aa..9891915d076 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1037,6 +1037,7 @@ symbols! {
         object_safe_for_dispatch,
         of,
         offset,
+        offset_of,
         omit_gdb_pretty_printer_section,
         on,
         on_unimplemented,
diff --git a/compiler/rustc_target/src/abi/mod.rs b/compiler/rustc_target/src/abi/mod.rs
index 8d2e92cc76c..589cd3cf96b 100644
--- a/compiler/rustc_target/src/abi/mod.rs
+++ b/compiler/rustc_target/src/abi/mod.rs
@@ -124,6 +124,21 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {
     {
         Ty::is_unit(self)
     }
+
+    pub fn offset_of_subfield<C>(self, cx: &C, indices: impl Iterator<Item = usize>) -> Size
+    where
+        Ty: TyAbiInterface<'a, C>,
+    {
+        let mut layout = self;
+        let mut offset = Size::ZERO;
+
+        for index in indices {
+            offset += layout.fields.offset(index);
+            layout = layout.field(cx, index);
+        }
+
+        offset
+    }
 }
 
 impl<'a, Ty> TyAndLayout<'a, Ty> {
diff --git a/compiler/rustc_ty_utils/src/consts.rs b/compiler/rustc_ty_utils/src/consts.rs
index b67607a4db6..e96df77d859 100644
--- a/compiler/rustc_ty_utils/src/consts.rs
+++ b/compiler/rustc_ty_utils/src/consts.rs
@@ -256,6 +256,7 @@ fn recurse_build<'tcx>(
         ExprKind::VarRef { .. }
         | ExprKind::UpvarRef { .. }
         | ExprKind::StaticRef { .. }
+        | ExprKind::OffsetOf { .. }
         | ExprKind::ThreadLocalRef(_) => {
             error(GenericConstantTooComplexSub::OperationNotSupported(node.span))?
         }
@@ -347,6 +348,7 @@ impl<'a, 'tcx> IsThirPolymorphic<'a, 'tcx> {
             | thir::ExprKind::ZstLiteral { .. }
             | thir::ExprKind::StaticRef { .. }
             | thir::ExprKind::InlineAsm(_)
+            | thir::ExprKind::OffsetOf { .. }
             | thir::ExprKind::ThreadLocalRef(_)
             | thir::ExprKind::Yield { .. } => false,
         }
diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs
index 30ec73cabf8..85745334e9f 100644
--- a/library/core/src/mem/mod.rs
+++ b/library/core/src/mem/mod.rs
@@ -1279,3 +1279,45 @@ pub trait SizedTypeProperties: Sized {
 #[doc(hidden)]
 #[unstable(feature = "sized_type_properties", issue = "none")]
 impl<T> SizedTypeProperties for T {}
+
+/// Expands to the offset in bytes of a field from the beginning of the given type.
+///
+/// Only structs, unions and tuples are supported.
+///
+/// Nested field accesses may be used, but not array indexes like in `C`'s `offsetof`.
+///
+/// Note that the output of this macro is not stable, except for `#[repr(C)]` types.
+///
+/// # Examples
+///
+/// ```
+/// #![feature(offset_of)]
+///
+/// use std::mem;
+/// #[repr(C)]
+/// struct FieldStruct {
+///     first: u8,
+///     second: u16,
+///     third: u8
+/// }
+///
+/// assert_eq!(mem::offset_of!(FieldStruct, first), 0);
+/// assert_eq!(mem::offset_of!(FieldStruct, second), 2);
+/// assert_eq!(mem::offset_of!(FieldStruct, third), 4);
+///
+/// #[repr(C)]
+/// struct NestedA {
+///     b: NestedB
+/// }
+///
+/// #[repr(C)]
+/// struct NestedB(u8);
+///
+/// assert_eq!(mem::offset_of!(NestedA, b.0), 0);
+/// ```
+#[unstable(feature = "offset_of", issue = "106655")]
+#[rustc_builtin_macro]
+#[cfg(not(bootstrap))]
+pub macro offset_of($Container:ty, $($fields:tt).+ $(,)?) {
+    // ...implementation defined...
+}
diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs
index f460da35dd3..84859a54c26 100644
--- a/library/core/tests/lib.rs
+++ b/library/core/tests/lib.rs
@@ -109,6 +109,7 @@
 #![feature(utf8_chunks)]
 #![feature(is_ascii_octdigit)]
 #![feature(get_many_mut)]
+#![cfg_attr(not(bootstrap), feature(offset_of))]
 #![deny(unsafe_op_in_unsafe_fn)]
 #![deny(fuzzy_provenance_casts)]
 
diff --git a/library/core/tests/mem.rs b/library/core/tests/mem.rs
index f7740a114e7..c08fc6fd2fb 100644
--- a/library/core/tests/mem.rs
+++ b/library/core/tests/mem.rs
@@ -364,3 +364,77 @@ fn const_maybe_uninit() {
 
     assert_eq!(FIELD_BY_FIELD, Foo { x: 1, y: 2 });
 }
+
+#[test]
+#[cfg(not(bootstrap))]
+fn offset_of() {
+    #[repr(C)]
+    struct Foo {
+        x: u8,
+        y: u16,
+        z: Bar,
+    }
+
+    #[repr(C)]
+    struct Bar(u8, u8);
+
+    assert_eq!(offset_of!(Foo, x), 0);
+    assert_eq!(offset_of!(Foo, y), 2);
+    assert_eq!(offset_of!(Foo, z.0), 4);
+    assert_eq!(offset_of!(Foo, z.1), 5);
+
+    // Layout of tuples is unstable
+    assert!(offset_of!((u8, u16), 0) <= size_of::<(u8, u16)>() - 1);
+    assert!(offset_of!((u8, u16), 1) <= size_of::<(u8, u16)>() - 2);
+}
+
+#[test]
+#[cfg(not(bootstrap))]
+fn const_offset_of() {
+    #[repr(C)]
+    struct Foo {
+        x: u8,
+        y: u16,
+    }
+
+    const X_OFFSET: usize = offset_of!(Foo, x);
+    const Y_OFFSET: usize = offset_of!(Foo, y);
+
+    assert_eq!(X_OFFSET, 0);
+    assert_eq!(Y_OFFSET, 2);
+}
+
+#[test]
+#[cfg(not(bootstrap))]
+fn offset_of_without_const_promotion() {
+    #[repr(C)]
+    struct Foo<SuppressConstPromotion> {
+        x: u8,
+        y: u16,
+        _scp: SuppressConstPromotion,
+    }
+
+    // Normally, offset_of is always const promoted.
+    // The generic parameter prevents this from happening.
+    // This is needed to test the codegen impl of offset_of
+    fn inner<SuppressConstPromotion>() {
+        assert_eq!(offset_of!(Foo<SuppressConstPromotion>, x), 0);
+        assert_eq!(offset_of!(Foo<SuppressConstPromotion>, y), 2);
+    }
+
+    inner::<()>();
+}
+
+#[test]
+#[cfg(not(bootstrap))]
+fn offset_of_dst() {
+    #[repr(C)]
+    struct Foo {
+        x: u8,
+        y: u16,
+        slice: [u8],
+    }
+
+    assert_eq!(offset_of!(Foo, x), 0);
+    assert_eq!(offset_of!(Foo, y), 2);
+}
diff --git a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs
index f0a1b1dfe56..5f1fdf00be8 100644
--- a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs
@@ -226,7 +226,8 @@ fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: H
                 | InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise,
             })
             .fold(NeverLoopResult::Otherwise, combine_seq),
-        ExprKind::Yield(_, _)
+        ExprKind::OffsetOf(_, _)
+        | ExprKind::Yield(_, _)
         | ExprKind::Closure { .. }
         | ExprKind::Path(_)
         | ExprKind::ConstBlock(_)
diff --git a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs
index 04225beeb70..7945275393c 100644
--- a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs
@@ -342,6 +342,7 @@ impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
             ExprKind::DropTemps(_) |
             ExprKind::Err(_) |
             ExprKind::InlineAsm(_) |
+            ExprKind::OffsetOf(_, _) |
             ExprKind::Let(_) |
             ExprKind::Lit(_) |
             ExprKind::Loop(_, _, _, _) |
diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs
index 2dac807c420..01927b6b5f1 100644
--- a/src/tools/clippy/clippy_lints/src/utils/author.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/author.rs
@@ -558,6 +558,10 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
                 kind!("InlineAsm(_)");
                 out!("// unimplemented: `ExprKind::InlineAsm` is not further destructured at the moment");
             },
+            ExprKind::OffsetOf(container, ref fields) => {
+                bind!(self, container, fields);
+                kind!("OffsetOf({container}, {fields})");
+            }
             ExprKind::Struct(qpath, fields, base) => {
                 bind!(self, qpath, fields);
                 opt_bind!(self, base);
diff --git a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
index 28c85717061..3df40942e7b 100644
--- a/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
+++ b/src/tools/clippy/clippy_utils/src/eager_or_lazy.rs
@@ -218,7 +218,8 @@ fn expr_eagerness<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> EagernessS
                 | ExprKind::AddrOf(..)
                 | ExprKind::Struct(..)
                 | ExprKind::Repeat(..)
-                | ExprKind::Block(Block { stmts: [], .. }, _) => (),
+                | ExprKind::Block(Block { stmts: [], .. }, _)
+                | ExprKind::OffsetOf(..) => (),
 
                 // Assignment might be to a local defined earlier, so don't eagerly evaluate.
                 // Blocks with multiple statements might be expensive, so don't eagerly evaluate.
diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs
index 3ee7147828b..d972ed82c25 100644
--- a/src/tools/clippy/clippy_utils/src/hir_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs
@@ -301,6 +301,9 @@ impl HirEqInterExpr<'_, '_, '_> {
             (&ExprKind::Unary(l_op, le), &ExprKind::Unary(r_op, re)) => l_op == r_op && self.eq_expr(le, re),
             (&ExprKind::Array(l), &ExprKind::Array(r)) => self.eq_exprs(l, r),
             (&ExprKind::DropTemps(le), &ExprKind::DropTemps(re)) => self.eq_expr(le, re),
+            (&ExprKind::OffsetOf(l_container, ref l_fields), &ExprKind::OffsetOf(r_container, ref r_fields)) => {
+                self.eq_ty(l_container, r_container) && over(l_fields, r_fields, |l, r| l.name == r.name)
+            },
             _ => false,
         };
         (is_eq && (!self.should_ignore(left) || !self.should_ignore(right)))
@@ -701,6 +704,12 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
                     }
                 }
             },
+            ExprKind::OffsetOf(container, fields) => {
+                self.hash_ty(container);
+                for field in fields {
+                    self.hash_name(field.name);
+                }
+            },
             ExprKind::Let(Let { pat, init, ty, .. }) => {
                 self.hash_expr(init);
                 if let Some(ty) = ty {
diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
index 354b6d71aa4..ecd712f32dc 100644
--- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
+++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
@@ -194,7 +194,7 @@ fn check_rvalue<'tcx>(
                 ))
             }
         },
-        Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf, _) | Rvalue::ShallowInitBox(_, _) => Ok(()),
+        Rvalue::NullaryOp(NullOp::SizeOf | NullOp::AlignOf | NullOp::OffsetOf(_), _) | Rvalue::ShallowInitBox(_, _) => Ok(()),
         Rvalue::UnaryOp(_, operand) => {
             let ty = operand.ty(body, tcx);
             if ty.is_integral() || ty.is_bool() {
diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs
index a5a4a921d94..e81eadceec0 100644
--- a/src/tools/clippy/clippy_utils/src/sugg.rs
+++ b/src/tools/clippy/clippy_utils/src/sugg.rs
@@ -139,6 +139,7 @@ impl<'a> Sugg<'a> {
             | hir::ExprKind::Field(..)
             | hir::ExprKind::Index(..)
             | hir::ExprKind::InlineAsm(..)
+            | hir::ExprKind::OffsetOf(..)
             | hir::ExprKind::ConstBlock(..)
             | hir::ExprKind::Lit(..)
             | hir::ExprKind::Loop(..)
@@ -197,6 +198,7 @@ impl<'a> Sugg<'a> {
             | ast::ExprKind::ForLoop(..)
             | ast::ExprKind::Index(..)
             | ast::ExprKind::InlineAsm(..)
+            | ast::ExprKind::OffsetOf(..)
             | ast::ExprKind::ConstBlock(..)
             | ast::ExprKind::Lit(..)
             | ast::ExprKind::IncludedBytes(..)
diff --git a/src/tools/clippy/clippy_utils/src/visitors.rs b/src/tools/clippy/clippy_utils/src/visitors.rs
index 1dc19bac984..5dcd71cef12 100644
--- a/src/tools/clippy/clippy_utils/src/visitors.rs
+++ b/src/tools/clippy/clippy_utils/src/visitors.rs
@@ -662,6 +662,7 @@ pub fn for_each_unconsumed_temporary<'tcx, B>(
             | ExprKind::Path(_)
             | ExprKind::Continue(_)
             | ExprKind::InlineAsm(_)
+            | ExprKind::OffsetOf(..)
             | ExprKind::Err(_) => (),
         }
         ControlFlow::Continue(())
diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs
index ac96bedf2fe..b76e3a2a87c 100644
--- a/src/tools/rustfmt/src/expr.rs
+++ b/src/tools/rustfmt/src/expr.rs
@@ -345,6 +345,7 @@ pub(crate) fn format_expr(
         // Style Guide RFC for InlineAsm variant pending
         // https://github.com/rust-dev-tools/fmt-rfcs/issues/152
         ast::ExprKind::InlineAsm(..) => Some(context.snippet(expr.span).to_owned()),
+        ast::ExprKind::OffsetOf(..) => Some(context.snippet(expr.span).to_owned()),
         ast::ExprKind::TryBlock(ref block) => {
             if let rw @ Some(_) =
                 rewrite_single_line_block(context, "try ", block, Some(&expr.attrs), None, shape)
diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs
index a26375ee643..ca171657407 100644
--- a/src/tools/rustfmt/src/utils.rs
+++ b/src/tools/rustfmt/src/utils.rs
@@ -499,6 +499,7 @@ pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr
         | ast::ExprKind::Field(..)
         | ast::ExprKind::IncludedBytes(..)
         | ast::ExprKind::InlineAsm(..)
+        | ast::ExprKind::OffsetOf(..)
         | ast::ExprKind::Let(..)
         | ast::ExprKind::Path(..)
         | ast::ExprKind::Range(..)
diff --git a/tests/mir-opt/const_prop/offset_of.main.ConstProp.diff b/tests/mir-opt/const_prop/offset_of.main.ConstProp.diff
new file mode 100644
index 00000000000..0437ec81f1e
--- /dev/null
+++ b/tests/mir-opt/const_prop/offset_of.main.ConstProp.diff
@@ -0,0 +1,43 @@
+- // MIR for `main` before ConstProp
++ // MIR for `main` after ConstProp
+  
+  fn main() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/offset_of.rs:+0:11: +0:11
+      let _1: usize;                       // in scope 0 at $DIR/offset_of.rs:+1:9: +1:10
+      scope 1 {
+          debug x => _1;                   // in scope 1 at $DIR/offset_of.rs:+1:9: +1:10
+          let _2: usize;                   // in scope 1 at $DIR/offset_of.rs:+2:9: +2:10
+          scope 2 {
+              debug y => _2;               // in scope 2 at $DIR/offset_of.rs:+2:9: +2:10
+              let _3: usize;               // in scope 2 at $DIR/offset_of.rs:+3:9: +3:11
+              scope 3 {
+                  debug z0 => _3;          // in scope 3 at $DIR/offset_of.rs:+3:9: +3:11
+                  let _4: usize;           // in scope 3 at $DIR/offset_of.rs:+4:9: +4:11
+                  scope 4 {
+                      debug z1 => _4;      // in scope 4 at $DIR/offset_of.rs:+4:9: +4:11
+                  }
+              }
+          }
+      }
+  
+      bb0: {
+          StorageLive(_1);                 // scope 0 at $DIR/offset_of.rs:+1:9: +1:10
+-         _1 = OffsetOf([0])(Foo);         // scope 0 at $DIR/offset_of.rs:+1:13: +1:31
++         _1 = const 0_usize;              // scope 0 at $DIR/offset_of.rs:+1:13: +1:31
+          StorageLive(_2);                 // scope 1 at $DIR/offset_of.rs:+2:9: +2:10
+-         _2 = OffsetOf([1])(Foo);         // scope 1 at $DIR/offset_of.rs:+2:13: +2:31
++         _2 = const 2_usize;              // scope 1 at $DIR/offset_of.rs:+2:13: +2:31
+          StorageLive(_3);                 // scope 2 at $DIR/offset_of.rs:+3:9: +3:11
+-         _3 = OffsetOf([2, 0])(Foo);      // scope 2 at $DIR/offset_of.rs:+3:14: +3:34
++         _3 = const 4_usize;              // scope 2 at $DIR/offset_of.rs:+3:14: +3:34
+          StorageLive(_4);                 // scope 3 at $DIR/offset_of.rs:+4:9: +4:11
+-         _4 = OffsetOf([2, 1])(Foo);      // scope 3 at $DIR/offset_of.rs:+4:14: +4:34
++         _4 = const 5_usize;              // scope 3 at $DIR/offset_of.rs:+4:14: +4:34
+          StorageDead(_4);                 // scope 3 at $DIR/offset_of.rs:+5:1: +5:2
+          StorageDead(_3);                 // scope 2 at $DIR/offset_of.rs:+5:1: +5:2
+          StorageDead(_2);                 // scope 1 at $DIR/offset_of.rs:+5:1: +5:2
+          StorageDead(_1);                 // scope 0 at $DIR/offset_of.rs:+5:1: +5:2
+          return;                          // scope 0 at $DIR/offset_of.rs:+5:2: +5:2
+      }
+  }
+  
diff --git a/tests/mir-opt/const_prop/offset_of.rs b/tests/mir-opt/const_prop/offset_of.rs
new file mode 100644
index 00000000000..54e19890c36
--- /dev/null
+++ b/tests/mir-opt/const_prop/offset_of.rs
@@ -0,0 +1,25 @@
+// unit-test
+// compile-flags: -O
+
+// EMIT_MIR offset_of.main.ConstProp.diff
+
+#![feature(offset_of)]
+
+use std::mem::offset_of;
+
+#[repr(C)]
+struct Foo {
+    x: u8,
+    y: u16,
+    z: Bar,
+}
+
+#[repr(C)]
+struct Bar(u8, u8);
+
+fn main() {
+    let x = offset_of!(Foo, x);
+    let y = offset_of!(Foo, y);
+    let z0 = offset_of!(Foo, z.0);
+    let z1 = offset_of!(Foo, z.1);
+}
diff --git a/tests/ui/liveness/liveness-offset-of.rs b/tests/ui/liveness/liveness-offset-of.rs
new file mode 100644
index 00000000000..461f546f3ee
--- /dev/null
+++ b/tests/ui/liveness/liveness-offset-of.rs
@@ -0,0 +1,26 @@
+#![feature(offset_of)]
+#![deny(dead_code)]
+
+use std::mem::offset_of;
+
+struct Alpha {
+    a: (),
+    b: (), //~ ERROR field `b` is never read
+    c: Beta,
+}
+
+struct Beta {
+    a: (), //~ ERROR field `a` is never read
+    b: (),
+}
+
+struct Gamma {
+    a: (), //~ ERROR field `a` is never read
+    b: (),
+}
+
+fn main() {
+    offset_of!(Alpha, a);
+    offset_of!(Alpha, c.b);
+    offset_of!((Gamma,), 0.b);
+}
diff --git a/tests/ui/liveness/liveness-offset-of.stderr b/tests/ui/liveness/liveness-offset-of.stderr
new file mode 100644
index 00000000000..32d13c8ed2c
--- /dev/null
+++ b/tests/ui/liveness/liveness-offset-of.stderr
@@ -0,0 +1,33 @@
+error: field `b` is never read
+  --> $DIR/liveness-offset-of.rs:8:5
+   |
+LL | struct Alpha {
+   |        ----- field in this struct
+LL |     a: (),
+LL |     b: (),
+   |     ^
+   |
+note: the lint level is defined here
+  --> $DIR/liveness-offset-of.rs:2:9
+   |
+LL | #![deny(dead_code)]
+   |         ^^^^^^^^^
+
+error: field `a` is never read
+  --> $DIR/liveness-offset-of.rs:13:5
+   |
+LL | struct Beta {
+   |        ---- field in this struct
+LL |     a: (),
+   |     ^
+
+error: field `a` is never read
+  --> $DIR/liveness-offset-of.rs:18:5
+   |
+LL | struct Gamma {
+   |        ----- field in this struct
+LL |     a: (),
+   |     ^
+
+error: aborting due to 3 previous errors
+
diff --git a/tests/ui/user-defined-macro-rules.rs b/tests/ui/macros/user-defined-macro-rules.rs
index 09e071ec454..09e071ec454 100644
--- a/tests/ui/user-defined-macro-rules.rs
+++ b/tests/ui/macros/user-defined-macro-rules.rs
diff --git a/tests/ui/offset-of/offset-of-arg-count.rs b/tests/ui/offset-of/offset-of-arg-count.rs
new file mode 100644
index 00000000000..163b07454ec
--- /dev/null
+++ b/tests/ui/offset-of/offset-of-arg-count.rs
@@ -0,0 +1,9 @@
+#![feature(offset_of)]
+
+use std::mem::offset_of;
+
+fn main() {
+    offset_of!(NotEnoughArguments); //~ ERROR expected one of
+    offset_of!(NotEnoughArgumentsWithAComma, ); //~ ERROR expected 2 arguments
+    offset_of!(Container, field, too many arguments); //~ ERROR expected 2 arguments
+}
diff --git a/tests/ui/offset-of/offset-of-arg-count.stderr b/tests/ui/offset-of/offset-of-arg-count.stderr
new file mode 100644
index 00000000000..ebecc982c51
--- /dev/null
+++ b/tests/ui/offset-of/offset-of-arg-count.stderr
@@ -0,0 +1,20 @@
+error: expected one of `!`, `(`, `+`, `,`, `::`, or `<`, found `<eof>`
+  --> $DIR/offset-of-arg-count.rs:6:16
+   |
+LL |     offset_of!(NotEnoughArguments);
+   |                ^^^^^^^^^^^^^^^^^^ expected one of `!`, `(`, `+`, `,`, `::`, or `<`
+
+error: expected 2 arguments
+  --> $DIR/offset-of-arg-count.rs:7:5
+   |
+LL |     offset_of!(NotEnoughArgumentsWithAComma, );
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: expected 2 arguments
+  --> $DIR/offset-of-arg-count.rs:8:5
+   |
+LL |     offset_of!(Container, field, too many arguments);
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 3 previous errors
+
diff --git a/tests/ui/offset-of/offset-of-dst-field.rs b/tests/ui/offset-of/offset-of-dst-field.rs
new file mode 100644
index 00000000000..b741cad05c2
--- /dev/null
+++ b/tests/ui/offset-of/offset-of-dst-field.rs
@@ -0,0 +1,14 @@
+#![feature(offset_of)]
+
+use std::mem::offset_of;
+
+#[repr(C)]
+struct Foo {
+    x: u8,
+    y: u16,
+    slice: [u8],
+}
+
+fn main() {
+    offset_of!(Foo, slice); //~ ERROR the size for values of type
+}
diff --git a/tests/ui/offset-of/offset-of-dst-field.stderr b/tests/ui/offset-of/offset-of-dst-field.stderr
new file mode 100644
index 00000000000..a490f384da5
--- /dev/null
+++ b/tests/ui/offset-of/offset-of-dst-field.stderr
@@ -0,0 +1,11 @@
+error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
+  --> $DIR/offset-of-dst-field.rs:13:5
+   |
+LL |     offset_of!(Foo, slice);
+   |     ^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
+   |
+   = help: the trait `Sized` is not implemented for `[u8]`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0277`.
diff --git a/tests/ui/offset-of/offset-of-private.rs b/tests/ui/offset-of/offset-of-private.rs
new file mode 100644
index 00000000000..0291b7825ca
--- /dev/null
+++ b/tests/ui/offset-of/offset-of-private.rs
@@ -0,0 +1,16 @@
+#![feature(offset_of)]
+
+use std::mem::offset_of;
+
+mod m {
+    #[repr(C)]
+    pub struct Foo {
+        pub public: u8,
+        private: u8,
+    }
+}
+
+fn main() {
+    offset_of!(m::Foo, public);
+    offset_of!(m::Foo, private); //~ ERROR field `private` of struct `Foo` is private
+}
diff --git a/tests/ui/offset-of/offset-of-private.stderr b/tests/ui/offset-of/offset-of-private.stderr
new file mode 100644
index 00000000000..8a186dd5a02
--- /dev/null
+++ b/tests/ui/offset-of/offset-of-private.stderr
@@ -0,0 +1,9 @@
+error[E0616]: field `private` of struct `Foo` is private
+  --> $DIR/offset-of-private.rs:15:24
+   |
+LL |     offset_of!(m::Foo, private);
+   |                        ^^^^^^^ private field
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0616`.