├── .github ├── dependabot.yml └── workflows │ ├── auto-merge.yml │ ├── gh-pages.yml │ ├── main.yml │ └── spec.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── GLOSSARY.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── console └── doc ├── doc ├── favicon.svg ├── glossary.html ├── highlight.css └── style.css ├── exe └── yarv ├── lib ├── yarv.rb └── yarv │ ├── call_data.rb │ ├── cfg.rb │ ├── dfg.rb │ ├── execution_context.rb │ ├── frame.rb │ ├── insn │ ├── adjuststack.rb │ ├── anytostring.rb │ ├── branchif.rb │ ├── branchnil.rb │ ├── branchunless.rb │ ├── concatarray.rb │ ├── concatstrings.rb │ ├── defined.rb │ ├── definemethod.rb │ ├── dup.rb │ ├── dup_hash.rb │ ├── duparray.rb │ ├── dupn.rb │ ├── expandarray.rb │ ├── getconstant.rb │ ├── getglobal.rb │ ├── getlocal.rb │ ├── getlocal_wc_0.rb │ ├── getlocal_wc_1.rb │ ├── intern.rb │ ├── invokeblock.rb │ ├── jump.rb │ ├── leave.rb │ ├── newarray.rb │ ├── newhash.rb │ ├── newrange.rb │ ├── nop.rb │ ├── objtostring.rb │ ├── opt_and.rb │ ├── opt_aref.rb │ ├── opt_aref_with.rb │ ├── opt_aset.rb │ ├── opt_aset_with.rb │ ├── opt_case_dispatch.rb │ ├── opt_div.rb │ ├── opt_empty_p.rb │ ├── opt_eq.rb │ ├── opt_ge.rb │ ├── opt_getinlinecache.rb │ ├── opt_gt.rb │ ├── opt_le.rb │ ├── opt_length.rb │ ├── opt_lt.rb │ ├── opt_ltlt.rb │ ├── opt_minus.rb │ ├── opt_mod.rb │ ├── opt_mult.rb │ ├── opt_neq.rb │ ├── opt_newarray_max.rb │ ├── opt_newarray_min.rb │ ├── opt_nil_p.rb │ ├── opt_not.rb │ ├── opt_or.rb │ ├── opt_plus.rb │ ├── opt_regexpmatch2.rb │ ├── opt_send_without_block.rb │ ├── opt_setinlinecache.rb │ ├── opt_size.rb │ ├── opt_str_freeze.rb │ ├── opt_str_uminus.rb │ ├── opt_succ.rb │ ├── pop.rb │ ├── putnil.rb │ ├── putobject.rb │ ├── putobject_int2fix_0.rb │ ├── putobject_int2fix_1.rb │ ├── putself.rb │ ├── putstring.rb │ ├── send.rb │ ├── setglobal.rb │ ├── setlocal.rb │ ├── setlocal_wc_0.rb │ ├── setlocal_wc_1.rb │ ├── setn.rb │ ├── splatarray.rb │ ├── swap.rb │ ├── topn.rb │ └── toregexp.rb │ ├── instruction.rb │ ├── instruction_sequence.rb │ ├── main.rb │ ├── soy.rb │ └── visitor.rb └── test ├── cfg_test.rb ├── compile_test.rb ├── dfg_test.rb ├── disasm_test.rb ├── insn ├── adjuststack_test.rb ├── anytostring_test.rb ├── branchif_test.rb ├── branchnil_test.rb ├── branchunless_test.rb ├── concatarray_test.rb ├── concatstrings_test.rb ├── defined_test.rb ├── definemethod_test.rb ├── dup_hash_test.rb ├── dup_test.rb ├── duparray_test.rb ├── getglobal_test.rb ├── getlocal_test.rb ├── getlocal_wc_0_test.rb ├── getlocal_wc_1_test.rb ├── intern_test.rb ├── invokeblock_test.rb ├── jump_test.rb ├── newarray_test.rb ├── newhash_test.rb ├── newrange_test.rb ├── nop_test.rb ├── objtostring_test.rb ├── opt_and_test.rb ├── opt_aref_test.rb ├── opt_aref_with_test.rb ├── opt_aset_test.rb ├── opt_aset_with_test.rb ├── opt_case_dispatch_test.rb ├── opt_div_test.rb ├── opt_empty_p_test.rb ├── opt_eq_test.rb ├── opt_ge_test.rb ├── opt_gt_test.rb ├── opt_le_test.rb ├── opt_length_test.rb ├── opt_lt_test.rb ├── opt_ltlt_test.rb ├── opt_minus_test.rb ├── opt_mod_test.rb ├── opt_mult_test.rb ├── opt_neq_test.rb ├── opt_newarray_max_test.rb ├── opt_newarray_min_test.rb ├── opt_nil_p_test.rb ├── opt_not_test.rb ├── opt_or_test.rb ├── opt_plus_test.rb ├── opt_regexpmatch2_test.rb ├── opt_send_without_block_test.rb ├── opt_size_test.rb ├── opt_str_freeze_test.rb ├── opt_str_uminus_test.rb ├── opt_succ_test.rb ├── putnil_test.rb ├── putobject_int2fix_0_test.rb ├── putobject_int2fix_1_test.rb ├── putobject_test.rb ├── putself_test.rb ├── putstring_test.rb ├── send_test.rb ├── setglobal_test.rb ├── setlocal_test.rb ├── setlocal_wc_0_test.rb ├── setlocal_wc_1_test.rb ├── splatarray_test.rb ├── swap_test.rb ├── topn_test.rb └── toregexp_test.rb ├── soy_test.rb ├── static_spec_test.rb ├── test_helper.rb └── visitor_test.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "bundler" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request 3 | 4 | permissions: 5 | contents: write 6 | pull-requests: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v2.4.0 16 | with: 17 | github-token: "${{ secrets.GITHUB_TOKEN }}" 18 | - name: Enable auto-merge for Dependabot PRs 19 | run: gh pr merge --auto --merge "$PR_URL" 20 | env: 21 | PR_URL: ${{github.event.pull_request.html_url}} 22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Github Pages 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | build-and-deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 🛎️ 12 | uses: actions/checkout@master 13 | 14 | - name: Set up Ruby 💎 15 | uses: ruby/setup-ruby@v1 16 | with: 17 | bundler-cache: true 18 | ruby-version: '3.1' 19 | 20 | - name: Generate documentation 🔧 21 | run: bin/doc 22 | 23 | - name: Deploy 🚀 24 | uses: peaceiris/actions-gh-pages@v4 25 | with: 26 | github_token: ${{ secrets.GITHUB_TOKEN }} 27 | publish_dir: ./doc 28 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | on: 3 | push: 4 | branches: main 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 🛎️ 12 | uses: actions/checkout@master 13 | with: 14 | submodules: recursive 15 | 16 | - name: Set up Ruby 💎 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | bundler-cache: true 20 | ruby-version: '3.2' 21 | 22 | - name: Lint and test 23 | run: | 24 | bundle exec rake stree:check 25 | bundle exec rake test 26 | -------------------------------------------------------------------------------- /.github/workflows/spec.yml: -------------------------------------------------------------------------------- 1 | name: Spec 2 | on: 3 | push: 4 | branches: main 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 🛎️ 12 | uses: actions/checkout@master 13 | with: 14 | submodules: recursive 15 | 16 | - name: Set up Ruby 💎 17 | uses: ruby/setup-ruby@v1 18 | with: 19 | bundler-cache: true 20 | ruby-version: '3.1' 21 | 22 | - name: Spec 23 | run: ./spec/mspec/bin/mspec -t ./exe/yarv || true 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/index.html 2 | /doc/glossary.html 3 | test.rb 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "spec/ruby"] 2 | path = spec/ruby 3 | url = https://github.com/ruby/spec 4 | [submodule "spec/mspec"] 5 | path = spec/mspec 6 | url = https://github.com/ruby/mspec 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.3.0] - 2022-05-10 10 | 11 | ### Added 12 | 13 | - [#85](https://github.com/kddnewton/yarv/pull/85) - Implement `putnil`. 14 | - [#90](https://github.com/kddnewton/yarv/pull/90) - Implement `opt_eq`. 15 | - [#91](https://github.com/kddnewton/yarv/pull/91) - Implement `newarray`. 16 | - [#93](https://github.com/kddnewton/yarv/pull/93) - Implement `opt_mod`. 17 | - [#94](https://github.com/kddnewton/yarv/pull/94) - Implement `duparray`. 18 | - [#96](https://github.com/kddnewton/yarv/pull/96) - Implement `opt_not`. 19 | - [#101](https://github.com/kddnewton/yarv/pull/101) - Implement `concatarray`. 20 | - [#98](https://github.com/kddnewton/yarv/pull/98) - Implement `swap`. 21 | - [#106](https://github.com/kddnewton/yarv/pull/106) - Implement `newhash`. 22 | - [#103](https://github.com/kddnewton/yarv/pull/103) - Implement `duphash`. 23 | - [#117](https://github.com/kddnewton/yarv/pull/117) - Implement `opt_ge`. 24 | - [#113](https://github.com/kddnewton/yarv/pull/113) - Implement `jump`. 25 | - [#117](https://github.com/kddnewton/yarv/pull/117) - Implement `opt_gt`. 26 | - [#117](https://github.com/kddnewton/yarv/pull/117) - Implement `opt_le`. 27 | - [#117](https://github.com/kddnewton/yarv/pull/117) - Implement `opt_lt`. 28 | - [#118](https://github.com/kddnewton/yarv/pull/118) - Implement `opt_str_freeze`. 29 | - [#120](https://github.com/kddnewton/yarv/pull/120) - Implement `opt_aref_with`. 30 | - [#122](https://github.com/kddnewton/yarv/pull/122) - Implement `branchnil`. 31 | - [#124](https://github.com/kddnewton/yarv/pull/124) - Implement `branchif`. 32 | - [#127](https://github.com/kddnewton/yarv/pull/127) - Add `--dump=insns` and `-e` options to `exe/yarv`. 33 | 34 | ### Changed 35 | 36 | - [#123](https://github.com/kddnewton/yarv/pull/123) - Handle leading arguments to method calls and fix local argument indices. 37 | - [#127](https://github.com/kddnewton/yarv/pull/127) - Better disasm formatting, and switch all of the instructions to use `#to_s` instead of `#pretty_print`. 38 | - [#127](https://github.com/kddnewton/yarv/pull/127) - Additionally track call data flags. 39 | - [#130](https://github.com/kddnewton/yarv/pull/130) - Improve formatting on the website. 40 | 41 | ## [0.2.0] - 2022-05-09 42 | 43 | ### Added 44 | 45 | - [#19](https://github.com/kddnewton/yarv/pull/19) - Add arm64 platform to Gemfile.lock for M1 macs. 46 | - [#20](https://github.com/kddnewton/yarv/pull/20) - Configure Ruby Spec to run on GH Actions. Allowing it to fail for now since we're nowhere near passing, but it is running on every PR so we can see progress. 47 | - [#24](https://github.com/kddnewton/yarv/pull/24) - Implement `opt_str_uminus`. 48 | - [#26](https://github.com/kddnewton/yarv/pull/26) - Implement `opt_minus`. 49 | - [#30](https://github.com/kddnewton/yarv/pull/33) - Implement `putstring`. 50 | - [#34](https://github.com/kddnewton/yarv/pull/34) - Implement `getglobal`. 51 | - [#39](https://github.com/kddnewton/yarv/pull/39) - Implement `setglobal` and `dup`. 52 | - [#42](https://github.com/kddnewton/yarv/pull/42) - Implement `opt_and`. 53 | - [#46](https://github.com/kddnewton/yarv/pull/46) - Implement `opt_getinlinecache`, `opt_setinlinecache`, and `getconstant`. 54 | - [#49](https://github.com/kddnewton/yarv/pull/49) - Implement `opt_empty_p`. 55 | - [#51](https://github.com/kddnewton/yarv/pull/51) - Implement `pop`. 56 | - [#54](https://github.com/kddnewton/yarv/pull/54) - Implement `opt_div`. 57 | - [#57](https://github.com/kddnewton/yarv/pull/57) - Implement `opt_or`. 58 | - [#58](https://github.com/kddnewton/yarv/pull/58) - Implement `opt_nil_p`. 59 | - [#59](https://github.com/kddnewton/yarv/pull/59) - Implement `opt_aref`. 60 | - [#60](https://github.com/kddnewton/yarv/pull/60) - Implement `branchunless`. Also change the compilation process to append instructions instead of mapping everything so that we can track instruction index to jump properly. Additionally, `YARV::ExecutionContext` now has knowledge of which instruction sequence is currently being executed. 61 | - [#61](https://github.com/kddnewton/yarv/pull/61) - Implement `definemethod`. This also changes the way we dispatch methods. Now in order to properly hook into our method definitions (including any monkey-patching), you need to call `context.call_method`. That way it will check for any redefined methods as well as hooking into the parent runtime to run the methods. 62 | - [#67](https://github.com/kddnewton/yarv/pull/67) - Implement `opt_length`. 63 | - [#69](https://github.com/kddnewton/yarv/pull/69) - Implement `putobject_INT2FIX_0_`. 64 | - [#72](https://github.com/kddnewton/yarv/pull/72) - Implement `opt_succ`. 65 | - [#76](https://github.com/kddnewton/yarv/pull/76) - Format the project with Syntax Tree. Add a check to CI that will run `rake syntax_tree:check` to verify everything is formatted. In order to run the format command locally, run `rake syntax_tree:format`. 66 | - [#80](https://github.com/kddnewton/yarv/pull/80) - Implement `getlocal_WC_0` and `setlocal_WC_0`. Additionally begin tracking locals on the frame. 67 | - [#83](https://github.com/kddnewton/yarv/pull/83) - Implement `putobject_INT2FIX_1_`. 68 | 69 | ### Changed 70 | 71 | - [#27](https://github.com/kddnewton/yarv/pull/27) - Split up the test files and run the tests through `rake`. 72 | - [#30](https://github.com/kddnewton/yarv/pull/30) - An execution context is now passed around to the different instructions instead of just a stack. This is going to allow saving much more information between instructions and allow jumping between instructions by manipulating the `program_counter` field. 73 | - [#62](https://github.com/kddnewton/yarv/pull/62) - Change all of the instructions from calling `execute` to calling `call`. This should make it easier to prototype missing instructions as you can use a lambda without having to build out a whole class. 74 | - [#63](https://github.com/kddnewton/yarv/pull/63) - Run the spec GitHub action in Ruby 3.1. 75 | - [#70](https://github.com/kddnewton/yarv/pull/70) - Fix the value passed into mspec for `__FILE__`. 76 | 77 | ### Removed 78 | 79 | - [#83](https://github.com/kddnewton/yarv/pull/83) - Remove the top-level `YARV.emulate` method in favor of `YARV.compile(...).eval`. 80 | 81 | ## [0.1.0] - 2022-05-09 82 | 83 | ### Added 84 | 85 | - 🎉 Initial release! 🎉 86 | 87 | [unreleased]: https://github.com/kddnewton/yarv/compare/v0.3.0...HEAD 88 | [0.3.0]: https://github.com/kddnewton/yarv/compare/v0.2.0...v0.3.0 89 | [0.2.0]: https://github.com/kddnewton/yarv/compare/v0.1.0...v0.2.0 90 | [0.1.0]: https://github.com/kddnewton/yarv/compare/002375...v0.1.0 91 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at kddnewton@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /GLOSSARY.md: -------------------------------------------------------------------------------- 1 | ## abstract syntax tree 2 | 3 | An intermediate data structure created by a compiler that is used as a representation of the source code. 4 | 5 | ## argc 6 | 7 | Short for argument count. The number of values being sent as part of a method call to the receiver. This includes any kind of argument (positional, keyword, block, etc.). 8 | 9 | ## binding 10 | 11 | An object that wraps a [control frame](#control-frame) in the YARV virtual machine and retains its context for future use. 12 | 13 | ## branch 14 | 15 | A place in a list of instructions where the next instruction to execute may no longer be the next instruction in sequence. 16 | 17 | ## byte 18 | 19 | The byte is a unit of digital information that most commonly consists of eight bits. 20 | 21 | ## bytecode 22 | 23 | A programming language consisting of simple, low-level instructions which are designed to be easy and fast to execute. 24 | 25 | ## call data 26 | 27 | Metadata about a specific call-site in the source. For example: `1.to_s` represents a single call-site. It has an `argc` of `0`, a `mid` (the ID of the method being called) of `:to_s`, and a `flag` value corresponding to `ARGS_SIMPLE`. 28 | 29 | ## call site 30 | 31 | Any place in source code where a method is called. 32 | 33 | ## call stack 34 | 35 | A stack of bindings used to track the scope of the program when a new method or block is called. Every time a new method is called, this call is pushed onto the stack. When that call returns, it is popped off the stack. 36 | 37 | ## calling convention 38 | 39 | An low-level scheme for how methods receive parameters from their caller and how they return a result. 40 | 41 | ## catch table 42 | 43 | A list of pointers to instructions in the bytecode that represent where to continue execution when the `throw` instruction is executed. This happens as a result of control-flow keywords like `break` and `next`. 44 | 45 | ## cd hash 46 | 47 | A Ruby hash used for handling optimized `case` statements. The keys are the conditions of `when` clauses in the `case` statement, 48 | and the values are the labels to which to jump. This optimization can be applied only when the keys can be directly compared. For example: 49 | 50 | ~~~ruby 51 | case 1 52 | when 1 53 | puts "foo" 54 | else 55 | puts "bar" 56 | end 57 | ~~~ 58 | 59 | ## control frame 60 | 61 | An object that encapsulates the execution context at some particular place in the code. This includes things like local variables, the current value of `self`, etc. 62 | 63 | ## dispatch 64 | 65 | Calling a method. 66 | 67 | ## execution context 68 | 69 | The global information available to the running Ruby process. This includes global variables, defined methods, constants, etc. 70 | 71 | ## frame 72 | 73 | See [control frame](#control-frame). 74 | 75 | ## instruction 76 | 77 | One unit of work for the virtual machine to execute. 78 | 79 | ## instruction argument 80 | 81 | Objects encoded into the bytecode that are used by an instruction that are known at compile-time. 82 | 83 | ## instruction operand 84 | 85 | See [instruction argument](#instruction-argument). 86 | 87 | ## instruction sequence 88 | 89 | A set of instructions to be performed by the virtual machine. Every method and block compiled in Ruby will have its own instruction sequence. When those methods or blocks are executed, a [control frame](#control-frame) will be created to wrap the execution of the instruction sequence with additional context. There are many different kinds of instruction sequences, including: 90 | 91 | * `top` - the main instruction sequence executed when your program first starts running 92 | * `method` - an instruction sequence representing the body of a method 93 | * `block` - an instruction sequence representing the body of a block 94 | * and many more 95 | 96 | ## iseq 97 | 98 | See [instruction sequence](#instruction-sequence). 99 | 100 | ## jump 101 | 102 | When the program counter is changed manually to dictate the next instruction for execution. 103 | 104 | ## local 105 | 106 | A temporary variable that can only be read in the current [control frame](#control-frame) or its children. 107 | 108 | ## local table 109 | 110 | A data structure that holds metadata about local variables and arguments declared within an instruction sequence. 111 | 112 | ## nop 113 | 114 | Short for no-op. It means to perform nothing when this instruction is executed. Typically this is used to create a space for another operation to [jump](#jump) to when executed. 115 | 116 | ## operand 117 | 118 | See [instruction argument](#instruction-argument). 119 | 120 | ## opt 121 | 122 | Short for [optimization](#optimization). 123 | 124 | ## optimization 125 | 126 | A specialized version of a more generic function. In the context of YARV, this entails special instructions that can be made faster than their more generic counterparts. For example, `opt_plus` is used whenever there is a single argument being passed through the `+` operator. 127 | 128 | ## pc 129 | 130 | See [program counter](#program-counter). 131 | 132 | ## pop 133 | 134 | Remove and return a value from the top of a stack. 135 | 136 | ## program counter 137 | 138 | The offset from the start of the instruction sequence to the currently-executing instruction. This can be dynamically changed by various instructions to accommodate constructs like `if`, `unless`, `while`, etc. 139 | 140 | ## push 141 | 142 | Add a value to the top of a stack (for example a frame, an instruction sequence, etc.). 143 | 144 | ## put 145 | 146 | See [push](#push). 147 | 148 | ## receiver 149 | 150 | The object receiving a message/method call. 151 | 152 | ## send 153 | 154 | See [dispatch](#dispatch). 155 | 156 | ## source code 157 | 158 | The human-readable representation of the code to be executed. 159 | 160 | ## sp 161 | 162 | See [stack pointer](#stack-pointer). 163 | 164 | ## specialization 165 | 166 | See [optimization](#optimization). 167 | 168 | ## stack 169 | 170 | A data structure where the last object to be added is the first object to be removed. Objects are added ([pushed](#push)) onto the stack and removed ([popped](#pop)) off of the stack. In the context of YARV, stacks are used to represent [control frames](#control-frame) and the [value stack](#value-stack). 171 | 172 | ## stack pointer 173 | 174 | A pointer to the next empty slot in the stack (i.e., the top). 175 | 176 | ## tracepoint 177 | 178 | A publication/subscription system for virtual machine events. Users can create tracepoints to get notified when certain events occur. 179 | 180 | ## value stack 181 | 182 | A data structure used to track return values, variables, and arguments. 183 | 184 | ## virtual machine 185 | 186 | A software implementation of a computer. In the context of YARV, the virtual machine executes the [bytecode](#bytecode) that Ruby compiles. 187 | 188 | ## vm 189 | 190 | See [virtual machine](#virtual-machine). 191 | 192 | ## yarv 193 | 194 | Stands for Yet Another Ruby Virtual Machine. It came around in Ruby 1.9 and replaced MRI (Matz' Ruby Interpreter). Previously Ruby was a tree-walk interpreter (it walked the syntax tree to execute). YARV replaced that by compiling the syntax tree into a bytecode that it executes, which is must faster. 195 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "kramdown" 6 | gem "rake" 7 | gem "rouge" 8 | gem "syntax_tree" 9 | gem "test-unit" 10 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | kramdown (2.5.1) 5 | rexml (>= 3.3.9) 6 | power_assert (2.0.5) 7 | prettier_print (1.2.1) 8 | rake (13.3.0) 9 | rexml (3.3.9) 10 | rouge (4.5.2) 11 | syntax_tree (6.2.0) 12 | prettier_print (>= 1.2.0) 13 | test-unit (3.6.8) 14 | power_assert 15 | 16 | PLATFORMS 17 | arm64-darwin-21 18 | arm64-darwin-22 19 | x86_64-darwin-21 20 | x86_64-linux 21 | 22 | DEPENDENCIES 23 | kramdown 24 | rake 25 | rouge 26 | syntax_tree 27 | test-unit 28 | 29 | BUNDLED WITH 30 | 2.3.6 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021-present Kevin Newton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YARV 2 | 3 | [![Build Status](https://github.com/ruby-syntax-tree/yarv/workflows/Main/badge.svg)](https://github.com/ruby-syntax-tree/yarv/actions) 4 | 5 | This project's aim is to provide an educational window into how CRuby's virtual machine works under the hood. 6 | 7 | CRuby uses a stack-based virtual machine named YARV. You can see the bytecode that will be executed by running `RubyVM::InstructionSequence.compile(source).disasm` or by running `ruby --dump=insns -e source`. At last count, there were `202` instructions, though that number gets significantly smaller if you factor out tracepoint and specialized instructions. Most instructions look similar to their machine code counterparts. 8 | 9 | There isn't really a canonical source for documentation for these instructions beyond the source code itself in [insns.def](https://github.com/ruby/ruby/blob/master/insns.def) or the original YARV [design document](http://www.atdot.net/yarv/yarvarch.ja.html). Because of this, this project aims to provide comprehensive documentation to make it easier to contribute to CRuby's development. The documentation is currently published at [https://ruby-syntax-tree.github.io/yarv/](https://ruby-syntax-tree.github.io/yarv/). It is generated by running `bin/doc`. 10 | 11 | As a part of the documentation effort, this project also aims to provide an emulator of YARV behavior. This will ensure the documentation doesn't get out of date as it will need to reflect actual Ruby semantics. To run the emulator, after cloning the repository, run `exe/yarv ` from the command line. 12 | 13 | ## Getting started 14 | 15 | To use this project as a CLI, you can execute the `exe/yarv` executable. That functions similarly to the `ruby` executable. You pass it the path to a Ruby file, and it will execute that file. 16 | 17 | To use this project as a library, you start with the `YARV.compile` method. This method is similar to the `RubyVM::InstructionSequence.compile` method, in that it accepts a string that represents Ruby source code and compiles it into instruction sequences. Once the source is compiled, you can call `eval` on the result to get it to evaluate your code. 18 | 19 | ## Related content 20 | 21 | To learn more about this kind of content, there are a number of articles and blog posts you can read. Most of them are in Japanese, so a translation tool may be necessary. Some of them are also quite old since a lot of them were written around the time YARV was created. They are listed below: 22 | 23 | * http://www.atdot.net/yarv/yarvarch.ja.html - (Mar 2004) YARV design document 24 | * https://i.loveruby.net/ja/rhg/book/ - (Jul 2004) Ruby source code complete explanation 25 | * https://magazine.rubyist.net/articles/0006/0006-YarvManiacs.html - (May 2005) YARV maniacs 26 | * http://graysoftinc.com/the-ruby-vm-interview/the-ruby-vm-serial-interview - (Feb 2007) The Ruby VM Serial Interview 27 | * https://lifegoo.pluskid.org/upload/doc/yarv/yarv_iset.html - (Apr 2008) YARV instruction set 28 | * https://patshaughnessy.net/2012/6/29/how-ruby-executes-your-code - (Jun 2012) How Ruby Executes Your Code (excerpt from _Ruby Under a Microscope_) 29 | * http://www.atdot.net/~ko1/diary/201212.html#d1 - (Dec 2012) ko1's 2012 Ruby VM advent calendar 30 | * https://qiita.com/sisshiki1969/items/3d25aa81a376eee2e7c2 - (Dec 2019) Ruby made with Rust 31 | * https://iliabylich.github.io/2020/01/25/evaluating-ruby-in-ruby.html - (Jan 2020) Evaluating Ruby in Ruby 32 | 33 | ## Contributing 34 | 35 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby-syntax-tree/yarv. In this project's current form there are lots of opportunities to contribute. To get started, please check the issues on the project board. 36 | 37 | CRuby contains tests that help with generating specific instructions, these are a good starting point when writing tests for our reimplemented instructions: [https://github.com/ruby/ruby/blob/master/bootstraptest/test_insns.rb](https://github.com/ruby/ruby/blob/master/bootstraptest/test_insns.rb). 38 | 39 | ## License 40 | 41 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 42 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rake/testtask" 4 | require "syntax_tree/rake_tasks" 5 | 6 | Rake::TestTask.new(:test) do |t| 7 | t.libs << "test" 8 | t.test_files = FileList["test/**/*_test.rb"] 9 | end 10 | 11 | source_files = FileList[%w[Gemfile Rakefile lib/**/*.rb test/**/*.rb]] 12 | SyntaxTree::Rake::CheckTask.new(:"stree:check", source_files) 13 | SyntaxTree::Rake::WriteTask.new(:"stree:write", source_files) 14 | 15 | task default: :test 16 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | $:.unshift File.expand_path("../lib", __dir__) 5 | require "yarv" 6 | 7 | require "irb" 8 | IRB.start 9 | -------------------------------------------------------------------------------- /bin/doc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "erb" 6 | require "fileutils" 7 | require "kramdown" 8 | require "stringio" 9 | require "syntax_tree" 10 | 11 | filepaths = Dir[File.expand_path("../lib/yarv/insn/*.rb", __dir__)] \ 12 | .reject { |filepath| filepath.end_with?("insn/insn.rb") } # Skip the abstract instruction. 13 | 14 | markdown = StringIO.new 15 | 16 | # For each YARV instruction file, we're going to yank out the comments. 17 | filepaths.each do |filepath| 18 | # Parse the source using SyntaxTree and grab all of the comments. 19 | ast = SyntaxTree.parse(SyntaxTree.read(filepath)) 20 | ast => { statements: { body: [*, SyntaxTree::ModuleDeclaration => mod] } } 21 | mod => { constant: { constant: { value: "YARV" } }, bodystmt: { statements: { body: [SyntaxTree::VoidStmt, *comments, SyntaxTree::ClassDeclaration] } } } 22 | 23 | formatted_comments = comments.map { |comment| comment.value[2..-1] }.join("\n") 24 | 25 | # Extract the ruby code block from the comments, generate the instruction 26 | # sequence for it, and then inject the commented sequence into the block 27 | ruby_code_match = formatted_comments.match(/~~~ruby\n([\S\s]*)~~~/) 28 | 29 | if ruby_code_match.nil? 30 | raise "Missing ruby example in #{filepath} doc comment" 31 | else 32 | ruby_code = ruby_code_match[1] 33 | end 34 | 35 | disasm = RubyVM::InstructionSequence.new(ruby_code).disasm 36 | disasm.gsub!(/^/, "# ") 37 | 38 | formatted_comments.gsub!(/^~~~\n$/, "\n" + disasm + "~~~\n") 39 | 40 | # Write the comments out in their own section. 41 | markdown.puts(<<~MARKDOWN) 42 | ## #{File.basename(filepath, ".rb")} 43 | 44 | #{formatted_comments} 45 | MARKDOWN 46 | end 47 | 48 | navigation = StringIO.new 49 | filepaths.each do |filepath| 50 | basename = File.basename(filepath, ".rb") 51 | navigation.puts("#{basename}") 52 | end 53 | navigation.puts 54 | 55 | template = ERB.new(DATA.read) 56 | FileUtils.mkdir_p("doc") 57 | 58 | body = Kramdown::Document.new(markdown.string, syntax_highlighter: :rouge).to_html 59 | body = ERB.new(<<~HTML).result_with_hash(body: body, nav: navigation.string) 60 | 68 |
69 | <%= body %> 70 |
71 | HTML 72 | 73 | File.write("doc/index.html", template.result_with_hash(body: body)) 74 | 75 | glossary = File.read(File.expand_path("../GLOSSARY.md", __dir__)) 76 | navigation = StringIO.new 77 | 78 | glossary.each_line(chomp: true) do |line| 79 | if line =~ /^## (.+)$/ 80 | heading = $1 81 | navigation.puts("#{heading}") 82 | end 83 | end 84 | 85 | body = Kramdown::Document.new(glossary, syntax_highlighter: :rouge).to_html 86 | body = ERB.new(<<~HTML).result_with_hash(body: body, nav: navigation.string) 87 | 95 |
96 | <%= body %> 97 |
98 | HTML 99 | 100 | File.write("doc/glossary.html", template.result_with_hash(body: body)) 101 | 102 | __END__ 103 | 104 | 105 | 106 | 107 | 108 | YARV 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 |
123 | <%= body %> 124 |
125 | 126 | 127 | -------------------------------------------------------------------------------- /doc/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /doc/highlight.css: -------------------------------------------------------------------------------- 1 | /* .highlighter-rouge { 2 | } 3 | 4 | div.highlight { 5 | } */ 6 | 7 | div.highlight { 8 | background-color: #f5f5f5; 9 | border: #aaa solid 1px; 10 | border-radius: 5px; 11 | font-size: 0.9em; 12 | overflow-x: auto; 13 | width: auto; 14 | } 15 | 16 | pre.highlight { 17 | box-sizing: border-box; 18 | margin: 0; 19 | padding: 0.5em 1em; 20 | } 21 | 22 | .highlight .hll { background-color: #ffffcc } 23 | .highlight .c { color: #999988; font-style: italic } /* Comment */ 24 | .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 25 | .highlight .k { color: #000000; font-weight: bold } /* Keyword */ 26 | .highlight .o { color: #000000; font-weight: bold } /* Operator */ 27 | .highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ 28 | .highlight .cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ 29 | .highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ 30 | .highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ 31 | .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ 32 | .highlight .ge { color: #000000; font-style: italic } /* Generic.Emph */ 33 | .highlight .gr { color: #aa0000 } /* Generic.Error */ 34 | .highlight .gh { color: #999999 } /* Generic.Heading */ 35 | .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ 36 | .highlight .go { color: #888888 } /* Generic.Output */ 37 | .highlight .gp { color: #555555 } /* Generic.Prompt */ 38 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 39 | .highlight .gu { color: #aaaaaa } /* Generic.Subheading */ 40 | .highlight .gt { color: #aa0000 } /* Generic.Traceback */ 41 | .highlight .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ 42 | .highlight .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ 43 | .highlight .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ 44 | .highlight .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ 45 | .highlight .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ 46 | .highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ 47 | .highlight .m { color: #009999 } /* Literal.Number */ 48 | .highlight .s { color: #d01040 } /* Literal.String */ 49 | .highlight .na { color: #008080 } /* Name.Attribute */ 50 | .highlight .nb { color: #0086B3 } /* Name.Builtin */ 51 | .highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ 52 | .highlight .no { color: #008080 } /* Name.Constant */ 53 | .highlight .nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ 54 | .highlight .ni { color: #800080 } /* Name.Entity */ 55 | .highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ 56 | .highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ 57 | .highlight .nl { color: #990000; font-weight: bold } /* Name.Label */ 58 | .highlight .nn { color: #555555 } /* Name.Namespace */ 59 | .highlight .nt { color: #000080 } /* Name.Tag */ 60 | .highlight .nv { color: #008080 } /* Name.Variable */ 61 | .highlight .ow { color: #000000; font-weight: bold } /* Operator.Word */ 62 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 63 | .highlight .mf { color: #009999 } /* Literal.Number.Float */ 64 | .highlight .mh { color: #009999 } /* Literal.Number.Hex */ 65 | .highlight .mi { color: #009999 } /* Literal.Number.Integer */ 66 | .highlight .mo { color: #009999 } /* Literal.Number.Oct */ 67 | .highlight .sb { color: #d01040 } /* Literal.String.Backtick */ 68 | .highlight .sc { color: #d01040 } /* Literal.String.Char */ 69 | .highlight .sd { color: #d01040 } /* Literal.String.Doc */ 70 | .highlight .s2 { color: #d01040 } /* Literal.String.Double */ 71 | .highlight .se { color: #d01040 } /* Literal.String.Escape */ 72 | .highlight .sh { color: #d01040 } /* Literal.String.Heredoc */ 73 | .highlight .si { color: #d01040 } /* Literal.String.Interpol */ 74 | .highlight .sx { color: #d01040 } /* Literal.String.Other */ 75 | .highlight .sr { color: #009926 } /* Literal.String.Regex */ 76 | .highlight .s1 { color: #d01040 } /* Literal.String.Single */ 77 | .highlight .ss { color: #990073 } /* Literal.String.Symbol */ 78 | .highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ 79 | .highlight .vc { color: #008080 } /* Name.Variable.Class */ 80 | .highlight .vg { color: #008080 } /* Name.Variable.Global */ 81 | .highlight .vi { color: #008080 } /* Name.Variable.Instance */ 82 | .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ 83 | -------------------------------------------------------------------------------- /doc/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | --primary-color: #008080; 3 | font-family: "Source Serif Pro", serif; 4 | font-size: 16px; 5 | line-height: 1.2em; 6 | margin: 0; 7 | } 8 | 9 | main { 10 | display: flex; 11 | } 12 | 13 | a { 14 | color: var(--primary-color); 15 | } 16 | 17 | aside { 18 | background-color: #f6f6f6; 19 | box-shadow: 1px 0 3px 0px rgb(0 0 0 / 33%); 20 | min-height: 100vh; 21 | } 22 | 23 | header { 24 | background-color: #666; 25 | } 26 | 27 | header h1 { 28 | color: white; 29 | margin: 0; 30 | padding: 0.5em 1em; 31 | white-space: nowrap; 32 | } 33 | 34 | nav { 35 | box-sizing: border-box; 36 | display: flex; 37 | flex-direction: column; 38 | max-height: 100vh; 39 | overflow-y: auto; 40 | padding: 1em; 41 | position: sticky; 42 | top: 0; 43 | } 44 | 45 | nav a { 46 | border-radius: 5px; 47 | padding: 0.5em 3em 0.5em 1em; 48 | text-decoration: none; 49 | } 50 | 51 | nav a:hover { 52 | background: #ddd; 53 | } 54 | 55 | article { 56 | box-sizing: border-box; 57 | flex-grow: 1; 58 | padding: 0 2em 2em; 59 | } 60 | 61 | article h2 { 62 | border-bottom: 2px solid currentColor; 63 | color: var(--primary-color); 64 | margin-top: 1.5em; 65 | padding-bottom: 0.2em; 66 | } 67 | 68 | article h2:first-child { 69 | margin-right: 2em; 70 | } 71 | 72 | article h3 { 73 | font-size: 1.5em; 74 | } 75 | -------------------------------------------------------------------------------- /exe/yarv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | 6 | $:.unshift(File.expand_path("../lib", __dir__)) 7 | require "yarv" 8 | 9 | sources = [] 10 | dump_insns = false 11 | dump_cfg = false 12 | dump_dfg = false 13 | dump_soy = false 14 | execute = true 15 | 16 | until ARGV.empty? 17 | arg = ARGV.shift 18 | if arg.start_with?('-') 19 | case arg 20 | when "-e" 21 | source = ARGV.shift 22 | raise unless source 23 | sources.push [source, "
", "-e"] 24 | when "--dump=insns" 25 | dump_insns = true 26 | execute = false 27 | when "--dump=cfg" 28 | dump_cfg = true 29 | execute = false 30 | when "--dump=dfg" 31 | dump_dfg = true 32 | execute = false 33 | when "--dump=soy" 34 | dump_soy = true 35 | execute = false 36 | when "--help" 37 | puts "Usage: #{$0} [options] [source files]" 38 | puts "Options:" 39 | puts " -e source Take a string as source code" 40 | puts " --dump=insns Dump instructions" 41 | puts " --dump=cfg Dump a control-flow-graph" 42 | puts " --dump=dfg Dump a data-flow-graph" 43 | puts " --dump=soy Dump a sea-of-YARV graph, in Mermaid format - see https://mermaid.live/" 44 | puts " --help Show this help" 45 | exit 0 46 | else 47 | raise "Unknown argument #{arg}" 48 | end 49 | else 50 | sources.push [File.read(arg), arg, File.expand_path(arg)] 51 | end 52 | end 53 | 54 | sources.each do |source, file, path| 55 | compiled = YARV.compile(source, file, path) 56 | 57 | if dump_insns 58 | puts compiled.disasm 59 | end 60 | 61 | if dump_cfg || dump_dfg || dump_soy 62 | compiled.all_iseqs.each do |iseq| 63 | cfg = YARV::CFG.new(iseq) 64 | puts cfg.disasm if dump_cfg 65 | 66 | if dump_dfg || dump_soy 67 | dfg = YARV::DFG.new(cfg) 68 | puts dfg.disasm if dump_dfg 69 | end 70 | 71 | if dump_soy 72 | soy = YARV::SOY.new(dfg) 73 | puts soy.mermaid if dump_soy 74 | end 75 | end 76 | end 77 | 78 | if execute 79 | compiled.eval 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/yarv.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "stringio" 4 | require "set" 5 | require "syntax_tree" 6 | 7 | require_relative "yarv/call_data" 8 | require_relative "yarv/execution_context" 9 | require_relative "yarv/frame" 10 | require_relative "yarv/instruction" 11 | require_relative "yarv/instruction_sequence" 12 | require_relative "yarv/cfg" 13 | require_relative "yarv/dfg" 14 | require_relative "yarv/soy" 15 | require_relative "yarv/main" 16 | require_relative "yarv/visitor" 17 | 18 | # Require all of the files nested under the lib/yarv/insn directory. 19 | Dir[File.expand_path("yarv/insn/*.rb", __dir__)].each do |filepath| 20 | require_relative "yarv/insn/#{File.basename(filepath, ".rb")}" 21 | end 22 | 23 | # The YARV module is a Ruby runtime that evaluates YARV instructions. 24 | module YARV 25 | # This is the main entry into the project. It accepts a Ruby string that 26 | # represents source code. You can optionally also pass all of the same 27 | # arguments as you would to RubyVM::InstructionSequence.compile. 28 | # 29 | # It compiles the source into an InstructionSequence object. You can then 30 | # execute it 31 | def self.compile( 32 | source, 33 | file = "", 34 | path = "", 35 | lineno = 1, 36 | coverage_enabled: false, 37 | debug_frozen_string_literal: false, 38 | frozen_string_literal: false, 39 | inline_const_cache: true, 40 | instructions_unification: true, 41 | operands_unification: true, 42 | peephole_optimization: true, 43 | specialized_instruction: true, 44 | stack_caching: true, 45 | tailcall_optimization: false, 46 | trace_instruction: false 47 | ) 48 | iseq = 49 | RubyVM::InstructionSequence.compile( 50 | source, 51 | file, 52 | path, 53 | lineno, 54 | coverage_enabled:, 55 | debug_frozen_string_literal:, 56 | frozen_string_literal:, 57 | inline_const_cache:, 58 | instructions_unification:, 59 | operands_unification:, 60 | peephole_optimization:, 61 | specialized_instruction:, 62 | stack_caching:, 63 | tailcall_optimization:, 64 | trace_instruction: 65 | ) 66 | 67 | InstructionSequence.compile(Main.new, iseq.to_a) 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/yarv/call_data.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # This class represents information about a specific call-site in the code. 5 | class CallData 6 | # stree-ignore 7 | FLAGS = [ 8 | :ARGS_SPLAT, # m(*args) 9 | :ARGS_BLOCKARG, # m(&block) 10 | :FCALL, # m(...) 11 | :VCALL, # m 12 | :ARGS_SIMPLE, # (ci->flag & (SPLAT|BLOCKARG)) && blockiseq == NULL && ci->kw_arg == NULL 13 | :BLOCKISEQ, # has blockiseq 14 | :KWARG, # has kwarg 15 | :KW_SPLAT, # m(**opts) 16 | :TAILCALL, # located at tail position 17 | :SUPER, # super 18 | :ZSUPER, # zsuper 19 | :OPT_SEND, # internal flag 20 | :KW_SPLAT_MUT # kw splat hash can be modified (to avoid allocating a new one) 21 | ] 22 | 23 | attr_reader :mid, :argc, :flag 24 | 25 | def initialize(mid, argc, flag) 26 | @mid = mid 27 | @argc = argc 28 | @flag = flag 29 | end 30 | 31 | def ==(other) 32 | other in CallData[mid: ^(mid), argc: ^(argc), flag: ^(flag)] 33 | end 34 | 35 | def deconstruct_keys(keys) 36 | { mid:, argc:, flag: } 37 | end 38 | 39 | def to_s 40 | "" 41 | end 42 | 43 | private 44 | 45 | def flags 46 | FLAGS 47 | .each_with_index 48 | .each_with_object([]) do |(value, index), result| 49 | result << value if flag & (1 << index) != 0 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/yarv/cfg.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # Constructs a control-flow-graph, or CFG, of a YARV instruction sequence. 5 | # We use conventional basic-blocks. 6 | class CFG 7 | attr_reader :iseq 8 | attr_reader :blocks 9 | attr_reader :block_map 10 | 11 | def initialize(iseq) 12 | if iseq.throw_handlers.any? 13 | raise "throw handlers not supported in CFG yet" 14 | end 15 | 16 | @iseq = iseq 17 | 18 | block_starts = find_block_starts(iseq) 19 | 20 | # Find basic blocks. 21 | @blocks = 22 | block_starts.map do |start| 23 | stop = 24 | (block_starts.select { |n| n > start } + [iseq.insns.length]).min 25 | length = stop - start 26 | last_insn = iseq.insns[start + length - 1] 27 | BasicBlock.new(self, start, length, last_insn.leaves?) 28 | end 29 | 30 | # Create a map of PCs of basic block starts, to basic block objects. 31 | @block_map = @blocks.map { |block| [block.start, block] }.to_h 32 | 33 | # Connect blocks with the preds and succs. 34 | @blocks.each do |block| 35 | last_insn = iseq.insns[block.start + block.length - 1] 36 | if last_insn.branches? && last_insn.respond_to?(:label) 37 | block.succs << block_map[iseq.labels[last_insn.label]] 38 | end 39 | if (!last_insn.branches? && !last_insn.leaves?) || 40 | last_insn.falls_through? 41 | block.succs << block_map[block.start + block.length] 42 | end 43 | block.succs.each { |succ| succ.preds << block } 44 | end 45 | 46 | # Verify. 47 | verify 48 | end 49 | 50 | def disasm(output = StringIO.new, prefix = "") 51 | output.puts prefix + iseq.disasm_header("cfg") 52 | blocks.each { |block| block.disasm output, prefix } 53 | output.string 54 | end 55 | 56 | private 57 | 58 | # Find all the instructions that start a basic block, because they're 59 | # either the start of an ISEQ, or they're the target of a branch, or 60 | # they are fallen through to from a branch. 61 | def find_block_starts(iseq) 62 | starts = Set.new([0]) 63 | iseq.insns.each_with_index do |insn, insn_pc| 64 | if insn.branches? && insn.respond_to?(:label) 65 | starts.add iseq.labels[insn.label] 66 | end 67 | starts.add insn_pc + 1 if insn.branches? && !insn.is_a?(Leave) 68 | end 69 | starts.to_a.sort 70 | end 71 | 72 | def verify 73 | # Only the last instruction in a block should branch. 74 | blocks.each do |block| 75 | (block.start..block.end - 1).each do |n| 76 | raise if iseq.insns[n].branches? 77 | end 78 | end 79 | end 80 | 81 | class BasicBlock 82 | attr_reader :cfg 83 | attr_reader :start 84 | attr_reader :length 85 | attr_reader :preds 86 | attr_reader :succs 87 | 88 | def initialize(cfg, start, length, leaves) 89 | @cfg = cfg 90 | @start = start 91 | @length = length 92 | @preds = [] 93 | @succs = [] 94 | @leaves = leaves 95 | end 96 | 97 | def disasm(output, prefix) 98 | disasm_block_header output, prefix 99 | disasm_block_body output, prefix 100 | end 101 | 102 | def disasm_block_header(output, prefix) 103 | output.print "#{prefix}block_#{start}:" 104 | unless preds.empty? 105 | output.print " # from: #{preds.map { |b| "block_#{b.start}" }.join(", ")}" 106 | end 107 | output.puts 108 | end 109 | 110 | def disasm_block_body(output, prefix) 111 | cfg.iseq.insns[ 112 | start...start + length 113 | ].each_with_index do |insn, insn_rel_pc| 114 | insn_pc = start + insn_rel_pc 115 | output.print prefix 116 | output.print " " 117 | output.print cfg.iseq.disasm_insn(insn, insn_pc) 118 | output.print yield(insn, insn_rel_pc) if block_given? 119 | output.puts 120 | end 121 | all_succs = succs.map { |b| "block_#{b.start}" } 122 | all_succs.push "leaves" if leaves? 123 | unless all_succs.empty? 124 | output.print prefix 125 | output.print " # to: #{all_succs.join(", ")}" 126 | output.puts 127 | end 128 | end 129 | 130 | def leaves? 131 | @leaves 132 | end 133 | 134 | def end 135 | start + length - 1 136 | end 137 | end 138 | end 139 | end 140 | -------------------------------------------------------------------------------- /lib/yarv/execution_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # This is the object that gets passed around all of the instructions as they 5 | # are being executed. 6 | class ExecutionContext 7 | # The system stack that tracks values through the execution of the program. 8 | attr_reader :stack 9 | 10 | # The global variables accessible to the program. These mirror the runtime 11 | # global variables if they have not been overridden. 12 | attr_reader :globals 13 | 14 | # The set of methods defined at runtime. 15 | attr_reader :methods 16 | 17 | # This is a stack of frames as they are being executed. 18 | attr_accessor :frames 19 | 20 | # The program counter used to determine which instruction to execute next. 21 | # This is an attr_accessor because it can be modified by instructions being 22 | # executed. 23 | attr_accessor :program_counter 24 | 25 | def initialize 26 | @stack = [] 27 | # Steal the LOAD_PATHS from the host but not LOADED_FEATURES 28 | @globals = { :$: => $:, :"$\"" => [] } 29 | @methods = {} 30 | @frames = [] 31 | @program_counter = 0 32 | end 33 | 34 | # Calls a method on the given receiver. This is our method dispatch logic. 35 | # First, it looks to see if there is a method defined on the receiver's 36 | # class that we defined explicitly in our runtime. If there is, then it's 37 | # going to save the necessary information and invoke it. Otherwise, it's 38 | # going to call into the parent runtime and let it handle the method call. 39 | # 40 | # Note that the array of arguments coming in here is necessarily the same 41 | # values that align with the parameters to the method being called. They are 42 | # simply the popped values off the top of the stack. It is the 43 | # responsibility of this method to ensure that they get copied into the 44 | # locals table in the correct order. 45 | def call_method(call_data, receiver, arguments, &block) 46 | if (method = methods[[receiver.class, call_data.mid]]) 47 | # We only support a subset of the valid argument permutations. This 48 | # validates each kind to make sure we don't accidentally try to handle a 49 | # method that we currently don't support. 50 | case method.args 51 | in {} 52 | # No arguments, we're good 53 | in { lead_num: ^(arguments.length), **nil } 54 | # Only leading arguments and we line up with the expected number 55 | end 56 | 57 | eval(method) do 58 | # Inside this block, we have already pushed the frame. So now we need 59 | # to establish the correct local variables. 60 | arguments.each_with_index do |argument, index| 61 | current_frame.locals[index] = argument 62 | end 63 | current_frame.set_block(block) if block_given? 64 | end 65 | 66 | stack.last 67 | elsif receiver.is_a?(Main) && call_data.mid == :require 68 | receiver.send(call_data.mid, self, *arguments) 69 | else 70 | receiver.send(call_data.mid, *arguments, &block) 71 | end 72 | end 73 | 74 | # This returns the current execution frame. 75 | def current_frame 76 | frames.last 77 | end 78 | 79 | # This returns the instruction sequence object that is currently being 80 | # executed. In other words, the instruction sequence that is at the top of 81 | # the frame stack. 82 | def current_iseq 83 | current_frame.iseq 84 | end 85 | 86 | # Defines a method on the given object's class keyed by the given name. The 87 | # iseq is an instance of the InstructionSequence class. 88 | def define_method(object, name, iseq) 89 | methods[[object.class, name]] = iseq 90 | end 91 | 92 | # This returns the parent execution frame. 93 | def parent_frame(depth = 1) 94 | frames[-1 - depth] 95 | end 96 | 97 | # This executes the given instruction sequence within a new execution frame. 98 | def with_frame(iseq) 99 | current_program_counter = program_counter 100 | current_stack_length = stack.length 101 | 102 | frames.push(Frame.new(iseq)) 103 | @program_counter = 0 104 | 105 | begin 106 | yield 107 | ensure 108 | frames.pop 109 | @program_counter = current_program_counter 110 | @stack = @stack[0..current_stack_length] 111 | end 112 | end 113 | 114 | # Pushes a new frame onto the stack, executes the instructions contained 115 | # within this instruction sequence, then pops the frame off the stack. 116 | def eval(iseq) 117 | with_frame(iseq) do 118 | yield if block_given? 119 | 120 | loop do 121 | insn = iseq.insns[program_counter] 122 | self.program_counter += 1 123 | 124 | insn.call(self) 125 | break if insn in Leave 126 | end 127 | end 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /lib/yarv/frame.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # This represents an execution frame. 5 | class Frame 6 | UNDEFINED = Object.new 7 | 8 | attr_reader :iseq, :locals, :block 9 | 10 | def initialize(iseq) 11 | @iseq = iseq 12 | @locals = Array.new(iseq.locals.length) { UNDEFINED } 13 | # TODO: more accurately, this should be in a locals table 14 | # e.g. local table (..., block: -1, ...) 15 | @block = nil 16 | end 17 | 18 | # Fetches the value of a local variable from the frame. If the value has 19 | # not yet been initialized, it will raise an error. 20 | def get_local(index) 21 | local = locals[index] 22 | if local == UNDEFINED 23 | raise NameError, 24 | "undefined local variable or method `#{iseq.locals[index]}' for #{iseq.selfo}" 25 | end 26 | 27 | local 28 | end 29 | 30 | # Sets the value of the local variable on the frame. 31 | def set_local(index, value) 32 | @locals[index] = value 33 | end 34 | 35 | def set_block(block) 36 | @block = block 37 | end 38 | 39 | def execute_block(*arguments) 40 | block.call(*arguments) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/yarv/insn/adjuststack.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `adjuststack` accepts a single integer argument and removes that many 7 | # elements from the top of the stack. 8 | # 9 | # ### TracePoint 10 | # 11 | # `adjuststack` cannot dispatch any TracePoint events. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # x = [true] 17 | # x[0] ||= nil 18 | # x[0] 19 | # ~~~ 20 | # 21 | class AdjustStack < Instruction 22 | attr_reader :size 23 | 24 | def initialize(size) 25 | @size = size 26 | end 27 | 28 | def ==(other) 29 | other in AdjustStack 30 | end 31 | 32 | def call(context) 33 | context.stack.pop(size) 34 | end 35 | 36 | def deconstruct_keys(keys) 37 | { size: } 38 | end 39 | 40 | def disasm(iseq) 41 | "%-38s %d" % ["adjuststack", size] 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/yarv/insn/anytostring.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `anytostring` ensures that the value on top of the stack is a string. 7 | # 8 | # It pops two values off the stack. If the first value is a string it pushes it back on 9 | # the stack. If the first value is not a string, it uses Ruby's built in string coercion 10 | # to coerce the second value to a string and then pushes that back on the stack. 11 | # 12 | # This is used in conjunction with `objtostring` as a fallback for when an object's `to_s` 13 | # method does not return a string 14 | # 15 | # ### TracePoint 16 | # 17 | # `anytostring` cannot dispatch any TracePoint events 18 | # 19 | # ### Usage 20 | # 21 | # ~~~ruby 22 | # "#{5}" 23 | # ~~~ 24 | # 25 | class AnyToString < Instruction 26 | def ==(other) 27 | other in AnyToString 28 | end 29 | 30 | def call(context) 31 | maybe_string, orig_val = context.stack.pop(2) 32 | string = maybe_string.is_a?(String) ? maybe_string : orig_val.to_s 33 | context.stack.push(string) 34 | end 35 | 36 | def disasm(iseq) 37 | "anytostring" 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/yarv/insn/branchif.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `branchif` has one argument: the jump index. It pops one value off the stack: 7 | # the jump condition. 8 | # 9 | # If the value popped off the stack is true, `branchif` jumps to 10 | # the jump index and continues executing there. 11 | # 12 | # ### TracePoint 13 | # 14 | # `branchif` does not dispatch any events. 15 | # 16 | # ### Usage 17 | # 18 | # ~~~ruby 19 | # x = true 20 | # x ||= "foo" 21 | # puts x 22 | # ~~~ 23 | # 24 | class BranchIf < Instruction 25 | attr_reader :label 26 | 27 | def initialize(label) 28 | @label = label 29 | end 30 | 31 | def ==(other) 32 | other in BranchIf # explicitly not comparing labels 33 | end 34 | 35 | def call(context) 36 | condition = context.stack.pop 37 | 38 | if condition 39 | jump_index = context.current_iseq.labels[label] 40 | context.program_counter = jump_index 41 | end 42 | end 43 | 44 | def deconstruct_keys(keys) 45 | { label: } 46 | end 47 | 48 | def branches? 49 | true 50 | end 51 | 52 | def falls_through? 53 | true 54 | end 55 | 56 | def reads 57 | 1 58 | end 59 | 60 | def writes 61 | 0 62 | end 63 | 64 | def disasm(iseq) 65 | target = iseq ? iseq.labels[label] : "??" 66 | "%-38s %s (%s)" % ["branchif", label, target] 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/yarv/insn/branchnil.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `branchnil` has one argument: the jump index. It pops one value off the stack: 7 | # the jump condition. 8 | # 9 | # If the value popped off the stack is nil, `branchnil` jumps to 10 | # the jump index and continues executing there. 11 | # 12 | # ### TracePoint 13 | # 14 | # There is no trace point for `branchnil`. 15 | # 16 | # ### Usage 17 | # 18 | # ~~~ruby 19 | # x = nil 20 | # if x&.to_s 21 | # puts "hi" 22 | # end 23 | # ~~~ 24 | # 25 | class BranchNil < Instruction 26 | attr_reader :label 27 | 28 | def initialize(label) 29 | @label = label 30 | end 31 | 32 | def ==(other) 33 | other in BranchNil # explicitly not comparing labels 34 | end 35 | 36 | def call(context) 37 | condition = context.stack.pop 38 | 39 | if condition.nil? 40 | jump_index = context.current_iseq.labels[label] 41 | context.program_counter = jump_index 42 | end 43 | end 44 | 45 | def deconstruct_keys(keys) 46 | { label: } 47 | end 48 | 49 | def branches? 50 | true 51 | end 52 | 53 | def falls_through? 54 | true 55 | end 56 | 57 | def disasm(iseq) 58 | target = iseq ? iseq.labels[label] : "??" 59 | "%-38s %s (%s)" % ["branchnil", label, target] 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/yarv/insn/branchunless.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `branchunless` has one argument, the jump index 7 | # and pops one value off the stack, the jump condition. 8 | # 9 | # If the value popped off the stack is false or nil, 10 | # `branchunless` jumps to the jump index and continues executing there. 11 | # 12 | # ### TracePoint 13 | # 14 | # `branchunless` does not dispatch any events. 15 | # 16 | # ### Usage 17 | # 18 | # ~~~ruby 19 | # if 2 + 3 20 | # puts "foo" 21 | # end 22 | # ~~~ 23 | # 24 | class BranchUnless < Instruction 25 | attr_reader :label 26 | 27 | def initialize(label) 28 | @label = label 29 | end 30 | 31 | def ==(other) 32 | other in BranchUnless # explicitly not comparing labels 33 | end 34 | 35 | def call(context) 36 | condition = context.stack.pop 37 | 38 | unless condition 39 | jump_index = context.current_iseq.labels[label] 40 | context.program_counter = jump_index 41 | end 42 | end 43 | 44 | def deconstruct_keys(keys) 45 | { label: } 46 | end 47 | 48 | def branches? 49 | true 50 | end 51 | 52 | def falls_through? 53 | true 54 | end 55 | 56 | def reads 57 | 1 58 | end 59 | 60 | def writes 61 | 0 62 | end 63 | 64 | def disasm(iseq) 65 | target = iseq ? iseq.labels[label] : "??" 66 | "%-38s %s (%s)" % ["branchunless", label, target] 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/yarv/insn/concatarray.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `concatarray` concatenates the two Arrays on top of the stack. 7 | # 8 | # It coerces the two objects at the top of the stack into Arrays by calling 9 | # `to_a` if necessary, and makes sure to `dup` the first Array if it was 10 | # already an Array, to avoid mutating it when concatenating. 11 | # 12 | # ### TracePoint 13 | # 14 | # `concatarray` can dispatch the `line` and `call` events. 15 | # 16 | # ### Usage 17 | # 18 | # ~~~ruby 19 | # [1, *2] 20 | # ~~~ 21 | # 22 | class ConcatArray < Instruction 23 | def ==(other) 24 | other in ConcatArray 25 | end 26 | 27 | def call(context) 28 | left, right = context.stack.pop(2) 29 | coerced_left = coerce(left) 30 | coerced_left = left.dup if coerced_left.equal?(left) 31 | coerced_left.concat(coerce(right)) 32 | context.stack.push(coerced_left) 33 | end 34 | 35 | def disasm(iseq) 36 | "concatarray" 37 | end 38 | 39 | private 40 | 41 | def coerce(object) 42 | object.respond_to?(:to_a) ? object.to_a : [object] 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/yarv/insn/concatstrings.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `concatstrings` just pops a number of strings from the stack joins them together 7 | # into a single string and pushes that string back on the stack. 8 | # 9 | # This does no coercion and so is always used in conjunction with `objtostring` 10 | # and `anytostring` to ensure the stack contents are always strings 11 | # 12 | # ### TracePoint 13 | # 14 | # `concatstrings` can dispatch the `line` and `call` events. 15 | # 16 | # ### Usage 17 | # 18 | # ~~~ruby 19 | # "#{5}" 20 | # ~~~ 21 | # 22 | class ConcatStrings < Instruction 23 | attr_reader :size 24 | 25 | def initialize(size) 26 | @size = size 27 | end 28 | 29 | def ==(other) 30 | other in ConcatStrings[size: ^(size)] 31 | end 32 | 33 | def call(context) 34 | strings = context.stack.pop(size) 35 | context.stack.push(strings.join) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { size: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s" % ["concatstrings", size] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/defined.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `defined` checks if the top value of the stack is defined. If it is, it 7 | # pushes its value onto the stack. Otherwise it pushes `nil`. 8 | # 9 | # ### TracePoint 10 | # 11 | # `defined` cannot dispatch any TracePoint events. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # defined?(x) 17 | # ~~~ 18 | # 19 | class Defined < Instruction 20 | DEFINED_TYPE = %i[ 21 | DEFINED_NOT_DEFINED 22 | DEFINED_NIL 23 | DEFINED_IVAR 24 | DEFINED_LVAR 25 | DEFINED_GVAR 26 | DEFINED_CVAR 27 | DEFINED_CONST 28 | DEFINED_METHOD 29 | DEFINED_YIELD 30 | DEFINED_ZSUPER 31 | DEFINED_SELF 32 | DEFINED_TRUE 33 | DEFINED_FALSE 34 | DEFINED_ASGN 35 | DEFINED_EXPR 36 | DEFINED_REF 37 | DEFINED_FUNC 38 | DEFINED_CONST_FROM 39 | ] 40 | 41 | attr_reader :type, :object, :value 42 | 43 | def initialize(type, object, value) 44 | raise if type >= DEFINED_TYPE.length 45 | 46 | @type = type 47 | @object = object 48 | @value = value 49 | end 50 | 51 | def ==(other) 52 | other in Defined[type: ^(type), object: ^(object), value: ^(value)] 53 | end 54 | 55 | def call(context) 56 | predicate = context.stack.pop 57 | context.stack.push(vm_defined?(context, predicate) ? value : nil) 58 | end 59 | 60 | def disasm(iseq) 61 | "%-38s %s, %s, %s" % ["defined", type, object.inspect, value.inspect] 62 | end 63 | 64 | private 65 | 66 | def vm_defined?(context, predicate) 67 | case DEFINED_TYPE[type] 68 | in :DEFINED_NOT_DEFINED 69 | raise "Compilation error" 70 | in :DEFINED_NIL 71 | true 72 | in :DEFINED_IVAR 73 | raise NotImplementedError, "defined?(ivar)" 74 | in :DEFINED_LVAR 75 | raise NotImplementedError, "defined?(lvar)" 76 | in :DEFINED_GVAR 77 | context.globals.key?(object) 78 | in :DEFINED_CVAR 79 | raise NotImplementedError, "defined?(cvar)" 80 | in :DEFINED_CONST 81 | raise NotImplementedError, "defined?(const)" 82 | in :DEFINED_METHOD 83 | raise NotImplementedError, "defined?(method)" 84 | in :DEFINED_YIELD 85 | raise NotImplementedError, "defined?(yield)" 86 | in :DEFINED_ZSUPER 87 | raise NotImplementedError, "defined?(zsuper)" 88 | in :DEFINED_SELF 89 | true 90 | in :DEFINED_TRUE 91 | true 92 | in :DEFINED_FALSE 93 | true 94 | in :DEFINED_ASGN 95 | raise NotImplementedError, "defined?(asgn)" 96 | in :DEFINED_EXPR 97 | raise NotImplementedError, "defined?(expr)" 98 | in :DEFINED_REF 99 | raise NotImplementedError, "defined?(ref)" 100 | in :DEFINED_FUNC 101 | raise NotImplementedError, "defined?(func)" 102 | in :DEFINED_CONST_FROM 103 | raise NotImplementedError, "defined?(const_from)" 104 | end 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/yarv/insn/definemethod.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `definemethod` defines a method on the class of the current value of `self`. 7 | # It accepts two arguments. The first is the name of the method being defined. 8 | # The second is the instruction sequence representing the body of the method. 9 | # 10 | # ### TracePoint 11 | # 12 | # `definemethod` does not dispatch any events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # def value = "value" 18 | # ~~~ 19 | # 20 | class DefineMethod < Instruction 21 | attr_reader :name, :iseq 22 | 23 | def initialize(name, iseq) 24 | @name = name 25 | @iseq = iseq 26 | end 27 | 28 | def ==(other) 29 | other in DefineMethod[name: ^(name), iseq: ^(iseq)] 30 | end 31 | 32 | def call(context) 33 | context.define_method(context.current_iseq.selfo, name, iseq) 34 | end 35 | 36 | def deconstruct_keys(keys) 37 | { name:, iseq: } 38 | end 39 | 40 | def reads 41 | 0 42 | end 43 | 44 | def writes 45 | 0 46 | end 47 | 48 | def disasm(containing_iseq) 49 | "%-38s %s, %s" % ["definemethod", name.inspect, iseq.name] 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/yarv/insn/dup.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `dup` copies the top value of the stack and pushes it onto the stack. 7 | # 8 | # ### TracePoint 9 | # 10 | # `dup` does not dispatch any events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # $global = 5 16 | # ~~~ 17 | # 18 | class Dup < Instruction 19 | def ==(other) 20 | other in Dup 21 | end 22 | 23 | def reads 24 | 1 25 | end 26 | 27 | def writes 28 | 2 29 | end 30 | 31 | def side_effects? 32 | false 33 | end 34 | 35 | def call(context) 36 | value = context.stack.pop 37 | context.stack.push(value) 38 | context.stack.push(value) 39 | end 40 | 41 | def disasm(iseq) 42 | "dup" 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/yarv/insn/dup_hash.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `duphash` pushes a hash onto the stack 7 | # 8 | # ### TracePoint 9 | # 10 | # `duphash` can dispatch the line event. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # { a: 1 } 16 | # ~~~ 17 | # 18 | class DupHash < Instruction 19 | attr_reader :value 20 | 21 | def initialize(value) 22 | @value = value 23 | end 24 | 25 | def ==(other) 26 | other in DupHash[value: ^(value)] 27 | end 28 | 29 | def call(context) 30 | context.stack.push(value.dup) 31 | end 32 | 33 | def deconstruct_keys(keys) 34 | { value: } 35 | end 36 | 37 | def disasm(iseq) 38 | "%-38s %s" % ["duphash", value.inspect] 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/yarv/insn/duparray.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `duparray` copies a literal Array and pushes it onto the stack. 7 | # 8 | # ### TracePoint 9 | # 10 | # `duparray` can dispatch the `line` event. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # [true] 16 | # ~~~ 17 | # 18 | class DupArray < Instruction 19 | attr_reader :value 20 | 21 | def initialize(value) 22 | @value = value 23 | end 24 | 25 | def ==(other) 26 | other in DupArray[value: ^(value)] 27 | end 28 | 29 | def call(context) 30 | context.stack.push(value.dup) 31 | end 32 | 33 | def deconstruct_keys(keys) 34 | { value: } 35 | end 36 | 37 | def disasm(iseq) 38 | "%-38s %s" % ["duparray", value.inspect] 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/yarv/insn/dupn.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `dupn` duplicates the top `n` stack elements. 7 | # 8 | # ### TracePoint 9 | # 10 | # `dupn` does not dispatch any TracePoint events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # Object::X ||= true 16 | # ~~~ 17 | # 18 | class DupN < Instruction 19 | attr_reader :offset 20 | 21 | def initialize(offset) 22 | @offset = offset 23 | end 24 | 25 | def ==(other) 26 | other in DupN[offset: ^(offset)] 27 | end 28 | 29 | def call(context) 30 | context.stack.concat(context.stack[-offset..].map(&:dup)) 31 | end 32 | 33 | def deconstruct_keys(keys) 34 | { offset: } 35 | end 36 | 37 | def disasm(iseq) 38 | "%-38s %d" % ["dupn", offset] 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/yarv/insn/expandarray.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `expandarray` looks at the top of the stack, and if the value is an array 7 | # it replaces it on the stack with `num` elements of the array, or `nil` if 8 | # the elements are missing. 9 | # 10 | # ### TracePoint 11 | # 12 | # `expandarray` does not dispatch any events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # x, = [true, false, nil] 18 | # ~~~ 19 | # 20 | class ExpandArray < Instruction 21 | attr_reader :size, :flag 22 | 23 | def initialize(size, flag) 24 | @size = size 25 | @flag = flag 26 | end 27 | 28 | def ==(other) 29 | other in ExpandArray[size: ^(size), flag: ^(flag)] 30 | end 31 | 32 | def call(context) 33 | raise # see vm_expandarray, it's a little subtle 34 | end 35 | 36 | def reads 37 | 1 38 | end 39 | 40 | def writes 41 | size 42 | end 43 | 44 | def disasm(iseq) 45 | "%-38s %d, %d" % ["expandarray", size, flag] 46 | end 47 | 48 | def deconstruct_keys(keys) 49 | { size:, flag: } 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/yarv/insn/getconstant.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `getconstant` performs a constant lookup and pushes the value of the 7 | # constant onto the stack. 8 | # 9 | # ### TracePoint 10 | # 11 | # `getconstant` does not dispatch any events. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # Constant 17 | # ~~~ 18 | # 19 | class GetConstant < Instruction 20 | attr_reader :name 21 | 22 | def initialize(name) 23 | @name = name 24 | end 25 | 26 | def ==(other) 27 | other in GetConstant[name: ^(name)] 28 | end 29 | 30 | def call(context) 31 | klass, allow_nil = context.stack.pop(2) 32 | 33 | if klass.nil? && !allow_nil 34 | raise NameError, "uninitialized constant #{name}" 35 | end 36 | 37 | # At the moment we're just looking up constants in the parent runtime. In 38 | # the future, we'll want to look up constants in the YARV runtime as well. 39 | context.stack.push((klass || Object).const_get(name)) 40 | end 41 | 42 | def deconstruct_keys(keys) 43 | { name: } 44 | end 45 | 46 | def disasm(iseq) 47 | "%-38s %s" % ["getconstant", name.inspect] 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/yarv/insn/getglobal.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `getglobal` pushes the value of a global variables onto the stack. 7 | # 8 | # ### TracePoint 9 | # 10 | # `getglobal` does not dispatch any events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # $$ 16 | # ~~~ 17 | # 18 | class GetGlobal < Instruction 19 | attr_reader :name 20 | 21 | def initialize(name) 22 | @name = name 23 | end 24 | 25 | def ==(other) 26 | other in GetGlobal[name: ^(name)] 27 | end 28 | 29 | def call(context) 30 | # If we're not currently tracking the global variable, then we're going to 31 | # steal the definition of it from the parent process by eval-ing it. 32 | if !context.globals.key?(name) && global_variables.include?(name) 33 | context.globals[name] = eval(name.to_s) 34 | end 35 | 36 | # If a global variable isn't defined for the given name, this is just 37 | # going to push `nil` onto the stack. 38 | context.stack.push(context.globals[name]) 39 | end 40 | 41 | def deconstruct_keys(keys) 42 | { name: } 43 | end 44 | 45 | def disasm(iseq) 46 | "%-38s %s" % ["getglobal", name.inspect] 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/yarv/insn/getlocal.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `getlocal` fetches the value of a local variable from a frame determined by 7 | # the level and index arguments. The level is the number of frames back to 8 | # look and the index is the index in the local table. It pushes the value it 9 | # finds onto the stack. 10 | # 11 | # ### TracePoint 12 | # 13 | # `getlocal` does not dispatch any events. 14 | # 15 | # ### Usage 16 | # 17 | # ~~~ruby 18 | # value = 5 19 | # tap { tap { value } } 20 | # ~~~ 21 | # 22 | class GetLocal < Instruction 23 | attr_reader :name, :index, :level 24 | 25 | def initialize(name, index, level) 26 | @name = name 27 | @index = index 28 | @level = level 29 | end 30 | 31 | def ==(other) 32 | other in GetLocal[name: ^(name), index: ^(index), level: ^(level)] 33 | end 34 | 35 | def call(context) 36 | value = context.parent_frame(level).get_local(index) 37 | context.stack.push(value) 38 | end 39 | 40 | def deconstruct_keys(keys) 41 | { name:, index:, level: } 42 | end 43 | 44 | def disasm(iseq) 45 | "%-38s %s@%d, %d" % ["getlocal", name, index, level] 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/yarv/insn/getlocal_wc_0.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `getlocal_WC_0` is a specialized version of the `getlocal` instruction. It 7 | # fetches the value of a local variable from the current frame determined by 8 | # the index given as its only argument. 9 | # 10 | # ### TracePoint 11 | # 12 | # `getlocal_WC_0` does not dispatch any events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # value = 5 18 | # value 19 | # ~~~ 20 | # 21 | class GetLocalWC0 < Instruction 22 | attr_reader :name, :index 23 | 24 | def initialize(name, index) 25 | @name = name 26 | @index = index 27 | end 28 | 29 | def ==(other) 30 | other in GetLocalWC0[name: ^(name), index: ^(index)] 31 | end 32 | 33 | def call(context) 34 | value = context.current_frame.get_local(index) 35 | context.stack.push(value) 36 | end 37 | 38 | def reads 39 | 0 40 | end 41 | 42 | def writes 43 | 1 44 | end 45 | 46 | def deconstruct_keys(keys) 47 | { name:, index: } 48 | end 49 | 50 | def disasm(iseq) 51 | "%-38s %s@%d" % ["getlocal_WC_0", name, index] 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/yarv/insn/getlocal_wc_1.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `getlocal_WC_1` is a specialized version of the `getlocal` instruction. It 7 | # fetches the value of a local variable from the parent frame determined by 8 | # the index given as its only argument. 9 | # 10 | # ### TracePoint 11 | # 12 | # `getlocal_WC_1` does not dispatch any events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # value = 5 18 | # self.then { value } 19 | # ~~~ 20 | # 21 | class GetLocalWC1 < Instruction 22 | attr_reader :name, :index 23 | 24 | def initialize(name, index) 25 | @name = name 26 | @index = index 27 | end 28 | 29 | def ==(other) 30 | other in GetLocalWC1[name: ^(name), index: ^(index)] 31 | end 32 | 33 | def call(context) 34 | value = context.parent_frame.get_local(index) 35 | context.stack.push(value) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { name:, index: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s@%d" % ["getlocal_WC_1", name, index] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/intern.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `intern` converts top element from stack to a symbol. 7 | # 8 | # ### TracePoint 9 | # 10 | # There is no trace point for `intern`. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # :"#{"foo"}" 16 | # ~~~ 17 | # 18 | class Intern < Instruction 19 | def ==(other) 20 | other in Intern 21 | end 22 | 23 | def call(context) 24 | string = context.stack.pop 25 | context.stack.push(string.to_sym) 26 | end 27 | 28 | def disasm(iseq) 29 | "intern" 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/yarv/insn/invokeblock.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `invokeblock` invokes the block passed to a method during a yield. 7 | # 8 | # ### TracePoint 9 | # 10 | # 11 | # ### Usage 12 | # 13 | # ~~~ruby 14 | # def foo; yield; end 15 | # 16 | # # == disasm: #@-e:1 (1,0)-(1,19)> (catch: FALSE) 17 | # # 0000 definemethod :foo, foo ( 1)[Li] 18 | # # 0003 putobject :foo 19 | # # 0005 leave 20 | # # 21 | # # == disasm: # (catch: FALSE) 22 | # # 0000 invokeblock ( 1)[LiCa] 23 | # # 0002 leave [Re] 24 | # # ~~~ 25 | # 26 | class InvokeBlock < Instruction 27 | attr_reader :call_data 28 | 29 | def initialize(call_data) 30 | @call_data = call_data 31 | end 32 | 33 | def ==(other) 34 | other in InvokeBlock[call_data: ^(call_data)] 35 | end 36 | 37 | def call(context) 38 | *arguments = context.stack.pop(call_data.argc) 39 | result = context.current_frame.execute_block(*arguments) 40 | 41 | context.stack.push(result) 42 | end 43 | 44 | def deconstruct_keys(keys) 45 | { call_data: } 46 | end 47 | 48 | def disasm(iseq) 49 | "%-38s %s" % ["invokeblock", call_data] 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/yarv/insn/jump.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `jump` has one argument, the jump index, which it uses to set the next 7 | # instruction to execute. 8 | # 9 | # ### TracePoint 10 | # 11 | # There is no trace point for `jump`. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # y = 0 17 | # if y == 0 18 | # puts "0" 19 | # else 20 | # puts "2" 21 | # end 22 | # ~~~ 23 | # 24 | class Jump < Instruction 25 | attr_reader :label 26 | 27 | def initialize(label) 28 | @label = label 29 | end 30 | 31 | def ==(other) 32 | other in Jump # explicitly not comparing labels 33 | end 34 | 35 | def call(context) 36 | jump_index = context.current_iseq.labels[label] 37 | context.program_counter = jump_index 38 | end 39 | 40 | def deconstruct_keys(keys) 41 | { label: } 42 | end 43 | 44 | def branches? 45 | true 46 | end 47 | 48 | def reads 49 | 0 50 | end 51 | 52 | def writes 53 | 0 54 | end 55 | 56 | def disasm(iseq) 57 | target = iseq ? iseq.labels[label] : "??" 58 | "%-38s %s (%s)" % ["jump", label, target] 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/yarv/insn/leave.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `leave` exits the current frame. 7 | # 8 | # ### TracePoint 9 | # 10 | # `leave` does not dispatch any events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # ;; 16 | # ~~~ 17 | # 18 | class Leave < Instruction 19 | def ==(other) 20 | other in Leave 21 | end 22 | 23 | def call(context) 24 | # skip for now 25 | end 26 | 27 | def branches? 28 | true 29 | end 30 | 31 | def leaves? 32 | true 33 | end 34 | 35 | def reads 36 | 1 37 | end 38 | 39 | def writes 40 | 0 41 | end 42 | 43 | def side_effects? 44 | # Leave doesn't really have a side effects... but we say it does so that 45 | # control flow has somewhere to end up. 46 | true 47 | end 48 | 49 | def disasm(iseq) 50 | "leave" 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/yarv/insn/newarray.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `newarray` puts a new array initialized with `size` values from the stack. 7 | # 8 | # ### TracePoint 9 | # 10 | # `newarray` dispatches a `line` event. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # ["string"] 16 | # ~~~ 17 | # 18 | class NewArray < Instruction 19 | attr_reader :size 20 | 21 | def initialize(size) 22 | @size = size 23 | end 24 | 25 | def ==(other) 26 | other in NewArray[size: ^(size)] 27 | end 28 | 29 | def call(context) 30 | array = context.stack.pop(size) 31 | context.stack.push(array) 32 | end 33 | 34 | def deconstruct_keys(keys) 35 | { size: } 36 | end 37 | 38 | def disasm(iseq) 39 | "%-38s %s" % ["newarray", size] 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/yarv/insn/newhash.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `newhash` puts a new hash onto the stack, using `num` elements from the 7 | # stack. `num` needs to be even. 8 | # 9 | # ### TracePoint 10 | # 11 | # `newhash` does not dispatch any events. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # def foo(key, value) 17 | # { key => value } 18 | # end 19 | # ~~~ 20 | # 21 | class NewHash < Instruction 22 | attr_reader :size 23 | 24 | def initialize(size) 25 | @size = size 26 | end 27 | 28 | def ==(other) 29 | other in NewHash[size: ^(size)] 30 | end 31 | 32 | def call(context) 33 | key_values = context.stack.pop(size) 34 | context.stack.push(Hash[*key_values]) 35 | end 36 | 37 | def deconstruct_keys(keys) 38 | { size: } 39 | end 40 | 41 | def disasm(iseq) 42 | "%-38s %s" % ["newhash", size] 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/yarv/insn/newrange.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `newrange` creates a Range. It takes one arguments, which is 0 if the end 7 | # is included or 1 if the end value is excluded. 8 | # 9 | # ### TracePoint 10 | # 11 | # `newrange` does not dispatch any events. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # x = 0 17 | # y = 1 18 | # p (x..y), (x...y) 19 | # ~~~ 20 | # 21 | class NewRange < Instruction 22 | attr_reader :exclude_end 23 | 24 | def initialize(exclude_end) 25 | unless exclude_end == 0 || exclude_end == 1 26 | raise ArgumentError, "invalid exclude_end: #{exclude_end.inspect}" 27 | end 28 | 29 | @exclude_end = exclude_end 30 | end 31 | 32 | def ==(other) 33 | other in NewRange[exclude_end: ^(exclude_end)] 34 | end 35 | 36 | def call(context) 37 | range_begin, range_end = context.stack.pop(2) 38 | context.stack.push(Range.new(range_begin, range_end, exclude_end == 1)) 39 | end 40 | 41 | def deconstruct_keys(keys) 42 | { exclude_end: } 43 | end 44 | 45 | def disasm(iseq) 46 | "%-38s %d" % ["newrange", exclude_end] 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/yarv/insn/nop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `nop` is a no-operation instruction. It is used to pad the instruction 7 | # sequence so there is a place for other instructions to jump to. 8 | # 9 | # ### TracePoint 10 | # 11 | # `nop` does not dispatch any events. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # raise rescue true 17 | # ~~~ 18 | # 19 | class Nop < Instruction 20 | def ==(other) 21 | other in Nop 22 | end 23 | 24 | def reads 25 | 0 26 | end 27 | 28 | def writes 29 | 0 30 | end 31 | 32 | def side_effects? 33 | false 34 | end 35 | 36 | def call(context) 37 | end 38 | 39 | def disasm(iseq) 40 | "%-38s" % ["nop"] 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/yarv/insn/objtostring.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `objtostring` pops a value from the stack, calls `to_s` on that value and then pushes 7 | # the result back to the stack. 8 | # 9 | # It has fast paths for String, Symbol, Module, Class, Nil, True, False & Number. 10 | # For everything else it calls `to_s` 11 | # 12 | # ### TracePoint 13 | # 14 | # `objtostring` cannot dispatch any TracePoint events. 15 | # 16 | # ### Usage 17 | # 18 | # ~~~ruby 19 | # "#{5}" 20 | # ~~~ 21 | # 22 | class ObjToString < Instruction 23 | attr_reader :call_data 24 | 25 | def initialize(call_data) 26 | @call_data = call_data 27 | end 28 | 29 | def ==(other) 30 | other in ObjToString[call_data: ^(call_data)] 31 | end 32 | 33 | def call(context) 34 | obj = context.stack.pop 35 | result = context.call_method(call_data, obj, []) 36 | 37 | context.stack.push(result) 38 | end 39 | 40 | def deconstruct_keys(keys) 41 | { call_data: } 42 | end 43 | 44 | def disasm(iseq) 45 | "%-38s %s" % ["objtostring", call_data] 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_and.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_and` is a specialization of the `opt_send_without_block` instruction 7 | # that occurs when the `&` operator is used. In CRuby, there are fast paths 8 | # for if both operands are integers. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_and` can dispatch both the `c_call` and `c_return` events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # 2 & 3 18 | # ~~~ 19 | # 20 | class OptAnd < Instruction 21 | attr_reader :call_data 22 | 23 | def initialize(call_data) 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptAnd[call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 33 | result = context.call_method(call_data, receiver, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { call_data: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s%s" % ["opt_and", call_data, "[CcCr]"] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_aref.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_aref` is a specialization of the `opt_send_without_block` instruction 7 | # that occurs when the `[]` operator is used. In CRuby, there are fast paths 8 | # if the receiver is an integer, array, or hash. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_aref` can dispatch both the `c_call` and `c_return` events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # 7[2] 18 | # ~~~ 19 | # 20 | class OptAref < Instruction 21 | attr_reader :call_data 22 | 23 | def initialize(call_data) 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptAref[call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 33 | result = context.call_method(call_data, receiver, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { call_data: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s%s" % ["opt_aref", call_data, "[CcCr]"] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_aref_with.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_aref_with` is a specialization of the `opt_aref` instruction that 7 | # occurs when the `[]` operator is used with a string argument known at 8 | # compile time. In CRuby, there are fast paths if the receiver is a hash. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_aref_with` does not dispatch any events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # { 'test' => true }['test'] 18 | # ~~~ 19 | # 20 | class OptArefWith < Instruction 21 | attr_reader :key, :call_data 22 | 23 | def initialize(key, call_data) 24 | @key = key 25 | @call_data = call_data 26 | end 27 | 28 | def ==(other) 29 | other in OptArefWith[key: ^(key), call_data: ^(call_data)] 30 | end 31 | 32 | def call(context) 33 | receiver = context.stack.pop 34 | result = context.call_method(call_data, receiver, [key]) 35 | 36 | context.stack.push(result) 37 | end 38 | 39 | def deconstruct_keys(keys) 40 | { key: } 41 | end 42 | 43 | def disasm(iseq) 44 | "%-38s %s, %s" % ["opt_aref_with", key, call_data] 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_aset.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_aset` is an instruction for setting the hash value by the key in `recv[obj] = set` format 7 | # 8 | # ### TracePoint 9 | # 10 | # There is no trace point for `opt_aset`. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # {}[:key] = value 16 | # ~~~ 17 | # 18 | class OptAset < Instruction 19 | attr_reader :call_data 20 | 21 | def initialize(call_data) 22 | @call_data = call_data 23 | end 24 | 25 | def ==(other) 26 | other in OptAset[call_data: ^(call_data)] 27 | end 28 | 29 | def call(context) 30 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 31 | result = context.call_method(call_data, receiver, arguments) 32 | 33 | context.stack.push(result) 34 | end 35 | 36 | def deconstruct_keys(keys) 37 | { call_data: } 38 | end 39 | 40 | def disasm(iseq) 41 | "%-38s %s%s" % ["opt_aset", call_data, "[CcCr]"] 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_aset_with.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_aset_with` is an instruction for setting the hash value by the known 7 | # string key in the `recv[obj] = set` format. 8 | # 9 | # ### TracePoint 10 | # 11 | # There is no trace point for `opt_aset_with`. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # {}["key"] = value 17 | # ~~~ 18 | # 19 | class OptAsetWith < Instruction 20 | attr_reader :key, :call_data 21 | 22 | def initialize(key, call_data) 23 | @key = key 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptAsetWith[key: ^(key), call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc) 33 | result = context.call_method(call_data, receiver, [key, *arguments]) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { key:, call_data: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s, %s" % ["opt_aset_with", key.inspect, call_data] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_case_dispatch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_case_dispatch` is a branch instruction that moves the control flow for 7 | # case statements. 8 | # 9 | # It has two arguments: the cdhash and an else_offset index. It pops one value off 10 | # the stack: a hash key. `opt_case_dispatch` looks up the key in the cdhash 11 | # and jumps to the corresponding index value, if there is one. 12 | # If there is no value in the cdhash, `opt_case_dispatch` jumps to the else_offset index. 13 | # 14 | # The cdhash is a Ruby hash used for handling optimized `case` statements. 15 | # The keys are the conditions of `when` clauses in the `case` statement, 16 | # and the values are the labels to which to jump. This optimization can be 17 | # applied only when the keys can be directly compared. 18 | # 19 | # ### TracePoint 20 | # 21 | # There is no trace point for `opt_case_dispatch`. 22 | # 23 | # ### Usage 24 | # 25 | # ~~~ruby 26 | # case 1 27 | # when 1 28 | # puts "foo" 29 | # else 30 | # puts "bar" 31 | # end 32 | # 33 | # # == disasm: #@:1 (1,0)-(1,49)> (catch: FALSE) 34 | # # 0000 putobject_INT2FIX_0_ ( 1)[Li] 35 | # # 0001 dup 36 | # # 0002 opt_case_dispatch , 12 37 | # # 0005 putobject_INT2FIX_1_ 38 | # # 0006 topn 1 39 | # # 0008 opt_send_without_block 40 | # # 0010 branchif 19 41 | # # 0012 pop 42 | # # 0013 putself 43 | # # 0014 putstring "bar" 44 | # # 0016 opt_send_without_block 45 | # # 0018 leave 46 | # # 0019 pop 47 | # # 0020 putself 48 | # # 0021 putstring "foo" 49 | # # 0023 opt_send_without_block 50 | # # 0025 leave 51 | # ~~~ 52 | # 53 | class OptCaseDispatch < Instruction 54 | attr_reader :cdhash, :else_offset 55 | 56 | def initialize(cdhash, else_offset) 57 | @cdhash = Hash[*cdhash] 58 | @else_offset = else_offset 59 | end 60 | 61 | def ==(other) 62 | other in OptCaseDispatch[cdhash: ^(cdhash), else_offset: ^(else_offset)] 63 | end 64 | 65 | def call(context) 66 | hash_key = context.stack.pop 67 | if (label = cdhash[hash_key]) 68 | jump_index = context.current_iseq.labels[label] 69 | context.program_counter = jump_index 70 | else 71 | jump_index = context.current_iseq.labels[else_offset] 72 | context.program_counter = jump_index 73 | end 74 | end 75 | 76 | def deconstruct_keys(keys) 77 | { cdhash:, else_offset: } 78 | end 79 | 80 | def disasm(iseq) 81 | "%-38s %s %s" % 82 | ["opt_case_dispatch", ",", else_offset["label_".length..]] 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_div.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_div` is a specialization of the `opt_send_without_block` instruction 7 | # that occurs when the `/` operator is used. In CRuby, there are fast paths 8 | # for if both operands are integers, or if both operands are floats. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_div` can dispatch both the `c_call` and `c_return` events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # 2 / 3 18 | # ~~~ 19 | # 20 | class OptDiv < Instruction 21 | attr_reader :call_data 22 | 23 | def initialize(call_data) 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptDiv[call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 33 | result = context.call_method(call_data, receiver, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { call_data: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s%s" % ["opt_div", call_data, "[CcCr]"] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_empty_p.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_empty_p` is an optimization applied when the method `empty?` is called 7 | # on a String, Array or a Hash. This optimization can be applied because Ruby 8 | # knows how to calculate the length of these objects using internal C macros. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_empty_p` can dispatch `c_call` and `c_return` events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # "".empty? 18 | # ~~~ 19 | # 20 | class OptEmptyP < Instruction 21 | attr_reader :call_data 22 | 23 | def initialize(call_data) 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptEmptyP[call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 33 | result = context.call_method(call_data, receiver, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { call_data: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s%s" % ["opt_empty_p", call_data, "[CcCr]"] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_eq.rb: -------------------------------------------------------------------------------- 1 | module YARV 2 | # ### Summary 3 | # 4 | # `opt_eq` is a specialization of the `opt_send_without_block` instruction 5 | # that occurs when the == operator is used. Fast paths exist within CRuby when 6 | # both operands are integers, floats, symbols or strings. 7 | # 8 | # ### TracePoint 9 | # 10 | # `opt_eq` can dispatch both the `c_call` and `c_return` events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # 2 == 2 16 | # ~~~ 17 | # 18 | class OptEq < Instruction 19 | attr_reader :call_data 20 | 21 | def initialize(call_data) 22 | @call_data = call_data 23 | end 24 | 25 | def ==(other) 26 | other in OptEq[call_data: ^(call_data)] 27 | end 28 | 29 | def reads 30 | 2 31 | end 32 | 33 | def writes 34 | 1 35 | end 36 | 37 | def call(context) 38 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 39 | result = context.call_method(call_data, receiver, arguments) 40 | 41 | context.stack.push(result) 42 | end 43 | 44 | def deconstruct_keys(keys) 45 | { call_data: } 46 | end 47 | 48 | def disasm(iseq) 49 | "%-38s %s%s" % ["opt_eq", call_data, "[CcCr]"] 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_ge.rb: -------------------------------------------------------------------------------- 1 | module YARV 2 | # ### Summary 3 | # 4 | # `opt_ge` is a specialization of the `opt_send_without_block` instruction 5 | # that occurs when the >= operator is used. Fast paths exist within CRuby when 6 | # both operands are integers or floats. 7 | # 8 | # ### TracePoint 9 | # 10 | # `opt_ge` can dispatch both the `c_call` and `c_return` events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # 4 >= 3 16 | # ~~~ 17 | # 18 | class OptGe < Instruction 19 | attr_reader :call_data 20 | 21 | def initialize(call_data) 22 | @call_data = call_data 23 | end 24 | 25 | def ==(other) 26 | other in OptGe[call_data: ^(call_data)] 27 | end 28 | 29 | def call(context) 30 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 31 | result = context.call_method(call_data, receiver, arguments) 32 | 33 | context.stack.push(result) 34 | end 35 | 36 | def deconstruct_keys(keys) 37 | { call_data: } 38 | end 39 | 40 | def disasm(iseq) 41 | "%-38s %s%s" % ["opt_ge", call_data, "[CcCr]"] 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_getinlinecache.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_getinlinecache` is a wrapper around a series of `getconstant` 7 | # instructions that allows skipping past them if the inline cache is currently 8 | # set. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_getinlinecache` does not dispatch any events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # Constant 18 | # ~~~ 19 | # 20 | class OptGetInlineCache < Instruction 21 | attr_reader :label, :cache 22 | 23 | def initialize(label, cache) 24 | @label = label 25 | @cache = cache 26 | end 27 | 28 | def ==(other) 29 | other in OptGetInlineCache[label: ^(label), cache: ^(cache)] 30 | end 31 | 32 | def call(context) 33 | # In CRuby, this is going to check if the cache is populated and then 34 | # potentially jump forward to the label. We're not going to track inline 35 | # caches in YARV, so we'll just always push nil onto the stack as if the 36 | # cache weren't yet populated. 37 | context.stack.push(nil) 38 | end 39 | 40 | def deconstruct_keys(keys) 41 | { label:, cache: } 42 | end 43 | 44 | def disasm(iseq) 45 | "%-38s %s, " % 46 | ["opt_getinlinecache", label["label_".length..], cache] 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_gt.rb: -------------------------------------------------------------------------------- 1 | module YARV 2 | # ### Summary 3 | # 4 | # `opt_gt` is a specialization of the `opt_send_without_block` instruction 5 | # that occurs when the > operator is used. Fast paths exist within CRuby when 6 | # both operands are integers or floats. 7 | # 8 | # ### TracePoint 9 | # 10 | # `opt_gt` can dispatch both the `c_call` and `c_return` events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # 4 > 3 16 | # ~~~ 17 | # 18 | class OptGt < Instruction 19 | attr_reader :call_data 20 | 21 | def initialize(call_data) 22 | @call_data = call_data 23 | end 24 | 25 | def ==(other) 26 | other in OptGt[call_data: ^(call_data)] 27 | end 28 | 29 | def call(context) 30 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 31 | result = context.call_method(call_data, receiver, arguments) 32 | 33 | context.stack.push(result) 34 | end 35 | 36 | def deconstruct_keys(keys) 37 | { call_data: } 38 | end 39 | 40 | def reads 41 | 2 42 | end 43 | 44 | def writes 45 | 1 46 | end 47 | 48 | def disasm(iseq) 49 | "%-38s %s%s" % ["opt_gt", call_data, "[CcCr]"] 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_le.rb: -------------------------------------------------------------------------------- 1 | module YARV 2 | # ### Summary 3 | # 4 | # `opt_le` is a specialization of the `opt_send_without_block` instruction 5 | # that occurs when the <= operator is used. Fast paths exist within CRuby when 6 | # both operands are integers or floats. 7 | # 8 | # ### TracePoint 9 | # 10 | # `opt_le` can dispatch both the `c_call` and `c_return` events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # 3 <= 4 16 | # ~~~ 17 | # 18 | class OptLe < Instruction 19 | attr_reader :call_data 20 | 21 | def initialize(call_data) 22 | @call_data = call_data 23 | end 24 | 25 | def ==(other) 26 | other in OptLe[call_data: ^(call_data)] 27 | end 28 | 29 | def call(context) 30 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 31 | result = context.call_method(call_data, receiver, arguments) 32 | 33 | context.stack.push(result) 34 | end 35 | 36 | def deconstruct_keys(keys) 37 | { call_data: } 38 | end 39 | 40 | def disasm(iseq) 41 | "%-38s %s%s" % ["opt_le", call_data, "[CcCr]"] 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_length.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_length` is a specialization of `opt_send_without_block`, when the 7 | # `length` method is called on a Ruby type with a known size. In CRuby there 8 | # are fast paths when the receiver is either a String, Hash or Array. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_length` can dispatch `c_call` and `c_return` events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # "".length 18 | # ~~~ 19 | # 20 | class OptLength < Instruction 21 | attr_reader :call_data 22 | 23 | def initialize(call_data) 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptLength[call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 33 | result = context.call_method(call_data, receiver, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { call_data: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s%s" % ["opt_length", call_data, "[CcCr]"] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_lt.rb: -------------------------------------------------------------------------------- 1 | module YARV 2 | # ### Summary 3 | # 4 | # `opt_lt` is a specialization of the `opt_send_without_block` instruction 5 | # that occurs when the < operator is used. Fast paths exist within CRuby when 6 | # both operands are integers or floats. 7 | # 8 | # ### TracePoint 9 | # 10 | # `opt_lt` can dispatch both the `c_call` and `c_return` events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # 3 < 4 16 | # ~~~ 17 | # 18 | class OptLt < Instruction 19 | attr_reader :call_data 20 | 21 | def initialize(call_data) 22 | @call_data = call_data 23 | end 24 | 25 | def ==(other) 26 | other in OptLt[call_data: ^(call_data)] 27 | end 28 | 29 | def call(context) 30 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 31 | result = context.call_method(call_data, receiver, arguments) 32 | 33 | context.stack.push(result) 34 | end 35 | 36 | def reads 37 | 2 38 | end 39 | 40 | def writes 41 | 1 42 | end 43 | 44 | def deconstruct_keys(keys) 45 | { call_data: } 46 | end 47 | 48 | def disasm(iseq) 49 | "%-38s %s%s" % ["opt_lt", call_data, "[CcCr]"] 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_ltlt.rb: -------------------------------------------------------------------------------- 1 | module YARV 2 | # ### Summary 3 | # 4 | # `opt_ltlt` is a specialization of the `opt_send_without_block` instruction 5 | # that occurs when the `<<` operator is used. Fast paths exists when the 6 | # receiver is either a String or an Array 7 | # 8 | # ### TracePoint 9 | # 10 | # `opt_ltlt` can dispatch both the `c_call` and `c_return` events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # "" << 2 16 | # 17 | # # == disasm: #@-e:1 (1,0)-(1,7)> (catch: FALSE) 18 | # # 0000 putstring "" ( 1)[Li] 19 | # # 0002 putobject 2 20 | # # 0004 opt_ltlt [CcCr] 21 | # # 0006 leave 22 | # ~~~ 23 | # 24 | class OptLtLt < Instruction 25 | attr_reader :call_data 26 | 27 | def initialize(call_data) 28 | @call_data = call_data 29 | end 30 | 31 | def ==(other) 32 | other in OptLtLt[call_data: ^(call_data)] 33 | end 34 | 35 | def call(context) 36 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 37 | result = context.call_method(call_data, receiver, arguments) 38 | 39 | context.stack.push(result) 40 | end 41 | 42 | def deconstruct_keys(keys) 43 | { call_data: } 44 | end 45 | 46 | def disasm(iseq) 47 | "%-38s %s%s" % ["opt_ltlt", call_data, "[CcCr]"] 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_minus.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_minus` is a specialization of the `opt_send_without_block` instruction 7 | # that occurs when the `-` operator is used. In CRuby, there are fast paths 8 | # for if both operands are integers or both operands are floats. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_minus` can dispatch both the `c_call` and `c_return` events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # 3 - 2 18 | # ~~~ 19 | # 20 | class OptMinus < Instruction 21 | attr_reader :call_data 22 | 23 | def initialize(call_data) 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptMinus[call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 33 | result = context.call_method(call_data, receiver, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def reads 39 | 2 40 | end 41 | 42 | def writes 43 | 1 44 | end 45 | 46 | def deconstruct_keys(keys) 47 | { call_data: } 48 | end 49 | 50 | def disasm(iseq) 51 | "%-38s %s%s" % ["opt_minus", call_data, "[CcCr]"] 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_mod.rb: -------------------------------------------------------------------------------- 1 | module YARV 2 | # ### Summary 3 | # 4 | # `opt_mod` is a specialization of the `opt_send_without_block` instruction 5 | # that occurs when the `%` operator is used. In CRuby, there are fast paths 6 | # for if both operands are integers or both operands are floats. 7 | # 8 | # ### TracePoint 9 | # 10 | # `opt_eq` can dispatch both the `c_call` and `c_return` events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # 4 % 2 16 | # ~~~ 17 | # 18 | class OptMod < Instruction 19 | attr_reader :call_data 20 | 21 | def initialize(call_data) 22 | @call_data = call_data 23 | end 24 | 25 | def ==(other) 26 | other in OptMod[call_data: ^(call_data)] 27 | end 28 | 29 | def call(context) 30 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 31 | result = context.call_method(call_data, receiver, arguments) 32 | 33 | context.stack.push(result) 34 | end 35 | 36 | def deconstruct_keys(keys) 37 | { call_data: } 38 | end 39 | 40 | def disasm(iseq) 41 | "%-38s %s%s" % ["opt_mod", call_data, "[CcCr]"] 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_mult.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_mult` is a specialization of the `opt_send_without_block` instruction 7 | # that occurs when the `*` operator is used. In CRuby, there are fast paths 8 | # for if both operands are integers or floats. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_mult` can dispatch both the `c_call` and `c_return` events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # 3 * 2 18 | # ~~~ 19 | # 20 | class OptMult < Instruction 21 | attr_reader :call_data 22 | 23 | def initialize(call_data) 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptMult[call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 33 | result = context.call_method(call_data, receiver, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { call_data: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s%s" % ["opt_mult", call_data, "[CcCr]"] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_neq.rb: -------------------------------------------------------------------------------- 1 | module YARV 2 | # ### Summary 3 | # 4 | # `opt_neq` is an optimisation that tests whether two values at the top of 5 | # the stack are not equal by testing their equality and performing a logical 6 | # NOT on the result. 7 | # 8 | # This allows `opt_neq` to use the fast paths optimized in `opt_eq` when both 9 | # operands are Integers, Floats, Symbols or Strings. 10 | # 11 | # 12 | # ### TracePoint 13 | # 14 | # `opt_neq` can dispatch both the `c_call` and `c_return` events. 15 | # 16 | # ### Usage 17 | # 18 | # ~~~ruby 19 | # 2 != 2 20 | # ~~~ 21 | # 22 | class OptNeq < Instruction 23 | attr_reader :cd_neq, :cd_eq 24 | 25 | def initialize(cd_eq, cd_neq) 26 | @cd_eq = cd_eq 27 | @cd_neq = cd_neq 28 | end 29 | 30 | def ==(other) 31 | other in OptNeq[cd_eq: ^(cd_eq), cd_neq: ^(cd_neq)] 32 | end 33 | 34 | def call(context) 35 | receiver, *arguments = context.stack.pop(cd_neq.argc + 1) 36 | result = context.call_method(cd_neq, receiver, arguments) 37 | 38 | context.stack.push(result) 39 | end 40 | 41 | def deconstruct_keys(keys) 42 | { cd_eq:, cd_neq: } 43 | end 44 | 45 | def disasm(iseq) 46 | "%-38s %s%s%s" % ["opt_neq", cd_eq, cd_neq, "[CcCr]"] 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_newarray_max.rb: -------------------------------------------------------------------------------- 1 | module YARV 2 | # ### Summary 3 | # 4 | # `opt_newarray_max` is an instruction that represents calling `max` on an 5 | # array literal. It is used to optimize quick comparisons of array elements. 6 | # 7 | # ### TracePoint 8 | # 9 | # `opt_newarray_max` does not dispatch any events. 10 | # 11 | # ### Usage 12 | # 13 | # ~~~ruby 14 | # [1, x = 2].max 15 | # ~~~ 16 | # 17 | class OptNewArrayMax < Instruction 18 | attr_reader :size 19 | 20 | def initialize(size) 21 | @size = size 22 | end 23 | 24 | def ==(other) 25 | other in OptNewArrayMax[size: ^(size)] 26 | end 27 | 28 | def call(context) 29 | elements = context.stack.pop(size) 30 | call_data = 31 | CallData.new(:max, 0, 1 << CallData::FLAGS.index(:ARGS_SIMPLE)) 32 | 33 | result = context.call_method(call_data, elements, []) 34 | context.stack.push(result) 35 | end 36 | 37 | def deconstruct_keys(keys) 38 | { size: } 39 | end 40 | 41 | def disasm(iseq) 42 | "%-38s %d" % ["opt_newarray_max", size] 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_newarray_min.rb: -------------------------------------------------------------------------------- 1 | module YARV 2 | # ### Summary 3 | # 4 | # `opt_newarray_min` is an instruction that represents calling `min` on an 5 | # array literal. It is used to optimize quick comparisons of array elements. 6 | # 7 | # ### TracePoint 8 | # 9 | # `opt_newarray_min` does not dispatch any events. 10 | # 11 | # ### Usage 12 | # 13 | # ~~~ruby 14 | # [1, x = 2].min 15 | # ~~~ 16 | # 17 | class OptNewArrayMin < Instruction 18 | attr_reader :size 19 | 20 | def initialize(size) 21 | @size = size 22 | end 23 | 24 | def ==(other) 25 | other in OptNewArrayMin[size: ^(size)] 26 | end 27 | 28 | def call(context) 29 | elements = context.stack.pop(size) 30 | call_data = 31 | CallData.new(:min, 0, 1 << CallData::FLAGS.index(:ARGS_SIMPLE)) 32 | 33 | result = context.call_method(call_data, elements, []) 34 | context.stack.push(result) 35 | end 36 | 37 | def deconstruct_keys(keys) 38 | { size: } 39 | end 40 | 41 | def disasm(iseq) 42 | "%-38s %d" % ["opt_newarray_min", size] 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_nil_p.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_nil_p` is an optimization applied when the method `nil?` is called. It 7 | # returns true immediately when the receiver is `nil` and defers to the `nil?` 8 | # method in other cases 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_nil_p` can dispatch `c_call` and `c_return` events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # "".nil? 18 | # ~~~ 19 | # 20 | class OptNilP < Instruction 21 | attr_reader :call_data 22 | 23 | def initialize(call_data) 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptNilP[call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 33 | result = context.call_method(call_data, receiver, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { call_data: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s%s" % ["opt_nil_p", call_data, "[CcCr]"] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_not.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_not` negates the value on top of the stack. 7 | # 8 | # ### TracePoint 9 | # 10 | # `opt_not` can dispatch both the `c_call` and `c_return` events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # !true 16 | # ~~~ 17 | # 18 | class OptNot < Instruction 19 | attr_reader :call_data 20 | 21 | def initialize(call_data) 22 | @call_data = call_data 23 | end 24 | 25 | def ==(other) 26 | other in OptNot[call_data: ^(call_data)] 27 | end 28 | 29 | def call(context) 30 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 31 | result = context.call_method(call_data, receiver, arguments) 32 | 33 | context.stack.push(result) 34 | end 35 | 36 | def deconstruct_keys(keys) 37 | { call_data: } 38 | end 39 | 40 | def disasm(iseq) 41 | "%-38s %s%s" % ["opt_not", call_data, "[CcCr]"] 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_or.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_or` is a specialization of the `opt_send_without_block` instruction 7 | # that occurs when the `|` operator is used. In CRuby, there are fast paths 8 | # for if both operands are integers. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_or` can dispatch both the `c_call` and `c_return` events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # 2 | 3 18 | # ~~~ 19 | # 20 | class OptOr < Instruction 21 | attr_reader :call_data 22 | 23 | def initialize(call_data) 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptOr[call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 33 | result = context.call_method(call_data, receiver, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { call_data: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s%s" % ["opt_or", call_data, "[CcCr]"] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_plus.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_plus` is a specialization of the `opt_send_without_block` instruction 7 | # that occurs when the `+` operator is used. In CRuby, there are fast paths 8 | # for if both operands are integers, floats, strings, or arrays. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_plus` can dispatch both the `c_call` and `c_return` events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # 2 + 3 18 | # ~~~ 19 | # 20 | class OptPlus < Instruction 21 | attr_reader :call_data 22 | 23 | def initialize(call_data) 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptPlus[call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 33 | result = context.call_method(call_data, receiver, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { call_data: } 40 | end 41 | 42 | def reads 43 | 2 44 | end 45 | 46 | def writes 47 | 1 48 | end 49 | 50 | def disasm(iseq) 51 | "%-38s %s%s" % ["opt_plus", call_data, "[CcCr]"] 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_regexpmatch2.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_regexpmatch2` is a specialization of the `opt_send_without_block` 7 | # instruction that occurs when the `=~` operator is used. 8 | # 9 | # ### TracePoint 10 | # 11 | # `opt_regexpmatch2` can dispatch both the `c_call` and `c_return` events. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # /a/ =~ "a" 17 | # ~~~ 18 | # 19 | class OptRegexpMatch2 < Instruction 20 | attr_reader :call_data 21 | 22 | def initialize(call_data) 23 | @call_data = call_data 24 | end 25 | 26 | def ==(other) 27 | other in OptRegexpMatch2[call_data: ^(call_data)] 28 | end 29 | 30 | def call(context) 31 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 32 | result = context.call_method(call_data, receiver, arguments) 33 | 34 | context.stack.push(result) 35 | end 36 | 37 | def deconstruct_keys(keys) 38 | { call_data: } 39 | end 40 | 41 | def disasm(iseq) 42 | "%-38s %s%s" % ["opt_regexpmatch2", call_data, "[CcCr]"] 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_send_without_block.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_send_without_block` is a specialization of the send instruction that 7 | # occurs when a method is being called without a block. 8 | # 9 | # ### TracePoint 10 | # 11 | # `opt_send_without_block` does not dispatch any events. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # puts "Hello, world!" 17 | # ~~~ 18 | # 19 | class OptSendWithoutBlock < Instruction 20 | attr_reader :call_data 21 | 22 | def initialize(call_data) 23 | @call_data = call_data 24 | end 25 | 26 | def ==(other) 27 | other in OptSendWithoutBlock[call_data: ^(call_data)] 28 | end 29 | 30 | def call(context) 31 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 32 | result = context.call_method(call_data, receiver, arguments) 33 | 34 | context.stack.push(result) 35 | end 36 | 37 | def deconstruct_keys(keys) 38 | { call_data: } 39 | end 40 | 41 | def reads 42 | call_data.argc + 1 43 | end 44 | 45 | def writes 46 | 1 47 | end 48 | 49 | def disasm(iseq) 50 | "%-38s %s" % ["opt_send_without_block", call_data] 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_setinlinecache.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_setinlinecache` is the final instruction after a series of 7 | # `getconstant` instructions that populates the inline cache associated with 8 | # an `opt_getinlinecache` instruction. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_setinlinecache` does not dispatch any events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # Constant 18 | # ~~~ 19 | # 20 | class OptSetInlineCache < Instruction 21 | attr_reader :cache 22 | 23 | def initialize(cache) 24 | @cache = cache 25 | end 26 | 27 | def ==(other) 28 | other in OptSetInlineCache[cache: ^(cache)] 29 | end 30 | 31 | def call(context) 32 | # Since we're not actually populating inline caches in YARV, we don't need 33 | # to do anything in this instruction. 34 | end 35 | 36 | def deconstruct_keys(keys) 37 | { cache: } 38 | end 39 | 40 | def disasm(iseq) 41 | "%-38s " % ["opt_setinlinecache", cache] 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_size.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_size` is a specialization of `opt_send_without_block`, when the 7 | # `size` method is called on a Ruby type with a known size. In CRuby there 8 | # are fast paths when the receiver is either a String, Hash or Array. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_size` can dispatch `c_call` and `c_return` events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # "".size 18 | # ~~~ 19 | # 20 | class OptSize < Instruction 21 | attr_reader :call_data 22 | 23 | def initialize(call_data) 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptSize[call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 33 | result = context.call_method(call_data, receiver, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { call_data: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s%s" % ["opt_size", call_data, "[CcCr]"] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_str_freeze.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_str_freeze` pushes a frozen known string value with no interpolation 7 | # onto the stack. 8 | # 9 | # ### TracePoint 10 | # 11 | # `opt_str_freeze` does not dispatch any events. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # "hello".freeze 17 | # ~~~ 18 | # 19 | class OptStrFreeze < Instruction 20 | attr_reader :value, :call_data 21 | 22 | def initialize(value, call_data) 23 | @value = value 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptStrFreeze[value: ^(value), call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | result = context.call_method(call_data, value, []) 33 | context.stack.push(result) 34 | end 35 | 36 | def deconstruct_keys(keys) 37 | { value:, call_data: } 38 | end 39 | 40 | def disasm(iseq) 41 | "%-38s %s, %s" % ["opt_str_freeze", value.inspect, call_data] 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_str_uminus.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_str_uminus` pushes a frozen known string value with no interpolation 7 | # onto the stack. 8 | # 9 | # ### TracePoint 10 | # 11 | # `opt_str_uminus` does not dispatch any events. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # -"string" 17 | # ~~~ 18 | # 19 | class OptStrUMinus < Instruction 20 | attr_reader :value, :call_data 21 | 22 | def initialize(value, call_data) 23 | @value = value 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptStrUMinus[value: ^(value), call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | arguments = context.stack.pop(call_data.argc) 33 | result = context.call_method(call_data, value, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { value:, call_data: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s, %s" % ["opt_str_uminus", value.inspect, call_data] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/opt_succ.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `opt_succ` is a specialization of the `opt_send_without_block` instruction 7 | # when the method being called is `succ`. Fast paths exist within CRuby when 8 | # the receiver is either a String or a Fixnum. 9 | # 10 | # ### TracePoint 11 | # 12 | # `opt_succ` can dispatch `c_call` and `c_return` events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # "".succ 18 | # ~~~ 19 | # 20 | class OptSucc < Instruction 21 | attr_reader :call_data 22 | 23 | def initialize(call_data) 24 | @call_data = call_data 25 | end 26 | 27 | def ==(other) 28 | other in OptSucc[call_data: ^(call_data)] 29 | end 30 | 31 | def call(context) 32 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 33 | result = context.call_method(call_data, receiver, arguments) 34 | 35 | context.stack.push(result) 36 | end 37 | 38 | def deconstruct_keys(keys) 39 | { call_data: } 40 | end 41 | 42 | def disasm(iseq) 43 | "%-38s %s%s" % ["opt_succ", call_data, "[CcCr]"] 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/yarv/insn/pop.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `pop` pops the top value off the stack. 7 | # 8 | # ### TracePoint 9 | # 10 | # `pop` does not dispatch any events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # a ||= 2 16 | # ~~~ 17 | # 18 | class Pop < Instruction 19 | def ==(other) 20 | other in Pop 21 | end 22 | 23 | def call(context) 24 | context.stack.pop 25 | end 26 | 27 | def reads 28 | 1 29 | end 30 | 31 | def writes 32 | 0 33 | end 34 | 35 | def side_effects? 36 | false 37 | end 38 | 39 | def disasm(iseq) 40 | "pop" 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/yarv/insn/putnil.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `putnil` pushes a global nil object onto the stack. 7 | # 8 | # ### TracePoint 9 | # 10 | # `putnil` can dispatch the line event. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # nil 16 | # ~~~ 17 | # 18 | class PutNil < Instruction 19 | def ==(other) 20 | other in PutNil 21 | end 22 | 23 | def call(context) 24 | context.stack.push(nil) 25 | end 26 | 27 | def reads 28 | 0 29 | end 30 | 31 | def writes 32 | 1 33 | end 34 | 35 | def side_effects? 36 | false 37 | end 38 | 39 | def disasm(iseq) 40 | "putnil" 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/yarv/insn/putobject.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `putobject` pushes a known value onto the stack. 7 | # 8 | # ### TracePoint 9 | # 10 | # `putobject` can dispatch the line event. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # 5 16 | # ~~~ 17 | # 18 | class PutObject < Instruction 19 | attr_reader :object 20 | 21 | def initialize(object) 22 | @object = object 23 | end 24 | 25 | def ==(other) 26 | other in PutObject[object: ^(object)] 27 | end 28 | 29 | def call(context) 30 | context.stack.push(object) 31 | end 32 | 33 | def deconstruct_keys(keys) 34 | { object: } 35 | end 36 | 37 | def reads 38 | 0 39 | end 40 | 41 | def writes 42 | 1 43 | end 44 | 45 | def side_effects? 46 | false 47 | end 48 | 49 | def disasm(iseq) 50 | "%-38s %s" % ["putobject", object.inspect] 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/yarv/insn/putobject_int2fix_0.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `putobject_INT2FIX_0_` pushes 0 on the stack. 7 | # It is a specialized instruction resulting from the operand 8 | # unification optimization. It is the equivalent to `putobject 0`. 9 | # 10 | # ### TracePoint 11 | # 12 | # `putobject` can dispatch the line event. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # 0 18 | # ~~~ 19 | # 20 | class PutObjectInt2Fix0 < Instruction 21 | def ==(other) 22 | other in PutObjectInt2Fix0 23 | end 24 | 25 | def call(context) 26 | context.stack.push(0) 27 | end 28 | 29 | def reads 30 | 0 31 | end 32 | 33 | def writes 34 | 1 35 | end 36 | 37 | def side_effects? 38 | false 39 | end 40 | 41 | def disasm(iseq) 42 | "putobject_INT2FIX_0_" 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/yarv/insn/putobject_int2fix_1.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `putobject_INT2FIX_1_` pushes 1 on the stack. 7 | # It is a specialized instruction resulting from the operand 8 | # unification optimization. It is the equivalent to `putobject 1`. 9 | # 10 | # ### TracePoint 11 | # 12 | # `putobject` can dispatch the line event. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # 1 18 | # ~~~ 19 | # 20 | class PutObjectInt2Fix1 < Instruction 21 | def ==(other) 22 | other in PutObjectInt2Fix1 23 | end 24 | 25 | def call(context) 26 | context.stack.push(1) 27 | end 28 | 29 | def reads 30 | 0 31 | end 32 | 33 | def writes 34 | 1 35 | end 36 | 37 | def side_effects? 38 | false 39 | end 40 | 41 | def disasm(iseq) 42 | "putobject_INT2FIX_1_" 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/yarv/insn/putself.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `putself` pushes the current value of self onto the stack. 7 | # 8 | # ### TracePoint 9 | # 10 | # `putself` can dispatch the line event. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # puts "Hello, world!" 16 | # ~~~ 17 | # 18 | class PutSelf < Instruction 19 | attr_reader :object 20 | 21 | def initialize(object) 22 | @object = object 23 | end 24 | 25 | def ==(other) 26 | other in PutSelf[object: ^(object)] 27 | end 28 | 29 | def call(context) 30 | context.stack.push(object) 31 | end 32 | 33 | def reads 34 | 0 35 | end 36 | 37 | def writes 38 | 1 39 | end 40 | 41 | def side_effects? 42 | false 43 | end 44 | 45 | def deconstruct_keys(keys) 46 | { object: } 47 | end 48 | 49 | def disasm(iseq) 50 | "putself" 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/yarv/insn/putstring.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `putstring` pushes a string literal onto the stack. 7 | # 8 | # ### TracePoint 9 | # 10 | # `putstring` can dispatch the line event. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # "foo" 16 | # ~~~ 17 | # 18 | class PutString < Instruction 19 | attr_reader :string 20 | 21 | def initialize(string) 22 | @string = string 23 | end 24 | 25 | def ==(other) 26 | other in PutString[string: ^(string)] 27 | end 28 | 29 | def reads 30 | 0 31 | end 32 | 33 | def writes 34 | 1 35 | end 36 | 37 | def call(context) 38 | context.stack.push(string) 39 | end 40 | 41 | def deconstruct_keys(keys) 42 | { string: } 43 | end 44 | 45 | def disasm(iseq) 46 | "%-38s %s" % ["putstring", string.inspect] 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/yarv/insn/send.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `send` invokes a method with a block. 7 | # 8 | # ### TracePoint 9 | # 10 | # `send` does not dispatch any events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # "hello".tap { |i| p i } 16 | # 17 | # # == disasm: #@-e:1 (1,0)-(1,23)> (catch: FALSE) 18 | # # 0000 putstring "hello" ( 1)[Li] 19 | # # 0002 send , block in
20 | # # 0005 leave 21 | # # 22 | # # == disasm: #@-e:1 (1,12)-(1,23)> (catch: FALSE) 23 | # # local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) 24 | # # [ 1] i@0 25 | # # 0000 putself ( 1)[LiBc] 26 | # # 0001 getlocal_WC_0 i@0 27 | # # 0003 opt_send_without_block 28 | # # 0005 leave [Br] 29 | # ~~~ 30 | # 31 | class Send < Instruction 32 | attr_reader :call_data, :block_iseq 33 | 34 | def initialize(call_data, block_iseq) 35 | @call_data = call_data 36 | @block_iseq = block_iseq 37 | end 38 | 39 | def ==(other) 40 | other in Send[call_data: ^(call_data), block_iseq: ^(block_iseq)] 41 | end 42 | 43 | def reads 44 | call_data.argc + 1 45 | end 46 | 47 | def writes 48 | 1 49 | end 50 | 51 | def call(context) 52 | receiver, *arguments = context.stack.pop(call_data.argc + 1) 53 | result = 54 | if block_iseq.nil? 55 | context.call_method(call_data, receiver, arguments) 56 | else 57 | context.call_method(call_data, receiver, arguments) do |*args| 58 | context.eval(block_iseq) do 59 | args.each_with_index do |arg, index| 60 | context.current_frame.locals[index] = arg 61 | end 62 | end 63 | end 64 | end 65 | 66 | context.stack.push(result) 67 | end 68 | 69 | def deconstruct_keys(keys) 70 | { call_data:, block_iseq: } 71 | end 72 | 73 | def disasm(iseq) 74 | "%-38s %s, %s" % ["send", call_data, block_iseq.name] 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/yarv/insn/setglobal.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `setglobal` sets the value of a global variable. 7 | # 8 | # ### TracePoint 9 | # 10 | # `setglobal` does not dispatch any events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # $global = 5 16 | # ~~~ 17 | # 18 | class SetGlobal < Instruction 19 | attr_reader :name 20 | 21 | def initialize(name) 22 | @name = name 23 | end 24 | 25 | def ==(other) 26 | other in SetGlobal[name: ^(name)] 27 | end 28 | 29 | def call(context) 30 | # If we're not currently tracking the global variable, then we're going to 31 | # steal the definition of it from the parent process by eval-ing it. 32 | if !context.globals.key?(name) && global_variables.include?(name) 33 | context.globals[name] = eval(name.to_s) 34 | end 35 | 36 | context.globals[name] = context.stack.pop 37 | end 38 | 39 | def deconstruct_keys(keys) 40 | { name: } 41 | end 42 | 43 | def disasm(iseq) 44 | "%-38s %s" % ["setglobal", name.inspect] 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/yarv/insn/setlocal.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `setlocal` sets the value of a local variable on a frame determined by the 7 | # level and index arguments. The level is the number of frames back to 8 | # look and the index is the index in the local table. It pops the value it is 9 | # setting off the stack. 10 | # 11 | # ### TracePoint 12 | # 13 | # `setlocal` does not dispatch any events. 14 | # 15 | # ### Usage 16 | # 17 | # ~~~ruby 18 | # value = 5 19 | # tap { tap { value = 10 } } 20 | # ~~~ 21 | # 22 | class SetLocal < Instruction 23 | attr_reader :name, :index, :level 24 | 25 | def initialize(name, index, level) 26 | @name = name 27 | @index = index 28 | @level = level 29 | end 30 | 31 | def ==(other) 32 | other in SetLocal[name: ^(name), index: ^(index), level: ^(level)] 33 | end 34 | 35 | def call(context) 36 | value = context.stack.pop 37 | context.parent_frame(level).set_local(index, value) 38 | end 39 | 40 | def deconstruct_keys(keys) 41 | { name:, index:, level: } 42 | end 43 | 44 | def disasm(iseq) 45 | "%-38s %s@%d, %d" % ["setlocal", name, index, level] 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/yarv/insn/setlocal_wc_0.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `setlocal_WC_0` is a specialized version of the `setlocal` instruction. It 7 | # sets the value of a local variable on the current frame to the value at the 8 | # top of the stack as determined by the index given as its only argument. 9 | # 10 | # ### TracePoint 11 | # 12 | # `setlocal_WC_0` does not dispatch any events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # value = 5 18 | # ~~~ 19 | # 20 | class SetLocalWC0 < Instruction 21 | attr_reader :name, :index 22 | 23 | def initialize(name, index) 24 | @name = name 25 | @index = index 26 | end 27 | 28 | def ==(other) 29 | other in SetLocalWC0[name: ^(name), index: ^(index)] 30 | end 31 | 32 | def call(context) 33 | value = context.stack.pop 34 | context.current_frame.set_local(index, value) 35 | end 36 | 37 | def reads 38 | 1 39 | end 40 | 41 | def writes 42 | 0 43 | end 44 | 45 | def disasm(iseq) 46 | "%-38s %s@%d" % ["setlocal_WC_0", name, index] 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/yarv/insn/setlocal_wc_1.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `setlocal_WC_1` is a specialized version of the `setlocal` instruction. It 7 | # sets the value of a local variable on the parent frame to the value at the 8 | # top of the stack as determined by the index given as its only argument. 9 | # 10 | # ### TracePoint 11 | # 12 | # `setlocal_WC_1` does not dispatch any events. 13 | # 14 | # ### Usage 15 | # 16 | # ~~~ruby 17 | # value = 5 18 | # self.then { value = 10 } 19 | # ~~~ 20 | # 21 | class SetLocalWC1 < Instruction 22 | attr_reader :name, :index 23 | 24 | def initialize(name, index) 25 | @name = name 26 | @index = index 27 | end 28 | 29 | def ==(other) 30 | other in SetLocalWC1[name: ^(name), index: ^(index)] 31 | end 32 | 33 | def call(context) 34 | value = context.stack.pop 35 | context.parent_frame.set_local(index, value) 36 | end 37 | 38 | def reads 39 | 1 40 | end 41 | 42 | def writes 43 | 0 44 | end 45 | 46 | def disasm(iseq) 47 | "%-38s %s@%d" % ["setlocal_WC_1", name, index] 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/yarv/insn/setn.rb: -------------------------------------------------------------------------------- 1 | module YARV 2 | # ### Summary 3 | # 4 | # `setn` is an instruction for set Nth stack entry to stack top 5 | # 6 | # ### TracePoint 7 | # 8 | # # `setn` does not dispatch any events. 9 | # 10 | # ### Usage 11 | # 12 | # ~~~ruby 13 | # {}[:key] = 'val' 14 | # ~~~ 15 | # 16 | class SetN < Instruction 17 | attr_reader :index 18 | 19 | def initialize(index) 20 | @index = index 21 | end 22 | 23 | def ==(other) 24 | other in SetN[index: ^(index)] 25 | end 26 | 27 | def call(context) 28 | context.stack[-index - 1] = context.stack.last 29 | end 30 | 31 | def deconstruct_keys(keys) 32 | { index: } 33 | end 34 | 35 | def disasm(iseq) 36 | "%-38s %s" % ["setn", index] 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/yarv/insn/splatarray.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `splatarray` calls to_a on an array to splat. 7 | # 8 | # It coerces the array object at the top of the stack into Array by calling 9 | # `to_a`. It pushes a duplicate of the array if there is a flag, and the original 10 | # array, if there isn't one. 11 | # 12 | # ### TracePoint 13 | # 14 | # `splayarray` does not dispatch any events. 15 | # 16 | # ### Usage 17 | # 18 | # ~~~ruby 19 | # x = *(5) 20 | # ~~~ 21 | # 22 | class SplatArray < Instruction 23 | attr_reader :flag 24 | 25 | def initialize(flag) 26 | @flag = flag 27 | end 28 | 29 | def ==(other) 30 | other in SplatArray 31 | end 32 | 33 | def call(context) 34 | array = coerce(context.stack.pop) 35 | final_array = flag ? array.dup : array 36 | context.stack.push(final_array) 37 | end 38 | 39 | def disasm(iseq) 40 | "splatarray" 41 | end 42 | 43 | private 44 | 45 | def coerce(object) 46 | object.respond_to?(:to_a) ? object.to_a : [object] 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/yarv/insn/swap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `swap` swaps the top two elements in the stack. 7 | # 8 | # ### TracePoint 9 | # 10 | # `swap` does not dispatch any events. 11 | # 12 | # ### Usage 13 | # 14 | # ~~~ruby 15 | # !!defined?([[]]) 16 | # ~~~ 17 | # 18 | class Swap < Instruction 19 | def ==(other) 20 | other in Swap 21 | end 22 | 23 | def call(context) 24 | left, right = context.stack.pop(2) 25 | context.stack.push(right) 26 | context.stack.push(left) 27 | end 28 | 29 | def disasm(iseq) 30 | "swap" 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/yarv/insn/topn.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `topn` has one argument: `n`. It gets the nth element from the top of the 7 | # stack and pushes it on the stack. 8 | # 9 | # ### TracePoint 10 | # 11 | # `topn` does not dispatch any events. 12 | # 13 | # ### Usage 14 | # 15 | # ~~~ruby 16 | # case 3 17 | # when 1..5 18 | # puts "foo" 19 | # end 20 | # 21 | # # == disasm: #@:1 (1,0)-(1,36)> (catch: FALSE) 22 | # # 0000 putobject 3 ( 1)[Li] 23 | # # 0002 putobject 1..5 24 | # # 0004 topn 1 25 | # # 0006 opt_send_without_block 26 | # # 0008 branchif 13 27 | # # 0010 pop 28 | # # 0011 putnil 29 | # # 0012 leave 30 | # # 0013 pop 31 | # # 0014 putself 32 | # # 0015 putstring "foo" 33 | # # 0017 opt_send_without_block 34 | # # 0019 leave 35 | # ~~~ 36 | # 37 | class TopN < Instruction 38 | attr_reader :n 39 | 40 | def initialize(n) 41 | @n = n 42 | end 43 | 44 | def ==(other) 45 | other in TopN[n: ^(n)] 46 | end 47 | 48 | def call(context) 49 | value = context.stack[-n - 1] 50 | context.stack.push(value) 51 | end 52 | 53 | def deconstruct_keys(keys) 54 | { n: } 55 | end 56 | 57 | def disasm(iseq) 58 | "%-38s %d" % ["topn", n] 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/yarv/insn/toregexp.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # ### Summary 5 | # 6 | # `toregexp` is generated when string interpolation is used inside a regex 7 | # literal `//`. It pops a defined number of values from the stack, combines 8 | # them into a single string and coerces that string into a `Regexp` object, 9 | # which it pushes back onto the stack 10 | # 11 | # ### TracePoint 12 | # 13 | # `toregexp` cannot dispatch any TracePoint events. 14 | # 15 | # ### Usage 16 | # 17 | # ~~~ruby 18 | # "/#{true}/" 19 | # 20 | # # == disasm: #@-e:1 (1,0)-(1,9)> (catch: FALSE) 21 | # # 0000 putobject "" ( 1)[Li] 22 | # # 0002 putobject true 23 | # # 0004 dup 24 | # # 0005 objtostring 25 | # # 0007 anytostring 26 | # # 0008 toregexp 0, 2 27 | # # 0011 leave 28 | # ~~~ 29 | # 30 | class ToRegexp < Instruction 31 | attr_reader :opts, :cnt 32 | 33 | def initialize(opts, cnt) 34 | @opts = opts 35 | @cnt = cnt 36 | end 37 | 38 | def ==(other) 39 | other in ToRegexp[opts: ^(opts), cnt: ^(cnt)] 40 | end 41 | 42 | def call(context) 43 | re_str = context.stack.pop(cnt).reverse.join 44 | context.stack.push(Regexp.new(re_str, opts)) 45 | end 46 | 47 | def deconstruct_keys(keys) 48 | { opts:, cnt: } 49 | end 50 | 51 | def disasm(iseq) 52 | "%-38s %s, %s" % ["toregexp", opts, cnt] 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/yarv/instruction.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # Abstract base instruction. 5 | class Instruction 6 | # Whether or not this instruction is a branch instruction. 7 | def branches? 8 | false 9 | end 10 | 11 | # Whether or not this instruction leaves the current frame. 12 | def leaves? 13 | false 14 | end 15 | 16 | # Whether or not this instruction falls through to the next instruction if 17 | # its branching fails. 18 | def falls_through? 19 | false 20 | end 21 | 22 | # How many values are read from the stack. 23 | def reads 24 | raise "not implemented #{self.class}" 25 | end 26 | 27 | # How many values are written to the stack. 28 | def writes 29 | raise "not implemented #{self.class}" 30 | end 31 | 32 | # Does the instruction have side effects? Control-flow counts as a 33 | # side-effect, as do some special-case instructions like Leave 34 | def side_effects? 35 | true 36 | end 37 | 38 | # A hook method to be called when the instruction is being disassembled. The 39 | # child classes will have their respective InstructionSequence passed in. 40 | def to_s 41 | disasm(nil) 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/yarv/main.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | # This is the self object at the top of the script. 5 | class Main 6 | def ==(other) 7 | other in Main 8 | end 9 | 10 | def inspect 11 | "main" 12 | end 13 | 14 | def require(context, filename) 15 | file_path = 16 | context.globals[:$:].find do |path| 17 | filename += ".rb" unless filename.end_with?(".rb") 18 | 19 | file_path = File.join(path, filename) 20 | next unless File.file?(file_path) && File.readable?(file_path) 21 | 22 | break file_path 23 | end 24 | 25 | raise LoadError, "cannot load such file -- #{filename}" unless file_path 26 | 27 | return false if context.globals[:"$\""].include?(file_path) 28 | 29 | iseq = 30 | File.open(file_path, "r") do |f| 31 | YARV.compile(f.read, file_path, file_path) 32 | end 33 | 34 | context.eval(iseq) 35 | true 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/yarv/visitor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module YARV 4 | class Visitor < SyntaxTree::Visitor 5 | attr_reader :iseq 6 | 7 | def initialize 8 | @iseq = nil 9 | end 10 | 11 | def visit_binary(node) 12 | case node.operator 13 | in :+ 14 | visit(node.left) 15 | visit(node.right) 16 | emit(OptPlus.new(call_data(node.operator, 1, %i[ARGS_SIMPLE]))) 17 | in :- 18 | visit(node.left) 19 | visit(node.right) 20 | emit(OptMinus.new(call_data(node.operator, 1, %i[ARGS_SIMPLE]))) 21 | in :* 22 | visit(node.left) 23 | visit(node.right) 24 | emit(OptMult.new(call_data(node.operator, 1, %i[ARGS_SIMPLE]))) 25 | in :/ 26 | visit(node.left) 27 | visit(node.right) 28 | emit(OptDiv.new(call_data(node.operator, 1, %i[ARGS_SIMPLE]))) 29 | in :"||" 30 | visit(node.left) 31 | emit(Dup.new) 32 | emit_jump(BranchIf) do 33 | emit(Pop.new) 34 | visit(node.right) 35 | end 36 | in :"&&" 37 | visit(node.left) 38 | emit(Dup.new) 39 | emit_jump(BranchUnless) do 40 | emit(Pop.new) 41 | visit(node.right) 42 | end 43 | end 44 | end 45 | 46 | def visit_bodystmt(node) 47 | node.rescue_clause => nil 48 | node.else_clause => nil 49 | node.ensure_clause => nil 50 | visit(node.statements) 51 | end 52 | 53 | def visit_call(node) 54 | visit(node.receiver) 55 | 56 | name = node.message == :call ? :call : node.message.value.to_sym 57 | send = OptSendWithoutBlock.new(call_data(name, 0, %i[ARGS_SIMPLE])) 58 | 59 | if node.operator in SyntaxTree::Op[value: "&."] 60 | emit(Dup.new) 61 | emit_jump(BranchNil) { emit(send) } 62 | else 63 | emit(send) 64 | end 65 | end 66 | 67 | def visit_else(node) 68 | visit(node.statements) 69 | end 70 | 71 | def visit_float(node) 72 | emit(PutObject.new(node.value.to_f)) 73 | end 74 | 75 | def visit_gvar(node) 76 | emit(GetGlobal.new(node.value.to_sym)) 77 | end 78 | 79 | def visit_if(node) 80 | visit(node.predicate) 81 | 82 | branch_offset = current_offset 83 | emit(:branch_placeholder) 84 | visit(node.statements) 85 | 86 | if node.consequent 87 | emit(Pop.new) 88 | emit_jump(Jump) do 89 | iseq.insns[branch_offset] = BranchUnless.new(emit_label) 90 | visit(node.consequent) 91 | emit(Pop.new) 92 | end 93 | else 94 | iseq.insns[branch_offset] = BranchUnless.new(emit_label) 95 | end 96 | end 97 | 98 | def visit_if_mod(node) 99 | visit(node.predicate) 100 | emit_jump(BranchUnless) do 101 | visit(node.statement) 102 | emit(Pop.new) 103 | end 104 | end 105 | 106 | def visit_imaginary(node) 107 | emit(PutObject.new(node.value.to_c)) 108 | end 109 | 110 | def visit_int(node) 111 | case (coerced = node.value.to_i) 112 | when 0 113 | emit(PutObjectInt2Fix0.new) 114 | when 1 115 | emit(PutObjectInt2Fix1.new) 116 | else 117 | emit(PutObject.new(coerced)) 118 | end 119 | end 120 | 121 | def visit_paren(node) 122 | visit(node.contents) 123 | end 124 | 125 | def visit_program(node) 126 | @iseq = InstructionSequence.new(Main.new, []) 127 | visit(node.statements) 128 | emit(Leave.new) 129 | iseq 130 | end 131 | 132 | def visit_rational(node) 133 | emit(PutObject.new(node.value.to_r)) 134 | end 135 | 136 | def visit_statements(node) 137 | visit_all(node.body) 138 | end 139 | 140 | def visit_string_concat(node) 141 | visit(node.left) 142 | visit(node.right) 143 | emit(ConcatStrings.new(2)) 144 | end 145 | 146 | def visit_string_dvar(node) 147 | visit(node.variable) 148 | emit(Dup.new) 149 | emit(ObjToString.new(call_data(:to_s, 0, %i[FCALL ARGS_SIMPLE]))) 150 | emit(AnyToString.new) 151 | end 152 | 153 | def visit_string_embexpr(node) 154 | visit(node.statements) 155 | emit(Dup.new) 156 | emit(ObjToString.new(call_data(:to_s, 0, %i[FCALL ARGS_SIMPLE]))) 157 | emit(AnyToString.new) 158 | end 159 | 160 | def visit_string_literal(node) 161 | case node.parts 162 | in [SyntaxTree::TStringContent[value:]] 163 | emit(PutString.new(value)) 164 | in [SyntaxTree::StringDVar => part] 165 | visit(SyntaxTree::TStringContent.new(value: "", location: nil)) 166 | visit(part) 167 | emit(ConcatStrings.new(2)) 168 | in [part] 169 | visit(part) 170 | else 171 | visit_all(node.parts) 172 | emit(ConcatStrings.new(node.parts.length)) 173 | end 174 | end 175 | 176 | def visit_symbol_literal(node) 177 | emit(PutObject.new(node.value.value.to_sym)) 178 | end 179 | 180 | def visit_tstring_content(node) 181 | emit(PutObject.new(node.value)) 182 | end 183 | 184 | def visit_unless_mod(node) 185 | visit(node.predicate) 186 | emit_jump(BranchIf) do 187 | visit(node.statement) 188 | emit(Pop.new) 189 | end 190 | end 191 | 192 | def visit_vcall(node) 193 | cdata = call_data(node.value.value.to_sym, 0, %i[FCALL VCALL ARGS_SIMPLE]) 194 | 195 | emit(PutSelf.new(iseq.selfo)) 196 | emit(OptSendWithoutBlock.new(cdata)) 197 | end 198 | 199 | def visit_void_stmt(node) 200 | end 201 | 202 | def visit_xstring_literal(node) 203 | emit(PutSelf.new(iseq.selfo)) 204 | visit_all(node.parts) 205 | emit(ConcatStrings.new(node.parts.length)) if node.parts.length > 1 206 | emit(OptSendWithoutBlock.new(call_data(:`, 1, %i[FCALL ARGS_SIMPLE]))) 207 | end 208 | 209 | private 210 | 211 | def call_data(mid, argc, flags) 212 | flag = 213 | flags.inject(0) { |sum, flag| sum | (1 << CallData::FLAGS.index(flag)) } 214 | 215 | CallData.new(mid, argc, flag) 216 | end 217 | 218 | def current_offset 219 | iseq.insns.length 220 | end 221 | 222 | def emit(insn) 223 | iseq << insn 224 | end 225 | 226 | def emit_jump(kind) 227 | offset = current_offset 228 | emit(:placeholder) 229 | 230 | yield 231 | iseq.insns[offset] = kind.new(emit_label) 232 | end 233 | 234 | def emit_label 235 | offset = current_offset 236 | label = :"label_#{offset}" 237 | 238 | iseq.labels[label] = offset 239 | label 240 | end 241 | end 242 | end 243 | -------------------------------------------------------------------------------- /test/cfg_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "test_helper" 4 | 5 | module YARV 6 | class CFGTest < Test::Unit::TestCase 7 | def test_branch 8 | assert_cfg(<<~DISASM, "foo ? 1 : 2") 9 | == cfg #> 10 | block_0: 11 | 0000 putself 12 | 0001 opt_send_without_block 13 | 0002 branchunless label_7 (5) 14 | # to: block_5, block_3 15 | block_3: # from: block_0 16 | 0003 putobject_INT2FIX_1_ 17 | 0004 leave 18 | # to: leaves 19 | block_5: # from: block_0 20 | 0005 putobject 2 21 | 0006 leave 22 | # to: leaves 23 | DISASM 24 | end 25 | 26 | def test_simple 27 | assert_cfg(<<~DISASM, "(n < 0 ? -1 : +1) + 100") 28 | == cfg #> 29 | block_0: 30 | 0000 putself 31 | 0001 opt_send_without_block 32 | 0002 putobject_INT2FIX_0_ 33 | 0003 opt_lt [CcCr] 34 | 0004 branchunless label_12 (7) 35 | # to: block_7, block_5 36 | block_5: # from: block_0 37 | 0005 putobject -1 38 | 0006 jump label_13 (8) 39 | # to: block_8 40 | block_7: # from: block_0 41 | 0007 putobject_INT2FIX_1_ 42 | # to: block_8 43 | block_8: # from: block_5, block_7 44 | 0008 putobject 100 45 | 0009 opt_plus [CcCr] 46 | 0010 leave 47 | # to: leaves 48 | DISASM 49 | end 50 | 51 | def test_loop 52 | source = <<~SOURCE 53 | n = 10 54 | sum = 0 55 | while n > 0 56 | sum += n 57 | n -= 1 58 | end 59 | sum 60 | SOURCE 61 | assert_cfg(<<~DISASM, source) 62 | == cfg #> 63 | block_0: 64 | 0000 putobject 10 65 | 0001 setlocal_WC_0 n@0 66 | 0002 putobject_INT2FIX_0_ 67 | 0003 setlocal_WC_0 sum@1 68 | 0004 jump label_28 (16) 69 | # to: block_16 70 | block_5: 71 | 0005 putnil 72 | 0006 pop 73 | 0007 jump label_28 (16) 74 | # to: block_16 75 | block_8: # from: block_16 76 | 0008 getlocal_WC_0 sum@1 77 | 0009 getlocal_WC_0 n@0 78 | 0010 opt_plus [CcCr] 79 | 0011 setlocal_WC_0 sum@1 80 | 0012 getlocal_WC_0 n@0 81 | 0013 putobject_INT2FIX_1_ 82 | 0014 opt_minus [CcCr] 83 | 0015 setlocal_WC_0 n@0 84 | # to: block_16 85 | block_16: # from: block_0, block_5, block_8 86 | 0016 getlocal_WC_0 n@0 87 | 0017 putobject_INT2FIX_0_ 88 | 0018 opt_gt , argc:1, ARGS_SIMPLE>[CcCr] 89 | 0019 branchif label_13 (8) 90 | # to: block_8, block_20 91 | block_20: # from: block_16 92 | 0020 putnil 93 | 0021 pop 94 | 0022 getlocal_WC_0 sum@1 95 | 0023 leave 96 | # to: leaves 97 | DISASM 98 | end 99 | 100 | def test_fib 101 | source = <<~SOURCE 102 | def fib(n) 103 | if n < 2 104 | n 105 | else 106 | fib(n - 1) + fib(n - 2) 107 | end 108 | end 109 | SOURCE 110 | assert_cfg(<<~DISASM, source) 111 | == cfg #> 112 | block_0: 113 | 0000 definemethod :fib, fib 114 | 0001 putobject :fib 115 | 0002 leave 116 | # to: leaves 117 | == cfg # 118 | block_0: 119 | 0000 getlocal_WC_0 n@0 120 | 0001 putobject 2 121 | 0002 opt_lt [CcCr] 122 | 0003 branchunless label_11 (6) 123 | # to: block_6, block_4 124 | block_4: # from: block_0 125 | 0004 getlocal_WC_0 n@0 126 | 0005 leave 127 | # to: leaves 128 | block_6: # from: block_0 129 | 0006 putself 130 | 0007 getlocal_WC_0 n@0 131 | 0008 putobject_INT2FIX_1_ 132 | 0009 opt_minus [CcCr] 133 | 0010 opt_send_without_block 134 | 0011 putself 135 | 0012 getlocal_WC_0 n@0 136 | 0013 putobject 2 137 | 0014 opt_minus [CcCr] 138 | 0015 opt_send_without_block 139 | 0016 opt_plus [CcCr] 140 | 0017 leave 141 | # to: leaves 142 | DISASM 143 | end 144 | 145 | private 146 | 147 | def assert_cfg(expected, source) 148 | string = +"" 149 | compiled = YARV.compile(source) 150 | compiled.all_iseqs.each do |iseq| 151 | cfg = YARV::CFG.new(iseq) 152 | string << cfg.disasm 153 | end 154 | assert_equal(expected, string) 155 | end 156 | end 157 | end 158 | -------------------------------------------------------------------------------- /test/compile_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | return unless ENV["CI"] 4 | require_relative "test_helper" 5 | 6 | module YARV 7 | class CompileTest < Test::Unit::TestCase 8 | Dir[File.join(RbConfig::CONFIG["libdir"], "**/*.rb")].each do |filepath| 9 | define_method(:"test_compile_#{filepath}") do 10 | YARV.compile(File.read(filepath), filepath, filepath) 11 | rescue SyntaxError 12 | # Skip past any files that have syntax errors. 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/disasm_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "test_helper" 4 | 5 | module YARV 6 | class DisasmTest < Test::Unit::TestCase 7 | def test_binary_plus 8 | assert_disasm(<<~DISASM, "2 + 3") 9 | == disasm #> (catch: FALSE) 10 | 0000 putobject 2 11 | 0001 putobject 3 12 | 0002 opt_plus [CcCr] 13 | 0003 leave 14 | DISASM 15 | end 16 | 17 | def test_branch_plus 18 | assert_disasm(<<~DISASM, "foo ? 1 : 2") 19 | == disasm #> (catch: FALSE) 20 | 0000 putself 21 | 0001 opt_send_without_block 22 | 0002 branchunless label_7 (5) 23 | 0003 putobject_INT2FIX_1_ 24 | 0004 leave 25 | 0005 putobject 2 26 | 0006 leave 27 | DISASM 28 | end 29 | 30 | private 31 | 32 | def assert_disasm(expected, source) 33 | assert_equal(expected, YARV.compile(source).disasm) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/insn/adjuststack_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class AdjustStackTest < TestCase 7 | def test_execute 8 | source = <<~RUBY 9 | x = [true] 10 | x[0] ||= nil 11 | x[0] 12 | RUBY 13 | 14 | YARV.compile(source).insns => [ 15 | DupArray, 16 | SetLocalWC0, 17 | GetLocalWC0, 18 | PutObjectInt2Fix0, 19 | DupN, 20 | OptAref, 21 | Dup, 22 | BranchIf, 23 | Pop, 24 | PutNil, 25 | OptAset, 26 | Pop, 27 | Jump, 28 | AdjustStack[size: 3], 29 | GetLocalWC0, 30 | PutObjectInt2Fix0, 31 | OptAref, 32 | Leave 33 | ] 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/insn/anytostring_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class AnyToStringTest < TestCase 7 | def test_execute 8 | assert_insns( 9 | [ 10 | PutObject, 11 | PutObject, 12 | Dup, 13 | ObjToString, 14 | AnyToString, 15 | ConcatStrings, 16 | Leave 17 | ], 18 | '"#{5}"' 19 | ) 20 | assert_stdout("\"5\"\n", 'p "#{5}"') 21 | end 22 | 23 | def test_produces_the_expected_instructions 24 | assert_stdout_for_instructions( 25 | "5\n", 26 | [ 27 | [:putself], 28 | [:putobject, ""], 29 | [:putobject, 5], 30 | [:dup], 31 | [:objtostring, { mid: :to_s, flag: 20, orig_argc: 0 }], 32 | [:anytostring], 33 | [:concatstrings, 2], 34 | [:opt_send_without_block, { mid: :puts, flag: 20, orig_argc: 1 }], 35 | [:leave] 36 | ] 37 | ) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/insn/branchif_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class BranchIfTest < TestCase 7 | def test_branchif_jumps_if_true 8 | source_code = "x = true; x ||= 'foo' ; puts x" 9 | assert_stdout("true\n", source_code) 10 | end 11 | 12 | def test_branchif_doesnt_jump_if_false 13 | source_code = "x = false; x ||= true; puts x" 14 | assert_stdout("true\n", source_code) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/insn/branchnil_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class BranchNilTest < TestCase 7 | def test_branchnil_jumps_if_nil 8 | source_code = "x = nil; if x&.to_s; puts 'hi'; end" 9 | assert_insns( 10 | [ 11 | PutNil, 12 | SetLocalWC0, 13 | GetLocalWC0, 14 | Dup, 15 | BranchNil, 16 | OptSendWithoutBlock, 17 | BranchUnless, 18 | PutSelf, 19 | PutString, 20 | OptSendWithoutBlock, 21 | Leave, 22 | PutNil, 23 | Leave 24 | ], 25 | source_code 26 | ) 27 | assert_stdout("", source_code) 28 | end 29 | 30 | def test_branchnil_doesnt_jump_if_not_nil 31 | source_code = "x = true; puts x&.to_s" 32 | assert_insns( 33 | [ 34 | PutObject, 35 | SetLocalWC0, 36 | PutSelf, 37 | GetLocalWC0, 38 | Dup, 39 | BranchNil, 40 | OptSendWithoutBlock, 41 | OptSendWithoutBlock, 42 | Leave 43 | ], 44 | source_code 45 | ) 46 | assert_stdout("true\n", source_code) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/insn/branchunless_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class BranchUnlessTest < TestCase 7 | def test_branchunless_jumps_if_false 8 | source_code = "if 2+3; puts 'foo'; end" 9 | assert_insns( 10 | [ 11 | PutObject, 12 | PutObject, 13 | OptPlus, 14 | BranchUnless, 15 | PutSelf, 16 | PutString, 17 | OptSendWithoutBlock, 18 | Leave, 19 | PutNil, 20 | Leave 21 | ], 22 | source_code 23 | ) 24 | assert_stdout("foo\n", source_code) 25 | end 26 | 27 | def test_branchunless_doesnt_jump_if_true 28 | source_code = "if 'bar'.empty?; puts 'foo'; end" 29 | assert_insns( 30 | [ 31 | PutString, 32 | OptEmptyP, 33 | BranchUnless, 34 | PutSelf, 35 | PutString, 36 | OptSendWithoutBlock, 37 | Leave, 38 | PutNil, 39 | Leave 40 | ], 41 | source_code 42 | ) 43 | assert_stdout("", source_code) 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/insn/concatarray_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class ConcatarrayTest < TestCase 7 | def test_execute 8 | assert_insns([DupArray, PutObject, ConcatArray, Leave], "[1,*2]") 9 | assert_stdout("[1, 2]\n", "p [1, *2]") 10 | end 11 | 12 | def test_coerces_the_left_element 13 | assert_stdout_for_instructions( 14 | "[2, 3]\n", 15 | [ 16 | [:putself], 17 | [:putobject, 2], 18 | [:putobject, 3], 19 | [:concatarray], 20 | [:opt_send_without_block, { mid: :p, flag: 20, orig_argc: 1 }] 21 | ] 22 | ) 23 | end 24 | 25 | def test_duplicates_the_left_element 26 | assert_stdout_for_instructions( 27 | "[1]\n", 28 | [ 29 | [:putself], 30 | [:duparray, [1]], 31 | [:dup], 32 | [:putobject, 2], 33 | [:concatarray], 34 | [:pop], 35 | [:opt_send_without_block, { mid: :p, flag: 20, orig_argc: 1 }] 36 | ] 37 | ) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/insn/concatstrings_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class ConcatstringsTest < TestCase 7 | def test_execute 8 | assert_insns( 9 | [ 10 | PutObject, 11 | PutObject, 12 | Dup, 13 | ObjToString, 14 | AnyToString, 15 | ConcatStrings, 16 | Leave 17 | ], 18 | '"#{5}"' 19 | ) 20 | assert_stdout("\"5\"\n", 'p "#{5}"') 21 | end 22 | 23 | def test_produces_the_expected_instructions 24 | assert_stdout_for_instructions( 25 | "5\n", 26 | [ 27 | [:putself], 28 | [:putobject, ""], 29 | [:putobject, 5], 30 | [:dup], 31 | [:objtostring, { mid: :to_s, flag: 20, orig_argc: 0 }], 32 | [:anytostring], 33 | [:concatstrings, 2], 34 | [:opt_send_without_block, { mid: :puts, flag: 20, orig_argc: 1 }], 35 | [:leave] 36 | ] 37 | ) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/insn/defined_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class DefinedTest < TestCase 7 | def test_execute 8 | assert_insns([PutNil, Defined, Leave], "defined?($foo)") 9 | assert_stdout("global-variable\n", "$foo = 1; puts defined?($foo)") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/definemethod_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class DefineMethodTest < TestCase 7 | def test_execute 8 | source = <<~SOURCE 9 | def value = "value" 10 | puts value 11 | SOURCE 12 | 13 | assert_insns( 14 | [ 15 | DefineMethod, 16 | PutSelf, 17 | PutSelf, 18 | OptSendWithoutBlock, 19 | OptSendWithoutBlock, 20 | Leave 21 | ], 22 | source 23 | ) 24 | assert_stdout("value\n", source) 25 | end 26 | 27 | def test_execute_with_leading_arguments 28 | source = <<~SOURCE 29 | def echo(value) = value 30 | puts echo(1) 31 | SOURCE 32 | 33 | assert_stdout("1\n", source) 34 | end 35 | 36 | def test_execute_with_leading_argument_and_other_locals 37 | source = <<~SOURCE 38 | def add2(value) 39 | addition = 2 40 | addition + value 41 | end 42 | 43 | puts add2(1) 44 | SOURCE 45 | 46 | assert_stdout("3\n", source) 47 | end 48 | 49 | def test_execute_with_leading_arguments_and_other_locals 50 | source = <<~SOURCE 51 | def add3(left, right) 52 | addition = 2 53 | addition + left + right 54 | end 55 | 56 | puts add3(3, 4) 57 | SOURCE 58 | 59 | assert_stdout("9\n", source) 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /test/insn/dup_hash_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class DupHashTest < TestCase 7 | def test_execute 8 | assert_insns([DupHash, Leave], "{ a: 1 }") 9 | assert_stdout("{:a=>1}\n", "p({ a: 1 })") 10 | end 11 | 12 | def test_hash_is_immutable 13 | ruby = <<~RUBY 14 | def foo 15 | { a: 1 } 16 | end 17 | foo.merge!({b: 2}) 18 | p foo 19 | RUBY 20 | assert_stdout("{:a=>1}\n", ruby) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/insn/dup_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class DupTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, Dup, SetGlobal, Leave], "$global = 5") 9 | assert_stdout("5\n", "p $global = 5") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/duparray_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class DupArrayTest < TestCase 7 | def test_execute 8 | assert_insns([DupArray, Leave], "[true]") 9 | assert_stdout("true\n", "p [true][0]") 10 | end 11 | 12 | def test_duplicates_the_literal 13 | assert_stdout("[true]\n", "def foo() [true] end; foo.push(false); p foo") 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/insn/getglobal_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class GetGlobalTest < TestCase 7 | def test_execute 8 | assert_insns([GetGlobal, Leave], "$$") 9 | assert_stdout("#{$$}\n", "p $$") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/getlocal_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class GetLocalTest < TestCase 7 | def test_execute 8 | YARV.compile("value = 5; tap { tap { value } }").insns => [ 9 | PutObject, 10 | SetLocalWC0, 11 | PutSelf, 12 | Send[ 13 | block_iseq: { 14 | insns: [ 15 | PutSelf, 16 | Send[ 17 | block_iseq: { insns: [GetLocal[name: :value, level: 2], Leave] } 18 | ], 19 | Leave 20 | ] 21 | } 22 | ], 23 | Leave 24 | ] 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/insn/getlocal_wc_0_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class GetLocalWC0Test < TestCase 7 | def test_execute 8 | assert_insns( 9 | [PutObject, SetLocalWC0, GetLocalWC0, Leave], 10 | "value = 5; value" 11 | ) 12 | assert_stdout("5\n", "value = 5; p value") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/insn/getlocal_wc_1_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class GetLocalWC1Test < TestCase 7 | def test_execute 8 | source = "value = 5; print(self.then { value })" 9 | 10 | assert_equal( 11 | [GetLocalWC1, Leave], 12 | YARV.compile(source).insns[4].block_iseq.insns.map(&:class) 13 | ) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/insn/intern_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class InternTest < TestCase 7 | def test_execute 8 | assert_insns([PutString, Intern, Leave], ':"#{"foo"}"') 9 | assert_stdout(":foo\n", 'p :"#{"foo"}"') 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/invokeblock_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class InvokeBlockTest < TestCase 7 | def test_executes_correctly 8 | source = <<~SOURCE 9 | def foo 10 | yield(10) 11 | end 12 | 13 | foo do |x| 14 | p x + 1 15 | end 16 | SOURCE 17 | 18 | assert_insns([DefineMethod, PutSelf, Send, Leave], source) 19 | iseq = YARV.compile(source) 20 | assert_equal( 21 | [PutObject, InvokeBlock, Leave], 22 | iseq.insns[0].iseq.insns.map(&:class) 23 | ) 24 | 25 | assert_stdout("11\n", source) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/insn/jump_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class JumpTest < TestCase 7 | def test_jump_moves_the_control_flow 8 | source_code = "y = 0; if y == 0 then puts '0' else puts '2' end" 9 | assert_stdout("0\n", source_code, peephole_optimization: false) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/newarray_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class NewArrayTest < TestCase 7 | def test_execute 8 | assert_insns([NewArray, Leave], "[]") 9 | assert_stdout(%([1, true, "string"]\n), "p([1, true, 'string'])") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/newhash_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class NewHashTest < TestCase 7 | def test_execute 8 | assert_insns([NewHash, Leave], "{}") 9 | assert_stdout("{:a=>2}\n", "k=:a;v=2;p({k=>v})") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/newrange_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class NewRangeTest < TestCase 7 | def test_execute 8 | assert_insns( 9 | [PutNil, SetLocalWC0, GetLocalWC0, PutNil, NewRange, Leave], 10 | "x=nil;x.." 11 | ) 12 | assert_stdout("nil..nil\nnil...nil\n", "x=nil;p(x..);p(x...)") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/insn/nop_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class NopTest < TestCase 7 | def test_execute 8 | assert_insns( 9 | [PutSelf, OptSendWithoutBlock, Nop, Leave], 10 | "raise rescue true" 11 | ) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/insn/objtostring_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class ObjToStringTest < TestCase 7 | def test_execute 8 | assert_insns( 9 | [ 10 | PutObject, 11 | PutObject, 12 | Dup, 13 | ObjToString, 14 | AnyToString, 15 | ConcatStrings, 16 | Leave 17 | ], 18 | '"#{5}"' 19 | ) 20 | assert_stdout("\"5\"\n", 'p "#{5}"') 21 | end 22 | 23 | def test_produces_the_expected_instructions 24 | assert_stdout_for_instructions( 25 | "5\n", 26 | [ 27 | [:putself], 28 | [:putobject, ""], 29 | [:putobject, 5], 30 | [:dup], 31 | [:objtostring, { mid: :to_s, flag: 20, orig_argc: 0 }], 32 | [:anytostring], 33 | [:concatstrings, 2], 34 | [:opt_send_without_block, { mid: :puts, flag: 20, orig_argc: 1 }], 35 | [:leave] 36 | ] 37 | ) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/insn/opt_and_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptAndTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptAnd, Leave], "2 & 3") 9 | assert_stdout("2\n", "p 2 & 3") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_aref_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptArefTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptAref, Leave], "7[2]") 9 | assert_stdout("1\n", "p 7[2]") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_aref_with_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptArefWithTest < TestCase 7 | def test_execute 8 | assert_insns([DupHash, OptArefWith, Leave], "{ 'test' => true }['test']") 9 | assert_stdout("true\n", "p({ 'test' => true }['test'])") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_aset_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptAsetTest < TestCase 7 | def test_execute 8 | assert_insns( 9 | [PutNil, NewHash, PutObject, PutString, SetN, OptAset, Pop, Leave], 10 | "{}[:key] = 'val'", 11 | peephole_optimization: false 12 | ) 13 | assert_stdout( 14 | "\"val\"\n", 15 | "p({}[:key] = 'val')", 16 | peephole_optimization: false 17 | ) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/insn/opt_aset_with_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptAsetWithTest < TestCase 7 | def test_execute 8 | YARV.compile("{}[\"true\"] = true").insns => [ 9 | NewHash, 10 | PutObject, 11 | Swap, 12 | TopN, 13 | OptAsetWith[key: "true"], 14 | Pop, 15 | Leave 16 | ] 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/insn/opt_case_dispatch_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptCaseDispatchTest < TestCase 7 | def test_opt_case_dispatch_jumps_to_correct_branch 8 | source_code = "case 1 when 0 then puts 'foo' else puts 'bar' end" 9 | assert_stdout("bar\n", source_code) 10 | end 11 | 12 | def test_opt_case_dispatch_can_use_cd_hash_to_jump_to_correct_branch 13 | source_code = "case 1 when 1 then puts 'foo' else puts 'bar' end" 14 | assert_stdout("foo\n", source_code) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/insn/opt_div_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptDivTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptDiv, Leave], "2 / 3") 9 | assert_stdout("0\n", "p 2 / 3") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_empty_p_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptEmptyPTest < TestCase 7 | def test_execute 8 | assert_insns([PutString, OptEmptyP, Leave], "''.empty?") 9 | assert_stdout("true\n", "p ''.empty?") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_eq_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptEqTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptEq, Leave], "2 == 2") 9 | assert_stdout("true\n", "p 2 == 2") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_ge_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptGeTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptGe, Leave], "4 >= 3") 9 | assert_stdout("true\n", "puts 4 >= 3") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_gt_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptGtTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptGt, Leave], "4 > 3") 9 | assert_stdout("true\n", "puts 4 > 3") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_le_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptLeTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptLe, Leave], "3 <= 4") 9 | assert_stdout("true\n", "puts 3 <= 4") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_length_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptLengthTest < TestCase 7 | def test_execute 8 | assert_insns([PutString, OptLength, Leave], "''.length") 9 | assert_stdout("0\n", "p ''.length") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_lt_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptLtTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptLt, Leave], "3 < 4") 9 | assert_stdout("true\n", "puts 3 < 4") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_ltlt_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptLtLtTest < TestCase 7 | def test_execute 8 | assert_insns([PutString, PutString, OptLtLt, Leave], "'' << 'a'") 9 | assert_stdout("\"a\"\n", "p (+'') << 'a'") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_minus_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptMinusTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptMinus, Leave], "3 - 2") 9 | assert_stdout("1\n", "p 3 - 2") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_mod_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptModTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptMod, Leave], "4 % 2") 9 | assert_stdout("0\n", "puts 4 % 2") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_mult_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptMultTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptMult, Leave], "3 * 2") 9 | assert_stdout("6\n", "p 3 * 2") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_neq_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptNeqTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptNeq, Leave], "2 != 2") 9 | assert_stdout("false\n", "p 2 != 2") 10 | assert_stdout("true\n", "p 1 != 2") 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/insn/opt_newarray_max_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptNewArrayMaxTest < TestCase 7 | def test_execute 8 | assert_insns( 9 | [PutObject, PutObject, Dup, SetLocalWC0, OptNewArrayMax, Leave], 10 | "[2, x = 3].max" 11 | ) 12 | assert_stdout("3\n", "p [2, x = 3].max") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/insn/opt_newarray_min_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptNewArrayMinTest < TestCase 7 | def test_execute 8 | assert_insns( 9 | [PutObject, PutObject, Dup, SetLocalWC0, OptNewArrayMin, Leave], 10 | "[2, x = 3].min" 11 | ) 12 | assert_stdout("2\n", "p [2, x = 3].min") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/insn/opt_nil_p_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptNilPTest < TestCase 7 | def test_execute 8 | assert_insns([PutString, OptNilP, Leave], "''.nil?") 9 | assert_stdout("false\n", "p ''.nil?") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_not_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptNotTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, OptNot, Leave], "!true") 9 | assert_stdout("false\n", "p !true") 10 | assert_stdout("true\n", "p !!true") 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/insn/opt_or_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptOrTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptOr, Leave], "2 | 3") 9 | assert_stdout("3\n", "p 2 | 3") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_plus_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptPlusTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, PutObject, OptPlus, Leave], "2 + 3") 9 | assert_stdout("5\n", "p 2 + 3") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_regexpmatch2_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptRegexpMatch2Test < TestCase 7 | def test_execute 8 | assert_insns( 9 | [ 10 | PutObject, 11 | PutString, 12 | OptRegexpMatch2, 13 | Dup, 14 | BranchUnless, 15 | Pop, 16 | GetGlobal, 17 | Leave 18 | ], 19 | "/true/ =~ 'true' && $~" 20 | ) 21 | 22 | assert_stdout("nil\n", "p (/true/ =~ 'true' && $~)") 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/insn/opt_send_without_block_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptSendWithoutBlockTest < TestCase 7 | def test_execute 8 | assert_insns( 9 | [PutSelf, PutString, OptSendWithoutBlock, Leave], 10 | "puts 'Hello, world!'" 11 | ) 12 | assert_stdout("Hello, world!\n", "puts 'Hello, world!'") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/insn/opt_size_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptSizeTest < TestCase 7 | def test_execute 8 | assert_insns([PutString, OptSize, Leave], "''.size") 9 | assert_stdout("4\n", "p '1111'.size") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_str_freeze_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptStrFreezeTest < TestCase 7 | def test_execute 8 | assert_insns([OptStrFreeze, Leave], "\"string\".freeze") 9 | assert_stdout("\"string\"\n", "p(-'string')") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_str_uminus_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptStrUMinusTest < TestCase 7 | def test_execute 8 | assert_insns([OptStrUMinus, Leave], "-\"string\"") 9 | assert_stdout("\"string\"\n", "p(-'string')") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/opt_succ_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class OptSuccTest < TestCase 7 | def test_execute 8 | assert_insns([PutString, OptSucc, Leave], "'a'.succ") 9 | assert_stdout("\"b\"\n", "p 'a'.succ") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/putnil_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class PutNilTest < TestCase 7 | def test_execute 8 | assert_insns([PutNil, Leave], "nil") 9 | assert_stdout("nil\n", "p nil") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/putobject_int2fix_0_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class PutObjectInt2Fix0Test < TestCase 7 | def test_execute 8 | assert_insns( 9 | [PutSelf, PutObjectInt2Fix0, OptSendWithoutBlock, Leave], 10 | "puts 0" 11 | ) 12 | assert_stdout("0\n", "puts 0") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/insn/putobject_int2fix_1_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class PutObjectInt2Fix1Test < TestCase 7 | def test_execute 8 | assert_insns( 9 | [PutSelf, PutObjectInt2Fix1, OptSendWithoutBlock, Leave], 10 | "puts 1" 11 | ) 12 | assert_stdout("1\n", "puts 1") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/insn/putobject_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class PutObjectTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, Leave], "5") 9 | assert_stdout("5\n", "puts 5") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/putself_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class PutSelfTest < TestCase 7 | def test_execute 8 | assert_insns([PutSelf, Leave], "self") 9 | assert_stdout("main\n", "p self") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/putstring_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class PutStringTest < TestCase 7 | def test_execute 8 | assert_insns([PutString, Leave], "'foo'") 9 | assert_stdout("foo\n", "puts 'foo'") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/send_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class SendTest < TestCase 7 | def test_executes_correctly 8 | source = <<~SOURCE 9 | true.tap { |i| p i } 10 | SOURCE 11 | 12 | assert_insns([PutObject, Send, Leave], source) 13 | iseq = YARV.compile(source) 14 | assert_equal( 15 | [PutSelf, GetLocalWC0, OptSendWithoutBlock, Leave], 16 | iseq.insns[1].block_iseq.insns.map(&:class) 17 | ) 18 | 19 | assert_stdout("true\n", source) 20 | end 21 | 22 | def test_executes_without_block_correctly 23 | source = "puts 'hello'" 24 | iseq = YARV.compile(source, specialized_instruction: false) 25 | assert_equal([PutSelf, PutString, Send, Leave], iseq.insns.map(&:class)) 26 | 27 | assert_stdout("hello\n", source) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/insn/setglobal_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class SetGlobalTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, Dup, SetGlobal, Leave], "$global = 5") 9 | assert_stdout("5\n", "p $global = 5") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/setlocal_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class SetLocalTest < TestCase 7 | def test_execute 8 | YARV.compile("value = 5; tap { tap { value = 10 } }").insns => [ 9 | PutObject, 10 | SetLocalWC0, 11 | PutSelf, 12 | Send[ 13 | block_iseq: { 14 | insns: [ 15 | PutSelf, 16 | Send[ 17 | block_iseq: { 18 | insns: [ 19 | PutObject, 20 | Dup, 21 | SetLocal[name: :value, level: 2], 22 | Leave 23 | ] 24 | } 25 | ], 26 | Leave 27 | ] 28 | } 29 | ], 30 | Leave 31 | ] 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/insn/setlocal_wc_0_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class SetLocalWC0Test < TestCase 7 | def test_execute 8 | assert_insns([PutObject, Dup, SetLocalWC0, Leave], "value = 5") 9 | assert_stdout("5\n", "p value = 5") 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/setlocal_wc_1_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class SetLocalWC1Test < TestCase 7 | def test_execute 8 | source = "value = 5; print(self.then { value = 5 })" 9 | 10 | assert_equal( 11 | [PutObject, Dup, SetLocalWC1, Leave], 12 | YARV.compile(source).insns[4].block_iseq.insns.map(&:class) 13 | ) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/insn/splatarray_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class SplatArrayTest < TestCase 7 | def test_execute 8 | assert_insns([PutObject, SplatArray, Dup, SetLocalWC0, Leave], "x = *(5)") 9 | assert_stdout("[5]\n", "x = *(5); p x") 10 | end 11 | 12 | def test_coerces_the_element 13 | assert_stdout_for_instructions( 14 | "[2]\n", 15 | [ 16 | [:putself], 17 | [:putobject, 2], 18 | [:splatarray, true], 19 | [:opt_send_without_block, { mid: :p, flag: 20, orig_argc: 1 }] 20 | ] 21 | ) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /test/insn/swap_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class SwapTest < TestCase 7 | def test_execute 8 | assert_insns( 9 | [PutNil, PutObject, Swap, Pop, OptNot, OptNot, Leave], 10 | "!!defined?([[]])" 11 | ) 12 | assert_stdout("true\n", "p !!defined?([[]])") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/insn/topn_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class TopNTest < TestCase 7 | def test_top_n_gets_nth_element_from_stack 8 | source_code = "case 3 when 1..5 then puts 'foo' end" 9 | assert_stdout("foo\n", source_code) 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/insn/toregexp_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class ToRegexpTest < TestCase 7 | def test_execute 8 | assert_insns( 9 | [PutObject, PutObject, Dup, ObjToString, AnyToString, ToRegexp, Leave], 10 | '/#{true}/' 11 | ) 12 | assert_stdout("\"/true/\"\n", 'p "/#{true}/"') 13 | end 14 | 15 | def test_execute_multiple_interpolations 16 | assert_insns( 17 | [PutObject, PutObject, Dup, ObjToString, AnyToString, ToRegexp, Leave], 18 | '/#{true}/' 19 | ) 20 | assert_stdout("\"/true 5/\"\n", 'p "/#{true} #{5}/"') 21 | end 22 | 23 | def test_produces_the_expected_instructions 24 | assert_stdout_for_instructions( 25 | "/true/\n", 26 | [ 27 | [:putself], 28 | [:putobject, ""], 29 | [:putobject, true], 30 | [:dup], 31 | [:objtostring, { mid: :to_s, flag: 20, orig_argc: 0 }], 32 | [:anytostring], 33 | [:toregexp, 0, 2], 34 | [:opt_send_without_block, { mid: :p, flag: 20, orig_argc: 1 }], 35 | [:leave] 36 | ] 37 | ) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/static_spec_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module YARV 6 | class StaticSpecTest < Test::Unit::TestCase 7 | # We're not stupid, we just want to cache results from previous tests for 8 | # use in subsequent tests in COMPILED_CACHE, CFG_CACHE etc. 9 | self.test_order = :defined 10 | 11 | SPEC_FILES = ["#{__dir__}/../spec/ruby/language/and_spec.rb"] 12 | 13 | COMPILED_CACHE = {} 14 | CFG_CACHE = {} 15 | DFG_CACHE = {} 16 | SOY_CACHE = {} 17 | 18 | def test_compile 19 | SPEC_FILES.each do |spec_file| 20 | compiled = 21 | without_warnings do 22 | YARV.compile( 23 | File.read(spec_file), 24 | spec_file, 25 | File.basename(spec_file) 26 | ) 27 | end 28 | COMPILED_CACHE[spec_file] = compiled 29 | end 30 | end 31 | 32 | def test_disasm 33 | SPEC_FILES.each do |spec_file| 34 | compiled = COMPILED_CACHE[spec_file] 35 | next unless compiled 36 | compiled.disasm 37 | end 38 | end 39 | 40 | def test_cfg 41 | SPEC_FILES.each do |spec_file| 42 | compiled = COMPILED_CACHE[spec_file] 43 | next unless compiled 44 | compiled.all_iseqs.each do |iseq| 45 | iseq_key = [spec_file, iseq.object_id] 46 | cfg = CFG.new(iseq) 47 | CFG_CACHE[iseq_key] = cfg 48 | end 49 | end 50 | end 51 | 52 | def test_cfg_disasm 53 | SPEC_FILES.each do |spec_file| 54 | compiled = COMPILED_CACHE[spec_file] 55 | next unless compiled 56 | compiled.all_iseqs.each do |iseq| 57 | iseq_key = [spec_file, iseq.object_id] 58 | cfg = CFG_CACHE[iseq_key] 59 | next unless cfg 60 | cfg.disasm 61 | end 62 | end 63 | end 64 | 65 | def test_dfg 66 | SPEC_FILES.each do |spec_file| 67 | compiled = COMPILED_CACHE[spec_file] 68 | next unless compiled 69 | compiled.all_iseqs.each do |iseq| 70 | iseq_key = [spec_file, iseq.object_id] 71 | cfg = CFG_CACHE[iseq_key] 72 | next unless cfg 73 | dfg = YARV::DFG.new(cfg) 74 | DFG_CACHE[iseq_key] = dfg 75 | end 76 | end 77 | end 78 | 79 | def test_dfg_disasm 80 | SPEC_FILES.each do |spec_file| 81 | compiled = COMPILED_CACHE[spec_file] 82 | next unless compiled 83 | compiled.all_iseqs.each do |iseq| 84 | iseq_key = [spec_file, iseq.object_id] 85 | dfg = DFG_CACHE[iseq_key] 86 | next unless dfg 87 | dfg.disasm 88 | end 89 | end 90 | end 91 | 92 | # def test_soy 93 | # SPEC_FILES.each do |spec_file| 94 | # compiled = COMPILED_CACHE[spec_file] 95 | # next unless compiled 96 | # compiled.all_iseqs.each do |iseq| 97 | # iseq_key = [spec_file, iseq.object_id] 98 | # dfg = DFG_CACHE[iseq_key] 99 | # next unless dfg 100 | # soy = YARV::SOY.new(dfg) 101 | # SOY_CACHE[iseq_key] = soy 102 | # end 103 | # end 104 | # end 105 | 106 | def test_soy_mermaid 107 | SPEC_FILES.each do |spec_file| 108 | compiled = COMPILED_CACHE[spec_file] 109 | next unless compiled 110 | compiled.all_iseqs.each do |iseq| 111 | iseq_key = [spec_file, iseq.object_id] 112 | soy = SOY_CACHE[iseq_key] 113 | next unless soy 114 | soy.mermaid 115 | end 116 | end 117 | end 118 | 119 | def without_warnings 120 | original_verbose = $VERBOSE 121 | $VERBOSE = nil 122 | begin 123 | yield 124 | ensure 125 | $VERBOSE = original_verbose 126 | end 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Why is this here!? In the power_assert library, it enables a tracepoint if 4 | # byebug is not defined. This causes a bunch of already-loaded iseqs to be 5 | # recompiled to their tracepoint variants, which means we can't specialize like 6 | # we want to in the tests. 7 | # https://github.com/ruby/power_assert/blob/9132517/lib/power_assert.rb#L5 8 | module Byebug 9 | end 10 | 11 | $:.unshift(File.expand_path("../lib", __dir__)) 12 | require "yarv" 13 | 14 | require "stringio" 15 | require "test/unit" 16 | 17 | module YARV 18 | class TestCase < Test::Unit::TestCase 19 | private 20 | 21 | def assert_insns(expected, code, **options) 22 | iseq = YARV.compile(code, **options) 23 | assert_equal(expected, iseq.insns.map(&:class)) 24 | end 25 | 26 | def assert_stdout(expected, code, **options) 27 | original = $stdout 28 | $stdout = StringIO.new 29 | 30 | YARV.compile(code, **options).eval 31 | assert_equal(expected, $stdout.string) 32 | ensure 33 | $stdout = original 34 | end 35 | 36 | # Allows to test instructions sequences that the compiler doesn't generate. 37 | def assert_stdout_for_instructions(expected, instructions) 38 | original = $stdout 39 | $stdout = StringIO.new 40 | iseq = RubyVM::InstructionSequence.compile("").to_a 41 | iseq[-1] = [1, :RUBY_EVENT_LINE, *instructions, [:leave]] 42 | InstructionSequence.compile(Main.new, iseq).eval 43 | assert_equal(expected, $stdout.string) 44 | ensure 45 | $stdout = original 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/visitor_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "test_helper" 4 | 5 | module YARV 6 | class VisitorTest < Test::Unit::TestCase 7 | def test_binary_plus 8 | assert_visit("2 + 3") 9 | end 10 | 11 | def test_binary_minus 12 | assert_visit("3 - 2") 13 | end 14 | 15 | def test_binary_mult 16 | assert_visit("2 * 3") 17 | end 18 | 19 | def test_binary_div 20 | assert_visit("3 / 2") 21 | end 22 | 23 | def test_call 24 | assert_visit("foo.bar") 25 | end 26 | 27 | def test_call_with_call 28 | assert_visit("foo.()") 29 | end 30 | 31 | def test_call_with_lonely 32 | assert_visit("foo&.bar") 33 | end 34 | 35 | def test_float 36 | assert_visit("1.0") 37 | end 38 | 39 | def test_gvar 40 | assert_visit("$foo") 41 | end 42 | 43 | def test_int 44 | assert_visit("3") 45 | end 46 | 47 | def test_int_0 48 | assert_visit("0") 49 | end 50 | 51 | def test_int_1 52 | assert_visit("1") 53 | end 54 | 55 | def test_paren 56 | assert_visit("(((0)))") 57 | end 58 | 59 | def test_rational 60 | assert_visit("1r") 61 | end 62 | 63 | def test_string_dvar_gvar 64 | assert_visit("\"\#$foo\"") 65 | end 66 | 67 | def test_string_literal 68 | assert_visit("\"foo \#{bar}\"") 69 | end 70 | 71 | def test_symbol_literal 72 | assert_visit(":foo") 73 | end 74 | 75 | def test_vcall 76 | assert_visit("foo") 77 | end 78 | 79 | def test_xstring_literal 80 | assert_visit("`foo`") 81 | end 82 | 83 | private 84 | 85 | def assert_visit(source) 86 | assert_equal( 87 | YARV.compile(source), 88 | Visitor.new.visit(SyntaxTree.parse(source)) 89 | ) 90 | end 91 | end 92 | end 93 | --------------------------------------------------------------------------------