blob: d8f4a35e76ecc9707f935f8474b376179625cdbf [file] [log] [blame]
//===--- Enum.cpp - Runtime declarations for enums ------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// Swift runtime functions in support of enums.
//
//===----------------------------------------------------------------------===//
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MathExtras.h"
#include "swift/Runtime/Metadata.h"
#include "swift/Runtime/Enum.h"
#include "swift/Runtime/Debug.h"
#include "Private.h"
#include <cstring>
#include <algorithm>
using namespace swift;
static unsigned getNumTagBytes(size_t size, unsigned emptyCases,
unsigned payloadCases) {
// We can use the payload area with a tag bit set somewhere outside of the
// payload area to represent cases. See how many bytes we need to cover
// all the empty cases.
unsigned numTags = payloadCases;
if (emptyCases > 0) {
if (size >= 4)
// Assume that one tag bit is enough if the precise calculation overflows
// an int32.
numTags += 1;
else {
unsigned bits = size * 8U;
unsigned casesPerTagBitValue = 1U << bits;
numTags += ((emptyCases + (casesPerTagBitValue-1U)) >> bits);
}
}
return (numTags <= 1 ? 0 :
numTags < 256 ? 1 :
numTags < 65536 ? 2 : 4);
}
/// This is a small and fast implementation of memcpy with a constant count. It
/// should be a performance win for small constant values where the function
/// can be inlined, the loop unrolled and the memory accesses merged.
template <unsigned count> static void small_memcpy(void *dest, const void *src) {
uint8_t *d8 = (uint8_t*)dest;
const uint8_t *s8 = (const uint8_t*)src;
for (unsigned i = 0; i < count; i++) {
*d8++ = *s8++;
}
}
static inline void small_memcpy(void *dest, const void *src, unsigned count) {
// This is specialization of the memcpy line below with
// specialization for values of 1, 2 and 4.
// memcpy(dst, src, count)
if (count == 1) {
small_memcpy<1>(dest, src);
} else if (count == 2) {
small_memcpy<2>(dest, src);
} else if (count == 4) {
small_memcpy<4>(dest, src);
} else {
crash("Tagbyte values should be 1, 2 or 4.");
}
}
void
swift::swift_initEnumValueWitnessTableSinglePayload(ValueWitnessTable *vwtable,
const TypeLayout *payloadLayout,
unsigned emptyCases) {
size_t payloadSize = payloadLayout->size;
unsigned payloadNumExtraInhabitants
= payloadLayout->getNumExtraInhabitants();
unsigned unusedExtraInhabitants = 0;
// If there are enough extra inhabitants for all of the cases, then the size
// of the enum is the same as its payload.
size_t size;
if (payloadNumExtraInhabitants >= emptyCases) {
size = payloadSize;
unusedExtraInhabitants = payloadNumExtraInhabitants - emptyCases;
} else {
size = payloadSize + getNumTagBytes(payloadSize,
emptyCases - payloadNumExtraInhabitants,
1 /*payload case*/);
}
size_t align = payloadLayout->flags.getAlignment();
vwtable->size = size;
vwtable->flags = payloadLayout->flags
.withExtraInhabitants(unusedExtraInhabitants > 0)
.withEnumWitnesses(true)
.withInlineStorage(ValueWitnessTable::isValueInline(size, align));
auto rawStride = llvm::alignTo(size, align);
vwtable->stride = rawStride == 0 ? 1 : rawStride;
// Substitute in better common value witnesses if we have them.
// If the payload type is a single-refcounted pointer, and the enum has
// a single empty case, then we can borrow the witnesses of the single
// refcounted pointer type, since swift_retain and objc_retain are both
// nil-aware. Most single-refcounted types will use the standard
// value witness tables for NativeObject or UnknownObject. This isn't
// foolproof but should catch the common case of optional class types.
#if OPTIONAL_OBJECT_OPTIMIZATION
auto payloadVWT = payload->getValueWitnesses();
if (emptyCases == 1
&& (payloadVWT == &VALUE_WITNESS_SYM(Bo)
#if SWIFT_OBJC_INTEROP
|| payloadVWT == &VALUE_WITNESS_SYM(BO)
#endif
)) {
#define WANT_ONLY_REQUIRED_VALUE_WITNESSES
#define VALUE_WITNESS(LOWER_ID, UPPER_ID) \
vwtable->LOWER_ID = payloadVWT->LOWER_ID;
#define DATA_VALUE_WITNESS(LOWER_ID, UPPER_ID, TYPE)
#include "swift/ABI/ValueWitness.def"
} else {
#endif
installCommonValueWitnesses(vwtable);
#if OPTIONAL_OBJECT_OPTIMIZATION
}
#endif
// If the payload has extra inhabitants left over after the ones we used,
// forward them as our own.
if (unusedExtraInhabitants > 0) {
auto xiVWTable = static_cast<ExtraInhabitantsValueWitnessTable*>(vwtable);
xiVWTable->extraInhabitantFlags = ExtraInhabitantFlags()
.withNumExtraInhabitants(unusedExtraInhabitants);
}
}
int swift::swift_getEnumCaseSinglePayload(const OpaqueValue *value,
const Metadata *payload,
unsigned emptyCases)
SWIFT_CC(RegisterPreservingCC_IMPL) {
auto *payloadWitnesses = payload->getValueWitnesses();
auto payloadSize = payloadWitnesses->getSize();
auto payloadNumExtraInhabitants = payloadWitnesses->getNumExtraInhabitants();
// If there are extra tag bits, check them.
if (emptyCases > payloadNumExtraInhabitants) {
auto *valueAddr = reinterpret_cast<const uint8_t*>(value);
auto *extraTagBitAddr = valueAddr + payloadSize;
unsigned extraTagBits = 0;
unsigned numBytes = getNumTagBytes(payloadSize,
emptyCases-payloadNumExtraInhabitants,
1 /*payload case*/);
#if defined(__BIG_ENDIAN__)
small_memcpy(reinterpret_cast<uint8_t*>(&extraTagBits) + 4 - numBytes,
extraTagBitAddr, numBytes);
#else
small_memcpy(&extraTagBits, extraTagBitAddr, numBytes);
#endif
// If the extra tag bits are zero, we have a valid payload or
// extra inhabitant (checked below). If nonzero, form the case index from
// the extra tag value and the value stored in the payload.
if (extraTagBits > 0) {
unsigned caseIndexFromExtraTagBits = payloadSize >= 4
? 0 : (extraTagBits - 1U) << (payloadSize*8U);
// In practice we should need no more than four bytes from the payload
// area.
unsigned caseIndexFromValue = 0;
#if defined(__BIG_ENDIAN__)
unsigned numPayloadTagBytes = std::min(size_t(4), payloadSize);
memcpy(reinterpret_cast<uint8_t*>(&caseIndexFromValue) + 4 -
numPayloadTagBytes, valueAddr, numPayloadTagBytes);
#else
memcpy(&caseIndexFromValue, valueAddr,
std::min(size_t(4), payloadSize));
#endif
return (caseIndexFromExtraTagBits | caseIndexFromValue)
+ payloadNumExtraInhabitants;
}
}
// If there are extra inhabitants, see whether the payload is valid.
if (payloadNumExtraInhabitants > 0) {
return
static_cast<const ExtraInhabitantsValueWitnessTable*>(payloadWitnesses)
->getExtraInhabitantIndex(value, payload);
}
// Otherwise, we have always have a valid payload.
return -1;
}
void swift::swift_storeEnumTagSinglePayload(OpaqueValue *value,
const Metadata *payload,
int whichCase, unsigned emptyCases)
SWIFT_CC(RegisterPreservingCC_IMPL) {
auto *payloadWitnesses = payload->getValueWitnesses();
auto payloadSize = payloadWitnesses->getSize();
unsigned payloadNumExtraInhabitants
= payloadWitnesses->getNumExtraInhabitants();
auto *valueAddr = reinterpret_cast<uint8_t*>(value);
auto *extraTagBitAddr = valueAddr + payloadSize;
unsigned numExtraTagBytes = emptyCases > payloadNumExtraInhabitants
? getNumTagBytes(payloadSize, emptyCases - payloadNumExtraInhabitants,
1 /*payload case*/)
: 0;
// For payload or extra inhabitant cases, zero-initialize the extra tag bits,
// if any.
if (whichCase < (int)payloadNumExtraInhabitants) {
// The two most common values for numExtraTagBytes are zero and one.
// Try to avoid calling bzero by specializing for these values.
if (numExtraTagBytes != 0) {
if (numExtraTagBytes == 1) {
// Zero a single byte.
*((char*)(extraTagBitAddr)) = 0;
} else {
// Zero the buffer.
memset(extraTagBitAddr, 0, numExtraTagBytes);
}
}
// If this is the payload case, we're done.
if (whichCase == -1)
return;
// Store the extra inhabitant.
static_cast<const ExtraInhabitantsValueWitnessTable*>(payloadWitnesses)
->storeExtraInhabitant(value, whichCase, payload);
return;
}
// Factor the case index into payload and extra tag parts.
unsigned caseIndex = whichCase - payloadNumExtraInhabitants;
unsigned payloadIndex, extraTagIndex;
if (payloadSize >= 4) {
extraTagIndex = 1;
payloadIndex = caseIndex;
} else {
unsigned payloadBits = payloadSize * 8U;
extraTagIndex = 1U + (caseIndex >> payloadBits);
payloadIndex = caseIndex & ((1U << payloadBits) - 1U);
}
// Store into the value.
#if defined(__BIG_ENDIAN__)
unsigned numPayloadTagBytes = std::min(size_t(4), payloadSize);
memcpy(valueAddr,
reinterpret_cast<uint8_t*>(&payloadIndex) + 4 - numPayloadTagBytes,
numPayloadTagBytes);
if (payloadSize > 4)
memset(valueAddr + 4, 0, payloadSize - 4);
memcpy(extraTagBitAddr,
reinterpret_cast<uint8_t*>(&extraTagIndex) + 4 - numExtraTagBytes,
numExtraTagBytes);
#else
memcpy(valueAddr, &payloadIndex, std::min(size_t(4), payloadSize));
if (payloadSize > 4)
memset(valueAddr + 4, 0, payloadSize - 4);
memcpy(extraTagBitAddr, &extraTagIndex, numExtraTagBytes);
#endif
}
void
swift::swift_initEnumMetadataMultiPayload(ValueWitnessTable *vwtable,
EnumMetadata *enumType,
unsigned numPayloads,
const TypeLayout * const *payloadLayouts) {
// Accumulate the layout requirements of the payloads.
size_t payloadSize = 0, alignMask = 0;
bool isPOD = true, isBT = true;
for (unsigned i = 0; i < numPayloads; ++i) {
const TypeLayout *payloadLayout = payloadLayouts[i];
payloadSize
= std::max(payloadSize, (size_t)payloadLayout->size);
alignMask |= payloadLayout->flags.getAlignmentMask();
isPOD &= payloadLayout->flags.isPOD();
isBT &= payloadLayout->flags.isBitwiseTakable();
}
// Store the max payload size in the metadata.
assignUnlessEqual(enumType->getPayloadSize(), payloadSize);
// The total size includes space for the tag.
unsigned totalSize = payloadSize + getNumTagBytes(payloadSize,
enumType->Description->Enum.getNumEmptyCases(),
numPayloads);
// Set up the layout info in the vwtable.
vwtable->size = totalSize;
vwtable->flags = ValueWitnessFlags()
.withAlignmentMask(alignMask)
.withPOD(isPOD)
.withBitwiseTakable(isBT)
// TODO: Extra inhabitants
.withExtraInhabitants(false)
.withEnumWitnesses(true)
.withInlineStorage(ValueWitnessTable::isValueInline(totalSize, alignMask+1))
;
auto rawStride = (totalSize + alignMask) & ~alignMask;
vwtable->stride = rawStride == 0 ? 1 : rawStride;
installCommonValueWitnesses(vwtable);
}
namespace {
struct MultiPayloadLayout {
size_t payloadSize;
size_t numTagBytes;
};
} // end anonymous namespace
static MultiPayloadLayout getMultiPayloadLayout(const EnumMetadata *enumType) {
size_t payloadSize = enumType->getPayloadSize();
size_t totalSize = enumType->getValueWitnesses()->size;
return {payloadSize, totalSize - payloadSize};
}
static void storeMultiPayloadTag(OpaqueValue *value,
MultiPayloadLayout layout,
unsigned tag) {
auto tagBytes = reinterpret_cast<char *>(value) + layout.payloadSize;
#if defined(__BIG_ENDIAN__)
small_memcpy(tagBytes,
reinterpret_cast<char *>(&tag) + 4 - layout.numTagBytes,
layout.numTagBytes);
#else
small_memcpy(tagBytes, &tag, layout.numTagBytes);
#endif
}
static void storeMultiPayloadValue(OpaqueValue *value,
MultiPayloadLayout layout,
unsigned payloadValue) {
auto bytes = reinterpret_cast<char *>(value);
#if defined(__BIG_ENDIAN__)
unsigned numPayloadValueBytes =
std::min(layout.payloadSize, sizeof(payloadValue));
memcpy(bytes + sizeof(OpaqueValue *) - numPayloadValueBytes,
reinterpret_cast<char *>(&payloadValue) + 4 - numPayloadValueBytes,
numPayloadValueBytes);
if (layout.payloadSize > sizeof(payloadValue) &&
layout.payloadSize > sizeof(OpaqueValue *)) {
memset(bytes, 0,
sizeof(OpaqueValue *) - numPayloadValueBytes);
memset(bytes + sizeof(OpaqueValue *), 0,
layout.payloadSize - sizeof(OpaqueValue *));
}
#else
memcpy(bytes, &payloadValue,
std::min(layout.payloadSize, sizeof(payloadValue)));
// If the payload is larger than the value, zero out the rest.
if (layout.payloadSize > sizeof(payloadValue))
memset(bytes + sizeof(payloadValue), 0,
layout.payloadSize - sizeof(payloadValue));
#endif
}
static unsigned loadMultiPayloadTag(const OpaqueValue *value,
MultiPayloadLayout layout) {
auto tagBytes = reinterpret_cast<const char *>(value) + layout.payloadSize;
unsigned tag = 0;
#if defined(__BIG_ENDIAN__)
small_memcpy(reinterpret_cast<char *>(&tag) + 4 - layout.numTagBytes,
tagBytes, layout.numTagBytes);
#else
small_memcpy(&tag, tagBytes, layout.numTagBytes);
#endif
return tag;
}
static unsigned loadMultiPayloadValue(const OpaqueValue *value,
MultiPayloadLayout layout) {
auto bytes = reinterpret_cast<const char *>(value);
unsigned payloadValue = 0;
#if defined(__BIG_ENDIAN__)
unsigned numPayloadValueBytes =
std::min(layout.payloadSize, sizeof(payloadValue));
memcpy(reinterpret_cast<char *>(&payloadValue) + 4 - numPayloadValueBytes,
bytes + sizeof(OpaqueValue *) - numPayloadValueBytes, numPayloadValueBytes);
#else
memcpy(&payloadValue, bytes,
std::min(layout.payloadSize, sizeof(payloadValue)));
#endif
return payloadValue;
}
void
swift::swift_storeEnumTagMultiPayload(OpaqueValue *value,
const EnumMetadata *enumType,
unsigned whichCase) {
auto layout = getMultiPayloadLayout(enumType);
unsigned numPayloads = enumType->Description->Enum.getNumPayloadCases();
if (whichCase < numPayloads) {
// For a payload case, store the tag after the payload area.
storeMultiPayloadTag(value, layout, whichCase);
} else {
// For an empty case, factor out the parts that go in the payload and
// tag areas.
unsigned whichEmptyCase = whichCase - numPayloads;
unsigned whichTag, whichPayloadValue;
if (layout.payloadSize >= 4) {
whichTag = numPayloads;
whichPayloadValue = whichEmptyCase;
} else {
unsigned numPayloadBits = layout.payloadSize * CHAR_BIT;
whichTag = numPayloads + (whichEmptyCase >> numPayloadBits);
whichPayloadValue = whichEmptyCase & ((1U << numPayloads) - 1U);
}
storeMultiPayloadTag(value, layout, whichTag);
storeMultiPayloadValue(value, layout, whichPayloadValue);
}
}
unsigned
swift::swift_getEnumCaseMultiPayload(const OpaqueValue *value,
const EnumMetadata *enumType) {
auto layout = getMultiPayloadLayout(enumType);
unsigned numPayloads = enumType->Description->Enum.getNumPayloadCases();
unsigned tag = loadMultiPayloadTag(value, layout);
if (tag < numPayloads) {
// If the tag indicates a payload, then we're done.
return tag;
} else {
// Otherwise, the other part of the discriminator is in the payload.
unsigned payloadValue = loadMultiPayloadValue(value, layout);
if (layout.payloadSize >= 4) {
return numPayloads + payloadValue;
} else {
unsigned numPayloadBits = layout.payloadSize * CHAR_BIT;
return (payloadValue | (tag - numPayloads) << numPayloadBits)
+ numPayloads;
}
}
}