diff options
author | Takashi Kokubun <takashikkbn@gmail.com> | 2025-03-03 13:46:53 -0800 |
---|---|---|
committer | Takashi Kokubun <takashikkbn@gmail.com> | 2025-04-18 21:52:59 +0900 |
commit | 0a543daf15e995ad12b0884bf89ea89b6b480dd2 (patch) | |
tree | 8cadafeccd9da51ea8c11014c3b2ab8701848387 | |
parent | 30db473389ca5bb6c68bec72de49330a72a2541c (diff) |
Add zjit_* instructions to profile the interpreter (https://github.com/Shopify/zjit/pull/16)
* Add zjit_* instructions to profile the interpreter
* Rename FixnumPlus to FixnumAdd
* Update a comment about Invalidate
* Rename Guard to GuardType
* Rename Invalidate to PatchPoint
* Drop unneeded debug!()
* Plan on profiling the types
* Use the output of GuardType as type refined outputs
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/13131
-rw-r--r-- | common.mk | 8 | ||||
-rw-r--r-- | insns.def | 1 | ||||
-rw-r--r-- | template/Makefile.in | 2 | ||||
-rw-r--r-- | tool/ruby_vm/models/bare_instructions.rb | 5 | ||||
-rw-r--r-- | tool/ruby_vm/models/instructions.rb | 1 | ||||
-rw-r--r-- | tool/ruby_vm/models/zjit_instructions.rb | 58 | ||||
-rw-r--r-- | tool/ruby_vm/views/_insn_sp_pc_dependency.erb | 27 | ||||
-rw-r--r-- | tool/ruby_vm/views/_zjit_helpers.erb | 14 | ||||
-rw-r--r-- | tool/ruby_vm/views/_zjit_instruction.erb | 8 | ||||
-rw-r--r-- | tool/ruby_vm/views/insns.inc.erb | 5 | ||||
-rw-r--r-- | tool/ruby_vm/views/insns_info.inc.erb | 1 | ||||
-rw-r--r-- | tool/ruby_vm/views/lib/ruby_vm/rjit/instruction.rb.erb | 14 | ||||
-rw-r--r-- | tool/ruby_vm/views/vm.inc.erb | 4 | ||||
-rw-r--r-- | vm.c | 18 | ||||
-rw-r--r-- | yjit/bindgen/src/main.rs | 2 | ||||
-rw-r--r-- | zjit.c | 43 | ||||
-rw-r--r-- | zjit.h | 19 | ||||
-rw-r--r-- | zjit/src/cruby_bindings.inc.rs | 226 | ||||
-rw-r--r-- | zjit/src/hir.rs | 39 | ||||
-rw-r--r-- | zjit/src/lib.rs | 1 | ||||
-rw-r--r-- | zjit/src/options.rs | 2 | ||||
-rw-r--r-- | zjit/src/profile.rs | 122 |
22 files changed, 498 insertions, 122 deletions
@@ -1194,10 +1194,12 @@ $(srcs_vpath)insns.inc: $(tooldir)/ruby_vm/views/insns.inc.erb $(inc_common_head $(srcs_vpath)insns_info.inc: $(tooldir)/ruby_vm/views/insns_info.inc.erb $(inc_common_headers) \ $(tooldir)/ruby_vm/views/_insn_type_chars.erb $(tooldir)/ruby_vm/views/_insn_name_info.erb \ $(tooldir)/ruby_vm/views/_insn_len_info.erb $(tooldir)/ruby_vm/views/_insn_operand_info.erb \ - $(tooldir)/ruby_vm/views/_attributes.erb $(tooldir)/ruby_vm/views/_comptime_insn_stack_increase.erb + $(tooldir)/ruby_vm/views/_attributes.erb $(tooldir)/ruby_vm/views/_comptime_insn_stack_increase.erb \ + $(tooldir)/ruby_vm/views/_zjit_helpers.erb $(srcs_vpath)vmtc.inc: $(tooldir)/ruby_vm/views/vmtc.inc.erb $(inc_common_headers) $(srcs_vpath)vm.inc: $(tooldir)/ruby_vm/views/vm.inc.erb $(inc_common_headers) \ - $(tooldir)/ruby_vm/views/_insn_entry.erb $(tooldir)/ruby_vm/views/_trace_instruction.erb + $(tooldir)/ruby_vm/views/_insn_entry.erb $(tooldir)/ruby_vm/views/_trace_instruction.erb \ + $(tooldir)/ruby_vm/views/_zjit_instruction.erb BUILTIN_RB_SRCS = \ $(srcdir)/ast.rb \ @@ -19568,6 +19570,7 @@ vm.$(OBJEXT): {$(VPATH)}vm_sync.h vm.$(OBJEXT): {$(VPATH)}vmtc.inc vm.$(OBJEXT): {$(VPATH)}yjit.h vm.$(OBJEXT): {$(VPATH)}yjit_hook.rbinc +vm.$(OBJEXT): {$(VPATH)}zjit.h vm_backtrace.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h vm_backtrace.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h vm_backtrace.$(OBJEXT): $(CCAN_DIR)/list/list.h @@ -21175,4 +21178,5 @@ zjit.$(OBJEXT): {$(VPATH)}vm_insnhelper.h zjit.$(OBJEXT): {$(VPATH)}vm_opts.h zjit.$(OBJEXT): {$(VPATH)}vm_sync.h zjit.$(OBJEXT): {$(VPATH)}zjit.c +zjit.$(OBJEXT): {$(VPATH)}zjit.h # AUTOGENERATED DEPENDENCIES END @@ -1265,6 +1265,7 @@ opt_plus (CALL_DATA cd) (VALUE recv, VALUE obj) (VALUE val) +// attr bool zjit_profile = true; { val = vm_opt_plus(recv, obj); diff --git a/template/Makefile.in b/template/Makefile.in index a1476e9beb..b425986a8a 100644 --- a/template/Makefile.in +++ b/template/Makefile.in @@ -640,6 +640,7 @@ $(INSNS): $(srcdir)/insns.def vm_opts.h \ $(tooldir)/ruby_vm/models/operands_unifications.rb \ $(tooldir)/ruby_vm/models/trace_instructions.rb \ $(tooldir)/ruby_vm/models/typemap.rb \ + $(tooldir)/ruby_vm/models/zjit_instructions.rb \ $(tooldir)/ruby_vm/scripts/converter.rb \ $(tooldir)/ruby_vm/scripts/insns2vm.rb \ $(tooldir)/ruby_vm/views/_attributes.erb \ @@ -655,6 +656,7 @@ $(INSNS): $(srcdir)/insns.def vm_opts.h \ $(tooldir)/ruby_vm/views/_notice.erb \ $(tooldir)/ruby_vm/views/_sp_inc_helpers.erb \ $(tooldir)/ruby_vm/views/_trace_instruction.erb \ + $(tooldir)/ruby_vm/views/_zjit_instruction.erb \ $(tooldir)/ruby_vm/views/insns.inc.erb \ $(tooldir)/ruby_vm/views/insns_info.inc.erb \ $(tooldir)/ruby_vm/views/optinsn.inc.erb \ diff --git a/tool/ruby_vm/models/bare_instructions.rb b/tool/ruby_vm/models/bare_instructions.rb index f813760cb6..491e3eaaa6 100644 --- a/tool/ruby_vm/models/bare_instructions.rb +++ b/tool/ruby_vm/models/bare_instructions.rb @@ -148,6 +148,10 @@ class RubyVM::BareInstructions @variables.find { |_, var_info| var_info[:type] == 'CALL_DATA' } end + def zjit_profile? + @attrs.fetch('zjit_profile').expr.expr != 'false;' + end + private def check_attribute_consistency @@ -186,6 +190,7 @@ class RubyVM::BareInstructions generate_attribute 'rb_snum_t', 'sp_inc', rets.size - pops.size generate_attribute 'bool', 'handles_sp', default_definition_of_handles_sp generate_attribute 'bool', 'leaf', default_definition_of_leaf + generate_attribute 'bool', 'zjit_profile', false end def default_definition_of_handles_sp diff --git a/tool/ruby_vm/models/instructions.rb b/tool/ruby_vm/models/instructions.rb index d243ffa49b..8856c49834 100644 --- a/tool/ruby_vm/models/instructions.rb +++ b/tool/ruby_vm/models/instructions.rb @@ -17,5 +17,6 @@ RubyVM::Instructions = RubyVM::BareInstructions.to_a + \ RubyVM::OperandsUnifications.to_a + \ RubyVM::InstructionsUnifications.to_a +require_relative 'zjit_instructions' require_relative 'trace_instructions' RubyVM::Instructions.freeze diff --git a/tool/ruby_vm/models/zjit_instructions.rb b/tool/ruby_vm/models/zjit_instructions.rb new file mode 100644 index 0000000000..2bf190e3f7 --- /dev/null +++ b/tool/ruby_vm/models/zjit_instructions.rb @@ -0,0 +1,58 @@ +require_relative '../helpers/c_escape' +require_relative 'bare_instructions' + +# Profile YARV instructions to optimize code generated by ZJIT +class RubyVM::ZJITInstructions + include RubyVM::CEscape + + attr_reader :name + + def initialize(orig) + @orig = orig + @name = as_tr_cpp "zjit @ #{@orig.name}" + end + + def pretty_name + return sprintf "%s(...)(...)(...)", @name + end + + def jump_destination + return @orig.name + end + + def bin + return sprintf "BIN(%s)", @name + end + + def width + return @orig.width + end + + def operands_info + return @orig.operands_info + end + + def rets + return ['...'] + end + + def pops + return ['...'] + end + + def attributes + return [] + end + + def has_attribute?(*) + return false + end + + @instances = RubyVM::Instructions.filter(&:zjit_profile?).map {|i| new(i) } + + def self.to_a + @instances + end + + RubyVM::Instructions.push(*to_a) +end diff --git a/tool/ruby_vm/views/_insn_sp_pc_dependency.erb b/tool/ruby_vm/views/_insn_sp_pc_dependency.erb new file mode 100644 index 0000000000..7e7d5ade11 --- /dev/null +++ b/tool/ruby_vm/views/_insn_sp_pc_dependency.erb @@ -0,0 +1,27 @@ +%# -*- C -*- +%# Copyright (c) 2019 Takashi Kokubun. All rights reserved. +%# +%# This file is a part of the programming language Ruby. Permission is hereby +%# granted, to either redistribute and/or modify this file, provided that the +%# conditions mentioned in the file COPYING are met. Consult the file for +%# details. +%# +PUREFUNC(MAYBE_UNUSED(static bool insn_may_depend_on_sp_or_pc(int insn, const VALUE *opes))); + +static bool +insn_may_depend_on_sp_or_pc(int insn, const VALUE *opes) +{ + switch (insn) { +% RubyVM::Instructions.each do |insn| +% # handles_sp?: If true, it requires to move sp in JIT +% # always_leaf?: If false, it may call an arbitrary method. pc should be moved +% # before the call, and the method may refer to caller's pc (lineno). +% unless !insn.is_a?(RubyVM::TraceInstructions) && !insn.is_a?(RubyVM::ZJITInstructions) && !insn.handles_sp? && insn.always_leaf? + case <%= insn.bin %>: +% end +% end + return true; + default: + return false; + } +} diff --git a/tool/ruby_vm/views/_zjit_helpers.erb b/tool/ruby_vm/views/_zjit_helpers.erb new file mode 100644 index 0000000000..0e4e8166e4 --- /dev/null +++ b/tool/ruby_vm/views/_zjit_helpers.erb @@ -0,0 +1,14 @@ +MAYBE_UNUSED(static int vm_insn_to_zjit_insn(int insn)); + +static int +vm_insn_to_zjit_insn(int insn) +{ + switch (insn) { +% RubyVM::ZJITInstructions.to_a.each do |insn| + case BIN(<%= insn.jump_destination %>): + return <%= insn.bin %>; +% end + default: + return insn; + } +} diff --git a/tool/ruby_vm/views/_zjit_instruction.erb b/tool/ruby_vm/views/_zjit_instruction.erb new file mode 100644 index 0000000000..45800f8c63 --- /dev/null +++ b/tool/ruby_vm/views/_zjit_instruction.erb @@ -0,0 +1,8 @@ +/* insn <%= insn.pretty_name %> */ +INSN_ENTRY(<%= insn.name %>) +{ + START_OF_ORIGINAL_INSN(<%= insn.name %>); + rb_zjit_profile_insn(BIN(<%= insn.jump_destination %>), ec); + DISPATCH_ORIGINAL_INSN(<%= insn.jump_destination %>); + END_INSN(<%= insn.name %>); +} diff --git a/tool/ruby_vm/views/insns.inc.erb b/tool/ruby_vm/views/insns.inc.erb index 29981a8a2d..cf34489279 100644 --- a/tool/ruby_vm/views/insns.inc.erb +++ b/tool/ruby_vm/views/insns.inc.erb @@ -12,6 +12,9 @@ edit: __FILE__, } -%> +#ifndef INSNS_INC +#define INSNS_INC 1 + /* BIN : Basic Instruction Name */ #define BIN(n) YARVINSN_##n @@ -24,3 +27,5 @@ enum ruby_vminsn_type { #define ASSERT_VM_INSTRUCTION_SIZE(array) \ STATIC_ASSERT(numberof_##array, numberof(array) == VM_INSTRUCTION_SIZE) + +#endif diff --git a/tool/ruby_vm/views/insns_info.inc.erb b/tool/ruby_vm/views/insns_info.inc.erb index 110d9dfae5..6ba12a856e 100644 --- a/tool/ruby_vm/views/insns_info.inc.erb +++ b/tool/ruby_vm/views/insns_info.inc.erb @@ -17,5 +17,6 @@ <%= render 'insn_operand_info' %> <%= render 'leaf_helpers' %> <%= render 'sp_inc_helpers' %> +<%= render 'zjit_helpers' %> <%= render 'attributes' %> <%= render 'comptime_insn_stack_increase' %> diff --git a/tool/ruby_vm/views/lib/ruby_vm/rjit/instruction.rb.erb b/tool/ruby_vm/views/lib/ruby_vm/rjit/instruction.rb.erb new file mode 100644 index 0000000000..793528af5d --- /dev/null +++ b/tool/ruby_vm/views/lib/ruby_vm/rjit/instruction.rb.erb @@ -0,0 +1,14 @@ +module RubyVM::RJIT # :nodoc: all + Instruction = Data.define(:name, :bin, :len, :operands) + + INSNS = { +% RubyVM::Instructions.each_with_index do |insn, i| + <%= i %> => Instruction.new( + name: :<%= insn.name %>, + bin: <%= i %>, # BIN(<%= insn.name %>) + len: <%= insn.width %>, # insn_len + operands: <%= (insn.operands unless insn.name.start_with?(/trace_|zjit_/)).inspect %>, + ), +% end + } +end diff --git a/tool/ruby_vm/views/vm.inc.erb b/tool/ruby_vm/views/vm.inc.erb index c1a3faf60a..b92d6d31bc 100644 --- a/tool/ruby_vm/views/vm.inc.erb +++ b/tool/ruby_vm/views/vm.inc.erb @@ -25,6 +25,10 @@ <%= render 'insn_entry', locals: { insn: insn } -%> % end % +% RubyVM::ZJITInstructions.to_a.each do |insn| +<%= render 'zjit_instruction', locals: { insn: insn } -%> +% end +% % RubyVM::TraceInstructions.to_a.each do |insn| <%= render 'trace_instruction', locals: { insn: insn } -%> % end @@ -44,6 +44,8 @@ #include "ractor_core.h" #include "vm_sync.h" #include "shape.h" +#include "insns.inc" +#include "zjit.h" #include "builtin.h" @@ -434,18 +436,26 @@ jit_compile(rb_execution_context_t *ec) const rb_iseq_t *iseq = ec->cfp->iseq; struct rb_iseq_constant_body *body = ISEQ_BODY(iseq); - // Increment the ISEQ's call counter and trigger JIT compilation if not compiled #if USE_ZJIT - extern bool rb_zjit_enabled_p; - extern uint64_t rb_zjit_call_threshold; +// Number of calls used to profile a YARV instruction for ZJIT +#define ZJIT_PROFILE_COUNT 1 + if (body->jit_entry == NULL && rb_zjit_enabled_p) { body->jit_entry_calls++; + + // At call-threshold - ZJIT_PROFILE_COUNT, rewrite some of the YARV + // instructions to zjit_* instructions to profile these instructions. + if (body->jit_entry_calls + ZJIT_PROFILE_COUNT == rb_zjit_call_threshold) { + rb_zjit_profile_iseq(iseq); + } + + // At call-threshold, compile the ISEQ with ZJIT. if (body->jit_entry_calls == rb_zjit_call_threshold) { - extern void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception); rb_zjit_compile_iseq(iseq, ec, false); } } #elif USE_YJIT + // Increment the ISEQ's call counter and trigger JIT compilation if not compiled if (body->jit_entry == NULL && rb_yjit_enabled_p) { body->jit_entry_calls++; if (rb_yjit_threshold_hit(iseq, body->jit_entry_calls)) { diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index b2d1694048..f2b973f2c8 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -318,7 +318,7 @@ fn main() { // From yjit.c .allowlist_function("rb_object_shape_count") - .allowlist_function("rb_iseq_(get|set)_yjit_payload") + .allowlist_function("rb_iseq_(get|set)_zjit_payload") .allowlist_function("rb_iseq_pc_at_idx") .allowlist_function("rb_iseq_opcode_at_pc") .allowlist_function("rb_(yjit|zjit)_reserve_addr_space") @@ -22,6 +22,7 @@ #include "iseq.h" #include "ruby/debug.h" #include "internal/cont.h" +#include "zjit.h" // For mmapp(), sysconf() #ifndef _WIN32 @@ -634,3 +635,45 @@ rb_RCLASS_ORIGIN(VALUE c) { return RCLASS_ORIGIN(c); } + +// Convert a given ISEQ's instructions to zjit_* instructions +void +rb_zjit_profile_iseq(const rb_iseq_t *iseq) +{ + // This table encodes an opcode into the instruction's address + const void *const *insn_table = rb_vm_get_insns_address_table(); + + unsigned int insn_idx = 0; + while (insn_idx < iseq->body->iseq_size) { + int insn = rb_vm_insn_decode(iseq->body->iseq_encoded[insn_idx]); + int zjit_insn = vm_insn_to_zjit_insn(insn); + if (insn != zjit_insn) { + iseq->body->iseq_encoded[insn_idx] = (VALUE)insn_table[zjit_insn]; + } + insn_idx += insn_len(insn); + } +} + +// Get profiling information for ISEQ +void * +rb_iseq_get_zjit_payload(const rb_iseq_t *iseq) +{ + RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq)); + if (iseq->body) { + return iseq->body->zjit_payload; + } + else { + // Body is NULL when constructing the iseq. + return NULL; + } +} + +// Set profiling information for ISEQ +void +rb_iseq_set_zjit_payload(const rb_iseq_t *iseq, void *payload) +{ + RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq)); + RUBY_ASSERT_ALWAYS(iseq->body); + RUBY_ASSERT_ALWAYS(NULL == iseq->body->zjit_payload); + iseq->body->zjit_payload = payload; +} diff --git a/zjit.h b/zjit.h new file mode 100644 index 0000000000..c474087da9 --- /dev/null +++ b/zjit.h @@ -0,0 +1,19 @@ +#ifndef ZJIT_H +#define ZJIT_H 1 +// +// This file contains definitions ZJIT exposes to the CRuby codebase +// + +#if USE_ZJIT +extern bool rb_zjit_enabled_p; +extern uint64_t rb_zjit_call_threshold; +void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception); +void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec); +void rb_zjit_profile_iseq(const rb_iseq_t *iseq); +#else +void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) {} +void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec) {} +void rb_zjit_profile_iseq(const rb_iseq_t *iseq) {} +#endif // #if USE_YJIT + +#endif // #ifndef ZJIT_H diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 1502ee6139..aa07e4d32e 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -597,117 +597,119 @@ pub const YARVINSN_setlocal_WC_0: ruby_vminsn_type = 106; pub const YARVINSN_setlocal_WC_1: ruby_vminsn_type = 107; pub const YARVINSN_putobject_INT2FIX_0_: ruby_vminsn_type = 108; pub const YARVINSN_putobject_INT2FIX_1_: ruby_vminsn_type = 109; -pub const YARVINSN_trace_nop: ruby_vminsn_type = 110; -pub const YARVINSN_trace_getlocal: ruby_vminsn_type = 111; -pub const YARVINSN_trace_setlocal: ruby_vminsn_type = 112; -pub const YARVINSN_trace_getblockparam: ruby_vminsn_type = 113; -pub const YARVINSN_trace_setblockparam: ruby_vminsn_type = 114; -pub const YARVINSN_trace_getblockparamproxy: ruby_vminsn_type = 115; -pub const YARVINSN_trace_getspecial: ruby_vminsn_type = 116; -pub const YARVINSN_trace_setspecial: ruby_vminsn_type = 117; -pub const YARVINSN_trace_getinstancevariable: ruby_vminsn_type = 118; -pub const YARVINSN_trace_setinstancevariable: ruby_vminsn_type = 119; -pub const YARVINSN_trace_getclassvariable: ruby_vminsn_type = 120; -pub const YARVINSN_trace_setclassvariable: ruby_vminsn_type = 121; -pub const YARVINSN_trace_opt_getconstant_path: ruby_vminsn_type = 122; -pub const YARVINSN_trace_getconstant: ruby_vminsn_type = 123; -pub const YARVINSN_trace_setconstant: ruby_vminsn_type = 124; -pub const YARVINSN_trace_getglobal: ruby_vminsn_type = 125; -pub const YARVINSN_trace_setglobal: ruby_vminsn_type = 126; -pub const YARVINSN_trace_putnil: ruby_vminsn_type = 127; -pub const YARVINSN_trace_putself: ruby_vminsn_type = 128; -pub const YARVINSN_trace_putobject: ruby_vminsn_type = 129; -pub const YARVINSN_trace_putspecialobject: ruby_vminsn_type = 130; -pub const YARVINSN_trace_putstring: ruby_vminsn_type = 131; -pub const YARVINSN_trace_putchilledstring: ruby_vminsn_type = 132; -pub const YARVINSN_trace_concatstrings: ruby_vminsn_type = 133; -pub const YARVINSN_trace_anytostring: ruby_vminsn_type = 134; -pub const YARVINSN_trace_toregexp: ruby_vminsn_type = 135; -pub const YARVINSN_trace_intern: ruby_vminsn_type = 136; -pub const YARVINSN_trace_newarray: ruby_vminsn_type = 137; -pub const YARVINSN_trace_pushtoarraykwsplat: ruby_vminsn_type = 138; -pub const YARVINSN_trace_duparray: ruby_vminsn_type = 139; -pub const YARVINSN_trace_duphash: ruby_vminsn_type = 140; -pub const YARVINSN_trace_expandarray: ruby_vminsn_type = 141; -pub const YARVINSN_trace_concatarray: ruby_vminsn_type = 142; -pub const YARVINSN_trace_concattoarray: ruby_vminsn_type = 143; -pub const YARVINSN_trace_pushtoarray: ruby_vminsn_type = 144; -pub const YARVINSN_trace_splatarray: ruby_vminsn_type = 145; -pub const YARVINSN_trace_splatkw: ruby_vminsn_type = 146; -pub const YARVINSN_trace_newhash: ruby_vminsn_type = 147; -pub const YARVINSN_trace_newrange: ruby_vminsn_type = 148; -pub const YARVINSN_trace_pop: ruby_vminsn_type = 149; -pub const YARVINSN_trace_dup: ruby_vminsn_type = 150; -pub const YARVINSN_trace_dupn: ruby_vminsn_type = 151; -pub const YARVINSN_trace_swap: ruby_vminsn_type = 152; -pub const YARVINSN_trace_opt_reverse: ruby_vminsn_type = 153; -pub const YARVINSN_trace_topn: ruby_vminsn_type = 154; -pub const YARVINSN_trace_setn: ruby_vminsn_type = 155; -pub const YARVINSN_trace_adjuststack: ruby_vminsn_type = 156; -pub const YARVINSN_trace_defined: ruby_vminsn_type = 157; -pub const YARVINSN_trace_definedivar: ruby_vminsn_type = 158; -pub const YARVINSN_trace_checkmatch: ruby_vminsn_type = 159; -pub const YARVINSN_trace_checkkeyword: ruby_vminsn_type = 160; -pub const YARVINSN_trace_checktype: ruby_vminsn_type = 161; -pub const YARVINSN_trace_defineclass: ruby_vminsn_type = 162; -pub const YARVINSN_trace_definemethod: ruby_vminsn_type = 163; -pub const YARVINSN_trace_definesmethod: ruby_vminsn_type = 164; -pub const YARVINSN_trace_send: ruby_vminsn_type = 165; -pub const YARVINSN_trace_sendforward: ruby_vminsn_type = 166; -pub const YARVINSN_trace_opt_send_without_block: ruby_vminsn_type = 167; -pub const YARVINSN_trace_objtostring: ruby_vminsn_type = 168; -pub const YARVINSN_trace_opt_ary_freeze: ruby_vminsn_type = 169; -pub const YARVINSN_trace_opt_hash_freeze: ruby_vminsn_type = 170; -pub const YARVINSN_trace_opt_str_freeze: ruby_vminsn_type = 171; -pub const YARVINSN_trace_opt_nil_p: ruby_vminsn_type = 172; -pub const YARVINSN_trace_opt_str_uminus: ruby_vminsn_type = 173; -pub const YARVINSN_trace_opt_duparray_send: ruby_vminsn_type = 174; -pub const YARVINSN_trace_opt_newarray_send: ruby_vminsn_type = 175; -pub const YARVINSN_trace_invokesuper: ruby_vminsn_type = 176; -pub const YARVINSN_trace_invokesuperforward: ruby_vminsn_type = 177; -pub const YARVINSN_trace_invokeblock: ruby_vminsn_type = 178; -pub const YARVINSN_trace_leave: ruby_vminsn_type = 179; -pub const YARVINSN_trace_throw: ruby_vminsn_type = 180; -pub const YARVINSN_trace_jump: ruby_vminsn_type = 181; -pub const YARVINSN_trace_branchif: ruby_vminsn_type = 182; -pub const YARVINSN_trace_branchunless: ruby_vminsn_type = 183; -pub const YARVINSN_trace_branchnil: ruby_vminsn_type = 184; -pub const YARVINSN_trace_once: ruby_vminsn_type = 185; -pub const YARVINSN_trace_opt_case_dispatch: ruby_vminsn_type = 186; -pub const YARVINSN_trace_opt_plus: ruby_vminsn_type = 187; -pub const YARVINSN_trace_opt_minus: ruby_vminsn_type = 188; -pub const YARVINSN_trace_opt_mult: ruby_vminsn_type = 189; -pub const YARVINSN_trace_opt_div: ruby_vminsn_type = 190; -pub const YARVINSN_trace_opt_mod: ruby_vminsn_type = 191; -pub const YARVINSN_trace_opt_eq: ruby_vminsn_type = 192; -pub const YARVINSN_trace_opt_neq: ruby_vminsn_type = 193; -pub const YARVINSN_trace_opt_lt: ruby_vminsn_type = 194; -pub const YARVINSN_trace_opt_le: ruby_vminsn_type = 195; -pub const YARVINSN_trace_opt_gt: ruby_vminsn_type = 196; -pub const YARVINSN_trace_opt_ge: ruby_vminsn_type = 197; -pub const YARVINSN_trace_opt_ltlt: ruby_vminsn_type = 198; -pub const YARVINSN_trace_opt_and: ruby_vminsn_type = 199; -pub const YARVINSN_trace_opt_or: ruby_vminsn_type = 200; -pub const YARVINSN_trace_opt_aref: ruby_vminsn_type = 201; -pub const YARVINSN_trace_opt_aset: ruby_vminsn_type = 202; -pub const YARVINSN_trace_opt_aset_with: ruby_vminsn_type = 203; -pub const YARVINSN_trace_opt_aref_with: ruby_vminsn_type = 204; -pub const YARVINSN_trace_opt_length: ruby_vminsn_type = 205; -pub const YARVINSN_trace_opt_size: ruby_vminsn_type = 206; -pub const YARVINSN_trace_opt_empty_p: ruby_vminsn_type = 207; -pub const YARVINSN_trace_opt_succ: ruby_vminsn_type = 208; -pub const YARVINSN_trace_opt_not: ruby_vminsn_type = 209; -pub const YARVINSN_trace_opt_regexpmatch2: ruby_vminsn_type = 210; -pub const YARVINSN_trace_invokebuiltin: ruby_vminsn_type = 211; -pub const YARVINSN_trace_opt_invokebuiltin_delegate: ruby_vminsn_type = 212; -pub const YARVINSN_trace_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 213; -pub const YARVINSN_trace_getlocal_WC_0: ruby_vminsn_type = 214; -pub const YARVINSN_trace_getlocal_WC_1: ruby_vminsn_type = 215; -pub const YARVINSN_trace_setlocal_WC_0: ruby_vminsn_type = 216; -pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 217; -pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 218; -pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 219; -pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 220; +pub const YARVINSN_zjit_opt_plus: ruby_vminsn_type = 110; +pub const YARVINSN_trace_nop: ruby_vminsn_type = 111; +pub const YARVINSN_trace_getlocal: ruby_vminsn_type = 112; +pub const YARVINSN_trace_setlocal: ruby_vminsn_type = 113; +pub const YARVINSN_trace_getblockparam: ruby_vminsn_type = 114; +pub const YARVINSN_trace_setblockparam: ruby_vminsn_type = 115; +pub const YARVINSN_trace_getblockparamproxy: ruby_vminsn_type = 116; +pub const YARVINSN_trace_getspecial: ruby_vminsn_type = 117; +pub const YARVINSN_trace_setspecial: ruby_vminsn_type = 118; +pub const YARVINSN_trace_getinstancevariable: ruby_vminsn_type = 119; +pub const YARVINSN_trace_setinstancevariable: ruby_vminsn_type = 120; +pub const YARVINSN_trace_getclassvariable: ruby_vminsn_type = 121; +pub const YARVINSN_trace_setclassvariable: ruby_vminsn_type = 122; +pub const YARVINSN_trace_opt_getconstant_path: ruby_vminsn_type = 123; +pub const YARVINSN_trace_getconstant: ruby_vminsn_type = 124; +pub const YARVINSN_trace_setconstant: ruby_vminsn_type = 125; +pub const YARVINSN_trace_getglobal: ruby_vminsn_type = 126; +pub const YARVINSN_trace_setglobal: ruby_vminsn_type = 127; +pub const YARVINSN_trace_putnil: ruby_vminsn_type = 128; +pub const YARVINSN_trace_putself: ruby_vminsn_type = 129; +pub const YARVINSN_trace_putobject: ruby_vminsn_type = 130; +pub const YARVINSN_trace_putspecialobject: ruby_vminsn_type = 131; +pub const YARVINSN_trace_putstring: ruby_vminsn_type = 132; +pub const YARVINSN_trace_putchilledstring: ruby_vminsn_type = 133; +pub const YARVINSN_trace_concatstrings: ruby_vminsn_type = 134; +pub const YARVINSN_trace_anytostring: ruby_vminsn_type = 135; +pub const YARVINSN_trace_toregexp: ruby_vminsn_type = 136; +pub const YARVINSN_trace_intern: ruby_vminsn_type = 137; +pub const YARVINSN_trace_newarray: ruby_vminsn_type = 138; +pub const YARVINSN_trace_pushtoarraykwsplat: ruby_vminsn_type = 139; +pub const YARVINSN_trace_duparray: ruby_vminsn_type = 140; +pub const YARVINSN_trace_duphash: ruby_vminsn_type = 141; +pub const YARVINSN_trace_expandarray: ruby_vminsn_type = 142; +pub const YARVINSN_trace_concatarray: ruby_vminsn_type = 143; +pub const YARVINSN_trace_concattoarray: ruby_vminsn_type = 144; +pub const YARVINSN_trace_pushtoarray: ruby_vminsn_type = 145; +pub const YARVINSN_trace_splatarray: ruby_vminsn_type = 146; +pub const YARVINSN_trace_splatkw: ruby_vminsn_type = 147; +pub const YARVINSN_trace_newhash: ruby_vminsn_type = 148; +pub const YARVINSN_trace_newrange: ruby_vminsn_type = 149; +pub const YARVINSN_trace_pop: ruby_vminsn_type = 150; +pub const YARVINSN_trace_dup: ruby_vminsn_type = 151; +pub const YARVINSN_trace_dupn: ruby_vminsn_type = 152; +pub const YARVINSN_trace_swap: ruby_vminsn_type = 153; +pub const YARVINSN_trace_opt_reverse: ruby_vminsn_type = 154; +pub const YARVINSN_trace_topn: ruby_vminsn_type = 155; +pub const YARVINSN_trace_setn: ruby_vminsn_type = 156; +pub const YARVINSN_trace_adjuststack: ruby_vminsn_type = 157; +pub const YARVINSN_trace_defined: ruby_vminsn_type = 158; +pub const YARVINSN_trace_definedivar: ruby_vminsn_type = 159; +pub const YARVINSN_trace_checkmatch: ruby_vminsn_type = 160; +pub const YARVINSN_trace_checkkeyword: ruby_vminsn_type = 161; +pub const YARVINSN_trace_checktype: ruby_vminsn_type = 162; +pub const YARVINSN_trace_defineclass: ruby_vminsn_type = 163; +pub const YARVINSN_trace_definemethod: ruby_vminsn_type = 164; +pub const YARVINSN_trace_definesmethod: ruby_vminsn_type = 165; +pub const YARVINSN_trace_send: ruby_vminsn_type = 166; +pub const YARVINSN_trace_sendforward: ruby_vminsn_type = 167; +pub const YARVINSN_trace_opt_send_without_block: ruby_vminsn_type = 168; +pub const YARVINSN_trace_objtostring: ruby_vminsn_type = 169; +pub const YARVINSN_trace_opt_ary_freeze: ruby_vminsn_type = 170; +pub const YARVINSN_trace_opt_hash_freeze: ruby_vminsn_type = 171; +pub const YARVINSN_trace_opt_str_freeze: ruby_vminsn_type = 172; +pub const YARVINSN_trace_opt_nil_p: ruby_vminsn_type = 173; +pub const YARVINSN_trace_opt_str_uminus: ruby_vminsn_type = 174; +pub const YARVINSN_trace_opt_duparray_send: ruby_vminsn_type = 175; +pub const YARVINSN_trace_opt_newarray_send: ruby_vminsn_type = 176; +pub const YARVINSN_trace_invokesuper: ruby_vminsn_type = 177; +pub const YARVINSN_trace_invokesuperforward: ruby_vminsn_type = 178; +pub const YARVINSN_trace_invokeblock: ruby_vminsn_type = 179; +pub const YARVINSN_trace_leave: ruby_vminsn_type = 180; +pub const YARVINSN_trace_throw: ruby_vminsn_type = 181; +pub const YARVINSN_trace_jump: ruby_vminsn_type = 182; +pub const YARVINSN_trace_branchif: ruby_vminsn_type = 183; +pub const YARVINSN_trace_branchunless: ruby_vminsn_type = 184; +pub const YARVINSN_trace_branchnil: ruby_vminsn_type = 185; +pub const YARVINSN_trace_once: ruby_vminsn_type = 186; +pub const YARVINSN_trace_opt_case_dispatch: ruby_vminsn_type = 187; +pub const YARVINSN_trace_opt_plus: ruby_vminsn_type = 188; +pub const YARVINSN_trace_opt_minus: ruby_vminsn_type = 189; +pub const YARVINSN_trace_opt_mult: ruby_vminsn_type = 190; +pub const YARVINSN_trace_opt_div: ruby_vminsn_type = 191; +pub const YARVINSN_trace_opt_mod: ruby_vminsn_type = 192; +pub const YARVINSN_trace_opt_eq: ruby_vminsn_type = 193; +pub const YARVINSN_trace_opt_neq: ruby_vminsn_type = 194; +pub const YARVINSN_trace_opt_lt: ruby_vminsn_type = 195; +pub const YARVINSN_trace_opt_le: ruby_vminsn_type = 196; +pub const YARVINSN_trace_opt_gt: ruby_vminsn_type = 197; +pub const YARVINSN_trace_opt_ge: ruby_vminsn_type = 198; +pub const YARVINSN_trace_opt_ltlt: ruby_vminsn_type = 199; +pub const YARVINSN_trace_opt_and: ruby_vminsn_type = 200; +pub const YARVINSN_trace_opt_or: ruby_vminsn_type = 201; +pub const YARVINSN_trace_opt_aref: ruby_vminsn_type = 202; +pub const YARVINSN_trace_opt_aset: ruby_vminsn_type = 203; +pub const YARVINSN_trace_opt_aset_with: ruby_vminsn_type = 204; +pub const YARVINSN_trace_opt_aref_with: ruby_vminsn_type = 205; +pub const YARVINSN_trace_opt_length: ruby_vminsn_type = 206; +pub const YARVINSN_trace_opt_size: ruby_vminsn_type = 207; +pub const YARVINSN_trace_opt_empty_p: ruby_vminsn_type = 208; +pub const YARVINSN_trace_opt_succ: ruby_vminsn_type = 209; +pub const YARVINSN_trace_opt_not: ruby_vminsn_type = 210; +pub const YARVINSN_trace_opt_regexpmatch2: ruby_vminsn_type = 211; +pub const YARVINSN_trace_invokebuiltin: ruby_vminsn_type = 212; +pub const YARVINSN_trace_opt_invokebuiltin_delegate: ruby_vminsn_type = 213; +pub const YARVINSN_trace_opt_invokebuiltin_delegate_leave: ruby_vminsn_type = 214; +pub const YARVINSN_trace_getlocal_WC_0: ruby_vminsn_type = 215; +pub const YARVINSN_trace_getlocal_WC_1: ruby_vminsn_type = 216; +pub const YARVINSN_trace_setlocal_WC_0: ruby_vminsn_type = 217; +pub const YARVINSN_trace_setlocal_WC_1: ruby_vminsn_type = 218; +pub const YARVINSN_trace_putobject_INT2FIX_0_: ruby_vminsn_type = 219; +pub const YARVINSN_trace_putobject_INT2FIX_1_: ruby_vminsn_type = 220; +pub const YARVINSN_trace_zjit_opt_plus: ruby_vminsn_type = 221; +pub const VM_INSTRUCTION_SIZE: ruby_vminsn_type = 222; pub type ruby_vminsn_type = u32; pub type rb_iseq_callback = ::std::option::Option< unsafe extern "C" fn(arg1: *const rb_iseq_t, arg2: *mut ::std::os::raw::c_void), @@ -996,4 +998,6 @@ unsafe extern "C" { line: ::std::os::raw::c_int, ); pub fn rb_RCLASS_ORIGIN(c: VALUE) -> VALUE; + pub fn rb_iseq_get_zjit_payload(iseq: *const rb_iseq_t) -> *mut ::std::os::raw::c_void; + pub fn rb_iseq_set_zjit_payload(iseq: *const rb_iseq_t, payload: *mut ::std::os::raw::c_void); } diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e112d4ccc7..1b9088a8b6 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4,7 +4,7 @@ use crate::{ cruby::*, get_option, - options::DumpHIR + options::DumpHIR, profile::{get_or_create_iseq_payload, InsnProfile} }; use std::collections::{HashMap, HashSet}; @@ -77,6 +77,18 @@ pub struct CallInfo { name: String, } +/// Invalidation reasons +#[derive(Debug, Clone)] +pub enum Invariant { + /// Basic operation is redefined + BOPRedefined { + /// {klass}_REDEFINED_OP_FLAG + klass: RedefinitionFlag, + /// BOP_{bop} + bop: ruby_basic_operators, + }, +} + #[derive(Debug, Clone)] pub enum Insn { PutSelf, @@ -125,6 +137,17 @@ pub enum Insn { // Control flow instructions Return { val: InsnId }, + + /// Fixnum + Fixnum + FixnumAdd { recv: InsnId, obj: InsnId }, + + /// Side-exist if val doesn't have the expected type. + // TODO: Replace is_fixnum with the type lattice + GuardType { val: InsnId, is_fixnum: bool }, + + /// Generate no code (or padding if necessary) and insert a patch point + /// that can be rewritten to a side exit when the Invariant is broken. + PatchPoint(Invariant), } #[derive(Default, Debug)] @@ -515,6 +538,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { let mut visited = HashSet::new(); let iseq_size = unsafe { get_iseq_encoded_size(iseq) }; + let payload = get_or_create_iseq_payload(iseq); while let Some((incoming_state, block, mut insn_idx)) = queue.pop_front() { if visited.contains(&block) { continue; } visited.insert(block); @@ -657,10 +681,19 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> { state.setn(n, top); } - YARVINSN_opt_plus => { + YARVINSN_opt_plus | YARVINSN_zjit_opt_plus => { let right = state.pop()?; let left = state.pop()?; - state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "+".into() }, args: vec![right] })); + if let Some(InsnProfile::OptPlus { recv_is_fixnum: true, obj_is_fixnum: true }) = payload.get_insn_profile(insn_idx as usize) { + state.push(fun.push_insn(block, Insn::PatchPoint(Invariant::BOPRedefined { klass: INTEGER_REDEFINED_OP_FLAG, bop: BOP_PLUS }))); + let left_fixnum = fun.push_insn(block, Insn::GuardType { val: left, is_fixnum: true }); + state.push(left_fixnum); + let right_fixnum = fun.push_insn(block, Insn::GuardType { val: right, is_fixnum: true }); + state.push(right_fixnum); + state.push(fun.push_insn(block, Insn::FixnumAdd { recv: left_fixnum, obj: right_fixnum })); + } else { + state.push(fun.push_insn(block, Insn::Send { self_val: left, call_info: CallInfo { name: "+".into() }, args: vec![right] })); + } } YARVINSN_opt_div => { let right = state.pop()?; diff --git a/zjit/src/lib.rs b/zjit/src/lib.rs index e768bc74ce..cc2674f6fb 100644 --- a/zjit/src/lib.rs +++ b/zjit/src/lib.rs @@ -13,6 +13,7 @@ mod backend; #[cfg(feature = "disasm")] mod disasm; mod options; +mod profile; use codegen::gen_function; use options::{debug, get_option, Options}; diff --git a/zjit/src/options.rs b/zjit/src/options.rs index 2c98cd140a..97622babb6 100644 --- a/zjit/src/options.rs +++ b/zjit/src/options.rs @@ -5,7 +5,7 @@ use std::{ffi::CStr, os::raw::c_char}; // Threshold==1 means compile on first execution #[unsafe(no_mangle)] #[allow(non_upper_case_globals)] -pub static mut rb_zjit_call_threshold: u64 = 1; +pub static mut rb_zjit_call_threshold: u64 = 2; #[derive(Clone, Copy, Debug)] pub struct Options { diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs new file mode 100644 index 0000000000..a66b2e6015 --- /dev/null +++ b/zjit/src/profile.rs @@ -0,0 +1,122 @@ +// We use the YARV bytecode constants which have a CRuby-style name +#![allow(non_upper_case_globals)] + +use core::ffi::c_void; +use std::collections::HashMap; + +use crate::cruby::*; + +/// Ephemeral state for profiling runtime information +struct Profiler { + cfp: CfpPtr, + iseq: IseqPtr, + insn_idx: usize, +} + +impl Profiler { + fn new(ec: EcPtr) -> Self { + let cfp = unsafe { get_ec_cfp(ec) }; + let iseq = unsafe { get_cfp_iseq(cfp) }; + Profiler { + cfp, + iseq, + insn_idx: unsafe { get_cfp_pc(cfp).offset_from(get_iseq_body_iseq_encoded(iseq)) as usize }, + } + } + + // Peek at the nth topmost value on the Ruby stack. + // Returns the topmost value when n == 0. + fn peek_at_stack(&self, n: isize) -> VALUE { + unsafe { + let sp: *mut VALUE = get_cfp_sp(self.cfp); + *(sp.offset(-1 - n)) + } + } +} + +/// API called from zjit_* instruction. opcode is the bare (non-zjit_*) instruction. +#[unsafe(no_mangle)] +pub extern "C" fn rb_zjit_profile_insn(opcode: ruby_vminsn_type, ec: EcPtr) { + with_vm_lock(src_loc!(), || { + let mut profiler = Profiler::new(ec); + profile_insn(&mut profiler, opcode); + }); +} + +/// Profile a YARV instruction +fn profile_insn(profiler: &mut Profiler, opcode: ruby_vminsn_type) { + match opcode { + YARVINSN_opt_plus => profile_opt_plus(profiler), + _ => {} + } +} + +/// Profile opt_plus instruction +fn profile_opt_plus(profiler: &mut Profiler) { + let recv = profiler.peek_at_stack(1); + let obj = profiler.peek_at_stack(0); + + let payload = get_or_create_iseq_payload(profiler.iseq); + payload.insns.insert(profiler.insn_idx, InsnProfile::OptPlus { + // TODO: profile the type and union it with past results + recv_is_fixnum: recv.fixnum_p(), + obj_is_fixnum: obj.fixnum_p(), + }); +} + +/// Profiling information for each YARV instruction +pub enum InsnProfile { + // TODO: Change it to { recv: Type, obj: Type } once the type lattice is merged + OptPlus { recv_is_fixnum: bool, obj_is_fixnum: bool }, +} + +/// This is all the data YJIT stores on an iseq. This will be dynamically allocated by C code +/// C code should pass an &mut IseqPayload to us when calling into ZJIT. +#[derive(Default)] +pub struct IseqPayload { + /// Profiling information for each YARV instruction, indexed by the instruction index + insns: HashMap<usize, InsnProfile>, +} + +impl IseqPayload { + /// Get the instruction profile for a given instruction index + pub fn get_insn_profile(&self, insn_idx: usize) -> Option<&InsnProfile> { + self.insns.get(&insn_idx) + } +} + +/// Get the payload for an iseq. For safety it's up to the caller to ensure the returned `&mut` +/// upholds aliasing rules and that the argument is a valid iseq. +pub fn get_iseq_payload(iseq: IseqPtr) -> Option<&'static mut IseqPayload> { + let payload = unsafe { rb_iseq_get_zjit_payload(iseq) }; + let payload: *mut IseqPayload = payload.cast(); + unsafe { payload.as_mut() } +} + +/// Get the payload object associated with an iseq. Create one if none exists. +pub fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload { + type VoidPtr = *mut c_void; + + let payload_non_null = unsafe { + let payload = rb_iseq_get_zjit_payload(iseq); + if payload.is_null() { + // Allocate a new payload with Box and transfer ownership to the GC. + // We drop the payload with Box::from_raw when the GC frees the iseq and calls us. + // NOTE(alan): Sometimes we read from an iseq without ever writing to it. + // We allocate in those cases anyways. + let new_payload = IseqPayload::default(); + let new_payload = Box::into_raw(Box::new(new_payload)); + rb_iseq_set_zjit_payload(iseq, new_payload as VoidPtr); + + new_payload + } else { + payload as *mut IseqPayload + } + }; + + // SAFETY: we should have the VM lock and all other Ruby threads should be asleep. So we have + // exclusive mutable access. + // Hmm, nothing seems to stop calling this on the same + // iseq twice, though, which violates aliasing rules. + unsafe { payload_non_null.as_mut() }.unwrap() +} |