Skip to content

Instantly share code, notes, and snippets.

@wanabe
Last active August 11, 2024 11:30
Show Gist options
  • Save wanabe/1ba6725f51a74389ec31cfa4da12fc1a to your computer and use it in GitHub Desktop.
Save wanabe/1ba6725f51a74389ec31cfa4da12fc1a to your computer and use it in GitHub Desktop.

なつやすみのにっき 2024/08/11

せっかく RISC-V 環境を手に入れたので ruby を動かしたい

ので、やってみます。

(WSL で)
$ git clone --depth 1 --branch v3_4_0_preview1 https://github.com/ruby/ruby.git

(コンテナ内で)
# apt-get install -y autoconf build-essential libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev ruby
# cd ruby
# ./autogen.sh
# mkdir build && cd build
# ../configure
# make -j$(nproc)

RJIT がサポートされていない環境なので当然無効になっていますが、無理やり有効にしたらどうなるのか見てみます。

diff --git a/configure.ac b/configure.ac
index e9a452e..b75d232 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3944,7 +3944,7 @@ AS_IF([test "$cross_compiling" = no],
         [arm64-darwin*|aarch64-darwin*|x86_64-darwin*], [
             RJIT_TARGET_OK=yes
         ],
-        [arm64-*linux*|aarch64-*linux*|x86_64-*linux*], [
+        [arm64-*linux*|aarch64-*linux*|x86_64-*linux*|riscv64-*linux*], [
             RJIT_TARGET_OK=yes
         ],
         [arm64-*bsd*|aarch64-*bsd*|x86_64-*bsd*], [
# make -j20 miniruby
...
../revision.h unchanged
linking miniruby
rjit_c.o: in function `builtin_inline_class_97':
/work/ruby/build/../rjit_c.rbinc:144:(.text+0x1e00): relocation truncated to fit: R_RISCV_PCREL_HI20 against `rjit_reserve_addr_space'
/usr/bin/ld: BFD (GNU Binutils for Ubuntu) 2.42.90.20240720 assertion fail ../../bfd/elfnn-riscv.c:3447
/usr/bin/ld: BFD (GNU Binutils for Ubuntu) 2.42.90.20240720 assertion fail ../../bfd/elfnn-riscv.c:3447
collect2: error: ld returned 1 exit status
make: *** [Makefile:298: miniruby] Error 1

どうやら関数の呼び出し元と呼び出し先が遠すぎるとエラーになるようです。アドレス全体を数値でコントロールしようとするのが無理筋なのでしょうか。

-mcmodel=large を指定できると話が早いのですが、-fPIC と併用ができないとのこと。それはそうですね。 ということでいろいろやってみた結果、アドレス取得する関数を変えてさらに最適化を -O2 まで下げるととりあえずビルドできました。

index e6d8d5d..6cc016e 100644
--- a/rjit_c.c
+++ b/rjit_c.c
@@ -55,6 +55,8 @@ align_ptr(uint8_t *ptr, uint32_t multiple)
 }
 #endif
 
+static VALUE mprotect_exec(rb_execution_context_t *ec, VALUE self, VALUE rb_mem_block, VALUE rb_mem_size);
+
 // Address space reservation. Memory pages are mapped on an as needed basis.
 // See the Rust mm module for details.
 static uint8_t *
@@ -66,7 +68,7 @@ rjit_reserve_addr_space(uint32_t mem_size)
     // On Linux
     #if defined(MAP_FIXED_NOREPLACE) && defined(_SC_PAGESIZE)
         uint32_t const page_size = (uint32_t)sysconf(_SC_PAGESIZE);
-        uint8_t *const cfunc_sample_addr = (void *)&rjit_reserve_addr_space;
+        uint8_t *const cfunc_sample_addr = (void *)&mprotect_exec;
         uint8_t *const probe_region_end = cfunc_sample_addr + INT32_MAX;
         // Align the requested address to page size
         uint8_t *req_addr = align_ptr(cfunc_sample_addr, page_size);
@@ -96,7 +98,7 @@ rjit_reserve_addr_space(uint32_t mem_size)
     #else
         // Try to map a chunk of memory as executable
         mem_block = mmap(
-            (void *)rjit_reserve_addr_space,
+            (void *)mprotect_exec,
             mem_size,
             PROT_NONE,
             MAP_PRIVATE | MAP_ANONYMOUS,
# make -j20 miniruby optflags="-O2 -fno-fast-math"
...

generating miniprelude.c
making ../rjit_c.rbinc
../revision.h unchanged
compiling ../rjit_c.c
miniprelude.c updated
compiling ../miniinit.c
linking miniruby

動作まではわかりませんがビルドだけはできたので、make install して試してみます。

# ruby --rjit-trace --rjit-call-threshold=2 --rjit -ve 'def add(a, b); a + b; end; 10.times do add(1,2) end'
ruby 3.4.0dev (2024-08-11) +RJIT [riscv64-linux]
warning: RJIT does not support riscv64-linux yet

とのこと。それはそうですね。対応していきます。

diff --git a/lib/ruby_vm/rjit/compiler.rb b/lib/ruby_vm/rjit/compiler.rb
index e5c3adf..41696e1 100644
--- a/lib/ruby_vm/rjit/compiler.rb
+++ b/lib/ruby_vm/rjit/compiler.rb
@@ -510,7 +510,7 @@ def assert(cond)
 
     def supported_platform?
       return @supported_platform if defined?(@supported_platform)
-      @supported_platform = RUBY_PLATFORM.match?(/x86_64/).tap do |supported|
+      @supported_platform = RUBY_PLATFORM.match?(/x86_64|riscv64/).tap do |supported|
         warn "warning: RJIT does not support #{RUBY_PLATFORM} yet" unless supported
       end
     end
# ruby --disable-gems --rjit-trace --rjit-call-threshold=2 --rjit -ve 'def add(a, b); a + b; end; 10.times do add
(1,2) end'
ruby 3.4.0dev (2024-08-11) +RJIT [riscv64-linux]
RuntimeError: unexpected offset: -139649552344349
/usr/local/lib/ruby/3.4.0+0/ruby_vm/rjit/assembler.rb:1053:in 'block in RubyVM::RJIT::Assembler#resolve_rel32'
<internal:array>:54:in 'Array#each'
/usr/local/lib/ruby/3.4.0+0/ruby_vm/rjit/assembler.rb:1048:in 'Enumerable#each_with_index'
/usr/local/lib/ruby/3.4.0+0/ruby_vm/rjit/assembler.rb:1048:in 'RubyVM::RJIT::Assembler#resolve_rel32'
/usr/local/lib/ruby/3.4.0+0/ruby_vm/rjit/assembler.rb:58:in 'RubyVM::RJIT::Assembler#assemble'
/usr/local/lib/ruby/3.4.0+0/ruby_vm/rjit/code_block.rb:22:in 'RubyVM::RJIT::CodeBlock#write'
/usr/local/lib/ruby/3.4.0+0/ruby_vm/rjit/insn_compiler.rb:5919:in 'block in RubyVM::RJIT::InsnCompiler#jit_direct_jump'
<internal:kernel>:135:in 'Kernel#then'
/usr/local/lib/ruby/3.4.0+0/ruby_vm/rjit/insn_compiler.rb:5917:in 'RubyVM::RJIT::InsnCompiler#jit_direct_jump'
/usr/local/lib/ruby/3.4.0+0/ruby_vm/rjit/insn_compiler.rb:5908:in 'RubyVM::RJIT::InsnCompiler#defer_compilation'
/usr/local/lib/ruby/3.4.0+0/ruby_vm/rjit/insn_compiler.rb:1452:in 'RubyVM::RJIT::InsnCompiler#opt_send_without_block'
/usr/local/lib/ruby/3.4.0+0/ruby_vm/rjit/insn_compiler.rb:91:in 'RubyVM::RJIT::InsnCompiler#compile'
/usr/local/lib/ruby/3.4.0+0/ruby_vm/rjit/compiler.rb:321:in 'RubyVM::RJIT::Compiler#compile_block'
/usr/local/lib/ruby/3.4.0+0/ruby_vm/rjit/compiler.rb:67:in 'RubyVM::RJIT::Compiler#compile'
-e:1:in 'block in <main>'
<internal:numeric>:238:in 'Integer#times'
-e:1:in '<main>'

x86_64 向けですが、JIT 処理に到達しました。 さて、RJIT は Ruby で書かれているので処理を置き換えることが可能らしいです。

RubyVM::RJIT::Compiler#compile を置き換えるとよさそうです。アセンブラ命令列と変換ロジックを用意するのは大変なので、gcc のインラインアセンブラで手動変換した結果を使います。

# ruby --disable-gems --rjit --rjit-call-threshold=2 -v riscv64.rb
ruby 3.4.0dev (2024-08-11) +RJIT [riscv64-linux]
3
nil
nil
nil
nil

nil を返すだけのコードが生成できました。

if RubyVM::RJIT.enabled?
module Riscv64
module Compiler
def supported_platform?
true
end
def compile(iseq, _cfp)
cb = RubyVM::RJIT::CodeBlock.new(mem_block: RubyVM::RJIT::C.mmap(4 * 1024), mem_size: 4 * 1024)
asm = Assembler.new
asm.write(
# Pop cfp: ec->cfp = cfp + 1 (a0 is EC, a1 is CFP, rb_control_frame_t.size is 56, offsetof(cfp) is 16)
0x93, 0x85, 0x85, 0x03, # addi a1,a1,56
0x0c, 0xe9, # sd a1, 16(a0)
# retval = Qnil (a0 is retval, 8 is `nil.object_id`)
0x11, 0x45, # li a0, 8
0x82, 0x80, # ret
)
iseq.body.jit_entry = cb.write(asm)
end
end
class Assembler < RubyVM::RJIT::Assembler
def write(...)
@bytes.push(...)
end
end
end
module RubyVM::RJIT
Compiler.prepend(Riscv64::Compiler)
end
end
return unless $0 == __FILE__
def add(a, b)
a + b
end
c = 5
while c > 0
c -= 1
p add(1,2)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment