From e66fad0d320632edfc75ea1d9cdb4f5674075626 Mon Sep 17 00:00:00 2001 From: Joao Matos Date: Fri, 3 Feb 2023 17:49:54 +0000 Subject: [PATCH] Add initial Emscripten generator. --- docs/UsersManual.md | 1 + src/CLI/CLI.cs | 37 ++-- src/CLI/Generator.cs | 61 ++++-- src/CLI/Options.cs | 4 +- src/Core/Platform.cs | 3 +- src/Generator/Driver.cs | 3 + src/Generator/Generator.cs | 1 + .../Emscripten/EmscriptenGenerator.cs | 73 +++++++ .../Emscripten/EmscriptenHeaders.cs | 48 +++++ .../Emscripten/EmscriptenMarshal.cs | 21 ++ .../Generators/Emscripten/EmscriptenModule.cs | 89 ++++++++ .../Emscripten/EmscriptenSources.cs | 202 ++++++++++++++++++ .../Emscripten/EmscriptenTypePrinter.cs | 11 + .../Passes/CheckDuplicatedNamesPass.cs | 4 + tests2/Builtins.h | 3 + tests2/emscripten/.gitignore | 4 + tests2/emscripten/premake5.lua | 21 ++ tests2/emscripten/test.mjs | 114 ++++++++++ tests2/emscripten/test.sh | 30 +++ tests2/emscripten/utils.mjs | 21 ++ tests2/test.sh | 1 + 21 files changed, 719 insertions(+), 33 deletions(-) create mode 100644 src/Generator/Generators/Emscripten/EmscriptenGenerator.cs create mode 100644 src/Generator/Generators/Emscripten/EmscriptenHeaders.cs create mode 100644 src/Generator/Generators/Emscripten/EmscriptenMarshal.cs create mode 100644 src/Generator/Generators/Emscripten/EmscriptenModule.cs create mode 100644 src/Generator/Generators/Emscripten/EmscriptenSources.cs create mode 100644 src/Generator/Generators/Emscripten/EmscriptenTypePrinter.cs create mode 100644 tests2/emscripten/.gitignore create mode 100644 tests2/emscripten/premake5.lua create mode 100644 tests2/emscripten/test.mjs create mode 100755 tests2/emscripten/test.sh create mode 100644 tests2/emscripten/utils.mjs diff --git a/docs/UsersManual.md b/docs/UsersManual.md index bd4865f9..0c45a91a 100644 --- a/docs/UsersManual.md +++ b/docs/UsersManual.md @@ -39,6 +39,7 @@ There is also experimental support for these JavaScript-related targets: - N-API (Node.js) - QuickJS - TypeScript +- Emscripten # 3. Native Targets diff --git a/src/CLI/CLI.cs b/src/CLI/CLI.cs index 54b2ed6c..b15b86b7 100644 --- a/src/CLI/CLI.cs +++ b/src/CLI/CLI.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using CppSharp.Generators; using Mono.Options; namespace CppSharp @@ -15,11 +16,11 @@ namespace CppSharp { 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); @@ -30,8 +31,8 @@ namespace CppSharp 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; }); @@ -208,26 +209,29 @@ namespace CppSharp 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; } @@ -247,6 +251,9 @@ namespace CppSharp case "linux": options.Platform = TargetPlatform.Linux; return; + case "emscripten": + options.Platform = TargetPlatform.Emscripten; + return; } errorMessages.Add($"Unknown target platform: {platform}. Defaulting to {options.Platform}"); @@ -262,6 +269,12 @@ namespace CppSharp 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}"); @@ -286,7 +299,7 @@ namespace CppSharp return; } - Generator gen = new Generator(options); + var gen = new Generator(options); bool validOptions = gen.ValidateOptions(errorMessages); PrintErrorMessages(errorMessages); diff --git a/src/CLI/Generator.cs b/src/CLI/Generator.cs index 5c6f72be..55b1c406 100644 --- a/src/CLI/Generator.cs +++ b/src/CLI/Generator.cs @@ -43,28 +43,51 @@ namespace CppSharp { 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) + switch (options.Architecture) { - tripleBuilder.Append("pc-win32-msvc"); - abi = CppAbi.Microsoft; + 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.MacOS) - { - tripleBuilder.Append("apple-darwin12.4.0"); - abi = CppAbi.Itanium; - } - 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(); diff --git a/src/CLI/Options.cs b/src/CLI/Options.cs index f679287c..bbcb2f38 100644 --- a/src/CLI/Options.cs +++ b/src/CLI/Options.cs @@ -6,7 +6,9 @@ namespace CppSharp enum TargetArchitecture { x86, - x64 + x64, + WASM32, + WASM64 } class Options diff --git a/src/Core/Platform.cs b/src/Core/Platform.cs index d0500f29..90740dfc 100644 --- a/src/Core/Platform.cs +++ b/src/Core/Platform.cs @@ -11,7 +11,8 @@ namespace CppSharp MacOS, iOS, WatchOS, - TVOS + TVOS, + Emscripten, } public static class Platform diff --git a/src/Generator/Driver.cs b/src/Generator/Driver.cs index 3f9a847e..726cb7cd 100644 --- a/src/Generator/Driver.cs +++ b/src/Generator/Driver.cs @@ -8,6 +8,7 @@ using CppSharp.Generators.C; 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; @@ -43,6 +44,8 @@ namespace CppSharp 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: diff --git a/src/Generator/Generator.cs b/src/Generator/Generator.cs index e9bcc3e8..cf414b48 100644 --- a/src/Generator/Generator.cs +++ b/src/Generator/Generator.cs @@ -14,6 +14,7 @@ namespace CppSharp.Generators CSharp = 2, C, CPlusPlus, + Emscripten, ObjectiveC, Java, Swift, diff --git a/src/Generator/Generators/Emscripten/EmscriptenGenerator.cs b/src/Generator/Generators/Emscripten/EmscriptenGenerator.cs new file mode 100644 index 00000000..f2f7e71a --- /dev/null +++ b/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 +{ + /// + /// Emscripten generator responsible for driving the generation of binding files. + /// Embind documentation: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html + /// + public class EmscriptenGenerator : CppGenerator + { + public EmscriptenGenerator(BindingContext context) : base(context) + { + } + + public override List 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 Generate(IEnumerable units) + { + var outputs = new List(); + + 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 { moduleGen } + }; + + output.Outputs[0].Process(); + + return output; + } + } +} diff --git a/src/Generator/Generators/Emscripten/EmscriptenHeaders.cs b/src/Generator/Generators/Emscripten/EmscriptenHeaders.cs new file mode 100644 index 00000000..5d618e82 --- /dev/null +++ b/src/Generator/Generators/Emscripten/EmscriptenHeaders.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using CppSharp.AST; + +namespace CppSharp.Generators.Emscripten +{ + /// + /// Generates Emscripten Embind C/C++ header files. + /// Embind documentation: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html + /// + public class EmscriptenHeaders : EmscriptenCodeGenerator + { + public EmscriptenHeaders(BindingContext context, IEnumerable 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; + } + } +} diff --git a/src/Generator/Generators/Emscripten/EmscriptenMarshal.cs b/src/Generator/Generators/Emscripten/EmscriptenMarshal.cs new file mode 100644 index 00000000..0e05a740 --- /dev/null +++ b/src/Generator/Generators/Emscripten/EmscriptenMarshal.cs @@ -0,0 +1,21 @@ +using CppSharp.Generators.C; + +namespace CppSharp.Generators.Emscripten +{ + public class EmscriptenMarshalNativeToManagedPrinter : MarshalPrinter + { + public EmscriptenMarshalNativeToManagedPrinter(MarshalContext marshalContext) + : base(marshalContext) + { + } + } + + public class EmscriptenMarshalManagedToNativePrinter : MarshalPrinter + { + public EmscriptenMarshalManagedToNativePrinter(MarshalContext ctx) + : base(ctx) + { + Context.MarshalToNative = this; + } + } +} diff --git a/src/Generator/Generators/Emscripten/EmscriptenModule.cs b/src/Generator/Generators/Emscripten/EmscriptenModule.cs new file mode 100644 index 00000000..e92c4bcf --- /dev/null +++ b/src/Generator/Generators/Emscripten/EmscriptenModule.cs @@ -0,0 +1,89 @@ +using System.IO; +using System.Linq; +using CppSharp.AST; +using CppSharp.Generators.C; + +namespace CppSharp.Generators.Emscripten +{ + /// + /// Generates Emscripten module init files. + /// Embind documentation: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html + /// + public class EmscriptenModule : EmscriptenCodeGenerator + { + private readonly Module module; + + public EmscriptenModule(BindingContext context, Module module) + : base(context, module.Units.GetGenerated()) + { + this.module = module; + } + + public override string FileExtension { get; } = "cpp"; + + public override void Process() + { + GenerateFilePreamble(CommentKind.BCPL); + NewLine(); + + PushBlock(BlockKind.Includes); + { + WriteInclude(new CInclude() + { + File = "emscripten/bind.h", + Kind = CInclude.IncludeKind.Angled + }); + + foreach (var unit in TranslationUnits) + WriteInclude(GetIncludeFileName(Context, unit), CInclude.IncludeKind.Quoted); + } + PopBlock(NewLineKind.Always); + + WriteLine("extern \"C\" {"); + NewLine(); + + PushBlock(BlockKind.ForwardReferences); + { + foreach (var unit in TranslationUnits.Where(unit => unit.IsGenerated)) + { + var name = GetTranslationUnitName(unit); + WriteLine($"void embind_init_{name}();"); + } + } + PopBlock(NewLineKind.Always); + + var moduleName = module.LibraryName; + WriteLine($"void embind_init_{moduleName}()"); + WriteOpenBraceAndIndent(); + + foreach (var unit in TranslationUnits) + { + var name = GetTranslationUnitName(unit); + WriteLine($"embind_init_{name}();"); + } + + UnindentAndWriteCloseBrace(); + + NewLine(); + WriteLine("}"); + NewLine(); + + WriteLine($"static struct EmBindInit_{moduleName} : emscripten::internal::InitFunc {{"); + WriteLineIndent($"EmBindInit_{moduleName}() : InitFunc(embind_init_{moduleName}) {{}}"); + WriteLine($"}} EmBindInit_{moduleName}_instance;"); + } + + public static string GetIncludeFileName(BindingContext context, TranslationUnit unit) + { + // TODO: Replace with GetIncludePath + string file; + if (context.Options.GenerateName != null) + file = context.Options.GenerateName(unit); + else + file = Path.GetFileNameWithoutExtension(unit.FileName) + .Replace('\\', '/'); + + return $"{file}.h"; + } + } +} diff --git a/src/Generator/Generators/Emscripten/EmscriptenSources.cs b/src/Generator/Generators/Emscripten/EmscriptenSources.cs new file mode 100644 index 00000000..4e786b04 --- /dev/null +++ b/src/Generator/Generators/Emscripten/EmscriptenSources.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CppSharp.AST; +using CppSharp.AST.Extensions; +using CppSharp.Generators.C; +using CppSharp.Generators.Cpp; + +namespace CppSharp.Generators.Emscripten +{ + public class EmscriptenCodeGenerator : MethodGroupCodeGenerator + { + protected EmscriptenCodeGenerator(BindingContext context, IEnumerable units) + : base(context, units) + { + } + + public virtual MarshalPrinter GetMarshalManagedToNativePrinter(MarshalContext ctx) + { + return new EmscriptenMarshalManagedToNativePrinter(ctx); + } + + public virtual MarshalPrinter GetMarshalNativeToManagedPrinter(MarshalContext ctx) + { + return new EmscriptenMarshalNativeToManagedPrinter(ctx); + } + + public override bool VisitClassTemplateDecl(ClassTemplate template) + { + return true; + } + + public override bool VisitFunctionTemplateDecl(FunctionTemplate template) + { + return true; + } + } + + /// + /// Generates Emscripten C/C++ source files. + /// Embind documentation: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html + /// + public class EmscriptenSources : EmscriptenCodeGenerator + { + public override string FileExtension => "cpp"; + + public EmscriptenSources(BindingContext context, IEnumerable units) + : base(context, units) + { + } + + public override void Process() + { + GenerateFilePreamble(CommentKind.BCPL); + + PushBlock(BlockKind.Includes); + { + WriteInclude(new CInclude() + { + File = "emscripten/bind.h", + Kind = CInclude.IncludeKind.Angled + }); + + foreach (var unit in TranslationUnits) + { + WriteInclude(unit.IncludePath, CInclude.IncludeKind.Angled); + } + } + PopBlock(NewLineKind.Always); + + var name = GetTranslationUnitName(TranslationUnit); + WriteLine($"extern \"C\" void embind_init_{name}()"); + WriteOpenBraceAndIndent(); + VisitNamespace(TranslationUnit); + UnindentAndWriteCloseBrace(); + } + + public override bool VisitClassDecl(Class @class) + { + if (@class.IsIncomplete) + return true; + + PushBlock(); + Write($"emscripten::class_<{@class.QualifiedOriginalName}"); + if (@class.HasBaseClass) + Write($", emscripten::base<{@class.BaseClass.QualifiedOriginalName}>"); + WriteLine($">(\"{@class.Name}\")"); + + VisitClassDeclContext(@class); + + WriteLineIndent(";"); + PopBlock(NewLineKind.BeforeNextBlock); + return true; + } + + public override void VisitClassConstructors(IEnumerable ctors) + { + var overloadCheck = new HashSet(); + foreach (var ctor in ctors) + { + if (overloadCheck.Contains(ctor.Parameters.Count)) + { + Console.WriteLine($"Ignoring overloaded ctor: {ctor.QualifiedOriginalName}"); + continue; + } + + var cppTypePrinter = new CppTypePrinter(Context) + { + PrintFlavorKind = CppTypePrintFlavorKind.Cpp + }; + cppTypePrinter.PushContext(TypePrinterContextKind.Native); + + var parameters = ctor.Parameters.Select(p => p.Type.Visit(cppTypePrinter).Type); + WriteLineIndent($".constructor<{string.Join(", ", parameters)}>()"); + + overloadCheck.Add(ctor.Parameters.Count); + } + } + + public override bool VisitMethodDecl(Method method) + { + Indent(); + var ret = VisitFunctionDecl(method); + Unindent(); + return ret; + } + + public override bool VisitFieldDecl(Field field) + { + WriteLineIndent($".field(\"{field.Name}\", &{field.Class.QualifiedOriginalName}::{field.OriginalName})"); + return true; + } + + public override void GenerateMethodGroup(List @group) + { + if (@group.Count > 1) + { + Console.WriteLine($"Ignoring method group: {@group.First().QualifiedOriginalName}"); + return; + } + + base.GenerateMethodGroup(@group); + } + + public override void GenerateFunctionGroup(List @group) + { + if (@group.Count > 1) + { + Console.WriteLine($"Ignoring function group: {@group.First().QualifiedOriginalName}"); + return; + } + + base.GenerateFunctionGroup(@group); + } + + public override void VisitDeclContextFunctions(DeclarationContext context) + { + PushBlock(); + base.VisitDeclContextFunctions(context); + PopBlock(NewLineKind.BeforeNextBlock); + } + + public override bool VisitFunctionDecl(Function function) + { + var prefix = function is Method ? ".function" : "emscripten::function"; + Write($"{prefix}(\"{function.Name}\", &{function.QualifiedOriginalName}"); + + var hasPointers = function.ReturnType.Type.IsPointer() || + function.Parameters.Any(p => p.Type.IsPointer()); + if (hasPointers) + Write(", emscripten::allow_raw_pointers()"); + + WriteLine(function is not Method ? ");" : $")"); + return true; + } + + public override bool VisitEnumDecl(Enumeration @enum) + { + if (@enum.IsIncomplete) + return false; + + PushBlock(); + + WriteLine($"emscripten::enum_<{@enum.Name}>(\"{@enum.QualifiedOriginalName}\")"); + foreach (var item in @enum.Items) + { + WriteLineIndent(@enum.IsScoped + ? $".value(\"{item.Name}\", {@enum.QualifiedOriginalName}::{item.OriginalName})" + : $".value(\"{item.Name}\", {item.QualifiedOriginalName})"); + } + WriteLineIndent(";"); + + PopBlock(NewLineKind.BeforeNextBlock); + return true; + } + + public override bool VisitTypedefDecl(TypedefDecl typedef) + { + return true; + } + } +} diff --git a/src/Generator/Generators/Emscripten/EmscriptenTypePrinter.cs b/src/Generator/Generators/Emscripten/EmscriptenTypePrinter.cs new file mode 100644 index 00000000..b5d5cd88 --- /dev/null +++ b/src/Generator/Generators/Emscripten/EmscriptenTypePrinter.cs @@ -0,0 +1,11 @@ +using CppSharp.Generators.C; + +namespace CppSharp.Generators.Emscripten +{ + public class EmscriptenTypePrinter : CppTypePrinter + { + public EmscriptenTypePrinter(BindingContext context) : base(context) + { + } + } +} diff --git a/src/Generator/Passes/CheckDuplicatedNamesPass.cs b/src/Generator/Passes/CheckDuplicatedNamesPass.cs index 1ccefa42..0a2d7622 100644 --- a/src/Generator/Passes/CheckDuplicatedNamesPass.cs +++ b/src/Generator/Passes/CheckDuplicatedNamesPass.cs @@ -7,6 +7,7 @@ using CppSharp.Generators; using CppSharp.Generators.C; using CppSharp.Generators.CLI; using CppSharp.Generators.CSharp; +using CppSharp.Generators.Emscripten; using CppSharp.Types; namespace CppSharp.Passes @@ -204,6 +205,9 @@ namespace CppSharp.Passes case GeneratorKind.C: typePrinter = new CppTypePrinter(Context) { PrintFlavorKind = CppTypePrintFlavorKind.C }; break; + case GeneratorKind.Emscripten: + typePrinter = new EmscriptenTypePrinter(Context); + break;; case GeneratorKind.CPlusPlus: case GeneratorKind.QuickJS: case GeneratorKind.NAPI: diff --git a/tests2/Builtins.h b/tests2/Builtins.h index 3aa8015c..9b270e53 100644 --- a/tests2/Builtins.h +++ b/tests2/Builtins.h @@ -1,3 +1,4 @@ +#include #include void ReturnsVoid () { } @@ -40,8 +41,10 @@ int16_t ReturnsInt16 () { return -5; } uint16_t ReturnsUInt16 () { return 5; } int32_t ReturnsInt32 () { return -5; } uint32_t ReturnsUInt32 () { return 5; } +#if !defined(__EMSCRIPTEN__) int64_t ReturnsInt64 () { return -5; } uint64_t ReturnsUInt64 () { return 5; } +#endif int8_t PassAndReturnsInt8 (int8_t v) { return v; } uint8_t PassAndReturnsUInt8 (uint8_t v) { return v; } diff --git a/tests2/emscripten/.gitignore b/tests2/emscripten/.gitignore new file mode 100644 index 00000000..36316b01 --- /dev/null +++ b/tests2/emscripten/.gitignore @@ -0,0 +1,4 @@ +gen +*.so +*.dylib +*.dll diff --git a/tests2/emscripten/premake5.lua b/tests2/emscripten/premake5.lua new file mode 100644 index 00000000..8fa7a738 --- /dev/null +++ b/tests2/emscripten/premake5.lua @@ -0,0 +1,21 @@ + +workspace "emscripten" + configurations { "debug", "release" } + location "gen" + symbols "On" + optimize "Off" + + project "test" + kind "SharedLib" + language "C++" + files + { + "gen/**.cpp", + } + includedirs + { + "..", + "../../include" + } + linkoptions { "--bind -sENVIRONMENT=node -sMODULARIZE=1 -sEXPORT_ALL -sEXPORT_ES6=1 -sUSE_ES6_IMPORT_META=1" } + targetextension ".mjs" \ No newline at end of file diff --git a/tests2/emscripten/test.mjs b/tests2/emscripten/test.mjs new file mode 100644 index 00000000..95b8b0bf --- /dev/null +++ b/tests2/emscripten/test.mjs @@ -0,0 +1,114 @@ +import wasmModule from "./gen/bin/debug/libtest.mjs"; +import { eq, ascii, floateq } from "./utils.mjs" + +const test = await wasmModule({ + onRuntimeInitialized() { + + } +}); + +function builtins() { + eq(test.ReturnsVoid(), undefined) + + eq(test.ReturnsBool(), true) + eq(test.PassAndReturnsBool(false), false) + + eq(test.ReturnsNullptr(), null) + eq(test.PassAndReturnsNullptr(null), null) + + eq(test.ReturnsChar(), ascii('a')); + eq(test.ReturnsSChar(), ascii('a')); + eq(test.ReturnsUChar(), ascii('a')); + + eq(test.PassAndReturnsChar(ascii('a')), ascii('a')); + eq(test.PassAndReturnsSChar(ascii('b')), ascii('b')); + eq(test.PassAndReturnsUChar(ascii('c')), ascii('c')); + + // TODO: add wchar_t tests + + eq(test.ReturnsFloat(), 5.0); + eq(test.ReturnsDouble(), -5.0); + //eq(test.ReturnsLongDouble(), -5.0); + + floateq(test.PassAndReturnsFloat(1.32), 1.32); + floateq(test.PassAndReturnsDouble(1.32), 1.32); + //float(test.PassAndReturnsLongDouble(1.32), 1.32); + + eq(test.ReturnsInt8(), -5); + eq(test.ReturnsUInt8(), 5); + eq(test.ReturnsInt16(), -5); + eq(test.ReturnsUInt16(), 5); + eq(test.ReturnsInt32(), -5); + eq(test.ReturnsUInt32(), 5); + + // TODO: + // https://github.com/WebAssembly/proposals/issues/7 + // https://github.com/emscripten-core/emscripten/issues/11140 + //eq(test.ReturnsInt64(), -5n); + //eq(test.ReturnsUInt64(), 5n); + + const int8 = { min: -(2 ** 7), max: (2 ** 7) - 1 }; + eq(test.PassAndReturnsInt8(int8.min), int8.min); + eq(test.PassAndReturnsInt8(int8.max), int8.max); + + const uint8 = { min: 0, max: (2 ** 8) - 1 }; + eq(test.PassAndReturnsUInt8(uint8.min), uint8.min); + eq(test.PassAndReturnsUInt8(uint8.max), uint8.max); + + const int16 = { min: -(2 ** 15), max: (2 ** 15) - 1 }; + eq(test.PassAndReturnsInt16(int16.min), int16.min); + eq(test.PassAndReturnsInt16(int16.max), int16.max); + + const uint16 = { min: 0, max: (2 ** 16) - 1 }; + eq(test.PassAndReturnsUInt16(uint16.min), uint16.min); + eq(test.PassAndReturnsUInt16(uint16.max), uint16.max); + + const int32 = { min: -(2 ** 31), max: (2 ** 31) - 1 }; + eq(test.PassAndReturnsInt32(int32.min), int32.min); + eq(test.PassAndReturnsInt32(int32.max), int32.max); + + const uint32 = { min: 0, max: (2 ** 32) - 1 }; + eq(test.PassAndReturnsUInt32(uint32.min), uint32.min); + eq(test.PassAndReturnsUInt32(uint32.max), uint32.max); + + //const int64 = { min: BigInt(2 ** 63) * -1n, max: BigInt(2 ** 63) - 1n }; + //eq(test.PassAndReturnsInt64(int64.min), int64.min); + //eq(test.PassAndReturnsInt64(int64.max), int64.max); + + //const uint64 = { min: BigInt(0), max: BigInt(2 ** 64) - 1n }; + //eq(test.PassAndReturnsUInt64(uint64.min), uint64.min); + //eq(test.PassAndReturnsUInt64(uint64.max), uint64.max); +} + +function enums() { + eq(test.Enum0.Item0.value, 0); + eq(test.Enum0.Item1.value, 1); + eq(test.Enum0.Item2.value, 5); + + eq(test.ReturnsEnum(), test.Enum0.Item0); + eq(test.PassAndReturnsEnum(test.Enum0.Item1), test.Enum0.Item1); +} + +function classes() { + var c = new test.Class(); + eq(typeof (c), "object") + eq(c.ReturnsVoid(), undefined) + eq(c.ReturnsInt(), 0) + eq(c.PassAndReturnsClassPtr(null), null) + + var c1 = new test.ClassWithSingleInheritance(); + eq(c1.__proto__.constructor.name, 'ClassWithSingleInheritance') + eq(c1.__proto__.__proto__.constructor.name, 'Class') + eq(c1.ReturnsVoid(), undefined); + eq(c1.ReturnsInt(), 0); + eq(c1.ChildMethod(), 2); + + var classWithField = new test.ClassWithField(); + eq(classWithField.ReturnsField(), 10); + eq(classWithField.Field, 10); +} + + +builtins(); +enums(); +classes(); \ No newline at end of file diff --git a/tests2/emscripten/test.sh b/tests2/emscripten/test.sh new file mode 100755 index 00000000..52807e87 --- /dev/null +++ b/tests2/emscripten/test.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -e +dir=$(cd "$(dirname "$0")"; pwd) +rootdir="$dir/../.." +dotnet_configuration=Release +configuration=debug +platform=x64 +jsinterp=node + +red=`tput setaf 1` +green=`tput setaf 2` +reset=`tput sgr0` + +generate=true + +if [ $generate = true ]; then + echo "${green}Generating bindings${reset}" + dotnet $rootdir/bin/${dotnet_configuration}_${platform}/CppSharp.CLI.dll \ + --gen=emscripten --platform=emscripten --arch=wasm32 \ + -I$dir/.. -I$rootdir/include -o $dir/gen -m tests $dir/../*.h +fi + +echo "${green}Building generated binding files${reset}" +premake=$rootdir/build/premake.sh +config=$configuration $premake --file=$dir/premake5.lua gmake +emmake make -C $dir/gen +echo + +echo "${green}Executing JS tests with Node${reset}" +$jsinterp $dir/test.mjs \ No newline at end of file diff --git a/tests2/emscripten/utils.mjs b/tests2/emscripten/utils.mjs new file mode 100644 index 00000000..fb0eb6c7 --- /dev/null +++ b/tests2/emscripten/utils.mjs @@ -0,0 +1,21 @@ +export function assert(actual, expected, message) { + if (arguments.length == 1) + expected = true; + + if (actual === expected) + return; + + if (actual !== null && expected !== null + && typeof actual == 'object' && typeof expected == 'object' + && actual.toString() === expected.toString()) + return; + + throw Error("assertion failed: got |" + actual + "|" + + ", expected |" + expected + "|" + + (message ? " (" + message + ")" : "")); +} + +export const eq = assert; +export const floateq = (actual, expected) => { assert(Math.abs(actual - expected) < 0.01) } + +export const ascii = v => v.charCodeAt(0) diff --git a/tests2/test.sh b/tests2/test.sh index 622401ef..f5abc712 100755 --- a/tests2/test.sh +++ b/tests2/test.sh @@ -4,3 +4,4 @@ dir=$(cd "$(dirname "$0")"; pwd) $dir/napi/test.sh $dir/quickjs/test.sh +$dir/emscripten/test.sh