tomhrr / dale

Lisp-flavoured C
BSD 3-Clause "New" or "Revised" License
1.02k stars 48 forks source link

Defining extern-c functions and how cast works #148

Closed porky11 closed 6 years ago

porky11 commented 7 years ago

When I define a c function, i would do it this way:

(def cSomeFunction (fn extern-c void ((x int)))) ;the original c function

(def T (struct intern ((a int)))) ;my own type for parameter

(def some-function (fn extern-c void ((x T)) ;the better typed version of the c function
  (cSomeFunction (cast x T)))

When T has the same bit-representation as the type in the c function, this also works:

(def cSomeFunction (fn extern-c void ((x T))))

This normally should work for struct types with a single field.

Another example where the first version works is casting array pointers:

(def cSomeFunction (fn extern-c void ((x (p int)))))

(def some-function (fn extern-c void ((x (p (array-of 2 int))))
  (cSomeFunction (cast x (p int))))

but the second version by just defining the c-function with the array-pointer-type does not work in this case, This means the pointer gets automatically converted when casting, but I don't think, it is already documented, when implicit casting occurs. I think, that number-types don't change their numeric values, while casting, pointer- and array-types automatically (de-)reference values, and other values don't change bit-representation, but it should be documented somewhere, how this works.

tomhrr commented 7 years ago

I'm not sure I follow why the second version does not work in the second case. For example:

$ cat test.dt
(def printf
  (fn extern-c int ((str (p (const char))) ...)))

(def strlen
  (fn extern-c size ((str (p (array-of 5 char))))))

(def main (fn extern-c int (void)
  (def s (var auto (array-of 5 char) (array #\a #\b #\c #\d #\NULL)))
  (def x (var auto \ (strlen (# s))))
  (printf "%u\n" x)
  0))
$ ./dalec test.dt && ./a.out
4

That is, the above defines strlen with an array pointer type, and works as expected.

The implicit casting rules are documented in https://github.com/tomhrr/dale/blob/master/doc/1-1-introduction.md, though it's possible that this section is inaccurate in some way.

porky11 commented 7 years ago

I know, the implicit casting rules are documented. I am more interested in the implicit conversion of bit-representation when casting types. In c int* and 'int[i]' are the same type, but if your example not (array-of i int) but (p (array-of i int)) seems to work similar to (p int), but when casting an array-type to a pointer, it works, so this will dereference pointer for example.

tomhrr commented 7 years ago

If I understand correctly: there is no implicit conversion of bit-representation. The reason (p (array-of i int)) can be cast to (p int) and still work is that the address of an array has the same value as the address of the first element. The only difference between ($ arr 0) and (# arr) is the type.

porky11 commented 7 years ago

but also (array-of i int) can be cast to (p int) but I thought the bit representations are different in that case (one is the pointer value, one is the bit-representaitons of the numbers). Do you mean, a variable of (array-of i int) and of (p (array-of i int)) are both pointers to the first array element? (that would be weird, because the pointer-type should point to something of the pointed type) and also when casting float to int for example the bit representation change, because the format of these numbers is different. (even llvm uses different functions for casting only bit-representation (bitcast) or casting between number-types (fptosi))

tomhrr commented 7 years ago

but also (array-of i int) can be cast to (p int) but I thought the bit representations are different in that case (one is the pointer value, one is the bit-representations of the numbers).

If an array variable is passed as an argument to a core form or another function, including cast, it decays into a (p {type}), pointing to the first element of the array. The exceptions to this are (should be) the same as in C.

Do you mean, a variable of (array-of i int) and of (p (array-of i int)) are both pointers to the first array element? (that would be weird, because the pointer-type should point to something of the pointed type)

No, not quite. A variable of (array-of i int) is not a pointer to the first array element, but it does decay into that when it is used, with some exceptions. The value of its decayed form is the same as the value of taking its address, though the types are different:

$ cat test.dt
(import cstdio)

(def main (fn extern-c int (void)
  (def x (var auto (array-of 2 int) (array 1 2)))
  (printf "%p %p\n" x (# x))
  (printf "%p %p\n" (p+ x 1) (p+ (# x) 1))
  0))
$ ./dalec test.dt && ./a.out
0x7fffffffeab8 0x7fffffffeab8
0x7fffffffeabc 0x7fffffffeac0

This is why the cast in the original second example works: the pointer-to-array can be cast to a pointer-to-first-element-of-array, and work as expected, without any change to the bits.

and also when casting float to int for example the bit representation change, because the format of these numbers is different. (even llvm uses different functions for casting only bit-representation (bitcast) or casting between number-types (fptosi))

Right, I see what you mean now in this respect. I'll look at adding a separate section to properly document how things are cast.