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

Add functions for instantiating builtin types through lookup. (#3857)

Note, my instinct is that `Float(dyn_size)` should be invalid. However,
I think the constant evaluation doesn't result in the call being
evaluated in eval.cpp when the size is non-constant. I think I could get
an error for symbolic phase calls, but that seems a little less
interesting already. Long-term maybe we want a way to mark functions as
_must_ be evaluated during constant phase?

Also, I think there may be a bug with literal value parse node
locations, I should be able to point at the position of `arg_ids[0]` but
it's missing a line number so I point at `loc` instead.
Jon Ross-Perkins 2 лет назад
Родитель
Сommit
6889aff49a

+ 32 - 0
toolchain/check/eval.cpp

@@ -302,6 +302,19 @@ static auto PerformAggregateIndex(Context& context, SemIR::Inst inst)
   return context.constant_values().Get(elements[index_val.getZExtValue()]);
 }
 
+// Enforces that the bit width is 64 for a float.
+static auto ValidateFloatBitWidth(Context& context, SemIRLoc loc,
+                                  SemIR::InstId inst_id) -> bool {
+  auto inst = context.insts().GetAs<SemIR::IntLiteral>(inst_id);
+  if (context.ints().Get(inst.int_id) == 64) {
+    return true;
+  }
+
+  CARBON_DIAGNOSTIC(CompileTimeFloatBitWidth, Error, "Bit width must be 64.");
+  context.emitter().Emit(loc, CompileTimeFloatBitWidth);
+  return false;
+}
+
 // Issues a diagnostic for a compile-time division by zero.
 static auto DiagnoseDivisionByZero(Context& context, SemIRLoc loc) -> void {
   CARBON_DIAGNOSTIC(CompileTimeDivisionByZero, Error, "Division by zero.");
@@ -497,6 +510,25 @@ static auto PerformBuiltinCall(Context& context, SemIRLoc loc, SemIR::Call call,
     case SemIR::BuiltinFunctionKind::None:
       CARBON_FATAL() << "Not a builtin function.";
 
+    case SemIR::BuiltinFunctionKind::IntMakeType32: {
+      return context.constant_values().Get(SemIR::InstId::BuiltinIntType);
+    }
+
+    case SemIR::BuiltinFunctionKind::FloatMakeType: {
+      // TODO: Support a symbolic constant width.
+      if (phase != Phase::Template) {
+        break;
+      }
+      if (!ValidateFloatBitWidth(context, loc, arg_ids[0])) {
+        return SemIR::ConstantId::Error;
+      }
+      return context.constant_values().Get(SemIR::InstId::BuiltinFloatType);
+    }
+
+    case SemIR::BuiltinFunctionKind::BoolMakeType: {
+      return context.constant_values().Get(SemIR::InstId::BuiltinBoolType);
+    }
+
     // Unary integer -> integer operations.
     case SemIR::BuiltinFunctionKind::IntNegate:
     case SemIR::BuiltinFunctionKind::IntComplement: {

+ 62 - 0
toolchain/check/testdata/builtins/bool/make_type.carbon

@@ -0,0 +1,62 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+// --- types.carbon
+
+library "types" api;
+
+fn Bool() -> type = "bool.make_type";
+
+// --- use_types.carbon
+
+library "uses_types" api;
+
+import library "types";
+
+var b: Bool() = false;
+
+// CHECK:STDOUT: --- types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Bool = %Bool
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Bool: <function> = fn_decl @Bool [template] {
+// CHECK:STDOUT:     %return.var: ref type = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Bool() -> type = "bool.make_type";
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: bool = bool_literal false [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Bool = %import_ref
+// CHECK:STDOUT:     .b = %b
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir2, inst+2, loc_11 [template = imports.%Bool]
+// CHECK:STDOUT:   %Bool.ref: <function> = name_ref Bool, %import_ref [template = imports.%Bool]
+// CHECK:STDOUT:   %.loc6_12.1: init type = call %Bool.ref() [template = bool]
+// CHECK:STDOUT:   %.loc6_13: type = value_of_initializer %.loc6_12.1 [template = bool]
+// CHECK:STDOUT:   %.loc6_12.2: type = converted %.loc6_12.1, %.loc6_13 [template = bool]
+// CHECK:STDOUT:   %b.var: ref bool = var b
+// CHECK:STDOUT:   %b: ref bool = bind_name b, %b.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Bool() -> type = "bool.make_type";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc6: bool = bool_literal false [template = constants.%.1]
+// CHECK:STDOUT:   assign file.%b.var, %.loc6
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 147 - 0
toolchain/check/testdata/builtins/float/make_type.carbon

@@ -0,0 +1,147 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+// --- types.carbon
+
+library "types" api;
+
+fn Float(size: i32) -> type = "float.make_type";
+
+// --- use_types.carbon
+
+library "uses_types" api;
+
+import library "types";
+
+var f: Float(64) = 0.0;
+
+fn GetFloat(dyn_size: i32) -> type {
+  return Float(dyn_size);
+}
+
+// --- fail_invalid_size.carbon
+
+library "invalid_size" api;
+
+import library "types";
+
+// CHECK:STDERR: fail_invalid_size.carbon:[[@LINE+4]]:20: ERROR: Bit width must be 64.
+// CHECK:STDERR: var invalid_float: Float(32);
+// CHECK:STDERR:                    ^~~~~~
+// CHECK:STDERR:
+var invalid_float: Float(32);
+
+var dyn_size: i32 = 64;
+// CHECK:STDERR: fail_invalid_size.carbon:[[@LINE+3]]:10: ERROR: Cannot evaluate type expression.
+// CHECK:STDERR: var dyn: Float(dyn_size);
+// CHECK:STDERR:          ^~~~~~~~~~~~~~~
+var dyn: Float(dyn_size);
+
+// CHECK:STDOUT: --- types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Float = %Float
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Float: <function> = fn_decl @Float [template] {
+// CHECK:STDOUT:     %size.loc4_10.1: i32 = param size
+// CHECK:STDOUT:     @Float.%size: i32 = bind_name size, %size.loc4_10.1
+// CHECK:STDOUT:     %return.var: ref type = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Float(%size: i32) -> type = "float.make_type";
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 64 [template]
+// CHECK:STDOUT:   %.2: f64 = real_literal 0e-1 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Float = %import_ref
+// CHECK:STDOUT:     .f = %f
+// CHECK:STDOUT:     .GetFloat = %GetFloat
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir2, inst+4, loc_11 [template = imports.%Float]
+// CHECK:STDOUT:   %Float.ref: <function> = name_ref Float, %import_ref [template = imports.%Float]
+// CHECK:STDOUT:   %.loc6_14: i32 = int_literal 64 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc6_13.1: init type = call %Float.ref(%.loc6_14) [template = f64]
+// CHECK:STDOUT:   %.loc6_16: type = value_of_initializer %.loc6_13.1 [template = f64]
+// CHECK:STDOUT:   %.loc6_13.2: type = converted %.loc6_13.1, %.loc6_16 [template = f64]
+// CHECK:STDOUT:   %f.var: ref f64 = var f
+// CHECK:STDOUT:   %f: ref f64 = bind_name f, %f.var
+// CHECK:STDOUT:   %GetFloat: <function> = fn_decl @GetFloat [template] {
+// CHECK:STDOUT:     %dyn_size.loc8_13.1: i32 = param dyn_size
+// CHECK:STDOUT:     @GetFloat.%dyn_size: i32 = bind_name dyn_size, %dyn_size.loc8_13.1
+// CHECK:STDOUT:     %return.var: ref type = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Float(%size: i32) -> type = "float.make_type";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @GetFloat(%dyn_size: i32) -> type {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %Float.ref: <function> = name_ref Float, file.%import_ref [template = imports.%Float]
+// CHECK:STDOUT:   %dyn_size.ref: i32 = name_ref dyn_size, %dyn_size
+// CHECK:STDOUT:   %.loc9_15.1: init type = call %Float.ref(%dyn_size.ref)
+// CHECK:STDOUT:   %.loc9_25: type = value_of_initializer %.loc9_15.1
+// CHECK:STDOUT:   %.loc9_15.2: type = converted %.loc9_15.1, %.loc9_25
+// CHECK:STDOUT:   return %.loc9_15.2
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc6: f64 = real_literal 0e-1 [template = constants.%.2]
+// CHECK:STDOUT:   assign file.%f.var, %.loc6
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- fail_invalid_size.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 32 [template]
+// CHECK:STDOUT:   %.2: i32 = int_literal 64 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Float = %import_ref
+// CHECK:STDOUT:     .invalid_float = %invalid_float
+// CHECK:STDOUT:     .dyn_size = %dyn_size
+// CHECK:STDOUT:     .dyn = %dyn
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir2, inst+4, loc_11 [template = imports.%Float]
+// CHECK:STDOUT:   %Float.ref.loc10: <function> = name_ref Float, %import_ref [template = imports.%Float]
+// CHECK:STDOUT:   %.loc10_26: i32 = int_literal 32 [template = constants.%.1]
+// CHECK:STDOUT:   %.loc10_25.1: init type = call %Float.ref.loc10(%.loc10_26) [template = <error>]
+// CHECK:STDOUT:   %.loc10_28: type = value_of_initializer %.loc10_25.1 [template = <error>]
+// CHECK:STDOUT:   %.loc10_25.2: type = converted %.loc10_25.1, %.loc10_28 [template = <error>]
+// CHECK:STDOUT:   %invalid_float.var: ref <error> = var invalid_float
+// CHECK:STDOUT:   %invalid_float: ref <error> = bind_name invalid_float, %invalid_float.var
+// CHECK:STDOUT:   %dyn_size.var: ref i32 = var dyn_size
+// CHECK:STDOUT:   %dyn_size: ref i32 = bind_name dyn_size, %dyn_size.var
+// CHECK:STDOUT:   %Float.ref.loc16: <function> = name_ref Float, %import_ref [template = imports.%Float]
+// CHECK:STDOUT:   %dyn_size.ref: ref i32 = name_ref dyn_size, %dyn_size
+// CHECK:STDOUT:   %.loc16_16: i32 = bind_value %dyn_size.ref
+// CHECK:STDOUT:   %.loc16_15.1: init type = call %Float.ref.loc16(%.loc16_16)
+// CHECK:STDOUT:   %.loc16_24: type = value_of_initializer %.loc16_15.1
+// CHECK:STDOUT:   %.loc16_15.2: type = converted %.loc16_15.1, %.loc16_24
+// CHECK:STDOUT:   %dyn.var: ref <error> = var dyn
+// CHECK:STDOUT:   %dyn: ref <error> = bind_name dyn, %dyn.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Float(%size: i32) -> type = "float.make_type";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc12: i32 = int_literal 64 [template = constants.%.2]
+// CHECK:STDOUT:   assign file.%dyn_size.var, %.loc12
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 62 - 0
toolchain/check/testdata/builtins/int/make_type_32.carbon

@@ -0,0 +1,62 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+// --- types.carbon
+
+library "types" api;
+
+fn Int() -> type = "int.make_type_32";
+
+// --- use_types.carbon
+
+library "uses_types" api;
+
+import library "types";
+
+var i: Int() = 0;
+
+// CHECK:STDOUT: --- types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Int = %Int
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %Int: <function> = fn_decl @Int [template] {
+// CHECK:STDOUT:     %return.var: ref type = var <return slot>
+// CHECK:STDOUT:   }
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int() -> type = "int.make_type_32";
+// CHECK:STDOUT:
+// CHECK:STDOUT: --- use_types.carbon
+// CHECK:STDOUT:
+// CHECK:STDOUT: constants {
+// CHECK:STDOUT:   %.1: i32 = int_literal 0 [template]
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: file {
+// CHECK:STDOUT:   package: <namespace> = namespace [template] {
+// CHECK:STDOUT:     .Int = %import_ref
+// CHECK:STDOUT:     .i = %i
+// CHECK:STDOUT:   }
+// CHECK:STDOUT:   %import_ref: <function> = import_ref ir2, inst+2, loc_11 [template = imports.%Int]
+// CHECK:STDOUT:   %Int.ref: <function> = name_ref Int, %import_ref [template = imports.%Int]
+// CHECK:STDOUT:   %.loc6_11.1: init type = call %Int.ref() [template = i32]
+// CHECK:STDOUT:   %.loc6_12: type = value_of_initializer %.loc6_11.1 [template = i32]
+// CHECK:STDOUT:   %.loc6_11.2: type = converted %.loc6_11.1, %.loc6_12 [template = i32]
+// CHECK:STDOUT:   %i.var: ref i32 = var i
+// CHECK:STDOUT:   %i: ref i32 = bind_name i, %i.var
+// CHECK:STDOUT: }
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @Int() -> type = "int.make_type_32";
+// CHECK:STDOUT:
+// CHECK:STDOUT: fn @__global_init() {
+// CHECK:STDOUT: !entry:
+// CHECK:STDOUT:   %.loc6: i32 = int_literal 0 [template = constants.%.1]
+// CHECK:STDOUT:   assign file.%i.var, %.loc6
+// CHECK:STDOUT:   return
+// CHECK:STDOUT: }
+// CHECK:STDOUT:

+ 1 - 0
toolchain/diagnostics/diagnostic_kind.def

@@ -247,6 +247,7 @@ CARBON_DIAGNOSTIC_KIND(BreakOutsideLoop)
 CARBON_DIAGNOSTIC_KIND(CompileTimeDivisionByZero)
 CARBON_DIAGNOSTIC_KIND(CompileTimeIntegerOverflow)
 CARBON_DIAGNOSTIC_KIND(CompileTimeIntegerNegateOverflow)
+CARBON_DIAGNOSTIC_KIND(CompileTimeFloatBitWidth)
 CARBON_DIAGNOSTIC_KIND(CompileTimeShiftOutOfRange)
 CARBON_DIAGNOSTIC_KIND(ContinueOutsideLoop)
 CARBON_DIAGNOSTIC_KIND(CopyOfUncopyableType)

+ 6 - 0
toolchain/lower/handle.cpp

@@ -209,6 +209,12 @@ static auto HandleBuiltinCall(FunctionContext& context, SemIR::InstId inst_id,
     case SemIR::BuiltinFunctionKind::None:
       CARBON_FATAL() << "No callee in function call.";
 
+    case SemIR::BuiltinFunctionKind::BoolMakeType:
+    case SemIR::BuiltinFunctionKind::FloatMakeType:
+    case SemIR::BuiltinFunctionKind::IntMakeType32:
+      context.SetLocal(inst_id, context.GetTypeAsValue());
+      return;
+
     case SemIR::BuiltinFunctionKind::IntNegate: {
       // Lower `-x` as `0 - x`.
       auto* operand = context.GetValue(arg_ids[0]);

+ 28 - 0
toolchain/lower/testdata/builtins/types.carbon

@@ -0,0 +1,28 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// AUTOUPDATE
+
+fn Int() -> type = "int.make_type_32";
+fn Float(size: i32) -> type = "float.make_type";
+fn Bool() -> type = "bool.make_type";
+
+fn F() {
+  var i: Int() = 0;
+  var f: Float(64) = 0.0;
+  var b: Bool() = false;
+}
+
+// CHECK:STDOUT: ; ModuleID = 'types.carbon'
+// CHECK:STDOUT: source_filename = "types.carbon"
+// CHECK:STDOUT:
+// CHECK:STDOUT: define void @F() {
+// CHECK:STDOUT:   %i = alloca i32, align 4
+// CHECK:STDOUT:   store i32 0, ptr %i, align 4
+// CHECK:STDOUT:   %f = alloca double, align 8
+// CHECK:STDOUT:   store double 0.000000e+00, ptr %f, align 8
+// CHECK:STDOUT:   %b = alloca i1, align 1
+// CHECK:STDOUT:   store i1 false, ptr %b, align 1
+// CHECK:STDOUT:   ret void
+// CHECK:STDOUT: }

+ 17 - 0
toolchain/sem_ir/builtin_function_kind.cpp

@@ -68,6 +68,10 @@ using Bool = BuiltinType<InstId::BuiltinBoolType>;
 // TODO: This only matches i32 for now. Support iN for all N, and the
 // Core.BigInt type we use to implement for integer literals.
 using AnyInt = BuiltinType<InstId::BuiltinIntType>;
+
+// Constraint that requires the type to be the type type.
+using Type = BuiltinType<InstId::BuiltinTypeType>;
+
 }  // namespace
 
 // Validates that this builtin has a signature matching the specified signature.
@@ -132,6 +136,19 @@ using IntU = TypeParam<1, AnyInt>;
 // Not a builtin function.
 constexpr BuiltinInfo None = {"", nullptr};
 
+// Returns the `i32` type. Doesn't take a bit size because we need an integer
+// type as a basis for that.
+constexpr BuiltinInfo IntMakeType32 = {"int.make_type_32",
+                                       ValidateSignature<auto()->Type>};
+
+// Returns float types, such as `f64`. Currently only supports `f64`.
+constexpr BuiltinInfo FloatMakeType = {"float.make_type",
+                                       ValidateSignature<auto(IntT)->Type>};
+
+// Returns the `bool` type.
+constexpr BuiltinInfo BoolMakeType = {"bool.make_type",
+                                      ValidateSignature<auto()->Type>};
+
 // "int.negate": integer negation.
 constexpr BuiltinInfo IntNegate = {"int.negate",
                                    ValidateSignature<auto(IntT)->IntT>};

+ 5 - 0
toolchain/sem_ir/builtin_function_kind.def

@@ -18,6 +18,11 @@
 
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(None)
 
+// Type factories.
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntMakeType32)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(FloatMakeType)
+CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(BoolMakeType)
+
 // Integer arithmetic.
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntNegate)
 CARBON_SEM_IR_BUILTIN_FUNCTION_KIND(IntAdd)