diff --git a/bindgen/Utils.h b/bindgen/Utils.h index 00ac8d1..b3ae0fa 100644 --- a/bindgen/Utils.h +++ b/bindgen/Utils.h @@ -116,11 +116,14 @@ static inline std::string replaceChar(const std::string &str, * @return true if given type is of type T or is an alias for type T. */ template static inline bool isAliasForType(Type *type) { - if (isInstanceOf(type)) { - auto *typeDef = dynamic_cast(type); + if (isInstanceOf(type)) { + return true; + } + auto *typeDef = dynamic_cast(type); + if (typeDef) { return isAliasForType(typeDef->getType().get()); } - return isInstanceOf(type); + return false; } #endif // UTILS_H diff --git a/bindgen/ir/Function.cpp b/bindgen/ir/Function.cpp index fb173da..5331961 100644 --- a/bindgen/ir/Function.cpp +++ b/bindgen/ir/Function.cpp @@ -1,5 +1,6 @@ #include "Function.h" #include "../Utils.h" +#include "Struct.h" Parameter::Parameter(std::string name, std::shared_ptr type) : TypeAndName(std::move(name), type) {} @@ -71,3 +72,19 @@ Function::~Function() { delete parameter; } } + +bool Function::isLegalScalaNativeFunction() const { + /* Return type and parameters types cannot be array types because array type + * in this case is always represented as a pointer to element type */ + if (isAliasForType(retType.get()) || + isAliasForType(retType.get())) { + return false; + } + for (const auto ¶meter : parameters) { + if (isAliasForType(parameter->getType().get()) || + isAliasForType(parameter->getType().get())) { + return false; + } + } + return true; +} diff --git a/bindgen/ir/Function.h b/bindgen/ir/Function.h index 0514309..45e5255 100644 --- a/bindgen/ir/Function.h +++ b/bindgen/ir/Function.h @@ -2,6 +2,7 @@ #define SCALA_NATIVE_BINDGEN_FUNCTION_H #include "TypeAndName.h" +#include "TypeDef.h" #include #include #include @@ -27,6 +28,12 @@ class Function { void setScalaName(std::string scalaName); + /** + * @return true if the function does not use values of structs or arrays + * (note: unions are represented as arrays) + */ + bool isLegalScalaNativeFunction() const; + private: std::string getVarargsParameterName() const; diff --git a/bindgen/ir/IR.cpp b/bindgen/ir/IR.cpp index 481f39b..5ac6bad 100644 --- a/bindgen/ir/IR.cpp +++ b/bindgen/ir/IR.cpp @@ -122,7 +122,15 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &s, const IR &ir) { } for (const auto &func : ir.functions) { - s << *func; + if (func->isLegalScalaNativeFunction()) { + s << *func; + } else { + llvm::errs() + << "Warning: Function " << func->getName() + << " is skipped because Scala Native does not support " + "passing structs and arrays by value.\n"; + llvm::errs().flush(); + } } s << "}\n\n"; diff --git a/docs/src/paradox/limitations/index.md b/docs/src/paradox/limitations/index.md index 4ca438f..9277ecd 100644 --- a/docs/src/paradox/limitations/index.md +++ b/docs/src/paradox/limitations/index.md @@ -1,35 +1,47 @@ # Limitations -There are multiple unsupported cases that should be considered when generating bindings: - - 1. Currently bindgen does not support passing structs by value. - For example, it will not be possible to call these two functions from Scala Native code: - - ```c - struct MyStruct { - int a; - }; - - struct MyStruct returnStruct(); - - void handleStruct(struct MyStruct mystr); - ``` - To support such cases one should generate bindings for C wrapper functions that use pointers to structs instead of actual structs. - 2. `#define`s for literals and variables are supported. For other types of `#define`s, - write wrapper functions that return defined values. - - ```c - // Supported - #define ESC 0x1b /* Defines for numerical and string literals. */ - extern const int pi_const; - #define PI pi_const /* Defines aliasing extern variables. */ - - // Not supported (non-exhaustive list) - #define COLS (getenv("COLS") ? atoi(getenv("COLS")) : 80) - #define MAX(a, b) (a > b ? a : b) - ``` - - 3. There is no way to reuse already generated bindings. - Bindgen outputs bindings also for headers that were included in a given header. See @github[#2](#2). - 4. Type qualifiers `const`, `volatile` and `restrict` are not supported. - 5. Extern variables are read-only. See @github[scala-native/scala-native#202](scala-native/scala-native#202). +There are multiple unsupported cases that should be considered when generating bindings. + +## Passing structs by value + +Scala Native does not support passing structs by value, bindgen skips such functions. +```c +struct MyStruct { + int a; +}; + +struct MyStruct returnStruct(); // skipped + +void handleStruct(struct MyStruct mystr); // skipped +``` +To support such cases one should generate bindings for C wrapper functions that use pointers to structs instead of +actual structs. + +## Limited support of `#define`s + +`#define`s for literals and variables are supported. For other types of `#define`s, +write wrapper functions that return defined values. + +```c +// Supported +#define ESC 0x1b /* Defines for numerical and string literals. */ +extern const int pi_const; +#define PI pi_const /* Defines aliasing extern variables. */ + +// Not supported (non-exhaustive list) +#define COLS (getenv("COLS") ? atoi(getenv("COLS")) : 80) +#define MAX(a, b) (a > b ? a : b) +``` + +## Reusing generated bindings + +There is no way to reuse already generated bindings. +Bindgen outputs bindings also for headers that were included in a given header. See @github[#2](#2). + +## Type qualifiers + +Type qualifiers `const`, `volatile` and `restrict` are not supported. + +## Updating extern variables + +Extern variables are read-only. See @github[scala-native/scala-native#202](scala-native/scala-native#202). diff --git a/tests/samples/Function.h b/tests/samples/Function.h index f2df768..dc6ca21 100644 --- a/tests/samples/Function.h +++ b/tests/samples/Function.h @@ -4,3 +4,21 @@ char one_arg(int a); void *two_args(float a, int b); double anonymous_args(float, int); double variadic_args(double a, char *varArgs, ...); + +struct s { + int val; +}; + +void acceptsStructValue(struct s); // function is skipped with warning + +typedef struct s s; + +s returnsStructValue(); // function is skipped with warning + +union u { + int a; +}; + +void acceptsUnionValue(union u); // function is skipped with warning + +void acceptsArray(int[10]); // it's okay because the type is pointer to int diff --git a/tests/samples/Function.scala b/tests/samples/Function.scala index cfddbef..3f5f63c 100644 --- a/tests/samples/Function.scala +++ b/tests/samples/Function.scala @@ -6,10 +6,32 @@ import scala.scalanative.native._ @native.link("bindgentests") @native.extern object Function { + type struct_s = native.CStruct1[native.CInt] + type s = struct_s + type union_u = native.CArray[Byte, native.Nat._4] def no_args(): native.CInt = native.extern def void_arg(): native.CFloat = native.extern def one_arg(a: native.CInt): native.CChar = native.extern def two_args(a: native.CFloat, b: native.CInt): native.Ptr[Byte] = native.extern def anonymous_args(anonymous0: native.CFloat, anonymous1: native.CInt): native.CDouble = native.extern def variadic_args(a: native.CDouble, varArgs: native.CString, varArgs0: native.CVararg*): native.CDouble = native.extern + def acceptsArray(anonymous0: native.Ptr[native.CInt]): Unit = native.extern } + +import Function._ + +object FunctionHelpers { + + implicit class struct_s_ops(val p: native.Ptr[struct_s]) extends AnyVal { + def `val`: native.CInt = !p._1 + def `val_=`(value: native.CInt): Unit = !p._1 = value + } + + def struct_s()(implicit z: native.Zone): native.Ptr[struct_s] = native.alloc[struct_s] + + implicit class union_u_pos(val p: native.Ptr[union_u]) extends AnyVal { + def a: native.Ptr[native.CInt] = p.cast[native.Ptr[native.CInt]] + def a_=(value: native.CInt): Unit = !p.cast[native.Ptr[native.CInt]] = value + } +} + diff --git a/tests/samples/PrivateMembers.h b/tests/samples/PrivateMembers.h index 244f96c..227b865 100644 --- a/tests/samples/PrivateMembers.h +++ b/tests/samples/PrivateMembers.h @@ -38,7 +38,7 @@ void __privateFunction(); // functions that should not be removed: __private_type *getPrivateType(); -void usesPrivateUnion(union __unionWithPrivateName); +void usesPrivateUnion(union __unionWithPrivateName *); void usesPrivateStruct(struct structWithPrivateType *, struct normalStruct *); void usesPrivateEnum(enum __privateEnum *); diff --git a/tests/samples/PrivateMembers.scala b/tests/samples/PrivateMembers.scala index 6817edf..1d488ac 100644 --- a/tests/samples/PrivateMembers.scala +++ b/tests/samples/PrivateMembers.scala @@ -19,7 +19,7 @@ object PrivateMembers { type privateStructWithTypedefPtr = native.Ptr[struct_privateStructWithTypedef] def getTypeThatUsesPrivateTypes(): pid_t = native.extern def getPrivateType(): native.Ptr[__private_type] = native.extern - def usesPrivateUnion(anonymous0: union___unionWithPrivateName): Unit = native.extern + def usesPrivateUnion(anonymous0: native.Ptr[union___unionWithPrivateName]): Unit = native.extern def usesPrivateStruct(anonymous0: native.Ptr[struct_structWithPrivateType], anonymous1: native.Ptr[struct_normalStruct]): Unit = native.extern def usesPrivateEnum(anonymous0: native.Ptr[enum___privateEnum]): Unit = native.extern } diff --git a/tests/samples/ReservedWords.h b/tests/samples/ReservedWords.h index c26f8da..0ad2886 100644 --- a/tests/samples/ReservedWords.h +++ b/tests/samples/ReservedWords.h @@ -20,7 +20,7 @@ union lazy { typedef union lazy lazy; -type with(match sealed, var implicit, lazy forSome); +type *with(match sealed, var implicit, lazy *forSome); typedef match def; typedef struct { diff --git a/tests/samples/ReservedWords.scala b/tests/samples/ReservedWords.scala index 30a5091..54de0c3 100644 --- a/tests/samples/ReservedWords.scala +++ b/tests/samples/ReservedWords.scala @@ -16,7 +16,7 @@ object ReservedWords { type `def` = `match` type struct_finally = native.CStruct2[`def`, `lazy`] type `finally` = struct_finally - def `with`(`sealed`: `match`, `implicit`: native.Ptr[`match`], `forSome`: `lazy`): `type` = native.extern + def `with`(`sealed`: `match`, `implicit`: native.Ptr[`match`], `forSome`: native.Ptr[`lazy`]): native.Ptr[`type`] = native.extern def `implicit`(`type`: native.Ptr[`finally`]): `match` = native.extern def _1(): Unit = native.extern }