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

Widen integer loads and stores to a multiple of 8 bits. (#5986)

This makes Carbon's loads and stores ABI-compatible with Clang's for
`bool` and `_BitInt(N)`.
Richard Smith 8 месяцев назад
Родитель
Сommit
742017c475

+ 46 - 1
toolchain/lower/function_context.cpp

@@ -318,6 +318,51 @@ auto FunctionContext::GetReturnTypeInfo(TypeInFile type)
   return result;
 }
 
+// Given a type used for an LLVM value, return the type that we use to store
+// that value in memory. This is the same type unless the type is a
+// non-multiple-of-8 integer type, which we explicitly widen to a multiple of 8
+// for Clang compatibility and to make our generated IR easier for LLVM to
+// handle.
+static auto GetWidenedMemoryType(llvm::Type* type) -> llvm::Type* {
+  if (auto* int_type = dyn_cast<llvm::IntegerType>(type)) {
+    auto width = llvm::alignToPowerOf2(int_type->getBitWidth(), 8);
+    if (width != int_type->getBitWidth()) {
+      return llvm::IntegerType::get(type->getContext(), width);
+    }
+  }
+  return type;
+}
+
+auto FunctionContext::LoadObject(TypeInFile type, llvm::Value* addr,
+                                 llvm::Twine name) -> llvm::Value* {
+  auto* llvm_type = GetType(type);
+  auto* load_type = GetWidenedMemoryType(llvm_type);
+
+  // TODO: Include alias and alignment information.
+  llvm::Value* value = builder().CreateLoad(load_type, addr, name);
+
+  if (load_type != llvm_type) {
+    value = builder().CreateTrunc(value, llvm_type);
+  }
+  return value;
+}
+
+auto FunctionContext::StoreObject(TypeInFile type, llvm::Value* value,
+                                  llvm::Value* addr) -> void {
+  // TODO: Include alias and alignment information.
+  auto* llvm_type = GetType(type);
+  CARBON_CHECK(value->getType() == llvm_type);
+
+  // Don't emit a store of `iN` if N is not a multiple of 8. See `LoadObject`.
+  auto* store_type = GetWidenedMemoryType(llvm_type);
+  if (store_type != llvm_type) {
+    // TODO: Should we consider creating a sext if the value is signed?
+    value = builder().CreateZExt(value, store_type);
+  }
+
+  builder().CreateStore(value, addr);
+}
+
 auto FunctionContext::CopyValue(TypeInFile type, SemIR::InstId source_id,
                                 SemIR::InstId dest_id) -> void {
   switch (GetValueRepr(type).repr.kind) {
@@ -326,7 +371,7 @@ auto FunctionContext::CopyValue(TypeInFile type, SemIR::InstId source_id,
     case SemIR::ValueRepr::None:
       break;
     case SemIR::ValueRepr::Copy:
-      builder().CreateStore(GetValue(source_id), GetValue(dest_id));
+      StoreObject(type, GetValue(source_id), GetValue(dest_id));
       break;
     case SemIR::ValueRepr::Pointer:
       CopyObject(type, source_id, dest_id);

+ 12 - 0
toolchain/lower/function_context.h

@@ -62,6 +62,10 @@ class FunctionContext {
   struct TypeInFile {
     const SemIR::File* file;
     SemIR::TypeId type_id;
+
+    auto GetPointeeType() -> TypeInFile {
+      return {.file = file, .type_id = file->GetPointeeType(type_id)};
+    }
   };
 
   // A value representation in a particular file. By convention, this represents
@@ -194,6 +198,14 @@ class FunctionContext {
   // Returns the debug location to associate with the specified instruction.
   auto GetDebugLoc(SemIR::InstId inst_id) -> llvm::DebugLoc;
 
+  // Emits a load of an object representation of type `type`.
+  auto LoadObject(TypeInFile type, llvm::Value* addr, llvm::Twine name = "")
+      -> llvm::Value*;
+
+  // Emits a store of an object representation of type `type`.
+  auto StoreObject(TypeInFile type, llvm::Value* value, llvm::Value* addr)
+      -> void;
+
   // After emitting an initializer `init_id`, finishes performing the
   // initialization of `dest_id` from that initializer. This is a no-op if the
   // initialization was performed in-place, and otherwise performs a store or a

+ 6 - 5
toolchain/lower/handle_aggregates.cpp

@@ -103,8 +103,8 @@ static auto GetAggregateElement(FunctionContext& context,
           // `elem_ptr` points to a value representation. Load it.
           auto result_type = context.GetTypeIdOfInst(result_inst_id);
           auto result_value_type = context.GetValueRepr(result_type).type();
-          return context.builder().CreateLoad(
-              context.GetType(result_value_type), elem_ptr, name + ".load");
+          return context.LoadObject(result_value_type, elem_ptr,
+                                    name + ".load");
         }
         case SemIR::ValueRepr::Custom:
           CARBON_FATAL(
@@ -265,10 +265,11 @@ static auto EmitAggregateValueRepr(FunctionContext& context,
       // Write the value representation to a local alloca so we can produce a
       // pointer to it as the value representation of the struct or tuple.
       auto* alloca = context.builder().CreateAlloca(llvm_value_rep_type);
-      for (auto [i, ref] :
+      for (auto [i, ref_id] :
            llvm::enumerate(context.sem_ir().inst_blocks().Get(refs_id))) {
-        context.builder().CreateStore(
-            context.GetValue(ref),
+        context.StoreObject(
+            context.GetValueRepr(context.GetTypeIdOfInst(ref_id)).type(),
+            context.GetValue(ref_id),
             context.builder().CreateStructGEP(llvm_value_rep_type, alloca, i));
       }
       return alloca;

+ 5 - 8
toolchain/lower/handle_call.cpp

@@ -409,17 +409,13 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
     case SemIR::BuiltinFunctionKind::FloatMulAssign:
     case SemIR::BuiltinFunctionKind::FloatDivAssign: {
       auto* lhs_ptr = context.GetValue(arg_ids[0]);
-      auto [lhs_type_file, lhs_type_id] = context.GetTypeIdOfInst(arg_ids[0]);
-      auto pointee_type_id = lhs_type_file->GetPointeeType(lhs_type_id);
-      // TODO: Factor out the code to create loads and stores, and include alias
-      // and alignment information.
-      auto* lhs_value = context.builder().CreateLoad(
-          context.GetFileContext(lhs_type_file).GetType(pointee_type_id),
-          lhs_ptr);
+      auto lhs_type = context.GetTypeIdOfInst(arg_ids[0]);
+      auto pointee_type = lhs_type.GetPointeeType();
+      auto* lhs_value = context.LoadObject(pointee_type, lhs_ptr);
       auto* result = CreateBinaryOperatorForBuiltin(
           context, inst_id, builtin_kind, lhs_value,
           context.GetValue(arg_ids[1]));
-      context.builder().CreateStore(result, lhs_ptr);
+      context.StoreObject(pointee_type, result, lhs_ptr);
       // TODO: Add a helper to get a "no value representation" value.
       context.SetLocal(inst_id,
                        llvm::PoisonValue::get(context.GetTypeOfInst(inst_id)));
@@ -535,6 +531,7 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
     // The vtable pointer is always at the start of the object in the Carbon
     // ABI, so a pointer to the object is a pointer to the vtable pointer - load
     // that to get a pointer to the vtable.
+    // TODO: Use `context.LoadObject`.
     auto* vtable =
         context.builder().CreateLoad(ptr_type, args.front(), "vtable");
     auto* i32_type = llvm::IntegerType::getInt32Ty(context.llvm_context());

+ 5 - 5
toolchain/lower/handle_expr_category.cpp

@@ -22,11 +22,11 @@ auto HandleInst(FunctionContext& context, SemIR::InstId inst_id,
       context.SetLocal(inst_id,
                        llvm::PoisonValue::get(context.GetType(inst_type)));
       break;
-    case SemIR::ValueRepr::Copy: {
-      auto* type = context.GetType(inst_type);
-      context.SetLocal(inst_id, context.builder().CreateLoad(
-                                    type, context.GetValue(inst.value_id)));
-    } break;
+    case SemIR::ValueRepr::Copy:
+      context.SetLocal(
+          inst_id,
+          context.LoadObject(inst_type, context.GetValue(inst.value_id)));
+      break;
     case SemIR::ValueRepr::Pointer:
       context.SetLocal(inst_id, context.GetValue(inst.value_id));
       break;

+ 5 - 4
toolchain/lower/testdata/array/iterate.carbon

@@ -90,8 +90,9 @@ fn F() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr i1 @_CHasValue.Optional.Core.b88d1103f417c6d4(ptr %self) !dbg !25 {
 // CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %self, i32 0, i32 0, !dbg !27
-// CHECK:STDOUT:   %1 = load i1, ptr %has_value, align 1, !dbg !27
-// CHECK:STDOUT:   ret i1 %1, !dbg !28
+// CHECK:STDOUT:   %1 = load i8, ptr %has_value, align 1, !dbg !27
+// CHECK:STDOUT:   %2 = trunc i8 %1 to i1, !dbg !27
+// CHECK:STDOUT:   ret i1 %2, !dbg !28
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr i32 @_CGet.Optional.Core.b88d1103f417c6d4(ptr %self) !dbg !29 {
@@ -109,13 +110,13 @@ fn F() {
 // CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %return, i32 0, i32 0, !dbg !37
 // CHECK:STDOUT:   %value1 = getelementptr inbounds nuw { i1, i32 }, ptr %return, i32 0, i32 1, !dbg !37
 // CHECK:STDOUT:   store i32 %value, ptr %value1, align 4, !dbg !37
-// CHECK:STDOUT:   store i1 true, ptr %has_value, align 1, !dbg !37
+// CHECK:STDOUT:   store i8 1, ptr %has_value, align 1, !dbg !37
 // CHECK:STDOUT:   ret void, !dbg !38
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr void @_CNone.Optional.Core.b88d1103f417c6d4(ptr sret({ i1, i32 }) %return) !dbg !39 {
 // CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %return, i32 0, i32 0, !dbg !40
-// CHECK:STDOUT:   store i1 false, ptr %has_value, align 1, !dbg !40
+// CHECK:STDOUT:   store i8 0, ptr %has_value, align 1, !dbg !40
 // CHECK:STDOUT:   ret void, !dbg !41
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 1 - 1
toolchain/lower/testdata/builtins/types.carbon

@@ -52,7 +52,7 @@ fn F() {
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %h.var), !dbg !11
 // CHECK:STDOUT:   store fp128 0xL00000000000000003FFF000000000000, ptr %h.var, align 16, !dbg !11
 // CHECK:STDOUT:   call void @llvm.lifetime.start.p0(ptr %b.var), !dbg !12
-// CHECK:STDOUT:   store i1 false, ptr %b.var, align 1, !dbg !12
+// CHECK:STDOUT:   store i8 0, ptr %b.var, align 1, !dbg !12
 // CHECK:STDOUT:   ret void, !dbg !13
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 3 - 2
toolchain/lower/testdata/class/generic.carbon

@@ -301,8 +301,9 @@ fn AccessTuple() -> (i32, i32, i32) {
 // CHECK:STDOUT: define linkonce_odr i1 @_CGetBool.C.Main.b88d1103f417c6d4(ptr %self) !dbg !27 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc6_16.1.v = getelementptr inbounds nuw { i1, i32 }, ptr %self, i32 0, i32 0, !dbg !28
-// CHECK:STDOUT:   %.loc6_16.2 = load i1, ptr %.loc6_16.1.v, align 1, !dbg !28
-// CHECK:STDOUT:   ret i1 %.loc6_16.2, !dbg !29
+// CHECK:STDOUT:   %.loc6_16.2 = load i8, ptr %.loc6_16.1.v, align 1, !dbg !28
+// CHECK:STDOUT:   %.loc6_16.21 = trunc i8 %.loc6_16.2 to i1, !dbg !28
+// CHECK:STDOUT:   ret i1 %.loc6_16.21, !dbg !29
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr i32 @_CGetT.C.Main.b88d1103f417c6d4(ptr %self) !dbg !30 {

+ 4 - 3
toolchain/lower/testdata/for/bindings.carbon

@@ -92,8 +92,9 @@ fn For() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr i1 @_CHasValue.Optional.Core.29d34654e802e24e(ptr %self) !dbg !18 {
 // CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, { i32, i32 } }, ptr %self, i32 0, i32 0, !dbg !20
-// CHECK:STDOUT:   %1 = load i1, ptr %has_value, align 1, !dbg !20
-// CHECK:STDOUT:   ret i1 %1, !dbg !21
+// CHECK:STDOUT:   %1 = load i8, ptr %has_value, align 1, !dbg !20
+// CHECK:STDOUT:   %2 = trunc i8 %1 to i1, !dbg !20
+// CHECK:STDOUT:   ret i1 %2, !dbg !21
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr void @_CGet.Optional.Core.29d34654e802e24e(ptr sret({ i32, i32 }) %return, ptr %self) !dbg !22 {
@@ -103,7 +104,7 @@ fn For() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr void @_CNone.Optional.Core.29d34654e802e24e(ptr sret({ i1, { i32, i32 } }) %return) !dbg !25 {
 // CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, { i32, i32 } }, ptr %return, i32 0, i32 0, !dbg !26
-// CHECK:STDOUT:   store i1 false, ptr %has_value, align 1, !dbg !26
+// CHECK:STDOUT:   store i8 0, ptr %has_value, align 1, !dbg !26
 // CHECK:STDOUT:   ret void, !dbg !27
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 5 - 4
toolchain/lower/testdata/for/break_continue.carbon

@@ -109,8 +109,9 @@ fn For() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr i1 @_CHasValue.Optional.Core.b88d1103f417c6d4(ptr %self) !dbg !34 {
 // CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %self, i32 0, i32 0, !dbg !36
-// CHECK:STDOUT:   %1 = load i1, ptr %has_value, align 1, !dbg !36
-// CHECK:STDOUT:   ret i1 %1, !dbg !37
+// CHECK:STDOUT:   %1 = load i8, ptr %has_value, align 1, !dbg !36
+// CHECK:STDOUT:   %2 = trunc i8 %1 to i1, !dbg !36
+// CHECK:STDOUT:   ret i1 %2, !dbg !37
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr i32 @_CGet.Optional.Core.b88d1103f417c6d4(ptr %self) !dbg !38 {
@@ -130,13 +131,13 @@ fn For() {
 // CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %return, i32 0, i32 0, !dbg !46
 // CHECK:STDOUT:   %value1 = getelementptr inbounds nuw { i1, i32 }, ptr %return, i32 0, i32 1, !dbg !46
 // CHECK:STDOUT:   store i32 %value, ptr %value1, align 4, !dbg !46
-// CHECK:STDOUT:   store i1 true, ptr %has_value, align 1, !dbg !46
+// CHECK:STDOUT:   store i8 1, ptr %has_value, align 1, !dbg !46
 // CHECK:STDOUT:   ret void, !dbg !47
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr void @_CNone.Optional.Core.b88d1103f417c6d4(ptr sret({ i1, i32 }) %return) !dbg !48 {
 // CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %return, i32 0, i32 0, !dbg !49
-// CHECK:STDOUT:   store i1 false, ptr %has_value, align 1, !dbg !49
+// CHECK:STDOUT:   store i8 0, ptr %has_value, align 1, !dbg !49
 // CHECK:STDOUT:   ret void, !dbg !50
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 5 - 4
toolchain/lower/testdata/for/for.carbon

@@ -97,8 +97,9 @@ fn For() {
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr i1 @_CHasValue.Optional.Core.b88d1103f417c6d4(ptr %self) !dbg !30 {
 // CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %self, i32 0, i32 0, !dbg !32
-// CHECK:STDOUT:   %1 = load i1, ptr %has_value, align 1, !dbg !32
-// CHECK:STDOUT:   ret i1 %1, !dbg !33
+// CHECK:STDOUT:   %1 = load i8, ptr %has_value, align 1, !dbg !32
+// CHECK:STDOUT:   %2 = trunc i8 %1 to i1, !dbg !32
+// CHECK:STDOUT:   ret i1 %2, !dbg !33
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr i32 @_CGet.Optional.Core.b88d1103f417c6d4(ptr %self) !dbg !34 {
@@ -118,13 +119,13 @@ fn For() {
 // CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %return, i32 0, i32 0, !dbg !42
 // CHECK:STDOUT:   %value1 = getelementptr inbounds nuw { i1, i32 }, ptr %return, i32 0, i32 1, !dbg !42
 // CHECK:STDOUT:   store i32 %value, ptr %value1, align 4, !dbg !42
-// CHECK:STDOUT:   store i1 true, ptr %has_value, align 1, !dbg !42
+// CHECK:STDOUT:   store i8 1, ptr %has_value, align 1, !dbg !42
 // CHECK:STDOUT:   ret void, !dbg !43
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define linkonce_odr void @_CNone.Optional.Core.b88d1103f417c6d4(ptr sret({ i1, i32 }) %return) !dbg !44 {
 // CHECK:STDOUT:   %has_value = getelementptr inbounds nuw { i1, i32 }, ptr %return, i32 0, i32 0, !dbg !45
-// CHECK:STDOUT:   store i1 false, ptr %has_value, align 1, !dbg !45
+// CHECK:STDOUT:   store i8 0, ptr %has_value, align 1, !dbg !45
 // CHECK:STDOUT:   ret void, !dbg !46
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 6 - 4
toolchain/lower/testdata/function/generic/call_recursive_reorder_more.carbon

@@ -83,14 +83,16 @@ fn M() {
 // CHECK:STDOUT:   %.loc39_14 = load ptr, ptr %ptr_bool.var, align 8, !dbg !21
 // CHECK:STDOUT:   %.loc39_24 = load ptr, ptr %ptr_i64.var, align 8, !dbg !22
 // CHECK:STDOUT:   %F.call.loc39 = call i32 @_CF.Main.9a89ac9e10448feb(i32 %.loc39_5, ptr %.loc39_14, ptr %.loc39_24, i32 0), !dbg !23
-// CHECK:STDOUT:   %.loc40_5 = load i1, ptr %val_bool.var, align 1, !dbg !24
+// CHECK:STDOUT:   %.loc40_5 = load i8, ptr %val_bool.var, align 1, !dbg !24
+// CHECK:STDOUT:   %.loc40_51 = trunc i8 %.loc40_5 to i1, !dbg !24
 // CHECK:STDOUT:   %.loc40_15 = load ptr, ptr %ptr_bool.var, align 8, !dbg !25
 // CHECK:STDOUT:   %.loc40_25 = load ptr, ptr %ptr_i16.var, align 8, !dbg !26
-// CHECK:STDOUT:   %F.call.loc40 = call i32 @_CF.Main.76a945443862cc1f(i1 %.loc40_5, ptr %.loc40_15, ptr %.loc40_25, i32 0), !dbg !27
+// CHECK:STDOUT:   %F.call.loc40 = call i32 @_CF.Main.76a945443862cc1f(i1 %.loc40_51, ptr %.loc40_15, ptr %.loc40_25, i32 0), !dbg !27
 // CHECK:STDOUT:   %.loc41_5 = load i64, ptr %val_i64.var, align 4, !dbg !28
-// CHECK:STDOUT:   %.loc41_14 = load i1, ptr %val_bool.var, align 1, !dbg !29
+// CHECK:STDOUT:   %.loc41_14 = load i8, ptr %val_bool.var, align 1, !dbg !29
+// CHECK:STDOUT:   %.loc41_142 = trunc i8 %.loc41_14 to i1, !dbg !29
 // CHECK:STDOUT:   %.loc41_24 = load ptr, ptr %ptr_bool.var, align 8, !dbg !30
-// CHECK:STDOUT:   %F.call.loc41 = call i32 @_CF.Main.02da1a7614ea56b6(i64 %.loc41_5, i1 %.loc41_14, ptr %.loc41_24, i32 0), !dbg !31
+// CHECK:STDOUT:   %F.call.loc41 = call i32 @_CF.Main.02da1a7614ea56b6(i64 %.loc41_5, i1 %.loc41_142, ptr %.loc41_24, i32 0), !dbg !31
 // CHECK:STDOUT:   %.loc42_5 = load double, ptr %val_f64.var, align 8, !dbg !32
 // CHECK:STDOUT:   %.loc42_14 = load ptr, ptr %ptr_bool.var, align 8, !dbg !33
 // CHECK:STDOUT:   %.loc42_24 = load ptr, ptr %ptr_bool.var, align 8, !dbg !34

+ 19 - 12
toolchain/lower/testdata/operators/overloaded.carbon

@@ -36,39 +36,45 @@ fn Calculate(a: Number, b: Number) -> Number {
 // CHECK:STDOUT: define void @"_COp.Number.Main:Negate.Core"(ptr sret({ i1 }) %return, ptr %self) !dbg !4 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc19_36.1.is_positive = getelementptr inbounds nuw { i1 }, ptr %self, i32 0, i32 0, !dbg !7
-// CHECK:STDOUT:   %.loc19_36.2 = load i1, ptr %.loc19_36.1.is_positive, align 1, !dbg !7
-// CHECK:STDOUT:   %.loc19_28 = xor i1 %.loc19_36.2, true, !dbg !8
+// CHECK:STDOUT:   %.loc19_36.2 = load i8, ptr %.loc19_36.1.is_positive, align 1, !dbg !7
+// CHECK:STDOUT:   %.loc19_36.21 = trunc i8 %.loc19_36.2 to i1, !dbg !7
+// CHECK:STDOUT:   %.loc19_28 = xor i1 %.loc19_36.21, true, !dbg !8
 // CHECK:STDOUT:   %.loc19_48.2.is_positive = getelementptr inbounds nuw { i1 }, ptr %return, i32 0, i32 0, !dbg !9
-// CHECK:STDOUT:   store i1 %.loc19_28, ptr %.loc19_48.2.is_positive, align 1, !dbg !9
+// CHECK:STDOUT:   %.loc19_48.3 = zext i1 %.loc19_28 to i8, !dbg !9
+// CHECK:STDOUT:   store i8 %.loc19_48.3, ptr %.loc19_48.2.is_positive, align 1, !dbg !9
 // CHECK:STDOUT:   ret void, !dbg !10
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
 // CHECK:STDOUT: define void @"_COp.Number.Main:MulWith.Core"(ptr sret({ i1 }) %return, ptr %self, ptr %other) !dbg !11 {
 // CHECK:STDOUT: entry:
 // CHECK:STDOUT:   %.loc24_33.1.is_positive = getelementptr inbounds nuw { i1 }, ptr %self, i32 0, i32 0, !dbg !12
-// CHECK:STDOUT:   %.loc24_33.2 = load i1, ptr %.loc24_33.1.is_positive, align 1, !dbg !12
-// CHECK:STDOUT:   br i1 %.loc24_33.2, label %and.rhs.loc24, label %and.result.loc24, !dbg !12
+// CHECK:STDOUT:   %.loc24_33.2 = load i8, ptr %.loc24_33.1.is_positive, align 1, !dbg !12
+// CHECK:STDOUT:   %.loc24_33.21 = trunc i8 %.loc24_33.2 to i1, !dbg !12
+// CHECK:STDOUT:   br i1 %.loc24_33.21, label %and.rhs.loc24, label %and.result.loc24, !dbg !12
 // CHECK:STDOUT:
 // CHECK:STDOUT: and.rhs.loc24:                                    ; preds = %entry
 // CHECK:STDOUT:   %.loc24_55.1.is_positive = getelementptr inbounds nuw { i1 }, ptr %other, i32 0, i32 0, !dbg !13
-// CHECK:STDOUT:   %.loc24_55.2 = load i1, ptr %.loc24_55.1.is_positive, align 1, !dbg !13
+// CHECK:STDOUT:   %.loc24_55.2 = load i8, ptr %.loc24_55.1.is_positive, align 1, !dbg !13
+// CHECK:STDOUT:   %.loc24_55.22 = trunc i8 %.loc24_55.2 to i1, !dbg !13
 // CHECK:STDOUT:   br label %and.result.loc24, !dbg !12
 // CHECK:STDOUT:
 // CHECK:STDOUT: and.result.loc24:                                 ; preds = %and.rhs.loc24, %entry
-// CHECK:STDOUT:   %0 = phi i1 [ false, %entry ], [ %.loc24_55.2, %and.rhs.loc24 ]
+// CHECK:STDOUT:   %0 = phi i1 [ false, %entry ], [ %.loc24_55.22, %and.rhs.loc24 ]
 // CHECK:STDOUT:   %.loc24_69.1 = xor i1 %0, true, !dbg !14
 // CHECK:STDOUT:   br i1 %.loc24_69.1, label %or.rhs, label %or.result, !dbg !14
 // CHECK:STDOUT:
 // CHECK:STDOUT: or.rhs:                                           ; preds = %and.result.loc24
 // CHECK:STDOUT:   %.loc25_38.1.is_positive = getelementptr inbounds nuw { i1 }, ptr %self, i32 0, i32 0, !dbg !15
-// CHECK:STDOUT:   %.loc25_38.2 = load i1, ptr %.loc25_38.1.is_positive, align 1, !dbg !15
-// CHECK:STDOUT:   %.loc25_30 = xor i1 %.loc25_38.2, true, !dbg !16
+// CHECK:STDOUT:   %.loc25_38.2 = load i8, ptr %.loc25_38.1.is_positive, align 1, !dbg !15
+// CHECK:STDOUT:   %.loc25_38.23 = trunc i8 %.loc25_38.2 to i1, !dbg !15
+// CHECK:STDOUT:   %.loc25_30 = xor i1 %.loc25_38.23, true, !dbg !16
 // CHECK:STDOUT:   br i1 %.loc25_30, label %and.rhs.loc25, label %and.result.loc25, !dbg !16
 // CHECK:STDOUT:
 // CHECK:STDOUT: and.rhs.loc25:                                    ; preds = %or.rhs
 // CHECK:STDOUT:   %.loc25_64.1.is_positive = getelementptr inbounds nuw { i1 }, ptr %other, i32 0, i32 0, !dbg !17
-// CHECK:STDOUT:   %.loc25_64.2 = load i1, ptr %.loc25_64.1.is_positive, align 1, !dbg !17
-// CHECK:STDOUT:   %.loc25_55 = xor i1 %.loc25_64.2, true, !dbg !18
+// CHECK:STDOUT:   %.loc25_64.2 = load i8, ptr %.loc25_64.1.is_positive, align 1, !dbg !17
+// CHECK:STDOUT:   %.loc25_64.24 = trunc i8 %.loc25_64.2 to i1, !dbg !17
+// CHECK:STDOUT:   %.loc25_55 = xor i1 %.loc25_64.24, true, !dbg !18
 // CHECK:STDOUT:   br label %and.result.loc25, !dbg !16
 // CHECK:STDOUT:
 // CHECK:STDOUT: and.result.loc25:                                 ; preds = %and.rhs.loc25, %or.rhs
@@ -78,7 +84,8 @@ fn Calculate(a: Number, b: Number) -> Number {
 // CHECK:STDOUT: or.result:                                        ; preds = %and.result.loc25, %and.result.loc24
 // CHECK:STDOUT:   %2 = phi i1 [ true, %and.result.loc24 ], [ %1, %and.result.loc25 ]
 // CHECK:STDOUT:   %.loc25_77.2.is_positive = getelementptr inbounds nuw { i1 }, ptr %return, i32 0, i32 0, !dbg !19
-// CHECK:STDOUT:   store i1 %2, ptr %.loc25_77.2.is_positive, align 1, !dbg !19
+// CHECK:STDOUT:   %.loc25_77.3 = zext i1 %2 to i8, !dbg !19
+// CHECK:STDOUT:   store i8 %.loc25_77.3, ptr %.loc25_77.2.is_positive, align 1, !dbg !19
 // CHECK:STDOUT:   ret void, !dbg !20
 // CHECK:STDOUT: }
 // CHECK:STDOUT:

+ 24 - 5
toolchain/lower/testdata/primitives/false_true.carbon → toolchain/lower/testdata/primitives/bool.carbon

@@ -6,9 +6,9 @@
 //
 // AUTOUPDATE
 // TIP: To test this file alone, run:
-// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/primitives/false_true.carbon
+// TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/lower/testdata/primitives/bool.carbon
 // TIP: To dump output, run:
-// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/primitives/false_true.carbon
+// TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/lower/testdata/primitives/bool.carbon
 
 fn F() -> bool {
   return false;
@@ -18,8 +18,14 @@ fn T() -> bool {
   return true;
 }
 
-// CHECK:STDOUT: ; ModuleID = 'false_true.carbon'
-// CHECK:STDOUT: source_filename = "false_true.carbon"
+fn LoadStore(p: bool*, b: bool) -> bool {
+  let v: bool = *p;
+  *p = b;
+  return v;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'bool.carbon'
+// CHECK:STDOUT: source_filename = "bool.carbon"
 // CHECK:STDOUT:
 // CHECK:STDOUT: define i1 @_CF.Main() !dbg !4 {
 // CHECK:STDOUT: entry:
@@ -31,16 +37,29 @@ fn T() -> bool {
 // CHECK:STDOUT:   ret i1 true, !dbg !9
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: define i1 @_CLoadStore.Main(ptr %p, i1 %b) !dbg !10 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc22_17.2 = load i8, ptr %p, align 1, !dbg !11
+// CHECK:STDOUT:   %.loc22_17.21 = trunc i8 %.loc22_17.2 to i1, !dbg !11
+// CHECK:STDOUT:   %0 = zext i1 %b to i8, !dbg !12
+// CHECK:STDOUT:   store i8 %0, ptr %p, align 1, !dbg !12
+// CHECK:STDOUT:   ret i1 %.loc22_17.21, !dbg !13
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
 // CHECK:STDOUT: !llvm.dbg.cu = !{!2}
 // CHECK:STDOUT:
 // CHECK:STDOUT: !0 = !{i32 7, !"Dwarf Version", i32 5}
 // CHECK:STDOUT: !1 = !{i32 2, !"Debug Info Version", i32 3}
 // CHECK:STDOUT: !2 = distinct !DICompileUnit(language: DW_LANG_C, file: !3, producer: "carbon", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
-// CHECK:STDOUT: !3 = !DIFile(filename: "false_true.carbon", directory: "")
+// CHECK:STDOUT: !3 = !DIFile(filename: "bool.carbon", directory: "")
 // CHECK:STDOUT: !4 = distinct !DISubprogram(name: "F", linkageName: "_CF.Main", scope: null, file: !3, line: 13, type: !5, spFlags: DISPFlagDefinition, unit: !2)
 // CHECK:STDOUT: !5 = !DISubroutineType(types: !6)
 // CHECK:STDOUT: !6 = !{}
 // CHECK:STDOUT: !7 = !DILocation(line: 14, column: 3, scope: !4)
 // CHECK:STDOUT: !8 = distinct !DISubprogram(name: "T", linkageName: "_CT.Main", scope: null, file: !3, line: 17, type: !5, spFlags: DISPFlagDefinition, unit: !2)
 // CHECK:STDOUT: !9 = !DILocation(line: 18, column: 3, scope: !8)
+// CHECK:STDOUT: !10 = distinct !DISubprogram(name: "LoadStore", linkageName: "_CLoadStore.Main", scope: null, file: !3, line: 21, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !11 = !DILocation(line: 22, column: 17, scope: !10)
+// CHECK:STDOUT: !12 = !DILocation(line: 23, column: 3, scope: !10)
+// CHECK:STDOUT: !13 = !DILocation(line: 24, column: 3, scope: !10)

+ 36 - 0
toolchain/lower/testdata/primitives/int_types.carbon

@@ -15,6 +15,18 @@ fn F_u16(a: u16) -> u16 { return a; }
 fn F_i64(a: i64) -> i64 { return a; }
 fn F_u65536(a: u65536) -> u65536 { return a; }
 
+fn LoadStore_Bytes(p: Core.Int(24)*, n: Core.Int(24)) -> Core.Int(24) {
+  let v: Core.Int(24) = *p;
+  *p = n;
+  return v;
+}
+
+fn LoadStore_NotBytes(p: Core.Int(13)*, n: Core.Int(13)) -> Core.Int(13) {
+  let v: Core.Int(13) = *p;
+  *p = n;
+  return v;
+}
+
 // CHECK:STDOUT: ; ModuleID = 'int_types.carbon'
 // CHECK:STDOUT: source_filename = "int_types.carbon"
 // CHECK:STDOUT:
@@ -38,6 +50,22 @@ fn F_u65536(a: u65536) -> u65536 { return a; }
 // CHECK:STDOUT:   ret i65536 %a, !dbg !13
 // CHECK:STDOUT: }
 // CHECK:STDOUT:
+// CHECK:STDOUT: define i24 @_CLoadStore_Bytes.Main(ptr %p, i24 %n) !dbg !14 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc19_25.2 = load i24, ptr %p, align 4, !dbg !15
+// CHECK:STDOUT:   store i24 %n, ptr %p, align 4, !dbg !16
+// CHECK:STDOUT:   ret i24 %.loc19_25.2, !dbg !17
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: define i13 @_CLoadStore_NotBytes.Main(ptr %p, i13 %n) !dbg !18 {
+// CHECK:STDOUT: entry:
+// CHECK:STDOUT:   %.loc25_25.2 = load i16, ptr %p, align 2, !dbg !19
+// CHECK:STDOUT:   %.loc25_25.21 = trunc i16 %.loc25_25.2 to i13, !dbg !19
+// CHECK:STDOUT:   %0 = zext i13 %n to i16, !dbg !20
+// CHECK:STDOUT:   store i16 %0, ptr %p, align 2, !dbg !20
+// CHECK:STDOUT:   ret i13 %.loc25_25.21, !dbg !21
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
 // CHECK:STDOUT: !llvm.module.flags = !{!0, !1}
 // CHECK:STDOUT: !llvm.dbg.cu = !{!2}
 // CHECK:STDOUT:
@@ -55,3 +83,11 @@ fn F_u65536(a: u65536) -> u65536 { return a; }
 // CHECK:STDOUT: !11 = !DILocation(line: 15, column: 27, scope: !10)
 // CHECK:STDOUT: !12 = distinct !DISubprogram(name: "F_u65536", linkageName: "_CF_u65536.Main", scope: null, file: !3, line: 16, type: !5, spFlags: DISPFlagDefinition, unit: !2)
 // CHECK:STDOUT: !13 = !DILocation(line: 16, column: 36, scope: !12)
+// CHECK:STDOUT: !14 = distinct !DISubprogram(name: "LoadStore_Bytes", linkageName: "_CLoadStore_Bytes.Main", scope: null, file: !3, line: 18, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !15 = !DILocation(line: 19, column: 25, scope: !14)
+// CHECK:STDOUT: !16 = !DILocation(line: 20, column: 3, scope: !14)
+// CHECK:STDOUT: !17 = !DILocation(line: 21, column: 3, scope: !14)
+// CHECK:STDOUT: !18 = distinct !DISubprogram(name: "LoadStore_NotBytes", linkageName: "_CLoadStore_NotBytes.Main", scope: null, file: !3, line: 24, type: !5, spFlags: DISPFlagDefinition, unit: !2)
+// CHECK:STDOUT: !19 = !DILocation(line: 25, column: 25, scope: !18)
+// CHECK:STDOUT: !20 = !DILocation(line: 26, column: 3, scope: !18)
+// CHECK:STDOUT: !21 = !DILocation(line: 27, column: 3, scope: !18)