libopid: provide tests
authorDmitry Selyutin <ghostmansd@gmail.com>
Sat, 9 Sep 2023 18:18:04 +0000 (21:18 +0300)
committerDmitry Selyutin <ghostmansd@gmail.com>
Wed, 13 Sep 2023 17:26:16 +0000 (20:26 +0300)
src/libopid/.gitignore
src/libopid/Makefile
src/libopid/opid-check.c [new file with mode: 0644]
src/libopid/opid-check.py [new file with mode: 0644]

index 162bd1dbdc26d4a0575d51188f88e2201ad7a886..5c09e3066fdf413fc6cb7e6f266ddefa67e7fcc4 100644 (file)
@@ -2,3 +2,4 @@
 *.a
 *.so
 *.o
+opid-dis
index 46bfbf192fbc398ae6c179fedfa05e41303c4555..fa56684643f9897e5fe5ed976de55e7b0a9dc2b4 100644 (file)
@@ -1,5 +1,6 @@
 CC?=gcc
 AR?=ar
+CPPFLAGS?=-I.
 CFLAGS?=-O3 -Werror -Wall -Wextra
 PYTHON?=python3
 
@@ -8,11 +9,14 @@ GENS:=\
        opid-dis-gen.c \
        opid-opc-gen.c \
 
-SRCS:=$(GENS)
+SRCS:=\
+       $(GENS) \
+       opid-check.c \
 
 OBJS:=\
        opid-dis-gen.o \
        opid-opc-gen.o \
+       opid-check.o \
 
 
 .SUFFIX: .so .a .o .c
@@ -28,12 +32,22 @@ build: libopid.so
 
 .PHONY: clean
 clean:
+       rm -f opid-check
        rm -f $(GENS)
        rm -f $(OBJS)
        rm -f libopid.so
        rm -f libopid.a
 
 
+.PHONY: check
+check: opid-check.py opid-check
+       python3 opid-check.py
+
+
+opid-check: opid-check.o libopid.a
+       $(CC) $(CPPFLAGS) $(CFLAGS) -fPIC $< -o $@ -L. -l:libopid.a
+
+
 libopid.so: libopid.a
        $(CC) -fPIC -shared -o $@ -L. -Wl,--whole-archive libopid.a -Wl,--no-whole-archive
 
@@ -47,4 +61,4 @@ libopid.a: $(OBJS)
 
 
 .c.o:
-       $(CC) $(CFLAGS) -fPIC -c $< -o $@
+       $(CC) $(CPPFLAGS) $(CFLAGS) -fPIC -c $< -o $@
diff --git a/src/libopid/opid-check.c b/src/libopid/opid-check.c
new file mode 100644 (file)
index 0000000..de04e12
--- /dev/null
@@ -0,0 +1,76 @@
+#include <opid.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define error(FORMAT, ...) \
+    do { \
+        (void)fprintf(stderr, FORMAT, ##__VA_ARGS__); \
+    } while (0)
+
+#define OPID_OPERAND_SIGNED    (UINT32_C(1) << UINT32_C(0))
+#define OPID_OPERAND_GPR       (UINT32_C(1) << UINT32_C(1))
+#define OPID_OPERAND_FPR       (UINT32_C(1) << UINT32_C(2))
+#define OPID_OPERAND_PAIR      (UINT32_C(1) << UINT32_C(3))
+#define OPID_OPERAND_CR3       (UINT32_C(1) << UINT32_C(4))
+#define OPID_OPERAND_CR5       (UINT32_C(1) << UINT32_C(5))
+#define OPID_OPERAND_NONZERO   (UINT32_C(1) << UINT32_C(6))
+#define OPID_OPERAND_ADDRESS   (UINT32_C(1) << UINT32_C(7))
+
+int
+main(void) {
+    ssize_t size;
+    uint32_t insn;
+    struct opid_ctx ctx;
+    enum opid_state state;
+    struct opid_operand const *operand;
+
+    do {
+        size = read(STDIN_FILENO, &insn, sizeof(insn));
+        if (size < 0) {
+            error("cannot fetch instruction: %s\n", strerror(errno));
+            return EXIT_FAILURE;
+        } else if (size == sizeof(uint32_t)) {
+            state = opid_disassemble(&ctx, insn);
+            if (state != OPID_SUCCESS) {
+                error("invalid instruction: %08" PRIx32 "\n", insn);
+                return EXIT_FAILURE;
+            }
+
+            printf("%s [", ctx.record->name);
+            opid_foreach_operand(&ctx, operand) {
+                printf("(%" PRIi32 " ",
+                    (int32_t)operand->value);
+                if (operand->flags & OPID_OPERAND_SIGNED)
+                    printf("s");
+                if (operand->flags & OPID_OPERAND_GPR)
+                    printf("g");
+                if (operand->flags & OPID_OPERAND_FPR)
+                    printf("f");
+                if (operand->flags & OPID_OPERAND_PAIR)
+                    printf("p");
+                if (operand->flags & OPID_OPERAND_CR3)
+                    printf("3");
+                if (operand->flags & OPID_OPERAND_CR5)
+                    printf("5");
+                if (operand->flags & OPID_OPERAND_NONZERO)
+                    printf("z");
+                if (operand->flags & OPID_OPERAND_ADDRESS)
+                    printf("a");
+                printf(")");
+            }
+            printf("]\n");
+        } else if (size != 0) {
+            error("truncated instruction: %zu\n", (size_t)size);
+            return EXIT_FAILURE;
+        }
+    } while (size != 0);
+
+    return EXIT_SUCCESS;
+}
diff --git a/src/libopid/opid-check.py b/src/libopid/opid-check.py
new file mode 100644 (file)
index 0000000..27a3cfb
--- /dev/null
@@ -0,0 +1,110 @@
+import os
+import re
+import subprocess
+import tempfile
+import sys
+
+from openpower.insndb.core import (
+    Database,
+    Style,
+    WordInstruction,
+    SignedOperand,
+    GPROperand,
+    FPROperand,
+    PairOperand,
+    CR3Operand,
+    CR5Operand,
+    NonZeroOperand,
+)
+from openpower.decoder.power_enums import find_wiki_dir
+
+
+DIS_INSN_PATTERN = r"([A-Za-z0-9_\.]+)"
+DIS_VALUE_PATTERN = r"-?\d+"
+DIS_FLAGS_PATTERN = r"[sgfp35za]*"
+DIS_OPERAND_PATTERN = rf"(?:\({DIS_VALUE_PATTERN}\s+{DIS_FLAGS_PATTERN}\))*"
+DIS_PATTERN = fr"^{DIS_INSN_PATTERN}\s+\[({DIS_OPERAND_PATTERN})\]$"
+DIS_REGEX = re.compile(DIS_PATTERN)
+
+
+def opid_check(db, insns):
+    with tempfile.TemporaryFile(mode="w+b") as stdin:
+        def remap_insn(insn):
+            (name, arguments) = insn
+            record = db[name]
+            insn = WordInstruction.assemble(record=db[name],
+                arguments=arguments)
+
+            def remap_operand(operand):
+                table = {
+                    SignedOperand: "s",
+                    GPROperand: "g",
+                    FPROperand: "f",
+                    PairOperand: "p",
+                    CR3Operand: "3",
+                    NonZeroOperand: "z",
+                    CR5Operand: "5",
+                }
+                flags = []
+                for (cls, flag) in table.items():
+                    if isinstance(operand, cls):
+                        flags.append(flag)
+                flags = "".join(flags)
+                value = next(operand.disassemble(insn, style=Style.LEGACY))
+                return (value, flags)
+
+            operands = tuple(map(remap_operand,
+                insn.dynamic_operands(record=record)))
+
+            return (insn, name, operands)
+
+        def assert_eq(index, actual, expected):
+            if actual != expected:
+                print(f"[{index}]", "actual  ", actual)
+                print(f"[{index}]", "expected", expected)
+                return False
+            return True
+
+        insns = tuple(map(remap_insn, insns))
+
+        for (insn, *_) in insns:
+            stdin.write(insn.bytes())
+        stdin.seek(0)
+
+        sp = subprocess.Popen("./opid-check",
+            stdin=stdin,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            encoding="UTF-8")
+        (stdout, stderr) = sp.communicate()
+        if stderr:
+            sys.stderr.write(map(str.strip, stderr.splitlines()))
+            raise ValueError(stderr)
+
+        for (index, line) in enumerate(map(str.strip, stdout.splitlines())):
+            match = DIS_REGEX.match(line)
+            if match is None:
+                raise ValueError(line)
+            (name, operands) = match.groups()
+            operands = operands.replace(")(", ":")
+            operands = operands.replace("(", "").replace(")", "")
+            operands = tuple(tuple(operand.split(" ")) for operand in operands.split(":"))
+            print(line)
+            assert_eq(index=index, actual=name, expected=insns[index][1])
+            assert_eq(index=index, actual=operands, expected=insns[index][2])
+
+
+def main():
+    db = Database(find_wiki_dir())
+    opid_check(db=db, insns=(
+        ("addpcis", ("1", "-1")),
+        ("addpcis", ("1", "0")),
+        ("addpcis", ("1", "+1")),
+        ("svshape2", ("8", "1", "31", "7", "1", "1")),
+        ("svshape", ("8", "1", "1", "14", "0")),
+    ))
+
+
+if __name__ == "__main__":
+    os.environ["SILENCELOG"] = "1"
+    main()