/* Copyright (c) 2019-2024 Griefer@Work                                       *
 *                                                                            *
 * This software is provided 'as-is', without any express or implied          *
 * warranty. In no event will the authors be held liable for any damages      *
 * arising from the use of this software.                                     *
 *                                                                            *
 * Permission is granted to anyone to use this software for any purpose,      *
 * including commercial applications, and to alter it and redistribute it     *
 * freely, subject to the following restrictions:                             *
 *                                                                            *
 * 1. The origin of this software must not be misrepresented; you must not    *
 *    claim that you wrote the original software. If you use this software    *
 *    in a product, an acknowledgement (see the following) in the product     *
 *    documentation is required:                                              *
 *    Portions Copyright (c) 2019-2024 Griefer@Work                           *
 * 2. Altered source versions must be plainly marked as such, and must not be *
 *    misrepresented as being the original software.                          *
 * 3. This notice may not be removed or altered from any source distribution. *
 */


BEGIN

	SET_NOSTDLIB()
	SET_SHARED()
	USELIB("libdl")
	STATIC_USELIBGCC()
	GCC_FLAGS({
		"-ffreestanding",

		/* Don't make assumptions based on __attribute__((nonnull)).
		 * We use the  attribute for functions  like `fwrite()'  and
		 * its  `stream' argument.  -- There  is no  valid reason to
		 * ever  call such  a function  with NULL,  but it's allowed
		 * and must be handled via `errno'.
		 *
		 * Thus, we must turn off optimizations that would remove
		 * the `stream == NULL' checks from builds, else we'd end
		 * up with a non-conforming library... */
		"-fno-delete-null-pointer-checks",

	})

	CC_FLAGS({
		/* Don't warn about us passing `(size_t)-1' to __attribute__((access)) functions. */
		"-Wno-stringop-overflow",
	})

	LD_FLAGS({
		/* Bind DT_INIT / DT_FINI */
		"-init=libc_init",
		"-fini=libc_fini",
		"--hash-style=gnu",

		/* Set program entry when libc gets exec'd */
		"-e", "libc_exec_main",
	})

	DEFINE({ "__BUILDING_LIBC" : "1" })


	BEGIN GROUP("libs.libc") { COMPILE };
		SOURCE({
			f"hybrid/arch/{XARCH}/*.c",
			f"libc/arch/{XARCH}/*.c",
			"libc/*.c",
			"hybrid/*.c",
			"auto/*.c",
			"user/*.c",
		})
#if 0 /* Enable this and run clang-tidy to test-compile libc/local headers. */
		for (local dir: fs.dir("kos/include/libc/local")) {
			SOURCE({ f"/kos/include/libc/local/{dir}/*.h" })
		}
#endif
	END
	BEGIN GROUP("libs.libc") { COMPILE };
		SET_LANGUAGE("assembler-with-cpp")
		SOURCE({
			"hybrid/arch/" + XARCH_ASMFILES,
			"libc/arch/" + XARCH_ASMFILES,
		})
	END

	local final LIBC_FEATURES_HEADER = f"/kos/include/{XARCH}-kos/crt-features/crt-kos{SUFFIX}.h";
	BEGIN GROUP("libs.libc") { LINK, ARCHIVE, MCOPY,
			enumerateSymbolsStep(
				elfBinary: f"{TARGET_BINPATH}{DISK_LIBPATH}libc.so",
				callback: [](self: BoundStep, symbols: {(Bytes, Bytes, Bytes, Bytes, Bytes)...}) {
					local symbols_set: {Bytes...} = HashSet();
					local final CRT_DLSUBST_GLOBALS = {
						"__peb", "environ", "_environ", "__environ",
						"__argc", "__argv", "_pgmptr",
						"program_invocation_name", "__progname_full",
						"program_invocation_short_name", "__progname",
					};
					for (local symbolName, none, symbolBind, symbolVisi, none, symbolSect: symbols) {
						if (symbolVisi != "DEFAULT")
							continue;
						if (symbolBind != "GLOBAL" && symbolBind != "WEAK")
							continue;
						local final LIBDL_SYMBOLS = {
							"dlopen", "dlfopen", "dlclose", "dlsym", "dlerror", "dlgethandle",
							"dlgetmodule", "dladdr", "dlmodulefd", "dlmodulename", "dlexceptaware",
							"dlmodulebase", "dllocksection", "dlunlocksection", "dlsectionname",
							"dlsectionindex", "dlsectionmodule", "dlclearcaches", "___tls_get_addr",
							"__tls_get_addr", "dltlsallocseg", "dltlsfreeseg", "dltlsalloc",
							"dltlsfree", "dltlsaddr", "dlauxctrl",
						};
						if (symbolSect == "UND") {
							if (symbolName in LIBDL_SYMBOLS)
								continue;
							if (symbolName !in CRT_DLSUBST_GLOBALS)
								print log: "WARNING: Unresolved symbol in libc:", repr str symbolName;
						} else if (symbolName.startswith("libc_")) {
							print log: "WARNING: Potentially unwanted libc export:", repr str symbolName;
						}
						symbols_set.insert(symbolName);
					}
					symbols_set.update(CRT_DLSUBST_GLOBALS);
					updateFileContents(self.group.fixFilename(LIBC_FEATURES_HEADER), [](fp: File){
						fp << COPYRIGHT << "\n\n\n";
						if (!TARGET.DL_HAVE_PE_COMPAT) {
							local dollarSymbols = [];
							for (local x: symbols_set.sorted()) {
								if ("$" in x) {
									dollarSymbols.append(x);
									continue;
								}
								fp << "#define __CRT_HAVE_" << x << "\n";
							}
							if (dollarSymbols) {
								fp << "#ifndef __COMPILER_NO_DOLLAR_IN_SYMBOL\n";
								for (local x: dollarSymbols)
									fp << "#define __CRT_HAVE_" << x << "\n";
								fp << "#endif /* !__COMPILER_NO_DOLLAR_IN_SYMBOL */\n";
							}
						} else {
							local dosSymbols = HashSet(); /* Symbols exposed with a DOS$ prefix */
							local dollarSymbols = [];
							for (local x: symbols_set.sorted()) {
								if (x.startswith("?")) {
									dollarSymbols.append(x.replace("?", "$Q").replace("@", "$A"));
								} else if (x.startswith("DOS$")) {
									dosSymbols.insert(x[4:]);
								} else if ("$" in x) {
									dollarSymbols.append(x);
								} else {
									fp << "#define __CRT_HAVE_" << x << "\n";
								}
							}
							if (dollarSymbols) {
								fp << "#ifndef __COMPILER_NO_DOLLAR_IN_SYMBOL\n";
								for (local x: dollarSymbols)
									fp << "#define __CRT_HAVE_" << x << "\n";
								fp << "#endif /* !__COMPILER_NO_DOLLAR_IN_SYMBOL */\n";
							}
							if (dosSymbols) {
								dosSymbols = dosSymbols.sorted();
								fp << "#ifdef __PE__\n";
								dollarSymbols = [];
								for (local x: dosSymbols) {
									/* Functions that only exist in DOS */
									if (x in symbols_set) {
										/* Hybrid symbol */
										dollarSymbols.append(x);
									} else {
										/* DOS-only symbol symbol */
										fp << "#define __CRT_HAVE_" << x << "\n";
									}
								}
								if (dollarSymbols) {
									fp << "#ifndef __COMPILER_NO_DOLLAR_IN_SYMBOL\n";
									for (local x: dollarSymbols)
										fp << "#define __CRT_HAVE_KOS$" << x << "\n";
									fp << "#endif /* !__COMPILER_NO_DOLLAR_IN_SYMBOL */\n";
								}
								fp << "#else /* __PE__ */\n";
								fp << "#ifndef __COMPILER_NO_DOLLAR_IN_SYMBOL\n";
								for (local x: dosSymbols)
									fp << "#define __CRT_HAVE_DOS$" << x << "\n";
								fp << "#endif /* !__COMPILER_NO_DOLLAR_IN_SYMBOL */\n";
								fp << "#endif /* !__PE__ */\n";
							}
						}
					});
				}
			).withMaybeModifiedOutputFiles({
				LIBC_FEATURES_HEADER,
			}),
		};
		SET_OUTPUT_LIBDLL("libc")
		LD_FLAGS({ "--gc-sections" })
		LINKER_SCRIPT(f"libc/_libc.ld")

		/* `libc' as a static library (link as `OBJECT("$OUTPATH/lib/libc.a")')
		 * NOTE: Don't forget to turn on `L_FLAGS("-Wl,--gc-sections")', to prevent
		 *       any  function  that you  didn't  use from  clobbering  your binary */
		SET_STATIC_OUTPUT(f"{TARGET_BINPATH}{DISK_LIBPATH}libc.a")

		SOURCE({
			f"hybrid/arch/{XARCH}/*.c",
			"hybrid/arch/" + XARCH_ASMFILES,
			"hybrid/*.c",
			f"libc/arch/{XARCH}/*.c",
			"libc/arch/" + XARCH_ASMFILES,
			"libc/*.c",
			"user/*.c",
			"auto/*.c",
		})
	END
END
