Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions MEOS.NET.Tests/ParallelArrayTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using MEOS.NET.Types;

namespace MEOS.NET.Tests
{
/// <summary>
/// Arrays a MEOS function reads in parallel are counted once, so the method
/// takes both and passes the one length — and states that they agree, since
/// a shorter one is read past the end of and MEOS cannot see it happen.
/// </summary>
[TestClass]
public class ParallelArrayTests : MeosTest
{
private static Text[] Texts(params string[] parts)
{
Text[] texts = new Text[parts.Length];
for (int i = 0; i < parts.Length; i++)
{
texts[i] = Text.In(parts[i])!;
}

return texts;
}

[TestMethod]
public void AJsonbIsBuiltFromItsKeysAndItsValues()
{
Jsonb? made = Jsonb.MakeTwoArg(Texts("a", "b"), Texts("one", "two"));

Assert.IsNotNull(made);
Assert.AreEqual("{\"a\": \"one\", \"b\": \"two\"}", made!.ToString());
}

[TestMethod]
public void ArraysOfDifferentLengthsAreRefusedBeforeTheCall()
{
Assert.ThrowsException<ArgumentException>(
() => Jsonb.MakeTwoArg(Texts("a", "b", "c"), Texts("one", "two")));
}
}
}
3 changes: 3 additions & 0 deletions MEOS.NET/Functions/Meos.Native.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5109,6 +5109,9 @@ private static partial class Native
[return: MarshalAs(UnmanagedType.U1)]
internal static partial bool GeomAzimuth(IntPtr gs1, IntPtr gs2, IntPtr result);

[LibraryImport(DllPath, EntryPoint = "geom_area", StringMarshalling = StringMarshalling.Utf8)]
internal static partial double GeomArea(IntPtr gs);

[LibraryImport(DllPath, EntryPoint = "geom_length", StringMarshalling = StringMarshalling.Utf8)]
internal static partial double GeomLength(IntPtr gs);

Expand Down
3 changes: 3 additions & 0 deletions MEOS.NET/Functions/Meos.meos_geo.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ public static double GeogPerimeter(IntPtr gs, bool use_spheroid)
public static bool GeomAzimuth(IntPtr gs1, IntPtr gs2, IntPtr result)
=> SafeExecution<bool>(() => Native.GeomAzimuth(gs1, gs2, result));

public static double GeomArea(IntPtr gs)
=> SafeExecution<double>(() => Native.GeomArea(gs));

public static double GeomLength(IntPtr gs)
=> SafeExecution<double>(() => Native.GeomLength(gs));

Expand Down
3 changes: 3 additions & 0 deletions MEOS.NET/Types/Geometry.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public class Geometry : Geo
{
internal Geometry(IntPtr ptr) : base(ptr) { }

public double Area()
=> Meos.GeomArea(this.Ptr);

public double? Azimuth(Geo gs2)
{
IntPtr _result = Marshal.AllocHGlobal(8);
Expand Down
33 changes: 33 additions & 0 deletions MEOS.NET/Types/Jsonb.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,5 +294,38 @@ public long ToInt64()
}
}

public static Jsonb? MakeTwoArg(Text[] keys, Text[] values)
{
if (keys.Length != values.Length)
{
throw new ArgumentException(
"keys and values are read in step, so they hold the same number of elements.");
}

IntPtr[] _keysValues = new IntPtr[keys.Length];
for (int i = 0; i < keys.Length; i++)
{
_keysValues[i] = keys[i].Ptr;
}

GCHandle _keys = GCHandle.Alloc(_keysValues, GCHandleType.Pinned);
IntPtr[] _valuesValues = new IntPtr[values.Length];
for (int i = 0; i < values.Length; i++)
{
_valuesValues[i] = values[i].Ptr;
}

GCHandle _values = GCHandle.Alloc(_valuesValues, GCHandleType.Pinned);
try
{
return MEOSFactory.WrapJsonb(Meos.JsonbMakeTwoArg(_keys.AddrOfPinnedObject(), _values.AddrOfPinnedObject(), keys.Length));
}
finally
{
_keys.Free();
_values.Free();
}
}

}
}
43 changes: 35 additions & 8 deletions tools/objectgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ def __init__(self, name: str, ret: str, params: list[tuple[str, str]],
out_params: list[tuple[str, int, str]] | None = None,
structs: list[tuple[str, str]] | None = None,
scalar_arrays: list[str] | None = None,
length_out: str | None = None, byte_buffer: bool = False):
length_out: str | None = None, byte_buffer: bool = False,
parallel: list[list[str]] | None = None):
self.name = name
self.ret = ret
self.params = params
Expand All @@ -297,11 +298,13 @@ def __init__(self, name: str, ret: str, params: list[tuple[str, str]],
# whether that buffer is the answer itself.
self.length_out = length_out
self.byte_buffer = byte_buffer
# Arrays the catalog counts once, so the method reads them in step.
self.parallel = parallel or []

def needs_a_body(self) -> bool:
"""Whether the call needs anything allocated, pinned or read around it."""
return bool(self.structs or self.arrays or self.scalar_arrays
or self.out_params or self.length_out
or self.out_params or self.length_out or self.parallel
or (self.ret.startswith("(") and self.ret.endswith(")")))


Expand Down Expand Up @@ -554,10 +557,16 @@ def method_for(self, cls: str, entry: dict) -> Method | None:
return None
ret_type, ret_expr = ret

# The length is the array's own, so it leaves the C# signature.
count_of = {codegen.csharp_param_name(a["lengthFrom"]["name"]):
codegen.csharp_param_name(a["param"])
for a in input_arrays}
# The length is the array's own, so it leaves the C# signature. Arrays
# the catalog gives ONE length are read in parallel and counted once, so
# the method takes the length from the first of them and states that the
# rest agree — nothing in MEOS can see a shorter one, and it reads past
# the end of it.
count_of: dict[str, list[str]] = {}
for a in input_arrays:
count_of.setdefault(
codegen.csharp_param_name(a["lengthFrom"]["name"]), []).append(
codegen.csharp_param_name(a["param"]))

if answers_existence:
# The `bool` is not an answer: what MEOS wrote is.
Expand All @@ -584,7 +593,7 @@ def method_for(self, cls: str, entry: dict) -> Method | None:
continue
if pname in count_of:
cast = "" if cs_type == "int" else f"({cs_type}) "
args.append(f"{cast}{count_of[pname]}.Length")
args.append(f"{cast}{count_of[pname][0]}.Length")
continue
if pname in counted:
pointee = clean(counted[pname])
Expand Down Expand Up @@ -629,7 +638,8 @@ def method_for(self, cls: str, entry: dict) -> Method | None:
body = f"{call}|>({read})"
return Method(pascal(oo), ret_type, sig, body, static,
arrays, outs, structs, scalar_arrays, length_out,
ret_type == "byte[]?")
ret_type == "byte[]?",
[names for names in count_of.values() if len(names) > 1])

def inherited_names(self, cls: str) -> set[tuple]:
names: set[tuple] = set()
Expand Down Expand Up @@ -696,6 +706,23 @@ def call_body(self, method: Method) -> list[str]:
read, and everything allocated is freed however the method leaves.
"""
setup, before, teardown = [], [], []
for names in method.parallel:
# MEOS reads these arrays in step off one count, so a shorter one is
# read past the end of. Nothing on the MEOS side can see it.
first, rest = names[0], names[1:]
test = " || ".join(
f"{ident(first)}.Length != {ident(other)}.Length" for other in rest)
named = ident(names[0]) if len(names) == 1 else (
", ".join(ident(n) for n in names[:-1]) + " and " + ident(names[-1]))
setup += [
f" if ({test})",
" {",
" throw new ArgumentException(",
f' "{named} are read in step, so they hold '
'the same number of elements.");',
" }",
"",
]
for name, struct in method.structs:
setup.append(
f" IntPtr {scratch(name)} = "
Expand Down
Loading