<commit_before>//===--- PartialApplyCombiner.cpp -----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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
//
//===----------------------------------------------------------------------===//

#include "swift/SIL/SILValue.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/SILOptimizer/Utils/ValueLifetime.h"

using namespace swift;

namespace {

// Helper class performing the apply{partial_apply(x,y)}(z) -> apply(z,x,y)
// peephole.
class PartialApplyCombiner {
  // True if temporaries are not created yet.
  bool isFirstTime = true;

  // partial_apply which is being processed.
  PartialApplyInst *pai;

  // Temporaries created as copies of alloc_stack arguments of
  // the partial_apply.
  SmallVector<SILValue, 8> tmpCopies;

  // Mapping from the original argument of partial_apply to
  // the temporary containing its copy.
  llvm::DenseMap<SILValue, SILValue> argToTmpCopy;

  // Set of lifetime endpoints for this partial_apply.
  //
  // Used to find the last uses of partial_apply, which is need to insert
  // releases/destroys of temporaries as early as possible.
  ValueLifetimeAnalysis::Frontier partialApplyFrontier;

  SILBuilder &builder;

  InstModCallbacks &callbacks;

  bool processSingleApply(FullApplySite ai);
  bool allocateTemporaries();
  void deallocateTemporaries();
  void destroyTemporaries();

public:
  PartialApplyCombiner(PartialApplyInst *pai, SILBuilder &builder,
                       InstModCallbacks &callbacks)
      : isFirstTime(true), pai(pai), builder(builder), callbacks(callbacks) {}
  SILInstruction *combine();
};

} // end anonymous namespace

/// Returns true on success.
bool PartialApplyCombiner::allocateTemporaries() {
  // A partial_apply [stack]'s argument are not owned by the partial_apply and
  // therefore their lifetime must outlive any uses.
  if (pai->isOnStack()) {
    return true;
  }

  // Copy the original arguments of the partial_apply into newly created
  // temporaries and use these temporaries instead of the original arguments
  // afterwards.
  //
  // This is done to "extend" the life-time of original partial_apply arguments,
  // as they may be destroyed/deallocated before the last use by one of the
  // apply instructions.
  //
  // TODO: Copy arguments of the partial_apply into new temporaries only if the
  // lifetime of arguments ends before their uses by apply instructions.
  bool needsDestroys = false;
  CanSILFunctionType paiTy =
      pai->getCallee()->getType().getAs<SILFunctionType>();

  // Emit a destroy value for each captured closure argument.
  ArrayRef<SILParameterInfo> paramList = paiTy->getParameters();
  auto argList = pai->getArguments();
  paramList = paramList.drop_front(paramList.size() - argList.size());

  llvm::SmallVector<std::pair<SILValue, uint16_t>, 8> argsToHandle;
  for (unsigned i : indices(argList)) {
    SILValue arg = argList[i];
    SILParameterInfo param = paramList[i];
    if (param.isIndirectMutating())
      continue;

    // Create a temporary and copy the argument into it, if:
    // - the argument stems from an alloc_stack
    // - the argument is consumed by the callee and is indirect
    //   (e.g. it is an @in argument)
    if (isa<AllocStackInst>(arg) ||
        (param.isConsumed() &&
         pai->getSubstCalleeConv().isSILIndirect(param))) {
      // If the argument has a dependent type, then we can not create a
      // temporary for it at the beginning of the function, so we must bail.
      //
      // TODO: This is because we are inserting alloc_stack at the beginning/end
      // of functions where the dependent type may not exist yet.
      if (arg->getType().hasOpenedExistential())
        return false;

      // If the temporary is non-trivial, we need to destroy it later.
      if (!arg->getType().isTrivial(*pai->getFunction()))
        needsDestroys = true;
      argsToHandle.push_back(std::make_pair(arg, i));
    }
  }

  if (needsDestroys) {
    // Compute the set of endpoints, which will be used to insert destroys of
    // temporaries. This may fail if the frontier is located on a critical edge
    // which we may not split (no CFG changes in SILCombine).
    ValueLifetimeAnalysis vla(pai);
    if (!vla.computeFrontier(partialApplyFrontier,
                             ValueLifetimeAnalysis::DontModifyCFG))
      return false;
  }

  for (auto argWithIdx : argsToHandle) {
    SILValue Arg = argWithIdx.first;
    builder.setInsertionPoint(pai->getFunction()->begin()->begin());
    // Create a new temporary at the beginning of a function.
    SILDebugVariable dbgVar(/*Constant*/ true, argWithIdx.second);
    auto *tmp = builder.createAllocStack(pai->getLoc(), Arg->getType(), dbgVar);
    builder.setInsertionPoint(pai);
    // Copy argument into this temporary.
    builder.createCopyAddr(pai->getLoc(), Arg, tmp, IsTake_t::IsNotTake,
                           IsInitialization_t::IsInitialization);

    tmpCopies.push_back(tmp);
    argToTmpCopy.insert(std::make_pair(Arg, tmp));
  }
  return true;
}

/// Emit dealloc_stack for all temporaries.
void PartialApplyCombiner::deallocateTemporaries() {
  // Insert dealloc_stack instructions at all function exit points.
  for (SILBasicBlock &block : *pai->getFunction()) {
    TermInst *term = block.getTerminator();
    if (!term->isFunctionExiting())
      continue;

    for (auto copy : tmpCopies) {
      builder.setInsertionPoint(term);
      builder.createDeallocStack(pai->getLoc(), copy);
    }
  }
}

/// Emit code to release/destroy temporaries.
void PartialApplyCombiner::destroyTemporaries() {
  // Insert releases and destroy_addrs as early as possible,
  // because we don't want to keep objects alive longer than
  // its really needed.
  for (auto op : tmpCopies) {
    auto tmpType = op->getType().getObjectType();
    if (tmpType.isTrivial(*pai->getFunction()))
      continue;
    for (auto *endPoint : partialApplyFrontier) {
      builder.setInsertionPoint(endPoint);
      if (!tmpType.isAddressOnly(*pai->getFunction())) {
        SILValue load = builder.emitLoadValueOperation(
            pai->getLoc(), op, LoadOwnershipQualifier::Take);
        builder.emitDestroyValueOperation(pai->getLoc(), load);
      } else {
        builder.createDestroyAddr(pai->getLoc(), op);
      }
    }
  }
}

/// Process an apply instruction which uses a partial_apply
/// as its callee.
/// Returns true on success.
bool PartialApplyCombiner::processSingleApply(FullApplySite paiAI) {
  builder.setInsertionPoint(paiAI.getInstruction());
  builder.setCurrentDebugScope(paiAI.getDebugScope());

  // Prepare the args.
  SmallVector<SILValue, 8> argList;
  // First the ApplyInst args.
  for (auto Op : paiAI.getArguments())
    argList.push_back(Op);

  SILInstruction *insertPoint = &*builder.getInsertionPoint();
  // Next, the partial apply args.

  // Pre-process partial_apply arguments only once, lazily.
  if (isFirstTime) {
    isFirstTime = false;
    if (!allocateTemporaries())
      return false;
  }

  // Now, copy over the partial apply args.
  for (auto arg : pai->getArguments()) {
    // If there is new temporary for this argument, use it instead.
    if (argToTmpCopy.count(arg)) {
      arg = argToTmpCopy.lookup(arg);
    }
    argList.push_back(arg);
  }

  builder.setInsertionPoint(insertPoint);
  builder.setCurrentDebugScope(paiAI.getDebugScope());

  // The thunk that implements the partial apply calls the closure function
  // that expects all arguments to be consumed by the function. However, the
  // captured arguments are not arguments of *this* apply, so they are not
  // pre-incremented. When we combine the partial_apply and this apply into
  // a new apply we need to retain all of the closure non-address type
  // arguments.
  auto paramInfo = pai->getSubstCalleeType()->getParameters();
  auto partialApplyArgs = pai->getArguments();
  // Set of arguments that need to be destroyed after each invocation.
  SmallVector<SILValue, 8> toBeDestroyedArgs;
  for (unsigned i : indices(partialApplyArgs)) {
    auto arg = partialApplyArgs[i];

    if (!arg->getType().isAddress()) {
      // Copy the argument as the callee may consume it.
      arg = builder.emitCopyValueOperation(pai->getLoc(), arg);
      // For non consumed parameters (e.g. guaranteed), we also need to
      // insert destroys after each apply instruction that we create.
      if (!paramInfo[paramInfo.size() - partialApplyArgs.size() + i]
               .isConsumed())
        toBeDestroyedArgs.push_back(arg);
    }
  }

  auto callee = pai->getCallee();
  SubstitutionMap subs = pai->getSubstitutionMap();

  // The partial_apply might be substituting in an open existential type.
  builder.addOpenedArchetypeOperands(pai);

  FullApplySite nai;
  if (auto *tai = dyn_cast<TryApplyInst>(paiAI))
    nai = builder.createTryApply(paiAI.getLoc(), callee, subs, argList,
                                 tai->getNormalBB(), tai->getErrorBB());
  else
    nai = builder.createApply(paiAI.getLoc(), callee, subs, argList,
                              cast<ApplyInst>(paiAI)->isNonThrowing());

  // We also need to destroy the partial_apply instruction itself because it is
  // consumed by the apply_instruction.
  if (auto *tai = dyn_cast<TryApplyInst>(paiAI)) {
    builder.setInsertionPoint(tai->getNormalBB()->begin());
    for (auto arg : toBeDestroyedArgs) {
      builder.emitDestroyValueOperation(pai->getLoc(), arg);
    }
    if (!pai->hasCalleeGuaranteedContext())
      builder.emitDestroyValueOperation(paiAI.getLoc(), pai);
    builder.setInsertionPoint(tai->getErrorBB()->begin());
    // Destroy the non-consumed parameters.
    for (auto arg : toBeDestroyedArgs) {
      builder.emitDestroyValueOperation(pai->getLoc(), arg);
    }
    if (!pai->hasCalleeGuaranteedContext())
      builder.emitDestroyValueOperation(pai->getLoc(), pai);
    builder.setInsertionPoint(paiAI.getInstruction());
  } else {
    // Destroy the non-consumed parameters.
    for (auto arg : toBeDestroyedArgs) {
      builder.emitDestroyValueOperation(pai->getLoc(), arg);
    }
    if (!pai->hasCalleeGuaranteedContext())
      builder.emitDestroyValueOperation(pai->getLoc(), pai);
  }

  if (auto *apply = dyn_cast<ApplyInst>(paiAI)) {
    callbacks.replaceValueUsesWith(SILValue(apply),
                                   cast<ApplyInst>(nai.getInstruction()));
  }
  callbacks.deleteInst(paiAI.getInstruction());
  return true;
}

/// Perform the apply{partial_apply(x,y)}(z) -> apply(z,x,y) peephole
/// by iterating over all uses of the partial_apply and searching
/// for the pattern to transform.
SILInstruction *PartialApplyCombiner::combine() {
  // We need to model @unowned_inner_pointer better before we can do the
  // peephole here.
  for (auto resultInfo : pai->getSubstCalleeType()->getResults())
    if (resultInfo.getConvention() == ResultConvention::UnownedInnerPointer)
      return nullptr;

  // Iterate over all uses of the partial_apply
  // and look for applies that use it as a callee.

  // Worklist of operands.
  SmallVector<Operand *, 8> uses(pai->getUses());

  // Uses may grow in this loop.
  for (size_t useIndex = 0; useIndex < uses.size(); ++useIndex) {
    auto *use = uses[useIndex];
    auto *user = use->getUser();

    // Recurse through conversions.
    if (auto *cfi = dyn_cast<ConvertEscapeToNoEscapeInst>(user)) {
      // TODO: Handle argument conversion. All the code in this file needs to be
      // cleaned up and generalized. The argument conversion handling in
      // optimizeApplyOfConvertFunctionInst should apply to any combine
      // involving an apply, not just a specific pattern.
      //
      // For now, just handle conversion to @noescape, which is irrelevant for
      // direct application of the closure.
      auto convertCalleeTy = cfi->getType().castTo<SILFunctionType>();
      auto escapingCalleeTy = convertCalleeTy->getWithExtInfo(
          convertCalleeTy->getExtInfo().withNoEscape(false));
      assert(use->get()->getType().castTo<SILFunctionType>() ==
             escapingCalleeTy);
      (void)escapingCalleeTy;
      uses.append(cfi->getUses().begin(), cfi->getUses().end());
      continue;
    }

    // Look through mark_dependence users of partial_apply [stack].
    if (auto *mdi = dyn_cast<MarkDependenceInst>(user)) {
      if (mdi->getValue() == use->get() &&
          mdi->getValue()->getType().is<SILFunctionType>() &&
          mdi->getValue()->getType().castTo<SILFunctionType>()->isNoEscape()) {
        llvm::copy(mdi->getUses(), std::back_inserter(uses));
      }
      continue;
    }
    // If this use of a partial_apply is not
    // an apply which uses it as a callee, bail.
    auto ai = FullApplySite::isa(user);
    if (!ai)
      continue;

    if (ai.getCallee() != use->get())
      continue;

    // We cannot handle generic apply yet. Bail.
    if (ai.hasSubstitutions())
      continue;

    if (!processSingleApply(ai))
      return nullptr;
  }

  // release/destroy and deallocate introduced temporaries.
  if (!tmpCopies.empty()) {
    destroyTemporaries();
    deallocateTemporaries();
  }

  return nullptr;
}

//===----------------------------------------------------------------------===//
//                            Top Level Entrypoint
//===----------------------------------------------------------------------===//

SILInstruction *swift::tryOptimizeApplyOfPartialApply(
    PartialApplyInst *pai, SILBuilder &builder, InstModCallbacks callbacks) {
  PartialApplyCombiner combiner(pai, builder, callbacks);
  return combiner.combine();
}
<commit_msg>[pa-combiner] Modernize some appending code to use a range instead of iterators. NFC.<commit_after>//===--- PartialApplyCombiner.cpp -----------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 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
//
//===----------------------------------------------------------------------===//

#include "swift/SIL/SILValue.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/SILOptimizer/Utils/ValueLifetime.h"

using namespace swift;

namespace {

// Helper class performing the apply{partial_apply(x,y)}(z) -> apply(z,x,y)
// peephole.
class PartialApplyCombiner {
  // True if temporaries are not created yet.
  bool isFirstTime = true;

  // partial_apply which is being processed.
  PartialApplyInst *pai;

  // Temporaries created as copies of alloc_stack arguments of
  // the partial_apply.
  SmallVector<SILValue, 8> tmpCopies;

  // Mapping from the original argument of partial_apply to
  // the temporary containing its copy.
  llvm::DenseMap<SILValue, SILValue> argToTmpCopy;

  // Set of lifetime endpoints for this partial_apply.
  //
  // Used to find the last uses of partial_apply, which is need to insert
  // releases/destroys of temporaries as early as possible.
  ValueLifetimeAnalysis::Frontier partialApplyFrontier;

  SILBuilder &builder;

  InstModCallbacks &callbacks;

  bool processSingleApply(FullApplySite ai);
  bool allocateTemporaries();
  void deallocateTemporaries();
  void destroyTemporaries();

public:
  PartialApplyCombiner(PartialApplyInst *pai, SILBuilder &builder,
                       InstModCallbacks &callbacks)
      : isFirstTime(true), pai(pai), builder(builder), callbacks(callbacks) {}
  SILInstruction *combine();
};

} // end anonymous namespace

/// Returns true on success.
bool PartialApplyCombiner::allocateTemporaries() {
  // A partial_apply [stack]'s argument are not owned by the partial_apply and
  // therefore their lifetime must outlive any uses.
  if (pai->isOnStack()) {
    return true;
  }

  // Copy the original arguments of the partial_apply into newly created
  // temporaries and use these temporaries instead of the original arguments
  // afterwards.
  //
  // This is done to "extend" the life-time of original partial_apply arguments,
  // as they may be destroyed/deallocated before the last use by one of the
  // apply instructions.
  //
  // TODO: Copy arguments of the partial_apply into new temporaries only if the
  // lifetime of arguments ends before their uses by apply instructions.
  bool needsDestroys = false;
  CanSILFunctionType paiTy =
      pai->getCallee()->getType().getAs<SILFunctionType>();

  // Emit a destroy value for each captured closure argument.
  ArrayRef<SILParameterInfo> paramList = paiTy->getParameters();
  auto argList = pai->getArguments();
  paramList = paramList.drop_front(paramList.size() - argList.size());

  llvm::SmallVector<std::pair<SILValue, uint16_t>, 8> argsToHandle;
  for (unsigned i : indices(argList)) {
    SILValue arg = argList[i];
    SILParameterInfo param = paramList[i];
    if (param.isIndirectMutating())
      continue;

    // Create a temporary and copy the argument into it, if:
    // - the argument stems from an alloc_stack
    // - the argument is consumed by the callee and is indirect
    //   (e.g. it is an @in argument)
    if (isa<AllocStackInst>(arg) ||
        (param.isConsumed() &&
         pai->getSubstCalleeConv().isSILIndirect(param))) {
      // If the argument has a dependent type, then we can not create a
      // temporary for it at the beginning of the function, so we must bail.
      //
      // TODO: This is because we are inserting alloc_stack at the beginning/end
      // of functions where the dependent type may not exist yet.
      if (arg->getType().hasOpenedExistential())
        return false;

      // If the temporary is non-trivial, we need to destroy it later.
      if (!arg->getType().isTrivial(*pai->getFunction()))
        needsDestroys = true;
      argsToHandle.push_back(std::make_pair(arg, i));
    }
  }

  if (needsDestroys) {
    // Compute the set of endpoints, which will be used to insert destroys of
    // temporaries. This may fail if the frontier is located on a critical edge
    // which we may not split (no CFG changes in SILCombine).
    ValueLifetimeAnalysis vla(pai);
    if (!vla.computeFrontier(partialApplyFrontier,
                             ValueLifetimeAnalysis::DontModifyCFG))
      return false;
  }

  for (auto argWithIdx : argsToHandle) {
    SILValue Arg = argWithIdx.first;
    builder.setInsertionPoint(pai->getFunction()->begin()->begin());
    // Create a new temporary at the beginning of a function.
    SILDebugVariable dbgVar(/*Constant*/ true, argWithIdx.second);
    auto *tmp = builder.createAllocStack(pai->getLoc(), Arg->getType(), dbgVar);
    builder.setInsertionPoint(pai);
    // Copy argument into this temporary.
    builder.createCopyAddr(pai->getLoc(), Arg, tmp, IsTake_t::IsNotTake,
                           IsInitialization_t::IsInitialization);

    tmpCopies.push_back(tmp);
    argToTmpCopy.insert(std::make_pair(Arg, tmp));
  }
  return true;
}

/// Emit dealloc_stack for all temporaries.
void PartialApplyCombiner::deallocateTemporaries() {
  // Insert dealloc_stack instructions at all function exit points.
  for (SILBasicBlock &block : *pai->getFunction()) {
    TermInst *term = block.getTerminator();
    if (!term->isFunctionExiting())
      continue;

    for (auto copy : tmpCopies) {
      builder.setInsertionPoint(term);
      builder.createDeallocStack(pai->getLoc(), copy);
    }
  }
}

/// Emit code to release/destroy temporaries.
void PartialApplyCombiner::destroyTemporaries() {
  // Insert releases and destroy_addrs as early as possible,
  // because we don't want to keep objects alive longer than
  // its really needed.
  for (auto op : tmpCopies) {
    auto tmpType = op->getType().getObjectType();
    if (tmpType.isTrivial(*pai->getFunction()))
      continue;
    for (auto *endPoint : partialApplyFrontier) {
      builder.setInsertionPoint(endPoint);
      if (!tmpType.isAddressOnly(*pai->getFunction())) {
        SILValue load = builder.emitLoadValueOperation(
            pai->getLoc(), op, LoadOwnershipQualifier::Take);
        builder.emitDestroyValueOperation(pai->getLoc(), load);
      } else {
        builder.createDestroyAddr(pai->getLoc(), op);
      }
    }
  }
}

/// Process an apply instruction which uses a partial_apply
/// as its callee.
/// Returns true on success.
bool PartialApplyCombiner::processSingleApply(FullApplySite paiAI) {
  builder.setInsertionPoint(paiAI.getInstruction());
  builder.setCurrentDebugScope(paiAI.getDebugScope());

  // Prepare the args.
  SmallVector<SILValue, 8> argList;
  // First the ApplyInst args.
  for (auto Op : paiAI.getArguments())
    argList.push_back(Op);

  SILInstruction *insertPoint = &*builder.getInsertionPoint();
  // Next, the partial apply args.

  // Pre-process partial_apply arguments only once, lazily.
  if (isFirstTime) {
    isFirstTime = false;
    if (!allocateTemporaries())
      return false;
  }

  // Now, copy over the partial apply args.
  for (auto arg : pai->getArguments()) {
    // If there is new temporary for this argument, use it instead.
    if (argToTmpCopy.count(arg)) {
      arg = argToTmpCopy.lookup(arg);
    }
    argList.push_back(arg);
  }

  builder.setInsertionPoint(insertPoint);
  builder.setCurrentDebugScope(paiAI.getDebugScope());

  // The thunk that implements the partial apply calls the closure function
  // that expects all arguments to be consumed by the function. However, the
  // captured arguments are not arguments of *this* apply, so they are not
  // pre-incremented. When we combine the partial_apply and this apply into
  // a new apply we need to retain all of the closure non-address type
  // arguments.
  auto paramInfo = pai->getSubstCalleeType()->getParameters();
  auto partialApplyArgs = pai->getArguments();
  // Set of arguments that need to be destroyed after each invocation.
  SmallVector<SILValue, 8> toBeDestroyedArgs;
  for (unsigned i : indices(partialApplyArgs)) {
    auto arg = partialApplyArgs[i];

    if (!arg->getType().isAddress()) {
      // Copy the argument as the callee may consume it.
      arg = builder.emitCopyValueOperation(pai->getLoc(), arg);
      // For non consumed parameters (e.g. guaranteed), we also need to
      // insert destroys after each apply instruction that we create.
      if (!paramInfo[paramInfo.size() - partialApplyArgs.size() + i]
               .isConsumed())
        toBeDestroyedArgs.push_back(arg);
    }
  }

  auto callee = pai->getCallee();
  SubstitutionMap subs = pai->getSubstitutionMap();

  // The partial_apply might be substituting in an open existential type.
  builder.addOpenedArchetypeOperands(pai);

  FullApplySite nai;
  if (auto *tai = dyn_cast<TryApplyInst>(paiAI))
    nai = builder.createTryApply(paiAI.getLoc(), callee, subs, argList,
                                 tai->getNormalBB(), tai->getErrorBB());
  else
    nai = builder.createApply(paiAI.getLoc(), callee, subs, argList,
                              cast<ApplyInst>(paiAI)->isNonThrowing());

  // We also need to destroy the partial_apply instruction itself because it is
  // consumed by the apply_instruction.
  if (auto *tai = dyn_cast<TryApplyInst>(paiAI)) {
    builder.setInsertionPoint(tai->getNormalBB()->begin());
    for (auto arg : toBeDestroyedArgs) {
      builder.emitDestroyValueOperation(pai->getLoc(), arg);
    }
    if (!pai->hasCalleeGuaranteedContext())
      builder.emitDestroyValueOperation(paiAI.getLoc(), pai);
    builder.setInsertionPoint(tai->getErrorBB()->begin());
    // Destroy the non-consumed parameters.
    for (auto arg : toBeDestroyedArgs) {
      builder.emitDestroyValueOperation(pai->getLoc(), arg);
    }
    if (!pai->hasCalleeGuaranteedContext())
      builder.emitDestroyValueOperation(pai->getLoc(), pai);
    builder.setInsertionPoint(paiAI.getInstruction());
  } else {
    // Destroy the non-consumed parameters.
    for (auto arg : toBeDestroyedArgs) {
      builder.emitDestroyValueOperation(pai->getLoc(), arg);
    }
    if (!pai->hasCalleeGuaranteedContext())
      builder.emitDestroyValueOperation(pai->getLoc(), pai);
  }

  if (auto *apply = dyn_cast<ApplyInst>(paiAI)) {
    callbacks.replaceValueUsesWith(SILValue(apply),
                                   cast<ApplyInst>(nai.getInstruction()));
  }
  callbacks.deleteInst(paiAI.getInstruction());
  return true;
}

/// Perform the apply{partial_apply(x,y)}(z) -> apply(z,x,y) peephole
/// by iterating over all uses of the partial_apply and searching
/// for the pattern to transform.
SILInstruction *PartialApplyCombiner::combine() {
  // We need to model @unowned_inner_pointer better before we can do the
  // peephole here.
  for (auto resultInfo : pai->getSubstCalleeType()->getResults())
    if (resultInfo.getConvention() == ResultConvention::UnownedInnerPointer)
      return nullptr;

  // Iterate over all uses of the partial_apply
  // and look for applies that use it as a callee.

  // Worklist of operands.
  SmallVector<Operand *, 8> uses(pai->getUses());

  // Uses may grow in this loop.
  for (size_t useIndex = 0; useIndex < uses.size(); ++useIndex) {
    auto *use = uses[useIndex];
    auto *user = use->getUser();

    // Recurse through conversions.
    if (auto *cfi = dyn_cast<ConvertEscapeToNoEscapeInst>(user)) {
      // TODO: Handle argument conversion. All the code in this file needs to be
      // cleaned up and generalized. The argument conversion handling in
      // optimizeApplyOfConvertFunctionInst should apply to any combine
      // involving an apply, not just a specific pattern.
      //
      // For now, just handle conversion to @noescape, which is irrelevant for
      // direct application of the closure.
      auto convertCalleeTy = cfi->getType().castTo<SILFunctionType>();
      auto escapingCalleeTy = convertCalleeTy->getWithExtInfo(
          convertCalleeTy->getExtInfo().withNoEscape(false));
      assert(use->get()->getType().castTo<SILFunctionType>() ==
             escapingCalleeTy);
      (void)escapingCalleeTy;
      llvm::copy(cfi->getUses(), std::back_inserter(uses));
      continue;
    }

    // Look through mark_dependence users of partial_apply [stack].
    if (auto *mdi = dyn_cast<MarkDependenceInst>(user)) {
      if (mdi->getValue() == use->get() &&
          mdi->getValue()->getType().is<SILFunctionType>() &&
          mdi->getValue()->getType().castTo<SILFunctionType>()->isNoEscape()) {
        llvm::copy(mdi->getUses(), std::back_inserter(uses));
      }
      continue;
    }
    // If this use of a partial_apply is not
    // an apply which uses it as a callee, bail.
    auto ai = FullApplySite::isa(user);
    if (!ai)
      continue;

    if (ai.getCallee() != use->get())
      continue;

    // We cannot handle generic apply yet. Bail.
    if (ai.hasSubstitutions())
      continue;

    if (!processSingleApply(ai))
      return nullptr;
  }

  // release/destroy and deallocate introduced temporaries.
  if (!tmpCopies.empty()) {
    destroyTemporaries();
    deallocateTemporaries();
  }

  return nullptr;
}

//===----------------------------------------------------------------------===//
//                            Top Level Entrypoint
//===----------------------------------------------------------------------===//

SILInstruction *swift::tryOptimizeApplyOfPartialApply(
    PartialApplyInst *pai, SILBuilder &builder, InstModCallbacks callbacks) {
  PartialApplyCombiner combiner(pai, builder, callbacks);
  return combiner.combine();
}
<|endoftext|>


<commit_before>//===- llvm/Transforms/DecomposeMultiDimRefs.cpp - Lower array refs to 1D ---=//
//
// DecomposeMultiDimRefs -
// Convert multi-dimensional references consisting of any combination
// of 2 or more array and structure indices into a sequence of
// instructions (using getelementpr and cast) so that each instruction
// has at most one index (except structure references,
// which need an extra leading index of [0]).
//
//===----------------------------------------------------------------------===//

#include "llvm/Transforms/Scalar/DecomposeMultiDimRefs.h"
#include "llvm/Constants.h"
#include "llvm/iMemory.h"
#include "llvm/iOther.h"
#include "llvm/BasicBlock.h"
#include "llvm/Function.h"
#include "llvm/Pass.h"

//
// For any combination of 2 or more array and structure indices,
// this function repeats the foll. until we have a one-dim. reference: {
//      ptr1 = getElementPtr [CompositeType-N] * lastPtr, uint firstIndex
//      ptr2 = cast [CompositeType-N] * ptr1 to [CompositeType-N] *
// }
// Then it replaces the original instruction with an equivalent one that
// uses the last ptr2 generated in the loop and a single index.
// If any index is (uint) 0, we omit the getElementPtr instruction.
//
static BasicBlock::iterator
decomposeArrayRef(BasicBlock::iterator& BBI)
{
  MemAccessInst *memI = cast<MemAccessInst>(*BBI);
  BasicBlock* BB = memI->getParent();
  Value* lastPtr = memI->getPointerOperand();
  vector<Instruction*> newIvec;

  // Process each index except the last one.
  //
  MemAccessInst::const_op_iterator OI = memI->idx_begin();
  MemAccessInst::const_op_iterator OE = memI->idx_end();
  for ( ; OI != OE; ++OI)
    {
      assert(isa<PointerType>(lastPtr->getType()));

      if (OI+1 == OE)                   // stop before the last operand
        break;

      // Check for a zero index.  This will need a cast instead of
      // a getElementPtr, or it may need neither.
      bool indexIsZero = bool(isa<ConstantUInt>(*OI) &&
                              cast<ConstantUInt>(*OI)->getValue() == 0);

      // Extract the first index.  If the ptr is a pointer to a structure
      // and the next index is a structure offset (i.e., not an array offset),
      // we need to include an initial [0] to index into the pointer.
      vector<Value*> idxVec(1, *OI);
      PointerType* ptrType = cast<PointerType>(lastPtr->getType());
      if (isa<StructType>(ptrType->getElementType())
          && ! ptrType->indexValid(*OI))
        idxVec.insert(idxVec.begin(), ConstantUInt::get(Type::UIntTy, 0));

      // Get the type obtained by applying the first index.
      // It must be a structure or array.
      const Type* nextType = MemAccessInst::getIndexedType(lastPtr->getType(),
                                                           idxVec, true);
      assert(isa<StructType>(nextType) || isa<ArrayType>(nextType));

      // Get a pointer to the structure or to the elements of the array.
      const Type* nextPtrType =
        PointerType::get(isa<StructType>(nextType)? nextType
                         : cast<ArrayType>(nextType)->getElementType());

      // Instruction 1: nextPtr1 = GetElementPtr lastPtr, idxVec
      // This is not needed if the index is zero.
      Value* gepValue;
      if (indexIsZero)
        gepValue = lastPtr;
      else
        {
          gepValue = new GetElementPtrInst(lastPtr, idxVec,"ptr1");
          newIvec.push_back(cast<Instruction>(gepValue));
        }

      // Instruction 2: nextPtr2 = cast nextPtr1 to nextPtrType
      // This is not needed if the two types are identical.
      Value* castInst;
      if (gepValue->getType() == nextPtrType)
        castInst = gepValue;
      else
        {
          castInst = new CastInst(gepValue, nextPtrType, "ptr2");
          newIvec.push_back(cast<Instruction>(castInst));
        }

      lastPtr = castInst;
    }

  //
  // Now create a new instruction to replace the original one
  //
  PointerType* ptrType = cast<PointerType>(lastPtr->getType());
  assert(ptrType);

  // First, get the final index vector.  As above, we may need an initial [0].
  vector<Value*> idxVec(1, *OI);
  if (isa<StructType>(ptrType->getElementType())
      && ! ptrType->indexValid(*OI))
    idxVec.insert(idxVec.begin(), ConstantUInt::get(Type::UIntTy, 0));

  const std::string newInstName = memI->hasName()? memI->getName()
                                                 : string("finalRef");
  Instruction* newInst = NULL;

  switch(memI->getOpcode())
    {
    case Instruction::Load:
      newInst = new LoadInst(lastPtr, idxVec /*, newInstName */); break;
    case Instruction::Store:
      newInst = new StoreInst(memI->getOperand(0),
                              lastPtr, idxVec /*, newInstName */); break;
      break;
    case Instruction::GetElementPtr:
      newInst = new GetElementPtrInst(lastPtr, idxVec /*, newInstName */); break;
    default:
      assert(0 && "Unrecognized memory access instruction"); break;
    }

  newIvec.push_back(newInst);

  // Replace all uses of the old instruction with the new
  memI->replaceAllUsesWith(newInst);

  BasicBlock::iterator newI = BBI;;
  for (int i = newIvec.size()-1; i >= 0; i--)
    newI = BB->getInstList().insert(newI, newIvec[i]);

  // Now delete the old instruction and return a pointer to the last new one
  BB->getInstList().remove(memI);
  delete memI;

  return newI + newIvec.size() - 1;           // pointer to last new instr
}

//---------------------------------------------------------------------------
// Entry point for array or  structure references with multiple indices.
//---------------------------------------------------------------------------

static bool
doDecomposeMultiDimRefs(Function *F)
{
  bool changed = false;

  for (Function::iterator BI = F->begin(), BE = F->end(); BI != BE; ++BI)
    for (BasicBlock::iterator newI, II = (*BI)->begin();
         II != (*BI)->end(); II = ++newI)
      {
        newI = II;
        if (MemAccessInst *memI = dyn_cast<MemAccessInst>(*II))
          if (memI->getNumOperands() > 1 + memI->getFirstIndexOperandNumber())
            {
              newI = decomposeArrayRef(II);
              changed = true;
            }
      }

  return changed;
}

namespace {
  struct DecomposeMultiDimRefsPass : public FunctionPass {
    virtual bool runOnFunction(Function *F) {
      return doDecomposeMultiDimRefs(F);
    }

    virtual void getAnalysisUsage(AnalysisUsage &AU) const {
      AU.preservesCFG();
    }
  };
}

Pass *createDecomposeMultiDimRefsPass() {
  return new DecomposeMultiDimRefsPass();
}
<commit_msg>Code cleanups<commit_after>//===- llvm/Transforms/DecomposeMultiDimRefs.cpp - Lower array refs to 1D -===//
//
// DecomposeMultiDimRefs -
// Convert multi-dimensional references consisting of any combination
// of 2 or more array and structure indices into a sequence of
// instructions (using getelementpr and cast) so that each instruction
// has at most one index (except structure references,
// which need an extra leading index of [0]).
//
//===----------------------------------------------------------------------===//

#include "llvm/Transforms/Scalar/DecomposeMultiDimRefs.h"
#include "llvm/Constants.h"
#include "llvm/iMemory.h"
#include "llvm/iOther.h"
#include "llvm/BasicBlock.h"
#include "llvm/Function.h"
#include "llvm/Pass.h"

namespace {
  struct DecomposePass : public BasicBlockPass {
    virtual bool runOnBasicBlock(BasicBlock *BB);

  private:
    static void decomposeArrayRef(BasicBlock::iterator &BBI);
  };
}

Pass *createDecomposeMultiDimRefsPass() {
  return new DecomposePass();
}

// runOnBasicBlock - Entry point for array or structure references with multiple
// indices.
//
bool DecomposePass::runOnBasicBlock(BasicBlock *BB) {
  bool Changed = false;

  for (BasicBlock::iterator II = BB->begin(); II != BB->end(); ) {
    if (MemAccessInst *MAI = dyn_cast<MemAccessInst>(*II)) {
      if (MAI->getNumOperands() > MAI->getFirstIndexOperandNumber()+1) {
        decomposeArrayRef(II);
        Changed = true;
      } else {
        ++II;
      }
    } else {
      ++II;
    }
  }

  return Changed;
}

//
// For any combination of 2 or more array and structure indices,
// this function repeats the foll. until we have a one-dim. reference: {
//      ptr1 = getElementPtr [CompositeType-N] * lastPtr, uint firstIndex
//      ptr2 = cast [CompositeType-N] * ptr1 to [CompositeType-N] *
// }
// Then it replaces the original instruction with an equivalent one that
// uses the last ptr2 generated in the loop and a single index.
// If any index is (uint) 0, we omit the getElementPtr instruction.
//
void DecomposePass::decomposeArrayRef(BasicBlock::iterator &BBI){
  MemAccessInst *memI = cast<MemAccessInst>(*BBI);
  BasicBlock* BB = memI->getParent();
  Value* lastPtr = memI->getPointerOperand();

  // Remove the instruction from the stream
  BB->getInstList().remove(BBI);

  vector<Instruction*> newIvec;

  // Process each index except the last one.
  //
  User::const_op_iterator OI = memI->idx_begin(), OE = memI->idx_end();
  for (; OI != OE && OI+1 != OE; ++OI) {
    assert(isa<PointerType>(lastPtr->getType()));

    // Check for a zero index.  This will need a cast instead of
    // a getElementPtr, or it may need neither.
    bool indexIsZero = isa<ConstantUInt>(*OI) &&
                       cast<Constant>(*OI)->isNullValue();

    // Extract the first index.  If the ptr is a pointer to a structure
    // and the next index is a structure offset (i.e., not an array offset),
    // we need to include an initial [0] to index into the pointer.
    vector<Value*> idxVec(1, *OI);
    PointerType* ptrType = cast<PointerType>(lastPtr->getType());
    if (isa<StructType>(ptrType->getElementType())
        && ! ptrType->indexValid(*OI))
      idxVec.insert(idxVec.begin(), ConstantUInt::get(Type::UIntTy, 0));

    // Get the type obtained by applying the first index.
    // It must be a structure or array.
    const Type* nextType = MemAccessInst::getIndexedType(lastPtr->getType(),
                                                         idxVec, true);
    assert(isa<StructType>(nextType) || isa<ArrayType>(nextType));

    // Get a pointer to the structure or to the elements of the array.
    const Type* nextPtrType =
      PointerType::get(isa<StructType>(nextType) ? nextType
                       : cast<ArrayType>(nextType)->getElementType());

    // Instruction 1: nextPtr1 = GetElementPtr lastPtr, idxVec
    // This is not needed if the index is zero.
    Value *gepValue;
    if (indexIsZero)
      gepValue = lastPtr;
    else {
      gepValue = new GetElementPtrInst(lastPtr, idxVec,"ptr1");
      newIvec.push_back(cast<Instruction>(gepValue));
    }

    // Instruction 2: nextPtr2 = cast nextPtr1 to nextPtrType
    // This is not needed if the two types are identical.
    Value *castInst;
    if (gepValue->getType() == nextPtrType)
      castInst = gepValue;
    else {
      castInst = new CastInst(gepValue, nextPtrType, "ptr2");
      newIvec.push_back(cast<Instruction>(castInst));
    }

    lastPtr = castInst;
  }

  //
  // Now create a new instruction to replace the original one
  //
  PointerType *ptrType = cast<PointerType>(lastPtr->getType());

  // First, get the final index vector.  As above, we may need an initial [0].
  vector<Value*> idxVec(1, *OI);
  if (isa<StructType>(ptrType->getElementType())
      && !ptrType->indexValid(*OI))
    idxVec.insert(idxVec.begin(), Constant::getNullValue(Type::UIntTy));

  Instruction* newInst = NULL;
  switch(memI->getOpcode()) {
  case Instruction::Load:
    newInst = new LoadInst(lastPtr, idxVec, memI->getName());
    break;
  case Instruction::Store:
    newInst = new StoreInst(memI->getOperand(0), lastPtr, idxVec);
    break;
  case Instruction::GetElementPtr:
    newInst = new GetElementPtrInst(lastPtr, idxVec, memI->getName());
    break;
  default:
    assert(0 && "Unrecognized memory access instruction");
  }
  newIvec.push_back(newInst);

  // Replace all uses of the old instruction with the new
  memI->replaceAllUsesWith(newInst);

  // Now delete the old instruction...
  delete memI;

  // Convert our iterator into an index... that cannot get invalidated
  unsigned ItOffs = BBI-BB->begin();

  // Insert all of the new instructions...
  BB->getInstList().insert(BBI, newIvec.begin(), newIvec.end());

  // Advance the iterator to the instruction following the one just inserted...
  BBI = BB->begin() + (ItOffs+newIvec.size());
}
<|endoftext|>


<commit_before>//===- SimplifyCFG.cpp - CFG Simplification Routines -------------*- C++ -*--=//
//
// This file provides several routines that are useful for simplifying CFGs in
// various ways...
//
//===----------------------------------------------------------------------===//

#include "llvm/Transforms/UnifyMethodExitNodes.h"
#include "llvm/BasicBlock.h"
#include "llvm/Method.h"
#include "llvm/iTerminators.h"
#include "llvm/iPHINode.h"
#include "llvm/Type.h"
using std::vector;

AnalysisID UnifyMethodExitNodes::ID(AnalysisID::create<UnifyMethodExitNodes>());

// UnifyAllExitNodes - Unify all exit nodes of the CFG by creating a new
// BasicBlock, and converting all returns to unconditional branches to this
// new basic block.  The singular exit node is returned.
//
// If there are no return stmts in the Method, a null pointer is returned.
//
bool UnifyMethodExitNodes::doit(Method *M, BasicBlock *&ExitNode) {
  vector<BasicBlock*> ReturningBlocks;

  // Loop over all of the blocks in a method, tracking all of the blocks that
  // return.
  //
  for(Method::iterator I = M->begin(), E = M->end(); I != E; ++I)
    if ((*I)->getTerminator()->getOpcode() == Instruction::Ret)
      ReturningBlocks.push_back(*I);

  if (ReturningBlocks.size() == 0) {
    ExitNode = 0;
    return false;                      // No blocks return
  } else if (ReturningBlocks.size() == 1) {
    ExitNode = ReturningBlocks.front();    // Already has a single return block
    return false;
  }

  // Otherwise, we need to insert a new basic block into the method, add a PHI
  // node (if the function returns a value), and convert all of the return
  // instructions into unconditional branches.
  //
  BasicBlock *NewRetBlock = new BasicBlock("UnifiedExitNode", M);

  if (M->getReturnType() != Type::VoidTy) {
    // If the method doesn't return void... add a PHI node to the block...
    PHINode *PN = new PHINode(M->getReturnType());
    NewRetBlock->getInstList().push_back(PN);

    // Add an incoming element to the PHI node for every return instruction that
    // is merging into this new block...
    for (vector<BasicBlock*>::iterator I = ReturningBlocks.begin(),
                                       E = ReturningBlocks.end(); I != E; ++I)
      PN->addIncoming((*I)->getTerminator()->getOperand(0), *I);

    // Add a return instruction to return the result of the PHI node...
    NewRetBlock->getInstList().push_back(new ReturnInst(PN));
  } else {
    // If it returns void, just add a return void instruction to the block
    NewRetBlock->getInstList().push_back(new ReturnInst());
  }

  // Loop over all of the blocks, replacing the return instruction with an
  // unconditional branch.
  //
  for (vector<BasicBlock*>::iterator I = ReturningBlocks.begin(),
                                     E = ReturningBlocks.end(); I != E; ++I) {
    delete (*I)->getInstList().pop_back();  // Remove the return insn
    (*I)->getInstList().push_back(new BranchInst(NewRetBlock));
  }
  ExitNode = NewRetBlock;
  return true;
}
<commit_msg>General cleanup<commit_after>//===- SimplifyCFG.cpp - CFG Simplification Routines -------------*- C++ -*--=//
//
// This file provides several routines that are useful for simplifying CFGs in
// various ways...
//
//===----------------------------------------------------------------------===//

#include "llvm/Transforms/UnifyMethodExitNodes.h"
#include "llvm/BasicBlock.h"
#include "llvm/Method.h"
#include "llvm/iTerminators.h"
#include "llvm/iPHINode.h"
#include "llvm/Type.h"
using std::vector;

AnalysisID UnifyMethodExitNodes::ID(AnalysisID::create<UnifyMethodExitNodes>());

// UnifyAllExitNodes - Unify all exit nodes of the CFG by creating a new
// BasicBlock, and converting all returns to unconditional branches to this
// new basic block.  The singular exit node is returned.
//
// If there are no return stmts in the Method, a null pointer is returned.
//
bool UnifyMethodExitNodes::doit(Method *M, BasicBlock *&ExitNode) {
  // Loop over all of the blocks in a method, tracking all of the blocks that
  // return.
  //
  vector<BasicBlock*> ReturningBlocks;
  for(Method::iterator I = M->begin(), E = M->end(); I != E; ++I)
    if (isa<ReturnInst>((*I)->getTerminator()))
      ReturningBlocks.push_back(*I);

  if (ReturningBlocks.empty()) {
    ExitNode = 0;
    return false;                      // No blocks return
  } else if (ReturningBlocks.size() == 1) {
    ExitNode = ReturningBlocks.front();    // Already has a single return block
    return false;
  }

  // Otherwise, we need to insert a new basic block into the method, add a PHI
  // node (if the function returns a value), and convert all of the return
  // instructions into unconditional branches.
  //
  BasicBlock *NewRetBlock = new BasicBlock("UnifiedExitNode", M);

  if (M->getReturnType() != Type::VoidTy) {
    // If the method doesn't return void... add a PHI node to the block...
    PHINode *PN = new PHINode(M->getReturnType());
    NewRetBlock->getInstList().push_back(PN);

    // Add an incoming element to the PHI node for every return instruction that
    // is merging into this new block...
    for (vector<BasicBlock*>::iterator I = ReturningBlocks.begin(),
                                       E = ReturningBlocks.end(); I != E; ++I)
      PN->addIncoming((*I)->getTerminator()->getOperand(0), *I);

    // Add a return instruction to return the result of the PHI node...
    NewRetBlock->getInstList().push_back(new ReturnInst(PN));
  } else {
    // If it returns void, just add a return void instruction to the block
    NewRetBlock->getInstList().push_back(new ReturnInst());
  }

  // Loop over all of the blocks, replacing the return instruction with an
  // unconditional branch.
  //
  for (vector<BasicBlock*>::iterator I = ReturningBlocks.begin(),
                                     E = ReturningBlocks.end(); I != E; ++I) {
    delete (*I)->getInstList().pop_back();  // Remove the return insn
    (*I)->getInstList().push_back(new BranchInst(NewRetBlock));
  }
  ExitNode = NewRetBlock;
  return true;
}
<|endoftext|>


<commit_before>
#include "../Flare.h"
#include "FlareSpacecraft.h"
#include "../Game/FlareAsteroid.h"
#include "../Game/FlareGame.h"
#include "../Player/FlarePlayerController.h"
#include "FlareWeapon.h"
#include "FlareBombComponent.h"
#include "FlareBomb.h"

#define LOCTEXT_NAMESPACE "FlareBomb"

/*----------------------------------------------------
	Constructor
----------------------------------------------------*/

AFlareBomb::AFlareBomb(const class FObjectInitializer& PCIP) : Super(PCIP)
{
	// Mesh data
	BombComp = PCIP.CreateDefaultSubobject<UFlareBombComponent>(this, TEXT("Root"));
	BombComp->bTraceComplexOnMove = true;
	BombComp->LDMaxDrawDistance = 100000; // 1km*/
	BombComp->SetSimulatePhysics(true);
	BombComp->SetLinearDamping(0);
	BombComp->SetAngularDamping(0);
	RootComponent = BombComp;

	// Settings
	SetActorEnableCollision(false);
	PrimaryActorTick.bCanEverTick = true;
	Paused = false;
}

/*----------------------------------------------------
	Gameplay
----------------------------------------------------*/

void AFlareBomb::PostInitializeComponents()
{
	Super::PostInitializeComponents();
}

void AFlareBomb::Initialize(const FFlareBombSave* Data, UFlareWeapon* Weapon)
{
	ParentWeapon = Weapon;
	WeaponDescription = Weapon->GetDescription();

	// Get the power from description
	if (WeaponDescription)
	{
		FFlareSpacecraftComponentSave ComponentData;
		ComponentData.ComponentIdentifier = WeaponDescription->Identifier;
		BombComp->Initialize(&ComponentData, Weapon->GetSpacecraft()->GetParent()->GetCompany(), Weapon->GetSpacecraft(), false);

		DamageSound = WeaponDescription->WeaponCharacteristics.DamageSound;

		ExplosionEffectTemplate = WeaponDescription->WeaponCharacteristics.ExplosionEffect;
		ExplosionEffectMaterial = WeaponDescription->WeaponCharacteristics.GunCharacteristics.ExplosionMaterial;

	}

	SetActorScale3D(ParentWeapon->GetSpacecraft()->GetActorScale3D());

	if (Data)
	{
		BombData = *Data;
	}
	else
	{
		BombData.Activated = false;
		BombData.Dropped = false;
		BombData.LifeTime = 0;
		BombData.DropParentDistance = 0;
	}

	if (BombData.Activated)
	{
		SetActorEnableCollision(true);
	}
}

void AFlareBomb::OnLaunched()
{
	FLOG("AFlareBomb::OnLaunched");

	DetachRootComponentFromParent(true);
	ParentWeapon->GetSpacecraft()->GetGame()->GetActiveSector()->RegisterBomb(this);

	// Spin to stabilize
	FVector FrontVector = BombComp->ComponentToWorld.TransformVector(FVector(1, 0, 0));
	BombComp->SetPhysicsAngularVelocity(FrontVector * WeaponDescription->WeaponCharacteristics.BombCharacteristics.DropAngularVelocity);
	BombComp->SetPhysicsLinearVelocity(ParentWeapon->GetSpacecraft()->Airframe->GetPhysicsLinearVelocity() + FrontVector * WeaponDescription->WeaponCharacteristics.BombCharacteristics.DropLinearVelocity * 100);

	BombData.DropParentDistance = GetParentDistance();
	BombData.Dropped = true;
	BombData.LifeTime = 0;
}

void AFlareBomb::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	// Activate after few centimeters
	if (BombData.Dropped && !BombData.Activated)
	{
		if (GetParentDistance() > BombData.DropParentDistance + WeaponDescription->WeaponCharacteristics.BombCharacteristics.ActivationDistance * 100)
		{
			SetActorEnableCollision(true);
			BombData.Activated = true;
		}
	}

	if (BombData.Dropped && BombData.Activated)
	{
		BombData.LifeTime += DeltaSeconds;
	}

	// Auto-destroy
	AFlarePlayerController* PC = Cast<AFlarePlayerController>(GetWorld()->GetFirstPlayerController());
	if (PC)
	{
		AFlareSpacecraft* PlayerShip = PC->GetShipPawn();
		if (PlayerShip)
		{
			// 5 km and 30s auto-destroy
			float Distance = (GetActorLocation() - PlayerShip->GetActorLocation()).Size();
			if (Distance > 500000 && BombData.LifeTime > 30)
			{
				OnBombDetonated(NULL, NULL, FVector(), FVector());
			}

			// Parent removed destroy
			if (!ParentWeapon->IsValidLowLevel() || !ParentWeapon->GetSpacecraft()->IsValidLowLevel())
			{
				OnBombDetonated(NULL, NULL, FVector(), FVector());
			}
		}
	}
}

void AFlareBomb::NotifyHit(class UPrimitiveComponent* MyComp, class AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit)
{
	Super::ReceiveHit(MyComp, Other, OtherComp, bSelfMoved, HitLocation, HitNormal, NormalImpulse, Hit);
	FLOG("AFlareBomb::NotifyHit");

	AFlareBomb* BombCandidate = Cast<AFlareBomb>(Other);
	AFlareAsteroid* Asteroid = Cast<AFlareAsteroid>(Other);
	AFlareSpacecraft* Spacecraft = Cast<AFlareSpacecraft>(Other);
	UFlareSpacecraftComponent* ShipComponent = Cast<UFlareSpacecraftComponent>(OtherComp);

	// Forget uninteresting hits
	if (!Other || !OtherComp || !ParentWeapon || Other == ParentWeapon->GetSpacecraft() || BombCandidate)
	{
		FLOG("AFlareBomb::NotifyHit : invalid hit");
		return;
	}

	// Spawn penetration effect
	if (ExplosionEffectTemplate)
	{
		UParticleSystemComponent* PSC = UGameplayStatics::SpawnEmitterAttached(
			ExplosionEffectTemplate,
			OtherComp,
			NAME_None,
			HitLocation,
			HitNormal.Rotation(),
			EAttachLocation::KeepWorldPosition,
			true);
		if (PSC)
		{
			PSC->SetWorldScale3D(FVector(1, 1, 1));
		}
	}

	// Physics impulse
	float ImpulseForce = 1000 * WeaponDescription->WeaponCharacteristics.ExplosionPower * WeaponDescription->WeaponCharacteristics.AmmoDamageRadius;
	FVector ImpulseDirection = (HitLocation - GetActorLocation()).GetUnsafeNormal();

	// Process damage
	if (Spacecraft)
	{
		Spacecraft->Airframe->AddImpulseAtLocation(ImpulseForce * ImpulseDirection, HitLocation);
		OnSpacecraftHit(Spacecraft, ShipComponent, HitLocation, NormalImpulse);
	}
	else if (Asteroid)
	{
		Asteroid->GetAsteroidComponent()->AddImpulseAtLocation(ImpulseForce * ImpulseDirection, HitLocation);
	}

	// Spawn impact decal
	if (ShipComponent && ExplosionEffectMaterial)
	{
		// Spawn
		UDecalComponent* Decal = UGameplayStatics::SpawnDecalAttached(
			ExplosionEffectMaterial,
			FMath::FRandRange(50, 150) * FVector(1, 1, 1),
			ShipComponent,
			NAME_None,
			HitLocation,
			HitNormal.Rotation(),
			EAttachLocation::KeepWorldPosition,
			120);

		// Instanciate and configure the decal material
		UMaterialInterface* DecalMaterial = Decal->GetMaterial(0);
		UMaterialInstanceDynamic* DecalMaterialInst = UMaterialInstanceDynamic::Create(DecalMaterial, GetWorld());
		if (DecalMaterialInst)
		{
			DecalMaterialInst->SetScalarParameterValue("RandomParameter", FMath::FRandRange(1, 0));
			Decal->SetMaterial(0, DecalMaterialInst);
		}
	}

	// Bomb has done its job, good job bomb
	OnBombDetonated(Spacecraft, ShipComponent, HitLocation, ImpulseDirection);
}

void AFlareBomb::OnSpacecraftHit(AFlareSpacecraft* HitSpacecraft, UFlareSpacecraftComponent* HitComponent, FVector HitLocation, FVector InertialNormal)
{
	FLOG("AFlareBomb::OnSpacecraftHit");
	UFlareCompany* OwnerCompany = ParentWeapon->GetSpacecraft()->GetCompany();
	AFlarePlayerController* PC = Cast<AFlarePlayerController>(GetWorld()->GetFirstPlayerController());

	// Apply damage
	HitSpacecraft->GetDamageSystem()->ApplyDamage(WeaponDescription->WeaponCharacteristics.ExplosionPower,
		WeaponDescription->WeaponCharacteristics.AmmoDamageRadius,
		HitLocation,
		EFlareDamage::DAM_HEAT,
		ParentWeapon->GetSpacecraft()->GetCompany());

	// Play sound
	if (HitSpacecraft->IsLocallyControlled())
	{
		UGameplayStatics::PlaySoundAtLocation(GetWorld(), DamageSound, HitLocation, 1, 1);
	}

	// Ship salvage
	if (!HitSpacecraft->IsStation() &&
		((WeaponDescription->WeaponCharacteristics.DamageType == EFlareShellDamageType::LightSalvage && HitSpacecraft->GetDescription()->Size == EFlarePartSize::S)
	 || (WeaponDescription->WeaponCharacteristics.DamageType == EFlareShellDamageType::HeavySalvage && HitSpacecraft->GetDescription()->Size == EFlarePartSize::L)))
	{
		FLOGV("AFlareBomb::OnSpacecraftHit : salvaging %s for %s", *HitSpacecraft->GetImmatriculation().ToString(), *OwnerCompany->GetCompanyName().ToString());
		HitSpacecraft->SetHarpooned(OwnerCompany);

		if (OwnerCompany == PC->GetCompany())
		{
			FFlareMenuParameterData Data;
			Data.Spacecraft = HitSpacecraft->GetParent();
			PC->Notify(LOCTEXT("HeavyShipHarpooned", "Ship harpooned !"),
				FText::Format(LOCTEXT("HeavyShipHarpoonedFormat", "If it is destroyed, you will retrieve {0} on the next day, if you are still in this sector."),
					FText::FromString(HitSpacecraft->GetImmatriculation().ToString())),
				FName("ship-harpooned"),
				EFlareNotification::NT_Military,
				10.0f,
				EFlareMenu::MENU_Ship,
				Data);
		}
	}
}

void AFlareBomb::OnBombDetonated(AFlareSpacecraft* HitSpacecraft, UFlareSpacecraftComponent* HitComponent, FVector HitLocation, FVector InertialNormal)
{
	// Unregister
	ParentWeapon->GetSpacecraft()->GetGame()->GetActiveSector()->UnregisterBomb(this);

	// Attach to the hull if it's a salvage harpoon
	if ((WeaponDescription->WeaponCharacteristics.DamageType == EFlareShellDamageType::LightSalvage)
	 || (WeaponDescription->WeaponCharacteristics.DamageType == EFlareShellDamageType::HeavySalvage))
	{
		if (HitSpacecraft && !HitSpacecraft->IsStation() && HitComponent && WeaponDescription &&
		   ((WeaponDescription->WeaponCharacteristics.DamageType == EFlareShellDamageType::LightSalvage && HitSpacecraft->GetDescription()->Size == EFlarePartSize::S)
		 || (WeaponDescription->WeaponCharacteristics.DamageType == EFlareShellDamageType::HeavySalvage && HitSpacecraft->GetDescription()->Size == EFlarePartSize::L)))
		{
			SetActorLocation(HitLocation);
			SetActorRotation(InertialNormal.Rotation());
			AttachToActor(HitSpacecraft, FAttachmentTransformRules(EAttachmentRule::KeepWorld, true), NAME_None);
		}
	}
	else
	{
		Destroy();
	}
}

FFlareBombSave* AFlareBomb::Save()
{
	// Physical data
	BombData.Location = GetActorLocation();
	BombData.Rotation = GetActorRotation();
	if (!Paused)
	{
		BombData.LinearVelocity = BombComp->GetPhysicsLinearVelocity();
		BombData.AngularVelocity = BombComp->GetPhysicsAngularVelocity();
	}

	// TODO Investigate on NULL ParentWeapon
	if (ParentWeapon)
	{
		BombData.ParentSpacecraft = ParentWeapon->GetSpacecraft()->GetImmatriculation();
		BombData.WeaponSlotIdentifier = ParentWeapon->SlotIdentifier;
	}

	return &BombData;
}

void AFlareBomb::SetPause(bool Pause)
{
	if (Paused == Pause)
	{
		return;
	}

	// On pause, save the state
	if (Pause)
	{
		Save();
	}

	// Toggle pause
	Paused = Pause;
	SetActorHiddenInGame(Pause);
	BombComp->SetSimulatePhysics(!Pause);
	CustomTimeDilation = (Pause ? 0.f : 1.0);

	// On unpause, restore physics
	if (!Pause)
	{
		BombComp->SetPhysicsLinearVelocity(BombData.LinearVelocity);
		BombComp->SetPhysicsAngularVelocity(BombData.AngularVelocity);
		if (!BombData.Dropped && ParentWeapon)
		{
			AttachToActor(ParentWeapon->GetSpacecraft(), FAttachmentTransformRules(EAttachmentRule::KeepWorld, true), NAME_None);
		}
	}
}

float AFlareBomb::GetParentDistance() const
{
	return (ParentWeapon->GetComponentLocation() - GetActorLocation()).Size();
}

#undef LOCTEXT_NAMESPACE
<commit_msg>#382 Fixed crash when bombs auto-detonate after out-of-sector respawn of the parent ship<commit_after>
#include "../Flare.h"
#include "FlareSpacecraft.h"
#include "../Game/FlareAsteroid.h"
#include "../Game/FlareGame.h"
#include "../Player/FlarePlayerController.h"
#include "FlareWeapon.h"
#include "FlareBombComponent.h"
#include "FlareBomb.h"

#define LOCTEXT_NAMESPACE "FlareBomb"

/*----------------------------------------------------
	Constructor
----------------------------------------------------*/

AFlareBomb::AFlareBomb(const class FObjectInitializer& PCIP) : Super(PCIP)
{
	// Mesh data
	BombComp = PCIP.CreateDefaultSubobject<UFlareBombComponent>(this, TEXT("Root"));
	BombComp->bTraceComplexOnMove = true;
	BombComp->LDMaxDrawDistance = 100000; // 1km*/
	BombComp->SetSimulatePhysics(true);
	BombComp->SetLinearDamping(0);
	BombComp->SetAngularDamping(0);
	RootComponent = BombComp;

	// Settings
	SetActorEnableCollision(false);
	PrimaryActorTick.bCanEverTick = true;
	Paused = false;
}

/*----------------------------------------------------
	Gameplay
----------------------------------------------------*/

void AFlareBomb::PostInitializeComponents()
{
	Super::PostInitializeComponents();
}

void AFlareBomb::Initialize(const FFlareBombSave* Data, UFlareWeapon* Weapon)
{
	ParentWeapon = Weapon;
	WeaponDescription = Weapon->GetDescription();

	// Get the power from description
	if (WeaponDescription)
	{
		FFlareSpacecraftComponentSave ComponentData;
		ComponentData.ComponentIdentifier = WeaponDescription->Identifier;
		BombComp->Initialize(&ComponentData, Weapon->GetSpacecraft()->GetParent()->GetCompany(), Weapon->GetSpacecraft(), false);

		DamageSound = WeaponDescription->WeaponCharacteristics.DamageSound;

		ExplosionEffectTemplate = WeaponDescription->WeaponCharacteristics.ExplosionEffect;
		ExplosionEffectMaterial = WeaponDescription->WeaponCharacteristics.GunCharacteristics.ExplosionMaterial;

	}

	SetActorScale3D(ParentWeapon->GetSpacecraft()->GetActorScale3D());

	if (Data)
	{
		BombData = *Data;
	}
	else
	{
		BombData.Activated = false;
		BombData.Dropped = false;
		BombData.LifeTime = 0;
		BombData.DropParentDistance = 0;
	}

	if (BombData.Activated)
	{
		SetActorEnableCollision(true);
	}
}

void AFlareBomb::OnLaunched()
{
	FLOG("AFlareBomb::OnLaunched");

	DetachRootComponentFromParent(true);
	ParentWeapon->GetSpacecraft()->GetGame()->GetActiveSector()->RegisterBomb(this);

	// Spin to stabilize
	FVector FrontVector = BombComp->ComponentToWorld.TransformVector(FVector(1, 0, 0));
	BombComp->SetPhysicsAngularVelocity(FrontVector * WeaponDescription->WeaponCharacteristics.BombCharacteristics.DropAngularVelocity);
	BombComp->SetPhysicsLinearVelocity(ParentWeapon->GetSpacecraft()->Airframe->GetPhysicsLinearVelocity() + FrontVector * WeaponDescription->WeaponCharacteristics.BombCharacteristics.DropLinearVelocity * 100);

	BombData.DropParentDistance = GetParentDistance();
	BombData.Dropped = true;
	BombData.LifeTime = 0;
}

void AFlareBomb::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	// Activate after few centimeters
	if (BombData.Dropped && !BombData.Activated)
	{
		if (GetParentDistance() > BombData.DropParentDistance + WeaponDescription->WeaponCharacteristics.BombCharacteristics.ActivationDistance * 100)
		{
			SetActorEnableCollision(true);
			BombData.Activated = true;
		}
	}

	if (BombData.Dropped && BombData.Activated)
	{
		BombData.LifeTime += DeltaSeconds;
	}

	// Auto-destroy
	AFlarePlayerController* PC = Cast<AFlarePlayerController>(GetWorld()->GetFirstPlayerController());
	if (PC)
	{
		AFlareSpacecraft* PlayerShip = PC->GetShipPawn();
		if (PlayerShip)
		{
			// 5 km and 30s auto-destroy
			float Distance = (GetActorLocation() - PlayerShip->GetActorLocation()).Size();
			if (Distance > 500000 && BombData.LifeTime > 30)
			{
				OnBombDetonated(NULL, NULL, FVector(), FVector());
			}

			// Parent removed destroy
			if (!ParentWeapon || !ParentWeapon->IsValidLowLevel() || !ParentWeapon->GetSpacecraft()->IsValidLowLevel())
			{
				OnBombDetonated(NULL, NULL, FVector(), FVector());
			}
		}
	}
}

void AFlareBomb::NotifyHit(class UPrimitiveComponent* MyComp, class AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit)
{
	Super::ReceiveHit(MyComp, Other, OtherComp, bSelfMoved, HitLocation, HitNormal, NormalImpulse, Hit);
	FLOG("AFlareBomb::NotifyHit");

	AFlareBomb* BombCandidate = Cast<AFlareBomb>(Other);
	AFlareAsteroid* Asteroid = Cast<AFlareAsteroid>(Other);
	AFlareSpacecraft* Spacecraft = Cast<AFlareSpacecraft>(Other);
	UFlareSpacecraftComponent* ShipComponent = Cast<UFlareSpacecraftComponent>(OtherComp);

	// Forget uninteresting hits
	if (!Other || !OtherComp || !ParentWeapon || Other == ParentWeapon->GetSpacecraft() || BombCandidate)
	{
		FLOG("AFlareBomb::NotifyHit : invalid hit");
		return;
	}

	// Spawn penetration effect
	if (ExplosionEffectTemplate)
	{
		UParticleSystemComponent* PSC = UGameplayStatics::SpawnEmitterAttached(
			ExplosionEffectTemplate,
			OtherComp,
			NAME_None,
			HitLocation,
			HitNormal.Rotation(),
			EAttachLocation::KeepWorldPosition,
			true);
		if (PSC)
		{
			PSC->SetWorldScale3D(FVector(1, 1, 1));
		}
	}

	// Physics impulse
	float ImpulseForce = 1000 * WeaponDescription->WeaponCharacteristics.ExplosionPower * WeaponDescription->WeaponCharacteristics.AmmoDamageRadius;
	FVector ImpulseDirection = (HitLocation - GetActorLocation()).GetUnsafeNormal();

	// Process damage
	if (Spacecraft)
	{
		Spacecraft->Airframe->AddImpulseAtLocation(ImpulseForce * ImpulseDirection, HitLocation);
		OnSpacecraftHit(Spacecraft, ShipComponent, HitLocation, NormalImpulse);
	}
	else if (Asteroid)
	{
		Asteroid->GetAsteroidComponent()->AddImpulseAtLocation(ImpulseForce * ImpulseDirection, HitLocation);
	}

	// Spawn impact decal
	if (ShipComponent && ExplosionEffectMaterial)
	{
		// Spawn
		UDecalComponent* Decal = UGameplayStatics::SpawnDecalAttached(
			ExplosionEffectMaterial,
			FMath::FRandRange(50, 150) * FVector(1, 1, 1),
			ShipComponent,
			NAME_None,
			HitLocation,
			HitNormal.Rotation(),
			EAttachLocation::KeepWorldPosition,
			120);

		// Instanciate and configure the decal material
		UMaterialInterface* DecalMaterial = Decal->GetMaterial(0);
		UMaterialInstanceDynamic* DecalMaterialInst = UMaterialInstanceDynamic::Create(DecalMaterial, GetWorld());
		if (DecalMaterialInst)
		{
			DecalMaterialInst->SetScalarParameterValue("RandomParameter", FMath::FRandRange(1, 0));
			Decal->SetMaterial(0, DecalMaterialInst);
		}
	}

	// Bomb has done its job, good job bomb
	OnBombDetonated(Spacecraft, ShipComponent, HitLocation, ImpulseDirection);
}

void AFlareBomb::OnSpacecraftHit(AFlareSpacecraft* HitSpacecraft, UFlareSpacecraftComponent* HitComponent, FVector HitLocation, FVector InertialNormal)
{
	FLOG("AFlareBomb::OnSpacecraftHit");
	UFlareCompany* OwnerCompany = ParentWeapon->GetSpacecraft()->GetCompany();
	AFlarePlayerController* PC = Cast<AFlarePlayerController>(GetWorld()->GetFirstPlayerController());

	// Apply damage
	HitSpacecraft->GetDamageSystem()->ApplyDamage(WeaponDescription->WeaponCharacteristics.ExplosionPower,
		WeaponDescription->WeaponCharacteristics.AmmoDamageRadius,
		HitLocation,
		EFlareDamage::DAM_HEAT,
		ParentWeapon->GetSpacecraft()->GetCompany());

	// Play sound
	if (HitSpacecraft->IsLocallyControlled())
	{
		UGameplayStatics::PlaySoundAtLocation(GetWorld(), DamageSound, HitLocation, 1, 1);
	}

	// Ship salvage
	if (!HitSpacecraft->IsStation() &&
		((WeaponDescription->WeaponCharacteristics.DamageType == EFlareShellDamageType::LightSalvage && HitSpacecraft->GetDescription()->Size == EFlarePartSize::S)
	 || (WeaponDescription->WeaponCharacteristics.DamageType == EFlareShellDamageType::HeavySalvage && HitSpacecraft->GetDescription()->Size == EFlarePartSize::L)))
	{
		FLOGV("AFlareBomb::OnSpacecraftHit : salvaging %s for %s", *HitSpacecraft->GetImmatriculation().ToString(), *OwnerCompany->GetCompanyName().ToString());
		HitSpacecraft->SetHarpooned(OwnerCompany);

		if (OwnerCompany == PC->GetCompany())
		{
			FFlareMenuParameterData Data;
			Data.Spacecraft = HitSpacecraft->GetParent();
			PC->Notify(LOCTEXT("HeavyShipHarpooned", "Ship harpooned !"),
				FText::Format(LOCTEXT("HeavyShipHarpoonedFormat", "If it is destroyed, you will retrieve {0} on the next day, if you are still in this sector."),
					FText::FromString(HitSpacecraft->GetImmatriculation().ToString())),
				FName("ship-harpooned"),
				EFlareNotification::NT_Military,
				10.0f,
				EFlareMenu::MENU_Ship,
				Data);
		}
	}
}

void AFlareBomb::OnBombDetonated(AFlareSpacecraft* HitSpacecraft, UFlareSpacecraftComponent* HitComponent, FVector HitLocation, FVector InertialNormal)
{
	// Unregister
	if (ParentWeapon)
	{
		ParentWeapon->GetSpacecraft()->GetGame()->GetActiveSector()->UnregisterBomb(this);
	}

	// Attach to the hull if it's a salvage harpoon
	if ((WeaponDescription->WeaponCharacteristics.DamageType == EFlareShellDamageType::LightSalvage)
	 || (WeaponDescription->WeaponCharacteristics.DamageType == EFlareShellDamageType::HeavySalvage))
	{
		if (HitSpacecraft && !HitSpacecraft->IsStation() && HitComponent && WeaponDescription &&
		   ((WeaponDescription->WeaponCharacteristics.DamageType == EFlareShellDamageType::LightSalvage && HitSpacecraft->GetDescription()->Size == EFlarePartSize::S)
		 || (WeaponDescription->WeaponCharacteristics.DamageType == EFlareShellDamageType::HeavySalvage && HitSpacecraft->GetDescription()->Size == EFlarePartSize::L)))
		{
			SetActorLocation(HitLocation);
			SetActorRotation(InertialNormal.Rotation());
			AttachToActor(HitSpacecraft, FAttachmentTransformRules(EAttachmentRule::KeepWorld, true), NAME_None);
		}
	}
	else
	{
		Destroy();
	}
}

FFlareBombSave* AFlareBomb::Save()
{
	// Physical data
	BombData.Location = GetActorLocation();
	BombData.Rotation = GetActorRotation();
	if (!Paused)
	{
		BombData.LinearVelocity = BombComp->GetPhysicsLinearVelocity();
		BombData.AngularVelocity = BombComp->GetPhysicsAngularVelocity();
	}

	// TODO Investigate on NULL ParentWeapon
	if (ParentWeapon)
	{
		BombData.ParentSpacecraft = ParentWeapon->GetSpacecraft()->GetImmatriculation();
		BombData.WeaponSlotIdentifier = ParentWeapon->SlotIdentifier;
	}

	return &BombData;
}

void AFlareBomb::SetPause(bool Pause)
{
	if (Paused == Pause)
	{
		return;
	}

	// On pause, save the state
	if (Pause)
	{
		Save();
	}

	// Toggle pause
	Paused = Pause;
	SetActorHiddenInGame(Pause);
	BombComp->SetSimulatePhysics(!Pause);
	CustomTimeDilation = (Pause ? 0.f : 1.0);

	// On unpause, restore physics
	if (!Pause)
	{
		BombComp->SetPhysicsLinearVelocity(BombData.LinearVelocity);
		BombComp->SetPhysicsAngularVelocity(BombData.AngularVelocity);
		if (!BombData.Dropped && ParentWeapon)
		{
			AttachToActor(ParentWeapon->GetSpacecraft(), FAttachmentTransformRules(EAttachmentRule::KeepWorld, true), NAME_None);
		}
	}
}

float AFlareBomb::GetParentDistance() const
{
	return (ParentWeapon->GetComponentLocation() - GetActorLocation()).Size();
}

#undef LOCTEXT_NAMESPACE
<|endoftext|>


<commit_before>#include "simdjson.h"
#include <unistd.h>

#include "benchmark.h"

SIMDJSON_PUSH_DISABLE_ALL_WARNINGS

// #define RAPIDJSON_SSE2 // bad for performance
// #define RAPIDJSON_SSE42 // bad for performance
#include "rapidjson/document.h"
#include "rapidjson/reader.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"

#include "sajson.h"

SIMDJSON_POP_DISABLE_WARNINGS

using namespace rapidjson;
using namespace simdjson;
struct stat_s {
  size_t number_count;
  size_t object_count;
  size_t array_count;
  size_t null_count;
  size_t true_count;
  size_t false_count;
  bool valid;
};

typedef struct stat_s stat_t;

bool stat_equal(const stat_t &s1, const stat_t &s2) {
  return (s1.valid == s2.valid) && (s1.number_count == s2.number_count) &&
         (s1.object_count == s2.object_count) &&
         (s1.array_count == s2.array_count) &&
         (s1.null_count == s2.null_count) && (s1.true_count == s2.true_count) &&
         (s1.false_count == s2.false_count);
}

void print_stat(const stat_t &s) {
  if (!s.valid) {
    printf("invalid\n");
    return;
  }
  printf("number: %zu object: %zu array: %zu null: %zu true: %zu false: %zu\n",
         s.number_count, s.object_count, s.array_count, s.null_count,
         s.true_count, s.false_count);
}

really_inline void simdjson_process_atom(stat_t &s,
                                         simdjson::dom::element element) {
  if (element.is<double>()) {
    s.number_count++;
  } else if (element.is<bool>()) {
    simdjson::error_code err;
    bool v;
    element.get<bool>().tie(v,err);
    if (v) {
      s.true_count++;
    } else {
      s.false_count++;
    }
  } else if (element.is_null()) {
    s.null_count++;
  }
}

void simdjson_recurse(stat_t &s, simdjson::dom::element element) {
  if (element.is<simdjson::dom::array>()) {
    s.array_count++;
    auto [array, array_error] = element.get<simdjson::dom::array>();
    if (array_error) { std::cerr << array_error << std::endl; abort(); }
    for (auto child : array) {
      if (child.is<simdjson::dom::array>() || child.is<simdjson::dom::object>()) {
        simdjson_recurse(s, child);
      } else {
        simdjson_process_atom(s, child);
      }
    }
  } else if (element.is<simdjson::dom::object>()) {
    s.object_count++;
    auto [object, object_error] = element.get<simdjson::dom::object>();
    if (object_error) { std::cerr << object_error << std::endl; abort(); }
    for (auto field : object) {
      if (field.value.is<simdjson::dom::array>() || field.value.is<simdjson::dom::object>()) {
        simdjson_recurse(s, field.value);
      } else {
        simdjson_process_atom(s, field.value);
      }
    }
  } else {
    simdjson_process_atom(s, element);
  }
}

never_inline stat_t
simdjson_compute_stats(const simdjson::padded_string &p) {
  stat_t s{};
  simdjson::dom::parser parser;
  auto [doc, error] = parser.parse(p);
  if (error) {
    s.valid = false;
    return s;
  }
  s.valid = true;
  simdjson_recurse(s, doc);
  return s;
}

// see
// https://github.com/miloyip/nativejson-benchmark/blob/master/src/tests/sajsontest.cpp
void sajson_traverse(stat_t &stats, const sajson::value &node) {
  using namespace sajson;
  switch (node.get_type()) {
  case TYPE_NULL:
    stats.null_count++;
    break;
  case TYPE_FALSE:
    stats.false_count++;
    break;
  case TYPE_TRUE:
    stats.true_count++;
    break;
  case TYPE_ARRAY: {
    stats.array_count++;
    auto length = node.get_length();
    for (size_t i = 0; i < length; ++i) {
      sajson_traverse(stats, node.get_array_element(i));
    }
    break;
  }
  case TYPE_OBJECT: {
    stats.object_count++;
    auto length = node.get_length();
    for (auto i = 0u; i < length; ++i) {
      sajson_traverse(stats, node.get_object_value(i));
    }
    break;
  }
  case TYPE_STRING:
    // skip
    break;

  case TYPE_DOUBLE:
  case TYPE_INTEGER:
    stats.number_count++; // node.get_number_value();
    break;
  default:
    assert(false && "unknown node type");
  }
}

never_inline stat_t
sasjon_compute_stats(const simdjson::padded_string &p) {
  stat_t answer{};
  char *buffer = (char *)malloc(p.size());
  if (buffer == nullptr) {
    return answer;
  }
  memcpy(buffer, p.data(), p.size());
  auto d = sajson::parse(sajson::dynamic_allocation(),
                         sajson::mutable_string_view(p.size(), buffer));
  answer.valid = d.is_valid();
  if (!answer.valid) {
    free(buffer);
    return answer;
  }
  answer.number_count = 0;
  answer.object_count = 0;
  answer.array_count = 0;
  answer.null_count = 0;
  answer.true_count = 0;
  answer.false_count = 0;
  sajson_traverse(answer, d.get_root());
  free(buffer);
  return answer;
}

void rapid_traverse(stat_t &stats, const rapidjson::Value &v) {
  switch (v.GetType()) {
  case kNullType:
    stats.null_count++;
    break;
  case kFalseType:
    stats.false_count++;
    break;
  case kTrueType:
    stats.true_count++;
    break;

  case kObjectType:
    for (Value::ConstMemberIterator m = v.MemberBegin(); m != v.MemberEnd();
         ++m) {
      rapid_traverse(stats, m->value);
    }
    stats.object_count++;
    break;
  case kArrayType:
    for (Value::ConstValueIterator i = v.Begin(); i != v.End();
         ++i) { // v.Size();
      rapid_traverse(stats, *i);
    }
    stats.array_count++;
    break;

  case kStringType:
    break;

  case kNumberType:
    stats.number_count++;
    break;
  }
}

never_inline stat_t
rapid_compute_stats(const simdjson::padded_string &p) {
  stat_t answer{};
  char *buffer = (char *)malloc(p.size() + 1);
  if (buffer == nullptr) {
    return answer;
  }
  memcpy(buffer, p.data(), p.size());
  buffer[p.size()] = '\0';
  rapidjson::Document d;
  d.ParseInsitu<kParseValidateEncodingFlag>(buffer);
  answer.valid = !d.HasParseError();
  if (!answer.valid) {
    free(buffer);
    return answer;
  }
  answer.number_count = 0;
  answer.object_count = 0;
  answer.array_count = 0;
  answer.null_count = 0;
  answer.true_count = 0;
  answer.false_count = 0;
  rapid_traverse(answer, d);
  free(buffer);
  return answer;
}

never_inline stat_t
rapid_accurate_compute_stats(const simdjson::padded_string &p) {
  stat_t answer{};
  char *buffer = (char *)malloc(p.size() + 1);
  if (buffer == nullptr) {
    return answer;
  }
  memcpy(buffer, p.data(), p.size());
  buffer[p.size()] = '\0';
  rapidjson::Document d;
  d.ParseInsitu<kParseValidateEncodingFlag | kParseFullPrecisionFlag>(buffer);
  answer.valid = !d.HasParseError();
  if (!answer.valid) {
    free(buffer);
    return answer;
  }
  answer.number_count = 0;
  answer.object_count = 0;
  answer.array_count = 0;
  answer.null_count = 0;
  answer.true_count = 0;
  answer.false_count = 0;
  rapid_traverse(answer, d);
  free(buffer);
  return answer;
}
int main(int argc, char *argv[]) {
  bool verbose = false;
  bool just_data = false;

  int c;
  while ((c = getopt(argc, argv, "vt")) != -1)
    switch (c) {
    case 't':
      just_data = true;
      break;
    case 'v':
      verbose = true;
      break;
    default:
      abort();
    }
  if (optind >= argc) {
    std::cerr
        << "Using different parsers, we compute the content statistics of "
           "JSON documents."
        << std::endl;
    std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
    std::cerr << "Or " << argv[0] << " -v <jsonfile>" << std::endl;
    exit(1);
  }
  const char *filename = argv[optind];
  if (optind + 1 < argc) {
    std::cerr << "warning: ignoring everything after " << argv[optind + 1]
              << std::endl;
  }
  auto [p, error] = simdjson::padded_string::load(filename);
  if (error) {
    std::cerr << "Could not load the file " << filename << std::endl;
    return EXIT_FAILURE;
  }
  // Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
  if (verbose) {
    std::cout << "Input has ";
    if (p.size() > 1000 * 1000)
      std::cout << p.size() / (1000 * 1000) << " MB ";
    else if (p.size() > 1000)
      std::cout << p.size() / 1000 << " KB ";
    else
      std::cout << p.size() << " B ";
    std::cout << std::endl;
  }
  stat_t s1 = simdjson_compute_stats(p);
  if (verbose) {
    printf("simdjson: ");
    print_stat(s1);
  }
  stat_t s2 = rapid_compute_stats(p);
  if (verbose) {
    printf("rapid:    ");
    print_stat(s2);
  }
  stat_t s2a = rapid_accurate_compute_stats(p);
  if (verbose) {
    printf("rapid full:    ");
    print_stat(s2a);
  }
  stat_t s3 = sasjon_compute_stats(p);
  if (verbose) {
    printf("sasjon:   ");
    print_stat(s3);
  }
  assert(stat_equal(s1, s2));
  assert(stat_equal(s1, s3));
  int repeat = 50;
  size_t volume = p.size();
  if (just_data) {
    printf("name cycles_per_byte cycles_per_byte_err gb_per_s gb_per_s_err \n");
  }
  BEST_TIME("simdjson            ", simdjson_compute_stats(p).valid, true, ,
            repeat, volume, !just_data);
  BEST_TIME("RapidJSON           ", rapid_compute_stats(p).valid, true, ,
            repeat, volume, !just_data);
  BEST_TIME("RapidJSON (precise) ", rapid_accurate_compute_stats(p).valid, true, ,
            repeat, volume, !just_data);
  BEST_TIME("sasjon              ", sasjon_compute_stats(p).valid, true, ,
            repeat, volume, !just_data);
}
<commit_msg>New API traversal tests.<commit_after>#include "simdjson.h"
#include <unistd.h>

#include "benchmark.h"

SIMDJSON_PUSH_DISABLE_ALL_WARNINGS

// #define RAPIDJSON_SSE2 // bad for performance
// #define RAPIDJSON_SSE42 // bad for performance
#include "rapidjson/document.h"
#include "rapidjson/reader.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"

#include "sajson.h"

SIMDJSON_POP_DISABLE_WARNINGS

using namespace rapidjson;
using namespace simdjson;
struct stat_s {
  size_t number_count;
  size_t object_count;
  size_t array_count;
  size_t null_count;
  size_t true_count;
  size_t false_count;
  bool valid;
};

typedef struct stat_s stat_t;

bool stat_equal(const stat_t &s1, const stat_t &s2) {
  return (s1.valid == s2.valid) && (s1.number_count == s2.number_count) &&
         (s1.object_count == s2.object_count) &&
         (s1.array_count == s2.array_count) &&
         (s1.null_count == s2.null_count) && (s1.true_count == s2.true_count) &&
         (s1.false_count == s2.false_count);
}

void print_stat(const stat_t &s) {
  if (!s.valid) {
    printf("invalid\n");
    return;
  }
  printf("number: %zu object: %zu array: %zu null: %zu true: %zu false: %zu\n",
         s.number_count, s.object_count, s.array_count, s.null_count,
         s.true_count, s.false_count);
}

really_inline void simdjson_process_atom(stat_t &s,
                                         simdjson::dom::element element) {
  if (element.is<double>()) {
    s.number_count++;
  } else if (element.is<bool>()) {
    simdjson::error_code err;
    bool v;
    element.get<bool>().tie(v, err);
    if (v) {
      s.true_count++;
    } else {
      s.false_count++;
    }
  } else if (element.is_null()) {
    s.null_count++;
  }
}

void simdjson_recurse(stat_t &s, simdjson::dom::element element) {
  if (element.is<simdjson::dom::array>()) {
    s.array_count++;
    auto [array, array_error] = element.get<simdjson::dom::array>();
    if (array_error) {
      std::cerr << array_error << std::endl;
      abort();
    }
    for (auto child : array) {
      if (child.is<simdjson::dom::array>() ||
          child.is<simdjson::dom::object>()) {
        simdjson_recurse(s, child);
      } else {
        simdjson_process_atom(s, child);
      }
    }
  } else if (element.is<simdjson::dom::object>()) {
    s.object_count++;
    auto [object, object_error] = element.get<simdjson::dom::object>();
    if (object_error) {
      std::cerr << object_error << std::endl;
      abort();
    }
    for (auto field : object) {
      if (field.value.is<simdjson::dom::array>() ||
          field.value.is<simdjson::dom::object>()) {
        simdjson_recurse(s, field.value);
      } else {
        simdjson_process_atom(s, field.value);
      }
    }
  } else {
    simdjson_process_atom(s, element);
  }
}

never_inline stat_t simdjson_compute_stats(const simdjson::padded_string &p) {
  stat_t s{};
  simdjson::dom::parser parser;
  auto [doc, error] = parser.parse(p);
  if (error) {
    s.valid = false;
    return s;
  }
  s.valid = true;
  simdjson_recurse(s, doc);
  return s;
}

///
struct Stat {
  size_t objectCount;
  size_t arrayCount;
  size_t numberCount;
  size_t stringCount;
  size_t trueCount;
  size_t falseCount;
  size_t nullCount;

  size_t memberCount;  // Number of members in all objects
  size_t elementCount; // Number of elements in all arrays
  size_t stringLength; // Number of code units in all strings
};

static void GenStatPlus(Stat &stat, const dom::element &v) {
  switch (v.type()) {
  case dom::element_type::ARRAY:
    for (dom::element child : dom::array(v)) {
      GenStatPlus(stat, child);
      stat.elementCount++;
    }
    stat.arrayCount++;
    break;
  case dom::element_type::OBJECT:
    for (dom::key_value_pair kv : dom::object(v)) {
      GenStatPlus(stat, dom::element(kv.value));
      stat.memberCount++;
      stat.stringCount++;
    }
    stat.objectCount++;
    break;
  case dom::element_type::INT64:
  case dom::element_type::UINT64:
  case dom::element_type::DOUBLE:
    stat.numberCount++;
    break;
  case dom::element_type::STRING: {
    stat.stringCount++;
    std::string_view sv = v.get<std::string_view>();
    stat.stringLength += sv.size();
  } break;
  case dom::element_type::BOOL:
    if (v.get<bool>()) {
      stat.trueCount++;
    } else {
      stat.falseCount++;
    }
    break;
  case dom::element_type::NULL_VALUE:
    ++stat.nullCount;
    break;
  }
}

static void RapidGenStat(Stat &stat, const rapidjson::Value &v) {
  switch (v.GetType()) {
  case kNullType:
    stat.nullCount++;
    break;
  case kFalseType:
    stat.falseCount++;
    break;
  case kTrueType:
    stat.trueCount++;
    break;

  case kObjectType:
    for (Value::ConstMemberIterator m = v.MemberBegin(); m != v.MemberEnd();
         ++m) {
      stat.stringLength += m->name.GetStringLength();
      RapidGenStat(stat, m->value);
    }
    stat.objectCount++;
    stat.memberCount += (v.MemberEnd() - v.MemberBegin());
    stat.stringCount += (v.MemberEnd() - v.MemberBegin()); // Key
    break;

  case kArrayType:
    for (Value::ConstValueIterator i = v.Begin(); i != v.End(); ++i)
      RapidGenStat(stat, *i);
    stat.arrayCount++;
    stat.elementCount += v.Size();
    break;

  case kStringType:
    stat.stringCount++;
    stat.stringLength += v.GetStringLength();
    break;

  case kNumberType:
    stat.numberCount++;
    break;
  }
}
never_inline Stat rapidjson_compute_stats_ref(const rapidjson::Value &doc) {
  Stat s{};
  RapidGenStat(s, doc);
  return s;
}

never_inline Stat
simdjson_compute_stats_refplus(const simdjson::dom::element &doc) {
  Stat s{};
  GenStatPlus(s, doc);
  return s;
}

// see
// https://github.com/miloyip/nativejson-benchmark/blob/master/src/tests/sajsontest.cpp
void sajson_traverse(stat_t &stats, const sajson::value &node) {
  using namespace sajson;
  switch (node.get_type()) {
  case TYPE_NULL:
    stats.null_count++;
    break;
  case TYPE_FALSE:
    stats.false_count++;
    break;
  case TYPE_TRUE:
    stats.true_count++;
    break;
  case TYPE_ARRAY: {
    stats.array_count++;
    auto length = node.get_length();
    for (size_t i = 0; i < length; ++i) {
      sajson_traverse(stats, node.get_array_element(i));
    }
    break;
  }
  case TYPE_OBJECT: {
    stats.object_count++;
    auto length = node.get_length();
    for (auto i = 0u; i < length; ++i) {
      sajson_traverse(stats, node.get_object_value(i));
    }
    break;
  }
  case TYPE_STRING:
    // skip
    break;

  case TYPE_DOUBLE:
  case TYPE_INTEGER:
    stats.number_count++; // node.get_number_value();
    break;
  default:
    assert(false && "unknown node type");
  }
}

never_inline stat_t sasjon_compute_stats(const simdjson::padded_string &p) {
  stat_t answer{};
  char *buffer = (char *)malloc(p.size());
  if (buffer == nullptr) {
    return answer;
  }
  memcpy(buffer, p.data(), p.size());
  auto d = sajson::parse(sajson::dynamic_allocation(),
                         sajson::mutable_string_view(p.size(), buffer));
  answer.valid = d.is_valid();
  if (!answer.valid) {
    free(buffer);
    return answer;
  }
  answer.number_count = 0;
  answer.object_count = 0;
  answer.array_count = 0;
  answer.null_count = 0;
  answer.true_count = 0;
  answer.false_count = 0;
  sajson_traverse(answer, d.get_root());
  free(buffer);
  return answer;
}

void rapid_traverse(stat_t &stats, const rapidjson::Value &v) {
  switch (v.GetType()) {
  case kNullType:
    stats.null_count++;
    break;
  case kFalseType:
    stats.false_count++;
    break;
  case kTrueType:
    stats.true_count++;
    break;

  case kObjectType:
    for (Value::ConstMemberIterator m = v.MemberBegin(); m != v.MemberEnd();
         ++m) {
      rapid_traverse(stats, m->value);
    }
    stats.object_count++;
    break;
  case kArrayType:
    for (Value::ConstValueIterator i = v.Begin(); i != v.End();
         ++i) { // v.Size();
      rapid_traverse(stats, *i);
    }
    stats.array_count++;
    break;

  case kStringType:
    break;

  case kNumberType:
    stats.number_count++;
    break;
  }
}

never_inline stat_t rapid_compute_stats(const simdjson::padded_string &p) {
  stat_t answer{};
  char *buffer = (char *)malloc(p.size() + 1);
  if (buffer == nullptr) {
    return answer;
  }
  memcpy(buffer, p.data(), p.size());
  buffer[p.size()] = '\0';
  rapidjson::Document d;
  d.ParseInsitu<kParseValidateEncodingFlag>(buffer);
  answer.valid = !d.HasParseError();
  if (!answer.valid) {
    free(buffer);
    return answer;
  }
  answer.number_count = 0;
  answer.object_count = 0;
  answer.array_count = 0;
  answer.null_count = 0;
  answer.true_count = 0;
  answer.false_count = 0;
  rapid_traverse(answer, d);
  free(buffer);
  return answer;
}

never_inline stat_t
rapid_accurate_compute_stats(const simdjson::padded_string &p) {
  stat_t answer{};
  char *buffer = (char *)malloc(p.size() + 1);
  if (buffer == nullptr) {
    return answer;
  }
  memcpy(buffer, p.data(), p.size());
  buffer[p.size()] = '\0';
  rapidjson::Document d;
  d.ParseInsitu<kParseValidateEncodingFlag | kParseFullPrecisionFlag>(buffer);
  answer.valid = !d.HasParseError();
  if (!answer.valid) {
    free(buffer);
    return answer;
  }
  answer.number_count = 0;
  answer.object_count = 0;
  answer.array_count = 0;
  answer.null_count = 0;
  answer.true_count = 0;
  answer.false_count = 0;
  rapid_traverse(answer, d);
  free(buffer);
  return answer;
}
int main(int argc, char *argv[]) {
  bool verbose = false;
  bool just_data = false;

  int c;
  while ((c = getopt(argc, argv, "vt")) != -1)
    switch (c) {
    case 't':
      just_data = true;
      break;
    case 'v':
      verbose = true;
      break;
    default:
      abort();
    }
  if (optind >= argc) {
    std::cerr
        << "Using different parsers, we compute the content statistics of "
           "JSON documents."
        << std::endl;
    std::cerr << "Usage: " << argv[0] << " <jsonfile>" << std::endl;
    std::cerr << "Or " << argv[0] << " -v <jsonfile>" << std::endl;
    exit(1);
  }
  const char *filename = argv[optind];
  if (optind + 1 < argc) {
    std::cerr << "warning: ignoring everything after " << argv[optind + 1]
              << std::endl;
  }
  auto [p, error] = simdjson::padded_string::load(filename);
  if (error) {
    std::cerr << "Could not load the file " << filename << std::endl;
    return EXIT_FAILURE;
  }
  // Gigabyte: https://en.wikipedia.org/wiki/Gigabyte
  if (verbose) {
    std::cout << "Input has ";
    if (p.size() > 1000 * 1000)
      std::cout << p.size() / (1000 * 1000) << " MB ";
    else if (p.size() > 1000)
      std::cout << p.size() / 1000 << " KB ";
    else
      std::cout << p.size() << " B ";
    std::cout << std::endl;
  }
  stat_t s1 = simdjson_compute_stats(p);
  if (verbose) {
    printf("simdjson: ");
    print_stat(s1);
  }
  stat_t s2 = rapid_compute_stats(p);
  if (verbose) {
    printf("rapid:    ");
    print_stat(s2);
  }
  stat_t s2a = rapid_accurate_compute_stats(p);
  if (verbose) {
    printf("rapid full:    ");
    print_stat(s2a);
  }
  stat_t s3 = sasjon_compute_stats(p);
  if (verbose) {
    printf("sasjon:   ");
    print_stat(s3);
  }
  assert(stat_equal(s1, s2));
  assert(stat_equal(s1, s3));
  int repeat = 50;
  size_t volume = p.size();
  if (just_data) {
    printf("name cycles_per_byte cycles_per_byte_err gb_per_s gb_per_s_err \n");
  }
  BEST_TIME("simdjson            ", simdjson_compute_stats(p).valid, true, ,
            repeat, volume, !just_data);
  BEST_TIME("RapidJSON           ", rapid_compute_stats(p).valid, true, ,
            repeat, volume, !just_data);
  BEST_TIME("RapidJSON (precise) ", rapid_accurate_compute_stats(p).valid, true,
            , repeat, volume, !just_data);
  BEST_TIME("sasjon              ", sasjon_compute_stats(p).valid, true, ,
            repeat, volume, !just_data);
  if (!just_data) {
    printf("API traversal tests\n");
    simdjson::dom::parser parser;
    auto [doc, err] = parser.parse(p);
    if (err) {
      std::cerr << err << std::endl;
    }
    size_t refval = simdjson_compute_stats_refplus(doc).objectCount;

    BEST_TIME("simdjson            ",
              simdjson_compute_stats_refplus(doc).objectCount, refval, , repeat,
              volume, !just_data);
    char *buffer = (char *)malloc(p.size() + 1);
    memcpy(buffer, p.data(), p.size());
    buffer[p.size()] = '\0';
    rapidjson::Document d;
    d.ParseInsitu<kParseValidateEncodingFlag>(buffer);
    BEST_TIME("rapid              ", rapidjson_compute_stats_ref(d).objectCount,
              refval, , repeat, volume, !just_data);
    free(buffer);
  }
}
<|endoftext|>