Skip to content

Skip functions that pass arrays or structs by value #119

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

Merged
merged 3 commits into from
Jul 16, 2018
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
9 changes: 6 additions & 3 deletions bindgen/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename T> static inline bool isAliasForType(Type *type) {
if (isInstanceOf<TypeDef>(type)) {
auto *typeDef = dynamic_cast<TypeDef *>(type);
if (isInstanceOf<T>(type)) {
return true;
}
auto *typeDef = dynamic_cast<TypeDef *>(type);
if (typeDef) {
return isAliasForType<T>(typeDef->getType().get());
}
return isInstanceOf<T>(type);
return false;
}

#endif // UTILS_H
17 changes: 17 additions & 0 deletions bindgen/ir/Function.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "Function.h"
#include "../Utils.h"
#include "Struct.h"

Parameter::Parameter(std::string name, std::shared_ptr<Type> type)
: TypeAndName(std::move(name), type) {}
Expand Down Expand Up @@ -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<Struct>(retType.get()) ||
isAliasForType<Union>(retType.get())) {
return false;
}
for (const auto &parameter : parameters) {
if (isAliasForType<Struct>(parameter->getType().get()) ||
isAliasForType<Union>(parameter->getType().get())) {
return false;
}
}
return true;
}
7 changes: 7 additions & 0 deletions bindgen/ir/Function.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define SCALA_NATIVE_BINDGEN_FUNCTION_H

#include "TypeAndName.h"
#include "TypeDef.h"
#include <llvm/Support/raw_ostream.h>
#include <string>
#include <vector>
Expand All @@ -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;

Expand Down
10 changes: 9 additions & 1 deletion bindgen/ir/IR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Copy link
Member

Choose a reason for hiding this comment

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

It would be nice to have a test for this. As I mentioned in another PR it might be preferable to do such testing in a separate test class.

llvm::errs().flush();
}
}

s << "}\n\n";
Expand Down
78 changes: 45 additions & 33 deletions docs/src/paradox/limitations/index.md
Original file line number Diff line number Diff line change
@@ -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).
18 changes: 18 additions & 0 deletions tests/samples/Function.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
22 changes: 22 additions & 0 deletions tests/samples/Function.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

2 changes: 1 addition & 1 deletion tests/samples/PrivateMembers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);

Expand Down
2 changes: 1 addition & 1 deletion tests/samples/PrivateMembers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion tests/samples/ReservedWords.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion tests/samples/ReservedWords.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down