Skip to content

Instantly share code, notes, and snippets.

@oliverlars
Last active November 30, 2024 02:33
Show Gist options
  • Save oliverlars/2e46bb5bb5b0953dba56f76b203c5bad to your computer and use it in GitHub Desktop.
Save oliverlars/2e46bb5bb5b0953dba56f76b203c5bad to your computer and use it in GitHub Desktop.
#include "friday.h"
Entry(InputWaveInfo)
{
wave_info *WaveInfo = (wave_info*)InputWaveInfo;
shared_wave_info *Shared = WaveInfo->Shared;
memory_arena Arena = {0};
Arena.Start = MemoryReserve(GigaBytes(64));
operating_system OS = GetOperatingSystem();
build_options BuildOptions = {0};
b32 PrintHelpMenu = False;
if(WaveIsFirstLane(WaveInfo))
{
b32 OptimisedBuild = False;
b32 Rebuild = False;
compiler Compiler = COMPILER_PLATFORM_DEFAULT;
for(s64 ArgIndex = 1; ArgIndex < Shared->ArgCount; ArgIndex++)
{
str8 Arg = Str8PushF(&Arena, "%s", Shared->Args[ArgIndex]);
if(0) {}
else if(Str8Equal(Arg, Str8("release"))) { OptimisedBuild = True; }
else if(Str8Equal(Arg, Str8("rebuild"))) { Rebuild = True; }
else if(Str8Equal(Arg, Str8("msvc")))
{
if(OS == OS_WINDOWS)
{
Compiler = COMPILER_MSVC;
}
else
{
printf(STR_COLOUR_YELLOW "WARNING " STR_COLOUR_RESET);
printf("This compiler choice is only supported on Windows...\n");
printf("Using platform default\n\n");
}
}
else if(Str8Equal(Arg, Str8("clang")))
{
Compiler = COMPILER_CLANG;
}
else if(Str8Equal(Arg, Str8("gcc")))
{
if(OS == OS_DARWIN || OS == OS_LINUX)
{
Compiler = COMPILER_GCC;
}
else
{
printf(STR_COLOUR_RED "WARNING " STR_COLOUR_RESET);
printf("This compiler choice is only supported on Mac and Linux...\n");
printf("Using platform default\n\n");
}
}
else if(Str8Equal(Arg, Str8("help"))) {
str8_list Help = {0};
Str8ListAppend(&Arena, &Help, Str8PushF(&Arena, "\nFriday MK%s\n\n", FRIDAY_VERSION));
Str8ListAppend(&Arena, &Help, Str8("Flags: \n"));
Str8ListAppend(&Arena, &Help, Str8(" - rebuild\n"));
Str8ListAppend(&Arena, &Help, Str8(" Rebuild all specified source files\n"));
Str8ListAppend(&Arena, &Help, Str8(" - debug \n"));
Str8ListAppend(&Arena, &Help, Str8(" The default, builds a non-optimised build with debug information\n"));
Str8ListAppend(&Arena, &Help, Str8(" - release \n"));
Str8ListAppend(&Arena, &Help, Str8(" Builds an optimised build without debug information\n"));
Str8ListAppend(&Arena, &Help, Str8(" - msvc \n"));
Str8ListAppend(&Arena, &Help, Str8(" Choose to compile with msvc (Windows only)\n"));
Str8ListAppend(&Arena, &Help, Str8(" - clang \n"));
Str8ListAppend(&Arena, &Help, Str8(" Choose to compile with clang\n"));
Str8ListAppend(&Arena, &Help, Str8(" - gcc \n"));
Str8ListAppend(&Arena, &Help, Str8(" Choose to compile with gcc (Mac and Linux only)\n"));
Str8ListAppend(&Arena, &Help, Str8(" - help \n"));
Str8ListAppend(&Arena, &Help, Str8(" Prints out help menu\n"));
printf("%.*s", Str8ToPrintfArgs(Str8ListJoin(&Arena, Help)));
PrintHelpMenu = True;
}
}
str8_list IncludePaths = {0};
str8_list SourceFiles = {0};
Str8ListAppend(&Arena, &IncludePaths, Str8("include/"));
Str8ListAppend(&Arena, &IncludePaths, Str8("embed/"));
AppendSourceFilesInFolder(&Arena, &SourceFiles, Str8("src"));
if(OS == OS_WINDOWS)
{
AppendSourceFilesInFolder(&Shared->Arena, &SourceFiles, Str8("src/win32"));
}
else if(OS == OS_DARWIN)
{
AppendSourceFilesInFolder(&Shared->Arena, &SourceFiles, Str8("src/darwin"));
}
EmbedFile(&Arena, Str8("test.c"), Str8("embed/source.h"), Str8("SourceFile"));
BuildOptions = (build_options){
.Compiler = Compiler,
.OutputName = Str8PushF(&Arena, "carnage"),
.OutputType = OUTPUT_EXECUTABLE,
.SourceFiles = SourceFiles,
.IncludePaths = IncludePaths,
.WorkingDirectory = GetExecutableDirectory(&Arena),
.OutputPath = Str8PushF(&Arena, "build/"),
.Optimise = OptimisedBuild,
.FullRebuild = Rebuild,
};
EmitCompileCommandsJson(&Arena, BuildOptions);
}
BroadcastToAllLanes(WaveInfo, BuildOptions);
BroadcastToAllLanes(WaveInfo, PrintHelpMenu);
if(!PrintHelpMenu)
{
Build(WaveInfo, &Arena, BuildOptions);
}
return 0;
}
int main(s32 ArgCount, char **Args)
{
shared_wave_info Shared = {0};
Shared.ArgCount = ArgCount;
Shared.Args = Args;
memory_arena SharedArena = {0};
SharedArena.Start = MemoryReserve(GigaBytes(64));
Shared.Arena = SharedArena;
Dispatch(&Shared, GetNumberOfCPUs());
}
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#define FRIDAY_VERSION "IV"
/*
Thanks to
- shachaf
- graphitemaster
For giving me a hand with the synchronisation barrier on mac
*/
typedef signed char s8;
typedef signed short int s16;
typedef signed int s32;
typedef signed long long s64;
typedef unsigned char u8;
typedef unsigned short int u16;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef float f32;
typedef double f64;
typedef unsigned char b8;
typedef unsigned short int b16;
typedef unsigned int b32;
typedef unsigned long long b64;
#define KiloBytes(X) (1024ll * X)
#define MegaBytes(X) (1024ll * KiloBytes(X))
#define GigaBytes(X) (1024ll * MegaBytes(X))
size_t strlen(const char *str);
#define Min(a, b) ((a) < (b) ? (a) : (b))
#define Str8ToPrintfArgs(String) (int)String.Size, (char*)String.Bytes
#define Str8(String) (str8) { strlen(String), (s8*)String }
#define ArenaPushType(Arena, Type) (Type *)ArenaPush(Arena, sizeof(Type))
#define ArenaPushTypeZero(Arena, Type) (Type *)ArenaPushZero(Arena, sizeof(Type))
struct shared_wave_info;
struct wave_info;
u8 *SharedArenaPush(struct wave_info *WaveInfo, s64 Bytes);
#define False 0
#define True (!False)
#define Null (void *)0
#define STR_COLOUR_RED "\x1B[31m"
#define STR_COLOUR_GREEN "\x1B[32m"
#define STR_COLOUR_YELLOW "\x1B[33m"
#define STR_COLOUR_BLUE "\x1B[34m"
#define STR_COLOUR_MAGENTA "\x1B[35m"
#define STR_COLOUR_CYAN "\x1B[36m"
#define STR_COLOUR_WHITE "\x1B[37m"
#define STR_COLOUR_RESET "\x1B[0m"
typedef enum operating_system
{
OS_WINDOWS,
OS_DARWIN,
OS_LINUX,
}
operating_system;
typedef struct memory_arena
{
u8 *Start;
s64 Committed;
s64 At;
} memory_arena;
typedef struct str8
{
s64 Size;
s8 *Bytes;
} str8;
typedef struct str8_list_node
{
struct str8_list_node *Next;
struct str8_list_node *Previous;
str8 String;
} str8_list_node;
typedef struct str8_list
{
str8_list_node *First;
str8_list_node *Last;
} str8_list;
#define array(T) struct { T *Items; s64 Count; }
typedef array(str8) str8_array;
#if defined(__APPLE__)
typedef enum compiler
{
COMPILER_PLATFORM_DEFAULT = 0,
COMPILER_CLANG = 0,
COMPILER_GCC = 1,
COMPILER_MSVC = COMPILER_PLATFORM_DEFAULT,
COMPILER_COUNT,
} compiler;
#elif defined(_WIN32)
typedef enum compiler
{
COMPILER_PLATFORM_DEFAULT = 0,
COMPILER_MSVC = 0,
COMPILER_CLANG = 1,
COMPILER_GCC = COMPILER_PLATFORM_DEFAULT,
COMPILER_COUNT,
} compiler;
#endif
typedef enum output_type
{
OUTPUT_EXECUTABLE,
OUTPUT_STATIC_LIBRARY,
OUTPUT_DYNAMIC_LIBRARY,
OUTPUT_COUNT,
} output_type;
typedef struct build_options
{
compiler Compiler;
output_type OutputType;
str8 OutputName;
str8 OutputPath;
str8 WorkingDirectory;
str8_list IncludePaths;
str8_list SourceFiles;
b32 Optimise;
b32 FullRebuild;
b32 GenerateDebugInfo;
b32 PrintHelpMenu;
} build_options;
u8* ArenaPush(memory_arena *Arena, s64 Bytes);
u8 *ArenaPushZero(memory_arena *Arena, s64 Bytes);
str8 Str8PushF(memory_arena *Arena, char *Format, ...);
str8 Str8ListJoin(memory_arena *Arena, str8_list List);
str8 Str8ArrayJoin(memory_arena *Arena, str8_array Array);
void Str8ListAppend(memory_arena *Arena, str8_list *List, str8 String);
b32 Str8EndsWith(str8 String, str8 EndsWith);
char *CStrFromStr8(memory_arena *Arena, str8 String);
str8_list ListOfDirectoryEntriesFromPath(memory_arena *Arena, str8 Path);
void GobbleWhitespace(str8 File, u64 *At);
void GobbleUpToWhitespace(str8 File, u64 *At);
operating_system GetOperatingSystem();
#if __APPLE__
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdatomic.h>
#include <spawn.h>
#include <sys/wait.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <mach/mach_time.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <os/os_sync_wait_on_address.h>
#include <os/lock.h>
extern char **environ;
#define MemoryReserve(NumberOfBytes) (u8 *)mmap(NULL, NumberOfBytes, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0)
#define MemoryCommit(Address, NumberOfBytes) mprotect(Address, NumberOfBytes, PROT_READ|PROT_WRITE)
#define MemoryCopy(Destination, Source, NumberOfBytes) memcpy(Destination, Source, NumberOfBytes)
#define MemoryZero(Destination, NumberOfBytes) memset(Destination, 0, NumberOfBytes)
#define thread_local _Thread_Local
operating_system GetOperatingSystem()
{
return OS_DARWIN;
}
u64 GetPageSize()
{
return getpagesize();
}
str8 LoadEntireFileIntoMemory(memory_arena *Arena, str8 Filename)
{
s32 FileHandle = open(CStrFromStr8(Arena, Filename), O_RDONLY);
if (FileHandle == -1)
{
printf("File not found");
return (str8){0};
}
struct stat FileStat;
fstat(FileHandle, &FileStat);
u8 *BackingMemory = ArenaPush(Arena, (u64)FileStat.st_size + 1);
BackingMemory[FileStat.st_size] = 0;
str8 Result = {0};
Result.Bytes = (s8*)BackingMemory;
Result.Size = FileStat.st_size;
ssize_t BytesRead = read(FileHandle, BackingMemory, FileStat.st_size);
return Result;
}
void WriteStringToDisk(memory_arena *Arena, str8 DestinationPath, str8 StringToWrite)
{
s32 FileDescriptor = creat(CStrFromStr8(Arena, DestinationPath), S_IRUSR | S_IWUSR );
s64 BytesWritten = write(FileDescriptor, StringToWrite.Bytes, StringToWrite.Size);
}
str8 GetExecutableDirectory(memory_arena *Arena)
{
s32 NumberOfBytes = PATH_MAX;
u8 *BackingMemory = ArenaPushZero(Arena, NumberOfBytes);
getcwd((char*)BackingMemory, PATH_MAX);
str8 Result = {0};
Result.Size = NumberOfBytes;
Result.Bytes = (s8*)BackingMemory;
return Result;
}
void AppendSourceFilesInFolder(memory_arena *Arena, str8_list *SourceFilesFromFolder, str8 Path)
{
str8 SourceFilesInDirectory = Str8PushF(Arena, "%.*s/", Str8ToPrintfArgs(Path));
DIR *Directory = opendir((char*)SourceFilesInDirectory.Bytes);
struct dirent *Entry;
while((Entry = readdir(Directory)))
{
if (Entry->d_name[0] != '.' && Str8EndsWith(Str8(Entry->d_name), Str8(".c")))
{
struct stat FileStat;
stat(Entry->d_name, &FileStat);
if (!S_ISDIR(FileStat.st_mode))
{
str8 SourceFilepath = Str8PushF(Arena, "%.*s/%s", Str8ToPrintfArgs(Path), Entry->d_name);
Str8ListAppend(Arena, SourceFilesFromFolder, SourceFilepath);
}
}
}
}
void MakeDirectoryFromPath(memory_arena *Arena, str8 Path)
{
mkdir(CStrFromStr8(Arena, Path), 0755);
}
b32 FileExists(memory_arena *Arena, str8 Filename)
{
return access(CStrFromStr8(Arena, Filename), F_OK) == 0;
}
s64 AtomicAdd(s64 *Value, s64 Addend)
{
return __sync_fetch_and_add(Value, Addend);
}
u64 TimeStampNow()
{
return mach_absolute_time();
}
f32 MilliSecondsFromTimeStamp(u64 TimeStamp)
{
mach_timebase_info_data_t Info;
mach_timebase_info(&Info);
return (f32)((TimeStamp) * Info.numer / Info.denom) / 1000000.0;
}
s64 GetNumberOfCPUs()
{
s32 NumberOfCPUs;
size_t len = sizeof(NumberOfCPUs);
sysctlbyname("hw.ncpu", &NumberOfCPUs, &len, NULL, 0);
return NumberOfCPUs;
}
str8_list ListOfDirectoryEntriesFromPath(memory_arena *Arena, str8 Path)
{
str8_list Entries = { 0 };
DIR *Directory = opendir(CStrFromStr8(Arena, Path));
struct dirent *Entry;
while((Entry = readdir(Directory)))
{
if(Entry->d_name[0] != '.')
{
Str8ListAppend(Arena, &Entries, Str8PushF(Arena, "%s", Entry->d_name));
}
}
return Entries;
}
void DeleteFileFromDisk(memory_arena *Arena, str8 Filename)
{
remove(CStrFromStr8(Arena, Filename));
}
b32 ExecuteCommand(memory_arena *Arena, str8 EntireCommandAndArguments, b32 *FailedToExecute)
{
s32 Pipes[2];
pid_t ProcessID;
posix_spawn_file_actions_t SpawnActions;
pipe(Pipes);
posix_spawn_file_actions_init(&SpawnActions);
posix_spawn_file_actions_adddup2(&SpawnActions, Pipes[1], STDOUT_FILENO);
posix_spawn_file_actions_addclose(&SpawnActions, Pipes[0]);
char** CompilerArguments = (char**)ArenaPushZero(Arena, 1024);
u64 ArgumentIndex = 0;
for(u64 Index = 0; Index < EntireCommandAndArguments.Size; Index++, ArgumentIndex++)
{
GobbleWhitespace(EntireCommandAndArguments, &Index);
u64 IndexUpToWhitespace = Index;
GobbleUpToWhitespace(EntireCommandAndArguments, &IndexUpToWhitespace);
str8 Argument = { 0 };
Argument.Bytes = EntireCommandAndArguments.Bytes + Index;
Argument.Size = IndexUpToWhitespace - Index;
CompilerArguments[ArgumentIndex] = CStrFromStr8(Arena, Argument);
Index += Argument.Size;
}
s32 ProcessStatus = posix_spawnp(&ProcessID, CompilerArguments[0], &SpawnActions, Null, CompilerArguments, environ);
if (ProcessStatus == 0)
{
close(Pipes[1]);
s32 ExitCode;
waitpid(ProcessID, &ExitCode, 0);
FailedToExecute[0] = WEXITSTATUS(ExitCode) != 0;
s64 NumberOfBytesRead = 0;
str8_list StdOutList = {0};
u8 *BackingMemory = ArenaPushZero(Arena, 4096);
while ((NumberOfBytesRead = read(Pipes[0], BackingMemory, 4096)) > 0)
{
str8 String = {.Bytes = (s8*)BackingMemory, .Size = NumberOfBytesRead};
Str8ListAppend(Arena, &StdOutList, String);
}
str8 OutputString = Str8ListJoin(Arena, StdOutList);
printf("%.*s", Str8ToPrintfArgs(OutputString));
}
return ProcessStatus == 0;
}
str8 CompileCommandFromBuildOptions(memory_arena *Arena, build_options *BuildOptions, str8 HashedFilename, str8 SourceFilename)
{
str8 DebugOrRelease[2] =
{
Str8("-g -O0"), Str8("-O2")
};
str8 Compilers[2] =
{
Str8("clang"), Str8("gcc")
};
str8_list IncludePathsWithFlag = {0};
for (str8_list_node *Node = BuildOptions->IncludePaths.First; Node != Null; Node = Node->Next)
{
Str8ListAppend(Arena, &IncludePathsWithFlag, Str8PushF(Arena, "-I%.*s ", Str8ToPrintfArgs(Node->String)));
}
str8 CompileCommand = Str8PushF(
Arena,
"%.*s -c %.*s %.*s -o%.*s%.*s %.*s ",
Str8ToPrintfArgs(Compilers[BuildOptions->Compiler]),
Str8ToPrintfArgs(SourceFilename),
Str8ToPrintfArgs(DebugOrRelease[BuildOptions->Optimise]),
Str8ToPrintfArgs(BuildOptions->OutputPath),
Str8ToPrintfArgs(HashedFilename),
Str8ToPrintfArgs(Str8ListJoin(Arena, IncludePathsWithFlag)));
return CompileCommand;
}
str8 LinkCommandFromBuildOptions(memory_arena *Arena, build_options *BuildOptions, str8_array ObjectFiles)
{
str8 LinkCommand = Str8PushF(
Arena,
"clang -o %.*s%.*s %.*s",
Str8ToPrintfArgs(BuildOptions->OutputPath),
Str8ToPrintfArgs(BuildOptions->OutputName),
Str8ToPrintfArgs(Str8ArrayJoin(Arena, ObjectFiles))
);
return LinkCommand;
}
#define Entry(Data) void *Entry(void *Data)
Entry(Data);
typedef struct barrier
{
atomic_ullong Generation;
atomic_ullong LanesWaiting;
s64 LaneCount;
}
barrier;
typedef struct shared_wave_info
{
s32 ArgCount;
char **Args;
barrier *Barrier;
s64 LaneCount;
memory_arena Arena;
build_options BuildOptions;
s64 Data[4096];
}
shared_wave_info;
typedef struct wave_info
{
shared_wave_info *Shared;
s32 Lane;
}
wave_info;
/*
Based on Raymond Chen's article on synchronization barriers
https://devblogs.microsoft.com/oldnewthing/20160824-00/?p=94155
*/
void WaveBarrier(barrier *Barrier)
{
s64 Generation = atomic_load_explicit(&Barrier->Generation, memory_order_acquire);
s64 LanesWaiting = atomic_fetch_add_explicit(&Barrier->LanesWaiting, 1, memory_order_acquire);
if (LanesWaiting < Barrier->LaneCount - 1)
{
while(Barrier->Generation == Generation)
{
os_sync_wait_on_address(&Barrier->Generation, Generation, sizeof(s64), OS_SYNC_WAIT_ON_ADDRESS_SHARED);
}
}
else if(LanesWaiting + 1 == Barrier->LaneCount)
{
Barrier->LanesWaiting = 0;
atomic_fetch_add_explicit(&Barrier->Generation, 1, memory_order_release);
os_sync_wake_by_address_all(&Barrier->Generation, sizeof(s64), OS_SYNC_WAKE_BY_ADDRESS_SHARED);
}
atomic_thread_fence(memory_order_release);
}
s64 LaneValueFromTotal(wave_info *WaveInfo, s64 Count)
{
s64 Base = Count / WaveInfo->Shared->LaneCount;
s64 Remainder = Count % WaveInfo->Shared->LaneCount;
s64 Result = Count / WaveInfo->Shared->LaneCount + (WaveInfo->Lane < Remainder ? 1 : 0);
if (Count < WaveInfo->Shared->LaneCount)
{
Result = (s64)(WaveInfo->Lane < Count);
}
return Result;
}
void Dispatch(shared_wave_info *Shared, s64 LaneCount)
{
wave_info *Entries = (wave_info*)ArenaPush(&Shared->Arena, LaneCount*sizeof(wave_info));
pthread_t *Waves = (pthread_t*)ArenaPush(&Shared->Arena, LaneCount*sizeof(pthread_t));
barrier Barrier = {0};
Shared->LaneCount = LaneCount;
Barrier.LaneCount = LaneCount;
for (s64 Lane = 0; Lane < LaneCount; Lane++)
{
Entries[Lane].Shared = Shared;
Entries[Lane].Lane = Lane;
Entries[Lane].Shared->Barrier = &Barrier;
pthread_create(Waves + Lane, Null, Entry, Entries + Lane);
}
for (s64 Lane = 0; Lane < LaneCount; Lane++)
{
pthread_join(Waves[Lane], Null);
}
}
b32 WaveIsFirstLane(wave_info *WaveInfo)
{
return WaveInfo->Lane == 0;
}
u8 *ArenaPushTransient(memory_arena *Arena, s64 Bytes);
void BroadcastBytes(wave_info *WaveInfo, void *Bytes, s64 ByteCount)
{
u8 *Result = Null;
if(WaveIsFirstLane(WaveInfo))
{
Result = ArenaPushTransient(&WaveInfo->Shared->Arena, ByteCount);
MemoryCopy(Result, Bytes, ByteCount);
}
WaveBarrier(WaveInfo->Shared->Barrier);
Result = (u8*)(WaveInfo->Shared->Arena.Start + WaveInfo->Shared->Arena.At);
MemoryCopy(Bytes, Result, ByteCount);
WaveBarrier(WaveInfo->Shared->Barrier);
}
#define BroadcastToAllLanes(WaveInfo, Variable) BroadcastBytes(WaveInfo, &Variable, sizeof(Variable))
#define SharedBetweenLanes(WaveInfo, Variable)
s64 WaveGetLaneIndex(wave_info *WaveInfo)
{
return WaveInfo->Lane;
}
u64 NearestPowerOfTwo(u64 Value)
{
if(Value == 0) return 1;
Value -= 1;
Value |= Value >> 1;
Value |= Value >> 2;
Value |= Value >> 4;
Value |= Value >> 8;
Value |= Value >> 16;
Value |= Value >> 32;
return Value + 1;
}
/*
GPU Gems 3
https://www.youtube.com/watch?v=1G8CZioSjnM
*/
u64 WavePrefixSum(wave_info *WaveInfo, u64 Value)
{
WaveInfo->Shared->Data[WaveInfo->Lane] = Value;
WaveInfo->Shared->Data[0] = 0;
WaveBarrier(WaveInfo->Shared->Barrier);
int Offset = 1;
for(int D = NearestPowerOfTwo(WaveInfo->Shared->LaneCount)/2; D > 0; D >>= 1)
{
WaveBarrier(WaveInfo->Shared->Barrier);
if(WaveInfo->Lane < D)
{
int AI = Offset * (2 * WaveInfo->Lane + 1)-1;
int BI = Offset * (2 * WaveInfo->Lane + 2)-1;
WaveInfo->Shared->Data[BI] += WaveInfo->Shared->Data[AI];
}
Offset *= 2;
}
for(int D = 2; D < NearestPowerOfTwo(WaveInfo->Shared->LaneCount); D *= 2)
{
Offset >>= 1;
WaveBarrier(WaveInfo->Shared->Barrier);
if(WaveInfo->Lane < D-1)
{
int AI = Offset*(WaveInfo->Lane + 1) - 1;
int BI = Offset*(WaveInfo->Lane + 1) + (Offset/2) - 1;
WaveInfo->Shared->Data[BI] += WaveInfo->Shared->Data[AI];
}
}
WaveBarrier(WaveInfo->Shared->Barrier);
return WaveInfo->Shared->Data[WaveInfo->Lane];
}
u64 WaveSum(wave_info *WaveInfo, u64 Value)
{
WaveInfo->Shared->Data[0] = 0;
WaveBarrier(WaveInfo->Shared->Barrier);
AtomicAdd(WaveInfo->Shared->Data + 0, Value);
WaveBarrier(WaveInfo->Shared->Barrier);
return WaveInfo->Shared->Data[0];
}
b32 WaveAnyTrue(wave_info *WaveInfo, b32 Expression)
{
WaveInfo->Shared->Data[0] = 0;
WaveBarrier(WaveInfo->Shared->Barrier);
AtomicAdd(WaveInfo->Shared->Data + 0, Expression);
WaveBarrier(WaveInfo->Shared->Barrier);
return WaveInfo->Shared->Data[0] > 0;
}
b32 WaveAllTrue(wave_info *WaveInfo, b32 Expression)
{
WaveInfo->Shared->Data[0] = 0;
WaveBarrier(WaveInfo->Shared->Barrier);
AtomicAdd(WaveInfo->Shared->Data + 0, Expression);
WaveBarrier(WaveInfo->Shared->Barrier);
return WaveInfo->Shared->Data[0] == WaveInfo->Shared->LaneCount;
}
#elif defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define MemoryCommit(Address, NumberOfBytes) VirtualAlloc((void*)(Address), NumberOfBytes, MEM_COMMIT, PAGE_READWRITE)
#define MemoryReserve(NumberOfBytes) (u8*)VirtualAlloc((void*)0, NumberOfBytes, MEM_RESERVE, PAGE_READWRITE)
#define MemoryCopy(Destination, Source, NumberOfBytes) RtlCopyMemory(Destination, Source, NumberOfBytes)
#define MemoryZero(Destination, NumberOfBytes) RtlZeroMemory(Destination, NumberOfBytes)
operating_system GetOperatingSystem()
{
return OS_WINDOWS;
}
u64 GetPageSize()
{
SYSTEM_INFO SystemInfo = {0};
GetSystemInfo(&SystemInfo);
return SystemInfo.dwPageSize;
}
str8 LoadEntireFileIntoMemory(memory_arena *Arena, str8 Filename)
{
HANDLE FileHandle = CreateFileA(CStrFromStr8(Arena, Filename),
GENERIC_READ,
FILE_SHARE_READ,
Null,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
Null);
if (FileHandle == INVALID_HANDLE_VALUE)
{
printf("File: %.*s not found\n", Str8ToPrintfArgs(Filename));
return (str8){0};
}
LARGE_INTEGER FileSize;
DWORD BytesRead;
GetFileSizeEx(FileHandle, &FileSize);
u8* BackingMemory = ArenaPush(Arena, (u64)FileSize.QuadPart+1);
BackingMemory[FileSize.QuadPart] = 0;
str8 Result = {0};
Result.Bytes = (s8*)BackingMemory;
Result.Size = FileSize.QuadPart;
ReadFile(FileHandle, BackingMemory, FileSize.QuadPart, &BytesRead, Null);
CloseHandle(FileHandle);
return Result;
}
void WriteStringToDisk(memory_arena *Arena, str8 DestinationPath, str8 StringToWrite)
{
HANDLE FileHandle = CreateFileA(CStrFromStr8(Arena, DestinationPath),
GENERIC_WRITE,
0,
Null,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
Null);
DWORD BytesWritten = 0;
WriteFile(FileHandle, StringToWrite.Bytes, StringToWrite.Size, &BytesWritten, Null);
CloseHandle(FileHandle);
}
void AppendSourceFilesInFolder(memory_arena *Arena, str8_list *SourceFilesFromFolder, str8 Path)
{
str8 PathWithWildcard = Str8PushF(Arena, "%.*s", Str8ToPrintfArgs(Path));
str8_list Entries = ListOfDirectoryEntriesFromPath(Arena, PathWithWildcard);
for(str8_list_node *Entry = Entries.First; Entry != Null; Entry = Entry->Next)
{
if(Str8EndsWith(Entry->String, Str8(".c")))
{
str8 SourceFilepath = Str8PushF(Arena, "%.*s/%.*s",
Str8ToPrintfArgs(Path),
Str8ToPrintfArgs(Entry->String));
Str8ListAppend(Arena, SourceFilesFromFolder, SourceFilepath);
}
}
}
str8 GetExecutableDirectory(memory_arena *Arena)
{
DWORD NumberOfBytes = GetCurrentDirectoryA(0, 0);
u8* BackingMemory = ArenaPushZero(Arena, NumberOfBytes);
GetCurrentDirectoryA(NumberOfBytes, (LPSTR)BackingMemory);
str8 Result = {0};
Result.Size = NumberOfBytes;
Result.Bytes = (s8*)BackingMemory;
return Result;
}
void MakeDirectoryFromPath(memory_arena *Arena, str8 Path)
{
CreateDirectoryA(CStrFromStr8(Arena, Path), Null);
}
b32 FileExists(memory_arena *Arena, str8 Filename)
{
DWORD FileAttributes = GetFileAttributesA((LPCSTR)CStrFromStr8(Arena, Filename));
return FileAttributes != INVALID_FILE_ATTRIBUTES && !(FileAttributes & FILE_ATTRIBUTE_DIRECTORY);
}
s64 AtomicIncrement(s64 volatile *Addend, s64 Value)
{
return InterlockedExchangeAdd64(Addend, Value);
}
u64 TimeStampNow()
{
LARGE_INTEGER Stamp = {0};
QueryPerformanceCounter(&Stamp);
return Stamp.QuadPart;
}
f32 MilliSecondsFromTimeStamp(u64 TimeStamp)
{
LARGE_INTEGER Frequency = {0};
QueryPerformanceFrequency(&Frequency);
return (f32)(1000.0*(TimeStamp) / Frequency.QuadPart);
}
s64 GetNumberOfCPUs()
{
SYSTEM_INFO SystemInfo = {0};
GetSystemInfo(&SystemInfo);
return SystemInfo.dwNumberOfProcessors;
}
void DeleteFileFromDisk(memory_arena *Arena, str8 Filename)
{
DeleteFileA(CStrFromStr8(Arena, Filename));
}
b32 ExecuteCommand(memory_arena *Arena, str8 EntireCommandAndArguments, b32 *FailedToExecute)
{
STARTUPINFOA StartupInfo = {0};
PROCESS_INFORMATION ProcessInfo = {0};
SECURITY_ATTRIBUTES SecurityAttributes = {0};
SecurityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
SecurityAttributes.bInheritHandle = True;
HANDLE StdOutHandleRead = 0;
HANDLE StdOutHandleWrite = 0;
CreatePipe(&StdOutHandleRead, &StdOutHandleWrite, &SecurityAttributes, 0);
b32 ProcessStatus = CreateProcessA(0, CStrFromStr8(Arena, EntireCommandAndArguments),
0, 0, 1, 0, 0, 0, &StartupInfo, &ProcessInfo);
if (ProcessStatus)
{
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
DWORD ExitCode;
GetExitCodeProcess(ProcessInfo.hProcess, &ExitCode);
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
CloseHandle(StdOutHandleWrite);
FailedToExecute[0] = ExitCode != 0;
DWORD NumberOfBytesRead = 0;
str8_list StdOutList = {0};
for (;;)
{
u8* BackingMemory = ArenaPushZero(Arena, 4096);
BOOL Success = ReadFile(StdOutHandleRead, BackingMemory, 4096, &NumberOfBytesRead, NULL);
if (!Success || NumberOfBytesRead == 0)
{
break;
}
str8 String = {.Bytes = (s8*)BackingMemory, .Size = NumberOfBytesRead };
Str8ListAppend(Arena, &StdOutList, String);
}
str8 OutputString = Str8ListJoin(Arena, StdOutList);
printf("%.*s", Str8ToPrintfArgs(OutputString));
}
return ProcessStatus;
}
str8 CompileCommandFromBuildOptions(memory_arena *Arena, build_options *BuildOptions, str8 HashedFilename, str8 SourceFilename)
{
str8 DebugOrRelease[2] =
{
Str8("/Zi /Od"), Str8("/O2")
};
str8 Compilers[2] =
{
Str8("cl.exe"),
Str8("clang-cl")
};
str8_list IncludePathsWithFlag = {0};
for (str8_list_node *Node = BuildOptions->IncludePaths.First; Node != Null; Node = Node->Next)
{
Str8ListAppend(Arena, &IncludePathsWithFlag, Str8PushF(Arena, "-I%.*s ", Str8ToPrintfArgs(Node->String)));
}
str8 CompileCommand = Str8PushF(
Arena,
"%.*s /c /TC /nologo /FS /EHsc %.*s %.*s /Fo%.*s%.*s %.*s ",
Str8ToPrintfArgs(Compilers[BuildOptions->Compiler]),
Str8ToPrintfArgs(SourceFilename),
Str8ToPrintfArgs(DebugOrRelease[BuildOptions->Optimise]),
Str8ToPrintfArgs(BuildOptions->OutputPath),
Str8ToPrintfArgs(HashedFilename),
Str8ToPrintfArgs(Str8ListJoin(Arena, IncludePathsWithFlag)));
return CompileCommand;
}
str8 LinkCommandFromBuildOptions(memory_arena *Arena, build_options *BuildOptions, str8_array ObjectFiles)
{
str8 LinkCommand = Str8PushF(
Arena,
"link /nologo /INCREMENTAL:NO /DEBUG /PDB:build/%.*s.pdb /OUT:%.*s%.*s.exe %.*s",
Str8ToPrintfArgs(BuildOptions->OutputName),
Str8ToPrintfArgs(BuildOptions->OutputPath),
Str8ToPrintfArgs(BuildOptions->OutputName),
Str8ToPrintfArgs(Str8ArrayJoin(Arena, ObjectFiles))
);
return LinkCommand;
}
#define Entry(Data) DWORD WINAPI Entry(void *Data)
Entry(Data);
typedef struct shared_wave_info
{
s32 ArgCount;
char **Args;
RTL_BARRIER *Barrier;
s64 LaneCount;
memory_arena Arena;
build_options BuildOptions;
volatile u64 Data[4096];
}
shared_wave_info;
typedef struct wave_info
{
shared_wave_info *Shared;
s32 Lane;
}
wave_info;
void WaveBarrier(RTL_BARRIER *Barrier)
{
EnterSynchronizationBarrier(Barrier, SYNCHRONIZATION_BARRIER_FLAGS_BLOCK_ONLY);
}
s64 LaneValueFromTotal(wave_info *WaveInfo, s64 Count)
{
s64 Base = Count / WaveInfo->Shared->LaneCount;
s64 Remainder = Count % WaveInfo->Shared->LaneCount;
s64 Result = Count / WaveInfo->Shared->LaneCount + (WaveInfo->Lane < Remainder ? 1 : 0);
if (Count < WaveInfo->Shared->LaneCount)
{
Result = (s64)(WaveInfo->Lane < Count);
}
return Result;
}
void Dispatch(shared_wave_info *Shared, s64 LaneCount)
{
wave_info *Entries = (wave_info*)ArenaPush(&Shared->Arena, LaneCount*sizeof(wave_info));
HANDLE *Waves = (HANDLE*)ArenaPush(&Shared->Arena, LaneCount*sizeof(HANDLE));
RTL_BARRIER Barrier;
Shared->LaneCount = LaneCount;
InitializeSynchronizationBarrier(&Barrier, LaneCount, -1);
for (s64 Lane = 0; Lane < LaneCount; Lane++)
{
Entries[Lane].Shared = Shared;
Entries[Lane].Lane = Lane;
Entries[Lane].Shared->Barrier = &Barrier;
DWORD ThreadID;
Waves[Lane] = CreateThread(0, 0, Entry, Entries + Lane, 0, &ThreadID);
}
WaitForMultipleObjects(LaneCount, Waves, True, INFINITE);
}
b32 WaveIsFirstLane(wave_info *WaveInfo)
{
return WaveInfo->Lane == 0;
}
u8 *ArenaPushTransient(memory_arena *Arena, s64 Bytes);
void BroadcastBytes(wave_info *WaveInfo, void *Bytes, s64 ByteCount)
{
u8 *Result = Null;
if(WaveIsFirstLane(WaveInfo))
{
Result = ArenaPushTransient(&WaveInfo->Shared->Arena, ByteCount);
MemoryCopy(Result, Bytes, ByteCount);
}
WaveBarrier(WaveInfo->Shared->Barrier);
Result = (u8*)(WaveInfo->Shared->Arena.Start + WaveInfo->Shared->Arena.At);
MemoryCopy(Bytes, Result, ByteCount);
WaveBarrier(WaveInfo->Shared->Barrier);
}
#define BroadcastToAllLanes(WaveInfo, Variable) BroadcastBytes(WaveInfo, &Variable, sizeof(Variable))
#define SharedBetweenLanes(WaveInfo, Variable)
s64 WaveGetLaneIndex(wave_info *WaveInfo)
{
return WaveInfo->Lane;
}
u64 NearestPowerOfTwo(u64 Value)
{
if(Value == 0) return 1;
Value -= 1;
Value |= Value >> 1;
Value |= Value >> 2;
Value |= Value >> 4;
Value |= Value >> 8;
Value |= Value >> 16;
Value |= Value >> 32;
return Value + 1;
}
/*
GPU Gems 3
https://www.youtube.com/watch?v=1G8CZioSjnM
*/
u64 WavePrefixSum(wave_info *WaveInfo, u64 Value)
{
WaveInfo->Shared->Data[WaveInfo->Lane] = Value;
WaveInfo->Shared->Data[0] = 0;
WaveBarrier(WaveInfo->Shared->Barrier);
int Offset = 1;
for(int D = NearestPowerOfTwo(WaveInfo->Shared->LaneCount)/2; D > 0; D >>= 1)
{
WaveBarrier(WaveInfo->Shared->Barrier);
if(WaveInfo->Lane < D)
{
int AI = Offset * (2 * WaveInfo->Lane + 1)-1;
int BI = Offset * (2 * WaveInfo->Lane + 2)-1;
WaveInfo->Shared->Data[BI] += WaveInfo->Shared->Data[AI];
}
Offset *= 2;
}
for(int D = 2; D < NearestPowerOfTwo(WaveInfo->Shared->LaneCount); D *= 2)
{
Offset >>= 1;
WaveBarrier(WaveInfo->Shared->Barrier);
if(WaveInfo->Lane < D-1)
{
int AI = Offset*(WaveInfo->Lane + 1) - 1;
int BI = Offset*(WaveInfo->Lane + 1) + (Offset/2) - 1;
WaveInfo->Shared->Data[BI] += WaveInfo->Shared->Data[AI];
}
}
WaveBarrier(WaveInfo->Shared->Barrier);
return WaveInfo->Shared->Data[WaveInfo->Lane];
}
u64 WaveSum(wave_info *WaveInfo, u64 Value)
{
WaveInfo->Shared->Data[0] = 0;
WaveBarrier(WaveInfo->Shared->Barrier);
AtomicIncrement(WaveInfo->Shared->Data + 0, Value);
WaveBarrier(WaveInfo->Shared->Barrier);
return WaveInfo->Shared->Data[0];
}
b32 WaveAnyTrue(wave_info *WaveInfo, b32 Expression)
{
WaveInfo->Shared->Data[0] = 0;
WaveBarrier(WaveInfo->Shared->Barrier);
AtomicIncrement((volatile s64*)WaveInfo->Shared->Data + 0, Expression);
WaveBarrier(WaveInfo->Shared->Barrier);
return WaveInfo->Shared->Data[0];
}
b32 WaveAllTrue(wave_info *WaveInfo, b32 Expression)
{
WaveInfo->Shared->Data[0] = 0;
WaveBarrier(WaveInfo->Shared->Barrier);
AtomicIncrement((volatile s64*)WaveInfo->Shared->Data + 0, Expression);
WaveBarrier(WaveInfo->Shared->Barrier);
return WaveInfo->Shared->Data[0];
}
str8_list ListOfDirectoryEntriesFromPath(memory_arena *Arena, str8 Path)
{
str8_list Entries = { 0 };
WIN32_FIND_DATAA FindData = {0};
str8 Directory = Str8PushF(Arena, "%.*s\\*", Str8ToPrintfArgs(Path));
HANDLE FindHandle = FindFirstFileA(CStrFromStr8(Arena, Directory), &FindData);
b32 NextFile = FindHandle != INVALID_HANDLE_VALUE;
for(;NextFile != False; NextFile = FindNextFileA(FindHandle, &FindData))
{
if(FindData.dwFileAttributes != INVALID_FILE_ATTRIBUTES && !(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
Str8ListAppend(Arena, &Entries, Str8PushF(Arena, "%s", FindData.cFileName));
}
}
return Entries;
}
str8_array ArrayOfDirectoryEntriesFromPath(memory_arena *Arena, str8 Path)
{
s64 Count = 0;
{
WIN32_FIND_DATAA FindData = {0};
str8 Directory = Str8PushF(Arena, "%.*s\\*", Str8ToPrintfArgs(Path));
HANDLE FindHandle = FindFirstFileA(CStrFromStr8(Arena, Directory), &FindData);
b32 NextFile = FindHandle != INVALID_HANDLE_VALUE;
for(;NextFile != False; NextFile = FindNextFileA(FindHandle, &FindData))
{
if(FindData.dwFileAttributes != INVALID_FILE_ATTRIBUTES && !(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
Count += 1;
}
}
}
str8_array Result = {0};
Result.Count = Count;
Result.Items = (str8*)ArenaPush(Arena, Count*sizeof(str8));
{
WIN32_FIND_DATAA FindData = {0};
str8 Directory = Str8PushF(Arena, "%.*s\\*", Str8ToPrintfArgs(Path));
HANDLE FindHandle = FindFirstFileA(CStrFromStr8(Arena, Directory), &FindData);
b32 NextFile = FindHandle != INVALID_HANDLE_VALUE;
s64 Index = 0;
for(;NextFile != False; NextFile = FindNextFileA(FindHandle, &FindData))
{
if(FindData.dwFileAttributes != INVALID_FILE_ATTRIBUTES && !(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
Result.Items[Index++] = Str8PushF(Arena, "%.*s%s", Str8ToPrintfArgs(Path), FindData.cFileName);
}
}
}
return Result;
}
#endif
u8* ArenaPush(memory_arena *Arena, s64 Bytes)
{
if(Arena->At + Bytes >= Arena->Committed)
{
s64 BytesNeeded = (Arena->At + Bytes - Arena->Committed);
u64 BytesToCommit = BytesNeeded + GetPageSize() - (BytesNeeded % GetPageSize());
MemoryCommit(Arena->Start + Arena->Committed, BytesToCommit);
Arena->Committed += BytesToCommit;
}
u8* Memory = Arena->Start + Arena->At;
Arena->At += Bytes;
return Memory;
}
u8 *ArenaPushTransient(memory_arena *Arena, s64 Bytes)
{
if(Arena->At + Bytes >= Arena->Committed)
{
s64 BytesNeeded = (Arena->At + Bytes - Arena->Committed);
u64 BytesToCommit = BytesNeeded + GetPageSize() - (BytesNeeded % GetPageSize());
MemoryCommit(Arena->Start + Arena->Committed, BytesToCommit);
Arena->Committed += BytesToCommit;
}
u8* Memory = Arena->Start + Arena->At;
return Memory;
}
u8 *ArenaPushZero(memory_arena *Arena, s64 Bytes)
{
u8 *Memory = ArenaPush(Arena, Bytes);
MemoryZero(Memory, Bytes);
return Memory;
}
str8 Str8PushFV(memory_arena *Arena, char *Format, va_list InputArgList)
{
va_list ArgList;
va_copy(ArgList, InputArgList);
u32 BytesNeeded = vsnprintf(0, 0, Format, InputArgList) + 1;
str8 Result = {0};
Result.Bytes = (s8*)ArenaPush(Arena, BytesNeeded);
Result.Size = vsnprintf((char *)Result.Bytes, BytesNeeded, Format, ArgList);
Result.Bytes[Result.Size] = 0;
va_end(ArgList);
return Result;
}
str8 Str8PushF(memory_arena *Arena, char *Format, ...)
{
va_list args;
va_start(args, Format);
str8 Result = Str8PushFV(Arena, Format, args);
va_end(args);
return Result;
}
char *CStrFromStr8(memory_arena *Arena, str8 String)
{
char *Result = (char *)ArenaPushZero(Arena, String.Size + 1);
MemoryCopy(Result, String.Bytes, String.Size);
return Result;
}
b32 Str8Equal(str8 A, str8 B)
{
if (A.Size != B.Size)
return False;
for (u64 Index = 0; Index < A.Size; Index++)
{
if (A.Bytes[Index] != B.Bytes[Index])
{
return False;
}
}
return True;
}
b32 Str8EndsWith(str8 String, str8 EndsWith)
{
str8 SubString = String;
SubString.Bytes += String.Size - EndsWith.Size;
SubString.Size = EndsWith.Size;
return Str8Equal(SubString, EndsWith);
}
void Str8ListAppend(memory_arena *Arena, str8_list *List, str8 String)
{
str8_list_node *Node = ArenaPushTypeZero(Arena, str8_list_node);
Node->String = Str8PushF(Arena, "%.*s", Str8ToPrintfArgs(String));
if (List->First == Null)
{
List->First = Node;
List->Last = List->First;
}
else
{
List->Last->Next = Node;
Node->Previous = List->Last;
List->Last = Node;
}
}
str8 Str8ListJoin(memory_arena *Arena, str8_list List)
{
u64 BytesToAllocate = 0;
for (str8_list_node *Node = List.First; Node != Null; Node = Node->Next)
{
BytesToAllocate += Node->String.Size;
}
u8 *Bytes = ArenaPush(Arena, BytesToAllocate);
u64 At = 0;
for (str8_list_node *Node = List.First; Node != Null; Node = Node->Next)
{
MemoryCopy(Bytes + At, Node->String.Bytes, Node->String.Size);
At += Node->String.Size;
}
str8 Result = {.Bytes = (s8*)Bytes, .Size = BytesToAllocate};
return Result;
}
str8 Str8ArrayJoin(memory_arena *Arena, str8_array Array)
{
u64 BytesToAllocate = 0;
for (int I = 0; I < Array.Count; I++)
{
BytesToAllocate += Array.Items[I].Size;
}
u8 *Bytes = ArenaPush(Arena, BytesToAllocate);
u64 At = 0;
for (int I = 0; I < Array.Count; I++)
{
MemoryCopy(Bytes + At, Array.Items[I].Bytes, Array.Items[I].Size);
At += Array.Items[I].Size;
}
str8 Result = {.Bytes = (s8*)Bytes, .Size = BytesToAllocate};
return Result;
}
str8_array Str8ArrayFromList(memory_arena *Arena, str8_list List)
{
str8_array Result = {0};
s64 Count = 0;
for(str8_list_node *Entry = List.First; Entry != Null; Entry = Entry->Next)
{
Count++;
}
Result.Count = Count;
Result.Items = (str8*)ArenaPush(Arena, sizeof(str8)*Count);
s64 Index = 0;
for(str8_list_node *Entry = List.First; Entry != Null; Entry = Entry->Next)
{
Result.Items[Index++] = Entry->String;
}
return Result;
}
#define FNV_64_PRIME ((u64)0x100000001b3ULL)
#define FNV_HASH_START ((u64)0xcbf29ce484222325ULL)
void FNVHash64(u64 *Hash, str8 String)
{
for (s64 Index = 0; Index < String.Size; Index++)
{
*Hash *= FNV_64_PRIME;
*Hash ^= (u64)String.Bytes[Index];
}
}
b32 AdvanceN(str8 File, u64 *At, u64 N)
{
if (*At + N < File.Size)
{
*At += N;
return True;
}
return False;
}
void GobbleWhitespace(str8 File, u64 *At)
{
while (*At < File.Size)
{
u64 Byte = File.Bytes[*At];
switch (Byte)
{
case ' ':
case '\r':
case '\t':
case '\n':
{
if(AdvanceN(File, At, 1) == False)
{
return;
}
}
break;
default:
{
return;
}
break;
}
}
}
void GobbleUpToWhitespace(str8 File, u64 *At)
{
while (*At < File.Size)
{
u64 Byte = File.Bytes[*At];
switch (Byte)
{
case ' ':
case '\r':
case '\t':
case '\n':
{
return;
}
break;
default:
{
if(AdvanceN(File, At, 1) == False)
{
return;
}
}
break;
}
}
}
str8 Str8UpToLastCharacter(memory_arena *Arena, str8 InputPath, u8 Character)
{
s64 SizeUptoLastCharacter = InputPath.Size - 1;
b32 FoundCharacter = False;
for (; SizeUptoLastCharacter >= 0; SizeUptoLastCharacter--)
{
if (InputPath.Bytes[SizeUptoLastCharacter] == Character)
{
FoundCharacter = True;
break;
}
}
str8 Path = {0};
if (FoundCharacter)
{
Path.Size = SizeUptoLastCharacter;
Path.Bytes = (s8*)ArenaPush(Arena, SizeUptoLastCharacter);
MemoryCopy(Path.Bytes, InputPath.Bytes, SizeUptoLastCharacter);
}
return Path;
}
void FindAllIncludesInFile(memory_arena *Arena, str8 Filename, str8_list IncludePaths, str8_list *IncludeChainFiles)
{
// Support unicode...
str8 CurrentFile = LoadEntireFileIntoMemory(Arena, Filename);
Str8ListAppend(Arena, IncludeChainFiles, CurrentFile);
int CommentNesting = 0;
for (u64 At = 0; At < CurrentFile.Size; At++)
{
u8 Byte = CurrentFile.Bytes[At];
u8 NextNonWhitespaceByte = 0;
u64 NextAt = At;
GobbleWhitespace(CurrentFile, &NextAt);
NextNonWhitespaceByte = CurrentFile.Bytes[NextAt+1];
if(Byte == '/' && ((NextAt > At && (NextNonWhitespaceByte == '/') || NextNonWhitespaceByte == '*')))
{
CommentNesting += 1;
}
if(CommentNesting > 0)
{
if((Byte == '*' && ((NextAt > At && (NextNonWhitespaceByte == '/') || Byte == '\n'))))
{
CommentNesting -= 1;
}
}
else if (Byte == '#')
{
if (AdvanceN(CurrentFile, &At, 1) == False)
{
return;
}
str8 Token = {0};
Token.Bytes = CurrentFile.Bytes + At;
str8 IncludeToken = Str8("include");
Token.Size = IncludeToken.Size;
if (At + Token.Size < CurrentFile.Size)
{
if (Str8Equal(Token, IncludeToken))
{
AdvanceN(CurrentFile, &At, Token.Size);
GobbleWhitespace(CurrentFile, &At);
if (CurrentFile.Bytes[At] == '"')
{
if (AdvanceN(CurrentFile, &At, 1))
{
str8 IncludePath = {0};
IncludePath.Bytes = CurrentFile.Bytes + At;
for (; AdvanceN(CurrentFile, &At, 1) && CurrentFile.Bytes[At] != '"';);
IncludePath.Size = CurrentFile.Bytes + At - IncludePath.Bytes;
str8 SourceDir = Str8UpToLastCharacter(Arena, Filename, '/');
str8 SourceDirPlusIncludePath = Str8PushF(Arena, "%.*s/%.*s", Str8ToPrintfArgs(SourceDir), Str8ToPrintfArgs(IncludePath));
b32 FoundFile = False;
if (FileExists(Arena, SourceDirPlusIncludePath))
{
FoundFile = True;
FindAllIncludesInFile(Arena, SourceDirPlusIncludePath, IncludePaths, IncludeChainFiles);
}
else
{
for (str8_list_node *Node = IncludePaths.First; Node != 0; Node = Node->Next)
{
str8 FullPath = Str8PushF(
Arena,
"%.*s/%.*s",
Str8ToPrintfArgs(Str8UpToLastCharacter(Arena, Node->String, '/')),
Str8ToPrintfArgs(IncludePath));
if(FileExists(Arena, FullPath))
{
FoundFile = True;
FindAllIncludesInFile(Arena, FullPath, IncludePaths, IncludeChainFiles);
break;
}
}
}
if (FoundFile == False)
{
printf("\nUnable to find include path...\n");
return;
}
}
}
}
}
}
}
}
void EmbedFile(memory_arena *Arena, str8 SourcePath, str8 DestinationPath, str8 Name)
{
str8 File = LoadEntireFileIntoMemory(Arena, SourcePath);
str8_list Embed = {0};
Str8ListAppend(Arena, &Embed, Str8PushF(Arena, "static unsigned char %.*s[] =\n{\n", Str8ToPrintfArgs(Name)));
for(s64 At = 0; At < File.Size;)
{
s8* Byte = File.Bytes + At;
if(At + 16 <= File.Size)
{
str8 Elements = Str8PushF(Arena,
" 0x%02x, 0x%02x, 0x%02x, 0x%02x,\n"
" 0x%02x, 0x%02x, 0x%02x, 0x%02x,\n"
" 0x%02x, 0x%02x, 0x%02x, 0x%02x,\n"
" 0x%02x, 0x%02x, 0x%02x, 0x%02x,\n",
Byte[0], Byte[1], Byte[2], Byte[3],
Byte[4], Byte[5], Byte[6], Byte[7],
Byte[8], Byte[9], Byte[10], Byte[11],
Byte[12], Byte[13], Byte[14], Byte[15]
);
Str8ListAppend(Arena, &Embed, Elements);
At += 16;
}
else
{
str8 Element = {0};
if(At % 4 == 0)
{
Element = Str8PushF(Arena, " 0x%02x,", Byte[0]);
}
else
{
if((At % 4) % 3 == 0)
{
Element = Str8PushF(Arena, "0x%02x,\n", Byte[0]);
}
else
{
Element = Str8PushF(Arena, "0x%02x, ", Byte[0]);
}
}
Str8ListAppend(Arena, &Embed, Element);
At += 1;
}
}
Str8ListAppend(Arena, &Embed, Str8("\n};\n"));
str8 JoinedEmbed = Str8ListJoin(Arena, Embed);
WriteStringToDisk(Arena, DestinationPath, JoinedEmbed);
}
u64 HashSourceFileAndIncludes(memory_arena *Arena, str8 SourceFilename, str8_list IncludePaths, b32 Optimise, compiler Compiler)
{
str8_list IncludeChainFiles = {0};
FindAllIncludesInFile(Arena, SourceFilename, IncludePaths, &IncludeChainFiles);
u64 Hash = FNV_HASH_START;
FNVHash64(&Hash, SourceFilename);
FNVHash64(&Hash, Str8PushF(Arena, "%d", Compiler));
FNVHash64(&Hash, Str8PushF(Arena, "%d", Optimise));
for (str8_list_node *File = IncludeChainFiles.First; File != 0; File = File->Next)
{
FNVHash64(&Hash, File->String);
}
return Hash;
}
b32 CompileTranslationUnit(memory_arena *Arena, build_options *BuildOptions, str8 SourceFilename, u64 Hash)
{
str8 HashedFilename = Str8PushF(Arena, "%llu.o", Hash);
b32 FoundFile = FileExists(Arena, Str8PushF(Arena, "build/%.*s", Str8ToPrintfArgs(HashedFilename)));
b32 FoundObjectFile = FoundFile;
if (FoundObjectFile && !BuildOptions->FullRebuild)
{
return False;
}
b32 FailedToExecute = False;
b32 FailedToBuild = False;
str8 CompileCommand = CompileCommandFromBuildOptions(Arena, BuildOptions, HashedFilename, SourceFilename);
if(ExecuteCommand(Arena, CompileCommand, &FailedToExecute) == False)
{
printf(STR_COLOUR_RED "ERROR: " STR_COLOUR_RESET);
printf("Failed to launch compiler\n");
FailedToBuild = True;
}
return FailedToBuild || FailedToExecute;
}
b32 LinkObjectFiles(memory_arena *Arena, build_options *BuildOptions, str8_array ObjectFileNames)
{
b32 FailedToBuild = False;
str8_array ObjectFilePaths = {0};
ObjectFilePaths.Count = ObjectFileNames.Count;
ObjectFilePaths.Items = (str8*)ArenaPush(Arena, ObjectFilePaths.Count*sizeof(str8));
for (s64 I = 0; I < ObjectFilePaths.Count; I++)
{
ObjectFilePaths.Items[I] = Str8PushF(Arena, "%.*s%.*s ",
Str8ToPrintfArgs(BuildOptions->OutputPath),
Str8ToPrintfArgs(ObjectFileNames.Items[I]));
}
str8_list BuildDirectoryEntries = ListOfDirectoryEntriesFromPath(Arena, BuildOptions->OutputPath);
for(str8_list_node *Entry = BuildDirectoryEntries.First; Entry != Null; Entry = Entry->Next)
{
if (Str8EndsWith(Entry->String, Str8(".o")))
{
b32 FoundMatch = False;
str8 FilePathToCheck = Str8PushF(Arena, "%.*s%.*s", Str8ToPrintfArgs(BuildOptions->OutputPath),
Str8ToPrintfArgs(Entry->String));
for (s64 J = 0; J < ObjectFileNames.Count; J++)
{
str8 ObjectFilePathWithoutSpace = ObjectFilePaths.Items[J];
ObjectFilePathWithoutSpace.Size -= 1;
if (Str8Equal(ObjectFilePathWithoutSpace, FilePathToCheck))
{
FoundMatch = True;
break;
}
}
if (FoundMatch == False)
{
DeleteFileFromDisk(Arena, FilePathToCheck);
}
}
}
str8 LinkCommand = LinkCommandFromBuildOptions(Arena, BuildOptions, ObjectFilePaths);
ExecuteCommand(Arena, LinkCommand, &FailedToBuild);
return FailedToBuild;
}
void Build(wave_info *WaveInfo, memory_arena *Arena, build_options BuildOptions)
{
u64 Start = TimeStampNow();
str8_array RelativeSourceFilePaths = {0};
str8_array ObjectFileNames = {0};
if(WaveIsFirstLane(WaveInfo))
{
RelativeSourceFilePaths = Str8ArrayFromList(&WaveInfo->Shared->Arena, BuildOptions.SourceFiles);
ObjectFileNames.Count = RelativeSourceFilePaths.Count;
ObjectFileNames.Items = (str8*)ArenaPush(&WaveInfo->Shared->Arena, sizeof(str8)*RelativeSourceFilePaths.Count);
}
BroadcastToAllLanes(WaveInfo, RelativeSourceFilePaths);
BroadcastToAllLanes(WaveInfo, ObjectFileNames);
s64 Count = LaneValueFromTotal(WaveInfo, RelativeSourceFilePaths.Count);
s64 Index = WavePrefixSum(WaveInfo, Count);
b32 FailedToBuild = False;
for(int I = Index; I < Index + Count; I++)
{
u64 PerFileStart = TimeStampNow();
u64 Hash = HashSourceFileAndIncludes(Arena,
RelativeSourceFilePaths.Items[I],
BuildOptions.IncludePaths,
BuildOptions.Optimise,
BuildOptions.Compiler);
b32 FailedToCompile = CompileTranslationUnit(Arena,
&BuildOptions,
RelativeSourceFilePaths.Items[I],
Hash);
FailedToBuild |= FailedToCompile;
ObjectFileNames.Items[I] = Str8PushF(Arena, "%llu.o", Hash);
if(!FailedToCompile)
{
printf("Compiled %.*s in %.2fms\n",
Str8ToPrintfArgs(RelativeSourceFilePaths.Items[I]),
MilliSecondsFromTimeStamp(TimeStampNow() - PerFileStart));
}
}
b32 AnyFailedToBuild = WaveAnyTrue(WaveInfo, FailedToBuild);
if(WaveIsFirstLane(WaveInfo) && !AnyFailedToBuild)
{
printf("\nCompiled all TUs in %.2fms\n", MilliSecondsFromTimeStamp(TimeStampNow() - Start));
{
u64 LinkStart = TimeStampNow();
LinkObjectFiles(Arena, &BuildOptions, ObjectFileNames);
printf("Linking finished in %.2fms\n", MilliSecondsFromTimeStamp(TimeStampNow() - LinkStart));
}
printf(STR_COLOUR_GREEN "\nSUCCESS\n" STR_COLOUR_RESET);
}
if(WaveIsFirstLane(WaveInfo) && AnyFailedToBuild)
{
printf(STR_COLOUR_RED "\nERROR\n" STR_COLOUR_RESET);
}
}
str8 PathFromFilePath(memory_arena *Arena, str8 SourceFilePath)
{
return Str8UpToLastCharacter(Arena, SourceFilePath, '/');
}
str8 FileNameFromFilePath(memory_arena *Arena, str8 FilePath)
{
str8 Path = PathFromFilePath(Arena, FilePath);
str8 FileName = Str8PushF(Arena, "%.*s", Str8ToPrintfArgs(FilePath));
FileName.Bytes += Path.Size + 1;
FileName.Size -= Path.Size + 1;
return FileName;
}
str8 ForwardSlashPathFromPath(str8 Path)
{
for(s64 I = 0; I < Path.Size; I++)
{
if(Path.Bytes[I] == '\\')
{
Path.Bytes[I] = '/';
}
}
return Path;
}
void EmitCompileCommandsJson(memory_arena *Arena, build_options BuildOptions)
{
str8_list Output = {0};
Str8ListAppend(Arena, &Output, Str8("[\n"));
for(str8_list_node *SourceNode = BuildOptions.SourceFiles.First;
SourceNode != Null;
SourceNode = SourceNode->Next)
{
str8 FileName = ForwardSlashPathFromPath(SourceNode->String);
str8 ObjectFileName = Str8PushF(Arena, "%.*s.o",
Str8ToPrintfArgs(
Str8UpToLastCharacter(Arena,
FileName,
'.')));
Str8ListAppend(Arena, &Output, Str8(" {\n"));
Str8ListAppend(Arena, &Output, Str8PushF(Arena,
" \"directory\": \"%.*s\",\n",
Str8ToPrintfArgs(ForwardSlashPathFromPath(BuildOptions.WorkingDirectory))));
Str8ListAppend(Arena,
&Output,
Str8PushF(Arena,
" \"command\": \"%.*s\",\n",
Str8ToPrintfArgs(CompileCommandFromBuildOptions(Arena,
&BuildOptions,
ObjectFileName,
FileName))));
Str8ListAppend(Arena,
&Output,
Str8PushF(Arena,
" \"file\": \"%.*s\"\n",
Str8ToPrintfArgs(FileName)));
Str8ListAppend(Arena, &Output, Str8(" }"));
if(SourceNode->Next)
{
Str8ListAppend(Arena, &Output, Str8(",\n"));
}
else
{
Str8ListAppend(Arena, &Output, Str8("\n"));
}
}
Str8ListAppend(Arena, &Output, Str8("]\n"));
WriteStringToDisk(Arena, Str8("compile_commands.json"), Str8ListJoin(Arena, Output));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment