python: Add utility function to override config parameters

Add a utility method, SimObject.apply_config that can be used to
implement SimObject param overrides from the command line. This
function provides safe and convenient semantics for CLI assignment:

* The override expression is evaluated in a restricted environment. The
  only global variables are the child objects and params from the root
  object.

* Only params can be overridden. For example, calling methods or setting
  attributes on SimObjects isn't possible.

* Vectors use non-standard list semantics which enable something similar
  to glob expansion on the shell. For example, setting:

      root.system.cpu[0:2].numThreads = 2

  will override numThreads for cpu 0 and 1 and:

      root.system.cpus[0,2].numThreads = 2

  sets it for cpus 0 and 2.

The intention is that the helper method is called to override default
values before calling m5.instantiate.

Change-Id: I73f99da21d6d8ce1ff2ec8db2bb34338456f6799
Reviewed-on: https://gem5-review.googlesource.com/c/12984
Reviewed-by: Jason Lowe-Power <jason@lowepower.com>
Reviewed-by: Andreas Sandberg <andreas.sandberg@arm.com>
Maintainer: Jason Lowe-Power <jason@lowepower.com>
diff --git a/src/python/m5/SimObject.py b/src/python/m5/SimObject.py
index 0a5436f..44f26ea 100644
--- a/src/python/m5/SimObject.py
+++ b/src/python/m5/SimObject.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2017 ARM Limited
+# Copyright (c) 2017-2018 ARM Limited
 # All rights reserved.
 #
 # The license below extends only to copyright in the software and shall
@@ -934,6 +934,63 @@
     if not "created" in self.__dict__:
       self.__dict__[name] = value
 
+class SimObjectCliWrapperException(Exception):
+    def __init__(self, message):
+        super(Exception, self).__init__(message)
+
+class SimObjectCliWrapper(object):
+    """
+    Wrapper class to restrict operations that may be done
+    from the command line on SimObjects.
+
+    Only parameters may be set, and only children may be accessed.
+
+    Slicing allows for multiple simultaneous assignment of items in
+    one statement.
+    """
+
+    def __init__(self, sim_objects):
+        self.__dict__['_sim_objects'] = list(sim_objects)
+
+    def __getattr__(self, key):
+        return SimObjectCliWrapper(sim_object._children[key]
+                for sim_object in self._sim_objects)
+
+    def __setattr__(self, key, val):
+        for sim_object in self._sim_objects:
+            if key in sim_object._params:
+                if sim_object._params[key].isCmdLineSettable():
+                    setattr(sim_object, key, val)
+                else:
+                    raise SimObjectCliWrapperException(
+                            'tried to set or unsettable' \
+                            'object parameter: ' + key)
+            else:
+                raise SimObjectCliWrapperException(
+                            'tried to set or access non-existent' \
+                            'object parameter: ' + key)
+
+    def __getitem__(self, idx):
+        """
+        Extends the list() semantics to also allow tuples,
+        for example object[1, 3] selects items 1 and 3.
+        """
+        out = []
+        if isinstance(idx, tuple):
+            for t in idx:
+                out.extend(self[t]._sim_objects)
+        else:
+            if isinstance(idx, int):
+                _range = range(idx, idx + 1)
+            elif not isinstance(idx, slice):
+                raise SimObjectCliWrapperException( \
+                        'invalid index type: ' + repr(idx))
+            for sim_object in self._sim_objects:
+                if isinstance(idx, slice):
+                    _range = range(*idx.indices(len(sim_object)))
+                out.extend(sim_object[i] for i in _range)
+        return SimObjectCliWrapper(out)
+
 # The SimObject class is the root of the special hierarchy.  Most of
 # the code in this class deals with the configuration hierarchy itself
 # (parent/child node relationships).
@@ -1525,6 +1582,30 @@
                 for dt in item.generateDeviceTree(state):
                     yield dt
 
+    # On a separate method otherwise certain buggy Python versions
+    # would fail with: SyntaxError: unqualified exec is not allowed
+    # in function 'apply_config'
+    def _apply_config_get_dict(self):
+        return {
+            child_name: SimObjectCliWrapper(
+                iter(self._children[child_name]))
+            for child_name in self._children
+        }
+
+    def apply_config(self, params):
+        """
+        exec a list of Python code strings contained in params.
+
+        The only exposed globals to those strings are the child
+        SimObjects of this node.
+
+        This function is intended to allow users to modify SimObject
+        parameters from the command line with Python statements.
+        """
+        d = self._apply_config_get_dict()
+        for param in params:
+            exec(param, d)
+
 # Function to provide to C++ so it can look up instances based on paths
 def resolveSimObject(name):
     obj = instanceDict[name]