libopid: cross-check assembly libopid
authorDmitry Selyutin <ghostmansd@gmail.com>
Wed, 13 Sep 2023 20:50:38 +0000 (23:50 +0300)
committerDmitry Selyutin <ghostmansd@gmail.com>
Wed, 13 Sep 2023 20:50:38 +0000 (23:50 +0300)
src/libopid/opid-check.c
src/libopid/opid-check.py
src/libopid/opid.h

index 6640bde4bc3ef26e5aa4bdd869999f786bdc162d..75053d74ff25a994ab1b4c818ed51011f6384154 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <errno.h>
 #include <inttypes.h>
+#include <limits.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdint.h>
 #include <string.h>
 #include <unistd.h>
 
+#define SEP ":"
+
 #define error(FORMAT, ...) \
     do { \
         (void)fprintf(stderr, FORMAT, ##__VA_ARGS__); \
     } while (0)
 
-int
-main(void) {
+struct uctx {
+    int insn_id;
+    int op_id;
+    int arg;
+    int argc;
+    char **argv;
+};
+
+static enum opid_state
+operand_cb(void *vuctx, struct opid_operand *operand) {
+    char *arg;
+    char *iter;
+    long long value;
+    struct uctx *uctx = vuctx;
+
+    if (uctx->arg == uctx->argc) {
+        error("missing operand");
+        return OPID_ERROR_CALLBACK;
+    }
+
+    arg = uctx->argv[uctx->arg];
+    if (strcmp(SEP, arg) == 0) {
+        error("invalid operand \"%s\"", arg);
+        return OPID_ERROR_CALLBACK;
+    }
+
+    errno = 0;
+    value = strtoll(uctx->argv[uctx->arg], &iter, 10);
+    if ((iter == arg) || (*iter != '\0') ||
+            ((value == LONG_MIN || value == LONG_MAX) && errno == ERANGE)) {
+        error("invalid operand \"%s\"", arg);
+        return OPID_ERROR_CALLBACK;
+    }
+
+    operand->value = (uint64_t)(int64_t)value;
+
+    ++uctx->arg;
+    ++uctx->op_id;
+
+    return OPID_SUCCESS;
+}
+
+static int
+assemble(int argc, char **argv) {
+    uint64_t insn;
+    struct uctx uctx = {
+        .insn_id = 0,
+        .op_id = 0,
+        .arg = 0,
+        .argc = argc,
+        .argv = argv,
+    };
+
+    while (uctx.arg != uctx.argc) {
+        ssize_t size;
+        uint32_t insn32;
+        struct opid_ctx ctx;
+        enum opid_state state;
+        char const *insn_name = uctx.argv[uctx.arg++];
+
+        uctx.op_id = 0;
+        state = opid_assemble(&ctx, insn_name, &insn, &uctx, operand_cb);
+        if (state != OPID_SUCCESS) {
+            if (state == OPID_ERROR_LOOKUP)
+                error("bad instruction name");
+            error(" (insn_name=\"%s\", insn_id=%d, op_id=%d)\n",
+                insn_name, uctx.insn_id, uctx.op_id);
+            return EXIT_FAILURE;
+        }
+        if (uctx.arg != uctx.argc) {
+            if (strcmp(SEP, uctx.argv[uctx.arg]) != 0) {
+                error("invalid separator ");
+                error(" (insn_name=\"%s\", insn_id=%d, op_id=%d)\n",
+                    insn_name, uctx.insn_id, uctx.op_id);
+                return EXIT_FAILURE;
+            }
+            ++uctx.arg;
+        }
+        ++uctx.insn_id;
+
+        insn32 = (uint32_t)insn;
+        size = write(STDOUT_FILENO, &insn32, sizeof(insn32));
+        if (size != sizeof(insn32)) {
+            error("cannot store instruction: %s\n", strerror(errno));
+            return EXIT_FAILURE;
+        }
+    }
+
+    return EXIT_SUCCESS;
+}
+
+static int
+disassemble(void) {
     ssize_t size;
-    uint32_t insn;
-    struct opid_ctx ctx;
-    enum opid_state state;
-    struct opid_operand const *operand;
 
     do {
+        uint32_t insn;
+
         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)) {
+            struct opid_ctx ctx;
+            enum opid_state state;
+            struct opid_operand const *operand;
+
             state = opid_disassemble(&ctx, insn);
             if (state != OPID_SUCCESS) {
-                error("invalid instruction: %08" PRIx32 "\n", insn);
+                error("invalid instruction: %016" PRIx32 "\n", insn);
                 return EXIT_FAILURE;
             }
 
@@ -65,3 +161,12 @@ main(void) {
 
     return EXIT_SUCCESS;
 }
+
+int
+main(int argc, char *argv[]) {
+    if (argc > 1) {
+        return assemble(--argc, ++argv);
+    } else {
+        return disassemble();
+    }
+}
index 27a3cfb0a2f446b2e5315ccf96bccf9e44f3c3d1..94bbd4199036475322e1608350d2a5f1a5a9132f 100644 (file)
@@ -27,76 +27,99 @@ 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 disassemble(db, insns):
+    stdin = tempfile.TemporaryFile(mode="w+b")
+
+    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)
+
+    stdout = subprocess.check_output(["./opid-check"], stdin=stdin, encoding="UTF-8")
+    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])
+
+    stdin.seek(0)
+
+    return stdin
+
+
+def assemble(db, insns):
+    command = [
+        "./opid-check",
+    ]
+    for (name, arguments) in insns:
+        command.append(name)
+        command.extend(arguments)
+        command.append(":")
+    command.pop()
+
+    stdout = tempfile.TemporaryFile(mode="w+b")
+    subprocess.check_call(command, stdout=stdout)
+    stdout.seek(0)
+
+    return stdout
+
+
+def check(db, insns):
+    with disassemble(db=db, insns=insns) as origin:
+        with assemble(db=db, insns=insns) as target:
+            origin = origin.read()
+            target = target.read()
+            if origin != target:
+                print("actual  ", "".join(f"\\x{byte:02x}" for byte in target))
+                print("expected", "".join(f"\\x{byte:02x}" for byte in origin))
 
 
 def main():
     db = Database(find_wiki_dir())
-    opid_check(db=db, insns=(
+    check(db=db, insns=(
         ("addpcis", ("1", "-1")),
         ("addpcis", ("1", "0")),
         ("addpcis", ("1", "+1")),
index 341f47425b6142a6349443142ad093bfaeed5037..82c6cacb366f888b96cb9b46c62ad853baae9d11 100644 (file)
@@ -6,6 +6,7 @@
 enum opid_state {
     OPID_SUCCESS,
     OPID_ERROR_LOOKUP,
+    OPID_ERROR_CALLBACK,
     OPID_ERROR_OPERAND_0_LOOKUP,
     OPID_ERROR_OPERAND_1_LOOKUP,
     OPID_ERROR_OPERAND_2_LOOKUP,