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

Compilation strategy for polymorphism #60

Open
thosakwe opened this issue Apr 22, 2018 · 6 comments
Open

Compilation strategy for polymorphism #60

thosakwe opened this issue Apr 22, 2018 · 6 comments
Assignees

Comments

@thosakwe
Copy link
Contributor

thosakwe commented Apr 22, 2018

In Bonobo, we have support for inheritance. Obviously, C does not. What C does have, though, are overloads for different types.

To compile methods that take parameters of types that have multiple forms, we can generate overloads.

For example, take the type Int. This is not 100% a perfect example, because integers are primitives, but Int in reality can have different sizes: Int8, Int16, Int32, Int64, all of which map to specific C types.

Say you have a function, add, in a module foo, that takes two Ints:

fn add(a: Int, b: Int): Int => a + b;

Creating overloads that take the different variants of Int, compiled to C, is not that difficult:

int32_t add(int8_t a, int8_t b);
int32_t add(int8_t a, int16_t b);
int32_t add(int8_t a, int32_t b);
int32_t add(int8_t a, int64_t b);
int32_t add(int16_t a, int8_t b);
int32_t add(int16_t a, int16_t b);
int32_t add(int16_t a, int32_t b);
// and so on...

This way, the C compiler would not compile about the types of the passed integers.

@thosakwe
Copy link
Contributor Author

However, the question is: How to determine the correct return type? Obviously it makes no sense for each overload to return int32_t.

@thosakwe
Copy link
Contributor Author

What would be smart is for BonoboTypes, or at least number types to have a size member, so that the compiler could easily deduce the right return type.

If the signature is:

fn add3(a: Int, b: Int, c: Int): Int

Then for each combination (ex. int32_t, int8_t, int16_t), the return type would be the variant of the largest size.

Thus, we can create the following:

int8_t add(int8_t a, int8_t b);
int16_t add(int8_t a, int16_t b);
int32_t add(int8_t a, int32_t b);
int64_t add(int8_t a, int64_t b);
int16_t add(int16_t a, int8_t b);
int16_t add(int16_t a, int16_t b);
int32_t add(int16_t a, int32_t b);
// and so on...

int16_t add3(int8_t a, int16_t b, int8_t c);
int16_t add3(int8_t a, int16_t b, int16_t c);
int32_t add3(int8_t a, int16_t b, int32_t c);
int64_t add3(int8_t a, int16_t b, int64_t c);
// and so on...

@thosakwe
Copy link
Contributor Author

class Size {
  final int c, llvm;
  const Size({@required this.c, @required this.llvm});
}

class BonoboType {
  Size get size;
}

@thosakwe
Copy link
Contributor Author

thosakwe commented Apr 22, 2018

The strategy of determining the correct parameters works nicely for numbers, but what about non-numbers?

type Automobile { fn run(fuel: Double): Void }

class Car : Automobile {
  run(fuel: Double) => print('Vroom!')
}

class Plane : Automobile {
  run(fuel: Double) => print('Whoosh!')
}

The generated C might look something like this:

typedef struct {} Automobile;
 
void Automobile_run(Car* this, double fuel) {
  Car_run(this, fuel);
} 

void Automobile_run(Plane* this, double fuel) {
  Plane_run(this, fuel);
}

typedef struct {
  Automobile* super;
} Car;

Car* Car_new() {
  Car* instance = (Car*) malloc(sizeof(Car));
  return instance;
}

void Car_run(Car* this, double fuel) {
  print("Vroom!");
}

typedef struct {
  Automobile* super;
} Plane;

Plane* Plane_new() {
  Plane* instance = (Plane*) malloc(sizeof(Plane));
  return instance;
}

void Plane_run(Plane* this, double fuel) {
  print("Whoosh!");
}

@thosakwe
Copy link
Contributor Author

The question is, what about a return type? Obviously, structs can't be inherited, but using void* is to haphazard.

What if:

typedef struct {
  Car *car = NULL;
  Plane *plane = NULL;
} Automobile;

This solution works well, IMO.

@thosakwe
Copy link
Contributor Author

thosakwe commented Apr 22, 2018

fn carFactory: Car => Car::new()

Generates:

Automobile* carFactory() {
  Car *car = Car_new();
  Automobile *automobile = (Automobile*) malloc(sizeof(Automobile));
  automobile->car = car;
  return car;
}

The compiler is definitely going to have to do a lot of heavy lifting, but I feel that this is a viable way to handle classes and polymorphism in the compiler.

@thosakwe thosakwe self-assigned this Apr 23, 2018
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

No branches or pull requests

1 participant