/*******************************************************************************
 * Copyright IBM Corp. and others 2017
 *
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which accompanies this
 * distribution and is available at https://www.eclipse.org/legal/epl-2.0/
 * or the Apache License, Version 2.0 which accompanies this distribution
 * and is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License, v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception [1] and GNU General Public
 * License, version 2 with the OpenJDK Assembly Exception [2].
 *
 * [1] https://www.gnu.org/software/classpath/license.html
 * [2] https://openjdk.org/legal/assembly-exception.html
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 OR GPL-2.0-only WITH OpenJDK-assembly-exception-1.0
 *******************************************************************************/

#include "compile/Compilation.hpp"
#include "compile/Method.hpp"
#include "env/FrontEnd.hpp"
#include "infra/List.hpp"
#include "il/Block.hpp"
#include "ilgen/VirtualMachineState.hpp"
#include "ilgen/IlBuilder.hpp"
#include "ilgen/MethodBuilder.hpp"
#include "ilgen/BytecodeBuilder.hpp"
#include "ilgen/TypeDictionary.hpp"

// should really move into IlInjector.hpp
#define TraceEnabled    (comp()->getOption(TR_TraceILGen))
#define TraceIL(m, ...) {if (TraceEnabled) {traceMsg(comp(), m, ##__VA_ARGS__);}}


OMR::BytecodeBuilder::BytecodeBuilder(TR::MethodBuilder *methodBuilder,
                                      int32_t bcIndex,
                                      const char *name,
                                      int32_t bcLength)
   : TR::IlBuilder(methodBuilder, methodBuilder->typeDictionary(), bcIndex),
   _fallThroughBuilder(0),
   _name(name),
   _bcLength(bcLength),
   _initialVMState(0),
   _vmState(0)
   {
   _successorBuilders = new (_types->trMemory()->heapMemoryRegion()) List<TR::BytecodeBuilder>(_types->trMemory());
   initialize(methodBuilder->details(), methodBuilder->methodSymbol(),
              methodBuilder->fe(), methodBuilder->symRefTab());
   initSequence();
   }

void
TR::BytecodeBuilder::initialize(TR::IlGeneratorMethodDetails * details,
                                TR::ResolvedMethodSymbol     * methodSymbol,
                                TR::FrontEnd                 * fe,
                                TR::SymbolReferenceTable     * symRefTab)
    {
    this->OMR::IlInjector::initialize(details, methodSymbol, fe, symRefTab);

    //addBytecodeBuilderToList relies on _comp and it won't be ready until now
    _methodBuilder->addToAllBytecodeBuildersList(this);
    }

void
OMR::BytecodeBuilder::appendBlock(TR::Block *block, bool addEdge)
   {
   if (block == NULL)
      {
      block = emptyBlock();
      }

   block->setByteCodeIndex(_bcIndex, _comp);
   return TR::IlBuilder::appendBlock(block, addEdge);
   }

uint32_t
OMR::BytecodeBuilder::countBlocks()
   {
   // only count each block once
   if (_count > -1)
      return _count;

   TraceIL("[ %p ] TR::BytecodeBuilder::countBlocks 0\n", this);

   _count = TR::IlBuilder::countBlocks();

   if (NULL != _fallThroughBuilder)
      _methodBuilder->addToBlockCountingWorklist(_fallThroughBuilder);

   ListIterator<TR::BytecodeBuilder> iter(_successorBuilders);
   for (TR::BytecodeBuilder *builder = iter.getFirst(); !iter.atEnd(); builder = iter.getNext())
      if (builder->_count < 0)
         _methodBuilder->addToBlockCountingWorklist(builder);

   TraceIL("[ %p ] TR::BytecodeBuilder::countBlocks %d\n", this, _count);

   return _count;
   }

bool
OMR::BytecodeBuilder::connectTrees()
   {
   if (!_connectedTrees)
      {
      TraceIL("[ %p ] TR::BytecodeBuilder::connectTrees\n", this);
      bool rc = TR::IlBuilder::connectTrees();
      addAllSuccessorBuildersToWorklist();
      return rc;
      }

   return true;
   }


void
OMR::BytecodeBuilder::addAllSuccessorBuildersToWorklist()
   {
   if (NULL != _fallThroughBuilder)
      _methodBuilder->addToTreeConnectingWorklist(_fallThroughBuilder);

   // iterate over _successorBuilders
   ListIterator<TR::BytecodeBuilder> iter(_successorBuilders);
   for (TR::BytecodeBuilder *builder = iter.getFirst(); !iter.atEnd(); builder = iter.getNext())
      _methodBuilder->addToTreeConnectingWorklist(builder);
   }

// Must be called *after* all code has been added to the bytecode builder
// Also, current VM state is assumed to be what should propagate to the fallthrough builder
void
OMR::BytecodeBuilder::AddFallThroughBuilder(TR::BytecodeBuilder *ftb)
   {
   TR_ASSERT_FATAL(comesBack(), "builder does not appear to have a fall through path");

   TraceIL("IlBuilder[ %p ]:: fallThrough successor [ %p ]\n", this, ftb);

   TR::BytecodeBuilder *b = ftb;
   transferVMState(&b);    // may change what b points at!

   if (b != ftb)
      TraceIL("IlBuilder[ %p ]:: fallThrough successor changed to [ %p ]\n", this, b);
   _fallThroughBuilder = b;
    _methodBuilder->addBytecodeBuilderToWorklist(ftb);

   // add explicit goto and register the actual fall-through block
   TR::IlBuilder *tgtb = b;
   TR::IlBuilder::Goto(&tgtb);
   }

// AddSuccessorBuilders() should be called with a list of TR::BytecodeBuilder ** pointers.
// Each one of these pointers could be changed by AddSuccessorBuilders() in the case where
// some operations need to be inserted along the control flow edges to synchronize the
// vm state from "this" builder to the target BytecodeBuilder. For this reason, the actual
// control flow edges should be created (i.e. with Goto, IfCmp*, etc.) *after* calling
// AddSuccessorBuilders, and the target used when creating those flow edges should take
// into account that AddSuccessorBuilders may change the builder object provided.
void
OMR::BytecodeBuilder::AddSuccessorBuilders(uint32_t numExits, ...)
   {
   va_list exits;
   va_start(exits, numExits);
   for (auto e = 0U; e < numExits; e++)
      {
      TR::BytecodeBuilder **builder = (TR::BytecodeBuilder **) va_arg(exits, TR::BytecodeBuilder **);
      if ((*builder)->_bcIndex < _bcIndex) //If the successor has a bcIndex < than the current bcIndex this may be a loop
         _methodSymbol->setMayHaveLoops(true);
      transferVMState(builder);            // may change what builder points at!
      _successorBuilders->add(*builder);   // must be the bytecode builder that comes back from transferVMState()
      _methodBuilder->addBytecodeBuilderToWorklist(*builder);
      TraceIL("IlBuilder[ %p ]:: successor [ %p ]\n", this, *builder);
      }
   va_end(exits);
   }

void
OMR::BytecodeBuilder::setHandlerInfo(uint32_t catchType)
   {
   TR::Block *catchBlock = getEntry();
   catchBlock->setIsCold();
   catchBlock->setHandlerInfo(catchType, static_cast<uint8_t>(comp()->getInlineDepth()), -1, _methodSymbol->getResolvedMethod(), comp());
   }

void
OMR::BytecodeBuilder::propagateVMState(TR::VirtualMachineState *fromVMState)
   {
   _initialVMState = fromVMState->makeCopy();
   _vmState = fromVMState->makeCopy();
   }

// transferVMState needs to be called before the actual transfer operation (Goto, IfCmp,
// etc.) is created because we may need to insert a builder object along that control
// flow edge to synchronize the vm state at the target (in the case of a merge point).
// On return, the object pointed at by the "b" parameter may have changed. The caller
// should direct control for this edge to whatever the parameter passed to "b" is
// pointing at on return
void
OMR::BytecodeBuilder::transferVMState(TR::BytecodeBuilder **b)
   {
   TR_ASSERT_FATAL(_vmState != NULL, "asked to transfer a NULL vmState from builder %p [ bci %d ]", this, _bcIndex);
   if ((*b)->initialVMState())
      {
      // there is already a vm state at the target builder
      // so we need to synchronize this builder's vm state with the target builder's vm state
      // for example, the local variables holding the elements on the operand stack may not match
      // create an intermediate builder object to do that work
      TR::BytecodeBuilder *intermediateBuilder = _methodBuilder->OrphanBytecodeBuilder((*b)->_bcIndex, (*b)->_name);

      _vmState->mergeInto((*b)->initialVMState(), intermediateBuilder);
      TraceIL("IlBuilder[ %p ]:: transferVMState merged vm state on way to [ %p ] using [ %p ]\n", this, *b, intermediateBuilder);

      TR::IlBuilder *tgtb = *b;
      intermediateBuilder->IlBuilder::Goto(&tgtb);
      intermediateBuilder->_fallThroughBuilder = *b;
      TraceIL("IlBuilder[ %p ]:: fallThrough successor [ %p ]\n", intermediateBuilder, *b);
      *b = intermediateBuilder; // branches should direct towards intermediateBuilder not original *b
      }
   else
      {
      (*b)->propagateVMState(_vmState);
      }
   }

void
OMR::BytecodeBuilder::Goto(TR::BytecodeBuilder **dest)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   Goto(*dest);
   }

void
OMR::BytecodeBuilder::Goto(TR::BytecodeBuilder *dest)
   {
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::Goto(dest);
   }

void
OMR::BytecodeBuilder::IfCmpEqual(TR::BytecodeBuilder **dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   IfCmpEqual(*dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpEqual(TR::BytecodeBuilder *dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   TR_ASSERT_FATAL(dest != NULL, "service cannot be called with NULL destination builder");
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::IfCmpEqual(dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpEqualZero(TR::BytecodeBuilder **dest, TR::IlValue *c)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   IfCmpEqualZero(*dest, c);
   }

void
OMR::BytecodeBuilder::IfCmpEqualZero(TR::BytecodeBuilder *dest, TR::IlValue *c)
   {
   TR_ASSERT_FATAL(dest != NULL, "service cannot be called with NULL destination builder");
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::IfCmpEqualZero(dest, c);
   }

void
OMR::BytecodeBuilder::IfCmpNotEqual(TR::BytecodeBuilder **dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   IfCmpNotEqual(*dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpNotEqual(TR::BytecodeBuilder *dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   TR_ASSERT_FATAL(dest != NULL, "service cannot be called with NULL destination builder");
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::IfCmpNotEqual(dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpNotEqualZero(TR::BytecodeBuilder **dest, TR::IlValue *c)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   IfCmpNotEqualZero(*dest, c);
   }

void
OMR::BytecodeBuilder::IfCmpNotEqualZero(TR::BytecodeBuilder *dest, TR::IlValue *c)
   {
   TR_ASSERT_FATAL(dest != NULL, "service cannot be called with NULL destination builder");
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::IfCmpNotEqualZero(dest, c);
   }

void
OMR::BytecodeBuilder::IfCmpLessThan(TR::BytecodeBuilder **dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   IfCmpLessThan(*dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpLessThan(TR::BytecodeBuilder *dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   TR_ASSERT_FATAL(dest != NULL, "service cannot be called with NULL destination builder");
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::IfCmpLessThan(dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpUnsignedLessThan(TR::BytecodeBuilder **dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   IfCmpUnsignedLessThan(*dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpUnsignedLessThan(TR::BytecodeBuilder *dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   TR_ASSERT_FATAL(dest != NULL, "service cannot be called with NULL destination builder");
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::IfCmpUnsignedLessThan(dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpLessOrEqual(TR::BytecodeBuilder **dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   IfCmpLessOrEqual(*dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpLessOrEqual(TR::BytecodeBuilder *dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   TR_ASSERT_FATAL(dest != NULL, "service cannot be called with NULL destination builder");
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::IfCmpLessOrEqual(dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpUnsignedLessOrEqual(TR::BytecodeBuilder **dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   IfCmpUnsignedLessOrEqual(*dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpUnsignedLessOrEqual(TR::BytecodeBuilder *dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   TR_ASSERT_FATAL(dest != NULL, "service cannot be called with NULL destination builder");
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::IfCmpUnsignedLessOrEqual(dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpGreaterThan(TR::BytecodeBuilder **dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   IfCmpGreaterThan(*dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpGreaterThan(TR::BytecodeBuilder *dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   TR_ASSERT_FATAL(dest != NULL, "service cannot be called with NULL destination builder");
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::IfCmpGreaterThan(dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpUnsignedGreaterThan(TR::BytecodeBuilder **dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   IfCmpUnsignedGreaterThan(*dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpUnsignedGreaterThan(TR::BytecodeBuilder *dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   TR_ASSERT_FATAL(dest != NULL, "service cannot be called with NULL destination builder");
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::IfCmpUnsignedGreaterThan(dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpGreaterOrEqual(TR::BytecodeBuilder **dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   IfCmpGreaterOrEqual(*dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpGreaterOrEqual(TR::BytecodeBuilder *dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   TR_ASSERT_FATAL(dest != NULL, "service cannot be called with NULL destination builder");
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::IfCmpGreaterOrEqual(dest, v1, v2);
   }
void
OMR::BytecodeBuilder::IfCmpUnsignedGreaterOrEqual(TR::BytecodeBuilder **dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   if (*dest == NULL)
      *dest = _methodBuilder->OrphanBytecodeBuilder(_bcIndex, _name);
   IfCmpUnsignedGreaterOrEqual(*dest, v1, v2);
   }

void
OMR::BytecodeBuilder::IfCmpUnsignedGreaterOrEqual(TR::BytecodeBuilder *dest, TR::IlValue *v1, TR::IlValue *v2)
   {
   TR_ASSERT_FATAL(dest != NULL, "service cannot be called with NULL destination builder");
   AddSuccessorBuilder(&dest);
   OMR::IlBuilder::IfCmpUnsignedGreaterOrEqual(dest, v1, v2);
   }

void *
OMR::BytecodeBuilder::client()
   {
   if (_client == NULL && _clientAllocator != NULL)
      _client = _clientAllocator(static_cast<TR::BytecodeBuilder *>(this));
   return _client;
   }

ClientAllocator OMR::BytecodeBuilder::_clientAllocator = NULL;
ClientAllocator OMR::BytecodeBuilder::_getImpl = NULL;