Code refactor: move more memory allocation logic into device API.
[blender.git] / intern / cycles / device / device_network.cpp
1 /*
2  * Copyright 2011-2013 Blender Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "device/device.h"
18 #include "device/device_intern.h"
19 #include "device/device_network.h"
20
21 #include "util/util_foreach.h"
22 #include "util/util_logging.h"
23
24 #if defined(WITH_NETWORK)
25
26 CCL_NAMESPACE_BEGIN
27
28 typedef map<device_ptr, device_ptr> PtrMap;
29 typedef vector<uint8_t> DataVector;
30 typedef map<device_ptr, DataVector> DataMap;
31
32 /* tile list */
33 typedef vector<RenderTile> TileList;
34
35 /* search a list of tiles and find the one that matches the passed render tile */
36 static TileList::iterator tile_list_find(TileList& tile_list, RenderTile& tile)
37 {
38         for(TileList::iterator it = tile_list.begin(); it != tile_list.end(); ++it)
39                 if(tile.x == it->x && tile.y == it->y && tile.start_sample == it->start_sample)
40                         return it;
41         return tile_list.end();
42 }
43
44 class NetworkDevice : public Device
45 {
46 public:
47         boost::asio::io_service io_service;
48         tcp::socket socket;
49         device_ptr mem_counter;
50         DeviceTask the_task; /* todo: handle multiple tasks */
51
52         thread_mutex rpc_lock;
53
54         virtual bool show_samples() const
55         {
56                 return false;
57         }
58
59         NetworkDevice(DeviceInfo& info, Stats &stats, const char *address)
60         : Device(info, stats, true), socket(io_service)
61         {
62                 error_func = NetworkError();
63                 stringstream portstr;
64                 portstr << SERVER_PORT;
65
66                 tcp::resolver resolver(io_service);
67                 tcp::resolver::query query(address, portstr.str());
68                 tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
69                 tcp::resolver::iterator end;
70
71                 boost::system::error_code error = boost::asio::error::host_not_found;
72                 while(error && endpoint_iterator != end)
73                 {
74                         socket.close();
75                         socket.connect(*endpoint_iterator++, error);
76                 }
77
78                 if(error)
79                         error_func.network_error(error.message());
80
81                 mem_counter = 0;
82         }
83
84         ~NetworkDevice()
85         {
86                 RPCSend snd(socket, &error_func, "stop");
87                 snd.write();
88         }
89
90         void mem_alloc(device_memory& mem)
91         {
92                 if(mem.name) {
93                         VLOG(1) << "Buffer allocate: " << mem.name << ", "
94                                     << string_human_readable_number(mem.memory_size()) << " bytes. ("
95                                     << string_human_readable_size(mem.memory_size()) << ")";
96                 }
97
98                 thread_scoped_lock lock(rpc_lock);
99
100                 mem.device_pointer = ++mem_counter;
101
102                 RPCSend snd(socket, &error_func, "mem_alloc");
103                 snd.add(mem);
104                 snd.write();
105         }
106
107         void mem_copy_to(device_memory& mem)
108         {
109                 thread_scoped_lock lock(rpc_lock);
110
111                 RPCSend snd(socket, &error_func, "mem_copy_to");
112
113                 snd.add(mem);
114                 snd.write();
115                 snd.write_buffer((void*)mem.data_pointer, mem.memory_size());
116         }
117
118         void mem_copy_from(device_memory& mem, int y, int w, int h, int elem)
119         {
120                 thread_scoped_lock lock(rpc_lock);
121
122                 size_t data_size = mem.memory_size();
123
124                 RPCSend snd(socket, &error_func, "mem_copy_from");
125
126                 snd.add(mem);
127                 snd.add(y);
128                 snd.add(w);
129                 snd.add(h);
130                 snd.add(elem);
131                 snd.write();
132
133                 RPCReceive rcv(socket, &error_func);
134                 rcv.read_buffer((void*)mem.data_pointer, data_size);
135         }
136
137         void mem_zero(device_memory& mem)
138         {
139                 thread_scoped_lock lock(rpc_lock);
140
141                 RPCSend snd(socket, &error_func, "mem_zero");
142
143                 snd.add(mem);
144                 snd.write();
145         }
146
147         void mem_free(device_memory& mem)
148         {
149                 if(mem.device_pointer) {
150                         thread_scoped_lock lock(rpc_lock);
151
152                         RPCSend snd(socket, &error_func, "mem_free");
153
154                         snd.add(mem);
155                         snd.write();
156
157                         mem.device_pointer = 0;
158                 }
159         }
160
161         void const_copy_to(const char *name, void *host, size_t size)
162         {
163                 thread_scoped_lock lock(rpc_lock);
164
165                 RPCSend snd(socket, &error_func, "const_copy_to");
166
167                 string name_string(name);
168
169                 snd.add(name_string);
170                 snd.add(size);
171                 snd.write();
172                 snd.write_buffer(host, size);
173         }
174
175         bool load_kernels(const DeviceRequestedFeatures& requested_features)
176         {
177                 if(error_func.have_error())
178                         return false;
179
180                 thread_scoped_lock lock(rpc_lock);
181
182                 RPCSend snd(socket, &error_func, "load_kernels");
183                 snd.add(requested_features.experimental);
184                 snd.add(requested_features.max_closure);
185                 snd.add(requested_features.max_nodes_group);
186                 snd.add(requested_features.nodes_features);
187                 snd.write();
188
189                 bool result;
190                 RPCReceive rcv(socket, &error_func);
191                 rcv.read(result);
192
193                 return result;
194         }
195
196         void task_add(DeviceTask& task)
197         {
198                 thread_scoped_lock lock(rpc_lock);
199
200                 the_task = task;
201
202                 RPCSend snd(socket, &error_func, "task_add");
203                 snd.add(task);
204                 snd.write();
205         }
206
207         void task_wait()
208         {
209                 thread_scoped_lock lock(rpc_lock);
210
211                 RPCSend snd(socket, &error_func, "task_wait");
212                 snd.write();
213
214                 lock.unlock();
215
216                 TileList the_tiles;
217
218                 /* todo: run this threaded for connecting to multiple clients */
219                 for(;;) {
220                         if(error_func.have_error())
221                                 break;
222
223                         RenderTile tile;
224
225                         lock.lock();
226                         RPCReceive rcv(socket, &error_func);
227
228                         if(rcv.name == "acquire_tile") {
229                                 lock.unlock();
230
231                                 /* todo: watch out for recursive calls! */
232                                 if(the_task.acquire_tile(this, tile)) { /* write return as bool */
233                                         the_tiles.push_back(tile);
234
235                                         lock.lock();
236                                         RPCSend snd(socket, &error_func, "acquire_tile");
237                                         snd.add(tile);
238                                         snd.write();
239                                         lock.unlock();
240                                 }
241                                 else {
242                                         lock.lock();
243                                         RPCSend snd(socket, &error_func, "acquire_tile_none");
244                                         snd.write();
245                                         lock.unlock();
246                                 }
247                         }
248                         else if(rcv.name == "release_tile") {
249                                 rcv.read(tile);
250                                 lock.unlock();
251
252                                 TileList::iterator it = tile_list_find(the_tiles, tile);
253                                 if(it != the_tiles.end()) {
254                                         tile.buffers = it->buffers;
255                                         the_tiles.erase(it);
256                                 }
257
258                                 assert(tile.buffers != NULL);
259
260                                 the_task.release_tile(tile);
261
262                                 lock.lock();
263                                 RPCSend snd(socket, &error_func, "release_tile");
264                                 snd.write();
265                                 lock.unlock();
266                         }
267                         else if(rcv.name == "task_wait_done") {
268                                 lock.unlock();
269                                 break;
270                         }
271                         else
272                                 lock.unlock();
273                 }
274         }
275
276         void task_cancel()
277         {
278                 thread_scoped_lock lock(rpc_lock);
279                 RPCSend snd(socket, &error_func, "task_cancel");
280                 snd.write();
281         }
282
283         int get_split_task_count(DeviceTask&)
284         {
285                 return 1;
286         }
287
288 private:
289         NetworkError error_func;
290 };
291
292 Device *device_network_create(DeviceInfo& info, Stats &stats, const char *address)
293 {
294         return new NetworkDevice(info, stats, address);
295 }
296
297 void device_network_info(vector<DeviceInfo>& devices)
298 {
299         DeviceInfo info;
300
301         info.type = DEVICE_NETWORK;
302         info.description = "Network Device";
303         info.id = "NETWORK";
304         info.num = 0;
305
306         /* todo: get this info from device */
307         info.advanced_shading = true;
308         info.has_volume_decoupled = false;
309         info.has_qbvh = false;
310         info.has_osl = false;
311
312         devices.push_back(info);
313 }
314
315 class DeviceServer {
316 public:
317         thread_mutex rpc_lock;
318
319         void network_error(const string &message) {
320                 error_func.network_error(message);
321         }
322
323         bool have_error() { return error_func.have_error(); }
324
325         DeviceServer(Device *device_, tcp::socket& socket_)
326         : device(device_), socket(socket_), stop(false), blocked_waiting(false)
327         {
328                 error_func = NetworkError();
329         }
330
331         void listen()
332         {
333                 /* receive remote function calls */
334                 for(;;) {
335                         listen_step();
336
337                         if(stop)
338                                 break;
339                 }
340         }
341
342 protected:
343         void listen_step()
344         {
345                 thread_scoped_lock lock(rpc_lock);
346                 RPCReceive rcv(socket, &error_func);
347
348                 if(rcv.name == "stop")
349                         stop = true;
350                 else
351                         process(rcv, lock);
352         }
353
354         /* create a memory buffer for a device buffer and insert it into mem_data */
355         DataVector &data_vector_insert(device_ptr client_pointer, size_t data_size)
356         {
357                 /* create a new DataVector and insert it into mem_data */
358                 pair<DataMap::iterator,bool> data_ins = mem_data.insert(
359                         DataMap::value_type(client_pointer, DataVector()));
360
361                 /* make sure it was a unique insertion */
362                 assert(data_ins.second);
363
364                 /* get a reference to the inserted vector */
365                 DataVector &data_v = data_ins.first->second;
366
367                 /* size the vector */
368                 data_v.resize(data_size);
369
370                 return data_v;
371         }
372
373         DataVector &data_vector_find(device_ptr client_pointer)
374         {
375                 DataMap::iterator i = mem_data.find(client_pointer);
376                 assert(i != mem_data.end());
377                 return i->second;
378         }
379
380         /* setup mapping and reverse mapping of client_pointer<->real_pointer */
381         void pointer_mapping_insert(device_ptr client_pointer, device_ptr real_pointer)
382         {
383                 pair<PtrMap::iterator,bool> mapins;
384
385                 /* insert mapping from client pointer to our real device pointer */
386                 mapins = ptr_map.insert(PtrMap::value_type(client_pointer, real_pointer));
387                 assert(mapins.second);
388
389                 /* insert reverse mapping from real our device pointer to client pointer */
390                 mapins = ptr_imap.insert(PtrMap::value_type(real_pointer, client_pointer));
391                 assert(mapins.second);
392         }
393
394         device_ptr device_ptr_from_client_pointer(device_ptr client_pointer)
395         {
396                 PtrMap::iterator i = ptr_map.find(client_pointer);
397                 assert(i != ptr_map.end());
398                 return i->second;
399         }
400
401         device_ptr device_ptr_from_client_pointer_erase(device_ptr client_pointer)
402         {
403                 PtrMap::iterator i = ptr_map.find(client_pointer);
404                 assert(i != ptr_map.end());
405
406                 device_ptr result = i->second;
407
408                 /* erase the mapping */
409                 ptr_map.erase(i);
410
411                 /* erase the reverse mapping */
412                 PtrMap::iterator irev = ptr_imap.find(result);
413                 assert(irev != ptr_imap.end());
414                 ptr_imap.erase(irev);
415
416                 /* erase the data vector */
417                 DataMap::iterator idata = mem_data.find(client_pointer);
418                 assert(idata != mem_data.end());
419                 mem_data.erase(idata);
420
421                 return result;
422         }
423
424         /* note that the lock must be already acquired upon entry.
425          * This is necessary because the caller often peeks at
426          * the header and delegates control to here when it doesn't
427          * specifically handle the current RPC.
428          * The lock must be unlocked before returning */
429         void process(RPCReceive& rcv, thread_scoped_lock &lock)
430         {
431                 if(rcv.name == "mem_alloc") {
432                         string name;
433                         network_device_memory mem(device);
434                         rcv.read(mem, name);
435                         lock.unlock();
436
437                         /* Allocate host side data buffer. */
438                         size_t data_size = mem.memory_size();
439                         device_ptr client_pointer = mem.device_pointer;
440
441                         DataVector &data_v = data_vector_insert(client_pointer, data_size);
442                         mem.data_pointer = (data_size)? (device_ptr)&(data_v[0]): 0;
443
444                         /* Perform the allocation on the actual device. */
445                         device->mem_alloc(mem);
446
447                         /* Store a mapping to/from client_pointer and real device pointer. */
448                         pointer_mapping_insert(client_pointer, mem.device_pointer);
449                 }
450                 else if(rcv.name == "mem_copy_to") {
451                         string name;
452                         network_device_memory mem(device);
453                         rcv.read(mem, name);
454                         lock.unlock();
455
456                         size_t data_size = mem.memory_size();
457                         device_ptr client_pointer = mem.device_pointer;
458
459                         if(client_pointer) {
460                                 /* Lookup existing host side data buffer. */
461                                 DataVector &data_v = data_vector_find(client_pointer);
462                                 mem.data_pointer = (device_ptr)&data_v[0];
463
464                                 /* Translate the client pointer to a real device pointer. */
465                                 mem.device_pointer = device_ptr_from_client_pointer(client_pointer);
466                         }
467                         else {
468                                 /* Allocate host side data buffer. */
469                                 DataVector &data_v = data_vector_insert(client_pointer, data_size);
470                                 mem.data_pointer = (data_size)? (device_ptr)&(data_v[0]): 0;
471                         }
472
473                         /* Copy data from network into memory buffer. */
474                         rcv.read_buffer((uint8_t*)mem.data_pointer, data_size);
475
476                         /* Copy the data from the memory buffer to the device buffer. */
477                         device->mem_copy_to(mem);
478
479                         if(!client_pointer) {
480                                 /* Store a mapping to/from client_pointer and real device pointer. */
481                                 pointer_mapping_insert(client_pointer, mem.device_pointer);
482                         }
483                 }
484                 else if(rcv.name == "mem_copy_from") {
485                         string name;
486                         network_device_memory mem(device);
487                         int y, w, h, elem;
488
489                         rcv.read(mem, name);
490                         rcv.read(y);
491                         rcv.read(w);
492                         rcv.read(h);
493                         rcv.read(elem);
494
495                         device_ptr client_pointer = mem.device_pointer;
496                         mem.device_pointer = device_ptr_from_client_pointer(client_pointer);
497
498                         DataVector &data_v = data_vector_find(client_pointer);
499
500                         mem.data_pointer = (device_ptr)&(data_v[0]);
501
502                         device->mem_copy_from(mem, y, w, h, elem);
503
504                         size_t data_size = mem.memory_size();
505
506                         RPCSend snd(socket, &error_func, "mem_copy_from");
507                         snd.write();
508                         snd.write_buffer((uint8_t*)mem.data_pointer, data_size);
509                         lock.unlock();
510                 }
511                 else if(rcv.name == "mem_zero") {
512                         string name;
513                         network_device_memory mem(device);
514                         rcv.read(mem, name);
515                         lock.unlock();
516
517                         size_t data_size = mem.memory_size();
518                         device_ptr client_pointer = mem.device_pointer;
519
520                         if(client_pointer) {
521                                 /* Lookup existing host side data buffer. */
522                                 DataVector &data_v = data_vector_find(client_pointer);
523                                 mem.data_pointer = (device_ptr)&data_v[0];
524
525                                 /* Translate the client pointer to a real device pointer. */
526                                 mem.device_pointer = device_ptr_from_client_pointer(client_pointer);
527                         }
528                         else {
529                                 /* Allocate host side data buffer. */
530                                 DataVector &data_v = data_vector_insert(client_pointer, data_size);
531                                 mem.data_pointer = (data_size)? (device_ptr)&(data_v[0]): 0;
532                         }
533
534                         /* Zero memory. */
535                         device->mem_zero(mem);
536
537                         if(!client_pointer) {
538                                 /* Store a mapping to/from client_pointer and real device pointer. */
539                                 pointer_mapping_insert(client_pointer, mem.device_pointer);
540                         }
541                 }
542                 else if(rcv.name == "mem_free") {
543                         string name;
544                         network_device_memory mem(device);
545
546                         rcv.read(mem, name);
547                         lock.unlock();
548
549                         device_ptr client_pointer = mem.device_pointer;
550
551                         mem.device_pointer = device_ptr_from_client_pointer_erase(client_pointer);
552
553                         device->mem_free(mem);
554                 }
555                 else if(rcv.name == "const_copy_to") {
556                         string name_string;
557                         size_t size;
558
559                         rcv.read(name_string);
560                         rcv.read(size);
561
562                         vector<char> host_vector(size);
563                         rcv.read_buffer(&host_vector[0], size);
564                         lock.unlock();
565
566                         device->const_copy_to(name_string.c_str(), &host_vector[0], size);
567                 }
568                 else if(rcv.name == "load_kernels") {
569                         DeviceRequestedFeatures requested_features;
570                         rcv.read(requested_features.experimental);
571                         rcv.read(requested_features.max_closure);
572                         rcv.read(requested_features.max_nodes_group);
573                         rcv.read(requested_features.nodes_features);
574
575                         bool result;
576                         result = device->load_kernels(requested_features);
577                         RPCSend snd(socket, &error_func, "load_kernels");
578                         snd.add(result);
579                         snd.write();
580                         lock.unlock();
581                 }
582                 else if(rcv.name == "task_add") {
583                         DeviceTask task;
584
585                         rcv.read(task);
586                         lock.unlock();
587
588                         if(task.buffer)
589                                 task.buffer = device_ptr_from_client_pointer(task.buffer);
590
591                         if(task.rgba_half)
592                                 task.rgba_half = device_ptr_from_client_pointer(task.rgba_half);
593
594                         if(task.rgba_byte)
595                                 task.rgba_byte = device_ptr_from_client_pointer(task.rgba_byte);
596
597                         if(task.shader_input)
598                                 task.shader_input = device_ptr_from_client_pointer(task.shader_input);
599
600                         if(task.shader_output)
601                                 task.shader_output = device_ptr_from_client_pointer(task.shader_output);
602
603                         task.acquire_tile = function_bind(&DeviceServer::task_acquire_tile, this, _1, _2);
604                         task.release_tile = function_bind(&DeviceServer::task_release_tile, this, _1);
605                         task.update_progress_sample = function_bind(&DeviceServer::task_update_progress_sample, this);
606                         task.update_tile_sample = function_bind(&DeviceServer::task_update_tile_sample, this, _1);
607                         task.get_cancel = function_bind(&DeviceServer::task_get_cancel, this);
608
609                         device->task_add(task);
610                 }
611                 else if(rcv.name == "task_wait") {
612                         lock.unlock();
613
614                         blocked_waiting = true;
615                         device->task_wait();
616                         blocked_waiting = false;
617
618                         lock.lock();
619                         RPCSend snd(socket, &error_func, "task_wait_done");
620                         snd.write();
621                         lock.unlock();
622                 }
623                 else if(rcv.name == "task_cancel") {
624                         lock.unlock();
625                         device->task_cancel();
626                 }
627                 else if(rcv.name == "acquire_tile") {
628                         AcquireEntry entry;
629                         entry.name = rcv.name;
630                         rcv.read(entry.tile);
631                         acquire_queue.push_back(entry);
632                         lock.unlock();
633                 }
634                 else if(rcv.name == "acquire_tile_none") {
635                         AcquireEntry entry;
636                         entry.name = rcv.name;
637                         acquire_queue.push_back(entry);
638                         lock.unlock();
639                 }
640                 else if(rcv.name == "release_tile") {
641                         AcquireEntry entry;
642                         entry.name = rcv.name;
643                         acquire_queue.push_back(entry);
644                         lock.unlock();
645                 }
646                 else {
647                         cout << "Error: unexpected RPC receive call \"" + rcv.name + "\"\n";
648                         lock.unlock();
649                 }
650         }
651
652         bool task_acquire_tile(Device *, RenderTile& tile)
653         {
654                 thread_scoped_lock acquire_lock(acquire_mutex);
655
656                 bool result = false;
657
658                 RPCSend snd(socket, &error_func, "acquire_tile");
659                 snd.write();
660
661                 do {
662                         if(blocked_waiting)
663                                 listen_step();
664
665                         /* todo: avoid busy wait loop */
666                         thread_scoped_lock lock(rpc_lock);
667
668                         if(!acquire_queue.empty()) {
669                                 AcquireEntry entry = acquire_queue.front();
670                                 acquire_queue.pop_front();
671
672                                 if(entry.name == "acquire_tile") {
673                                         tile = entry.tile;
674
675                                         if(tile.buffer) tile.buffer = ptr_map[tile.buffer];
676
677                                         result = true;
678                                         break;
679                                 }
680                                 else if(entry.name == "acquire_tile_none") {
681                                         break;
682                                 }
683                                 else {
684                                         cout << "Error: unexpected acquire RPC receive call \"" + entry.name + "\"\n";
685                                 }
686                         }
687                 } while(acquire_queue.empty() && !stop && !have_error());
688
689                 return result;
690         }
691
692         void task_update_progress_sample()
693         {
694                 ; /* skip */
695         }
696
697         void task_update_tile_sample(RenderTile&)
698         {
699                 ; /* skip */
700         }
701
702         void task_release_tile(RenderTile& tile)
703         {
704                 thread_scoped_lock acquire_lock(acquire_mutex);
705
706                 if(tile.buffer) tile.buffer = ptr_imap[tile.buffer];
707
708                 {
709                         thread_scoped_lock lock(rpc_lock);
710                         RPCSend snd(socket, &error_func, "release_tile");
711                         snd.add(tile);
712                         snd.write();
713                         lock.unlock();
714                 }
715
716                 do {
717                         if(blocked_waiting)
718                                 listen_step();
719
720                         /* todo: avoid busy wait loop */
721                         thread_scoped_lock lock(rpc_lock);
722
723                         if(!acquire_queue.empty()) {
724                                 AcquireEntry entry = acquire_queue.front();
725                                 acquire_queue.pop_front();
726
727                                 if(entry.name == "release_tile") {
728                                         lock.unlock();
729                                         break;
730                                 }
731                                 else {
732                                         cout << "Error: unexpected release RPC receive call \"" + entry.name + "\"\n";
733                                 }
734                         }
735                 } while(acquire_queue.empty() && !stop);
736         }
737
738         bool task_get_cancel()
739         {
740                 return false;
741         }
742
743         /* properties */
744         Device *device;
745         tcp::socket& socket;
746
747         /* mapping of remote to local pointer */
748         PtrMap ptr_map;
749         PtrMap ptr_imap;
750         DataMap mem_data;
751
752         struct AcquireEntry {
753                 string name;
754                 RenderTile tile;
755         };
756
757         thread_mutex acquire_mutex;
758         list<AcquireEntry> acquire_queue;
759
760         bool stop;
761         bool blocked_waiting;
762 private:
763         NetworkError error_func;
764
765         /* todo: free memory and device (osl) on network error */
766
767 };
768
769 void Device::server_run()
770 {
771         try {
772                 /* starts thread that responds to discovery requests */
773                 ServerDiscovery discovery;
774
775                 for(;;) {
776                         /* accept connection */
777                         boost::asio::io_service io_service;
778                         tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), SERVER_PORT));
779
780                         tcp::socket socket(io_service);
781                         acceptor.accept(socket);
782
783                         string remote_address = socket.remote_endpoint().address().to_string();
784                         printf("Connected to remote client at: %s\n", remote_address.c_str());
785
786                         DeviceServer server(this, socket);
787                         server.listen();
788
789                         printf("Disconnected.\n");
790                 }
791         }
792         catch(exception& e) {
793                 fprintf(stderr, "Network server exception: %s\n", e.what());
794         }
795 }
796
797 CCL_NAMESPACE_END
798
799 #endif
800
801