├── .gitignore ├── Makefile.am ├── README ├── atom.h ├── autogen.sh ├── banzai.cc ├── configure.ac ├── dynamic_linking_loader.h ├── field_container.h ├── packet.h ├── packet_latches.h ├── pipeline.h ├── prog_to_run.cc └── stage.h /.gitignore: -------------------------------------------------------------------------------- 1 | .deps/ 2 | Makefile 3 | Makefile.in 4 | aclocal.m4 5 | autom4te.cache/ 6 | banzai 7 | *.o 8 | config.h 9 | config.h.in 10 | config.log 11 | config.status 12 | configure 13 | depcomp 14 | install-sh 15 | missing 16 | stamp-h1 17 | .libs/ 18 | compile 19 | config.guess 20 | config.sub 21 | *.la 22 | libtool 23 | ltmain.sh 24 | *.lo 25 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | AM_CXXFLAGS = $(PICKY_CXXFLAGS) 2 | 3 | common_source = pipeline.h stage.h field_container.h atom.h packet.h packet_latches.h 4 | 5 | include_HEADERS = $(common_source) 6 | bin_PROGRAMS = banzai 7 | lib_LTLIBRARIES = libprogtorun.la 8 | 9 | banzai_LDADD = -ldl 10 | banzai_SOURCES = $(common_source) banzai.cc dynamic_linking_loader.h 11 | 12 | libprogtorun_la_SOURCES = $(common_source) prog_to_run.cc 13 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | A simulator for packet programs executing within a switch pipeline. 2 | Ensure you have automake and autoconf 3 | (The build-essential + autotools-dev + libtool + autoconf packages should provide this on Ubuntu; 4 | xcode developer tools should provide this on Mac) 5 | 6 | To install, run: ./autogen.sh && ./configure && sudo make install 7 | -------------------------------------------------------------------------------- /atom.h: -------------------------------------------------------------------------------- 1 | #ifndef ATOM_H_ 2 | #define ATOM_H_ 3 | 4 | #include 5 | 6 | #include "packet.h" 7 | 8 | /// Convenience typedef for state scalar 9 | typedef FieldContainer StateScalar; 10 | 11 | /// Convenience typedef for state array 12 | typedef FieldContainer> StateArray; 13 | 14 | /// A Function object that represents an atomic unit of execution 15 | /// i.e. something that the hardware can finish before the next packet shows up 16 | /// (This includes updates to any underlying hidden state.) 17 | /// The Atom encapsulates a function that captures the functionality of this unit 18 | /// and state that can be mutated between calls to this unit. 19 | class Atom { 20 | public: 21 | /// Convenience typedef for a function that takes a packet and returns a 22 | /// new one. Represents a sequential block of code that executes within a stage. 23 | /// Could also modify state in the process. 24 | typedef std::function SequentialFunction; 25 | 26 | /// Constructor to Atom takes a SequentialFunction object and an initial value of state 27 | Atom(const SequentialFunction & t_sequential_function, const StateScalar & t_state_scalar, const StateArray & t_state_array) 28 | : sequential_function_(t_sequential_function), state_scalar_(t_state_scalar), state_array_(t_state_array) {} 29 | 30 | /// Overload function call operator 31 | void operator() (Packet & input) { 32 | assert(not input.is_bubble()); 33 | sequential_function_(input, state_scalar_, state_array_); 34 | } 35 | 36 | private: 37 | /// Underlying sequential function that implements the atomic action 38 | SequentialFunction sequential_function_; 39 | 40 | /// Hidden State that is used to implement the atomic action 41 | StateScalar state_scalar_; 42 | 43 | /// Hidden StateArray 44 | StateArray state_array_; 45 | }; 46 | 47 | #endif // ATOM_H_ 48 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | exec autoreconf -fi 4 | -------------------------------------------------------------------------------- /banzai.cc: -------------------------------------------------------------------------------- 1 | #include "packet.h" 2 | #include "pipeline.h" 3 | #include "atom.h" 4 | #include "dynamic_linking_loader.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /// Split string based on another string used as delimiter 12 | /// using C++11's regex_token_iterator 13 | /// Based on http://en.cppreference.com/w/cpp/regex/regex_token_iterator 14 | /// and http://stackoverflow.com/a/9437426/1152801 15 | std::vector split(const std::string & input, const std::string & regex_str) { 16 | std::regex regex_object(regex_str); 17 | std::sregex_token_iterator first{input.begin(), input.end(), regex_object, -1}, last; 18 | return {first, last}; 19 | } 20 | 21 | /// Get set of strings from a comma separated value string 22 | std::set string_set_from_csv(const std::string & csv) { 23 | const std::vector field_vector = split(csv, ","); 24 | return std::set(field_vector.begin(), field_vector.end()); 25 | } 26 | 27 | int main(const int argc __attribute__ ((unused)), const char ** argv __attribute__((unused))) { 28 | try { 29 | if (argc < 6) { 30 | std::cerr << "Usage: " << argv[0] << " prog_to_run_as_library seed comma_separated_input_field_list comma_seperated_output_field_set num_ticks" << std::endl; 31 | return EXIT_FAILURE; 32 | } 33 | 34 | // Get cmdline args 35 | const auto prog_to_run(argv[1]); 36 | const uint32_t seed = static_cast(std::atoi(argv[2])); 37 | const PacketFieldSet input_field_set = string_set_from_csv(std::string(argv[3])); 38 | const PacketFieldSet output_field_set = string_set_from_csv(std::string(argv[4])); 39 | const uint32_t num_ticks = static_cast(std::atoi(argv[5])); 40 | 41 | /// PRNG to generate random packet fields, 42 | std::default_random_engine prng = std::default_random_engine(seed); 43 | 44 | /// Uniform distribution over ints to generate random packet fields 45 | std::uniform_int_distribution packet_field_dist(std::numeric_limits::lowest(), std::numeric_limits::max()); 46 | 47 | // Construct shared library loader for prog_to_run 48 | DynamicLinkingLoader dynamic_linking_loader(prog_to_run); 49 | Pipeline pipeline = dynamic_linking_loader.get_object("test_pipeline"); 50 | 51 | // Run for num_ticks 52 | for (uint32_t i = 0; i < num_ticks; i++) { 53 | // Generate random input packets using packet_field_set 54 | // Construct Packet using an empty FieldContainer to signal that this isn't 55 | // a default-constructed packet, which is treated as a bubble. 56 | auto input_packet = Packet(FieldContainer()); 57 | for (const auto & field_name : input_field_set) input_packet(field_name) = packet_field_dist(prng); 58 | 59 | // Print out user-specified fields in the output_packet 60 | auto output_packet = pipeline.tick(input_packet); 61 | if (not output_packet.is_bubble()) for (const auto & field_name : output_field_set) std::cerr << field_name << " " << output_packet(field_name) << std::endl; 62 | } 63 | return EXIT_SUCCESS; 64 | } catch (const std::exception & e) { 65 | std::cerr << "Caught exception in main " << std::endl << e.what() << std::endl; 66 | return EXIT_FAILURE; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | AC_PREREQ([2.61]) 2 | AC_INIT([domino], [0.1], [anirudh@csail.mit.edu]) 3 | AM_INIT_AUTOMAKE([foreign subdir-objects]) 4 | AC_CONFIG_SRCDIR([banzai.cc]) 5 | AC_CONFIG_HEADERS([config.h]) 6 | LT_INIT([disable-static]) 7 | 8 | # Checks for programs. 9 | AC_PROG_CXX 10 | 11 | # Add picky CXXFLAGS 12 | CPPFLAGS="-std=c++14 -pthread" 13 | PICKY_CXXFLAGS="-pedantic -Wconversion -Wsign-conversion -Wall -Wextra -Weffc++ -Werror" 14 | AC_SUBST([PICKY_CXXFLAGS]) 15 | 16 | # Checks for header files. 17 | AC_LANG_PUSH(C++) 18 | 19 | AC_CHECK_HEADERS([algorithm array cassert cmath queue \ 20 | cstdio string sys/stat.h sys/types.h ctime tuple unistd.h unordered_map \ 21 | utility vector], [], [AC_MSG_ERROR([Missing header file])]) 22 | 23 | # C++ libraries are harder to check (http://nerdland.net/2009/07/detecting-c-libraries-with-autotools/), 24 | # so use headers to check 25 | 26 | # Checks for typedefs, structures, and compiler characteristics. 27 | AC_TYPE_SIZE_T 28 | AC_TYPE_UINT64_T 29 | AC_LANG_POP(C++) 30 | 31 | AC_CONFIG_FILES([Makefile]) 32 | 33 | AC_OUTPUT 34 | -------------------------------------------------------------------------------- /dynamic_linking_loader.h: -------------------------------------------------------------------------------- 1 | #ifndef DYNAMIC_LINKING_LOADER_H_ 2 | #define DYNAMIC_LINKING_LOADER_H_ 3 | 4 | #include 5 | 6 | #include 7 | 8 | /// C++ RAII interface to the Linux Dynamic Linking Loader 9 | /// i.e. dladdr, dlclose, dlerror, dlopen, dlsym, dlvsym 10 | class DynamicLinkingLoader { 11 | public: 12 | /// Construct DynamicLinkingLoader to load library_file_name 13 | DynamicLinkingLoader(const std::string & library_file_name) { 14 | // Make sure library_file_name is actually treated as a file path 15 | // by dlopen (instead of searching a bunch of env variables such as 16 | // DT_RUNPATH, DT_RPATH, and LD_LIBRARY_PATH). Check man dlopen for details. 17 | 18 | // Check if we don't have a '/' in library_file_name. If so, it's already a path. 19 | // If not, prepend "./" to force it to be treated like a relative path. 20 | std::string file_name_for_dlopen(library_file_name); 21 | if (library_file_name.find('/') == std::string::npos) { 22 | file_name_for_dlopen.insert(file_name_for_dlopen.begin(), {'.', '/'}); 23 | } 24 | 25 | // Now call dlopen eagerly (RTLD_NOW) 26 | handle_ = dlopen(file_name_for_dlopen.c_str(), RTLD_NOW); 27 | if (handle_ == nullptr) { 28 | throw std::runtime_error(dlerror()); 29 | } 30 | 31 | // Clear out dlerror() 32 | dlerror(); 33 | } 34 | 35 | /// Return a copy of an object represented by symbol_name 36 | /// by dyn_cast from void * to ReturnType* and then copy 37 | /// constructing an object of type ReturnType 38 | template 39 | ReturnType get_object(const std::string & symbol_name) { 40 | dlerror(); 41 | void * object = dlsym(handle_, symbol_name.c_str()); 42 | const char * error = dlerror(); 43 | if (error != nullptr) { 44 | throw std::runtime_error(error); 45 | } else if (object == nullptr) { 46 | throw std::runtime_error("Pointing to null object\n"); 47 | } else { 48 | // XXX: Danger 49 | return ReturnType(*static_cast(object)); 50 | } 51 | } 52 | 53 | /// Destructor for DynamicLinkingLoader 54 | ~DynamicLinkingLoader() { 55 | try { 56 | int ret = dlclose(handle_); 57 | if (ret != 0) { 58 | throw std::runtime_error(dlerror()); 59 | } 60 | dlerror(); 61 | } catch (const std::exception & e) { 62 | std::cerr << "Caught exception in destructor " << e.what() << std::endl; 63 | } 64 | } 65 | 66 | /// Delete all copy constructors, move constructors, and 67 | /// copy assignment, move assignment operators 68 | DynamicLinkingLoader(const DynamicLinkingLoader &) = delete; 69 | DynamicLinkingLoader(DynamicLinkingLoader &&) = delete; 70 | DynamicLinkingLoader & operator=(const DynamicLinkingLoader &) = delete; 71 | DynamicLinkingLoader & operator=(DynamicLinkingLoader &&) = delete; 72 | 73 | private: 74 | /// Opaque handle to library_file_name 75 | void * handle_ = nullptr; 76 | }; 77 | 78 | #endif // DYNAMIC_LINKING_LOADER_H_ 79 | -------------------------------------------------------------------------------- /field_container.h: -------------------------------------------------------------------------------- 1 | #ifndef FIELD_CONTAINER_H_ 2 | #define FIELD_CONTAINER_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | /// FieldContainer: a map from the field name as a string 11 | /// to the field value as an integer. 12 | /// Two FieldContainers, can be added (unioned) together, 13 | /// so long as they don't have the same fields 14 | /// in them, in which case it isn't clear which 15 | /// value should take precedence. 16 | /// FieldContainers can be used to represent both packets with named fields 17 | /// and state with named fields as part of that state. 18 | template 19 | class FieldContainer { 20 | public: 21 | typedef std::string FieldName; 22 | 23 | /// Constructor from map 24 | FieldContainer(const std::map & t_field_map = {}) : field_map_(t_field_map) {} 25 | 26 | /// Return reference to underlying member 27 | FieldType & operator() (const FieldName & field_name) { return field_map_[field_name]; } 28 | 29 | /// Return const reference to underlying member 30 | const FieldType & operator() (const FieldName & field_name) const { return field_map_.at(field_name); } 31 | 32 | /// Overload += operator to merge a FieldContainer into this 33 | /// as long as they have no fields in common 34 | FieldContainer & operator+=(const FieldContainer & fc) { 35 | // Check that none of fc's keys are in this ... 36 | for (const auto & key_pair : fc.field_map_) { 37 | if(this->field_map_.find(key_pair.first) != this->field_map_.end()) { 38 | if (this->field_map_.at(key_pair.first) != fc.field_map_.at(key_pair.first)) { 39 | throw std::logic_error("Can't perform FieldContainer union here: " + key_pair.first + " belongs in both " + this->str() + " and " + fc.str() + " and has different values in both"); 40 | } 41 | } 42 | } 43 | 44 | // ... and vice versa. 45 | for (const auto & key_pair : this->field_map_) { 46 | if(fc.field_map_.find(key_pair.first) != fc.field_map_.end()) { 47 | if (this->field_map_.at(key_pair.first) != fc.field_map_.at(key_pair.first)) { 48 | throw std::logic_error("Can't perform FieldContainer union here: " + key_pair.first + " belongs in both " + this->str() + " and " + fc.str() + " and has different values in both"); 49 | } 50 | } 51 | } 52 | 53 | // Collapse the fc key set 54 | for (const auto & key_pair : fc.field_map_) field_map_[key_pair.first] = key_pair.second; 55 | 56 | return *this; 57 | } 58 | 59 | /// String representation of object 60 | std::string str() const { 61 | std::string ret = "("; 62 | for (const auto & key_pair : field_map_) ret += key_pair.first + " : " + std::to_string(key_pair.second) + ", "; 63 | ret += ")"; 64 | return ret; 65 | } 66 | 67 | /// Print to stream 68 | friend std::ostream & operator<< (std::ostream & out, const FieldContainer & field_container) { 69 | out << field_container.str(); 70 | return out; 71 | } 72 | 73 | /// Get list of all fields 74 | std::vector field_list() const { 75 | std::vector ret; 76 | for (const auto & key_pair : field_map_) ret.emplace_back(key_pair.first); 77 | return ret; 78 | } 79 | 80 | private: 81 | /// Map from FieldName to field value. 82 | std::map field_map_ = {}; 83 | }; 84 | 85 | #endif // FIELD_CONTAINER_H_ 86 | -------------------------------------------------------------------------------- /packet.h: -------------------------------------------------------------------------------- 1 | #ifndef PACKET_H_ 2 | #define PACKET_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "field_container.h" 9 | 10 | /// Wrapper around FieldContainer representing a Packet 11 | /// including a Boolean denoting whether it's initialized or not. 12 | class Packet { 13 | public: 14 | typedef std::string FieldName; 15 | typedef int FieldType; 16 | 17 | /// Packet constructor 18 | Packet(const FieldContainer & t_field_container) 19 | : bubble_(false), 20 | packet_(t_field_container) {} 21 | Packet() {} 22 | 23 | /// Check if we have a bubble, to bypass packet processing 24 | bool is_bubble() const { return bubble_; } 25 | 26 | /// Reference to underlying field 27 | FieldType & operator() (const FieldName & field_name) { return packet_(field_name); } 28 | 29 | /// Overload += operator 30 | Packet & operator+=(const Packet & t_packet) { 31 | assert(not t_packet.is_bubble()); 32 | if (this->bubble_) { 33 | this->bubble_ = false; 34 | this->packet_ = t_packet.packet_; 35 | return *this; 36 | } else { 37 | assert(not t_packet.bubble_ and not this->bubble_); 38 | this->packet_ += t_packet.packet_; 39 | return *this; 40 | } 41 | } 42 | 43 | /// Print to stream 44 | friend std::ostream & operator<< (std::ostream & out, const Packet & t_packet) { 45 | if (t_packet.bubble_) out << "Bubble \n"; 46 | else out << t_packet.packet_ << "\n"; 47 | return out; 48 | } 49 | 50 | private: 51 | /// Is this a bubble? i.e. no packet 52 | bool bubble_ = true; 53 | 54 | /// Underlying FieldContainer managed by Packet 55 | FieldContainer packet_ = FieldContainer(); 56 | }; 57 | 58 | /// Typedef for an std::set representing a set of packet fields 59 | typedef std::set PacketFieldSet; 60 | 61 | #endif // PACKET_H_ 62 | -------------------------------------------------------------------------------- /packet_latches.h: -------------------------------------------------------------------------------- 1 | #ifndef PACKET_LATCHES_H_ 2 | #define PACKET_LATCHES_H_ 3 | 4 | #include 5 | 6 | #include "field_container.h" 7 | #include "packet.h" 8 | 9 | /// Two Packets representing 10 | /// the read and write halves of a pipeline register 11 | class PacketLatches { 12 | public: 13 | /// Reference to read half 14 | auto & read_half() const { return read_ ; } 15 | 16 | /// Reference to write half 17 | auto & write_half() { return write_; } 18 | 19 | /// Swap halves 20 | void swap() { std::swap(read_, write_); } 21 | 22 | private: 23 | Packet read_ = {}; 24 | Packet write_ = {}; 25 | }; 26 | 27 | #endif // PACKET_LATCHES_H_ 28 | -------------------------------------------------------------------------------- /pipeline.h: -------------------------------------------------------------------------------- 1 | #ifndef PIPELINE_H_ 2 | #define PIPELINE_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "packet_latches.h" 9 | #include "stage.h" 10 | 11 | /// A Pipeline class that repesents a switch pipeline made up of several 12 | /// stages that pass data from one stage to the next. 13 | class Pipeline { 14 | public: 15 | /// Pipeline constructor from initializer list 16 | Pipeline(const std::initializer_list & t_stages_list) : Pipeline(std::vector(t_stages_list)) {} 17 | 18 | /// Pipeline constructor 19 | Pipeline(const std::vector & t_stages) 20 | : stages_(t_stages), 21 | packet_latches_(std::vector(stages_.size() >= 1 ? stages_.size() - 1 : 0)) {} 22 | 23 | /// Tick the pipeline synchronously with input from the outside 24 | Packet tick(const Packet & packet) { 25 | assert(not stages_.empty()); 26 | 27 | /// If there's only one pipeline stage, there are no latches, directly return 28 | if (stages_.size() == 1) { 29 | assert(packet_latches_.empty()); 30 | return stages_.front().tick(packet); 31 | } else { 32 | /// Feed input to the first stage of the pipeline 33 | packet_latches_.front().write_half() = stages_.front().tick(packet); 34 | 35 | /// Execute stages 1 through n - 2 36 | for (uint32_t i = 1; i < stages_.size() - 1; i++) packet_latches_.at(i).write_half() = stages_.at(i).tick(packet_latches_.at(i - 1).read_half()); 37 | 38 | /// Execute last stage 39 | auto ret = stages_.back().tick(packet_latches_.back().read_half()); 40 | 41 | /// Swap read and write halves of packet latches akin to double buffering 42 | for (auto & packet_latch : packet_latches_) packet_latch.swap(); 43 | 44 | return ret; 45 | } 46 | } 47 | 48 | private: 49 | /// All stages that are part of the pipeline 50 | std::vector stages_; 51 | 52 | /// All latches that are part of the pipeline 53 | std::vector packet_latches_; 54 | }; 55 | 56 | #endif // PIPELINE_H_ 57 | -------------------------------------------------------------------------------- /prog_to_run.cc: -------------------------------------------------------------------------------- 1 | #include "packet.h" 2 | #include "atom.h" 3 | #include "pipeline.h" 4 | extern "C" { 5 | void atom0(Packet &packet, StateScalar & state_scalar __attribute__((unused)), StateArray & state_array __attribute__((unused))) { 6 | packet("new_hop") = (packet("sport") * packet("dport")) % 10; 7 | if (packet("arrival_time") - state_scalar("last_time") > 5) { 8 | state_scalar("next_hop") = packet("new_hop"); 9 | }; 10 | state_scalar("last_time") = packet("arrival_time"); 11 | packet("next_hop") = state_scalar("next_hop"); 12 | } 13 | PacketFieldSet test_fields({"arrival_time", "dport", "new_hop", "next_hop", 14 | "sport"}); 15 | Pipeline test_pipeline{ 16 | {Atom(atom0, StateScalar(std::map{{"last_time", 0}, {"next_hop", 0}}) 17 | , StateArray(std::map>{}))}}; 18 | } 19 | -------------------------------------------------------------------------------- /stage.h: -------------------------------------------------------------------------------- 1 | #ifndef STAGE_H_ 2 | #define STAGE_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "packet.h" 11 | #include "atom.h" 12 | 13 | /// The core logic that captures the packet processing functionality 14 | /// of a stage. The semantics of a stage are parallel execution 15 | /// of all the underlying atoms. All state for a physical stage is 16 | /// encapsulated within these atoms. 17 | /// This class also passes data between the input of this stage 18 | /// and the output from this stage i.e. between 19 | /// the read and write pipeline registers on either side of the stage's 20 | /// combinational circuit. (i.e. input and output from Stage::tick()) 21 | class Stage { 22 | public: 23 | /// Constructor for Stage that takes an std::initializer_list 24 | Stage(const std::initializer_list & t_atom_list) : Stage(std::vector(t_atom_list)) {}; 25 | 26 | /// Constructor for Stage that takes an Atom vector 27 | Stage(const std::vector & t_atoms) : atoms_(t_atoms) {}; 28 | 29 | /// Tick this stage by calling all atoms 30 | /// and combining their outputs. 31 | Packet tick(const Packet & input) { 32 | if (input.is_bubble()) { 33 | return Packet(); 34 | } else { 35 | Packet ret; 36 | /// These functions can be executed in any order 37 | /// A shuffle emulates this non determinisim. 38 | std::random_shuffle(atoms_.begin(), atoms_.end()); 39 | for (auto & atom : atoms_) { 40 | Packet tmp = input; 41 | atom(tmp); 42 | ret += tmp; 43 | } 44 | return ret; 45 | } 46 | } 47 | 48 | private: 49 | /// Atom vector, to be executed in parallel 50 | std::vector atoms_; 51 | }; 52 | 53 | #endif // STAGE_H_ 54 | --------------------------------------------------------------------------------