// vim: ft=cpp

#include <stdint.h>
#include <arch/memory.h>
#include <j6/cap_flags.h>
#include <j6/errors.h>
#include <j6/types.h>
#include <util/counted.h>

#include "profiler.h"
#include "syscalls/helpers.h"

/*[[[cog code generation
from definitions.context import Context

ctx = Context(definitions_path)
ctx.parse("syscalls.def")
syscalls = ctx.interfaces["syscalls"]

for obj in syscalls.exposes:
    cog.outl(f'#include "objects/{obj.cname}.h"')
]]]*/
//[[[end]]]


/*[[[cog code generation
cog.outl(f'constexpr size_t syscall_count = {len(syscalls.methods)};')
]]]*/
//[[[end]]]
DECLARE_PROFILE_CLASS(syscall_profiles, syscall_count);
namespace {
    enum class req { required, optional, zero_ok };

    template <typename T>
    __attribute__((always_inline))
    inline bool range_check(const T* param) {
        return reinterpret_cast<uintptr_t>(param) < arch::kernel_offset;
    }

    template <typename T>
    __attribute__((always_inline))
    inline bool check_refparam(const T* param, req required) {
        return range_check(param) && ((required == req::optional) || param);
    }

    template <typename T>
    __attribute__((always_inline))
    inline bool check_refparam(const T* param, size_t len, req required) {
        bool nullok = (required == req::optional) || (required == req::zero_ok && !len);
        return range_check(param) && (param || nullok);
    }

    template <typename T>
    __attribute__((always_inline))
    inline bool check_refparam(const T* param, size_t *len, req required) {
        if (!range_check(len)) return false;
        if (len) return check_refparam(param, *len, required);
        // if len is null but param is not, call that invalid.
        else return !param && required == req::optional;
    }

    // When called with more than 6 arguments, the 7th argument to the
    // verify function is the stack pointer holding the rest. "Pop" them
    // into variables to be passed normally with pop_from.
    template <typename T>
    inline T pop_from(uint64_t *&sp) {
        return *reinterpret_cast<T*>(sp++);
    }
}

DEFINE_PROFILE_CLASS(syscall_profiles);

namespace syscalls {

using util::buffer;

/*[[[cog code generation
cbool = {True: "true", False: "false"}

def get_caps(opts, type):
    caps = opts.get("cap", list())
    return [f"j6_cap_{type.name}_{c}" for c in caps]

for id, scope, method in syscalls.methods:
    if scope:
        name = f"{scope.name}_{method.name}"
    else:
        name = f"{method.name}"

    args = []        # The function call args to call the impl
    argdefs = []     # The user-facing syscall args signature
    cxxargdefs = []  # The kernel syscall impl args signature
    refparams = []   # The list of params which are pointers

    handles = []
    objparams = []

    if method.constructor:
        argdefs.append(("j6_handle_t *", "self"))
        cxxargdefs.append("j6_handle_t *self")
        args.append("self")
        refparams.append(("self", "required"))

    elif not method.static:
        argdefs.append(("j6_handle_t", "self"))

        if "handle" in method.options:
            args.append("self")
        else:
            args.append("self_obj")
            cxxargdefs.append(f"obj::{scope.cname} *self")
            objparams.append((f"obj::{scope.cname} *", "self"))

        handles.append((
            scope.cname,
            "self",
            get_caps(method.options, scope),
            False))

    for param in method.params:
        needs_obj = param.type.needs_object(param.options)
        needs_handle = ("handle" in param.options) or needs_obj

        for type, suffix in param.type.c_names(param.options):
            arg = f"{param.name}{suffix}"
            argdefs.append((type, arg))

        for type, suffix in param.type.cxx_names(param.options):
            arg = f"{param.name}{suffix}"

            if needs_handle:
                handles.append((
                    param.type.object.cname,
                    arg,
                    get_caps(param.options, param.type.object),
                    param.optional != "required"))

                if needs_obj:
                    objparams.append((type, arg))
                    args.append(f"{arg}_obj")
                    cxxargdefs.append(f"{type} {arg}")
                    break

            cxxargdefs.append(f"{type} {arg}")
            args.append(arg)

        if not needs_handle and param.caps:
            handles.append((
                f"obj::{param.type.object.cname}",
                arg,
                get_caps(param.options, param.type.object,
                param.optional != "required")))

        if param.refparam:
            subs = param.type.c_names(param.options)
            checkargs = map(lambda x: f"{param.name}{x[1]}", subs)

            refparams.append((", ".join(checkargs), param.optional))

    first_args = ", ".join(map(lambda x: f"{x[0]} {x[1]}", argdefs[:6]))
    extra_argdefs = argdefs[6:]

    if extra_argdefs:
        first_args += ", uint64_t *sp"

    attr = ""
    if "noreturn" in method.options:
        attr = "[[noreturn]] "

    cog.outl(f"""{attr}j6_status_t {name} ({", ".join(cxxargdefs)});""")
    cog.outl(f"""{attr}j6_status_t _syscall_verify_{name} ({first_args}) {{""")

    for type, arg in extra_argdefs:
        cog.outl(f"    {type} {arg} = pop_from<{type}>(sp);")

    if extra_argdefs:
        cog.outl()

    for checkargs, optional in refparams:
        cog.outl(f"    if (!check_refparam({checkargs}, req::{optional}))")
        cog.outl( "        return j6_err_invalid_arg;")
        cog.outl()

    if handles:
        cog.outl(f"    j6_status_t res;")

    for type, arg, caps, optional in handles:
        capsval = "0"
        if caps:
            capsval = " | ".join(caps)

        optstr = (optional and "true") or "false"

        cog.outl(f"    obj::{type} *{arg}_obj = nullptr;")
        cog.outl(f"    res = get_handle<typename obj::{type}>({arg}, {capsval}, {arg}_obj, {optstr});")
        cog.outl(f"    if (res != j6_status_ok) return res;")
        cog.outl()

    if "noreturn" in method.options:
        if handles:
            cog.outl(f"""   static_assert(false, "Cannot define a `noreturn` syscall that holds handles.");""")
        cog.outl(f"""    {name}({", ".join(args)});""")

    else:
        cog.outl("    j6_status_t _retval;")
        cog.outl("    {")
        cog.outl(f"""        profiler<syscall_profiles, {id}> profile {{"{name}"}};""")
        cog.outl(f"""        _retval = {name}({", ".join(args)});""")
        cog.outl("    }\n")

        for type, arg, caps, optional in handles:
            cog.outl(f"    release_handle({arg});")

        cog.outl(f"""    return _retval;""")

    cog.outl("}")
    cog.outl("\n")
]]]*/
//[[[end]]]

} // namespace syscalls
