| ... | ... | @@ -238,16 +238,6 @@ pub const NativeTargetInfo = struct { |
| 238 | 238 | // A user could then run that zig compiler on riscv64-linux-gnu. This use case is well-defined |
| 239 | 239 | // and supported by Zig. But that means that we must detect the system ABI here rather than |
| 240 | 240 | // relying on `Target.current`. |
| 241 | | const LdInfo = struct { |
| 242 | | ld_path_buffer: [255]u8, |
| 243 | | ld_path_max: u8, |
| 244 | | abi: Target.Abi, |
| 245 | | |
| 246 | | pub fn ldPath(self: *const @This()) []const u8 { |
| 247 | | const m: usize = self.ld_path_max; |
| 248 | | return self.ld_path_buffer[0 .. m + 1]; |
| 249 | | } |
| 250 | | }; |
| 251 | 241 | const all_abis = comptime blk: { |
| 252 | 242 | assert(@enumToInt(Target.Abi.none) == 0); |
| 253 | 243 | const fields = std.meta.fields(Target.Abi)[1..]; |
| ... | ... | @@ -333,7 +323,7 @@ pub const NativeTargetInfo = struct { |
| 333 | 323 | // trick won't work. The next thing we fall back to is the same thing, but for /usr/bin/env. |
| 334 | 324 | // Since that path is hard-coded into the shebang line of many portable scripts, it's a |
| 335 | 325 | // reasonably reliable path to check for. |
| 336 | | return abiAndDynamicLinkerFromUsrBinEnv(cpu, os) catch |err| switch (err) { |
| 326 | return abiAndDynamicLinkerFromUsrBinEnv(cpu, os, ld_info_list) catch |err| switch (err) { |
| 337 | 327 | error.FileSystem, |
| 338 | 328 | error.SystemResources, |
| 339 | 329 | error.SymLinkLoop, |
| ... | ... | @@ -343,8 +333,6 @@ pub const NativeTargetInfo = struct { |
| 343 | 333 | => |e| return e, |
| 344 | 334 | |
| 345 | 335 | error.UnableToReadElfFile, |
| 346 | | error.ElfNotADynamicExecutable, |
| 347 | | error.InvalidElfProgramHeaders, |
| 348 | 336 | error.InvalidElfClass, |
| 349 | 337 | error.InvalidElfVersion, |
| 350 | 338 | error.InvalidElfEndian, |
| ... | ... | @@ -373,6 +361,10 @@ pub const NativeTargetInfo = struct { |
| 373 | 361 | error.NotDir => return error.GnuLibCVersionUnavailable, |
| 374 | 362 | error.Unexpected => return error.GnuLibCVersionUnavailable, |
| 375 | 363 | }; |
| 364 | return glibcVerFromLinkName(link_name); |
| 365 | } |
| 366 | |
| 367 | fn glibcVerFromLinkName(link_name: []const u8) !std.builtin.Version { |
| 376 | 368 | // example: "libc-2.3.4.so" |
| 377 | 369 | // example: "libc-2.27.so" |
| 378 | 370 | const prefix = "libc-"; |
| ... | ... | @@ -389,7 +381,11 @@ pub const NativeTargetInfo = struct { |
| 389 | 381 | }; |
| 390 | 382 | } |
| 391 | 383 | |
| 392 | | fn abiAndDynamicLinkerFromUsrBinEnv(cpu: Target.Cpu, os: Target.Os) !NativeTargetInfo { |
| 384 | fn abiAndDynamicLinkerFromUsrBinEnv( |
| 385 | cpu: Target.Cpu, |
| 386 | os: Target.Os, |
| 387 | ld_info_list: []const LdInfo, |
| 388 | ) !NativeTargetInfo { |
| 393 | 389 | const env_file = std.fs.openFileAbsoluteC("/usr/bin/env", .{}) catch |err| switch (err) { |
| 394 | 390 | error.NoSpaceLeft => unreachable, |
| 395 | 391 | error.NameTooLong => unreachable, |
| ... | ... | @@ -409,8 +405,7 @@ pub const NativeTargetInfo = struct { |
| 409 | 405 | else => |e| return e, |
| 410 | 406 | }; |
| 411 | 407 | var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 align(@alignOf(elf.Elf64_Ehdr)) = undefined; |
| 412 | | const hdr_bytes_len = try wrapRead(env_file.pread(&hdr_buf, 0)); |
| 413 | | if (hdr_bytes_len < @sizeOf(elf.Elf32_Ehdr)) return error.InvalidElfFile; |
| 408 | _ = try preadFull(env_file, &hdr_buf, 0, hdr_buf.len); |
| 414 | 409 | const hdr32 = @ptrCast(*elf.Elf32_Ehdr, &hdr_buf); |
| 415 | 410 | const hdr64 = @ptrCast(*elf.Elf64_Ehdr, &hdr_buf); |
| 416 | 411 | if (!mem.eql(u8, hdr32.e_ident[0..4], "\x7fELF")) return error.InvalidElfMagic; |
| ... | ... | @@ -430,7 +425,6 @@ pub const NativeTargetInfo = struct { |
| 430 | 425 | var phoff = elfInt(is_64, need_bswap, hdr32.e_phoff, hdr64.e_phoff); |
| 431 | 426 | const phentsize = elfInt(is_64, need_bswap, hdr32.e_phentsize, hdr64.e_phentsize); |
| 432 | 427 | const phnum = elfInt(is_64, need_bswap, hdr32.e_phnum, hdr64.e_phnum); |
| 433 | | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); |
| 434 | 428 | |
| 435 | 429 | var result: NativeTargetInfo = .{ |
| 436 | 430 | .target = .{ |
| ... | ... | @@ -439,67 +433,234 @@ pub const NativeTargetInfo = struct { |
| 439 | 433 | .abi = Target.Abi.default(cpu.arch, os), |
| 440 | 434 | }, |
| 441 | 435 | }; |
| 436 | var rpath_offset: ?u64 = null; // Found inside PT_DYNAMIC |
| 442 | 437 | |
| 443 | | const ph_total_size = std.math.mul(u32, phentsize, phnum) catch |err| switch (err) { |
| 444 | | error.Overflow => return error.InvalidElfProgramHeaders, |
| 445 | | }; |
| 446 | 438 | var ph_buf: [16 * @sizeOf(elf.Elf64_Phdr)]u8 align(@alignOf(elf.Elf64_Phdr)) = undefined; |
| 439 | if (phentsize > @sizeOf(elf.Elf64_Phdr)) return error.InvalidElfFile; |
| 440 | |
| 447 | 441 | var ph_i: u16 = 0; |
| 448 | 442 | while (ph_i < phnum) { |
| 449 | | // Reserve some bytes so that we can deref the 64-bit struct fields even when the ELF file is 32-bits. |
| 450 | | const reserve = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr); |
| 451 | | const read_byte_len = try wrapRead(env_file.pread(ph_buf[0 .. ph_buf.len - reserve], phoff)); |
| 452 | | if (read_byte_len < phentsize) return error.ElfNotADynamicExecutable; |
| 453 | | var buf_i: usize = 0; |
| 454 | | while (buf_i < read_byte_len and ph_i < phnum) : ({ |
| 443 | // Reserve some bytes so that we can deref the 64-bit struct fields |
| 444 | // even when the ELF file is 32-bits. |
| 445 | const ph_reserve: usize = @sizeOf(elf.Elf64_Phdr) - @sizeOf(elf.Elf32_Phdr); |
| 446 | const ph_read_byte_len = try preadFull(env_file, ph_buf[0 .. ph_buf.len - ph_reserve], phoff, phentsize); |
| 447 | var ph_buf_i: usize = 0; |
| 448 | while (ph_buf_i < ph_read_byte_len and ph_i < phnum) : ({ |
| 455 | 449 | ph_i += 1; |
| 456 | 450 | phoff += phentsize; |
| 457 | | buf_i += phentsize; |
| 451 | ph_buf_i += phentsize; |
| 458 | 452 | }) { |
| 459 | | const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[buf_i])); |
| 460 | | const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[buf_i])); |
| 453 | const ph32 = @ptrCast(*elf.Elf32_Phdr, @alignCast(@alignOf(elf.Elf32_Phdr), &ph_buf[ph_buf_i])); |
| 454 | const ph64 = @ptrCast(*elf.Elf64_Phdr, @alignCast(@alignOf(elf.Elf64_Phdr), &ph_buf[ph_buf_i])); |
| 461 | 455 | const p_type = elfInt(is_64, need_bswap, ph32.p_type, ph64.p_type); |
| 462 | 456 | switch (p_type) { |
| 463 | 457 | elf.PT_INTERP => { |
| 464 | 458 | const p_offset = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); |
| 465 | 459 | const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); |
| 466 | | var interp_buf: [255]u8 = undefined; |
| 467 | | if (p_filesz > interp_buf.len) return error.NameTooLong; |
| 468 | | var read_offset: usize = 0; |
| 469 | | while (true) { |
| 470 | | const len = try wrapRead(env_file.pread( |
| 471 | | interp_buf[read_offset .. p_filesz - read_offset], |
| 472 | | p_offset + read_offset, |
| 473 | | )); |
| 474 | | if (len == 0) return error.UnexpectedEndOfFile; |
| 475 | | read_offset += len; |
| 476 | | if (read_offset == p_filesz) break; |
| 477 | | } |
| 460 | if (p_filesz > result.dynamic_linker_buffer.len) return error.NameTooLong; |
| 461 | _ = try preadFull(env_file, result.dynamic_linker_buffer[0..p_filesz], p_offset, p_filesz); |
| 478 | 462 | // PT_INTERP includes a null byte in p_filesz. |
| 479 | | result.setDynamicLinker(interp_buf[0 .. p_filesz - 1]); |
| 463 | const len = p_filesz - 1; |
| 464 | // dynamic_linker_max is "max", not "len". |
| 465 | // We know it will fit in u8 because we check against dynamic_linker_buffer.len above. |
| 466 | result.dynamic_linker_max = @intCast(u8, len - 1); |
| 467 | |
| 468 | // Use it to determine ABI. |
| 469 | const full_ld_path = result.dynamic_linker_buffer[0..len]; |
| 470 | for (ld_info_list) |ld_info| { |
| 471 | const standard_ld_basename = fs.path.basename(ld_info.ldPath()); |
| 472 | if (std.mem.endsWith(u8, full_ld_path, standard_ld_basename)) { |
| 473 | result.target.abi = ld_info.abi; |
| 474 | break; |
| 475 | } |
| 476 | } |
| 480 | 477 | }, |
| 481 | | elf.PT_DYNAMIC => { |
| 482 | | std.debug.warn("found PT_DYNAMIC\n", .{}); |
| 478 | // We only need this for detecting glibc version. |
| 479 | elf.PT_DYNAMIC => if (Target.current.os.tag == .linux and result.target.isGnuLibC()) { |
| 480 | var dyn_off = elfInt(is_64, need_bswap, ph32.p_offset, ph64.p_offset); |
| 481 | const p_filesz = elfInt(is_64, need_bswap, ph32.p_filesz, ph64.p_filesz); |
| 482 | const dyn_size: u64 = if (is_64) @sizeOf(elf.Elf64_Dyn) else @sizeOf(elf.Elf32_Dyn); |
| 483 | const dyn_num = p_filesz / dyn_size; |
| 484 | var dyn_buf: [16 * @sizeOf(elf.Elf64_Dyn)]u8 align(@alignOf(elf.Elf64_Dyn)) = undefined; |
| 485 | var dyn_i: usize = 0; |
| 486 | dyn: while (dyn_i < dyn_num) { |
| 487 | // Reserve some bytes so that we can deref the 64-bit struct fields |
| 488 | // even when the ELF file is 32-bits. |
| 489 | const dyn_reserve: usize = @sizeOf(elf.Elf64_Dyn) - @sizeOf(elf.Elf32_Dyn); |
| 490 | const dyn_read_byte_len = try preadFull( |
| 491 | env_file, |
| 492 | dyn_buf[0 .. dyn_buf.len - dyn_reserve], |
| 493 | dyn_off, |
| 494 | dyn_size, |
| 495 | ); |
| 496 | var dyn_buf_i: usize = 0; |
| 497 | while (dyn_buf_i < dyn_read_byte_len and dyn_i < dyn_num) : ({ |
| 498 | dyn_i += 1; |
| 499 | dyn_off += dyn_size; |
| 500 | dyn_buf_i += dyn_size; |
| 501 | }) { |
| 502 | const dyn32 = @ptrCast( |
| 503 | *elf.Elf32_Dyn, |
| 504 | @alignCast(@alignOf(elf.Elf32_Dyn), &dyn_buf[dyn_buf_i]), |
| 505 | ); |
| 506 | const dyn64 = @ptrCast( |
| 507 | *elf.Elf64_Dyn, |
| 508 | @alignCast(@alignOf(elf.Elf64_Dyn), &dyn_buf[dyn_buf_i]), |
| 509 | ); |
| 510 | const tag = elfInt(is_64, need_bswap, dyn32.d_tag, dyn64.d_tag); |
| 511 | const val = elfInt(is_64, need_bswap, dyn32.d_val, dyn64.d_val); |
| 512 | if (tag == elf.DT_RUNPATH) { |
| 513 | rpath_offset = val; |
| 514 | break :dyn; |
| 515 | } |
| 516 | } |
| 517 | } |
| 483 | 518 | }, |
| 484 | 519 | else => continue, |
| 485 | 520 | } |
| 486 | 521 | } |
| 487 | 522 | } |
| 488 | 523 | |
| 524 | if (Target.current.os.tag == .linux and result.target.isGnuLibC()) { |
| 525 | if (rpath_offset) |rpoff| { |
| 526 | const shstrndx = elfInt(is_64, need_bswap, hdr32.e_shstrndx, hdr64.e_shstrndx); |
| 527 | |
| 528 | var shoff = elfInt(is_64, need_bswap, hdr32.e_shoff, hdr64.e_shoff); |
| 529 | const shentsize = elfInt(is_64, need_bswap, hdr32.e_shentsize, hdr64.e_shentsize); |
| 530 | const str_section_off = shoff + @as(u64, shentsize) * @as(u64, shstrndx); |
| 531 | |
| 532 | var sh_buf: [16 * @sizeOf(elf.Elf64_Shdr)]u8 align(@alignOf(elf.Elf64_Shdr)) = undefined; |
| 533 | if (sh_buf.len < shentsize) return error.InvalidElfFile; |
| 534 | |
| 535 | _ = try preadFull(env_file, &sh_buf, str_section_off, shentsize); |
| 536 | const shstr32 = @ptrCast(*elf.Elf32_Shdr, @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf)); |
| 537 | const shstr64 = @ptrCast(*elf.Elf64_Shdr, @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf)); |
| 538 | const shstrtab_off = elfInt(is_64, need_bswap, shstr32.sh_offset, shstr64.sh_offset); |
| 539 | const shstrtab_size = elfInt(is_64, need_bswap, shstr32.sh_size, shstr64.sh_size); |
| 540 | var strtab_buf: [4096:0]u8 = undefined; |
| 541 | const shstrtab_len = std.math.min(shstrtab_size, strtab_buf.len); |
| 542 | const shstrtab_read_len = try preadFull(env_file, &strtab_buf, shstrtab_off, shstrtab_len); |
| 543 | const shstrtab = strtab_buf[0..shstrtab_read_len]; |
| 544 | |
| 545 | const shnum = elfInt(is_64, need_bswap, hdr32.e_shnum, hdr64.e_shnum); |
| 546 | var sh_i: u16 = 0; |
| 547 | const dynstr: ?struct { offset: u64, size: u64 } = find_dyn_str: while (sh_i < shnum) { |
| 548 | // Reserve some bytes so that we can deref the 64-bit struct fields |
| 549 | // even when the ELF file is 32-bits. |
| 550 | const sh_reserve: usize = @sizeOf(elf.Elf64_Shdr) - @sizeOf(elf.Elf32_Shdr); |
| 551 | const sh_read_byte_len = try preadFull( |
| 552 | env_file, |
| 553 | sh_buf[0 .. sh_buf.len - sh_reserve], |
| 554 | shoff, |
| 555 | shentsize, |
| 556 | ); |
| 557 | var sh_buf_i: usize = 0; |
| 558 | while (sh_buf_i < sh_read_byte_len and sh_i < shnum) : ({ |
| 559 | sh_i += 1; |
| 560 | shoff += shentsize; |
| 561 | sh_buf_i += shentsize; |
| 562 | }) { |
| 563 | const sh32 = @ptrCast( |
| 564 | *elf.Elf32_Shdr, |
| 565 | @alignCast(@alignOf(elf.Elf32_Shdr), &sh_buf[sh_buf_i]), |
| 566 | ); |
| 567 | const sh64 = @ptrCast( |
| 568 | *elf.Elf64_Shdr, |
| 569 | @alignCast(@alignOf(elf.Elf64_Shdr), &sh_buf[sh_buf_i]), |
| 570 | ); |
| 571 | const sh_name_off = elfInt(is_64, need_bswap, sh32.sh_name, sh64.sh_name); |
| 572 | // TODO this pointer cast should not be necessary |
| 573 | const sh_name = mem.toSliceConst(u8, @ptrCast([*:0]u8, shstrtab[sh_name_off..].ptr)); |
| 574 | if (mem.eql(u8, sh_name, ".dynstr")) { |
| 575 | break :find_dyn_str .{ |
| 576 | .offset = elfInt(is_64, need_bswap, sh32.sh_offset, sh64.sh_offset), |
| 577 | .size = elfInt(is_64, need_bswap, sh32.sh_size, sh64.sh_size), |
| 578 | }; |
| 579 | } |
| 580 | } |
| 581 | } else null; |
| 582 | |
| 583 | if (dynstr) |ds| { |
| 584 | const strtab_len = std.math.min(ds.size, strtab_buf.len); |
| 585 | const strtab_read_len = try preadFull(env_file, &strtab_buf, ds.offset, shstrtab_len); |
| 586 | const strtab = strtab_buf[0..strtab_read_len]; |
| 587 | // TODO this pointer cast should not be necessary |
| 588 | const rpath_list = mem.toSliceConst(u8, @ptrCast([*:0]u8, strtab[rpoff..].ptr)); |
| 589 | var it = mem.tokenize(rpath_list, ":"); |
| 590 | while (it.next()) |rpath| { |
| 591 | var dir = fs.cwd().openDirList(rpath) catch |err| switch (err) { |
| 592 | error.NameTooLong => unreachable, |
| 593 | error.InvalidUtf8 => unreachable, |
| 594 | error.BadPathName => unreachable, |
| 595 | error.DeviceBusy => unreachable, |
| 596 | |
| 597 | error.FileNotFound, |
| 598 | error.NotDir, |
| 599 | error.AccessDenied, |
| 600 | error.NoDevice, |
| 601 | => continue, |
| 602 | |
| 603 | error.ProcessFdQuotaExceeded, |
| 604 | error.SystemFdQuotaExceeded, |
| 605 | error.SystemResources, |
| 606 | error.SymLinkLoop, |
| 607 | error.Unexpected, |
| 608 | => |e| return e, |
| 609 | }; |
| 610 | defer dir.close(); |
| 611 | |
| 612 | var link_buf: [std.os.PATH_MAX]u8 = undefined; |
| 613 | const link_name = std.os.readlinkatC( |
| 614 | dir.fd, |
| 615 | glibc_so_basename, |
| 616 | &link_buf, |
| 617 | ) catch |err| switch (err) { |
| 618 | error.NameTooLong => unreachable, |
| 619 | |
| 620 | error.AccessDenied, |
| 621 | error.FileNotFound, |
| 622 | error.NotDir, |
| 623 | => continue, |
| 624 | |
| 625 | error.SystemResources, |
| 626 | error.FileSystem, |
| 627 | error.SymLinkLoop, |
| 628 | error.Unexpected, |
| 629 | => |e| return e, |
| 630 | }; |
| 631 | result.target.os.version_range.linux.glibc = glibcVerFromLinkName( |
| 632 | link_name, |
| 633 | ) catch |err| switch (err) { |
| 634 | error.UnrecognizedGnuLibCFileName, |
| 635 | error.InvalidGnuLibCVersion, |
| 636 | => continue, |
| 637 | }; |
| 638 | break; |
| 639 | } |
| 640 | } |
| 641 | } |
| 642 | } |
| 643 | |
| 489 | 644 | return result; |
| 490 | 645 | } |
| 491 | 646 | |
| 492 | | fn wrapRead(res: std.os.ReadError!usize) !usize { |
| 493 | | return res catch |err| switch (err) { |
| 494 | | error.OperationAborted => unreachable, // Windows-only |
| 495 | | error.WouldBlock => unreachable, // Did not request blocking mode |
| 496 | | error.SystemResources => return error.SystemResources, |
| 497 | | error.IsDir => return error.UnableToReadElfFile, |
| 498 | | error.BrokenPipe => return error.UnableToReadElfFile, |
| 499 | | error.ConnectionResetByPeer => return error.UnableToReadElfFile, |
| 500 | | error.Unexpected => return error.Unexpected, |
| 501 | | error.InputOutput => return error.FileSystem, |
| 502 | | }; |
| 647 | fn preadFull(file: fs.File, buf: []u8, offset: u64, min_read_len: usize) !usize { |
| 648 | var i: u64 = 0; |
| 649 | while (i < min_read_len) { |
| 650 | const len = file.pread(buf[i .. buf.len - i], offset + i) catch |err| switch (err) { |
| 651 | error.OperationAborted => unreachable, // Windows-only |
| 652 | error.WouldBlock => unreachable, // Did not request blocking mode |
| 653 | error.SystemResources => return error.SystemResources, |
| 654 | error.IsDir => return error.UnableToReadElfFile, |
| 655 | error.BrokenPipe => return error.UnableToReadElfFile, |
| 656 | error.ConnectionResetByPeer => return error.UnableToReadElfFile, |
| 657 | error.Unexpected => return error.Unexpected, |
| 658 | error.InputOutput => return error.FileSystem, |
| 659 | }; |
| 660 | if (len == 0) return error.UnexpectedEndOfFile; |
| 661 | i += len; |
| 662 | } |
| 663 | return i; |
| 503 | 664 | } |
| 504 | 665 | |
| 505 | 666 | fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os) !NativeTargetInfo { |
| ... | ... | @@ -513,20 +674,31 @@ pub const NativeTargetInfo = struct { |
| 513 | 674 | result.dynamic_linker_max = result.target.standardDynamicLinkerPath(&result.dynamic_linker_buffer); |
| 514 | 675 | return result; |
| 515 | 676 | } |
| 516 | | }; |
| 517 | 677 | |
| 518 | | fn elfInt(is_64: bool, need_bswap: bool, int_32: var, int_64: var) @TypeOf(int_64) { |
| 519 | | if (is_64) { |
| 520 | | if (need_bswap) { |
| 521 | | return @byteSwap(@TypeOf(int_64), int_64); |
| 522 | | } else { |
| 523 | | return int_64; |
| 678 | const LdInfo = struct { |
| 679 | ld_path_buffer: [255]u8, |
| 680 | ld_path_max: u8, |
| 681 | abi: Target.Abi, |
| 682 | |
| 683 | pub fn ldPath(self: *const LdInfo) []const u8 { |
| 684 | const m: usize = self.ld_path_max; |
| 685 | return self.ld_path_buffer[0 .. m + 1]; |
| 524 | 686 | } |
| 525 | | } else { |
| 526 | | if (need_bswap) { |
| 527 | | return @byteSwap(@TypeOf(int_32), int_32); |
| 687 | }; |
| 688 | |
| 689 | fn elfInt(is_64: bool, need_bswap: bool, int_32: var, int_64: var) @TypeOf(int_64) { |
| 690 | if (is_64) { |
| 691 | if (need_bswap) { |
| 692 | return @byteSwap(@TypeOf(int_64), int_64); |
| 693 | } else { |
| 694 | return int_64; |
| 695 | } |
| 528 | 696 | } else { |
| 529 | | return int_32; |
| 697 | if (need_bswap) { |
| 698 | return @byteSwap(@TypeOf(int_32), int_32); |
| 699 | } else { |
| 700 | return int_32; |
| 701 | } |
| 530 | 702 | } |
| 531 | 703 | } |
| 532 | | } |
| 704 | }; |