diff --git a/ports/webassembly/Makefile b/ports/webassembly/Makefile index 435636531cc26..bfd518910ad16 100644 --- a/ports/webassembly/Makefile +++ b/ports/webassembly/Makefile @@ -46,7 +46,7 @@ INC += -I$(BUILD) INC += -I$(VARIANT_DIR) CFLAGS += -std=c99 -Wall -Werror -Wdouble-promotion -Wfloat-conversion -CFLAGS += -Os -DNDEBUG +CFLAGS += -Oz -fdata-sections -ffunction-sections -DNDEBUG CFLAGS += $(INC) EXPORTED_FUNCTIONS_EXTRA += ,\ @@ -107,6 +107,7 @@ SRC_SHARED = $(addprefix shared/,\ ) SRC_C += \ + mp_stubs.c \ lexer_dedent.c \ main.c \ modjs.c \ @@ -123,6 +124,12 @@ SRC_JS += \ objpyproxy.js \ proxy_js.js \ +SRC_C_RELOCATABLE = \ + near_api.c \ + modnear.c + +SRC_C_ALL_RELOCATABLE = $(SRC_C) $(SRC_C_RELOCATABLE) + OBJ += $(PY_O) OBJ += $(addprefix $(BUILD)/, $(SRC_SHARED:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) @@ -157,3 +164,29 @@ test_min: $(BUILD)/micropython.min.mjs $(TOP)/tests/run-tests.py # Remaining make rules. include $(TOP)/py/mkrules.mk + +OBJ_EXCLUDE = \ + $(BUILD)/py/modio.o \ + $(BUILD)/py/modsys.o \ + $(BUILD)/py/modbuiltins.o + + +#OBJ_WASM = $(filter-out $(OBJ_EXCLUDE),$(OBJ)) +OBJ_WASM = $(OBJ) +OBJ_WASM += $(BUILD)/mp_stubs.o + +WASM_CFLAGS = -O0 -std=c99 -Wall -Werror -Wdouble-promotion -Wfloat-conversion $(INC) +WASM_LDFLAGS = -Wl,--gc-sections -sSTANDALONE_WASM=0 -sSINGLE_FILE=1 -sALLOW_MEMORY_GROWTH=0 -sWASM_BIGINT=1 + +wasm: $(BUILD)/custom_wasm.o $(OBJ_WASM) + $(ECHO) "LINK $@ $(OBJ_WASM)" + $(Q)emcc $(WASM_CFLAGS) $(WASM_LDFLAGS) \ + -o $(BUILD)/micropython.wasm \ + $^ \ + -s IMPORTED_MEMORY=0 -s ERROR_ON_UNDEFINED_SYMBOLS=0 \ + -sEXPORTED_FUNCTIONS='["_mp_sum","_c_sum","_sanity_check"]' \ + --no-entry + +$(BUILD)/custom_wasm.o: custom_wasm.c + $(ECHO) "CC $<" + $(Q)emcc $(WASM_CFLAGS) -c -o $@ $^ diff --git a/ports/webassembly/compile_micropython.sh b/ports/webassembly/compile_micropython.sh new file mode 100755 index 0000000000000..986fca39cce25 --- /dev/null +++ b/ports/webassembly/compile_micropython.sh @@ -0,0 +1,124 @@ +#!/bin/bash + +#!/bin/bash + +emcc -Oz \ + micropython_exports.c \ + ../../py/mpstate.c \ + ../../py/nlr.c \ + ../../py/obj.c \ + ../../py/objtype.c \ + ../../py/runtime.c \ + ../../py/gc.c \ + ../../py/malloc.c \ + ../../py/qstr.c \ + ../../py/vstr.c \ + ../../py/mpprint.c \ + ../../py/parse.c \ + ../../py/lexer.c \ + ../../py/scope.c \ + ../../py/compile.c \ + ../../py/emitcommon.c \ + ../../py/emitbc.c \ + ../../py/asmbase.c \ + ../../py/asmx64.c \ + ../../py/emitnx64.c \ + ../../py/asmx86.c \ + ../../py/emitnx86.c \ + ../../py/asmthumb.c \ + ../../py/emitnthumb.c \ + ../../py/emitinlinethumb.c \ + ../../py/asmarm.c \ + ../../py/emitnarm.c \ + ../../py/formatfloat.c \ + ../../py/parsenumbase.c \ + ../../py/parsenum.c \ + ../../py/emitglue.c \ + ../../py/persistentcode.c \ + ../../py/scheduler.c \ + ../../py/nativeglue.c \ + ../../py/pairheap.c \ + ../../py/ringbuf.c \ + ../../py/stackctrl.c \ + ../../py/argcheck.c \ + ../../py/warning.c \ + ../../py/profile.c \ + ../../py/map.c \ + ../../py/objarray.c \ + ../../py/objbool.c \ + ../../py/objboundmeth.c \ + ../../py/objcell.c \ + ../../py/objclosure.c \ + ../../py/objcomplex.c \ + ../../py/objdeque.c \ + ../../py/objdict.c \ + ../../py/objenumerate.c \ + ../../py/objexcept.c \ + ../../py/objfilter.c \ + ../../py/objfloat.c \ + ../../py/objfun.c \ + ../../py/objgenerator.c \ + ../../py/objgetitemiter.c \ + ../../py/objint.c \ + ../../py/objlist.c \ + ../../py/objmap.c \ + ../../py/objmodule.c \ + ../../py/objobject.c \ + ../../py/objpolyiter.c \ + ../../py/objproperty.c \ + ../../py/objnone.c \ + ../../py/objnamedtuple.c \ + ../../py/objrange.c \ + ../../py/objreversed.c \ + ../../py/objset.c \ + ../../py/objsingleton.c \ + ../../py/objslice.c \ + ../../py/objstr.c \ + ../../py/objstrunicode.c \ + ../../py/objstringio.c \ + ../../py/objtuple.c \ + ../../py/objtype.c \ + ../../py/objzip.c \ + ../../py/opmethods.c \ + ../../py/sequence.c \ + ../../py/stream.c \ + ../../py/binary.c \ + ../../py/builtinimport.c \ + ../../py/builtinevex.c \ + ../../py/builtinhelp.c \ + ../../py/modarray.c \ + ../../py/modbuiltins.c \ + ../../py/modcollections.c \ + ../../py/modgc.c \ + ../../py/modio.c \ + ../../py/modmath.c \ + ../../py/modcmath.c \ + ../../py/modmicropython.c \ + ../../py/modstruct.c \ + ../../py/modsys.c \ + ../../py/modthread.c \ + ../../py/vm.c \ + ../../py/bc.c \ + ../../py/showbc.c \ + ../../py/repl.c \ + ../../py/smallint.c \ + ../../py/frozenmod.c \ + -o micropython_core.wasm \ + -I. -I../.. -I../../py -Ivariants/standard/ -Ibuild-standard/ \ + -s SIDE_MODULE=2 \ + -s EXPORTED_FUNCTIONS='["_mp_js_init","_mp_js_do_str","_mp_js_get_int"]' \ + -s EXPORTED_RUNTIME_METHODS=[] \ + -s ALLOW_MEMORY_GROWTH=1 \ + --no-entry + + +#emcc -Oz \ +# micropython_exports.c \ +# ../../py/*.c \ +# -o micropython_core.wasm \ +# -I. -I../.. -I../../py -Ivariants/standard/ -Ibuild-standard/ \ +# -s SIDE_MODULE=2 \ +# -s EXPORTED_FUNCTIONS='["_mp_js_init","_mp_js_do_str","_mp_js_get_int"]' \ +# -s EXPORTED_RUNTIME_METHODS=[] \ +# -s ALLOW_MEMORY_GROWTH=0 \ +# --no-entry diff --git a/ports/webassembly/custom_wasm.c b/ports/webassembly/custom_wasm.c new file mode 100644 index 0000000000000..5d2fe33218048 --- /dev/null +++ b/ports/webassembly/custom_wasm.c @@ -0,0 +1,45 @@ +#include +#include "py/mpconfig.h" +#include "py/mpstate.h" +#include "py/builtin.h" +#include "py/obj.h" +#include "py/compile.h" +#include "py/runtime.h" +#include "py/gc.h" + +extern void value_return(uint64_t value_len, uint64_t value_ptr); + +//#define HEAP_SIZE 32768 + +//static char heap[HEAP_SIZE]; + +void init_micropython(void) { + // gc_init(heap, heap + sizeof(heap)); + mp_init(); +} + + +void mp_sum() { + init_micropython(); + + //mp_obj_t args[2] = {mp_obj_new_int(40), mp_obj_new_int(2)}; + //mp_obj_t fun = mp_load_name(qstr_from_str("sum")); + //mp_obj_t result = mp_call_function_n_kw(fun, 2, 0, args); + + //mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, "sum(a, b)", 9, false); + //mp_parse_tree_t pt = mp_parse(lex, MP_PARSE_EVAL_INPUT); + //mp_obj_t module_fun = mp_compile(&pt, lex->source_name, false); + + //mp_obj_t result = mp_call_function_n_kw(module_fun, 2, 0, args); + + //value_return(mp_obj_get_int(result), sizeof(uint32_t)); + value_return(4, 0); +} + +void c_sum(uint32_t a, uint32_t b) { + value_return(sizeof(uint32_t), (a + b)); +} + +void sanity_check() { + value_return(0, 4); +} diff --git a/ports/webassembly/easy.wat b/ports/webassembly/easy.wat new file mode 100644 index 0000000000000..9ee82774ba76b --- /dev/null +++ b/ports/webassembly/easy.wat @@ -0,0 +1,18 @@ +(module + (import "env" "value_return" (func $value_return (param i64 i64))) + (func $hello_easy (export "hello_easy") + ;; Store the value 42 at memory location 0 + i32.const 0 + i32.const 42 + i32.store + + ;; Push the pointer (0) and length (4) of our result onto the stack + i64.const 0 ;; pointer + i64.const 4 ;; length (4 bytes for i32) + + ;; Call value_return + call $value_return + ) + (memory 1) + (export "memory" (memory 0)) +) diff --git a/ports/webassembly/easy_contract.c b/ports/webassembly/easy_contract.c new file mode 100644 index 0000000000000..128bf851740b7 --- /dev/null +++ b/ports/webassembly/easy_contract.c @@ -0,0 +1,9 @@ +#include + +void value_return(uint64_t ptr, uint64_t len); + +__attribute__((export_name("hello_easy_c"))) +void hello_easy_c() { + value_return(0, 4); +} + diff --git a/ports/webassembly/easy_python.c b/ports/webassembly/easy_python.c new file mode 100644 index 0000000000000..246168c1ad800 --- /dev/null +++ b/ports/webassembly/easy_python.c @@ -0,0 +1,18 @@ +#include + +void value_return(uint64_t ptr, uint64_t len); + +void mp_js_init(); +int mp_js_do_str(const char* src); +int mp_js_get_int(const char* name); + +__attribute__((export_name("hello_easy_python"))) +void hello_easy_python() { + //mp_js_init(); + + //mp_js_do_str("result = 42"); + + //int32_t result = mp_js_get_int("result"); + + value_return(0, sizeof(int32_t)); +} diff --git a/ports/webassembly/micropython_exports.c b/ports/webassembly/micropython_exports.c new file mode 100644 index 0000000000000..8233433715f71 --- /dev/null +++ b/ports/webassembly/micropython_exports.c @@ -0,0 +1,35 @@ +#include +#include "py/compile.h" +#include "py/runtime.h" +#include "py/gc.h" +#include "py/mperrno.h" + +#define MICROPY_HEAP_SIZE (256 * 1024) +static char heap[MICROPY_HEAP_SIZE]; + +__attribute__((visibility("default"))) +void mp_js_init(void) { + mp_stack_set_limit(10000); + gc_init(heap, heap + sizeof(heap)); + mp_init(); +} + +__attribute__((visibility("default"))) +void mp_js_do_str(const char* src) { + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0); + mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_obj_t module_fun = mp_compile(&parse_tree, lex->source_name, false); + mp_call_function_0(module_fun); + nlr_pop(); + } else { + mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val); + } +} + +__attribute__((visibility("default"))) +int mp_js_get_int(const char* name) { + mp_obj_t obj = mp_load_name(qstr_from_str(name)); + return mp_obj_get_int(obj); +} diff --git a/ports/webassembly/micropython_lib.c b/ports/webassembly/micropython_lib.c new file mode 100644 index 0000000000000..71fdc8058851a --- /dev/null +++ b/ports/webassembly/micropython_lib.c @@ -0,0 +1,36 @@ +#include +#include "py/compile.h" +#include "py/runtime.h" +#include "py/gc.h" +#include "py/mperrno.h" + +//#define MICROPY_HEAP_SIZE (256 * 1024) +//static char heap[MICROPY_HEAP_SIZE]; + +void mp_init(); + +void mp_js_init(void) { + mp_stack_set_limit(10000); +// gc_init(heap, heap + sizeof(heap)); + mp_init(); +} + +mp_obj_t mp_js_do_str(const char* src) { + //nlr_buf_t nlr; + //if (nlr_push(&nlr) == 0) { + mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0); + mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT); + mp_obj_t module_fun = mp_compile(&parse_tree, lex->source_name, false); + mp_call_function_0(module_fun); + //nlr_pop(); + return mp_const_none; + //} else { + // return (mp_obj_t)nlr.ret_val; + //} + //return mp_const_none; +} + +int mp_js_get_int(const char* name) { + mp_obj_t obj = mp_load_name(qstr_from_str(name)); + return mp_obj_get_int(obj); +} diff --git a/ports/webassembly/modnear.c b/ports/webassembly/modnear.c new file mode 100644 index 0000000000000..668d8191e9d79 --- /dev/null +++ b/ports/webassembly/modnear.c @@ -0,0 +1,60 @@ +#include "py/runtime.h" +#include "py/obj.h" +#include "py/objstr.h" +#include "py/mphal.h" +#include "near_api.h" + +static mp_obj_t mod_near_input(mp_obj_t register_id_obj) { + uint64_t register_id = mp_obj_get_int(register_id_obj); + near_input(register_id); + return mp_const_none; +} + +static mp_obj_t mod_near_read_register(mp_obj_t register_id_obj, mp_obj_t ptr_obj) { + uint64_t register_id = mp_obj_get_int(register_id_obj); + uint64_t ptr = mp_obj_get_int(ptr_obj); + near_read_register(register_id, ptr); + return mp_const_none; +} + +static mp_obj_t mod_near_register_len(mp_obj_t register_id_obj) { + uint64_t register_id = mp_obj_get_int(register_id_obj); + uint64_t len = near_register_len(register_id); + return mp_obj_new_int_from_ull(len); +} + +static mp_obj_t mod_near_value_return(mp_obj_t value_len_obj, mp_obj_t value_ptr_obj) { + uint64_t value_len = mp_obj_get_int(value_len_obj); + uint64_t value_ptr = mp_obj_get_int(value_ptr_obj); + near_value_return(value_len, value_ptr); + return mp_const_none; +} + +static mp_obj_t mod_near_log_utf8(mp_obj_t len_obj, mp_obj_t ptr_obj) { + uint64_t len = mp_obj_get_int(len_obj); + uint64_t ptr = mp_obj_get_int(ptr_obj); + near_log_utf8(len, ptr); + return mp_const_none; +} + +static MP_DEFINE_CONST_FUN_OBJ_1(mod_near_input_obj, mod_near_input); +static MP_DEFINE_CONST_FUN_OBJ_2(mod_near_read_register_obj, mod_near_read_register); +static MP_DEFINE_CONST_FUN_OBJ_1(mod_near_register_len_obj, mod_near_register_len); +static MP_DEFINE_CONST_FUN_OBJ_2(mod_near_value_return_obj, mod_near_value_return); +static MP_DEFINE_CONST_FUN_OBJ_2(mod_near_log_utf8_obj, mod_near_log_utf8); + +static const mp_rom_map_elem_t near_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_near) }, + { MP_ROM_QSTR(MP_QSTR_input), MP_ROM_PTR(&mod_near_input_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_register), MP_ROM_PTR(&mod_near_read_register_obj) }, + { MP_ROM_QSTR(MP_QSTR_register_len), MP_ROM_PTR(&mod_near_register_len_obj) }, + { MP_ROM_QSTR(MP_QSTR_value_return), MP_ROM_PTR(&mod_near_value_return_obj) }, + { MP_ROM_QSTR(MP_QSTR_log_utf8), MP_ROM_PTR(&mod_near_log_utf8_obj) }, +}; +static MP_DEFINE_CONST_DICT(near_module_globals, near_module_globals_table); + +const mp_obj_module_t mp_module_near = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&near_module_globals, +}; + diff --git a/ports/webassembly/mp_stubs.c b/ports/webassembly/mp_stubs.c new file mode 100644 index 0000000000000..5bb9705069795 --- /dev/null +++ b/ports/webassembly/mp_stubs.c @@ -0,0 +1,75 @@ +#include "py/obj.h" +#include "py/mpconfig.h" + +mp_obj_t mp_sys_stdin_obj = MP_OBJ_NULL; +mp_obj_t mp_sys_stdout_obj = MP_OBJ_NULL; +mp_obj_t mp_sys_stderr_obj = MP_OBJ_NULL; + + +#include + +#include + +int32_t invoke_ii(int32_t a, int32_t b) { + return 0; +} + +void invoke_viii(int32_t a, int32_t b, int32_t c, int32_t d) { +} + +int32_t invoke_iiii(int32_t a, int32_t b, int32_t c, int32_t d) { + return 0; +} + +void invoke_v(int32_t a) { +} + +int32_t invoke_iii(int32_t a, int32_t b, int32_t c) { + return 0; +} + +void invoke_vi(int32_t a, int32_t b) { +} + +void invoke_vii(int32_t a, int32_t b, int32_t c) { +} + +int32_t invoke_i(int32_t a) { + return 0; +} + +void mp_js_hook(void) { +} + +int32_t invoke_iiiii(int32_t a, int32_t b, int32_t c, int32_t d, int32_t e) { + return 0; +} + +void invoke_viiii(int32_t a, int32_t b, int32_t c, int32_t d, int32_t e) { +} + +void emscripten_scan_registers(int32_t a) { +} + +uint32_t mp_js_ticks_ms(void) { + return 0; +} + +int fd_write(void) { + return 0; +} + +void _emscripten_throw_longjmp(void) { +} + +uint32_t mp_hal_stdout_tx_strn(const char *str, size_t len) { + return 0; +} + +int __wasi_fd_close(int a) { + return 0; +} + +int __wasi_fd_write(int a, int b, int c, int d) { + return 0; +} diff --git a/ports/webassembly/mpconfigport.h b/ports/webassembly/mpconfigport.h index ab56162ca2b07..74c75bfc7b249 100644 --- a/ports/webassembly/mpconfigport.h +++ b/ports/webassembly/mpconfigport.h @@ -45,41 +45,41 @@ #define MICROPY_ENABLE_GC (1) #define MICROPY_ENABLE_PYSTACK (1) #define MICROPY_STACK_CHECK (0) -#define MICROPY_KBD_EXCEPTION (1) -#define MICROPY_REPL_EVENT_DRIVEN (1) +#define MICROPY_KBD_EXCEPTION (0) +#define MICROPY_REPL_EVENT_DRIVEN (0) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) #define MICROPY_ENABLE_DOC_STRING (1) #define MICROPY_WARNINGS (1) #define MICROPY_ERROR_PRINTER (&mp_stderr_print) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) #define MICROPY_USE_INTERNAL_ERRNO (1) -#define MICROPY_USE_INTERNAL_PRINTF (0) +#define MICROPY_USE_INTERNAL_PRINTF (1) #define MICROPY_EPOCH_IS_1970 (1) -#define MICROPY_PY_ASYNCIO_TASK_QUEUE_PUSH_CALLBACK (1) -#define MICROPY_PY_RANDOM_SEED_INIT_FUNC (mp_js_random_u32()) -#define MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME (1) -#define MICROPY_PY_TIME_TIME_TIME_NS (1) -#define MICROPY_PY_TIME_INCLUDEFILE "ports/webassembly/modtime.c" +#define MICROPY_PY_ASYNCIO_TASK_QUEUE_PUSH_CALLBACK (0) +//#define MICROPY_PY_RANDOM_SEED_INIT_FUNC (mp_js_random_u32()) +#define MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME (0) +#define MICROPY_PY_TIME_TIME_TIME_NS (0) +//#define MICROPY_PY_TIME_INCLUDEFILE "ports/webassembly/modtime.c" #ifndef MICROPY_VFS -#define MICROPY_VFS (1) +#define MICROPY_VFS (0) #endif -#define MICROPY_VFS_POSIX (MICROPY_VFS) +#define MICROPY_VFS_POSIX (0) #define MICROPY_PY_SYS_PLATFORM "webassembly" #ifndef MICROPY_PY_JS -#define MICROPY_PY_JS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#define MICROPY_PY_JS (0) #endif #ifndef MICROPY_PY_JSFFI -#define MICROPY_PY_JSFFI (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES) +#define MICROPY_PY_JSFFI (0) #endif -#define MICROPY_EVENT_POLL_HOOK \ - do { \ - extern void mp_handle_pending(bool); \ - mp_handle_pending(true); \ - } while (0); +//#define MICROPY_EVENT_POLL_HOOK \ +// do { \ +// extern void mp_handle_pending(bool); \ +// mp_handle_pending(true); \ +// } while (0); // Whether the VM will periodically call mp_js_hook(), which checks for // interrupt characters on stdin (or equivalent input). @@ -126,3 +126,15 @@ typedef long mp_off_t; extern const struct _mp_print_t mp_stderr_print; uint32_t mp_js_random_u32(void); + +#define MICROPY_PY_NEAR (1) + +#if MICROPY_PY_NEAR +extern const struct _mp_obj_module_t mp_module_near; +#define MICROPY_PY_NEAR_DEF { MP_ROM_QSTR(MP_QSTR_near), MP_ROM_PTR(&mp_module_near) }, +#else +#define MICROPY_PY_NEAR_DEF +#endif + +#define MICROPY_PORT_BUILTIN_MODULES \ + MICROPY_PY_NEAR_DEF diff --git a/ports/webassembly/mphalport.c b/ports/webassembly/mphalport.c index 9ab47762e3e97..90c9af5b4d958 100644 --- a/ports/webassembly/mphalport.c +++ b/ports/webassembly/mphalport.c @@ -35,7 +35,7 @@ static void stderr_print_strn(void *env, const char *str, size_t len) { const mp_print_t mp_stderr_print = {NULL, stderr_print_strn}; -mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { +mp_uint_t __attribute__((weak)) mp_hal_stdout_tx_strn(const char *str, size_t len) { return write(1, str, len); } diff --git a/ports/webassembly/near_api.c b/ports/webassembly/near_api.c new file mode 100644 index 0000000000000..7e4bb19a1932a --- /dev/null +++ b/ports/webassembly/near_api.c @@ -0,0 +1,22 @@ +#include "near_api.h" +#include + +EM_JS(void, near_input, (uint64_t register_id), { + env.input(register_id); +}); + +EM_JS(void, near_read_register, (uint64_t register_id, uint64_t ptr), { + env.read_register(register_id, ptr); +}); + +EM_JS(uint64_t, near_register_len, (uint64_t register_id), { + return env.register_len(register_id); +}); + +EM_JS(void, near_value_return, (uint64_t value_len, uint64_t value_ptr), { + env.value_return(value_len, value_ptr); +}); + +EM_JS(void, near_log_utf8, (uint64_t len, uint64_t ptr), { + env.log_utf8(len, ptr); +}); diff --git a/ports/webassembly/near_api.h b/ports/webassembly/near_api.h new file mode 100644 index 0000000000000..23e7ffe0e22c0 --- /dev/null +++ b/ports/webassembly/near_api.h @@ -0,0 +1,12 @@ +#ifndef MICROPY_INCLUDED_WASM_NEAR_API_H +#define MICROPY_INCLUDED_WASM_NEAR_API_H + +#include "py/obj.h" + +void near_input(uint64_t register_id); +void near_read_register(uint64_t register_id, uint64_t ptr); +uint64_t near_register_len(uint64_t register_id); +void near_value_return(uint64_t value_len, uint64_t value_ptr); +void near_log_utf8(uint64_t len, uint64_t ptr); + +#endif // MICROPY_INCLUDED_WASM_NEAR_API_H diff --git a/ports/webassembly/notes.md b/ports/webassembly/notes.md new file mode 100644 index 0000000000000..0fe2feb0589f7 --- /dev/null +++ b/ports/webassembly/notes.md @@ -0,0 +1,97 @@ +# Notes + +## What I am trying to achieve +A contract with exported function which calls uPy for some calculation and returns result of this calculation. + +## What is achieved +Wrapper and uPy are build and linked together in a single .wasm. + +If uPy is used in runtime, the contract call fails when being prepared for execution by NEAR VM. If uPy is not used by `hello_easy_python`, the exceution of this function succeeds on testnet. + +## What works +(meaning) +- .wasm can be deployed on chain +near contract deploy pywasm-test-account.testnet use-file easy_python.wasm without-init-call network-config testnet sign-with-seed-phrase '***REDACTED***' --seed-phrase-hd-path 'm/44'\''/397'\''/0'\''' send + +- Function can be called on NEAR VM successfully +near contract call-function as-transaction pywasm-test-account.testnet hello_easy_python json-args {} prepaid-gas '30.0 Tgas' attached-deposit '0 NEAR' sign-as pywasm-test-account.testnet network-config testnet sign-with-seed-phrase '***REDACTED***' --seed-phrase-hd-path 'm/44'\''/397'\''/0'\''' send + +### .wat -> .wasm +wat2wasm easy.wat + +### Building and running simple C code -> .wasm +emcc -Oz easy_contract.c -o easy_contract.wasm \ + -s STANDALONE_WASM \ + -s EXPORTED_FUNCTIONS='["_hello_easy_c"]' \ + -s IMPORTED_MEMORY \ + -s ALLOW_MEMORY_GROWTH=0 \ + -s ERROR_ON_UNDEFINED_SYMBOLS=0 + +### Building C -> .wasm, with MPy linked but not used +emcc -c micropython_lib.c -fPIC -I. -I../.. -I../../py -Ivariants/standard/ -Ibuild-standard/ -DMICROPY_PERSISTENT_CODE_LOAD=0 -DMICROPY_VFS_POSIX=0 -o micropython_lib.o + +emcc -Oz easy_python.c micropython_lib.o -o easy_python.wasm -s STANDALONE_WASM -s EXPORTED_FUNCTIONS='["_hello_easy_python"]' -s IMPORTED_MEMORY -s ALLOW_MEMORY_GROWTH=1 -s ERROR_ON_UNDEFINED_SYMBOLS=0 --no-entry + +This is one iteration better, we link **some** Micropython code into the resulting .wasm but if actually used causes VM to crash on compile time (see below). + +## What does not work + +### C -> .wasm + mp_init() +Problems with mp_init() - in runtime - Near VM compilation fails + +Might be a linking issue, or memory handling issue, for example this code would cause the same error: + + +```c + #define MICROPY_HEAP_SIZE (32 * 1024) + static char heap[MICROPY_HEAP_SIZE]; + + __attribute__((export_name("hello_easy_c"))) + void hello_easy_c() { + + uint32_t result = 42; + *((int32_t*)heap) = result; + value_return((uint64_t)heap, sizeof(int32_t)); + } +``` + +Error: + 0: Error: An error occurred during a `FunctionCall` Action, parameter is debug message. + CompilationError(PrepareError(Memory)) + +Location: + src/common.rs:955 + + +### Linking Micropython -> .wasm, then using it as a library +./compile_micropython.sh (fails due to symbols being included twice) + +or building .wasm using new makefile rule "make relocatable" - the resulting file is not self-sufficient and relies on missing mp_init + +Import[484]: + 21 - memory[0] pages: initial=1 <- env.memory + 20 - table[0] type=funcref initial=433 <- env.__indirect_function_table + 19 - global[0] i32 mutable=1 <- env.__stack_pointer + 18 - global[1] i32 mutable=0 <- env.__memory_base + 17 - global[2] i32 mutable=0 <- env.__table_base + 16 - func[0] sig=3 <- env.emscripten_longjmp + 15 - func[1] sig=6 <- env.memset + 14 - func[2] sig=6 <- env.memcpy + 13 - func[3] sig=6 <- env.strncmp + 12 - func[4] sig=6 <- env.memcmp + 11 - func[5] sig=2 <- env.strlen + 10 - func[6] sig=6 <- env.memmove + 9 - func[7] sig=5 <- env.strcmp + 8 - func[8] sig=3 <- env.mp_reader_new_file + 7 - func[9] sig=10 <- env.pow + 6 - func[10] sig=11 <- env.nan + 5 - func[11] sig=5 <- env.invoke_ii + 4 - func[12] sig=5 <__wasm_setjmp_test> <- env.__wasm_setjmp_test + 3 - func[13] sig=0 <- env.setTempRet0 + 2 - func[14] sig=8 <- env.getTempRet0 + 1 - func[15] sig=4 <- env.invoke_viii +71 - func[16] sig=1 <__wasm_setjmp> <- env.__wasm_setjmp +[...] + +Of course, those functions are not available and their implementation should be included in the .wasm + diff --git a/ports/webassembly/qstrdefsport.h b/ports/webassembly/qstrdefsport.h index 421344bd494da..af1976632ed4d 100644 --- a/ports/webassembly/qstrdefsport.h +++ b/ports/webassembly/qstrdefsport.h @@ -2,3 +2,10 @@ // *FORMAT-OFF* Q(/lib) Q(asyncio.core) +Q(near) +Q(input) +Q(read_register) +Q(register_len) +Q(value_return) +Q(log_utf8) + diff --git a/py/misc.h b/py/misc.h index cf1810d4e784b..38bfbf8f8a6e2 100644 --- a/py/misc.h +++ b/py/misc.h @@ -374,8 +374,8 @@ static uint32_t mp_ctz(uint32_t x) { // mp_int_t can be larger than long, i.e. Windows 64-bit, nan-box variants static inline uint32_t mp_clz_mpi(mp_int_t x) { - MP_STATIC_ASSERT(sizeof(mp_int_t) == sizeof(long long) - || sizeof(mp_int_t) == sizeof(long)); + //MP_STATIC_ASSERT(sizeof(mp_int_t) == sizeof(long long) + // || sizeof(mp_int_t) == sizeof(long)); // ugly, but should compile to single intrinsic unless O0 is set if (sizeof(mp_int_t) == sizeof(long)) {