tream.Writef(" %" PRIu64 " (0x%" PRIx64 ")", *GetData<uint64_t>(),
                    *GetData<uint64_t>());
      break;

    case Kind::Index:
      stream.Writef(" %" PRIindex, *GetData<Index>());
      break;

    case Kind::Float32: {
      stream.Writef(" %g", *GetData<float>());
      char buffer[WABT_MAX_FLOAT_HEX + 1];
      WriteFloatHex(buffer, sizeof(buffer), *GetData<uint32_t>());
      stream.Writef(" (%s)", buffer);
      break;
    }

    case Kind::Float64: {
      stream.Writef(" %g", *GetData<double>());
      char buffer[WABT_MAX_DOUBLE_HEX + 1];
      WriteDoubleHex(buffer, sizeof(buffer), *GetData<uint64_t>());
      stream.Writef(" (%s)", buffer);
      break;
    }

    case Kind::Uint32Uint32:
      WriteArray<uint32_t>(
          stream, [&stream](uint32_t value) { stream.Writef("%u", value); });
      break;

    case Kind::BlockSig: {
      auto type = *GetData<Type>();
      if (IsTypeIndex(type)) {
        stream.Writef(" type:%d", static_cast<int>(type));
      } else if (type != Type::Void) {
        stream.Writef(" %s", GetTypeName(type));
      }
      break;
    }

    case Kind::BrTable: {
      WriteArray<Index>(stream, [&stream](Index index) {
        stream.Writef("%" PRIindex, index);
      });
      break;
    }
  }
}

bool operator==(const OpcodeInfo& lhs, const OpcodeInfo& rhs) {
  return lhs.opcode_ == rhs.opcode_ && lhs.kind_ == rhs.kind_ &&
         lhs.data_ == rhs.data_;
}

bool operator!=(const OpcodeInfo& lhs, const OpcodeInfo& rhs) {
  return !(lhs == rhs);
}

bool operator<(const OpcodeInfo& lhs, const OpcodeInfo& rhs) {
  if (lhs.opcode_ < rhs.opcode_) {
    return true;
  }
  if (lhs.opcode_ > rhs.opcode_) {
    return false;
  }
  if (lhs.kind_ < rhs.kind_) {
    return true;
  }
  if (lhs.kind_ > rhs.kind_) {
    return false;
  }
  if (lhs.data_ < rhs.data_) {
    return true;
  }
  if (lhs.data_ > rhs.data_) {
    return false;
  }
  return false;
}

bool operator<=(const OpcodeInfo& lhs, const OpcodeInfo& rhs) {
  return lhs < rhs || lhs == rhs;
}

bool 