From f30fa2d3db7669ec05a745e14abdaa8b3d771980 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 27 Feb 2025 16:50:43 -0800 Subject: [PATCH] Move the `EvalConstantInst` overloads out of `eval.cpp` into their own file. (#5040) For now they're all in the same file; we might consider splitting them further if that file gets too large. --- toolchain/check/BUILD | 2 + toolchain/check/eval.cpp | 538 ++-------------------------------- toolchain/check/eval_inst.cpp | 373 +++++++++++++++++++++++ toolchain/check/eval_inst.h | 123 ++++++++ toolchain/check/type.cpp | 58 ++++ toolchain/check/type.h | 12 + 6 files changed, 597 insertions(+), 509 deletions(-) create mode 100644 toolchain/check/eval_inst.cpp create mode 100644 toolchain/check/eval_inst.h diff --git a/toolchain/check/BUILD b/toolchain/check/BUILD index 186ffbd387c9..d31dc1a01372 100644 --- a/toolchain/check/BUILD +++ b/toolchain/check/BUILD @@ -22,6 +22,7 @@ cc_library( "decl_name_stack.cpp", "deduce.cpp", "eval.cpp", + "eval_inst.cpp", "facet_type.cpp", "function.cpp", "generic.cpp", @@ -57,6 +58,7 @@ cc_library( "deduce.h", "diagnostic_helpers.h", "eval.h", + "eval_inst.h", "facet_type.h", "function.h", "generic.h", diff --git a/toolchain/check/eval.cpp b/toolchain/check/eval.cpp index c5cb700e864b..0ad141a8da68 100644 --- a/toolchain/check/eval.cpp +++ b/toolchain/check/eval.cpp @@ -6,6 +6,7 @@ #include "toolchain/base/kind_switch.h" #include "toolchain/check/diagnostic_helpers.h" +#include "toolchain/check/eval_inst.h" #include "toolchain/check/facet_type.h" #include "toolchain/check/generic.h" #include "toolchain/check/import_ref.h" @@ -694,39 +695,6 @@ static auto PerformArrayIndex(EvalContext& eval_context, SemIR::ArrayIndex inst) return eval_context.GetConstantValue(elements[index_val.getZExtValue()]); } -// Enforces that an integer type has a valid bit width. -static auto ValidateIntType(Context& context, SemIRLoc loc, - SemIR::IntType result) -> bool { - auto bit_width = - context.insts().TryGetAs(result.bit_width_id); - if (!bit_width) { - // Symbolic bit width. - return true; - } - const auto& bit_width_val = context.ints().Get(bit_width->int_id); - if (bit_width_val.isZero() || - (context.types().IsSignedInt(bit_width->type_id) && - bit_width_val.isNegative())) { - CARBON_DIAGNOSTIC(IntWidthNotPositive, Error, - "integer type width of {0} is not positive", TypedInt); - context.emitter().Emit( - loc, IntWidthNotPositive, - {.type = bit_width->type_id, .value = bit_width_val}); - return false; - } - if (bit_width_val.ugt(IntStore::MaxIntWidth)) { - CARBON_DIAGNOSTIC(IntWidthTooLarge, Error, - "integer type width of {0} is greater than the " - "maximum supported width of {1}", - TypedInt, int); - context.emitter().Emit(loc, IntWidthTooLarge, - {.type = bit_width->type_id, .value = bit_width_val}, - IntStore::MaxIntWidth); - return false; - } - return true; -} - // Forms a constant int type as an evaluation result. Requires that width_id is // constant. static auto MakeIntTypeResult(Context& context, SemIRLoc loc, @@ -742,31 +710,6 @@ static auto MakeIntTypeResult(Context& context, SemIRLoc loc, return MakeConstantResult(context, result, phase); } -// 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; -} - -// Enforces that a float type has a valid bit width. -static auto ValidateFloatType(Context& context, SemIRLoc loc, - SemIR::FloatType result) -> bool { - auto bit_width = - context.insts().TryGetAs(result.bit_width_id); - if (!bit_width) { - // Symbolic bit width. - return true; - } - return ValidateFloatBitWidth(context, loc, result.bit_width_id); -} - // Performs a conversion between integer types, truncating if the value doesn't // fit in the destination type. static auto PerformIntConvert(Context& context, SemIR::InstId arg_id, @@ -1585,454 +1528,33 @@ static auto MakeConstantForCall(EvalContext& eval_context, SemIRLoc loc, return SemIR::ConstantId::NotConstant; } -// The result of constant evaluation of an instruction. -class ConstantEvalResult { - public: - // Produce a new constant as the result of an evaluation. The phase of the - // produced constant must be the same as the greatest phase of the operands in - // the evaluation. This will typically be the case if the evaluation uses all - // of its operands. - static auto New(SemIR::Inst inst) -> ConstantEvalResult { - return ConstantEvalResult(inst); - } - - // Produce an existing constant as the result of an evaluation. - static constexpr auto Existing(SemIR::ConstantId existing_id) - -> ConstantEvalResult { - CARBON_CHECK(existing_id.is_constant()); - return ConstantEvalResult(existing_id); - } - - // Indicates that an error was produced by evaluation. - static const ConstantEvalResult Error; - - // Indicates that we encountered an instruction whose evaluation is - // non-constant despite having constant operands. This should be rare; - // usually we want to produce an error in this case. - static const ConstantEvalResult NotConstant; - - // Indicates that we encountered an instruction for which we've not - // implemented constant evaluation yet. Instruction is treated as not - // constant. - static const ConstantEvalResult TODO; - - // Returns whether the result of evaluation is that we should produce a new - // constant described by `new_inst()` rather than an existing `ConstantId` - // described by `existing()`. - auto is_new() const -> bool { return !result_id_.has_value(); } - - // Returns the existing constant that this the instruction evaluates to, or - // `None` if this is evaluation produces a new constant. - auto existing() const -> SemIR::ConstantId { return result_id_; } - - // Returns the new constant instruction that is the result of evaluation. - auto new_inst() const -> SemIR::Inst { - CARBON_CHECK(is_new()); - return new_inst_; - } - - private: - constexpr explicit ConstantEvalResult(SemIR::ConstantId raw_id) - : result_id_(raw_id) {} - - explicit ConstantEvalResult(SemIR::Inst inst) - : result_id_(SemIR::ConstantId::None), new_inst_(inst) {} - - SemIR::ConstantId result_id_; - union { - SemIR::Inst new_inst_; - }; -}; - -constexpr ConstantEvalResult ConstantEvalResult::Error = - Existing(SemIR::ErrorInst::SingletonConstantId); - -constexpr ConstantEvalResult ConstantEvalResult::NotConstant = - ConstantEvalResult(SemIR::ConstantId::NotConstant); - -constexpr ConstantEvalResult ConstantEvalResult::TODO = NotConstant; - -// `EvalConstantInst` evaluates an instruction whose operands are all constant, -// in a context unrelated to the enclosing evaluation. The function is given the -// instruction after its operands, including its type, are replaced by their -// evaluated value, and returns a `ConstantEvalResult` describing the result of -// evaluating the instruction. -// -// An overload is provided for each type whose constant kind is one of the -// following: -// -// - InstConstantKind::Indirect -// - InstConstantKind::SymbolicOnly -// - InstConstantKind::Conditional -// -// ... except for cases where the result of evaluation depends on the evaluation -// context itself. Those cases are handled by explicit specialization of -// `TryEvalTypedInst`. - -static auto EvalConstantInst(Context& context, SemIRLoc loc, - SemIR::ArrayType inst) -> ConstantEvalResult { - auto bound_inst = context.insts().Get(inst.bound_id); - auto int_bound = bound_inst.TryAs(); - if (!int_bound) { - CARBON_CHECK(context.constant_values().Get(inst.bound_id).is_symbolic(), - "Unexpected inst {0} for template constant int", bound_inst); - return ConstantEvalResult::New(inst); - } - // TODO: We should check that the size of the resulting array type - // fits in 64 bits, not just that the bound does. Should we use a - // 32-bit limit for 32-bit targets? - const auto& bound_val = context.ints().Get(int_bound->int_id); - if (context.types().IsSignedInt(int_bound->type_id) && - bound_val.isNegative()) { - CARBON_DIAGNOSTIC(ArrayBoundNegative, Error, - "array bound of {0} is negative", TypedInt); - context.emitter().Emit(loc, ArrayBoundNegative, - {.type = int_bound->type_id, .value = bound_val}); - return ConstantEvalResult::Error; - } - if (bound_val.getActiveBits() > 64) { - CARBON_DIAGNOSTIC(ArrayBoundTooLarge, Error, - "array bound of {0} is too large", TypedInt); - context.emitter().Emit(loc, ArrayBoundTooLarge, - {.type = int_bound->type_id, .value = bound_val}); - return ConstantEvalResult::Error; - } - return ConstantEvalResult::New(inst); -} - -static auto EvalConstantInst(Context& context, SemIRLoc loc, - SemIR::IntType inst) -> ConstantEvalResult { - return ValidateIntType(context, loc, inst) ? ConstantEvalResult::New(inst) - : ConstantEvalResult::Error; -} - -static auto EvalConstantInst(Context& context, SemIRLoc loc, - SemIR::FloatType inst) -> ConstantEvalResult { - return ValidateFloatType(context, loc, inst) ? ConstantEvalResult::New(inst) - : ConstantEvalResult::Error; -} - -static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, - SemIR::ArrayInit init) -> ConstantEvalResult { - // TODO: Add an `ArrayValue` to represent a constant array object - // representation instead of using a `TupleValue`. - return ConstantEvalResult::New( - SemIR::TupleValue{.type_id = init.type_id, .elements_id = init.inits_id}); -} - -static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, - SemIR::ClassInit init) -> ConstantEvalResult { - // TODO: Add a `ClassValue` to represent a constant class object - // representation instead of using a `StructValue`. - return ConstantEvalResult::New(SemIR::StructValue{ - .type_id = init.type_id, .elements_id = init.elements_id}); -} - -static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, - SemIR::StructInit init) -> ConstantEvalResult { - return ConstantEvalResult::New(SemIR::StructValue{ - .type_id = init.type_id, .elements_id = init.elements_id}); -} - -static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, - SemIR::TupleInit init) -> ConstantEvalResult { - return ConstantEvalResult::New(SemIR::TupleValue{ - .type_id = init.type_id, .elements_id = init.elements_id}); -} - -static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, - SemIR::FunctionDecl inst) -> ConstantEvalResult { - return ConstantEvalResult::New(SemIR::StructValue{ - .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty}); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::ClassDecl inst) -> ConstantEvalResult { - // If the class has generic parameters, we don't produce a class type, but a - // callable whose return value is a class type. - if (context.classes().Get(inst.class_id).has_parameters()) { - return ConstantEvalResult::New(SemIR::StructValue{ - .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty}); - } - - // A non-generic class declaration evaluates to the class type. - return ConstantEvalResult::New( - SemIR::ClassType{.type_id = SemIR::TypeType::SingletonTypeId, - .class_id = inst.class_id, - .specific_id = SemIR::SpecificId::None}); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::InterfaceDecl inst) -> ConstantEvalResult { - // If the interface has generic parameters, we don't produce an interface - // type, but a callable whose return value is an interface type. - if (context.interfaces().Get(inst.interface_id).has_parameters()) { - return ConstantEvalResult::New(SemIR::StructValue{ - .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty}); - } - - // A non-generic interface declaration evaluates to a facet type. - return ConstantEvalResult::New(FacetTypeFromInterface( - context, inst.interface_id, SemIR::SpecificId::None)); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::SpecificConstant inst) - -> ConstantEvalResult { - // Pull the constant value out of the specific. - return ConstantEvalResult::Existing(SemIR::GetConstantValueInSpecific( - context.sem_ir(), inst.specific_id, inst.inst_id)); -} - -// Performs an access into an aggregate, retrieving the specified element. -static auto PerformAggregateAccess(Context& context, SemIR::Inst inst) - -> ConstantEvalResult { - auto access_inst = inst.As(); - if (auto aggregate = context.insts().TryGetAs( - access_inst.aggregate_id)) { - auto elements = context.inst_blocks().Get(aggregate->elements_id); - auto index = static_cast(access_inst.index.index); - CARBON_CHECK(index < elements.size(), "Access out of bounds."); - // `Phase` is not used here. If this element is a concrete constant, then - // so is the result of indexing, even if the aggregate also contains a - // symbolic context. - return ConstantEvalResult::Existing( - context.constant_values().Get(elements[index])); - } - - return ConstantEvalResult::New(inst); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::ClassElementAccess inst) - -> ConstantEvalResult { - return PerformAggregateAccess(context, inst); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::StructAccess inst) -> ConstantEvalResult { - return PerformAggregateAccess(context, inst); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::TupleAccess inst) -> ConstantEvalResult { - return PerformAggregateAccess(context, inst); -} - -static auto EvalConstantInst(Context& context, SemIRLoc loc, - SemIR::ImplWitnessAccess inst) - -> ConstantEvalResult { - // This is PerformAggregateAccess followed by GetConstantInSpecific. - if (auto witness = - context.insts().TryGetAs(inst.witness_id)) { - auto elements = context.inst_blocks().Get(witness->elements_id); - auto index = static_cast(inst.index.index); - CARBON_CHECK(index < elements.size(), "Access out of bounds."); - auto element = elements[index]; - if (!element.has_value()) { - // TODO: Perhaps this should be a `{}` value with incomplete type? - CARBON_DIAGNOSTIC(ImplAccessMemberBeforeComplete, Error, - "accessing member from impl before the end of " - "its definition"); - // TODO: Add note pointing to the impl declaration. - context.emitter().Emit(loc, ImplAccessMemberBeforeComplete); - return ConstantEvalResult::Error; - } - - LoadImportRef(context, element); - return ConstantEvalResult::Existing(GetConstantValueInSpecific( - context.sem_ir(), witness->specific_id, element)); - } - - return ConstantEvalResult::New(inst); -} - -static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, - SemIR::BindValue /*inst*/) -> ConstantEvalResult { - // TODO: Handle this once we've decided how to represent constant values of - // reference expressions. - return ConstantEvalResult::TODO; -} - -static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, - SemIR::Deref /*inst*/) -> ConstantEvalResult { - // TODO: Handle this. - return ConstantEvalResult::TODO; -} - -static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, - SemIR::Temporary /*inst*/) -> ConstantEvalResult { - // TODO: Handle this. Can we just return the value of `init_id`? - return ConstantEvalResult::TODO; -} - -static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, - SemIR::VtablePtr /*inst*/) -> ConstantEvalResult { - // TODO: Handle this. - return ConstantEvalResult::TODO; -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::AsCompatible inst) -> ConstantEvalResult { - // AsCompatible changes the type of the source instruction; its constant - // value, if there is one, needs to be modified to be of the same type. - auto value_id = context.constant_values().Get(inst.source_id); - CARBON_CHECK(value_id.is_constant()); - - auto value_inst = - context.insts().Get(context.constant_values().GetInstId(value_id)); - auto phase = GetPhase(context.constant_values(), - context.types().GetConstantId(inst.type_id)); - value_inst.SetType(inst.type_id); - - // Finish computing the new phase by incorporating the phases of the - // arguments. +// Given an instruction, compute its phase based on its operands. +static auto ComputeInstPhase(Context& context, SemIR::Inst inst) -> Phase { EvalContext eval_context(context, SemIR::InstId::None); - auto kinds = value_inst.ArgKinds(); - GetConstantValueForArg(eval_context, kinds.first, value_inst.arg0(), &phase); - GetConstantValueForArg(eval_context, kinds.second, value_inst.arg1(), &phase); + + auto phase = GetPhase(context.constant_values(), + context.types().GetConstantId(inst.type_id())); + auto kinds = inst.ArgKinds(); + GetConstantValueForArg(eval_context, kinds.first, inst.arg0(), &phase); + GetConstantValueForArg(eval_context, kinds.second, inst.arg1(), &phase); CARBON_CHECK(IsConstant(phase)); - - // We can't use `ConstantEvalResult::New` because it would use the wrong - // phase, so manually build a new constant. - return ConstantEvalResult::Existing( - MakeConstantResult(context, value_inst, phase)); + return phase; } -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::BindAlias inst) -> ConstantEvalResult { - return ConstantEvalResult::Existing( - context.constant_values().Get(inst.value_id)); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::ExportDecl inst) -> ConstantEvalResult { - return ConstantEvalResult::Existing( - context.constant_values().Get(inst.value_id)); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::NameRef inst) -> ConstantEvalResult { - return ConstantEvalResult::Existing( - context.constant_values().Get(inst.value_id)); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::ValueParamPattern inst) - -> ConstantEvalResult { - // TODO: Treat this as a non-expression (here and in GetExprCategory) - // once generic deduction doesn't need patterns to have constant values. - return ConstantEvalResult::Existing( - context.constant_values().Get(inst.subpattern_id)); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::Converted inst) -> ConstantEvalResult { - return ConstantEvalResult::Existing( - context.constant_values().Get(inst.result_id)); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::InitializeFrom inst) -> ConstantEvalResult { - return ConstantEvalResult::Existing( - context.constant_values().Get(inst.src_id)); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::SpliceBlock inst) -> ConstantEvalResult { - return ConstantEvalResult::Existing( - context.constant_values().Get(inst.result_id)); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::ValueOfInitializer inst) - -> ConstantEvalResult { - return ConstantEvalResult::Existing( - context.constant_values().Get(inst.init_id)); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::FacetAccessType inst) - -> ConstantEvalResult { - if (auto facet_value = context.insts().TryGetAs( - inst.facet_value_inst_id)) { - return ConstantEvalResult::Existing( - context.constant_values().Get(facet_value->type_inst_id)); +// Convert a ConstantEvalResult to a ConstantId. Factored out of +// TryEvalTypedInst to avoid repeated instantiation of common code. +static auto ConvertEvalResultToConstantId(Context& context, + ConstantEvalResult result, + Phase orig_phase) + -> SemIR::ConstantId { + if (result.is_new()) { + return MakeConstantResult( + context, result.new_inst(), + result.same_phase_as_inst() + ? orig_phase + : ComputeInstPhase(context, result.new_inst())); } - return ConstantEvalResult::New(inst); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::FacetAccessWitness inst) - -> ConstantEvalResult { - if (auto facet_value = context.insts().TryGetAs( - inst.facet_value_inst_id)) { - return ConstantEvalResult::Existing( - context.constant_values().Get(facet_value->witness_inst_id)); - } - return ConstantEvalResult::New(inst); -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::UnaryOperatorNot inst) - -> ConstantEvalResult { - // `not true` -> `false`, `not false` -> `true`. - // All other uses of unary `not` are non-constant. - auto const_id = context.constant_values().Get(inst.operand_id); - if (const_id.is_concrete()) { - auto value = context.insts().GetAs( - context.constant_values().GetInstId(const_id)); - value.value = SemIR::BoolValue::From(!value.value.ToBool()); - return ConstantEvalResult::New(value); - } - return ConstantEvalResult::NotConstant; -} - -static auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, - SemIR::ConstType inst) -> ConstantEvalResult { - // `const (const T)` evaluates to `const T`. - if (context.types().Is(inst.inner_id)) { - return ConstantEvalResult::Existing( - context.types().GetConstantId(inst.inner_id)); - } - // Otherwise, `const T` evaluates to itself. - return ConstantEvalResult::New(inst); -} - -static auto EvalConstantInst(Context& context, SemIRLoc loc, - SemIR::RequireCompleteType inst) - -> ConstantEvalResult { - auto witness_type_id = - GetSingletonType(context, SemIR::WitnessType::SingletonInstId); - - // If the type is a concrete constant, require it to be complete now. - auto complete_type_id = inst.complete_type_id; - if (context.types().GetConstantId(complete_type_id).is_concrete()) { - if (!TryToCompleteType(context, complete_type_id, loc, [&] { - // TODO: It'd be nice to report the original type prior to - // evaluation here. - CARBON_DIAGNOSTIC(IncompleteTypeInMonomorphization, Error, - "type {0} is incomplete", SemIR::TypeId); - return context.emitter().Build(loc, IncompleteTypeInMonomorphization, - complete_type_id); - })) { - return ConstantEvalResult::Error; - } - return ConstantEvalResult::New(SemIR::CompleteTypeWitness{ - .type_id = witness_type_id, - .object_repr_id = context.types().GetObjectRepr(complete_type_id)}); - } - - // If it's not a concrete constant, require it to be complete once it - // becomes one. - return ConstantEvalResult::New(inst); -} - -static auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, - SemIR::ImportRefUnloaded inst) - -> ConstantEvalResult { - CARBON_FATAL("ImportRefUnloaded should be loaded before TryEvalInst: {0}", - inst); + return result.existing(); } // Evaluates an instruction of a known type in an evaluation context. The @@ -2078,14 +1600,12 @@ static auto TryEvalTypedInst(EvalContext& eval_context, SemIR::InstId inst_id, ConstantKind == SemIR::InstConstantKind::WheneverPossible) { return MakeConstantResult(eval_context.context(), inst, phase); } else { - ConstantEvalResult result = EvalConstantInst( - eval_context.context(), eval_context.GetDiagnosticLoc({inst_id}), - inst.As()); - if (result.is_new()) { - return MakeConstantResult(eval_context.context(), result.new_inst(), - phase); - } - return result.existing(); + return ConvertEvalResultToConstantId( + eval_context.context(), + EvalConstantInst(eval_context.context(), + eval_context.GetDiagnosticLoc({inst_id}), + inst.As()), + phase); } } } diff --git a/toolchain/check/eval_inst.cpp b/toolchain/check/eval_inst.cpp new file mode 100644 index 000000000000..dfac6ce84381 --- /dev/null +++ b/toolchain/check/eval_inst.cpp @@ -0,0 +1,373 @@ +// 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 + +#include "toolchain/check/eval_inst.h" + +#include "toolchain/check/facet_type.h" +#include "toolchain/check/import_ref.h" +#include "toolchain/check/type.h" +#include "toolchain/check/type_completion.h" + +namespace Carbon::Check { + +// Performs an access into an aggregate, retrieving the specified element. +static auto PerformAggregateAccess(Context& context, SemIR::Inst inst) + -> ConstantEvalResult { + auto access_inst = inst.As(); + if (auto aggregate = context.insts().TryGetAs( + access_inst.aggregate_id)) { + auto elements = context.inst_blocks().Get(aggregate->elements_id); + auto index = static_cast(access_inst.index.index); + CARBON_CHECK(index < elements.size(), "Access out of bounds."); + // `Phase` is not used here. If this element is a concrete constant, then + // so is the result of indexing, even if the aggregate also contains a + // symbolic context. + return ConstantEvalResult::Existing( + context.constant_values().Get(elements[index])); + } + + return ConstantEvalResult::NewSamePhase(inst); +} + +auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, + SemIR::ArrayInit inst) -> ConstantEvalResult { + // TODO: Add an `ArrayValue` to represent a constant array object + // representation instead of using a `TupleValue`. + return ConstantEvalResult::NewSamePhase( + SemIR::TupleValue{.type_id = inst.type_id, .elements_id = inst.inits_id}); +} + +auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::ArrayType inst) + -> ConstantEvalResult { + auto bound_inst = context.insts().Get(inst.bound_id); + auto int_bound = bound_inst.TryAs(); + if (!int_bound) { + CARBON_CHECK(context.constant_values().Get(inst.bound_id).is_symbolic(), + "Unexpected inst {0} for template constant int", bound_inst); + return ConstantEvalResult::NewSamePhase(inst); + } + // TODO: We should check that the size of the resulting array type + // fits in 64 bits, not just that the bound does. Should we use a + // 32-bit limit for 32-bit targets? + const auto& bound_val = context.ints().Get(int_bound->int_id); + if (context.types().IsSignedInt(int_bound->type_id) && + bound_val.isNegative()) { + CARBON_DIAGNOSTIC(ArrayBoundNegative, Error, + "array bound of {0} is negative", TypedInt); + context.emitter().Emit(loc, ArrayBoundNegative, + {.type = int_bound->type_id, .value = bound_val}); + return ConstantEvalResult::Error; + } + if (bound_val.getActiveBits() > 64) { + CARBON_DIAGNOSTIC(ArrayBoundTooLarge, Error, + "array bound of {0} is too large", TypedInt); + context.emitter().Emit(loc, ArrayBoundTooLarge, + {.type = int_bound->type_id, .value = bound_val}); + return ConstantEvalResult::Error; + } + return ConstantEvalResult::NewSamePhase(inst); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::AsCompatible inst) -> ConstantEvalResult { + // AsCompatible changes the type of the source instruction; its constant + // value, if there is one, needs to be modified to be of the same type. + auto value_id = context.constant_values().Get(inst.source_id); + CARBON_CHECK(value_id.is_constant()); + + auto value_inst = + context.insts().Get(context.constant_values().GetInstId(value_id)); + value_inst.SetType(inst.type_id); + return ConstantEvalResult::NewAnyPhase(value_inst); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::BindAlias inst) + -> ConstantEvalResult { + // An alias evaluates to the value it's bound to. + return ConstantEvalResult::Existing( + context.constant_values().Get(inst.value_id)); +} + +auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, + SemIR::BindValue /*inst*/) -> ConstantEvalResult { + // TODO: Handle this once we've decided how to represent constant values of + // reference expressions. + return ConstantEvalResult::TODO; +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::ClassElementAccess inst) -> ConstantEvalResult { + return PerformAggregateAccess(context, inst); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::ClassDecl inst) + -> ConstantEvalResult { + // If the class has generic parameters, we don't produce a class type, but a + // callable whose return value is a class type. + if (context.classes().Get(inst.class_id).has_parameters()) { + return ConstantEvalResult::NewSamePhase(SemIR::StructValue{ + .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty}); + } + + // A non-generic class declaration evaluates to the class type. + return ConstantEvalResult::NewSamePhase( + SemIR::ClassType{.type_id = SemIR::TypeType::SingletonTypeId, + .class_id = inst.class_id, + .specific_id = SemIR::SpecificId::None}); +} + +auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, + SemIR::ClassInit inst) -> ConstantEvalResult { + // TODO: Add a `ClassValue` to represent a constant class object + // representation instead of using a `StructValue`. + return ConstantEvalResult::NewSamePhase(SemIR::StructValue{ + .type_id = inst.type_id, .elements_id = inst.elements_id}); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::ConstType inst) + -> ConstantEvalResult { + // `const (const T)` evaluates to `const T`. + if (context.types().Is(inst.inner_id)) { + return ConstantEvalResult::Existing( + context.types().GetConstantId(inst.inner_id)); + } + // Otherwise, `const T` evaluates to itself. + return ConstantEvalResult::NewSamePhase(inst); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::Converted inst) + -> ConstantEvalResult { + // A conversion evaluates to the result of the conversion. + return ConstantEvalResult::Existing( + context.constant_values().Get(inst.result_id)); +} + +auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, + SemIR::Deref /*inst*/) -> ConstantEvalResult { + // TODO: Handle this. + return ConstantEvalResult::TODO; +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::ExportDecl inst) -> ConstantEvalResult { + // An export instruction evaluates to the exported declaration. + return ConstantEvalResult::Existing( + context.constant_values().Get(inst.value_id)); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::FacetAccessType inst) -> ConstantEvalResult { + if (auto facet_value = context.insts().TryGetAs( + inst.facet_value_inst_id)) { + return ConstantEvalResult::Existing( + context.constant_values().Get(facet_value->type_inst_id)); + } + return ConstantEvalResult::NewSamePhase(inst); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::FacetAccessWitness inst) -> ConstantEvalResult { + if (auto facet_value = context.insts().TryGetAs( + inst.facet_value_inst_id)) { + return ConstantEvalResult::Existing( + context.constant_values().Get(facet_value->witness_inst_id)); + } + return ConstantEvalResult::NewSamePhase(inst); +} + +auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::FloatType inst) + -> ConstantEvalResult { + return ValidateFloatType(context, loc, inst) + ? ConstantEvalResult::NewSamePhase(inst) + : ConstantEvalResult::Error; +} + +auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, + SemIR::FunctionDecl inst) -> ConstantEvalResult { + // A function declaration evaluates to a function object, which is an empty + // object of function type. + // TODO: Eventually we may need to handle captures here. + return ConstantEvalResult::NewSamePhase(SemIR::StructValue{ + .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty}); +} + +auto EvalConstantInst(Context& context, SemIRLoc loc, + SemIR::ImplWitnessAccess inst) -> ConstantEvalResult { + // This is PerformAggregateAccess followed by GetConstantInSpecific. + if (auto witness = + context.insts().TryGetAs(inst.witness_id)) { + auto elements = context.inst_blocks().Get(witness->elements_id); + auto index = static_cast(inst.index.index); + CARBON_CHECK(index < elements.size(), "Access out of bounds."); + auto element = elements[index]; + if (!element.has_value()) { + // TODO: Perhaps this should be a `{}` value with incomplete type? + CARBON_DIAGNOSTIC(ImplAccessMemberBeforeComplete, Error, + "accessing member from impl before the end of " + "its definition"); + // TODO: Add note pointing to the impl declaration. + context.emitter().Emit(loc, ImplAccessMemberBeforeComplete); + return ConstantEvalResult::Error; + } + + LoadImportRef(context, element); + return ConstantEvalResult::Existing(GetConstantValueInSpecific( + context.sem_ir(), witness->specific_id, element)); + } + + return ConstantEvalResult::NewSamePhase(inst); +} + +auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, + SemIR::ImportRefUnloaded inst) -> ConstantEvalResult { + CARBON_FATAL("ImportRefUnloaded should be loaded before TryEvalInst: {0}", + inst); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::InitializeFrom inst) -> ConstantEvalResult { + // Initialization is not performed in-place during constant evaluation, so + // just return the value of the initializer. + return ConstantEvalResult::Existing( + context.constant_values().Get(inst.src_id)); +} + +auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::IntType inst) + -> ConstantEvalResult { + return ValidateIntType(context, loc, inst) + ? ConstantEvalResult::NewSamePhase(inst) + : ConstantEvalResult::Error; +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::InterfaceDecl inst) -> ConstantEvalResult { + // If the interface has generic parameters, we don't produce an interface + // type, but a callable whose return value is an interface type. + if (context.interfaces().Get(inst.interface_id).has_parameters()) { + return ConstantEvalResult::NewSamePhase(SemIR::StructValue{ + .type_id = inst.type_id, .elements_id = SemIR::InstBlockId::Empty}); + } + + // A non-generic interface declaration evaluates to a facet type. + return ConstantEvalResult::NewSamePhase(FacetTypeFromInterface( + context, inst.interface_id, SemIR::SpecificId::None)); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, SemIR::NameRef inst) + -> ConstantEvalResult { + // A name reference evaluates to the value the name resolves to. + return ConstantEvalResult::Existing( + context.constant_values().Get(inst.value_id)); +} + +auto EvalConstantInst(Context& context, SemIRLoc loc, + SemIR::RequireCompleteType inst) -> ConstantEvalResult { + auto witness_type_id = + GetSingletonType(context, SemIR::WitnessType::SingletonInstId); + + // If the type is a concrete constant, require it to be complete now. + auto complete_type_id = inst.complete_type_id; + if (context.types().GetConstantId(complete_type_id).is_concrete()) { + if (!TryToCompleteType(context, complete_type_id, loc, [&] { + // TODO: It'd be nice to report the original type prior to + // evaluation here. + CARBON_DIAGNOSTIC(IncompleteTypeInMonomorphization, Error, + "type {0} is incomplete", SemIR::TypeId); + return context.emitter().Build(loc, IncompleteTypeInMonomorphization, + complete_type_id); + })) { + return ConstantEvalResult::Error; + } + return ConstantEvalResult::NewSamePhase(SemIR::CompleteTypeWitness{ + .type_id = witness_type_id, + .object_repr_id = context.types().GetObjectRepr(complete_type_id)}); + } + + // If it's not a concrete constant, require it to be complete once it + // becomes one. + return ConstantEvalResult::NewSamePhase(inst); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::SpecificConstant inst) -> ConstantEvalResult { + // Pull the constant value out of the specific. + return ConstantEvalResult::Existing(SemIR::GetConstantValueInSpecific( + context.sem_ir(), inst.specific_id, inst.inst_id)); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::SpliceBlock inst) -> ConstantEvalResult { + // SpliceBlock evaluates to the result value that is (typically) within the + // block. This can be constant even if the block contains other non-constant + // instructions. + return ConstantEvalResult::Existing( + context.constant_values().Get(inst.result_id)); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::StructAccess inst) -> ConstantEvalResult { + return PerformAggregateAccess(context, inst); +} + +auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, + SemIR::StructInit inst) -> ConstantEvalResult { + return ConstantEvalResult::NewSamePhase(SemIR::StructValue{ + .type_id = inst.type_id, .elements_id = inst.elements_id}); +} + +auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, + SemIR::Temporary /*inst*/) -> ConstantEvalResult { + // TODO: Handle this. Can we just return the value of `init_id`? + return ConstantEvalResult::TODO; +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::TupleAccess inst) -> ConstantEvalResult { + return PerformAggregateAccess(context, inst); +} + +auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, + SemIR::TupleInit inst) -> ConstantEvalResult { + return ConstantEvalResult::NewSamePhase(SemIR::TupleValue{ + .type_id = inst.type_id, .elements_id = inst.elements_id}); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::UnaryOperatorNot inst) -> ConstantEvalResult { + // `not true` -> `false`, `not false` -> `true`. + // All other uses of unary `not` are non-constant. + auto const_id = context.constant_values().Get(inst.operand_id); + if (const_id.is_concrete()) { + auto value = context.insts().GetAs( + context.constant_values().GetInstId(const_id)); + value.value = SemIR::BoolValue::From(!value.value.ToBool()); + return ConstantEvalResult::NewSamePhase(value); + } + return ConstantEvalResult::NotConstant; +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::ValueOfInitializer inst) -> ConstantEvalResult { + // Values of value expressions and initializing expressions are represented in + // the same way during constant evaluation, so just return the value of the + // operand. + return ConstantEvalResult::Existing( + context.constant_values().Get(inst.init_id)); +} + +auto EvalConstantInst(Context& context, SemIRLoc /*loc*/, + SemIR::ValueParamPattern inst) -> ConstantEvalResult { + // TODO: Treat this as a non-expression (here and in GetExprCategory) + // once generic deduction doesn't need patterns to have constant values. + return ConstantEvalResult::Existing( + context.constant_values().Get(inst.subpattern_id)); +} + +auto EvalConstantInst(Context& /*context*/, SemIRLoc /*loc*/, + SemIR::VtablePtr /*inst*/) -> ConstantEvalResult { + // TODO: Handle this. + return ConstantEvalResult::TODO; +} + +} // namespace Carbon::Check diff --git a/toolchain/check/eval_inst.h b/toolchain/check/eval_inst.h new file mode 100644 index 000000000000..058de381c7f9 --- /dev/null +++ b/toolchain/check/eval_inst.h @@ -0,0 +1,123 @@ +// 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 + +#ifndef CARBON_TOOLCHAIN_CHECK_EVAL_INST_H_ +#define CARBON_TOOLCHAIN_CHECK_EVAL_INST_H_ + +#include "toolchain/check/eval.h" + +namespace Carbon::Check { + +// The result of constant evaluation of an instruction. +class ConstantEvalResult { + public: + // Produce a new constant as the result of an evaluation. The phase of the + // produced constant must be the same as the greatest phase of the operands in + // the evaluation. This will typically be the case if the evaluation uses all + // of its operands. + static auto NewSamePhase(SemIR::Inst inst) -> ConstantEvalResult { + return ConstantEvalResult(inst, /*same_phase_as_inst=*/true); + } + + // Produce a new constant as the result of an evaluation. The constant may + // have any phase. Use `NewSamePhase` instead where possible, as it avoids a + // phase recomputation. + static auto NewAnyPhase(SemIR::Inst inst) -> ConstantEvalResult { + return ConstantEvalResult(inst, /*same_phase_as_inst=*/false); + } + + // Produce an existing constant as the result of an evaluation. + static constexpr auto Existing(SemIR::ConstantId existing_id) + -> ConstantEvalResult { + CARBON_CHECK(existing_id.is_constant()); + return ConstantEvalResult(existing_id); + } + + // Indicates that an error was produced by evaluation. + static const ConstantEvalResult Error; + + // Indicates that we encountered an instruction whose evaluation is + // non-constant despite having constant operands. This should be rare; + // usually we want to produce an error in this case. + static const ConstantEvalResult NotConstant; + + // Indicates that we encountered an instruction for which we've not + // implemented constant evaluation yet. Instruction is treated as not + // constant. + static const ConstantEvalResult TODO; + + // Returns whether the result of evaluation is that we should produce a new + // constant described by `new_inst()` rather than an existing `ConstantId` + // described by `existing()`. + auto is_new() const -> bool { return !result_id_.has_value(); } + + // Returns the existing constant that this the instruction evaluates to, or + // `None` if this is evaluation produces a new constant. + auto existing() const -> SemIR::ConstantId { return result_id_; } + + // Returns the new constant instruction that is the result of evaluation. + auto new_inst() const -> SemIR::Inst { + CARBON_CHECK(is_new()); + return new_inst_; + } + + // Whether the new constant instruction is known to have the same phase as the + // evaluated instruction. Requires `is_new()`. + auto same_phase_as_inst() const -> bool { + CARBON_CHECK(is_new()); + return same_phase_as_inst_; + } + + private: + constexpr explicit ConstantEvalResult(SemIR::ConstantId raw_id) + : result_id_(raw_id), same_phase_as_inst_(false) {} + + explicit ConstantEvalResult(SemIR::Inst inst, bool same_phase_as_inst) + : result_id_(SemIR::ConstantId::None), + new_inst_(inst), + same_phase_as_inst_(same_phase_as_inst) {} + + SemIR::ConstantId result_id_; + union { + SemIR::Inst new_inst_; + }; + bool same_phase_as_inst_; +}; + +constexpr ConstantEvalResult ConstantEvalResult::Error = + Existing(SemIR::ErrorInst::SingletonConstantId); + +constexpr ConstantEvalResult ConstantEvalResult::NotConstant = + ConstantEvalResult(SemIR::ConstantId::NotConstant); + +constexpr ConstantEvalResult ConstantEvalResult::TODO = NotConstant; + +// `EvalConstantInst` evaluates an instruction whose operands are all constant, +// in a context unrelated to the enclosing evaluation. The function is given the +// instruction after its operands, including its type, are replaced by their +// evaluated value, and returns a `ConstantEvalResult` describing the result of +// evaluating the instruction. +// +// An overload is defined for each type whose constant kind is one of the +// following: +// +// - InstConstantKind::Indirect +// - InstConstantKind::SymbolicOnly +// - InstConstantKind::Conditional +// +// ... except for cases where the result of evaluation depends on the evaluation +// context itself. Those cases are handled by explicit specialization of +// `TryEvalTypedInst` in `eval.cpp` instead. +// +// Overloads are *declared* for all types, because there isn't a good way to +// declare only the overloads we want here without duplicating the list of +// types. Missing overloads will be diagnosed when linking. +#define CARBON_SEM_IR_INST_KIND(Kind) \ + auto EvalConstantInst(Context& context, SemIRLoc loc, SemIR::Kind inst) \ + -> ConstantEvalResult; +#include "toolchain/sem_ir/inst_kind.def" + +} // namespace Carbon::Check + +#endif // CARBON_TOOLCHAIN_CHECK_EVAL_INST_H_ diff --git a/toolchain/check/type.cpp b/toolchain/check/type.cpp index 9e54c936a4b6..2d4ee442ff2b 100644 --- a/toolchain/check/type.cpp +++ b/toolchain/check/type.cpp @@ -10,6 +10,64 @@ namespace Carbon::Check { +// Enforces that an integer type has a valid bit width. +auto ValidateIntType(Context& context, SemIRLoc loc, SemIR::IntType result) + -> bool { + auto bit_width = + context.insts().TryGetAs(result.bit_width_id); + if (!bit_width) { + // Symbolic bit width. + return true; + } + const auto& bit_width_val = context.ints().Get(bit_width->int_id); + if (bit_width_val.isZero() || + (context.types().IsSignedInt(bit_width->type_id) && + bit_width_val.isNegative())) { + CARBON_DIAGNOSTIC(IntWidthNotPositive, Error, + "integer type width of {0} is not positive", TypedInt); + context.emitter().Emit( + loc, IntWidthNotPositive, + {.type = bit_width->type_id, .value = bit_width_val}); + return false; + } + if (bit_width_val.ugt(IntStore::MaxIntWidth)) { + CARBON_DIAGNOSTIC(IntWidthTooLarge, Error, + "integer type width of {0} is greater than the " + "maximum supported width of {1}", + TypedInt, int); + context.emitter().Emit(loc, IntWidthTooLarge, + {.type = bit_width->type_id, .value = bit_width_val}, + IntStore::MaxIntWidth); + return false; + } + return true; +} + +// Enforces that the bit width is 64 for a float. +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; +} + +// Enforces that a float type has a valid bit width. +auto ValidateFloatType(Context& context, SemIRLoc loc, SemIR::FloatType result) + -> bool { + auto bit_width = + context.insts().TryGetAs(result.bit_width_id); + if (!bit_width) { + // Symbolic bit width. + return true; + } + return ValidateFloatBitWidth(context, loc, result.bit_width_id); +} + // Gets or forms a type_id for a type, given the instruction kind and arguments. template static auto GetTypeImpl(Context& context, EachArgT... each_arg) diff --git a/toolchain/check/type.h b/toolchain/check/type.h index 8d7f7ee77055..93128f9f42c0 100644 --- a/toolchain/check/type.h +++ b/toolchain/check/type.h @@ -11,6 +11,18 @@ namespace Carbon::Check { +// Enforces that an integer type has a valid bit width. +auto ValidateIntType(Context& context, SemIRLoc loc, SemIR::IntType result) + -> bool; + +// Enforces that the bit width is 64 for a float. +auto ValidateFloatBitWidth(Context& context, SemIRLoc loc, + SemIR::InstId inst_id) -> bool; + +// Enforces that a float type has a valid bit width. +auto ValidateFloatType(Context& context, SemIRLoc loc, SemIR::FloatType result) + -> bool; + // Gets the type to use for an unbound associated entity declared in this // interface. For example, this is the type of `I.T` after // `interface I { let T:! type; }`. The name of the interface is used for