From 6889aff49ace3ba1f2b23afc7f7a26c918469293 Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Thu, 4 Apr 2024 16:47:06 -0700 Subject: [PATCH] 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. --- toolchain/check/eval.cpp | 32 ++++ .../testdata/builtins/bool/make_type.carbon | 62 ++++++++ .../testdata/builtins/float/make_type.carbon | 147 ++++++++++++++++++ .../testdata/builtins/int/make_type_32.carbon | 62 ++++++++ toolchain/diagnostics/diagnostic_kind.def | 1 + toolchain/lower/handle.cpp | 6 + .../lower/testdata/builtins/types.carbon | 28 ++++ toolchain/sem_ir/builtin_function_kind.cpp | 17 ++ toolchain/sem_ir/builtin_function_kind.def | 5 + 9 files changed, 360 insertions(+) create mode 100644 toolchain/check/testdata/builtins/bool/make_type.carbon create mode 100644 toolchain/check/testdata/builtins/float/make_type.carbon create mode 100644 toolchain/check/testdata/builtins/int/make_type_32.carbon create mode 100644 toolchain/lower/testdata/builtins/types.carbon diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index 85207c110c58..d04ac612873f 100644 --- a/toolchain/check/eval.cpp +++ b/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(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: { diff --git a/toolchain/check/testdata/builtins/bool/make_type.carbon b/toolchain/check/testdata/builtins/bool/make_type.carbon new file mode 100644 index 000000000000..7e27a4c8a904 --- /dev/null +++ b/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 [template] { +// CHECK:STDOUT: .Bool = %Bool +// CHECK:STDOUT: } +// CHECK:STDOUT: %Bool: = fn_decl @Bool [template] { +// CHECK:STDOUT: %return.var: ref type = var +// 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 [template] { +// CHECK:STDOUT: .Bool = %import_ref +// CHECK:STDOUT: .b = %b +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref: = import_ref ir2, inst+2, loc_11 [template = imports.%Bool] +// CHECK:STDOUT: %Bool.ref: = 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: diff --git a/toolchain/check/testdata/builtins/float/make_type.carbon b/toolchain/check/testdata/builtins/float/make_type.carbon new file mode 100644 index 000000000000..e5c8dd488e5f --- /dev/null +++ b/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 [template] { +// CHECK:STDOUT: .Float = %Float +// CHECK:STDOUT: } +// CHECK:STDOUT: %Float: = 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 +// 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 [template] { +// CHECK:STDOUT: .Float = %import_ref +// CHECK:STDOUT: .f = %f +// CHECK:STDOUT: .GetFloat = %GetFloat +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref: = import_ref ir2, inst+4, loc_11 [template = imports.%Float] +// CHECK:STDOUT: %Float.ref: = 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: = 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 +// 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: = 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 [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: = import_ref ir2, inst+4, loc_11 [template = imports.%Float] +// CHECK:STDOUT: %Float.ref.loc10: = 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 = ] +// CHECK:STDOUT: %.loc10_28: type = value_of_initializer %.loc10_25.1 [template = ] +// CHECK:STDOUT: %.loc10_25.2: type = converted %.loc10_25.1, %.loc10_28 [template = ] +// CHECK:STDOUT: %invalid_float.var: ref = var invalid_float +// CHECK:STDOUT: %invalid_float: ref = 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: = 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 = var dyn +// CHECK:STDOUT: %dyn: ref = 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: diff --git a/toolchain/check/testdata/builtins/int/make_type_32.carbon b/toolchain/check/testdata/builtins/int/make_type_32.carbon new file mode 100644 index 000000000000..af29da67ac79 --- /dev/null +++ b/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 [template] { +// CHECK:STDOUT: .Int = %Int +// CHECK:STDOUT: } +// CHECK:STDOUT: %Int: = fn_decl @Int [template] { +// CHECK:STDOUT: %return.var: ref type = var +// 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 [template] { +// CHECK:STDOUT: .Int = %import_ref +// CHECK:STDOUT: .i = %i +// CHECK:STDOUT: } +// CHECK:STDOUT: %import_ref: = import_ref ir2, inst+2, loc_11 [template = imports.%Int] +// CHECK:STDOUT: %Int.ref: = 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: diff --git a/toolchain/diagnostics/diagnostic_kind.def b/toolchain/diagnostics/diagnostic_kind.def index 2e86134c4330..6503a82ae9ef 100644 --- a/toolchain/diagnostics/diagnostic_kind.def +++ b/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) diff --git a/toolchain/lower/handle.cpp b/toolchain/lower/handle.cpp index 72faea545b37..b4d13a93bb31 100644 --- a/toolchain/lower/handle.cpp +++ b/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]); diff --git a/toolchain/lower/testdata/builtins/types.carbon b/toolchain/lower/testdata/builtins/types.carbon new file mode 100644 index 000000000000..86c794ceb910 --- /dev/null +++ b/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: } diff --git a/toolchain/sem_ir/builtin_function_kind.cpp b/toolchain/sem_ir/builtin_function_kind.cpp index 1b4896f3df5b..3d1c173812ce 100644 --- a/toolchain/sem_ir/builtin_function_kind.cpp +++ b/toolchain/sem_ir/builtin_function_kind.cpp @@ -68,6 +68,10 @@ using Bool = BuiltinType; // 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; + +// Constraint that requires the type to be the type type. +using Type = BuiltinType; + } // 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", + ValidateSignatureType>}; + +// Returns float types, such as `f64`. Currently only supports `f64`. +constexpr BuiltinInfo FloatMakeType = {"float.make_type", + ValidateSignatureType>}; + +// Returns the `bool` type. +constexpr BuiltinInfo BoolMakeType = {"bool.make_type", + ValidateSignatureType>}; + // "int.negate": integer negation. constexpr BuiltinInfo IntNegate = {"int.negate", ValidateSignatureIntT>}; diff --git a/toolchain/sem_ir/builtin_function_kind.def b/toolchain/sem_ir/builtin_function_kind.def index 13f1c8241148..28a15e4bab20 100644 --- a/toolchain/sem_ir/builtin_function_kind.def +++ b/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)