diff --git a/.github/workflows/microcontroller_core.yml b/.github/workflows/microcontroller_core.yml index b8ceb28..f3e4f91 100644 --- a/.github/workflows/microcontroller_core.yml +++ b/.github/workflows/microcontroller_core.yml @@ -36,4 +36,10 @@ jobs: run: gcc -DTEST64 float-test.c -o float-test -lm - name: Run float-test - run: ./float-test \ No newline at end of file + run: ./float-test + + - name: Build profiler-test + run: gcc -DTEST64 profiler-test.c -o profiler-test -lm + + - name: Run profiler-test + run: ./profiler-test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 28f1ba7..24856e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -.DS_Store \ No newline at end of file +.DS_Store +.vscode \ No newline at end of file diff --git a/microcontroller/core/CMakeLists.txt b/microcontroller/core/CMakeLists.txt index 50f08e1..9e0aad3 100644 --- a/microcontroller/core/CMakeLists.txt +++ b/microcontroller/core/CMakeLists.txt @@ -1,2 +1,6 @@ -idf_component_register(SRCS "src/c-runtime.c" - INCLUDE_DIRS "include") +idf_component_register(SRCS "src/c-runtime.c" "src/profiler.c" + INCLUDE_DIRS "include" + LDFRAGMENTS linker.lf + WHOLE_ARCHIVE) + +target_compile_options(${COMPONENT_LIB} PRIVATE -mtext-section-literals) \ No newline at end of file diff --git a/microcontroller/core/include/c-runtime.h b/microcontroller/core/include/c-runtime.h index 5ed4c60..3bc7782 100644 --- a/microcontroller/core/include/c-runtime.h +++ b/microcontroller/core/include/c-runtime.h @@ -97,6 +97,7 @@ extern bool CR_SECTION value_to_truefalse(value_t v); inline value_t bool_to_value(bool b) { return b ? VALUE_TRUE : VALUE_FALSE; } inline bool value_to_bool(value_t v) { return value_to_truefalse(v); } +inline bool is_bool_value(value_t v) { return (v & 3) == 2; } inline bool safe_value_to_bool(value_t v) { // any value can be a boolean value. @@ -166,7 +167,7 @@ extern void CR_SECTION interrupt_handler_end(); extern void CR_SECTION gc_initialize(); extern class_object* CR_SECTION gc_get_class_of(value_t value); -extern bool gc_is_instance_of(const class_object* clazz, value_t obj); +extern bool CR_SECTION gc_is_instance_of(const class_object* clazz, value_t obj); extern void* CR_SECTION method_lookup(value_t obj, uint32_t index); extern pointer_t CR_SECTION gc_allocate_object(const class_object* clazz); @@ -224,18 +225,21 @@ extern value_t CR_SECTION gc_new_intarray(int32_t n, int32_t init_value); extern value_t CR_SECTION gc_make_intarray(int32_t n, ...); extern int32_t CR_SECTION gc_intarray_length(value_t obj); extern int32_t* CR_SECTION gc_intarray_get(value_t obj, int32_t index); +extern bool CR_SECTION gc_is_intarray(value_t v); extern value_t CR_SECTION safe_value_to_floatarray(value_t v); extern value_t CR_SECTION gc_new_floatarray(int32_t n, float init_value); extern value_t CR_SECTION gc_make_floatarray(int32_t n, ...); extern int32_t CR_SECTION gc_floatarray_length(value_t obj); extern float* CR_SECTION gc_floatarray_get(value_t obj, int32_t index); +extern bool CR_SECTION gc_is_floatarray(value_t v); extern value_t CR_SECTION safe_value_to_boolarray(value_t v); extern value_t CR_SECTION gc_new_bytearray(bool is_boolean, int32_t n, int32_t init_value); extern value_t CR_SECTION gc_make_bytearray(bool is_boolean, int32_t n, ...); extern int32_t CR_SECTION gc_bytearray_length(value_t obj); extern uint8_t* CR_SECTION gc_bytearray_get(value_t obj, int32_t index); +extern bool CR_SECTION gc_is_boolarray(value_t v); extern value_t CR_SECTION safe_value_to_vector(value_t v); extern value_t CR_SECTION gc_new_vector(int32_t n, value_t init_value); @@ -251,6 +255,7 @@ extern value_t CR_SECTION gc_make_array(int32_t is_any, int32_t n, ...); extern int32_t CR_SECTION gc_array_length(value_t obj); extern value_t* CR_SECTION gc_array_get(value_t obj, int32_t index); extern value_t CR_SECTION gc_array_set(value_t obj, int32_t index, value_t new_value); +extern bool CR_SECTION gc_is_anyarray(value_t v); extern int32_t CR_SECTION get_all_array_length(value_t obj); extern value_t CR_SECTION get_anyobj_length_property(value_t obj, int property); diff --git a/microcontroller/core/include/profiler.h b/microcontroller/core/include/profiler.h new file mode 100644 index 0000000..abeb41f --- /dev/null +++ b/microcontroller/core/include/profiler.h @@ -0,0 +1,25 @@ +#ifndef __BS_PROFILER__ +#define __BS_PROFILER__ + +#include +#include "c-runtime.h" + +#ifdef TEST64 +typedef uint64_t typeint_t; +#define CORE_TEXT_SECTION +#define CORE_DATA_SECTION +#else +typedef uint32_t typeint_t; +#define CORE_TEXT_SECTION __attribute__((section(".core_text"))) +#define CORE_DATA_SECTION __attribute__((section(".core_data"))) +#endif + + +void CORE_TEXT_SECTION bs_profiler_profile(uint8_t fid, uint8_t* call_count, typeint_t** type_profile, value_t p1, value_t p2, value_t p3, value_t p4); + +#ifndef TEST64 +extern void bs_logger_push_profile(uint8_t fid, char *profile_str); +#endif + +#endif /* __BS_PROFILER__ */ + diff --git a/microcontroller/core/linker.lf b/microcontroller/core/linker.lf new file mode 100644 index 0000000..98601c2 --- /dev/null +++ b/microcontroller/core/linker.lf @@ -0,0 +1,24 @@ +[sections:core_text] +entries: + .core_text + +[sections:core_data] +entries: + .core_data + +[scheme:core_text_default] +entries: + core_text -> flash_text + +[scheme:core_data_default] +entries: + core_data -> flash_rodata + + +[mapping:core_text] +archive: * +entries: + * (core_text_default); + core_text -> flash_text KEEP() + * (core_data_default); + core_data -> flash_rodata KEEP() diff --git a/microcontroller/core/src/c-runtime.c b/microcontroller/core/src/c-runtime.c index a17ffa6..abeb816 100644 --- a/microcontroller/core/src/c-runtime.c +++ b/microcontroller/core/src/c-runtime.c @@ -789,6 +789,11 @@ int32_t* gc_intarray_get(value_t obj, int32_t index) { } } +bool gc_is_intarray(value_t v) { + const class_object* type = gc_get_class_of(v); + return type == &intarray_object.clazz; +} + // A float array static CLASS_OBJECT(floatarray_object, 1) = { @@ -851,6 +856,11 @@ float* gc_floatarray_get(value_t obj, int32_t index) { } } +bool gc_is_floatarray(value_t v) { + const class_object* type = gc_get_class_of(v); + return type == &floatarray_object.clazz; +} + // A byte array and a boolean array CLASS_OBJECT(class_Uint8Array, 1) = { @@ -934,6 +944,11 @@ uint8_t* gc_bytearray_get(value_t obj, int32_t idx) { } } +bool gc_is_boolarray(value_t v) { + const class_object* type = gc_get_class_of(v); + return type == &boolarray_object.clazz; +} + // A fixed-length array static CLASS_OBJECT(vector_object, 1) = { @@ -1184,6 +1199,11 @@ value_t gc_safe_array_acc(value_t obj, int32_t index, char op, value_t value) { return gc_safe_array_set(obj, index, new_value); } +bool gc_is_anyarray(value_t v) { + const class_object* type = gc_get_class_of(v); + return type == &anyarray_object.clazz; +} + // Compute an object size. It is always an even number. // // length: length of object_type.body[] diff --git a/microcontroller/core/src/profiler.c b/microcontroller/core/src/profiler.c new file mode 100644 index 0000000..ca483d9 --- /dev/null +++ b/microcontroller/core/src/profiler.c @@ -0,0 +1,121 @@ +#include +#include +#include + +#include "../include/profiler.h" +#include "../include/c-runtime.h" + + +#define BS_PROFILER_TAG "BS_PROFILER" + +#define TYPEINT_SIZE sizeof(typeint_t) +#define PARAMS_NUM 4 +#define TYPE_PROFILE_COLUMN (PARAMS_NUM + 1) +#define TYPE_PROFILE_ROW 5 +#define TYPE_PROFILE_SIZE TYPEINT_SIZE * TYPE_PROFILE_COLUMN * TYPE_PROFILE_ROW +#define TYPE_COUNT_THRESHOLD 5 +#define CALL_COUNT_THRESHOLD 5 + +char types_str[128]; + +static typeint_t value_to_typeint(value_t p) { + uint32_t last_2bit = p & 3; + if (last_2bit == 3 && p != VALUE_UNDEF) { + class_object* obj_class = gc_get_class_of(p); + return obj_class == NULL ? VALUE_UNDEF : (typeint_t)obj_class; + } else { + return last_2bit; + } +} + +static const char* typeint_to_str(typeint_t typeint) { + if (typeint == 0b00) { + return "integer"; + } else if (typeint == 0b01) { + return "float"; + } else if (typeint == 0b10) { + return "boolean"; + } else if (typeint == 0b11) { + return "undefined"; + } else { + return ((class_object*)typeint)->name; + } +} + +static inline bool row_is_empty(typeint_t* type_profile_row) { + return *type_profile_row == 0; +} + +static inline void increment_row_count(typeint_t* type_profile_row) { + *type_profile_row += 1; +} + +static void set_initial_row(typeint_t* type_profile_row, value_t p1, value_t p2, value_t p3, value_t p4) { + *type_profile_row = 1; + *(type_profile_row + 1) = value_to_typeint(p1); + *(type_profile_row + 2) = value_to_typeint(p2); + *(type_profile_row + 3) = value_to_typeint(p3); + *(type_profile_row + 4) = value_to_typeint(p4); +} + +static bool row_match_param_types(typeint_t* type_profile_row, value_t p1, value_t p2, value_t p3, value_t p4) { + bool p1_match = value_to_typeint(p1) == *(type_profile_row + 1); + bool p2_match = value_to_typeint(p2) == *(type_profile_row + 2); + bool p3_match = value_to_typeint(p3) == *(type_profile_row + 3); + bool p4_match = value_to_typeint(p4) == *(type_profile_row + 4); + return p1_match && p2_match && p3_match && p4_match; +} + +static char* row_to_str(typeint_t* type_profile_row) { + const char* p1_str = typeint_to_str(*(type_profile_row + 1)); + const char* p2_str = typeint_to_str(*(type_profile_row + 2)); + const char* p3_str = typeint_to_str(*(type_profile_row + 3)); + const char* p4_str = typeint_to_str(*(type_profile_row + 4)); + snprintf(types_str, sizeof(types_str), "%s, %s, %s, %s", p1_str, p2_str, p3_str, p4_str); + return types_str; +} + +static void send_row(uint8_t fid, typeint_t* type_profile_row) { + char* s = row_to_str(type_profile_row); +#ifdef TEST64 + printf("%s\n", s); +#else + bs_logger_push_profile(fid, s); +#endif +} + +static typeint_t* get_row_on_threshold(uint8_t fid, typeint_t* type_profile, value_t p1, value_t p2, value_t p3, value_t p4) { + for (int i = 0; i < TYPE_PROFILE_ROW; i++) { + typeint_t* row = type_profile + TYPE_PROFILE_COLUMN * i; + if(row_is_empty(row)) { + set_initial_row(row, p1, p2, p3, p4); + return NULL; + } else if (row_match_param_types(row, p1, p2, p3, p4)) { + increment_row_count(row); + return *row == TYPE_COUNT_THRESHOLD ? row : NULL; + } + } + return NULL; +} + +void bs_profiler_profile(uint8_t fid, uint8_t* call_count, typeint_t** type_profile, value_t p1, value_t p2, value_t p3, value_t p4) { + if (*call_count < CALL_COUNT_THRESHOLD) { + *call_count += 1; + return; + } else if (*call_count == CALL_COUNT_THRESHOLD) { + *call_count += 1; + *type_profile = malloc(TYPE_PROFILE_SIZE); + memset(*type_profile, 0, TYPE_PROFILE_SIZE); + return; + } else if (*type_profile) { + typeint_t* result_row = get_row_on_threshold(fid, *type_profile, p1, p2, p3, p4); + if (result_row != NULL) { + send_row(fid, result_row); + free(*type_profile); + *type_profile = NULL; + } + return; + } + return; +} + diff --git a/microcontroller/core/test/profiler-test.c b/microcontroller/core/test/profiler-test.c new file mode 100644 index 0000000..30824dd --- /dev/null +++ b/microcontroller/core/test/profiler-test.c @@ -0,0 +1,107 @@ +// Test code +// To cmpile, +// cc -DTEST64 profiler-test.c -lm + +#include +#include "../src/c-runtime.c" +#include "../src/profiler.c" + +#define Assert_true(v) assert_true(v, __LINE__) + +static void assert_true(bool value, int line) { + if (!value) + printf("*** ERROR line %d\n", line); +} + +#define Assert_str_equals(a, b) assert_str_equals(a, b, __LINE__) + +static void assert_str_equals(const char* a, const char* b, int line) { + if (strcmp(a, b) != 0) + printf("*** ERROR line %d: %s, %s\n", line, a, b); +} + +static int32_t test_function_object00(int32_t v) { + return v + 1; +} + +static void test_converter() { + value_t any_i = int_to_value(42); + typeint_t typeint_i = value_to_typeint(any_i); + const char* result_str_i = typeint_to_str(typeint_i); + Assert_str_equals(result_str_i, "integer"); + + value_t any_f = float_to_value(13.5); + typeint_t typeint_f = value_to_typeint(any_f); + const char* result_str_f = typeint_to_str(typeint_f); + Assert_str_equals(result_str_f, "float"); + + value_t any_b = VALUE_FALSE; + typeint_t typeint_b = value_to_typeint(any_b); + const char* result_str_b = typeint_to_str(typeint_b); + Assert_str_equals(result_str_b, "boolean"); + + value_t any_u = VALUE_UNDEF; + typeint_t typeint_u = value_to_typeint(any_u); + const char* result_str_u = typeint_to_str(typeint_u); + Assert_str_equals(result_str_u, "undefined"); + + value_t any_n = VALUE_NULL; + typeint_t typeint_n = value_to_typeint(any_n); + const char* result_str_n = typeint_to_str(typeint_n); + Assert_str_equals(result_str_n, "undefined"); + + value_t s = gc_new_string("test"); + typeint_t typeint_s = value_to_typeint(s); + const char* result_str_s = typeint_to_str(typeint_s); + Assert_str_equals(result_str_s, "string"); + + value_t arr = gc_new_array(true, 2, int_to_value(4)); + typeint_t typeint_arr = value_to_typeint(arr); + const char* result_str_arr = typeint_to_str(typeint_arr); + Assert_str_equals(result_str_arr, "Array"); + + value_t iarr = gc_new_intarray(3, 0); + typeint_t typeint_iarr = value_to_typeint(iarr); + const char* result_str_iarr = typeint_to_str(typeint_iarr); + Assert_str_equals(result_str_iarr, "Array"); + + value_t farr = gc_new_floatarray(4, 1.2); + typeint_t typeint_farr = value_to_typeint(farr); + const char* result_str_farr = typeint_to_str(typeint_farr); + Assert_str_equals(result_str_farr, "Array"); + + value_t barr = gc_new_bytearray(true, 5, 0); + typeint_t typeint_barr = value_to_typeint(barr); + const char* result_str_barr = typeint_to_str(typeint_barr); + Assert_str_equals(result_str_barr, "Array"); + + value_t func = gc_new_function(test_function_object00, "(i)i", int_to_value(3)); + typeint_t typeint_func = value_to_typeint(func); + const char* result_str_func = typeint_to_str(typeint_func); + Assert_str_equals(result_str_func, "Function"); +} + +void test_type_counter() { + value_t p1_1 = int_to_value(3); + value_t p2_1 = gc_new_string("test"); + value_t p1_2 = float_to_value(1.2); + value_t p2_2 = float_to_value(3.4); + typeint_t* type_profile = malloc(TYPE_PROFILE_SIZE); + for (int i = 0; i < TYPE_COUNT_THRESHOLD - 2; i++) { + get_row_on_threshold(0, type_profile, p1_1, p2_1, VALUE_UNDEF, VALUE_UNDEF); + } + typeint_t* r1 = get_row_on_threshold(0, type_profile, p1_1, p2_1, VALUE_UNDEF, VALUE_UNDEF); + Assert_true(r1 == NULL); + typeint_t* r2 = get_row_on_threshold(0, type_profile, p1_2, p2_2, VALUE_UNDEF, VALUE_UNDEF); + Assert_true(r1 == NULL); + typeint_t* r3 = get_row_on_threshold(0, type_profile, p1_1, p2_1, VALUE_UNDEF, VALUE_UNDEF); + Assert_true(r3 != NULL); + Assert_str_equals(row_to_str(r3), "integer, string, undefined, undefined"); +} + +int main() { + gc_initialize(); + test_converter(); + test_type_counter(); + puts("done"); +} \ No newline at end of file diff --git a/microcontroller/ports/esp32/main/include/cmd.h b/microcontroller/ports/esp32/main/include/cmd.h index 6b3e2de..92076b0 100644 --- a/microcontroller/ports/esp32/main/include/cmd.h +++ b/microcontroller/ports/esp32/main/include/cmd.h @@ -15,6 +15,7 @@ typedef enum { BS_CMD_RESULT_ERROR, BS_CMD_RESULT_MEMINFO, BS_CMD_RESULT_EXECTIME, + BS_CMD_RESULT_PROFILE, // Sentinel BS_CMD_END diff --git a/microcontroller/ports/esp32/main/include/logger.h b/microcontroller/ports/esp32/main/include/logger.h index 644ffa5..8a46c78 100644 --- a/microcontroller/ports/esp32/main/include/logger.h +++ b/microcontroller/ports/esp32/main/include/logger.h @@ -22,6 +22,11 @@ void bs_logger_push_log(char *str); */ void bs_logger_push_error(char *str); +/** + * Push the profiling data to the log queue. + */ +void bs_logger_push_profile(uint8_t fid, char *profile_str); + /** * Reset log queue. */ diff --git a/microcontroller/ports/esp32/main/logger.c b/microcontroller/ports/esp32/main/logger.c index 8b91330..b537194 100644 --- a/microcontroller/ports/esp32/main/logger.c +++ b/microcontroller/ports/esp32/main/logger.c @@ -46,6 +46,17 @@ void bs_logger_push_error(char *str) { xQueueSend(log_queue, &log, portMAX_DELAY); } +void bs_logger_push_profile(uint8_t fid, char *profile_str) { + uint32_t str_len = strlen(profile_str); + log_t log; + log.str = (uint8_t*)malloc(str_len + 3); // contain cmd, fid and null + log.str[0] = BS_CMD_RESULT_PROFILE; + log.str[1] = fid; + strcpy((char*)(log.str+2), profile_str); + log.str_len = str_len + 2; + xQueueSend(log_queue, &log, portMAX_DELAY); +} + void bs_logger_reset() { xQueueReset(log_queue); } diff --git a/microcontroller/ports/esp32/main/main.c b/microcontroller/ports/esp32/main/main.c index 6faca86..50e306c 100644 --- a/microcontroller/ports/esp32/main/main.c +++ b/microcontroller/ports/esp32/main/main.c @@ -7,6 +7,7 @@ #include "include/logger.h" #include "include/event.h" #include "include/ble.h" +#include "profiler.h" void app_main(void) { @@ -14,7 +15,7 @@ void app_main(void) { bs_logger_register_sender(bs_ble_send_notification); bs_shell_register_sender(bs_ble_send_notification); - xTaskCreatePinnedToCore(bs_shell_task, "bs_shell_task", 4096, NULL, 1, NULL, 0); + xTaskCreatePinnedToCore(bs_shell_task, "bs_shell_task", 4096 * 16, NULL, 1, NULL, 0); xTaskCreatePinnedToCore(bs_logger_task, "bs_logger_task", 4096, NULL, 1, NULL, 0); xTaskCreatePinnedToCore(bs_event_handler_task, "bs_event_handler_task", 4096, NULL, 1, NULL, 0); } diff --git a/microcontroller/ports/esp32/main/shell.c b/microcontroller/ports/esp32/main/shell.c index 7010090..687a6d0 100644 --- a/microcontroller/ports/esp32/main/shell.c +++ b/microcontroller/ports/esp32/main/shell.c @@ -197,7 +197,7 @@ void bs_shell_task(void *arg) { break; case BS_CMD_RESET: ESP_LOGI(BS_SHELL_TAG, "Soft reset"); - // shell_reset(); + shell_reset(); send_result_meminfo(); break; default: diff --git a/microcontroller/ports/esp32/sdkconfig b/microcontroller/ports/esp32/sdkconfig index aa62a2c..cbe7edd 100644 --- a/microcontroller/ports/esp32/sdkconfig +++ b/microcontroller/ports/esp32/sdkconfig @@ -582,7 +582,6 @@ CONFIG_BT_MAX_DEVICE_NAME_LEN=32 # CONFIG_BT_BLE_RPA_SUPPORTED is not set CONFIG_BT_BLE_RPA_TIMEOUT=900 # CONFIG_BT_BLE_HIGH_DUTY_ADV_INTERVAL is not set -# CONFIG_BT_ABORT_WHEN_ALLOCATION_FAILS is not set # end of Bluedroid Options # @@ -1042,15 +1041,8 @@ CONFIG_ESP_CONSOLE_UART=y CONFIG_ESP_CONSOLE_MULTIPLE_UART=y CONFIG_ESP_CONSOLE_UART_NUM=0 CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 -CONFIG_ESP_INT_WDT=y -CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 -CONFIG_ESP_INT_WDT_CHECK_CPU1=y -CONFIG_ESP_TASK_WDT_EN=y -CONFIG_ESP_TASK_WDT_INIT=y -# CONFIG_ESP_TASK_WDT_PANIC is not set -CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 -CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y -CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP_INT_WDT is not set +# CONFIG_ESP_TASK_WDT_EN is not set # CONFIG_ESP_PANIC_HANDLER_IRAM is not set # CONFIG_ESP_DEBUG_STUBS_ENABLE is not set CONFIG_ESP_DEBUG_OCDAWARE=y @@ -2121,15 +2113,7 @@ CONFIG_CONSOLE_UART_DEFAULT=y CONFIG_CONSOLE_UART=y CONFIG_CONSOLE_UART_NUM=0 CONFIG_CONSOLE_UART_BAUDRATE=115200 -CONFIG_INT_WDT=y -CONFIG_INT_WDT_TIMEOUT_MS=300 -CONFIG_INT_WDT_CHECK_CPU1=y -CONFIG_TASK_WDT=y -CONFIG_ESP_TASK_WDT=y -# CONFIG_TASK_WDT_PANIC is not set -CONFIG_TASK_WDT_TIMEOUT_S=5 -CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y -CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_INT_WDT is not set # CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set CONFIG_ESP32_DEBUG_OCDAWARE=y CONFIG_BROWNOUT_DET=y @@ -2207,4 +2191,4 @@ CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y CONFIG_SUPPORT_TERMIOS=y CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 -# End of deprecated options \ No newline at end of file +# End of deprecated options diff --git a/microcontroller/ports/esp32/sdkconfig.old b/microcontroller/ports/esp32/sdkconfig.old index c391f2d..93ec205 100644 --- a/microcontroller/ports/esp32/sdkconfig.old +++ b/microcontroller/ports/esp32/sdkconfig.old @@ -317,9 +317,9 @@ CONFIG_PARTITION_TABLE_MD5=y # # Compiler options # -CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y +# CONFIG_COMPILER_OPTIMIZATION_DEFAULT is not set # CONFIG_COMPILER_OPTIMIZATION_SIZE is not set -# CONFIG_COMPILER_OPTIMIZATION_PERF is not set +CONFIG_COMPILER_OPTIMIZATION_PERF=y # CONFIG_COMPILER_OPTIMIZATION_NONE is not set CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y # CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set @@ -1224,7 +1224,6 @@ CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 # # Port # -CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y # CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set # CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y diff --git a/modules/esp32/bluescript/button.ts b/modules/esp32/bluescript/button.ts index e9a78f9..314ca56 100644 --- a/modules/esp32/bluescript/button.ts +++ b/modules/esp32/bluescript/button.ts @@ -1,4 +1,4 @@ -import { integer, code } from "../../base"; +import type { integer, code } from "../../base"; export const buttonOnPressed = (buttonPin: integer, callback: () => void) => {} \ No newline at end of file diff --git a/modules/esp32/bluescript/display.ts b/modules/esp32/bluescript/display.ts index e96f538..4fde96b 100644 --- a/modules/esp32/bluescript/display.ts +++ b/modules/esp32/bluescript/display.ts @@ -1,4 +1,4 @@ -import { integer, code } from "../../base"; +import type { integer, code } from "../../base"; class Display { ICON_HEART: integer; diff --git a/modules/esp32/bluescript/gpio.ts b/modules/esp32/bluescript/gpio.ts index 33a33e4..d350ae4 100644 --- a/modules/esp32/bluescript/gpio.ts +++ b/modules/esp32/bluescript/gpio.ts @@ -1,4 +1,4 @@ -import { integer, code } from "../../base"; +import type { integer, code } from "../../base"; class GPIO { pinNum:integer; diff --git a/modules/esp32/bluescript/timer.ts b/modules/esp32/bluescript/timer.ts index ee1a42d..90cd2f6 100644 --- a/modules/esp32/bluescript/timer.ts +++ b/modules/esp32/bluescript/timer.ts @@ -1,4 +1,4 @@ -import { integer, code } from "../../base"; +import type { integer, code } from "../../base"; export const setInterval = (func:()=>void, delay:integer):integer => { diff --git a/modules/esp32/bluescript/utils.ts b/modules/esp32/bluescript/utils.ts index 68f7e76..72c655e 100644 --- a/modules/esp32/bluescript/utils.ts +++ b/modules/esp32/bluescript/utils.ts @@ -1,4 +1,4 @@ -import { integer } from "../../base"; +import type { integer, float } from "../../base"; export const print = (value: any) => { @@ -6,4 +6,14 @@ export const print = (value: any) => { export const randInt = (min: integer, max: integer):integer => { return 2; -} \ No newline at end of file +} + + + +export function assert(test: boolean) {} + +export function abs(i:integer):integer { return 2; } + +export function fabs(f:float):float { return 2; } + +export function sqrt(f:float):float {return 2.0; } \ No newline at end of file diff --git a/modules/esp32/c/include/utils.h b/modules/esp32/c/include/utils.h index e6b92b0..74e6559 100644 --- a/modules/esp32/c/include/utils.h +++ b/modules/esp32/c/include/utils.h @@ -8,9 +8,17 @@ void MD_SECTION_TEXT fbody_print(value_t self, value_t _value); int32_t MD_SECTION_TEXT fbody_randInt(value_t self, int32_t _min, int32_t _max); +void MD_SECTION_TEXT fbody_assert(value_t self, int32_t _test); +int32_t MD_SECTION_TEXT fbody_abs(value_t self, int32_t _i); +float MD_SECTION_TEXT fbody_fabs(value_t self, float _f); +float MD_SECTION_TEXT fbody_sqrt(value_t self, float _f); extern MD_SECTION_DATA struct func_body _print; extern MD_SECTION_DATA struct func_body _randInt; +extern MD_SECTION_DATA struct func_body _assert; +extern MD_SECTION_DATA struct func_body _abs; +extern MD_SECTION_DATA struct func_body _fabs; +extern MD_SECTION_DATA struct func_body _sqrt; #endif /* __BS_PRINT__ */ diff --git a/modules/esp32/c/utils.c b/modules/esp32/c/utils.c index 24b536f..63c45d0 100644 --- a/modules/esp32/c/utils.c +++ b/modules/esp32/c/utils.c @@ -1,7 +1,9 @@ #include #include +#include #include "include/utils.h" #include "logger.h" +#include "assert.h" char message[256]; @@ -33,6 +35,32 @@ int32_t fbody_randInt(value_t self, int32_t _min, int32_t _max) { return rand() % (_max - _min + 1) + _min; } +void fbody_assert(value_t self, int32_t _test) { + assert(_test); +} + +int32_t fbody_abs(value_t self, int32_t _i) { + int32_t _result = 0; + _result = abs(_i); + { int32_t ret_value_ = (_result); return ret_value_; } +} + +float fbody_fabs(value_t self, float _f) { + float _result = 0.0; + _result = fabsf(_f);; + { float ret_value_ = (_result); ; return ret_value_; } +} + +float fbody_sqrt(value_t self, float _f) { + float _result = 0.0; + _result = sqrt(_f);; + { float ret_value_ = (_result); ; return ret_value_; } +} + +struct func_body _assert = { fbody_assert, "(b)v" }; +struct func_body _abs = { fbody_abs, "(i)i" }; +struct func_body _fabs = { fbody_fabs, "(f)f" }; +struct func_body _sqrt = { fbody_sqrt, "(f)f" }; struct func_body _print = { fbody_print, "(a)v" }; struct func_body _randInt = {fbody_randInt, "(ii)i"}; diff --git a/notebook/src/hooks/use-repl.ts b/notebook/src/hooks/use-repl.ts index f7a4075..59837e9 100644 --- a/notebook/src/hooks/use-repl.ts +++ b/notebook/src/hooks/use-repl.ts @@ -55,11 +55,11 @@ export default function useRepl() { } } - const execute = async () => { + const execute = async (useJIT: boolean = true) => { setCompileError(""); let updatedCurrentCell:Cell; try { - const compileResult = await network.compile(currentCell.code); + const compileResult = useJIT ? await network.compileWithProfiling(currentCell.code) : await network.compile(currentCell.code); const bufferGenerator = new BytecodeGenerator(MAX_MTU); bufferGenerator.loadToRAM(compileResult.iram.address, Buffer.from(compileResult.iram.data, "hex")); bufferGenerator.loadToRAM(compileResult.dram.address, Buffer.from(compileResult.dram.data, "hex")); @@ -77,8 +77,11 @@ export default function useRepl() { } } onReceiveExectime.current = (exectime:number) => { - setExecutedCells([...executedCells, {...updatedCurrentCell, executionTime:exectime}]); - setCurrentCell({code:""}); + if (updatedCurrentCell.executionTime === undefined) { + updatedCurrentCell.executionTime = exectime + setExecutedCells([...executedCells, updatedCurrentCell]); + setCurrentCell({code:""}); + } } } @@ -96,9 +99,23 @@ export default function useRepl() { case BYTECODE.RESULT_MEMINFO: onReceiveMeminfo.current(parseResult.meminfo); break; - case BYTECODE.RESULT_EXECTIME: + case BYTECODE.RESULT_EXECTIME: onReceiveExectime.current(parseResult.exectime); - break; + break; + case BYTECODE.RESULT_PROFILE: { + console.log("receive profile", parseResult.fid, parseResult.paramtypes); + network.jitCompile(parseResult.fid, parseResult.paramtypes).then((compileResult) => { + console.log(compileResult) + const bufferGenerator = new BytecodeGenerator(MAX_MTU); + bufferGenerator.loadToRAM(compileResult.iram.address, Buffer.from(compileResult.iram.data, "hex")); + bufferGenerator.loadToRAM(compileResult.dram.address, Buffer.from(compileResult.dram.data, "hex")); + bufferGenerator.loadToFlash(compileResult.flash.address, Buffer.from(compileResult.flash.data, "hex")); + bufferGenerator.jump(compileResult.entryPoint); + bluetooth.current.sendBuffers(bufferGenerator.generate()).then(() => console.log("JIT finish!")) + }) + break; + } + } } diff --git a/notebook/src/services/bluetooth.ts b/notebook/src/services/bluetooth.ts index 0306ff4..c0792a8 100644 --- a/notebook/src/services/bluetooth.ts +++ b/notebook/src/services/bluetooth.ts @@ -8,14 +8,26 @@ export default class Bluetooth { private device: BluetoothDevice | undefined = undefined; private characteristic: BluetoothRemoteGATTCharacteristic | undefined = undefined; private notificationHandler:((event: Event)=>void) | undefined = undefined; + private waitingBuffers:Buffer[][] = []; + private isInProgress: boolean = false; public async sendBuffers(buffs: Buffer[]) { + if (this.isInProgress) { + this.waitingBuffers.push(buffs); + return; + } + this.isInProgress = true await this.init(); let start = performance.now(); for (const buff of buffs) { await this.characteristic?.writeValueWithResponse(buff); } let end = performance.now(); + this.isInProgress = false; + const firstBuff = this.waitingBuffers.shift(); + if (firstBuff !== undefined) { + await this.sendBuffers(firstBuff) + } return end - start } diff --git a/notebook/src/services/network.ts b/notebook/src/services/network.ts index 2215b1e..5bf7d16 100644 --- a/notebook/src/services/network.ts +++ b/notebook/src/services/network.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import { CompileError } from "../utils/error"; +import { CompileError, InternalError } from "../utils/error"; import { MemInfo } from "../utils/type"; @@ -15,6 +15,14 @@ export async function compile(src: string): Promise { return post("compile", {src}); } +export async function compileWithProfiling(src: string): Promise { + return post("compile-with-profiling", {src}); +} + +export async function jitCompile(funcId: number, paramTypes: string[]): Promise { + return post("jit-compile", {funcId, paramTypes}); +} + export async function reset(memInfo:MemInfo, useFlash:boolean) { return post("reset", {...memInfo, useFlash}); } @@ -37,6 +45,9 @@ async function post(path: string, body: object) { if (e.response?.status === CompileError.errorCode) { throw new CompileError(JSON.parse(e.response?.data).message.messages) } + if (e.response?.status === InternalError.errorCode) { + throw new InternalError(JSON.parse(e.response?.data).message.message) + } } throw e; } diff --git a/notebook/src/utils/bytecode.ts b/notebook/src/utils/bytecode.ts index 4dfce91..cf66c62 100644 --- a/notebook/src/utils/bytecode.ts +++ b/notebook/src/utils/bytecode.ts @@ -10,7 +10,8 @@ export enum BYTECODE { RESULT_LOG, RESULT_ERROR, RESULT_MEMINFO, - RESULT_EXECTIME + RESULT_EXECTIME, + RESULT_PROFILE } const LOAD_HEADER_SIZE = 9; @@ -111,14 +112,15 @@ export class BytecodeGenerator { } } -type parseResult = +type ParseResult = {bytecode:BYTECODE.RESULT_LOG, log:string} | {bytecode:BYTECODE.RESULT_ERROR, log:string} | {bytecode:BYTECODE.RESULT_MEMINFO, meminfo:MemInfo} | {bytecode:BYTECODE.RESULT_EXECTIME, exectime:number} | + {bytecode:BYTECODE.RESULT_PROFILE, fid:number, paramtypes:string[]} | {bytecode:BYTECODE.NONE} -export function bytecodeParser(data: DataView):parseResult { +export function bytecodeParser(data: DataView):ParseResult { const bytecode = data.getUint8(0); switch (bytecode) { case BYTECODE.RESULT_LOG: @@ -137,6 +139,10 @@ export function bytecodeParser(data: DataView):parseResult { return {bytecode, meminfo}; case BYTECODE.RESULT_EXECTIME: return {bytecode, exectime:data.getFloat32(1, true)}; + case BYTECODE.RESULT_PROFILE: + let uint8arr = new Uint8Array(data.buffer, 2); + let textDecoder = new TextDecoder(); + return {bytecode:BYTECODE.RESULT_PROFILE, fid: data.getUint8(1), paramtypes:textDecoder.decode(uint8arr).split(", ")}; default: return {bytecode:BYTECODE.NONE} } diff --git a/notebook/src/utils/error.ts b/notebook/src/utils/error.ts index 25f9f79..4a877d1 100644 --- a/notebook/src/utils/error.ts +++ b/notebook/src/utils/error.ts @@ -31,4 +31,13 @@ interface SourceLocation { line: number; column: number; }; -} \ No newline at end of file +} + +export class InternalError extends Error { + static errorCode = 462; + + public constructor(message?: string) { + super(`Internal Error: ${message}`); + } +} + diff --git a/notebook/src/view/repl.tsx b/notebook/src/view/repl.tsx index b3abaab..ec620ed 100644 --- a/notebook/src/view/repl.tsx +++ b/notebook/src/view/repl.tsx @@ -17,6 +17,7 @@ import useRepl, {Cell} from '../hooks/use-repl'; export default function Repl() { const {replParams, replActions} = useRepl(); const [useFlash, setUseFlash] = useState(false); + const [useJIT, setUseJIT] = useState(true); const onUseFlashChange = async (event: React.ChangeEvent) => { setUseFlash(event.target.checked); @@ -41,12 +42,20 @@ export default function Repl() { } label="Use Flash" labelPlacement="start" + /> + setUseJIT(v.target.checked)} inputProps={{ 'aria-label': 'controlled' }}/> + } + label="Use JIT" + labelPlacement="start" /> @@ -58,7 +67,7 @@ export default function Repl() { code={cell.code} executionLog={executionLogStr(cell)} key={index}/> })} replActions.execute()} setCode={(code)=>replActions.setCurrentCell({...replParams.currentCell, code})}/> + onExecuteClick={() => replActions.execute(useJIT)} setCode={(code)=>replActions.setCurrentCell({...replParams.currentCell, code})}/>
{replParams.compileError}
diff --git a/server/package.json b/server/package.json index 25c794e..d6045f7 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,7 @@ { "dependencies": { "@babel/parser": "^7.19.1", + "@babel/traverse": "^7.25.9", "@babel/types": "^7.19.0", "nedb": "^1.8.0", "readline": "^1.3.0" diff --git a/server/src/jit/jit-code-generator.ts b/server/src/jit/jit-code-generator.ts new file mode 100644 index 0000000..19f749d --- /dev/null +++ b/server/src/jit/jit-code-generator.ts @@ -0,0 +1,322 @@ +import {CodeGenerator} from "../transpiler/code-generator/code-generator"; +import { + callCounterName, + FunctionProfile, maxParamNum, profileFunctionName, + Profiler, typeProfilerName +} from "./profiler"; +import { + FunctionEnv, + getVariableNameTable, GlobalEnv, + GlobalVariableNameTable, + VariableEnv, + VariableNameTableMaker +} from "../transpiler/code-generator/variables"; +import * as AST from "@babel/types"; +import { + Any, + ArrayType, + BooleanT, + encodeType, + Float, + FunctionType, + Integer, + StaticType, + StringT +} from "../transpiler/types"; +import {FunctionDeclaration} from "@babel/types"; +import * as cr from "../transpiler/code-generator/c-runtime"; +import {getStaticType, NameInfo, NameTableMaker} from "../transpiler/names"; +import {jitTypecheck, JitTypeChecker} from "./jit-type-checker"; +import {getSpecializedNode, JITCompileError, ProfileError} from "./utils"; +import {InstanceType} from "../transpiler/classes"; +import {classNameInC} from "../transpiler/code-generator/c-runtime"; + + +export function jitTranspile(codeId: number, ast: AST.Node, + typeChecker: (maker: NameTableMaker) => JitTypeChecker, + codeGenerator: (initializerName: string, codeId: number, moduleId: number) => JitCodeGenerator, + gvnt?: GlobalVariableNameTable, + importer?: (name: string) => GlobalVariableNameTable, + moduleId: number = -1, + startLine: number = 1, header: string = '') { + const maker = new VariableNameTableMaker(moduleId) + const nameTable = new GlobalVariableNameTable(gvnt) + jitTypecheck(ast, maker, nameTable, typeChecker(maker), importer) + const nullEnv = new GlobalEnv(new GlobalVariableNameTable(), cr.globalRootSetName) + const mainFuncName = `${cr.mainFunctionName}${codeId}_${moduleId < 0 ? '' : moduleId}` + const generator = codeGenerator(mainFuncName, codeId, moduleId) + generator.visit(ast, nullEnv) // nullEnv will not be used. + if (generator.errorLog.hasError()) + throw generator.errorLog + else + return { code: generator.getCode(header), + main: mainFuncName, names: nameTable } +} + +function originalFunctionName(name: string) { + return `original${name}`; +} + +function originalFunctionBodyName(name: string) { + return `original_${cr.functionBodyName(name)}`; +} + +function specializedFunctionName(name: string) { + return `specialized${name}`; +} + +function specializedFunctionBodyName(name: string) { + return `specialized_${cr.functionBodyName(name)}`; +} + + +// returns '(' or '(' +// '(' is returned if the type cannot be checked. +function checkType(type?: StaticType) { + if (type instanceof ArrayType) { + if (type.elementType === Integer) + return 'gc_is_intarray('; + if (type.elementType === Float) + return 'gc_is_floatarray('; + if (type.elementType === BooleanT) + return 'gc_is_boolarray('; + if (type.elementType === Any) + return 'gc_is_anyarray('; + else + throw new JITCompileError('Unknown array type.'); + } + + if (type instanceof InstanceType) { + return `gc_is_instance_of(&${classNameInC(type.name())}.clazz, `; + } + + switch (type) { + case Integer: + return 'is_int_value('; + case Float: + return 'is_float_value('; + case BooleanT: + return 'is_bool_value('; + case StringT: + return `gc_is_string_object(`; + default: + return undefined; + } +} + + +export class JitCodeGenerator extends CodeGenerator{ + private profiler: Profiler; + private bsSrc: string[]; + + + constructor(initializerName: string, codeId: number, moduleId: number, profiler: Profiler, src: string) { + super(initializerName, codeId, moduleId); + this.profiler = profiler; + this.bsSrc = src.split('\n'); + } + + functionDeclaration(node: AST.FunctionDeclaration, env: VariableEnv) { + const name = (node.id as AST.Identifier).name + const funcInfo = env.table.lookup(name) + const funcType = getStaticType(node) as FunctionType; + const funcName = funcInfo ? funcInfo.transpiledName(name) : name + const fenv = new FunctionEnv(getVariableNameTable(node), env) + + if (!Profiler.funcNeedsProfiling(funcType)) { + super.functionDeclaration(node, env); + return; + } + + let funcProfile = this.profiler.getFunctionProfileByName(name); + if (funcProfile === undefined) { + const src = this.bsSrc.slice((node.loc?.start.line ?? 0) - 1, node.loc?.end.line).join('\n'); + funcProfile = this.profiler.setFunctionProfile(name, src, funcType) + } + const originalFuncName = originalFunctionName(funcName) + const originalFuncBodyName = originalFunctionBodyName(funcName) + const specializedFuncName = specializedFunctionName(funcName); + const specializedFuncBodyName = specializedFunctionBodyName(funcName); + + switch (funcProfile.state.state) { + case 'profiling': + const isFreeVariable = fenv.isFreeVariable(funcInfo) + this.functionBodyDeclaration2(node, originalFuncName, originalFuncBodyName, fenv, isFreeVariable) + if (!isFreeVariable) + this.wrapperFunctionBodyDeclaration(node, funcName, false, funcProfile, fenv) + break + case 'specializing': { + const specializedNode = getSpecializedNode(node); + if (specializedNode === undefined) + throw new ProfileError(`Cannot find specialized node. Node: ${node}`) + const specializedFenv = new FunctionEnv(getVariableNameTable(specializedNode), env) + this.functionBodyDeclaration2(specializedNode, specializedFuncName, specializedFuncBodyName, specializedFenv, false) + this.wrapperFunctionBodyDeclaration(node, funcName, true, funcProfile, fenv) + this.profiler.setFunctionState(name, {state: 'specialized', type: funcProfile.state.type}) + break + } + case 'undoing': + this.signatures += this.makeFunctionStruct(funcName, funcType, false) + this.signatures += `extern ${cr.typeToCType(funcType.returnType, originalFuncBodyName)}${super.makeSimpleParameterList(funcType)};\n` + this.result.nl().write(`${funcName}.${cr.functionPtr} = ${originalFuncBodyName};`).nl() + this.profiler.setFunctionState(name, {state: 'unspecialized'}) + break + case 'specialized': { + const specializedNode = getSpecializedNode(node); + if (specializedNode === undefined) + throw new ProfileError(`Cannot find specialized node. Node: ${node}`) + const specializedFenv = new FunctionEnv(getVariableNameTable(specializedNode), env) + this.functionBodyDeclaration2(specializedNode, specializedFuncName, specializedFuncBodyName, specializedFenv, true) + this.functionBodyDeclaration2(node, originalFuncName, originalFuncBodyName, fenv, true) + break + } + case 'unspecialized': + this.functionBodyDeclaration2(node, funcName, cr.functionBodyName(funcName), fenv, true) + break + } + } + + private functionBodyDeclaration2(node: AST.FunctionDeclaration, funcName: string, bodyName: string, fenv: FunctionEnv, + isFreeVariable: boolean) { + fenv.allocateRootSet() + const funcType = getStaticType(node) as FunctionType; + + const prevResult = this.result + this.result = this.declarations + this.functionBody(node, fenv, funcType, bodyName) + this.result = prevResult + + this.signatures += this.makeFunctionStruct(funcName, funcType, false) + if (isFreeVariable) { + this.result.nl().write(`${funcName}.${cr.functionPtr} = ${bodyName};`).nl() + } else { + this.declarations.write(`${cr.funcStructInC} ${funcName} = { ${bodyName}, "${encodeType(funcType)}" };`).nl() + } + } + + private wrapperFunctionBodyDeclaration(node: AST.FunctionDeclaration, funcName: string, isFreeVariable: boolean, + funcProfile: FunctionProfile, fenv: FunctionEnv) { + const funcType = getStaticType(node) as FunctionType; + const wrapperFuncName = funcName + const wrapperBodyName = cr.functionBodyName(funcName) + + const prevResult = this.result + this.result = this.declarations + if (funcProfile.state.state === 'profiling') + this.wrapperFunctionBodyForProfiling(node, fenv, funcType, funcName, funcProfile.id) + else if (funcProfile.state.state === 'specializing') + this.wrapperFunctionBodyForSpecializing(node, fenv, funcType, funcName, funcProfile.state.type) + this.result = prevResult + + this.signatures += this.makeFunctionStruct(wrapperFuncName, funcType, false) + if (isFreeVariable) { + this.result.nl().write(`${funcName}.${cr.functionPtr} = ${wrapperBodyName};`).nl() + } else { + this.declarations.write(`${cr.funcStructInC} ${wrapperFuncName} = { ${wrapperBodyName}, "${encodeType(funcType)}" };`).nl() + } + } + + private wrapperFunctionBodyForProfiling(node: AST.FunctionDeclaration, fenv: FunctionEnv, + funcType: FunctionType, funcName: string, funcProfileId: number, + modifier: string = 'static ') { + const wrapperBodyName = cr.functionBodyName(funcName) + const originalFuncName = originalFunctionName(funcName) + const bodyResult = this.result.copy() + const sig = this.makeParameterList(funcType, node, fenv, bodyResult) + this.result.write(`${modifier}${cr.typeToCType(funcType.returnType, wrapperBodyName)}${sig}`).write(' {') + this.result.right().nl(); + + this.functionProfiling(node, fenv, funcType, funcProfileId); + + this.result.write('return '); + this.functionCall(node, fenv, originalFuncName, funcType, funcType.paramTypes, 'self') + this.result.left().nl(); + this.result.write('}').nl(); + } + + private wrapperFunctionBodyForSpecializing(node: AST.FunctionDeclaration, fenv: FunctionEnv, + funcType: FunctionType, funcName: string, specializedType: FunctionType, + modifier: string = 'static ') { + const wrapperBodyName = cr.functionBodyName(funcName) + const originalFuncName = originalFunctionName(funcName) + const specializedFuncName = specializedFunctionName(funcName) + const bodyResult = this.result.copy() + const sig = this.makeParameterList(funcType, node, fenv, bodyResult) + this.result.write(`${modifier}${cr.typeToCType(funcType.returnType, wrapperBodyName)}${sig}`).write(' {') + this.result.right().nl(); + + this.result.write('if (') + this.parameterCheck(node, fenv, funcType.paramTypes, specializedType.paramTypes) + this.result.write(') {') + this.result.right().nl() + + this.result.write('return '); + this.functionCall(node, fenv, specializedFuncName, specializedType, funcType.paramTypes, 'self') + + this.result.left().nl() + this.result.write('} else {') + this.result.right().nl() + + this.result.write('return '); + this.functionCall(node, fenv, originalFuncName, funcType, funcType.paramTypes, 'self') + this.signatures += this.makeFunctionStruct(originalFuncName, funcType, false) + + this.result.left().nl() + this.result.write('}') + + this.result.left().nl(); + this.result.write('}').nl(); + } + + private functionProfiling(node: FunctionDeclaration, fenv: FunctionEnv, funcType: FunctionType, funcProfilerId:number) { + this.result.write(`static uint8_t ${callCounterName} = 0;`).nl(); + this.result.write(`static typeint_t* ${typeProfilerName} = 0;`).nl(); + this.result.write(`${profileFunctionName}(`) + let profSig = `${funcProfilerId}, &${callCounterName}, &${typeProfilerName}`; + let c = 0; + for (let i = 0; i < funcType.paramTypes.length; i++) { + const paramName = (node.params[i] as AST.Identifier).name + const info = fenv.table.lookup(paramName) + if (info !== undefined && funcType.paramTypes[i]==Any) { + c += 1; + profSig += ', ' + const name = info.transpiledName(paramName) + profSig += name + } + } + profSig += `, VALUE_UNDEF`.repeat(maxParamNum - c); + this.result.write(profSig).write(');').nl(); + } + + private functionCall(node: AST.FunctionDeclaration, fenv: FunctionEnv, funcName: string, funcType: FunctionType, + argTypes: StaticType[], firstArg?: string) { + const ftype = cr.funcTypeToCType(funcType) + this.result.write(`((${ftype})${funcName}.${cr.functionPtr})(`) + let paramSig = firstArg ? [firstArg] : []; + for (let i = 0; i < funcType.paramTypes.length; i++) { + const paramName = (node.params[i] as AST.Identifier).name + const info = fenv.table.lookup(paramName) + if (info !== undefined) { + const name = info.transpiledName(paramName) + paramSig.push(`${cr.typeConversion(argTypes[i], funcType.paramTypes[i], node)}${name})`); + } + } + this.result.write(paramSig.join(', ')).write(');'); + } + + private parameterCheck(node: AST.FunctionDeclaration, fenv: FunctionEnv, srcParamTypes: StaticType[], targetParamTypes: StaticType[]) { + let paramSig:string[] = []; + for (let i = 0; i < srcParamTypes.length; i++) { + const paramName = (node.params[i] as AST.Identifier).name + const info = fenv.table.lookup(paramName) + if (info !== undefined) { + const check = checkType(targetParamTypes[i]); + if (check) { + const name = info.transpiledName(paramName) + paramSig.push(`${check}${name})`); + } + } + } + this.result.write(paramSig.join(' && ')) + } +} \ No newline at end of file diff --git a/server/src/jit/jit-type-checker.ts b/server/src/jit/jit-type-checker.ts new file mode 100644 index 0000000..5ac85be --- /dev/null +++ b/server/src/jit/jit-type-checker.ts @@ -0,0 +1,36 @@ +import TypeChecker from "../transpiler/type-checker"; +import {NameInfo, NameTable, NameTableMaker} from "../transpiler/names"; +import * as AST from "@babel/types"; +import {getSpecializedNode} from "./utils"; +import {Any} from "../transpiler/types"; + + +export function jitTypecheck(ast: AST.Node, maker: NameTableMaker, names: NameTable, + typeChecker: TypeChecker, + importer?: (file: string) => NameTable): NameTable { + // importer reads a given source file and returns a name table. + // If the source file is not found, importer throws an error message. The type of the message must be string. + // importer may also throw an ErrorLog object. + typeChecker.firstPass = true + typeChecker.result = Any + typeChecker.visit(ast, names) + if (typeChecker.errorLog.hasError()) + throw typeChecker.errorLog + + typeChecker.firstPass = false + typeChecker.result = Any + typeChecker.visit(ast, names) + if (typeChecker.errorLog.hasError()) + throw typeChecker.errorLog + + return names +} + +export class JitTypeChecker extends TypeChecker { + functionDeclaration(node: AST.FunctionDeclaration, names: NameTable): void { + super.functionDeclaration(node, names) + const specializedNode = getSpecializedNode(node) + if (specializedNode !== undefined) + super.functionDeclaration(specializedNode, names) + } +} \ No newline at end of file diff --git a/server/src/jit/profiler.ts b/server/src/jit/profiler.ts new file mode 100644 index 0000000..61dac72 --- /dev/null +++ b/server/src/jit/profiler.ts @@ -0,0 +1,91 @@ +import {Any, ArrayType, FunctionType, StaticType} from "../transpiler/types"; +import {ProfileError} from "./utils"; + + +export type FunctionState = + {state: 'profiling'} | + {state: 'specializing', type: FunctionType} | + {state: 'undoing'} | + {state: 'specialized', type: FunctionType} | + {state: 'unspecialized'} + +export type FunctionProfile = { + id: number, + name: string, + src: string, + type: FunctionType, + state: FunctionState, +} + +export const callCounterName = "call_count"; +export const typeProfilerName = "type_profile"; +export const profileFunctionName = "bs_profiler_profile"; +export const maxParamNum = 4; + +export class Profiler { + private nextFuncId: number = 0; + private profiles: Map = new Map(); + private idToName: Map = new Map(); + + setFunctionProfile(name: string, src: string, type: FunctionType) { + const id = this.nextFuncId++; + const profile:FunctionProfile = {id, name, src, type, state: {state: "profiling"}} + this.profiles.set(name, profile); + this.idToName.set(id, name); + return profile; + } + + getFunctionProfileById(id: number) { + const funcName = this.idToName.get(id); + return funcName ? this.profiles.get(funcName) : undefined; + } + + getFunctionProfileByName(name: string) { + return this.profiles.get(name); + } + + setFunctionState(name: string, state: FunctionState) { + const func = this.profiles.get(name); + if (func !== undefined) + func.state = state; + } + + setFuncSpecializedType(id: number, paramTypes: StaticType[]) { + const funcName = this.idToName.get(id); + if (funcName === undefined) + throw new ProfileError(`Cannot find the target function. id: ${id}`); + const func = this.profiles.get(funcName); + if (func === undefined) + throw new ProfileError(`Cannot not find the target function. name: ${funcName}`); + if (!Profiler.funcIsSpecializable(func, paramTypes)) { + func.state = {state: 'undoing'} + return; + } + + const specializedParamTypes: StaticType[] = []; + let s = 0; + for (const ofpt of func.type.paramTypes) { + if (ofpt !== Any) + specializedParamTypes.push(ofpt) + else + specializedParamTypes.push(paramTypes[s++]); + } + func.state = {state: 'specializing', type: new FunctionType(func.type.returnType, specializedParamTypes)}; + } + + static funcNeedsProfiling(funcType: FunctionType) { + const returnType = funcType.returnType; + if (!funcType.paramTypes.includes(Any) || funcType.paramTypes.filter(t=> t === Any).length > maxParamNum) + return false; + if (returnType instanceof FunctionType) + return false; + const acceptableElementTypes: StaticType[] = ['integer', 'float', 'boolean', 'any'] + if (returnType instanceof ArrayType && !acceptableElementTypes.includes(returnType.elementType)) + return false; + return true; + } + + static funcIsSpecializable(func: FunctionProfile, paramTypes: StaticType[]) { + return !paramTypes.slice(0, func.type.paramTypes.length).every(t => t === Any); + } +} \ No newline at end of file diff --git a/server/src/jit/utils.ts b/server/src/jit/utils.ts new file mode 100644 index 0000000..dbbaa20 --- /dev/null +++ b/server/src/jit/utils.ts @@ -0,0 +1,129 @@ +import { + booleanLiteral, + identifier, + isFunctionDeclaration, + tsAnyKeyword, + tsArrayType, tsBooleanKeyword, tsFunctionType, tsStringKeyword, tSTypeAnnotation, + tsTypeAnnotation, + tsTypeReference, tsVoidKeyword +} from "@babel/types"; +import {Any, ArrayType, BooleanT, Float, FunctionType, Integer, StaticType, StringT, Void} from "../transpiler/types"; +import * as AST from '@babel/types' +import traverse from "@babel/traverse"; +import {Profiler} from "./profiler"; +import {InstanceType} from "../transpiler/classes"; +import {GlobalVariableNameTable} from "../transpiler/code-generator/variables"; + +export const specializedFuncPrefix = "0"; + + +export function typeStringToStaticType(typeString: string, gvnt?: GlobalVariableNameTable):StaticType { + if (typeString === 'integer' || typeString === 'float' || typeString === 'boolean' || typeString === 'string') { + return typeString + } else if (typeString === 'undefined') { + return 'null' + } else if (typeString === 'Array') { + return new ArrayType('any') + } else if (typeString === 'Array') { + return new ArrayType('integer') + } else if (typeString === 'Array') { + return new ArrayType('float') + } else if (typeString === 'Array') { + return new ArrayType('boolean') + } else if (typeString === 'Array') { + return 'any' + } else if (typeString === 'Function') { + return 'any' + } else { + const type = gvnt === undefined ? undefined : gvnt.lookup(typeString)?.type + if (type === undefined || !(type instanceof InstanceType)) + throw new ProfileError(`Cannot find the profiled class: ${typeString}`) + return type + } +} + +function staticTypeToTSType(type: StaticType): AST.TSType { + if (type === Integer || type === Float) + return tsTypeReference(identifier(type)); + + if (type === BooleanT) + return tsBooleanKeyword(); + + if (type === Any) + return tsAnyKeyword(); + + if (type === StringT) + return tsStringKeyword(); + + if (type === Void) + return tsVoidKeyword(); + + if (type instanceof ArrayType) + return tsArrayType(staticTypeToTSType(type.elementType)); + + if (type instanceof FunctionType) { + const paramNodes = type.paramTypes.map((p, i) => { + const id = identifier(`p${i}`); + id.typeAnnotation = staticTypeToNode(p); + return id + }) + return tsFunctionType(undefined, paramNodes, staticTypeToNode(type.returnType)) + } + + if (type instanceof InstanceType) + return tsTypeReference(identifier(type.name())); + + return tsAnyKeyword(); +} + + +function staticTypeToNode(type: StaticType):AST.TSTypeAnnotation { + return tsTypeAnnotation(staticTypeToTSType(type)); +} + +function addSpecializedNode(node: AST.FunctionDeclaration, specializedNode: AST.FunctionDeclaration) { + ((node as unknown) as { specialized: AST.FunctionDeclaration }).specialized = specializedNode +} + +export function getSpecializedNode(node: AST.FunctionDeclaration) { + return ((node as unknown) as { specialized?: AST.FunctionDeclaration }).specialized +} + + +export function convertAst(ast: AST.Node, profiler: Profiler) { + traverse(ast, { + Program: (path) => { + path.node.body.forEach(statement => { + if (!isFunctionDeclaration(statement)) + return + const name = statement.id?.name + const profile = name ? profiler.getFunctionProfileByName(name) : undefined; + if (profile === undefined || (profile.state.state !== 'specializing' && profile.state.state !== 'specialized')) + return; + const specializedType = profile.state.type + const clone = structuredClone(statement) + if (clone.id === null || clone.id === undefined) + return; + clone.id.name = specializedFuncPrefix + name + clone.params.forEach((p, i) => { + p.typeAnnotation = staticTypeToNode(specializedType.paramTypes[i]) + }) + clone.returnType = staticTypeToNode(specializedType.returnType) + addSpecializedNode(statement, clone) + }) + } + }) +} + + +export class ProfileError extends Error { + public constructor(message?: string) { + super(`Profile Error: ${message}`); + } +} + +export class JITCompileError extends Error { + public constructor(message?: string) { + super(`JIT Compile Error: ${message}`); + } +} diff --git a/server/src/server/server.ts b/server/src/server/server.ts index c0d27ad..2682214 100644 --- a/server/src/server/server.ts +++ b/server/src/server/server.ts @@ -3,10 +3,12 @@ import {Buffer} from "node:buffer"; import {ErrorLog} from "../transpiler/utils"; import Session from "./session"; import {execSync} from "child_process"; +import {JITCompileError, ProfileError} from "../jit/utils"; const ERROR_CODE = { COMPILE_ERROR: 460, - LINK_ERROR: 461 + LINK_ERROR: 461, + INTERNAL_ERROR: 462, } export default class HttpServer { @@ -72,11 +74,38 @@ export default class HttpServer { responseBody = {}; statusCode = 200; break; - case "/check": - const cmd_result = execSync("ls ../microcontroller/ports/esp32/build/").toString(); - responseBody = {cmd_result}; - statusCode = 200; + case "/dummy-compile": + if (this.session === undefined) { + statusCode = 400; + responseBody = {error: "Session have not started."} + break; + } + responseBody = this.session.dummyExecute(); + statusCode = 200; + break; + case "/compile-with-profiling": + if (this.session === undefined) { + statusCode = 400; + responseBody = {error: "Session have not started."} break; + } + responseBody = this.session.executeWithProfiling(JSON.parse(requestBody).src); + statusCode = 200; + break; + case "/jit-compile": + if (this.session === undefined) { + statusCode = 400; + responseBody = {error: "Session have not started."} + break; + } + responseBody = this.session.jitExecute(JSON.parse(requestBody)); + statusCode = 200; + break; + case "/check": + const cmd_result = execSync("ls ../microcontroller/ports/esp32/build/").toString(); + responseBody = {cmd_result}; + statusCode = 200; + break; default: responseBody = {message: "Page not found."}; statusCode = 404; @@ -87,6 +116,9 @@ export default class HttpServer { if (e instanceof ErrorLog) { responseBody = {message: e}; statusCode = ERROR_CODE.COMPILE_ERROR; + } else if (e instanceof ProfileError || e instanceof JITCompileError) { + responseBody = {message: e}; + statusCode = ERROR_CODE.INTERNAL_ERROR; } else { responseBody = {message: e}; statusCode = 500; diff --git a/server/src/server/session.ts b/server/src/server/session.ts index fa5fa5f..2960a84 100644 --- a/server/src/server/session.ts +++ b/server/src/server/session.ts @@ -4,11 +4,18 @@ import {FILE_PATH} from "../constants"; import {transpile} from "../transpiler/code-generator/code-generator"; import {execSync} from "child_process"; import {MemoryInfo, ShadowMemory} from "../linker/shadow-memory"; +import {Profiler} from "../jit/profiler"; +import {runBabelParser} from "../transpiler/utils"; +import {convertAst, typeStringToStaticType} from "../jit/utils"; +import {JitCodeGenerator, jitTranspile} from "../jit/jit-code-generator"; +import {NameInfo, NameTableMaker} from "../transpiler/names"; +import {JitTypeChecker} from "../jit/jit-type-checker"; const cProlog = ` #include #include "../../microcontroller/core/include/c-runtime.h" +#include "../../microcontroller/core/include/profiler.h" ` @@ -16,6 +23,7 @@ export default class Session { currentCodeId: number = 0; nameTable?: GlobalVariableNameTable; shadowMemory: ShadowMemory; + profiler: Profiler; constructor(memoryInfo: MemoryInfo) { // Read module files. @@ -28,6 +36,7 @@ export default class Session { } }); this.shadowMemory = new ShadowMemory(FILE_PATH.MCU_ELF, memoryInfo); + this.profiler = new Profiler(); } public execute(tsString: string) { @@ -42,7 +51,6 @@ export default class Session { // Compile fs.writeFileSync(FILE_PATH.C_FILE, cString); execSync(`xtensa-esp32-elf-gcc -c -O2 ${FILE_PATH.C_FILE} -o ${FILE_PATH.OBJ_FILE} -w -fno-common -mtext-section-literals -mlongcalls`); - const buffer = fs.readFileSync(FILE_PATH.OBJ_FILE); this.nameTable = tResult.names; // Link @@ -50,4 +58,82 @@ export default class Session { const end = performance.now(); return {...lResult, compileTime:end-start} } + + public executeWithProfiling(tsString: string) { + this.currentCodeId += 1; + const ast = runBabelParser(tsString, 1) + + const codeGenerator = (initializerName: string, codeId: number, moduleId: number) => { + return new JitCodeGenerator(initializerName, codeId, moduleId, this.profiler, tsString); + } + + const typeChecker = (maker: NameTableMaker) => { + return new JitTypeChecker(maker, undefined); + } + + const start = performance.now(); + // Transpile + const tResult = jitTranspile(this.currentCodeId, ast, typeChecker, codeGenerator, this.nameTable, undefined) + const entryPointName = tResult.main; + const cString = cProlog + tResult.code; + + // Compile + fs.writeFileSync(FILE_PATH.C_FILE, cString); + execSync(`xtensa-esp32-elf-gcc -c -O2 ${FILE_PATH.C_FILE} -o ${FILE_PATH.OBJ_FILE} -w -fno-common -mtext-section-literals -mlongcalls`); + this.nameTable = tResult.names; + + // Link + const lResult = this.shadowMemory.loadAndLink(FILE_PATH.OBJ_FILE, entryPointName); + const end = performance.now(); + return {...lResult, compileTime:end-start} + } + + public jitExecute(profile: {funcId: number, paramTypes: string[]}) { + console.log(profile) + const func = this.profiler.getFunctionProfileById(profile.funcId); + if (func === undefined) + return {}; + + this.profiler.setFuncSpecializedType(profile.funcId, profile.paramTypes.map(t => typeStringToStaticType(t, this.nameTable))) + + const codeGenerator = (initializerName: string, codeId: number, moduleId: number) => { + return new JitCodeGenerator(initializerName, codeId, moduleId, this.profiler, func.src); + } + + const typeChecker = (maker: NameTableMaker) => { + return new JitTypeChecker(maker, undefined); + } + + // Transpile + const ast = runBabelParser(func.src, 1); + const start = performance.now(); + + convertAst(ast, this.profiler); + // const tResult = transpile(0, func.src, this.nameTable, undefined, -1, ast, codeGenerator); + const tResult = jitTranspile(this.currentCodeId, ast, typeChecker, codeGenerator, this.nameTable, undefined) + const entryPointName = tResult.main; + const cString = cProlog + tResult.code; + + // Compile + fs.writeFileSync(FILE_PATH.C_FILE, cString); + execSync(`xtensa-esp32-elf-gcc -c -O2 ${FILE_PATH.C_FILE} -o ${FILE_PATH.OBJ_FILE} -w -fno-common -mtext-section-literals -mlongcalls`); + this.nameTable = tResult.names; + + // Link + const lResult = this.shadowMemory.loadAndLink(FILE_PATH.OBJ_FILE, entryPointName); + const end = performance.now(); + return {...lResult, compileTime:end-start} + } + + public dummyExecute() { + const start = performance.now(); + // Compile + execSync(`xtensa-esp32-elf-gcc -c -O2 ./temp-files/dummy-code.c -o ${FILE_PATH.OBJ_FILE} -w -fno-common -mtext-section-literals -mlongcalls`); + const buffer = fs.readFileSync(FILE_PATH.OBJ_FILE); + + // Link + const lResult = this.shadowMemory.loadAndLink(FILE_PATH.OBJ_FILE, "bluescript_main6_"); + const end = performance.now(); + return {...lResult, compileTime:end-start} + } } \ No newline at end of file diff --git a/server/src/transpiler/code-generator/code-generator.ts b/server/src/transpiler/code-generator/code-generator.ts index 8ced5bb..02a7e54 100644 --- a/server/src/transpiler/code-generator/code-generator.ts +++ b/server/src/transpiler/code-generator/code-generator.ts @@ -46,9 +46,9 @@ export function transpile(codeId: number, src: string, gvnt?: GlobalVariableName export class CodeGenerator extends visitor.NodeVisitor { errorLog = new ErrorLog() - private result = new CodeWriter() - private signatures = '' // function prototypes etc. - private declarations = new CodeWriter() // function declarations etc. + protected result = new CodeWriter() + protected signatures = '' // function prototypes etc. + protected declarations = new CodeWriter() // function declarations etc. private endWithReturn = false private initializerName: string // the name of an initializer function private globalRootSetName: string // the rootset name for global variables @@ -666,9 +666,9 @@ export class CodeGenerator extends visitor.NodeVisitor { this method generates the following C code: static int32_t ${bodyName}(value_t self, int32_t _n) { ... function body ... } */ - private functionBody(node: AST.FunctionDeclaration | AST.ArrowFunctionExpression | AST.ClassMethod, - fenv: FunctionEnv, funcType: FunctionType, bodyName: string, - modifier: string = 'static ') { + protected functionBody(node: AST.FunctionDeclaration | AST.ArrowFunctionExpression | AST.ClassMethod, + fenv: FunctionEnv, funcType: FunctionType, bodyName: string, + modifier: string = 'static ') { const bodyResult = this.result.copy() bodyResult.right() const sig = this.makeParameterList(funcType, node, fenv, bodyResult) @@ -701,8 +701,8 @@ export class CodeGenerator extends visitor.NodeVisitor { return funcHeader } - private makeParameterList(funcType: FunctionType, node: AST.FunctionDeclaration | AST.ArrowFunctionExpression | AST.ClassMethod, - fenv: FunctionEnv, bodyResult?: CodeWriter, simpleName: boolean = false) { + protected makeParameterList(funcType: FunctionType, node: AST.FunctionDeclaration | AST.ArrowFunctionExpression | AST.ClassMethod, + fenv: FunctionEnv, bodyResult?: CodeWriter, simpleName: boolean = false) { let sig = `(${cr.anyTypeInC} self` const bodyResult2 = bodyResult?.copy() @@ -757,7 +757,7 @@ export class CodeGenerator extends visitor.NodeVisitor { return sig + ')' } - private makeSimpleParameterList(funcType: FunctionType) { + protected makeSimpleParameterList(funcType: FunctionType) { let sig = `(${cr.anyTypeInC} self` for (let i = 0; i < funcType.paramTypes.length; i++) { sig += `, ${cr.typeToCType(funcType.paramTypes[i], `p${i}`)}` @@ -766,7 +766,7 @@ export class CodeGenerator extends visitor.NodeVisitor { return sig + ')' } - private makeFunctionStruct(name: string, type: FunctionType, isConst: boolean) { + protected makeFunctionStruct(name: string, type: FunctionType, isConst: boolean) { let body: string = '' if (isConst) { const bodyName = cr.functionBodyName(name) diff --git a/server/src/transpiler/names.ts b/server/src/transpiler/names.ts index 7145ff0..501b1af 100644 --- a/server/src/transpiler/names.ts +++ b/server/src/transpiler/names.ts @@ -174,7 +174,7 @@ export class BlockNameTable implements NameTable { for (const k in this.elements) f(this.elements[k], k) } - + record(key: string, t: StaticType, maker: NameTableMaker, init?: (i: Info) => void): boolean { const old = this.elements[key] diff --git a/server/src/transpiler/type-checker.ts b/server/src/transpiler/type-checker.ts index 51a6032..785d6dc 100644 --- a/server/src/transpiler/type-checker.ts +++ b/server/src/transpiler/type-checker.ts @@ -249,7 +249,7 @@ export default class TypeChecker extends visitor.NodeVisi } private returnStatementArg(node: AST.Node, argument: AST.Expression | null | undefined, - names: NameTable): void { + names: NameTable): void { const rtype = names.returnType() this.assert(rtype !== null, 'return must be in a function body', node) if (argument) { @@ -325,7 +325,7 @@ export default class TypeChecker extends visitor.NodeVisi this.result = clazz this.visit(node.body, names) clazz.sortProperties() - + if (!clazz.findConstructor()) { // this class has a default constructor. this.assert(clazz.declaredProperties() === 0, 'a constructor is missing', node) @@ -1199,7 +1199,7 @@ export default class TypeChecker extends visitor.NodeVisi tsTypeReference(node: AST.TSTypeReference, names: NameTable): void { this.assertSyntax(AST.isIdentifier(node.typeName), node) - this.assertSyntax(node.typeParameters === undefined, node) + this.assertSyntax(node.typeParameters === undefined || node.typeParameters === null, node) const name = (node.typeName as AST.Identifier).name if (name === Float) this.result = Float diff --git a/server/tests/jit/jit-code-generator.test.ts b/server/tests/jit/jit-code-generator.test.ts new file mode 100644 index 0000000..cf26da4 --- /dev/null +++ b/server/tests/jit/jit-code-generator.test.ts @@ -0,0 +1,454 @@ +import {beforeAll, expect, test} from "@jest/globals"; +import {execSync} from "child_process"; +import {Profiler} from "../../src/jit/profiler"; +import {typeStringToStaticType} from "../../src/jit/utils"; +import { + compile, execute, + initialCompile, + tempCFilePath, tempExecutableFilePath, +} from "./test-jit-utils"; + + +beforeAll(() => { + execSync('mkdir -p ./temp-files') +}) + +test('simple code', () => { + const src = 'print(1 + 1)' + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src, profiler, file1, result0.names) + expect(execute([file0, file1], [result1.main], tempCFilePath('file2'), tempExecutableFilePath('bscript'))) + .toEqual('2\n') +}) + +test('profile: integer, float, boolean', () => { + const src = ` +function add(i, f, b) { + return b ? i + f : i + f + f; +} + +for(let i = 0; i < 15; i++) { + add(1, 3.3, true) +} +print(add(1, 3.5, true)) + ` + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src, profiler, file1, result0.names) + expect(execute([file0, file1], [result1.main], tempCFilePath('file2'), tempExecutableFilePath('bscript'))) + .toEqual("integer, float, boolean, undefined\n4.500000\n") +}) + +test('profile: string', () => { + const src = ` +function ss(s) { + return 3; +} + +for(let i = 0; i < 15; i++) { + ss("hello"); +} +print(ss("hello")) + ` + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src, profiler, file1, result0.names) + expect(execute([file0, file1], [result1.main], tempCFilePath('file2'), tempExecutableFilePath('bscript'))) + .toEqual("string, undefined, undefined, undefined\n3\n") +}) + +test('profile: intarray, floatarray, boolarray', () => { + const src = ` +function add0(iarr, farr, barr) { + return barr[0] ? iarr[0] + farr[0] : iarr[0] + farr[0] + farr[0] +} + +let iarr = [1, 2, 3]; +let farr = [4.1, 5.1, 6.1]; +let barr = [true, false]; +for(let i = 0; i < 15; i++) { + add0(iarr, farr, barr); +} +print(add0(iarr, farr, barr)); + ` + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src, profiler, file1, result0.names) + expect(execute([file0, file1], [result1.main], tempCFilePath('file2'), tempExecutableFilePath('bscript'))) + .toEqual("Array, Array, Array, undefined\n5.100000\n") +}) + +test('profile: anyarray, array', () => { + const src = ` +function aarr0(aarr, arr) { + return aarr[0]; +} + +let aarr:any[] = [1, "foo", 3]; +let arr = ["hello"]; +for(let i = 0; i < 15; i++) { + aarr0(aarr, arr); +} +print(aarr0(aarr, arr)); + ` + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src, profiler, file1, result0.names) + expect(execute([file0, file1], [result1.main], tempCFilePath('file2'), tempExecutableFilePath('bscript'))) + .toEqual("Array, Array, undefined, undefined\n1\n") +}) + +test('profile: class', () => { + const src = ` +class Rectangle { + x: integer + y: integer + + constructor(x:integer, y:integer) { + this.x = x; + this.y = y; + } +} + +function area(rect) { + return rect.x * rect.y; +} + +let rect = new Rectangle(11, 4) +for(let i = 0; i < 15; i++) { + area(rect); +} +print(area(rect)); + ` + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src, profiler, file1, result0.names) + expect(execute([file0, file1], [result1.main], tempCFilePath('file2'), tempExecutableFilePath('bscript'))) + .toEqual("Rectangle, undefined, undefined, undefined\n44\n") +}) + +test('profile: not profile function with function return type', () => { + const src = ` +function func(a:()=>integer) { + return a(); +} + +for(let i = 0; i < 15; i++) { + func(()=>3) +} + ` + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src, profiler, file1, result0.names) + expect(execute([file0, file1], [result1.main], tempCFilePath('file2'), tempExecutableFilePath('bscript'))) + .toEqual('') +}) + +test('profile: not profile with type annotations', () => { + const src = ` +function add(a:integer, b:integer) { + return a + b; +} + +for(let i = 0; i < 15; i++) { + add(1, 3) +} + ` + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src, profiler, file1, result0.names) + expect(execute([file0, file1], [result1.main], tempCFilePath('file2'), tempExecutableFilePath('bscript'))) + .toEqual('') +}) + +test('profile: not profile with too many params', () => { + const src = ` +function add(a, b, c, d, e) { + return a + b + c + d + e; +} + +for(let i = 0; i < 15; i++) { + add(1, 2, 3, 4, 5) +} + ` + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src, profiler, file1, result0.names) + expect(execute([file0, file1], [result1.main], tempCFilePath('file2'), tempExecutableFilePath('bscript'))) + .toEqual('') +}) + +test('profile: function redefinition during profiling', () => { + const src1 = ` +function add(a, b) { + return a + b; +} + +for(let i = 0; i < 3; i++) { + add(1, 3) +} +print(add(1, 3)) + ` + + const src2 = ` +function add(a, b) { + return a + b + 2; +} + +for(let i = 0; i < 15; i++) { + add(1, 3) +} +print(add(1, 3)) + ` + + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src1, profiler, file1, result0.names) + const file2 = tempCFilePath('file2') + const result2 = compile(1, src2, profiler, file2, result1.names) + expect(execute([file0, file1, file2], [result1.main, result2.main], tempCFilePath('file4'), tempExecutableFilePath('bscript'))) + .toEqual(`4\ninteger, integer, undefined, undefined\n6\n`) +}) + + +test('jit compile: integer, float, boolean', () => { + const src1 = ` +function add(i, f, b) { + return b ? i + f : i + f + f; +} +print(add(1, 4.4, true)) + ` + + const src3 = ` +print(add(1, 5.5, false)) + ` + + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src1, profiler, file1, result0.names) + + const func = profiler.getFunctionProfileById(0); + if (func === undefined) + throw new Error(`Cannot fine func.`) + profiler.setFuncSpecializedType(0, ["integer", "integer", "undefined", "undefined"].map(t => typeStringToStaticType(t, result1.names))) + + const file2 = tempCFilePath('file2') + const result2 = compile(1, func.src, profiler, file2, result1.names) + + const file3 = tempCFilePath('file3') + const result3 = compile(2, src3, profiler, file3, result2.names) + expect(execute([file0, file1, file2, file3], [result1.main, result2.main, result3.main], tempCFilePath('file4'), tempExecutableFilePath('bscript'))) + .toEqual(`5.400000\n12.000000\n`) +}) + +test('jit compile: string', () => { + const src1 = ` +function printStr(str) { + print(str); +} + +printStr("hello"); + ` + + const src3 = ` +printStr("world"); + ` + + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src1, profiler, file1, result0.names) + + const func = profiler.getFunctionProfileById(0); + if (func === undefined) + throw new Error(`Cannot fined func.`) + profiler.setFuncSpecializedType(0, ["string", "undefined", "undefined", "undefined"].map(t => typeStringToStaticType(t, result1.names))) + + const file2 = tempCFilePath('file2') + const result2 = compile(1, func.src, profiler, file2, result1.names) + + const file3 = tempCFilePath('file3') + const result3 = compile(2, src3, profiler, file3, result2.names) + expect(execute([file0, file1, file2, file3], [result1.main, result2.main, result3.main], tempCFilePath('file4'), tempExecutableFilePath('bscript'))) + .toEqual(`hello\nworld\n`) +}) + +test('jit compile: intarray, floatarray, boolarray', () => { + const src1 = ` +function add0(iarr, farr, barr) { + return barr[0] ? iarr[0] + farr[0] : iarr[0] + farr[0] + farr[0] +} + +print(add0([1, 3], [1.1, 4.4], [true, false])); + ` + + const src3 = ` +print(add0([1, 3], [1.1, 4.4], [false, false])); + ` + + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src1, profiler, file1, result0.names) + + const func = profiler.getFunctionProfileById(0); + if (func === undefined) + throw new Error(`Cannot fined func.`) + profiler.setFuncSpecializedType(0, ["Array", "Array", "Array", "undefined"].map(t => typeStringToStaticType(t, result1.names))) + + const file2 = tempCFilePath('file2') + const result2 = compile(1, func.src, profiler, file2, result1.names) + + const file3 = tempCFilePath('file3') + const result3 = compile(2, src3, profiler, file3, result2.names) + expect(execute([file0, file1, file2, file3], [result1.main, result2.main, result3.main], tempCFilePath('file4'), tempExecutableFilePath('bscript'))) + .toEqual(`2.100000\n3.200000\n`) +}) + +test('jit compile: anyarray, array', () => { + const src1 = ` +function aarr0(aarr, arr) { + return aarr[0]; +} + +let aarr:any[] = [1, "foo", 3]; +let arr = ["hello"]; + +print(aarr0(aarr, arr)); + ` + + const src3 = ` +print(aarr0(aarr, arr)); + ` + + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src1, profiler, file1, result0.names) + + const func = profiler.getFunctionProfileById(0); + if (func === undefined) + throw new Error(`Cannot fined func.`) + profiler.setFuncSpecializedType(0, ["Array", "Array", "undefined", "undefined"].map(t => typeStringToStaticType(t, result1.names))) + + const file2 = tempCFilePath('file2') + const result2 = compile(1, func.src, profiler, file2, result1.names) + + const file3 = tempCFilePath('file3') + const result3 = compile(2, src3, profiler, file3, result2.names) + expect(execute([file0, file1, file2, file3], [result1.main, result2.main, result3.main], tempCFilePath('file4'), tempExecutableFilePath('bscript'))) + .toEqual(`1\n1\n`) +}) + +test('jit compile: class', () => { + const src1 = ` + +class Rectangle { + x: integer + y: integer + + constructor(x:integer, y:integer) { + this.x = x; + this.y = y; + } +} + +function area(rect) { + return rect.x * rect.y; +} + +print(area(new Rectangle(3, 4))) + ` + + const src3 = ` +print(area(new Rectangle(3, 4))) + ` + + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src1, profiler, file1, result0.names) + + const func = profiler.getFunctionProfileById(0); + if (func === undefined) + throw new Error(`Cannot fined func.`) + profiler.setFuncSpecializedType(0, ["Rectangle", "undefined", "undefined", "undefined"].map(t => typeStringToStaticType(t, result1.names))) + + const file2 = tempCFilePath('file2') + const result2 = compile(1, func.src, profiler, file2, result1.names) + + const file3 = tempCFilePath('file3') + const result3 = compile(2, src3, profiler, file3, result2.names) + expect(execute([file0, file1, file2, file3], [result1.main, result2.main, result3.main], tempCFilePath('file4'), tempExecutableFilePath('bscript'))) + .toEqual(`12\n12\n`) +}) + +test('jit compile: function redefinition after jit compile', () => { + const src1 = ` +function add(a, b) { + return a + b +} +print(add(4, 5)) + ` + + const src3 = ` +print(add(4, 5)) + ` + + const src4 = ` +function add(a, b) { + return a + b + 2 +} +print(add(4, 5)) + ` + + + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src1, profiler, file1, result0.names) + + const func = profiler.getFunctionProfileById(0); + if (func === undefined) + throw new Error(`Cannot fine func.`) + profiler.setFuncSpecializedType(0, ["integer", "integer", "undefined", "undefined"].map(t => typeStringToStaticType(t, result1.names))) + + const file2 = tempCFilePath('file2') + const result2 = compile(1, func.src, profiler, file2, result1.names) + + const file3 = tempCFilePath('file3') + const result3 = compile(2, src3, profiler, file3, result2.names) + + const file4 = tempCFilePath('file4') + const result4 = compile(3, src4, profiler, file4, result3.names) + expect(execute([file0, file1, file2, file3, file4], [result1.main, result2.main, result3.main, result4.main], tempCFilePath('file5'), tempExecutableFilePath('bscript'))) + .toEqual(`9\n9\n11\n`) +}) \ No newline at end of file diff --git a/server/tests/jit/test-jit-utils.ts b/server/tests/jit/test-jit-utils.ts new file mode 100644 index 0000000..e24dc81 --- /dev/null +++ b/server/tests/jit/test-jit-utils.ts @@ -0,0 +1,113 @@ +import {transpile} from "../../src/transpiler/code-generator/code-generator"; +import * as fs from "fs"; +import {execSync} from "child_process"; +import {JitCodeGenerator, jitTranspile} from "../../src/jit/jit-code-generator"; +import {NameInfo, NameTableMaker} from "../../src/transpiler/names"; +import {JitTypeChecker} from "../../src/jit/jit-type-checker"; +import {Profiler} from "../../src/jit/profiler"; +import {runBabelParser} from "../../src/transpiler/utils"; +import {GlobalVariableNameTable} from "../../src/transpiler/code-generator/variables"; +import {convertAst} from "../../src/jit/utils"; + + +const prolog = `// predefined native functions +function print(m: any) {} +function print_i32(m: integer) {} +function performance_now(): integer { return 0 } +` + + +const prologCcode = `/* To compile, cc -DTEST64 this_file.c c-runtime.c */ +#include "../../microcontroller/core/include/c-runtime.h" +#include "../../microcontroller/core/include/profiler.h" + +` +const prologCcode2 = ` +#include +#include + +static void fbody_print(value_t self, value_t m) { + if (is_int_value(m)) + printf("%d\\n", value_to_int(m)); + else if (is_float_value(m)) + printf("%f\\n", value_to_float(m)); + else if (m == VALUE_NULL || m == VALUE_UNDEF) + puts("undefined"); + else if (gc_is_string_object(m)) + puts(gc_string_literal_cstr(m)); + else { + class_object* cls = gc_get_class_of(m); + if (cls == NULL) + puts("??"); + else + printf("\\n", cls->name); + } +} + +static void fbody_print_i32(value_t self, int32_t i) { + printf("%d\\n", i); +} + +/* msec */ +static int32_t fbody_performance_now(value_t self) { + static struct timespec ts0 = { 0, -1 }; + struct timespec ts; + if (ts0.tv_nsec < 0) + clock_gettime(CLOCK_REALTIME, &ts0); + + clock_gettime(CLOCK_REALTIME, &ts); + return (int32_t)((ts.tv_sec - ts0.tv_sec) * 1000 + (ts.tv_nsec - ts0.tv_nsec) / 1000000); +} +` + +const prologCode3 = `struct _print { void (*fptr)(value_t, value_t); const char* sig; } _print = { fbody_print, "(a)v" }; +struct _print_i32 { void (*fptr)(value_t, int32_t); const char* sig; } _print_i32 = { fbody_print_i32, "(i)v" }; +struct _performance_now { int32_t (*fptr)(value_t); const char* sig; } _performance_now = { fbody_performance_now, "()i" }; +` + + +export function tempCFilePath(fname: string) { + return `./temp-files/${fname}.c`; +} + +export function tempExecutableFilePath(fname: string) { + return `./temp-files/${fname}`; +} + +export function initialCompile(destFile: string) { + const result = transpile(0, prolog) + fs.writeFileSync(destFile, prologCcode + prologCcode2 + prologCode3) + return result; +} + +export function compile(id: number, src: string, profiler: Profiler, destFile: string, globalNames?: GlobalVariableNameTable) { + const codeGenerator = (initializerName: string, codeId: number, moduleId: number) => { + return new JitCodeGenerator(initializerName, codeId, moduleId, profiler, src); + } + const typeChecker = (maker: NameTableMaker) => { + return new JitTypeChecker(maker, undefined); + } + + const ast = runBabelParser(src, 1) + convertAst(ast, profiler); + fs.writeFileSync('./temp-files/code.json', JSON.stringify(ast)) + const result = jitTranspile(id, ast, typeChecker, codeGenerator, globalNames, undefined) + fs.writeFileSync(destFile, prologCcode + result.code); + return result; +} + +export function execute(inputFiles: string[], mainFuncNames: string[], lastCFile: string, outputFile: string): string { + const lastCode = ` +${prologCcode} +${mainFuncNames.map(mainFunc => `extern void ${mainFunc}();`).join('\n')} + +int main() { + gc_initialize(); + ${mainFuncNames.map(mainFunc => `try_and_catch(${mainFunc});`).join('\n')} +} + ` + fs.writeFileSync(lastCFile, lastCode) + execSync(`cc -g -DTEST64 -O2 ${inputFiles.map(f => `${f}`).join(' ')} ${lastCFile} ../microcontroller/core/src/c-runtime.c ../microcontroller/core/src/profiler.c -o ${outputFile}`) + return execSync(outputFile).toString() +} +