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

Can't compile cup program which has a function which returns a struct #9

Open
Weltspear opened this issue Jul 27, 2022 · 16 comments
Open

Comments

@Weltspear
Copy link
Contributor

I tried to compile the following program:

struct A{
	a: int;
	b: int;
	c: int;
}

fn a(): A{
	let a_: A;
	a_.a = 1;
	a_.b = 2;
	a_.c = 2;
	return a_;
}


fn main(){

}

Compiler tells me:

A
compiler/codegen.cup:66:9: Unsupported type size
@mustafaquraish
Copy link
Owner

This is a known limitation, and there are currently no plans (by me at least) of passing around structs as values to/from functions. The reason for this is so that all function return values can fit in a single x86_64 register.

Allowing passing structs as arguments / as return values would necessitate figuring out how (and where) to properly allocate the space in the caller, and then copying the whole struct into that location.

If this is something you're interested in working on, I'm happy to provide any assistance I can, but I am personally more focussed on other projects at the moment to work on this myself.

@Weltspear
Copy link
Contributor Author

Okay, maybe I'll try to play around with that, but I'm not really an assembly programmer or something so idk.

@Weltspear
Copy link
Contributor Author

Weltspear commented Jul 27, 2022

Would it make sense to push struct fields to the stack instead of passing a struct on the register?

@mustafaquraish
Copy link
Owner

Would it make sense to push struct fields to the stack instead of passing a struct on the register?

Yep. let's assume the following example:

fn get_a(): A {
    let obj: A;
    ...
    return obj;
}

fn foo() {
    let a_in_foo: A = get_a();
}

Here, we'd want to do something like:

  1. Allocate space for a_in_foo in the stack frame for foo
  2. Pass an extra (hidden) parameter to get_a, which represents &a_in_foo (the address)
  3. Have return obj copy over the struct directly into a_in_foo using the address.

There are some cases to be careful of, which I'm not exactly sure what the best way to handle is, such as:

fn foo(): A {  return get_a(); }
fn bar(): A {  return foo(); }
fn baz(): A {  return bar(); }

One way to deal with this would be to allocate / copy an intermediate object in each function, but that seems very wasteful. It would be nice to have some way to "figure out" where the object actually needs to be allocated, and then just pass around pointers.

Obviously that is more complex, and doesn't need to all be implemented at once. Just make sure that any design you pick leaves room for improvements like these to be made in the future, and that at the very least such situations are handled correctly, even if it is inefficient.

@Weltspear
Copy link
Contributor Author

A bit offtopic question but can I share ideas/suggestions in issue tracker?

@mustafaquraish
Copy link
Owner

A bit offtopic question but can I share ideas/suggestions in issue tracker?

Sure.

@Weltspear
Copy link
Contributor Author

Struct copying for var to var cannot be done too?

@mustafaquraish
Copy link
Owner

Struct copying for var to var cannot be done too?

It would be nice to support that as well. I didn't mention it because that part would be fairly straightforward, we just copy the fields from the location of the first variable to the other.

@Weltspear
Copy link
Contributor Author

Weltspear commented Jul 30, 2022

Struct copying for var to var cannot be done too?

I've just noticed that you can do something like that:

import "std/memory.cup"

struct A{
    i: i32;
}

initialize A(b: i32){
    self.i = b;
}

fn main(){
    let a: A* = new A(5);
    let b = 5;
	
    let c: A = *a;
    
    print(c.i);
}

Copy by dereferencing.

@mustafaquraish
Copy link
Owner

I've just noticed that you can do ...
Copy by dereferencing.

I don't believe I ever added any logic in the compiler that loops over all members of a struct and copies it over. Even though this passes type checking, I suspect this won't actually work for structs that don't fit in a register. In the current state of the language this should throw an error.

@Weltspear
Copy link
Contributor Author

Would it make sense to push struct fields to the stack instead of passing a struct on the register?

Yep. let's assume the following example:

fn get_a(): A {
    let obj: A;
    ...
    return obj;
}

fn foo() {
    let a_in_foo: A = get_a();
}

Here, we'd want to do something like:

  1. Allocate space for a_in_foo in the stack frame for foo
  2. Pass an extra (hidden) parameter to get_a, which represents &a_in_foo (the address)
  3. Have return obj copy over the struct directly into a_in_foo using the address.

There are some cases to be careful of, which I'm not exactly sure what the best way to handle is, such as:

fn foo(): A {  return get_a(); }
fn bar(): A {  return foo(); }
fn baz(): A {  return bar(); }

One way to deal with this would be to allocate / copy an intermediate object in each function, but that seems very wasteful. It would be nice to have some way to "figure out" where the object actually needs to be allocated, and then just pass around pointers.

Obviously that is more complex, and doesn't need to all be implemented at once. Just make sure that any design you pick leaves room for improvements like these to be made in the future, and that at the very least such situations are handled correctly, even if it is inefficient.

Btw by allocating space in stack frame do you mean just pushing onto stack or something else?

@mustafaquraish
Copy link
Owner

Btw by allocating space in stack frame do you mean just pushing onto stack or something else?

You should be able to use the add_variable_to_current_block(var) function in parser.cup. you're not "pushing" anything yet, we just reserve more space in the function prologue to be able to store the struct, and the compiler just tracks the offsets.

@Weltspear
Copy link
Contributor Author

So we allocate more space during parsing of the function?

@Weltspear
Copy link
Contributor Author

From what I see it is already done in function parsing.

@Weltspear
Copy link
Contributor Author

I don't think I'm able to implement this.

@mustafaquraish
Copy link
Owner

I don't think I'm able to implement this.

No worries, thanks for trying!

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

2 participants