{"id":15404253,"url":"https://github.com/k0kubun/llrb","last_synced_at":"2025-04-05T12:06:57.555Z","repository":{"id":56881723,"uuid":"88408003","full_name":"k0kubun/llrb","owner":"k0kubun","description":"LLVM-based JIT Compiler for Ruby","archived":false,"fork":false,"pushed_at":"2020-11-19T05:39:54.000Z","size":517,"stargazers_count":307,"open_issues_count":0,"forks_count":6,"subscribers_count":27,"default_branch":"master","last_synced_at":"2024-04-23T17:07:00.323Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/k0kubun.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-16T11:09:12.000Z","updated_at":"2024-04-07T00:02:54.000Z","dependencies_parsed_at":"2022-08-20T23:10:59.498Z","dependency_job_id":null,"html_url":"https://github.com/k0kubun/llrb","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k0kubun%2Fllrb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k0kubun%2Fllrb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k0kubun%2Fllrb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k0kubun%2Fllrb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/k0kubun","download_url":"https://codeload.github.com/k0kubun/llrb/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247332606,"owners_count":20921853,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T16:12:00.391Z","updated_at":"2025-04-05T12:06:57.532Z","avatar_url":"https://github.com/k0kubun.png","language":"C","funding_links":[],"categories":["C"],"sub_categories":[],"readme":"# LLRB\n\nLLRB is a LLVM-based JIT compiler for Ruby.\n\n## Project status\n\nI'm currently working on another JIT approach: [YARV-MJIT](https://github.com/k0kubun/yarv-mjit).\n\n## What's LLRB?\n\nThis is an experimental project to implement an idea presented by [@evanphx](https://github.com/evanphx) at [RubyKaigi 2015 Keynote](http://rubykaigi.org/2015/presentations/evanphx):\u003cbr\u003e\nMethod JIT compiler inlining CRuby core functions using LLVM.\n\n### How does it work?\n\nOn build time, some core functions are compiled to LLVM bitcode (binary form of LLVM IR) files via LLVM IR.\n\n```\n ________     _________     ______________\n|        |   |         |   |              |\n| CRuby  |   | CRuby   |   | CRuby        |\n| C code |--\u003e| LLVM IR |--\u003e| LLVM bitcode |\n|________|   |_________|   |______________|\n```\n\nThose files are separated per function to load only necessary functions.\nYou can see how they are separated in [ext directory](./ext).\n\nAfter profiler of LLRB JIT is started, when Ruby is running,\nLLRB compiles Ruby method's YARV Instruction Sequence to native machine code.\n\n```\n ______     ______________      __________      ___________      _________\n|      |   |              |    |          |    |           |    |         |\n| YARV |   | ISeq Control |    | LLVM IR  |    | Optimized |    | Machine |\n| ISeq |--\u003e| Flow Graph   |-*-\u003e| for ISeq |-*-\u003e| LLVM IR   |-*-\u003e| code    |\n|______|   |______________| |  |__________| |  |___________| |  |_________|\n                            |               |                |\n                            | Link          | Optimize       | JIT compile\n                      ______|_______     ___|____          __|____\n                     |              |   |        |        |       |\n                     | CRuby        |   | LLVM   |        | LLVM  |\n                     | LLVM Bitcode |   | Passes |        | MCJIT |\n                     |______________|   |________|        |_______|\n```\n\n### Does it improve performance?\n\nNow basic instruction inlining is done. Let's see its effect.\n\nConsider following Ruby method, which is the same as [ruby/benchmark/bm\\_loop\\_whileloop.rb](https://github.com/ruby/ruby/blob/v2_4_1/benchmark/bm_loop_whileloop.rb).\n\n```rb\ndef while_loop\n  i = 0\n  while i\u003c30_000_000\n    i += 1\n  end\nend\n```\n\nThe YARV ISeq, compilation target in LLRB, is this:\n\n```\n\u003e puts RubyVM::InstructionSequence.of(method(:while_loop)).disasm\n== disasm: #\u003cISeq:while_loop@(pry)\u003e=====================================\n== catch table\n| catch type: break  st: 0015 ed: 0035 sp: 0000 cont: 0035\n| catch type: next   st: 0015 ed: 0035 sp: 0000 cont: 0012\n| catch type: redo   st: 0015 ed: 0035 sp: 0000 cont: 0015\n|------------------------------------------------------------------------\nlocal table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])\n[ 1] i\n0000 trace            8                                               (   1)\n0002 trace            1                                               (   2)\n0004 putobject_OP_INT2FIX_O_0_C_\n0005 setlocal_OP__WC__0 3\n0007 trace            1                                               (   3)\n0009 jump             25\n0011 putnil\n0012 pop\n0013 jump             25\n0015 trace            1                                               (   4)\n0017 getlocal_OP__WC__0 3\n0019 putobject_OP_INT2FIX_O_1_C_\n0020 opt_plus         \u003ccallinfo!mid:+, argc:1, ARGS_SIMPLE\u003e, \u003ccallcache\u003e\n0023 setlocal_OP__WC__0 3\n0025 getlocal_OP__WC__0 3                                             (   3)\n0027 putobject        30000000\n0029 opt_lt           \u003ccallinfo!mid:\u003c, argc:1, ARGS_SIMPLE\u003e, \u003ccallcache\u003e\n0032 branchif         15\n0034 putnil\n0035 trace            16                                              (   6)\n0037 leave                                                            (   4)\n=\u003e nil\n```\n\nBy LLRB compiler, such YARV ISeq is compiled to LLVM IR like:\n\n```llvm\ndefine i64 @llrb_exec(i64, i64) {\nlabel_0:\n  call void @llrb_insn_trace(i64 %0, i64 %1, i32 8, i64 52)\n  call void @llrb_insn_trace(i64 %0, i64 %1, i32 1, i64 52)\n  call void @llrb_insn_setlocal_level0(i64 %1, i64 3, i64 1)\n  call void @llrb_insn_trace(i64 %0, i64 %1, i32 1, i64 52)\n  br label %label_25\n\nlabel_15:                                         ; preds = %label_25\n  call void @llrb_insn_trace(i64 %0, i64 %1, i32 1, i64 52)\n  %2 = call i64 @llrb_insn_getlocal_level0(i64 %1, i64 3)\n  call void @llrb_set_pc(i64 %1, i64 94225474387824)\n  %opt_plus = call i64 @llrb_insn_opt_plus(i64 %2, i64 3)\n  call void @llrb_insn_setlocal_level0(i64 %1, i64 3, i64 %opt_plus)\n  br label %label_25\n\nlabel_25:                                         ; preds = %label_15, %label_0\n  %3 = call i64 @llrb_insn_getlocal_level0(i64 %1, i64 3)\n  call void @llrb_set_pc(i64 %1, i64 94225474387896)\n  %opt_lt = call i64 @llrb_insn_opt_lt(i64 %3, i64 60000001)\n  %RTEST_mask = and i64 %opt_lt, -9\n  %RTEST = icmp ne i64 %RTEST_mask, 0\n  br i1 %RTEST, label %label_15, label %label_34\n\nlabel_34:                                         ; preds = %label_25\n  call void @llrb_insn_trace(i64 %0, i64 %1, i32 16, i64 8)\n  call void @llrb_set_pc(i64 %1, i64 94225474387960)\n  call void @llrb_push_result(i64 %1, i64 8)\n  ret i64 %1\n}\n```\n\nAs LLRB compiler links precompiled LLVM bitcode of CRuby functions,\nusing LLVM's FunctionInliningPass (\"Pass\" is LLVM's optimizer),\nsome C functions are inlined and inlined code will be well optimized by Passes like InstCombinePass.\n\n```llvm\ndefine i64 @llrb_exec(i64, i64) #0 {\n  ...\n\nland.lhs.true.i:                                  ; preds = %label_25\n  %49 = load %struct.rb_vm_struct*, %struct.rb_vm_struct** @ruby_current_vm, align 8, !dbg !3471, !tbaa !3472\n  %arrayidx.i = getelementptr inbounds %struct.rb_vm_struct, %struct.rb_vm_struct* %49, i64 0, i32 39, i64 7, !dbg !3471\n  %50 = load i16, i16* %arrayidx.i, align 2, !dbg !3471, !tbaa !3473\n  %and2.i = and i16 %50, 1, !dbg !3471\n  %tobool6.i = icmp eq i16 %and2.i, 0, !dbg !3471\n  br i1 %tobool6.i, label %if.then.i, label %if.else11.i, !dbg !3475, !prof !3380\n\nif.then.i:                                        ; preds = %land.lhs.true.i\n  call void @llvm.dbg.value(metadata i64 %48, i64 0, metadata !2680, metadata !3361) #7, !dbg !3477\n  call void @llvm.dbg.value(metadata i64 60000001, i64 0, metadata !2683, metadata !3361) #7, !dbg !3478\n  %cmp7.i = icmp slt i64 %48, 60000001, !dbg !3479\n  %..i = select i1 %cmp7.i, i64 20, i64 0, !dbg !3481\n  br label %llrb_insn_opt_lt.exit\n\nif.else11.i:                                      ; preds = %land.lhs.true.i, %label_25\n  %call35.i = call i64 (i64, i64, i32, ...) @rb_funcall(i64 %48, i64 60, i32 1, i64 60000001) #7, !dbg !3483\n  br label %llrb_insn_opt_lt.exit, !dbg !3486\n\nllrb_insn_opt_lt.exit:                            ; preds = %if.then.i, %if.else11.i\n  %retval.1.i = phi i64 [ %..i, %if.then.i ], [ %call35.i, %if.else11.i ]\n  %RTEST_mask = and i64 %retval.1.i, -9\n  %RTEST = icmp eq i64 %RTEST_mask, 0\n\n  ...\n}\n```\n\nIn this example, you can see many things are inlined.\nLLRB's compiled code fetches RubyVM state and check whether `\u003c` method is redefined or not,\nand if `\u003c` is not redefined, `if.then.i` block is used and in that block `icmp slt` is used instead of calling Ruby method `#\u003c`.\nNote that it's done by just inlining YARV's `opt_lt` instruction directly and it's not hard to implement.\n\nThus, following benchmark shows the performance is improved.\n\n```rb\nruby = Class.new\ndef ruby.script\n  i = 0\n  while i\u003c 30_000_000\n    i += 1\n  end\nend\n\nllrb = Class.new\ndef llrb.script\n  i = 0\n  while i\u003c 30_000_000\n    i += 1\n  end\nend\n\nLLRB::JIT.compile(llrb, :script)\n\nBenchmark.ips do |x|\n  x.report('Ruby') { ruby.script }\n  x.report('LLRB') { llrb.script }\n  x.compare!\nend\n```\n\nOn [wercker](https://app.wercker.com/k0kubun/llrb/runs/build/5966cdab1ee2040001449915?step=5966cdded82c270001e8740):\n\n```\nCalculating -------------------------------------\n                Ruby      7.449  (± 0.0%) i/s -     38.000  in   5.101634s\n                LLRB     36.974  (± 0.0%) i/s -    186.000  in   5.030540s\n\nComparison:\n                LLRB:       37.0 i/s\n                Ruby:        7.4 i/s - 4.96x  slower\n```\n\n## How is the design?\n### Built as C extension\n\nCurrently LLRB is in an experimental stage. For fast development and to stay up-to-date\nfor CRuby core changes, LLRB is built as C extension.\nWe can use bundler, benchmark-ips, pry, everything else in normal C extension repository. It's hard in just CRuby fork.\n\nFor optimization, unfortunately it needs to export some symbols from CRuby,\nso it needs to compile [k0kubun/ruby's llrb branch](https://github.com/k0kubun/ruby/tree/llrb)\nand install llrb.gem from that Ruby.\n\nBut I had the rule that I don't add modification except exporting symbols.\nAnd LLRB code refers to such exported functions or variables in CRuby core by including CRuby header as possible.\nI believe it contributes to maintainability of LLRB prototype.\n\n### Conservative design for safety\n\nYARV is already proven to be reliable. In LLRB, YARV is not modified at all.\nThen, how is JIT achieved?\n\nYARV has `opt_call_c_function` instruction, which is explained to [\"call native compiled method\"](https://github.com/ruby/ruby/blob/v2_4_1/insns.def#L2136).\nWhy not use that?\n\nLLRB compiles any ISeq to following ISeq.\n\n```\n0000 opt_call_c_function\n0002 leave\n```\n\nSo simple. Note that `opt_call_c_function` [can handle YARV's internal exception](https://github.com/ruby/ruby/blob/v2_4_1/insns.def#L2147-L2151).\nIn that instruction, we can do everything.\n\nOne more conservative thing in LLRB is that it fills `leave` instructions to remaining places.\nTo let YARV catch table work, it needs to update program counter properly,\nand then it requires an instruction to the place that program counter points to.\n\nFor safe exit when catch table is used, `leave` instructions are filled to the rest of first `opt_call_c_function`.\n\n### Sampling-based lightweight profiler\n\nSampling profiler is promising approach to reduce the overhead of profiling without spoiling profiling efficiency.\nLLRB's profiler to schedule JIT-ed ISeq is implemented in almost the same way as [stackprof](https://github.com/tmm1/stackprof).\nIt is widely-used on production and proven to be a reliable approach.\n\nIt kicks profiler in some CPU-time interval, and the parameter can be modified if necessary.\nUsing [optcarrot](https://github.com/mame/optcarrot) benchmark, I tested profiler overhead\nand LLRB's profiler didn't reduce the fps of optcarrot with current parameter.\n\nAlso, it uses `rb_postponed_job_register_one` API, which is used by stackprof too, to do JIT compile.\nSo the compilation is done in very safe timing.\n\n### Less compilation effort\n\nCRuby's C functions to inline are precompiled as LLVM bitcode on LLRB build process.\nLLRB's compiler builds LLVM IR using LLVM IRBuilder, so the bitcode files are directly linked to that.\n\nIt means that LLRB has no overhead of parsing and compiling C source code on runtime.\nIt has less compilation effort, right?\n\nCurrently the performance bottleneck is not in compiler, unfortunately!\nSo it doesn't use extra thread for JIT compilation for now.\n\n## Build dependency\n\n- **64bit CPU**\n  - This should be fixed later\n- LLVM/clang 3.8+\n  - `llvm-config`, `clang` and `llvm-as` commands need to appear in `$PATH`\n- [CRuby fork in k0kubun/ruby's llrb branch](https://github.com/k0kubun/ruby/tree/llrb)\n\n## Usage\n\nOnce build dependency is met, execute `gem install llrb` and do:\n\n```ruby\nrequire 'llrb/start'\n```\n\n`llrb/start` file does `LLRB::JIT.start`. Note that you can also do that by `ruby -rllrb/start -e \"...\"`.\n\nIf you want to see which method is compiled, compile the gem with `#define LLRB_ENABLE_DEBUG 1`.\nAgain, it's in an experimental stage and currently it doesn't improve performance in real-world application.\n\n## TODOs\n\n- Improve performance...\n- Implement ISeq method inlining\n- Support all YARV instructions\n  - `expandarray`, `reverse`, `reput`, `defineclass`, `once`, `opt_call_c_function` are not supported yet.\n- Care about unexpectedly GCed object made during compilation\n\n## License\n\nThe same as Ruby.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk0kubun%2Fllrb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fk0kubun%2Fllrb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk0kubun%2Fllrb/lists"}