From 12975d07ac9034fbc921d1b9f39396be9d53607b Mon Sep 17 00:00:00 2001 From: Zack Puhl Date: Sat, 31 Jan 2026 14:19:57 -0500 Subject: [PATCH] [stdlib] Reduce inline code volume from sorting macros (#2831) * reduce codegen in sorting macros * remove testing file... * Fix and some renaming, removing some sub-modules that should not be in use. --------- Co-authored-by: Christoffer Lerno --- lib/std/sort/binarysearch.c3 | 38 ++++++++++++++++++-------------- lib/std/sort/countingsort.c3 | 29 ++++++++++-------------- lib/std/sort/insertionsort.c3 | 18 +++++++-------- lib/std/sort/quicksort.c3 | 36 ++++++++++++++---------------- lib/std/sort/sort.c3 | 3 +++ releasenotes.md | 2 ++ src/compiler/compiler_internal.h | 1 + src/compiler/sema_stmts.c | 4 +++- 8 files changed, 68 insertions(+), 63 deletions(-) diff --git a/lib/std/sort/binarysearch.c3 b/lib/std/sort/binarysearch.c3 index 35c86b6e0..53fd6e9c4 100644 --- a/lib/std/sort/binarysearch.c3 +++ b/lib/std/sort/binarysearch.c3 @@ -9,11 +9,18 @@ in [0, array.len) where x would be inserted or cmp(i) is true and cmp(j) is true @require @is_valid_cmp_fn(#cmp: ...cmp, #list: list, #context: ...context) : "Expected a comparison function which compares values" @require @is_valid_context(...cmp, ...context) : "Expected a valid context" *> -macro usz binarysearch(list, x, cmp = ..., context = ...) @builtin +macro usz binarysearch(list, element, cmp = ..., context = ...) @builtin +{ + var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; + var used_ctx = $defined(context) ??? context : (TypeNotSet)null; + return _binarysearch{$typeof(list), $typeof(element), $typeof(used_cmp), $typeof(used_ctx)}(list, element, used_cmp, used_ctx); +} + +fn usz _binarysearch(ListType list, ElementType element, CmpFnType cmp, ContextType context) @noinline @local { usz i; - var $no_cmp = !$defined(cmp); - var $has_context = $defined(context); + var $no_cmp = $typeof(cmp) == TypeNotSet; + var $has_context = $typeof(context) != TypeNotSet; $if $kindof(list) == SLICE: usz len = lengthof(list); @@ -23,23 +30,23 @@ macro usz binarysearch(list, x, cmp = ..., context = ...) @builtin $if $no_cmp: switch { - case greater(list[half], x): j = half; - case less(list[half], x): i = half + 1; + case greater(list[half], element): j = half; + case less(list[half], element): i = half + 1; default: return half; } $else $switch: $case $defined(cmp(list[0], list[0], context)): - int res = cmp(list[half], x, context); + int res = cmp(list[half], element, context); $case $defined(cmp(list[0], list[0])): assert(!$has_context); - int res = cmp(list[half], x); + int res = cmp(list[half], element); $case $defined(cmp(&list[0], &list[0], context)): - int res = cmp(&list[half], &x, context); + int res = cmp(&list[half], &element, context); $case $defined(cmp(&list[0], &list[0])): assert(!$has_context); - int res = cmp(&list[half], &x); + int res = cmp(&list[half], &element); $default: assert(false, "Invalid comparison function"); $endswitch @@ -59,23 +66,22 @@ macro usz binarysearch(list, x, cmp = ..., context = ...) @builtin $if $no_cmp: switch { - case greater((*list)[half], x): j = half; - case less((*list)[half], x): i = half + 1; + case greater((*list)[half], element): j = half; + case less((*list)[half], element): i = half + 1; default: return half; } $else - $switch: $case $defined(cmp((*list)[0], (*list)[0], context)): - int res = cmp(list[half], x, context); + int res = cmp(list[half], element, context); $case $defined(cmp((*list)[0], (*list)[0])): assert(!$has_context); - int res = cmp((*list)[half], x); + int res = cmp((*list)[half], element); $case $defined(cmp(&(*list)[0], &(*list)[0], context)): - int res = cmp(&(*list)[half], &x, context); + int res = cmp(&(*list)[half], &element, context); $case $defined(cmp(&(*list)[0], &(*list)[0])): assert(!$has_context); - int res = cmp(&(*list)[half], &x); + int res = cmp(&(*list)[half], &element); $default: assert(false, "Invalid comparison function"); $endswitch diff --git a/lib/std/sort/countingsort.c3 b/lib/std/sort/countingsort.c3 index cf7f4b8bf..34b4ad2b2 100644 --- a/lib/std/sort/countingsort.c3 +++ b/lib/std/sort/countingsort.c3 @@ -1,7 +1,4 @@ module std::sort; -import std::sort::is; -import std::sort::cs @public; -import std::sort::qs; <* Sort list using the counting sort algorithm. @@ -15,38 +12,36 @@ macro void countingsort(list, key_fn = ...) @builtin var list_length = $kindof(list) == SLICE ??? list.len : lengthof(*list); var $ListType = $kindof(list) == SLICE ??? $typeof(list) : $typeof(*list); $if $defined(key_fn): - cs::csort{$ListType, $typeof(key_fn)}(list, 0, list_length, key_fn, ~(uint)0); + csort{$ListType, $typeof(key_fn)}(list, 0, list_length, key_fn, ~(uint)0); $else - cs::csort{$ListType, void*}(list, 0, list_length, ~(uint)0); + csort{$ListType, void*}(list, 0, list_length, ~(uint)0); $endif } macro void insertionsort_indexed(list, start, end, cmp = ..., context = ...) @builtin { - // When the context or cmp functions are not defined, we can simply use a dummy/default type. - var $CmpFnType = $defined(cmp) ??? $typeof(cmp) : uint; - var $ContextType = $defined(context) ??? $typeof(context) : uint; + var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; + var used_ctx = $defined(context) ??? context : (TypeNotSet)null; $if $kindof(list) == SLICE: - is::isort{$typeof((list)), $CmpFnType, $ContextType}(list, (usz)start, (usz)end, ...cmp, ...context); + isort{$typeof((list)), $typeof(used_cmp), $typeof(used_ctx)}(list, (usz)start, (usz)end, used_cmp, used_ctx); $else - is::isort{$typeof((*list)), $CmpFnType, $ContextType}(list, (usz)start, (usz)end, ...cmp, ...context); + isort{$typeof((*list)), $typeof(used_cmp), $typeof(used_ctx)}(list, (usz)start, (usz)end, used_cmp, used_ctx); $endif } macro void quicksort_indexed(list, start, end, cmp = ..., context = ...) @builtin { - // When the context or cmp functions are not defined, we can simply use a dummy/default type. - var $CmpFnType = $defined(cmp) ??? $typeof(cmp) : uint; - var $ContextType = $defined(context) ??? $typeof(context) : uint; + var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; + var used_ctx = $defined(context) ??? context : (TypeNotSet)null; $if $kindof(list) == SLICE: - qs::qsort{$typeof((list)), $CmpFnType, $ContextType}(list, (isz)start, (isz)(end-1), ...cmp, ...context); + qsort{$typeof((list)), $typeof(used_cmp), $typeof(used_ctx)}(list, (isz)start, (isz)(end-1), used_cmp, used_ctx); $else - qs::qsort{$typeof((*list)), $CmpFnType, $ContextType}(list, (isz)start, (isz)(end-1), ...cmp, ...context); + qsort{$typeof((*list)), $typeof(used_cmp), $typeof(used_ctx)}(list, (isz)start, (isz)(end-1), used_cmp, used_ctx); $endif } -module std::sort::cs ; +module std::sort @private; alias Counts @private = usz[256]; alias Ranges @private = usz[257]; @@ -90,7 +85,7 @@ fn void csort(ListType list, usz low, usz high, uint byte_idx) @if (NO_KEY_FN) In other words, `csort(a_list, low, high, key_fn: some_fn, byte_idx: index_val)` breaks the existing API since explicitly naming `byte_idx` becomes a requirement. *> -macro void _csort(ListType list, usz low, usz high, uint byte_idx, KeyFn key_fn = ...) @private +macro void _csort(ListType list, usz low, usz high, uint byte_idx, KeyFn key_fn = ...) { if (high <= low) return; $if NO_KEY_FN: diff --git a/lib/std/sort/insertionsort.c3 b/lib/std/sort/insertionsort.c3 index a4ca4780d..9bfbffaf4 100644 --- a/lib/std/sort/insertionsort.c3 +++ b/lib/std/sort/insertionsort.c3 @@ -1,5 +1,4 @@ module std::sort; -import std::sort::is; <* Sort list using the quick sort algorithm. @@ -10,17 +9,16 @@ import std::sort::is; *> macro void insertionsort(list, cmp = ..., context = ...) @builtin @safemacro { - // When the context or cmp functions are not defined, we can simply use a dummy/default type. - var $CmpFnType = $defined(cmp) ??? $typeof(cmp) : uint; - var $ContextType = $defined(context) ??? $typeof(context) : uint; + var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; + var used_ctx = $defined(context) ??? context : (TypeNotSet)null; $if $kindof(list) == SLICE: - is::isort{$typeof(list), $CmpFnType, $ContextType}(list, 0, lengthof(list), ...cmp, ...context); + isort{$typeof(list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, lengthof(list), used_cmp, used_ctx); $else - is::isort{$typeof(*list), $CmpFnType, $ContextType}(list, 0, lengthof(*list), ...cmp, ...context); + isort{$typeof(*list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, lengthof(*list), used_cmp, used_ctx); $endif } -module std::sort::is ; +module std::sort @private; alias ElementType = $typeof(((Type){})[0]); const bool IS_SLICE = Type.kindof == SLICE; @@ -28,10 +26,10 @@ alias ListType = $typefrom(IS_SLICE ??? Type : Type*); macro ElementType list_get(ListType l, i) => IS_SLICE ??? l[i] : (*l)[i]; macro ElementType* list_get_ref(ListType l, i) => IS_SLICE ??? &l[i] : &(*l)[i]; -macro void isort(ListType list, usz low, usz high, CmpFn comp = ..., Context context = ...) +fn void isort(ListType list, usz low, usz high, CmpFn comp, Context context) @noinline @private { - var $has_cmp = $defined(comp); - var $has_context = $defined(context); + var $has_cmp = $typeof(comp) != TypeNotSet; + var $has_context = $typeof(context) != TypeNotSet; var $cmp_by_value = $has_cmp &&& $defined($typefrom(CmpFn.paramsof[0].type) p = list_get(list, 0)); var $has_get_ref = IS_SLICE ||| $defined(&(*list)[0]); for (usz i = low; i < high; ++i) diff --git a/lib/std/sort/quicksort.c3 b/lib/std/sort/quicksort.c3 index dcad5307d..2502d3e7b 100644 --- a/lib/std/sort/quicksort.c3 +++ b/lib/std/sort/quicksort.c3 @@ -1,5 +1,4 @@ module std::sort; -import std::sort::qs; <* Sort list using the quick sort algorithm. @@ -11,13 +10,12 @@ import std::sort::qs; *> macro void quicksort(list, cmp = ..., context = ...) @builtin { - // When the context or cmp functions are not defined, we can simply use a dummy/default type. - var $CmpFnType = $defined(cmp) ??? $typeof(cmp) : uint; - var $ContextType = $defined(context) ??? $typeof(context) : uint; + var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; + var used_ctx = $defined(context) ??? context : (TypeNotSet)null; $if $kindof(list) == SLICE: - qs::qsort{$typeof(list), $CmpFnType, $ContextType}(list, 0, (isz)list.len - 1, ...cmp, ...context); + qsort{$typeof(list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, (isz)list.len - 1, used_cmp, used_ctx); $else - qs::qsort{$typeof(*list), $CmpFnType, $ContextType}(list, 0, (isz)lengthof(*list) - 1, ...cmp, ...context); + qsort{$typeof(*list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, (isz)lengthof(*list) - 1, used_cmp, used_ctx); $endif } @@ -33,17 +31,17 @@ macro void quicksort(list, cmp = ..., context = ...) @builtin *> macro quickselect(list, isz k, cmp = ..., context = ...) @builtin { - // When the context or cmp functions are not defined, we can simply use a dummy/default type. - var $CmpFnType = $defined(cmp) ??? $typeof(cmp) : uint; - var $ContextType = $defined(context) ??? $typeof(context) : uint; + var used_cmp = $defined(cmp) ??? cmp : (TypeNotSet)null; + var used_ctx = $defined(context) ??? context : (TypeNotSet)null; $if $kindof(list) == SLICE: - return qs::qselect{$typeof(list), $CmpFnType, $ContextType}(list, 0, (isz)list.len - 1, k, ...cmp, ...context); + return qselect{$typeof(list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, (isz)list.len - 1, k, used_cmp, used_ctx); $else - return qs::qselect{$typeof(*list), $CmpFnType, $ContextType}(list, 0, (isz)lengthof(*list) - 1, k, ...cmp, ...context); + return qselect{$typeof(*list), $typeof(used_cmp), $typeof(used_ctx)}(list, 0, (isz)lengthof(*list) - 1, k, used_cmp, used_ctx); $endif } -module std::sort::qs ; + +module std::sort @private; alias ElementType = $typeof(((Type){})[0]); const bool IS_SLICE = Type.kindof == SLICE; @@ -65,7 +63,7 @@ alias Stack @private = StackElementItem[64]; // Based on https://alienryderflex.com/quicksort by Darel Rex Finley, Public Domain. -macro void qsort(ListType list, isz low, isz high, CmpFn cmp = ..., Context context = ...) +fn void qsort(ListType list, isz low, isz high, CmpFn cmp, Context context) @noinline { if (low >= 0 && high >= 0 && low < high) { @@ -82,7 +80,7 @@ macro void qsort(ListType list, isz low, isz high, CmpFn cmp = ..., Context cont if (l < h) { - l = @partition(list, l, h, ...cmp, ...context); + l = @partition(list, l, h, cmp, context); stack[i + 1].low = l + 1; stack[i + 1].high = stack[i].high; stack[i++].high = l; @@ -103,7 +101,7 @@ macro void qsort(ListType list, isz low, isz high, CmpFn cmp = ..., Context cont @require low <= k : "kth smallest element is smaller than lower bounds" @require k <= high : "kth smallest element is larger than upper bounds" *> -macro ElementType? qselect(ListType list, isz low, isz high, isz k, CmpFn cmp = ..., Context context = ...) +fn ElementType? qselect(ListType list, isz low, isz high, isz k, CmpFn cmp, Context context) @noinline { if (low >= 0 && high >= 0 && low < high) { @@ -114,7 +112,7 @@ macro ElementType? qselect(ListType list, isz low, isz high, isz k, CmpFn cmp = usz max_retries = 64; while (l <= h && max_retries--) { - pivot = @partition(list, l, h, ...cmp, ...context); + pivot = @partition(list, l, h, cmp, context); if (k == pivot) return list_get(list, k); if (k < pivot) { @@ -129,10 +127,10 @@ macro ElementType? qselect(ListType list, isz low, isz high, isz k, CmpFn cmp = return NOT_FOUND~; } -macro isz @partition(ListType list, isz l, isz h, CmpFn cmp = ..., Context context = ...) +macro isz @partition(ListType list, isz l, isz h, CmpFn cmp, Context context) { - var $has_cmp = $defined(cmp); - var $has_context = $defined(context); + var $has_cmp = $typeof(cmp) != sort::TypeNotSet; + var $has_context = $typeof(context) != sort::TypeNotSet; var $cmp_by_value = $has_cmp &&& $defined($typefrom(CmpFn.paramsof[0].type) v = list_get(list, 0)); ElementType pivot = list_get(list, l); diff --git a/lib/std/sort/sort.c3 b/lib/std/sort/sort.c3 index 6fdb3b36b..1ae2f6634 100644 --- a/lib/std/sort/sort.c3 +++ b/lib/std/sort/sort.c3 @@ -1,5 +1,8 @@ module std::sort; +<* Essentially `EmptySlot` but specifically for sorting macro expansions. *> +typedef TypeNotSet @private = void*; + macro bool @list_is_by_ref(#list) @const { return $kindof(#list) == SLICE ||| ($kindof(#list) == POINTER &&& $kindof(*#list) != SLICE); diff --git a/releasenotes.md b/releasenotes.md index 749fd46a6..dea23cd30 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -5,9 +5,11 @@ ### Changes / improvements ### Stdlib changes +- Summarize sort macros as generic function wrappers to reduce the amount of generated code. #2831 ### Fixes - Add error message if directory with output file name already exists +- Regression where nested lambdas would be evaluated twice. ## 0.7.9 Change list diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index d93b45dbd..dd60c639f 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -712,6 +712,7 @@ typedef struct Decl_ bool no_strip : 1; bool is_cond : 1; bool is_if : 1; + bool is_body_checked : 1; bool attr_nopadding : 1; bool attr_compact : 1; bool resolved_attributes : 1; diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index 6727b6bd0..e799259e7 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -200,6 +200,7 @@ static inline bool sema_analyse_assert_stmt(SemaContext *context, Ast *statement */ static inline bool sema_analyse_break_stmt(SemaContext *context, Ast *statement) { + ASSERT(!statement->contbreak_stmt.is_resolved); // If there is no break target and there is no label, // we skip. if (!context->break_jump.target && !statement->contbreak_stmt.is_label) @@ -3430,7 +3431,8 @@ bool sema_analyse_function_body(SemaContext *context, Decl *func) { // Stop if it's already poisoned. if (!decl_ok(func)) return false; - + if (func->is_body_checked) return true; + func->is_body_checked = true; context->generic_instance = func->is_templated ? declptr(func->instance_id) : NULL; // Check the signature here we test for variadic raw, since we don't support it. Signature *signature = &func->func_decl.signature;