rust - মরিচের 128-বিট পূর্ণসংখ্যা ger i128` কীভাবে 64-বিট সিস্টেমে কাজ করে?




x86-64 bigint (3)

i128 128-বিট পূর্ণসংখ্যা রয়েছে, এগুলি i128 (এবং স্বাক্ষরযুক্ত স্বাক্ষরের জন্য u128 ) টাইপ দিয়ে চিহ্নিত করা হয়:

let a: i128 = 170141183460469231731687303715884105727;

i128 কীভাবে এই i128 মানগুলিকে 64-বিট সিস্টেমে কাজ করে; যেমন এটি গাণিতিক কীভাবে করে?

যেহেতু, আমি যতদূর জানি, মানটি একটি x86-64 সিপিইউর একটি রেজিস্টারে ফিট করতে পারে না, i128 এক i128 মানের জন্য 2 টি নিবন্ধ ব্যবহার করে? অথবা তারা পরিবর্তে তাদের উপস্থাপনের জন্য কোনও ধরণের বড় পূর্ণসংখ্যার কাঠামো ব্যবহার করছেন?


X86_64 এ, সম্ভবত একটি পরিষ্কার উদাহরণ সরবরাহ করার জন্য -O পতাকা, ফাংশন দিয়ে সংকলিত

pub fn leet(a : i128) -> i128 {
    a + 1337
}

সংকলন

example::leet:
  mov rdx, rsi
  mov rax, rdi
  add rax, 1337
  adc rdx, 0
  ret

(আমার আসল পোস্টটি আপনি যে u128 i128 সম্পর্কে জিজ্ঞাসা করেছেন তার চেয়ে u128 ছিল The ফাংশনটি একইভাবে i128 সংকলন করে, একটি ভাল বিক্ষোভ যা স্বাক্ষরিত এবং স্বাক্ষরবিহীন সংযোজন একটি আধুনিক সিপিইউতে একই।)

অন্য তালিকাটি অবিস্মরণীয় কোড তৈরি করেছে। এটি কোনও ডিবাগারে পা রাখা নিরাপদ, কারণ এটি নিশ্চিত করে যে আপনি যে কোনও জায়গায় ব্রেকপয়েন্ট রাখতে পারেন এবং প্রোগ্রামের যে কোনও লাইনে যে কোনও ভেরিয়েবলের অবস্থা পরীক্ষা করতে পারবেন। এটি পড়া ধীর এবং শক্ত। অপ্টিমাইজড সংস্করণটি কোডটির সাথে অনেক কাছাকাছি যা প্রকৃতপক্ষে উত্পাদনে চলবে।

এই ফাংশনটির প্যারামিটারটি 64৪-বিট রেজিস্টারগুলির একটি জোরে পাস করা হয়, আরএসআই: আরডিআই। আরডিএক্স: রেক্স, আর একটি রেজিস্টারে ফলাফল ফিরে আসে। কোডের প্রথম দুটি লাইন একটিকে যোগফল শুরু করে।

তৃতীয় লাইন ইনপুটটির নিম্ন শব্দটিতে 1337 যুক্ত করে। যদি এটি ওভারফ্লো হয় তবে এটি সিপিইউর বহনকারী পতাকাটিতে 1 বহন করে। চতুর্থ লাইন ইনপুটটির উচ্চ শব্দের সাথে শূন্য যুক্ত করে — এটি বহন করা হলে 1 টি —

আপনি এটি দুটি অঙ্কের সংখ্যায় এক-অঙ্কের সংখ্যার সাধারণ সংযোজন হিসাবে ভাবতে পারেন

  a  b
+ 0  7
______
 

তবে বেস 18,446,744,073,709,551,616 ভিত্তিতে। আপনি এখনও সর্বনিম্ন "অঙ্ক" যুক্ত করছেন, সম্ভবত পরবর্তী কলামে একটি 1 বহন করছেন, তারপরে পরবর্তী অঙ্কটি যুক্ত করে বহন করুন। বিয়োগ খুব মিল।

গুণকে অবশ্যই পরিচয় (2⁶⁴a + বি) (2⁶⁴c + d) = 2¹²⁸ac + 2⁶⁴ (বিজ্ঞাপন + বিসি) + বিডি ব্যবহার করতে হবে, যেখানে এই প্রতিটি গুণকে এক রেজিস্টারে পণ্যটির উপরের অর্ধেক এবং পণ্যটির নীচের অর্ধেক প্রদান করে অন্য। এই শর্তগুলির কিছু বাদ দেওয়া হবে, কারণ 128 তম উপরে বিটগুলি একটি u128 খাপ u128 এবং তা ফেলে দেওয়া হয়। তবুও, এটি বেশ কয়েকটি মেশিনের নির্দেশনা নেয়। বিভাগও বিভিন্ন পদক্ষেপ নেয়। একটি স্বাক্ষরিত মানের জন্য, গুণ এবং বিভাগের অতিরিক্ত অপারেন্ডস এবং ফলাফলের লক্ষণগুলিকে রূপান্তর করতে হবে। এই অপারেশনগুলি মোটেই খুব দক্ষ নয়।

অন্যান্য স্থাপত্যগুলিতে এটি আরও সহজ বা শক্ত হয় gets আরআইএসসি-ভি একটি 128-বিট নির্দেশ-সেট এক্সটেনশানটি সংজ্ঞায়িত করেছে, যদিও আমার জ্ঞানের কাছে কেউ এটিকে সিলিকনে প্রয়োগ করেনি। এই এক্সটেনশনটি ছাড়াই, আরআইএসসি-ভি আর্কিটেকচার ম্যানুয়ালটি একটি শর্তসাপেক্ষ শাখার প্রস্তাব দেয় : addi t0, t1, +imm; blt t0, t1, overflow addi t0, t1, +imm; blt t0, t1, overflow

স্পার্কের x86 এর নিয়ন্ত্রণ পতাকার মতো নিয়ন্ত্রণ কোড রয়েছে add,cc সেগুলি সেট করার জন্য আপনাকে একটি বিশেষ নির্দেশ ব্যবহার করতে হবে, add,cc করতে হবে। অন্যদিকে, এমআইপিএসের জন্য আপনাকে দুটি স্বাক্ষরবিহীন পূর্ণসংখ্যার যোগফলের তুলনায় কঠোরভাবে কম কিনা তা যাচাই করা দরকার। যদি তাই হয় তবে সংযোজন উপচে পড়েছে। কমপক্ষে আপনি শর্তাধীন শাখা ছাড়াই ক্যারি বিটের মানতে অন্য একটি রেজিস্টার সেট করতে সক্ষম হন।


সংকলক এগুলি একাধিক নিবন্ধগুলিতে সংরক্ষণ করবে এবং প্রয়োজনে সেই মানগুলিতে পাটিগণিত করতে একাধিক নির্দেশনা ব্যবহার করবে। বেশিরভাগ আইএসএ-তে এক্স 86-এর adc মতো একটি অ্যাড-সহ ক্যারি নির্দেশনা রয়েছে যা প্রসারিত-যথার্থ পূর্ণসংখ্যা অ্যাড / সাব করতে মোটামুটি দক্ষ করে তোলে।

উদাহরণস্বরূপ, দেওয়া

fn main() {
    let a = 42u128;
    let b = a + 1337;
}

অপ্টিমাইজেশন ছাড়াই x86-64 এর জন্য সংকলন করার সময় সংকলকটি নিম্নলিখিতটি উত্পন্ন করে:
(@ পিটারকর্ডস দ্বারা মন্তব্যগুলি যুক্ত)

playground::main:
    sub rsp, 56
    mov qword ptr [rsp + 32], 0
    mov qword ptr [rsp + 24], 42         # store 128-bit 0:42 on the stack
                                         # little-endian = low half at lower address

    mov rax, qword ptr [rsp + 24]
    mov rcx, qword ptr [rsp + 32]        # reload it to registers

    add rax, 1337                        # add 1337 to the low half
    adc rcx, 0                           # propagate carry to the high half. 1337u128 >> 64 = 0

    setb    dl                           # save carry-out (setb is an alias for setc)
    mov rsi, rax
    test    dl, 1                        # check carry-out (to detect overflow)
    mov qword ptr [rsp + 16], rax        # store the low half result
    mov qword ptr [rsp + 8], rsi         # store another copy of the low half
    mov qword ptr [rsp], rcx             # store the high half
                             # These are temporary copies of the halves; probably the high half at lower address isn't intentional
    jne .LBB8_2                       # jump if 128-bit add overflowed (to another not-shown block of code after the ret, I think)

    mov rax, qword ptr [rsp + 16]
    mov qword ptr [rsp + 40], rax     # copy low half to RSP+40
    mov rcx, qword ptr [rsp]
    mov qword ptr [rsp + 48], rcx     # copy high half to RSP+48
                  # This is the actual b, in normal little-endian order, forming a u128 at RSP+40
    add rsp, 56
    ret                               # with retval in EAX/RAX = low half result

যেখানে আপনি দেখতে পাচ্ছেন যে 42 মানটি rax এবং rcx সঞ্চিত রয়েছে।

(সম্পাদকের দ্রষ্টব্য: x86-64 সি কলিং কনভেনশনগুলি আরডিএক্স: আরএক্সে 128-বিট পূর্ণসংখ্যা ফেরত দেয় But তবে এই মূলটি মোটেই কোনও মান দেয় না All সমস্ত অপ্রয়োজনীয় অনুলিপি খাঁটি অপ্টিমাইজেশন অক্ষম করা থেকে, এবং সেই জং আসলে ওভারফ্লো পরীক্ষা করে সংশোধনের ধাপ.)

তুলনা করার জন্য, এখানে x86-64-তে মরিচা 64৪-বিট পূর্ণসংখ্যার asm রয়েছে যেখানে কোনও যোগ-সহ-বহনের প্রয়োজন নেই, প্রতিটি মানের জন্য কেবল একটি একক রেজিস্টার বা স্ট্যাক-স্লট।

playground::main:
    sub rsp, 24
    mov qword ptr [rsp + 8], 42           # store
    mov rax, qword ptr [rsp + 8]          # reload
    add rax, 1337                         # add
    setb    cl
    test    cl, 1                         # check for carry-out (overflow)
    mov qword ptr [rsp], rax              # store the result
    jne .LBB8_2                           # branch on non-zero carry-out

    mov rax, qword ptr [rsp]              # reload the result
    mov qword ptr [rsp + 16], rax         # and copy it (to b)
    add rsp, 24
    ret

.LBB8_2:
    call panic function because of integer overflow

সেটবি / পরীক্ষাটি এখনও পুরোপুরি নিরর্থক: জেসি (সিএফ = 1 যদি লাফ করুন) ঠিক ঠিক কাজ করবে।

অপ্টিমাইজেশান সক্ষম করার সাথে, মরিচা সংকলক ওভারফ্লো পরীক্ষা করে না তাই + .wrapping_add() মতো কাজ করে।


হ্যাঁ, 32-বিট মেশিনগুলিতে 64-বিট ইন্টিজারগুলি ঠিক একইভাবে পরিচালনা করা হয়েছিল, বা 16-বিট মেশিনে 32-বিট ইন্টিজারগুলি, এমনকি 8- বিট মেশিনে 16- এবং 32-বিট ইন্টিজারগুলি (এখনও মাইক্রোকন্ট্রোলারদের জন্য প্রযোজ্য! )। হ্যাঁ, আপনি দুটি নিবন্ধে বা মেমরির অবস্থানগুলিতে বা যে কোনও কিছুতে (এটি আসলে কোনও বিষয় নয়) সংরক্ষণ করুন। সংযোজন এবং বিয়োগফল দু'টি নির্দেশাবলী গ্রহণ করে এবং বহন পতাকা ব্যবহার করে tri গুণটির জন্য তিনটি গুণ এবং কিছু সংযোজন প্রয়োজন (ইতিমধ্যে -৪-বিট চিপগুলির মধ্যে একটি 64x64-> 128 গুণমান অপারেশন রয়েছে যা দুটি রেজিস্টারে আউটপুট দেয়)। বিভাগ ... একটি সাবরুটিন প্রয়োজন এবং বেশ ধীর হয় (কিছু ক্ষেত্রে যেখানে ধ্রুবক দ্বারা বিভাগ একটি শিফট বা গুণে রূপান্তরিত হতে পারে) তবে এটি এখনও কার্যকর হয়। বিটওয়াইজ এবং / অথবা / এক্সোর কেবল উপরের এবং নীচের অংশগুলিতে পৃথকভাবে করতে হবে। ঘূর্ণন এবং মাস্কিং দিয়ে শিফ্টগুলি সম্পন্ন করা যায়। এবং এটি বেশ কিছু জিনিসকে .েকে রাখে।





llvm-codegen