/*
 * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


#ifndef PRODUCT
#ifndef ZERO

#include "asm/macroAssembler.inline.hpp"
#include "compiler/disassembler.hpp"
#include "memory/resourceArea.hpp"
#include "utilities/globalDefinitions.hpp"

#include "utilities/vmassert_uninstall.hpp"
BEGIN_ALLOW_FORBIDDEN_FUNCTIONS
#include <regex>
END_ALLOW_FORBIDDEN_FUNCTIONS
#include "utilities/vmassert_reinstall.hpp"

#include "unittest.hpp"

static const char* replace_addr_expr(const char* str)
{
    // Remove any address expression "0x0123456789abcdef" found in order to
    // aid string comparison. Also remove any trailing printout from a padded
    // buffer (too brittle?).

    std::basic_string<char> tmp1 = std::regex_replace(str, std::regex("0x[0-9a-fA-F]+"), "<addr>");
    // Padding: aarch64
    std::basic_string<char> tmp2 = std::regex_replace(tmp1, std::regex("\\s+<addr>:\\s+\\.inst\\t<addr> ; undefined"), "");
    std::basic_string<char> tmp3 = std::regex_replace(tmp2, std::regex("\\s+<addr>:\\s+udf\\t#0"), "");
    // Padding: riscv
    std::basic_string<char> tmp4  = std::regex_replace(tmp3, std::regex("\\s+<addr>:\\s+unimp"), "");
    // Padding: x64
    std::basic_string<char> tmp5  = std::regex_replace(tmp4, std::regex("\\s+<addr>:\\s+hlt[ \\t]+(?!\\n\\s+;;)"), "");
    std::basic_string<char> red  = std::regex_replace(tmp5, std::regex("(\\s+<addr>:\\s+nop)[ \\t]*"), "$1");

    return os::strdup(red.c_str());
}

static const char* delete_header_line(const char* str)
{
    // Remove (second) header line in output, e.g.:
    // Decoding CodeBlob, name: CodeStringTest, at [<addr>, <addr>] 8 bytes\n

    std::basic_string<char> red = std::regex_replace(str, std::regex("Decoding.+bytes\\n"), "");

    return os::strdup(red.c_str());
}

static void asm_remarks_check(const AsmRemarks &rem1,
                              const AsmRemarks &rem2)
{
    ASSERT_EQ(rem1.ref(), rem2.ref()) << "Should share the same collection.";
}

static void dbg_strings_check(const DbgStrings &dbg1,
                              const DbgStrings &dbg2)
{
    ASSERT_EQ(dbg1.ref(), dbg2.ref()) << "Should share the same collection.";
}

static void disasm_string_check(CodeBuffer* cbuf, CodeBlob* blob)
{
    if (Disassembler::is_abstract())
    {
        return;   // No disassembler available (no comments will be used).
    }
    stringStream out1, out2;

    Disassembler::decode(cbuf->insts_begin(), cbuf->insts_end(), &out1, &cbuf->asm_remarks());
    Disassembler::decode(blob->code_begin(), blob->code_end(), &out2, &blob->asm_remarks());

    EXPECT_STREQ(replace_addr_expr(out1.as_string()),
                 replace_addr_expr(out2.as_string()))
        << "1. Output should be identical.";

    stringStream out3;

    Disassembler::decode(blob, &out3);

    EXPECT_STREQ(replace_addr_expr(out2.as_string()),
                 replace_addr_expr(delete_header_line(out3.as_string())))
        << "2. Output should be identical.";
}

static void copy_and_compare(CodeBuffer* cbuf)
{
    bool remarks_empty = cbuf->asm_remarks().is_empty();
    bool strings_empty = cbuf->dbg_strings().is_empty();

    BufferBlob* blob = BufferBlob::create("CodeBuffer Copy&Compare", cbuf);

    // 1. Check Assembly Remarks are shared by buffer and blob.
    asm_remarks_check(cbuf->asm_remarks(), blob->asm_remarks());

    // 2. Check Debug Strings are shared by buffer and blob.
    dbg_strings_check(cbuf->dbg_strings(), blob->dbg_strings());

    // 3. Check that the disassembly output matches.
    disasm_string_check(cbuf, blob);

    BufferBlob::free(blob);

    ASSERT_EQ(remarks_empty, cbuf->asm_remarks().is_empty())
        << "Expecting property to be unchanged.";
    ASSERT_EQ(strings_empty, cbuf->dbg_strings().is_empty())
        << "Expecting property to be unchanged.";
}

static void code_buffer_test()
{
    constexpr int BUF_SZ = 256;

    ResourceMark rm;
    CodeBuffer cbuf("CodeStringTest", BUF_SZ, BUF_SZ);
    MacroAssembler as(&cbuf);

    ASSERT_TRUE(cbuf.asm_remarks().is_empty());
    ASSERT_TRUE(cbuf.dbg_strings().is_empty());

    ASSERT_TRUE(cbuf.blob()->asm_remarks().is_empty());
    ASSERT_TRUE(cbuf.blob()->dbg_strings().is_empty());

    int re, sz, n;

    re = cbuf.insts_remaining();

    // 1. Generate a first entry.
    as.block_comment("First block comment.");
    as.nop();

    sz = re - cbuf.insts_remaining();

    ASSERT_TRUE(sz > 0);

    ASSERT_FALSE(cbuf.asm_remarks().is_empty());
    ASSERT_TRUE(cbuf.dbg_strings().is_empty());

    ASSERT_TRUE(cbuf.blob()->asm_remarks().is_empty());
    ASSERT_TRUE(cbuf.blob()->dbg_strings().is_empty());

    copy_and_compare(&cbuf);

    n = re/sz;
    ASSERT_TRUE(n > 0);

    // 2. Generate additional entries without causing the buffer to expand.
    for (unsigned i = 0; i < unsigned(n)/2; i++)
    {
        ASSERT_FALSE(cbuf.insts()->maybe_expand_to_ensure_remaining(sz));
        ASSERT_TRUE(cbuf.insts_remaining()/sz >= n/2);

        stringStream strm;
        strm.print("Comment No. %d", i);
        as.block_comment(strm.as_string());
        as.nop();
    }
    ASSERT_FALSE(cbuf.asm_remarks().is_empty());

    copy_and_compare(&cbuf);

    re = cbuf.insts_remaining();

    // 3. Generate a single code with a debug string.
    as.unimplemented("First debug string.");

    ASSERT_FALSE(cbuf.asm_remarks().is_empty());
    ASSERT_FALSE(cbuf.dbg_strings().is_empty());

    sz = re - cbuf.insts_remaining();
    n = (re - sz)/sz;
    ASSERT_TRUE(n > 0);

    // 4. Generate additional code with debug strings.
    for (unsigned i = 0; i < unsigned(n); i++)
    {
        ASSERT_TRUE(cbuf.insts_remaining() >= sz);

        stringStream strm;
        strm.print("Fixed address string No. %d", i);
        as.unimplemented(strm.as_string());
    }
    ASSERT_TRUE(cbuf.insts_remaining() >= 0);

    ASSERT_FALSE(cbuf.asm_remarks().is_empty());
    ASSERT_FALSE(cbuf.dbg_strings().is_empty());

    ASSERT_TRUE(cbuf.blob()->asm_remarks().is_empty());
    ASSERT_TRUE(cbuf.blob()->dbg_strings().is_empty());

    copy_and_compare(&cbuf);
}

static void buffer_blob_test()
{
    constexpr int BUF_SZ = 256;

    ResourceMark rm;
    BufferBlob* blob = BufferBlob::create("BufferBlob Test", BUF_SZ);
    CodeBuffer cbuf(blob);
    MacroAssembler as(&cbuf);

    ASSERT_FALSE(cbuf.insts()->has_locs());

    // The x86-64 version of 'stop' will use relocation info. that will result
    // in tainting the location start and limit if no location info. buffer is
    // present.
    static uint8_t s_loc_buf[BUF_SZ];  // Raw memory buffer used for relocInfo.
    cbuf.insts()->initialize_shared_locs((relocInfo*)&s_loc_buf[0], BUF_SZ);

    int re = cbuf.insts_remaining();

    as.block_comment("First block comment.");
    as.nop();
    as.unimplemented("First debug string.");

    int sz = re - cbuf.insts_remaining();

    ASSERT_TRUE(sz > 0);
    constexpr int LIM_GEN = 51; // Limit number of entries generated.

    for (unsigned i = 0; i < LIM_GEN; i++)
    {
        if (cbuf.insts_remaining() < sz) break;

        stringStream strm1;
        strm1.print("Comment No. %d", i);
        as.block_comment(strm1.as_string());
        as.nop();

        stringStream strm2;
        strm2.print("Fixed address string No. %d", i);
        as.unimplemented(strm2.as_string());
    }
    ASSERT_TRUE(cbuf.insts_remaining() >= 0);

    ASSERT_FALSE(cbuf.asm_remarks().is_empty());
    ASSERT_FALSE(cbuf.dbg_strings().is_empty());

    copy_and_compare(&cbuf);

    ASSERT_TRUE(blob->asm_remarks().is_empty());
    ASSERT_TRUE(blob->dbg_strings().is_empty());

    BufferBlob::free(blob);
}

#if defined(PPC) || defined(S390)
// Neither ppc nor s390 compiler use code strings
TEST_VM(codestrings, DISABLED_validate)
#else
TEST_VM(codestrings, validate)
#endif
{
    code_buffer_test();
    buffer_blob_test();
}

#endif // not ZERO
#endif // not PRODUCT
