arch, x86: Rework the debug faults and microops.

This makes the non-fatal microops advance the PC, and adds missing
functions. The *_once Faults now also can be run once per *something*.
They would previously be run once per Fault invoke function which is
common to all M5WarnOnceFaults. The warn_once microop will now warn
once per message.

Change-Id: I05974b93f3b2700077a411b243679c2ff0e8c2cb
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/20739
Reviewed-by: Gabe Black <gabeblack@google.com>
Reviewed-by: Brandon Potter <Brandon.Potter@amd.com>
Maintainer: Gabe Black <gabeblack@google.com>
Tested-by: kokoro <noreply+kokoro@google.com>
diff --git a/src/arch/generic/debugfaults.hh b/src/arch/generic/debugfaults.hh
index 5c5aa01..1c8d882 100644
--- a/src/arch/generic/debugfaults.hh
+++ b/src/arch/generic/debugfaults.hh
@@ -50,78 +50,125 @@
 
 class M5DebugFault : public FaultBase
 {
-  public:
-    enum DebugFunc
-    {
-        PanicFunc,
-        FatalFunc,
-        WarnFunc,
-        WarnOnceFunc
-    };
-
   protected:
-    std::string message;
-    DebugFunc func;
-
-  public:
-    M5DebugFault(DebugFunc _func, std::string _message) :
-        message(_message), func(_func)
-    {}
-
-    FaultName
-    name() const
+    std::string _message;
+    virtual void debugFunc() = 0;
+    void
+    advancePC(ThreadContext *tc, const StaticInstPtr &inst)
     {
-        switch (func) {
-          case PanicFunc:
-            return "panic fault";
-          case FatalFunc:
-            return "fatal fault";
-          case WarnFunc:
-            return "warn fault";
-          case WarnOnceFunc:
-            return "warn_once fault";
-          default:
-            panic("unrecognized debug function number\n");
+        if (inst) {
+            auto pc = tc->pcState();
+            inst->advancePC(pc);
+            tc->pcState(pc);
         }
     }
 
+  public:
+    M5DebugFault(std::string _m) : _message(_m) {}
+
+    template <class ...Args>
+    M5DebugFault(const std::string &format, const Args &...args) :
+        _message(csprintf(format, args...))
+    {}
+
+    std::string message() { return _message; }
+
     void
     invoke(ThreadContext *tc, const StaticInstPtr &inst =
-           StaticInst::nullStaticInstPtr)
+           StaticInst::nullStaticInstPtr) override
     {
-        switch (func) {
-          case PanicFunc:
-            panic(message);
-            break;
-          case FatalFunc:
-            fatal(message);
-            break;
-          case WarnFunc:
-            warn(message);
-            break;
-          case WarnOnceFunc:
-            warn_once(message);
-            break;
-          default:
-            panic("unrecognized debug function number\n");
-        }
+        debugFunc();
+        advancePC(tc, inst);
     }
 };
 
-template <int Func>
-class M5VarArgsFault : public M5DebugFault
+// The "Flavor" template parameter is to keep warn, hack or inform messages
+// with the same token from blocking each other.
+template <class Flavor>
+class M5DebugOnceFault : public M5DebugFault
 {
+  protected:
+    bool &once;
+
+    template <class F, class OnceToken>
+    static bool &
+    lookUpToken(const OnceToken &token)
+    {
+        static std::map<OnceToken, bool> tokenMap;
+        return tokenMap[token];
+    }
+
   public:
-    template<typename ...Args>
-    M5VarArgsFault(const std::string &format, const Args &...args) :
-        M5DebugFault((DebugFunc)Func, csprintf(format, args...))
+    template <class OnceToken, class ...Args>
+    M5DebugOnceFault(const OnceToken &token, const std::string &format,
+            const Args &...args) :
+        M5DebugFault(format, args...), once(lookUpToken<Flavor>(token))
     {}
+
+    void
+    invoke(ThreadContext *tc, const StaticInstPtr &inst =
+           StaticInst::nullStaticInstPtr) override
+    {
+        if (!once) {
+            once = true;
+            debugFunc();
+        }
+        advancePC(tc, inst);
+    }
 };
 
-typedef M5VarArgsFault<M5DebugFault::PanicFunc> M5PanicFault;
-typedef M5VarArgsFault<M5DebugFault::FatalFunc> M5FatalFault;
-typedef M5VarArgsFault<M5DebugFault::WarnFunc> M5WarnFault;
-typedef M5VarArgsFault<M5DebugFault::WarnOnceFunc> M5WarnOnceFault;
+class M5PanicFault : public M5DebugFault
+{
+  public:
+    using M5DebugFault::M5DebugFault;
+    void debugFunc() override { panic(message()); }
+    FaultName name() const override { return "panic fault"; }
+};
+
+class M5FatalFault : public M5DebugFault
+{
+  public:
+    using M5DebugFault::M5DebugFault;
+    void debugFunc() override { fatal(message()); }
+    FaultName name() const override { return "fatal fault"; }
+};
+
+template <class Base>
+class M5WarnFaultBase : public Base
+{
+  public:
+    using Base::Base;
+    void debugFunc() override { warn(this->message()); }
+    FaultName name() const override { return "warn fault"; }
+};
+
+using M5WarnFault = M5WarnFaultBase<M5DebugFault>;
+using M5WarnOnceFault = M5WarnFaultBase<M5DebugOnceFault<M5WarnFault>>;
+
+template <class Base>
+class M5HackFaultBase : public Base
+{
+  public:
+    using Base::Base;
+    void debugFunc() override { hack(this->message()); }
+    FaultName name() const override { return "hack fault"; }
+};
+
+using M5HackFault = M5HackFaultBase<M5DebugFault>;
+using M5HackOnceFault = M5HackFaultBase<M5DebugOnceFault<M5HackFault>>;
+
+template <class Base>
+class M5InformFaultBase : public Base
+{
+  public:
+    using Base::Base;
+    void debugFunc() override { inform(this->message()); }
+    FaultName name() const override { return "inform fault"; }
+};
+
+using M5InformFault = M5InformFaultBase<M5DebugFault>;
+using M5InformOnceFault =
+    M5InformFaultBase<M5DebugOnceFault<M5InformFault>>;
 
 } // namespace GenericISA
 
diff --git a/src/arch/x86/insts/badmicroop.cc b/src/arch/x86/insts/badmicroop.cc
index 0924873..5d9c381 100644
--- a/src/arch/x86/insts/badmicroop.cc
+++ b/src/arch/x86/insts/badmicroop.cc
@@ -39,6 +39,7 @@
 
 #include "arch/x86/insts/badmicroop.hh"
 
+#include "arch/generic/debugfaults.hh"
 #include "arch/x86/generated/decoder.hh"
 #include "arch/x86/isa_traits.hh"
 
@@ -56,8 +57,8 @@
 // try to delete the static memory when it was destructed.
 
 const StaticInstPtr badMicroop =
-    new X86ISAInst::MicroPanic(dummyMachInst, "BAD",
+    new X86ISAInst::MicroDebug(dummyMachInst, "panic", "BAD",
         StaticInst::IsMicroop | StaticInst::IsLastMicroop,
-        "Invalid microop!", 0);
+        new GenericISA::M5PanicFault("Invalid microop!"));
 
 } // namespace X86ISA
diff --git a/src/arch/x86/isa/microops/debug.isa b/src/arch/x86/isa/microops/debug.isa
index 6852f68..da1f9fc 100644
--- a/src/arch/x86/isa/microops/debug.isa
+++ b/src/arch/x86/isa/microops/debug.isa
@@ -42,21 +42,21 @@
 //////////////////////////////////////////////////////////////////////////
 
 output header {{
-    class MicroDebugBase : public X86ISA::X86MicroopBase
+    class MicroDebug : public X86ISA::X86MicroopBase
     {
       protected:
-        typedef GenericISA::M5DebugFault::DebugFunc DebugFunc;
-        DebugFunc func;
-        std::string message;
-        uint8_t cc;
+        std::shared_ptr<GenericISA::M5DebugFault> fault;
 
       public:
-        MicroDebugBase(ExtMachInst machInst, const char * mnem,
-                const char * instMnem, uint64_t setFlags,
-                DebugFunc _func, std::string _message, uint8_t _cc) :
-            X86MicroopBase(machInst, mnem, instMnem, setFlags, No_OpClass),
-                    func(_func), message(_message), cc(_cc)
-        {}
+        MicroDebug(ExtMachInst _machInst, const char *mnem,
+                const char *instMnem, uint64_t setFlags,
+                GenericISA::M5DebugFault *_fault);
+
+        Fault
+        execute(ExecContext *xc, Trace::InstRecord *traceData) const
+        {
+            return fault;
+        }
 
         std::string
         generateDisassembly(Addr pc, const SymbolTable *symtab) const
@@ -64,25 +64,37 @@
             std::stringstream response;
 
             printMnemonic(response, instMnem, mnemonic);
-            response << "\"" << message << "\"";
+            response << "\"" << fault->message() << "\"";
 
             return response.str();
         }
     };
-}};
 
-def template MicroDebugDeclare {{
-    class %(class_name)s : public %(base_class)s
+    class MicroDebugFlags : public MicroDebug
     {
+      protected:
+        uint8_t cc;
+
       public:
-        %(class_name)s(ExtMachInst _machInst, const char * instMnem,
-                uint64_t setFlags, std::string _message, uint8_t _cc);
+        MicroDebugFlags(ExtMachInst _machInst, const char *mnem,
+                const char *instMnem, uint64_t setFlags,
+                GenericISA::M5DebugFault *_fault, uint8_t _cc);
 
         Fault execute(ExecContext *, Trace::InstRecord *) const;
     };
 }};
 
-def template MicroDebugExecute {{
+output decoder {{
+    MicroDebug::MicroDebug(ExtMachInst _machInst, const char *mnem,
+            const char *instMnem, uint64_t setFlags,
+            GenericISA::M5DebugFault *_fault) :
+        X86ISA::X86MicroopBase(_machInst, mnem, instMnem,
+                               setFlags, No_OpClass),
+        fault(_fault)
+    {}
+}};
+
+def template MicroDebugFlagsExecute {{
         Fault
         %(class_name)s::execute(ExecContext *xc,
                 Trace::InstRecord *traceData) const
@@ -90,82 +102,95 @@
             %(op_decl)s
             %(op_rd)s
             if (%(cond_test)s) {
-                return std::make_shared<GenericISA::M5DebugFault>(func,
-                                                                  message);
+                return %(base_class)s::execute(xc, traceData);
             } else {
                 return NoFault;
             }
         }
 }};
 
-def template MicroDebugConstructor {{
+def template MicroDebugFlagsConstructor {{
     %(class_name)s::%(class_name)s(
-            ExtMachInst machInst, const char * instMnem, uint64_t setFlags,
-            std::string _message, uint8_t _cc) :
-        %(base_class)s(machInst, "%(func)s", instMnem,
-                setFlags, %(func_num)s, _message, _cc)
+            ExtMachInst machInst, const char *mnem,
+            const char *instMnem, uint64_t setFlags,
+            GenericISA::M5DebugFault *_fault, uint8_t _cc) :
+        %(base_class)s(machInst, mnem, instMnem, setFlags, _fault),
+        cc(_cc)
     {
         %(constructor)s;
     }
 }};
 
 let {{
+    iop = InstObjParams("", "MicroDebugFlags", "MicroDebug",
+            {"code": "",
+             "cond_test": "checkCondition(ccFlagBits | cfofBits | \
+                                              dfBit | ecfBit | ezfBit, cc)"})
+    exec_output = MicroDebugFlagsExecute.subst(iop)
+    decoder_output = MicroDebugFlagsConstructor.subst(iop)
+}};
+
+let {{
     class MicroDebug(X86Microop):
-        def __init__(self, message, flags=None):
+        def __init__(self, name, fault, message, once, flags):
+            self.name = name
+            self.fault = fault
             self.message = message
-            if flags:
-                if not isinstance(flags, (list, tuple)):
-                    raise Exception, "flags must be a list or tuple of flags"
-                self.cond = " | ".join(flags)
-                self.className += "Flags"
-            else:
-                self.cond = "0"
+            self.once = once
+            self.flags = flags
+            if flags and not isinstance(flags, (list, tuple)):
+                raise Exception, "flags must be a list or tuple of flags"
+
+            self.className = "MicroDebugFlags" if flags else "MicroDebug"
 
         def getAllocator(self, microFlags):
-            allocator = '''new %(class_name)s(machInst, macrocodeBlock,
-                    %(flags)s, "%(message)s", %(cc)s)''' % {
-                "class_name" : self.className,
-                "flags" : self.microFlagsText(microFlags),
-                "message" : self.message,
-                "cc" : self.cond}
-            return allocator
+            if self.once:
+                fault_allocator_template = \
+                    "new %(fault_type)s(%(token)s, %(message)s)"
+            else:
+                fault_allocator_template = \
+                    "new %(fault_type)s(%(message)s)"
+            fault_allocator = fault_allocator_template % {
+                "fault_type": self.fault,
+                "token": "std::string(\"%s\")" % self.message,
+                "message": "\"%s\"" % self.message
+            }
 
-    exec_output = ""
-    header_output = ""
-    decoder_output = ""
+            args = ["machInst", "\"%s\"" % self.name, "macrocodeBlock",
+                self.microFlagsText(microFlags), fault_allocator]
 
-    def buildDebugMicro(func, func_num):
-        global exec_output, header_output, decoder_output
+            if self.flags:
+                args.append(" | ".join(self.flags))
 
-        iop = InstObjParams(func, "Micro%sFlags" % func.capitalize(),
-                "MicroDebugBase",
-                {"code": "",
-                 "func": func,
-                 "func_num": "GenericISA::M5DebugFault::%s" % func_num,
-                 "cond_test": "checkCondition(ccFlagBits | cfofBits | \
-                                              dfBit | ecfBit | ezfBit, cc)"})
-        exec_output += MicroDebugExecute.subst(iop)
-        header_output += MicroDebugDeclare.subst(iop)
-        decoder_output += MicroDebugConstructor.subst(iop)
+            return "new " + self.className + "(" + ", ".join(args) + ")"
 
-        iop = InstObjParams(func, "Micro%s" % func.capitalize(),
-                "MicroDebugBase",
-                {"code": "",
-                 "func": func,
-                 "func_num": "GenericISA::M5DebugFault::%s" % func_num,
-                 "cond_test": "true"})
-        exec_output += MicroDebugExecute.subst(iop)
-        header_output += MicroDebugDeclare.subst(iop)
-        decoder_output += MicroDebugConstructor.subst(iop)
+    def buildDebugMicro(name, with_once=False):
+        global microopClasses
+
+        fault_class = "GenericISA::M5" + name.capitalize() + "Fault"
 
         class MicroDebugChild(MicroDebug):
-            className = "Micro%s" % func.capitalize()
+            def __init__(self, message, flags=None):
+                super(MicroDebugChild, self).__init__(
+                        name, fault_class, message, False, flags)
 
-        global microopClasses
-        microopClasses[func] = MicroDebugChild
+        microopClasses[name] = MicroDebugChild
 
-    buildDebugMicro("panic", "PanicFunc")
-    buildDebugMicro("fatal", "FatalFunc")
-    buildDebugMicro("warn", "WarnFunc")
-    buildDebugMicro("warn_once", "WarnOnceFunc")
+        if with_once:
+            fault_once_class = \
+                "GenericISA::M5" + name.capitalize() + "OnceFault"
+            name_once = name + "_once"
+
+            class MicroDebugOnceChild(MicroDebug):
+                def __init__(self, message, flags=None):
+                    super(MicroDebugOnceChild, self).__init__(
+                            name_once, fault_once_class, message, True, flags)
+
+            microopClasses[name_once] = MicroDebugOnceChild
+
+    buildDebugMicro("panic")
+    buildDebugMicro("fatal")
+    buildDebugMicro("hack", True)
+    buildDebugMicro("inform", True)
+    buildDebugMicro("warn", True)
 }};