/*
 * Copyright (C) 2015-2023 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#pragma once

#if ENABLE(B3_JIT)

#include "AirInst.h"
#include "B3SparseCollection.h"
#include <wtf/FastMalloc.h>
#include <wtf/Noncopyable.h>
#include <wtf/ScopedLambda.h>
#include <wtf/SequesteredMalloc.h>
#include <wtf/TZoneMalloc.h>
#include <wtf/text/CString.h>

namespace JSC { namespace B3 { namespace Air {

class Code;
struct GenerationContext;

class Special {
    WTF_MAKE_NONCOPYABLE(Special);
    // TODO: move AirSpecial.h to use SequesteredMalloc
    WTF_MAKE_TZONE_ALLOCATED_EXPORT(Special, JS_EXPORT_PRIVATE);
public:
    static const char* const dumpPrefix;
    
    Special();
    JS_EXPORT_PRIVATE virtual ~Special();

    Code& code() const { return *m_code; }

    CString name() const;

    virtual void forEachArg(Inst&, const ScopedLambda<Inst::EachArgCallback>&) = 0;
    virtual bool isValid(Inst&) = 0;
    virtual bool admitsStack(Inst&, unsigned argIndex) = 0;
    virtual bool admitsExtendedOffsetAddr(Inst&, unsigned argIndex) = 0;
    virtual std::optional<unsigned> shouldTryAliasingDef(Inst&);

    // This gets called on for each Inst that uses this Special. Note that there is no way to
    // guarantee that a Special gets used from just one Inst, because Air might taildup late. So,
    // if you want to pass this information down to generate(), then you have to either:
    //
    // 1) Generate Air that starts with a separate Special per Patch Inst, and then merge
    //    usedRegister sets. This is probably not great, but it optimizes for the common case that
    //    Air didn't duplicate code or that such duplication didn't cause any interesting changes to
    //    register assignment.
    //
    // 2) Have the Special maintain a UncheckedKeyHashMap<Inst*, RegisterSetBuilder>. This works because the analysis
    //    that feeds into this call is performed just before code generation and there is no way
    //    for the Vector<>'s that contain the Insts to be reallocated. This allows generate() to
    //    consult the UncheckedKeyHashMap.
    //
    // 3) Hybrid: you could use (1) and fire up a UncheckedKeyHashMap if you see multiple calls.
    //
    // Note that it's not possible to rely on reportUsedRegisters() being called in the same order
    // as generate(). If we could rely on that, then we could just have each Special instance
    // maintain a Vector of RegisterSetBuilder's and then process that vector in the right order in
    // generate(). But, the ordering difference is unlikely to change since it would harm the
    // performance of the liveness analysis.
    //
    // Currently, we do (1) for B3 stackmaps.
    virtual void reportUsedRegisters(Inst&, const RegisterSetBuilder&) = 0;
    
    virtual MacroAssembler::Jump generate(Inst&, CCallHelpers&, GenerationContext&) = 0;

    virtual RegisterSetBuilder extraEarlyClobberedRegs(Inst&) = 0;
    virtual RegisterSetBuilder extraClobberedRegs(Inst&) = 0;
    
    // By default, this returns false.
    virtual bool isTerminal(Inst&);

    // By default, this returns true.
    virtual bool hasNonArgEffects(Inst&);

    // By default, this returns true.
    virtual bool hasNonArgNonControlEffects(Inst&);

    void dump(PrintStream&) const;
    void deepDump(PrintStream&) const;

protected:
    virtual void dumpImpl(PrintStream&) const = 0;
    virtual void deepDumpImpl(PrintStream&) const = 0;

private:
    friend class Code;
    friend class SparseCollection<Special>;

    unsigned m_index { UINT_MAX };
    Code* m_code { nullptr };
};

class DeepSpecialDump {
public:
    DeepSpecialDump(const Special* special)
        : m_special(special)
    {
    }

    void dump(PrintStream& out) const
    {
        if (m_special)
            m_special->deepDump(out);
        else
            out.print("<null>");
    }

private:
    const Special* m_special;
};

inline DeepSpecialDump deepDump(const Special* special)
{
    return DeepSpecialDump(special);
}

} } } // namespace JSC::B3::Air

#endif // ENABLE(B3_JIT)
