#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <utility>
#include <stack>
#include <numeric>
#include <algorithm>
#include <tuple>
#include <stdio.h>
#include <bitset>
#include <limits.h>
#include <complex>
#include <deque>
#include <iomanip>
#include <list>
#include <cstring>
using namespace std;
#define pi pair<int,int>
#define pl pair<long long,long long>
#define chmax(a,b) (a<b ? a=b:0)
#define chmin(a,b) (a>b ? a=b:0)
#define en cout << endl //セミコロンつけろ
//#define MM 1000000000
//#define MOD MM+7
typedef long long ll;
const int MM = 1e9;
const int MOD = MM+7;
const long double PI = acos(-1);
const long long INF = 1e15;
int dx[8] = {-1,0,1,0,-1,-1,1,1};
int dy[8] = {0,-1,0,1,-1,1,1,-1};
// 'A' = 65, 'Z' = 90, 'a' = 97, 'z' = 122

template<typename T> //最大公約数
T GCD(T u,T v){
    return v ? GCD(v,u%v) : u;
}
template<typename T> //最小公倍数
T LCM(T x,T y){
    T gc = GCD(x,y);
    return x*y/gc;
}

const int COM_MAX = 500500;
long long fac[COM_MAX],finv[COM_MAX],inv[COM_MAX];
void init(){
    fac[0] = fac[1] = 1;
    finv[0] = finv[1] = 1;
    inv[1] = 1;
    for(int i = 2; i < COM_MAX; i++){
        fac[i] = fac[i-1]*i%MOD;
        inv[i] = MOD - inv[MOD%i]*(MOD/i)%MOD;
        finv[i] = finv[i-1]*inv[i]%MOD;
    }
}
long long COM(int n,int k){
    if(n < k || n < 0 || k < 0) return 0;
    return fac[n]*(finv[k]*finv[n-k]%MOD)%MOD;
}


// a^n mod を計算する
long long modpow(long long a, long long n, long long mod) {
    long long res = 1;
    while (n > 0) {
        if (n & 1) res = res * a % mod;
        a = a * a % mod;
        n >>= 1;
    }
    return res;
}

// a^{-1} mod を計算する
long long modinv(long long a, long long mod) {
    return modpow(a, mod - 2, mod);
}


struct BinaryIndexedTree
{
  vector< int > data;

  BinaryIndexedTree(int sz)
  {
    data.assign(sz + 1, 0);
  }

  int Sum(int k)
  {
    int sum = 0;
    for(; k > 0; k -= k & -k) sum += data[k];
    return sum;
  }

  void Add(int k, int x)
  {
    for(; k < data.size(); k += k & -k) data[k] += x;
  }
};

template<class T> 
struct WeightedUnionFind {
    const T sum = 0;
    vector<int> par;
    vector<T> weight;
    vector<T> diff_weight;

    WeightedUnionFind(int n){
        par.assign(n, -1), diff_weight.assign(n, sum);
    }

    int root(int x){
        if (par[x] < 0) return x;
        else {
            int r = root(par[x]);
            diff_weight[x] += diff_weight[par[x]];
            return par[x] = r;
        }
    }
     
    T function_weight(int x){
        root(x);
        return diff_weight[x];
    }

    bool same(int x,int y){
        return root(x) == root(y);
    }

    bool merge(int x,int y,T w = 0){
        w += function_weight(x); 
        w -= function_weight(y);

        x = root(x);
        y = root(y);
        if (x == y) return false;
        if (par[x] > par[y]){
            swap(x,y);
            w *= -1;
        }
        par[x] += par[y];
        par[y] = x;
        diff_weight[y] = w;
        return true;
    }

    T diff(int x,int y){
        return function_weight(y) - function_weight(x);
    }

    T size(int x){
        return -par[root(x)];
    }
};

int main(){
	int N, Q; cin >> N >> Q;
	WeightedUnionFind<int> uf(N);
	vector<int> ans(0);
	for (int i = 0; i < Q; i++){
		int c, x, y; cin >> c >> x >> y;
		if (c == 0){
			int z; cin >> z;
			if (!uf.same(x, y)){
				uf.merge(x, y, z);
			}
		} 
		else {
			int res;
			if (uf.same(x, y)){
				res = uf.diff(x, y);
			} else {
				res = MM;
			}
			ans.push_back(res);
		}
	}
	for (int i = 0; i < ans.size(); i++){
		if (ans[i] == MM) cout << '?' << endl;
		else cout << ans[i] << endl;
	}
}


Measured memory_bytes: 4224