瀏覽代碼

Insert a `value_of_initializer` after a call to `ImplicitAs` where possible. (#4473)

This avoids going through memory when performing an implicit conversion
to a type with a by-copy value representation, such as i32.
Richard Smith 1 年之前
父節點
當前提交
26d7717d60

+ 36 - 13
toolchain/check/convert.cpp

@@ -674,6 +674,32 @@ static auto IsValidExprCategoryForConversionTarget(
   }
 }
 
+// Determines whether we can pull a value directly out of an initializing
+// expression of type `type_id` to initialize a target of type `type_id` and
+// kind `target_kind`.
+static auto CanUseValueOfInitializer(const SemIR::File& sem_ir,
+                                     SemIR::TypeId type_id,
+                                     ConversionTarget::Kind target_kind)
+    -> bool {
+  if (!IsValidExprCategoryForConversionTarget(SemIR::ExprCategory::Value,
+                                              target_kind)) {
+    // We don't want a value expression.
+    return false;
+  }
+
+  if (SemIR::InitRepr::ForType(sem_ir, type_id).kind !=
+      SemIR::InitRepr::ByCopy) {
+    // The initializing expression doesn't contain a copy of a value.
+    return false;
+  }
+
+  // If the value representation is a copy of the object representation, we
+  // already have a value of the right form and can use that value directly.
+  auto value_rep = SemIR::ValueRepr::ForType(sem_ir, type_id);
+  return value_rep.kind == SemIR::ValueRepr::Copy &&
+         value_rep.type_id == type_id;
+}
+
 // Returns the non-adapter type that is compatible with the specified type.
 static auto GetCompatibleBaseType(Context& context, SemIR::TypeId type_id)
     -> SemIR::TypeId {
@@ -738,19 +764,9 @@ static auto PerformBuiltinConversion(Context& context, SemIR::LocId loc_id,
     // If the source is an initializing expression, we may be able to pull a
     // value right out of it.
     if (value_cat == SemIR::ExprCategory::Initializing &&
-        IsValidExprCategoryForConversionTarget(SemIR::ExprCategory::Value,
-                                               target.kind) &&
-        SemIR::InitRepr::ForType(sem_ir, value_type_id).kind ==
-            SemIR::InitRepr::ByCopy) {
-      auto value_rep = SemIR::ValueRepr::ForType(sem_ir, value_type_id);
-      if (value_rep.kind == SemIR::ValueRepr::Copy &&
-          value_rep.type_id == value_type_id) {
-        // The initializer produces an object representation by copy, and the
-        // value representation is a copy of the object representation, so we
-        // already have a value of the right form.
-        return context.AddInst<SemIR::ValueOfInitializer>(
-            loc_id, {.type_id = value_type_id, .init_id = value_id});
-      }
+        CanUseValueOfInitializer(sem_ir, value_type_id, target.kind)) {
+      return context.AddInst<SemIR::ValueOfInitializer>(
+          loc_id, {.type_id = value_type_id, .init_id = value_id});
     }
   }
 
@@ -998,6 +1014,13 @@ auto Convert(Context& context, SemIR::LocId loc_id, SemIR::InstId expr_id,
                                          : ImplicitAsConversionFailure,
                                      expr_id, target.type_id);
     });
+
+    // Pull a value directly out of the initializer if possible and wanted.
+    if (expr_id != SemIR::InstId::BuiltinError &&
+        CanUseValueOfInitializer(sem_ir, target.type_id, target.kind)) {
+      expr_id = context.AddInst<SemIR::ValueOfInitializer>(
+          loc_id, {.type_id = target.type_id, .init_id = expr_id});
+    }
   }
 
   // Track that we performed a type conversion, if we did so.

+ 3 - 4
toolchain/check/testdata/as/overloaded.carbon

@@ -246,10 +246,9 @@ let n: i32 = ((4 as i32) as X) as i32;
 // CHECK:STDOUT:   %.loc23_32.3: <bound method> = bound_method %.loc23_26.6, %.loc23_32.2
 // CHECK:STDOUT:   %.loc23_26.7: %X = bind_value %.loc23_26.6
 // CHECK:STDOUT:   %Convert.call.loc23_32: init i32 = call %.loc23_32.3(%.loc23_26.7)
-// CHECK:STDOUT:   %.loc23_32.4: init i32 = converted %.loc23_26.5, %Convert.call.loc23_32
-// CHECK:STDOUT:   %.loc23_38.1: i32 = value_of_initializer %.loc23_32.4
-// CHECK:STDOUT:   %.loc23_38.2: i32 = converted %.loc23_32.4, %.loc23_38.1
-// CHECK:STDOUT:   %n: i32 = bind_name n, %.loc23_38.2
+// CHECK:STDOUT:   %.loc23_32.4: i32 = value_of_initializer %Convert.call.loc23_32
+// CHECK:STDOUT:   %.loc23_32.5: i32 = converted %.loc23_26.5, %.loc23_32.4
+// CHECK:STDOUT:   %n: i32 = bind_name n, %.loc23_32.5
 // CHECK:STDOUT:   return
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 5
toolchain/check/testdata/operators/overloaded/implicit_as.carbon

@@ -310,11 +310,9 @@ fn Test() {
 // CHECK:STDOUT:   %.loc30_18.5: <bound method> = bound_method %.loc30_18.3, %.loc30_18.4
 // CHECK:STDOUT:   %.loc30_18.6: %X = bind_value %.loc30_18.3
 // CHECK:STDOUT:   %Convert.call.loc30: init i32 = call %.loc30_18.5(%.loc30_18.6)
-// CHECK:STDOUT:   %.loc30_18.7: init i32 = converted %Source.call.loc30, %Convert.call.loc30
-// CHECK:STDOUT:   %.loc30_18.8: ref i32 = temporary_storage
-// CHECK:STDOUT:   %.loc30_18.9: ref i32 = temporary %.loc30_18.8, %.loc30_18.7
-// CHECK:STDOUT:   %.loc30_18.10: i32 = bind_value %.loc30_18.9
-// CHECK:STDOUT:   %Sink_i32.call: init %.1 = call %Sink_i32.ref(%.loc30_18.10)
+// CHECK:STDOUT:   %.loc30_18.7: i32 = value_of_initializer %Convert.call.loc30
+// CHECK:STDOUT:   %.loc30_18.8: i32 = converted %Source.call.loc30, %.loc30_18.7
+// CHECK:STDOUT:   %Sink_i32.call: init %.1 = call %Sink_i32.ref(%.loc30_18.8)
 // CHECK:STDOUT:   %Sink_X.ref: %Sink_X.type = name_ref Sink_X, file.%Sink_X.decl [template = constants.%Sink_X]
 // CHECK:STDOUT:   %Source.ref.loc31: %Source.type = name_ref Source, file.%Source.decl [template = constants.%Source]
 // CHECK:STDOUT:   %int.make_type_32: init type = call constants.%Int32() [template = i32]

+ 1 - 4
toolchain/lower/testdata/class/convert.carbon

@@ -46,10 +46,7 @@ fn DoIt() {
 // CHECK:STDOUT:   %.loc22_31.2.n = getelementptr inbounds nuw { i32 }, ptr %w.var, i32 0, i32 0, !dbg !13
 // CHECK:STDOUT:   call void @llvm.memcpy.p0.p0.i64(ptr align 4 %w.var, ptr align 4 @struct.loc22_32, i64 4, i1 false), !dbg !14
 // CHECK:STDOUT:   %Convert.call = call i32 @"_CConvert.IntWrapper.Main:ImplicitAs.Core"(ptr %w.var), !dbg !15
-// CHECK:STDOUT:   %.loc23_11.6.temp = alloca i32, align 4, !dbg !15
-// CHECK:STDOUT:   store i32 %Convert.call, ptr %.loc23_11.6.temp, align 4, !dbg !15
-// CHECK:STDOUT:   %.loc23_11.8 = load i32, ptr %.loc23_11.6.temp, align 4, !dbg !15
-// CHECK:STDOUT:   call void @_CConsume.Main(i32 %.loc23_11.8), !dbg !16
+// CHECK:STDOUT:   call void @_CConsume.Main(i32 %Convert.call), !dbg !16
 // CHECK:STDOUT:   ret void, !dbg !17
 // CHECK:STDOUT: }
 // CHECK:STDOUT: