Elide storage of bssl::Span size if known at compile time.

This does not yet migrate callers to actually benefit from this; only a
few callers needed changes due to loss of some implicit conversions.

Tested: IDA/Diaphora and ghidriff find no differences in generated code
other than type identifiers with the extra template argument.

Change-Id: I36de895ca96c50a6f8a624049fcca93db2602a11
Bug: 390229582
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/82847
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index 0407f3f..5f3fe4b 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -31,6 +31,18 @@
 
 namespace {
 
+TEST(CBSTest, CtorFromSpan) {
+  const uint8_t buf[4] = "foo";
+
+  auto span_from_static_extent = bssl::Span<const uint8_t, 4>(buf);
+  CBS cbs_from_static_extent(span_from_static_extent);
+  EXPECT_EQ(4u, CBS_len(&cbs_from_static_extent));
+
+  auto span_from_dynamic_extent = bssl::Span<const uint8_t>(buf);
+  CBS cbs_from_dynamic_extent(span_from_dynamic_extent);
+  EXPECT_EQ(4u, CBS_len(&cbs_from_dynamic_extent));
+}
+
 TEST(CBSTest, Skip) {
   static const uint8_t kData[] = {1, 2, 3};
   CBS data;
diff --git a/include/openssl/span.h b/include/openssl/span.h
index 440b657..93de8d5 100644
--- a/include/openssl/span.h
+++ b/include/openssl/span.h
@@ -24,6 +24,7 @@
 #include <stdlib.h>
 
 #include <algorithm>
+#include <limits>
 #include <string_view>
 #include <type_traits>
 
@@ -33,24 +34,27 @@
 
 #if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L
 #include <ranges>
-BSSL_NAMESPACE_BEGIN
-template <typename T>
-class Span;
-BSSL_NAMESPACE_END
-
-// Mark `Span` as satisfying the `view` and `borrowed_range` concepts. This
-// should be done before the definition of `Span`, so that any inlined calls to
-// range functionality use the correct specializations.
-template <typename T>
-inline constexpr bool std::ranges::enable_view<bssl::Span<T>> = true;
-template <typename T>
-inline constexpr bool std::ranges::enable_borrowed_range<bssl::Span<T>> = true;
 #endif
 
 BSSL_NAMESPACE_BEGIN
+inline constexpr size_t dynamic_extent = std::numeric_limits<size_t>::max();
 
-template <typename T>
+template <typename T, size_t N = dynamic_extent>
 class Span;
+BSSL_NAMESPACE_END
+
+#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L
+// Mark `Span` as satisfying the `view` and `borrowed_range` concepts. This
+// should be done before the definition of `Span`, so that any inlined calls to
+// range functionality use the correct specializations.
+template <typename T, size_t N>
+inline constexpr bool std::ranges::enable_view<bssl::Span<T, N>> = true;
+template <typename T, size_t N>
+inline constexpr bool std::ranges::enable_borrowed_range<bssl::Span<T, N>> =
+    true;
+#endif
+
+BSSL_NAMESPACE_BEGIN
 
 namespace internal {
 template <typename T>
@@ -67,6 +71,32 @@
   friend bool operator!=(Span<T> lhs, Span<T> rhs) { return !(lhs == rhs); }
 };
 
+// Container class to store the size of a span at runtime or compile time.
+template <typename T, size_t N>
+class SpanStorage : private SpanBase<const T> {
+ public:
+  constexpr SpanStorage(T *data, size_t size) : data_(data) {
+    BSSL_CHECK(size == N);
+  }
+  constexpr T *data() const { return data_; }
+  constexpr size_t size() const { return N; }
+
+ private:
+  T *data_;
+};
+
+template <typename T>
+class SpanStorage<T, dynamic_extent> : private SpanBase<const T> {
+ public:
+  constexpr SpanStorage(T *data, size_t size) : data_(data), size_(size) {}
+  constexpr T *data() const { return data_; }
+  constexpr size_t size() const { return size_; }
+
+ private:
+  T *data_;
+  size_t size_;
+};
+
 // Heuristically test whether C is a container type that can be converted into
 // a Span<T> by checking for data() and size() member functions.
 template <typename C, typename T>
@@ -74,6 +104,10 @@
     std::is_convertible_v<decltype(std::declval<C>().data()), T *> &&
     std::is_integral_v<decltype(std::declval<C>().size())>>;
 
+// A fake type used to be able to SFINAE between two different container
+// constructors - by giving one this as a second default argument, and one not.
+struct AllowRedeclaringConstructor {};
+
 }  // namespace internal
 
 // A Span<T> is a non-owning reference to a contiguous array of objects of type
@@ -107,11 +141,9 @@
 // Note that Spans have value type sematics. They are cheap to construct and
 // copy, and should be passed by value whenever a method would otherwise accept
 // a reference or pointer to a container or array.
-template <typename T>
-class Span : private internal::SpanBase<const T> {
+template <typename T, size_t N>
+class Span : public internal::SpanStorage<T, N> {
  public:
-  static const size_t npos = static_cast<size_t>(-1);
-
   using element_type = T;
   using value_type = std::remove_cv_t<T>;
   using size_type = size_t;
@@ -123,90 +155,161 @@
   using iterator = T *;
   using const_iterator = const T *;
 
-  constexpr Span() : Span(nullptr, 0) {}
-  constexpr Span(T *ptr, size_t len) : data_(ptr), size_(len) {}
+  template <typename U = T,
+            typename = std::enable_if_t<N == 0 || N == dynamic_extent, U>>
+  constexpr Span() : internal::SpanStorage<T, N>(nullptr, 0) {}
 
-  template <size_t N>
-  constexpr Span(T (&array)[N]) : Span(array, N) {}
+  // NOTE: This constructor may abort() at runtime if len differs from the
+  // compile-time size, if any.
+  constexpr Span(T *ptr, size_t len) : internal::SpanStorage<T, N>(ptr, len) {}
+
+  template <size_t NA,
+            typename = std::enable_if_t<N == NA || N == dynamic_extent>>
+  // NOLINTNEXTLINE(google-explicit-constructor): same as std::span.
+  constexpr Span(T (&array)[NA]) : internal::SpanStorage<T, N>(array, NA) {}
+
+  template <
+      size_t NA, typename U,
+      typename = std::enable_if_t<std::is_convertible_v<U (*)[], T (*)[]>>,
+      typename = std::enable_if_t<N == dynamic_extent || N == NA>>
+  // NOLINTNEXTLINE(google-explicit-constructor): same as std::span.
+  constexpr Span(Span<U, NA> other)
+      : internal::SpanStorage<T, N>(other.data(), other.size()) {}
 
   template <typename C, typename = internal::EnableIfContainer<C, T>,
-            typename = std::enable_if_t<std::is_const<T>::value, C>>
+            typename = std::enable_if_t<std::is_const<T>::value, C>,
+            typename = std::enable_if_t<N == dynamic_extent, C>>
+  // NOLINTNEXTLINE(google-explicit-constructor): same as std::span.
   constexpr Span(const C &container)
-      : data_(container.data()), size_(container.size()) {}
+      : internal::SpanStorage<T, N>(container.data(), container.size()) {}
 
+  // NOTE: This constructor may abort() at runtime if the container's length
+  // differs from the compile-time size, if any.
+  template <typename C, typename = internal::EnableIfContainer<C, T>,
+            typename = std::enable_if_t<std::is_const<T>::value, C>,
+            typename = std::enable_if_t<N != dynamic_extent, C>>
+  constexpr explicit Span(const C &container,
+                          internal::AllowRedeclaringConstructor = {})
+      : internal::SpanStorage<T, N>(container.data(), container.size()) {}
+
+  // NOTE: This constructor may abort() at runtime if the container's length
+  // differs from the compile-time size, if any.
   template <typename C, typename = internal::EnableIfContainer<C, T>,
             typename = std::enable_if_t<!std::is_const<T>::value, C>>
   constexpr explicit Span(C &container)
-      : data_(container.data()), size_(container.size()) {}
+      : internal::SpanStorage<T, N>(container.data(), container.size()) {}
 
-  constexpr T *data() const { return data_; }
-  constexpr size_t size() const { return size_; }
-  constexpr bool empty() const { return size_ == 0; }
+  using internal::SpanStorage<T, N>::data;
+  using internal::SpanStorage<T, N>::size;
+  constexpr bool empty() const { return size() == 0; }
 
-  constexpr iterator begin() const { return data_; }
-  constexpr const_iterator cbegin() const { return data_; }
-  constexpr iterator end() const { return data_ + size_; }
+  constexpr iterator begin() const { return data(); }
+  constexpr const_iterator cbegin() const { return data(); }
+  constexpr iterator end() const { return data() + size(); }
   constexpr const_iterator cend() const { return end(); }
 
   constexpr T &front() const {
-    if (size_ == 0) {
-      abort();
-    }
-    return data_[0];
+    BSSL_CHECK(size() != 0);
+    return data()[0];
   }
   constexpr T &back() const {
-    if (size_ == 0) {
-      abort();
-    }
-    return data_[size_ - 1];
+    BSSL_CHECK(size() != 0);
+    return data()[size() - 1];
   }
 
   constexpr T &operator[](size_t i) const {
-    if (i >= size_) {
-      abort();
-    }
-    return data_[i];
+    BSSL_CHECK(i < size());
+    return data()[i];
   }
   T &at(size_t i) const { return (*this)[i]; }
 
-  constexpr Span subspan(size_t pos = 0, size_t len = npos) const {
-    if (pos > size_) {
-      // absl::Span throws an exception here. Note std::span and Chromium
-      // base::span additionally forbid pos + len being out of range, with a
-      // special case at npos/dynamic_extent, while absl::Span::subspan clips
-      // the span. For now, we align with absl::Span in case we switch to it in
-      // the future.
-      abort();
-    }
-    return Span(data_ + pos, std::min(size_ - pos, len));
-  }
-
-  constexpr Span first(size_t len) const {
-    if (len > size_) {
-      abort();
-    }
-    return Span(data_, len);
-  }
-
-  constexpr Span last(size_t len) const {
-    if (len > size_) {
-      abort();
-    }
-    return Span(data_ + size_ - len, len);
-  }
-
  private:
-  T *data_;
-  size_t size_;
-};
+  static constexpr size_t SubspanOutLen(size_t size, size_t pos, size_t len) {
+    // NOTE: This differs from std::span's subspan length in that this one
+    // performs clipping.
+    //
+    // For std::span, this would be:
+    //
+    // len != dynamic_extent ? len : size - pos
+    return std::min(size - pos, len);
+  }
+  static constexpr size_t SubspanTypeOutLen(size_t size, size_t pos,
+                                            size_t len) {
+    // NOTE: This differs from std::span's subspan length in that this one
+    // performs clipping, and thus has to return dynamic extent whenever the
+    // input span has dynamic extent.
+    //
+    // For std::span, this would be:
+    //
+    // len != dynamic_extent
+    //   ? len
+    //   : (size != dynamic_extent ? size - pos : dynamic_extent)
+    if (size == dynamic_extent) {
+      return dynamic_extent;
+    }
+    return SubspanOutLen(size, pos, len);
+  }
 
-template <typename T>
-const size_t Span<T>::npos;
+ public:
+  // NOTE: This method may abort() at runtime if pos is out of range.
+  constexpr Span<T> subspan(size_t pos = 0, size_t len = dynamic_extent) const {
+    // absl::Span throws an exception here. Note std::span and Chromium
+    // base::span additionally forbid pos + len being out of range, with a
+    // special case at npos/dynamic_extent, while absl::Span::subspan clips
+    // the span. For now, we align with absl::Span in case we switch to it in
+    // the future.
+    BSSL_CHECK(pos <= size());
+    return Span<T>(data() + pos, SubspanOutLen(size(), pos, len));
+  }
+
+  // NOTE: This method may abort() at runtime if len is out of range.
+  template <size_t pos, size_t len = dynamic_extent>
+  constexpr Span<T, SubspanTypeOutLen(N, pos, len)> subspan() const {
+    // absl::Span throws an exception here. Note std::span and Chromium
+    // base::span additionally forbid pos + len being out of range, with a
+    // special case at npos/dynamic_extent, while absl::Span::subspan clips
+    // the span. For now, we align with absl::Span in case we switch to it in
+    // the future.
+    //
+    // Removing clipping however will allow making the return type have a
+    // static extent whenever len is static, which matches std::span and
+    // could improve efficiency.
+    BSSL_CHECK(pos <= size());
+    return Span<T, SubspanTypeOutLen(N, pos, len)>(data() + pos,
+                                                   std::min(size() - pos, len));
+  }
+
+  // NOTE: This method may abort() at runtime if len is out of range.
+  constexpr Span<T> first(size_t len) const {
+    BSSL_CHECK(len <= size());
+    return Span<T>(data(), len);
+  }
+
+  // NOTE: This method may abort() at runtime if len is out of range.
+  template <size_t len>
+  constexpr Span<T, len> first() const {
+    BSSL_CHECK(len <= size());
+    return Span<T, len>(data(), len);
+  }
+
+  // NOTE: This method may abort() at runtime if len is out of range.
+  constexpr Span<T> last(size_t len) const {
+    BSSL_CHECK(len <= size());
+    return Span<T>(data() + size() - len, len);
+  }
+
+  // NOTE: This method may abort() at runtime if len is out of range.
+  template <size_t len>
+  constexpr Span<T, len> last() const {
+    BSSL_CHECK(len <= size());
+    return Span<T, len>(data() + size() - len, len);
+  }
+};
 
 template <typename T>
 Span(T *, size_t) -> Span<T>;
 template <typename T, size_t size>
-Span(T (&array)[size]) -> Span<T>;
+Span(T (&array)[size]) -> Span<T, size>;
 template <
     typename C,
     typename T = std::remove_pointer_t<decltype(std::declval<C>().data())>,
@@ -224,8 +327,8 @@
 }
 
 template <typename T, size_t N>
-constexpr Span<T> MakeSpan(T (&array)[N]) {
-  return Span<T>(array, N);
+constexpr Span<T, N> MakeSpan(T (&array)[N]) {
+  return array;
 }
 
 template <typename T>
@@ -240,7 +343,7 @@
 }
 
 template <typename T, size_t size>
-constexpr Span<const T> MakeConstSpan(T (&array)[size]) {
+constexpr Span<const T, size> MakeConstSpan(T (&array)[size]) {
   return array;
 }
 
diff --git a/pki/input.h b/pki/input.h
index 933c537..9e2d5d3 100644
--- a/pki/input.h
+++ b/pki/input.h
@@ -95,8 +95,7 @@
   constexpr uint8_t operator[](size_t idx) const { return data_[idx]; }
   constexpr uint8_t front() const { return data_.front(); }
   constexpr uint8_t back() const { return data_.back(); }
-  constexpr Input subspan(size_t pos = 0,
-                          size_t len = Span<const uint8_t>::npos) const {
+  constexpr Input subspan(size_t pos = 0, size_t len = dynamic_extent) const {
     return Input(data_.subspan(pos, len));
   }
   constexpr Input first(size_t len) const { return Input(data_.first(len)); }
diff --git a/ssl/extensions.cc b/ssl/extensions.cc
index 0820d92..b5f19bd 100644
--- a/ssl/extensions.cc
+++ b/ssl/extensions.cc
@@ -4578,7 +4578,8 @@
   }
 
   Span<const uint16_t> sigalgs =
-      cred->sigalgs.empty() ? Span(kSignSignatureAlgorithms) : cred->sigalgs;
+      cred->sigalgs.empty() ? Span<const uint16_t>(kSignSignatureAlgorithms)
+                            : Span<const uint16_t>(cred->sigalgs);
   for (uint16_t sigalg : sigalgs) {
     if (!ssl_pkey_supports_algorithm(ssl, cred->pubkey.get(), sigalg,
                                      /*is_verify=*/false)) {
diff --git a/ssl/internal.h b/ssl/internal.h
index cd5b67d..e62202a 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -118,10 +118,10 @@
 // it would have liked to have written. The strings written consist of
 // |fixed_names_len| strings from |fixed_names| followed by |objects_len|
 // strings taken by projecting |objects| through |name|.
-template <typename T, typename Name>
+template <typename T, typename Name, size_t S1, size_t S2>
 inline size_t GetAllNames(const char **out, size_t max_out,
-                          Span<const char *const> fixed_names, Name(T::*name),
-                          Span<const T> objects) {
+                          Span<const char *const, S1> fixed_names,
+                          Name(T::*name), Span<const T, S2> objects) {
   auto span = bssl::Span(out, max_out);
   for (size_t i = 0; !span.empty() && i < fixed_names.size(); i++) {
     span[0] = fixed_names[i];
diff --git a/ssl/span_test.cc b/ssl/span_test.cc
index 55258e5..61deb58 100644
--- a/ssl/span_test.cc
+++ b/ssl/span_test.cc
@@ -22,59 +22,153 @@
 BSSL_NAMESPACE_BEGIN
 namespace {
 
-static void TestCtor(Span<int> s, const int *ptr, size_t size) {
+template <size_t N>
+static void TestCtor(Span<int, N> s, const int *ptr, size_t size) {
   EXPECT_EQ(s.data(), ptr);
   EXPECT_EQ(s.size(), size);
 }
 
-static void TestConstCtor(Span<const int> s, const int *ptr, size_t size) {
+template <size_t N>
+static void TestConstCtor(Span<const int, N> s, const int *ptr, size_t size) {
   EXPECT_EQ(s.data(), ptr);
   EXPECT_EQ(s.size(), size);
 }
 
+template <class T, size_t N>
+constexpr static bool IsRuntimeSized(Span<T, N>) {
+  return N == dynamic_extent;
+}
+
+TEST(SpanTest, CompileTimeSizes) {
+  static_assert(sizeof(Span<int, 4>) == sizeof(int *));
+  static_assert(sizeof(Span<int>) == sizeof(std::pair<int *, size_t>));
+}
+
 TEST(SpanTest, CtorEmpty) {
   Span<int> s;
   TestCtor(s, nullptr, 0);
 }
 
+TEST(SpanTest, CtorEmptyCompileTIme) {
+  Span<int, 0> s;
+  TestCtor(s, nullptr, 0);
+}
+
 TEST(SpanTest, CtorFromPtrAndSize) {
   std::vector<int> v = {7, 8, 9, 10};
   Span<int> s(v.data(), v.size());
   TestCtor(s, v.data(), v.size());
+  TestConstCtor<dynamic_extent>(Span<int>(v.data(), v.size()), v.data(),
+                                v.size());
+}
+
+TEST(SpanTest, CtorFromPtrAndSizeCompileTime) {
+  std::vector<int> v = {7, 8, 9, 10};
+  Span<int, 4> s(v.data(), v.size());
+  TestCtor(s, v.data(), v.size());
+  TestConstCtor<4>(Span<int, 4>(v.data(), v.size()), v.data(), v.size());
+}
+
+TEST(SpanTest, ConstCtorFromVector) {
+  std::vector<int> v = {1, 2};
+  // Const ctor is implicit.
+  TestConstCtor<dynamic_extent>(v, v.data(), v.size());
+}
+
+TEST(SpanTest, ConstCtorFromVectorCompileTime) {
+  std::vector<int> v = {1, 2};
+  // Static extent constructor can only be invoked explicitly.
+  TestConstCtor<2>(Span<const int, 2>(v), v.data(), v.size());
 }
 
 TEST(SpanTest, CtorFromVector) {
   std::vector<int> v = {1, 2};
-  // Const ctor is implicit.
-  TestConstCtor(v, v.data(), v.size());
   // Mutable is explicit.
   Span<int> s(v);
   TestCtor(s, v.data(), v.size());
 }
 
+TEST(SpanTest, CtorFromVectorCompileTime) {
+  std::vector<int> v = {1, 2};
+  // Mutable is explicit.
+  Span<int, 2> s(v);
+  TestCtor(s, v.data(), v.size());
+}
+
 TEST(SpanTest, CtorConstFromArray) {
   int v[] = {10, 11};
   // Array ctor is implicit for const and mutable T.
-  TestConstCtor(v, v, 2);
-  TestCtor(v, v, 2);
+  TestConstCtor<dynamic_extent>(v, v, 2);
+  TestCtor<dynamic_extent>(v, v, 2);
+}
+
+TEST(SpanTest, CtorConstFromArrayCompileTime) {
+  int v[] = {10, 11};
+  // Array ctor is implicit for const and mutable T.
+  TestConstCtor<2>(v, v, 2);
+  TestCtor<2>(v, v, 2);
+}
+
+TEST(SpanTest, Compare) {
+  int v[] = {10, 11};
+  int w[] = {10, 11};
+  int x[] = {11, 10, 11};
+  Span<int> sv = v;
+  Span<int> sw = w;
+  Span<int> sx = x;
+  EXPECT_TRUE(sv == sw);
+  EXPECT_FALSE(sv != sw);
+  EXPECT_FALSE(sv == sx);
+  EXPECT_TRUE(sv != sx);
+}
+
+TEST(SpanTest, CompareCompileTime) {
+  int v[] = {10, 11};
+  int w[] = {10, 11};
+  int x[] = {11, 10, 11};
+  Span<int, 2> sv = v;
+  Span<int, 2> sw = w;
+  Span<int, 3> sx = x;
+  EXPECT_TRUE(sv == sw);
+  EXPECT_FALSE(sv != sw);
+  EXPECT_FALSE(sv == sx);
+  EXPECT_TRUE(sv != sx);
 }
 
 TEST(SpanTest, MakeSpan) {
   std::vector<int> v = {100, 200, 300};
+  EXPECT_TRUE(IsRuntimeSized(MakeSpan(v)));
   TestCtor(MakeSpan(v), v.data(), v.size());
   TestCtor(MakeSpan(v.data(), v.size()), v.data(), v.size());
-  TestConstCtor(MakeSpan(v.data(), v.size()), v.data(), v.size());
-  TestConstCtor(MakeSpan(v), v.data(), v.size());
+  TestConstCtor<dynamic_extent>(MakeSpan(v.data(), v.size()), v.data(),
+                                v.size());
+  TestConstCtor<dynamic_extent>(MakeSpan(v), v.data(), v.size());
+}
+
+TEST(SpanTest, MakeSpanCompileTime) {
+  int v[3] = {100, 200, 300};
+  EXPECT_FALSE(IsRuntimeSized(MakeSpan(v)));
+  TestCtor(MakeSpan(v), v, 3);
+  TestConstCtor<3>(MakeSpan(v), v, 3);
 }
 
 TEST(SpanTest, MakeConstSpan) {
   std::vector<int> v = {100, 200, 300};
+  EXPECT_TRUE(IsRuntimeSized(MakeConstSpan(v)));
   TestConstCtor(MakeConstSpan(v), v.data(), v.size());
   TestConstCtor(MakeConstSpan(v.data(), v.size()), v.data(), v.size());
   // But not:
   // TestConstCtor(MakeSpan(v), v.data(), v.size());
 }
 
+TEST(SpanTest, MakeConstSpanCompileTime) {
+  int v[3] = {100, 200, 300};
+  EXPECT_FALSE(IsRuntimeSized(MakeConstSpan(v)));
+  TestConstCtor(MakeConstSpan(v), v, 3);
+  // But not:
+  // TestConstCtor(MakeSpan(v), v.data(), v.size());
+}
+
 TEST(SpanTest, Accessor) {
   std::vector<int> v({42, 23, 5, 101, 80});
   Span<int> s(v);
@@ -86,15 +180,64 @@
   EXPECT_EQ(s.end(), v.data() + v.size());
 }
 
+TEST(SpanTest, AccessorCompiletime) {
+  std::vector<int> v({42, 23, 5, 101, 80});
+  Span<int, 5> s(v.data(), v.size());
+  for (size_t i = 0; i < s.size(); ++i) {
+    EXPECT_EQ(s[i], v[i]);
+    EXPECT_EQ(s.at(i), v.at(i));
+  }
+  EXPECT_EQ(s.begin(), v.data());
+  EXPECT_EQ(s.end(), v.data() + v.size());
+}
+
 TEST(SpanTest, ConstExpr) {
   static constexpr int v[] = {1, 2, 3, 4};
   constexpr bssl::Span<const int> span1(v);
   static_assert(span1.size() == 4u, "wrong size");
+  static_assert(IsRuntimeSized(span1), "unexpectedly compile-time sized");
   constexpr bssl::Span<const int> span2 = MakeConstSpan(v);
   static_assert(span2.size() == 4u, "wrong size");
+  static_assert(IsRuntimeSized(span2), "unexpectedly compile-time sized");
   static_assert(span2.subspan(1).size() == 3u, "wrong size");
+  static_assert(IsRuntimeSized(span2.subspan(1)),
+                "unexpectedly compile-time sized");
+  static_assert(IsRuntimeSized(span2.subspan<1>()),
+                "unexpectedly compile-time sized");
   static_assert(span2.first(1).size() == 1u, "wrong size");
+  static_assert(IsRuntimeSized(span2.first(1)),
+                "unexpectedly compile-time sized");
+  static_assert(!IsRuntimeSized(span2.first<1>()),
+                "unexpectedly runtime sized");
   static_assert(span2.last(1).size() == 1u, "wrong size");
+  static_assert(IsRuntimeSized(span2.last(1)),
+                "unexpectedly compile-time sized");
+  static_assert(!IsRuntimeSized(span2.last<1>()), "unexpectedly runtime sized");
+  static_assert(span2[0] == 1, "wrong value");
+}
+
+TEST(SpanTest, ConstExprCompileTime) {
+  static constexpr int v[] = {1, 2, 3, 4};
+  constexpr bssl::Span<const int, 4> span1(v);
+  static_assert(span1.size() == 4u, "wrong size");
+  static_assert(!IsRuntimeSized(span1), "unexpectedly runtime sized");
+  constexpr bssl::Span<const int, 4> span2 = MakeConstSpan(v);
+  static_assert(span2.size() == 4u, "wrong size");
+  static_assert(!IsRuntimeSized(span2), "unexpectedly runtime sized");
+  static_assert(span2.subspan(1).size() == 3u, "wrong size");
+  static_assert(IsRuntimeSized(span2.subspan(1)),
+                "unexpectedly compile-time sized");
+  static_assert(!IsRuntimeSized(span2.subspan<1>()),
+                "unexpectedly runtime sized");
+  static_assert(span2.first(1).size() == 1u, "wrong size");
+  static_assert(IsRuntimeSized(span2.first(1)),
+                "unexpectedly compile-time sized");
+  static_assert(!IsRuntimeSized(span2.first<1>()),
+                "unexpectedly runtime sized");
+  static_assert(span2.last(1).size() == 1u, "wrong size");
+  static_assert(IsRuntimeSized(span2.last(1)),
+                "unexpectedly compile-time sized");
+  static_assert(!IsRuntimeSized(span2.last<1>()), "unexpectedly runtime sized");
   static_assert(span2[0] == 1, "wrong value");
 }
 
@@ -115,5 +258,22 @@
   EXPECT_DEATH_IF_SUPPORTED(empty.back(), "");
 }
 
+TEST(SpanDeathTest, BoundsChecksCompileTime) {
+  // Make an array that's larger than we need, so that a failure to bounds check
+  // won't crash.
+  const int v[] = {1, 2, 3, 4};
+  Span<const int, 3> span(v, 3);
+  // Out of bounds access.
+  EXPECT_DEATH_IF_SUPPORTED(span[3], "");
+  EXPECT_DEATH_IF_SUPPORTED(span.subspan(4), "");
+  EXPECT_DEATH_IF_SUPPORTED(span.first(4), "");
+  EXPECT_DEATH_IF_SUPPORTED(span.last(4), "");
+  // Accessing an empty span.
+  Span<const int, 0> empty(v, 0);
+  EXPECT_DEATH_IF_SUPPORTED(empty[0], "");
+  EXPECT_DEATH_IF_SUPPORTED(empty.front(), "");
+  EXPECT_DEATH_IF_SUPPORTED(empty.back(), "");
+}
+
 }  // namespace
 BSSL_NAMESPACE_END
diff --git a/ssl/ssl_cipher.cc b/ssl/ssl_cipher.cc
index d81d00a..227daaf 100644
--- a/ssl/ssl_cipher.cc
+++ b/ssl/ssl_cipher.cc
@@ -1346,7 +1346,7 @@
   return TLS1_2_VERSION;
 }
 
-static const char *kUnknownCipher = "(NONE)";
+static const char *const kUnknownCipher = "(NONE)";
 
 // return the actual cipher being used
 const char *SSL_CIPHER_get_name(const SSL_CIPHER *cipher) {
@@ -1573,6 +1573,6 @@
 }
 
 size_t SSL_get_all_standard_cipher_names(const char **out, size_t max_out) {
-  return GetAllNames(out, max_out, Span<const char *>(),
+  return GetAllNames(out, max_out, Span<const char *const>(),
                      &SSL_CIPHER::standard_name, Span(kCiphers));
 }
diff --git a/ssl/ssl_key_share.cc b/ssl/ssl_key_share.cc
index fcf7643..d155b55 100644
--- a/ssl/ssl_key_share.cc
+++ b/ssl/ssl_key_share.cc
@@ -533,6 +533,6 @@
 }
 
 size_t SSL_get_all_group_names(const char **out, size_t max_out) {
-  return GetAllNames(out, max_out, Span<const char *>(), &NamedGroup::name,
+  return GetAllNames(out, max_out, Span<const char *const>(), &NamedGroup::name,
                      Span(kNamedGroups));
 }
diff --git a/ssl/ssl_privkey.cc b/ssl/ssl_privkey.cc
index 74a330a..41c1cb1 100644
--- a/ssl/ssl_privkey.cc
+++ b/ssl/ssl_privkey.cc
@@ -506,9 +506,9 @@
 }
 
 size_t SSL_get_all_signature_algorithm_names(const char **out, size_t max_out) {
-  const char *kPredefinedNames[] = {"ecdsa_sha256", "ecdsa_sha384",
-                                    "ecdsa_sha512"};
-  return GetAllNames(out, max_out, kPredefinedNames,
+  const char *const kPredefinedNames[] = {"ecdsa_sha256", "ecdsa_sha384",
+                                          "ecdsa_sha512"};
+  return GetAllNames(out, max_out, Span(kPredefinedNames),
                      &SignatureAlgorithmName::name,
                      Span(kSignatureAlgorithmNames));
 }
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index efb4bce..659efbd 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -10932,7 +10932,8 @@
         // record_size_limit
         0x00, 0x1c, 0x00, 0x02, 0x40, 0x01};
 
-    auto in = dtls ? Span(kClientHelloDTLS) : Span(kClientHelloTLS);
+    auto in = dtls ? Span<const uint8_t>(kClientHelloDTLS)
+                   : Span<const uint8_t>(kClientHelloTLS);
     SSL_CLIENT_HELLO client_hello;
     ASSERT_TRUE(
         SSL_parse_client_hello(ssl.get(), &client_hello, in.data(), in.size()));
diff --git a/ssl/ssl_versions.cc b/ssl/ssl_versions.cc
index 0bbeed8..1a772f3 100644
--- a/ssl/ssl_versions.cc
+++ b/ssl/ssl_versions.cc
@@ -90,7 +90,7 @@
 // The following functions map between API versions and wire versions. The
 // public API works on wire versions.
 
-static const char *kUnknownVersion = "unknown";
+static const char *const kUnknownVersion = "unknown";
 
 struct VersionInfo {
   uint16_t version;