Skip to content

Operator overloading #249

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

Closed
8 tasks done
Tracked by #682
SimplyTheOther opened this issue Feb 24, 2021 · 7 comments · Fixed by #801
Closed
8 tasks done
Tracked by #682

Operator overloading #249

SimplyTheOther opened this issue Feb 24, 2021 · 7 comments · Fixed by #801
Assignees

Comments

@SimplyTheOther
Copy link
Member

SimplyTheOther commented Feb 24, 2021

Rust supports overloading some operators using traits in std::ops or std::cmp, as mentioned in the reference.

The operators that can be overloaded are:

  • DereferenceExpr (with std::ops::Deref::deref(&x) or std::ops::DerefMut::deref_mut(&mut x))
  • NegationExpr (with std::ops::Neg or std::ops::Not)
  • ArithmeticOrLogicalExpr (with std::ops::x where x is Add, Sub, Mul, Div, Rem, BitAnd, BitOr, BitXor, Shl, or Shr)
  • ComparisonExpr (with std::cmp::PartialEq::x where x is eq, ne, gt, lt, ge, or le)
  • CompoundAssignmentExpr (with std::ops::xAssign where x is one of the values listed in ArithmeticOrLogicalExpr)

Currently, gccrs does not implement operator overloading and presumably just creates GENERIC/GIMPLE of the operator expression with a type that can't be handled.

One method of implementing operator overloading could be a pass that replaces certain OperatorExprs with function calls. This has some issues:

  • If this pass occurs after name resolution but before lowering to HIR, the types involved are not known. As such, there is no way to know which function calls to replace (as you can't overload the operators on integers, for instance).
  • If this pass occurs after type resolution (in HIR), then the extent to which the HIR can be simplified is limited. For example, currently the HIR has no representation for a CompoundAssignmentExpr - it converts one into an AssignmentExpr and an ArithmeticLogicalExpr. But it is possible to overload operator +=, for instance, without also overloading operator +. So the HIR would have to have a separate CompoundAssignmentExpr representation for this reason.

Another method of implementing operator overloading could simply be to convert every OperatorExpr to a function call at some point, even ones for primitive types. This would also lead to some issues:

  • Type resolution would be more difficult, as some type inference can conceivably be performed by knowing certain properties about an operator (e.g. that ComparisonExprs have a boolean output). Either type resolution would not be able to access this information or more complicated code would be required to allow it.
  • Additional code would be required to convert some of these function calls back to operators when converting to GENERIC/GIMPLE so that primitive types don't require a function call every time an operation is performed on them.

  • Implement a lang-item parsing and lookup at type-resolution level
  • Revert the desugaring of the HIR CompoundAssignmentExpr into an assignment
  • Refactor backend method call expression logic to be more reusable for operator overloading
  • Let GCC inline the operator overloading and support the inline attribute
  • Implement ArithmeticOrLogicalExpr logic
  • Implement CompoundAssignmentExpr TREE_ADDRESSABLE is not being set when required #804
  • Implement DereferenceExpr Deref operator overloading #809
  • Implement NegationExpr
@bjorn3
Copy link

bjorn3 commented Feb 24, 2021

Rustc keeps these intact up to the HIR. After typechecking, while lowering to MIR it generates THIR (typed HIR) on the fly, which does lower it to function calls as necessary I believe. It may also happen during the THIR to MIR lowering. The HIR is always immutable in rustc.

@NalaGinrut
Copy link
Contributor

  • If this pass occurs after name resolution but before lowering to HIR, the types involved are not known. As such, there is no way to know which function calls to replace (as you can't overload the operators on integers, for instance).

Generally speaking, the pass could be immutable by just recording the necessary information and delay the actual replacement to the later pass.

  • Additional code would be required to convert some of these function calls back to operators when converting to GENERIC/GIMPLE so that primitive types don't require a function call every time an operation is performed on them.

I'm afraid the IR recovery back and forth is a bad way to go. We may record the op-func relationship in a table. So we don't have to recover the node. I presume there would be a recursive node replacement, which introduces complexity.

@philberty
Copy link
Member

This is something that will have to be tackled as part of traits in my opinion.

@philberty
Copy link
Member

I think we need to be able to do #442 and #434 with #471 before we can do operator overloading.

I suspect there is some GENERIC magic that will help here from C++ or there is an example in libgccjit and operator overloading for primitive types as well.

For the first pass this will need to fix #367 as well.

Though thinking we also need to fix #241 and I am starting to think we should enforce our HIR to be immutable now since it has been the case so far. Then we can emit THIR so we have a chance during type resolution to fix up type coercions and adjustments properly without resorting to wierd hacks in the GENERIC generation.

@philberty
Copy link
Member

I think THIR will solve a lot of niggly issues for the traits milestone: https://rustc-dev-guide.rust-lang.org/thir.html

@philberty
Copy link
Member

I just made a toy compiler explorer to see how GCC in C++ handles inlining of operator overloading:

https://godbolt.org/z/7G4xh5WYE

It seems we won't need to mark any operator overloads as inline if optimizations are turned on we get the inlining for free. I have a toy branch working on this, but its only handling custom ADTTypes that implement the add trait, I have some issues with primitive types which also implement the trait at the moment, there might be something to do with lang-items that helps here but I have a lot of buggy code in this branch to deal with first.

@philberty
Copy link
Member

The deref operator overloading is tracked over: #809

philberty added a commit that referenced this issue Nov 16, 2021
unary rhs nullptr refactor

Fixes #249
philberty added a commit that referenced this issue Nov 16, 2021
Core traits such as the arithmetic operations have generic arguments such
as: pub trait Add<Rhs = Self>

Addresses #249
philberty added a commit that referenced this issue Nov 16, 2021
Unary operator expressions can be treated as simply having a nullptr
rvalue. This patch updates the shared operator overloading code to allow
for a nullptr rhs to canonicalize the code path for all operator overloads.

Fixes #249
bors bot added a commit that referenced this issue Nov 16, 2021
801: operator overloading r=philberty a=philberty

This change adds operator overloading by following how the C++ front-end
does it. We are relying on GCC to inline the operator overloads which does
occur once optimizations are enabled. It also brings back the desurgared
compound assignment expression (e2b761b)
for lang_items such as add_assign. You can find more information on how the algorithm works in:

- c47d5cb 
- a7fb60b 

These were refactored in: 0f74fe2

Fixes #249



Co-authored-by: Philip Herron <[email protected]>
@bors bors bot closed this as completed in c47d5cb Nov 16, 2021
@bors bors bot closed this as completed in #801 Nov 16, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants