Add BPF support & C-based BPF tic-tac-toe (#1422)

Add initial support for BPF and a C port of tictactoe
This commit is contained in:
jackcmay
2018-10-04 09:44:44 -07:00
committed by GitHub
parent 74b63c12a0
commit 13d4443d4d
27 changed files with 1321 additions and 124 deletions

View File

@ -0,0 +1,9 @@
#!/bin/bash -ex
OUTDIR="${1:-../../../target/release/}"
THISDIR=$(dirname "$0")
mkdir -p "$OUTDIR"
/usr/local/opt/llvm/bin/clang -Werror -target bpf -O2 -emit-llvm -fno-builtin -o "$OUTDIR"/move_funds_c.bc -c "$THISDIR"/src/move_funds.c
/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -function-sections -o "$OUTDIR"/move_funds_c.o "$OUTDIR"/move_funds_c.bc
#/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble "$OUTDIR"/move_funds_c.o

View File

@ -0,0 +1,3 @@
#!/bin/sh
/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/release/move_funds_c.o

View File

@ -0,0 +1,130 @@
//#include <stdint.h>
//#include <stddef.h>
#if 1
// one way to define a helper function is with index as a fixed value
#define BPF_TRACE_PRINTK_IDX 6
static int (*sol_print)(int, int, int, int, int) = (void *)BPF_TRACE_PRINTK_IDX;
#else
// relocation is another option
extern int sol_print(int, int, int, int, int);
#endif
typedef long long unsigned int uint64_t;
typedef long long int int64_t;
typedef unsigned char uint8_t;
typedef enum { false = 0, true } bool;
#define SIZE_PUBKEY 32
typedef struct {
uint8_t x[SIZE_PUBKEY];
} SolPubkey;
typedef struct {
SolPubkey *key;
int64_t tokens;
uint64_t userdata_len;
uint8_t *userdata;
SolPubkey *program_id;
} SolKeyedAccounts;
// TODO support BPF function calls rather then forcing everything to be inlined
#define SOL_FN_PREFIX __attribute__((always_inline)) static
// TODO move this to a registered helper
SOL_FN_PREFIX void sol_memcpy(void *dst, void *src, int len) {
for (int i = 0; i < len; i++) {
*((uint8_t *)dst + i) = *((uint8_t *)src + i);
}
}
#define sol_panic() _sol_panic(__LINE__)
SOL_FN_PREFIX void _sol_panic(uint64_t line) {
sol_print(0, 0, 0xFF, 0xFF, line);
char *pv = (char *)1;
*pv = 1;
}
SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccounts *ka,
uint8_t **userdata, uint64_t *userdata_len) {
if (num_ka != *(uint64_t *)src) {
return -1;
}
src += sizeof(uint64_t);
// TODO fixed iteration loops ok? unrolled?
for (int i = 0; i < num_ka; i++) { // TODO this should end up unrolled, confirm
// key
ka[i].key = (SolPubkey *)src;
src += SIZE_PUBKEY;
// tokens
ka[i].tokens = *(uint64_t *)src;
src += sizeof(uint64_t);
// account userdata
ka[i].userdata_len = *(uint64_t *)src;
src += sizeof(uint64_t);
ka[i].userdata = src;
src += ka[i].userdata_len;
// program_id
ka[i].program_id = (SolPubkey *)src;
src += SIZE_PUBKEY;
}
// tx userdata
*userdata_len = *(uint64_t *)src;
src += sizeof(uint64_t);
*userdata = src;
return 0;
}
// -- Debug --
SOL_FN_PREFIX void print_key(SolPubkey *key) {
for (int j = 0; j < SIZE_PUBKEY; j++) {
sol_print(0, 0, 0, j, key->x[j]);
}
}
SOL_FN_PREFIX void print_userdata(uint8_t *data, int len) {
for (int j = 0; j < len; j++) {
sol_print(0, 0, 0, j, data[j]);
}
}
SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka,
uint8_t *userdata, uint64_t userdata_len) {
sol_print(0, 0, 0, 0, num_ka);
for (int i = 0; i < num_ka; i++) {
// key
print_key(ka[i].key);
// tokens
sol_print(0, 0, 0, 0, ka[i].tokens);
// account userdata
print_userdata(ka[i].userdata, ka[i].userdata_len);
// program_id
print_key(ka[i].program_id);
}
// tx userdata
print_userdata(userdata, userdata_len);
}
void entrypoint(char *buf) {
SolKeyedAccounts ka[3];
uint64_t userdata_len;
uint8_t *userdata;
if (0 != sol_deserialize((uint8_t *)buf, 3, ka, &userdata, &userdata_len)) {
return;
}
print_params(3, ka, userdata, userdata_len);
}

View File

@ -0,0 +1,8 @@
[package]
name = "noop_rust"
version = "0.1.0"
authors = ["Jack May <jack@solana.com>"]
[dependencies]
rbpf = { git = "https://github.com/solana-labs/rbpf" }
solana = { path = "../../.." }

10
programs/bpf/noop_rust/build.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash -ex
# TODO building release flavor with rust produces a bunch of output .bc files
INTERDIR=../../../target/release
OUTDIR="${1:-../../../target/debug/}"
mkdir -p "$OUTDIR"
# cargo +nightly rustc --release -- -C panic=abort --emit=llvm-ir
cargo +nightly rustc --release -- -C panic=abort --emit=llvm-bc
cp "$INTERDIR"/deps/noop_rust-*.bc "$OUTDIR"/noop_rust.bc
/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -o "$OUTDIR"/noop_rust.o "$OUTDIR"/noop_rust.bc

3
programs/bpf/noop_rust/dump.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble target/release/noop_rust.o

View File

@ -0,0 +1,15 @@
extern crate rbpf;
use std::mem::transmute;
#[no_mangle]
#[link_section = ".text,entrypoint"] // TODO platform independent needed
pub extern "C" fn entrypoint(_raw: *mut u8) {
let bpf_func_trace_printk = unsafe {
transmute::<u64, extern "C" fn(u64, u64, u64, u64, u64)>(
rbpf::helpers::BPF_TRACE_PRINTK_IDX as u64,
)
};
bpf_func_trace_printk(0, 0, 1, 2, 3);
}

View File

@ -0,0 +1,9 @@
#!/bin/bash -ex
OUTDIR="${1:-../../../target/release/}"
THISDIR=$(dirname "$0")
mkdir -p "$OUTDIR"
/usr/local/opt/llvm/bin/clang -Werror -target bpf -O2 -emit-llvm -fno-builtin -o "$OUTDIR"/tictactoe_c.bc -c "$THISDIR"/src/tictactoe.c
/usr/local/opt/llvm/bin/llc -march=bpf -filetype=obj -function-sections -o "$OUTDIR"/tictactoe_c.o "$OUTDIR"/tictactoe_c.bc
# /usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble "$OUTDIR"/tictactoe_c.o

View File

@ -0,0 +1,3 @@
#!/bin/sh
/usr/local/opt/llvm/bin/llvm-objdump -color -source -disassemble ../../../target/release/tictactoe_c.o

View File

@ -0,0 +1,57 @@
# Notes:
# TODO needs more investigation into what r10 is used for
# -O2 adds changes the generated code from:
# main:
# 0: b7 01 00 00 00 00 00 00 r1 = 0
# 1: 63 1a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r1
# 2: bf 10 00 00 00 00 00 00 r0 = r1
# 3: 95 00 00 00 00 00 00 00 exit
# To:
# main:
# 0: b7 00 00 00 00 00 00 00 r0 = 0
# 1: 95 00 00 00 00 00 00 00 exit
# - building bpf module that includes stdint.h with clang only (no w/ llc) results in an error
# $(TOOLS_DIR)/clang -nostdlib -O2 -fpie -target bpf -o bpf.o -c bpf.c
# => n file included from /usr/include/sys/_types/_intptr_t.h:30:
# => /usr/include/machine/types.h:37:2: error: architecture not supported
# => error architecture not supported
TOOLS_DIR = /usr/local/opt/llvm/bin
test_x86.o: test.c
$(TOOLS_DIR)/clang -O2 -fpie -target x86_64 -o $@ -c $<
test_x86.so: test_x86.o
$(TOOLS_DIR)/ld.lld --shared -o $@ $<
test_x86_dylib.o: test.c
$(TOOLS_DIR)/clang -O2 -fpie -target x86_64-apple-darwin13.0.0 -o $@ -c $<
test_x86.dylib: test_x86_dylib.o
/usr/local/opt/llvm/bin/ld64.lld -dylib -lc -arch x86_64 -o $@ $<
# TODO does not work if pulling in stdlib, claims unsupported architecture
bpf_clang.o: bpf.c
$(TOOLS_DIR)/clang -nostdlib -O2 -fpie -target bpf -o bpf.o -c bpf.c
bpf.o: bpf.c
$(TOOLS_DIR)/clang -O2 -emit-llvm -c $< -o - | $(TOOLS_DIR)/llc -march=bpf -filetype=obj -o $@
bpf_rust.o: bpf_rust.rs
rustc +nightly -C opt-level=2 -C panic=abort --emit llvm-bc $<
$(TOOLS_DIR)/llc -march=bpf -filetype=obj -function-sections -o $@ bpf_rust.bc
dumpall:
$(TOOLS_DIR)/llvm-objdump -color -source -disassemble *.o
cleanall:
rm -f *.o
rm -f *.so
rm -f *.dylib
rm -f *.ll
rm -rf *.bc
all: bpf_clang.o bpf.o

View File

@ -0,0 +1,370 @@
//#include <stdint.h>
//#include <stddef.h>
#if 1
#define BPF_TRACE_PRINTK_IDX 6
static int (*sol_print)(int, int, int, int, int) = (void *)BPF_TRACE_PRINTK_IDX;
#else
// relocation is another option
extern int sol_print(int, int, int, int, int);
#endif
typedef long long unsigned int uint64_t;
typedef long long int int64_t;
typedef unsigned char uint8_t;
typedef enum { false = 0, true } bool;
#define SIZE_PUBKEY 32
typedef struct {
uint8_t x[SIZE_PUBKEY];
} SolPubkey;
typedef struct {
SolPubkey *key;
int64_t tokens;
uint64_t userdata_len;
uint8_t *userdata;
SolPubkey *program_id;
} SolKeyedAccounts;
// TODO support BPF function calls rather then forcing everything to be inlined
#define SOL_FN_PREFIX __attribute__((always_inline)) static
// TODO move this to a registered helper
SOL_FN_PREFIX void sol_memcpy(void *dst, void *src, int len) {
for (int i = 0; i < len; i++) {
*((uint8_t *)dst + i) = *((uint8_t *)src + i);
}
}
#define sol_panic() _sol_panic(__LINE__)
SOL_FN_PREFIX void _sol_panic(uint64_t line) {
sol_print(0, 0, 0xFF, 0xFF, line);
char *pv = (char *)1;
*pv = 1;
}
SOL_FN_PREFIX int sol_deserialize(uint8_t *src, uint64_t num_ka, SolKeyedAccounts *ka,
uint8_t **userdata, uint64_t *userdata_len) {
if (num_ka != *(uint64_t *)src) {
return -1;
}
src += sizeof(uint64_t);
// TODO fixed iteration loops ok? unrolled?
for (int i = 0; i < num_ka; i++) { // TODO this should end up unrolled, confirm
// key
ka[i].key = (SolPubkey *)src;
src += SIZE_PUBKEY;
// tokens
ka[i].tokens = *(uint64_t *)src;
src += sizeof(uint64_t);
// account userdata
ka[i].userdata_len = *(uint64_t *)src;
src += sizeof(uint64_t);
ka[i].userdata = src;
src += ka[i].userdata_len;
// program_id
ka[i].program_id = (SolPubkey *)src;
src += SIZE_PUBKEY;
}
// tx userdata
*userdata_len = *(uint64_t *)src;
src += sizeof(uint64_t);
*userdata = src;
return 0;
}
// -- TicTacToe --
// Board Coodinates
// | 0,0 | 1,0 | 2,0 |
// | 0,1 | 1,1 | 2,1 |
// | 0,2 | 1,2 | 2,2 |
typedef enum {
Result_Ok,
Result_Panic,
Result_GameInProgress,
Result_InvalidArguments,
Result_InvalidMove,
Result_InvalidUserdata,
Result_InvalidTimestamp,
Result_NoGame,
Result_NotYourTurn,
Result_PlayerNotFound,
Result_UserdataTooSmall,
} Result;
typedef enum { BoardItem_F, BoardItem_X, BoardItem_O } BoardItem;
typedef enum {
State_Waiting,
State_XMove,
State_OMove,
State_XWon,
State_OWon,
State_Draw,
} State;
typedef struct {
SolPubkey player_x;
SolPubkey player_o;
State state;
BoardItem board[9];
int64_t keep_alive[2];
} Game;
typedef enum {
Command_Init = 0,
Command_Join,
Command_KeepAlive,
Command_Move,
} Command;
SOL_FN_PREFIX void game_dump_board(Game *self) {
sol_print(0, 0, 0x9, 0x9, 0x9);
sol_print(0, 0, self->board[0], self->board[1], self->board[2]);
sol_print(0, 0, self->board[3], self->board[4], self->board[5]);
sol_print(0, 0, self->board[6], self->board[7], self->board[8]);
sol_print(0, 0, 0x9, 0x9, 0x9);
}
SOL_FN_PREFIX void game_create(Game *self, SolPubkey *player_x) {
sol_memcpy(self->player_x.x, player_x, SIZE_PUBKEY);
// TODO self->player_o = 0;
self->state = State_Waiting;
self->keep_alive[0] = 0;
self->keep_alive[1] = 0;
// TODO fixed iteration loops ok? unrolled?
for (int i = 0; i < 9; i++) {
self->board[i] = BoardItem_F;
}
}
SOL_FN_PREFIX Result game_join(Game *self, SolPubkey *player_o,
int64_t timestamp) {
if (self->state == State_Waiting) {
sol_memcpy(self->player_o.x, player_o, SIZE_PUBKEY);
self->state = State_XMove;
if (timestamp <= self->keep_alive[1]) {
return Result_InvalidTimestamp;
} else {
self->keep_alive[1] = timestamp;
return Result_Ok;
}
}
return Result_GameInProgress;
}
SOL_FN_PREFIX bool game_same(BoardItem x_or_o, BoardItem one, BoardItem two,
BoardItem three) {
if (x_or_o == one && x_or_o == two && x_or_o == three) {
return true;
}
return false;
}
SOL_FN_PREFIX bool game_same_player(SolPubkey *one, SolPubkey *two) {
// TODO fixed iteration loops ok? unrolled?
for (int i = 0; i < SIZE_PUBKEY; i++) {
if (one->x[i] != two->x[i]) {
return false;
}
}
return true;
}
SOL_FN_PREFIX Result game_next_move(Game *self, SolPubkey *player, int x, int y) {
int board_index = y * 3 + x;
if (board_index >= 9 || self->board[board_index] != BoardItem_F) {
return Result_InvalidMove;
}
BoardItem x_or_o;
State won_state;
switch (self->state) {
case State_XMove:
if (!game_same_player(player, &self->player_x)) {
return Result_PlayerNotFound;
}
self->state = State_OMove;
x_or_o = BoardItem_X;
won_state = State_XWon;
break;
case State_OMove:
if (!game_same_player(player, &self->player_o)) {
return Result_PlayerNotFound;
}
self->state = State_XMove;
x_or_o = BoardItem_O;
won_state = State_OWon;
break;
default:
return Result_NotYourTurn;
}
self->board[board_index] = x_or_o;
// game_dump_board(self);
bool winner =
// Check rows
game_same(x_or_o, self->board[0], self->board[1], self->board[2]) ||
game_same(x_or_o, self->board[3], self->board[4], self->board[5]) ||
game_same(x_or_o, self->board[6], self->board[7], self->board[8]) ||
// Check columns
game_same(x_or_o, self->board[0], self->board[3], self->board[6]) ||
game_same(x_or_o, self->board[1], self->board[4], self->board[7]) ||
game_same(x_or_o, self->board[2], self->board[5], self->board[8]) ||
// Check both diagonals
game_same(x_or_o, self->board[0], self->board[4], self->board[8]) ||
game_same(x_or_o, self->board[2], self->board[4], self->board[6]);
if (winner) {
self->state = won_state;
}
{
int draw = true;
// TODO fixed iteration loops ok? unrolled?
for (int i = 0; i < 9; i++) {
if (BoardItem_F == self->board[i]) {
draw = false;
break;
}
}
if (draw) {
self->state = State_Draw;
}
}
return Result_Ok;
}
SOL_FN_PREFIX Result game_keep_alive(Game *self, SolPubkey *player,
int64_t timestamp) {
switch (self->state) {
case State_Waiting:
case State_XMove:
case State_OMove:
if (game_same_player(player, &self->player_x)) {
if (timestamp <= self->keep_alive[0]) {
return Result_InvalidTimestamp;
}
self->keep_alive[0] = timestamp;
} else if (game_same_player(player, &self->player_o)) {
if (timestamp <= self->keep_alive[1]) {
return Result_InvalidTimestamp;
}
self->keep_alive[1] = timestamp;
} else {
return Result_PlayerNotFound;
}
break;
default:
break;
}
return Result_Ok;
}
void entrypoint(uint8_t *buf) {
SolKeyedAccounts ka[3];
uint64_t userdata_len;
uint8_t *userdata;
int err = 0;
if (0 != sol_deserialize(buf, 3, ka, &userdata, &userdata_len)) {
sol_panic();
}
if (sizeof(Game) > ka[1].userdata_len) {
sol_print(0, 0, 0xFF, sizeof(Game), ka[1].userdata_len);
sol_panic();
}
Game game;
sol_memcpy(&game, ka[1].userdata, ka[1].userdata_len);
Command command = *userdata;
sol_print(0, 0, 0, 0, command);
switch (command) {
case Command_Init:
game_create(&game, ka[2].key);
break;
case Command_Join:
err = game_join(&game, ka[0].key, userdata[8]);
break;
case Command_KeepAlive:
err = game_keep_alive(&game, ka[0].key, /*TODO*/ 0);
break;
case Command_Move:
err = game_next_move(&game, ka[0].key, userdata[8], userdata[9]);
break;
default:
sol_panic();
}
sol_memcpy(ka[1].userdata, &game, ka[1].userdata_len);
sol_print(0, 0, 0, err, game.state);
}
// // -- Debug --
// SOL_FN_PREFIX void print_key(SolPubkey *key) {
// for (int j = 0; j < SIZE_PUBKEY; j++) {
// sol_print(0, 0, 0, j, key->x[j]);
// }
// }
// SOL_FN_PREFIX void print_userdata(uint8_t *data, int len) {
// for (int j = 0; j < len; j++) {
// sol_print(0, 0, 0, j, data[j]);
// }
// }
// SOL_FN_PREFIX void print_params(uint64_t num_ka, SolKeyedAccounts *ka,
// uint8_t *userdata, uint64_t userdata_len) {
// sol_print(0, 0, 0, 0, num_ka);
// for (int i = 0; i < num_ka; i++) {
// // key
// print_key(ka[i].key);
// // tokens
// sol_print(0, 0, 0, 0, ka[i].tokens);
// // account userdata
// print_userdata(ka[i].userdata, ka[i].userdata_len);
// // program_id
// print_key(ka[i].program_id);
// }
// // tx userdata
// print_userdata(userdata, userdata_len);
// }
// void entrypoint(char *buf) {
// SolKeyedAccounts ka[3];
// uint64_t userdata_len;
// uint8_t *userdata;
// if (0 != sol_deserialize((uint8_t *)buf, 3, ka, &userdata_len, &userdata)) {
// return;
// }
// print_params(3, ka, userdata, userdata_len);
// }

View File

@ -15,7 +15,7 @@ authors = [
[dependencies]
bincode = "1.0.0"
generic-array = { version = "0.12.0", default-features = false, features = ["serde"] }
solana_program_interface = { path = "../../common" }
solana_program_interface = { path = "../../../common" }
[lib]
name = "move_funds"

View File

@ -13,7 +13,7 @@ authors = [
]
[dependencies]
solana_program_interface = { path = "../../common" }
solana_program_interface = { path = "../../../common" }
[lib]
name = "noop"

View File

@ -3,4 +3,7 @@ extern crate solana_program_interface;
use solana_program_interface::account::KeyedAccount;
#[no_mangle]
pub extern "C" fn process(_infos: &mut Vec<KeyedAccount>, _data: &[u8]) {}
pub extern "C" fn process(_infos: &mut Vec<KeyedAccount>, _data: &[u8]) {
//println!("AccountInfos: {:#?}", _infos);
//println!("data: {:#?}", data);
}

View File

@ -1,21 +0,0 @@
[package]
name = "print"
version = "0.1.0"
authors = [
"Anatoly Yakovenko <anatoly@solana.com>",
"Greg Fitzgerald <greg@solana.com>",
"Stephen Akridge <stephen@solana.com>",
"Michael Vines <mvines@solana.com>",
"Rob Walker <rob@solana.com>",
"Pankaj Garg <pankaj@solana.com>",
"Tyera Eulberg <tyera@solana.com>",
"Jack May <jack@solana.com>",
]
[dependencies]
solana_program_interface = { path = "../../common" }
[lib]
name = "print"
crate-type = ["dylib"]

View File

@ -1,9 +0,0 @@
extern crate solana_program_interface;
use solana_program_interface::account::KeyedAccount;
#[no_mangle]
pub extern "C" fn process(infos: &mut Vec<KeyedAccount>, _data: &[u8]) {
println!("AccountInfos: {:#?}", infos);
//println!("data: {:#?}", data);
}