1// Copyright 2011 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package runtime 6 7import ( 8 "runtime/internal/sys" 9 "unsafe" 10) 11 12//go:cgo_export_dynamic runtime.end _end 13//go:cgo_export_dynamic runtime.etext _etext 14//go:cgo_export_dynamic runtime.edata _edata 15 16//go:cgo_import_dynamic libc____errno ___errno "libc.so" 17//go:cgo_import_dynamic libc_clock_gettime clock_gettime "libc.so" 18//go:cgo_import_dynamic libc_exit exit "libc.so" 19//go:cgo_import_dynamic libc_getcontext getcontext "libc.so" 20//go:cgo_import_dynamic libc_kill kill "libc.so" 21//go:cgo_import_dynamic libc_madvise madvise "libc.so" 22//go:cgo_import_dynamic libc_malloc malloc "libc.so" 23//go:cgo_import_dynamic libc_mmap mmap "libc.so" 24//go:cgo_import_dynamic libc_munmap munmap "libc.so" 25//go:cgo_import_dynamic libc_open open "libc.so" 26//go:cgo_import_dynamic libc_pthread_attr_destroy pthread_attr_destroy "libc.so" 27//go:cgo_import_dynamic libc_pthread_attr_getstack pthread_attr_getstack "libc.so" 28//go:cgo_import_dynamic libc_pthread_attr_init pthread_attr_init "libc.so" 29//go:cgo_import_dynamic libc_pthread_attr_setdetachstate pthread_attr_setdetachstate "libc.so" 30//go:cgo_import_dynamic libc_pthread_attr_setstack pthread_attr_setstack "libc.so" 31//go:cgo_import_dynamic libc_pthread_create pthread_create "libc.so" 32//go:cgo_import_dynamic libc_pthread_self pthread_self "libc.so" 33//go:cgo_import_dynamic libc_pthread_kill pthread_kill "libc.so" 34//go:cgo_import_dynamic libc_raise raise "libc.so" 35//go:cgo_import_dynamic libc_read read "libc.so" 36//go:cgo_import_dynamic libc_select select "libc.so" 37//go:cgo_import_dynamic libc_sched_yield sched_yield "libc.so" 38//go:cgo_import_dynamic libc_sem_init sem_init "libc.so" 39//go:cgo_import_dynamic libc_sem_post sem_post "libc.so" 40//go:cgo_import_dynamic libc_sem_reltimedwait_np sem_reltimedwait_np "libc.so" 41//go:cgo_import_dynamic libc_sem_wait sem_wait "libc.so" 42//go:cgo_import_dynamic libc_setitimer setitimer "libc.so" 43//go:cgo_import_dynamic libc_sigaction sigaction "libc.so" 44//go:cgo_import_dynamic libc_sigaltstack sigaltstack "libc.so" 45//go:cgo_import_dynamic libc_sigprocmask sigprocmask "libc.so" 46//go:cgo_import_dynamic libc_sysconf sysconf "libc.so" 47//go:cgo_import_dynamic libc_usleep usleep "libc.so" 48//go:cgo_import_dynamic libc_write write "libc.so" 49//go:cgo_import_dynamic libc_pipe pipe "libc.so" 50//go:cgo_import_dynamic libc_pipe2 pipe2 "libc.so" 51 52//go:linkname libc____errno libc____errno 53//go:linkname libc_clock_gettime libc_clock_gettime 54//go:linkname libc_exit libc_exit 55//go:linkname libc_getcontext libc_getcontext 56//go:linkname libc_kill libc_kill 57//go:linkname libc_madvise libc_madvise 58//go:linkname libc_malloc libc_malloc 59//go:linkname libc_mmap libc_mmap 60//go:linkname libc_munmap libc_munmap 61//go:linkname libc_open libc_open 62//go:linkname libc_pthread_attr_destroy libc_pthread_attr_destroy 63//go:linkname libc_pthread_attr_getstack libc_pthread_attr_getstack 64//go:linkname libc_pthread_attr_init libc_pthread_attr_init 65//go:linkname libc_pthread_attr_setdetachstate libc_pthread_attr_setdetachstate 66//go:linkname libc_pthread_attr_setstack libc_pthread_attr_setstack 67//go:linkname libc_pthread_create libc_pthread_create 68//go:linkname libc_pthread_self libc_pthread_self 69//go:linkname libc_pthread_kill libc_pthread_kill 70//go:linkname libc_raise libc_raise 71//go:linkname libc_read libc_read 72//go:linkname libc_select libc_select 73//go:linkname libc_sched_yield libc_sched_yield 74//go:linkname libc_sem_init libc_sem_init 75//go:linkname libc_sem_post libc_sem_post 76//go:linkname libc_sem_reltimedwait_np libc_sem_reltimedwait_np 77//go:linkname libc_sem_wait libc_sem_wait 78//go:linkname libc_setitimer libc_setitimer 79//go:linkname libc_sigaction libc_sigaction 80//go:linkname libc_sigaltstack libc_sigaltstack 81//go:linkname libc_sigprocmask libc_sigprocmask 82//go:linkname libc_sysconf libc_sysconf 83//go:linkname libc_usleep libc_usleep 84//go:linkname libc_write libc_write 85//go:linkname libc_pipe libc_pipe 86//go:linkname libc_pipe2 libc_pipe2 87 88var ( 89 libc____errno, 90 libc_clock_gettime, 91 libc_exit, 92 libc_getcontext, 93 libc_kill, 94 libc_madvise, 95 libc_malloc, 96 libc_mmap, 97 libc_munmap, 98 libc_open, 99 libc_pthread_attr_destroy, 100 libc_pthread_attr_getstack, 101 libc_pthread_attr_init, 102 libc_pthread_attr_setdetachstate, 103 libc_pthread_attr_setstack, 104 libc_pthread_create, 105 libc_pthread_self, 106 libc_pthread_kill, 107 libc_raise, 108 libc_read, 109 libc_sched_yield, 110 libc_select, 111 libc_sem_init, 112 libc_sem_post, 113 libc_sem_reltimedwait_np, 114 libc_sem_wait, 115 libc_setitimer, 116 libc_sigaction, 117 libc_sigaltstack, 118 libc_sigprocmask, 119 libc_sysconf, 120 libc_usleep, 121 libc_write, 122 libc_pipe, 123 libc_pipe2 libcFunc 124) 125 126var sigset_all = sigset{[4]uint32{^uint32(0), ^uint32(0), ^uint32(0), ^uint32(0)}} 127 128func getPageSize() uintptr { 129 n := int32(sysconf(__SC_PAGESIZE)) 130 if n <= 0 { 131 return 0 132 } 133 return uintptr(n) 134} 135 136func osinit() { 137 ncpu = getncpu() 138 if physPageSize == 0 { 139 physPageSize = getPageSize() 140 } 141} 142 143func tstart_sysvicall(newm *m) uint32 144 145// May run with m.p==nil, so write barriers are not allowed. 146//go:nowritebarrier 147func newosproc(mp *m) { 148 var ( 149 attr pthreadattr 150 oset sigset 151 tid pthread 152 ret int32 153 size uint64 154 ) 155 156 if pthread_attr_init(&attr) != 0 { 157 throw("pthread_attr_init") 158 } 159 // Allocate a new 2MB stack. 160 if pthread_attr_setstack(&attr, 0, 0x200000) != 0 { 161 throw("pthread_attr_setstack") 162 } 163 // Read back the allocated stack. 164 if pthread_attr_getstack(&attr, unsafe.Pointer(&mp.g0.stack.hi), &size) != 0 { 165 throw("pthread_attr_getstack") 166 } 167 mp.g0.stack.lo = mp.g0.stack.hi - uintptr(size) 168 if pthread_attr_setdetachstate(&attr, _PTHREAD_CREATE_DETACHED) != 0 { 169 throw("pthread_attr_setdetachstate") 170 } 171 172 // Disable signals during create, so that the new thread starts 173 // with signals disabled. It will enable them in minit. 174 sigprocmask(_SIG_SETMASK, &sigset_all, &oset) 175 ret = pthread_create(&tid, &attr, funcPC(tstart_sysvicall), unsafe.Pointer(mp)) 176 sigprocmask(_SIG_SETMASK, &oset, nil) 177 if ret != 0 { 178 print("runtime: failed to create new OS thread (have ", mcount(), " already; errno=", ret, ")\n") 179 if ret == -_EAGAIN { 180 println("runtime: may need to increase max user processes (ulimit -u)") 181 } 182 throw("newosproc") 183 } 184} 185 186func exitThread(wait *uint32) { 187 // We should never reach exitThread on Solaris because we let 188 // libc clean up threads. 189 throw("exitThread") 190} 191 192var urandom_dev = []byte("/dev/urandom\x00") 193 194//go:nosplit 195func getRandomData(r []byte) { 196 fd := open(&urandom_dev[0], 0 /* O_RDONLY */, 0) 197 n := read(fd, unsafe.Pointer(&r[0]), int32(len(r))) 198 closefd(fd) 199 extendRandom(r, int(n)) 200} 201 202func goenvs() { 203 goenvs_unix() 204} 205 206// Called to initialize a new m (including the bootstrap m). 207// Called on the parent thread (main thread in case of bootstrap), can allocate memory. 208func mpreinit(mp *m) { 209 mp.gsignal = malg(32 * 1024) 210 mp.gsignal.m = mp 211} 212 213func miniterrno() 214 215// Called to initialize a new m (including the bootstrap m). 216// Called on the new thread, cannot allocate memory. 217func minit() { 218 asmcgocall(unsafe.Pointer(funcPC(miniterrno)), unsafe.Pointer(&libc____errno)) 219 220 minitSignals() 221 222 getg().m.procid = uint64(pthread_self()) 223} 224 225// Called from dropm to undo the effect of an minit. 226func unminit() { 227 unminitSignals() 228} 229 230func sigtramp() 231 232//go:nosplit 233//go:nowritebarrierrec 234func setsig(i uint32, fn uintptr) { 235 var sa sigactiont 236 237 sa.sa_flags = _SA_SIGINFO | _SA_ONSTACK | _SA_RESTART 238 sa.sa_mask = sigset_all 239 if fn == funcPC(sighandler) { 240 fn = funcPC(sigtramp) 241 } 242 *((*uintptr)(unsafe.Pointer(&sa._funcptr))) = fn 243 sigaction(i, &sa, nil) 244} 245 246//go:nosplit 247//go:nowritebarrierrec 248func setsigstack(i uint32) { 249 var sa sigactiont 250 sigaction(i, nil, &sa) 251 if sa.sa_flags&_SA_ONSTACK != 0 { 252 return 253 } 254 sa.sa_flags |= _SA_ONSTACK 255 sigaction(i, &sa, nil) 256} 257 258//go:nosplit 259//go:nowritebarrierrec 260func getsig(i uint32) uintptr { 261 var sa sigactiont 262 sigaction(i, nil, &sa) 263 return *((*uintptr)(unsafe.Pointer(&sa._funcptr))) 264} 265 266// setSignaltstackSP sets the ss_sp field of a stackt. 267//go:nosplit 268func setSignalstackSP(s *stackt, sp uintptr) { 269 *(*uintptr)(unsafe.Pointer(&s.ss_sp)) = sp 270} 271 272//go:nosplit 273//go:nowritebarrierrec 274func sigaddset(mask *sigset, i int) { 275 mask.__sigbits[(i-1)/32] |= 1 << ((uint32(i) - 1) & 31) 276} 277 278func sigdelset(mask *sigset, i int) { 279 mask.__sigbits[(i-1)/32] &^= 1 << ((uint32(i) - 1) & 31) 280} 281 282//go:nosplit 283func (c *sigctxt) fixsigcode(sig uint32) { 284} 285 286//go:nosplit 287func semacreate(mp *m) { 288 if mp.waitsema != 0 { 289 return 290 } 291 292 var sem *semt 293 _g_ := getg() 294 295 // Call libc's malloc rather than malloc. This will 296 // allocate space on the C heap. We can't call malloc 297 // here because it could cause a deadlock. 298 _g_.m.libcall.fn = uintptr(unsafe.Pointer(&libc_malloc)) 299 _g_.m.libcall.n = 1 300 _g_.m.scratch = mscratch{} 301 _g_.m.scratch.v[0] = unsafe.Sizeof(*sem) 302 _g_.m.libcall.args = uintptr(unsafe.Pointer(&_g_.m.scratch)) 303 asmcgocall(unsafe.Pointer(&asmsysvicall6x), unsafe.Pointer(&_g_.m.libcall)) 304 sem = (*semt)(unsafe.Pointer(_g_.m.libcall.r1)) 305 if sem_init(sem, 0, 0) != 0 { 306 throw("sem_init") 307 } 308 mp.waitsema = uintptr(unsafe.Pointer(sem)) 309} 310 311//go:nosplit 312func semasleep(ns int64) int32 { 313 _m_ := getg().m 314 if ns >= 0 { 315 _m_.ts.tv_sec = ns / 1000000000 316 _m_.ts.tv_nsec = ns % 1000000000 317 318 _m_.libcall.fn = uintptr(unsafe.Pointer(&libc_sem_reltimedwait_np)) 319 _m_.libcall.n = 2 320 _m_.scratch = mscratch{} 321 _m_.scratch.v[0] = _m_.waitsema 322 _m_.scratch.v[1] = uintptr(unsafe.Pointer(&_m_.ts)) 323 _m_.libcall.args = uintptr(unsafe.Pointer(&_m_.scratch)) 324 asmcgocall(unsafe.Pointer(&asmsysvicall6x), unsafe.Pointer(&_m_.libcall)) 325 if *_m_.perrno != 0 { 326 if *_m_.perrno == _ETIMEDOUT || *_m_.perrno == _EAGAIN || *_m_.perrno == _EINTR { 327 return -1 328 } 329 throw("sem_reltimedwait_np") 330 } 331 return 0 332 } 333 for { 334 _m_.libcall.fn = uintptr(unsafe.Pointer(&libc_sem_wait)) 335 _m_.libcall.n = 1 336 _m_.scratch = mscratch{} 337 _m_.scratch.v[0] = _m_.waitsema 338 _m_.libcall.args = uintptr(unsafe.Pointer(&_m_.scratch)) 339 asmcgocall(unsafe.Pointer(&asmsysvicall6x), unsafe.Pointer(&_m_.libcall)) 340 if _m_.libcall.r1 == 0 { 341 break 342 } 343 if *_m_.perrno == _EINTR { 344 continue 345 } 346 throw("sem_wait") 347 } 348 return 0 349} 350 351//go:nosplit 352func semawakeup(mp *m) { 353 if sem_post((*semt)(unsafe.Pointer(mp.waitsema))) != 0 { 354 throw("sem_post") 355 } 356} 357 358//go:nosplit 359func closefd(fd int32) int32 { 360 return int32(sysvicall1(&libc_close, uintptr(fd))) 361} 362 363//go:nosplit 364func exit(r int32) { 365 sysvicall1(&libc_exit, uintptr(r)) 366} 367 368//go:nosplit 369func getcontext(context *ucontext) /* int32 */ { 370 sysvicall1(&libc_getcontext, uintptr(unsafe.Pointer(context))) 371} 372 373//go:nosplit 374func madvise(addr unsafe.Pointer, n uintptr, flags int32) { 375 sysvicall3(&libc_madvise, uintptr(addr), uintptr(n), uintptr(flags)) 376} 377 378//go:nosplit 379func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (unsafe.Pointer, int) { 380 p, err := doMmap(uintptr(addr), n, uintptr(prot), uintptr(flags), uintptr(fd), uintptr(off)) 381 if p == ^uintptr(0) { 382 return nil, int(err) 383 } 384 return unsafe.Pointer(p), 0 385} 386 387//go:nosplit 388func doMmap(addr, n, prot, flags, fd, off uintptr) (uintptr, uintptr) { 389 var libcall libcall 390 libcall.fn = uintptr(unsafe.Pointer(&libc_mmap)) 391 libcall.n = 6 392 libcall.args = uintptr(noescape(unsafe.Pointer(&addr))) 393 asmcgocall(unsafe.Pointer(&asmsysvicall6x), unsafe.Pointer(&libcall)) 394 return libcall.r1, libcall.err 395} 396 397//go:nosplit 398func munmap(addr unsafe.Pointer, n uintptr) { 399 sysvicall2(&libc_munmap, uintptr(addr), uintptr(n)) 400} 401 402const ( 403 _CLOCK_REALTIME = 3 404 _CLOCK_MONOTONIC = 4 405) 406 407//go:nosplit 408func nanotime1() int64 { 409 var ts mts 410 sysvicall2(&libc_clock_gettime, _CLOCK_MONOTONIC, uintptr(unsafe.Pointer(&ts))) 411 return ts.tv_sec*1e9 + ts.tv_nsec 412} 413 414//go:nosplit 415func open(path *byte, mode, perm int32) int32 { 416 return int32(sysvicall3(&libc_open, uintptr(unsafe.Pointer(path)), uintptr(mode), uintptr(perm))) 417} 418 419func pthread_attr_destroy(attr *pthreadattr) int32 { 420 return int32(sysvicall1(&libc_pthread_attr_destroy, uintptr(unsafe.Pointer(attr)))) 421} 422 423func pthread_attr_getstack(attr *pthreadattr, addr unsafe.Pointer, size *uint64) int32 { 424 return int32(sysvicall3(&libc_pthread_attr_getstack, uintptr(unsafe.Pointer(attr)), uintptr(addr), uintptr(unsafe.Pointer(size)))) 425} 426 427func pthread_attr_init(attr *pthreadattr) int32 { 428 return int32(sysvicall1(&libc_pthread_attr_init, uintptr(unsafe.Pointer(attr)))) 429} 430 431func pthread_attr_setdetachstate(attr *pthreadattr, state int32) int32 { 432 return int32(sysvicall2(&libc_pthread_attr_setdetachstate, uintptr(unsafe.Pointer(attr)), uintptr(state))) 433} 434 435func pthread_attr_setstack(attr *pthreadattr, addr uintptr, size uint64) int32 { 436 return int32(sysvicall3(&libc_pthread_attr_setstack, uintptr(unsafe.Pointer(attr)), uintptr(addr), uintptr(size))) 437} 438 439func pthread_create(thread *pthread, attr *pthreadattr, fn uintptr, arg unsafe.Pointer) int32 { 440 return int32(sysvicall4(&libc_pthread_create, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(fn), uintptr(arg))) 441} 442 443func pthread_self() pthread { 444 return pthread(sysvicall0(&libc_pthread_self)) 445} 446 447func signalM(mp *m, sig int) { 448 sysvicall2(&libc_pthread_kill, uintptr(pthread(mp.procid)), uintptr(sig)) 449} 450 451//go:nosplit 452//go:nowritebarrierrec 453func raise(sig uint32) /* int32 */ { 454 sysvicall1(&libc_raise, uintptr(sig)) 455} 456 457func raiseproc(sig uint32) /* int32 */ { 458 pid := sysvicall0(&libc_getpid) 459 sysvicall2(&libc_kill, pid, uintptr(sig)) 460} 461 462//go:nosplit 463func read(fd int32, buf unsafe.Pointer, nbyte int32) int32 { 464 r1, err := sysvicall3Err(&libc_read, uintptr(fd), uintptr(buf), uintptr(nbyte)) 465 if c := int32(r1); c >= 0 { 466 return c 467 } 468 return -int32(err) 469} 470 471//go:nosplit 472func sem_init(sem *semt, pshared int32, value uint32) int32 { 473 return int32(sysvicall3(&libc_sem_init, uintptr(unsafe.Pointer(sem)), uintptr(pshared), uintptr(value))) 474} 475 476//go:nosplit 477func sem_post(sem *semt) int32 { 478 return int32(sysvicall1(&libc_sem_post, uintptr(unsafe.Pointer(sem)))) 479} 480 481//go:nosplit 482func sem_reltimedwait_np(sem *semt, timeout *timespec) int32 { 483 return int32(sysvicall2(&libc_sem_reltimedwait_np, uintptr(unsafe.Pointer(sem)), uintptr(unsafe.Pointer(timeout)))) 484} 485 486//go:nosplit 487func sem_wait(sem *semt) int32 { 488 return int32(sysvicall1(&libc_sem_wait, uintptr(unsafe.Pointer(sem)))) 489} 490 491func setitimer(which int32, value *itimerval, ovalue *itimerval) /* int32 */ { 492 sysvicall3(&libc_setitimer, uintptr(which), uintptr(unsafe.Pointer(value)), uintptr(unsafe.Pointer(ovalue))) 493} 494 495//go:nosplit 496//go:nowritebarrierrec 497func sigaction(sig uint32, act *sigactiont, oact *sigactiont) /* int32 */ { 498 sysvicall3(&libc_sigaction, uintptr(sig), uintptr(unsafe.Pointer(act)), uintptr(unsafe.Pointer(oact))) 499} 500 501//go:nosplit 502//go:nowritebarrierrec 503func sigaltstack(ss *stackt, oss *stackt) /* int32 */ { 504 sysvicall2(&libc_sigaltstack, uintptr(unsafe.Pointer(ss)), uintptr(unsafe.Pointer(oss))) 505} 506 507//go:nosplit 508//go:nowritebarrierrec 509func sigprocmask(how int32, set *sigset, oset *sigset) /* int32 */ { 510 sysvicall3(&libc_sigprocmask, uintptr(how), uintptr(unsafe.Pointer(set)), uintptr(unsafe.Pointer(oset))) 511} 512 513func sysconf(name int32) int64 { 514 return int64(sysvicall1(&libc_sysconf, uintptr(name))) 515} 516 517func usleep1(usec uint32) 518 519//go:nosplit 520func usleep(µs uint32) { 521 usleep1(µs) 522} 523 524func walltime1() (sec int64, nsec int32) { 525 var ts mts 526 sysvicall2(&libc_clock_gettime, _CLOCK_REALTIME, uintptr(unsafe.Pointer(&ts))) 527 return ts.tv_sec, int32(ts.tv_nsec) 528} 529 530//go:nosplit 531func write1(fd uintptr, buf unsafe.Pointer, nbyte int32) int32 { 532 r1, err := sysvicall3Err(&libc_write, fd, uintptr(buf), uintptr(nbyte)) 533 if c := int32(r1); c >= 0 { 534 return c 535 } 536 return -int32(err) 537} 538 539//go:nosplit 540func pipe() (r, w int32, errno int32) { 541 var p [2]int32 542 _, e := sysvicall1Err(&libc_pipe, uintptr(noescape(unsafe.Pointer(&p)))) 543 return p[0], p[1], int32(e) 544} 545 546//go:nosplit 547func pipe2(flags int32) (r, w int32, errno int32) { 548 var p [2]int32 549 _, e := sysvicall2Err(&libc_pipe2, uintptr(noescape(unsafe.Pointer(&p))), uintptr(flags)) 550 return p[0], p[1], int32(e) 551} 552 553//go:nosplit 554func closeonexec(fd int32) { 555 fcntl(fd, _F_SETFD, _FD_CLOEXEC) 556} 557 558//go:nosplit 559func setNonblock(fd int32) { 560 flags := fcntl(fd, _F_GETFL, 0) 561 fcntl(fd, _F_SETFL, flags|_O_NONBLOCK) 562} 563 564func osyield1() 565 566//go:nosplit 567func osyield() { 568 _g_ := getg() 569 570 // Check the validity of m because we might be called in cgo callback 571 // path early enough where there isn't a m available yet. 572 if _g_ != nil && _g_.m != nil { 573 sysvicall0(&libc_sched_yield) 574 return 575 } 576 osyield1() 577} 578 579//go:linkname executablePath os.executablePath 580var executablePath string 581 582func sysargs(argc int32, argv **byte) { 583 n := argc + 1 584 585 // skip over argv, envp to get to auxv 586 for argv_index(argv, n) != nil { 587 n++ 588 } 589 590 // skip NULL separator 591 n++ 592 593 // now argv+n is auxv 594 auxv := (*[1 << 28]uintptr)(add(unsafe.Pointer(argv), uintptr(n)*sys.PtrSize)) 595 sysauxv(auxv[:]) 596} 597 598const ( 599 _AT_NULL = 0 // Terminates the vector 600 _AT_PAGESZ = 6 // Page size in bytes 601 _AT_SUN_EXECNAME = 2014 // exec() path name 602) 603 604func sysauxv(auxv []uintptr) { 605 for i := 0; auxv[i] != _AT_NULL; i += 2 { 606 tag, val := auxv[i], auxv[i+1] 607 switch tag { 608 case _AT_PAGESZ: 609 physPageSize = val 610 case _AT_SUN_EXECNAME: 611 executablePath = gostringnocopy((*byte)(unsafe.Pointer(val))) 612 } 613 } 614} 615