//===--- Enum.cpp - Runtime declarations for enums -----------*- C++ -*--===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://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/Basic/Fallthrough.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, *s8 = (uint8_t*)src;
  for (unsigned int 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));
  vwtable->stride = llvm::RoundUpToAlignment(size, align);
  
  // 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 == &_TWVBo
#if SWIFT_OBJC_INTEROP
          || payloadVWT == &_TWVBO
#endif
          )) {
#define COPY_PAYLOAD_WITNESS(NAME) vwtable->NAME = payloadVWT->NAME;
    FOR_ALL_FUNCTION_VALUE_WITNESSES(COPY_PAYLOAD_WITNESS)
#undef COPY_PAYLOAD_WITNESS
  } 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) {
  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;
    // FIXME: endianness
    unsigned numBytes = getNumTagBytes(payloadSize,
                                       emptyCases-payloadNumExtraInhabitants,
                                       1 /*payload case*/);

    small_memcpy(&extraTagBits, extraTagBitAddr, numBytes);

    // 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.
      // FIXME: endianness.
      unsigned caseIndexFromValue = 0;
      memcpy(&caseIndexFromValue, valueAddr,
             std::min(size_t(4), payloadSize));
      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) {
  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.
  // FIXME: endianness.
  memcpy(valueAddr, &payloadIndex, std::min(size_t(4), payloadSize));
  if (payloadSize > 4)
    memset(valueAddr + 4, 0, payloadSize - 4);
  memcpy(extraTagBitAddr, &extraTagIndex, numExtraTagBytes);
}

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.
  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))
    ;
  vwtable->stride = (totalSize + alignMask) & ~alignMask;
  
  installCommonValueWitnesses(vwtable);
}

namespace {
struct MultiPayloadLayout {
  size_t payloadSize;
  size_t numTagBytes;
};
}

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;
  small_memcpy(tagBytes, &tag, layout.numTagBytes);
}

static void storeMultiPayloadValue(OpaqueValue *value,
                                   MultiPayloadLayout layout,
                                   unsigned payloadValue) {
  auto bytes = reinterpret_cast<char *>(value);
  
  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));
}

static unsigned loadMultiPayloadTag(const OpaqueValue *value,
                                    MultiPayloadLayout layout) {
  auto tagBytes = reinterpret_cast<const char *>(value) + layout.payloadSize;

  unsigned tag = 0;
  small_memcpy(&tag, tagBytes, layout.numTagBytes);

  return tag;
}

static unsigned loadMultiPayloadValue(const OpaqueValue *value,
                                      MultiPayloadLayout layout) {
  auto bytes = reinterpret_cast<const char *>(value);
  unsigned payloadValue = 0;
  memcpy(&payloadValue, bytes,
         std::min(layout.payloadSize, sizeof(payloadValue)));
  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;
    }
  }
}
