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

#include "beam_asm.hpp"

extern "C"
{
#include "erl_record.h"
}

void BeamModuleAssembler::emit_is_any_native_record(const ArgLabel &Fail,
                                                    const ArgRegister &Src) {
    mov_arg(ARG3, Src);

    emit_is_boxed(resolve_beam_label(Fail), Src, ARG3);

    preserve_cache(
            [&]() {
                x86::Gp boxed_ptr = emit_ptr_val(RET, ARG3);
                a.mov(RETd, emit_boxed_val(boxed_ptr, 0, sizeof(Uint32)));
                a.and_(RETb, imm(_TAG_HEADER_MASK));
                a.cmp(RETb, imm(_TAG_HEADER_RECORD));
                a.jne(resolve_beam_label(Fail));
            },
            RET);
}

void BeamModuleAssembler::emit_is_native_record(const ArgLabel &Fail,
                                                const ArgRegister &Src,
                                                const ArgAtom &Module,
                                                const ArgAtom &Name) {
    x86::Gp boxed_ptr;

    mov_arg(ARG3, Src);
    boxed_ptr = emit_ptr_val(RET, ARG3);

    mov_preserve_cache(
            RET,
            emit_boxed_val(boxed_ptr,
                           offsetof(ErtsRecordInstance, record_definition),
                           sizeof(Uint64)));
    boxed_ptr = emit_ptr_val(RET, RET);
    cmp_arg(emit_boxed_val(boxed_ptr, offsetof(ErtsRecordDefinition, module)),
            Module);
    preserve_cache([&]() {
        a.jne(resolve_beam_label(Fail));
    });
    cmp_arg(emit_boxed_val(boxed_ptr, offsetof(ErtsRecordDefinition, name)),
            Name);
    preserve_cache([&]() {
        a.jne(resolve_beam_label(Fail));
    });
}

void BeamModuleAssembler::emit_is_record_accessible(const ArgLabel &Fail,
                                                    const ArgRegister &Src,
                                                    const ArgAtom &Scope) {
    x86::Gp boxed_ptr;
    mov_arg(ARG3, Src);

    boxed_ptr = emit_ptr_val(RET, ARG3);

    mov_preserve_cache(
            RET,
            emit_boxed_val(boxed_ptr,
                           offsetof(ErtsRecordInstance, record_definition)));
    boxed_ptr = emit_ptr_val(RET, RET);
    cmp_arg(emit_boxed_val(boxed_ptr,
                           offsetof(ErtsRecordDefinition, is_exported)),
            ArgAtom(am_true));
    if (Scope.get() == am_external) {
        comment("external operation");
        preserve_cache([&]() {
            a.jne(resolve_beam_label(Fail));
        });
    } else {
        Label next = a.new_label();

        comment("auto_local operation");
        preserve_cache([&]() {
            a.short_().je(next);
        });

        cmp_arg(emit_boxed_val(boxed_ptr,
                               offsetof(ErtsRecordDefinition, module)),
                ArgAtom(mod));
        preserve_cache([&]() {
            a.jne(resolve_beam_label(Fail));
        });

        a.bind(next);
    }
}

void BeamModuleAssembler::emit_i_get_record_elements(
        const ArgLabel &Fail,
        const ArgRegister &Src,
        const ArgWord &Size,
        const Span<const ArgVal> &args) {
    mov_arg(ARG3, Src);
    a.mov(ARG1, c_p);
    load_x_reg_array(ARG2);
    mov_imm(ARG4, args.size());
    embed_vararg_rodata(args, ARG5, 0);

    emit_enter_runtime<Update::eStack>();
    runtime_call<bool (*)(Process *, Eterm *, Eterm, Uint, const Eterm *),
                 erl_get_record_elements>();
    emit_leave_runtime<Update::eStack>();

    a.test(RETb, RETb);
    a.je(resolve_beam_label(Fail));
}

void BeamModuleAssembler::emit_i_create_local_native_record(
        const ArgLiteral &Def,
        const ArgRegister &Dst,
        const ArgWord &Live,
        const ArgWord &size,
        const Span<const ArgVal> &args) {
    Label next = a.new_label();

    a.mov(ARG1, c_p);
    load_x_reg_array(ARG2);
    mov_arg(ARG3, Def);
    mov_arg(ARG4, Live);
    mov_imm(ARG5, args.size());
    embed_vararg_rodata(args, ARG6, 0);

    emit_enter_runtime<Update::eHeapAlloc | Update::eReductions>();

    runtime_call<
            Eterm (*)(Process *, Eterm *, Eterm, Uint, Uint, const Eterm *),
            erl_create_local_native_record>();

    emit_leave_runtime<Update::eHeapAlloc | Update::eReductions>();

    emit_test_the_non_value(RET);
    a.short_().jne(next);

    emit_raise_exception();

    a.bind(next);
    mov_arg(Dst, RET);
}

void BeamModuleAssembler::emit_i_create_native_record(
        const ArgConstant &Id,
        const ArgRegister &Dst,
        const ArgWord &Live,
        const ArgWord &size,
        const Span<const ArgVal> &args) {
    Label next = a.new_label();

    a.mov(ARG1, c_p);
    load_x_reg_array(ARG2);
    mov_arg(ARG3, Id);
    mov_arg(ARG4, Live);
    mov_imm(ARG5, args.size());
    embed_vararg_rodata(args, ARG6, 0);

    emit_enter_runtime<Update::eHeapAlloc | Update::eReductions>();

    runtime_call<
            Eterm (*)(Process *, Eterm *, Eterm, Uint, Uint, const Eterm *),
            erl_create_native_record>();

    emit_leave_runtime<Update::eHeapAlloc | Update::eReductions>();

    emit_test_the_non_value(RET);
    a.short_().jne(next);

    emit_raise_exception();

    a.bind(next);
    mov_arg(Dst, RET);
}

void BeamModuleAssembler::emit_i_update_native_record(
        const ArgSource &Src,
        const ArgRegister &Dst,
        const ArgWord &Live,
        const ArgWord &size,
        const Span<const ArgVal> &args) {
    Label next = a.new_label();

    mov_arg(ARG3, Src);
    a.mov(ARG1, c_p);
    load_x_reg_array(ARG2);
    mov_arg(ARG4, Live);
    mov_imm(ARG5, args.size());
    embed_vararg_rodata(args, ARG6, 0);

    emit_enter_runtime<Update::eHeapAlloc | Update::eReductions>();

    runtime_call<
            Eterm (*)(Process *, Eterm *, Eterm, Uint, Uint, const Eterm *args),
            erl_update_native_record>();

    emit_leave_runtime<Update::eHeapAlloc | Update::eReductions>();

    emit_test_the_non_value(RET);
    a.short_().jne(next);

    emit_raise_exception();

    a.bind(next);
    mov_arg(Dst, RET);
}

void BeamModuleAssembler::emit_get_record_field(const ArgLabel &Fail,
                                                const ArgRegister &Src,
                                                const ArgConstant &Id,
                                                const ArgAtom &Name,
                                                const ArgRegister &Dst) {
    a.mov(ARG1, c_p);
    mov_arg(ARG2, Src);
    mov_arg(ARG3, Id);
    mov_arg(ARG4, Name);

    emit_enter_runtime<Update::eHeapAlloc>();
    if (Id.isImmed()) {
        comment("local record");
        runtime_call<Eterm (*)(Process *, Eterm, Eterm, Eterm),
                     erl_get_local_record_field>();
    } else {
        comment("external record");
        runtime_call<Eterm (*)(Process *, Eterm, Eterm, Eterm),
                     erl_get_record_field>();
    }
    emit_leave_runtime<Update::eHeapAlloc>();

    if (Fail.get() != 0) {
        emit_test_the_non_value(RET);
        a.je(resolve_beam_label(Fail));
    } else {
        Label next = a.new_label();

        emit_test_the_non_value(RET);
        a.short_().jne(next);

        emit_raise_exception();

        a.bind(next);
    }

    mov_arg(Dst, RET);
}
