inst.h 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #ifndef CARBON_TOOLCHAIN_SEM_IR_INST_H_
  5. #define CARBON_TOOLCHAIN_SEM_IR_INST_H_
  6. #include <cstdint>
  7. #include <type_traits>
  8. #include "common/check.h"
  9. #include "common/ostream.h"
  10. #include "common/struct_reflection.h"
  11. #include "toolchain/base/index_base.h"
  12. #include "toolchain/parse/tree.h"
  13. #include "toolchain/sem_ir/builtin_kind.h"
  14. #include "toolchain/sem_ir/inst_kind.h"
  15. #include "toolchain/sem_ir/typed_insts.h"
  16. namespace Carbon::SemIR {
  17. // Information about an instruction-like type, which is a type that an Inst can
  18. // be converted to and from. The `Enabled` parameter is used to check
  19. // requirements on the type in the specializations of this template.
  20. template <typename InstLikeType, bool Enabled = true>
  21. struct InstLikeTypeInfo;
  22. // A helper base class for instruction-like types that are structs.
  23. template <typename InstLikeType>
  24. struct InstLikeTypeInfoBase {
  25. // The derived class. Useful to allow SFINAE on whether a type is
  26. // instruction-like: `typename InstLikeTypeInfo<T>::Self` is valid only if `T`
  27. // is instruction-like.
  28. using Self = InstLikeTypeInfo<InstLikeType>;
  29. // A corresponding std::tuple<...> type.
  30. using Tuple =
  31. decltype(StructReflection::AsTuple(std::declval<InstLikeType>()));
  32. static constexpr int FirstArgField = HasKindMemberAsField<InstLikeType> +
  33. HasParseNodeMember<InstLikeType> +
  34. HasTypeIdMember<InstLikeType>;
  35. static constexpr int NumArgs = std::tuple_size_v<Tuple> - FirstArgField;
  36. static_assert(NumArgs <= 2,
  37. "Unsupported: typed inst has more than two data fields");
  38. template <int N>
  39. using ArgType = std::tuple_element_t<FirstArgField + N, Tuple>;
  40. template <int N>
  41. static auto Get(InstLikeType inst) -> ArgType<N> {
  42. return std::get<FirstArgField + N>(StructReflection::AsTuple(inst));
  43. }
  44. };
  45. // A particular type of instruction is instruction-like.
  46. template <typename TypedInst>
  47. struct InstLikeTypeInfo<
  48. TypedInst, static_cast<bool>(std::is_same_v<const InstKind::Definition,
  49. decltype(TypedInst::Kind)>)>
  50. : InstLikeTypeInfoBase<TypedInst> {
  51. static_assert(!HasKindMemberAsField<TypedInst>,
  52. "Instruction type should not have a kind field");
  53. static auto GetKind(TypedInst /*inst*/) -> InstKind {
  54. return TypedInst::Kind;
  55. }
  56. static auto IsKind(InstKind kind) -> bool { return kind == TypedInst::Kind; }
  57. // A name that can be streamed to an llvm::raw_ostream.
  58. static auto DebugName() -> InstKind { return TypedInst::Kind; }
  59. };
  60. // An instruction category is instruction-like.
  61. template <typename InstCat>
  62. struct InstLikeTypeInfo<
  63. InstCat, static_cast<bool>(
  64. std::is_same_v<const InstKind&, decltype(InstCat::Kinds[0])>)>
  65. : InstLikeTypeInfoBase<InstCat> {
  66. static_assert(HasKindMemberAsField<InstCat>,
  67. "Instruction category should have a kind field");
  68. static auto GetKind(InstCat cat) -> InstKind { return cat.kind; }
  69. static auto IsKind(InstKind kind) -> bool {
  70. for (InstKind k : InstCat::Kinds) {
  71. if (k == kind) {
  72. return true;
  73. }
  74. }
  75. return false;
  76. }
  77. // A name that can be streamed to an llvm::raw_ostream.
  78. static auto DebugName() -> std::string {
  79. std::string str;
  80. llvm::raw_string_ostream out(str);
  81. out << "{";
  82. llvm::ListSeparator sep;
  83. for (auto kind : InstCat::Kinds) {
  84. out << sep << kind;
  85. }
  86. out << "}";
  87. return out.str();
  88. }
  89. };
  90. // A type-erased representation of a SemIR instruction, that may be constructed
  91. // from the specific kinds of instruction defined in `typed_insts.h`. This
  92. // provides access to common fields present on most or all kinds of
  93. // instructions:
  94. //
  95. // - `parse_node` for error placement.
  96. // - `kind` for run-time logic when the input Kind is unknown.
  97. // - `type_id` for quick type checking.
  98. //
  99. // In addition, kind-specific data can be accessed by casting to the specific
  100. // kind of instruction:
  101. //
  102. // - Use `inst.kind()` or `Is<InstLikeType>` to determine what kind of
  103. // instruction it is.
  104. // - Cast to a specific type using `inst.As<InstLikeType>()`
  105. // - Using the wrong kind in `inst.As<InstLikeType>()` is a programming error,
  106. // and will CHECK-fail in debug modes (opt may too, but it's not an API
  107. // guarantee).
  108. // - Use `inst.TryAs<InstLikeType>()` to safely access type-specific instruction
  109. // data where the instruction's kind is not known.
  110. class Inst : public Printable<Inst> {
  111. public:
  112. template <typename TypedInst,
  113. typename Info = typename InstLikeTypeInfo<TypedInst>::Self>
  114. // NOLINTNEXTLINE(google-explicit-constructor)
  115. Inst(TypedInst typed_inst)
  116. : parse_node_(Parse::NodeId::Invalid),
  117. // Always overwritten below.
  118. kind_(InstKind::Create({})),
  119. type_id_(TypeId::Invalid),
  120. arg0_(InstId::InvalidIndex),
  121. arg1_(InstId::InvalidIndex) {
  122. if constexpr (HasParseNodeMember<TypedInst>) {
  123. parse_node_ = typed_inst.parse_node;
  124. }
  125. if constexpr (HasKindMemberAsField<TypedInst>) {
  126. kind_ = typed_inst.kind;
  127. } else {
  128. kind_ = TypedInst::Kind;
  129. }
  130. if constexpr (HasTypeIdMember<TypedInst>) {
  131. type_id_ = typed_inst.type_id;
  132. }
  133. if constexpr (Info::NumArgs > 0) {
  134. arg0_ = ToRaw(Info::template Get<0>(typed_inst));
  135. }
  136. if constexpr (Info::NumArgs > 1) {
  137. arg1_ = ToRaw(Info::template Get<1>(typed_inst));
  138. }
  139. }
  140. // Returns whether this instruction has the specified type.
  141. template <typename TypedInst, typename Info = InstLikeTypeInfo<TypedInst>>
  142. auto Is() const -> bool {
  143. return Info::IsKind(kind());
  144. }
  145. // Casts this instruction to the given typed instruction, which must match the
  146. // instruction's kind, and returns the typed instruction.
  147. template <typename TypedInst, typename Info = InstLikeTypeInfo<TypedInst>>
  148. auto As() const -> TypedInst {
  149. CARBON_CHECK(Is<TypedInst>()) << "Casting inst of kind " << kind()
  150. << " to wrong kind " << Info::DebugName();
  151. auto build_with_parse_node_onwards = [&](auto... parse_node_onwards) {
  152. if constexpr (HasKindMemberAsField<TypedInst>) {
  153. return TypedInst{kind(), parse_node_onwards...};
  154. } else {
  155. return TypedInst{parse_node_onwards...};
  156. }
  157. };
  158. auto build_with_type_id_onwards = [&](auto... type_id_onwards) {
  159. if constexpr (HasParseNodeMember<TypedInst>) {
  160. return build_with_parse_node_onwards(
  161. decltype(TypedInst::parse_node)(parse_node()), type_id_onwards...);
  162. } else {
  163. return build_with_parse_node_onwards(type_id_onwards...);
  164. }
  165. };
  166. auto build_with_args = [&](auto... args) {
  167. if constexpr (HasTypeIdMember<TypedInst>) {
  168. return build_with_type_id_onwards(type_id(), args...);
  169. } else {
  170. return build_with_type_id_onwards(args...);
  171. }
  172. };
  173. if constexpr (Info::NumArgs == 0) {
  174. return build_with_args();
  175. } else if constexpr (Info::NumArgs == 1) {
  176. return build_with_args(
  177. FromRaw<typename Info::template ArgType<0>>(arg0_));
  178. } else if constexpr (Info::NumArgs == 2) {
  179. return build_with_args(
  180. FromRaw<typename Info::template ArgType<0>>(arg0_),
  181. FromRaw<typename Info::template ArgType<1>>(arg1_));
  182. }
  183. }
  184. // If this instruction is the given kind, returns a typed instruction,
  185. // otherwise returns nullopt.
  186. template <typename TypedInst>
  187. auto TryAs() const -> std::optional<TypedInst> {
  188. if (Is<TypedInst>()) {
  189. return As<TypedInst>();
  190. } else {
  191. return std::nullopt;
  192. }
  193. }
  194. auto parse_node() const -> Parse::NodeId { return parse_node_; }
  195. auto kind() const -> InstKind { return kind_; }
  196. // Gets the type of the value produced by evaluating this instruction.
  197. auto type_id() const -> TypeId { return type_id_; }
  198. auto Print(llvm::raw_ostream& out) const -> void;
  199. private:
  200. friend class InstTestHelper;
  201. // Raw constructor, used for testing.
  202. explicit Inst(InstKind kind, Parse::NodeId parse_node, TypeId type_id,
  203. int32_t arg0, int32_t arg1)
  204. : parse_node_(parse_node),
  205. kind_(kind),
  206. type_id_(type_id),
  207. arg0_(arg0),
  208. arg1_(arg1) {}
  209. // Convert a field to its raw representation, used as `arg0_` / `arg1_`.
  210. static constexpr auto ToRaw(IdBase base) -> int32_t { return base.index; }
  211. static constexpr auto ToRaw(BuiltinKind kind) -> int32_t {
  212. return kind.AsInt();
  213. }
  214. // Convert a field from its raw representation.
  215. template <typename T>
  216. static constexpr auto FromRaw(int32_t raw) -> T {
  217. return T(raw);
  218. }
  219. template <>
  220. constexpr auto FromRaw<BuiltinKind>(int32_t raw) -> BuiltinKind {
  221. return BuiltinKind::FromInt(raw);
  222. }
  223. Parse::NodeId parse_node_;
  224. InstKind kind_;
  225. TypeId type_id_;
  226. // Use `As` to access arg0 and arg1.
  227. int32_t arg0_;
  228. int32_t arg1_;
  229. };
  230. // TODO: This is currently 20 bytes because we sometimes have 2 arguments for a
  231. // pair of Insts. However, InstKind is 1 byte; if args were 3.5 bytes, we could
  232. // potentially shrink Inst by 4 bytes. This may be worth investigating further.
  233. static_assert(sizeof(Inst) == 20, "Unexpected Inst size");
  234. // Instruction-like types can be printed by converting them to instructions.
  235. template <typename TypedInst,
  236. typename = typename InstLikeTypeInfo<TypedInst>::Self>
  237. inline auto operator<<(llvm::raw_ostream& out, TypedInst inst)
  238. -> llvm::raw_ostream& {
  239. Inst(inst).Print(out);
  240. return out;
  241. }
  242. } // namespace Carbon::SemIR
  243. #endif // CARBON_TOOLCHAIN_SEM_IR_INST_H_