gem5 SimObject 创建过程
gem5 中 SimObject 的创建过程如果简单的来讲就是 src/python/m5/simulate.py
中的一句 m5.instantiate()
,这个方法完成了 SimObject 的创建并且进行了端口的初始化。原先想的是创建对象的过程很简单,肯定是在这个方法的执行过程中进行了 cpp 的代码的 new 操作,然后创建出的对象,实际不然,创建的过程远比想象的复杂。
创建过程
进入到 m5.instantiate()
中,可以看到:
# Create the C++ sim objects and connect ports
for obj in root.descendants():
obj.createCCObject()
for obj in root.descendants():
obj.connectPorts()
这个方法中的这部分代码用于创建 SimObject 并连接端口,再继续往下跟踪代码,进入到 obj.createCCObject()
中:
def createCCObject(self):
if self.abstract:
fatal(f"Cannot instantiate an abstract SimObject ({self.path()})")
self.getCCParams()
self.getCCObject() # force creation
这个函数的定义如上,可以看到先是进行了是否是抽象类的检查,如果不是抽象类才能继续往下创建。在通过抽象类的检查之后,先是进行 Params 文件(就是实现python 和 cpp 参数绑定文件)的创建,然后再创建具体的 SimObject。再通过 self.getCCObject()
向下跟踪:
def getCCObject(self):
if not self._ccObject:
# Make sure this object is in the configuration hierarchy
if not self._parent and not isRoot(self):
raise RuntimeError("Attempt to instantiate orphan node")
# Cycles in the configuration hierarchy are not supported. This
# will catch the resulting recursion and stop.
self._ccObject = -1
if not self.abstract:
params = self.getCCParams()
self._ccObject = params.create()
elif self._ccObject == -1:
raise RuntimeError(
f"{self.path()}: Cycle found in configuration hierarchy."
)
return self._ccObject
可以看到创建 SimObject 最为关键的一步是调用 self._ccObject = params.create()
,即通过定义的 Params 类中的 create 方法将 SimObject 创建出来,而不是简简单单的去 new。
通过翻阅各个 Params 头文件中的类,以 build/X86/params/BaseTimingSimpleCPU.hh
为例:
namespace gem5
{
struct BaseTimingSimpleCPUParams
: public BaseSimpleCPUParams
{
gem5::TimingSimpleCPU * create() const;
};
} // namespace gem5
自动生成的 Params 类中确实声明了 create
方法。但是问题又来了,这个方法的实现在哪里?很天然的想到方法的实现可能也放在与 Params 相关的自动生成的文件中,与 Params 相关的自动生成的文件还存在于 build/X86/python/_m5
下,这是这个目录下的各个文件实现了动态绑定。我们选取之前与 build/X86/params/BaseTimingSimpleCPU.hh
对应的 build/X86/python/_m5/param_BaseTimingSimpleCPU.cc
来进行分析:
namespace py = pybind11;
namespace gem5
{
static void
module_init(py::module_ &m_internal)
{
py::module_ m = m_internal.def_submodule("param_BaseTimingSimpleCPU");
py::class_<BaseTimingSimpleCPUParams, BaseSimpleCPUParams, std::unique_ptr<BaseTimingSimpleCPUParams, py::nodelete>>(m, "BaseTimingSimpleCPUParams")
.def(py::init<>())
.def("create", &BaseTimingSimpleCPUParams::create)
;
py::class_<gem5::TimingSimpleCPU, gem5::BaseSimpleCPU, std::unique_ptr<gem5::TimingSimpleCPU, py::nodelete>>(m, "gem5_COLONS_TimingSimpleCPU")
;
}
static EmbeddedPyBind embed_obj("BaseTimingSimpleCPU", module_init, "BaseSimpleCPU");
} // namespace gem5
这部分列举出的代码是实现动态绑定的。
namespace
{
class DummyBaseTimingSimpleCPUParamsClass
{
public:
gem5::TimingSimpleCPU *create() const;
};
template <class CxxClass, class Enable=void>
class DummyBaseTimingSimpleCPUShunt;
template <class CxxClass>
class DummyBaseTimingSimpleCPUShunt<CxxClass, std::enable_if_t<
std::is_constructible_v<CxxClass, const BaseTimingSimpleCPUParams &>>>
{
public:
using Params = BaseTimingSimpleCPUParams;
static gem5::TimingSimpleCPU *
create(const Params &p)
{
return new CxxClass(p);
}
};
template <class CxxClass>
class DummyBaseTimingSimpleCPUShunt<CxxClass, std::enable_if_t<
!std::is_constructible_v<CxxClass, const BaseTimingSimpleCPUParams &>>>
{
public:
using Params = DummyBaseTimingSimpleCPUParamsClass;
static gem5::TimingSimpleCPU *
create(const Params &p)
{
return nullptr;
}
};
} // anonymous namespace
[[maybe_unused]] gem5::TimingSimpleCPU *
DummyBaseTimingSimpleCPUShunt<gem5::TimingSimpleCPU>::Params::create() const
{
return DummyBaseTimingSimpleCPUShunt<gem5::TimingSimpleCPU>::create(*this);
}
首先定义了 DummyBaseTimingSimpleCPUParamsClass
类,这个类中也包含了 create
方法。随后声明了一个模板 DummyBaseTimingSimpleCPUShunt
,这个模板中有一个 class Enable
参数可以用来指定条件编译,即在 class Enable
为true或者false的情况下编译出不同的版本。
对于 Enable 的判断结果来自 std::enable_if_t<std::is_constructible_v<CxxClass, const BaseTimingSimpleCPUParams &>>
,简单的来说,就是如果 CxxClass
这个模板参数的构造函数中只接受 const BaseTimingSimpleCPUParams &
作为构造函数,那就返回 true, 否则返回false。对于返回为 true 的情况,模板类中的 create 方法返回一个 CxxClass 的对象;对于返回 false 的情况, create 方法返回 null。
最为关键的是最后一部分,正是最后一部分实现了自动生成的 BaseTimingSimpleCPUParams
类的 create 方法实现。可以看到最后一部分定义了 DummyBaseTimingSimpleCPUShunt<gem5::TimingSimpleCPU>::Params::create()
方法,在 Enable 返回 true 的时候,DummyBaseTimingSimpleCPUShunt<gem5::TimingSimpleCPU>::Params
刚好是 BaseTimingSimpleCPUParams
,也就是说在定义 BaseTimingSimpleCPU
这个 SimObject 的时候如果存在一个只接受 const BaseTimingSimpleCPUParams &
的方法,在创建对象的时候会调用这个方法。如果Enable 返回了 false,最后一部分定义的是 DummyBaseTimingSimpleCPUParamsClass
的 create 方法,实际上等于什么都没做,如果你在定义某个 SimObject 的时候使用了不同形式的构造方法导致了 Enable 返回了 false,那么在创建 SimObject 对象的时候应该不会被创建,而是应该会报出 create 方法未实现这样的错误。
这实际上需要你自己去实现这个 create 方法,这可能是 gem5 留的一个后门,可能也是灵活性的一种体现。举例来说,src/sim/process.hh
就是一个使Enable 返回 false 的 SimObject,但是其在 src/sim/process.cc
的最后,有如下代码:
Process *
ProcessParams::create() const
{
// If not specified, set the executable parameter equal to the
// simulated system's zeroth command line parameter
const std::string &exec = (executable == "") ? cmd[0] : executable;
auto *obj_file = loader::createObjectFile(exec);
fatal_if(!obj_file, "Cannot load object file %s.", exec);
Process *process = Process::tryLoaders(*this, obj_file);
fatal_if(!process, "Unknown error creating process object.");
return process;
}
等于自己补充实现了 create 方法,辅助创建了 Simobject。
以上就是 SimObject 创建过程的解析。