#include <bits/stdc++.h>
#define loop(n) for (int ngtkana_is_genius = 0; ngtkana_is_genius < int(n); ngtkana_is_genius++)
#define rep(i, begin, end) for(int i = int(begin); i < int(end); i++)
#define lint long long
auto cmn = [](auto& a, auto b){if (a > b) {a = b; return true;} return false;};
auto cmx = [](auto& a, auto b){if (a < b) {a = b; return true;} return false;};
void debug_impl() { std::cerr << std::endl; }
template <typename Head, typename... Tail>
void debug_impl(Head head, Tail... tail){
  std::cerr << " " << head;
  debug_impl(tail...);
}
#define debug(...) std::cerr << std::boolalpha << "[" << #__VA_ARGS__ << "]:";\
  debug_impl(__VA_ARGS__);\
  std::cerr << std::noboolalpha;


template <typename Value, typename BinaryOp1, typename BinaryOp2>
class weighted_union_find_tree
{
  int                n;
  std::vector<int>   prt;
  std::vector<Value> wt;
  BinaryOp1          op1;
  BinaryOp2          op2;
  Value              id;

  auto op1_eq (Value& x, Value y) const {return x = op1(x, y);}
  auto op2_eq (Value& x, Value y) const {return x = op2(x, y);}
  auto inv_eq (Value& x)          const {return x = op2(id, x);}
  auto inv    (Value& x)          const {return inv_eq(x);}

public:
  weighted_union_find_tree
    (int n, BinaryOp1 op1, BinaryOp2 op2, Value id) :
    n(n), prt(n, -1), wt(n, id), op1(op1), op2(op2), id(id)
    {}

  bool is_root (int x)        const {return prt.at(x) < 0;}
  int  size    (int x)              {return -prt.at(find(x));}
  bool same    (int x, int y)       {return find(x) == find(y);}
  auto get     (int x)
  {
    find(x);
    return wt.at(x);
  }

  // x - y
  auto diff    (int x, int y)
  {
    assert(same(x, y));
    return op2(get(x), get(y));
  }
  auto collect     ()         const {return prt;}
  auto collect_wts ()         const {return wt;}

  // Path compression.
  int  find    (int x)
  {
    if (is_root(x)) return x;
    else
    {
      auto root = find(prt.at(x));
      op1_eq(wt.at(x), wt.at(prt.at(x)));
      return prt.at(x) = root;
    }
  }

  // Returns `true` if x and y are newly connected.
  // x is the parent.
  // x - y = d
  bool unite   (int x, int y, Value d)
  {
    inv_eq(d);
    op1_eq(d, get(x));
    op2_eq(d, get(y));
    if ((x = find(x)) == (y = find(y)))
    {
      assert(op2(get(x), get(y)) == d);
      return false;
    }
    if (size(x) < size(y))
    {
      std::swap(x, y);
      inv_eq(d);
    }
    prt.at(x) += prt.at(y);
    prt.at(y) = x;
    wt.at(y) = d;
    return true;
  }
};

template <typename Value, typename BinaryOp1, typename BinaryOp2>
auto make_weighted_union_find_tree(int n, BinaryOp1 op1, BinaryOp2 op2, Value id)
{
  return weighted_union_find_tree<Value, BinaryOp1, BinaryOp2>(n, op1, op2, id);
}

template <typename T>
std::istream& operator>> (std::istream& is, std::vector<T>& v)
{
  for (auto & x : v) is >> x;
  return is;
}

template <typename T>
std::ostream& operator<< (std::ostream& os, const std::vector<T>& v)
{
  auto n = v.size();
  os << "{";
  for (size_t i = 0; i < n; i++)
    os << (i > 0 ? "," : "") << v[i];
  return os << "}";
}

int main()
{
  std::cin.tie(0); std::cin.sync_with_stdio(false);
  int n, q; std::cin >> n >> q;
  auto uf = make_weighted_union_find_tree
  (
    n,
    [](auto x, auto y){return x + y;},
    [](auto x, auto y){return x - y;},
    0
  );
  while (q--)
  {
    int c; std::cin >> c;
    if (c == 0)
    {
      int x, y, z;
      std::cin >> x >> y >> z;
      uf.unite(y, x, z);
    }
    else
    {
      int x, y; std::cin >> x >> y;
      if (uf.same(x, y))
      {
        std::cout << uf.diff(y, x) << std::endl;
      }
      else
      {
        std::cout << "?" << std::endl;
      }
    }
  }
  return 0;
}


Measured memory_bytes: 3584