From 738b60d5314e22b60b787f8f59fc3c9891ef1204 Mon Sep 17 00:00:00 2001 From: Vas Crabb Date: Sun, 29 Nov 2020 19:09:48 +1100 Subject: luaengine_mem.cpp: Cleanup and enhancements. * Changed memory manager banks, regions and shares to lightweight wrappers. * Exposed tag, width and Endianness of regions and shares. * Exposed memory tag and entry of banks (scripts can change entry). * Modernised syntax and switched to abbreviated integer types. --- src/frontend/mame/luaengine_mem.cpp | 479 +++++++++++++++++++++++------------- 1 file changed, 312 insertions(+), 167 deletions(-) diff --git a/src/frontend/mame/luaengine_mem.cpp b/src/frontend/mame/luaengine_mem.cpp index 6da564c866a..59ffe9582dd 100644 --- a/src/frontend/mame/luaengine_mem.cpp +++ b/src/frontend/mame/luaengine_mem.cpp @@ -14,6 +14,35 @@ namespace { +template +struct memory_object_map +{ + memory_object_map(T const &m) : map(m) { } + + T const ↦ +}; + +template +using standard_memory_object_map = memory_object_map > >; + + +template +std::string get_endianness_name(T const &obj) +{ + std::string endianness; + switch (obj.endianness()) + { + case endianness_t::ENDIANNESS_BIG: + endianness = "big"; + break; + case endianness_t::ENDIANNESS_LITTLE: + endianness = "little"; + break; + } + return endianness; +} + + //------------------------------------------------- // region_read - templated region readers for , // -> manager:machine():memory().regions[":maincpu"]:read_i8(0xC000) @@ -75,7 +104,7 @@ T share_read(memory_share &share, offs_t address) { T mem_content = 0; const offs_t lowmask = share.bytewidth() - 1; - uint8_t *ptr = (uint8_t *)share.ptr(); + u8 *ptr = (u8 *)share.ptr(); for (int i = 0; i < sizeof(T); i++) { int addr = share.endianness() == ENDIANNESS_LITTLE ? address + sizeof(T) - 1 - i : address + i; @@ -102,7 +131,7 @@ template void share_write(memory_share &share, offs_t address, T val) { const offs_t lowmask = share.bytewidth() - 1; - uint8_t *ptr = (uint8_t *)share.ptr(); + u8 *ptr = (u8 *)share.ptr(); for (int i = 0; i < sizeof(T); i++) { int addr = share.endianness() == ENDIANNESS_BIG ? address + sizeof(T) - 1 - i : address + i; @@ -122,6 +151,109 @@ void share_write(memory_share &share, offs_t address, T val) +namespace sol { + +template struct is_container > : std::true_type { }; + + +template +struct usertype_container > : lua_engine::immutable_container_helper, T const, typename T::const_iterator> +{ +private: + template + static int next_pairs(lua_State *L) + { + typename usertype_container::indexed_iterator &i(stack::unqualified_get >(L, 1)); + if (i.src.end() == i.it) + return stack::push(L, lua_nil); + int result; + if constexpr (Indexed) + result = stack::push(L, i.ix + 1); + else + result = stack::push(L, i.it->first); + result += stack::push_reference(L, *i.it->second); + ++i; + return result; + } + + template + static int start_pairs(lua_State *L) + { + memory_object_map &self(usertype_container::get_self(L)); + stack::push(L, next_pairs); + stack::push >(L, self.map, self.map.begin()); + stack::push(L, lua_nil); + return 3; + } + +public: + static int at(lua_State *L) + { + memory_object_map &self(usertype_container::get_self(L)); + std::ptrdiff_t const index(stack::unqualified_get(L, 2)); + if ((0 >= index) || (self.map.size() < index)) + return stack::push(L, lua_nil); + auto const found(std::next(self.map.begin(), index - 1)); + if (!found->second) + return stack::push(L, lua_nil); + else + return stack::push_reference(L, *found->second); + } + + static int get(lua_State *L) + { + memory_object_map &self(usertype_container::get_self(L)); + char const *const tag(stack::unqualified_get(L)); + auto const found(self.map.find(tag)); + if ((self.map.end() == found) || !found->second) + return stack::push(L, lua_nil); + else + return stack::push_reference(L, *found->second); + } + + static int index_get(lua_State *L) + { + return get(L); + } + + static int index_of(lua_State *L) + { + memory_object_map &self(usertype_container::get_self(L)); + auto &obj(stack::unqualified_getsecond)>(L, 2)); + auto it(self.map.begin()); + std::ptrdiff_t ix(0); + while ((self.map.end() != it) && (it->second.get() != &obj)) + { + ++it; + ++ix; + } + if (self.map.end() == it) + return stack::push(L, lua_nil); + else + return stack::push(L, ix + 1); + } + + static int size(lua_State *L) + { + memory_object_map &self(usertype_container::get_self(L)); + return stack::push(L, self.map.size()); + } + + static int empty(lua_State *L) + { + memory_object_map &self(usertype_container::get_self(L)); + return stack::push(L, self.map.empty()); + } + + static int next(lua_State *L) { return stack::push(L, next_pairs); } + static int pairs(lua_State *L) { return start_pairs(L); } + static int ipairs(lua_State *L) { return start_pairs(L); } +}; + +} // namespace sol + + + //------------------------------------------------- // mem_read - templated memory readers for , // -> manager:machine().devices[":maincpu"].spaces["program"]:read_i8(0xC000) @@ -291,7 +423,7 @@ T lua_engine::addr_space::direct_mem_read(offs_t address) for (int i = 0; i < sizeof(T); i++) { int addr = space.endianness() == ENDIANNESS_LITTLE ? address + sizeof(T) - 1 - i : address + i; - uint8_t *base = (uint8_t *)space.get_read_ptr(addr & ~lowmask); + u8 *base = (u8 *)space.get_read_ptr(addr & ~lowmask); if (base) { if constexpr (sizeof(T) > 1) @@ -318,7 +450,7 @@ void lua_engine::addr_space::direct_mem_write(offs_t address, T val) for (int i = 0; i < sizeof(T); i++) { int addr = space.endianness() == ENDIANNESS_BIG ? address + sizeof(T) - 1 - i : address + i; - uint8_t *base = (uint8_t *)space.get_read_ptr(addr & ~lowmask); + u8 *base = (u8 *)space.get_read_ptr(addr & ~lowmask); if (base) { if (space.endianness() == ENDIANNESS_BIG) @@ -364,56 +496,61 @@ void lua_engine::initialize_memory() * space.map[] - table of address map entries (k=index, v=address_map_entry) */ - auto addr_space_type = sol().registry().new_usertype("addr_space", sol::call_constructor, sol::constructors>()); - addr_space_type.set("read_i8", &addr_space::mem_read); - addr_space_type.set("read_u8", &addr_space::mem_read); - addr_space_type.set("read_i16", &addr_space::mem_read); - addr_space_type.set("read_u16", &addr_space::mem_read); - addr_space_type.set("read_i32", &addr_space::mem_read); - addr_space_type.set("read_u32", &addr_space::mem_read); - addr_space_type.set("read_i64", &addr_space::mem_read); - addr_space_type.set("read_u64", &addr_space::mem_read); - addr_space_type.set("write_i8", &addr_space::mem_write); - addr_space_type.set("write_u8", &addr_space::mem_write); - addr_space_type.set("write_i16", &addr_space::mem_write); - addr_space_type.set("write_u16", &addr_space::mem_write); - addr_space_type.set("write_i32", &addr_space::mem_write); - addr_space_type.set("write_u32", &addr_space::mem_write); - addr_space_type.set("write_i64", &addr_space::mem_write); - addr_space_type.set("write_u64", &addr_space::mem_write); - addr_space_type.set("read_log_i8", &addr_space::log_mem_read); - addr_space_type.set("read_log_u8", &addr_space::log_mem_read); - addr_space_type.set("read_log_i16", &addr_space::log_mem_read); - addr_space_type.set("read_log_u16", &addr_space::log_mem_read); - addr_space_type.set("read_log_i32", &addr_space::log_mem_read); - addr_space_type.set("read_log_u32", &addr_space::log_mem_read); - addr_space_type.set("read_log_i64", &addr_space::log_mem_read); - addr_space_type.set("read_log_u64", &addr_space::log_mem_read); - addr_space_type.set("write_log_i8", &addr_space::log_mem_write); - addr_space_type.set("write_log_u8", &addr_space::log_mem_write); - addr_space_type.set("write_log_i16", &addr_space::log_mem_write); - addr_space_type.set("write_log_u16", &addr_space::log_mem_write); - addr_space_type.set("write_log_i32", &addr_space::log_mem_write); - addr_space_type.set("write_log_u32", &addr_space::log_mem_write); - addr_space_type.set("write_log_i64", &addr_space::log_mem_write); - addr_space_type.set("write_log_u64", &addr_space::log_mem_write); - addr_space_type.set("read_direct_i8", &addr_space::direct_mem_read); - addr_space_type.set("read_direct_u8", &addr_space::direct_mem_read); - addr_space_type.set("read_direct_i16", &addr_space::direct_mem_read); - addr_space_type.set("read_direct_u16", &addr_space::direct_mem_read); - addr_space_type.set("read_direct_i32", &addr_space::direct_mem_read); - addr_space_type.set("read_direct_u32", &addr_space::direct_mem_read); - addr_space_type.set("read_direct_i64", &addr_space::direct_mem_read); - addr_space_type.set("read_direct_u64", &addr_space::direct_mem_read); - addr_space_type.set("write_direct_i8", &addr_space::direct_mem_write); - addr_space_type.set("write_direct_u8", &addr_space::direct_mem_write); - addr_space_type.set("write_direct_i16", &addr_space::direct_mem_write); - addr_space_type.set("write_direct_u16", &addr_space::direct_mem_write); - addr_space_type.set("write_direct_i32", &addr_space::direct_mem_write); - addr_space_type.set("write_direct_u32", &addr_space::direct_mem_write); - addr_space_type.set("write_direct_i64", &addr_space::direct_mem_write); - addr_space_type.set("write_direct_u64", &addr_space::direct_mem_write); - addr_space_type.set("read_range", [](addr_space &sp, sol::this_state s, u64 first, u64 last, int width, sol::object opt_step) { + auto addr_space_type = sol().registry().new_usertype( + "addr_space", + sol::call_constructor, + sol::constructors>()); + addr_space_type["read_i8"] = &addr_space::mem_read; + addr_space_type["read_u8"] = &addr_space::mem_read; + addr_space_type["read_i16"] = &addr_space::mem_read; + addr_space_type["read_u16"] = &addr_space::mem_read; + addr_space_type["read_i32"] = &addr_space::mem_read; + addr_space_type["read_u32"] = &addr_space::mem_read; + addr_space_type["read_i64"] = &addr_space::mem_read; + addr_space_type["read_u64"] = &addr_space::mem_read; + addr_space_type["write_i8"] = &addr_space::mem_write; + addr_space_type["write_u8"] = &addr_space::mem_write; + addr_space_type["write_i16"] = &addr_space::mem_write; + addr_space_type["write_u16"] = &addr_space::mem_write; + addr_space_type["write_i32"] = &addr_space::mem_write; + addr_space_type["write_u32"] = &addr_space::mem_write; + addr_space_type["write_i64"] = &addr_space::mem_write; + addr_space_type["write_u64"] = &addr_space::mem_write; + addr_space_type["read_log_i8"] = &addr_space::log_mem_read; + addr_space_type["read_log_u8"] = &addr_space::log_mem_read; + addr_space_type["read_log_i16"] = &addr_space::log_mem_read; + addr_space_type["read_log_u16"] = &addr_space::log_mem_read; + addr_space_type["read_log_i32"] = &addr_space::log_mem_read; + addr_space_type["read_log_u32"] = &addr_space::log_mem_read; + addr_space_type["read_log_i64"] = &addr_space::log_mem_read; + addr_space_type["read_log_u64"] = &addr_space::log_mem_read; + addr_space_type["write_log_i8"] = &addr_space::log_mem_write; + addr_space_type["write_log_u8"] = &addr_space::log_mem_write; + addr_space_type["write_log_i16"] = &addr_space::log_mem_write; + addr_space_type["write_log_u16"] = &addr_space::log_mem_write; + addr_space_type["write_log_i32"] = &addr_space::log_mem_write; + addr_space_type["write_log_u32"] = &addr_space::log_mem_write; + addr_space_type["write_log_i64"] = &addr_space::log_mem_write; + addr_space_type["write_log_u64"] = &addr_space::log_mem_write; + addr_space_type["read_direct_i8"] = &addr_space::direct_mem_read; + addr_space_type["read_direct_u8"] = &addr_space::direct_mem_read; + addr_space_type["read_direct_i16"] = &addr_space::direct_mem_read; + addr_space_type["read_direct_u16"] = &addr_space::direct_mem_read; + addr_space_type["read_direct_i32"] = &addr_space::direct_mem_read; + addr_space_type["read_direct_u32"] = &addr_space::direct_mem_read; + addr_space_type["read_direct_i64"] = &addr_space::direct_mem_read; + addr_space_type["read_direct_u64"] = &addr_space::direct_mem_read; + addr_space_type["write_direct_i8"] = &addr_space::direct_mem_write; + addr_space_type["write_direct_u8"] = &addr_space::direct_mem_write; + addr_space_type["write_direct_i16"] = &addr_space::direct_mem_write; + addr_space_type["write_direct_u16"] = &addr_space::direct_mem_write; + addr_space_type["write_direct_i32"] = &addr_space::direct_mem_write; + addr_space_type["write_direct_u32"] = &addr_space::direct_mem_write; + addr_space_type["write_direct_i64"] = &addr_space::direct_mem_write; + addr_space_type["write_direct_u64"] = &addr_space::direct_mem_write; + addr_space_type["read_range"] = + [] (addr_space &sp, sol::this_state s, u64 first, u64 last, int width, sol::object opt_step) + { lua_State *L = s; luaL_Buffer buff; offs_t space_size = sp.space.addrmask(); @@ -436,58 +573,46 @@ void lua_engine::initialize_memory() switch (width) { case 8: - { - u8 *dest = (u8 *)luaL_buffinitsize(L, &buff, byte_count); - for(; first <= last; first += step) - *dest++ = sp.mem_read(first); - break; - } + { + u8 *dest = (u8 *)luaL_buffinitsize(L, &buff, byte_count); + for ( ; first <= last; first += step) + *dest++ = sp.mem_read(first); + break; + } case 16: - { - u16 *dest = (u16 *)luaL_buffinitsize(L, &buff, byte_count); - for(; first <= last; first += step) - *dest++ = sp.mem_read(first); - break; - } + { + u16 *dest = (u16 *)luaL_buffinitsize(L, &buff, byte_count); + for ( ; first <= last; first += step) + *dest++ = sp.mem_read(first); + break; + } case 32: - { - u32 *dest = (u32 *)luaL_buffinitsize(L, &buff, byte_count); - for(; first <= last; first += step) - *dest++ = sp.mem_read(first); - break; - } + { + u32 *dest = (u32 *)luaL_buffinitsize(L, &buff, byte_count); + for(; first <= last; first += step) + *dest++ = sp.mem_read(first); + break; + } case 64: - { - u64 *dest = (u64 *)luaL_buffinitsize(L, &buff, byte_count); - for(; first <= last; first += step) - *dest++ = sp.mem_read(first); - break; - } + { + u64 *dest = (u64 *)luaL_buffinitsize(L, &buff, byte_count); + for(; first <= last; first += step) + *dest++ = sp.mem_read(first); + break; + } default: luaL_error(L, "Invalid width. Must be 8/16/32/64"); return sol::make_reference(L, nullptr); } luaL_pushresultsize(&buff, byte_count); return sol::make_reference(L, sol::stack_reference(L, -1)); - }); - addr_space_type.set("name", sol::property([](addr_space &sp) { return sp.space.name(); })); - addr_space_type.set("shift", sol::property([](addr_space &sp) { return sp.space.addr_shift(); })); - addr_space_type.set("index", sol::property([](addr_space &sp) { return sp.space.spacenum(); })); - addr_space_type.set("address_mask", sol::property([](addr_space &sp) { return sp.space.addrmask(); })); - addr_space_type.set("data_width", sol::property([](addr_space &sp) { return sp.space.data_width(); })); - addr_space_type.set("endianness", sol::property([](addr_space &sp) { - std::string endianness; - switch (sp.space.endianness()) - { - case endianness_t::ENDIANNESS_BIG: - endianness = "big"; - break; - case endianness_t::ENDIANNESS_LITTLE: - endianness = "little"; - break; - } - return endianness; - })); + }; + addr_space_type["name"] = sol::property([] (addr_space &sp) { return sp.space.name(); }); + addr_space_type["shift"] = sol::property([] (addr_space &sp) { return sp.space.addr_shift(); }); + addr_space_type["index"] = sol::property([] (addr_space &sp) { return sp.space.spacenum(); }); + addr_space_type["address_mask"] = sol::property([] (addr_space &sp) { return sp.space.addrmask(); }); + addr_space_type["data_width"] = sol::property([] (addr_space &sp) { return sp.space.data_width(); }); + addr_space_type["endianness"] = sol::property([] (addr_space &sp) { return get_endianness_name(sp.space); }); /* address_map_entry library @@ -499,20 +624,22 @@ void lua_engine::initialize_memory() * mapentry.readtype * mapentry.writetype */ - addr_space_type.set("map", sol::property([this](addr_space &sp) { - address_space &space = sp.space; - sol::table map = sol().create_table(); - for (address_map_entry &entry : space.map()->m_entrylist) + addr_space_type["map"] = sol::property( + [this] (addr_space &sp) { - sol::table mapentry = sol().create_table(); - mapentry["offset"] = entry.m_addrstart & space.addrmask(); - mapentry["endoff"] = entry.m_addrend & space.addrmask(); - mapentry["readtype"] = entry.m_read.m_type; - mapentry["writetype"] = entry.m_write.m_type; - map.add(mapentry); - } - return map; - })); + address_space &space = sp.space; + sol::table map = sol().create_table(); + for (address_map_entry &entry : space.map()->m_entrylist) + { + sol::table mapentry = sol().create_table(); + mapentry["offset"] = entry.m_addrstart & space.addrmask(); + mapentry["endoff"] = entry.m_addrend & space.addrmask(); + mapentry["readtype"] = entry.m_read.m_type; + mapentry["writetype"] = entry.m_write.m_type; + map.add(mapentry); + } + return map; + }); /* memory_manager library @@ -524,25 +651,23 @@ void lua_engine::initialize_memory() * memory.shares[] - table of memory shares (k=tag, v=memory_share) */ - auto memory_type = sol().registry().new_usertype("memory", "new", sol::no_constructor); - memory_type.set("banks", sol::property([this](memory_manager &mm) { - sol::table table = sol().create_table(); - for (auto &bank : mm.banks()) - table[bank.second->tag()] = bank.second.get(); - return table; - })); - memory_type.set("regions", sol::property([this](memory_manager &mm) { - sol::table table = sol().create_table(); - for (auto ®ion : mm.regions()) - table[region.second->name()] = region.second.get(); - return table; - })); - memory_type.set("shares", sol::property([this](memory_manager &mm) { - sol::table table = sol().create_table(); - for (auto &share : mm.shares()) - table[share.first] = share.second.get(); - return table; - })); + auto memory_type = sol().registry().new_usertype("memory", sol::no_constructor); + memory_type["banks"] = sol::property([] (memory_manager &mm) { return standard_memory_object_map(mm.banks()); }); + memory_type["regions"] = sol::property([] (memory_manager &mm) { return standard_memory_object_map(mm.regions()); }); + memory_type["shares"] = sol::property([] (memory_manager &mm) { return standard_memory_object_map(mm.shares()); }); + + +/* memory_bank library + * + * manager:machine():memory().banks[bank_tag] + * + * region.tag - absolute tag of the bank + * bank.entry - get/set the selected entry + */ + + auto bank_type = sol().registry().new_usertype("membank", sol::no_constructor); + bank_type["tag"] = sol::property(&memory_bank::tag); + bank_type["entry"] = sol::property(&memory_bank::entry, &memory_bank::set_entry); /* memory_region library @@ -553,27 +678,37 @@ void lua_engine::initialize_memory() * region:read_*(addr) * region:write_*(addr, val) * - * region.size + * region.tag - absolute tag of the region + * region.size - size in bytes + * region.length - length in items + * region.endianness - endiannes as string ("big" or "little") + * region.bitwidth - item width in bits + * region.bytewidth - item width in bytes */ - auto region_type = sol().registry().new_usertype("region", "new", sol::no_constructor); - region_type.set("read_i8", ®ion_read); - region_type.set("read_u8", ®ion_read); - region_type.set("read_i16", ®ion_read); - region_type.set("read_u16", ®ion_read); - region_type.set("read_i32", ®ion_read); - region_type.set("read_u32", ®ion_read); - region_type.set("read_i64", ®ion_read); - region_type.set("read_u64", ®ion_read); - region_type.set("write_i8", ®ion_write); - region_type.set("write_u8", ®ion_write); - region_type.set("write_i16", ®ion_write); - region_type.set("write_u16", ®ion_write); - region_type.set("write_i32", ®ion_write); - region_type.set("write_u32", ®ion_write); - region_type.set("write_i64", ®ion_write); - region_type.set("write_u64", ®ion_write); - region_type.set("size", sol::property(&memory_region::bytes)); + auto region_type = sol().registry().new_usertype("region", sol::no_constructor); + region_type["read_i8"] = ®ion_read; + region_type["read_u8"] = ®ion_read; + region_type["read_i16"] = ®ion_read; + region_type["read_u16"] = ®ion_read; + region_type["read_i32"] = ®ion_read; + region_type["read_u32"] = ®ion_read; + region_type["read_i64"] = ®ion_read; + region_type["read_u64"] = ®ion_read; + region_type["write_i8"] = ®ion_write; + region_type["write_u8"] = ®ion_write; + region_type["write_i16"] = ®ion_write; + region_type["write_u16"] = ®ion_write; + region_type["write_i32"] = ®ion_write; + region_type["write_u32"] = ®ion_write; + region_type["write_i64"] = ®ion_write; + region_type["write_u64"] = ®ion_write; + region_type["tag"] = sol::property(&memory_region::name); + region_type["size"] = sol::property(&memory_region::bytes); + region_type["length"] = sol::property([] (memory_region &r) { return r.bytes() / r.bytewidth(); }); + region_type["endianness"] = sol::property(&get_endianness_name); + region_type["bitwidth"] = sol::property(&memory_region::bitwidth); + region_type["bytewidth"] = sol::property(&memory_region::bytewidth); /* memory_share library @@ -584,26 +719,36 @@ void lua_engine::initialize_memory() * share:read_*(addr) * share:write_*(addr, val) * - * region.size + * share.tag - absolute tag of the share + * share.size - size in bytes + * share.length - length in items + * region.endianness - endiannes as string ("big" or "little") + * share.bitwidth - item width in bits + * share.bytewidth - item width in bytes */ - auto share_type = sol().registry().new_usertype("share", "new", sol::no_constructor); - share_type.set("read_i8", &share_read); - share_type.set("read_u8", &share_read); - share_type.set("read_i16", &share_read); - share_type.set("read_u16", &share_read); - share_type.set("read_i32", &share_read); - share_type.set("read_u32", &share_read); - share_type.set("read_i64", &share_read); - share_type.set("read_u64", &share_read); - share_type.set("write_i8", &share_write); - share_type.set("write_u8", &share_write); - share_type.set("write_i16", &share_write); - share_type.set("write_u16", &share_write); - share_type.set("write_i32", &share_write); - share_type.set("write_u32", &share_write); - share_type.set("write_i64", &share_write); - share_type.set("write_u64", &share_write); - share_type.set("size", sol::property(&memory_share::bytes)); + auto share_type = sol().registry().new_usertype("share", sol::no_constructor); + share_type["read_i8"] = &share_read; + share_type["read_u8"] = &share_read; + share_type["read_i16"] = &share_read; + share_type["read_u16"] = &share_read; + share_type["read_i32"] = &share_read; + share_type["read_u32"] = &share_read; + share_type["read_i64"] = &share_read; + share_type["read_u64"] = &share_read; + share_type["write_i8"] = &share_write; + share_type["write_u8"] = &share_write; + share_type["write_i16"] = &share_write; + share_type["write_u16"] = &share_write; + share_type["write_i32"] = &share_write; + share_type["write_u32"] = &share_write; + share_type["write_i64"] = &share_write; + share_type["write_u64"] = &share_write; + share_type["tag"] = sol::property(&memory_share::name); + share_type["size"] = sol::property(&memory_share::bytes); + share_type["length"] = sol::property([] (memory_share &s) { return s.bytes() / s.bytewidth(); }); + share_type["endianness"] = sol::property(&get_endianness_name); + share_type["bitwidth"] = sol::property(&memory_share::bitwidth); + share_type["bytewidth"] = sol::property(&memory_share::bytewidth); } -- cgit v1.2.3