From 467f25b9279cd48c520f49cfcc7623ca037d0d01 Mon Sep 17 00:00:00 2001 From: jyelon Date: Thu, 17 Feb 2022 20:02:08 -0500 Subject: [PATCH] Lots of work on removing malloc from driver --- luprex/core/Makefile | 2 + luprex/core/cpp/drivenengine.cpp | 74 ++- luprex/core/cpp/drivenengine.hpp | 57 +- luprex/core/cpp/driver-common.cpp | 83 ++- luprex/core/cpp/driver-linux.cpp | 55 +- luprex/core/cpp/driver-mingw.cpp | 50 +- luprex/core/cpp/driver-util.cpp | 29 + luprex/core/cpp/driver-util.hpp | 13 + luprex/core/cpp/driver.hpp | 3 +- luprex/core/cpp/luastack.hpp | 3 +- luprex/core/cpp/main.cpp | 47 +- luprex/core/cpp/umm-malloc.cpp | 929 ++++++++++++++++++++++++++++++ luprex/core/cpp/umm-malloc.hpp | 108 ++++ luprex/core/cpp/util.cpp | 18 - luprex/core/cpp/util.hpp | 11 +- luprex/core/lua/ut-globaldb.lua | 8 +- luprex/core/lua/ut-tablecmp.lua | 8 +- 17 files changed, 1301 insertions(+), 197 deletions(-) create mode 100644 luprex/core/cpp/driver-util.cpp create mode 100644 luprex/core/cpp/driver-util.hpp create mode 100644 luprex/core/cpp/umm-malloc.cpp create mode 100644 luprex/core/cpp/umm-malloc.hpp diff --git a/luprex/core/Makefile b/luprex/core/Makefile index b019627c..5edcd814 100644 --- a/luprex/core/Makefile +++ b/luprex/core/Makefile @@ -64,6 +64,7 @@ LUA_OBJ_FILES=\ CORE_OBJ_FILES=\ obj/invocation.o\ + obj/umm-malloc.o\ obj/spookyv2.o\ obj/debugcollector.o\ obj/drivenengine.o\ @@ -95,6 +96,7 @@ CORE_OBJ_FILES=\ obj/drivertests.o\ obj/printbuffer.o\ obj/main.o \ + obj/driver-util.o\ obj/$(DRIVER).o\ diff --git a/luprex/core/cpp/drivenengine.cpp b/luprex/core/cpp/drivenengine.cpp index 43a8e662..00c08e28 100644 --- a/luprex/core/cpp/drivenengine.cpp +++ b/luprex/core/cpp/drivenengine.cpp @@ -1,5 +1,32 @@ #include "drivenengine.hpp" +#include +#include +#include +#include +#include +static std::vector> makers; + +void DrivenEngine::register_maker(const char *kind, DrivenEngineMaker maker) { + std::string skind(kind); + makers.push_back(std::make_pair(skind, maker)); +} + +void DrivenEngine::print_usage(std::ostream &strm, const char *progname) { + strm << "Usage: " << progname << " " << std::endl; + for (const auto &mpair : makers) { + strm << " Mode can be: " << mpair.first << std::endl; + } +} + +UniqueDrivenEngine DrivenEngine::make(const char *kind) { + for (const auto &mpair : makers) { + if (strcmp(mpair.first.c_str(), kind) == 0) { + return mpair.second(); + } + } + return nullptr; +} Channel::Channel(DrivenEngine *de, int chid, int port, const std::string &target, bool stop) { chid_ = chid; @@ -127,14 +154,14 @@ int DrivenEngine::find_unused_chid() { return 0; } -Channel *DrivenEngine::get_chid(int chid) { +Channel *DrivenEngine::get_chid(int chid) const { assert(unsigned(chid) < MAX_CHAN); assert(channels_[chid].get() != nullptr); return channels_[chid].get(); } void DrivenEngine::listen_port(int port) { - listen_ports_.insert(port); + listen_ports_.push_back(port); } double DrivenEngine::get_clock() { @@ -143,7 +170,7 @@ double DrivenEngine::get_clock() { SharedChannel DrivenEngine::new_outgoing_channel(const std::string &target) { int chid = find_unused_chid(); - new_outgoing_.insert(chid); + new_outgoing_.push_back(chid); SharedChannel result = std::make_shared(this, chid, 0, target, stop_driver_); channels_[chid] = result; return result; @@ -180,30 +207,33 @@ void DrivenEngine::stop_driver() { } } -void DrivenEngine::drv_get_listen_ports(std::set &ports) { - ports = listen_ports_; +const std::vector &DrivenEngine::drv_get_listen_ports() const { + return listen_ports_; } -void DrivenEngine::drv_get_new_outgoing(std::set &channels) { - channels = std::move(new_outgoing_); +const std::vector &DrivenEngine::drv_get_new_outgoing() const { + return new_outgoing_; +} + +void DrivenEngine::drv_clear_new_outgoing() { new_outgoing_.clear(); } -const std::string &DrivenEngine::drv_get_target(int chid) { +const std::string &DrivenEngine::drv_get_target(int chid) const { return get_chid(chid)->target_; } -bool DrivenEngine::drv_outgoing_empty(int chid) { +bool DrivenEngine::drv_outgoing_empty(int chid) const { int nbytes; const char *bytes; drv_peek_outgoing(chid, &nbytes, &bytes); return (nbytes == 0); } -bool DrivenEngine::drv_get_channel_released(int chid) { +bool DrivenEngine::drv_get_channel_released(int chid) const { return channels_[chid].use_count() == 1; } -void DrivenEngine::drv_peek_outgoing(int chid, int *nbytes, const char **bytes) { +void DrivenEngine::drv_peek_outgoing(int chid, int *nbytes, const char **bytes) const { return get_chid(chid)->peek_outgoing(nbytes, bytes); } @@ -222,7 +252,7 @@ void DrivenEngine::drv_recv_incoming(int chid, int nbytes, const char *bytes) { } } -void DrivenEngine::drv_notify_close(int chid, const std::string &err) { +void DrivenEngine::drv_notify_close(int chid, std::string_view err) { Channel *ch = get_chid(chid); ch->closed_ = true; ch->error_ = err; @@ -236,8 +266,20 @@ int DrivenEngine::drv_notify_accept(int port) { return chid; } -void DrivenEngine::drv_set_lua_source(util::LuaSourcePtr source) { - lua_source_ = std::move(source); +void DrivenEngine::drv_clear_lua_source() { + lua_source_.reset(); + rescan_lua_source_ = false; +} + +void DrivenEngine::drv_add_lua_source(const char *fn, const char *data) { + if (lua_source_ == nullptr) { + lua_source_.reset(new util::LuaSourceVec); + } + lua_source_->emplace_back(std::string(fn), std::string(data)); +} + +void DrivenEngine::drv_set_lua_source(util::LuaSourcePtr src) { + lua_source_ = std::move(src); rescan_lua_source_ = false; } @@ -250,11 +292,11 @@ void DrivenEngine::drv_invoke_event_update(double clock) { event_update(); } -bool DrivenEngine::drv_get_rescan_lua_source() { +bool DrivenEngine::drv_get_rescan_lua_source() const { return rescan_lua_source_; } -bool DrivenEngine::drv_get_stop_driver() { +bool DrivenEngine::drv_get_stop_driver() const { return stop_driver_; } diff --git a/luprex/core/cpp/drivenengine.hpp b/luprex/core/cpp/drivenengine.hpp index b8a2b472..967d33e4 100644 --- a/luprex/core/cpp/drivenengine.hpp +++ b/luprex/core/cpp/drivenengine.hpp @@ -95,11 +95,13 @@ #include #include #include -#include +#include #include "streambuffer.hpp" #include "util.hpp" class DrivenEngine; +using UniqueDrivenEngine = std::unique_ptr; +using DrivenEngineMaker = UniqueDrivenEngine (*)(); class Channel { public: @@ -299,36 +301,41 @@ public: // Get a list of all the listening ports. The driver is expected // to fetch this set shortly after the event_init callback is invoked. // - void drv_get_listen_ports(std::set &ports); + const std::vector &drv_get_listen_ports() const; // Get a list of all recently-opened channels that were created using // drv_new_outgoing_channel. The driver should initiate outgoing // connections for these channels. // - void drv_get_new_outgoing(std::set &closed); + const std::vector &drv_get_new_outgoing() const; + + // Clear the list of recently-opened channels that were created using + // drv_new_outgoing_channel. + // + void drv_clear_new_outgoing(); // Get the target of a channel. A target is a string like // "www.whatever.com:80". It indicates the host and port that the channel // is supposed to be talking to. Non-socket channels and incoming channels // have empty targets. // - const std::string &drv_get_target(int chid); + const std::string &drv_get_target(int chid) const; // Return true if the outgoing buffer is empty. // - bool drv_outgoing_empty(int chid); + bool drv_outgoing_empty(int chid) const; // Return true if the user has released all references to this channel. // In this case, the driver should initiate shutdown of the channel, // and the driver should eventually call drv_notify_close. // - bool drv_get_channel_released(int chid); + bool drv_get_channel_released(int chid) const; // Get a pointer to the bytes in the outgoing buffer. The pointer returned // here is naturally only valid until the buffer is changed. This function // is used for all channels, including sockets and stdio. // - void drv_peek_outgoing(int chid, int *nbytes, const char **bytes); + void drv_peek_outgoing(int chid, int *nbytes, const char **bytes) const; // Notifies the channel that some bytes were transmitted. This causes those // bytes to be removed from the outgoing buffer. This function is used for @@ -349,7 +356,7 @@ public: // delete it. Closing a channel prevents it from showing up in // 'drv_list_channels'. // - void drv_notify_close(int chid, const std::string &err); + void drv_notify_close(int chid, std::string_view err); // Notify the DrivenEngine that somebody connected to an incoming port. // This will cause the DrivenEngine to allocate a new channel and put the @@ -359,11 +366,17 @@ public: // int drv_notify_accept(int port); + + // Clear the lua source code. + // + void drv_clear_lua_source(); + // Set the lua source code. The driver is expected to read the lua source // code and store it (using this function) once before invoking // + void drv_add_lua_source(const char *fn, const char *data); void drv_set_lua_source(util::LuaSourcePtr source); - + // Invoke the init or update event. // void drv_invoke_event_init(int argc, char *argv[]); @@ -374,11 +387,11 @@ public: // When the driver sees this flag, it should rescan the source and call // drv_set_source. // - bool drv_get_rescan_lua_source(); + bool drv_get_rescan_lua_source() const; // If true, the engine is done. Stop the driver. // - bool drv_get_stop_driver(); + bool drv_get_stop_driver() const; ////////////////////////////////////////////////////////////// // @@ -409,29 +422,39 @@ public: static void set(DrivenEngine *de); static DrivenEngine *get(); +public: + ////////////////////////////////////////////////////////////// + // + // The Registry of DrivenEngineMakers. + // + ////////////////////////////////////////////////////////////// + + static void register_maker(const char *kind, DrivenEngineMaker maker); + + static void print_usage(std::ostream &strm, const char *progname); + + static UniqueDrivenEngine make(const char *kind); + private: // Find a currently-unused channel ID. Channel IDs // are small integers that are reused. int find_unused_chid(); // Get the channel associated with the specified channel ID. - Channel *get_chid(int chid); + Channel *get_chid(int chid) const; private: SharedChannel channels_[MAX_CHAN]; int next_unused_chid_; SharedChannel stdio_channel_; std::vector accepted_channels_; - std::set new_outgoing_; + std::vector new_outgoing_; util::LuaSourcePtr lua_source_; - std::set listen_ports_; + std::vector listen_ports_; bool rescan_lua_source_; double clock_; bool stop_driver_; friend class Channel; }; -using UniqueDrivenEngine = std::unique_ptr; -using DrivenEngineMaker = UniqueDrivenEngine (*)(); - #endif // DRIVENENGINE_HPP diff --git a/luprex/core/cpp/driver-common.cpp b/luprex/core/cpp/driver-common.cpp index 392d7aff..d6f76cd5 100644 --- a/luprex/core/cpp/driver-common.cpp +++ b/luprex/core/cpp/driver-common.cpp @@ -1,3 +1,6 @@ +#define OPENSSL_HEAP_SIZE (4*1024*1024) +#define CHBUF_SIZE (256*1024) +#define POLLVEC_SIZE (DrivenEngine::MAX_CHAN+1) static MonoClock monoclock; @@ -7,7 +10,7 @@ namespace util { } } -static void if_error_print_and_exit(const std::string &str) { +static void if_error_print_and_exit(const UmmString &str) { if (!str.empty()) { std::cerr << std::endl << "error: " << str << std::endl; exit(1); @@ -28,12 +31,12 @@ static SSL_CTX *new_ssl_context(bool server_cert, bool root_certs, const std::st return ctx; } -static std::string err_print_errors_str() { +static UmmString err_print_errors_str() { BIO *bio = BIO_new(BIO_s_mem()); ERR_print_errors(bio); char *buf; size_t len = BIO_get_mem_data(bio, &buf); - std::string ret(buf, len); + UmmString ret(buf, len); BIO_free(bio); return ret; } @@ -58,8 +61,12 @@ static int ssl_ctx_use_privatekey_str(SSL_CTX *ctx, const char *str) { return status; } +static std::unique_ptr chbuf; +static std::unique_ptr pollvec; + class Driver { public: + enum ChanState { CHAN_INACTIVE, CHAN_PLAINTEXT, @@ -87,19 +94,18 @@ public: DrivenEngine *driven_; std::vector chans_; std::map listen_sockets_; - std::unique_ptr chbuf; bool read_console_recently_; SSL_CTX *ssl_ctx_with_root_certs_; SSL_CTX *ssl_ctx_with_server_certs_; SSL_CTX *ssl_ctx_with_no_certs_; + void handle_listen_ports() { - std::set listenports; - driven_->drv_get_listen_ports(listenports); + const std::vector &listenports = driven_->drv_get_listen_ports(); for (int port : listenports) { if (listen_sockets_.find(port) == listen_sockets_.end()) { - std::string err; + UmmString err; SOCKET sock = listen_on_port(port, err); if_error_print_and_exit(err); assert(sock != INVALID_SOCKET); @@ -114,7 +120,7 @@ public: } } - void close_channel(ChanInfo &chan, const std::string &err) { + void close_channel(ChanInfo &chan, std::string_view err) { // std::cerr << "Closing channel " << chan.chid << std::endl; assert(chan.state != CHAN_INACTIVE); // Close and release the SSL channel. @@ -196,10 +202,9 @@ public: } void handle_new_outgoing_sockets() { - std::set chans; - driven_->drv_get_new_outgoing(chans); + const std::vector &chans = driven_->drv_get_new_outgoing(); for (int chid : chans) { - std::string err; + UmmString err; SOCKET sock = open_connection(driven_->drv_get_target(chid), err); if (sock == INVALID_SOCKET) { driven_->drv_notify_close(chid, err); @@ -208,10 +213,13 @@ public: make_channel(sock, chid, ssl_ctx_with_no_certs_, CHAN_SSL_CONNECTING); } } + if (!chans.empty()) { + driven_->drv_clear_new_outgoing(); + } } void accept_connection(int port, SOCKET sock) { - std::string err; + UmmString err; SOCKET socket = accept_on_socket(sock, err); if_error_print_and_exit(err); if (socket != INVALID_SOCKET) { @@ -222,7 +230,7 @@ public: } void advance_plaintext(ChanInfo &chan) { - std::string err; + UmmString err; // If the channel has no outgoing bytes and has been released, // just close it. @@ -359,8 +367,8 @@ public: void handle_socket_input_output() { - std::string err; - int mstimeout = 1000; + UmmString err; + int mstimeout = read_console_recently_ ? 100 : 1000; // Peek output buffers and determine channel release flags. for (ChanInfo &chan : chans_) { @@ -373,17 +381,15 @@ public: } // Construct the struct pollfd vector. - int pollsize = listen_sockets_.size() + chans_.size() + 1; - PollVector pollvec(pollsize); - int index = 0; + int pollsize = 0; for (const auto &p : listen_sockets_) { - struct pollfd &pfd = pollvec[index++]; + struct pollfd &pfd = pollvec[pollsize++]; pfd.fd = p.second; pfd.events = POLLIN; pfd.revents = 0; } for (const ChanInfo &chan : chans_) { - struct pollfd &pfd = pollvec[index++]; + struct pollfd &pfd = pollvec[pollsize++]; assert(chan.socket != INVALID_SOCKET); pfd.fd = chan.socket; pfd.events = 0; @@ -395,14 +401,13 @@ public: if (chan.ready_on_outgoing && (chan.nbytes > 0)) pfd.events |= POLLOUT; // std::cerr << "evt=" << pfd.events << ".nb=" << chan.nbytes << " "; } - fill_stdio_pollfd(pollvec, mstimeout, read_console_recently_); // Do the poll. - socket_poll(pollvec, mstimeout, err); + socket_poll(pollvec.get(), pollsize, mstimeout, err); if_error_print_and_exit(err); // Check listening sockets. - index = 0; + int index = 0; for (auto &p : listen_sockets_) { struct pollfd &pfd = pollvec[index++]; if (pfd.revents & (POLLIN | POLLERR)) { @@ -435,14 +440,8 @@ public: } void drive(DrivenEngine *de, int argc, char *argv[]) { - disable_randomization(argc, argv); - socket_init(); - SSL_load_error_strings(); - ERR_load_crypto_strings(); - enable_tty_raw(); driven_ = de; read_console_recently_ = false; - chbuf.reset(new char[65536]); ssl_ctx_with_root_certs_ = new_ssl_context(false, true, ""); ssl_ctx_with_server_certs_ = new_ssl_context(true, false, ""); @@ -480,13 +479,33 @@ public: SSL_CTX_free(ssl_ctx_with_root_certs_); SSL_CTX_free(ssl_ctx_with_server_certs_); DrivenEngine::set(nullptr); - socket_uninit(); } }; -void driver_drive(DrivenEngine *de, int argc, char *argv[]) { +void driver_drive(int argc, char *argv[]) { + // The only place in the driver where we're allowed to use malloc + // is here, before even looking at the arguments. That way, the + // impact on the malloc heap is always exactly the same, which + // doesn't break the determinism of the execution during replay. + + umm_init_heap(malloc(OPENSSL_HEAP_SIZE), OPENSSL_HEAP_SIZE); + CRYPTO_set_mem_functions(umm_malloc_ssl, umm_realloc_ssl, umm_free_ssl); + chbuf.reset(new char[CHBUF_SIZE]); + pollvec.reset(new struct pollfd[POLLVEC_SIZE]); + ERR_load_crypto_strings(); + SSL_load_error_strings(); + Driver driver; - driver.drive(de, argc, argv); + if (argc < 2) { + DrivenEngine::print_usage(std::cerr, argv[0]); + exit(1); + } + UniqueDrivenEngine engine = DrivenEngine::make(argv[1]); + if (engine == nullptr) { + DrivenEngine::print_usage(std::cerr, argv[0]); + exit(1); + } + driver.drive(engine.get(), argc, argv); } diff --git a/luprex/core/cpp/driver-linux.cpp b/luprex/core/cpp/driver-linux.cpp index c27db184..e93c1b53 100644 --- a/luprex/core/cpp/driver-linux.cpp +++ b/luprex/core/cpp/driver-linux.cpp @@ -1,5 +1,7 @@ #include "driver.hpp" +#include "umm-malloc.hpp" +#include "driver-util.hpp" #include "util.hpp" #include "drivenengine.hpp" #include "dummycert.hpp" @@ -31,11 +33,10 @@ using SOCKET=int; const int INVALID_SOCKET = -1; -using PollVector = std::vector; struct termios orig_termios; -static std::string strerror_str(int err) { +static UmmString strerror_str(int err) { char errbuf[256]; return strerror_r(errno, errbuf, 256); } @@ -65,12 +66,11 @@ static void enable_tty_raw() { assert(status >= 0); } -static SOCKET open_connection(const std::string &target, std::string &err) { +static SOCKET open_connection(std::string_view target, UmmString &err) { struct addrinfo *addrs = nullptr; struct addrinfo *goodaddr = nullptr; struct addrinfo hints; SOCKET sock = INVALID_SOCKET; - std::string host, port; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; @@ -79,7 +79,8 @@ static SOCKET open_connection(const std::string &target, std::string &err) { hints.ai_flags = AI_NUMERICSERV; err.clear(); - util::split_host_port(target, host, port); + UmmString host, port; + drv::split_host_port(target, host, port); int status = getaddrinfo(host.c_str(), port.c_str(), &hints, &addrs); if (status != 0) { err = gai_strerror(status); @@ -111,7 +112,7 @@ error_general: return INVALID_SOCKET; } -static SOCKET listen_on_port(int port, std::string &err) { +static SOCKET listen_on_port(int port, UmmString &err) { int status, enable; err.clear(); @@ -142,7 +143,7 @@ error_errno: return INVALID_SOCKET; } -static SOCKET accept_on_socket(SOCKET listen_socket, std::string &err) { +static SOCKET accept_on_socket(SOCKET listen_socket, UmmString &err) { err.clear(); SOCKET chsock = accept(listen_socket, nullptr, nullptr); if (chsock >= 0) { @@ -162,7 +163,7 @@ static SOCKET accept_on_socket(SOCKET listen_socket, std::string &err) { // zero: would block // negative: channel closed, possibly cleanly or possibly with error // -static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string &err) { +static int socket_send(SOCKET socket, const char *bytes, int nbytes, UmmString &err) { err.clear(); int wbytes = send(socket, bytes, nbytes, 0); if (wbytes < 0) { @@ -177,7 +178,7 @@ static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string } } -static int socket_recv(SOCKET socket, char *bytes, int nbytes, std::string &err) { +static int socket_recv(SOCKET socket, char *bytes, int nbytes, UmmString &err) { err.clear(); int nrecv = recv(socket, bytes, nbytes, 0); if (nrecv < 0) { @@ -198,8 +199,17 @@ static int socket_close(SOCKET socket) { return close(socket); } -static int socket_poll(PollVector &pollvec, int mstimeout, std::string &err) { - int status = poll(&pollvec[0], pollvec.size(), mstimeout); +static int socket_poll(struct pollfd *pollvec, int pollcount, int mstimeout, UmmString &err) { + // socket_poll is implicitly expected to also poll stdin, + // if the OS allows that. Linux does, so we add stdin to the + // poll vector. The poll vector is required to have at + // least one free space in order to do this. + pollvec[pollcount].fd = 0; + pollvec[pollcount].events = POLLIN; + pollcount += 1; + + // Do the poll. + int status = poll(pollvec, pollcount, mstimeout); if (status < 0) { err = strerror_str(errno); return -1; @@ -207,14 +217,6 @@ static int socket_poll(PollVector &pollvec, int mstimeout, std::string &err) { return 0; } -static void socket_init() { - // Nothing needed on linux -} - -static void socket_uninit() { - // Nothing needed on linux -} - static int console_write(const char *bytes, int nbytes) { return write(1, bytes, nbytes); } @@ -223,15 +225,7 @@ static int console_read(char *bytes, int nbytes) { return read(0, bytes, nbytes); } -// The last element in the vector is supposed to be -// for polling stdio. But on windows, you can't poll -// stdio, so on windows, we remove the last element from -// the vector and we reduce mstimeout instead. -static void fill_stdio_pollfd(PollVector &pollvec, int &mstimeout, bool read_console_recently) { - struct pollfd &stdiopoll = pollvec.back(); - stdiopoll.fd = 0; - stdiopoll.events = POLLIN; -} + static void disable_randomization(int argc, char *argv[]) { const int old_personality = personality(ADDR_NO_RANDOMIZE); @@ -243,6 +237,11 @@ static void disable_randomization(int argc, char *argv[]) { } } +void driver_sysinit(int argc, char *argv[]) { + disable_randomization(argc, argv); + enable_tty_raw(); +} + class MonoClock { private: struct timespec base_; diff --git a/luprex/core/cpp/driver-mingw.cpp b/luprex/core/cpp/driver-mingw.cpp index d16e3689..89bd2dc5 100644 --- a/luprex/core/cpp/driver-mingw.cpp +++ b/luprex/core/cpp/driver-mingw.cpp @@ -2,6 +2,8 @@ #define _WIN32_WINNT 0x0600 #include "driver.hpp" +#include "umm-malloc.hpp" +#include "driver-util.hpp" #include "util.hpp" #include "drivenengine.hpp" #include "dummycert.hpp" @@ -23,18 +25,12 @@ #include #include -using PollVector = std::vector; - static void set_nonblocking(SOCKET sock) { u_long mode = 1; // 1 to enable non-blocking socket int status = ioctlsocket(sock, FIONBIO, &mode); assert(status == 0); } -static void enable_tty_raw() { - // Do nothing on windows. -} - static std::string winsock_error_string(int errcode) { std::ostringstream oss; oss << "error " << errcode; @@ -46,21 +42,21 @@ static PADDRINFOA find_good_addr(PADDRINFOA addrinfo) { if (addr->ai_family == AF_INET) { return addr; } - } + }std::string return nullptr; } -static SOCKET open_connection(const std::string &target, std::string &err) { +static SOCKET open_connection(std::string_view target, UmmString &err) { PADDRINFOA addrs = nullptr; PADDRINFOA goodaddr = nullptr; SOCKET sock = INVALID_SOCKET; - std::string host, port; + std::string_view host, port; err.clear(); util::split_host_port(target, host, port); - int status = getaddrinfo(host.c_str(), port.c_str(), nullptr, &addrs); + int status = getaddrinfo(host.data(), port.data(), nullptr, &addrs); while (status == WSATRY_AGAIN) { - status = getaddrinfo(host.c_str(), port.c_str(), nullptr, &addrs); + status = getaddrinfo(host.data(), port.data(), nullptr, &addrs); } if (status == WSAHOST_NOT_FOUND) { err = "host not found"; @@ -98,7 +94,7 @@ error: return SOCKET_ERROR; } -SOCKET listen_on_port(int port, std::string &err) { +SOCKET listen_on_port(int port, UmmString &err) { int status; err.clear(); SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); @@ -131,11 +127,10 @@ error: return SOCKET_ERROR; } -static SOCKET accept_on_socket(SOCKET listen_socket, std::string &err) { +static SOCKET accept_on_socket(SOCKET listen_socket, UmmString &err) { SOCKET chsock = accept(listen_socket, nullptr, nullptr); if (chsock != INVALID_SOCKET) { set_nonblocking(chsock); - std::cerr << "accepted socket is " << chsock << std::endl; return chsock; } else { int errcode = WSAGetLastError(); @@ -148,7 +143,7 @@ static SOCKET accept_on_socket(SOCKET listen_socket, std::string &err) { } } -static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string &err) { +static int socket_send(SOCKET socket, const char *bytes, int nbytes, UmmString &err) { err.clear(); int wbytes = send(socket, bytes, nbytes, 0); if (wbytes == SOCKET_ERROR) { @@ -165,7 +160,7 @@ static int socket_send(SOCKET socket, const char *bytes, int nbytes, std::string } } -static int socket_recv(SOCKET socket, char *bytes, int nbytes, std::string &err) { +static int socket_recv(SOCKET socket, char *bytes, int nbytes, UmmString &err) { err.clear(); int nrecv = recv(socket, bytes, nbytes, 0); if (nrecv < 0) { @@ -187,8 +182,8 @@ static int socket_close(SOCKET socket) { return closesocket(socket); } -static int socket_poll(PollVector &pollvec, int mstimeout, std::string &err) { - int status = WSAPoll(&pollvec[0], pollvec.size(), mstimeout); +static int socket_poll(struct pollfd *pollvec, int pollcount, int mstimeout, UmmString &err) { + int status = WSAPoll(pollvec, pollcount, mstimeout); if (status < 0) { err = winsock_error_string(WSAGetLastError()); return -1; @@ -196,7 +191,7 @@ static int socket_poll(PollVector &pollvec, int mstimeout, std::string &err) { return status; } -static void socket_init() { +static void init_winsock() { WSADATA data; int errcode = WSAStartup(2, &data); if (errcode != 0) { @@ -205,10 +200,6 @@ static void socket_init() { } } -static void socket_uninit() { - // Nothing needed. -} - static int console_write(const char *bytes, int nbytes) { if (nbytes == 0) return 0; HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE); @@ -243,17 +234,8 @@ static int console_read(char *bytes, int nbytes) { } } -// The last element in the vector is supposed to be -// for polling stdio. But on windows, you can't poll -// stdio, so on windows, we remove the last element from -// the vector and we reduce mstimeout instead. -static void fill_stdio_pollfd(PollVector &pollvec, int &mstimeout, bool read_console_recently) { - pollvec.pop_back(); - if (mstimeout > 100) mstimeout = 100; -} - -static void disable_randomization(int argc, char *argv[]) { - // Do nothing. +void driver_sysinit(int argc, char *argv[]) { + init_winsock(); } class MonoClock { diff --git a/luprex/core/cpp/driver-util.cpp b/luprex/core/cpp/driver-util.cpp new file mode 100644 index 00000000..114b9fc7 --- /dev/null +++ b/luprex/core/cpp/driver-util.cpp @@ -0,0 +1,29 @@ + +#include "driver-util.hpp" +#include "luastack.hpp" + +namespace drv { + +void split_host_port(std::string_view target, UmmString &host, UmmString &port) { + size_t lastcolon = target.rfind(':'); + if (lastcolon == std::string_view::npos) { + host = ""; port = ""; return; + } + host = target.substr(0, lastcolon); + port = target.substr(lastcolon + 1); + if ((host == "") || (port == "")) { + host = ""; port = ""; return; + } +} + +} // namespace drv + +LuaDefine(unittests_driverutil, "", "some unit tests") { + // Test split_host_port + UmmString host, port; + drv::split_host_port("stanford.edu:80", host, port); + LuaAssertStrEq(L, host, "stanford.edu"); + LuaAssertStrEq(L, port, "80"); + + return 0; +} diff --git a/luprex/core/cpp/driver-util.hpp b/luprex/core/cpp/driver-util.hpp new file mode 100644 index 00000000..3edbcd91 --- /dev/null +++ b/luprex/core/cpp/driver-util.hpp @@ -0,0 +1,13 @@ + +#ifndef DRIVER_UTIL_HPP +#define DRIVER_UTIL_HPP + +#include "umm-malloc.hpp" + +namespace drv { + +void split_host_port(std::string_view target, UmmString &host, UmmString &port); + +} + +#endif // DRIVER_UTIL_HPP diff --git a/luprex/core/cpp/driver.hpp b/luprex/core/cpp/driver.hpp index f1d053c0..e1b0cbb6 100644 --- a/luprex/core/cpp/driver.hpp +++ b/luprex/core/cpp/driver.hpp @@ -3,6 +3,7 @@ class DrivenEngine; -void driver_drive(DrivenEngine *de, int argc, char *argv[]); +void driver_sysinit(int argc, char *argv[]); +void driver_drive(int argc, char *argv[]); #endif // DRIVER_HPP diff --git a/luprex/core/cpp/luastack.hpp b/luprex/core/cpp/luastack.hpp index f88bd34e..30b08e55 100644 --- a/luprex/core/cpp/luastack.hpp +++ b/luprex/core/cpp/luastack.hpp @@ -163,6 +163,7 @@ extern "C" { #include #include +#include class LuaSlot { protected: @@ -507,5 +508,5 @@ public: #define LuaStringify(x) #x #define LuaAssert(L, x) if (!(x)) { luaL_error((L), "Assert failed: %s (file %s line %d)", LuaStringify(x), __FILE__, __LINE__); } -#define LuaAssertStrEq(L, x, y) { std::string _s1_ = (x); std::string _s2_ = (y); if (_s1_ != _s2_) luaL_error((L), "Assert failed: value=%s (file %s line %d)", _s1_.c_str(), __FILE__, __LINE__); } +#define LuaAssertStrEq(L, x, y) { std::string _s1_(x); std::string _s2_(y); if (_s1_ != _s2_) luaL_error((L), "Assert failed: value=%s (file %s line %d)", _s1_.c_str(), __FILE__, __LINE__); } #endif // LUASTACK_HPP diff --git a/luprex/core/cpp/main.cpp b/luprex/core/cpp/main.cpp index 2e6356c8..62be566a 100644 --- a/luprex/core/cpp/main.cpp +++ b/luprex/core/cpp/main.cpp @@ -7,44 +7,21 @@ #include "source.hpp" #include -struct EngineMaker { - const char *name; - DrivenEngineMaker func; -}; - -static EngineMaker makers[] = { - { "textgame", make_TextGame }, - { "lpxclient", make_LpxClient }, - { "lpxserver", make_LpxServer }, - { "driverlistentest", make_DriverListenTest }, - { "driverwebservertest", make_DriverWebServerTest }, - { "driverdnsfailtest", make_DriverDNSFailTest }, - { "driverprintclocktest", make_DriverPrintClockTest }, - { "unittest", make_RunUnitTests }, - { nullptr, nullptr }, -}; - -static void usage() { - std::cerr << "Usage: main " << std::endl; - for (int i = 0; makers[i].name != nullptr; i++) { - std::cerr << " can be: " << makers[i].name << std::endl; - } - exit(1); -} int main(int argc, char **argv) { + driver_sysinit(argc, argv); SourceDB::register_lua_builtins(); - UniqueDrivenEngine engine; - if (argc < 2) usage(); - std::string mode = argv[1]; - for (int i = 0; makers[i].name != nullptr; i++) { - if (mode == makers[i].name) { - engine = makers[i].func(); - break; - } - } - if (engine == nullptr) usage(); - driver_drive(engine.get(), argc, argv); + + DrivenEngine::register_maker("textgame", make_TextGame); + DrivenEngine::register_maker("lpxclient", make_LpxClient); + DrivenEngine::register_maker("lpxserver", make_LpxServer); + DrivenEngine::register_maker("driverlistentest", make_DriverListenTest); + DrivenEngine::register_maker("driverwebservertest", make_DriverWebServerTest); + DrivenEngine::register_maker("driverdnsfailtest", make_DriverDNSFailTest); + DrivenEngine::register_maker("driverprintclocktest", make_DriverPrintClockTest); + DrivenEngine::register_maker("unittest", make_RunUnitTests); + + driver_drive(argc, argv); } diff --git a/luprex/core/cpp/umm-malloc.cpp b/luprex/core/cpp/umm-malloc.cpp new file mode 100644 index 00000000..323c080f --- /dev/null +++ b/luprex/core/cpp/umm-malloc.cpp @@ -0,0 +1,929 @@ +/* ---------------------------------------------------------------------------- + * umm_malloc.c - a memory allocator for embedded systems (microcontrollers) + * + * The MIT License (MIT) + * + * Copyright (c) 2015 Ralph Hempel + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ---------------------------------------------------------------------------- + * + * Note: this version is significantly modified from the version + * distributed by Ralph Hempel. In particular, block numbers are + * 32 bits in this version. + * ---------------------------------------------------------------------------- + */ + +#include +#include +#include +#include +#include +#include + +#include "umm-malloc.hpp" + +/* + * -------------------------------------------------------------------------- + * UMM_BEST_FIT (default) + * + * Set this if you want to use a best-fit algorithm for allocating new blocks. + * On by default, turned off by UMM_FIRST_FIT + * + * UMM_FIRST_FIT + * + * Set this if you want to use a first-fit algorithm for allocating new blocks. + * Faster than UMM_BEST_FIT but can result in higher fragmentation. + * + * ---------------------------------------------------------------------------- + */ + +#define UMM_BEST_FIT +#undef UMM_FIRST_FIT + + +/* + * -------------------------------------------------------------------------- + * + * Three macros to make it easier to protect the memory allocator in a + * multitasking system. You should set these macros up to use whatever your + * system uses for this purpose. You can disable interrupts entirely, or just + * disable task switching - it's up to you + * + * NOTE WELL that these macros MUST be allowed to nest, because umm_free() is + * called from within umm_malloc() + * + * -------------------------------------------------------------------------- + */ + +#define UMM_CRITICAL_DECL(tag) +#define UMM_CRITICAL_ENTRY(tag) +#define UMM_CRITICAL_EXIT(tag) + + +/* + * -------------------------------------------------------------------------- + * + * Debug Logging. + * + * -------------------------------------------------------------------------- + */ + +// #define DBGLOG(format, ...) { fprintf(stderr, format,##__VA_ARGS__); fflush(stderr); } + +#define DBGLOG(format, ...) + + +/* ------------------------------------------------------------------------- */ + +typedef struct umm_ptr_t { + uint32_t next; + uint32_t prev; +} umm_ptr; + +typedef struct umm_block_t { + union { + umm_ptr used; + } header; + union { + umm_ptr free; + uint8_t data[8]; + } body; +} umm_block; + +#define UMM_FREELIST_MASK ((uint32_t)(0x80000000)) +#define UMM_BLOCKNO_MASK ((uint32_t)(0x7FFFFFFF)) + +/* ------------------------------------------------------------------------- */ + +struct umm_heap_config { + umm_block *pheap; + size_t heap_size; + uint32_t numblocks; +}; + +static struct umm_heap_config umm_heap_current; + +#define UMM_HEAP (umm_heap_current.pheap) +#define UMM_HEAPSIZE (umm_heap_current.heap_size) +#define UMM_NUMBLOCKS (umm_heap_current.numblocks) + +#define UMM_BLOCKSIZE (sizeof(umm_block)) +#define UMM_BLOCK_LAST (UMM_NUMBLOCKS - 1) + +/* ------------------------------------------------------------------------- + * These macros evaluate to the address of the block and data respectively + */ + +#define UMM_BLOCK(b) (UMM_HEAP[b]) +#define UMM_DATA(b) (UMM_BLOCK(b).body.data) + +/* ------------------------------------------------------------------------- + * These macros evaluate to the index of the block - NOT the address!!! + */ + +#define UMM_NBLOCK(b) (UMM_BLOCK(b).header.used.next) +#define UMM_PBLOCK(b) (UMM_BLOCK(b).header.used.prev) +#define UMM_NFREE(b) (UMM_BLOCK(b).body.free.next) +#define UMM_PFREE(b) (UMM_BLOCK(b).body.free.prev) + +/* ---------------------------------------------------------------------------- + * One of the coolest things about this little library is that it's VERY + * easy to get debug information about the memory heap by simply iterating + * through all of the memory blocks. + * + * As you go through all the blocks, you can check to see if it's a free + * block by looking at the high order bit of the next block index. You can + * also see how big the block is by subtracting the next block index from + * the current block number. + * + * The umm_info function does all of that and makes the results available + * in the ummHeapInfo structure. + * ---------------------------------------------------------------------------- + */ + +typedef struct UMM_HEAP_INFO_t { + unsigned int totalEntries; + unsigned int usedEntries; + unsigned int freeEntries; + + unsigned int totalBlocks; + unsigned int usedBlocks; + unsigned int freeBlocks; + unsigned int freeBlocksSquared; + + unsigned int maxFreeContiguousBlocks; + + int usage_metric; + int fragmentation_metric; +} +UMM_HEAP_INFO; + +static UMM_HEAP_INFO ummHeapInfo; + +static void compute_usage_metric(void) +{ + if (0 == ummHeapInfo.freeBlocks) { + ummHeapInfo.usage_metric = -1; // No free blocks! + } else { + ummHeapInfo.usage_metric = (int)((ummHeapInfo.usedBlocks * 100) / (ummHeapInfo.freeBlocks)); + } +} + +static void compute_fragmentation_metric(void) +{ + if (0 == ummHeapInfo.freeBlocks) { + ummHeapInfo.fragmentation_metric = 0; // No free blocks ... so no fragmentation either! + } else { + ummHeapInfo.fragmentation_metric = 100 - (((uint32_t)(sqrtf(ummHeapInfo.freeBlocksSquared)) * 100) / (ummHeapInfo.freeBlocks)); + } +} + +#define DBGLOG_32_BIT_PTR(x) ((uint32_t)(((uintptr_t)(x)) & 0xffffffff)) + +void umm_info() { + uint32_t blockNo = 0; + + UMM_CRITICAL_DECL(id_info); + + /* Protect the critical section... */ + UMM_CRITICAL_ENTRY(id_info); + + /* + * Clear out all of the entries in the ummHeapInfo structure before doing + * any calculations.. + */ + memset(&ummHeapInfo, 0, sizeof(ummHeapInfo)); + + DBGLOG("\n"); + DBGLOG("+----------+-------+--------+--------+-------+--------+--------+\n"); + DBGLOG("|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n", + DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)), + blockNo, + UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, + UMM_PBLOCK(blockNo), + (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK) - blockNo, + UMM_NFREE(blockNo), + UMM_PFREE(blockNo)); + + /* + * Now loop through the block lists, and keep track of the number and size + * of used and free blocks. The terminating condition is an nb pointer with + * a value of zero... + */ + + blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK; + + while (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK) { + size_t curBlocks = (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK) - blockNo; + + ++ummHeapInfo.totalEntries; + ummHeapInfo.totalBlocks += curBlocks; + + /* Is this a free block? */ + + if (UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK) { + ++ummHeapInfo.freeEntries; + ummHeapInfo.freeBlocks += curBlocks; + ummHeapInfo.freeBlocksSquared += (curBlocks * curBlocks); + + if (ummHeapInfo.maxFreeContiguousBlocks < curBlocks) { + ummHeapInfo.maxFreeContiguousBlocks = curBlocks; + } + + DBGLOG("|0x%08x|B %5i|NB %5i|PB %5i|Z %5u|NF %5i|PF %5i|\n", + DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)), + blockNo, + UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, + UMM_PBLOCK(blockNo), + (uint32_t)curBlocks, + UMM_NFREE(blockNo), + UMM_PFREE(blockNo)); + + } else { + ++ummHeapInfo.usedEntries; + ummHeapInfo.usedBlocks += curBlocks; + + DBGLOG("|0x%08x|B %5i|NB %5i|PB %5i|Z %5u| |\n", + DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)), + blockNo, + UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, + UMM_PBLOCK(blockNo), + (uint32_t)curBlocks); + } + + blockNo = UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK; + } + + /* + * The very last block is used as a placeholder to indicate that + * there are no more blocks in the heap, so it cannot be used + * for anything - at the same time, the size of this block must + * ALWAYS be exactly 1 ! + */ + + DBGLOG("|0x%08x|B %5i|NB %5i|PB %5i|Z %5i|NF %5i|PF %5i|\n", + DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)), + blockNo, + UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK, + UMM_PBLOCK(blockNo), + UMM_NUMBLOCKS - blockNo, + UMM_NFREE(blockNo), + UMM_PFREE(blockNo)); + + DBGLOG("+----------+-------+--------+--------+-------+--------+--------+\n"); + + DBGLOG("Total Entries %5i Used Entries %5i Free Entries %5i\n", + ummHeapInfo.totalEntries, + ummHeapInfo.usedEntries, + ummHeapInfo.freeEntries); + + DBGLOG("Total Blocks %5i Used Blocks %5i Free Blocks %5i\n", + ummHeapInfo.totalBlocks, + ummHeapInfo.usedBlocks, + ummHeapInfo.freeBlocks); + + DBGLOG("+--------------------------------------------------------------+\n"); + + compute_usage_metric(); + DBGLOG("Usage Metric: %5i\n", ummHeapInfo.usage_metric); + + compute_fragmentation_metric(); + DBGLOG("Fragmentation Metric: %5i\n", ummHeapInfo.fragmentation_metric); + + DBGLOG("+--------------------------------------------------------------+\n"); + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(id_info); +} + +/* ------------------------------------------------------------------------ */ + +static uint32_t umm_blocks(size_t size) { + + /* + * The calculation of the block size is not too difficult, but there are + * a few little things that we need to be mindful of. + * + * When a block removed from the free list, the space used by the free + * pointers is available for data. That's what the first calculation + * of size is doing. + * + * We don't check for the special case of (size == 0) here as this needs + * special handling in the caller depending on context. For example when we + * realloc() a block to size 0 it should simply be freed. + * + * We do NOT need to check for allocating more blocks than the heap can + * possibly hold - the allocator figures this out for us. + * + * There are only two cases left to consider: + * + * 1. (size <= body) Obviously this is just one block + * 2. (blocks > (2^15)) This should return ((2^15)) to force a + * failure when the allocator runs + * + * If the requested size is greater that 32677-2 blocks (max block index + * minus the overhead of the top and bottom bookkeeping blocks) then we + * will return an incorrectly truncated value when the result is cast to + * a uint32_t. + */ + + if (size <= (sizeof(((umm_block *)0)->body))) { + return 1; + } + + /* + * If it's for more than that, then we need to figure out the number of + * additional whole blocks the size of an umm_block are required, so + * reduce the size request by the number of bytes in the body of the + * first block. + */ + + size -= (sizeof(((umm_block *)0)->body)); + + /* NOTE WELL that we take advantage of the fact that INT16_MAX is the + * number of blocks that we can index in 15 bits :-) + * + * The below expression looks wierd, but it's right. Assuming body + * size of 4 bytes and a block size of 8 bytes: + * + * BYTES (BYTES-BODY) (BYTES-BODY-1)/BLOCKSIZE BLOCKS + * 1 n/a n/a 1 + * 5 1 0 2 + * 12 8 0 2 + * 13 9 1 3 + */ + + size_t blocks = (2 + ((size-1) / (UMM_BLOCKSIZE))); + + if (blocks > (INT16_MAX)) { + blocks = INT16_MAX; + } + + return (uint32_t)blocks; +} + +/* ------------------------------------------------------------------------ */ +/* + * Split the block `c` into two blocks: `c` and `c + blocks`. + * + * - `new_freemask` should be `0` if `c + blocks` used, or `UMM_FREELIST_MASK` + * otherwise. + * + * Note that free pointers are NOT modified by this function. + */ +static void umm_split_block(uint32_t c, + uint32_t blocks, + uint32_t new_freemask) { + + UMM_NBLOCK(c + blocks) = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) | new_freemask; + UMM_PBLOCK(c + blocks) = c; + + UMM_PBLOCK(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) = (c + blocks); + UMM_NBLOCK(c) = (c + blocks); +} + +/* ------------------------------------------------------------------------ */ + +static void umm_disconnect_from_free_list(uint32_t c) { + /* Disconnect this block from the FREE list */ + + UMM_NFREE(UMM_PFREE(c)) = UMM_NFREE(c); + UMM_PFREE(UMM_NFREE(c)) = UMM_PFREE(c); + + /* And clear the free block indicator */ + + UMM_NBLOCK(c) &= (~UMM_FREELIST_MASK); +} + +/* ------------------------------------------------------------------------ + * The umm_assimilate_up() function does not assume that UMM_NBLOCK(c) + * has the UMM_FREELIST_MASK bit set. It only assimilates up if the + * next block is free. + */ + +static void umm_assimilate_up(uint32_t c) { + + if (UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK) { + + /* + * The next block is a free block, so assimilate up and remove it from + * the free list + */ + + DBGLOG("Assimilate up to next block, which is FREE\n"); + + /* Disconnect the next block from the FREE list */ + + umm_disconnect_from_free_list(UMM_NBLOCK(c)); + + /* Assimilate the next block with this one */ + + UMM_PBLOCK(UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK) = c; + UMM_NBLOCK(c) = UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK; + } +} + +/* ------------------------------------------------------------------------ + * The umm_assimilate_down() function assumes that UMM_NBLOCK(c) does NOT + * have the UMM_FREELIST_MASK bit set. In other words, try to assimilate + * up before assimilating down. + */ + +static uint32_t umm_assimilate_down(uint32_t c, uint32_t freemask) { + + // We are going to assimilate down to the previous block because + // it was free, so remove it from the fragmentation metric + + UMM_NBLOCK(UMM_PBLOCK(c)) = UMM_NBLOCK(c) | freemask; + UMM_PBLOCK(UMM_NBLOCK(c)) = UMM_PBLOCK(c); + + return UMM_PBLOCK(c); +} + +/* ------------------------------------------------------------------------- */ + +void umm_init_heap(void *ptr, size_t size) +{ + /* init heap pointer and size, and memset it to 0 */ + UMM_HEAP = (umm_block *)ptr; + UMM_HEAPSIZE = size; + UMM_NUMBLOCKS = (UMM_HEAPSIZE / UMM_BLOCKSIZE); + memset(UMM_HEAP, 0x00, UMM_HEAPSIZE); + + /* Set up umm_block[0], which just points to umm_block[1] */ + UMM_NBLOCK(0) = 1; + UMM_NFREE(0) = 1; + UMM_PFREE(0) = 1; + + /* + * Now, we need to set the whole heap space as a huge free block. We should + * not touch umm_block[0], since it's special: umm_block[0] is the head of + * the free block list. It's a part of the heap invariant. + * + * See the detailed explanation at the beginning of the file. + * + * umm_block[1] has pointers: + * + * - next `umm_block`: the last one umm_block[n] + * - prev `umm_block`: umm_block[0] + * + * Plus, it's a free `umm_block`, so we need to apply `UMM_FREELIST_MASK` + * + * And it's the last free block, so the next free block is 0 which marks + * the end of the list. The previous block and free block pointer are 0 + * too, there is no need to initialize these values due to the init code + * that memsets the entire umm_ space to 0. + */ + UMM_NBLOCK(1) = UMM_BLOCK_LAST | UMM_FREELIST_MASK; + + /* + * Last umm_block[n] has the next block index at 0, meaning it's + * the end of the list, and the previous block is umm_block[1]. + * + * The last block is a special block and can never be part of the + * free list, so its pointers are left at 0 too. + */ + + UMM_PBLOCK(UMM_BLOCK_LAST) = 1; + +// DBGLOG(true, "nblock(0) %04x pblock(0) %04x nfree(0) %04x pfree(0) %04x\n", UMM_NBLOCK(0) & UMM_BLOCKNO_MASK, UMM_PBLOCK(0), UMM_NFREE(0), UMM_PFREE(0)); +// DBGLOG(true, "nblock(1) %04x pblock(1) %04x nfree(1) %04x pfree(1) %04x\n", UMM_NBLOCK(1) & UMM_BLOCKNO_MASK, UMM_PBLOCK(1), UMM_NFREE(1), UMM_PFREE(1)); + +} + +/* ------------------------------------------------------------------------ + * Must be called only from within critical sections guarded by + * UMM_CRITICAL_ENTRY(id) and UMM_CRITICAL_EXIT(id). + */ + +static void umm_free_core(void *ptr) { + + uint32_t c; + + /* + * FIXME: At some point it might be a good idea to add a check to make sure + * that the pointer we're being asked to free up is actually within + * the umm_heap! + * + */ + + /* Figure out which block we're in. Note the use of truncated division... */ + + c = (((uint8_t *)ptr) - (uint8_t *)(&(UMM_HEAP[0]))) / UMM_BLOCKSIZE; + + DBGLOG("Freeing block %6i\n", c); + + /* Now let's assimilate this block with the next one if possible. */ + + umm_assimilate_up(c); + + /* Then assimilate with the previous block if possible */ + + if (UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK) { + + DBGLOG("Assimilate down to previous block, which is FREE\n"); + + c = umm_assimilate_down(c, UMM_FREELIST_MASK); + } else { + /* + * The previous block is not a free block, so add this one to the head + * of the free list + */ + DBGLOG("Just add to head of free list\n"); + + UMM_PFREE(UMM_NFREE(0)) = c; + UMM_NFREE(c) = UMM_NFREE(0); + UMM_PFREE(c) = 0; + UMM_NFREE(0) = c; + + UMM_NBLOCK(c) |= UMM_FREELIST_MASK; + } +} + +/* ------------------------------------------------------------------------ */ + +void umm_free(void *ptr) { + UMM_CRITICAL_DECL(id_free); + + /* If we're being asked to free a NULL pointer, well that's just silly! */ + + if ((void *)0 == ptr) { + DBGLOG("free a null pointer -> do nothing\n"); + + return; + } + + /* Free the memory withing a protected critical section */ + + UMM_CRITICAL_ENTRY(id_free); + + umm_free_core(ptr); + + UMM_CRITICAL_EXIT(id_free); +} + +/* ------------------------------------------------------------------------ + * Must be called only from within critical sections guarded by + * UMM_CRITICAL_ENTRY(id) and UMM_CRITICAL_EXIT(id). + */ + +static void *umm_malloc_core(size_t size) { + uint32_t blocks; + uint32_t blockSize = 0; + + uint32_t bestSize; + uint32_t bestBlock; + + uint32_t cf; + + blocks = umm_blocks(size); + + /* + * Now we can scan through the free list until we find a space that's big + * enough to hold the number of blocks we need. + * + * This part may be customized to be a best-fit, worst-fit, or first-fit + * algorithm + */ + + cf = UMM_NFREE(0); + + bestBlock = UMM_NFREE(0); + bestSize = 0x7FFFFFFF; + + while (cf) { + blockSize = (UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK) - cf; + + DBGLOG("Looking at block %6i size %6i\n", cf, blockSize); + + #if defined UMM_BEST_FIT + if ((blockSize >= blocks) && (blockSize < bestSize)) { + bestBlock = cf; + bestSize = blockSize; + } + #elif defined UMM_FIRST_FIT + /* This is the first block that fits! */ + if ((blockSize >= blocks)) { + break; + } + #else + #error "No UMM_*_FIT is defined - check umm_malloc_cfg.h" + #endif + + cf = UMM_NFREE(cf); + } + + if (0x7FFFFFFF != bestSize) { + cf = bestBlock; + blockSize = bestSize; + } + + if (UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK && blockSize >= blocks) { + + /* + * This is an existing block in the memory heap, we just need to split off + * what we need, unlink it from the free list and mark it as in use, and + * link the rest of the block back into the freelist as if it was a new + * block on the free list... + */ + + if (blockSize == blocks) { + /* It's an exact fit and we don't neet to split off a block. */ + DBGLOG("Allocating %6i blocks starting at %6i - exact\n", blocks, cf); + + /* Disconnect this block from the FREE list */ + + umm_disconnect_from_free_list(cf); + } else { + + /* It's not an exact fit and we need to split off a block. */ + DBGLOG("Allocating %6i blocks starting at %6i - existing\n", blocks, cf); + + /* + * split current free block `cf` into two blocks. The first one will be + * returned to user, so it's not free, and the second one will be free. + */ + umm_split_block(cf, blocks, UMM_FREELIST_MASK /*new block is free*/); + + /* + * `umm_split_block()` does not update the free pointers (it affects + * only free flags), but effectively we've just moved beginning of the + * free block from `cf` to `cf + blocks`. So we have to adjust pointers + * to and from adjacent free blocks. + */ + + /* previous free block */ + UMM_NFREE(UMM_PFREE(cf)) = cf + blocks; + UMM_PFREE(cf + blocks) = UMM_PFREE(cf); + + /* next free block */ + UMM_PFREE(UMM_NFREE(cf)) = cf + blocks; + UMM_NFREE(cf + blocks) = UMM_NFREE(cf); + } + + } else { + /* Out of memory */ + + DBGLOG("Can't allocate %5i blocks\n", blocks); + + return (void *)NULL; + } + + return (void *)&UMM_DATA(cf); +} + +/* ------------------------------------------------------------------------ */ + +void *umm_malloc(size_t size) { + UMM_CRITICAL_DECL(id_malloc); + + void *ptr = NULL; + + /* + * the very first thing we do is figure out if we're being asked to allocate + * a size of 0 - and if we are we'll simply return a null pointer. if not + * then reduce the size by 1 byte so that the subsequent calculations on + * the number of blocks to allocate are easier... + */ + + if (0 == size) { + DBGLOG("malloc a block of 0 bytes -> do nothing\n"); + + return ptr; + } + + /* Allocate the memory withing a protected critical section */ + + UMM_CRITICAL_ENTRY(id_malloc); + + ptr = umm_malloc_core(size); + + UMM_CRITICAL_EXIT(id_malloc); + + return ptr; +} + +/* ------------------------------------------------------------------------ */ + +void *umm_realloc(void *ptr, size_t size) { + UMM_CRITICAL_DECL(id_realloc); + + uint32_t blocks; + uint32_t blockSize; + uint32_t prevBlockSize = 0; + uint32_t nextBlockSize = 0; + + uint32_t c; + + size_t curSize; + + /* + * This code looks after the case of a NULL value for ptr. The ANSI C + * standard says that if ptr is NULL and size is non-zero, then we've + * got to work the same a malloc(). If size is also 0, then our version + * of malloc() returns a NULL pointer, which is OK as far as the ANSI C + * standard is concerned. + */ + + if (((void *)NULL == ptr)) { + DBGLOG("realloc the NULL pointer - call malloc()\n"); + + return umm_malloc(size); + } + + /* + * Now we're sure that we have a non_NULL ptr, but we're not sure what + * we should do with it. If the size is 0, then the ANSI C standard says that + * we should operate the same as free. + */ + + if (0 == size) { + DBGLOG("realloc to 0 size, just free the block\n"); + + umm_free(ptr); + + return (void *)NULL; + } + + /* + * Otherwise we need to actually do a reallocation. A naiive approach + * would be to malloc() a new block of the correct size, copy the old data + * to the new block, and then free the old block. + * + * While this will work, we end up doing a lot of possibly unnecessary + * copying. So first, let's figure out how many blocks we'll need. + */ + + blocks = umm_blocks(size); + + /* Figure out which block we're in. Note the use of truncated division... */ + + c = (((uint8_t *)ptr) - (uint8_t *)(&(UMM_HEAP[0]))) / UMM_BLOCKSIZE; + + /* Figure out how big this block is ... the free bit is not set :-) */ + + blockSize = (UMM_NBLOCK(c) - c); + + /* Figure out how many bytes are in this block */ + + curSize = (blockSize * UMM_BLOCKSIZE) - (sizeof(((umm_block *)0)->header)); + + /* Protect the critical section... */ + UMM_CRITICAL_ENTRY(id_realloc); + + /* Now figure out if the previous and/or next blocks are free as well as + * their sizes - this will help us to minimize special code later when we + * decide if it's possible to use the adjacent blocks. + * + * We set prevBlockSize and nextBlockSize to non-zero values ONLY if they + * are free! + */ + + if ((UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK)) { + nextBlockSize = (UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK) - UMM_NBLOCK(c); + } + + if ((UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK)) { + prevBlockSize = (c - UMM_PBLOCK(c)); + } + + DBGLOG("realloc blocks %i blockSize %i nextBlockSize %i prevBlockSize %i\n", blocks, blockSize, nextBlockSize, prevBlockSize); + + /* + * Ok, now that we're here we know how many blocks we want and the current + * blockSize. The prevBlockSize and nextBlockSize are set and we can figure + * out the best strategy for the new allocation as follows: + * + * 1. If the new block is the same size or smaller than the current block do + * nothing. + * 2. If the next block is free and adding it to the current block gives us + * EXACTLY enough memory, assimilate the next block. This avoids unwanted + * fragmentation of free memory. + * + * The following cases may be better handled with memory copies to reduce + * fragmentation + * + * 3. If the previous block is NOT free and the next block is free and + * adding it to the current block gives us enough memory, assimilate + * the next block. This may introduce a bit of fragmentation. + * 4. If the prev block is free and adding it to the current block gives us + * enough memory, remove the previous block from the free list, assimilate + * it, copy to the new block. + * 5. If the prev and next blocks are free and adding them to the current + * block gives us enough memory, assimilate the next block, remove the + * previous block from the free list, assimilate it, copy to the new block. + * 6. Otherwise try to allocate an entirely new block of memory. If the + * allocation works free the old block and return the new pointer. If + * the allocation fails, return NULL and leave the old block intact. + * + * TODO: Add some conditional code to optimise for less fragmentation + * by simply allocating new memory if we need to copy anyways. + * + * All that's left to do is decide if the fit was exact or not. If the fit + * was not exact, then split the memory block so that we use only the requested + * number of blocks and add what's left to the free list. + */ + + // Case 1 - block is same size or smaller + if (blockSize >= blocks) { + DBGLOG("realloc the same or smaller size block - %i, do nothing\n", blocks); + /* This space intentionally left blank */ + + // Case 2 - block + next block fits EXACTLY + } else if ((blockSize + nextBlockSize) == blocks) { + DBGLOG("exact realloc using next block - %i\n", blocks); + umm_assimilate_up(c); + blockSize += nextBlockSize; + + // Case 3 - prev block NOT free and block + next block fits + } else if ((0 == prevBlockSize) && (blockSize + nextBlockSize) >= blocks) { + DBGLOG("realloc using next block - %i\n", blocks); + umm_assimilate_up(c); + blockSize += nextBlockSize; + + // Case 4 - prev block + block fits + } else if ((prevBlockSize + blockSize) >= blocks) { + DBGLOG("realloc using prev block - %i\n", blocks); + umm_disconnect_from_free_list(UMM_PBLOCK(c)); + c = umm_assimilate_down(c, 0); + memmove((void *)&UMM_DATA(c), ptr, curSize); + ptr = (void *)&UMM_DATA(c); + blockSize += prevBlockSize; + + // Case 5 - prev block + block + next block fits + } else if ((prevBlockSize + blockSize + nextBlockSize) >= blocks) { + DBGLOG("realloc using prev and next block - %i\n", blocks); + umm_assimilate_up(c); + umm_disconnect_from_free_list(UMM_PBLOCK(c)); + c = umm_assimilate_down(c, 0); + memmove((void *)&UMM_DATA(c), ptr, curSize); + ptr = (void *)&UMM_DATA(c); + blockSize += (prevBlockSize + nextBlockSize); + + // Case 6 - default is we need to realloc a new block + } else { + DBGLOG("realloc a completely new block %i\n", blocks); + void *oldptr = ptr; + if ((ptr = umm_malloc_core(size))) { + DBGLOG("realloc %i to a bigger block %i, copy, and free the old\n", blockSize, blocks); + memcpy(ptr, oldptr, curSize); + umm_free_core(oldptr); + } else { + DBGLOG("realloc %i to a bigger block %i failed - return NULL and leave the old block!\n", blockSize, blocks); + /* This space intentionally left blnk */ + } + blockSize = blocks; + } + + /* Now all we need to do is figure out if the block fit exactly or if we + * need to split and free ... + */ + + if (blockSize > blocks) { + DBGLOG("split and free %i blocks from %i\n", blocks, blockSize); + umm_split_block(c, blocks, 0); + umm_free_core((void *)&UMM_DATA(c + blocks)); + } + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(id_realloc); + + return ptr; +} + + +// The openssl library expects a version of malloc that takes +// extra parameters 'file' and 'line'. We provide these for openssl. +// The extra parameters are ignored. + +void *umm_malloc_ssl(size_t size, const char *file, int line) { + return umm_malloc(size); +} + +void *umm_realloc_ssl(void *ptr, size_t size, const char *file, int line) { + return umm_realloc(ptr, size); +} + +void umm_free_ssl(void *ptr, const char *file, int line) { + umm_free(ptr); +} diff --git a/luprex/core/cpp/umm-malloc.hpp b/luprex/core/cpp/umm-malloc.hpp new file mode 100644 index 00000000..00864591 --- /dev/null +++ b/luprex/core/cpp/umm-malloc.hpp @@ -0,0 +1,108 @@ +//////////////////////////////////////////////////////////////////// +// +// To achieve determinism of replay logs, the driver is not allowed +// to touch the malloc heap. We've given the driver its own +// separate heap. The driver's heap is accessed using umm_malloc, +// umm_free, and umm_realloc. +// +// To initialize the umm heap, you allocate a single large +// block of RAM from the OS, perhaps 4M. You pass that block +// to umm_heap_init. The umm_malloc routine will allocate out of +// that block. The umm heap cannot grow, if the block fills up, umm_malloc +// will fail. It is fine to get the initial heap block from the +// regular system malloc! That doesn't compromise determinism, +// since you're always allocating the same 4M block. +// +// The UMM malloc library is distributed under the MIT license +// by Ralph Hempel. This is a slightly-modified version, you can +// find the original online. Mr. Hempel considers this to be a malloc +// for microcontrollers, but it works just fine on workstations. +// Of all the mallocs to choose from, I picked this one because it +// satisfies three main criteria: it works out of a fixed block +// of RAM, it's not terribly inefficient, and it's not too complicated +// to modify. +// +// Warning! Don't forget that using the C++ STL tends to use malloc +// all over the place. For example, if you create a std::string, +// you're using malloc! Don't use normal STL classes in the driver! +// Fortunately, most STL classes allow you to specify a custom +// allocator class. The following classes use the UMM allocator: +// +// UmmString - same as std::string. +// UmmVector - same as std::vector. +// UmmMap - same as std::map +// UmmSet - same as std::set +// +// Sadly, routines that accept std::string, std::vector, std::map, +// or std::set do not accept their Umm equivalents. It is possible +// to write a routine that accepts both std::string and UmmString +// by accepting a std::string_view parameter. Other than that, +// these types are separate. +// +//////////////////////////////////////////////////////////////////// + +#ifndef UMM_MALLOC_HPP +#define UMM_MALLOC_HPP + +#include +#include +#include +#include +#include +#include +#include + +void umm_init_heap(void *ptr, size_t size); +void *umm_malloc(size_t size); +void *umm_realloc(void *ptr, size_t size); +void umm_free(void *ptr); +void umm_info(); +void *umm_malloc_ssl(size_t size, const char *file, int line); +void *umm_realloc_ssl(void *ptr, size_t size, const char *file, int line); +void umm_free_ssl(void *ptr, const char *file, int line); + + + +template +class UmmAllocator +{ +public: + using value_type = T; + UmmAllocator() noexcept {} + template UmmAllocator(UmmAllocator const&) noexcept {} + + value_type* allocate(std::size_t n) + { + return static_cast(umm_malloc(n*sizeof(value_type))); + } + + void deallocate(value_type* p, std::size_t) noexcept + { + umm_free(p); + } +}; + +template +bool operator==(UmmAllocator const&, UmmAllocator const&) noexcept +{ + return true; +} + +template +bool operator!=(UmmAllocator const&, UmmAllocator const&) noexcept +{ + return false; +} + +using UmmString = std::basic_string, UmmAllocator>; + +template +using UmmVector = std::vector>; + +template > +using UmmMap = std::map>>; + +template > +using UmmSet = std::set>; + +#endif // UMM_MALLOC_HPP diff --git a/luprex/core/cpp/util.cpp b/luprex/core/cpp/util.cpp index 1e56a5d7..de40cc39 100644 --- a/luprex/core/cpp/util.cpp +++ b/luprex/core/cpp/util.cpp @@ -269,18 +269,6 @@ double strtodouble(const std::string &value) { } } -void split_host_port(const std::string &target, std::string &host, std::string &port) { - size_t lastcolon = target.rfind(':'); - if (lastcolon == std::string::npos) { - host = ""; port = ""; return; - } - host = target.substr(0, lastcolon); - port = target.substr(lastcolon + 1); - if ((host == "") || (port == "")) { - host = ""; port = ""; return; - } -} - std::string ltrim(std::string s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); @@ -436,12 +424,6 @@ LuaDefine(unittests_util, "", "some unit tests") { LuaAssert(L, std::isnan(util::strtodouble("12ab"))); LuaAssert(L, std::isnan(util::strtodouble(""))); - // Test split_host_port - std::string host, port; - util::split_host_port("stanford.edu:80", host, port); - LuaAssertStrEq(L, host, "stanford.edu"); - LuaAssertStrEq(L, port, "80"); - // Test trim, ltrim, rtrim LuaAssert(L, util::ltrim(" foo ") == "foo "); LuaAssert(L, util::rtrim(" foo ") == " foo"); diff --git a/luprex/core/cpp/util.hpp b/luprex/core/cpp/util.hpp index e0f905c0..9ee21628 100644 --- a/luprex/core/cpp/util.hpp +++ b/luprex/core/cpp/util.hpp @@ -2,6 +2,7 @@ #define UTIL_HPP #include +#include #include #include #include @@ -99,10 +100,6 @@ int64_t strtoint(const std::string &value, int64_t errval); // String to double. Returns NAN if the number is not parseable. double strtodouble(const std::string &value); -// Split a string of the form "hostname:port" into a hostname and a port. -// On failure, returns empty strings. -void split_host_port(const std::string &target, std::string &host, std::string &port); - // Trim strings: left end, right end, both ends. std::string ltrim(std::string s); std::string rtrim(std::string s); @@ -120,6 +117,9 @@ LuaSourcePtr make_lua_source(const std::string &code); // Return true if the line of code is a lua comment. bool is_lua_comment(const std::string &line); +// This has to go away. +LuaSourcePtr read_lua_source(const std::string &dir); + // Remove nullptrs from a vector of unique pointers. template void remove_nullptrs(std::vector> &vec) { @@ -128,9 +128,6 @@ void remove_nullptrs(std::vector> &vec) { vec.erase(iter, vec.end()); } -// Read the lua source code from the specified directory. -LuaSourcePtr read_lua_source(const std::string &directory); - // An XYZ coordinate, general purpose. struct XYZ { float x, y, z; diff --git a/luprex/core/lua/ut-globaldb.lua b/luprex/core/lua/ut-globaldb.lua index b6e4e362..431ce663 100644 --- a/luprex/core/lua/ut-globaldb.lua +++ b/luprex/core/lua/ut-globaldb.lua @@ -1,10 +1,10 @@ makeclass("unittests") function unittests.globaldb() - local g1a = global("unittest-g1") - local g2a = global("unittest-g2") - local g1b = global("unittest-g1") - local g2b = global("unittest-g2") + local g1a = global.table("unittest-g1") + local g2a = global.table("unittest-g2") + local g1b = global.table("unittest-g1") + local g2b = global.table("unittest-g2") assert(g1a == g1b) assert(g2a == g2b) assert(g1a.__global == "unittest-g1") diff --git a/luprex/core/lua/ut-tablecmp.lua b/luprex/core/lua/ut-tablecmp.lua index ce596227..53d5b101 100644 --- a/luprex/core/lua/ut-tablecmp.lua +++ b/luprex/core/lua/ut-tablecmp.lua @@ -52,9 +52,9 @@ function unittests.diffcompare() assert(tdc({}, {a=_G}, {}, {}) == "a=globals;") -- GlobalDB tables should be forced to NIL. - assert(tdc({}, {a=global("foo")}, {}, {a=global("foo")}) == "a=nil;"); - assert(tdc({}, {}, {}, {a=global("foo")}) == "a=nil;"); - assert(tdc({}, {a=global("foo")}, {}, {}) == ""); + assert(tdc({}, {a=global.table("foo")}, {}, {a=global.table("foo")}) == "a=nil;"); + assert(tdc({}, {}, {}, {a=global.table("foo")}) == "a=nil;"); + assert(tdc({}, {a=global.table("foo")}, {}, {}) == ""); -- Set up some numbered tables for tests involving such. local mtab10 = {} @@ -118,7 +118,7 @@ function unittests.diffapply() -- GlobalDB tables should be forced to NIL. rtab={a=3} - assert(not tda({}, {a=global("foo")}, rtab)) + assert(not tda({}, {a=global.table("foo")}, rtab)) assert(rtab.a == nil) -- Unnumbered tables should be forced to NIL.