Skip to content

Commit

Permalink
Add initial Emscripten generator. (mono#1712)
Browse files Browse the repository at this point in the history
  • Loading branch information
tritao committed Feb 3, 2023
1 parent 9b06e7b commit 117567d
Show file tree
Hide file tree
Showing 21 changed files with 719 additions and 33 deletions.
1 change: 1 addition & 0 deletions docs/UsersManual.md
Expand Up @@ -39,6 +39,7 @@ There is also experimental support for these JavaScript-related targets:
- N-API (Node.js)
- QuickJS
- TypeScript
- Emscripten

# 3. Native Targets

Expand Down
37 changes: 25 additions & 12 deletions src/CLI/CLI.cs
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using CppSharp.Generators;
using Mono.Options;

namespace CppSharp
Expand All @@ -15,11 +16,11 @@ static bool ParseCommandLineArgs(string[] args, List<string> errorMessages, ref
{
var showHelp = false;

optionSet.Add("I=", "the {PATH} of a folder to search for include files", (i) => { AddIncludeDirs(i, errorMessages); });
optionSet.Add("I=", "the {PATH} of a folder to search for include files", i => { AddIncludeDirs(i, errorMessages); });
optionSet.Add("l=", "{LIBRARY} that that contains the symbols of the generated code", l => options.Libraries.Add(l));
optionSet.Add("L=", "the {PATH} of a folder to search for additional libraries", l => options.LibraryDirs.Add(l));
optionSet.Add("D:", "additional define with (optional) value to add to be used while parsing the given header files", (n, v) => AddDefine(n, v, errorMessages));
optionSet.Add("A=", "additional Clang arguments to pass to the compiler while parsing the given header files", (v) => AddArgument(v, errorMessages));
optionSet.Add("A=", "additional Clang arguments to pass to the compiler while parsing the given header files", v => AddArgument(v, errorMessages));

optionSet.Add("o=|output=", "the {PATH} for the generated bindings file (doesn't need the extension since it will depend on the generator)", v => HandleOutputArg(v, errorMessages));
optionSet.Add("on=|outputnamespace=", "the {NAMESPACE} that will be used for the generated code", on => options.OutputNamespace = on);
Expand All @@ -30,8 +31,8 @@ static bool ParseCommandLineArgs(string[] args, List<string> errorMessages, ref
optionSet.Add("d|debug", "enables debug mode which generates more verbose code to aid debugging", v => options.Debug = true);
optionSet.Add("c|compile", "enables automatic compilation of the generated code", v => options.Compile = true);
optionSet.Add("g=|gen=|generator=", "the {TYPE} of generated code: 'csharp' or 'cli' ('cli' supported only for Windows)", g => { GetGeneratorKind(g, errorMessages); });
optionSet.Add("p=|platform=", "the {PLATFORM} that the generated code will target: 'win', 'osx' or 'linux'", p => { GetDestinationPlatform(p, errorMessages); });
optionSet.Add("a=|arch=", "the {ARCHITECTURE} that the generated code will target: 'x86' or 'x64'", a => { GetDestinationArchitecture(a, errorMessages); });
optionSet.Add("p=|platform=", "the {PLATFORM} that the generated code will target: 'win', 'osx' or 'linux' or 'emscripten'", p => { GetDestinationPlatform(p, errorMessages); });
optionSet.Add("a=|arch=", "the {ARCHITECTURE} that the generated code will target: 'x86' or 'x64' or 'wasm32' or 'wasm64'", a => { GetDestinationArchitecture(a, errorMessages); });
optionSet.Add("prefix=", "sets a string prefix to the names of generated files", a => { options.Prefix = a; });

optionSet.Add("exceptions", "enables support for C++ exceptions in the parser", v => { options.EnableExceptions = true; });
Expand Down Expand Up @@ -208,26 +209,29 @@ static void GetGeneratorKind(string generator, List<string> errorMessages)
switch (generator.ToLower())
{
case "csharp":
options.Kind = CppSharp.Generators.GeneratorKind.CSharp;
options.Kind = GeneratorKind.CSharp;
return;
case "cli":
options.Kind = CppSharp.Generators.GeneratorKind.CLI;
options.Kind = GeneratorKind.CLI;
return;
case "c":
options.Kind = CppSharp.Generators.GeneratorKind.C;
options.Kind = GeneratorKind.C;
return;
case "cpp":
options.Kind = CppSharp.Generators.GeneratorKind.CPlusPlus;
options.Kind = GeneratorKind.CPlusPlus;
return;
case "napi":
options.Kind = CppSharp.Generators.GeneratorKind.NAPI;
options.Kind = GeneratorKind.NAPI;
return;
case "qjs":
options.Kind = CppSharp.Generators.GeneratorKind.QuickJS;
options.Kind = GeneratorKind.QuickJS;
return;
case "ts":
case "typescript":
options.Kind = CppSharp.Generators.GeneratorKind.TypeScript;
options.Kind = GeneratorKind.TypeScript;
return;
case "emscripten":
options.Kind = GeneratorKind.Emscripten;
return;
}

Expand All @@ -247,6 +251,9 @@ static void GetDestinationPlatform(string platform, List<string> errorMessages)
case "linux":
options.Platform = TargetPlatform.Linux;
return;
case "emscripten":
options.Platform = TargetPlatform.Emscripten;
return;
}

errorMessages.Add($"Unknown target platform: {platform}. Defaulting to {options.Platform}");
Expand All @@ -262,6 +269,12 @@ static void GetDestinationArchitecture(string architecture, List<string> errorMe
case "x64":
options.Architecture = TargetArchitecture.x64;
return;
case "wasm32":
options.Architecture = TargetArchitecture.WASM32;
return;
case "wasm64":
options.Architecture = TargetArchitecture.WASM64;
return;
}

errorMessages.Add($"Unknown target architecture: {architecture}. Defaulting to {options.Architecture}");
Expand All @@ -286,7 +299,7 @@ static void Main(string[] args)
return;
}

Generator gen = new Generator(options);
var gen = new Generator(options);

var validOptions = gen.ValidateOptions(errorMessages);
PrintErrorMessages(errorMessages);
Expand Down
61 changes: 42 additions & 19 deletions src/CLI/Generator.cs
Expand Up @@ -26,28 +26,51 @@ void SetupTargetTriple()
{
var tripleBuilder = new StringBuilder();

if (options.Architecture == TargetArchitecture.x64)
tripleBuilder.Append("x86_64-");
else if (options.Architecture == TargetArchitecture.x86)
tripleBuilder.Append("i686-");

if (options.Platform == TargetPlatform.Windows)
{
tripleBuilder.Append("pc-win32-msvc");
abi = CppAbi.Microsoft;
}
else if (options.Platform == TargetPlatform.MacOS)
switch (options.Architecture)
{
tripleBuilder.Append("apple-darwin12.4.0");
abi = CppAbi.Itanium;
case TargetArchitecture.x64:
tripleBuilder.Append("x86_64-");
break;
case TargetArchitecture.x86:
tripleBuilder.Append("i686-");
break;
case TargetArchitecture.WASM32:
tripleBuilder.Append("wasm32-");
break;
case TargetArchitecture.WASM64:
tripleBuilder.Append("wasm64-");
break;
}
else if (options.Platform == TargetPlatform.Linux)
{
tripleBuilder.Append("linux-gnu");
abi = CppAbi.Itanium;

if (options.Cpp11ABI)
tripleBuilder.Append("-cxx11abi");
switch (options.Platform)
{
case TargetPlatform.Windows:
tripleBuilder.Append("pc-win32-msvc");
abi = CppAbi.Microsoft;
break;
case TargetPlatform.MacOS:
tripleBuilder.Append("apple-darwin12.4.0");
abi = CppAbi.Itanium;
break;
case TargetPlatform.Linux:
{
tripleBuilder.Append("linux-gnu");
abi = CppAbi.Itanium;

if (options.Cpp11ABI)
tripleBuilder.Append("-cxx11abi");
break;
}
case TargetPlatform.Emscripten:
{
if (options.Architecture != TargetArchitecture.WASM32 &&
options.Architecture != TargetArchitecture.WASM64)
throw new Exception("Emscripten target is only compatible with WASM architectures");

tripleBuilder.Append("unknown-emscripten");
abi = CppAbi.Itanium;
break;
}
}

triple = tripleBuilder.ToString();
Expand Down
4 changes: 3 additions & 1 deletion src/CLI/Options.cs
Expand Up @@ -6,7 +6,9 @@ namespace CppSharp
enum TargetArchitecture
{
x86,
x64
x64,
WASM32,
WASM64
}

class Options
Expand Down
3 changes: 2 additions & 1 deletion src/Core/Platform.cs
Expand Up @@ -11,7 +11,8 @@ public enum TargetPlatform
MacOS,
iOS,
WatchOS,
TVOS
TVOS,
Emscripten,
}

public static class Platform
Expand Down
3 changes: 3 additions & 0 deletions src/Generator/Driver.cs
Expand Up @@ -8,6 +8,7 @@
using CppSharp.Generators.CLI;
using CppSharp.Generators.Cpp;
using CppSharp.Generators.CSharp;
using CppSharp.Generators.Emscripten;
using CppSharp.Generators.TS;
using CppSharp.Parser;
using CppSharp.Passes;
Expand Down Expand Up @@ -43,6 +44,8 @@ Generator CreateGeneratorFromKind(GeneratorKind kind)
return new CLIGenerator(Context);
case GeneratorKind.CSharp:
return new CSharpGenerator(Context);
case GeneratorKind.Emscripten:
return new EmscriptenGenerator(Context);
case GeneratorKind.QuickJS:
return new QuickJSGenerator(Context);
case GeneratorKind.NAPI:
Expand Down
1 change: 1 addition & 0 deletions src/Generator/Generator.cs
Expand Up @@ -14,6 +14,7 @@ public enum GeneratorKind
CSharp = 2,
C,
CPlusPlus,
Emscripten,
ObjectiveC,
Java,
Swift,
Expand Down
73 changes: 73 additions & 0 deletions src/Generator/Generators/Emscripten/EmscriptenGenerator.cs
@@ -0,0 +1,73 @@
using System.Collections.Generic;
using System.Linq;
using CppSharp.AST;
using CppSharp.Generators.Cpp;

namespace CppSharp.Generators.Emscripten
{
/// <summary>
/// Emscripten generator responsible for driving the generation of binding files.
/// Embind documentation: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html
/// </summary>
public class EmscriptenGenerator : CppGenerator
{
public EmscriptenGenerator(BindingContext context) : base(context)
{
}

public override List<GeneratorOutput> Generate()
{
var outputs = base.Generate();

foreach (var module in Context.Options.Modules)
{
if (module == Context.Options.SystemModule)
continue;

var output = GenerateModule(module);
if (output != null)
{
OnUnitGenerated(output);
outputs.Add(output);
}
}

return outputs;
}

public override List<CodeGenerator> Generate(IEnumerable<TranslationUnit> units)
{
var outputs = new List<CodeGenerator>();

var header = new EmscriptenHeaders(Context, units);
outputs.Add(header);

var source = new EmscriptenSources(Context, units);
outputs.Add(source);

return outputs;
}

public override GeneratorOutput GenerateModule(Module module)
{
if (module == Context.Options.SystemModule)
return null;

var moduleGen = new EmscriptenModule(Context, module);

var output = new GeneratorOutput
{
TranslationUnit = new TranslationUnit
{
FilePath = $"{module.LibraryName}_embind_module.cpp",
Module = module
},
Outputs = new List<CodeGenerator> { moduleGen }
};

output.Outputs[0].Process();

return output;
}
}
}
48 changes: 48 additions & 0 deletions src/Generator/Generators/Emscripten/EmscriptenHeaders.cs
@@ -0,0 +1,48 @@
using System.Collections.Generic;
using CppSharp.AST;

namespace CppSharp.Generators.Emscripten
{
/// <summary>
/// Generates Emscripten Embind C/C++ header files.
/// Embind documentation: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html
/// </summary>
public class EmscriptenHeaders : EmscriptenCodeGenerator
{
public EmscriptenHeaders(BindingContext context, IEnumerable<TranslationUnit> units)
: base(context, units)
{
CTypePrinter.PushContext(TypePrinterContextKind.Managed);
}

//public override bool ShouldGenerateNamespaces => false;

public override void Process()
{
GenerateFilePreamble(CommentKind.BCPL);

PushBlock(BlockKind.Includes);
WriteLine("#pragma once");
NewLine();
PopBlock();

var name = GetTranslationUnitName(TranslationUnit);
WriteLine($"extern \"C\" void embind_init_{name}();");
}

public override bool VisitClassDecl(Class @class)
{
return true;
}

public override bool VisitEvent(Event @event)
{
return true;
}

public override bool VisitFieldDecl(Field field)
{
return true;
}
}
}
21 changes: 21 additions & 0 deletions src/Generator/Generators/Emscripten/EmscriptenMarshal.cs
@@ -0,0 +1,21 @@
using CppSharp.Generators.C;

namespace CppSharp.Generators.Emscripten
{
public class EmscriptenMarshalNativeToManagedPrinter : MarshalPrinter<MarshalContext, CppTypePrinter>
{
public EmscriptenMarshalNativeToManagedPrinter(MarshalContext marshalContext)
: base(marshalContext)
{
}
}

public class EmscriptenMarshalManagedToNativePrinter : MarshalPrinter<MarshalContext, CppTypePrinter>
{
public EmscriptenMarshalManagedToNativePrinter(MarshalContext ctx)
: base(ctx)
{
Context.MarshalToNative = this;
}
}
}

0 comments on commit 117567d

Please sign in to comment.