#include <bits/stdc++.h>

#define REP(i,n) for (int i=0;i<(n);i++)

using namespace std;

typedef long long ll;

class weighted_union_find_tree {
private:
    size_t *par;
    ll *dist;

public:
    weighted_union_find_tree(size_t n) {
        par = new size_t[n];
        dist = new ll[n];
        for (size_t i = 0; i < n; ++i) {
            par[i] = i;
            dist[i] = 0;
        }
    }

    ~weighted_union_find_tree() {
        delete [] par;
        delete [] dist;
    }

    size_t find(size_t x) {
        if (par[x] == x) {
            return x;
        }
        int old_par_x = par[x];
        ll old_dist_par_x = dist[par[x]];
        int old_dist_x = dist[x];
        par[x] = find(par[x]);
        //printf("par[%d] is updated from %d to %d\n", x, old_par_x, par[x]);
        dist[x] += dist[old_par_x]; 
        //printf("dist[%d] is updated from %lld to %lld\n", x, old_dist_x, dist[x]);
        return par[x];
    }

    void relation(size_t x, size_t y, ll d) {
        if (find(x) == find(y)) {
            if (dist[y] - dist[x] != d) {
                cerr << "contradiction" << endl;
                cerr << "a[" << y << "] - a[" << x << "] must be " << dist[y] - dist[x] << " from previous information." << endl;
                cerr << "But new information says that it is " << d << "." <<  endl; 
            }
        } else {
            if (dist[x] + d > dist[y]) {
                int old_par_y = par[y];
                int old_par_par_y = par[par[y]];
                par[par[y]] = par[x];
                //printf("par[%d] is updated from %d to %d\n", old_par_y, old_par_par_y, par[par[y]]);
                ll old_dist_old_par_y = dist[old_par_y];
                dist[old_par_y] = dist[x] + d - dist[y];
                //printf("dist[%d] is updated from %lld to %lld\n", old_par_y, old_dist_old_par_y, dist[old_par_y]);
            } else {
                int old_par_x = par[x];
                int old_par_par_x = par[par[x]];
                par[par[x]] = par[y];
                //printf("par[%d] is updated from %d to %d\n", old_par_x, old_par_par_x, par[par[x]]);
                ll old_dist_old_par_x = dist[old_par_x];
                dist[old_par_x] = dist[y] - d - dist[x];
                //printf("dist[%d] is updated from %lld to %lld\n", old_par_x, old_dist_old_par_x, dist[old_par_x]);
            }
        }
    }

    ll difference(size_t x, size_t y) {
        //printf("difference(%d, %d)\n", x, y);
        if (find(x) != find(y)) {
            // no relation
            return LLONG_MAX;
        }
        //printf("par[x] = %d, dist[x] = %d\n", par[x], dist[x]);
        //printf("par[y] = %d, dist[y] = %d\n", par[y], dist[y]);
        return dist[y] - dist[x];
    }
};

int main(void) {
    cin.tie(0);
    ios::sync_with_stdio(false);

    int n, q;
    cin >> n >> q;
    weighted_union_find_tree t(n);
    REP(_, q) {
        int query_type;
        cin >> query_type;
        if (query_type == 0) {
            int x, y, z;
            cin >> x >> y >> z;
            //printf("relation query %d %d %d\n", x, y, z);
            t.relation(x, y, z);
        } else if (query_type == 1) {
            int x, y;
            cin >> x >> y;
            //printf("difference query %d %d\n", x, y);
            ll d = t.difference(x, y);
            if (d != LLONG_MAX) {
                cout << d << '\n';
            } else {
                cout << "?\n";
            }
        }
        //cout << endl;
    }
    cout << flush;
    return 0;
}



Measured memory_bytes: 4404