diff --git a/include/maestro.h b/include/maestro.h index fbcb205bc3d2b20cb80fdc71cfdee7786a444658..289c3cea1eea78e8b9c9cb5efdbf8964f744d8b0 100644 --- a/include/maestro.h +++ b/include/maestro.h @@ -61,7 +61,7 @@ extern "C" { ** considered part of a joint entity (e.g., the ranks of an MPI ** application, a set of processes in a worker pool, ...). ** - ** Multiple posix threads can be in the same component. + ** Multiple posix threads can be in the same component. ** ** All processes that use the same *component_name* need to provide a ** unique *component_index*. The indices do not need to be @@ -76,7 +76,7 @@ extern "C" { ** @param workflow_name The workflow ID. ** ** @param component_name The component ID. - ** + ** ** @param component_index The unique index among all processes ** using the same component_name. ** @@ -85,8 +85,8 @@ extern "C" { mstro_status mstro_init(const char *workflow_name, const char *component_name, - uint64_t component_index); - + int64_t component_index); + /** ** @brief De-initialize the maestro library ** @@ -101,7 +101,7 @@ extern "C" { ** ** @return An unmodifiable, unchanging value. **/ - + const char * mstro_version(void); @@ -118,27 +118,27 @@ extern "C" { @section Intro Maestro is the Middleware for Memory- and Data-awareness in Workflows. - + This project has received funding from the European Union’s Horizon 2020 research and innovation program through grant agreement 801101. https://www.maestro-data.eu/ - + @section Entrypoints The core API (@ref MSTRO_Core) provides an interface to the Core Data Object abstraction (@ref MSTRO_CDO), which depends on Attributes (@ref MSTRO_Attr) like Scope (@ref MSTRO_Scope, post-D3.2) and Memory System Model (@ref MSTRO_MSM, post-D3.2), as well as Transformations (@ref - MSTRO_Transformations, post-D3.2) and movement (@ref MSTRO_Pool). + MSTRO_Transformations, post-D3.2) and movement (@ref MSTRO_Pool). The pool manager protocol is based on the @ref MSTRO_Pool_Protocol protobuf (https://developers.google.com/protocol-buffers) specification which should make it possible to implement components for maestro in other languages. - + TODO Pool Manager (@ref MSTRO_I_OFI, @ref MSTRO_I_PM_Registry, @ref MSTRO_Protocols, etc.) @ref MSTRO_Transport (./transport) diff --git a/include/maestro/status.h b/include/maestro/status.h index 18f4e1abc830cfacd73f36992f4910f1bae5a115..e32a73ecf0f27fa5e22292dc4247c4a1ca09fb5b 100644 --- a/include/maestro/status.h +++ b/include/maestro/status.h @@ -62,6 +62,7 @@ extern "C" { MSTRO_INVMSG, /**< invalid pool protocol message */ MSTRO_OFI_FAIL, /**< fabric error */ MSTRO_TIMEOUT, /**< timeout occured */ + MSTRO_NOT_TWICE, /**< can only be called once from a process */ /**@private */ MSTRO_NO_PM, /**< an operation was not performed because @@ -72,7 +73,7 @@ extern "C" { MSTRO_NOMATCH, /**< A regular expression or string comparison * did not match */ MSTRO_NOENT, /**< A lookup did not find a suitable entry */ - + mstro_status_MAX /* number of enum values */ } mstro_status; @@ -84,7 +85,7 @@ extern "C" { **/ const char * mstro_status_description(mstro_status s); - + /** @} (end of Groyp MSTRO_Status) */ #ifdef __cplusplus } /* extern "C" */ diff --git a/maestro/core.c b/maestro/core.c index 5f8a951b4e618c1c74f808456cf544ba70510031..8837e2aee3f52432201207879553e54466a62fae 100644 --- a/maestro/core.c +++ b/maestro/core.c @@ -310,17 +310,9 @@ mstro_core_init(const char *workflow_name, struct mstro_core_initdata *data = malloc(sizeof(struct mstro_core_initdata)); if(data) { - if(workflow_name==NULL) { - data->workflow_name = strdup(getenv(MSTRO_ENV_WORKFLOW_NAME)); - } else { - data->workflow_name = strdup(workflow_name); - } + data->workflow_name = strdup(workflow_name); - if(component_name==NULL) { - data->component_name = strdup(getenv(MSTRO_ENV_COMPONENT_NAME)); - } else { - data->component_name = strdup(component_name); - } + data->component_name = strdup(component_name); data->component_index = component_index; @@ -410,7 +402,10 @@ mstro_core_init(const char *workflow_name, } else { ERR("ERROR: Thread %" PRIxPTR " calling mstro_core_init again\n", (intptr_t)pthread_self()); - status = MSTRO_FAIL; + status = MSTRO_NOT_TWICE; + /*release lock and exit*/ + pthread_mutex_unlock(&g_initdata_mtx); + goto BAILOUT; } pthread_mutex_unlock(&g_initdata_mtx); diff --git a/maestro/init.c b/maestro/init.c index e2ea643d76310cc28a5f9fbdab9b276dc1760f31..c286b605d4d3123dda09ebc1ea64b4a35a70c58d 100644 --- a/maestro/init.c +++ b/maestro/init.c @@ -57,7 +57,7 @@ static mstro_nanosec_t g_startup_time; mstro_status mstro_init(const char *workflow_name, const char *component_name, - uint64_t component_index) + int64_t component_index) { mstro_status status = MSTRO_UNIMPL; @@ -80,14 +80,15 @@ mstro_init(const char *workflow_name, goto BAILOUT; } g_startup_time = mstro_clock(); - + /* FIXME: read config */ WARN("should read config file here\n"); - if(workflow_name==NULL) { + /*Environment variables overrides values defined in user code*/ + if(getenv(MSTRO_ENV_WORKFLOW_NAME)!=NULL) { workflow_name = getenv(MSTRO_ENV_WORKFLOW_NAME); } - if(component_name==NULL) { + if(getenv(MSTRO_ENV_COMPONENT_NAME)!=NULL) { component_name = getenv(MSTRO_ENV_COMPONENT_NAME); } if(workflow_name==NULL || component_name==NULL) { @@ -95,7 +96,13 @@ mstro_init(const char *workflow_name, status = MSTRO_INVARG; goto BAILOUT; } - status = mstro_core_init(workflow_name, component_name, component_index + /* check that component index is not negative, because negative is reserved */ + if(component_index < 0){ + ERR("Component index can not be negative \n"); + status = MSTRO_INVARG; + goto BAILOUT; + } + status = mstro_core_init(workflow_name, component_name, (uint64_t) component_index /* FIXME: core config file fragment parameter */ ); BAILOUT: @@ -135,5 +142,3 @@ mstro_finalize(void) BAILOUT: return status; } - - diff --git a/maestro/status.c b/maestro/status.c index 905488c868838f658c4ea7472c23faa687deaf68..f6ceb08018253b247282760f7e4f79ccf24a12cf 100644 --- a/maestro/status.c +++ b/maestro/status.c @@ -47,6 +47,7 @@ const char *g_mstro_status_names[] = { [MSTRO_INVMSG] = "invalid pool protocol message", [MSTRO_OFI_FAIL] = "network fabric error", [MSTRO_TIMEOUT] = "timeout occurred", + [MSTRO_NOT_TWICE] = "can only be called once from a process", [MSTRO_NO_PM] = "No pool manager connection exists", [MSTRO_NOMATCH] = "A matching entry does not exist, or a template does not apply", @@ -59,6 +60,6 @@ mstro_status_description(mstro_status s) { if(s>=mstro_status_MAX) s=mstro_status_MAX; - + return g_mstro_status_names[s]; } diff --git a/tests/check_init.c b/tests/check_init.c index 8f8d95ce01728832adad7fe43ba1dce069c8cbfd..1597a4a54ccb2d69a0e519f4abedabc88a637a46 100644 --- a/tests/check_init.c +++ b/tests/check_init.c @@ -51,6 +51,7 @@ CHEAT_TEST(init_finalize_works, cheat_assert(MSTRO_OK == mstro_finalize()); ) + CHEAT_TEST(init_finalize_fromenv_works, { char wf[1024]; @@ -61,9 +62,36 @@ CHEAT_TEST(init_finalize_fromenv_works, putenv(cn); cheat_assert(mstro_status_description(mstro_status_MAX)==mstro_status_description(mstro_status_MAX+1)); cheat_assert(MSTRO_OK == mstro_init(NULL, NULL, 42)); - cheat_assert(NULL!=mstro_component_name()); + cheat_assert(NULL!=mstro_component_name()); cheat_assert(MSTRO_OK == mstro_finalize()); } ) +CHEAT_TEST(init_finalize_fromenv_onlycomponnent, + { + char cn[1024]; + sprintf(cn,"%s=%s", MSTRO_ENV_COMPONENT_NAME, "InitEnv"); + putenv(cn); + cheat_assert(mstro_status_description(mstro_status_MAX)==mstro_status_description(mstro_status_MAX+1)); + cheat_assert(MSTRO_INVARG == mstro_init(NULL, NULL, 42)); + cheat_assert(NULL!=mstro_component_name()); + cheat_assert(MSTRO_FAIL == mstro_finalize()); + } + ) + +CHEAT_TEST(init_finalize_negativecomponentindex, + { + cheat_assert(mstro_status_description(mstro_status_MAX)==mstro_status_description(mstro_status_MAX+1)); + cheat_assert(MSTRO_INVARG == mstro_init("Tests","Init",-20)); + cheat_assert(MSTRO_FAIL == mstro_finalize()); + } + ) +CHEAT_TEST(init_twice_finalize, + { + cheat_assert(mstro_status_description(mstro_status_MAX)==mstro_status_description(mstro_status_MAX+1)); + cheat_assert(MSTRO_OK == mstro_init("Tests","Init",20)); + cheat_assert(MSTRO_NOT_TWICE == mstro_init("Tests","Init",20)); + cheat_assert(MSTRO_OK == mstro_finalize()); + } + ) diff --git a/tests/check_subscribe_local.c b/tests/check_subscribe_local.c index 0fc5a7f31310c765497717adbe6896295b2a68ce..c05f181be43a76401f798c3ebd3e69cd0f0f9c83 100644 --- a/tests/check_subscribe_local.c +++ b/tests/check_subscribe_local.c @@ -46,46 +46,58 @@ #include <unistd.h> CHEAT_DECLARE( - mstro_subscription decl_sub, offer_sub, withdraw_sub; + mstro_subscription decl_sub, decl_ack_sub, offer_sub, withdraw_sub, seal_sub, dispose_sub; + mstro_cdo_selector match_all_selector; - atomic_bool subscriber_dead; - - + + /* perform the subscriptions */ void subscribe(void) { - mstro_cdo_selector match_all_selector; + cheat_assert(MSTRO_OK==mstro_cdo_selector_create( NULL, NULL, NULL, &match_all_selector)); cheat_assert(MSTRO_OK==mstro_subscribe(match_all_selector, - MSTRO_POOL_EVENT_DECLARE, + MSTRO_POOL_EVENT_DECLARE_ACK, false, - &decl_sub)); + &decl_ack_sub)); + + cheat_assert(MSTRO_OK==mstro_subscribe(match_all_selector, + MSTRO_POOL_EVENT_DECLARE, + false, + &decl_sub)); + + cheat_assert(MSTRO_OK==mstro_subscribe(match_all_selector,MSTRO_POOL_EVENT_SEAL,false, &seal_sub)); + + cheat_assert(MSTRO_OK==mstro_subscribe(match_all_selector, MSTRO_POOL_EVENT_OFFER, false, &offer_sub)); cheat_assert(MSTRO_OK==mstro_subscribe(match_all_selector, - MSTRO_POOL_EVENT_OFFER, + MSTRO_POOL_EVENT_WITHDRAW, true, &withdraw_sub)); + + cheat_assert(MSTRO_OK==mstro_subscribe(match_all_selector,MSTRO_POOL_EVENT_DISPOSE,false, &dispose_sub)); } /* perform the un-subscriptions */ void unsubscribe(void) { - mstro_cdo_selector match_all_selector; - cheat_assert(MSTRO_OK==mstro_cdo_selector_create( - NULL, NULL, NULL, &match_all_selector)); + cheat_assert(MSTRO_OK==mstro_cdo_selector_dispose(match_all_selector)); cheat_assert(MSTRO_OK==mstro_subscription_dispose(decl_sub)); cheat_assert(MSTRO_OK==mstro_subscription_dispose(offer_sub)); cheat_assert(MSTRO_OK==mstro_subscription_dispose(withdraw_sub)); + cheat_assert(MSTRO_OK==mstro_subscription_dispose(decl_ack_sub)); + cheat_assert(MSTRO_OK==mstro_subscription_dispose(seal_sub)); + cheat_assert(MSTRO_OK==mstro_subscription_dispose(dispose_sub)); } - size_t num_offer=0, num_withdraw=0, num_declare=0; + size_t num_offer=0, num_withdraw=0, num_declare=0, num_decl_ack=0, num_seal=0, num_dispose=0; void mark_subscriber_dead(void*unused) { @@ -93,18 +105,29 @@ CHEAT_DECLARE( subscriber_dead = true; cheat_assert(num_declare==3); + cheat_assert(num_decl_ack==3); + cheat_assert(num_seal==3); cheat_assert(num_offer==3); cheat_assert(num_withdraw==3); + cheat_assert(num_dispose==3); + fprintf(stdout, "Subscriber terminated\n"); } - + void* subscriber_thread(void*unused) { unused=unused; + struct timespec t; + t.tv_sec = 0; + t.tv_nsec = 100; + mstro_pool_event e2; + pthread_cleanup_push(mark_subscriber_dead, NULL); - + + cheat_assert(MSTRO_OK!=mstro_subscription_timedwait(decl_sub, &t, &e2)); + fprintf(stdout, "subscriber started\n"); while(1) { mstro_pool_event e; @@ -117,6 +140,25 @@ CHEAT_DECLARE( fprintf(stdout, "%zu decl event(s)\n", count); num_declare+=count; } + + cheat_assert(MSTRO_OK==mstro_subscription_poll(decl_ack_sub,&e)); + if(e!=NULL) { + mstro_pool_event tmp; + size_t count=0; + LL_COUNT(e,tmp, count); + fprintf(stdout, "%zu decl ack event(s)\n", count); + num_decl_ack+=count; + } + + cheat_assert(MSTRO_OK==mstro_subscription_poll(seal_sub,&e)); + if(e!=NULL) { + mstro_pool_event tmp; + size_t count=0; + LL_COUNT(e,tmp, count); + fprintf(stdout, "%zu seal event(s)\n", count); + num_seal+=count; + } + cheat_assert(MSTRO_OK==mstro_subscription_poll(offer_sub,&e)); if(e!=NULL) { mstro_pool_event tmp; @@ -125,6 +167,10 @@ CHEAT_DECLARE( fprintf(stdout, "%zu offer event(s)\n", count); num_offer+=count; } + // test invalid input + // FIXME mstro_subscription_wait wait forever with correct input + cheat_assert(MSTRO_OK!=mstro_subscription_wait(withdraw_sub, NULL)); + cheat_assert(MSTRO_OK==mstro_subscription_poll(withdraw_sub,&e)); if(e!=NULL) { mstro_pool_event tmp; @@ -135,15 +181,25 @@ CHEAT_DECLARE( cheat_assert(MSTRO_OK==mstro_subscription_ack(withdraw_sub, e)); num_withdraw+=count; } + + cheat_assert(MSTRO_OK==mstro_subscription_poll(dispose_sub,&e)); + if(e!=NULL) { + mstro_pool_event tmp; + size_t count=0; + LL_COUNT(e,tmp, count); + fprintf(stdout, "%zu dispose event(s)\n", count); + num_dispose+=count; + } + if(e==NULL) cheat_assert(MSTRO_INVARG==mstro_pool_event_dispose(e)); else cheat_assert(MSTRO_OK==mstro_pool_event_dispose(e)); - + pthread_testcancel(); } pthread_cleanup_pop(1); - + } void @@ -170,7 +226,7 @@ CHEAT_DECLARE( CHEAT_TEST(cdo_local_subscriptions_work, cheat_assert(MSTRO_OK == mstro_init("Tests","LOCALSUBSCRIPTIONS",0)); - + subscribe(); pthread_t sub_thread; cheat_assert(0==pthread_create(&sub_thread, @@ -182,13 +238,10 @@ CHEAT_TEST(cdo_local_subscriptions_work, operate(); cheat_assert(0==pthread_cancel(sub_thread)); - + while(!subscriber_dead) { sleep(1); } unsubscribe(); cheat_assert(MSTRO_OK == mstro_finalize()); ) - - - diff --git a/tests/check_uuid.c b/tests/check_uuid.c index 8afc446326ca2a0ee51ee026873f7c971e116a7c..a476b1b5ac8b25e16af5c131e04dd7bebd4e7180 100644 --- a/tests/check_uuid.c +++ b/tests/check_uuid.c @@ -52,19 +52,19 @@ CHEAT_TEST(uuid_create_export, uuid_t *uuid1; cheat_assert(UUID_RC_OK==uuid_create(&uuid1)); cheat_assert(UUID_RC_OK==uuid_load(uuid1,"nil")); - + char *str = NULL; cheat_assert(UUID_RC_OK==uuid_export(uuid1,UUID_FMT_STR,&str, NULL)); free(str); cheat_assert(UUID_RC_OK==uuid_destroy(uuid1)); ) - + CHEAT_TEST(uuid_create_dup, uuid_t *uuid1, *uuid2; cheat_assert(UUID_RC_OK==uuid_create(&uuid1)); cheat_assert(UUID_RC_OK==uuid_load(uuid1,"nil")); - + cheat_assert(UUID_RC_OK==uuid_clone(uuid1, &uuid2)); cheat_assert(UUID_RC_OK!=uuid_load(uuid2,"foo")); @@ -72,4 +72,53 @@ CHEAT_TEST(uuid_create_dup, cheat_assert(UUID_RC_OK==uuid_destroy(uuid2)); ) + CHEAT_TEST(uuid_compare, + uuid_t *uuid1, *uuid2; + + int result = -1; + cheat_assert(UUID_RC_OK==uuid_create(&uuid1)); + cheat_assert(UUID_RC_OK==uuid_load(uuid1,"nil")); + + + cheat_assert(UUID_RC_OK==uuid_clone(uuid1, &uuid2)); + /*Test cloned uuids if they are the same*/ + uuid_compare(uuid1, uuid2, &result); + cheat_assert(result==0); + + + cheat_assert(UUID_RC_OK==uuid_destroy(uuid1)); + uuid1 = NULL; /*set the pointer manually to NULL to avoid further use*/ + + cheat_assert(UUID_RC_OK==uuid_compare(uuid1, uuid2, &result)); + cheat_assert(UUID_RC_OK==uuid_compare(uuid2, uuid1, &result)); + cheat_assert(UUID_RC_OK==uuid_destroy(uuid2)); + uuid2 = NULL; /*set the pointer manually to NULL to avoid further use*/ + + //compare two null uuids + cheat_assert(UUID_RC_OK==uuid_compare(uuid1, uuid2, &result)); + cheat_assert(result==0); + ) + + +CHEAT_TEST(uuid_import_export, + uuid_t *uuid1, *uuid2; + uuid_rc_t msg; + size_t data_len = 100; + char *data = NULL; + char *data2 = NULL; + cheat_assert(UUID_RC_OK==uuid_create(&uuid1)); + cheat_assert(UUID_RC_OK==uuid_create(&uuid2)); + cheat_assert(UUID_RC_OK==uuid_load(uuid1,"nil")); + + + cheat_assert(UUID_RC_OK==uuid_export(uuid1, UUID_FMT_TXT, &data, NULL)); + + cheat_assert(UUID_RC_OK==uuid_export(uuid1, UUID_FMT_STR, &data2, NULL)); + + msg = uuid_import(uuid2, UUID_FMT_STR, data2, &data_len); + + printf("%s", uuid_error(msg)); + + cheat_assert(UUID_RC_OK==uuid_destroy(uuid1)); + ) diff --git a/tests/coverage.c b/tests/coverage.c index feff9477b6c46deafc060de8e7cd186ba573a8c6..5761b23703b4931a3cca371885ebb4143cdaeb43 100644 --- a/tests/coverage.c +++ b/tests/coverage.c @@ -44,8 +44,10 @@ #include "maestro.h" #include "maestro/i_cdo.h" #include <stdint.h> - +#include "maestro/i_uuid_ui64.h" +#include "maestro/i_uuid_ui128.h" #include "maestro/i_base64.h" + CHEAT_TEST(base64, { size_t dummy; @@ -54,8 +56,8 @@ CHEAT_TEST(base64, /* hopefully no one has that much memory... */ cheat_assert(NULL==base64_encode(str, SIZE_MAX/3 ,&dummy)); }) - - + + CHEAT_TEST(base64decode, { size_t dummy; @@ -63,8 +65,8 @@ CHEAT_TEST(base64, cheat_assert(NULL==base64_decode((const unsigned char*)"===", 3, &dummy)); }) - -CHEAT_TEST(null_cdo, { + +CHEAT_TEST(null_cdo, { cheat_assert(MSTRO_CDO_STATE_INVALID==mstro_cdo_state_get(NULL)) ; cheat_assert(NULL==mstro_cdo_name(NULL)); }) @@ -96,3 +98,103 @@ CHEAT_TEST(invalid_cdo_withdraw, { CHEAT_TEST(invalid_cdo_dispose, { cheat_assert(MSTRO_INVARG==mstro_cdo_dispose(NULL)); }) + +CHEAT_TEST(math64, + ui64_t zero,max, value_64, tmp_64; + unsigned long value_ul = 50505050; + unsigned long value_ul2; + char * str; + zero = ui64_zero(); + max = ui64_max(); + value_64 = ui64_n2i(value_ul); + value_ul2 = ui64_i2n(value_64); + value_64 = ui64_s2i("01010101010101", NULL, 2); + cheat_assert( NULL == ui64_i2s(value_64, NULL, 0 , 2)); + + tmp_64 = ui64_add(max, zero, NULL); + cheat_assert(0 == ui64_cmp(max,tmp_64)); + + tmp_64 = ui64_sub(max, zero, NULL); + cheat_assert(0 == ui64_cmp(max,tmp_64)); + + tmp_64 = ui64_addn(zero, 1, NULL); + + tmp_64 = ui64_subn(tmp_64, 1, NULL); + + cheat_assert(0 == ui64_cmp(tmp_64,zero)); + + tmp_64 = ui64_mul(max, zero, NULL); + + cheat_assert(0 == ui64_cmp(tmp_64,zero)); + + tmp_64 = ui64_muln(max,1, NULL); + + cheat_assert(0 == ui64_cmp(tmp_64,max)); + + tmp_64 = ui64_div(max,value_64, NULL); + + cheat_assert(0 != ui64_cmp(max,tmp_64)); + + tmp_64 = ui64_divn(value_64, 1, NULL); + + cheat_assert(0 == ui64_cmp(value_64,tmp_64)); + + tmp_64 = ui64_rol(value_64,1, NULL); + + tmp_64 = ui64_ror(tmp_64,1, NULL); + + cheat_assert(0 == ui64_cmp(value_64,tmp_64)); + + cheat_assert(1 < ui64_len(max)); + + ) + + CHEAT_TEST(math128, + ui128_t zero,max, value_128, tmp_128; + unsigned long value_ul = 50505050; + unsigned long value_ul2; + char * str; + zero = ui128_zero(); + max = ui128_max(); + value_128 = ui128_n2i(value_ul); + value_ul2 = ui128_i2n(value_128); + value_128 = ui128_s2i("01010101010101", NULL, 2); + cheat_assert( NULL == ui128_i2s(value_128, NULL, 0 , 2)); + + tmp_128 = ui128_add(max, zero, NULL); + cheat_assert(0 == ui128_cmp(max,tmp_128)); + + tmp_128 = ui128_sub(max, zero, NULL); + cheat_assert(0 == ui128_cmp(max,tmp_128)); + + tmp_128 = ui128_addn(zero, 1, NULL); + + tmp_128 = ui128_subn(tmp_128, 1, NULL); + + cheat_assert(0 == ui128_cmp(tmp_128,zero)); + + tmp_128 = ui128_mul(max, zero, NULL); + + cheat_assert(0 == ui128_cmp(tmp_128,zero)); + + tmp_128 = ui128_muln(max,1, NULL); + + cheat_assert(0 == ui128_cmp(tmp_128,max)); + + tmp_128 = ui128_div(max,value_128, NULL); + + cheat_assert(0 != ui128_cmp(max,tmp_128)); + + tmp_128 = ui128_divn(value_128, 1, NULL); + + cheat_assert(0 == ui128_cmp(value_128,tmp_128)); + + tmp_128 = ui128_rol(value_128,1, NULL); + + tmp_128 = ui128_ror(tmp_128,1, NULL); + + cheat_assert(0 == ui128_cmp(value_128,tmp_128)); + + cheat_assert(1 < ui128_len(max)); + + )