Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C API Support #1626

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from
Open

C API Support #1626

wants to merge 25 commits into from

Conversation

youwuyou
Copy link

@youwuyou youwuyou commented Jun 18, 2024

This PR adds the initial support of the C API implementation for interoperating with other programming languages, such as Fortran, Python, and Julia. The language linkage with key word extern "c" is used to enable the interfacing.

C API Design:

  • Conveys an uniform naming convention as specified here
  • Usage of macros and std::dynamic_pointer_cast to avoid code duplication

Changes:

  • Added header file include/ginkgo/c_api.h (define macros, declares function/structs) and source file core/c_api.cpp (concretize definitions of declared functions that internally calls ginkgo)
  • Necessary changes in CMake files cmake/install_helpers.cmake and core/CMakeLists.txt to include the C API

Example Usage:

  • Calling ginkgo from C through the C API can be seen here
  • The Julia package Ginkgo.jl calls ginkgo routines with ccall through the from Clang generated Julia side api.jl, which internally executes compiled dynamic libraries of ginkgo. It supports both locally compiled dynamic libraries or ones that are hosted under an artifact package ginkgo_jll.jl.

@youwuyou youwuyou self-assigned this Jun 18, 2024
@youwuyou youwuyou requested a review from a team June 18, 2024 10:49
Copy link
Member

@MarcelKoch MarcelKoch left a comment

Choose a reason for hiding this comment

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

Thanks for the work, this looks already quite good! I left some remarks below.

The big points I want to bring up are:

  • executor function names. I'm not sure _cpu, _gpu parts are necessary for executor function names. From the rest of the name, it is already implied if it is for a cpu or gpu executor or not. Also this implies a type hierarchy level that is not really present in ginkgo at the moment.
  • naming consistency. Should the prefix be ginkgo or gko. I might tend to gko, since that matches our namespace.
  • error handling. A lot of Ginkgo functions can throw, and I'm unsure what will happen on the C side in that case. I guess the standard approach would be to catch the exception and return error codes instead.

* @param _name Name of the datatype of the array
*
*/
#define DEFINE_ARRAY_OVERLOAD(_ctype, _cpptype, _name) \
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure the define macros should be placed in the header. They add information that is unnecessary for users, and which they should not rely on.

Copy link
Collaborator

Choose a reason for hiding this comment

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

True, since they are only required in c_api.cpp, they could also be defined there.

Copy link
Member

Choose a reason for hiding this comment

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

In addition to putting them in c_api.cpp, I would also recommend prefixing all of them with GKO_.

\
size_t ginkgo_array_##_name##_get_size(gko_array_##_name array_st_ptr) \
{ \
return (*array_st_ptr).arr.get_size(); \
Copy link
Member

Choose a reason for hiding this comment

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

Am I missing something or could the (*struct).member syntax be simplified:

Suggested change
return (*array_st_ptr).arr.get_size(); \
return array_st_ptr->arr.get_size(); \

Comment on lines +822 to +823
void ginkgo_linop_apply(gko_linop A_st_ptr, gko_linop b_st_ptr,
gko_linop x_st_ptr);
Copy link
Member

Choose a reason for hiding this comment

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

how does this work with b and x as vectors? Is there a way to convert the ginkgo_matrix_dense_f32 to a gko_linop?

* parameter
*
*/
struct gko_deferred_factory_parameter_st;
Copy link
Member

Choose a reason for hiding this comment

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

Why does this use gko_ instead of ginkgo_? Same for the other instances below.

Copy link
Author

Choose a reason for hiding this comment

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

I was thinking to use gko_ for struct objects and ginkgo_ for actual methods, and thus I used gko_linop, gko_deferred_factory_parameter, gko_executor etc.

Copy link
Member

Choose a reason for hiding this comment

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

I think it's more common for c libraries to have a single prefix that they stick to.

Copy link
Author

Choose a reason for hiding this comment

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

Makes sense! Probably we should opt for using just gko_ then?

Copy link
Member

Choose a reason for hiding this comment

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

That is what I would prefer.

gko_linop x_st_ptr);

//-------------------- Iterative solvers -----------------------------
gko_linop ginkgo_linop_cg_preconditioned_f64_create(
Copy link
Member

Choose a reason for hiding this comment

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

I think I would prefer

Suggested change
gko_linop ginkgo_linop_cg_preconditioned_f64_create(
gko_linop ginkgo_solver_cg_preconditioned_f64_create(

since that matches again the namespace.
Also, I would leave out preconditioned from the name, since there is no non-preconditioned version.

/* ----------------------------------------------------------------------
* C memory management
* ---------------------------------------------------------------------- */
void c_char_ptr_free(char* ptr);
Copy link
Member

Choose a reason for hiding this comment

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

what is this doing here? It doesn't seem to be related to ginkgo.

Copy link
Author

Choose a reason for hiding this comment

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

I was experimenting a bit around the I/O interfacing and tried to have dense matrices printed using _write_mtx (defined under macro).

Copy link
Member

Choose a reason for hiding this comment

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

If you need this as a public function, it must be prefixed (for functions, you seem to use ginkgo_).

include/ginkgo/c_api.h Show resolved Hide resolved
Comment on lines +143 to +151
if (auto omp_exec = std::dynamic_pointer_cast<gko::OmpExecutor>(
exec_st_ptr->shared_ptr)) {
return new gko_executor_st{
gko::CudaExecutor::create(device_id, exec_st_ptr->shared_ptr)};
} else if (auto reference_exec =
std::dynamic_pointer_cast<gko::ReferenceExecutor>(
exec_st_ptr->shared_ptr)) {
return new gko_executor_st{
gko::CudaExecutor::create(device_id, exec_st_ptr->shared_ptr)};
Copy link
Member

Choose a reason for hiding this comment

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

could be combined

Suggested change
if (auto omp_exec = std::dynamic_pointer_cast<gko::OmpExecutor>(
exec_st_ptr->shared_ptr)) {
return new gko_executor_st{
gko::CudaExecutor::create(device_id, exec_st_ptr->shared_ptr)};
} else if (auto reference_exec =
std::dynamic_pointer_cast<gko::ReferenceExecutor>(
exec_st_ptr->shared_ptr)) {
return new gko_executor_st{
gko::CudaExecutor::create(device_id, exec_st_ptr->shared_ptr)};
if ( std::dynamic_pointer_cast<gko::OmpExecutor>(
exec_st_ptr->shared_ptr) ||
std::dynamic_pointer_cast<gko::ReferenceExecutor>(
exec_st_ptr->shared_ptr)) {
return new gko_executor_st{
gko::CudaExecutor::create(device_id, exec_st_ptr->shared_ptr)};

same for hip and sycl

}
}

size_t ginkgo_executor_cuda_get_num_devices()
Copy link
Member

Choose a reason for hiding this comment

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

this should return an int instead of size_t, since that's what the ginkgo function returns.
Or is the size_t required from the julia side.
This also applies to many of the executor functions.

/* ----------------------------------------------------------------------
* Library functions for creating arrays and array operations in GINKGO
* ---------------------------------------------------------------------- */
DECLARE_ARRAY_OVERLOAD(int16_t, int16_t, i16)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think the int16 overload is useful. It can't be used to create matrices, or basically anything relevant. Instead, a size_t might be more helpful, although this is also not necessary for the functions currently available in the C-API.

Copy link
Collaborator

@greole greole left a comment

Choose a reason for hiding this comment

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

Just some quick comments

core/c_api.cpp Outdated Show resolved Hide resolved
include/ginkgo/c_api.h Outdated Show resolved Hide resolved
Comment on lines +6 to +7
#ifndef C_API_H
#define C_API_H
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
#ifndef C_API_H
#define C_API_H
#ifndef GKO_C_API_H
#define GKO_C_API_H

Maybe better to prefix with GKO to avoid potential collisions.

* @param _name Name of the datatype of the array
*
*/
#define DEFINE_ARRAY_OVERLOAD(_ctype, _cpptype, _name) \
Copy link
Collaborator

Choose a reason for hiding this comment

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

True, since they are only required in c_api.cpp, they could also be defined there.

* its construction by removing the repetitive typing of array's name.
*
* @param _ctype_value Type name of the element type in C
*
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
*

We rather keep all the @param together instead of separating them by an empty line.

Copy link
Author

Choose a reason for hiding this comment

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

Thank you! I will edit the ones that do not match the format

* construction by removing the repetitive typing of array's name.
*
* @param _ctype Type name of the element type in C
*
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
*

We rather keep the @param together instead of separating them by an empty line.

youwuyou and others added 2 commits June 24, 2024 16:15
remove redundant author comments

Co-authored-by: Gregor Olenik <gregor.olenik@web.de>
remove redundant author comments

Co-authored-by: Gregor Olenik <gregor.olenik@web.de>
Copy link
Member

@thoasm thoasm left a comment

Choose a reason for hiding this comment

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

Good work in general!
Most of my comments are about names and moving things to the CPP file.
Also, you seem to mix the prefix gko_ (mostly structs) and ginkgo_ (functions).
I don't think we need to make a distinction here,
I would recommend to use gko_ for everything (and GKO_ for macros), as you have already discussed with @MarcelKoch in the comments.

I need to read up a bit more on C APIs to give my final review, so consider this 1/2.

//
// SPDX-License-Identifier: BSD-3-Clause

#include <../include/ginkgo/c_api.h>
Copy link
Member

Choose a reason for hiding this comment

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

<> means it's in one of the header directories, so this should be:

Suggested change
#include <../include/ginkgo/c_api.h>
#include <ginkgo/c_api.h>

Alternatively, if you want to specify the relative path, use regular quotations:

#include "../include/ginkgo/c_api.h"

* @param _name Name of the datatype of the array
*
*/
#define DEFINE_ARRAY_OVERLOAD(_ctype, _cpptype, _name) \
Copy link
Member

Choose a reason for hiding this comment

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

In addition to putting them in c_api.cpp, I would also recommend prefixing all of them with GKO_.

* @brief A build instruction for declaring gko::array<T> in the C API header
* file
*/
#define DECLARE_ARRAY_OVERLOAD(_ctype, _cpptype, _name) \
Copy link
Member

Choose a reason for hiding this comment

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

This macro must have a GKO_ prefix because it's a public-facing header.

* @param _name Name of the datatype of the dense matrix
*
*/
#define DEFINE_DENSE_OVERLOAD(_ctype, _cpptype, _name) \
Copy link
Member

Choose a reason for hiding this comment

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

Prefix with GKO_ and put it into the c_api.cpp file.

* @brief A build instruction for declaring gko::matrix::Dense<T> in the C API
* header file
*/
#define DECLARE_DENSE_OVERLOAD(_ctype, _cpptype, _name) \
Copy link
Member

Choose a reason for hiding this comment

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

GKO_ Prefix

* @param _name_dense Name of the datatype of the dense matrix for apply
* function
*/
#define DEFINE_CSR_OVERLOAD(_ctype_value, _ctype_index, _cpptype_value, \
Copy link
Member

Choose a reason for hiding this comment

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

GKO_ and move it to c_api.cpp

* @brief A build instruction for declaring gko::matrix::Csr<Tv,Ti> in the C API
* header file
*/
#define DECLARE_CSR_OVERLOAD(_ctype_value, _ctype_index, _cpptype_value, \
Copy link
Member

Choose a reason for hiding this comment

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

GKO_ prefix

/* ----------------------------------------------------------------------
* C memory management
* ---------------------------------------------------------------------- */
void c_char_ptr_free(char* ptr);
Copy link
Member

Choose a reason for hiding this comment

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

If you need this as a public function, it must be prefixed (for functions, you seem to use ginkgo_).

@thoasm
Copy link
Member

thoasm commented Aug 23, 2024

I also have a general question: Should we add some form of CI to check if the API is still working?

@MarcelKoch MarcelKoch added this to the Ginkgo 1.9.0 milestone Aug 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants