Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - lerno

Pages: 1 [2] 3 4 ... 17
16
General Discussion / Unsigned conversions
« on: February 20, 2019, 10:51:52 PM »
In C, comparing a i32 with an u32 will do the somewhat counterintuitive thing of promoting the i32 to u32.

This leads to the somewhat confusing situation that the following C code:

Code: [Select]
  int a = -1;
  unsigned int b = 1;
  unsigned short c = 1;
  if (a < b) printf("Less!\n");
  if (a > b) printf("More!\n");
  if (a < c) printf("Less short!\n");
  if (a > c) printf("More short!\n");

Prints:

More!
Less short!

The following rules might help:

1. The comparison operations < > <= >= are not allowed between signed and unsigned where the promotion would be to unsigned (that is, where the unsigned type is has greater or equal bit size of the signed). So i32 < u16 is fine, but not i32 < u32 or i16 < u32.

2. >= 0 is always an error for unsigned numbers. This would prevent the following bug: for (unsigned i = size; i >= 0; i--). The analyser should detect this condition as always being true and creating an *error* for it.

A more drastic change that could be considered instead of (1) would be to always do sign promotion whenever there is a comparison with signed and unsigned values.

So !i32 < u64) would cast both sides to i64. The downside of that (or any similar scheme) is that it differs from C in a critical, rarely learned part of the language. It feels dangerous. Prohibiting conversions feel more "safe". Note that comparisons (except for ==) is where the conversion from signed to unsigned is where the danger is. For example cast<i32>(-1) + cast<u64>(12) will still yield 11 as expected due to the behaviour of unsigned arithmetics.

17
Implementation Details / Re: C2 in C
« on: February 12, 2019, 01:17:55 AM »
My pure C implementation of C2 is moving forward slowly (at https://github.com/lerno/titanos - look at the development branch). It's using LLVM exclusively (no C gen).

I'm focusing on the areas where C2C is weak today, so that would both be LLVM and constant folding.

The compiler uses BigInt for integer constants (and really should do the same for floats, but right now it doesn't). I recently worked on the implicit casts and in order to make sense out of it I've changed a bit from C's implicit casting, mostly following Zig.

To recap C's casting rules:

  • with two operands where at least one is float, upgrade everything to the biggest float (float -> double -> long double)
  • if no float is found, int conversion resumes.
  • If both types are signed or both types are unsigned: promote to the largest of the (eg short -> int -> long etc) (unsigned short -> unsigned int -> unsigned long etc)
  • If they have different sign and the signed version can represent represent all numbers of the unsigned (e.g. u16 - i32), promote to the signed version.
  • If it's not possible to represent it, promote to the unsigned version of the type. (e.g. u32 - i32 => u32, u64 - i32 => u64)

For constant folding I've followed these rules instead:

  • Float conversion as with C
  • Folding an operation with bigint and bit limited int will convert the bigint to that bit size. If it's not possible to convert the constant, a compile time error will occur. E.g. const i8 c = 1; const i32 d = c + 200; is illegal since 200 cannot be converted to i8 without loss.
  • If two non bigints are folded, it's converted to the biggest type (like in C), for signed / unsigned conversion: if the unsigned constant may be contained in the signed type, then the conversion is valid, otherwise it is a compile time error. (e.g. cast<u8>(200) + cast<i8>(1) is a compile time error)
  • In addition, constant overflow is a compile time error. So cast<u8>(200) + cast<u8>(100) would be a compile time error as 300 does not fit in u8.

In order to make unsigned <=> signed conversions I'm thinking of some very simple lossy conversion, like

Code: [Select]
i8 a = -1;
u8 b = @ucast(a); // Bitcast of a
a = @scast(b); // Bitcast of b

Maybe even add a special assignment like (placeholder syntax!):

Code: [Select]
b u= a; // same as b = @ucast(a)
if (b u== a) ...; // same as if (b == @ucast(a)) ...
a s= b; // same as a = @scast(a)
if (b s== a) ...; // same as if (@scast(b) == a) ...

18
Implementation Details / Re: CTC (partial, full, none)
« on: February 12, 2019, 12:47:59 AM »
Of what use is CTC partial? I don't really see it.

19
Ideas / Explicit non-null-ness
« on: February 02, 2019, 12:28:24 PM »
I suggest we reuse & arg to mean a pointer that is guaranteed to be non-null.

Consider the following methods:

Code: [Select]
Foo& foo();
Foo* foo2();
void bar(Foo& f);
void bar2(Foo* f);

Unlike in C++, both Foo& and Foo* are pointer, the former guaranteed to be not null.

Code: [Select]
Foo *f1 = foo(); // Non null to nullable ok
Foo &f2 = foo2(); // Nullable to non null not allowed

A check allows conversion:

Code: [Select]
Foo *f = foo2();
assert(f);
Foo &f2 = f;

Or:

Code: [Select]
Foo *f = foo2();
Foo &f2 = f ? f : foo();

With the elvis operator:

Code: [Select]
Foo &f = foo2() ?: foo();

Using pointer without nullcheck is a warning:

Code: [Select]
Foo* f = foo2();
return f.a; // warn, f may be null.

Solution is adding the assert test, or supress null warning with an attribute

Code: [Select]
Foo *f = foo2();
return f.a @(notnull);

20
Implementation Details / Re: C2 in C
« on: February 01, 2019, 10:37:40 PM »
I know, but the parser is sometimes hardcoded to identify types (or sometimes even just built in types). This should be relaxed in order to allow more compile time evaluation – which in turn opens up for semantic macros.

21
Ideas / Re: Require explicit (un)initialization.
« on: January 31, 2019, 12:43:19 AM »
I like the reuse of void.

In regards to int a; initialized to zero implicitly - I understand the thought that this is counter to whar one usually expects. On the other habd this makes static and local variables behave exactly the same way and it simplifies how one reasons about struct init.

For example, it is more natural to expect Foo f = { .one_field = 1 } to actually zero out all other fields in the struct if we have implicit zeroing.

An alternative would be to disallow int a; completely, requiring it to either be initialized to ”void” or some value. On the other hand, that would then also need to be true for göobals, and I don’t know if this added requirement for statics is something that would be appreciated.

22
Ideas / Require explicit (un)initialization.
« on: January 30, 2019, 06:07:18 PM »
Instead of warning on non-initialized variables and explicitly initializing, consider the following change:

Code: [Select]
int a; // a initialized to 0, as if it was static.
int a = 10; // a initialized to 10
int a = uninitialized; // a not initialized, warning if used before later assignment.

Alternatives to uninitialized:

Code: [Select]
int a = ---;
int a = *;
int a = ?;

23
Ideas / Re: Macro-system design
« on: January 30, 2019, 10:48:55 AM »
All of the above ties into the generics proposal. The generics being a way to create packets of macros basically, but has space advantages.

24
Ideas / Re: Macro-system design
« on: January 30, 2019, 10:47:08 AM »
Allowing nested macros could allow us to do recursive macro resolution, which opens up compile time execution.

25
Ideas / Re: Macro-system design
« on: January 30, 2019, 10:41:04 AM »
In the case of the inline body I think this would be reasonable:

Code: [Select]
public macro foreach(thelist, @body(Element *) ) {
   Element* iname = thelist.first;
   while (iname != nil) {
      @body(iname);
      iname = iname.next;
   }
}

Or a version that is more flexible:
Code: [Select]
public macro foreach(thelist, @body(typeof(thelist.first)) ) {
   typeof(thelist.first) iname = thelist.first;
   while (iname != nil) {
      @body(iname);
      iname = iname.next;
   }
}

Usage:

Code: [Select]
foreach(list, Element *i) { // <- Note type declaration!
   i.print();
}

Since type is going to appear very often, we could make a shortcut for it, like $@ as prefix meaning "typeof".

We then get

Code: [Select]
public macro foreach(thelist, @body($@thelist.first) ) {
   $@thelist.first iname = thelist.first;
   while (iname != nil) {
      @body(iname);
      iname = iname.next;
   }
}

Possibly we could even write the code like this:

Code: [Select]
public macro foreach(thelist, @body($element_type) ) {
   $element_type iname = thelist.first;
   while (iname != nil) {
      @body(iname);
      iname = iname.next;
   }
}

In this case $element_type works like "auto", but is also assigned the type, which then can be referred to in the signature.

26
Ideas / Re: Macro-system design
« on: January 30, 2019, 10:33:30 AM »
1. I think it's a bad idea to syntax check the macros using types (the "List" type) there. In order to show constraints, prefer contracts like in the Go2 proposal. Such a contract can include type if one wants to.

Possibility (1) inline require/contract

Code: [Select]
public macro foreach(thelist, iname) {
   @require {
      thelist.first;
      thelist.first.next == thelist.first;
   }
   Element* iname = thelist.first;
   while (iname != nil) {
       // macro body should be here, How to specify?
      iname = iname.next;
   }
}

We can then continue making this even more general:

Code: [Select]
public macro foreach(thelist, iname) {
   @require {
      thelist.first;
      thelist.first.next == thelist.first;
   }
   typeof(iname.first) iname = thelist.first;
   while (iname != nil) {
       // macro body should be here, How to specify?
      iname = iname.next;
   }
}

I suggest the use of compile time variables to clean up things:

Code: [Select]
public macro foreach(thelist, iname) {
   @require {
      thelist.first;
      thelist.first.next == thelist.first;
   }
   $element_type = typeof(iname.first); // Not visible after compilation
   $element_type iname = thelist.first;
   while (iname != nil) {
       // macro body should be here, How to specify?
      iname = iname.next;
   }
}

The alternative to inline contracts are external ones:

Code: [Select]
type List contract {
   self.first;
   self.first.next == self.first;
}

public macro foreach(@List thelist, iname) {
   $element_type = typeof(iname.first); // Not visible after compilation
   $element_type iname = thelist.first;
   while (iname != nil) {
       // macro body should be here, How to specify?
      iname = iname.next;
   }
}


27
Ideas / Re: Operation's priorities - more difficulties then benefits
« on: January 24, 2019, 04:26:35 PM »
Changing operator meanings to mean something completely different makes zero sense.

Note that operator precedence is already changed.

28
Ideas / Re: Readability of keywords "uint32", "float64" etc
« on: January 23, 2019, 11:12:46 PM »
I've recently discovered that i32 can be significantly faster than "register sized" int.

29
Implementation Details / C2 in C
« on: January 23, 2019, 01:12:38 AM »
I'm implementing a version of the C2 parser in C right now. Not all that much to see. It parses all of C2 as far as I know (...probably a quite a few bugs...) and mostly follows C2C's architecture. Reading recipe files and a some other stuff and also the semantic analysis is just started.

However the idea is that the parser is prepared from the first for some compile time evaluation. For example, this would be completely valid and resolved during compile time:

Code: [Select]
Foo foo[sizeof(i32) > 0 ? 1 : 2];

I'm intending to have everything constant folded by default to open up for some lightweight compile time resolution. I suspect it will help with future semantic macros and generics.

That said it's far from being done.

30
Ideas / Re: Syntax of const pointers
« on: January 02, 2019, 02:45:37 AM »
I've thought about this some more and I'm actually leaning towards 5 now, since that would make things easier when reading.

Do we want to write:
Code: [Select]
int volatile x = 10;
Foo const foo = { ... };
int * const y = &x;

// or
volatile int x = 10;
const Foo foo = { ... };
int const* y = &x;

Maybe it's just me, but in the simple case then the prefix keyword really rules.

And for the complex ones, maybe () isn't such a bad idea:

// 4
Code: [Select]
int* volatile x = 10;     // volatile pointer to int
Foo* const foo = { ... }; // const pointer to Foo
Foo const* foo = { ... }; // pointer to const Foo
int* const y = &x;        // const pointer to int
int const* y = &x;        // pointer to const int

// 5
Code: [Select]
int volatile* x = 10;     // volatile pointer to int
Foo const* foo = { ... }; // const pointer to Foo
const Foo* foo = { ... }; // pointer to const Foo
int const* y = &x;        // const pointer to int
const int* y = &x;        // pointer to const int

// 1
Code: [Select]
volatile (int*) x = 10;     // volatile pointer to int
const (Foo*) foo = { ... }; // const pointer to Foo
(const Foo)* foo = { ... }; // pointer to const Foo
const (int*) y = &x;        // const pointer to int
(const int)* y = &x;        // pointer to const int

Note that it looks very different visually depending on where you put the "*". (1) shines when the star is put next to the type.

One more thing to consider should be the parsing. For example: an expression starting with '(' can be deducted to always be an expression on C, but with ( ) in the type names, that would not be obvious.

Pages: 1 [2] 3 4 ... 17