A bidirectional RNN runs one vanilla RNN left-to-right and another right-to-left, then concatenates their hidden states at each step, so every position sees the whole sequence.
Each direction uses starting from zeros. Implement birnn(X, fwd, bwd) where X has shape (T, d_in) and fwd, bwd are (Wx, Wh, b) tuples. Return an array of shape (T, 2 * hidden) with the forward state first.