From ca2eece6a2faa85e952af2dc05b0e237d61ff930 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 9 Jan 2024 17:04:29 +0000 Subject: [PATCH 1/3] Add experimental NativeAOT-LLVM support --- crates/bindings-csharp/Codegen/Module.cs | 31 ++++++++++++++---- crates/bindings-csharp/Runtime/Module.cs | 15 +++------ crates/bindings-csharp/Runtime/RawBindings.cs | 8 ++++- crates/bindings-csharp/Runtime/bindings.c | 9 ++++-- .../Runtime/build/SpacetimeDB.Runtime.props | 18 ++++++++++- .../Runtime/build/SpacetimeDB.Runtime.targets | 32 +++++++++++++++++-- .../project/csharp/StdbModule._csproj | 2 -- crates/cli/src/tasks/csharp.rs | 7 +++- .../StdbModule.csproj | 2 -- modules/sdk-test-cs/StdbModule.csproj | 2 -- .../StdbModule.csproj | 1 - 11 files changed, 95 insertions(+), 32 deletions(-) diff --git a/crates/bindings-csharp/Codegen/Module.cs b/crates/bindings-csharp/Codegen/Module.cs index 769e1ebed4..ba8b2da6fe 100644 --- a/crates/bindings-csharp/Codegen/Module.cs +++ b/crates/bindings-csharp/Codegen/Module.cs @@ -286,26 +286,43 @@ class {r.Name}: IReducer {{ // #nullable enable + using static SpacetimeDB.RawBindings; using SpacetimeDB.Module; using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; using static SpacetimeDB.Runtime; using System.Diagnostics.CodeAnalysis; static class ModuleRegistration {{ {string.Join("\n", addReducers.Select(r => r.Class))} -#pragma warning disable CA2255 - // [ModuleInitializer] - doesn't work because assemblies are loaded lazily; - // might make use of it later down the line, but for now assume there is only one - // module so we can use `Main` instead. - +#if EXPERIMENTAL_WASM_AOT + // In AOT mode we're building a library. + // Main method won't be called automatically, so we need to export it as a preinit function. + [UnmanagedCallersOnly(EntryPoint = ""__preinit__10_init_csharp"")] +#else // Prevent trimming of FFI exports that are invoked from C and not visible to C# trimmer. - [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(SpacetimeDB.Module.FFI))] + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(FFI))] +#endif public static void Main() {{ {string.Join("\n", addReducers.Select(r => $"FFI.RegisterReducer(new {r.Name}());"))} {string.Join("\n", tableNames.Select(t => $"FFI.RegisterTable({t}.MakeTableDesc());"))} }} -#pragma warning restore CA2255 + +// Exports only work from the main assembly, so we need to generate forwarding methods. +#if EXPERIMENTAL_WASM_AOT + [UnmanagedCallersOnly(EntryPoint = ""__describe_module__"")] + public static Buffer __describe_module__() => FFI.__describe_module__(); + + [UnmanagedCallersOnly(EntryPoint = ""__call_reducer__"")] + public static Buffer __call_reducer__( + uint id, + Buffer caller_identity, + Buffer caller_address, + ulong timestamp, + Buffer args + ) => FFI.__call_reducer__(id, caller_identity, caller_address, timestamp, args); +#endif }} " ); diff --git a/crates/bindings-csharp/Runtime/Module.cs b/crates/bindings-csharp/Runtime/Module.cs index 6711ff99ea..fd94fafe53 100644 --- a/crates/bindings-csharp/Runtime/Module.cs +++ b/crates/bindings-csharp/Runtime/Module.cs @@ -127,14 +127,11 @@ public TableDef(string tableName, ColumnDefWithAttrs[] columns) // Important: the position must be stored here, before filtering. .Select((col, pos) => (col, pos)) .Where(pair => pair.col.Attrs != ColumnAttrs.UnSet) - .Select( - pair => - new ConstraintDef( - $"ct_{tableName}_{pair.col.ColumnDef.ColName}_{pair.col.Attrs}", - pair.col.Attrs, - new[] { (uint)pair.pos } - ) - ) + .Select(pair => new ConstraintDef( + $"ct_{tableName}_{pair.col.ColumnDef.ColName}_{pair.col.Attrs}", + pair.col.Attrs, + new[] { (uint)pair.pos } + )) .ToArray(); TableType = "user"; TableAccess = tableName.StartsWith('_') ? "private" : "public"; @@ -276,7 +273,6 @@ public static void RegisterReducer(IReducer reducer) bool anonymous = false ) => module.SetTypeRef(typeRef, type, anonymous); - // [UnmanagedCallersOnly(EntryPoint = "__describe_module__")] public static RawBindings.Buffer __describe_module__() { // replace `module` with a temporary internal module that will register ModuleDef, AlgebraicType and other internal types @@ -300,7 +296,6 @@ public static RawBindings.Buffer __describe_module__() } } - // [UnmanagedCallersOnly(EntryPoint = "__call_reducer__")] public static RawBindings.Buffer __call_reducer__( uint id, RawBindings.Buffer caller_identity, diff --git a/crates/bindings-csharp/Runtime/RawBindings.cs b/crates/bindings-csharp/Runtime/RawBindings.cs index 113566934e..07b58d9445 100644 --- a/crates/bindings-csharp/Runtime/RawBindings.cs +++ b/crates/bindings-csharp/Runtime/RawBindings.cs @@ -12,7 +12,13 @@ public static partial class RawBindings // For now this must match the name of the `.c` file (`bindings.c`). // In the future C# will allow to specify Wasm import namespace in // `LibraryImport` directly. - const string StdbNamespace = "bindings"; + const string StdbNamespace = +#if EXPERIMENTAL_WASM_AOT + "spacetime_7.0" +#else + "bindings" +#endif + ; // This custom marshaller takes care of checking the status code // returned from the host and throwing an exception if it's not 0. diff --git a/crates/bindings-csharp/Runtime/bindings.c b/crates/bindings-csharp/Runtime/bindings.c index 368c723502..5aae9f9f53 100644 --- a/crates/bindings-csharp/Runtime/bindings.c +++ b/crates/bindings-csharp/Runtime/bindings.c @@ -1,3 +1,4 @@ +#ifndef EXPERIMENTAL_WASM_AOT #include // #include // #include @@ -111,6 +112,7 @@ EXPORT(Buffer, __call_reducer__, (uint32_t id, Buffer caller_identity, Buffer caller_address, uint64_t timestamp, Buffer args), &id, &caller_identity, &caller_address, ×tamp, &args); +#endif // Shims to avoid dependency on WASI in the generated Wasm file. @@ -208,9 +210,10 @@ int32_t WASI_NAME(fd_write)(__wasi_fd_t fd, const __wasi_ciovec_t* iovs, // Note: this will produce ugly broken output, but there's not much we can // do about it until we have proper line-buffered WASI writer in the core. // It's better than nothing though. - _console_log_imp((LogLevel){fd == STDERR_FILENO ? /*WARN*/ 1 : /*INFO*/ 2}, - CSTR("wasi"), CSTR(__FILE__), __LINE__, iovs[i].buf, - iovs[i].buf_len); + // _console_log_imp((LogLevel){fd == STDERR_FILENO ? /*WARN*/ 1 : /*INFO*/ + // 2}, + // CSTR("wasi"), CSTR(__FILE__), __LINE__, iovs[i].buf, + // iovs[i].buf_len); *retptr0 += iovs[i].buf_len; } return 0; diff --git a/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.props b/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.props index 25dda52d70..d9b880e907 100644 --- a/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.props +++ b/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.props @@ -4,10 +4,26 @@ true full true - false false true false + + Library + Shared + $(DefineConstants);EXPERIMENTAL_WASM_AOT + false + false + spacetime_7.0 + https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json + + + + + Exe + + false + + diff --git a/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets b/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets index 5a1144449c..a109b1d8af 100644 --- a/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets +++ b/crates/bindings-csharp/Runtime/build/SpacetimeDB.Runtime.targets @@ -1,6 +1,33 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_WasmNativeFileForLinking Include="@(NativeFileReference)" /> @@ -9,7 +36,7 @@ - + 20 @@ -22,6 +49,7 @@ https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$(WasiSdkVersion)/wasi-sdk-$(WasiSdkVersion).0$(WasiSdkFilenameSuffix).tar.gz $([System.IO.Path]::Combine($([System.Environment]::GetFolderPath(SpecialFolder.UserProfile)), '.wasi-sdk', "wasi-sdk-$(WasiSdkVersion)")) + $(WasiSdkRoot) $([System.IO.Path]::Combine($(WasiSdkRoot), 'share', 'wasi-sysroot')) $([System.IO.Path]::Combine($(WasiSdkRoot), 'bin', 'clang')) $(WasiClang).exe diff --git a/crates/cli/src/subcommands/project/csharp/StdbModule._csproj b/crates/cli/src/subcommands/project/csharp/StdbModule._csproj index a77d464bc0..eb550105bb 100644 --- a/crates/cli/src/subcommands/project/csharp/StdbModule._csproj +++ b/crates/cli/src/subcommands/project/csharp/StdbModule._csproj @@ -1,8 +1,6 @@ - - Exe net8.0 wasi-wasm enable diff --git a/crates/cli/src/tasks/csharp.rs b/crates/cli/src/tasks/csharp.rs index f084b6799d..5a35f4cd37 100644 --- a/crates/cli/src/tasks/csharp.rs +++ b/crates/cli/src/tasks/csharp.rs @@ -62,7 +62,12 @@ pub(crate) fn build_csharp(project_path: &Path, build_debug: bool) -> anyhow::Re .run()?; // check if file exists - let mut output_path = project_path.join(format!("bin/{config_name}/net8.0/wasi-wasm/AppBundle/StdbModule.wasm")); + let subdir = if std::env::var_os("EXPERIMENTAL_WASM_AOT").map_or(false, |v| v == "1") { + "publish" + } else { + "AppBundle" + }; + let mut output_path = project_path.join(format!("bin/{config_name}/net8.0/wasi-wasm/{subdir}/StdbModule.wasm")); if !output_path.exists() { // check for the old .NET 7 path for projects that haven't migrated yet output_path = project_path.join(format!("bin/{config_name}/net7.0/StdbModule.wasm")); diff --git a/modules/sdk-test-connect-disconnect-cs/StdbModule.csproj b/modules/sdk-test-connect-disconnect-cs/StdbModule.csproj index 7cf0f29373..c8a53d2a2a 100644 --- a/modules/sdk-test-connect-disconnect-cs/StdbModule.csproj +++ b/modules/sdk-test-connect-disconnect-cs/StdbModule.csproj @@ -1,8 +1,6 @@ - - Exe net8.0 wasi-wasm enable diff --git a/modules/sdk-test-cs/StdbModule.csproj b/modules/sdk-test-cs/StdbModule.csproj index e39f3a5212..1662a04f8e 100644 --- a/modules/sdk-test-cs/StdbModule.csproj +++ b/modules/sdk-test-cs/StdbModule.csproj @@ -1,8 +1,6 @@ - - Exe net8.0 wasi-wasm enable diff --git a/modules/spacetimedb-quickstart-cs/StdbModule.csproj b/modules/spacetimedb-quickstart-cs/StdbModule.csproj index 3939f4d5c3..0d2476bf57 100644 --- a/modules/spacetimedb-quickstart-cs/StdbModule.csproj +++ b/modules/spacetimedb-quickstart-cs/StdbModule.csproj @@ -2,7 +2,6 @@ - Exe net8.0 wasi-wasm enable From e499eb7c12ab9e18cdf3080c5406ccf48a47a992 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 26 Mar 2024 19:20:11 +0000 Subject: [PATCH 2/3] Fix codegen ambiguity --- crates/bindings-csharp/Codegen/Module.cs | 215 +++++++++++------------ 1 file changed, 104 insertions(+), 111 deletions(-) diff --git a/crates/bindings-csharp/Codegen/Module.cs b/crates/bindings-csharp/Codegen/Module.cs index ba8b2da6fe..73ea27f562 100644 --- a/crates/bindings-csharp/Codegen/Module.cs +++ b/crates/bindings-csharp/Codegen/Module.cs @@ -26,89 +26,86 @@ public class Module : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { - var tables = context - .SyntaxProvider - .ForAttributeWithMetadataName( - fullyQualifiedMetadataName: "SpacetimeDB.TableAttribute", - predicate: (node, ct) => true, // already covered by attribute restrictions - transform: (context, ct) => - { - var table = (TypeDeclarationSyntax)context.TargetNode; + var tables = context.SyntaxProvider.ForAttributeWithMetadataName( + fullyQualifiedMetadataName: "SpacetimeDB.TableAttribute", + predicate: (node, ct) => true, // already covered by attribute restrictions + transform: (context, ct) => + { + var table = (TypeDeclarationSyntax)context.TargetNode; - var resolvedTable = - (ITypeSymbol?)context.SemanticModel.GetDeclaredSymbol(table) - ?? throw new System.Exception("Could not resolve table"); + var resolvedTable = + (ITypeSymbol?)context.SemanticModel.GetDeclaredSymbol(table) + ?? throw new System.Exception("Could not resolve table"); - var fields = resolvedTable - .GetMembers() - .OfType() - .Where(f => !f.IsStatic) - .Select(f => + var fields = resolvedTable + .GetMembers() + .OfType() + .Where(f => !f.IsStatic) + .Select(f => + { + var indexKind = f.GetAttributes() + .Where(a => + a.AttributeClass?.ToDisplayString() == "SpacetimeDB.ColumnAttribute" + ) + .Select(a => (ColumnAttrs)a.ConstructorArguments[0].Value!) + .SingleOrDefault(); + + if (indexKind.HasFlag(ColumnAttrs.AutoInc)) { - var indexKind = f.GetAttributes() - .Where( - a => - a.AttributeClass?.ToDisplayString() - == "SpacetimeDB.ColumnAttribute" - ) - .Select(a => (ColumnAttrs)a.ConstructorArguments[0].Value!) - .SingleOrDefault(); - - if (indexKind.HasFlag(ColumnAttrs.AutoInc)) + var isValidForAutoInc = f.Type.SpecialType switch { - var isValidForAutoInc = f.Type.SpecialType switch - { - SpecialType.System_Byte - or SpecialType.System_SByte - or SpecialType.System_Int16 - or SpecialType.System_UInt16 - or SpecialType.System_Int32 - or SpecialType.System_UInt32 - or SpecialType.System_Int64 - or SpecialType.System_UInt64 - => true, - SpecialType.None - => f.Type.ToString() switch - { - "System.Int128" or "System.UInt128" => true, - _ => false - }, - _ => false - }; - - if (!isValidForAutoInc) - { - throw new System.Exception( - $"Type {f.Type} is not valid for AutoInc or Identity as it's not an integer." - ); - } + SpecialType.System_Byte + or SpecialType.System_SByte + or SpecialType.System_Int16 + or SpecialType.System_UInt16 + or SpecialType.System_Int32 + or SpecialType.System_UInt32 + or SpecialType.System_Int64 + or SpecialType.System_UInt64 + => true, + SpecialType.None + => f.Type.ToString() switch + { + "System.Int128" or "System.UInt128" => true, + _ => false + }, + _ => false + }; + + if (!isValidForAutoInc) + { + throw new System.Exception( + $"Type {f.Type} is not valid for AutoInc or Identity as it's not an integer." + ); } + } - return ( - Name: f.Name, - Type: SymbolToName(f.Type), - TypeInfo: GetTypeInfo(f.Type), - IndexKind: indexKind - ); - }) - .ToArray(); + return ( + Name: f.Name, + Type: SymbolToName(f.Type), + TypeInfo: GetTypeInfo(f.Type), + IndexKind: indexKind + ); + }) + .ToArray(); - return new - { - Scope = new Scope(table), - Name = table.Identifier.Text, - FullName = SymbolToName(context.SemanticModel.GetDeclaredSymbol(table)!), - Fields = fields, - }; - } - ); + return new + { + Scope = new Scope(table), + Name = table.Identifier.Text, + FullName = SymbolToName(context.SemanticModel.GetDeclaredSymbol(table)!), + Fields = fields, + }; + } + ); tables .Select( (t, ct) => { - var autoIncFields = t.Fields - .Where(f => f.IndexKind.HasFlag(ColumnAttrs.AutoInc)) + var autoIncFields = t.Fields.Where(f => + f.IndexKind.HasFlag(ColumnAttrs.AutoInc) + ) .Select(f => f.Name); var extensions = @@ -197,50 +194,44 @@ or SpecialType.System_UInt64 var tableNames = tables.Select((t, ct) => t.FullName).Collect(); - var reducers = context - .SyntaxProvider - .ForAttributeWithMetadataName( - fullyQualifiedMetadataName: "SpacetimeDB.ReducerAttribute", - predicate: (node, ct) => true, // already covered by attribute restrictions - transform: (context, ct) => - { - var method = (IMethodSymbol) - context.SemanticModel.GetDeclaredSymbol(context.TargetNode)!; + var reducers = context.SyntaxProvider.ForAttributeWithMetadataName( + fullyQualifiedMetadataName: "SpacetimeDB.ReducerAttribute", + predicate: (node, ct) => true, // already covered by attribute restrictions + transform: (context, ct) => + { + var method = (IMethodSymbol) + context.SemanticModel.GetDeclaredSymbol(context.TargetNode)!; - if (!method.ReturnsVoid) - { - throw new System.Exception($"Reducer {method} must return void"); - } + if (!method.ReturnsVoid) + { + throw new System.Exception($"Reducer {method} must return void"); + } - var exportName = (string?) - context - .Attributes - .SingleOrDefault() - ?.ConstructorArguments - .SingleOrDefault() - .Value; + var exportName = (string?) + context + .Attributes.SingleOrDefault() + ?.ConstructorArguments + .SingleOrDefault() + .Value; - return new - { - Name = method.Name, - ExportName = exportName ?? method.Name, - FullName = SymbolToName(method), - Args = method - .Parameters - .Select( - p => - ( - p.Name, - p.Type, - IsDbEvent: p.Type.ToString() - == "SpacetimeDB.Runtime.DbEventArgs" - ) + return new + { + Name = method.Name, + ExportName = exportName ?? method.Name, + FullName = SymbolToName(method), + Args = method + .Parameters.Select(p => + ( + p.Name, + p.Type, + IsDbEvent: p.Type.ToString() == "SpacetimeDB.Runtime.DbEventArgs" ) - .ToArray(), - Scope = new Scope((TypeDeclarationSyntax)context.TargetNode.Parent!) - }; - } - ); + ) + .ToArray(), + Scope = new Scope((TypeDeclarationSyntax)context.TargetNode.Parent!) + }; + } + ); var addReducers = reducers .Select( @@ -293,6 +284,8 @@ class {r.Name}: IReducer {{ using static SpacetimeDB.Runtime; using System.Diagnostics.CodeAnalysis; + using Buffer = SpacetimeDB.RawBindings.Buffer; + static class ModuleRegistration {{ {string.Join("\n", addReducers.Select(r => r.Class))} From 33789f82cd69ba5838fd9f57acc3d7c1f832d1a9 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Tue, 2 Apr 2024 15:39:29 +0100 Subject: [PATCH 3/3] Restore logging redirect --- crates/bindings-csharp/Runtime/bindings.c | 24 +++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/bindings-csharp/Runtime/bindings.c b/crates/bindings-csharp/Runtime/bindings.c index 5aae9f9f53..6d9b4670d9 100644 --- a/crates/bindings-csharp/Runtime/bindings.c +++ b/crates/bindings-csharp/Runtime/bindings.c @@ -1,11 +1,12 @@ -#ifndef EXPERIMENTAL_WASM_AOT #include // #include // #include #include #include +#ifndef EXPERIMENTAL_WASM_AOT #include "driver.h" +#endif #define OPAQUE_TYPEDEF(name, T) \ typedef struct name { \ @@ -23,10 +24,16 @@ OPAQUE_TYPEDEF(BufferIter, uint32_t); #define CSTR(s) (uint8_t*)s, sizeof(s) - 1 -#define IMPORT(ret, name, params, args) \ - __attribute__((import_module("spacetime_7.0"), \ - import_name(#name))) extern ret name##_imp params; \ +#define STDB_EXTERN(name) \ + __attribute__((import_module("spacetime_7.0"), import_name(#name))) extern + +#ifndef EXPERIMENTAL_WASM_AOT +#define IMPORT(ret, name, params, args) \ + STDB_EXTERN(name) ret name##_imp params; \ ret name params { return name##_imp args; } +#else +#define IMPORT(ret, name, params, args) STDB_EXTERN(name) ret name params; +#endif IMPORT(void, _console_log, (LogLevel level, const uint8_t* target, uint32_t target_len, @@ -74,6 +81,7 @@ IMPORT(void, _buffer_consume, (Buffer buf, uint8_t* dst, uint32_t dst_len), (buf, dst, dst_len)); IMPORT(Buffer, _buffer_alloc, (const uint8_t* data, uint32_t len), (data, len)); +#ifndef EXPERIMENTAL_WASM_AOT static MonoClass* ffi_class; #define CEXPORT(name) __attribute__((export_name(#name))) name @@ -210,10 +218,10 @@ int32_t WASI_NAME(fd_write)(__wasi_fd_t fd, const __wasi_ciovec_t* iovs, // Note: this will produce ugly broken output, but there's not much we can // do about it until we have proper line-buffered WASI writer in the core. // It's better than nothing though. - // _console_log_imp((LogLevel){fd == STDERR_FILENO ? /*WARN*/ 1 : /*INFO*/ - // 2}, - // CSTR("wasi"), CSTR(__FILE__), __LINE__, iovs[i].buf, - // iovs[i].buf_len); + _console_log((LogLevel){fd == STDERR_FILENO ? /*WARN*/ 1 : /*INFO*/ + 2}, + CSTR("wasi"), CSTR(__FILE__), __LINE__, iovs[i].buf, + iovs[i].buf_len); *retptr0 += iovs[i].buf_len; } return 0;