diff --git a/.gitignore b/.gitignore index 259148f..a75adc9 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ *.exe *.out *.app +.vscode/settings.json +exchange/.bash_history +.gitignore +exchange/exchange-emu.ext2 diff --git a/exchange/.gitignore b/exchange/.gitignore new file mode 100644 index 0000000..18e684e --- /dev/null +++ b/exchange/.gitignore @@ -0,0 +1,15 @@ +*.dSYM/** +.DS_store +.vscode +lambda-state +*.o +exchange +exchange-emu +exchange-linux +exchange-cli +exchange-cli-emu +exchange-cli-linux +state.bin + + + diff --git a/exchange/Makefile b/exchange/Makefile new file mode 100644 index 0000000..9541175 --- /dev/null +++ b/exchange/Makefile @@ -0,0 +1,90 @@ +TOOLCHAIN:=cartesi/toolchain:0.15.0 +LINUX:=gcc:12.2.0 + +CXX = g++ +CXXFLAGS = -std=c++17 +SRCS = exchange-cli.cpp state-mgr.cpp + +RUN_TOOLCHAIN = \ + docker run \ + -e USER=$$(id -u -n) \ + -e GROUP=$$(id -g -n) \ + -e UID=$$(id -u) \ + -e GID=$$(id -g) \ + -v `pwd`:/home/$$(id -u -n) \ + -w /home/$$(id -u -n) \ + --rm \ + -$(INTERACTIVE)t $(TOOLCHAIN) + +RUN_LINUX = \ + docker run \ + -e USER=$$(id -u -n) \ + -e GROUP=$$(id -g -n) \ + -e UID=$$(id -u) \ + -e GID=$$(id -g) \ + -v `pwd`:/home/$$(id -u -n) \ + -w /home/$$(id -u -n) \ + --rm \ + -$(INTERACTIVE)t $(LINUX) + +RUN_CARTESI_MACHINE := \ + cartesi-machine.lua \ + --append-dtb-bootargs="single=yes" \ + --flash-drive="label:fs,filename:exchange-cli-emu.ext2" \ + --flash-drive=label:"state.bin,filename:state.bin,shared" \ + -i + +exchange-cli: $(SRCS) + $(CXX) $(CXXFLAGS) -o $@ $^ + +STATE_SIZE := 8192 +STATE_FILE = state.bin +$(STATE_FILE): exchange-cli-linux + dd if=/dev/zero of=$@ bs=1 count=$(STATE_SIZE) + $(RUN_LINUX) ./exchange-cli-linux --init-state --setup-test-fixture --lambda-state $(STATE_FILE) + chmod 777 $(STATE_FILE) + +init-state: + rm -f $(STATE_FILE) + $(MAKE) $(STATE_FILE) + +exchange-cli-emu.ext2: exchange-cli-emu $(STATE_FILE) + mkdir -p fs + cp -f exchange-cli-emu $(STATE_FILE) fs/ + $(RUN_TOOLCHAIN) genext2fs -f -i 512 -b 8192 -d fs $@ + truncate -s %4096 $@ + +exchange-cli-emu: CXX = $(RUN_TOOLCHAIN) riscv64-cartesi-linux-gnu-g++ +exchange-cli-emu: %.o = $(RUN_TOOLCHAIN) g++ +exchange-cli-emu: $(SRCS) + $(CXX) $(CXXFLAGS) -o $@ $^ + +exchange-cli-linux: CXX = $(RUN_LINUX) g++ +exchange-cli-linux: %.o = $(RUN_LINUX) g++ +exchange-cli-linux: $(SRCS) + $(CXX) $(CXXFLAGS) -o $@ $^ + +cartesi-machine: exchange-cli-emu.ext2 + $(RUN_CARTESI_MACHINE) /bin/sh + +run-emu: exchange-cli-emu.ext2 + $(RUN_CARTESI_MACHINE) /mnt/fs/exchange-cli-emu --interactive --lambda-state /mnt/fs/state.bin + +run-linux: INTERACTIVE =i +run-linux: exchange-cli-emu.ext2 + $(RUN_LINUX) ./exchange-cli-linux --lambda-state state.bin --interactive + + +%.o: %.cpp $(wildcard *.h) + $(CXX) $(CXXFLAGS) -c $< -o $@ + +toolchain-env: INTERACTIVE =i +toolchain-env: + $(RUN_TOOLCHAIN) /bin/bash + +linux-env: INTERACTIVE =i +linux-env: + $(RUN_LINUX) /bin/sh + +clean: + rm -f $(OBJS) exchange-cli exchange-cli-emu exchange-cli-linux diff --git a/exchange/README.md b/exchange/README.md new file mode 100644 index 0000000..56ff8b4 --- /dev/null +++ b/exchange/README.md @@ -0,0 +1,73 @@ +# Toy Exchange + +## Build +``` +make +``` + +## Run initializing state +``` +dd if=/dev/zero of=lambda-state bs=1 count=8192 +./exchange --interactive --init-state --setup-test-fixture --lambda-state ./lambda-state +``` + +## Run using existing state +``` +./exchange --interactive --lambda-state ./lambda-state +``` + +## Run interactive tests +``` +./exchange --interactive --lambda-state ./lambda-state +gold:exchange mpernambuco$ ./exchange --interactive --init-state --setup-test-fixture --lambda-state /Users/mpernambuco/ctsi2/hackaton/lambadex/exchange/lambda-state +init state.... + +> book ctsi/usdc +id tradr qty bid | ask qty tradr id +0 perna 40 111 | 112 50 diego 0 +0 perna 50 110 | 120 100 diego 0 +0 perna 100 100 | + +> wallet perna +ctsi: 1000000 +usdc: 980060 + +> wallet diego +ctsi: 999850 +usdc: 1000000 + +> deposit perna brl 10 +> wallet perna +brl: 10 +ctsi: 1000000 +usdc: 980060 + + +> buy ctsi/brl perna 10 1 +Execution - Trader: perna Order ID: 0 Execution Type: N Side: B Quantity: 1 Price: 10 + + +> book ctsi/brl +id tradr qty bid | ask qty tradr id +0 perna 1 10 | + +> sell ctsi/brl diego 10 1 +Execution - Trader: diego Order ID: 0 Execution Type: N Side: S Quantity: 1 Price: 10 +Execution - Trader: perna Order ID: 0 Execution Type: E Side: B Quantity: 1 Price: 10 +Execution - Trader: diego Order ID: 0 Execution Type: E Side: S Quantity: 1 Price: 10 +Enter command. Format is: (buy|sell|book) symbol trader price qty + +> book ctsi/brl +id tradr qty bid | ask qty tradr id + +> wallet perna +brl: 0 +ctsi: 1000001 +usdc: 980060 + +> wallet diego +brl: 10 +ctsi: 999848 +usdc: 1000000 + +``` diff --git a/exchange/exchange-cli.cpp b/exchange/exchange-cli.cpp new file mode 100644 index 0000000..592f08e --- /dev/null +++ b/exchange/exchange-cli.cpp @@ -0,0 +1,251 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "exchange.h" + +#define TEST_SYMBOL "ctsi/usdc" + +void print_book(FILE *out,const book &book) { + auto b = book.bids.begin(); + auto a = book.asks.begin(); + fprintf(out, "id \ttradr\tqty\tbid | ask\tqty \ttradr\tid\n"); + while (b!=book.bids.end() || a!=book.asks.end()) { + if (b!=book.bids.end()) { + fprintf(out, "%d\t%s\t%d\t%d", b->id, b->trader.data(), b->qty, b->price); + } else { + fprintf(out, " \t \t \t "); + } + fprintf(out, " | "); + if (a!=book.asks.end()) { + fprintf(out, "%d\t%d\t%s\t%d", a->price, a->qty, a->trader.data(), a->id); + } else { + fprintf(out, " \t \t \t "); + } + fprintf(out, "\n"); + if (b!=book.bids.end()) ++b; + if (a!=book.asks.end()) ++a; + } +} + +void print_wallet(FILE *out, const wallet_type &wallet) { + for(auto &entry: wallet) { + fprintf(out, "%s: %d\n", entry.first.data(), entry.second); + } +} + +static void print_execution_report(FILE *out, execution_report r) { + fprintf(out, "Execution - Trader: %s ", r.trader.data()); + fprintf(out, "Order ID: %d ", r.order_id); + fprintf(out, "Execution Type: %c ", (char)r.type); + fprintf(out, "Side: %c ", (char)r.side); + fprintf(out, "Quantity: %d ", r.qty); + fprintf(out, "Price: %d", r.price); + fprintf(out, "Text: %s\n", r.text.c_str()); +} + +static void setup_test_fixture(exchange &ex) { + // // setup playground + execution_reports_type r; + ex.deposit(trader_type{"perna"}, token_type{"ctsi"}, 1000000); + auto x = ex.find_wallet(trader_type{"perna"}); + print_wallet(stdout, *x); + ex.deposit(trader_type{"perna"}, token_type{"usdc"}, 1000000); + ex.deposit(trader_type{"diego"}, token_type{"ctsi"}, 1000000); + ex.deposit(trader_type{"diego"}, token_type{"usdc"}, 1000000); + + ex.new_order(order{0, "perna", TEST_SYMBOL, side_type::buy, 100, 100}, r); + ex.new_order(order{0, "diego", TEST_SYMBOL, side_type::sell, 120, 100}, r); + ex.new_order(order{0, "perna", TEST_SYMBOL, side_type::buy, 110, 50}, r); + ex.new_order(order{0, "perna", TEST_SYMBOL, side_type::buy, 111, 40}, r); + ex.new_order(order{0, "diego", TEST_SYMBOL, side_type::sell, 112, 50}, r); + auto *book = ex.find_book(symbol_type{TEST_SYMBOL}); + print_book(stdout, *book); +} + +static bool print_error(FILE *out, const char* message) { + fprintf(out, "ERROR: %s\n", message); + return false; +} + +static bool process_input(exchange &ex, FILE *in, FILE *out, bool interactive = false) { + char cmd[1024]; + std::string error_message; + symbol_type symbol; + token_type token; + trader_type trader; + currency_type price; + qty_type qty; + execution_reports_type reports; + memset(cmd, 0, sizeof(cmd)); + memset(symbol.data(), 0, sizeof(symbol)); + memset(token.data(), 0, sizeof(token)); + memset(trader.data(), 0, sizeof(trader)); + if (1 != fscanf(in, "%s", cmd)) { + return print_error(out, "missing command"); + } + if (0 == strcmp(cmd, "q")) { + return false; + } + if (0 == strcmp(cmd, "buy")) { + if (4 != fscanf(in, "%s %s %d %d", symbol.data(), trader.data(), &price, &qty)) { + return print_error(out, "insufficient arguments for buy command"); + } + ex.new_order(order{0, trader, symbol, side_type::buy, price, qty}, reports); + } else if (0 == strcmp(cmd, "sell")) { + if (4 != fscanf(in, "%s %s %d %d", symbol.data(), trader.data(), &price, &qty)) { + return print_error(out, "insufficient arguments for sell command"); + } + ex.new_order(order{0, trader, symbol, side_type::sell, price, qty}, reports); + } else if (0 == strcmp(cmd, "book")) { + if (1 != fscanf(in, "%s", symbol.data())) { + return print_error(out, "insufficient arguments for book command"); + } + auto *pbook = ex.find_book(symbol); + // todo: return book in standard output format + if (pbook) { + print_book(out, *pbook); + } else { + fprintf(out, "No book for symbol %s\n", symbol.data()); + } + } else if (0 == strcmp(cmd, "wallet")) { + if (1 != fscanf(in, "%s", trader.data())) { + return print_error(out, "insufficient arguments for book command"); + } + auto *pwallet = ex.find_wallet(trader); + if (pwallet) { + print_wallet(out, *pwallet); + } else { + fprintf(out, "No wallet for trader %s\n", trader.data()); + } + } else if (0 == strcmp(cmd, "deposit")) { + if (3 != fscanf(in, "%s %s %d", trader.data(), token.data(), &qty)) { + return print_error(out, "insufficient arguments for sell command"); + } + ex.deposit(trader, token, qty); + } else if (0 == strcmp(cmd, "withdraw")) { + if (3 != fscanf(in, "%s %s %d", trader.data(), token.data(), &qty)) { + return print_error(out, "insufficient arguments for sell command"); + } + if (!ex.withdraw(trader, token, qty, &error_message)) { + printf("ERROR: %s\n", error_message.c_str()); + } + } else { + return print_error(out, "invalid command"); + } + // TODO: convert the execution reports in standard output formant suitable for parsing + for(auto &report: reports) { + print_execution_report(out, report); + } + return true; +} + +static void process_inputs(exchange &ex, bool interactive) { + while(true) { + if (interactive) { + fprintf(stdout, "Enter command. Format is: (buy|sell|book) symbol trader price qty\n> "); + } + if (!process_input(ex, stdin, stdout, true)) { + return; + } + } +} + +static void print_help_and_exit(const char* program_name) { + printf("Usage: %s --lambda-state= [options...] \n", program_name); + printf("Options:\n"); + printf(" --lambda-state= Where to read the lambda state from\n"); + printf(" --setup-test-fixture Setup test fixture\n"); + printf(" --interactive Interactive mode\n"); + printf(" --help Show help\n"); + exit(0); +} + +int main(int argc, char** argv) { + //unique_ptr state; + std::unique_ptr state; + + std::string opt_lambda_state; + bool opt_init_state = false; + bool opt_setup_test_fixture = false; + bool opt_interactive = false; + bool opt_help = false; + + // parse command line arguments + for (int i = 1; i < argc; i++) { + std::string arg = argv[i]; + if (arg == "--lambda-state") { + if (i + 1 < argc) { + opt_lambda_state = argv[++i]; + } else { + std::cerr << "--state-file option requires a filename argument" << std::endl; + return 1; + } + } else if (arg == "--setup-test-fixture") { + opt_setup_test_fixture = true; + } else if (arg == "--init-state") { + opt_init_state = true; + } else if (arg == "--interactive") { + opt_interactive = true; + } else if (arg == "--help") { + opt_help = true; + } else { + std::cerr << "Unknown option: " << arg << std::endl; + return 1; + } + } + + // handle commands + if (opt_help) { + print_help_and_exit(argv[0]); + } + + // map lambda state + if (opt_lambda_state.empty()) { + std::cerr << "--lambda-state is required" << std::endl; + return 1; + } + if (opt_lambda_state.find("0x") == 0) { + auto length_index = opt_lambda_state.find(':'); + if (length_index == std::string::npos) { + std::cerr << "--lambda-state must be in the format 0x:" << std::endl; + return 1; + } + auto phys_addr = std::stoull(opt_lambda_state.substr(2, length_index-2), nullptr, 16); + auto length = std::stoull(opt_lambda_state.substr(length_index+1), nullptr, 16); + state = std::unique_ptr(new lambda_state(phys_addr, length)); + } else { + // dd if=/dev/zero of=lambda-state bs=1 count=8192 + state = std::unique_ptr(new lambda_state(opt_lambda_state.c_str())); + } + + // initialize arena + arena = reinterpret_cast(state->get_state()); + exchange *pex = reinterpret_cast(arena->get_data()); + if (opt_init_state) { + printf("init state....\n"); + // initialize arena + arena = new (state->get_state()) memory_arena(state->get_length()); + memset(arena->get_data(), 0, arena->get_data_length()); + // create master object at the start of the arena + void *root = arena->allocate(sizeof(exchange)); + pex = new (root) exchange(); + if (opt_setup_test_fixture) { + // setup test fixtureexchange ex; + setup_test_fixture(*pex); + } + } else { + process_inputs(*pex, opt_interactive); + } + return 0; +} + diff --git a/exchange/exchange.h b/exchange/exchange.h new file mode 100644 index 0000000..946575b --- /dev/null +++ b/exchange/exchange.h @@ -0,0 +1,301 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "state-mgr.h" + +using symbol_type = std::array; +using token_type = std::array; +using currency_type = int; +using qty_type = int; +using trader_type = std::array; +using id_type = int; +using request_id_type = int; +enum class side_type : char { buy = 'B', sell = 'S' }; +enum class exec_type : char { + new_order = 'N', // ack new order + cancel = 'C', // ack cancel + execution = 'E', // trade execution (partial or full) + rejection = 'R' // order rejection +}; +using contract_address_type = std::string; +using request_type = char; + +struct new_order_request { + id_type id; + trader_type trader; + symbol_type symbol; + side_type side; + currency_type price; + qty_type qty; +}; + +struct deposit_request { + id_type id; + trader_type trader; + token_type token; + currency_type amount; +}; + +struct withdraw_request { + id_type id; + trader_type trader; + token_type token; + currency_type amount; +}; + +// Trading instrument +struct instrument { + token_type base; // token being traded + token_type quote; // quote token - "price " of 1 base token +}; + +// Token exchange order +struct order { + id_type id; // order id provided by the exchange + trader_type trader; // trader id + symbol_type symbol; // instrument's symbol (ticker) + side_type side; // buy or sell + currency_type price; // limit price in instrument.quote + qty_type qty; // remaining quantity + + bool matches(const order& other) { + return (side == side_type::buy && price >= other.price) || (side == side_type::sell && price <= other.price); + } + + bool is_filled() { + return qty == 0; + } + +}; + +// comparator for sorting bid offers +struct best_bid { + bool operator()(const order &a, const order &b) const { + return a.price > b.price; + } +}; + +// comparator for sortting ask offers +struct best_ask { + bool operator()(const order &a, const order &b) const { + return a.price < b.price; + } +}; + + using bids_type = std::multiset>; + using asks_type = std::multiset>; + +struct book { + symbol_type symbol; + bids_type bids; + asks_type asks; +}; + +// exchange notifications +struct execution_report { + trader_type trader; // trader id + id_type order_id; // order id + exec_type type; // execution type + side_type side; // side of executed order + qty_type qty; // qtd executed + currency_type price; // execution price + std::string text; // optional text +}; +using execution_reports_type = std::vector; + +using wallet_type = std::map, arena_allocator>>; +using books_type = std::map, arena_allocator>>; +using wallets_type = std::map, arena_allocator>>; +using tokens_type = std::map, arena_allocator>>; +using instruments_type = std::map, arena_allocator>>; + +// exchange class to be "deserialized" from lambda state +class exchange { + tokens_type tokens; + instruments_type instruments; + books_type books; + wallets_type wallets; + id_type next_id{0}; + +public: + exchange() { + tokens[token_type{"ctsi"}] = contract_address_type{"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}; + tokens[token_type{"usdc"}] = contract_address_type{"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}; + instruments[symbol_type{"ctsi/usdc"}] = instrument{token_type{"ctsi"}, token_type{"usdc"}}; + } + + bool new_order(order o, execution_reports_type &reports) { + // validate order + auto instrument = instruments.find(o.symbol); + if (instrument == instruments.end()) { + reports.push_back({o.trader, o.id, exec_type::rejection, o.side, o.qty, o.price, "Invalid symbol"}); + return false; + } + if (o.side == side_type::buy) { + auto size = o.qty * o.price;; + auto balance = get_balance(o.trader, instrument->second.quote); + if (balance < size) { + reports.push_back({o.trader, o.id, exec_type::rejection, o.side, o.qty, o.price, "Insufficient funds"}); + return false; + } + subtract_from_balance(o.trader, instrument->second.quote, size); + } else { + auto size = o.qty; + auto balance = get_balance(o.trader, instrument->second.base); + if (balance < size) { + reports.push_back({o.trader, o.id, exec_type::rejection, o.side, o.qty, o.price, "Insufficient funds"}); + return false; + } + subtract_from_balance(o.trader, instrument->second.base, size); + } + + // send report acknowledging new order + reports.push_back({o.trader, o.id, exec_type::new_order, o.side, o.qty, o.price}); + // match against existing orders + auto &book = find_or_create_book(o.symbol); + if (o.side == side_type::buy) { + match(o, book.asks, instrument->second, reports); + if (!o.is_filled()) { + book.bids.insert(o); + } + } else { + match(o, book.bids, instrument->second, reports); + if (!o.is_filled()) { + book.asks.insert(o); + } + } + return true; + } + + wallet_type* find_wallet(const trader_type &trader) { + auto it = wallets.find(trader); + if (it != wallets.end()) { + return &it->second; + } + return nullptr; + } + + book *find_book(const symbol_type &symbol) { + auto it = books.find(symbol); + if (it != books.end()) { + return &it->second; + } + return nullptr; + } + + void deposit(const trader_type &trader, const token_type &token, currency_type amount) { + add_to_balance(trader, token, amount); + } + + bool withdraw(const trader_type trader, const token_type token, currency_type amount, std::string *error_message=nullptr) { + if (get_balance(trader, token) < amount) { + if(error_message) { + *error_message = "insufficient funds"; + } + + return false; + } + subtract_from_balance(trader, token, amount); + return true; + } + +private: + + book &find_or_create_book(const symbol_type &symbol) { + auto it = books.find(symbol); + if (it != books.end()) { + return it->second; + } + return books.emplace(symbol, book{symbol, {}, {}}).first->second;; + } + + wallet_type &find_or_create_wallet(const trader_type &trader) { + auto it = wallets.find(trader); + if (it != wallets.end()) { + return it->second; + } + return wallets.emplace(trader, wallet_type{}).first->second;; + } + + void subtract_from_balance(const trader_type &trader, const token_type &token, currency_type amount) { + if (tokens.find(token) == tokens.end()) { + throw std::runtime_error("invalid token"); + } + auto &wallet = find_or_create_wallet(trader); + wallet[token] -= amount; + } + + void add_to_balance(const trader_type &trader, const token_type &token, currency_type amount) { + if (tokens.find(token) == tokens.end()) { + throw std::runtime_error("invalid token"); + } + auto &wallet = find_or_create_wallet(trader); + wallet[token] += amount; + } + + // match order against existing offers, executing trades and notifying both parties + template + void match(order& o, T &offers, instrument &instr, execution_reports_type& reports) { + auto it = offers.begin(); + while(it != offers.end()) { + // ok to drop const becasue the set is ordered by a custom comparator whose key(price) won't be changed + auto &best_offer = const_cast(*it); + if (o.is_filled() || !o.matches(best_offer)) { + return; + } + auto &buy_order = o.side == side_type::buy ? o : best_offer; + auto &sell_order = o.side == side_type::buy ? best_offer : o; + auto &buyer = buy_order.trader; + auto &seller = sell_order.trader; + // execute trade and notify both parties + auto exec_qty = std::min(o.qty, best_offer.qty); + buy_order.qty -= exec_qty; + sell_order.qty -= exec_qty; + // exchange tokens + auto exec_price = (o.price + best_offer.price) /2; + add_to_balance(buyer, instr.quote, exec_qty * buy_order.price); // add balance locked at the limit order price + subtract_from_balance(buyer, instr.quote, exec_qty * exec_price); // subtract balance at the execution price + add_to_balance(buyer, instr.base, exec_qty); // add bought tokens + subtract_from_balance(seller, instr.base, exec_qty); // subtract sold tokens + add_to_balance(seller, instr.quote, exec_qty * exec_price); // add balance at the execution price + // notify both parties + reports.push_back({buyer, buy_order.id, exec_type::execution, side_type::buy, exec_qty, exec_price}); + reports.push_back({seller, sell_order.id, exec_type::execution, side_type::sell, exec_qty, exec_price}); + // remove offer from book if filled + if (best_offer.is_filled()) { + offers.erase(it); + } + // find next best offer + it = offers.begin(); + } + } + + currency_type get_balance(const trader_type &trader, const token_type &token) { + auto wallet = find_wallet(trader); + if (!wallet) { + return 0; + } + auto it = wallet->find(token); + if (it == wallet->end()) { + return 0; + } + return it->second; + } + + + id_type get_next_id() { + return ++next_id; + } +}; + + diff --git a/exchange/state-mgr.cpp b/exchange/state-mgr.cpp new file mode 100644 index 0000000..30ee68c --- /dev/null +++ b/exchange/state-mgr.cpp @@ -0,0 +1,3 @@ +#include "state-mgr.h" + +memory_arena *arena = nullptr; diff --git a/exchange/state-mgr.h b/exchange/state-mgr.h new file mode 100644 index 0000000..a3f7498 --- /dev/null +++ b/exchange/state-mgr.h @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#include +#include + +#define LAMBDA_VIRTUAL_START UINT64_C(0x1000000000) + +class memory_arena { + uint64_t m_length; + uint64_t m_next_free; + unsigned char m_data[0]; +public: + memory_arena(uint64_t length) : m_length(length), m_next_free(0) { } + void *get_data() { + return m_data; + } + void *allocate(int length) { + auto p = m_data + m_next_free; + m_next_free += length; + if (m_next_free > get_data_length()) { + throw std::bad_alloc(); + } + return p; + } + void deallocate(void *p, int length) { + } + uint64_t get_data_length() { + return m_length - offsetof(memory_arena, m_data); + } + +}; + +extern memory_arena *arena; + +template +class arena_allocator { +public: + arena_allocator() = default; + using pointer = T*; + using value_type = T; + pointer allocate(std::size_t n) { + return static_cast(arena->allocate(n * sizeof(T))); + } + void deallocate(pointer p, std::size_t n) noexcept { + arena->deallocate(p, n * sizeof(T)); + } +}; + +class lambda_state { + int m_fd; + uint64_t m_length = 0; + void *m_state; + +public: + lambda_state(const char *file_name) { + m_length = std::filesystem::file_size(file_name); + m_fd = open(file_name, O_RDWR); + m_state = mmap((void*)LAMBDA_VIRTUAL_START, m_length, PROT_WRITE | PROT_READ, MAP_SHARED | MAP_FIXED, m_fd, 0); + if (m_state == MAP_FAILED) { + throw std::runtime_error(std::string("mmap - ") + std::strerror(errno)); + } + if (m_state != reinterpret_cast(LAMBDA_VIRTUAL_START)) { + close(m_fd); + throw std::runtime_error("mmap - unexpected address"); + } + } + + lambda_state(uint64_t phys_start, uint64_t length) : m_length(length) { + m_length = length; + m_fd = open("/dev/mem", O_RDWR); + m_state = mmap((void*)LAMBDA_VIRTUAL_START, m_length, PROT_WRITE | PROT_READ, MAP_SHARED | MAP_FIXED, m_fd, phys_start); + if (m_state == MAP_FAILED) { + throw std::runtime_error(std::string("mmap - ") + std::strerror(errno)); + } + if (m_state != reinterpret_cast(LAMBDA_VIRTUAL_START)) { + close(m_fd); + throw std::runtime_error("mmap - unexpected address"); + } + } + + virtual ~lambda_state() { + printf("munmap\n"); + munmap(m_state, m_length); + close(m_fd); + } + + uint64_t get_length() { + return m_length; + } + void *get_state() { + return m_state; + } + +};