#include <bits/stdc++.h>
using ll = long long;
using namespace std;
#define _overload3(_1, _2, _3, name, ...) name
#define _rep(i, n) repi(i, 0, n)
#define repi(i, a, b) for(int i = int(a); i < int(b); ++i)
#define rep(...) _overload3(__VA_ARGS__, repi, _rep, )(__VA_ARGS__)
#define all(x) (x).begin(), (x).end()
const long long INFLL = 1LL << 60;
const int INFINT = 1 << 30;
const long long MOD = 1e9 + 7;

template <class T> void vecout(T V) {
    auto t = V.begin();
    while(t != V.end()) {
        // cout << " " << *t++;
        cout << *t++;
        if(t != V.end()) cout << " ";
    }
    cout << endl;
};

template <class T> using vec_alias = ::std::vector<T>;
template <class Group, template <class> class Container>
class potentialized_union_find {
  protected:
    class node_type;

  public:
    using value_structure = Group;
    using value_type = typename value_structure::value_type;
    using container_type = Container<node_type>;
    using size_type = typename container_type::size_type;

  protected:
    class node_type {
      public:
        typename potentialized_union_find::size_type parent;
        typename potentialized_union_find::size_type size;
        typename potentialized_union_find::value_type value;
    };

    container_type tree;

  private:
    value_type potential(size_type x) {
        value_type ret = value_structure::identity();
        while(tree[x].parent != x) {
            tree[x].value = value_structure::operation(
                tree[tree[x].parent].value, tree[x].value);
            ret = value_structure::operation(tree[x].value, ret);
            x = tree[x].parent = tree[tree[x].parent].parent;
        }
        return ::std::move(ret);
    }

  public:
    potentialized_union_find() : tree() {}
    explicit potentialized_union_find(const size_type size)
        : tree(size, {0, 1, value_structure::identity()}) {
        for(size_type i = 0; i < size; ++i)
            tree[i].parent = i;
    }

    bool empty() const { return tree.empty(); }
    size_type size() const { return tree.size(); }

    size_type root(size_type x) {
        assert(x < size());
        while(tree[x].parent != x) {
            tree[x].value = value_structure::operation(
                tree[tree[x].parent].value, tree[x].value);
            x = tree[x].parent = tree[tree[x].parent].parent;
        }
        return x;
    }
    bool same(const size_type x, const size_type y) {
        assert(x < size());
        assert(y < size());
        return root(x) == root(y);
    }
    size_type size(const size_type x) {
        assert(x < size());
        return tree[root(x)].size;
    }

    value_type diff(size_type x, size_type y) {
        assert(x < size());
        assert(y < size());
        assert(same(x, y));
        return value_structure::operation(
            value_structure::inverse(potential(x)), potential(y));
    }

    ::std::pair<size_type, size_type> unite(size_type x, size_type y,
                                            value_type w) {
        // something wrong...
        assert(x < size());
        assert(y < size());
        w = value_structure::operation(
            value_structure::operation(potential(x), ::std::move(w)),
            value_structure::inverse(potential(y)));
        x = root(x);
        y = root(y);
        if(x != y) {
            if(tree[x].size < tree[y].size) {
                ::std::swap(x, y);
                w = value_structure::inverse(::std::move(w));
            }
            tree[x].size += tree[y].size;
            tree[y].value = move(w);
            tree[y].parent = x;
        }
        return {x, y};
    }
};

template <class T> class plus_abelian {
  public:
    using value_type = T;
    static value_type operation(const value_type &x, const value_type &y) {
        return x + y;
    }
    static value_type identity() { return value_type(0); }
    static value_type inverse(const value_type &x) { return -x; }
    static value_type reverse(const value_type &x) { return x; }
};

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    int n, q;
    cin >> n >> q;
    potentialized_union_find<plus_abelian<int>, vec_alias> uf(n);

    int c, x, y, z, t;
    rep(i, q) {
        cin >> c >> x >> y;
        if(c) {
            if(uf.same(x, y))
                cout << uf.diff(x, y) << "\n";
            else
                cout << "?\n";
        } else {
            cin >> z;
            uf.unite(x, y, z);
        }
    }
}



Measured memory_bytes: 5192