]>
Commit | Line | Data |
---|---|---|
30cc6b51 | 1 | /* Plugin for offload execution on Intel MIC devices. |
2 | ||
5f74ee59 | 3 | Copyright (C) 2014-2015 Free Software Foundation, Inc. |
30cc6b51 | 4 | |
5 | Contributed by Ilya Verbin <ilya.verbin@intel.com>. | |
6 | ||
c35c9a62 | 7 | This file is part of the GNU Offloading and Multi Processing Library |
8 | (libgomp). | |
30cc6b51 | 9 | |
10 | Libgomp is free software; you can redistribute it and/or modify it | |
11 | under the terms of the GNU General Public License as published by | |
12 | the Free Software Foundation; either version 3, or (at your option) | |
13 | any later version. | |
14 | ||
15 | Libgomp is distributed in the hope that it will be useful, but WITHOUT ANY | |
16 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
17 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
18 | more details. | |
19 | ||
20 | Under Section 7 of GPL version 3, you are granted additional | |
21 | permissions described in the GCC Runtime Library Exception, version | |
22 | 3.1, as published by the Free Software Foundation. | |
23 | ||
24 | You should have received a copy of the GNU General Public License and | |
25 | a copy of the GCC Runtime Library Exception along with this program; | |
26 | see the files COPYING3 and COPYING.RUNTIME respectively. If not, see | |
27 | <http://www.gnu.org/licenses/>. */ | |
28 | ||
29 | /* Host side part of a libgomp plugin. */ | |
30 | ||
31 | #include <stdint.h> | |
32 | #include <stdio.h> | |
33 | #include <stdlib.h> | |
34 | #include <string.h> | |
35 | #include <utility> | |
36 | #include <vector> | |
0d8c703d | 37 | #include <map> |
ca4c3545 | 38 | #include "libgomp-plugin.h" |
30cc6b51 | 39 | #include "compiler_if_host.h" |
40 | #include "main_target_image.h" | |
5f74ee59 | 41 | #include "gomp-constants.h" |
30cc6b51 | 42 | |
43 | #define LD_LIBRARY_PATH_ENV "LD_LIBRARY_PATH" | |
44 | #define MIC_LD_LIBRARY_PATH_ENV "MIC_LD_LIBRARY_PATH" | |
b30bdf28 | 45 | #define OFFLOAD_ACTIVE_WAIT_ENV "OFFLOAD_ACTIVE_WAIT" |
30cc6b51 | 46 | |
47 | #ifdef DEBUG | |
48 | #define TRACE(...) \ | |
49 | { \ | |
50 | fprintf (stderr, "HOST:\t%s:%s ", __FILE__, __FUNCTION__); \ | |
51 | fprintf (stderr, __VA_ARGS__); \ | |
52 | fprintf (stderr, "\n"); \ | |
53 | } | |
54 | #else | |
55 | #define TRACE { } | |
56 | #endif | |
57 | ||
58 | ||
0d8c703d | 59 | /* Start/end addresses of functions and global variables on a device. */ |
60 | typedef std::vector<addr_pair> AddrVect; | |
61 | ||
62 | /* Addresses for one image and all devices. */ | |
63 | typedef std::vector<AddrVect> DevAddrVect; | |
64 | ||
65 | /* Addresses for all images and all devices. */ | |
70046055 | 66 | typedef std::map<const void *, DevAddrVect> ImgDevAddrMap; |
0d8c703d | 67 | |
68 | ||
69 | /* Total number of available devices. */ | |
70 | static int num_devices; | |
71 | ||
72 | /* Total number of shared libraries with offloading to Intel MIC. */ | |
73 | static int num_images; | |
74 | ||
75 | /* Two dimensional array: one key is a pointer to image, | |
76 | second key is number of device. Contains a vector of pointer pairs. */ | |
77 | static ImgDevAddrMap *address_table; | |
78 | ||
79 | /* Thread-safe registration of the main image. */ | |
80 | static pthread_once_t main_image_is_registered = PTHREAD_ONCE_INIT; | |
81 | ||
30cc6b51 | 82 | static VarDesc vd_host2tgt = { |
83 | { 1, 1 }, /* dst, src */ | |
84 | { 1, 0 }, /* in, out */ | |
85 | 1, /* alloc_if */ | |
86 | 1, /* free_if */ | |
87 | 4, /* align */ | |
88 | 0, /* mic_offset */ | |
89 | { 0, 0, 0, 0, 0, 0, 0, 0 }, /* is_static, is_static_dstn, has_length, | |
90 | is_stack_buf, sink_addr, alloc_disp, | |
91 | is_noncont_src, is_noncont_dst */ | |
92 | 0, /* offset */ | |
93 | 0, /* size */ | |
94 | 1, /* count */ | |
95 | 0, /* alloc */ | |
96 | 0, /* into */ | |
97 | 0 /* ptr */ | |
98 | }; | |
99 | ||
100 | static VarDesc vd_tgt2host = { | |
101 | { 1, 1 }, /* dst, src */ | |
102 | { 0, 1 }, /* in, out */ | |
103 | 1, /* alloc_if */ | |
104 | 1, /* free_if */ | |
105 | 4, /* align */ | |
106 | 0, /* mic_offset */ | |
107 | { 0, 0, 0, 0, 0, 0, 0, 0 }, /* is_static, is_static_dstn, has_length, | |
108 | is_stack_buf, sink_addr, alloc_disp, | |
109 | is_noncont_src, is_noncont_dst */ | |
110 | 0, /* offset */ | |
111 | 0, /* size */ | |
112 | 1, /* count */ | |
113 | 0, /* alloc */ | |
114 | 0, /* into */ | |
115 | 0 /* ptr */ | |
116 | }; | |
117 | ||
118 | ||
30cc6b51 | 119 | __attribute__((constructor)) |
120 | static void | |
0d8c703d | 121 | init (void) |
30cc6b51 | 122 | { |
123 | const char *ld_lib_path = getenv (LD_LIBRARY_PATH_ENV); | |
124 | const char *mic_lib_path = getenv (MIC_LD_LIBRARY_PATH_ENV); | |
b30bdf28 | 125 | const char *active_wait = getenv (OFFLOAD_ACTIVE_WAIT_ENV); |
126 | ||
127 | /* Disable active wait by default to avoid useless CPU usage. */ | |
128 | if (!active_wait) | |
129 | setenv (OFFLOAD_ACTIVE_WAIT_ENV, "0", 0); | |
30cc6b51 | 130 | |
131 | if (!ld_lib_path) | |
0d8c703d | 132 | goto out; |
30cc6b51 | 133 | |
b30bdf28 | 134 | /* Add path specified in LD_LIBRARY_PATH to MIC_LD_LIBRARY_PATH, which is |
135 | required by liboffloadmic. */ | |
30cc6b51 | 136 | if (!mic_lib_path) |
137 | setenv (MIC_LD_LIBRARY_PATH_ENV, ld_lib_path, 1); | |
138 | else | |
139 | { | |
140 | size_t len = strlen (mic_lib_path) + strlen (ld_lib_path) + 2; | |
141 | bool use_alloca = len <= 2048; | |
142 | char *mic_lib_path_new = (char *) (use_alloca ? alloca (len) | |
143 | : malloc (len)); | |
144 | if (!mic_lib_path_new) | |
145 | { | |
146 | fprintf (stderr, "%s: Can't allocate memory\n", __FILE__); | |
147 | exit (1); | |
148 | } | |
149 | ||
150 | sprintf (mic_lib_path_new, "%s:%s", mic_lib_path, ld_lib_path); | |
151 | setenv (MIC_LD_LIBRARY_PATH_ENV, mic_lib_path_new, 1); | |
152 | ||
153 | if (!use_alloca) | |
154 | free (mic_lib_path_new); | |
155 | } | |
0d8c703d | 156 | |
157 | out: | |
158 | address_table = new ImgDevAddrMap; | |
159 | num_devices = _Offload_number_of_devices (); | |
30cc6b51 | 160 | } |
161 | ||
ca4c3545 | 162 | extern "C" const char * |
163 | GOMP_OFFLOAD_get_name (void) | |
164 | { | |
165 | const char *res = "intelmic"; | |
166 | TRACE ("(): return %s", res); | |
167 | return res; | |
168 | } | |
169 | ||
170 | extern "C" unsigned int | |
171 | GOMP_OFFLOAD_get_caps (void) | |
172 | { | |
173 | unsigned int res = GOMP_OFFLOAD_CAP_OPENMP_400; | |
174 | TRACE ("(): return %x", res); | |
175 | return res; | |
176 | } | |
177 | ||
30cc6b51 | 178 | extern "C" enum offload_target_type |
179 | GOMP_OFFLOAD_get_type (void) | |
180 | { | |
181 | enum offload_target_type res = OFFLOAD_TARGET_TYPE_INTEL_MIC; | |
182 | TRACE ("(): return %d", res); | |
183 | return res; | |
184 | } | |
185 | ||
186 | extern "C" int | |
187 | GOMP_OFFLOAD_get_num_devices (void) | |
188 | { | |
0d8c703d | 189 | TRACE ("(): return %d", num_devices); |
190 | return num_devices; | |
30cc6b51 | 191 | } |
192 | ||
193 | static void | |
194 | offload (const char *file, uint64_t line, int device, const char *name, | |
195 | int num_vars, VarDesc *vars, VarDesc2 *vars2) | |
196 | { | |
197 | OFFLOAD ofld = __offload_target_acquire1 (&device, file, line); | |
198 | if (ofld) | |
199 | __offload_offload1 (ofld, name, 0, num_vars, vars, vars2, 0, NULL, NULL); | |
200 | else | |
201 | { | |
202 | fprintf (stderr, "%s:%d: Offload target acquire failed\n", file, line); | |
203 | exit (1); | |
204 | } | |
205 | } | |
206 | ||
207 | static void | |
208 | register_main_image () | |
209 | { | |
210 | __offload_register_image (&main_target_image); | |
211 | } | |
212 | ||
0d8c703d | 213 | /* liboffloadmic loads and runs offload_target_main on all available devices |
214 | during a first call to offload (). */ | |
30cc6b51 | 215 | extern "C" void |
216 | GOMP_OFFLOAD_init_device (int device) | |
217 | { | |
218 | TRACE (""); | |
219 | pthread_once (&main_image_is_registered, register_main_image); | |
220 | offload (__FILE__, __LINE__, device, "__offload_target_init_proc", 0, | |
221 | NULL, NULL); | |
222 | } | |
223 | ||
ca4c3545 | 224 | extern "C" void |
225 | GOMP_OFFLOAD_fini_device (int device) | |
226 | { | |
227 | TRACE (""); | |
228 | /* Unreachable for GOMP_OFFLOAD_CAP_OPENMP_400. */ | |
229 | abort (); | |
230 | } | |
231 | ||
30cc6b51 | 232 | static void |
233 | get_target_table (int device, int &num_funcs, int &num_vars, void **&table) | |
234 | { | |
235 | VarDesc vd1[2] = { vd_tgt2host, vd_tgt2host }; | |
236 | vd1[0].ptr = &num_funcs; | |
237 | vd1[0].size = sizeof (num_funcs); | |
238 | vd1[1].ptr = &num_vars; | |
239 | vd1[1].size = sizeof (num_vars); | |
240 | VarDesc2 vd1g[2] = { { "num_funcs", 0 }, { "num_vars", 0 } }; | |
241 | ||
242 | offload (__FILE__, __LINE__, device, "__offload_target_table_p1", 2, | |
243 | vd1, vd1g); | |
244 | ||
245 | int table_size = num_funcs + 2 * num_vars; | |
246 | if (table_size > 0) | |
247 | { | |
248 | table = new void * [table_size]; | |
249 | ||
250 | VarDesc vd2; | |
251 | vd2 = vd_tgt2host; | |
252 | vd2.ptr = table; | |
253 | vd2.size = table_size * sizeof (void *); | |
254 | VarDesc2 vd2g = { "table", 0 }; | |
255 | ||
256 | offload (__FILE__, __LINE__, device, "__offload_target_table_p2", 1, | |
257 | &vd2, &vd2g); | |
258 | } | |
259 | } | |
260 | ||
0d8c703d | 261 | /* Offload TARGET_IMAGE to all available devices and fill address_table with |
262 | corresponding target addresses. */ | |
263 | ||
30cc6b51 | 264 | static void |
70046055 | 265 | offload_image (const void *target_image) |
30cc6b51 | 266 | { |
267 | struct TargetImage { | |
268 | int64_t size; | |
269 | /* 10 characters is enough for max int value. */ | |
270 | char name[sizeof ("lib0000000000.so")]; | |
271 | char data[]; | |
272 | } __attribute__ ((packed)); | |
273 | ||
0d8c703d | 274 | void *image_start = ((void **) target_image)[0]; |
275 | void *image_end = ((void **) target_image)[1]; | |
30cc6b51 | 276 | |
0d8c703d | 277 | TRACE ("(target_image = %p { %p, %p })", |
278 | target_image, image_start, image_end); | |
30cc6b51 | 279 | |
280 | int64_t image_size = (uintptr_t) image_end - (uintptr_t) image_start; | |
281 | TargetImage *image | |
282 | = (TargetImage *) malloc (sizeof (int64_t) + sizeof ("lib0000000000.so") | |
283 | + image_size); | |
284 | if (!image) | |
285 | { | |
286 | fprintf (stderr, "%s: Can't allocate memory\n", __FILE__); | |
287 | exit (1); | |
288 | } | |
289 | ||
290 | image->size = image_size; | |
0d8c703d | 291 | sprintf (image->name, "lib%010d.so", num_images++); |
30cc6b51 | 292 | memcpy (image->data, image_start, image->size); |
293 | ||
294 | TRACE ("() __offload_register_image %s { %p, %d }", | |
295 | image->name, image_start, image->size); | |
296 | __offload_register_image (image); | |
297 | ||
0d8c703d | 298 | /* Receive tables for target_image from all devices. */ |
299 | DevAddrVect dev_table; | |
300 | for (int dev = 0; dev < num_devices; dev++) | |
30cc6b51 | 301 | { |
0d8c703d | 302 | int num_funcs = 0; |
303 | int num_vars = 0; | |
304 | void **table = NULL; | |
30cc6b51 | 305 | |
0d8c703d | 306 | get_target_table (dev, num_funcs, num_vars, table); |
30cc6b51 | 307 | |
0d8c703d | 308 | AddrVect curr_dev_table; |
30cc6b51 | 309 | |
0d8c703d | 310 | for (int i = 0; i < num_funcs; i++) |
311 | { | |
312 | addr_pair tgt_addr; | |
313 | tgt_addr.start = (uintptr_t) table[i]; | |
314 | tgt_addr.end = tgt_addr.start + 1; | |
315 | TRACE ("() func %d:\t0x%llx..0x%llx", i, | |
316 | tgt_addr.start, tgt_addr.end); | |
317 | curr_dev_table.push_back (tgt_addr); | |
318 | } | |
30cc6b51 | 319 | |
0d8c703d | 320 | for (int i = 0; i < num_vars; i++) |
321 | { | |
322 | addr_pair tgt_addr; | |
323 | tgt_addr.start = (uintptr_t) table[num_funcs+i*2]; | |
324 | tgt_addr.end = tgt_addr.start + (uintptr_t) table[num_funcs+i*2+1]; | |
325 | TRACE ("() var %d:\t0x%llx..0x%llx", i, tgt_addr.start, tgt_addr.end); | |
326 | curr_dev_table.push_back (tgt_addr); | |
327 | } | |
30cc6b51 | 328 | |
0d8c703d | 329 | dev_table.push_back (curr_dev_table); |
30cc6b51 | 330 | } |
331 | ||
0d8c703d | 332 | address_table->insert (std::make_pair (target_image, dev_table)); |
333 | ||
334 | free (image); | |
30cc6b51 | 335 | } |
336 | ||
d3d8e632 | 337 | /* Return the libgomp version number we're compatible with. There is |
338 | no requirement for cross-version compatibility. */ | |
339 | ||
340 | extern "C" unsigned | |
341 | GOMP_OFFLOAD_version (void) | |
342 | { | |
343 | return GOMP_VERSION; | |
344 | } | |
345 | ||
30cc6b51 | 346 | extern "C" int |
d3d8e632 | 347 | GOMP_OFFLOAD_load_image (int device, const unsigned version, |
348 | void *target_image, addr_pair **result) | |
30cc6b51 | 349 | { |
0d8c703d | 350 | TRACE ("(device = %d, target_image = %p)", device, target_image); |
30cc6b51 | 351 | |
d3d8e632 | 352 | if (GOMP_VERSION_DEV (version) > GOMP_VERSION_INTEL_MIC) |
353 | GOMP_PLUGIN_fatal ("Offload data incompatible with intelmic plugin" | |
354 | " (expected %u, received %u)", | |
355 | GOMP_VERSION_INTEL_MIC, GOMP_VERSION_DEV (version)); | |
356 | ||
0d8c703d | 357 | /* If target_image is already present in address_table, then there is no need |
358 | to offload it. */ | |
359 | if (address_table->count (target_image) == 0) | |
360 | offload_image (target_image); | |
30cc6b51 | 361 | |
0d8c703d | 362 | AddrVect *curr_dev_table = &(*address_table)[target_image][device]; |
363 | int table_size = curr_dev_table->size (); | |
364 | addr_pair *table = (addr_pair *) malloc (table_size * sizeof (addr_pair)); | |
365 | if (table == NULL) | |
366 | { | |
367 | fprintf (stderr, "%s: Can't allocate memory\n", __FILE__); | |
368 | exit (1); | |
369 | } | |
30cc6b51 | 370 | |
0d8c703d | 371 | std::copy (curr_dev_table->begin (), curr_dev_table->end (), table); |
372 | *result = table; | |
30cc6b51 | 373 | return table_size; |
374 | } | |
375 | ||
0d8c703d | 376 | extern "C" void |
d3d8e632 | 377 | GOMP_OFFLOAD_unload_image (int device, unsigned version, |
378 | const void *target_image) | |
0d8c703d | 379 | { |
d3d8e632 | 380 | if (GOMP_VERSION_DEV (version) > GOMP_VERSION_INTEL_MIC) |
381 | return; | |
382 | ||
0d8c703d | 383 | TRACE ("(device = %d, target_image = %p)", device, target_image); |
384 | ||
385 | /* TODO: Currently liboffloadmic doesn't support __offload_unregister_image | |
386 | for libraries. */ | |
387 | ||
388 | address_table->erase (target_image); | |
389 | } | |
390 | ||
30cc6b51 | 391 | extern "C" void * |
392 | GOMP_OFFLOAD_alloc (int device, size_t size) | |
393 | { | |
394 | TRACE ("(size = %d)", size); | |
395 | ||
396 | void *tgt_ptr; | |
397 | VarDesc vd1[2] = { vd_host2tgt, vd_tgt2host }; | |
398 | vd1[0].ptr = &size; | |
399 | vd1[0].size = sizeof (size); | |
400 | vd1[1].ptr = &tgt_ptr; | |
401 | vd1[1].size = sizeof (void *); | |
402 | VarDesc2 vd1g[2] = { { "size", 0 }, { "tgt_ptr", 0 } }; | |
403 | ||
404 | offload (__FILE__, __LINE__, device, "__offload_target_alloc", 2, vd1, vd1g); | |
405 | ||
406 | return tgt_ptr; | |
407 | } | |
408 | ||
409 | extern "C" void | |
410 | GOMP_OFFLOAD_free (int device, void *tgt_ptr) | |
411 | { | |
412 | TRACE ("(tgt_ptr = %p)", tgt_ptr); | |
413 | ||
414 | VarDesc vd1 = vd_host2tgt; | |
415 | vd1.ptr = &tgt_ptr; | |
416 | vd1.size = sizeof (void *); | |
417 | VarDesc2 vd1g = { "tgt_ptr", 0 }; | |
418 | ||
419 | offload (__FILE__, __LINE__, device, "__offload_target_free", 1, &vd1, &vd1g); | |
420 | } | |
421 | ||
422 | extern "C" void * | |
423 | GOMP_OFFLOAD_host2dev (int device, void *tgt_ptr, const void *host_ptr, | |
424 | size_t size) | |
425 | { | |
426 | TRACE ("(tgt_ptr = %p, host_ptr = %p, size = %d)", tgt_ptr, host_ptr, size); | |
427 | if (!size) | |
428 | return tgt_ptr; | |
429 | ||
430 | VarDesc vd1[2] = { vd_host2tgt, vd_host2tgt }; | |
431 | vd1[0].ptr = &tgt_ptr; | |
432 | vd1[0].size = sizeof (void *); | |
433 | vd1[1].ptr = &size; | |
434 | vd1[1].size = sizeof (size); | |
435 | VarDesc2 vd1g[2] = { { "tgt_ptr", 0 }, { "size", 0 } }; | |
436 | ||
437 | offload (__FILE__, __LINE__, device, "__offload_target_host2tgt_p1", 2, | |
438 | vd1, vd1g); | |
439 | ||
440 | VarDesc vd2 = vd_host2tgt; | |
441 | vd2.ptr = (void *) host_ptr; | |
442 | vd2.size = size; | |
443 | VarDesc2 vd2g = { "var", 0 }; | |
444 | ||
445 | offload (__FILE__, __LINE__, device, "__offload_target_host2tgt_p2", 1, | |
446 | &vd2, &vd2g); | |
447 | ||
448 | return tgt_ptr; | |
449 | } | |
450 | ||
451 | extern "C" void * | |
452 | GOMP_OFFLOAD_dev2host (int device, void *host_ptr, const void *tgt_ptr, | |
453 | size_t size) | |
454 | { | |
455 | TRACE ("(host_ptr = %p, tgt_ptr = %p, size = %d)", host_ptr, tgt_ptr, size); | |
456 | if (!size) | |
457 | return host_ptr; | |
458 | ||
459 | VarDesc vd1[2] = { vd_host2tgt, vd_host2tgt }; | |
460 | vd1[0].ptr = &tgt_ptr; | |
461 | vd1[0].size = sizeof (void *); | |
462 | vd1[1].ptr = &size; | |
463 | vd1[1].size = sizeof (size); | |
464 | VarDesc2 vd1g[2] = { { "tgt_ptr", 0 }, { "size", 0 } }; | |
465 | ||
466 | offload (__FILE__, __LINE__, device, "__offload_target_tgt2host_p1", 2, | |
467 | vd1, vd1g); | |
468 | ||
469 | VarDesc vd2 = vd_tgt2host; | |
470 | vd2.ptr = (void *) host_ptr; | |
471 | vd2.size = size; | |
472 | VarDesc2 vd2g = { "var", 0 }; | |
473 | ||
474 | offload (__FILE__, __LINE__, device, "__offload_target_tgt2host_p2", 1, | |
475 | &vd2, &vd2g); | |
476 | ||
477 | return host_ptr; | |
478 | } | |
479 | ||
43895be5 | 480 | extern "C" void * |
481 | GOMP_OFFLOAD_dev2dev (int device, void *dst_ptr, const void *src_ptr, | |
482 | size_t size) | |
483 | { | |
484 | TRACE ("(dst_ptr = %p, src_ptr = %p, size = %d)", dst_ptr, src_ptr, size); | |
485 | if (!size) | |
486 | return dst_ptr; | |
487 | ||
488 | VarDesc vd1[3] = { vd_host2tgt, vd_host2tgt, vd_host2tgt }; | |
489 | vd1[0].ptr = &dst_ptr; | |
490 | vd1[0].size = sizeof (void *); | |
491 | vd1[1].ptr = &src_ptr; | |
492 | vd1[1].size = sizeof (void *); | |
493 | vd1[2].ptr = &size; | |
494 | vd1[2].size = sizeof (size); | |
495 | VarDesc2 vd1g[3] = { { "dst_ptr", 0 }, { "src_ptr", 0 }, { "size", 0 } }; | |
496 | ||
497 | offload (__FILE__, __LINE__, device, "__offload_target_tgt2tgt", 3, vd1, | |
498 | vd1g); | |
499 | ||
500 | return dst_ptr; | |
501 | } | |
502 | ||
30cc6b51 | 503 | extern "C" void |
504 | GOMP_OFFLOAD_run (int device, void *tgt_fn, void *tgt_vars) | |
505 | { | |
506 | TRACE ("(tgt_fn = %p, tgt_vars = %p)", tgt_fn, tgt_vars); | |
507 | ||
508 | VarDesc vd1[2] = { vd_host2tgt, vd_host2tgt }; | |
509 | vd1[0].ptr = &tgt_fn; | |
510 | vd1[0].size = sizeof (void *); | |
511 | vd1[1].ptr = &tgt_vars; | |
512 | vd1[1].size = sizeof (void *); | |
513 | VarDesc2 vd1g[2] = { { "tgt_fn", 0 }, { "tgt_vars", 0 } }; | |
514 | ||
515 | offload (__FILE__, __LINE__, device, "__offload_target_run", 2, vd1, vd1g); | |
516 | } |