برنامه نویسی

ردیابی مکان منبع دستورالعمل در Arkscript

گزارش خطای خوب در زبانهای برنامه نویسی بسیار مهم است. انجام این کار در زمان کامپایل در ArkScript آسان بود زیرا ما تمام زمینه مورد نیاز خود را داریم ، اما از آنجا که ما کد را به Bytecode می پردازیم ، که پس از آن توسط دستگاه مجازی اجرا می شود ، زمینه های زیادی را از دست می دهیم: پرونده های منبع ، مسیر آنها ، محتوای آنها ، ما دیگر آن را نداریم! خطاهای زمان اجرا ما فقط می تواند وضعیت داخلی VM را نشان دهد. این مقاله در مورد چگونگی تغییر همه آن است.

چندین راه حل

من به صفحه نقاشی رفتم و سه راه حل خود را به من ارائه داد:

  1. یک جدول مکان منبع را در کد بایت ایجاد کنید و یک دستورالعمل را به یک پرونده و خط نقشه برداری کنید.
  2. دستورالعمل های ویژه ای را منتشر کنید که توسط VM پرش می شود ، و فقط در هنگام سقوط VM استفاده می شود ، برای پیدا کردن نزدیکترین SOURCE دستورالعمل ؛
  3. اندازه دستورالعمل ها را به 8 بایت گسترش دهید و از 4 بایت جدید برای ردیابی پرونده منبع استفاده کنید (به عنوان مثال 2 بایت برای یک شناسه) و خط (2 بایت برای خط به اندازه کافی برای ردیابی خطوط 64K+ پرونده ها به نظر می رسد).

مورد دوم خیلی سریع از روی میز بود ، زیرا من یک شکار داشتم که مانع اجرای برنامه های بیش از حد به دلخواه من می شد. این همچنین بهینه ساز IR را مختل می کند ، و من مجبور شدم الگوهای دستورالعمل بیشتری را به عنوان تشخیص دهم SOURCE دستورالعمل ها را می توان با دستورالعمل های بهینه در هم آمیخت.

راه حل سوم مانند کار زیادی برای یک سود کوچک احساس می شود ، زیرا فقط در هنگام رسیدگی به خطاها استفاده می شود. این همچنین می تواند اندازه پرونده های Bytecode را دو برابر کند و تحولات آینده VM را قفل کند زیرا من نمی توانم از آن 4 بایت اضافی برای هر چیز دیگری استفاده کنم.

📝 توجه داشته باشید

همانطور که رابرت نیستروم در Reddit خاطرنشان کرد ، باعث می شود Bytecode بزرگتر شود VM باعث از دست دادن حافظه پنهان بیشتر می شود و عملکرد را بدتر می کند.

همانطور که احتمالاً حدس زده اید ، من با اولین راه حل رفتم.

اجرای

داده های محل منبع به تجزیه و تحلیل به هر گره AST اضافه می شود ، و آخرین پاس کامپایلر که می تواند به AST دسترسی پیدا کند ، AST پایین تر است که کار آن تولید IR است ، از این رو اضافه کردن دو قسمت منطقی است. source_file وت source_line به IR::Entity ساختار

امتیاز جایزه: با استفاده از این راه حل ردیابی منبع ، وجود دارد (تقریباً) 0 اصلاح در بهینه ساز IR! نزدیک به 0، از آنجا که من مجبور شدم محل منبع را به هر موجودیت IR بهینه شده از موجودات IR فشرده اضافه کنم.

کدام دستورالعمل را باید ردیابی کنیم؟

همه آنها!

ممکن است از خود بپرسید ، “اما آیا این کار بایت تولید شده را دو برابر بزرگتر از راه حل 3 نمی کند؟” ، و شما تا حدی درست است. برای درست کردن این ، ما باید De-Duplication را معرفی کنیم!

راه حل پیشنهادی ردیابی هر مکان منبع دستورالعمل بود ، اما بسیاری از دستورالعمل ها به همان صورت و خط ، به عنوان یک عبارت واحد مانند اشاره می کنند (if (< value 5) (print "you can pass!") (go-to-jail)) حدود 10-12 دستورالعمل را شامل می شود.

اگر ما محل منبع اولین دستورالعمل را در یک سری دستورالعمل ها از همان پرونده و در همان خط پیگیری کنیم ، این بیش از حد کافی است!

std::optional<InstLocation> prev = std::nullopt;

for (const auto& inst : page)
{
  if (inst.hasValidSourceLocation())
  {
    // we are guaranteed to have a value since we listed all
    // existing filenames in IRCompiler::process before,
    // thus we do not have to check if std::ranges::find
    // returned a valid iterator.
    auto file_id = static_cast<uint16_t>(
      std::distance(
        m_filenames.begin(),
        std::ranges::find(m_filenames, inst.filename())));

    std::optional<internal::InstLoc> prev = std::nullopt;
    if (!locations.empty())
      prev = locations.back();

     // skip redundant instruction location
     if (!(
           prev.has_value() &&
           prev->filename_id == file_id &&
           prev->line == inst.sourceLine() &&
           prev->page_pointer == i))
       locations.push_back(
         { .page_pointer = static_cast<uint16_t>(i),
           .inst_pointer = ip,
           .filename_id = file_id,
           .line = static_cast<uint32_t>(inst.sourceLine()) });
  }

  if (inst.kind() != IR::Kind::Label)
    ++ip;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

سوءاستفاده از مکان منبع جدید برای خطاهای ما

اکنون ما باید نام های پرونده و (صفحه ، دستورالعمل ، شناسه نام پرونده ، خط) را ردیابی کنیم تا مکانهای منبع برای خطاهای خود داشته باشیم. اینها در دو جدول داده در کد بایت تقسیم شده اند.

با داشتن آن جداول ، با توجه به نشانگر صفحه و نشانگر دستورالعمل ، می توانیم نزدیکترین اطلاعات محل منبع و پرونده مرتبط را با شناسه آن (فهرست در جدول نام های پرونده) بازیابی کنیم.

std::optional<InstLoc> VM::findSourceLocation(
    const std::size_t ip,
    const std::size_t pp
) {
  std::optional<InstLoc> match = std::nullopt;

  for (const auto location : m_state.m_inst_locations)
  {
    if (location.page_pointer == pp && !match)
      match = location;

    // select the best match: we want to find the location
    // that's nearest our instruction pointer, but not equal
    // to it as the IP will always be pointing to the next
    // instruction, not yet executed. Thus, the erroneous
    // instruction is the previous one.
    if (location.page_pointer == pp && match &&
        location.inst_pointer < ip / 4)
      match = location;

    // early exit because we won't find anything better, as
    // inst locations are ordered by ascending (pp, ip)
    if (location.page_pointer > pp || (
          location.page_pointer == pp &&
          location.inst_pointer >= ip / 4))
      break;
  }

  return match;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

نتایج!

با توجه به کد نادرست زیر:

(let foo (fun (a b) (+ a b)))

(foo 1 2 3)
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

ما در هنگام تماس با خطای اریتی انتظار داریم foo، همانطور که استدلال های زیادی را تصویب کردیم.

ArityError: When calling `(foo)', received 3 arguments, but expected 2: `(foo a b)'

In file a.ark
    1 | (let foo (fun (a b) (+ a b)))
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
    2 |
    3 | (foo 1 2 3)

[   2] In function `foo' (a.ark:1)
[   1] In global scope (a.ark:3)

Current scope variables values:
foo = Function@1
At IP: 0, PP: 1, SP: 5
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

از نظر کد بایت ، موارد زیر را ایجاد می کند:

Symbols table (length: 3)
0) foo
1) a
2) b

Constants table (length: 4)
0) (PageAddr) 1
1) (Number) 1
2) (Number) 2
3) (Number) 3

Instruction locations table (length: 3)
 PP, IP
  0,  0 -> a.ark:0
  0,  4 -> a.ark:2
  1,  4 -> a.ark:0

Code segment 0 (length: 24)
   0 39 00 00 00 LOAD_CONST_STORE
   1 38 00 20 01 LOAD_CONST_LOAD_CONST
   2 03 00 00 03 LOAD_CONST 3 (Number)
   3 02 00 00 00 LOAD_SYMBOL_BY_INDEX (0)
   4 0b 00 00 03 CALL (3)
   5 0a 00 00 00 HALT

Code segment 1 (length: 28)
   0 05 00 00 01 STORE a
   1 05 00 00 02 STORE b
   2 02 00 00 01 LOAD_SYMBOL_BY_INDEX (1)
   3 02 00 00 00 LOAD_SYMBOL_BY_INDEX (0)
   4 20 00 00 00 ADD
   5 09 00 00 00 RET
   6 0a 00 00 00 HALT
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

همانطور که مشاهده می کنید ، جدول مکان های دستورالعمل به لطف De-Dupplication کاملاً اندک است ، و ما تمام اطلاعات لازم را برای گزارش صحیح خطاها داریم!

در اصل از lexp.lt

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا