Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add C example, handwritten example, and generic tester #5

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions C/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.PHONY: all

#LLVM_BUILD_DIR=/path/to/llvm-build
LLVM_BUILD_DIR=/home/user/repos/benchmarking/wasmception/build/llvm
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the upstream LLVM has wasm target enabled by default. How do you want to handle the LLVM installation?

Copy link
Author

@poemm poemm May 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is old and outdated. I don't want this repo to include the wrc20.c code anymore. I think that this PR should be closed, but am waiting for feedback before I close.

This is a great question which I need advice for in the current wrc20.c repo https://github.com/poemm/C_ewasm_contracts . It is good that newer version of ubuntu have LLVM 8+ by default. But getting LLVM 8+ may be a challenge for some users. The linked repo gives instructions in the README and compilation commands in the Makefile, but, in my experience, I have had to restart the compilation several times before it finished. Maybe this conversation should be moved to that repo, but I am happy to continue here.


all:
$(LLVM_BUILD_DIR)/bin/clang -cc1 -Ofast -emit-llvm -triple=wasm32-unknown-unknown-wasm wrc20.c
$(LLVM_BUILD_DIR)/bin/llc -O3 -filetype=obj wrc20.ll -o wrc20.o
$(LLVM_BUILD_DIR)/bin/wasm-ld --no-entry wrc20.o -o wrc20.wasm --strip-all -allow-undefined-file=wrc20.syms -export=_main
rm wrc20.ll wrc20.o

1 change: 1 addition & 0 deletions C/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
To see how to convert `wrc20.wasm` to be meet ewasm requirements, see the tutorial https://github.com/ewasm/testnet/blob/e86d665364adfde7232feaaae178c31543734ff2/c_cpp_guide.md .
133 changes: 133 additions & 0 deletions C/wrc20.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@


// to avoid includes from libc, just hard-code things
typedef unsigned char uint8_t;
typedef int uint32_t;
typedef unsigned long long uint64_t;


// types used for Ethereum stuff
typedef uint8_t* bytes; // an array of bytes with unrestricted length
typedef uint8_t bytes32[32]; // an array of 32 bytes
typedef uint8_t address[20]; // an array of 20 bytes
typedef unsigned __int128 u128; // a 128 bit number, represented as a 16 bytes long little endian unsigned integer in memory
//typedef uint256_t u256; // a 256 bit number, represented as a 32 bytes long little endian unsigned integer in memory
typedef uint32_t i32; // same as i32 in WebAssembly
typedef uint32_t i32ptr; // same as i32 in WebAssembly, but treated as a pointer to a WebAssembly memory offset
typedef uint64_t i64; // same as i64 in WebAssembly

// functions for ethereum stuff
void useGas(i64 amount);
void getCaller(i32ptr* resultOffset);
// memory offset to load the address into (address)
i32 getCallDataSize();
void callDataCopy(i32ptr* resultOffset, i32 dataOffset, i32 length);
// memory offset to load data into (bytes), the offset in the input data, the length of data to copy
void revert(i32ptr* dataOffset, i32 dataLength);
void finish(i32ptr* dataOffset, i32 dataLength);
void storageStore(i32ptr* pathOffset, i32ptr* resultOffset);
void storageLoad(i32ptr* pathOffset, i32ptr* resultOffset);
//the memory offset to load the path from (bytes32), the memory offset to store/load the result at (bytes32)
void printMemHex(i32ptr* offset, i32 length);
void printStorageHex(i32ptr* key);


i64 reverse_bytes(i64 a){
i64 b = 0;
b += (a & 0xff00000000000000)>>56;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace += with |= and magic happens: https://godbolt.org/z/GYL1L0
Unfortunately, wasm does not have bswap support.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

But Wasm has no bswap opcode. I would have to look at the output wasm, but don't think there is a difference since addition + and or | both use the ALU in one cycle.

b += (a & 0x00ff000000000000)>>40;
b += (a & 0x0000ff0000000000)>>24;
b += (a & 0x000000ff00000000)>>8;
b += (a & 0x00000000ff000000)<<8;
b += (a & 0x0000000000ff0000)<<24;
b += (a & 0x000000000000ff00)<<40;
b += (a & 0x00000000000000ff)<<56;
return b;
}


// global data used in next function, will be allocated to WebAssembly memory
bytes32 addy[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
bytes32 balance[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

void do_balance() {
if (getCallDataSize() != 24)
revert(0, 0);

// get address to check balance of, note: padded to 32 bytes since used as key
callDataCopy((i32ptr*)addy, 4, 20);

// get balance
storageLoad((i32ptr*)addy, (i32ptr*)balance);

// return balance
finish((i32ptr*)balance, 32);
}


// global data used in next function, will be allocated to WebAssembly memory
bytes32 sender[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
bytes32 recipient[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
bytes32 value[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
bytes32 recipient_balance[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
bytes32 sender_balance[1] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

void do_transfer() {
if (getCallDataSize() != 32)
revert(0, 0);

// get caller
getCaller((i32ptr*)sender);

// get recipient from message at byte 4, length 20
callDataCopy((i32ptr*)recipient, 4, 20);

// get amount to transfer from message at byte 24, length 8
callDataCopy((i32ptr*)value, 24, 8);
*(i64*)value = reverse_bytes(*(i64*)value);

// get balances
storageLoad((i32ptr*)sender, (i32ptr*)sender_balance);
storageLoad((i32ptr*)recipient, (i32ptr*)recipient_balance);
*(i64*)sender_balance = reverse_bytes(*(i64*)sender_balance);
*(i64*)recipient_balance = reverse_bytes(*(i64*)recipient_balance);

// make sure sender has enough
if (*(i64*)sender_balance < *(i64*)value)
revert(0, 0);

// adjust balances
* (i64*)sender_balance -= * (i64*)value;
* (i64*)recipient_balance += * (i64*)value;

// store results
*(i64*)sender_balance = reverse_bytes(*(i64*)sender_balance);
*(i64*)recipient_balance = reverse_bytes(*(i64*)recipient_balance);
storageStore((i32ptr*)sender, (i32ptr*)sender_balance);
storageStore((i32ptr*)recipient, (i32ptr*)recipient_balance);

}


// global data used in next function, will be allocated to WebAssembly memory
i32 selector[1] = {0};

void _main(void) {
if (getCallDataSize() < 4)
revert(0, 0);

// get first four bytes of message, use for calling
callDataCopy((i32ptr*)selector, 0, 4); //from byte 0, length 4

// call function based on selector value
switch (*selector) {
case 0x1a029399:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious, why are the selector values here different from those in the example pseudo-code for this repository?

Copy link
Author

@poemm poemm Apr 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that you are referring to the wrc20 spec https://gist.github.com/axic/16158c5c88fbc7b1d09dfa8c658bc363 where the selector has value 0x9993021a. The selector here is reversed because callDataCopy() puts the call data into wasm memory, and Wasm reverses the bytes when it loads/stores memory to the stack. I could have also done reverse_bytes() on selector and used the same selector byte-ordering as the wrc20 spec.

do_balance();
break;
case 0xbd9f355d:
do_transfer();
break;
default:
revert(0, 0);
}
}
10 changes: 10 additions & 0 deletions C/wrc20.syms
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
useGas
getCaller
getCallDataSize
callDataCopy
revert
finish
storageStore
storageLoad
printMemHex
printStorageHex
Binary file added C/wrc20.wasm
Binary file not shown.
Binary file added C/wrc20_ewasmified.wasm
Binary file not shown.
Binary file not shown.
184 changes: 184 additions & 0 deletions handwritten/wrc20_handwritten_faster_get_balance.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
(module
(func $revert (import "ethereum" "revert") (param i32 i32))
(func $finish (import "ethereum" "finish") (param i32 i32))
(func $getCallDataSize (import "ethereum" "getCallDataSize") (result i32))
(func $callDataCopy (import "ethereum" "callDataCopy") (param i32 i32 i32))
(func $storageLoad (import "ethereum" "storageLoad") (param i32 i32))
(func $storageStore (import "ethereum" "storageStore") (param i32 i32))
(func $getCaller (import "ethereum" "getCaller") (param i32))
(memory (export "memory") 1)
(func (export "main")
block
block
call $getCallDataSize
i32.const 4
i32.ge_u
br_if 0
i32.const 0
i32.const 0
call $revert
br 1
end
i32.const 0 ;;selector, 4 bytes
i32.const 0
i32.const 4
call $callDataCopy
block
i32.const 0 ;;load selector
i32.load
i32.const 0x1a029399
i32.eq
i32.eqz
br_if 0
call $do_balance
br 1
end
block
i32.const 0 ;;load selector
i32.load
i32.const 0xbd9f355d
i32.eq
i32.eqz
br_if 0
call $do_transfer
br 1
end
i32.const 0
i32.const 0
call $revert
end)
(func $do_balance
block
block
call $getCallDataSize
i32.const 24
i32.eq
br_if 0
i32.const 0
i32.const 0
call $revert
br 1
end
i32.const 0 ;;address to bytes 0-31, last 12 bytes are 0-padded
i32.const 4
i32.const 20
call $callDataCopy
i32.const 0 ;; get token balance of address in bytes 0-31, put in bytes 32-63
i32.const 32
call $storageLoad
i32.const 32 ;; return first 8 bytes of balance
i32.const 8
call $finish
end)
(func $do_transfer
(local i64 i64 i64) ;;sender_balance, recipient_balance, value
block
block
call $getCallDataSize
i32.const 32
i32.eq
br_if 0
i32.const 0
i32.const 0
call $revert
br 1
end
;; memory bytes 0 32 64
;; senderAddy recipientAddy tmpForTokenValues
i32.const 0 ;;sender address to bytes 0-19 (storage key uses bytes 0-31)
call $getCaller
i32.const 32 ;;recipient address to bytes 32-51 (storage key uses bytes 32-63)
i32.const 4
i32.const 20
call $callDataCopy
i32.const 64 ;;temporarily put transfer_value in bytes 64-71, reverse 8 msb, put in in local 0
i32.const 24
i32.const 8
call $callDataCopy
i32.const 64
i64.load
call $i64.reverse_bytes
set_local 0
i32.const 0 ;;temporarily put sender_balance into bytes 64-95, reverse 8 msb, put it in local 1
i32.const 64
call $storageLoad
i32.const 64
i64.load
call $i64.reverse_bytes
set_local 1
i32.const 32 ;;temporarily put recipient_balance into bytes 64-95, reverse 8 msb, put in local 2
i32.const 64
call $storageLoad
i32.const 64
i64.load
call $i64.reverse_bytes
set_local 2
block ;; if transver_value < sender_balance, then revert
get_local 0
get_local 1
i64.le_u
br_if 0
i32.const 0
i32.const 0
call $revert
br 1
end
get_local 1 ;;sender_balance -= value
get_local 0
i64.sub
set_local 1
get_local 2 ;;recipient_balance += value
get_local 0
i64.add
set_local 2
i32.const 64 ;;reverse sender_balance, write to memory, put in storage
get_local 1
call $i64.reverse_bytes
i64.store
i32.const 0
i32.const 64
call $storageStore
i32.const 64 ;;reverse recipient_balance, write to memory, put in storage
get_local 2
call $i64.reverse_bytes
i64.store
i32.const 32
i32.const 64
call $storageStore
end)
(func $i64.reverse_bytes (param i64) (result i64)
(local i64 i64) ;;iter variable, val to return
block
loop
get_local 1 ;;iter variable
i64.const 8
i64.ge_u
br_if 1
get_local 0 ;;original
i64.const 56 ;;shift left
get_local 1
i64.const 8
i64.mul
i64.sub
i64.shl
i64.const 56 ;;shift right
i64.shr_u
i64.const 56 ;;shift left
i64.const 8
get_local 1
i64.mul
i64.sub
i64.shl
get_local 2 ;;update
i64.add
set_local 2
get_local 1 ;;iter+=1
i64.const 1
i64.add
set_local 1
br 0
end
end
get_local 2
)
)
Binary file added handwritten/wrc20_handwritten_faster_transfer.wasm
Binary file not shown.
Loading