diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs
index 55daf441a75..ae4a0e15fab 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder.rs
@@ -507,10 +507,18 @@ impl<'a, 'tcx> Decodable<DecodeContext<'a, 'tcx>> for ExpnId {
 
 impl<'a, 'tcx> Decodable<DecodeContext<'a, 'tcx>> for Span {
     fn decode(decoder: &mut DecodeContext<'a, 'tcx>) -> Span {
+        let start = decoder.position();
         let mode = SpanEncodingMode::decode(decoder);
         let data = match mode {
             SpanEncodingMode::Direct => SpanData::decode(decoder),
-            SpanEncodingMode::Shorthand(position) => decoder.with_position(position, |decoder| {
+            SpanEncodingMode::RelativeOffset(offset) => {
+                decoder.with_position(start - offset, |decoder| {
+                    let mode = SpanEncodingMode::decode(decoder);
+                    debug_assert!(matches!(mode, SpanEncodingMode::Direct));
+                    SpanData::decode(decoder)
+                })
+            }
+            SpanEncodingMode::AbsoluteOffset(addr) => decoder.with_position(addr, |decoder| {
                 let mode = SpanEncodingMode::decode(decoder);
                 debug_assert!(matches!(mode, SpanEncodingMode::Direct));
                 SpanData::decode(decoder)
diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs
index e968f48a60c..f322588e365 100644
--- a/compiler/rustc_metadata/src/rmeta/encoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/encoder.rs
@@ -169,7 +169,19 @@ impl<'a, 'tcx> Encodable<EncodeContext<'a, 'tcx>> for ExpnId {
 impl<'a, 'tcx> Encodable<EncodeContext<'a, 'tcx>> for Span {
     fn encode(&self, s: &mut EncodeContext<'a, 'tcx>) {
         match s.span_shorthands.entry(*self) {
-            Entry::Occupied(o) => SpanEncodingMode::Shorthand(*o.get()).encode(s),
+            Entry::Occupied(o) => {
+                // If an offset is smaller than the absolute position, we encode with the offset.
+                // This saves space since smaller numbers encode in less bits.
+                let last_location = *o.get();
+                // This cannot underflow. Metadata is written with increasing position(), so any
+                // previously saved offset must be smaller than the current position.
+                let offset = s.opaque.position() - last_location;
+                if offset < last_location {
+                    SpanEncodingMode::RelativeOffset(offset).encode(s)
+                } else {
+                    SpanEncodingMode::AbsoluteOffset(last_location).encode(s)
+                }
+            }
             Entry::Vacant(v) => {
                 let position = s.opaque.position();
                 v.insert(position);
diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs
index d496e7494e7..bafd3f0b84d 100644
--- a/compiler/rustc_metadata/src/rmeta/mod.rs
+++ b/compiler/rustc_metadata/src/rmeta/mod.rs
@@ -68,7 +68,8 @@ pub const METADATA_HEADER: &[u8] = &[b'r', b'u', b's', b't', 0, 0, 0, METADATA_V
 
 #[derive(Encodable, Decodable)]
 enum SpanEncodingMode {
-    Shorthand(usize),
+    RelativeOffset(usize),
+    AbsoluteOffset(usize),
     Direct,
 }