mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-04 22:02:52 +01:00
Though I started this thinking about performance of parse of large
decimal integers, I extended it to generally improve performance of
integer values (TBH I hadn't expected such a difference for binary/hex,
but I'll take it).
Note I think tests change because I'm making subtle changes to bit
widths. The changes themselves appear harmless to me, but happy to make
changes if it'd help.
Bumping up the number of digits by 10x because it's not really a
performance issue anymore (eh, maybe somebody will want to specify a
256-byte value in binary). But, at a certain point it still seems like a
mistake if somebody has that many digits in a row.
Fixes #980
Highlighting benchmark differences:
```diff
- BM_ComputeValue_IntDecimalN/1 37.1 ns 37.1 ns 18887116
+ BM_ComputeValue_IntDecimalN/1 21.9 ns 21.9 ns 31902433
- BM_ComputeValue_IntDecimalN/10000 1251228680 ns 1250457559 ns 1
+ BM_ComputeValue_IntDecimalN/10000 458818 ns 458626 ns 1523
- BM_ComputeValue_IntBinaryN/1 29.0 ns 29.0 ns 24058533
+ BM_ComputeValue_IntBinaryN/1 22.2 ns 22.1 ns 31566949
- BM_ComputeValue_IntBinaryN/10000 1390557 ns 1389782 ns 506
+ BM_ComputeValue_IntBinaryN/10000 16402 ns 16396 ns 42744
- BM_ComputeValue_IntHexN/1 34.0 ns 34.0 ns 20562432
+ BM_ComputeValue_IntHexN/1 22.4 ns 22.4 ns 31238055
- BM_ComputeValue_IntHexN/10000 5387942 ns 5385262 ns 130
+ BM_ComputeValue_IntHexN/10000 39249 ns 39233 ns 17859
```
Benchmark before:
```
----------------------------------------------------------------------------
Benchmark Time CPU Iterations
----------------------------------------------------------------------------
BM_Lex_Float 10.6 ns 10.6 ns 66138191
BM_Lex_Int 15.5 ns 15.4 ns 45149703
BM_Lex_IntDecimalN/1 3.11 ns 3.11 ns 225524908
BM_Lex_IntDecimalN/10 11.8 ns 11.8 ns 56719805
BM_Lex_IntDecimalN/100 102 ns 102 ns 6867468
BM_Lex_IntDecimalN/1000 943 ns 942 ns 745313
BM_Lex_IntDecimalN/10000 9465 ns 9461 ns 73970
BM_ComputeValue_Float 61.6 ns 61.6 ns 11377463
BM_ComputeValue_Int 106 ns 106 ns 6587381
BM_ComputeValue_IntDecimalN/1 37.1 ns 37.1 ns 18887116
BM_ComputeValue_IntDecimalN/10 87.7 ns 87.7 ns 7960837
BM_ComputeValue_IntDecimalN/100 7963 ns 7956 ns 88858
BM_ComputeValue_IntDecimalN/1000 1212577 ns 1211906 ns 578
BM_ComputeValue_IntDecimalN/10000 1251228680 ns 1250457559 ns 1
BM_ComputeValue_IntBinaryN/1 29.0 ns 29.0 ns 24058533
BM_ComputeValue_IntBinaryN/10 69.4 ns 69.4 ns 10108642
BM_ComputeValue_IntBinaryN/100 963 ns 962 ns 726982
BM_ComputeValue_IntBinaryN/1000 21562 ns 21551 ns 32506
BM_ComputeValue_IntBinaryN/10000 1390557 ns 1389782 ns 506
BM_ComputeValue_IntHexN/1 34.0 ns 34.0 ns 20562432
BM_ComputeValue_IntHexN/10 70.4 ns 70.4 ns 9953165
BM_ComputeValue_IntHexN/100 1474 ns 1473 ns 472776
BM_ComputeValue_IntHexN/1000 61818 ns 61762 ns 11363
BM_ComputeValue_IntHexN/10000 5387942 ns 5385262 ns 130
```
Benchmark after:
```
----------------------------------------------------------------------------
Benchmark Time CPU Iterations
----------------------------------------------------------------------------
BM_Lex_Float 10.9 ns 10.9 ns 63993114
BM_Lex_Int 15.1 ns 15.1 ns 46869766
BM_Lex_IntDecimalN/1 3.16 ns 3.16 ns 220923300
BM_Lex_IntDecimalN/10 12.2 ns 12.2 ns 57731654
BM_Lex_IntDecimalN/100 102 ns 102 ns 6875516
BM_Lex_IntDecimalN/1000 942 ns 942 ns 742359
BM_Lex_IntDecimalN/10000 9353 ns 9350 ns 75096
BM_ComputeValue_Float 44.9 ns 44.9 ns 15619691
BM_ComputeValue_Int 48.9 ns 48.9 ns 14361507
BM_ComputeValue_IntDecimalN/1 21.9 ns 21.9 ns 31902433
BM_ComputeValue_IntDecimalN/10 30.3 ns 30.3 ns 23134117
BM_ComputeValue_IntDecimalN/100 224 ns 223 ns 3092567
BM_ComputeValue_IntDecimalN/1000 5834 ns 5830 ns 117469
BM_ComputeValue_IntDecimalN/10000 458818 ns 458626 ns 1523
BM_ComputeValue_IntBinaryN/1 22.2 ns 22.1 ns 31566949
BM_ComputeValue_IntBinaryN/10 32.9 ns 32.9 ns 21306927
BM_ComputeValue_IntBinaryN/100 198 ns 198 ns 3545277
BM_ComputeValue_IntBinaryN/1000 1671 ns 1669 ns 419656
BM_ComputeValue_IntBinaryN/10000 16402 ns 16396 ns 42744
BM_ComputeValue_IntHexN/1 22.4 ns 22.4 ns 31238055
BM_ComputeValue_IntHexN/10 47.8 ns 47.7 ns 14694407
BM_ComputeValue_IntHexN/100 436 ns 436 ns 1609794
BM_ComputeValue_IntHexN/1000 3966 ns 3962 ns 177109
BM_ComputeValue_IntHexN/10000 39249 ns 39233 ns 17859
```
Assisted-by: Google Antigravity with Gemini
438 lines
16 KiB
C++
438 lines
16 KiB
C++
// 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_BASE_INT_H_
|
|
#define CARBON_TOOLCHAIN_BASE_INT_H_
|
|
|
|
#include "common/check.h"
|
|
#include "llvm/ADT/APInt.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "toolchain/base/canonical_value_store.h"
|
|
#include "toolchain/base/index_base.h"
|
|
#include "toolchain/base/mem_usage.h"
|
|
#include "toolchain/base/value_store.h"
|
|
#include "toolchain/base/yaml.h"
|
|
|
|
namespace Carbon {
|
|
|
|
// Forward declare a testing peer so we can friend it.
|
|
namespace Testing {
|
|
struct IntStoreTestPeer;
|
|
} // namespace Testing
|
|
|
|
// Corresponds to a canonicalized integer value. This is used both for integer
|
|
// literal tokens, and integer values in SemIR. These always represent the
|
|
// abstract mathematical value -- signed and regardless of the needed precision.
|
|
//
|
|
// Small values are internalized into the ID itself. Large values are
|
|
// represented as an index into an array of `APInt`s with a canonicalized bit
|
|
// width. The ID itself can be queried for whether it is a value-embedded-ID or
|
|
// an index ID. The ID also provides APIs for extracting either the value or an
|
|
// index.
|
|
//
|
|
// ## Details of the encoding scheme ##
|
|
//
|
|
// We need all the values from a maximum to minimum, as well as a healthy range
|
|
// of indices, to fit within the token ID bits.
|
|
//
|
|
// We represent this as a signed `TokenIdBits`-bit 2s compliment integer. The
|
|
// sign extension from TokenIdBits to a register size can be folded into the
|
|
// shift used to extract those bits from compressed bitfield storage.
|
|
//
|
|
// We then divide the smallest 1/4th of that bit width's space to represent
|
|
// indices, and the larger 3/4ths to embedded values. For 23-bits total this
|
|
// still gives us 2 million unique integers larger than the embedded ones, which
|
|
// would be difficult to fill without exceeding the number of tokens we can lex
|
|
// (8 million). For non-token based integers, the indices can continue downward
|
|
// to the 32-bit signed integer minimum, supporting approximately 1.998 billion
|
|
// unique larger integers.
|
|
//
|
|
// Note the `None` ID can't be used with a token. This is OK as we expect
|
|
// invalid tokens to be *error* tokens and not need to represent an invalid
|
|
// integer.
|
|
class IntId : public Printable<IntId> {
|
|
public:
|
|
static constexpr llvm::StringLiteral Label = "int";
|
|
|
|
// The encoding of integer IDs ensures that IDs associated with tokens during
|
|
// lexing can fit into a compressed storage space. We arrange for
|
|
// `TokenIdBits` to be the minimum number of bits of storage for token
|
|
// associated IDs. The constant is public so the lexer can ensure it reserves
|
|
// adequate space.
|
|
//
|
|
// Note that there may still be IDs either not associated with
|
|
// tokens or computed after lexing outside of this range.
|
|
static constexpr int TokenIdBits = 23;
|
|
|
|
static const IntId None;
|
|
|
|
static auto MakeFromTokenPayload(uint32_t payload) -> IntId {
|
|
// Token-associated IDs are signed `TokenIdBits` integers, so force sign
|
|
// extension from that bit.
|
|
return IntId(static_cast<int32_t>(payload << TokenIdBitsShift) >>
|
|
TokenIdBitsShift);
|
|
}
|
|
|
|
// Construct an ID from a raw 32-bit ID value.
|
|
static constexpr auto MakeRaw(int32_t raw_id) -> IntId {
|
|
return IntId(raw_id);
|
|
}
|
|
|
|
// Tests whether the ID is an embedded value ID.
|
|
//
|
|
// Note `None` is not an embedded value, so this implies `has_value()` is
|
|
// true.
|
|
constexpr auto is_embedded_value() const -> bool { return id_ > ZeroIndexId; }
|
|
|
|
// Tests whether the ID is an index ID.
|
|
//
|
|
// Note `None` is represented as an index ID, so this is *not* sufficient to
|
|
// test `has_value()`.
|
|
constexpr auto is_index() const -> bool { return id_ <= ZeroIndexId; }
|
|
|
|
// Test whether a value is present.
|
|
//
|
|
// This does not distinguish between embedded values and index IDs, only
|
|
// whether some value is present.
|
|
constexpr auto has_value() const -> bool { return id_ != NoneId; }
|
|
|
|
// Converts an ID to the embedded value. Requires that `is_embedded_value()`
|
|
// is true.
|
|
constexpr auto AsValue() const -> int {
|
|
CARBON_DCHECK(is_embedded_value());
|
|
return id_;
|
|
}
|
|
|
|
// Converts an ID to an index. Requires that `is_index()` is true.
|
|
//
|
|
// Note `None` is represented as an index ID, and can be converted here.
|
|
constexpr auto AsIndex() const -> int {
|
|
CARBON_DCHECK(is_index());
|
|
return ZeroIndexId - id_;
|
|
}
|
|
|
|
// Returns the ID formatted as a lex token payload.
|
|
constexpr auto AsTokenPayload() const -> uint32_t {
|
|
uint32_t payload = id_;
|
|
// Ensure this ID round trips as the token payload.
|
|
CARBON_DCHECK(*this == MakeFromTokenPayload(payload));
|
|
return payload;
|
|
}
|
|
|
|
constexpr auto AsRaw() const -> int32_t { return id_; }
|
|
|
|
auto Print(llvm::raw_ostream& out) const -> void {
|
|
out << Label << "(";
|
|
if (is_embedded_value()) {
|
|
out << "value: " << AsValue();
|
|
} else if (is_index()) {
|
|
out << "index: " << AsIndex();
|
|
} else {
|
|
CARBON_CHECK(!has_value());
|
|
out << "<none>";
|
|
}
|
|
out << ")";
|
|
}
|
|
|
|
friend constexpr auto operator==(IntId lhs, IntId rhs) -> bool {
|
|
return lhs.id_ == rhs.id_;
|
|
}
|
|
friend constexpr auto operator<=>(IntId lhs, IntId rhs)
|
|
-> std::strong_ordering {
|
|
return lhs.id_ <=> rhs.id_;
|
|
}
|
|
|
|
private:
|
|
friend class IntStore;
|
|
friend Testing::IntStoreTestPeer;
|
|
|
|
// The shift needed when adjusting a between a `TokenIdBits`-width integer and
|
|
// a 32-bit integer.
|
|
static constexpr int TokenIdBitsShift = 32 - TokenIdBits;
|
|
|
|
// The maximum embedded value in an ID.
|
|
static constexpr int32_t MaxValue =
|
|
std::numeric_limits<int32_t>::max() >> TokenIdBitsShift;
|
|
|
|
// The ID value that represents an index of `0`. This is the first ID value
|
|
// representing an index, and all indices are `<=` to this.
|
|
//
|
|
// `ZeroIndexId` is the first index ID, and we encode indices as successive
|
|
// negative numbers counting downwards. The setup allows us to both use a
|
|
// comparison with this ID to distinguish value and index IDs, and to compute
|
|
// the actual index from the ID.
|
|
//
|
|
// The computation of an index in fact is just a subtraction:
|
|
// `ZeroIndexId - id_`. Subtraction is *also* how most CPUs implement the
|
|
// comparison, and so all of this ends up carefully constructed to enable very
|
|
// small code size when testing for an embedded value and when that test fails
|
|
// computing and using the index.
|
|
static constexpr int32_t ZeroIndexId =
|
|
std::numeric_limits<int32_t>::min() >> (TokenIdBitsShift + 1);
|
|
|
|
// The minimum embedded value in an ID.
|
|
static constexpr int32_t MinValue = ZeroIndexId + 1;
|
|
|
|
// The `None` ID, which needs to be placed after the largest index, which
|
|
// count downwards as IDs so below the smallest index ID, in order to optimize
|
|
// the code sequence needed to distinguish between integer and value IDs and
|
|
// to convert index IDs into actual indices small.
|
|
static constexpr int32_t NoneId = std::numeric_limits<int32_t>::min();
|
|
|
|
// The `None` index. This is the result of converting a `None` ID into an
|
|
// index. We ensure that conversion can be done so that we can simplify the
|
|
// code that first tries to use an embedded value, then converts to an index
|
|
// and checks that the index is still `None`.
|
|
static const int32_t NoneIndex;
|
|
|
|
// Document the specific values of some of these constants to help visualize
|
|
// how the bit patterns map from the above computations.
|
|
//
|
|
// clang-format off: visualizing bit positions
|
|
//
|
|
// Each bit is either `T` for part of the token or `P` as part
|
|
// of the available payload that we use for the ID:
|
|
//
|
|
// 0bTTTT'TTTT'TPPP'PPPP'PPPP'PPPP'PPPP'PPPP
|
|
static_assert(MaxValue == 0b0000'0000'0011'1111'1111'1111'1111'1111);
|
|
static_assert(ZeroIndexId == 0b1111'1111'1110'0000'0000'0000'0000'0000);
|
|
static_assert(MinValue == 0b1111'1111'1110'0000'0000'0000'0000'0001);
|
|
static_assert(NoneId == 0b1000'0000'0000'0000'0000'0000'0000'0000);
|
|
// clang-format on
|
|
|
|
constexpr explicit IntId(int32_t id) : id_(id) {}
|
|
|
|
int32_t id_;
|
|
};
|
|
|
|
inline constexpr IntId IntId::None(IntId::NoneId);
|
|
|
|
// Note that we initialize the `None` index in a constexpr context which
|
|
// ensures there is no UB in forming it. This helps ensure all the ID -> index
|
|
// conversions are correct because the `None` ID is at the limit of that range.
|
|
inline constexpr int32_t IntId::NoneIndex = None.AsIndex();
|
|
|
|
// A canonicalizing value store with deep optimizations for integers.
|
|
//
|
|
// This stores integers as abstract, signed mathematical integers. The bit width
|
|
// of specific `APInt` values, either as inputs or outputs, is disregarded for
|
|
// the purpose of canonicalization and the returned integer may use a very
|
|
// different bit width `APInt` than was used when adding. There are also
|
|
// optimized paths for adding integer values representable using native integer
|
|
// types.
|
|
//
|
|
// Because the integers in the store are canonicalized with only a minimum bit
|
|
// width, there are helper functions to coerce them to a specific desired bit
|
|
// width for use.
|
|
//
|
|
// This leverages a significant optimization for small integer values -- rather
|
|
// than canonicalizing and making them unique in a `ValueStore`, they are
|
|
// directly embedded in the `IntId` itself. Only larger integers are stored in
|
|
// an array of `APInt` values and represented as an index in the ID.
|
|
class IntStore {
|
|
public:
|
|
// We rely on `APInt` values having a minimum bit width.
|
|
static constexpr int MinAPWidth = 64;
|
|
|
|
// The maximum supported bit width of an integer type.
|
|
// TODO: Pick a maximum size and document it in the design. For now
|
|
// we use 2^^23, because that's the largest size that LLVM supports.
|
|
static constexpr int MaxIntWidth = 1 << 23;
|
|
|
|
// Pick a canonical bit width for the provided number of significant bits.
|
|
static auto CanonicalBitWidth(int significant_bits) -> int;
|
|
|
|
// Accepts a signed `int64_t` and uses the mathematical signed integer value
|
|
// of it as the added integer value.
|
|
//
|
|
// Returns the ID corresponding to this integer value, storing an `APInt` if
|
|
// necessary to represent it.
|
|
auto Add(int64_t value) -> IntId {
|
|
// First try directly making this into an ID.
|
|
if (IntId id = TryMakeValue(value); id.has_value()) [[likely]] {
|
|
return id;
|
|
}
|
|
|
|
// Fallback for larger values.
|
|
return AddLarge(value);
|
|
}
|
|
|
|
// Returns the ID corresponding to this integer value.
|
|
auto Add(llvm::APSInt value) -> IntId {
|
|
return value.isSigned() ? AddSigned(value) : AddUnsigned(value);
|
|
}
|
|
|
|
// Returns the ID corresponding to this signed integer value, storing an
|
|
// `APInt` if necessary to represent it.
|
|
auto AddSigned(llvm::APInt value) -> IntId {
|
|
// First try directly making this into an ID.
|
|
if (IntId id = TryMakeSignedValue(value); id.has_value()) [[likely]] {
|
|
return id;
|
|
}
|
|
|
|
// Fallback for larger values.
|
|
return AddSignedLarge(std::move(value));
|
|
}
|
|
|
|
// Returns the ID corresponding to an equivalent signed integer value for the
|
|
// provided unsigned integer value, storing an `APInt` if necessary to
|
|
// represent it.
|
|
auto AddUnsigned(llvm::APInt value) -> IntId {
|
|
// First try directly making this into an ID.
|
|
if (IntId id = TryMakeUnsignedValue(value); id.has_value()) [[likely]] {
|
|
return id;
|
|
}
|
|
|
|
// Fallback for larger values.
|
|
return AddUnsignedLarge(std::move(value));
|
|
}
|
|
|
|
// Returns the value for an ID.
|
|
//
|
|
// This will always be a signed `APInt` with a canonical bit width for the
|
|
// specific integer value in question.
|
|
auto Get(IntId id) const -> llvm::APInt {
|
|
if (id.is_embedded_value()) [[likely]] {
|
|
return llvm::APInt(MinAPWidth, id.AsValue(), /*isSigned=*/true);
|
|
}
|
|
return values_.Get(APIntId(id.AsIndex()));
|
|
}
|
|
|
|
// Returns the value for an ID adjusted to a specific bit width.
|
|
//
|
|
// Note that because we store canonical mathematical integers as signed
|
|
// integers, this always sign extends or truncates to the target width. The
|
|
// caller can then use that as a signed or unsigned integer as needed.
|
|
auto GetAtWidth(IntId id, int bit_width) const -> llvm::APInt {
|
|
llvm::APInt value = Get(id);
|
|
if (static_cast<int>(value.getBitWidth()) != bit_width) {
|
|
value = value.sextOrTrunc(bit_width);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// Returns the value for an ID adjusted to the bit width specified with
|
|
// another integer ID.
|
|
//
|
|
// This simply looks up the width integer ID, and then calls the above
|
|
// `GetAtWidth` overload using the value found for it. See that overload for
|
|
// more details.
|
|
auto GetAtWidth(IntId id, IntId bit_width_id) const -> llvm::APInt {
|
|
const llvm::APInt bit_width = Get(bit_width_id);
|
|
CARBON_CHECK(
|
|
bit_width.isStrictlyPositive() && bit_width.isSignedIntN(MinAPWidth),
|
|
"Invalid bit width value: {0}", bit_width);
|
|
return GetAtWidth(id, bit_width.getSExtValue());
|
|
}
|
|
|
|
// Accepts a signed `int64_t` and uses the mathematical signed integer value
|
|
// of it as the integer value to lookup. Returns the canonical ID for that
|
|
// value or returns `None` if not in the store.
|
|
auto Lookup(int64_t value) const -> IntId {
|
|
if (IntId id = TryMakeValue(value); id.has_value()) [[likely]] {
|
|
return id;
|
|
}
|
|
|
|
// Fallback for larger values.
|
|
return LookupLarge(value);
|
|
}
|
|
|
|
// Looks up the canonical ID for this signed integer value, or returns `None`
|
|
// if not in the store.
|
|
auto LookupSigned(llvm::APInt value) const -> IntId {
|
|
if (IntId id = TryMakeSignedValue(value); id.has_value()) [[likely]] {
|
|
return id;
|
|
}
|
|
|
|
// Fallback for larger values.
|
|
return LookupSignedLarge(std::move(value));
|
|
}
|
|
|
|
// Output a YAML description of this data structure. Note that this will only
|
|
// include the integers that required storing, not those successfully embedded
|
|
// into the ID space.
|
|
auto OutputYaml() const -> Yaml::OutputMapping;
|
|
|
|
auto values() const [[clang::lifetimebound]] -> auto {
|
|
return values_.values();
|
|
}
|
|
auto size() const -> size_t { return values_.size(); }
|
|
|
|
// Collects the memory usage of the separately stored integers.
|
|
auto CollectMemUsage(MemUsage& mem_usage, llvm::StringRef label) const
|
|
-> void;
|
|
|
|
private:
|
|
friend struct Testing::IntStoreTestPeer;
|
|
|
|
// Used for `values_`; tracked using `IntId`'s index range.
|
|
struct APIntId : IdBase<APIntId> {
|
|
static constexpr llvm::StringLiteral Label = "ap_int";
|
|
static const APIntId None;
|
|
using IdBase::IdBase;
|
|
};
|
|
|
|
static auto MakeIndexOrNone(int index) -> IntId {
|
|
CARBON_DCHECK(index >= 0 && index <= IntId::NoneIndex);
|
|
return IntId(IntId::ZeroIndexId - index);
|
|
}
|
|
|
|
// Tries to make a signed 64-bit integer into an embedded value in the ID, and
|
|
// if unable to do that returns the `None` ID.
|
|
static auto TryMakeValue(int64_t value) -> IntId {
|
|
if (IntId::MinValue <= value && value <= IntId::MaxValue) {
|
|
return IntId(value);
|
|
}
|
|
|
|
return IntId::None;
|
|
}
|
|
|
|
// Tries to make a signed APInt into an embedded value in the ID, and if
|
|
// unable to do that returns the `None` ID.
|
|
static auto TryMakeSignedValue(llvm::APInt value) -> IntId {
|
|
if (value.sge(IntId::MinValue) && value.sle(IntId::MaxValue)) {
|
|
return IntId(value.getSExtValue());
|
|
}
|
|
|
|
return IntId::None;
|
|
}
|
|
|
|
// Tries to make an unsigned APInt into an embedded value in the ID, and if
|
|
// unable to do that returns the `None` ID.
|
|
static auto TryMakeUnsignedValue(llvm::APInt value) -> IntId {
|
|
if (value.ule(IntId::MaxValue)) {
|
|
return IntId(value.getZExtValue());
|
|
}
|
|
|
|
return IntId::None;
|
|
}
|
|
|
|
// Canonicalize an incoming signed APInt to the correct bit width.
|
|
static auto CanonicalizeSigned(llvm::APInt value) -> llvm::APInt;
|
|
|
|
// Canonicalize an incoming unsigned APInt to the correct bit width.
|
|
static auto CanonicalizeUnsigned(llvm::APInt value) -> llvm::APInt;
|
|
|
|
// Helper functions for handling values that are large enough to require an
|
|
// allocated `APInt` for storage. Creating or manipulating that storage is
|
|
// only a few lines of code, but we move these out-of-line because the
|
|
// generated code is big and harms performance for the non-`Large` common
|
|
// case.
|
|
auto AddLarge(int64_t value) -> IntId;
|
|
auto AddSignedLarge(llvm::APInt value) -> IntId;
|
|
auto AddUnsignedLarge(llvm::APInt value) -> IntId;
|
|
auto LookupLarge(int64_t value) const -> IntId;
|
|
auto LookupSignedLarge(llvm::APInt value) const -> IntId;
|
|
|
|
// Stores values which don't fit in an IntId. These are always signed.
|
|
CanonicalValueStore<APIntId, llvm::APInt> values_;
|
|
};
|
|
|
|
inline constexpr IntStore::APIntId IntStore::APIntId::None(
|
|
IntId::None.AsIndex());
|
|
|
|
} // namespace Carbon
|
|
|
|
#endif // CARBON_TOOLCHAIN_BASE_INT_H_
|