[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 <christoffer@aegik.com>
This commit is contained in:
Zack Puhl
2026-01-31 14:19:57 -05:00
committed by GitHub
parent 3d512abaf7
commit 12975d07ac
8 changed files with 68 additions and 63 deletions

View File

@@ -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) <ListType, ElementType, CmpFnType, ContextType> @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

View File

@@ -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 <Type, KeyFn>;
module std::sort <Type, KeyFn> @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:

View File

@@ -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 <Type, CmpFn, Context>;
module std::sort <Type, CmpFn, Context> @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)

View File

@@ -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 <Type, CmpFn, Context>;
module std::sort <Type, CmpFn, Context> @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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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;