python: Add support for scoped enums

At the moment gem5 has support for enum params that either generate a
unscoped within the Enums namespace or a struct encapsulated enum. The
Enums namespace is getting quite big and some params have the same
names which results in collisions. This change adds support for the
scoped enums.

Change-Id: I930e1cc3b814081627b653939e75d6c43956a334
Signed-off-by: Nikos Nikoleris <nikos.nikoleris@arm.com>
Reviewed-by: Andreas Sandberg <andreas.sandberg@arm.com>
Reviewed-on: https://gem5-review.googlesource.com/c/15395
Reviewed-by: Jason Lowe-Power <jason@lowepower.com>
Maintainer: Jason Lowe-Power <jason@lowepower.com>
diff --git a/src/python/m5/params.py b/src/python/m5/params.py
index 1b03a66..74bd40b 100644
--- a/src/python/m5/params.py
+++ b/src/python/m5/params.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2012-2014, 2017 ARM Limited
+# Copyright (c) 2012-2014, 2017, 2018 ARM Limited
 # All rights reserved.
 #
 # The license below extends only to copyright in the software and shall
@@ -1243,7 +1243,10 @@
             raise TypeError, "Enum-derived class must define "\
                   "attribute 'map' or 'vals'"
 
-        cls.cxx_type = 'Enums::%s' % name
+        if cls.is_class:
+            cls.cxx_type = '%s' % name
+        else:
+            cls.cxx_type = 'Enums::%s' % name
 
         super(MetaEnum, cls).__init__(name, bases, init_dict)
 
@@ -1260,22 +1263,36 @@
 #ifndef $idem_macro
 #define $idem_macro
 
+''')
+        if cls.is_class:
+            code('''\
+enum class $name {
+''')
+        else:
+            code('''\
 $wrapper $wrapper_name {
     enum $name {
 ''')
-        code.indent(2)
+            code.indent(1)
+        code.indent(1)
         for val in cls.vals:
             code('$val = ${{cls.map[val]}},')
         code('Num_$name = ${{len(cls.vals)}}')
-        code.dedent(2)
-        code('    };')
+        code.dedent(1)
+        code('};')
 
-        if cls.wrapper_is_struct:
-            code('    static const char *${name}Strings[Num_${name}];')
-            code('};')
+        if cls.is_class:
+            code('''\
+extern const char *${name}Strings[static_cast<int>(${name}::Num_${name})];
+''')
+        elif cls.wrapper_is_struct:
+            code('static const char *${name}Strings[Num_${name}];')
         else:
             code('extern const char *${name}Strings[Num_${name}];')
-            code('}')
+
+        if not cls.is_class:
+            code.dedent(1)
+            code('};')
 
         code()
         code('#endif // $idem_macro')
@@ -1290,9 +1307,14 @@
             code('const char *${wrapper_name}::${name}Strings'
                 '[Num_${name}] =')
         else:
-            code('namespace Enums {')
-            code.indent(1)
-            code(' const char *${name}Strings[Num_${name}] =')
+            if cls.is_class:
+                code('''\
+const char *${name}Strings[static_cast<int>(${name}::Num_${name})] =
+''')
+            else:
+                code('namespace Enums {')
+                code.indent(1)
+                code('const char *${name}Strings[Num_${name}] =')
 
         code('{')
         code.indent(1)
@@ -1301,14 +1323,15 @@
         code.dedent(1)
         code('};')
 
-        if not cls.wrapper_is_struct:
-            code('} // namespace $wrapper_name')
+        if not cls.wrapper_is_struct and not cls.is_class:
             code.dedent(1)
+            code('} // namespace $wrapper_name')
+
 
     def pybind_def(cls, code):
         name = cls.__name__
-        wrapper_name = cls.wrapper_name
         enum_name = cls.__name__ if cls.enum_name is None else cls.enum_name
+        wrapper_name = enum_name if cls.is_class else cls.wrapper_name
 
         code('''#include "pybind11/pybind11.h"
 #include "pybind11/stl.h"
@@ -1322,8 +1345,11 @@
 {
     py::module m = m_internal.def_submodule("enum_${name}");
 
-    py::enum_<${wrapper_name}::${enum_name}>(m, "enum_${name}")
 ''')
+        if cls.is_class:
+            code('py::enum_<${enum_name}>(m, "enum_${name}")')
+        else:
+            code('py::enum_<${wrapper_name}::${enum_name}>(m, "enum_${name}")')
 
         code.indent()
         code.indent()
@@ -1352,6 +1378,8 @@
     # If true, the enum is wrapped in a struct rather than a namespace
     wrapper_is_struct = False
 
+    is_class = False
+
     # If not None, use this as the enum name rather than this class name
     enum_name = None
 
@@ -1390,6 +1418,24 @@
     def __str__(self):
         return self.value
 
+# This param will generate a scoped c++ enum and its python bindings.
+class ScopedEnum(Enum):
+    __metaclass__ = MetaEnum
+    vals = []
+    cmd_line_settable = True
+
+    # The name of the wrapping namespace or struct
+    wrapper_name = None
+
+    # If true, the enum is wrapped in a struct rather than a namespace
+    wrapper_is_struct = False
+
+    # If true, the generated enum is a scoped enum
+    is_class = True
+
+    # If not None, use this as the enum name rather than this class name
+    enum_name = None
+
 # how big does a rounding error need to be before we warn about it?
 frequency_tolerance = 0.001  # 0.1%
 
@@ -2041,7 +2087,7 @@
     allParams = baseParams.copy()
 
 __all__ = ['Param', 'VectorParam',
-           'Enum', 'Bool', 'String', 'Float',
+           'Enum', 'ScopedEnum', 'Bool', 'String', 'Float',
            'Int', 'Unsigned', 'Int8', 'UInt8', 'Int16', 'UInt16',
            'Int32', 'UInt32', 'Int64', 'UInt64',
            'Counter', 'Addr', 'Tick', 'Percent',