diff --git a/Project.toml b/Project.toml index 42cc5a98..483879ee 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Legate" uuid = "1238f2cf-6593-4d60-9aca-2f5364e49909" -version = "0.1.2" +version = "0.1.3" [deps] CxxWrap = "1f15a43c-97ca-5a2a-ae31-89f07a497df4" diff --git a/lib/legate_jl_wrapper/VERSION b/lib/legate_jl_wrapper/VERSION index 3252f073..e3082644 100644 --- a/lib/legate_jl_wrapper/VERSION +++ b/lib/legate_jl_wrapper/VERSION @@ -1 +1 @@ -25.10.3 \ No newline at end of file +25.10.5 \ No newline at end of file diff --git a/lib/legate_jl_wrapper/include/wrapper.inl b/lib/legate_jl_wrapper/include/wrapper.inl index 9570310c..7b0683e6 100644 --- a/lib/legate_jl_wrapper/include/wrapper.inl +++ b/lib/legate_jl_wrapper/include/wrapper.inl @@ -18,6 +18,7 @@ */ #include "legate.h" +#include "legate/io/hdf5/interface.h" #include "legate/mapping/machine.h" #include "legate/runtime/runtime.h" #include "legate/timing/timing.h" @@ -64,6 +65,18 @@ inline bool has_started() { return legate::has_started(); } */ inline bool has_finished() { return legate::has_finished(); } +/** + * @ingroup legate_wrapper + * @brief Block until all pending Legate tasks have completed. + */ +inline void runtime_sync() { + Runtime::get_runtime()->issue_execution_fence(true); +} + +/** + * @ingroup legate_wrapper + * @brief Provide number of runtime processors. + */ inline int32_t num_procs() { return legate::Runtime::get_runtime()->get_machine().count(); } @@ -345,4 +358,18 @@ inline uint64_t time_nanoseconds() { } } // namespace time +namespace hdf5 { +inline LogicalArray read_h5(const std::string& file_path, + const std::string& dataset_name) { + return legate::io::hdf5::from_file(std::filesystem::path(file_path), + dataset_name); +} + +inline void write_h5(const LogicalArray& array, const std::string& file_path, + const std::string& dataset_name) { + legate::io::hdf5::to_file(array, std::filesystem::path(file_path), + dataset_name); +} +} // namespace hdf5 + } // namespace legate_wrapper diff --git a/lib/legate_jl_wrapper/src/module.cpp b/lib/legate_jl_wrapper/src/module.cpp index 8c030a04..7505c772 100644 --- a/lib/legate_jl_wrapper/src/module.cpp +++ b/lib/legate_jl_wrapper/src/module.cpp @@ -192,7 +192,13 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { .method("data", &LogicalArray::data) // returns LogicalStore .method("get_physical_array", &LogicalArray::get_physical_array) // return PhysicalArray - .method("unbound", &LogicalArray::unbound); + .method("unbound", &LogicalArray::unbound) + .method("shape", [](const LogicalArray& arr) { + auto s = arr.data().shape(); + std::vector result; + for (int i = 0; i < arr.dim(); i++) result.push_back(s[i]); + return result; + }); mod.add_type("AutoTask") .method("add_input", static_cast( @@ -228,6 +234,7 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { mod.method("get_runtime", &legate_wrapper::runtime::get_runtime); mod.method("has_started", &legate_wrapper::runtime::has_started); mod.method("has_finished", &legate_wrapper::runtime::has_finished); + mod.method("runtime_sync", &legate_wrapper::runtime::runtime_sync); /* tasking */ mod.method("align", &legate_wrapper::tasking::align); mod.method("domain_from_shape", &legate_wrapper::tasking::domain_from_shape); @@ -257,6 +264,9 @@ JLCXX_MODULE define_julia_module(jlcxx::Module& mod) { mod.method("time_microseconds", &legate_wrapper::time::time_microseconds); mod.method("time_nanoseconds", &legate_wrapper::time::time_nanoseconds); + /* hdf5 */ + mod.method("_read_h5", &legate_wrapper::hdf5::read_h5); + mod.method("_write_h5", &legate_wrapper::hdf5::write_h5); mod.method("num_procs", &legate_wrapper::runtime::num_procs); wrap_ufi(mod); diff --git a/src/api/data.jl b/src/api/data.jl index 7a2383b1..3caec341 100644 --- a/src/api/data.jl +++ b/src/api/data.jl @@ -285,6 +285,47 @@ function get_ptr(arr::PhysicalStore) return _get_ptr(CxxWrap.CxxPtr(arr)) # cxxwrap call end +""" + h5read(path::String, name::String) -> LogicalArray + +Read a dataset from an HDF5 file into a LogicalArray. + +# Arguments +- `path`: Path to the HDF5 file. +- `name`: Name of the dataset to read. +""" +function h5read(path::String, name::String) + impl = _read_h5(path, name) # cxxwrap call + ndim = Int(dim(impl)) + shp_h5 = Tuple(Int.(shape(impl))) + # HDF5 stores in C (row-major) order; Julia is column-major. + # HDF5.jl bridges this by reversing dimensions, so we do the same. + shp_jl = ndim > 1 ? reverse(shp_h5) : shp_h5 + T = code_type_map[Int(code(type(impl)))] + return LogicalArray{T,ndim}(impl, shp_jl) +end + +""" + h5write(path::String, name::String, array::LogicalArray) + +Write a LogicalArray to a dataset in an HDF5 file. + +# Arguments +- `path`: Path to the HDF5 file. +- `name`: Name of the dataset to write. +- `array`: The array to write. +""" +function h5write(path::String, name::String, array::LogicalArray{T,1}) where {T} + _write_h5(array.handle, path, name) +end + +# mimic HDF5.jl ordering. Julia -> column-major && HDF5 -> c-order +# reshape Legate array keeping bytes same +function h5write(path::String, name::String, array::LogicalArray{T,N}) where {T,N} + arr = Array{T,N}(array) + la_rev = LogicalArray(reshape(arr, reverse(size(arr)))) + _write_h5(la_rev.handle, path, name) +end function partition_by_tiling(store::LogicalStore{T,N}, tile_shape) where {T,N} impl = partition_by_tiling(store.handle, to_cxx_vector(tile_shape)) # cxxwrap call return LogicalStorePartition{T,N}(impl) @@ -293,4 +334,4 @@ end function partition_by_tiling(store::LogicalStore{T,N}, tile_shape, color_shape) where {T,N} impl = partition_by_tiling(store.handle, to_cxx_vector(tile_shape), to_cxx_vector(color_shape)) # cxxwrap call return LogicalStorePartition{T,N}(impl) -end \ No newline at end of file +end diff --git a/src/api/runtime.jl b/src/api/runtime.jl index 056a3f4b..48bc43cd 100644 --- a/src/api/runtime.jl +++ b/src/api/runtime.jl @@ -40,6 +40,15 @@ Check whether the Legate runtime has finished. """ has_finished +""" + runtime_sync() + +Block until all pending Legate tasks have completed. + +Useful before reading files written by `h5write` or other async operations. +""" +runtime_sync + """ create_library(name::String) -> Library diff --git a/src/utilities/attach.jl b/src/utilities/attach.jl index 897a619b..1d2b3025 100644 --- a/src/utilities/attach.jl +++ b/src/utilities/attach.jl @@ -78,7 +78,7 @@ end # conversion from Base Julia array to LogicalArray function (::Type{<:LogicalArray{A}})(arr::Array{B}) where {A,B} dims = Base.size(arr) - out = Legate.create_array(A, dims) + out = Legate.create_array(collect(Int64, dims), A) attached = Legate.attach_external(arr) copyto!(out, attached) return out @@ -86,7 +86,7 @@ end function (::Type{<:LogicalArray})(arr::Array{B}) where {B} dims = Base.size(arr) - out = Legate.create_array(B, dims) + out = Legate.create_array(collect(Int64, dims), B) attached = Legate.attach_external(arr) copyto!(out, attached) return out diff --git a/test/Project.toml b/test/Project.toml index 92aa2989..63f2137a 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,5 +1,7 @@ [deps] CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" +Legate = "1238f2cf-6593-4d60-9aca-2f5364e49909" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [extras] diff --git a/test/runtests.jl b/test/runtests.jl index df4ef3d1..faafbdca 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,6 @@ using Legate using Test +using HDF5 const VERBOSE = get(ENV, "VERBOSE", "1") != "0" const run_gpu_tests = (get(ENV, "GPUTESTS", "1") != "0") && (get(ENV, "NO_CUDA", "OFF") != "ON") @@ -15,6 +16,8 @@ if run_gpu_tests end end +include("tests/hdf5.jl") + # include("tests/tasking.jl") # if run_gpu_tests # include("tests/tasking_gpu.jl") diff --git a/test/tests/hdf5.jl b/test/tests/hdf5.jl new file mode 100644 index 00000000..65f13bca --- /dev/null +++ b/test/tests/hdf5.jl @@ -0,0 +1,73 @@ +using HDF5 + +# Write a Julia array to HDF5 using HDF5.jl, read it back with Legate, and compare. +function test_hdf5_read(T::Type, shape::Tuple) + path = tempname() * ".h5" + dataset = "data" + original = rand(T, shape) + + h5open(path, "w") do f + write(f, dataset, original) + end + + legate_arr = Legate.h5read(path, dataset) + result = Array(legate_arr) + + rm(path; force=true) + return result == original +end + +# Write a LogicalArray with Legate.h5write, read it back with HDF5.jl, and compare. +function test_hdf5_write(T::Type, shape::Tuple) + path = tempname() * ".h5" + dataset = "data" + original = rand(T, shape...) + + legate_arr = Legate.LogicalArray(original) + Legate.h5write(path, dataset, legate_arr) + Legate.runtime_sync() + + result = h5open(path, "r") do f + read(f, dataset) + end + + rm(path; force=true) + return result == original +end + +# Write with Legate, read back with Legate (roundtrip). +function test_hdf5_roundtrip(T::Type, shape::Tuple) + path = tempname() * ".h5" + dataset = "data" + original = rand(T, shape...) + + legate_arr = Legate.LogicalArray(original) + Legate.h5write(path, dataset, legate_arr) + Legate.runtime_sync() + + result_arr = Legate.h5read(path, dataset) + result = Array(result_arr) + + rm(path; force=true) + return result == original +end + +@testset verbose = true "HDF5 Interoperability" begin + for T in Base.uniontypes(Legate.SUPPORTED_NUMERIC_TYPES) + @testset "Type: $T" begin + for shape in [(10,), (4, 5), (3, 4, 5)] + @testset "Shape: $shape" begin + @testset "HDF5.jl write → Legate read" begin + @test test_hdf5_read(T, shape) + end + @testset "Legate write → HDF5.jl read" begin + @test test_hdf5_write(T, shape) + end + @testset "Legate roundtrip" begin + @test test_hdf5_roundtrip(T, shape) + end + end + end + end + end +end