root/tools/perf/util/perf-regs-arch/perf_regs_riscv.c
// SPDX-License-Identifier: GPL-2.0

#include <errno.h>
#include <regex.h>
#include <string.h>
#include <linux/kernel.h>
#include <linux/zalloc.h>

#include "../debug.h"
#include "../perf_regs.h"
#include "../../arch/riscv/include/perf_regs.h"

/*
 * RISC-V SDT argument formats (GCC 'nor' constraint):
 *
 * Register:  REG        e.g. a0, t1, s0, sp
 * Memory:    NUM(REG)   e.g. 8(a0), -20(s0)
 * Constant:  NUM        e.g. 99  (not supported by uprobe, skip)
 *
 * Note: 'zero' (x0) is hardwired to 0 and not in pt_regs; skip it.
 *
 * Uprobe target format:
 *   Register: %REG       e.g. %a0
 *   Memory:   +NUM(%REG) or -NUM(%REG)
 */

/* RISC-V register ABI names: ra, sp, gp, tp, t0-t6, s0-s11, a0-a7 */
#define SDT_OP_REGEX1  "^(ra|sp|gp|tp|t[0-6]|s[0-9]|s1[01]|a[0-7])$"

/* RISC-V memory operand: [-]NUM(REG) */
#define SDT_OP_REGEX2  "^(\\-)?([0-9]+)\\((ra|sp|gp|tp|t[0-6]|s[0-9]|s1[01]|a[0-7])\\)$"

static regex_t sdt_op_regex1, sdt_op_regex2;

static int sdt_init_op_regex(void)
{
        static int initialized;
        int ret = 0;

        if (initialized)
                return 0;

        ret = regcomp(&sdt_op_regex1, SDT_OP_REGEX1, REG_EXTENDED);
        if (ret)
                goto error;

        ret = regcomp(&sdt_op_regex2, SDT_OP_REGEX2, REG_EXTENDED);
        if (ret)
                goto free_regex1;

        initialized = 1;
        return 0;

free_regex1:
        regfree(&sdt_op_regex1);
error:
        pr_debug4("Regex compilation error.\n");
        return -ret;
}

/*
 * Parse OP and convert it into uprobe format.
 * Possible variants of OP (RISC-V, GCC 'nor' constraint):
 *
 *   Format         Example       Uprobe
 *   ----------------------------------------
 *   REG            a0            %a0
 *   NUM(REG)       8(a0)         +8(%a0)
 *   -NUM(REG)      -20(s0)       -20(%s0)
 *   NUM            99            (skip, constant not supported)
 */
int __perf_sdt_arg_parse_op_riscv(char *old_op, char **new_op)
{
        int ret, new_len;
        regmatch_t rm[4];
        char prefix;

        /*
         * Constant argument: pure integer with no trailing '(' (e.g. "99", "-1").
         * uprobe does not support immediate values, so skip them.
         * Memory operands like "8(a0)" or "-20(s0)" contain '(' so are NOT
         * treated as constants here; they will be matched by REGEX2 below.
         */
        if (strchr(old_op, '(') == NULL &&
            ((*old_op >= '0' && *old_op <= '9') ||
             (*old_op == '-' && old_op[1] >= '0' && old_op[1] <= '9'))) {
                pr_debug4("Skipping unsupported SDT argument: %s\n", old_op);
                return SDT_ARG_SKIP;
        }

        ret = sdt_init_op_regex();
        if (ret < 0)
                return ret;

        if (!regexec(&sdt_op_regex1, old_op, 2, rm, 0)) {
                /* REG --> %REG */
                new_len = 2;    /* % NULL */
                new_len += (int)(rm[1].rm_eo - rm[1].rm_so);

                *new_op = zalloc(new_len);
                if (!*new_op)
                        return -ENOMEM;

                scnprintf(*new_op, new_len, "%%%.*s",
                        (int)(rm[1].rm_eo - rm[1].rm_so), old_op + rm[1].rm_so);
        } else if (!regexec(&sdt_op_regex2, old_op, 4, rm, 0)) {
                /*
                 * NUM(REG) or -NUM(REG) --> +NUM(%REG) or -NUM(%REG)
                 * rm[1]: optional '-'
                 * rm[2]: decimal offset
                 * rm[3]: register name
                 */
                prefix = (rm[1].rm_so == -1) ? '+' : '-';

                new_len = 5;    /* sign ( % ) NULL */
                new_len += (int)(rm[2].rm_eo - rm[2].rm_so);
                new_len += (int)(rm[3].rm_eo - rm[3].rm_so);

                *new_op = zalloc(new_len);
                if (!*new_op)
                        return -ENOMEM;

                scnprintf(*new_op, new_len, "%c%.*s(%%%.*s)", prefix,
                        (int)(rm[2].rm_eo - rm[2].rm_so), old_op + rm[2].rm_so,
                        (int)(rm[3].rm_eo - rm[3].rm_so), old_op + rm[3].rm_so);
        } else {
                pr_debug4("Skipping unsupported SDT argument: %s\n", old_op);
                return SDT_ARG_SKIP;
        }

        return SDT_ARG_VALID;
}

uint64_t __perf_reg_mask_riscv(bool intr __maybe_unused)
{
        return PERF_REGS_MASK;
}

const char *__perf_reg_name_riscv(int id)
{
        switch (id) {
        case PERF_REG_RISCV_PC:
                return "pc";
        case PERF_REG_RISCV_RA:
                return "ra";
        case PERF_REG_RISCV_SP:
                return "sp";
        case PERF_REG_RISCV_GP:
                return "gp";
        case PERF_REG_RISCV_TP:
                return "tp";
        case PERF_REG_RISCV_T0:
                return "t0";
        case PERF_REG_RISCV_T1:
                return "t1";
        case PERF_REG_RISCV_T2:
                return "t2";
        case PERF_REG_RISCV_S0:
                return "s0";
        case PERF_REG_RISCV_S1:
                return "s1";
        case PERF_REG_RISCV_A0:
                return "a0";
        case PERF_REG_RISCV_A1:
                return "a1";
        case PERF_REG_RISCV_A2:
                return "a2";
        case PERF_REG_RISCV_A3:
                return "a3";
        case PERF_REG_RISCV_A4:
                return "a4";
        case PERF_REG_RISCV_A5:
                return "a5";
        case PERF_REG_RISCV_A6:
                return "a6";
        case PERF_REG_RISCV_A7:
                return "a7";
        case PERF_REG_RISCV_S2:
                return "s2";
        case PERF_REG_RISCV_S3:
                return "s3";
        case PERF_REG_RISCV_S4:
                return "s4";
        case PERF_REG_RISCV_S5:
                return "s5";
        case PERF_REG_RISCV_S6:
                return "s6";
        case PERF_REG_RISCV_S7:
                return "s7";
        case PERF_REG_RISCV_S8:
                return "s8";
        case PERF_REG_RISCV_S9:
                return "s9";
        case PERF_REG_RISCV_S10:
                return "s10";
        case PERF_REG_RISCV_S11:
                return "s11";
        case PERF_REG_RISCV_T3:
                return "t3";
        case PERF_REG_RISCV_T4:
                return "t4";
        case PERF_REG_RISCV_T5:
                return "t5";
        case PERF_REG_RISCV_T6:
                return "t6";
        default:
                return NULL;
        }

        return NULL;
}

uint64_t __perf_reg_ip_riscv(void)
{
        return PERF_REG_RISCV_PC;
}

uint64_t __perf_reg_sp_riscv(void)
{
        return PERF_REG_RISCV_SP;
}