1394 lines
41 KiB
C
1394 lines
41 KiB
C
|
|
#include <stdio.h>
|
|
#include <windows.h>
|
|
#include <fileapi.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <assert.h>
|
|
|
|
#define ASSERT assert
|
|
|
|
|
|
|
|
|
|
typedef struct Arena Arena;
|
|
struct Arena {
|
|
Arena *prev;
|
|
Arena *next;
|
|
Arena *current;
|
|
int64_t base;
|
|
int64_t cursor;
|
|
int64_t reserved;
|
|
int64_t committed;
|
|
// The data continues here
|
|
};
|
|
|
|
|
|
#define ARENA_DEFAULT_RESERVE_SIZE (64ll*1024ll*1024ll)
|
|
#define ARENA_DEFAULT_COMMIT_SIZE (64ll*1024)
|
|
#define ARENA_MIN_SIZE (sizeof(Arena))
|
|
|
|
|
|
Arena *
|
|
Arena_Allocate_sized(int64_t reserved, int64_t committed) {
|
|
ASSERT(committed >= ARENA_MIN_SIZE);
|
|
if (reserved < committed) {
|
|
reserved = committed;
|
|
}
|
|
Arena *result = NULL;
|
|
int64_t size = sizeof(Arena)+(sizeof(void*)-1);
|
|
uint8_t *allocated_data = VirtualAlloc(0, reserved, MEM_RESERVE, PAGE_READWRITE);
|
|
VirtualAlloc(allocated_data, committed, MEM_COMMIT, PAGE_READWRITE);
|
|
result = (Arena *)allocated_data;
|
|
memset(result, 0, sizeof(Arena));
|
|
result->cursor = ARENA_MIN_SIZE;
|
|
result->reserved = reserved;
|
|
result->committed = committed;
|
|
result->current = result;
|
|
return result;
|
|
}
|
|
|
|
|
|
Arena *
|
|
Arena_Allocate(void) {
|
|
return Arena_Allocate_sized(ARENA_DEFAULT_RESERVE_SIZE, ARENA_DEFAULT_COMMIT_SIZE);
|
|
}
|
|
|
|
void
|
|
Arena_Release(Arena *arena) {
|
|
for (; arena; arena = arena->next) {
|
|
VirtualFree(arena, 0, MEM_RELEASE);
|
|
}
|
|
}
|
|
|
|
void *
|
|
Arena_Push_aligned(Arena *arena, int64_t total_bytes, int64_t alignement) {
|
|
Arena *arena_curr = arena->current;
|
|
uintptr_t start_address = ((uintptr_t)arena_curr)+arena_curr->cursor;
|
|
uintptr_t start_address_aligned = (start_address+(alignement-1))-((start_address+(alignement-1))%alignement);
|
|
int64_t new_cursor = arena_curr->cursor+(start_address_aligned+total_bytes)-start_address;
|
|
void *result = (void *)start_address_aligned;
|
|
|
|
// Not enough memory in the current arena, allocate a new arena
|
|
if (new_cursor > arena_curr->committed) {
|
|
if (new_cursor > arena_curr->reserved) {
|
|
int64_t committed = ARENA_MIN_SIZE+total_bytes+alignement-1;
|
|
if (committed < ARENA_DEFAULT_COMMIT_SIZE) {
|
|
committed = ARENA_DEFAULT_COMMIT_SIZE;
|
|
}
|
|
Arena *arena_new = Arena_Allocate_sized(ARENA_DEFAULT_RESERVE_SIZE, committed);
|
|
start_address = ((uintptr_t)arena_new) + arena_new->cursor;
|
|
start_address_aligned = (start_address+(alignement-1))-((start_address+(alignement-1))%alignement);
|
|
new_cursor = arena_new->cursor + (start_address_aligned+total_bytes)-start_address;
|
|
result = (void *)start_address_aligned;
|
|
|
|
arena_curr->next = arena_new;
|
|
arena_new->prev = arena_curr;
|
|
arena_new->base = arena_curr->base + arena_curr->cursor;
|
|
arena->current = arena_new;
|
|
}
|
|
else {
|
|
int64_t committed = arena_curr->committed+ARENA_DEFAULT_COMMIT_SIZE;
|
|
if (committed < new_cursor+1) {
|
|
committed = new_cursor+1;
|
|
}
|
|
if (committed > arena_curr->reserved) {
|
|
committed = arena_curr->reserved;
|
|
}
|
|
VirtualAlloc(arena_curr, committed, MEM_COMMIT, PAGE_READWRITE);
|
|
arena_curr->committed = committed;
|
|
}
|
|
}
|
|
|
|
arena->current->cursor = new_cursor;
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
#define ARENA_NEW(arena, T) ((T*)Arena_Push_aligned(arena, sizeof(T), sizeof(void*)))
|
|
#define ARENA_NEW_ZERO(arena, T) ((T*)memset(Arena_Push_aligned(arena, sizeof(T), sizeof(void*)),0,sizeof(T)))
|
|
|
|
#define ARENA_NEW_ARRAY(arena, elements, T) ((T*)Arena_Push_aligned(arena, sizeof(T)*elements, sizeof(void*)))
|
|
|
|
|
|
#define ARRLEN(a) (sizeof(a)/sizeof(a[0]))
|
|
|
|
|
|
|
|
int64_t
|
|
Arena_Get_cursor(Arena *arena) {
|
|
Arena *arena_curr = arena->current;
|
|
int64_t result = arena_curr->base+arena_curr->cursor;
|
|
return result;
|
|
}
|
|
|
|
void
|
|
Arena_Pop_to_cursor(Arena *arena, int64_t cursor) {
|
|
Arena *arena_curr = arena->current;
|
|
while (arena_curr) {
|
|
if (arena_curr->base < cursor) {
|
|
int64_t new_cursor = cursor - arena_curr->base;
|
|
ASSERT(new_cursor <= arena->cursor);
|
|
ASSERT(new_cursor >= ARENA_MIN_SIZE);
|
|
arena_curr->cursor = new_cursor;
|
|
break;
|
|
}
|
|
else {
|
|
Arena *arena_prev = arena_curr->prev;
|
|
VirtualFree(arena_curr, 0, MEM_RELEASE);
|
|
arena_curr = arena_prev;
|
|
arena_curr->next = NULL;
|
|
}
|
|
}
|
|
arena->current = arena_curr;
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
Arena *arena;
|
|
int64_t cursor;
|
|
} ArenaTemp;
|
|
|
|
#define SCRATCH_POOL_COUNT 4
|
|
|
|
Arena *scratch_pool[SCRATCH_POOL_COUNT] = {0};
|
|
|
|
ArenaTemp
|
|
Arena_Get_scratch(Arena *conflicts[], int64_t conflicts_count) {
|
|
ArenaTemp result = {0};
|
|
int64_t selected_scratch = -1;
|
|
for (int64_t scratch_i=0; scratch_i < SCRATCH_POOL_COUNT; scratch_i+=1) {
|
|
if (scratch_pool[scratch_i] == NULL) {
|
|
scratch_pool[scratch_i] = Arena_Allocate();
|
|
selected_scratch = scratch_i;
|
|
break;
|
|
}
|
|
|
|
bool we_have_conflict = false;
|
|
for (int64_t conflict_i=0; conflict_i < conflicts_count; conflict_i+=1) {
|
|
if (scratch_pool[scratch_i] == conflicts[conflict_i]) {
|
|
we_have_conflict = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!we_have_conflict) {
|
|
selected_scratch = scratch_i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (selected_scratch != -1) {
|
|
result.arena = scratch_pool[selected_scratch];
|
|
result.cursor = Arena_Get_cursor(result.arena);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
Arena_Release_scratch(ArenaTemp scratch) {
|
|
// TODO: Release the arena if is empty
|
|
Arena_Pop_to_cursor(scratch.arena, scratch.cursor);
|
|
}
|
|
|
|
#define WITH_SCRATCH(scratch, conflicts, conflicts_count) \
|
|
int _i_=(scratch=Arena_Get_scratch(conflicts,conflicts_count),0); !_i_; _i_+=1,Arena_Release_scratch(scratch)
|
|
|
|
|
|
|
|
#define LIST_PUSH(first,last,node) do { \
|
|
if (last) { \
|
|
(last)->next=(node); \
|
|
(last)=(last)->next; \
|
|
} \
|
|
else (last) = (node); \
|
|
if (!first) (first) = (last); \
|
|
}while(0)
|
|
|
|
|
|
typedef struct {
|
|
const uint8_t *data;
|
|
int64_t len;
|
|
} String;
|
|
|
|
|
|
#define STR_LIT(s) (String){.data = (s), .len = (sizeof(s)-1)}
|
|
#define STR_VARG(s) ((int)(s).len), ((s).data)
|
|
|
|
bool
|
|
String_match(String a, String b) {
|
|
if (a.len != b.len) {
|
|
return false;
|
|
}
|
|
for (int64_t cursor = 0; cursor < a.len; cursor+=1) {
|
|
if (a.data[cursor] != a.data[cursor]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
String_matches_end(String str, String end) {
|
|
if (str.len < end.len) {
|
|
return false;
|
|
}
|
|
int64_t end_cursor = end.len-1;
|
|
int64_t str_cursor = str.len-1;
|
|
for (; end_cursor >= 0; (end_cursor -= 1),(str_cursor -= 1)) {
|
|
if (str.data[str_cursor] != end.data[end_cursor]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
String
|
|
String_concatenate(Arena *arena, String first, String second) {
|
|
uint8_t *data = ARENA_NEW_ARRAY(arena, first.len+second.len, uint8_t);
|
|
memcpy(data, first.data, first.len);
|
|
memcpy(&data[first.len], second.data, second.len);
|
|
String result = {0};
|
|
result.data = data;
|
|
result.len = first.len+second.len;
|
|
return result;
|
|
}
|
|
|
|
|
|
String
|
|
String_path_concatenate(Arena *arena, String first, String second) {
|
|
uint8_t *data = ARENA_NEW_ARRAY(arena, first.len+second.len+1, uint8_t);
|
|
uint8_t *dst = data;
|
|
memcpy(dst, first.data, first.len);
|
|
dst += first.len;
|
|
dst[0] = '\\';
|
|
dst += 1;
|
|
memcpy(dst, second.data, second.len);
|
|
String result = {0};
|
|
result.data = data;
|
|
result.len = first.len+second.len+1;
|
|
return result;
|
|
}
|
|
|
|
String
|
|
String_clone(Arena *arena, String src) {
|
|
uint8_t *data = ARENA_NEW_ARRAY(arena, src.len, uint8_t);
|
|
memcpy(data, src.data, src.len);
|
|
String result = {0};
|
|
result.data = data;
|
|
result.len = src.len;
|
|
return result;
|
|
}
|
|
|
|
|
|
String
|
|
String_clone_from_cstring(Arena *arena, const char *src) {
|
|
int64_t len = strlen(src);
|
|
uint8_t *data = ARENA_NEW_ARRAY(arena, len, uint8_t);
|
|
memcpy(data, src, len);
|
|
String result = {0};
|
|
result.data = data;
|
|
result.len = len;
|
|
return result;
|
|
}
|
|
|
|
const char *
|
|
String_clone_to_cstring(Arena *arena, String src) {
|
|
char *result = ARENA_NEW_ARRAY(arena, src.len+1, char);
|
|
memcpy(result, src.data, src.len);
|
|
result[src.len] = '\0';
|
|
return result;
|
|
}
|
|
|
|
// djb2 http://www.cse.yorku.ca/~oz/hash.html
|
|
uint64_t
|
|
String_hash(String s) {
|
|
uint64_t hash = 5381;
|
|
for (int cursor = 0; cursor < s.len; cursor+=1) {
|
|
uint64_t c = s.data[cursor];
|
|
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
|
|
String
|
|
String_format(Arena *arena, const char *fmt, ...) {
|
|
int64_t arena_prev_cursor = Arena_Get_cursor(arena);
|
|
char *buffer = ARENA_NEW_ARRAY(arena, 1024*1024, char);
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
int64_t chars_written = vsnprintf(buffer, 1024*1024, fmt, argptr);
|
|
va_end(argptr);
|
|
|
|
String result = {0};
|
|
if (chars_written <= 0) {
|
|
Arena_Pop_to_cursor(arena, arena_prev_cursor);
|
|
return result;
|
|
}
|
|
int64_t arena_current_cursor = Arena_Get_cursor(arena);
|
|
Arena_Pop_to_cursor(arena, arena_current_cursor - (1024*1024-chars_written));
|
|
result.len = chars_written;
|
|
result.data = buffer;
|
|
return result;
|
|
}
|
|
|
|
|
|
typedef struct StringNode StringNode;
|
|
struct StringNode {
|
|
String value;
|
|
StringNode *next;
|
|
};
|
|
|
|
typedef struct {
|
|
StringNode *first;
|
|
StringNode *last;
|
|
int64_t count;
|
|
} StringList;
|
|
|
|
|
|
|
|
typedef struct {
|
|
StringList strings;
|
|
} StringBuilder;
|
|
|
|
|
|
void
|
|
StringBuilder_Append(Arena *arena, StringBuilder *sb, String s) {
|
|
StringNode *node = ARENA_NEW_ZERO(arena, StringNode);
|
|
node->value = String_clone(arena, s);
|
|
LIST_PUSH(sb->strings.first, sb->strings.last, node);
|
|
sb->strings.count += 1;
|
|
}
|
|
|
|
bool
|
|
StringBuilder_Write_to_file(StringBuilder *sb, String filepath) {
|
|
bool success = false;
|
|
ArenaTemp scratch;
|
|
for (WITH_SCRATCH(scratch, NULL, 0)) {
|
|
const char *cfilepath = String_clone_to_cstring(scratch.arena, filepath);
|
|
HANDLE file_handle = CreateFileA(
|
|
cfilepath,
|
|
GENERIC_WRITE,
|
|
FILE_SHARE_WRITE,
|
|
NULL,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
|
|
if (file_handle == INVALID_HANDLE_VALUE) {
|
|
continue;
|
|
}
|
|
|
|
for (StringNode *string_node = sb->strings.first; string_node; string_node = string_node->next) {
|
|
if (string_node->value.len > 0) {
|
|
WriteFile(file_handle, string_node->value.data, string_node->value.len, NULL, NULL);
|
|
}
|
|
}
|
|
|
|
success = true;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
|
|
|
|
// HASHMAP Stuff
|
|
|
|
#define HASHMAP_MAX_ITEMS_PER_BUCKET 16
|
|
typedef struct HashMapBucket HashMapBucket;
|
|
struct HashMapBucket {
|
|
uint64_t items_count;
|
|
uint64_t keys[HASHMAP_MAX_ITEMS_PER_BUCKET];
|
|
HashMapBucket *next;
|
|
uint64_t values[HASHMAP_MAX_ITEMS_PER_BUCKET];
|
|
};
|
|
|
|
|
|
typedef struct {
|
|
uint64_t total_slots;
|
|
HashMapBucket *slots[];
|
|
} HashMap;
|
|
|
|
|
|
static inline uint64_t
|
|
HashMap_String_key(String s) {
|
|
return String_hash(s);
|
|
}
|
|
|
|
|
|
HashMap *
|
|
HashMap_New(Arena *arena, uint64_t total_slots);
|
|
|
|
uint64_t *
|
|
HashMap_Lookup(HashMap *hashmap, uint64_t key);
|
|
|
|
bool
|
|
HashMap_Delete(HashMap *hashmap, uint64_t key);
|
|
|
|
void
|
|
HashMap_Delete_all_values(HashMap *hashmap);
|
|
|
|
uint64_t *
|
|
HashMap_Lookup_or_create(Arena *arena, HashMap *hashmap, uint64_t key, bool *found);
|
|
|
|
bool
|
|
HashMap_Insert(Arena *arena, HashMap *hashmap, uint64_t key, uint64_t value, bool overwrite);
|
|
|
|
|
|
|
|
HashMap *
|
|
HashMap_New(Arena *arena, uint64_t total_slots) {
|
|
HashMap *result = Arena_Push_aligned(arena, sizeof(HashMap)+sizeof(void*)*total_slots, 8);
|
|
if (result != NULL) {
|
|
result->total_slots = total_slots;
|
|
for (int64_t i = 0; i < (int64_t)total_slots; i += 1) {
|
|
result->slots[i] = NULL;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
uint64_t *
|
|
HashMap_Lookup(HashMap *hashmap, uint64_t key) {
|
|
uint64_t slot_pos = key%hashmap->total_slots;
|
|
for (HashMapBucket *bucket = hashmap->slots[slot_pos]; bucket; bucket = bucket->next) {
|
|
for (int64_t i = 0; i < bucket->items_count; i += 1) {
|
|
if (bucket->keys[i] == key) {
|
|
return &(bucket->values[i]);
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool
|
|
HashMap_Delete(HashMap *hashmap, uint64_t key) {
|
|
uint64_t slot_pos = key%hashmap->total_slots;
|
|
for (HashMapBucket *bucket = hashmap->slots[slot_pos]; bucket; bucket = bucket->next) {
|
|
for (int64_t i = 0; i < bucket->items_count; i += 1) {
|
|
if (bucket->keys[i] == key) {
|
|
int64_t last_item_index = bucket->items_count-1;
|
|
bucket->keys[i] = bucket->keys[last_item_index];
|
|
bucket->values[i] = bucket->values[last_item_index];
|
|
bucket->items_count -= 1;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void
|
|
HashMap_Delete_all_values(HashMap *hashmap) {
|
|
int64_t total_slots = hashmap->total_slots;
|
|
for (int64_t slot_i = 0; slot_i < total_slots; slot_i+=1) {
|
|
for (HashMapBucket *bucket = hashmap->slots[slot_i]; bucket; bucket = bucket->next) {
|
|
bucket->items_count = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
uint64_t *
|
|
HashMap_Lookup_or_create(Arena *arena, HashMap *hashmap, uint64_t key, bool *found) {
|
|
uint64_t slot_pos = key%hashmap->total_slots;
|
|
HashMapBucket **append_bucket = NULL;
|
|
HashMapBucket *target_bucket = NULL;
|
|
|
|
if (hashmap->slots[slot_pos]) {
|
|
|
|
HashMapBucket *bucket = hashmap->slots[slot_pos];
|
|
for (;;) {
|
|
|
|
// Iterate over the bucket items
|
|
for (int64_t i = 0; i < bucket->items_count; i += 1) {
|
|
if (bucket->keys[i] == key) {
|
|
if (found) *found = true;
|
|
return &(bucket->values[i]);
|
|
}
|
|
}
|
|
|
|
// Check if the bucket has empty items to target it in case the key is not found
|
|
if (!target_bucket && bucket->items_count < HASHMAP_MAX_ITEMS_PER_BUCKET) {
|
|
target_bucket = bucket;
|
|
}
|
|
|
|
// If the next bucket is NULL we end
|
|
if (bucket->next == NULL) {
|
|
break;
|
|
}
|
|
|
|
// Advance the bucket
|
|
bucket = bucket->next;
|
|
}
|
|
|
|
if (!target_bucket) {
|
|
append_bucket = &(bucket->next);
|
|
}
|
|
}
|
|
else {
|
|
append_bucket = &(hashmap->slots[slot_pos]);
|
|
}
|
|
|
|
// If append bucket is not null we have to create a new bucket and append it to the
|
|
// append_bucket pointer
|
|
if (append_bucket) {
|
|
target_bucket = ARENA_NEW_ZERO(arena, HashMapBucket);
|
|
if (target_bucket) {
|
|
target_bucket->items_count = 0;
|
|
target_bucket->next = NULL;
|
|
*append_bucket = target_bucket;
|
|
}
|
|
}
|
|
|
|
uint64_t *result = NULL;
|
|
|
|
// If a target bucket exist we add a new item to it
|
|
if (target_bucket) {
|
|
target_bucket->keys[target_bucket->items_count] = key;
|
|
target_bucket->values[target_bucket->items_count] = 0;
|
|
result = &(target_bucket->values[target_bucket->items_count]);
|
|
target_bucket->items_count += 1;
|
|
}
|
|
|
|
// If we are here we didn't find the key
|
|
if (found) *found = false;
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
bool
|
|
HashMap_Insert(Arena *arena, HashMap *hashmap, uint64_t key, uint64_t value, bool overwrite) {
|
|
bool found = false;
|
|
uint64_t *val_ptr = HashMap_Lookup_or_create(arena, hashmap, key, &found);
|
|
if (!val_ptr) return false;
|
|
if (found && !overwrite) return false;
|
|
*val_ptr = value;
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
const uint8_t *data;
|
|
int64_t cursor;
|
|
int64_t size;
|
|
} ProcessBinaryContext;
|
|
|
|
static inline bool
|
|
Get_U8(ProcessBinaryContext *ctx, uint8_t *out) {
|
|
if (ctx->cursor+1 <= ctx->size) {
|
|
if (out) {
|
|
(*out) = ctx->data[ctx->cursor];
|
|
}
|
|
ctx->cursor += 1;
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static inline bool
|
|
Peek_U16(ProcessBinaryContext *ctx, uint16_t *out) {
|
|
if (ctx->cursor+2 <= ctx->size) {
|
|
if (out) {
|
|
uint16_t low = ctx->data[ctx->cursor];
|
|
uint16_t high = ctx->data[ctx->cursor+1];
|
|
(*out) = (high << 8)|low;
|
|
}
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static inline bool
|
|
Get_U16(ProcessBinaryContext *ctx, uint16_t *out) {
|
|
if (Peek_U16(ctx, out)) {
|
|
ctx->cursor+=2;
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static inline bool
|
|
Peek_U32(ProcessBinaryContext *ctx, uint32_t *out) {
|
|
if (ctx->cursor+4 <= ctx->size) {
|
|
if (out) {
|
|
uint32_t c0 = (uint8_t)(ctx->data[ctx->cursor]);
|
|
uint32_t c1 = (uint8_t)(ctx->data[ctx->cursor+1]);
|
|
uint32_t c2 = (uint8_t)(ctx->data[ctx->cursor+2]);
|
|
uint32_t c3 = (uint8_t)(ctx->data[ctx->cursor+3]);
|
|
(*out) = (c3 << 24)|(c2 << 16)|(c1 << 8)|(c0);
|
|
}
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static inline bool
|
|
Get_U32(ProcessBinaryContext *ctx, uint32_t *out) {
|
|
if (Peek_U32(ctx, out)) {
|
|
ctx->cursor+=4;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static inline bool
|
|
Get_U64(ProcessBinaryContext *ctx, uint64_t *out) {
|
|
if (ctx->cursor+8 <= ctx->size) {
|
|
if (out) {
|
|
uint64_t c0 = (uint8_t)(ctx->data[ctx->cursor]);
|
|
uint64_t c1 = (uint8_t)(ctx->data[ctx->cursor+1]);
|
|
uint64_t c2 = (uint8_t)(ctx->data[ctx->cursor+2]);
|
|
uint64_t c3 = (uint8_t)(ctx->data[ctx->cursor+3]);
|
|
uint64_t c4 = (uint8_t)(ctx->data[ctx->cursor+4]);
|
|
uint64_t c5 = (uint8_t)(ctx->data[ctx->cursor+5]);
|
|
uint64_t c6 = (uint8_t)(ctx->data[ctx->cursor+6]);
|
|
uint64_t c7 = (uint8_t)(ctx->data[ctx->cursor+7]);
|
|
(*out) = (c7<<56)|(c6<<48)|(c5<<40)|(c4<<32)|(c3 << 24)|(c2 << 16)|(c1 << 8)|(c0);
|
|
ctx->cursor += 8;
|
|
}
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static inline const uint8_t *
|
|
Get_bytes(ProcessBinaryContext *ctx, uint32_t count) {
|
|
const uint8_t *result = NULL;
|
|
if (ctx->cursor+count <= ctx->size) {
|
|
result = &(ctx->data[ctx->cursor]);
|
|
ctx->cursor += count;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static inline bool
|
|
Seek_to(ProcessBinaryContext *ctx, int64_t cursor) {
|
|
if (cursor < 0 && cursor > ctx->size) {
|
|
return false;
|
|
}
|
|
ctx->cursor = cursor;
|
|
return true;
|
|
}
|
|
|
|
static inline const uint8_t *
|
|
Get_current_addr(ProcessBinaryContext *ctx) {
|
|
if (ctx->cursor >= ctx->size) {
|
|
return NULL;
|
|
}
|
|
return &(ctx->data[ctx->cursor]);
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
Get_all_obj_in_directory_ex(Arena *arena, String path, StringList *list_result) {
|
|
String path_wildcard = String_path_concatenate(arena, path, STR_LIT("*"));
|
|
|
|
WIN32_FIND_DATAA file_data;
|
|
HANDLE hfind = FindFirstFile(String_clone_to_cstring(arena, path_wildcard), &file_data);
|
|
if (hfind == INVALID_HANDLE_VALUE) {
|
|
return;
|
|
}
|
|
|
|
for (bool there_are_more = true; there_are_more; there_are_more = FindNextFileA(hfind, &file_data)) {
|
|
String filename = String_clone_from_cstring(arena, file_data.cFileName);
|
|
if (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
if (String_match(filename, STR_LIT("."))) {
|
|
continue;
|
|
}
|
|
if (String_match(filename, STR_LIT(".."))) {
|
|
continue;
|
|
}
|
|
String next_directory = String_path_concatenate(arena, path, filename);
|
|
Get_all_obj_in_directory_ex(arena, next_directory, list_result);
|
|
}
|
|
else if (file_data.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
|
|
if (String_matches_end(filename , STR_LIT(".obj"))) {
|
|
StringNode *string_node = ARENA_NEW_ZERO(arena, StringNode);
|
|
string_node->value = String_path_concatenate(arena, path, filename);
|
|
LIST_PUSH(list_result->first, list_result->last, string_node);
|
|
list_result->count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
FindClose(hfind);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
uint8_t *
|
|
Read_entire_file(Arena *arena, String filepath, int64_t *size_out) {
|
|
uint8_t *result_data = NULL;
|
|
int64_t result_size = 0;
|
|
|
|
int64_t arena_cursor = Arena_Get_cursor(arena);
|
|
|
|
bool has_errors = false;
|
|
const char *cfilepath = String_clone_to_cstring(arena, filepath);
|
|
HANDLE file_handle = CreateFileA(
|
|
cfilepath,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
if (file_handle == INVALID_HANDLE_VALUE) {
|
|
has_errors = true;
|
|
}
|
|
|
|
int64_t size = 0;
|
|
if (!has_errors) {
|
|
DWORD get_size_result = GetFileSize(file_handle, NULL);
|
|
if (get_size_result == INVALID_FILE_SIZE) {
|
|
has_errors = true;
|
|
}
|
|
else {
|
|
size = (int64_t)get_size_result;
|
|
}
|
|
}
|
|
|
|
uint8_t *file_data = NULL;
|
|
if (!has_errors) {
|
|
file_data = ARENA_NEW_ARRAY(arena, size, uint8_t);
|
|
if (!ReadFile(file_handle, file_data, size, NULL, NULL)) {
|
|
has_errors = true;
|
|
}
|
|
}
|
|
|
|
CloseHandle(file_handle);
|
|
|
|
if (has_errors) {
|
|
Arena_Pop_to_cursor(arena, arena_cursor);
|
|
}
|
|
else {
|
|
result_data = file_data;
|
|
result_size = size;
|
|
}
|
|
|
|
*size_out = result_size;
|
|
return result_data;
|
|
}
|
|
|
|
|
|
|
|
StringList
|
|
Get_all_obj_in_directory(Arena *arena, String path) {
|
|
StringList result = {0};
|
|
Get_all_obj_in_directory_ex(arena, path, &result);
|
|
return result;
|
|
}
|
|
|
|
enum {
|
|
COFF_MACHINE_UNKNOWN = 0x0,
|
|
// The content of this field is assumed to be applicable to any machine type
|
|
};
|
|
|
|
|
|
typedef enum {
|
|
TYPE_NULL = 0,
|
|
TYPE_POINTER = 1,
|
|
TYPE_FUNCTION = 2,
|
|
TYPE_ARRAY = 3
|
|
}ImageSymDtype;
|
|
|
|
|
|
enum {
|
|
CLASS_END_OF_FUNCTION = -1,
|
|
// A special symbol that represents the end of function, for debugging purposes.
|
|
|
|
CLASS_NULL = 0,
|
|
// No assigned storage class.
|
|
|
|
CLASS_AUTOMATIC = 1,
|
|
// The automatic (stack) variable. The Value field specifies the stack frame offset.
|
|
|
|
CLASS_EXTERNAL = 2,
|
|
// A value that Microsoft tools use for external symbols. The Value field indicates the size if
|
|
// the section number is IMAGE_SYM_UNDEFINED (0). If the section number is not zero, then the
|
|
// Value field specifies the offset within the section.
|
|
|
|
CLASS_STATIC = 3,
|
|
// The offset of the symbol within the section. If the Value field is zero, then the symbol
|
|
// represents a section name.
|
|
|
|
CLASS_REGISTER = 4,
|
|
// A register variable. The Value field specifies the register number.
|
|
|
|
CLASS_EXTERNAL_DEF = 5,
|
|
// A symbol that is defined externally.
|
|
|
|
CLASS_LABEL = 6,
|
|
// A code label that is defined within the module. The Value field specifies the offset of the
|
|
// symbol within the section.
|
|
|
|
CLASS_UNDEFINED_LABEL = 7,
|
|
// A reference to a code label that is not defined.
|
|
|
|
CLASS_MEMBER_OF_STRUCT = 8,
|
|
// The structure member. The Value field specifies the n th member.
|
|
|
|
CLASS_ARGUMENT = 9,
|
|
// A formal argument (parameter) of a function. The Value field specifies the n th argument.
|
|
|
|
CLASS_STRUCT_TAG = 10,
|
|
// The structure tag-name entry.
|
|
|
|
CLASS_MEMBER_OF_UNION = 11,
|
|
// A union member. The Value field specifies the n th member.
|
|
|
|
CLASS_UNION_TAG = 12,
|
|
// The Union tag-name entry.
|
|
|
|
CLASS_TYPE_DEFINITION = 13,
|
|
// A Typedef entry.
|
|
|
|
CLASS_UNDEFINED_STATIC = 14,
|
|
// A static data declaration.
|
|
|
|
CLASS_ENUM_TAG = 15,
|
|
// An enumerated type tagname entry.
|
|
|
|
CLASS_MEMBER_OF_ENUM = 16,
|
|
// A member of an enumeration. The Value field specifies the n th member.
|
|
|
|
CLASS_REGISTER_PARAM = 17,
|
|
// A register parameter.
|
|
|
|
CLASS_BIT_FIELD = 18,
|
|
// A bit-field reference. The Value field specifies the n th bit in the bit field.
|
|
|
|
CLASS_BLOCK = 100,
|
|
// A .bb (beginning of block) or .eb (end of block) record. The Value field is the relocatable
|
|
// address of the code location.
|
|
|
|
CLASS_FUNCTION = 101,
|
|
// A value that Microsoft tools use for symbol records that define the extent of a function:
|
|
// begin function (.bf ), end function ( .ef ), and lines in function ( .lf ). For .lf records,
|
|
// the Value field gives the number of source lines in the function. For .ef records, the Value
|
|
// field gives the size of the function code.
|
|
|
|
CLASS_END_OF_STRUCT = 102,
|
|
// An end-of-structure entry.
|
|
CLASS_FILE = 103,
|
|
// A value that Microsoft tools, as well as traditional COFF format, use for the source-file
|
|
// symbol record. The symbol is followed by auxiliary records that name the file.
|
|
|
|
CLASS_SECTION = 104,
|
|
// A definition of a section (Microsoft tools use STATIC storage class instead).
|
|
|
|
CLASS_WEAK_EXTERNAL = 105,
|
|
// A weak external. For more information, see Auxiliary Format 3: Weak Externals.
|
|
|
|
CLASS_CLR_TOKEN = 107,
|
|
// A CLR token symbol. The name is an ASCII string that consists of the hexadecimal value of the
|
|
// token. For more information, see CLR Token Definition (Object Only).
|
|
};
|
|
|
|
typedef struct {
|
|
bool success;
|
|
int64_t time_stamp;
|
|
StringList exported_symbols;
|
|
StringList imported_symbols;
|
|
} ProcessCoffResult;
|
|
|
|
|
|
ProcessCoffResult
|
|
Process_coff_from_memory(Arena *arena, const uint8_t *data, int64_t size) {
|
|
|
|
ProcessCoffResult result = {0};
|
|
|
|
ProcessBinaryContext ctx = {0};
|
|
ctx.data = data;
|
|
ctx.size = size;
|
|
|
|
|
|
typedef struct {
|
|
bool big_obj;
|
|
uint16_t machine;
|
|
uint32_t time_stamp;
|
|
uint32_t number_of_sections;
|
|
uint32_t pointer_to_symbol_table;
|
|
uint32_t number_of_symbols;
|
|
} COFFInfo;
|
|
COFFInfo info = {0};
|
|
|
|
if (!Get_U16(&ctx, &(info.machine))) {
|
|
return result;
|
|
}
|
|
if (info.machine == COFF_MACHINE_UNKNOWN) { // ASUME ANON_OBJECT_HEADER
|
|
info.big_obj = true;
|
|
uint16_t magic2;
|
|
if (!Get_U16(&ctx, &magic2)) {
|
|
return result;
|
|
}
|
|
if (magic2 != 0xFFFF) {
|
|
return result;
|
|
}
|
|
uint16_t version;
|
|
if (!Get_U16(&ctx, &version)) {
|
|
return result;
|
|
}
|
|
if (version < 2) {
|
|
return result;
|
|
}
|
|
if (version < 2) {
|
|
return result;
|
|
}
|
|
// Get the real machine
|
|
if (!Get_U16(&ctx, &(info.machine))) {
|
|
return result;
|
|
}
|
|
// Time date stamp
|
|
if (!Get_U32(&ctx, &info.time_stamp)) {
|
|
return result;
|
|
}
|
|
// CLSID
|
|
if (!Get_bytes(&ctx, 16)) {
|
|
return result;
|
|
}
|
|
// Size of data
|
|
if (!Get_U32(&ctx, NULL)) {
|
|
return result;
|
|
}
|
|
// Flags
|
|
if (!Get_U32(&ctx, NULL)) {
|
|
return result;
|
|
}
|
|
// Metadata size
|
|
if (!Get_U32(&ctx, NULL)) {
|
|
return result;
|
|
}
|
|
// Metadata offset
|
|
if (!Get_U32(&ctx, NULL)) {
|
|
return result;
|
|
}
|
|
// Number of sections (32 bits)
|
|
if (!Get_U32(&ctx, &info.number_of_sections)) {
|
|
return result;
|
|
}
|
|
// Pointer to symbol table
|
|
if (!Get_U32(&ctx, &info.pointer_to_symbol_table)) {
|
|
return result;
|
|
}
|
|
// Number of symbols
|
|
if (!Get_U32(&ctx, &info.number_of_symbols)) {
|
|
return result;
|
|
}
|
|
}
|
|
else { // Normal COFF header
|
|
// Number of sections
|
|
uint16_t number_of_sections = 0;
|
|
if (!Get_U16(&ctx, &number_of_sections)) {
|
|
return result;
|
|
}
|
|
info.number_of_sections = number_of_sections;
|
|
|
|
// Time date stamp
|
|
if (!Get_U32(&ctx, &info.time_stamp)) {
|
|
return result;
|
|
}
|
|
|
|
// Pointer to symbol table
|
|
if (!Get_U32(&ctx, &info.pointer_to_symbol_table)) {
|
|
return result;
|
|
}
|
|
|
|
// Number of symbols
|
|
if (!Get_U32(&ctx, &info.number_of_symbols)) {
|
|
return result;
|
|
}
|
|
|
|
// Size of optional header
|
|
if (!Get_U16(&ctx, NULL)) {
|
|
return result;
|
|
}
|
|
|
|
// Characteristics
|
|
if (!Get_U16(&ctx, NULL)) {
|
|
return result;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
if (info.pointer_to_symbol_table == 0) {
|
|
return result;
|
|
}
|
|
|
|
if (!Seek_to(&ctx, info.pointer_to_symbol_table)) {
|
|
return result;
|
|
}
|
|
|
|
// NOTE(Tano): This is very bad we have to validate the size of the string table
|
|
uint32_t pointer_to_string_table = 0;
|
|
if (info.big_obj) {
|
|
pointer_to_string_table = info.pointer_to_symbol_table+info.number_of_symbols*20;
|
|
}
|
|
else {
|
|
pointer_to_string_table = info.pointer_to_symbol_table+info.number_of_symbols*18;
|
|
}
|
|
|
|
for (int64_t i = 0; i < info.number_of_symbols; i+=1) {
|
|
typedef struct {
|
|
union {
|
|
const char str[8];
|
|
uint64_t pack;
|
|
struct {
|
|
uint32_t packl;
|
|
uint32_t packh;
|
|
};
|
|
};
|
|
} MaybeName;
|
|
MaybeName maybe_name = {0};
|
|
if (!Get_U64(&ctx, &(maybe_name.pack))) {
|
|
return result;
|
|
}
|
|
|
|
const char *name = NULL;
|
|
if (maybe_name.packl == 0) {
|
|
// NOTE(Tano): This is very bad we have to validate if is inside the string table
|
|
// and if it is null terminated
|
|
uint32_t prev_cursor = ctx.cursor;
|
|
if (!Seek_to(&ctx, pointer_to_string_table+maybe_name.packh)) {
|
|
return result;
|
|
}
|
|
name = Get_current_addr(&ctx);
|
|
if (!name) {
|
|
return result;
|
|
}
|
|
if (!Seek_to(&ctx, prev_cursor)) {
|
|
return result;
|
|
}
|
|
|
|
}
|
|
else {
|
|
name = maybe_name.str;
|
|
}
|
|
|
|
uint32_t value = 0;
|
|
if (!Get_U32(&ctx, &value)) {
|
|
return result;
|
|
}
|
|
|
|
int32_t section_number = 0;
|
|
|
|
if (info.big_obj) {
|
|
if (!Get_U32(&ctx, (uint32_t*)§ion_number)) {
|
|
return result;
|
|
}
|
|
}
|
|
else {
|
|
int16_t section_number_16 = 0;
|
|
if (!Get_U16(&ctx, (uint16_t*)§ion_number_16)) {
|
|
return result;
|
|
}
|
|
section_number = section_number_16;
|
|
}
|
|
|
|
uint16_t type = 0;
|
|
if (!Get_U16(&ctx, &type)) {
|
|
return result;
|
|
}
|
|
int8_t storage_class = 0;
|
|
if (!Get_U8(&ctx, (uint8_t *)&storage_class)) {
|
|
return result;
|
|
}
|
|
if (!Get_U8(&ctx, NULL)) {
|
|
return result;
|
|
}
|
|
|
|
|
|
if (storage_class != CLASS_EXTERNAL) { // Only care about external
|
|
continue;
|
|
}
|
|
|
|
StringNode *string_node = ARENA_NEW_ZERO(arena, StringNode);
|
|
string_node->value = String_clone_from_cstring(arena, name);
|
|
if (section_number == 0) { // UNDEFINED (Is imported)
|
|
LIST_PUSH(result.imported_symbols.first, result.imported_symbols.last, string_node);
|
|
result.imported_symbols.count += 1;
|
|
}
|
|
else {// Otherwise exported
|
|
LIST_PUSH(result.exported_symbols.first, result.exported_symbols.last, string_node);
|
|
result.exported_symbols.count += 1;
|
|
}
|
|
}
|
|
|
|
result.time_stamp = info.time_stamp;
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
typedef struct ObjectDependency ObjectDependency;
|
|
struct ObjectDependency {
|
|
int64_t id;
|
|
int64_t weight;
|
|
ObjectDependency *next;
|
|
};
|
|
|
|
typedef struct {
|
|
String filename;
|
|
int64_t size_in_bytes;
|
|
bool valid;
|
|
int64_t time_stamp;
|
|
StringList exported_symbols;
|
|
StringList imported_symbols;
|
|
ObjectDependency *first_dependency;
|
|
ObjectDependency *last_dependency;
|
|
int64_t dependency_count;
|
|
int64_t dependents_count;
|
|
int64_t group_id;
|
|
} ObjectInfo;
|
|
|
|
|
|
bool
|
|
Assign_group_if_depends_on_it(ObjectInfo objects[], int64_t object_id, bool visited[], int64_t group_id) {
|
|
if (objects[object_id].group_id == group_id) {
|
|
return true;
|
|
}
|
|
if (objects[object_id].group_id != 0) {
|
|
return false;
|
|
}
|
|
if (visited[object_id]) {
|
|
return false;
|
|
}
|
|
visited[object_id] = true;
|
|
bool depends = false;
|
|
for (ObjectDependency *dep = objects[object_id].first_dependency; dep; dep = dep->next) {
|
|
depends |= Assign_group_if_depends_on_it(objects, dep->id, visited, group_id);
|
|
}
|
|
if (depends) {
|
|
objects[object_id].group_id = group_id;
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool
|
|
Try_assign_group(ObjectInfo objects[], int64_t objects_count, int64_t object_id, int64_t group_id) {
|
|
if (objects[object_id].group_id != 0) {
|
|
return false;
|
|
}
|
|
objects[object_id].group_id = group_id;
|
|
ArenaTemp scratch;
|
|
for (WITH_SCRATCH(scratch, NULL, 0)) {
|
|
bool *visited = ARENA_NEW_ARRAY(scratch.arena, objects_count, bool);
|
|
memset(visited, 0, objects_count*sizeof(bool));
|
|
visited[object_id] = true;
|
|
for (ObjectDependency *dep = objects[object_id].first_dependency; dep; dep = dep->next) {
|
|
Assign_group_if_depends_on_it(objects, dep->id, visited, group_id);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
main(int argc, const char *argv[]) {
|
|
Arena *arena = Arena_Allocate();
|
|
if (argc != 3) {
|
|
const char *exec = "disaster";
|
|
if (argc > 0) {
|
|
exec = argv[0];
|
|
}
|
|
printf("USAGE: %s path output\n", exec);
|
|
return -1;
|
|
}
|
|
|
|
String path = String_clone_from_cstring(arena, argv[1]);
|
|
String output = String_clone_from_cstring(arena, argv[2]);
|
|
StringList list = Get_all_obj_in_directory(arena, path);
|
|
|
|
int64_t total_size = 0;
|
|
|
|
ObjectInfo *objects = ARENA_NEW_ARRAY(arena, list.count, ObjectInfo);
|
|
int64_t object_i = 0;
|
|
for (StringNode *node = list.first; node; (node = node->next),(object_i+=1)) {
|
|
memset(&objects[object_i], 0, sizeof(ObjectInfo));
|
|
ArenaTemp scratch;
|
|
for (WITH_SCRATCH(scratch, (Arena*[]){arena}, 1)) {
|
|
int64_t size = 0;
|
|
objects[object_i].filename = node->value;
|
|
|
|
uint8_t *file_data = Read_entire_file(scratch.arena, node->value, &size);
|
|
if (!file_data) {
|
|
printf("Failed to read file %.*s\n", (int)(node->value.len), node->value.data);
|
|
}
|
|
else {
|
|
objects[object_i].size_in_bytes = size;
|
|
ProcessCoffResult process_coff_result = Process_coff_from_memory(arena, file_data, size);
|
|
if (process_coff_result.success) {
|
|
objects[object_i].valid = true;
|
|
objects[object_i].time_stamp = process_coff_result.time_stamp;
|
|
objects[object_i].exported_symbols = process_coff_result.exported_symbols;
|
|
objects[object_i].imported_symbols = process_coff_result.imported_symbols;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
printf("Total object files: %lld\n", list.count);
|
|
|
|
int64_t total_exported = 0;
|
|
int64_t errors = 0;
|
|
HashMap *exported_to_object = HashMap_New(arena, 1024*1024);
|
|
for (int64_t object_i = 0; object_i < list.count; object_i+=1) {
|
|
for (StringNode *snode = objects[object_i].exported_symbols.first; snode; snode = snode->next) {
|
|
if (HashMap_Insert(arena, exported_to_object, HashMap_String_key(snode->value), object_i, false)) {
|
|
int64_t first_obj_i = *HashMap_Lookup(exported_to_object, HashMap_String_key(snode->value));
|
|
if (first_obj_i != object_i) {
|
|
ObjectInfo *first_obj = &objects[first_obj_i];
|
|
printf("Error: symbol %.*s exported by more than one object file, first object file: %.*s , current object file: %.*s\n",
|
|
(int)(snode->value.len), snode->value.data,
|
|
(int)(first_obj->filename.len), first_obj->filename.data,
|
|
(int)(objects[object_i].filename.len), objects[object_i].filename.data
|
|
);
|
|
errors += 1;
|
|
}
|
|
}
|
|
total_exported += 1;
|
|
}
|
|
}
|
|
// printf("TOTAL_EXPORTED: %lld\n", total_exported);
|
|
// printf("ERRORS: %lld\n", errors);
|
|
|
|
// Find for each object file which object files have his imported symbols
|
|
for (int64_t object_i = 0; object_i < list.count; object_i+=1) {
|
|
ObjectInfo *current_object = &objects[object_i];
|
|
for (StringNode *snode = current_object->imported_symbols.first; snode; snode = snode->next) {
|
|
int64_t *dependency_ptr = HashMap_Lookup(exported_to_object, HashMap_String_key(snode->value));
|
|
if (!dependency_ptr) {
|
|
continue;
|
|
}
|
|
int64_t dependency_id = *dependency_ptr;
|
|
// Linear search
|
|
bool found = false;
|
|
for (ObjectDependency *dep = current_object->first_dependency; dep; dep = dep->next) {
|
|
if (dep->id == dependency_id) {
|
|
dep->weight += 1;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
ObjectDependency *new_dep = ARENA_NEW_ZERO(arena, ObjectDependency);
|
|
new_dep->id = dependency_id;
|
|
new_dep->weight = 1;
|
|
LIST_PUSH(current_object->first_dependency, current_object->last_dependency, new_dep);
|
|
current_object->dependency_count += 1;
|
|
// Increase by one the dependants count of the dependency
|
|
objects[dependency_id].dependents_count += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Assign groups based on dependency cycles
|
|
{
|
|
int64_t group_id = 1;
|
|
for (int64_t object_i = 0; object_i < list.count; object_i+=1) {
|
|
if (Try_assign_group(objects, list.count, object_i, group_id)) {
|
|
group_id += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
ArenaTemp scratch;
|
|
|
|
for (WITH_SCRATCH(scratch, (Arena*[]){arena}, 0)) {
|
|
StringBuilder string_builder = {0};
|
|
StringBuilder_Append(scratch.arena, &string_builder, STR_LIT("digraph {\n"));
|
|
for (int64_t object_i = 0; object_i < list.count; object_i+=1) {
|
|
ObjectInfo *current_object = &objects[object_i];
|
|
String src_filename = current_object->filename;
|
|
String line_group = String_format(scratch.arena, "\"%.*s\" [class = \"%lld\"]\n", STR_VARG(src_filename), current_object->group_id);
|
|
StringBuilder_Append(scratch.arena, &string_builder, line_group);
|
|
for (ObjectDependency *dep = current_object->first_dependency; dep; dep = dep->next) {
|
|
ObjectInfo *dep_object = &objects[dep->id];
|
|
String dst_filename = dep_object->filename;
|
|
String line = String_format(scratch.arena, "\"%.*s\" -> \"%.*s\"\n", STR_VARG(src_filename), STR_VARG(dst_filename));
|
|
StringBuilder_Append(scratch.arena, &string_builder, line);
|
|
}
|
|
}
|
|
StringBuilder_Append(scratch.arena, &string_builder, STR_LIT("}\n"));
|
|
String output_object_info = String_concatenate(scratch.arena, output, STR_LIT("_graph.dot"));
|
|
if (!StringBuilder_Write_to_file(&string_builder, output_object_info)) {
|
|
printf("Failed to write file %.*s\n", STR_VARG(output_object_info));
|
|
}
|
|
}
|
|
|
|
for (WITH_SCRATCH(scratch, (Arena*[]){arena}, 0)) {
|
|
StringBuilder string_builder = {0};
|
|
StringBuilder_Append(scratch.arena, &string_builder, STR_LIT("From, To, Weight\n"));
|
|
for (int64_t object_i = 0; object_i < list.count; object_i+=1) {
|
|
ObjectInfo *current_object = &objects[object_i];
|
|
String src_filename = current_object->filename;
|
|
for (ObjectDependency *dep = current_object->first_dependency; dep; dep = dep->next) {
|
|
ObjectInfo *dep_object = &objects[dep->id];
|
|
String dst_filename = dep_object->filename;
|
|
String line = String_format(scratch.arena, "\"%.*s\", \"%.*s\", \"%lld\"\n", STR_VARG(src_filename), STR_VARG(dst_filename), dep->weight);
|
|
StringBuilder_Append(scratch.arena, &string_builder, line);
|
|
}
|
|
}
|
|
String output_object_info = String_concatenate(scratch.arena, output, STR_LIT("_edge_table.csv"));
|
|
if (!StringBuilder_Write_to_file(&string_builder, output_object_info)) {
|
|
printf("Failed to write file %.*s\n", STR_VARG(output_object_info));
|
|
}
|
|
}
|
|
|
|
for (WITH_SCRATCH(scratch, (Arena*[]){arena}, 0)) {
|
|
StringBuilder string_builder = {0};
|
|
|
|
StringBuilder_Append(scratch.arena, &string_builder,
|
|
STR_LIT("File, Valid, Time stamp, Size, Dependencies, Dependents, Group id\n"));
|
|
|
|
for (int64_t object_i = 0; object_i < list.count; object_i+=1) {
|
|
ObjectInfo *current_object = &objects[object_i];
|
|
String line = String_format(scratch.arena, "\"%.*s\", \"%s\", \"%lld\", \"%lld\", \"%lld\", \"%lld\", \"%lld\"\n",
|
|
STR_VARG(current_object->filename), current_object->valid ? "true" : "false", current_object->time_stamp, current_object->size_in_bytes, current_object->dependency_count, current_object->dependents_count, current_object->group_id);
|
|
StringBuilder_Append(scratch.arena, &string_builder, line);
|
|
}
|
|
String output_object_info = String_concatenate(scratch.arena, output, STR_LIT("_objects_info.csv"));
|
|
if (!StringBuilder_Write_to_file(&string_builder, output_object_info)) {
|
|
printf("Failed to write file %.*s\n", STR_VARG(output_object_info));
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|