Skip to content

Commit

Permalink
C#: add experimental NativeAOT-LLVM support (#713)
Browse files Browse the repository at this point in the history
* Add experimental NativeAOT-LLVM support

* Fix codegen ambiguity

* Restore logging redirect
  • Loading branch information
RReverser committed Apr 3, 2024
1 parent 56887f5 commit 02d0428
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 146 deletions.
246 changes: 128 additions & 118 deletions crates/bindings-csharp/Codegen/Module.cs
Expand Up @@ -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<IFieldSymbol>()
.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();
var fields = resolvedTable
.GetMembers()
.OfType<IFieldSymbol>()
.Where(f => !f.IsStatic)
.Select(f =>
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 =
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -286,26 +277,45 @@ class {r.Name}: IReducer {{
// <auto-generated />
#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;
using Buffer = SpacetimeDB.RawBindings.Buffer;
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
}}
"
);
Expand Down
15 changes: 5 additions & 10 deletions crates/bindings-csharp/Runtime/Module.cs
Expand Up @@ -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";
Expand Down Expand Up @@ -276,7 +273,6 @@ public static void RegisterReducer(IReducer reducer)
bool anonymous = false
) => module.SetTypeRef<T>(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
Expand All @@ -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,
Expand Down
8 changes: 7 additions & 1 deletion crates/bindings-csharp/Runtime/RawBindings.cs
Expand Up @@ -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.
Expand Down

1 comment on commit 02d0428

@github-actions
Copy link

@github-actions github-actions bot commented on 02d0428 Apr 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Criterion benchmark results

Criterion benchmark report

YOU SHOULD PROBABLY IGNORE THESE RESULTS.

Criterion is a wall time based benchmarking system that is extremely noisy when run on CI. We collect these results for longitudinal analysis, but they are not reliable for comparing individual PRs.

Go look at the callgrind report instead.

empty

db on disk new latency old latency new throughput old throughput
sqlite 💿 416.5±1.20ns 411.8±1.66ns - -
sqlite 🧠 408.9±1.50ns 405.8±2.21ns - -
stdb_raw 💿 713.2±1.33ns 705.1±1.91ns - -
stdb_raw 🧠 679.7±1.51ns 687.8±2.06ns - -

insert_1

db on disk schema indices preload new latency old latency new throughput old throughput

insert_bulk

db on disk schema indices preload count new latency old latency new throughput old throughput
sqlite 💿 u32_u64_str btree_each_column 2048 256 515.0±0.55µs 508.9±0.99µs 1941 tx/sec 1964 tx/sec
sqlite 💿 u32_u64_str unique_0 2048 256 138.1±0.24µs 132.3±0.77µs 7.1 Ktx/sec 7.4 Ktx/sec
sqlite 💿 u32_u64_u64 btree_each_column 2048 256 425.3±0.45µs 413.3±0.60µs 2.3 Ktx/sec 2.4 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 2048 256 130.8±12.25µs 121.9±0.51µs 7.5 Ktx/sec 8.0 Ktx/sec
sqlite 🧠 u32_u64_str btree_each_column 2048 256 446.6±0.52µs 442.6±0.56µs 2.2 Ktx/sec 2.2 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 2048 256 121.9±0.83µs 117.6±0.40µs 8.0 Ktx/sec 8.3 Ktx/sec
sqlite 🧠 u32_u64_u64 btree_each_column 2048 256 374.2±0.56µs 361.1±0.46µs 2.6 Ktx/sec 2.7 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 2048 256 108.5±0.63µs 104.7±0.86µs 9.0 Ktx/sec 9.3 Ktx/sec
stdb_raw 💿 u32_u64_str btree_each_column 2048 256 712.3±1.15µs 713.7±0.52µs 1403 tx/sec 1401 tx/sec
stdb_raw 💿 u32_u64_str unique_0 2048 256 618.6±0.97µs 623.6±1.23µs 1616 tx/sec 1603 tx/sec
stdb_raw 💿 u32_u64_u64 btree_each_column 2048 256 402.1±0.48µs 401.4±0.42µs 2.4 Ktx/sec 2.4 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 2048 256 358.9±0.36µs 361.0±0.21µs 2.7 Ktx/sec 2.7 Ktx/sec
stdb_raw 🧠 u32_u64_str btree_each_column 2048 256 559.0±0.63µs 561.8±0.28µs 1789 tx/sec 1779 tx/sec
stdb_raw 🧠 u32_u64_str unique_0 2048 256 467.7±0.74µs 471.1±0.67µs 2.1 Ktx/sec 2.1 Ktx/sec
stdb_raw 🧠 u32_u64_u64 btree_each_column 2048 256 370.1±0.13µs 370.6±0.61µs 2.6 Ktx/sec 2.6 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 2048 256 329.8±0.66µs 330.4±0.18µs 3.0 Ktx/sec 3.0 Ktx/sec

iterate

db on disk schema indices new latency old latency new throughput old throughput
sqlite 💿 u32_u64_str unique_0 21.8±0.13µs 21.0±0.07µs 44.7 Ktx/sec 46.5 Ktx/sec
sqlite 💿 u32_u64_u64 unique_0 19.6±0.06µs 19.9±0.09µs 49.9 Ktx/sec 49.0 Ktx/sec
sqlite 🧠 u32_u64_str unique_0 20.3±0.18µs 19.7±0.13µs 48.2 Ktx/sec 49.5 Ktx/sec
sqlite 🧠 u32_u64_u64 unique_0 18.4±0.06µs 18.8±0.10µs 53.0 Ktx/sec 52.1 Ktx/sec
stdb_raw 💿 u32_u64_str unique_0 17.7±0.00µs 17.7±0.00µs 55.2 Ktx/sec 55.2 Ktx/sec
stdb_raw 💿 u32_u64_u64 unique_0 14.8±0.00µs 14.9±0.00µs 65.8 Ktx/sec 65.7 Ktx/sec
stdb_raw 🧠 u32_u64_str unique_0 17.7±0.00µs 17.7±0.00µs 55.3 Ktx/sec 55.3 Ktx/sec
stdb_raw 🧠 u32_u64_u64 unique_0 14.8±0.00µs 14.8±0.00µs 65.9 Ktx/sec 65.9 Ktx/sec

find_unique

db on disk key type preload new latency old latency new throughput old throughput

filter

db on disk key type index strategy load count new latency old latency new throughput old throughput
sqlite 💿 string index 2048 256 64.8±0.15µs 68.6±0.22µs 15.1 Ktx/sec 14.2 Ktx/sec
sqlite 💿 u64 index 2048 256 63.4±0.33µs 64.3±0.14µs 15.4 Ktx/sec 15.2 Ktx/sec
sqlite 🧠 string index 2048 256 64.2±0.54µs 65.5±0.18µs 15.2 Ktx/sec 14.9 Ktx/sec
sqlite 🧠 u64 index 2048 256 59.0±0.20µs 60.9±0.10µs 16.6 Ktx/sec 16.0 Ktx/sec
stdb_raw 💿 string index 2048 256 5.7±0.00µs 5.7±0.00µs 171.5 Ktx/sec 171.1 Ktx/sec
stdb_raw 💿 u64 index 2048 256 5.5±0.00µs 5.5±0.00µs 177.5 Ktx/sec 177.0 Ktx/sec
stdb_raw 🧠 string index 2048 256 5.7±0.00µs 5.7±0.02µs 172.6 Ktx/sec 171.9 Ktx/sec
stdb_raw 🧠 u64 index 2048 256 5.5±0.00µs 5.5±0.00µs 178.8 Ktx/sec 178.1 Ktx/sec

serialize

schema format count new latency old latency new throughput old throughput
u32_u64_str bflatn_to_bsatn_fast_path 100 4.0±0.00µs 4.0±0.00µs 24.1 Mtx/sec 23.8 Mtx/sec
u32_u64_str bflatn_to_bsatn_slow_path 100 3.7±0.01µs 3.7±0.01µs 25.5 Mtx/sec 25.8 Mtx/sec
u32_u64_str bsatn 100 2.5±0.00µs 2.5±0.15µs 38.9 Mtx/sec 38.6 Mtx/sec
u32_u64_str json 100 5.1±0.03µs 5.3±0.06µs 18.8 Mtx/sec 17.9 Mtx/sec
u32_u64_str product_value 100 676.0±0.92ns 649.8±1.01ns 141.1 Mtx/sec 146.8 Mtx/sec
u32_u64_u64 bflatn_to_bsatn_fast_path 100 1335.5±0.82ns 1431.6±14.88ns 71.4 Mtx/sec 66.6 Mtx/sec
u32_u64_u64 bflatn_to_bsatn_slow_path 100 2.9±0.00µs 2.9±0.01µs 32.8 Mtx/sec 32.7 Mtx/sec
u32_u64_u64 bsatn 100 1758.4±34.59ns 1774.7±39.03ns 54.2 Mtx/sec 53.7 Mtx/sec
u32_u64_u64 json 100 3.4±0.03µs 3.1±0.03µs 28.1 Mtx/sec 30.9 Mtx/sec
u32_u64_u64 product_value 100 599.7±1.28ns 600.2±6.39ns 159.0 Mtx/sec 158.9 Mtx/sec
u64_u64_u32 bflatn_to_bsatn_fast_path 100 1106.3±0.89ns 1113.4±6.79ns 86.2 Mtx/sec 85.7 Mtx/sec
u64_u64_u32 bflatn_to_bsatn_slow_path 100 2.9±0.04µs 2.9±0.00µs 32.7 Mtx/sec 32.8 Mtx/sec
u64_u64_u32 bsatn 100 1748.8±2.84ns 1660.1±33.79ns 54.5 Mtx/sec 57.4 Mtx/sec
u64_u64_u32 json 100 3.3±0.05µs 3.4±0.15µs 28.7 Mtx/sec 28.2 Mtx/sec
u64_u64_u32 product_value 100 629.7±0.19ns 601.9±0.15ns 151.5 Mtx/sec 158.4 Mtx/sec

stdb_module_large_arguments

arg size new latency old latency new throughput old throughput
64KiB 90.5±8.94µs 90.0±6.01µs - -

stdb_module_print_bulk

line count new latency old latency new throughput old throughput
1 45.6±7.34µs 48.1±2.93µs - -
100 359.7±4.11µs 367.2±83.87µs - -
1000 3.0±0.03ms 3.0±0.05ms - -

remaining

name new latency old latency new throughput old throughput
sqlite/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 47.4±0.13µs 46.7±0.34µs 20.6 Ktx/sec 20.9 Ktx/sec
sqlite/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 41.6±0.26µs 47.3±17.55µs 23.5 Ktx/sec 20.7 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 40.9±0.19µs 40.2±0.19µs 23.9 Ktx/sec 24.3 Ktx/sec
sqlite/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 35.4±0.07µs 35.7±0.19µs 27.6 Ktx/sec 27.3 Ktx/sec
stdb_module/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 2.9±0.03ms 3.0±0.00ms 349 tx/sec 337 tx/sec
stdb_module/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 1898.3±10.66µs 1927.3±56.61µs 526 tx/sec 518 tx/sec
stdb_raw/💿/update_bulk/u32_u64_str/unique_0/load=2048/count=256 1095.0±3.60µs 1097.2±0.73µs 913 tx/sec 911 tx/sec
stdb_raw/💿/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 715.3±0.72µs 723.4±0.82µs 1397 tx/sec 1382 tx/sec
stdb_raw/🧠/update_bulk/u32_u64_str/unique_0/load=2048/count=256 904.4±0.95µs 908.8±0.57µs 1105 tx/sec 1100 tx/sec
stdb_raw/🧠/update_bulk/u32_u64_u64/unique_0/load=2048/count=256 657.3±0.79µs 664.8±0.24µs 1521 tx/sec 1504 tx/sec

Please sign in to comment.