Просмотр исходного кода

Use more compact storage for impl lookup buckets. (#4351)

If the bucket is of size zero or one, which is expected to be the common
case, then store it directly. For the remaining cases, use a side table
of lookup buckets.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Richard Smith 1 год назад
Родитель
Сommit
9d5ec52232

+ 3 - 3
toolchain/check/handle_impl.cpp

@@ -279,9 +279,9 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
       {.self_id = self_type_id, .constraint_id = constraint_type_id}};
 
   // Add the impl declaration.
-  auto& lookup_bucket = context.impls().GetOrAddLookupBucket(
+  auto lookup_bucket_ref = context.impls().GetOrAddLookupBucket(
       impl_info.self_id, impl_info.constraint_id);
-  for (auto prev_impl_id : lookup_bucket) {
+  for (auto prev_impl_id : lookup_bucket_ref) {
     if (MergeImplRedecl(context, impl_info, prev_impl_id)) {
       impl_decl.impl_id = prev_impl_id;
       break;
@@ -292,7 +292,7 @@ static auto BuildImplDecl(Context& context, Parse::AnyImplDeclId node_id,
   if (!impl_decl.impl_id.is_valid()) {
     impl_info.generic_id = FinishGenericDecl(context, impl_decl_id);
     impl_decl.impl_id = context.impls().Add(impl_info);
-    lookup_bucket.push_back(impl_decl.impl_id);
+    lookup_bucket_ref.push_back(impl_decl.impl_id);
   } else {
     FinishGenericRedecl(context, impl_decl_id,
                         context.impls().Get(impl_decl.impl_id).generic_id);

+ 192 - 65
toolchain/check/testdata/impl/no_prelude/generic_redeclaration.carbon

@@ -12,17 +12,24 @@
 
 library "[[@TEST_NAME]]";
 
+interface Interface {}
+
 interface I {}
 interface J {}
 interface K {}
+interface L {}
 
-impl forall [T:! I] T as K;
-impl forall [T:! J] T as K;
+impl forall [T:! I] T as Interface;
+impl forall [T:! J] T as Interface;
+impl forall [T:! K] T as Interface;
+impl forall [T:! L] T as Interface;
 
 // These are two different impls, so this is not a redefinition, even though the
 // self type and constraint type are the same.
-impl forall [T:! I] T as K {}
-impl forall [T:! J] T as K {}
+impl forall [T:! I] T as Interface {}
+impl forall [T:! J] T as Interface {}
+impl forall [T:! K] T as Interface {}
+impl forall [T:! L] T as Interface {}
 
 // --- fail_same_self_and_interface_redefined.carbon
 
@@ -59,74 +66,128 @@ impl (C, C).0 as I {}
 // CHECK:STDOUT: --- same_self_and_interface.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {
-// CHECK:STDOUT:   %.1: type = interface_type @I [template]
+// CHECK:STDOUT:   %.1: type = interface_type @Interface [template]
 // CHECK:STDOUT:   %Self.1: %.1 = bind_symbolic_name Self 0 [symbolic]
-// CHECK:STDOUT:   %.2: type = interface_type @J [template]
+// CHECK:STDOUT:   %.2: type = interface_type @I [template]
 // CHECK:STDOUT:   %Self.2: %.2 = bind_symbolic_name Self 0 [symbolic]
-// CHECK:STDOUT:   %.3: type = interface_type @K [template]
+// CHECK:STDOUT:   %.3: type = interface_type @J [template]
 // CHECK:STDOUT:   %Self.3: %.3 = bind_symbolic_name Self 0 [symbolic]
-// CHECK:STDOUT:   %T.1: %.1 = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %T.2: %.2 = bind_symbolic_name T 0 [symbolic]
-// CHECK:STDOUT:   %.4: type = tuple_type () [template]
-// CHECK:STDOUT:   %.5: <witness> = interface_witness () [template]
+// CHECK:STDOUT:   %.4: type = interface_type @K [template]
+// CHECK:STDOUT:   %Self.4: %.4 = bind_symbolic_name Self 0 [symbolic]
+// CHECK:STDOUT:   %.5: type = interface_type @L [template]
+// CHECK:STDOUT:   %Self.5: %.5 = bind_symbolic_name Self 0 [symbolic]
+// CHECK:STDOUT:   %T.1: %.2 = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %T.2: %.3 = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %T.3: %.4 = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %T.4: %.5 = bind_symbolic_name T 0 [symbolic]
+// CHECK:STDOUT:   %.6: type = tuple_type () [template]
+// CHECK:STDOUT:   %.7: <witness> = interface_witness () [template]
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: file {
 // CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Interface = %Interface.decl
 // CHECK:STDOUT:     .I = %I.decl
 // CHECK:STDOUT:     .J = %J.decl
 // CHECK:STDOUT:     .K = %K.decl
+// CHECK:STDOUT:     .L = %L.decl
 // CHECK:STDOUT:   }
-// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%.1] {} {}
-// CHECK:STDOUT:   %J.decl: type = interface_decl @J [template = constants.%.2] {} {}
-// CHECK:STDOUT:   %K.decl: type = interface_decl @K [template = constants.%.3] {} {}
+// CHECK:STDOUT:   %Interface.decl: type = interface_decl @Interface [template = constants.%.1] {} {}
+// CHECK:STDOUT:   %I.decl: type = interface_decl @I [template = constants.%.2] {} {}
+// CHECK:STDOUT:   %J.decl: type = interface_decl @J [template = constants.%.3] {} {}
+// CHECK:STDOUT:   %K.decl: type = interface_decl @K [template = constants.%.4] {} {}
+// CHECK:STDOUT:   %L.decl: type = interface_decl @L [template = constants.%.5] {} {}
 // CHECK:STDOUT:   impl_decl @impl.1 [template] {
-// CHECK:STDOUT:     %T.patt: %.1 = symbolic_binding_pattern T 0
+// CHECK:STDOUT:     %T.patt: %.2 = symbolic_binding_pattern T 0
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%.1]
-// CHECK:STDOUT:     %T.param: %.1 = param T, runtime_param<invalid>
-// CHECK:STDOUT:     %T.loc8: %.1 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T.1)]
-// CHECK:STDOUT:     %T.ref: %.1 = name_ref T, %T.loc8 [symbolic = %T.1 (constants.%T.1)]
-// CHECK:STDOUT:     %.loc8_21.1: type = facet_type_access %T.ref [symbolic = %T.1 (constants.%T.1)]
-// CHECK:STDOUT:     %.loc8_21.2: type = converted %T.ref, %.loc8_21.1 [symbolic = %T.1 (constants.%T.1)]
-// CHECK:STDOUT:     %K.ref: type = name_ref K, file.%K.decl [template = constants.%.3]
+// CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%.2]
+// CHECK:STDOUT:     %T.param: %.2 = param T, runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc11: %.2 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T.1)]
+// CHECK:STDOUT:     %T.ref: %.2 = name_ref T, %T.loc11 [symbolic = %T.1 (constants.%T.1)]
+// CHECK:STDOUT:     %.loc11_21.1: type = facet_type_access %T.ref [symbolic = %T.1 (constants.%T.1)]
+// CHECK:STDOUT:     %.loc11_21.2: type = converted %T.ref, %.loc11_21.1 [symbolic = %T.1 (constants.%T.1)]
+// CHECK:STDOUT:     %Interface.ref: type = name_ref Interface, file.%Interface.decl [template = constants.%.1]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   impl_decl @impl.2 [template] {
-// CHECK:STDOUT:     %T.patt: %.2 = symbolic_binding_pattern T 0
+// CHECK:STDOUT:     %T.patt: %.3 = symbolic_binding_pattern T 0
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %J.ref: type = name_ref J, file.%J.decl [template = constants.%.2]
-// CHECK:STDOUT:     %T.param: %.2 = param T, runtime_param<invalid>
-// CHECK:STDOUT:     %T.loc9: %.2 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T.2)]
-// CHECK:STDOUT:     %T.ref: %.2 = name_ref T, %T.loc9 [symbolic = %T.1 (constants.%T.2)]
-// CHECK:STDOUT:     %.loc9_21.1: type = facet_type_access %T.ref [symbolic = %T.1 (constants.%T.2)]
-// CHECK:STDOUT:     %.loc9_21.2: type = converted %T.ref, %.loc9_21.1 [symbolic = %T.1 (constants.%T.2)]
-// CHECK:STDOUT:     %K.ref: type = name_ref K, file.%K.decl [template = constants.%.3]
+// CHECK:STDOUT:     %J.ref: type = name_ref J, file.%J.decl [template = constants.%.3]
+// CHECK:STDOUT:     %T.param: %.3 = param T, runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc12: %.3 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T.2)]
+// CHECK:STDOUT:     %T.ref: %.3 = name_ref T, %T.loc12 [symbolic = %T.1 (constants.%T.2)]
+// CHECK:STDOUT:     %.loc12_21.1: type = facet_type_access %T.ref [symbolic = %T.1 (constants.%T.2)]
+// CHECK:STDOUT:     %.loc12_21.2: type = converted %T.ref, %.loc12_21.1 [symbolic = %T.1 (constants.%T.2)]
+// CHECK:STDOUT:     %Interface.ref: type = name_ref Interface, file.%Interface.decl [template = constants.%.1]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   impl_decl @impl.3 [template] {
-// CHECK:STDOUT:     %T.patt: %.1 = symbolic_binding_pattern T 0
+// CHECK:STDOUT:     %T.patt: %.4 = symbolic_binding_pattern T 0
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%.1]
-// CHECK:STDOUT:     %T.param: %.1 = param T, runtime_param<invalid>
-// CHECK:STDOUT:     %T.loc13: %.1 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T.1)]
-// CHECK:STDOUT:     %T.ref: %.1 = name_ref T, %T.loc13 [symbolic = %T.1 (constants.%T.1)]
-// CHECK:STDOUT:     %.loc13_21.1: type = facet_type_access %T.ref [symbolic = %T.1 (constants.%T.1)]
-// CHECK:STDOUT:     %.loc13_21.2: type = converted %T.ref, %.loc13_21.1 [symbolic = %T.1 (constants.%T.1)]
-// CHECK:STDOUT:     %K.ref: type = name_ref K, file.%K.decl [template = constants.%.3]
+// CHECK:STDOUT:     %K.ref: type = name_ref K, file.%K.decl [template = constants.%.4]
+// CHECK:STDOUT:     %T.param: %.4 = param T, runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc13: %.4 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T.3)]
+// CHECK:STDOUT:     %T.ref: %.4 = name_ref T, %T.loc13 [symbolic = %T.1 (constants.%T.3)]
+// CHECK:STDOUT:     %.loc13_21.1: type = facet_type_access %T.ref [symbolic = %T.1 (constants.%T.3)]
+// CHECK:STDOUT:     %.loc13_21.2: type = converted %T.ref, %.loc13_21.1 [symbolic = %T.1 (constants.%T.3)]
+// CHECK:STDOUT:     %Interface.ref: type = name_ref Interface, file.%Interface.decl [template = constants.%.1]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT:   impl_decl @impl.4 [template] {
+// CHECK:STDOUT:     %T.patt: %.5 = symbolic_binding_pattern T 0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %L.ref: type = name_ref L, file.%L.decl [template = constants.%.5]
+// CHECK:STDOUT:     %T.param: %.5 = param T, runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc14: %.5 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T.4)]
+// CHECK:STDOUT:     %T.ref: %.5 = name_ref T, %T.loc14 [symbolic = %T.1 (constants.%T.4)]
+// CHECK:STDOUT:     %.loc14_21.1: type = facet_type_access %T.ref [symbolic = %T.1 (constants.%T.4)]
+// CHECK:STDOUT:     %.loc14_21.2: type = converted %T.ref, %.loc14_21.1 [symbolic = %T.1 (constants.%T.4)]
+// CHECK:STDOUT:     %Interface.ref: type = name_ref Interface, file.%Interface.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   impl_decl @impl.5 [template] {
 // CHECK:STDOUT:     %T.patt: %.2 = symbolic_binding_pattern T 0
 // CHECK:STDOUT:   } {
-// CHECK:STDOUT:     %J.ref: type = name_ref J, file.%J.decl [template = constants.%.2]
+// CHECK:STDOUT:     %I.ref: type = name_ref I, file.%I.decl [template = constants.%.2]
 // CHECK:STDOUT:     %T.param: %.2 = param T, runtime_param<invalid>
-// CHECK:STDOUT:     %T.loc14: %.2 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T.2)]
-// CHECK:STDOUT:     %T.ref: %.2 = name_ref T, %T.loc14 [symbolic = %T.1 (constants.%T.2)]
-// CHECK:STDOUT:     %.loc14_21.1: type = facet_type_access %T.ref [symbolic = %T.1 (constants.%T.2)]
-// CHECK:STDOUT:     %.loc14_21.2: type = converted %T.ref, %.loc14_21.1 [symbolic = %T.1 (constants.%T.2)]
-// CHECK:STDOUT:     %K.ref: type = name_ref K, file.%K.decl [template = constants.%.3]
+// CHECK:STDOUT:     %T.loc18: %.2 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T.1)]
+// CHECK:STDOUT:     %T.ref: %.2 = name_ref T, %T.loc18 [symbolic = %T.1 (constants.%T.1)]
+// CHECK:STDOUT:     %.loc18_21.1: type = facet_type_access %T.ref [symbolic = %T.1 (constants.%T.1)]
+// CHECK:STDOUT:     %.loc18_21.2: type = converted %T.ref, %.loc18_21.1 [symbolic = %T.1 (constants.%T.1)]
+// CHECK:STDOUT:     %Interface.ref: type = name_ref Interface, file.%Interface.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   impl_decl @impl.6 [template] {
+// CHECK:STDOUT:     %T.patt: %.3 = symbolic_binding_pattern T 0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %J.ref: type = name_ref J, file.%J.decl [template = constants.%.3]
+// CHECK:STDOUT:     %T.param: %.3 = param T, runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc19: %.3 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T.2)]
+// CHECK:STDOUT:     %T.ref: %.3 = name_ref T, %T.loc19 [symbolic = %T.1 (constants.%T.2)]
+// CHECK:STDOUT:     %.loc19_21.1: type = facet_type_access %T.ref [symbolic = %T.1 (constants.%T.2)]
+// CHECK:STDOUT:     %.loc19_21.2: type = converted %T.ref, %.loc19_21.1 [symbolic = %T.1 (constants.%T.2)]
+// CHECK:STDOUT:     %Interface.ref: type = name_ref Interface, file.%Interface.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   impl_decl @impl.7 [template] {
+// CHECK:STDOUT:     %T.patt: %.4 = symbolic_binding_pattern T 0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %K.ref: type = name_ref K, file.%K.decl [template = constants.%.4]
+// CHECK:STDOUT:     %T.param: %.4 = param T, runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc20: %.4 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T.3)]
+// CHECK:STDOUT:     %T.ref: %.4 = name_ref T, %T.loc20 [symbolic = %T.1 (constants.%T.3)]
+// CHECK:STDOUT:     %.loc20_21.1: type = facet_type_access %T.ref [symbolic = %T.1 (constants.%T.3)]
+// CHECK:STDOUT:     %.loc20_21.2: type = converted %T.ref, %.loc20_21.1 [symbolic = %T.1 (constants.%T.3)]
+// CHECK:STDOUT:     %Interface.ref: type = name_ref Interface, file.%Interface.decl [template = constants.%.1]
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   impl_decl @impl.8 [template] {
+// CHECK:STDOUT:     %T.patt: %.5 = symbolic_binding_pattern T 0
+// CHECK:STDOUT:   } {
+// CHECK:STDOUT:     %L.ref: type = name_ref L, file.%L.decl [template = constants.%.5]
+// CHECK:STDOUT:     %T.param: %.5 = param T, runtime_param<invalid>
+// CHECK:STDOUT:     %T.loc21: %.5 = bind_symbolic_name T 0, %T.param [symbolic = %T.1 (constants.%T.4)]
+// CHECK:STDOUT:     %T.ref: %.5 = name_ref T, %T.loc21 [symbolic = %T.1 (constants.%T.4)]
+// CHECK:STDOUT:     %.loc21_21.1: type = facet_type_access %T.ref [symbolic = %T.1 (constants.%T.4)]
+// CHECK:STDOUT:     %.loc21_21.2: type = converted %T.ref, %.loc21_21.1 [symbolic = %T.1 (constants.%T.4)]
+// CHECK:STDOUT:     %Interface.ref: type = name_ref Interface, file.%Interface.decl [template = constants.%.1]
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: interface @I {
+// CHECK:STDOUT: interface @Interface {
 // CHECK:STDOUT:   %Self: %.1 = bind_symbolic_name Self 0 [symbolic = constants.%Self.1]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
@@ -134,7 +195,7 @@ impl (C, C).0 as I {}
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: interface @J {
+// CHECK:STDOUT: interface @I {
 // CHECK:STDOUT:   %Self: %.2 = bind_symbolic_name Self 0 [symbolic = constants.%Self.2]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
@@ -142,7 +203,7 @@ impl (C, C).0 as I {}
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: interface @K {
+// CHECK:STDOUT: interface @J {
 // CHECK:STDOUT:   %Self: %.3 = bind_symbolic_name Self 0 [symbolic = constants.%Self.3]
 // CHECK:STDOUT:
 // CHECK:STDOUT: !members:
@@ -150,37 +211,87 @@ impl (C, C).0 as I {}
 // CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: generic impl @impl.1(%T.loc8: %.1) {
-// CHECK:STDOUT:   %T.1: %.1 = bind_symbolic_name T 0 [symbolic = %T.1 (constants.%T.1)]
+// CHECK:STDOUT: interface @K {
+// CHECK:STDOUT:   %Self: %.4 = bind_symbolic_name Self 0 [symbolic = constants.%Self.4]
 // CHECK:STDOUT:
-// CHECK:STDOUT:   impl: %T.1 as %.3;
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: generic impl @impl.2(%T.loc9: %.2) {
-// CHECK:STDOUT:   %T.1: %.2 = bind_symbolic_name T 0 [symbolic = %T.1 (constants.%T.2)]
+// CHECK:STDOUT: interface @L {
+// CHECK:STDOUT:   %Self: %.5 = bind_symbolic_name Self 0 [symbolic = constants.%Self.5]
 // CHECK:STDOUT:
-// CHECK:STDOUT:   impl: %T.2 as %.3;
+// CHECK:STDOUT: !members:
+// CHECK:STDOUT:   .Self = %Self
+// CHECK:STDOUT:   witness = ()
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: generic impl @impl.3(%T.loc13: %.1) {
-// CHECK:STDOUT:   %T.1: %.1 = bind_symbolic_name T 0 [symbolic = %T.1 (constants.%T.1)]
+// CHECK:STDOUT: generic impl @impl.1(%T.loc11: %.2) {
+// CHECK:STDOUT:   %T.1: %.2 = bind_symbolic_name T 0 [symbolic = %T.1 (constants.%T.1)]
 // CHECK:STDOUT:
-// CHECK:STDOUT:   impl: %T.1 as %.3 {
-// CHECK:STDOUT:     %.loc13_28: <witness> = interface_witness () [template = constants.%.5]
+// CHECK:STDOUT:   impl: %T.1 as %.1;
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic impl @impl.2(%T.loc12: %.3) {
+// CHECK:STDOUT:   %T.1: %.3 = bind_symbolic_name T 0 [symbolic = %T.1 (constants.%T.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   impl: %T.2 as %.1;
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic impl @impl.3(%T.loc13: %.4) {
+// CHECK:STDOUT:   %T.1: %.4 = bind_symbolic_name T 0 [symbolic = %T.1 (constants.%T.3)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   impl: %T.3 as %.1;
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic impl @impl.4(%T.loc14: %.5) {
+// CHECK:STDOUT:   %T.1: %.5 = bind_symbolic_name T 0 [symbolic = %T.1 (constants.%T.4)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   impl: %T.4 as %.1;
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic impl @impl.5(%T.loc18: %.2) {
+// CHECK:STDOUT:   %T.1: %.2 = bind_symbolic_name T 0 [symbolic = %T.1 (constants.%T.1)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   impl: %T.1 as %.1 {
+// CHECK:STDOUT:     %.loc18_36: <witness> = interface_witness () [template = constants.%.7]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     witness = %.loc18_36
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic impl @impl.6(%T.loc19: %.3) {
+// CHECK:STDOUT:   %T.1: %.3 = bind_symbolic_name T 0 [symbolic = %T.1 (constants.%T.2)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   impl: %T.2 as %.1 {
+// CHECK:STDOUT:     %.loc19_36: <witness> = interface_witness () [template = constants.%.7]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   !members:
+// CHECK:STDOUT:     witness = %.loc19_36
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: generic impl @impl.7(%T.loc20: %.4) {
+// CHECK:STDOUT:   %T.1: %.4 = bind_symbolic_name T 0 [symbolic = %T.1 (constants.%T.3)]
+// CHECK:STDOUT:
+// CHECK:STDOUT:   impl: %T.3 as %.1 {
+// CHECK:STDOUT:     %.loc20_36: <witness> = interface_witness () [template = constants.%.7]
 // CHECK:STDOUT:
 // CHECK:STDOUT:   !members:
-// CHECK:STDOUT:     witness = %.loc13_28
+// CHECK:STDOUT:     witness = %.loc20_36
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: generic impl @impl.4(%T.loc14: %.2) {
-// CHECK:STDOUT:   %T.1: %.2 = bind_symbolic_name T 0 [symbolic = %T.1 (constants.%T.2)]
+// CHECK:STDOUT: generic impl @impl.8(%T.loc21: %.5) {
+// CHECK:STDOUT:   %T.1: %.5 = bind_symbolic_name T 0 [symbolic = %T.1 (constants.%T.4)]
 // CHECK:STDOUT:
-// CHECK:STDOUT:   impl: %T.2 as %.3 {
-// CHECK:STDOUT:     %.loc14_28: <witness> = interface_witness () [template = constants.%.5]
+// CHECK:STDOUT:   impl: %T.4 as %.1 {
+// CHECK:STDOUT:     %.loc21_36: <witness> = interface_witness () [template = constants.%.7]
 // CHECK:STDOUT:
 // CHECK:STDOUT:   !members:
-// CHECK:STDOUT:     witness = %.loc14_28
+// CHECK:STDOUT:     witness = %.loc21_36
 // CHECK:STDOUT:   }
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
@@ -192,14 +303,30 @@ impl (C, C).0 as I {}
 // CHECK:STDOUT:   %T.1 => constants.%T.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific @impl.3(constants.%T.1) {
+// CHECK:STDOUT: specific @impl.3(constants.%T.3) {
+// CHECK:STDOUT:   %T.1 => constants.%T.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @impl.4(constants.%T.4) {
+// CHECK:STDOUT:   %T.1 => constants.%T.4
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @impl.5(constants.%T.1) {
 // CHECK:STDOUT:   %T.1 => constants.%T.1
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
-// CHECK:STDOUT: specific @impl.4(constants.%T.2) {
+// CHECK:STDOUT: specific @impl.6(constants.%T.2) {
 // CHECK:STDOUT:   %T.1 => constants.%T.2
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: specific @impl.7(constants.%T.3) {
+// CHECK:STDOUT:   %T.1 => constants.%T.3
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: specific @impl.8(constants.%T.4) {
+// CHECK:STDOUT:   %T.1 => constants.%T.4
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: --- fail_same_self_and_interface_redefined.carbon
 // CHECK:STDOUT:
 // CHECK:STDOUT: constants {

+ 109 - 15
toolchain/sem_ir/impl.h

@@ -57,22 +57,111 @@ struct Impl : public EntityWithParamsBase,
 // A collection of `Impl`s, which can be accessed by the self type and
 // constraint implemented.
 class ImplStore {
+ private:
+  // An ID of either a single impl or a lookup bucket.
+  class ImplOrLookupBucketId : public IdBase {
+   private:
+    explicit constexpr ImplOrLookupBucketId(int index) : IdBase(index) {}
+
+   public:
+    // An explicitly invalid ID, corresponding to to ImplId::Invalid.
+    static const ImplOrLookupBucketId Invalid;
+
+    static auto ForImplId(ImplId impl_id) -> ImplOrLookupBucketId {
+      return ImplOrLookupBucketId(impl_id.index);
+    }
+
+    static auto ForBucket(int bucket) -> ImplOrLookupBucketId {
+      return ImplOrLookupBucketId(ImplId::InvalidIndex - bucket - 1);
+    }
+
+    // Returns whether this ID represents a bucket index, rather than an ImplId.
+    // An invalid ID is not a bucket index.
+    auto is_bucket() const { return index < ImplId::InvalidIndex; }
+
+    // Returns the bucket index represented by this ID. Requires is_bucket().
+    auto bucket() const -> int {
+      CARBON_CHECK(is_bucket());
+      return ImplId::InvalidIndex - index - 1;
+    }
+
+    // Returns the ImplId index represented by this ID. Requires !is_bucket().
+    auto impl_id() const -> ImplId {
+      CARBON_CHECK(!is_bucket());
+      return ImplId(index);
+    }
+  };
+
  public:
-  // TODO: Switch to something like TinyPtrVector. We expect it to be rare for
-  // there to be more than one ImplId per bucket.
-  using LookupBucket = llvm::SmallVector<ImplId, 1>;
-
-  // Returns the lookup bucket containing the list of impls with this self type
-  // and constraint, or adds a new bucket if this is the first time we've seen
-  // an impl of this kind. The lookup bucket should only include impls from the
-  // current file and its API file; impls from other files should not be added
-  // to it.
+  // A reference to an impl lookup bucket. This represents a list of impls with
+  // the same self and constraint type.
+  //
+  // The bucket is held indirectly as an `ImplOrLookupBucketId`, in one of three
+  // states:
+  //
+  //   - An invalid `ImplId` represents an empty bucket.
+  //   - A valid `ImplId` represents a bucket with exactly one impl. This is
+  //     expected to be by far the most common case.
+  //   - A lookup bucket index represents an index within the `ImplStore`'s
+  //     array of variable-sized lookup buckets.
+  class LookupBucketRef {
+   public:
+    LookupBucketRef(ImplStore& store, ImplOrLookupBucketId& id)
+        : store_(&store), id_(&id), single_id_storage_(ImplId::Invalid) {
+      if (!id.is_bucket()) {
+        single_id_storage_ = id.impl_id();
+      }
+    }
+
+    auto begin() const -> const ImplId* {
+      if (id_->is_bucket()) {
+        return store_->lookup_buckets_[id_->bucket()].begin();
+      }
+      return &single_id_storage_;
+    }
+
+    auto end() const -> const ImplId* {
+      if (id_->is_bucket()) {
+        return store_->lookup_buckets_[id_->bucket()].end();
+      }
+      return &single_id_storage_ + (id_->is_valid() ? 1 : 0);
+    }
+
+    // Adds an impl to this lookup bucket. Only impls from the current file and
+    // its API file should be added in this way. Impls from other files do not
+    // need to be found by impl redeclaration lookup so should not be added.
+    auto push_back(ImplId impl_id) -> void {
+      if (!id_->is_valid()) {
+        *id_ = ImplOrLookupBucketId::ForImplId(impl_id);
+        single_id_storage_ = impl_id;
+      } else if (!id_->is_bucket()) {
+        auto first_id = id_->impl_id();
+        *id_ = ImplOrLookupBucketId::ForBucket(store_->lookup_buckets_.size());
+        store_->lookup_buckets_.push_back({first_id, impl_id});
+      } else {
+        store_->lookup_buckets_[id_->bucket()].push_back(impl_id);
+      }
+    }
+
+   private:
+    ImplStore* store_;
+    ImplOrLookupBucketId* id_;
+    // Storage for a single ImplId. Used to support iteration over the contents
+    // of the bucket when it contains a single ImplId.
+    ImplId single_id_storage_;
+  };
+
+  // Returns a reference to the lookup bucket containing the list of impls with
+  // this self type and constraint, or adds a new bucket if this is the first
+  // time we've seen an impl of this kind. The lookup bucket reference remains
+  // valid until this function is called again.
   auto GetOrAddLookupBucket(TypeId self_id, TypeId constraint_id)
-      -> LookupBucket& {
-    return lookup_
-        .Insert(std::pair{self_id, constraint_id},
-                [] { return LookupBucket(); })
-        .value();
+      -> LookupBucketRef {
+    return LookupBucketRef(
+        *this, lookup_
+                   .Insert(std::pair{self_id, constraint_id},
+                           [] { return ImplOrLookupBucketId::Invalid; })
+                   .value());
   }
 
   // Adds the specified impl to the store. Does not add it to impl lookup.
@@ -100,9 +189,14 @@ class ImplStore {
 
  private:
   ValueStore<ImplId> values_;
-  Map<std::pair<TypeId, TypeId>, LookupBucket> lookup_;
+  Map<std::pair<TypeId, TypeId>, ImplOrLookupBucketId> lookup_;
+  // Buckets with at least 2 entries, which will be rare; see LookupBucketRef.
+  llvm::SmallVector<llvm::SmallVector<ImplId, 2>> lookup_buckets_;
 };
 
+constexpr inline ImplStore::ImplOrLookupBucketId
+    ImplStore::ImplOrLookupBucketId::Invalid(InvalidIndex);
+
 }  // namespace Carbon::SemIR
 
 #endif  // CARBON_TOOLCHAIN_SEM_IR_IMPL_H_