From f56fad47af4e1b6f1c7684387be5ce3f9095ff65 Mon Sep 17 00:00:00 2001 From: Rune Date: Wed, 11 Mar 2026 19:50:31 +0100 Subject: [PATCH] step towards c++ --- CxxBuilder/src/Program.bf | 152 ++++++++++++++++++++++++--- src/ClangC.bf | 2 +- src/Generator.bf | 213 ++++++++------------------------------ 3 files changed, 184 insertions(+), 183 deletions(-) diff --git a/CxxBuilder/src/Program.bf b/CxxBuilder/src/Program.bf index 883c746..1affc07 100644 --- a/CxxBuilder/src/Program.bf +++ b/CxxBuilder/src/Program.bf @@ -11,6 +11,12 @@ static class Program public static int Main(String[] args) { + if (args.Count == 3 && args[0] == "---__defsyms__") + { + GenerateAliases(args); + return 0; + } + mixin PrintUsage() { Console.WriteLine(""" @@ -19,8 +25,9 @@ static class Program Usage: $(Var CxxBuilderExe) [] -- src=<> target=<> config=<> builddir=<> output=<> [cflags=<>] Example: $(Var CxxBuilderExe) **.c vk_*.cpp -- "src=$(ProjectDir)/MyLib/src" target=$(TargetTriple) config=$(Configuration) "builddir=$(BuildDir)" output=MyLib cflags=-Isome/dir - --template - TODO --cmake - builds $src/CMakeLists.txt to build, patterns and output are ignored + ! --template - TODO + ! --cpp-aliases - creates aliases from the mangled c++ symbols to the symbols queried by Cpp2Beef-generated c++ bindigs $src - The source dir, all patterns are rooted here $target - the llvm target triple passed to clang @@ -36,27 +43,36 @@ static class Program { if (!condition) { - Console.WriteLine(str); + Console.Error.WriteLine(str); return 1; } } enum { Default, CMake, Template } mode = .Default; + enum { None, CppAliases } actions = .None; StringView src = null, target = null, config = null, builddir = null, output = null, cflags = null; var iter = args.GetEnumerator(); skipPatterns: do { for (let arg in iter) { - if (arg == "--") break skipPatterns; - if (arg == "--cmake") + if (!arg.StartsWith("--")) continue; + switch (arg) { + case "--": break skipPatterns; + case "--cmake": Assert!(mode == .Default, "Conflicting options"); mode = .CMake; + /*case "--cpp-aliases": + actions |= .CppAliases;*/ + default: + Console.Error.WriteLine($"Invalid option {_}"); + return 1; } } PrintUsage!(); } + Assert!(!(mode == .CMake && actions.HasFlag(.CppAliases)), "Comflicting options --cmake and --cpp-aliases"); for (let arg in iter) { var parts = arg.Split('=', 2); @@ -84,7 +100,7 @@ static class Program Assert!(!target.IsNull, "Missing var 'target'"); Assert!(!config.IsNull, "Missing var 'config'"); Assert!(!builddir.IsNull, "Missing var 'builddir'"); - Assert!(!output.IsNull || mode == .CMake, "Missing var 'output'"); + Assert!(!output.IsNull || (mode == .CMake && !actions.HasFlag(.CppAliases)), "Missing var 'output'"); switch (mode) { @@ -104,14 +120,14 @@ static class Program WriteVarPath("builddir", builddir); buffer.AppendF($""" target = {target} - cflags = {(config == "Release") ? "-O2" : "-O2 -g"} {cflags} + cflags = {(config == "Release") ? "-O3" : "-O2 -g"} {cflags} """); writer.Write(buffer); } writer.Write(""" \n cc = clang - ar = ar + ar = llvm-ar rule cc command = $cc $cflags -target $target -MD -MF $out.d -c -o $out $in @@ -126,7 +142,8 @@ static class Program """); String ar = scope .(1024); - ar.AppendF($"\nbuild $builddir/{output}.{target.Contains("windows") ? "lib" : "a"}: ar"); + StringView libExtension = target.Contains("windows") ? "lib" : "a"; + ar.AppendF($"\nbuild $builddir/{output}.{libExtension}: ar"); int pcount = 0; for (let arg in args) @@ -148,11 +165,45 @@ static class Program let abssrc = Path.GetAbsolutePath(src, Directory.GetCurrentDirectory(..scope .(128)), ..scope .(128)); if (FileMatcher.HandleMatches(patterns, abssrc, scope => HandleMatch) case .Err(let err)) { - Console.WriteLine($"Syntax Error in Pattern: {err}"); + Console.Error.WriteLine($"Syntax Error in Pattern: {err}"); return 1; } writer.WriteLine(ar); + + if (actions.HasFlag(.CppAliases)) + { + const String exec = .Empty +#if BF_PLATFORM_WINDOWS + + "cmd /c "; +#endif + writer.Write($""" + + nm = llvm-nm + ld = {exec}ld + + rule nm + command = {exec}$nm --demangle $in > $out + description = Exporting symbols from $in + + rule generate_aliases + command = "{Environment.GetExecutableFilePath(..scope .(64))}" ---__defsyms__ $in $out + description = Generating aliases + + rule ld_aliases + command = $ld -r -o $out @$in "$builddir/{output}.{libExtension}" + description = Creating aliases + + rule ar_add + command = $ar qs $builddir/{output}.{libExtension} $in + + build $builddir/_symbols__.txt: nm $builddir/{output}.{libExtension} + build $builddir/_aliases__.txt: generate_aliases $builddir/_symbols__.txt + build $builddir/_aliases__.o: ld_aliases $builddir/_aliases__.txt + build add_to_output: ar_add $builddir/_aliases__.o + + """); + } case .CMake: #if !DEBUG if (File.Exists(scope $"{builddir}/build.ninja")) @@ -167,9 +218,84 @@ static class Program case .Template: //TODO } - let ret = system(scope $"ninja -C {builddir}"); - if (ret != 0) - Console.WriteLine($"Failed to build {output}"); - return ret; + var ret = system(scope $"ninja -C {builddir}"); + mixin CheckRetCode(StringView fmt) + { + if (ret != 0) + { + Console.Error.WriteLine(fmt, output); + return ret; + } + } + CheckRetCode!("Failed to build {}"); + + /*if (actions.HasFlag(.CppAliases)) + { + let aliasesDir = scope $"{builddir}/cpp2beef_aliases"; + Directory.CreateDirectory(aliasesDir); + { + StreamWriter writer = scope .()..Create(scope $"{aliasesDir}/aliases.c"); + ret = system(scope $"llvm-nm --export-symbols \"{builddir}/{output}.{target.Contains("windows") ? "lib" : "a"}\" > {aliasesDir}/mangled.txt"); + CheckRetCode!("Failed to fetch symbols from {}"); + ret = system(scope $"cat {aliasesDir}/mangled.txt | llvm-undname >{aliasesDir}/demangled.txt 2>{ +#if BF_PLATFORM_WINDOWS + ("nul") +#else + ("/dev/null") +#endif + }"); + CheckRetCode!("Failed to demangle symbols from {}"); + var mangled = scope StreamReader()..Open(scope $"{aliasesDir}/magled.txt").Lines; + var demangled = scope StreamReader()..Open(scope $"{aliasesDir}/demangled.txt").Lines; + for (let dline in demangled) + { + if (dline->IsEmpty) continue; + let mline = mangled.GetNext().Value; + if (mline == dline) continue; + String identifier = scope .(dline); + for (let i < identifier.Length) + if (!identifier[i].IsLetterOrDigit) + identifier[i] = '_'; + writer.Write($"extern void {identifier}() __attribute__((weak, alias(\"{mline.Value}\")));\n"); + } + } + File.WriteAllText(scope $"{aliasesDir}/build.ninja", scope $""" + rule cc + command = clang -x c -target {target} -c -o $out $in + description = Compiling aliases for {output} + + build aliases.o: cc aliases.c + + """); + ret = system(scope $"ninja -C {aliasesDir}"); + CheckRetCode!("Failed to build aliases for {}"); + }*/ + + return 0; + } + + static void GenerateAliases(String[] args) + { + StreamReader reader = scope .()..Open(args[1]); + StreamWriter writer = scope .()..Create(args[2]); + String str = scope .(256); + for (var line in reader.Lines) + { + line->Trim(); + if (line->IsEmpty || line->EndsWith(':')) continue; + var split = line->Split(' ', 3); + if (split.GetNext().Value == "U") continue; + int addr = int.Parse(split.Current, .Hex); + let kind = split.GetNext().Value; + if (kind != "T" || addr == 0) continue; + let sym = split.GetNext().Value; + if (!str.IsEmpty) str..Clear()..Append(' '); + str.Append("--defsym="); + for (let i < sym.Length) + str.Append(sym[i].IsLetterOrDigit ? sym[i] : '_'); + str.Append('='); + addr.ToString(str); + writer.Write(str); + } } } \ No newline at end of file diff --git a/src/ClangC.bf b/src/ClangC.bf index 2473d52..6547d0a 100644 --- a/src/ClangC.bf +++ b/src/ClangC.bf @@ -164,7 +164,7 @@ static class Program } Directory.CreateDirectory("clang-c"); - generator.Generate("clang-c.h", null); + generator.Generate("clang-c.h"); return 0; } diff --git a/src/Generator.bf b/src/Generator.bf index 7a013ed..d609505 100644 --- a/src/Generator.bf +++ b/src/Generator.bf @@ -64,6 +64,7 @@ abstract class Cpp2BeefGenerator }, &attrs); if (Clang.GetCursorAvailability(cursor) == .Deprecated) str.Append("[Obsolete] "); + //if (Clang.Cursor_IsFunctionInlined(cursor) != 0) str.Append("[Inline] "); if (cursor.kind == .EnumDecl) str.Append("[AllowDuplicates] "); else if (attrs.HasFlag(.Packed)) str.Append("[Packed] "); if (attrs.HasFlag(.NoDiscard)) str.Append("[NoDiscard] "); @@ -197,13 +198,10 @@ abstract class Cpp2BeefGenerator return false; } - protected virtual void ModifyWrapperPrintingPolicy(CXPrintingPolicy policy) {} - protected CXIndex index = Clang.CreateIndex(excludeDeclarationsFromPCH: 0, displayDiagnostics: 1) ~ Clang.DisposeIndex(_); protected CXTranslationUnit unit; protected append String str = .(1024); - protected append String wrapperBuf = .(1024); protected CXCursor currentCursor = Clang.GetNullCursor(); protected append String cursorIndent = .(16); @@ -213,8 +211,6 @@ abstract class Cpp2BeefGenerator protected CXPrintingPolicy printingPolicy; private StreamWriter currentWritter; - private StreamWriter wrapperWritter; - protected StringView WrapperFilePath; private append String wrapperTemplateChain = .(16); private append String templateParams = .(16); @@ -229,7 +225,7 @@ abstract class Cpp2BeefGenerator protected class FileInfo : this(CXSourceLocation prevEnd, CXFile file) { public append String block = .(64); - public enum { None = 0, LSquirly = 1, RSquirly = 0b10 } queuedTokens = .None; + public enum { None = 0, LSquirly = 1, RSquirly = _<<1, Semicolon = _<<1 } queuedTokens = .None; public (CXType type, int32 curWidth) bitfield = default; } protected FileInfo fileInfo = null; @@ -279,40 +275,12 @@ abstract class Cpp2BeefGenerator ParsingFailed } - public Result Generate(char8* headerPath, StringView wrapperPath = null) + public Result Generate(char8* headerPath) { unitMacros.Clear(); fileInfos.Clear(); lastRecordOrEnum = Clang.GetNullCursor(); - if (!wrapperPath.IsNull) - { - wrapperWritter = scope:: .(); - wrapperWritter.Create(wrapperPath); - WrapperFilePath = wrapperPath; - wrapperBuf.Set(""" - #define private public - #define protected public - - #include " - """); - Path.GetFileName(.(headerPath), wrapperBuf); - wrapperBuf.Append(""" - " - #include - - template - using __type = T; - - extern "C" - { - - """); - FlushWrapper(); - } - else - wrapperWritter = null; - let args = Args; #if DEBUG findLang: do @@ -331,7 +299,7 @@ abstract class Cpp2BeefGenerator if (unit == default) return .Err(.ParsingFailed); printingPolicy = Clang.GetCursorPrintingPolicy(Clang.GetTranslationUnitCursor(unit)); - ModifyWrapperPrintingPolicy(printingPolicy); + Clang.PrintingPolicy_SetProperty(printingPolicy, .TerseOutput, 1); defer Clang.PrintingPolicy_Dispose(printingPolicy); PreGeneration(); @@ -371,9 +339,6 @@ abstract class Cpp2BeefGenerator }, Internal.UnsafeCastToPtr(this)); PostGeneration(); - if (wrapperBuf.IsEmpty) - wrapperWritter.Write("}\n\n//begin-comptime\n"); - for (let kv in fileInfos) { fileInfo = kv.value; @@ -462,11 +427,6 @@ abstract class Cpp2BeefGenerator currentWritter.Write(str); str.Clear(); } - protected void FlushWrapper() - { - wrapperWritter.Write(wrapperBuf); - wrapperBuf.Clear(); - } protected virtual void GetIndentation(CXSourceLocation location, String outString) { @@ -627,6 +587,8 @@ abstract class Cpp2BeefGenerator QueuedToken!(decltype(fileInfo.queuedTokens).LSquirly, "{"); QueuedToken!(decltype(fileInfo.queuedTokens).RSquirly, "}"); + if (!fileInfo.queuedTokens.HasFlag(.RSquirly)) + QueuedToken!(decltype(fileInfo.queuedTokens).Semicolon, ";"); if (!isWritingQueuedToken) continue; if (fileInfo.queuedTokens == .None) block = @block; @@ -866,22 +828,18 @@ abstract class Cpp2BeefGenerator } } - const int int_maxDigits = scope $"{int.MaxValue:X}".Length + 1; - protected virtual void CppWrapperName(CXCursor cursor, String outString) - { - String hashCode = ScopeCXString!(Clang.GetCursorUSR(cursor)) - .GetHashCode().ToString(..scope .(int_maxDigits), "X", null); - outString.Append("cpp2beef_"); - if (hashCode.StartsWith('-')) hashCode[0] = 'm'; - outString.Append('0', int_maxDigits - hashCode.Length); - outString.Append(hashCode); - } - protected virtual enum { C, Cpp } Linkable_Attributes(CXCursor cursor) { let mangledName = ScopeCXString!(Clang.Cursor_GetMangling(cursor)); let name = GetNameInBindings(cursor, ..scope .(mangledName.Length)); WriteCustomAttributes(cursor); + void LinkNameWhitespace() + { + if (Flags.HasFlag(.PreseveColumns)) + str..Append('\n')..Append(cursorIndent); + else + str.Append(' '); + } if (mangledName == name) { str.Append("[CLink] "); @@ -891,20 +849,16 @@ abstract class Cpp2BeefGenerator { str.Append("[LinkName("); mangledName.QuoteString(str); - str.Append(")] "); + str.Append(")]"); + LinkNameWhitespace(); return .C; } else { - str.Append("[LinkName(\""); - CppWrapperName(cursor, str); - str.Append('"'); - if (!wrapperTemplateChain.IsEmpty) - if (templateParams.IsEmpty) - str.Append(" + __template_chain"); - else - str.Append(" + CppWrapperF($\"", wrapperTemplateChain, "\")"); - str.Append(")] "); + str.Append("[LinkName("); + ScopeCXString!(Clang.GetCursorPrettyPrinted(cursor, printingPolicy)).QuoteString(str); + str.Append(")]"); + LinkNameWhitespace(); return .Cpp; } } @@ -1111,89 +1065,9 @@ abstract class Cpp2BeefGenerator } } - protected virtual void WriteMethodWrapper(CXCursor cursor) - { - let parent = Clang.GetCursorType(Clang.GetCursorSemanticParent(cursor)); - var resultType = Clang.GetCursorResultType(cursor); - bool nonStatic = (cursor.kind == .CXXMethod && Clang.CXXMethod_IsStatic(cursor) == 0) || cursor.kind == .Destructor || cursor.kind == .ConversionFunction; - - if (!wrapperTemplateChain.IsEmpty) - FlushWrapper(); - StringView parentSpelling = "\" + __cpp_type + \""; - if (wrapperTemplateChain.IsEmpty) - parentSpelling = ScopeCXString!::(Clang.GetFullyQualifiedName(parent, printingPolicy, 0)); - - if (cursor.kind == .Constructor) - wrapperBuf.Append("__type<", parentSpelling, "> "); - else - { - wrapperBuf.Append("__type<"); - wrapperBuf.Append(ScopeCXString!(Clang.GetFullyQualifiedName(resultType, printingPolicy, 0))); - wrapperBuf.Append("> "); - } - CppWrapperName(cursor, wrapperBuf); - if (!wrapperTemplateChain.IsEmpty) - { - if (templateParams.IsEmpty) - wrapperBuf.Append("\" + __template_chain + \""); - else - wrapperBuf.Append("\" + CppWrapperF($\"", wrapperTemplateChain, "\") + \""); - } - wrapperBuf.Append('('); - if (nonStatic) - wrapperBuf.Append(parentSpelling, " *self"); - for (int i < Clang.Cursor_GetNumArguments(cursor)) - { - let arg = Clang.Cursor_GetArgument(cursor, (.)i); - if (nonStatic || i > 0) wrapperBuf.Append(", "); - wrapperBuf.Append("__type<"); - wrapperBuf.Append(ScopeCXString!(Clang.GetFullyQualifiedName(Clang.GetCursorType(arg), printingPolicy, 0))); - wrapperBuf.Append("> p"); - i.ToString(wrapperBuf); - } - wrapperBuf.Append(") { "); - if (resultType.kind != .Void) wrapperBuf.Append("return "); - if (nonStatic) - { - if (cursor.kind != .Destructor) - wrapperBuf.Append("self->", GetCursorSpelling!(cursor)); - else - wrapperBuf.Append("self->~", parentSpelling); - } - else if (cursor.kind == .Constructor) - wrapperBuf.Append("return ", parentSpelling); - else - wrapperBuf.Append(fullCursorName); - wrapperBuf.Append('('); - for (int i < Clang.Cursor_GetNumArguments(cursor)) - { - if (i > 0) wrapperBuf.Append(", "); - wrapperBuf.Append('p'); - i.ToString(wrapperBuf); - } - wrapperBuf.Append("); }"); - - if (wrapperTemplateChain.IsEmpty) - { - wrapperBuf.Append('\n'); - FlushWrapper(); - } - else - { - if (defferedWrapperWrite == null) - defferedWrapperWrite = new .(1024); - defferedWrapperWrite.Append(cursorIndent, "\t + \"extern \\\"C\\\" "); - wrapperBuf.ToString(defferedWrapperWrite); - defferedWrapperWrite.Append("\\n\"\n"); - wrapperBuf.Clear(); - } - } - protected virtual void FunctionDecl(CXCursor cursor) { - if (Linkable_Attributes(cursor) == .Cpp) - WriteMethodWrapper(cursor); - + Linkable_Attributes(cursor); AccessSpecifier(cursor); str.Append("static extern "); WriteTypeAndName(cursor, Clang.GetCursorResultType(cursor)); @@ -1208,8 +1082,7 @@ abstract class Cpp2BeefGenerator { void Attributes() { - if (Linkable_Attributes(cursor) == .Cpp) - WriteMethodWrapper(cursor); + Linkable_Attributes(cursor); } let spelling = GetCursorSpelling!(cursor); @@ -1345,18 +1218,6 @@ abstract class Cpp2BeefGenerator if (linkLang == .Cpp && type.kind != .LValueReference && type.kind != .RValueReference) str.Append("ref "); WriteTypeAndName(cursor, type); - if (linkLang == .Cpp) - { - let wrapperName = CppWrapperName(cursor, ..scope .()); - str.Append(" { [LinkName(\"", wrapperName, "\")] get; }"); - wrapperBuf.Append("__type<"); - wrapperBuf.Append(ScopeCXString!(Clang.GetFullyQualifiedName(type, printingPolicy, 0))); - wrapperBuf.Append("> "); - if (type.kind != .LValueReference && type.kind != .RValueReference) - wrapperBuf.Append('&'); - wrapperBuf.Append(wrapperName, "() { return ", fullCursorName, "; }\n"); - FlushWrapper(); - } str.Append(';'); default: Runtime.FatalError(scope $"Unhandled var linkage: {_}"); @@ -1381,15 +1242,16 @@ abstract class Cpp2BeefGenerator }, null) != 0; } - protected virtual void Record(CXCursor cursor) + protected virtual void Record(CXCursor cursor, bool attributes = true) { WriteCustomAttributes(cursor); - switch (cursor.kind) - { - case .StructDecl, .ClassDecl: str.Append("[CRepr] "); - case .UnionDecl: str.Append("[CRepr, Union] "); - default: Runtime.FatalError("Unhandled record type"); - } + if (attributes) + switch (cursor.kind) + { + case .StructDecl, .ClassDecl: str.Append("[CRepr] "); + case .UnionDecl: str.Append("[CRepr, Union] "); + default: Runtime.FatalError("Unhandled record type"); + } AccessSpecifier(cursor); str.Append("struct "); if (Clang.Cursor_IsAnonymous(cursor) == 0) @@ -1483,9 +1345,22 @@ abstract class Cpp2BeefGenerator Self self = (.)Internal.UnsafeCastToObject(client_data); if (self.fileInfo.bitfield != default && Clang.Cursor_IsBitField(cursor) == 0) self.DumpBitfieldStorage(); - self.WriteCursor(cursor); if (Clang.Cursor_IsAnonymousRecordDecl(cursor) != 0) - self.str..TrimEnd()..Append(";"); + { + self.BeginCursor(cursor); + switch (cursor.kind) + { + case .StructDecl, .ClassDecl: self.str.Append("[CRepr] "); + case .UnionDecl: self.str.Append("[CRepr, Union] "); + default: Runtime.FatalError("Unhandled record type"); + } + self.AccessSpecifier(cursor); + self.str.Append("using "); + self.Record(cursor, attributes: false); + self.fileInfo.queuedTokens |= .Semicolon; + } + else + self.WriteCursor(cursor); return .Continue; }, Internal.UnsafeCastToPtr(this));