// Copyright 2018 The Clspv Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "ConstantEmitter.h"

#include <cassert>

#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/APInt.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Value.h"
#include "llvm/Support/ErrorHandling.h"

using namespace llvm;

namespace clspv {

void ConstantEmitter::Emit(Constant *c) {
  AlignTo(Layout.getABITypeAlign(c->getType()).value());
  if (auto i = dyn_cast<ConstantInt>(c)) {
    EmitInt(i);
  } else if (auto f = dyn_cast<ConstantFP>(c)) {
    EmitFP(f);
  } else if (auto st = dyn_cast<ConstantStruct>(c)) {
    EmitStruct(st);
  } else if (auto ag = dyn_cast<ConstantArray>(c)) {
    EmitAggregate(ag);
  } else if (auto ag = dyn_cast<ConstantVector>(c)) {
    EmitAggregate(ag);
  } else if (auto cds = dyn_cast<ConstantDataSequential>(c)) {
    EmitDataSequential(cds);
  } else if (auto ag = dyn_cast<ConstantAggregate>(c)) {
    EmitAggregate(ag);
  } else if (auto ag = dyn_cast<ConstantAggregateZero>(c)) {
    EmitAggregateZero(ag);
  } else {
    errs() << "Don't know how to emit " << *c << " with value id "
           << int(c->getValueID()) << " compared to "
           << int(Value::ConstantVectorVal) << "\n";
    llvm_unreachable("Unhandled constant");
  }
}

void ConstantEmitter::EmitZeroes(size_t n) {
  for (size_t i = 0; i < n; ++i) {
    Out << "00";
    ++Offset;
  }
}

void ConstantEmitter::AlignTo(size_t alignment) {
  size_t overflow = Offset % alignment;
  if (overflow) {
    const size_t padding = alignment - overflow;
    EmitZeroes(padding);
  }
  assert(0 == (Offset % alignment));
}

void ConstantEmitter::EmitStruct(ConstantStruct *c) {
  const StructLayout *sl = Layout.getStructLayout(c->getType());
  AlignTo(sl->getAlignment().value()); // Might be redundant.
  const size_t BaseOffset = Offset;
  for (unsigned i = 0; i < c->getNumOperands(); ++i) {
    EmitZeroes(sl->getElementOffset(i) - (Offset - BaseOffset));
    Emit(c->getOperand(i));
  }
  EmitZeroes(sl->getSizeInBytes() - (Offset - BaseOffset));
}

void ConstantEmitter::EmitDataSequential(ConstantDataSequential *c) {
  for (unsigned i = 0; i < c->getNumElements(); ++i) {
    // The elements will align themselves.
    Constant *elem = c->getElementAsConstant(i);
    Emit(elem);
  }
}

void ConstantEmitter::EmitAggregate(ConstantAggregate *c) {
  for (unsigned i = 0; i < c->getNumOperands(); ++i) {
    // The elements will align themselves.
    Emit(c->getOperand(i));
  }
}

void ConstantEmitter::EmitAggregateZero(ConstantAggregateZero *c) {
  if (StructType *sty = dyn_cast<StructType>(c->getType())) {

    const StructLayout *sl = Layout.getStructLayout(sty);
    AlignTo(sl->getAlignment().value()); // Might be redundant.
    const size_t BaseOffset = Offset;
    for (unsigned i = 0; i < c->getElementCount().getFixedValue(); ++i) {
      EmitZeroes(sl->getElementOffset(i) - (Offset - BaseOffset));
      Emit(c->getElementValue(i));
    }
    EmitZeroes(sl->getSizeInBytes() - (Offset - BaseOffset));

  } else {
    for (unsigned i = 0; i < c->getElementCount().getFixedValue(); ++i) {
      // The elements will align themselves.
      Emit(c->getElementValue(i));
    }
  }
}

void ConstantEmitter::EmitInt(ConstantInt *c) {
  EmitRaw(c->getBitWidth(), c->getValue().getRawData());
}

void ConstantEmitter::EmitFP(ConstantFP *c) {
  APInt asInt = c->getValueAPF().bitcastToAPInt();
  EmitRaw(asInt.getBitWidth(), asInt.getRawData());
}

void ConstantEmitter::EmitRaw(unsigned num_bits, const uint64_t *data) {
  unsigned num_bytes = (num_bits + 7) / 8;
  while (num_bytes) {
    uint64_t word = *data++;
    for (int i = 0; i < 8 && num_bytes; --num_bytes, ++i) {
      EmitByte(word & 0xff);
      word = (word >> 8);
    }
  }
}

void ConstantEmitter::EmitByte(size_t byte) {
  static const char hex[] = "0123456789abcdef";
  Out << hex[(byte & 0xf0) >> 4] << hex[byte & 0xf];
  ++Offset;
}

} // namespace clspv
