cyclic dependency checking

This commit is contained in:
jacob 2025-08-23 21:48:59 -05:00
parent b42299688d
commit 86c24dd112
24 changed files with 260 additions and 116 deletions

View File

@ -1,7 +1,5 @@
@Layer base
@Dep mp3
//- Api
@IncludeC base_core.h
@IncludeC base_intrinsics.h

View File

@ -0,0 +1,9 @@
@Layer base_win32
//- Api
@IncludeC base_win32_entry.h
@IncludeC base_win32_job.h
//- Impl
@IncludeC base_win32_entry.c
@IncludeC base_win32_job.c

View File

@ -0,0 +1,7 @@
@Layer gpu_dx12
//- Api
@IncludeC gpu_dx12.h
//- Impl
@IncludeC gpu_dx12.c

View File

@ -230,6 +230,7 @@ Struct(L_BlobItemList)
Struct(L_Blob)
{
i64 id;
L_Blob *next;
L_BlobItemList names;
@ -243,8 +244,6 @@ Struct(L_Blob)
L_BlobItemList default_windows_impls;
L_BlobItemList errors;
L_BlobTopoColor topo_color;
};
Struct(L_ParseResult)
@ -261,15 +260,7 @@ L_BlobItem *L_PushBlobItem(Arena *arena, L_BlobItemList *list, String token_file
item->s = item_string;
item->token_file = token_file;
item->token_pos = token_pos;
if (list->last)
{
list->last->next = item;
}
else
{
list->first = item;
}
list->last = item;
SllQueuePush(list->first, list->last, item);
++list->count;
return item;
}
@ -294,16 +285,8 @@ L_ParseResult L_ParseBlobsFromPaths(Arena *arena, StringList paths)
L_TokenList tokens = L_TokensFromString(scratch.arena, data);
L_ParseMode mode = L_ParseMode_None;
L_Blob *blob = PushStruct(arena, L_Blob);
if (result.last_blob)
{
result.last_blob->next = blob;
}
else
{
result.first_blob = blob;
}
result.last_blob = blob;
++result.blobs_count;
SllQueuePush(result.first_blob, result.last_blob, blob);
blob->id = result.blobs_count++;
for (L_Token *token = tokens.first->next; !token->eof; token = token->next)
{
String s = token->s;
@ -394,7 +377,15 @@ L_ParseResult L_ParseBlobsFromPaths(Arena *arena, StringList paths)
{
String name = blob->names.first->s;
u64 hash = HashFnv64(Fnv64Basis, name);
SetDictValue(arena, result.blobs_dict, hash, (u64)blob);
DictEntry *entry = EnsureDictEntry(arena, result.blobs_dict, hash);
if (entry->value == 0)
{
entry->value = (u64)blob;
}
else
{
L_PushBlobItem(arena, &blob->errors, file, blob->names.first->token_pos, StringF(arena, "Layer '%F' already exists", FmtString(name)));
}
}
else if (blob->names.count == 0)
{
@ -445,15 +436,7 @@ L_TopoItem *L_PushTopoItem(Arena *arena, L_TopoItemList *list, String token_file
item->s = item_string;
item->token_file = token_file;
item->token_pos = token_pos;
if (list->last)
{
list->last->next = item;
}
else
{
list->first = item;
}
list->last = item;
SllQueuePush(list->first, list->last, item);
++list->count;
return item;
}
@ -466,95 +449,170 @@ L_Topo L_TopoFromLayerName(Arena *arena, StringList starting_layer_names, String
/* Parse blobs */
L_ParseResult parsed = L_ParseBlobsFromPaths(scratch.arena, paths);
/* Topological sort */
u64 sorted_count = 0;
L_Blob **sorted = PushStructsNoZero(scratch.arena, L_Blob *, parsed.blobs_count);
Struct(Node)
{
/* Build array from post-order DFS */
Node *next;
L_Blob *blob;
i32 state; /* 0 = enter, 1 = exit */
};
/* Topological sort via post-order DFS */
Node *first_stack = 0;
Node *first_post = 0;
Node *last_post = 0;
i8 *seen_blobs = PushStructs(scratch.arena, i8, parsed.blobs_count);
i8 *entered_blobs = PushStructs(scratch.arena, i8, parsed.blobs_count);
{
u64 queue_count = 0;
L_Blob **queue = PushStructsNoZero(scratch.arena, L_Blob *, parsed.blobs_count);
for (StringListNode *n = starting_layer_names.first; n; n = n->next)
{
String s = n->s;
L_Blob *starting_blob = (L_Blob *)DictValueFromHash(parsed.blobs_dict, HashFnv64(Fnv64Basis, s));
if (starting_blob)
{
if (starting_blob->topo_color == L_BlobTopoColor_None)
Node *n = PushStruct(scratch.arena, Node);
n->blob = starting_blob;
n->state = 0;
SllStackPush(first_stack, n);
}
else
{
queue[queue_count++] = starting_blob;
L_PushTopoItem(arena, &result.errors, Lit(""), 0, StringF(arena, "Starting layer not found with name '%F'", FmtString(s)));
}
}
while (first_stack)
{
L_Blob *blob = 0;
Node *stack_node = first_stack;
blob = stack_node->blob;
SllStackPop(first_stack);
if (stack_node->state == 0)
{ /* Enter */
if (!seen_blobs[blob->id])
{
if (entered_blobs[blob->id])
{ /* Cycle detected */
Node *first_cycle = 0;
{
i8 *cycle_blobs_seen = PushStructs(scratch.arena, i8, parsed.blobs_count);
{
Node *cycle_node = PushStruct(scratch.arena, Node);
cycle_node->blob = blob;
SllStackPush(first_cycle, cycle_node);
}
for (Node *n = first_stack; n; n = n->next)
{
if (entered_blobs[n->blob->id])
{
if (!cycle_blobs_seen[n->blob->id])
{
Node *cycle_node = PushStruct(scratch.arena, Node);
cycle_node->blob = n->blob;
SllStackPush(first_cycle, cycle_node);
cycle_blobs_seen[n->blob->id] = 1;
}
}
if (n->blob == blob) break;
}
}
StringList cycle_names_list = ZI;
for (Node *cycle = first_cycle; cycle; cycle = cycle->next)
{
String name = ZI;
if (cycle->blob->names.count > 0) name = cycle->blob->names.first->s;
PushStringToList(scratch.arena, &cycle_names_list, name);
}
String cycle_names_str = StringFromStringList(scratch.arena, cycle_names_list, Lit(" -> "));
String name = ZI;
String token_file = ZI;
i64 token_pos = 0;
if (blob->names.count > 0)
{
name = blob->names.first->s;
token_file = PushString(arena, blob->names.first->token_file);
token_pos = blob->names.first->token_pos;
}
if (cycle_names_list.count > 1)
{
L_PushTopoItem(arena, &result.errors, token_file, token_pos, StringF(arena, "Cyclic dependency detected while processing dependencies for layer '%F' (%F)", FmtString(name), FmtString(cycle_names_str)));
}
else
{
L_PushTopoItem(arena, &result.errors, token_file, token_pos, StringF(arena, "Cyclic dependency detected while processing dependencies for layer '%F'", FmtString(name)));
}
}
else
{
L_PushTopoItem(arena, &result.errors, Lit(""), 0, StringF(arena, "Layer not found with name '%F'", FmtString(s)));
}
}
while (queue_count > 0)
/* Push blob impls */
#if PlatformIsWindows
for (L_BlobItem *impl_item = blob->default_windows_impls.first; impl_item; impl_item = impl_item->next)
{
L_Blob *blob = queue[--queue_count];
blob->topo_color = L_BlobTopoColor_Visiting;
b32 has_unvisited_deps = 0;
String impl_name = impl_item->s;
u64 impl_hash = HashFnv64(Fnv64Basis, impl_name);
L_Blob *impl = (L_Blob *)DictValueFromHash(parsed.blobs_dict, impl_hash);
if (impl)
{
if (!seen_blobs[impl->id])
{
Node *n = PushStruct(scratch.arena, Node);
n->blob = impl;
n->state = 0;
SllStackPush(first_stack, n);
}
}
else
{
String file = PushString(arena, impl_item->token_file);
L_PushTopoItem(arena, &result.errors, file, impl_item->token_pos, StringF(arena, "Layer '%F' not found", FmtString(impl_name)));
}
}
#endif
/* Push blob exit */
{
entered_blobs[blob->id] = 1;
Node *n = PushStruct(scratch.arena, Node);
n->blob = blob;
n->state = 1;
SllStackPush(first_stack, n);
}
/* Push blob deps */
for (L_BlobItem *dep_item = blob->deps.first; dep_item; dep_item = dep_item->next)
{
String dep_name = dep_item->s;
u64 dep_hash = HashFnv64(Fnv64Basis, dep_name);
L_Blob *dep_blob = (L_Blob *)DictValueFromHash(parsed.blobs_dict, dep_hash);
if (dep_blob)
L_Blob *dep = (L_Blob *)DictValueFromHash(parsed.blobs_dict, dep_hash);
if (dep)
{
if (dep_blob->topo_color == L_BlobTopoColor_None)
if (!seen_blobs[dep->id])
{
has_unvisited_deps = 1;
dep_blob->topo_color = L_BlobTopoColor_Queued;
queue[queue_count++] = dep_blob;
}
else if (dep_blob->topo_color == L_BlobTopoColor_Visiting)
{
String a = ZI;
String b = ZI;
if (blob->names.count > 0) a = blob->names.first->s;
if (dep_blob->names.count > 0) b = dep_blob->names.first->s;
String s = StringF(arena, "Cyclic dependency detected while processing layer '%F' -> '%F'", FmtString(a), FmtString(b));
String file = PushString(arena, dep_item->token_file);
L_PushTopoItem(arena, &result.errors, file, dep_item->token_pos, s);
Node *n = PushStruct(scratch.arena, Node);
n->blob = dep;
n->state = 0;
SllStackPush(first_stack, n);
}
}
else
{
String s = StringF(arena, "Layer '%F' not found", FmtString(dep_name));
String file = PushString(arena, dep_item->token_file);
L_PushTopoItem(arena, &result.errors, file, dep_item->token_pos, s);
L_PushTopoItem(arena, &result.errors, file, dep_item->token_pos, StringF(arena, "Layer '%F' not found", FmtString(dep_name)));
}
}
}
}
if (has_unvisited_deps)
{
queue[queue_count++] = blob;
blob->topo_color = L_BlobTopoColor_Queued;
}
else
{
sorted[sorted_count++] = blob;
blob->topo_color = L_BlobTopoColor_Finished;
{ /* Exit */
entered_blobs[blob->id] = 0;
seen_blobs[blob->id] = 1;
SllQueuePush(first_post, last_post, stack_node);
}
}
}
/* Reverse sorted array */
i64 l = 0;
i64 r = sorted_count - 1;
while (l < r)
{
L_Blob *swp = sorted[l];
sorted[l] = sorted[r];
sorted[r] = swp;
++l;
--r;
}
}
/* Process sorted blobs into result */
for (u64 i = 0; i < sorted_count; ++i)
/* Process post blobs into result */
for (Node *n = first_post; n; n = n->next)
{
L_Blob *blob = sorted[i];
L_Blob *blob = n->blob;
/* C includes */
for (L_BlobItem *bitem = blob->c_includes.first; bitem; bitem = bitem->next)
{
@ -574,7 +632,7 @@ L_Topo L_TopoFromLayerName(Arena *arena, StringList starting_layer_names, String
/* Process errors of untouched blobs */
for (L_Blob *blob = parsed.first_blob; blob; blob = blob->next)
{
if (blob->topo_color == L_BlobTopoColor_None && blob->errors.count > 0)
if (!seen_blobs[blob->id] && blob->errors.count > 0)
{
for (L_BlobItem *bitem = blob->errors.first; bitem; bitem = bitem->next)
{
@ -613,15 +671,7 @@ Error *PushError(Arena *arena, ErrorList *list, String file, i64 pos, String s)
e->s = s;
e->file = file;
e->pos = pos;
if (list->last)
{
list->last->next = e;
}
else
{
list->first = e;
}
list->last = e;
SllQueuePush(list->first, list->last, e);
++list->count;
return e;
}

View File

@ -258,6 +258,64 @@ void __asan_unpoison_memory_region(void const volatile *add, size_t);
#define NsFromSeconds(s) ((i64)((s) * 1000000000.0))
#define SecondsFromNs(ns) ((f64)(ns) / 1000000000.0)
////////////////////////////////
//~ Linked list helper macros
/* Taken from the rad debugger
* https://github.com/EpicGamesExt/raddebugger/blob/be5634c44867a2e31f6a109df5e574930992df01/src/base/base_core.h#L239
*/
#define CheckNil(nil,p) ((p) == 0 || (p) == nil)
#define SetNil(nil,p) ((p) = nil)
//- Singly linked list stack (first & next pointers)
#define SllStackPush_N(f,n,next) ((n)->next=(f), (f)=(n))
#define SllStackPop_N(f,next) ((f)=(f)->next)
#define SllStackPush(f,n) SllStackPush_N(f,n,next)
#define SllStackPop(f) SllStackPop_N(f,next)
//- Singly linked list queue (first, last, & next pointers)
#define SllQueuePush_NZ(nil,f,l,n,next) (CheckNil(nil,f)?\
((f)=(l)=(n),SetNil(nil,(n)->next)):\
((l)->next=(n),(l)=(n),SetNil(nil,(n)->next)))
#define SllQueuePushFront_NZ(nil,f,l,n,next) (CheckNil(nil,f)?\
((f)=(l)=(n),SetNil(nil,(n)->next)):\
((n)->next=(f),(f)=(n)))
#define SllQueuePop_NZ(nil,f,l,next) ((f)==(l)?\
(SetNil(nil,f),SetNil(nil,l)):\
((f)=(f)->next))
#define SllQueuePush_N(f,l,n,next) SllQueuePush_NZ(0,f,l,n,next)
#define SllQueuePushFront_N(f,l,n,next) SllQueuePushFront_NZ(0,f,l,n,next)
#define SllQueuePop_N(f,l,next) SllQueuePop_NZ(0,f,l,next)
#define SllQueuePush(f,l,n) SllQueuePush_NZ(0,f,l,n,next)
#define SllQueuePushFront(f,l,n) SllQueuePushFront_NZ(0,f,l,n,next)
#define SllQueuePop(f,l) SllQueuePop_NZ(0,f,l,next)
//- Doubly linked list (first, last, next, & prev pointers)
#define DllInsert_NPZ(nil,f,l,p,n,next,prev) (CheckNil(nil,f) ? \
((f) = (l) = (n), SetNil(nil,(n)->next), SetNil(nil,(n)->prev)) :\
CheckNil(nil,p) ? \
((n)->next = (f), (f)->prev = (n), (f) = (n), SetNil(nil,(n)->prev)) :\
((p)==(l)) ? \
((l)->next = (n), (n)->prev = (l), (l) = (n), SetNil(nil, (n)->next)) :\
(((!CheckNil(nil,p) && CheckNil(nil,(p)->next)) ? (0) : ((p)->next->prev = (n))), ((n)->next = (p)->next), ((p)->next = (n)), ((n)->prev = (p))))
#define DllPushBack_NPZ(nil,f,l,n,next,prev) DllInsert_NPZ(nil,f,l,l,n,next,prev)
#define DllPushFront_NPZ(nil,f,l,n,next,prev) DllInsert_NPZ(nil,l,f,f,n,prev,next)
#define DllRemove_NPZ(nil,f,l,n,next,prev) (((n) == (f) ? (f) = (n)->next : (0)),\
((n) == (l) ? (l) = (l)->prev : (0)),\
(CheckNil(nil,(n)->prev) ? (0) :\
((n)->prev->next = (n)->next)),\
(CheckNil(nil,(n)->next) ? (0) :\
((n)->next->prev = (n)->prev)))
#define DllInsert_NP(f,l,p,n,next,prev) DllInsert_NPZ(0,f,l,p,n,next,prev)
#define DllPushBack_NP(f,l,n,next,prev) DllPushBack_NPZ(0,f,l,n,next,prev)
#define DllPushFront_NP(f,l,n,next,prev) DllPushFront_NPZ(0,f,l,n,next,prev)
#define DllRemove_NP(f,l,n,next,prev) DllRemove_NPZ(0,f,l,n,next,prev)
#define DllInsert(f,l,p,n) DllInsert_NPZ(0,f,l,p,n,next,prev)
#define DllPushBack(f,l,n) DllPushBack_NPZ(0,f,l,n,next,prev)
#define DllPushFront(f,l,n) DllPushFront_NPZ(0,f,l,n,next,prev)
#define DllRemove(f,l,n) DllRemove_NPZ(0,f,l,n,next,prev)
////////////////////////////////
//~ Type helper macros

View File

@ -0,0 +1,4 @@
@Layer mp3_mmf
//- Impl
@IncludeC mp3_mmf.c

View File

@ -0,0 +1,7 @@
@Layer platform_win32
//- Api
@IncludeC platform_win32.h
//- Impl
@IncludeC platform_win32.c

View File

@ -8,9 +8,6 @@
//- Api
@IncludeC playback_core.h
//- Impl
@IncludeC playback_core.c
//- Wasapi impl
@DefaultWindowsImpl playback_wasapi

View File

@ -0,0 +1,7 @@
@Layer playback_wasapi
//- Api
@IncludeC playback_wasapi.h
//- Impl
@IncludeC playback_wasapi.c

View File

@ -1,6 +1,5 @@
@Layer pp
//- Dependencies
@Dep base
@Dep gpu
@ -12,6 +11,7 @@
@Dep mixer
@Dep bitbuff
@Dep rendertest
@Dep playback
//- Api
@IncludeC pp_sim.h

View File

@ -0,0 +1,7 @@
@Layer ttf_dwrite
//- Api
@IncludeC ttf_dwrite.h
//- Impl
@IncludeC ttf_dwrite.c