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

assign and bind to deal with different const of functions #118

Merged
merged 1 commit into from
Jul 9, 2024

Conversation

AdityaAtulTewari
Copy link
Contributor

assign is used when the function is const wrt to the first level of the referenced object.
bind is it's counterpart.

Both should only be used in very isolated cases.

@AdityaAtulTewari
Copy link
Contributor Author

@ywwu928 please make sure this doesn't do extra stores.

@AdityaAtulTewari
Copy link
Contributor Author

AdityaAtulTewari commented Jun 13, 2024

  • Add an assert to applyFunc to check that the object structure actually has not changed. Should be a byte level comparison of the original and the final.

@aneeshdurg
Copy link
Contributor

Would it be beneficial to introduce a wrapper around references to arrays that has a const ref, but uses const_cast to allow interior mutability? Something like this:

template <typename T>
class ConstVecWrapper {
public:
  ConstVecWrapper(std::vector<T>& input) : ref(input) {}

  T& operator[](size_t idx) {
    return const_cast<std::vector<T>&>(ref)[idx];
  }

private:
  const std::vector<T>& ref;
}

Using a wrapper like this would allow the compiler to prevent any mutation of the input reference, and you'd use const_cast as a sort of "unsafe" marker to say that you're explicitly ok with the access there.

@AdityaAtulTewari
Copy link
Contributor Author

I think that approach makes sense. However, the goal is to completely reduce the burden on the programmer for the provided methods. If the programmer has to keep track of that how is it different from knowing when to use applyFunc?

@aneeshdurg
Copy link
Contributor

The idea is that by having some kind of class like this wrap the reference to the array most users don't need to know about how the const semantics are handled internally - they just have a reference that only exposes interior mutability. I think the risk with applyFunc as it is now is that if the function does mutate the outer object it could get silently lost right?

@AdityaAtulTewari
Copy link
Contributor Author

Correct this would loose the internal mutations. How about you write out an example use case of what you are saying and we examine that.

@aneeshdurg
Copy link
Contributor

I don't think this should replace applyFunc, it's there to ensure that applyFunc isn't called with a functor that treats the input as having exterior mutability with no extra cost.

// let's say we have a function f that attempts to do something that mutates the ptr itself
void f(GlobalRef<T> ref) {
  ref->size += 1; // contrived example, but whatever
}
// If I call the applyFunc with f, my understanding is that the write to ref->size won't persist
// what if applyFunc was defined as follows:
void applyFunc(ConstWrapper<T> ref, Func f) {
  ...
}

// When calling applyFunc if I just did...
GlobalRef<T> ref;
ConstVecWrapper<T> mut_ref(ref); // (this isn't necessary I think you can make it implicit cast)
applyFunc(ref, f); // ...I'll get a compiler error about the type of f

// If I just naively refactor this to...
void f(ConstWrapper<T> ref) {
  ref->size += 1; // ...now this will fail to compile because `ref->size` will have type `const size_t&`
  // Only operator[] allows mutable access

  // now I know that f shouldn't be called via `applyFunc` but with `bindFunc`/`fmap` 
}

@AdityaAtulTewari
Copy link
Contributor Author

AdityaAtulTewari commented Jun 14, 2024

Separate Idea: At that point why not just have two impls of bind. One with a ConstWrapper and one without?

Let's consider a more involved example. Because I think what you are saying is a good idea.

template<typename VT, typename ET>
struct CSR {
  Array<uint64_t> edgeIndexes;
  Array<uint64_t> edgeDsts;
  Array<VT> vertexData;
  Array<ET> edgeData;
  uint64_t numVertices;
  uint64_t numEdges;
};

Very obviously when accessing this data structure certain things (for example those wrapped in arrays) can be made mutable without any cost. Perhaps we can implicitly promote things like Arrays and other non-resizeable DS as automatically ConstWrapped. For example calling get on a hashmap should be ConstWrapped that function itself should have that property.

It would be much nicer to be able to do this with two overloaded versions of bind:

void f(const CSR& graph) {
   graph.vertexData[0] = 5;
}

void g(CSR graph) {
 graph.numVertices ++;
}

globalRef<CSR> csr = distGraph.getLocalCSR();
bind(csr, f); // No writeback
bind(csr, g); // Yes writeback

I think what we are saying is the same, just instead of using mutable, you want to use an explicit separate type.

@aneeshdurg
Copy link
Contributor

Thar definitely makes sense, but it wouldn't work for the case where we want to be able to mutate array elements right?
I think having an overload for const would be a good solution for the time being though

@AdityaAtulTewari AdityaAtulTewari force-pushed the AdityaAtulTewari/be-the-compiler branch from c3bf573 to 0c38f0f Compare July 9, 2024 16:38
@AdityaAtulTewari
Copy link
Contributor Author

We will merge this for now and I'll update on llvm patches in today's meeting.

@AdityaAtulTewari AdityaAtulTewari merged commit f419b9d into main Jul 9, 2024
20 checks passed
@AdityaAtulTewari AdityaAtulTewari deleted the AdityaAtulTewari/be-the-compiler branch July 9, 2024 17:43
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

Successfully merging this pull request may close these issues.

2 participants