power_play/src/meta/meta.c
2025-09-04 16:33:51 -05:00

723 lines
26 KiB
C

/* TODO: Move decls to meta.h */
#define MetaRebuildCode 1317212284
////////////////////////////////
//~ Default base layer compiler definitions
#ifndef IsConsoleApp
# define IsConsoleApp 1
#endif
#ifndef RtcIsEnabled
# define RtcIsEnabled 1
#endif
#ifndef UnoptimizedIsEnabled
# define UnoptimizedIsEnabled 1
#endif
#ifndef AsanIsEnabled
# define AsanIsEnabled 0
#endif
#ifndef CrtlibIsEnabled
# define CrtlibIsEnabled 1
#endif
#ifndef DebinfoEnabled
# define DebinfoEnabled 1
#endif
#ifndef DeveloperIsEnabled
# define DeveloperIsEnabled 1
#endif
#ifndef ProfilingIsEnabled
# define ProfilingIsEnabled 0
#endif
#ifndef UnoptimizedIsEnabled
# define UnoptimizedIsEnabled 1
#endif
#ifndef TestsAreEnabled
# define TestsAreEnabled 0
#endif
////////////////////////////////
//~ Includes
//- Header files
#include "../base/base_inc.h"
#include "meta_os/meta_os_inc.h"
#include "meta_file/meta_file_inc.h"
#include "meta_lay.h"
#include "meta.h"
//- Source files
#include "meta_lay.c"
////////////////////////////////
//~ Util
/* TODO: Move this to OS layer */
void Echo(String msg)
{
HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (console_handle != INVALID_HANDLE_VALUE)
{
WriteFile(console_handle, msg.text, msg.len, 0, 0);
}
}
void EchoLine(String msg)
{
HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE);
if (console_handle != INVALID_HANDLE_VALUE)
{
WriteFile(console_handle, msg.text, msg.len, 0, 0);
WriteFile(console_handle, "\n", 1, 0, 0);
}
}
Struct(LineCol)
{
i64 line;
i64 col;
};
LineCol LineColFromPos(String data, i64 pos)
{
TempArena scratch = BeginScratchNoConflict();
LineCol result = ZI;
for (u64 cur = 0; cur < data.len && cur <= pos; ++cur)
{
u8 c = data.text[cur];
if (c == '\n')
{
++result.line;
result.col = 0;
}
else if (c != '\r')
{
++result.col;
}
}
result.line += 1;
EndScratch(scratch);
return result;
}
////////////////////////////////
//~ Build
void StartupMeta(void)
{
Arena *arena = AcquireArena(Gibi(64));
M_ErrorList errors = ZI;
i32 ret = 0;
////////////////////////////////
//~ Dirty check
//- Return rebuild code if metaprogram is dirty
{
/* Read old metahash */
u64 old_metahash = 0;
if (F_IsFile(Lit("metahash.dat")))
{
String hashes_str = F_DataFromFile(arena, Lit("metahash.dat"));
if (hashes_str.len == sizeof(old_metahash))
{
CopyBytes(&old_metahash, hashes_str.text, sizeof(old_metahash));
}
OS_Rm(Lit("metahash.dat"));
}
/* Calculate new metahash */
u64 new_metahash = 0;
{
StringList check_files = ZI;
F_FilesFromDir(arena, &check_files, Lit("../src/prof"), F_IterFlag_Recurse);
F_FilesFromDir(arena, &check_files, Lit("../src/base"), F_IterFlag_Recurse);
F_FilesFromDir(arena, &check_files, Lit("../src/meta"), F_IterFlag_Recurse);
PushStringToList(arena, &check_files, Lit("../src/config.h"));
for (StringListNode *n = check_files.first; n; n = n->next)
{
String file = n->s;
new_metahash = RandU64FromSeeds(HashFnv64(new_metahash, file), OS_LastWriteTimestampFromPath(file));
}
}
/* Exit if metaprogram needs recompilation */
if (old_metahash == 0 || old_metahash == new_metahash)
{
F_ClearWrite(Lit("metahash.dat"), StringFromStruct(&new_metahash));
}
else
{
EchoLine(Lit("Metaprogram is dirty"));
ExitNow(MetaRebuildCode);
}
}
////////////////////////////////
//~ Args
//- Unpack args
StringList args = GetCommandLineArgs();
for (StringListNode *n = args.first; n; n = n->next)
{
String arg = n->s;
}
//- Generate compiler flags
StringList compiler_defs = ZI;
StringList warning_flags_msvc = ZI;
StringList warning_flags_clang = ZI;
StringList flags_msvc = ZI;
StringList flags_clang = ZI;
StringList compiler_flags_msvc = ZI;
StringList compiler_flags_clang = ZI;
StringList linker_flags_msvc = ZI;
StringList linker_flags_clang = ZI;
{
//- Shared definitions
PushStringToList(arena, &compiler_defs, Lit("-DIsConsoleApp=0"));
PushStringToList(arena, &compiler_defs, Lit("-DRtcIsEnabled=1"));
PushStringToList(arena, &compiler_defs, Lit("-DAsanIsEnabled=0"));
PushStringToList(arena, &compiler_defs, Lit("-DCrtlibIsEnabled=1"));
PushStringToList(arena, &compiler_defs, Lit("-DDebinfoEnabled=1"));
PushStringToList(arena, &compiler_defs, Lit("-DDeveloperIsEnabled=1"));
PushStringToList(arena, &compiler_defs, Lit("-DProfilingIsEnabled=0"));
PushStringToList(arena, &compiler_defs, Lit("-DUnoptimizedIsEnabled=1"));
PushStringToList(arena, &compiler_defs, Lit("-DTestsAreEnabled=0"));
//- Msvc
{
PushStringToList(arena, &compiler_flags_msvc, Lit("-diagnostics:column"));
PushStringToList(arena, &flags_msvc, Lit("-INCREMENTAL:NO"));
PushStringToList(arena, &flags_msvc, Lit("-nologo"));
/* Debug info */
PushStringToList(arena, &flags_msvc, Lit("-DEBUG:FULL"));
PushStringToList(arena, &compiler_flags_msvc, Lit("-Z7"));
/* Enable warnings */
PushStringToList(arena, &warning_flags_msvc, Lit("-W4"));
PushStringToList(arena, &warning_flags_msvc, Lit("-WX"));
// PushStringToList(arena, &warning_flags_msvc, Lit("-we4013")); /* function undefined; assuming extern returning int */
/* Disable warnings */
PushStringToList(arena, &warning_flags_msvc, Lit("-wd4244")); /* 'function': conversion from 'int' to 'f32', possible loss of data */
PushStringToList(arena, &warning_flags_msvc, Lit("-wd4201")); /* nonstandard extension used: nameless struct/union */
PushStringToList(arena, &warning_flags_msvc, Lit("-wd4324")); /* structure was padded due to alignment specifier */
PushStringToList(arena, &warning_flags_msvc, Lit("-wd4100")); /* unreferenced parameter */
PushStringToList(arena, &warning_flags_msvc, Lit("-wd4101")); /* unreferenced local variable */
PushStringToList(arena, &warning_flags_msvc, Lit("-wd4189")); /* local variable is initialized but not referenced */
PushStringToList(arena, &warning_flags_msvc, Lit("-wd4200")); /* nonstandard extension used: zero-sized array in struct/union */
}
//- Clang
{
PushStringToList(arena, &flags_clang, Lit("-std=c99"));
PushStringToList(arena, &flags_clang, Lit("-fno-finite-loops"));
PushStringToList(arena, &flags_clang, Lit("-fno-strict-aliasing"));
PushStringToList(arena, &flags_clang, Lit("-g -gcodeview"));
PushStringToList(arena, &flags_clang, Lit("-O0"));
PushStringToList(arena, &flags_clang, Lit("-msse4.2"));
/* Enable warnings */
PushStringToList(arena, &warning_flags_clang, Lit("-Wall"));
PushStringToList(arena, &warning_flags_clang, Lit("-Werror"));
PushStringToList(arena, &warning_flags_clang, Lit("-Wframe-larger-than=65536"));
PushStringToList(arena, &warning_flags_clang, Lit("-Wmissing-prototypes"));
PushStringToList(arena, &warning_flags_clang, Lit("-Wmissing-declarations"));
PushStringToList(arena, &warning_flags_clang, Lit("-Wunused-variable"));
PushStringToList(arena, &warning_flags_clang, Lit("-Wunused-but-set-variable"));
PushStringToList(arena, &warning_flags_clang, Lit("-Wunused-parameter"));
PushStringToList(arena, &warning_flags_clang, Lit("-Wimplicit-fallthrough"));
PushStringToList(arena, &warning_flags_clang, Lit("-Wswitch"));
/* Disable warnings */
PushStringToList(arena, &warning_flags_clang, Lit("-Wno-initializer-overrides"));
PushStringToList(arena, &warning_flags_clang, Lit("-Wno-microsoft-enum-forward-reference"));
}
}
////////////////////////////////
//~ Parse
//- Lex layers
M_TokenFileList lexed = ZI;
{
StringList src_dirs = ZI;
PushStringToList(arena, &src_dirs, Lit("../src"));
lexed = M_TokensFromSrcDirs(arena, src_dirs);
}
//- Parse layers
M_LayerList parsed = ZI;
{
parsed = M_LayersFromTokenFiles(arena, lexed);
}
//- Flatten layers
M_Layer flattened = ZI;
{
StringList starting_layer_names = ZI;
PushStringToList(arena, &starting_layer_names, Lit("pp"));
flattened = M_GetFlattenedEntries(arena, parsed, starting_layer_names);
}
M_AppendErrors(arena, &errors, flattened.errors);
//- Process flattened layers
StringList c_include_lines = ZI;
StringList gpu_include_lines = ZI;
StringList c_startup_lines = ZI;
StringList c_store_lines = ZI;
Struct(EmbeddedDir) { EmbeddedDir *next; String store_name; String dir_name; };
EmbeddedDir *first_dir_embed = 0;
EmbeddedDir *last_dir_embed = 0;
u64 num_dir_embeds = 0;
if (errors.count <= 0)
{
for (M_Entry *entry = flattened.first; entry->valid; entry = entry->next)
{
M_EntryKind kind = entry->kind;
M_Token *entry_tok = entry->name_token;
M_Token *arg0_tok = entry->arg_tokens[0];
M_Token *arg1_tok = entry->arg_tokens[1];
switch(kind)
{
case M_EntryKind_Unknown:
{
M_PushError(arena, &errors, entry_tok, StringF(arena, "Unknown entry kind \"%F\"", FmtString(entry_tok->s)));
} break;
case M_EntryKind_IncludeC:
case M_EntryKind_IncludeGpu:
{
if (arg0_tok->valid)
{
String token_file = arg0_tok->file->name;
String token_parent_dir = F_GetParentDir(token_file);
String arg_file = arg0_tok->s;
String full = F_GetFull(arena, StringF(arena, "%F/%F", FmtString(token_parent_dir), FmtString(arg_file)));
if (F_IsFile(full))
{
String line = StringF(arena, "#include \"%F\"", FmtString(full));
if (kind == M_EntryKind_IncludeC)
{
PushStringToList(arena, &c_include_lines, line);
}
else
{
PushStringToList(arena, &gpu_include_lines, line);
}
}
else
{
String err = StringF(arena, "File '%F' not found", FmtString(full));
M_PushError(arena, &errors, arg0_tok, err);
}
}
else
{
M_PushError(arena, &errors, entry_tok, Lit("Expected file name"));
}
} break;
case M_EntryKind_Startup:
{
if (arg0_tok->valid)
{
String startup = arg0_tok->s;
String line = StringF(arena, " %F();", FmtString(startup));
PushStringToList(arena, &c_startup_lines, line);
}
else
{
M_PushError(arena, &errors, entry_tok, Lit("Expected startup function name"));
}
} break;
case M_EntryKind_EmbedDir:
{
if (arg0_tok->valid && arg1_tok->valid)
{
String store_name = arg0_tok->s;
String token_file = arg1_tok->file->name;
String token_parent_dir = F_GetParentDir(token_file);
String arg_dir = arg1_tok->s;
String full = F_GetFullCrossPlatform(arena, StringF(arena, "%F/%F", FmtString(token_parent_dir), FmtString(arg_dir)));
if (F_IsDir(full))
{
u64 hash = HashFnv64(Fnv64Basis, StringF(arena, "%F/", FmtString(store_name)));
String line = StringF(arena, "ResourceStore %F = { 0x%F };", FmtString(store_name), FmtHex(hash));
PushStringToList(arena, &c_store_lines, line);
EmbeddedDir *ed = PushStruct(arena, EmbeddedDir);
ed->store_name = store_name;
ed->dir_name = full;
QueuePush(first_dir_embed, last_dir_embed, ed);
++num_dir_embeds;
}
else
{
String err = StringF(arena, "Directory '%F' not found", FmtString(full));
M_PushError(arena, &errors, arg1_tok, err);
}
}
else
{
M_PushError(arena, &errors, entry_tok, Lit("Expected resource store & directory name"));
}
} break;
}
}
}
//- Echo meta errors
if (ret == 0)
{
ret = errors.count > 0;
}
for (M_Error *e = errors.first; e; e = e->next)
{
String msg = ZI;
M_Token *token = e->token;
String token_file = token->file->name;
String token_file_data = token->file->data;
if (token_file.len > 0)
{
i64 token_pos = -1;
if (token->s.len > 0
&& token_file_data.len > 0
&& token->s.text > token_file_data.text
&& token->s.text < (token_file_data.text + token_file_data.len))
{
token_pos = token->s.text - token_file_data.text;
}
LineCol line_col = ZI;
if (token_pos >= 0)
{
line_col = LineColFromPos(token_file_data, token_pos);
}
msg = StringF(arena,
"%F:%F:%F: error: %F",
FmtString(token_file),
FmtSint(line_col.line),
FmtSint(line_col.col),
FmtString(e->msg));
}
else
{
msg = StringF(arena, "error: %F", FmtString(e->msg));
}
EchoLine(msg);
}
////////////////////////////////
//~ Generate
//- Generate C file
String c_out_file = F_GetFull(arena, Lit("pp_gen.c"));
if (errors.count <= 0)
{
StringList c_out_lines = ZI;
PushStringToList(arena, &c_out_lines, Lit("// Auto generated file"));
/* Include ase layer */
{
String base_inc_path = F_GetFull(arena, Lit("../src/base/base_inc.h"));
PushStringToList(arena, &c_out_lines, Lit(""));
PushStringToList(arena, &c_out_lines, Lit("//- Base layer includes"));
String line = StringF(arena, "#include \"%F\"", FmtString(base_inc_path));
PushStringToList(arena, &c_out_lines, line);
}
/* Include dependency layers */
if (c_out_lines.count > 0)
{
PushStringToList(arena, &c_out_lines, Lit(""));
PushStringToList(arena, &c_out_lines, Lit("//- Dependency includes"));
for (StringListNode *n = c_include_lines.first; n; n = n->next)
{
PushStringToList(arena, &c_out_lines, n->s);
}
}
/* Define resource stores */
if (c_store_lines.count > 0)
{
PushStringToList(arena, &c_out_lines, Lit(""));
PushStringToList(arena, &c_out_lines, Lit("//- Resource stores"));
for (StringListNode *n = c_store_lines.first; n; n = n->next)
{
PushStringToList(arena, &c_out_lines, n->s);
}
}
/* Define StartupLayers */
{
PushStringToList(arena, &c_out_lines, Lit(""));
PushStringToList(arena, &c_out_lines, Lit("//- Startup"));
PushStringToList(arena, &c_out_lines, Lit("void StartupLayers(void)"));
PushStringToList(arena, &c_out_lines, Lit("{"));
for (StringListNode *n = c_startup_lines.first; n; n = n->next)
{
PushStringToList(arena, &c_out_lines, n->s);
}
PushStringToList(arena, &c_out_lines, Lit("}"));
PushStringToList(arena, &c_out_lines, Lit(""));
}
/* Write to file */
String c_out = StringFromList(arena, c_out_lines, Lit("\n"));
F_ClearWrite(c_out_file, c_out);
}
//- Generate archive file
String arc_out_file = F_GetFull(arena, Lit("resources.arc"));
if (ret == 0)
{
EchoLine(Lit("[Creating archive]"));
Struct(EntryNode)
{
EntryNode *next;
String entry_name;
String file_name;
};
EntryNode *first_entry = 0;
EntryNode *last_entry = 0;
u64 entries_count = 0;
for (EmbeddedDir *ed = first_dir_embed; ed; ed = ed->next)
{
String store_name = ed->store_name;
String embed_dir = ed->dir_name;
StringList files = ZI;
F_FilesFromDir(arena, &files, embed_dir, F_IterFlag_Recurse);
for (StringListNode *file_node = files.first; file_node; file_node = file_node->next)
{
String file_name = file_node->s;
if (F_IsFile(file_name))
{
String entry_name = file_name;
if (entry_name.len > (embed_dir.len + 1))
{
entry_name.len -= embed_dir.len + 1;
entry_name.text += embed_dir.len + 1;
}
entry_name = StringF(arena, "%F/%F", FmtString(store_name), FmtString(entry_name));
for (u64 i = 0; i < entry_name.len; ++i)
{
if (entry_name.text[i] == '\\')
{
entry_name.text[i] = '/';
}
}
EntryNode *en = PushStruct(arena, EntryNode);
en->entry_name = entry_name;
en->file_name = file_name;
QueuePush(first_entry, last_entry, en);
++entries_count;
}
}
}
BB_Buff bb = BB_AcquireBuff(Gibi(64));
BB_Writer bw = BB_WriterFromBuff(&bb);
/* Write magic */
BB_WriteUBits(&bw, ResourceEmbeddedMagic, 64);
/* Write header */
BB_WriteUBits(&bw, entries_count, 64);
/* Reserve entries space */
u64 entry_size = 8 /* Name start */
+ 8 /* Name end */
+ 8 /* Data start */
+ 8; /* Data end */
u8 *entries_start = BB_GetWrittenRaw(&bw) + BB_GetNumBytesWritten(&bw);
u64 entries_size = entry_size * entries_count;
String entries_str = STRING(entries_size, entries_start);
BB_WriteSeekBytes(&bw, entries_size);
BB_Buff entries_bb = BB_BuffFromString(entries_str);
BB_Writer entries_bw = BB_WriterFromBuff(&entries_bb);
/* Write entries */
for (EntryNode *en = first_entry; en; en = en->next)
{
/* TODO: Copy file data directly into archive file */
String file_data = F_DataFromFile(arena, en->file_name);
/* Write name */
BB_WriteAlignByte(&bw, 64);
u64 name_start = BB_GetNumBytesWritten(&bw) + 1;
BB_WriteString(&bw, en->entry_name);
u64 name_len = BB_GetNumBytesWritten(&bw) - name_start;
/* Write data */
BB_WriteAlignByte(&bw, 64);
/* FIXME: Why no +1 here? */
u64 data_start = BB_GetNumBytesWritten(&bw);
BB_WriteBytes(&bw, file_data);
u64 data_len = BB_GetNumBytesWritten(&bw) - data_start;
/* Write entry */
BB_WriteUBits(&entries_bw, name_start, 64);
BB_WriteUBits(&entries_bw, name_len, 64);
BB_WriteUBits(&entries_bw, data_start, 64);
BB_WriteUBits(&entries_bw, data_len, 64);
}
/* Write to file */
String arc_out = ZI;
arc_out.len = BB_GetNumBytesWritten(&bw);
arc_out.text = BB_GetWrittenRaw(&bw);
F_ClearWrite(arc_out_file, arc_out);
EchoLine(StringF(arena, "Built %F (%F mb)", FmtString(F_GetFileName(arc_out_file)), FmtFloatP((f32)arc_out.len / 1024 / 1024, 3)));
}
//- Generate rc file
String rc_out_file = F_GetFull(arena, Lit("resources.rc"));
{
StringList rc_out_lines = ZI;
String arc_file_cp = F_GetFullCrossPlatform(arena, arc_out_file);
String line = StringF(arena, "%F RCDATA \"%F\"", FmtString(Lit(Stringize(W32_EmbeddedDataName))), FmtString(arc_file_cp));
PushStringToList(arena, &rc_out_lines, line);
/* Write to file */
String rc_out = StringFromList(arena, rc_out_lines, Lit("\n"));
F_ClearWrite(rc_out_file, rc_out);
}
////////////////////////////////
//~ Compile
//- Compile RC
String res_out_file = F_GetFull(arena, Lit("resources.res"));
if (ret == 0)
{
EchoLine(Lit("[Compiling RC]"));
String cmd = StringF(arena, "cmd /c rc.exe -nologo -fo %F %F", FmtString(res_out_file), FmtString(rc_out_file));
OS_CommandResult result = OS_RunCommand(arena, cmd);
String output = TrimWhitespace(result.output);
EchoLine(F_GetFileName(rc_out_file));
if (output.len > 0)
{
EchoLine(output);
}
ret = result.code;
}
//- Compile C
String c_out_obj_file = Lit("pp_gen.obj");
if (ret == 0)
{
EchoLine(Lit("[Compiling C]"));
String cmd = StringF(arena,
"cmd /c cl.exe /c %F -Fo:%F %F %F %F %F",
FmtString(c_out_file),
FmtString(c_out_obj_file),
FmtString(StringFromList(arena, flags_msvc, Lit(" "))),
FmtString(StringFromList(arena, compiler_flags_msvc, Lit(" "))),
FmtString(StringFromList(arena, warning_flags_msvc, Lit(" "))),
FmtString(StringFromList(arena, compiler_defs, Lit(" "))));
OS_CommandResult result = OS_RunCommand(arena, cmd);
String output = TrimWhitespace(result.output);
EchoLine(output);
ret = result.code;
}
////////////////////////////////
//~ Link
//- Link
String exe_file = Lit("pp.exe");
if (ret == 0)
{
EchoLine(Lit("[Linking]"));
String cmd = StringF(arena,
"cmd /c link.exe %F %F /OUT:%F %F %F",
FmtString(c_out_obj_file),
FmtString(res_out_file),
FmtString(exe_file),
FmtString(StringFromList(arena, flags_msvc, Lit(" "))),
FmtString(StringFromList(arena, linker_flags_msvc, Lit(" "))));
OS_CommandResult result = OS_RunCommand(arena, cmd);
String output = TrimWhitespace(result.output);
if (output.len > 0)
{
EchoLine(output);
}
ret = result.code;
}
#if 0
//- Generate compiler cmds
String msvc_cmd_str = ZI;
String clang_cmd_str = ZI;
/* Msvc */
{
String compiler_str = StringF(arena,
"%F %F %F %F",
FmtString(StringFromList(arena, flags_msvc, Lit(" "))),
FmtString(StringFromList(arena, compiler_flags_msvc, Lit(" "))),
FmtString(StringFromList(arena, warning_flags_msvc, Lit(" "))),
FmtString(StringFromList(arena, compiler_defs, Lit(" "))));
String linker_str = StringF(arena,
"%F %F",
FmtString(StringFromList(arena, flags_msvc, Lit(" "))),
FmtString(StringFromList(arena, linker_flags_msvc, Lit(" "))));
msvc_cmd_str = StringF(arena, "\"cl\" %F %F -link %F", FmtString(c_out_file), FmtString(compiler_str), FmtString(linker_str));
}
/* Clang */
{
String compiler_str = StringF(arena,
"%F %F %F %F",
FmtString(StringFromList(arena, flags_clang, Lit(" "))),
FmtString(StringFromList(arena, compiler_flags_clang, Lit(" "))),
FmtString(StringFromList(arena, warning_flags_clang, Lit(" "))),
FmtString(StringFromList(arena, compiler_defs, Lit(" "))));
clang_cmd_str = StringF(arena, "\"clang\" %F %F", FmtString(c_out_file), FmtString(compiler_str));
}
//- Compile C
if (ret == 0) {
EchoLine(Lit("Compiling"));
String cmd_str = msvc_cmd_str;
// String cmd_str = clang_cmd_str;
OS_CommandResult result = OS_RunCommand(arena, cmd_str);
String output = result.output;
output = Trim(output, Lit("\n"));
output = Trim(output, Lit("\r"));
output = Trim(output, Lit("\n"));
EchoLine(output);
if (result.code == 0)
{
EchoLine(Lit("Compilation succeeded"));
}
else
{
EchoLine(Lit("Compilation failed"));
}
ret = result.code;
}
#endif
ExitNow(ret);
}
////////////////////////////////
//~ @hookdef Startup
void StartupLayers(void)
{
OS_Startup();
StartupMeta();
}