diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 9f77688..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "bundler" - directory: "/" - schedule: - interval: "daily" - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml deleted file mode 100644 index 5468e6d..0000000 --- a/.github/workflows/auto-merge.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Dependabot auto-merge -on: pull_request - -permissions: - contents: write - pull-requests: write - -jobs: - dependabot: - runs-on: ubuntu-latest - if: ${{ github.actor == 'dependabot[bot]' }} - steps: - - name: Dependabot metadata - id: metadata - uses: dependabot/fetch-metadata@v2.4.0 - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" - - name: Enable auto-merge for Dependabot PRs - run: gh pr merge --auto --merge "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml deleted file mode 100644 index 2dc2c60..0000000 --- a/.github/workflows/gh-pages.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Github Pages -on: - push: - branches: - - main - -jobs: - build-and-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout 🛎️ - uses: actions/checkout@master - - - name: Set up Ruby 💎 - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - ruby-version: '3.1' - - - name: Generate documentation 🔧 - run: bin/doc - - - name: Deploy 🚀 - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./doc diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 325fa9a..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Main -on: - push: - branches: main - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout 🛎️ - uses: actions/checkout@master - with: - submodules: recursive - - - name: Set up Ruby 💎 - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - ruby-version: '3.2' - - - name: Lint and test - run: | - bundle exec rake stree:check - bundle exec rake test diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml deleted file mode 100644 index ccc5c2a..0000000 --- a/.github/workflows/spec.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Spec -on: - push: - branches: main - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout 🛎️ - uses: actions/checkout@master - with: - submodules: recursive - - - name: Set up Ruby 💎 - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - ruby-version: '3.1' - - - name: Spec - run: ./spec/mspec/bin/mspec -t ./exe/yarv || true diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1a6f6be..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/doc/index.html -/doc/glossary.html -test.rb diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index f5660ca..0000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "spec/ruby"] - path = spec/ruby - url = https://github.com/ruby/spec -[submodule "spec/mspec"] - path = spec/mspec - url = https://github.com/ruby/mspec diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 773d773..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,90 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -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). - -## [Unreleased] - -## [0.3.0] - 2022-05-10 - -### Added - -- [#85](https://github.com/kddnewton/yarv/pull/85) - Implement `putnil`. -- [#90](https://github.com/kddnewton/yarv/pull/90) - Implement `opt_eq`. -- [#91](https://github.com/kddnewton/yarv/pull/91) - Implement `newarray`. -- [#93](https://github.com/kddnewton/yarv/pull/93) - Implement `opt_mod`. -- [#94](https://github.com/kddnewton/yarv/pull/94) - Implement `duparray`. -- [#96](https://github.com/kddnewton/yarv/pull/96) - Implement `opt_not`. -- [#101](https://github.com/kddnewton/yarv/pull/101) - Implement `concatarray`. -- [#98](https://github.com/kddnewton/yarv/pull/98) - Implement `swap`. -- [#106](https://github.com/kddnewton/yarv/pull/106) - Implement `newhash`. -- [#103](https://github.com/kddnewton/yarv/pull/103) - Implement `duphash`. -- [#117](https://github.com/kddnewton/yarv/pull/117) - Implement `opt_ge`. -- [#113](https://github.com/kddnewton/yarv/pull/113) - Implement `jump`. -- [#117](https://github.com/kddnewton/yarv/pull/117) - Implement `opt_gt`. -- [#117](https://github.com/kddnewton/yarv/pull/117) - Implement `opt_le`. -- [#117](https://github.com/kddnewton/yarv/pull/117) - Implement `opt_lt`. -- [#118](https://github.com/kddnewton/yarv/pull/118) - Implement `opt_str_freeze`. -- [#120](https://github.com/kddnewton/yarv/pull/120) - Implement `opt_aref_with`. -- [#122](https://github.com/kddnewton/yarv/pull/122) - Implement `branchnil`. -- [#124](https://github.com/kddnewton/yarv/pull/124) - Implement `branchif`. -- [#127](https://github.com/kddnewton/yarv/pull/127) - Add `--dump=insns` and `-e` options to `exe/yarv`. - -### Changed - -- [#123](https://github.com/kddnewton/yarv/pull/123) - Handle leading arguments to method calls and fix local argument indices. -- [#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`. -- [#127](https://github.com/kddnewton/yarv/pull/127) - Additionally track call data flags. -- [#130](https://github.com/kddnewton/yarv/pull/130) - Improve formatting on the website. - -## [0.2.0] - 2022-05-09 - -### Added - -- [#19](https://github.com/kddnewton/yarv/pull/19) - Add arm64 platform to Gemfile.lock for M1 macs. -- [#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. -- [#24](https://github.com/kddnewton/yarv/pull/24) - Implement `opt_str_uminus`. -- [#26](https://github.com/kddnewton/yarv/pull/26) - Implement `opt_minus`. -- [#30](https://github.com/kddnewton/yarv/pull/33) - Implement `putstring`. -- [#34](https://github.com/kddnewton/yarv/pull/34) - Implement `getglobal`. -- [#39](https://github.com/kddnewton/yarv/pull/39) - Implement `setglobal` and `dup`. -- [#42](https://github.com/kddnewton/yarv/pull/42) - Implement `opt_and`. -- [#46](https://github.com/kddnewton/yarv/pull/46) - Implement `opt_getinlinecache`, `opt_setinlinecache`, and `getconstant`. -- [#49](https://github.com/kddnewton/yarv/pull/49) - Implement `opt_empty_p`. -- [#51](https://github.com/kddnewton/yarv/pull/51) - Implement `pop`. -- [#54](https://github.com/kddnewton/yarv/pull/54) - Implement `opt_div`. -- [#57](https://github.com/kddnewton/yarv/pull/57) - Implement `opt_or`. -- [#58](https://github.com/kddnewton/yarv/pull/58) - Implement `opt_nil_p`. -- [#59](https://github.com/kddnewton/yarv/pull/59) - Implement `opt_aref`. -- [#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](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. -- [#67](https://github.com/kddnewton/yarv/pull/67) - Implement `opt_length`. -- [#69](https://github.com/kddnewton/yarv/pull/69) - Implement `putobject_INT2FIX_0_`. -- [#72](https://github.com/kddnewton/yarv/pull/72) - Implement `opt_succ`. -- [#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`. -- [#80](https://github.com/kddnewton/yarv/pull/80) - Implement `getlocal_WC_0` and `setlocal_WC_0`. Additionally begin tracking locals on the frame. -- [#83](https://github.com/kddnewton/yarv/pull/83) - Implement `putobject_INT2FIX_1_`. - -### Changed - -- [#27](https://github.com/kddnewton/yarv/pull/27) - Split up the test files and run the tests through `rake`. -- [#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. -- [#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. -- [#63](https://github.com/kddnewton/yarv/pull/63) - Run the spec GitHub action in Ruby 3.1. -- [#70](https://github.com/kddnewton/yarv/pull/70) - Fix the value passed into mspec for `__FILE__`. - -### Removed - -- [#83](https://github.com/kddnewton/yarv/pull/83) - Remove the top-level `YARV.emulate` method in favor of `YARV.compile(...).eval`. - -## [0.1.0] - 2022-05-09 - -### Added - -- 🎉 Initial release! 🎉 - -[unreleased]: https://github.com/kddnewton/yarv/compare/v0.3.0...HEAD -[0.3.0]: https://github.com/kddnewton/yarv/compare/v0.2.0...v0.3.0 -[0.2.0]: https://github.com/kddnewton/yarv/compare/v0.1.0...v0.2.0 -[0.1.0]: https://github.com/kddnewton/yarv/compare/002375...v0.1.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index b0c6bcf..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -- Using welcoming and inclusive language -- Being respectful of differing viewpoints and experiences -- Gracefully accepting constructive criticism -- Focusing on what is best for the community -- Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -- The use of sexualized language or imagery and unwelcome sexual attention or - advances -- Trolling, insulting/derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or electronic - address, without explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at kddnewton@gmail.com. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/GLOSSARY.md b/GLOSSARY.md deleted file mode 100644 index 968dda7..0000000 --- a/GLOSSARY.md +++ /dev/null @@ -1,194 +0,0 @@ -## abstract syntax tree - -An intermediate data structure created by a compiler that is used as a representation of the source code. - -## argc - -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.). - -## binding - -An object that wraps a [control frame](#control-frame) in the YARV virtual machine and retains its context for future use. - -## branch - -A place in a list of instructions where the next instruction to execute may no longer be the next instruction in sequence. - -## byte - -The byte is a unit of digital information that most commonly consists of eight bits. - -## bytecode - -A programming language consisting of simple, low-level instructions which are designed to be easy and fast to execute. - -## call data - -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`. - -## call site - -Any place in source code where a method is called. - -## call stack - -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. - -## calling convention - -An low-level scheme for how methods receive parameters from their caller and how they return a result. - -## catch table - -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`. - -## cd hash - -A Ruby hash used for handling optimized `case` statements. The keys are the conditions of `when` clauses in the `case` statement, -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: - -~~~ruby -case 1 -when 1 - puts "foo" -else - puts "bar" -end -~~~ - -## control frame - -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. - -## dispatch - -Calling a method. - -## execution context - -The global information available to the running Ruby process. This includes global variables, defined methods, constants, etc. - -## frame - -See [control frame](#control-frame). - -## instruction - -One unit of work for the virtual machine to execute. - -## instruction argument - -Objects encoded into the bytecode that are used by an instruction that are known at compile-time. - -## instruction operand - -See [instruction argument](#instruction-argument). - -## instruction sequence - -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: - -* `top` - the main instruction sequence executed when your program first starts running -* `method` - an instruction sequence representing the body of a method -* `block` - an instruction sequence representing the body of a block -* and many more - -## iseq - -See [instruction sequence](#instruction-sequence). - -## jump - -When the program counter is changed manually to dictate the next instruction for execution. - -## local - -A temporary variable that can only be read in the current [control frame](#control-frame) or its children. - -## local table - -A data structure that holds metadata about local variables and arguments declared within an instruction sequence. - -## nop - -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. - -## operand - -See [instruction argument](#instruction-argument). - -## opt - -Short for [optimization](#optimization). - -## optimization - -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. - -## pc - -See [program counter](#program-counter). - -## pop - -Remove and return a value from the top of a stack. - -## program counter - -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. - -## push - -Add a value to the top of a stack (for example a frame, an instruction sequence, etc.). - -## put - -See [push](#push). - -## receiver - -The object receiving a message/method call. - -## send - -See [dispatch](#dispatch). - -## source code - -The human-readable representation of the code to be executed. - -## sp - -See [stack pointer](#stack-pointer). - -## specialization - -See [optimization](#optimization). - -## stack - -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). - -## stack pointer - -A pointer to the next empty slot in the stack (i.e., the top). - -## tracepoint - -A publication/subscription system for virtual machine events. Users can create tracepoints to get notified when certain events occur. - -## value stack - -A data structure used to track return values, variables, and arguments. - -## virtual machine - -A software implementation of a computer. In the context of YARV, the virtual machine executes the [bytecode](#bytecode) that Ruby compiles. - -## vm - -See [virtual machine](#virtual-machine). - -## yarv - -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. diff --git a/Gemfile b/Gemfile deleted file mode 100644 index de708b3..0000000 --- a/Gemfile +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -gem "kramdown" -gem "rake" -gem "rouge" -gem "syntax_tree" -gem "test-unit" diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 1cba90b..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,30 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - kramdown (2.5.1) - rexml (>= 3.3.9) - power_assert (2.0.5) - prettier_print (1.2.1) - rake (13.3.0) - rexml (3.3.9) - rouge (4.5.2) - syntax_tree (6.2.0) - prettier_print (>= 1.2.0) - test-unit (3.6.8) - power_assert - -PLATFORMS - arm64-darwin-21 - arm64-darwin-22 - x86_64-darwin-21 - x86_64-linux - -DEPENDENCIES - kramdown - rake - rouge - syntax_tree - test-unit - -BUNDLED WITH - 2.3.6 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 2a8c765..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2021-present Kevin Newton - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index f9646ef..0000000 --- a/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# YARV - -[![Build Status](https://github.com/ruby-syntax-tree/yarv/workflows/Main/badge.svg)](https://github.com/ruby-syntax-tree/yarv/actions) - -This project's aim is to provide an educational window into how CRuby's virtual machine works under the hood. - -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. - -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`. - -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. - -## Getting started - -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. - -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. - -## Related content - -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: - -* http://www.atdot.net/yarv/yarvarch.ja.html - (Mar 2004) YARV design document -* https://i.loveruby.net/ja/rhg/book/ - (Jul 2004) Ruby source code complete explanation -* https://magazine.rubyist.net/articles/0006/0006-YarvManiacs.html - (May 2005) YARV maniacs -* http://graysoftinc.com/the-ruby-vm-interview/the-ruby-vm-serial-interview - (Feb 2007) The Ruby VM Serial Interview -* https://lifegoo.pluskid.org/upload/doc/yarv/yarv_iset.html - (Apr 2008) YARV instruction set -* https://patshaughnessy.net/2012/6/29/how-ruby-executes-your-code - (Jun 2012) How Ruby Executes Your Code (excerpt from _Ruby Under a Microscope_) -* http://www.atdot.net/~ko1/diary/201212.html#d1 - (Dec 2012) ko1's 2012 Ruby VM advent calendar -* https://qiita.com/sisshiki1969/items/3d25aa81a376eee2e7c2 - (Dec 2019) Ruby made with Rust -* https://iliabylich.github.io/2020/01/25/evaluating-ruby-in-ruby.html - (Jan 2020) Evaluating Ruby in Ruby - -## Contributing - -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. - -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). - -## License - -The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 3949e3c..0000000 --- a/Rakefile +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "rake/testtask" -require "syntax_tree/rake_tasks" - -Rake::TestTask.new(:test) do |t| - t.libs << "test" - t.test_files = FileList["test/**/*_test.rb"] -end - -source_files = FileList[%w[Gemfile Rakefile lib/**/*.rb test/**/*.rb]] -SyntaxTree::Rake::CheckTask.new(:"stree:check", source_files) -SyntaxTree::Rake::WriteTask.new(:"stree:write", source_files) - -task default: :test diff --git a/bin/console b/bin/console deleted file mode 100755 index db9ed85..0000000 --- a/bin/console +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -$:.unshift File.expand_path("../lib", __dir__) -require "yarv" - -require "irb" -IRB.start diff --git a/bin/doc b/bin/doc deleted file mode 100755 index 2a55e56..0000000 --- a/bin/doc +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require "bundler/setup" -require "erb" -require "fileutils" -require "kramdown" -require "stringio" -require "syntax_tree" - -filepaths = Dir[File.expand_path("../lib/yarv/insn/*.rb", __dir__)] \ - .reject { |filepath| filepath.end_with?("insn/insn.rb") } # Skip the abstract instruction. - -markdown = StringIO.new - -# For each YARV instruction file, we're going to yank out the comments. -filepaths.each do |filepath| - # Parse the source using SyntaxTree and grab all of the comments. - ast = SyntaxTree.parse(SyntaxTree.read(filepath)) - ast => { statements: { body: [*, SyntaxTree::ModuleDeclaration => mod] } } - mod => { constant: { constant: { value: "YARV" } }, bodystmt: { statements: { body: [SyntaxTree::VoidStmt, *comments, SyntaxTree::ClassDeclaration] } } } - - formatted_comments = comments.map { |comment| comment.value[2..-1] }.join("\n") - - # Extract the ruby code block from the comments, generate the instruction - # sequence for it, and then inject the commented sequence into the block - ruby_code_match = formatted_comments.match(/~~~ruby\n([\S\s]*)~~~/) - - if ruby_code_match.nil? - raise "Missing ruby example in #{filepath} doc comment" - else - ruby_code = ruby_code_match[1] - end - - disasm = RubyVM::InstructionSequence.new(ruby_code).disasm - disasm.gsub!(/^/, "# ") - - formatted_comments.gsub!(/^~~~\n$/, "\n" + disasm + "~~~\n") - - # Write the comments out in their own section. - markdown.puts(<<~MARKDOWN) - ## #{File.basename(filepath, ".rb")} - - #{formatted_comments} - MARKDOWN -end - -navigation = StringIO.new -filepaths.each do |filepath| - basename = File.basename(filepath, ".rb") - navigation.puts("#{basename}") -end -navigation.puts - -template = ERB.new(DATA.read) -FileUtils.mkdir_p("doc") - -body = Kramdown::Document.new(markdown.string, syntax_highlighter: :rouge).to_html -body = ERB.new(<<~HTML).result_with_hash(body: body, nav: navigation.string) - -
- <%= body %> -
-HTML - -File.write("doc/index.html", template.result_with_hash(body: body)) - -glossary = File.read(File.expand_path("../GLOSSARY.md", __dir__)) -navigation = StringIO.new - -glossary.each_line(chomp: true) do |line| - if line =~ /^## (.+)$/ - heading = $1 - navigation.puts("#{heading}") - end -end - -body = Kramdown::Document.new(glossary, syntax_highlighter: :rouge).to_html -body = ERB.new(<<~HTML).result_with_hash(body: body, nav: navigation.string) - -
- <%= body %> -
-HTML - -File.write("doc/glossary.html", template.result_with_hash(body: body)) - -__END__ - - - - - - YARV - - - - - - - - - - - - - -
- <%= body %> -
- - diff --git a/exe/yarv b/exe/yarv deleted file mode 100755 index 362a62c..0000000 --- a/exe/yarv +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require "bundler/setup" - -$:.unshift(File.expand_path("../lib", __dir__)) -require "yarv" - -sources = [] -dump_insns = false -dump_cfg = false -dump_dfg = false -dump_soy = false -execute = true - -until ARGV.empty? - arg = ARGV.shift - if arg.start_with?('-') - case arg - when "-e" - source = ARGV.shift - raise unless source - sources.push [source, "
", "-e"] - when "--dump=insns" - dump_insns = true - execute = false - when "--dump=cfg" - dump_cfg = true - execute = false - when "--dump=dfg" - dump_dfg = true - execute = false - when "--dump=soy" - dump_soy = true - execute = false - when "--help" - puts "Usage: #{$0} [options] [source files]" - puts "Options:" - puts " -e source Take a string as source code" - puts " --dump=insns Dump instructions" - puts " --dump=cfg Dump a control-flow-graph" - puts " --dump=dfg Dump a data-flow-graph" - puts " --dump=soy Dump a sea-of-YARV graph, in Mermaid format - see https://mermaid.live/" - puts " --help Show this help" - exit 0 - else - raise "Unknown argument #{arg}" - end - else - sources.push [File.read(arg), arg, File.expand_path(arg)] - end -end - -sources.each do |source, file, path| - compiled = YARV.compile(source, file, path) - - if dump_insns - puts compiled.disasm - end - - if dump_cfg || dump_dfg || dump_soy - compiled.all_iseqs.each do |iseq| - cfg = YARV::CFG.new(iseq) - puts cfg.disasm if dump_cfg - - if dump_dfg || dump_soy - dfg = YARV::DFG.new(cfg) - puts dfg.disasm if dump_dfg - end - - if dump_soy - soy = YARV::SOY.new(dfg) - puts soy.mermaid if dump_soy - end - end - end - - if execute - compiled.eval - end -end diff --git a/doc/favicon.svg b/favicon.svg similarity index 100% rename from doc/favicon.svg rename to favicon.svg diff --git a/doc/glossary.html b/glossary.html similarity index 100% rename from doc/glossary.html rename to glossary.html diff --git a/doc/highlight.css b/highlight.css similarity index 100% rename from doc/highlight.css rename to highlight.css diff --git a/index.html b/index.html new file mode 100644 index 0000000..e683385 --- /dev/null +++ b/index.html @@ -0,0 +1,2194 @@ + + + + + + YARV + + + + + + + + + + + + + +
+ +
+

adjuststack

+ +

Summary

+ +

adjuststack accepts a single integer argument and removes that many +elements from the top of the stack.

+ +

TracePoint

+ +

adjuststack cannot dispatch any TracePoint events.

+ +

Usage

+ +
x = [true]
+x[0] ||= nil
+x[0]
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(3,4)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] x@0
+# 0000 duparray                               [true]                    (   1)[Li]
+# 0002 setlocal_WC_0                          x@0
+# 0004 getlocal_WC_0                          x@0                       (   2)[Li]
+# 0006 putobject_INT2FIX_0_
+# 0007 dupn                                   2
+# 0009 opt_aref                               <calldata!mid:[], argc:1, ARGS_SIMPLE>[CcCr]
+# 0011 dup
+# 0012 branchif                               21
+# 0014 pop
+# 0015 putnil
+# 0016 opt_aset                               <calldata!mid:[]=, argc:2, ARGS_SIMPLE>[CcCr]
+# 0018 pop
+# 0019 jump                                   23
+# 0021 adjuststack                            3
+# 0023 getlocal_WC_0                          x@0                       (   3)[Li]
+# 0025 putobject_INT2FIX_0_
+# 0026 opt_aref                               <calldata!mid:[], argc:1, ARGS_SIMPLE>[CcCr]
+# 0028 leave
+
+ +

anytostring

+ +

Summary

+ +

anytostring ensures that the value on top of the stack is a string.

+ +

It pops two values off the stack. If the first value is a string it pushes it back on +the stack. If the first value is not a string, it uses Ruby’s built in string coercion +to coerce the second value to a string and then pushes that back on the stack.

+ +

This is used in conjunction with objtostring as a fallback for when an object’s to_s +method does not return a string

+ +

TracePoint

+ +

anytostring cannot dispatch any TracePoint events

+ +

Usage

+ +
"#{5}"
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,6)> (catch: FALSE)
+# 0000 putobject                              ""                        (   1)[Li]
+# 0002 putobject                              5
+# 0004 dup
+# 0005 objtostring                            <calldata!mid:to_s, argc:0, FCALL|ARGS_SIMPLE>
+# 0007 anytostring
+# 0008 concatstrings                          2
+# 0010 leave
+
+ +

branchif

+ +

Summary

+ +

branchif has one argument: the jump index. It pops one value off the stack: +the jump condition.

+ +

If the value popped off the stack is true, branchif jumps to +the jump index and continues executing there.

+ +

TracePoint

+ +

branchif does not dispatch any events.

+ +

Usage

+ +
x = true
+x ||= "foo"
+puts x
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(3,6)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] x@0
+# 0000 putobject                              true                      (   1)[Li]
+# 0002 setlocal_WC_0                          x@0
+# 0004 getlocal_WC_0                          x@0                       (   2)[Li]
+# 0006 dup
+# 0007 branchif                               15
+# 0009 pop
+# 0010 putstring                              "foo"
+# 0012 dup
+# 0013 setlocal_WC_0                          x@0
+# 0015 pop
+# 0016 putself                                                          (   3)[Li]
+# 0017 getlocal_WC_0                          x@0
+# 0019 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0021 leave
+
+ +

branchnil

+ +

Summary

+ +

branchnil has one argument: the jump index. It pops one value off the stack: +the jump condition.

+ +

If the value popped off the stack is nil, branchnil jumps to +the jump index and continues executing there.

+ +

TracePoint

+ +

There is no trace point for branchnil.

+ +

Usage

+ +
x = nil
+if x&.to_s
+  puts "hi"
+end
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(4,3)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] x@0
+# 0000 putnil                                                           (   1)[Li]
+# 0001 setlocal_WC_0                          x@0
+# 0003 getlocal_WC_0                          x@0                       (   2)[Li]
+# 0005 dup
+# 0006 branchnil                              10
+# 0008 opt_send_without_block                 <calldata!mid:to_s, argc:0, ARGS_SIMPLE>
+# 0010 branchunless                           18
+# 0012 putself                                                          (   3)[Li]
+# 0013 putstring                              "hi"
+# 0015 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0017 leave
+# 0018 putnil
+# 0019 leave
+
+ +

branchunless

+ +

Summary

+ +

branchunless has one argument, the jump index +and pops one value off the stack, the jump condition.

+ +

If the value popped off the stack is false or nil, +branchunless jumps to the jump index and continues executing there.

+ +

TracePoint

+ +

branchunless does not dispatch any events.

+ +

Usage

+ +
if 2 + 3
+  puts "foo"
+end
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(3,3)> (catch: FALSE)
+# 0000 putobject                              2                         (   1)[Li]
+# 0002 putobject                              3
+# 0004 opt_plus                               <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 branchunless                           14
+# 0008 putself                                                          (   2)[Li]
+# 0009 putstring                              "foo"
+# 0011 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0013 leave
+# 0014 putnil
+# 0015 leave
+
+ +

concatarray

+ +

Summary

+ +

concatarray concatenates the two Arrays on top of the stack.

+ +

It coerces the two objects at the top of the stack into Arrays by calling +to_a if necessary, and makes sure to dup the first Array if it was +already an Array, to avoid mutating it when concatenating.

+ +

TracePoint

+ +

concatarray can dispatch the line and call events.

+ +

Usage

+ +
[1, *2]
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,7)> (catch: FALSE)
+# 0000 duparray                               [1]                       (   1)[Li]
+# 0002 putobject                              2
+# 0004 concatarray
+# 0005 leave
+
+ +

concatstrings

+ +

Summary

+ +

concatstrings just pops a number of strings from the stack joins them together +into a single string and pushes that string back on the stack.

+ +

This does no coercion and so is always used in conjunction with objtostring +and anytostring to ensure the stack contents are always strings

+ +

TracePoint

+ +

concatstrings can dispatch the line and call events.

+ +

Usage

+ +
"#{5}"
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,6)> (catch: FALSE)
+# 0000 putobject                              ""                        (   1)[Li]
+# 0002 putobject                              5
+# 0004 dup
+# 0005 objtostring                            <calldata!mid:to_s, argc:0, FCALL|ARGS_SIMPLE>
+# 0007 anytostring
+# 0008 concatstrings                          2
+# 0010 leave
+
+ +

defined

+ +

Summary

+ +

defined checks if the top value of the stack is defined. If it is, it +pushes its value onto the stack. Otherwise it pushes nil.

+ +

TracePoint

+ +

defined cannot dispatch any TracePoint events.

+ +

Usage

+ +
defined?(x)
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,11)> (catch: FALSE)
+# 0000 putself                                                          (   1)[Li]
+# 0001 defined                                func, :x, "method"
+# 0005 leave
+
+ +

definemethod

+ +

Summary

+ +

definemethod defines a method on the class of the current value of self. +It accepts two arguments. The first is the name of the method being defined. +The second is the instruction sequence representing the body of the method.

+ +

TracePoint

+ +

definemethod does not dispatch any events.

+ +

Usage

+ +
def value = "value"
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,19)> (catch: FALSE)
+# 0000 definemethod                           :value, value             (   1)[Li]
+# 0003 putobject                              :value
+# 0005 leave
+# 
+# == disasm: #<ISeq:value@<compiled>:1 (1,0)-(1,19)> (catch: FALSE)
+# 0000 putstring                              "value"                   (   1)[Ca]
+# 0002 leave                                  [Re]
+
+ +

dup

+ +

Summary

+ +

dup copies the top value of the stack and pushes it onto the stack.

+ +

TracePoint

+ +

dup does not dispatch any events.

+ +

Usage

+ +
$global = 5
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,11)> (catch: FALSE)
+# 0000 putobject                              5                         (   1)[Li]
+# 0002 dup
+# 0003 setglobal                              :$global
+# 0005 leave
+
+ +

dup_hash

+ +

Summary

+ +

duphash pushes a hash onto the stack

+ +

TracePoint

+ +

duphash can dispatch the line event.

+ +

Usage

+ +
{ a: 1 }
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,8)> (catch: FALSE)
+# 0000 duphash                                {:a=>1}                   (   1)[Li]
+# 0002 leave
+
+ +

duparray

+ +

Summary

+ +

duparray copies a literal Array and pushes it onto the stack.

+ +

TracePoint

+ +

duparray can dispatch the line event.

+ +

Usage

+ +
[true]
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,6)> (catch: FALSE)
+# 0000 duparray                               [true]                    (   1)[Li]
+# 0002 leave
+
+ +

dupn

+ +

Summary

+ +

dupn duplicates the top n stack elements.

+ +

TracePoint

+ +

dupn does not dispatch any TracePoint events.

+ +

Usage

+ +
Object::X ||= true
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,18)> (catch: FALSE)
+# 0000 opt_getinlinecache                     9, <is:0>                 (   1)[Li]
+# 0003 putobject                              true
+# 0005 getconstant                            :Object
+# 0007 opt_setinlinecache                     <is:0>
+# 0009 dup
+# 0010 defined                                constant-from, :X, true
+# 0014 branchunless                           25
+# 0016 dup
+# 0017 putobject                              true
+# 0019 getconstant                            :X
+# 0021 dup
+# 0022 branchif                               32
+# 0024 pop
+# 0025 putobject                              true
+# 0027 dupn                                   2
+# 0029 swap
+# 0030 setconstant                            :X
+# 0032 swap
+# 0033 pop
+# 0034 leave
+
+ +

expandarray

+ +

Summary

+ +

expandarray looks at the top of the stack, and if the value is an array +it replaces it on the stack with num elements of the array, or nil if +the elements are missing.

+ +

TracePoint

+ +

expandarray does not dispatch any events.

+ +

Usage

+ +
x, = [true, false, nil]
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,23)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] x@0
+# 0000 duparray                               [true, false, nil]        (   1)[Li]
+# 0002 dup
+# 0003 expandarray                            1, 0
+# 0006 setlocal_WC_0                          x@0
+# 0008 leave
+
+ +

getconstant

+ +

Summary

+ +

getconstant performs a constant lookup and pushes the value of the +constant onto the stack.

+ +

TracePoint

+ +

getconstant does not dispatch any events.

+ +

Usage

+ +
Constant
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,8)> (catch: FALSE)
+# 0000 opt_getinlinecache                     9, <is:0>                 (   1)[Li]
+# 0003 putobject                              true
+# 0005 getconstant                            :Constant
+# 0007 opt_setinlinecache                     <is:0>
+# 0009 leave
+
+ +

getglobal

+ +

Summary

+ +

getglobal pushes the value of a global variables onto the stack.

+ +

TracePoint

+ +

getglobal does not dispatch any events.

+ +

Usage

+ +
$$
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,2)> (catch: FALSE)
+# 0000 getglobal                              :$$                       (   1)[Li]
+# 0002 leave
+
+ +

getlocal

+ +

Summary

+ +

getlocal fetches the value of a local variable from a frame determined by +the level and index arguments. The level is the number of frames back to +look and the index is the index in the local table. It pushes the value it +finds onto the stack.

+ +

TracePoint

+ +

getlocal does not dispatch any events.

+ +

Usage

+ +
value = 5
+tap { tap { value } }
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(2,21)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] value@0
+# 0000 putobject                              5                         (   1)[Li]
+# 0002 setlocal_WC_0                          value@0
+# 0004 putself                                                          (   2)[Li]
+# 0005 send                                   <calldata!mid:tap, argc:0, FCALL>, block in <compiled>
+# 0008 leave
+# 
+# == disasm: #<ISeq:block in <compiled>@<compiled>:2 (2,4)-(2,21)> (catch: FALSE)
+# 0000 putself                                                          (   2)[LiBc]
+# 0001 send                                   <calldata!mid:tap, argc:0, FCALL>, block (2 levels) in <compiled>
+# 0004 leave                                  [Br]
+# 
+# == disasm: #<ISeq:block (2 levels) in <compiled>@<compiled>:2 (2,10)-(2,19)> (catch: FALSE)
+# 0000 getlocal                               value@0, 2                (   2)[LiBc]
+# 0003 leave                                  [Br]
+
+ +

getlocal_wc_0

+ +

Summary

+ +

getlocal_WC_0 is a specialized version of the getlocal instruction. It +fetches the value of a local variable from the current frame determined by +the index given as its only argument.

+ +

TracePoint

+ +

getlocal_WC_0 does not dispatch any events.

+ +

Usage

+ +
value = 5
+value
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(2,5)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] value@0
+# 0000 putobject                              5                         (   1)[Li]
+# 0002 setlocal_WC_0                          value@0
+# 0004 getlocal_WC_0                          value@0                   (   2)[Li]
+# 0006 leave
+
+ +

getlocal_wc_1

+ +

Summary

+ +

getlocal_WC_1 is a specialized version of the getlocal instruction. It +fetches the value of a local variable from the parent frame determined by +the index given as its only argument.

+ +

TracePoint

+ +

getlocal_WC_1 does not dispatch any events.

+ +

Usage

+ +
value = 5
+self.then { value }
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(2,19)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] value@0
+# 0000 putobject                              5                         (   1)[Li]
+# 0002 setlocal_WC_0                          value@0
+# 0004 putself                                                          (   2)[Li]
+# 0005 send                                   <calldata!mid:then, argc:0, FCALL>, block in <compiled>
+# 0008 leave
+# 
+# == disasm: #<ISeq:block in <compiled>@<compiled>:2 (2,10)-(2,19)> (catch: FALSE)
+# 0000 getlocal_WC_1                          value@0                   (   2)[LiBc]
+# 0002 leave                                  [Br]
+
+ +

intern

+ +

Summary

+ +

intern converts top element from stack to a symbol.

+ +

TracePoint

+ +

There is no trace point for intern.

+ +

Usage

+ +
:"#{"foo"}"
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,11)> (catch: FALSE)
+# 0000 putstring                              "foo"                     (   1)[Li]
+# 0002 intern
+# 0003 leave
+
+ +

invokeblock

+ +

Summary

+ +

invokeblock invokes the block passed to a method during a yield.

+ +

TracePoint

+ +

Usage

+ +
def foo; yield; end
+
+# == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,19)> (catch: FALSE)
+# 0000 definemethod                           :foo, foo                 (   1)[Li]
+# 0003 putobject                              :foo
+# 0005 leave
+#
+# == disasm: #<ISeq:foo@-e:1 (1,0)-(1,19)> (catch: FALSE)
+# 0000 invokeblock                            <calldata!argc:0, ARGS_SIMPLE>(   1)[LiCa]
+# 0002 leave                                  [Re]
+# ~~~
+
+## jump
+
+### Summary
+
+`jump` has one argument, the jump index, which it uses to set the next
+instruction to execute.
+
+### TracePoint
+
+There is no trace point for `jump`.
+
+### Usage
+
+~~~ruby
+y = 0
+if y == 0
+  puts "0"
+else
+  puts "2"
+end
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(6,3)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] y@0
+# 0000 putobject_INT2FIX_0_                                             (   1)[Li]
+# 0001 setlocal_WC_0                          y@0
+# 0003 getlocal_WC_0                          y@0                       (   2)[Li]
+# 0005 putobject_INT2FIX_0_
+# 0006 opt_eq                                 <calldata!mid:==, argc:1, ARGS_SIMPLE>[CcCr]
+# 0008 branchunless                           16
+# 0010 putself                                                          (   3)[Li]
+# 0011 putstring                              "0"
+# 0013 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0015 leave                                                            (   5)
+# 0016 putself                                [Li]
+# 0017 putstring                              "2"
+# 0019 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0021 leave
+
+ +

leave

+ +

Summary

+ +

leave exits the current frame.

+ +

TracePoint

+ +

leave does not dispatch any events.

+ +

Usage

+ +
;;
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,2)> (catch: FALSE)
+# 0000 putnil                                                           (   1)
+# 0001 leave
+
+ +

newarray

+ +

Summary

+ +

newarray puts a new array initialized with size values from the stack.

+ +

TracePoint

+ +

newarray dispatches a line event.

+ +

Usage

+ +
["string"]
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,10)> (catch: FALSE)
+# 0000 putstring                              "string"                  (   1)[Li]
+# 0002 newarray                               1
+# 0004 leave
+
+ +

newhash

+ +

Summary

+ +

newhash puts a new hash onto the stack, using num elements from the +stack. num needs to be even.

+ +

TracePoint

+ +

newhash does not dispatch any events.

+ +

Usage

+ +
def foo(key, value)
+  { key => value }
+end
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(3,3)> (catch: FALSE)
+# 0000 definemethod                           :foo, foo                 (   1)[Li]
+# 0003 putobject                              :foo
+# 0005 leave
+# 
+# == disasm: #<ISeq:foo@<compiled>:1 (1,0)-(3,3)> (catch: FALSE)
+# local table (size: 2, argc: 2 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 2] key@0<Arg> [ 1] value@1<Arg>
+# 0000 getlocal_WC_0                          key@0                     (   2)[LiCa]
+# 0002 getlocal_WC_0                          value@1
+# 0004 newhash                                2
+# 0006 leave                                                            (   3)[Re]
+
+ +

newrange

+ +

Summary

+ +

newrange creates a Range. It takes one arguments, which is 0 if the end +is included or 1 if the end value is excluded.

+ +

TracePoint

+ +

newrange does not dispatch any events.

+ +

Usage

+ +
x = 0
+y = 1
+p (x..y), (x...y)
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(3,17)> (catch: FALSE)
+# local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 2] x@0        [ 1] y@1
+# 0000 putobject_INT2FIX_0_                                             (   1)[Li]
+# 0001 setlocal_WC_0                          x@0
+# 0003 putobject_INT2FIX_1_                                             (   2)[Li]
+# 0004 setlocal_WC_0                          y@1
+# 0006 putself                                                          (   3)[Li]
+# 0007 getlocal_WC_0                          x@0
+# 0009 getlocal_WC_0                          y@1
+# 0011 newrange                               0
+# 0013 getlocal_WC_0                          x@0
+# 0015 getlocal_WC_0                          y@1
+# 0017 newrange                               1
+# 0019 opt_send_without_block                 <calldata!mid:p, argc:2, FCALL|ARGS_SIMPLE>
+# 0021 leave
+
+ +

nop

+ +

Summary

+ +

nop is a no-operation instruction. It is used to pad the instruction +sequence so there is a place for other instructions to jump to.

+ +

TracePoint

+ +

nop does not dispatch any events.

+ +

Usage

+ +
raise rescue true
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,17)> (catch: TRUE)
+# == catch table
+# | catch type: rescue st: 0000 ed: 0003 sp: 0000 cont: 0004
+# | == disasm: #<ISeq:rescue in <compiled>@<compiled>:1 (1,6)-(1,17)> (catch: TRUE)
+# | local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# | [ 1] $!@0
+# | 0000 getlocal_WC_0                          $!@0                      (   1)
+# | 0002 putobject                              StandardError
+# | 0004 checkmatch                             3
+# | 0006 branchunless                           11
+# | 0008 putobject                              true
+# | 0010 leave
+# | 0011 getlocal_WC_0                          $!@0
+# | 0013 throw                                  0
+# | catch type: retry  st: 0003 ed: 0004 sp: 0000 cont: 0000
+# |------------------------------------------------------------------------
+# 0000 putself                                                          (   1)[Li]
+# 0001 opt_send_without_block                 <calldata!mid:raise, argc:0, FCALL|VCALL|ARGS_SIMPLE>
+# 0003 nop
+# 0004 leave
+
+ +

objtostring

+ +

Summary

+ +

objtostring pops a value from the stack, calls to_s on that value and then pushes +the result back to the stack.

+ +

It has fast paths for String, Symbol, Module, Class, Nil, True, False & Number. +For everything else it calls to_s

+ +

TracePoint

+ +

objtostring cannot dispatch any TracePoint events.

+ +

Usage

+ +
"#{5}"
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,6)> (catch: FALSE)
+# 0000 putobject                              ""                        (   1)[Li]
+# 0002 putobject                              5
+# 0004 dup
+# 0005 objtostring                            <calldata!mid:to_s, argc:0, FCALL|ARGS_SIMPLE>
+# 0007 anytostring
+# 0008 concatstrings                          2
+# 0010 leave
+
+ +

opt_and

+ +

Summary

+ +

opt_and is a specialization of the opt_send_without_block instruction +that occurs when the & operator is used. In CRuby, there are fast paths +for if both operands are integers.

+ +

TracePoint

+ +

opt_and can dispatch both the c_call and c_return events.

+ +

Usage

+ +
2 & 3
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
+# 0000 putobject                              2                         (   1)[Li]
+# 0002 putobject                              3
+# 0004 opt_and                                <calldata!mid:&, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_aref

+ +

Summary

+ +

opt_aref is a specialization of the opt_send_without_block instruction +that occurs when the [] operator is used. In CRuby, there are fast paths +if the receiver is an integer, array, or hash.

+ +

TracePoint

+ +

opt_aref can dispatch both the c_call and c_return events.

+ +

Usage

+ +
7[2]
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,4)> (catch: FALSE)
+# 0000 putobject                              7                         (   1)[Li]
+# 0002 putobject                              2
+# 0004 opt_aref                               <calldata!mid:[], argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_aref_with

+ +

Summary

+ +

opt_aref_with is a specialization of the opt_aref instruction that +occurs when the [] operator is used with a string argument known at +compile time. In CRuby, there are fast paths if the receiver is a hash.

+ +

TracePoint

+ +

opt_aref_with does not dispatch any events.

+ +

Usage

+ +
{ 'test' => true }['test']
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,26)> (catch: FALSE)
+# 0000 duphash                                {"test"=>true}            (   1)[Li]
+# 0002 opt_aref_with                          "test", <calldata!mid:[], argc:1, ARGS_SIMPLE>
+# 0005 leave
+
+ +

opt_aset

+ +

Summary

+ +

opt_aset is an instruction for setting the hash value by the key in recv[obj] = set format

+ +

TracePoint

+ +

There is no trace point for opt_aset.

+ +

Usage

+ +
{}[:key] = value
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,16)> (catch: FALSE)
+# 0000 putnil                                                           (   1)[Li]
+# 0001 newhash                                0
+# 0003 putobject                              :key
+# 0005 putself
+# 0006 opt_send_without_block                 <calldata!mid:value, argc:0, FCALL|VCALL|ARGS_SIMPLE>
+# 0008 setn                                   3
+# 0010 opt_aset                               <calldata!mid:[]=, argc:2, ARGS_SIMPLE>[CcCr]
+# 0012 pop
+# 0013 leave
+
+ +

opt_aset_with

+ +

Summary

+ +

opt_aset_with is an instruction for setting the hash value by the known +string key in the recv[obj] = set format.

+ +

TracePoint

+ +

There is no trace point for opt_aset_with.

+ +

Usage

+ +
{}["key"] = value
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,17)> (catch: FALSE)
+# 0000 newhash                                0                         (   1)[Li]
+# 0002 putself
+# 0003 opt_send_without_block                 <calldata!mid:value, argc:0, FCALL|VCALL|ARGS_SIMPLE>
+# 0005 swap
+# 0006 topn                                   1
+# 0008 opt_aset_with                          "key", <calldata!mid:[]=, argc:2, ARGS_SIMPLE>
+# 0011 pop
+# 0012 leave
+
+ +

opt_case_dispatch

+ +

Summary

+ +

opt_case_dispatch is a branch instruction that moves the control flow for +case statements.

+ +

It has two arguments: the cdhash and an else_offset index. It pops one value off +the stack: a hash key. opt_case_dispatch looks up the key in the cdhash +and jumps to the corresponding index value, if there is one. +If there is no value in the cdhash, opt_case_dispatch jumps to the else_offset index.

+ +

The cdhash is a Ruby hash used for handling optimized case statements. +The keys are the conditions of when clauses in the case statement, +and the values are the labels to which to jump. This optimization can be +applied only when the keys can be directly compared.

+ +

TracePoint

+ +

There is no trace point for opt_case_dispatch.

+ +

Usage

+ +
case 1
+when 1
+  puts "foo"
+else
+  puts "bar"
+end
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,49)> (catch: FALSE)
+# 0000 putobject_INT2FIX_0_                                             (   1)[Li]
+# 0001 dup
+# 0002 opt_case_dispatch                      <cdhash>, 12
+# 0005 putobject_INT2FIX_1_
+# 0006 topn                                   1
+# 0008 opt_send_without_block                 <calldata!mid:===, argc:1, FCALL|ARGS_SIMPLE>
+# 0010 branchif                               19
+# 0012 pop
+# 0013 putself
+# 0014 putstring                              "bar"
+# 0016 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0018 leave
+# 0019 pop
+# 0020 putself
+# 0021 putstring                              "foo"
+# 0023 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0025 leave
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(6,3)> (catch: FALSE)
+# 0000 putobject_INT2FIX_1_                                             (   1)[Li]
+# 0001 dup
+# 0002 opt_case_dispatch                      <cdhash>, 12
+# 0005 putobject_INT2FIX_1_                                             (   2)
+# 0006 topn                                   1
+# 0008 opt_send_without_block                 <calldata!mid:===, argc:1, FCALL|ARGS_SIMPLE>
+# 0010 branchif                               19
+# 0012 pop                                                              (   5)
+# 0013 putself                                [Li]
+# 0014 putstring                              "bar"
+# 0016 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0018 leave
+# 0019 pop                                                              (   2)
+# 0020 putself                                                          (   3)[Li]
+# 0021 putstring                              "foo"
+# 0023 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0025 leave                                                            (   5)
+
+ +

opt_div

+ +

Summary

+ +

opt_div is a specialization of the opt_send_without_block instruction +that occurs when the / operator is used. In CRuby, there are fast paths +for if both operands are integers, or if both operands are floats.

+ +

TracePoint

+ +

opt_div can dispatch both the c_call and c_return events.

+ +

Usage

+ +
2 / 3
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
+# 0000 putobject                              2                         (   1)[Li]
+# 0002 putobject                              3
+# 0004 opt_div                                <calldata!mid:/, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_empty_p

+ +

Summary

+ +

opt_empty_p is an optimization applied when the method empty? is called +on a String, Array or a Hash. This optimization can be applied because Ruby +knows how to calculate the length of these objects using internal C macros.

+ +

TracePoint

+ +

opt_empty_p can dispatch c_call and c_return events.

+ +

Usage

+ +
"".empty?
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,9)> (catch: FALSE)
+# 0000 putstring                              ""                        (   1)[Li]
+# 0002 opt_empty_p                            <calldata!mid:empty?, argc:0, ARGS_SIMPLE>[CcCr]
+# 0004 leave
+
+ +

opt_eq

+ +

Summary

+ +

opt_eq is a specialization of the opt_send_without_block instruction +that occurs when the == operator is used. Fast paths exist within CRuby when +both operands are integers, floats, symbols or strings.

+ +

TracePoint

+ +

opt_eq can dispatch both the c_call and c_return events.

+ +

Usage

+ +
2 == 2
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,6)> (catch: FALSE)
+# 0000 putobject                              2                         (   1)[Li]
+# 0002 putobject                              2
+# 0004 opt_eq                                 <calldata!mid:==, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_ge

+ +

Summary

+ +

opt_ge is a specialization of the opt_send_without_block instruction +that occurs when the >= operator is used. Fast paths exist within CRuby when +both operands are integers or floats.

+ +

TracePoint

+ +

opt_ge can dispatch both the c_call and c_return events.

+ +

Usage

+ +
4 >= 3
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,6)> (catch: FALSE)
+# 0000 putobject                              4                         (   1)[Li]
+# 0002 putobject                              3
+# 0004 opt_ge                                 <calldata!mid:>=, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_getinlinecache

+ +

Summary

+ +

opt_getinlinecache is a wrapper around a series of getconstant +instructions that allows skipping past them if the inline cache is currently +set.

+ +

TracePoint

+ +

opt_getinlinecache does not dispatch any events.

+ +

Usage

+ +
Constant
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,8)> (catch: FALSE)
+# 0000 opt_getinlinecache                     9, <is:0>                 (   1)[Li]
+# 0003 putobject                              true
+# 0005 getconstant                            :Constant
+# 0007 opt_setinlinecache                     <is:0>
+# 0009 leave
+
+ +

opt_gt

+ +

Summary

+ +

opt_gt is a specialization of the opt_send_without_block instruction +that occurs when the > operator is used. Fast paths exist within CRuby when +both operands are integers or floats.

+ +

TracePoint

+ +

opt_gt can dispatch both the c_call and c_return events.

+ +

Usage

+ +
4 > 3
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
+# 0000 putobject                              4                         (   1)[Li]
+# 0002 putobject                              3
+# 0004 opt_gt                                 <calldata!mid:>, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_le

+ +

Summary

+ +

opt_le is a specialization of the opt_send_without_block instruction +that occurs when the <= operator is used. Fast paths exist within CRuby when +both operands are integers or floats.

+ +

TracePoint

+ +

opt_le can dispatch both the c_call and c_return events.

+ +

Usage

+ +
3 <= 4
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,6)> (catch: FALSE)
+# 0000 putobject                              3                         (   1)[Li]
+# 0002 putobject                              4
+# 0004 opt_le                                 <calldata!mid:<=, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_length

+ +

Summary

+ +

opt_length is a specialization of opt_send_without_block, when the +length method is called on a Ruby type with a known size. In CRuby there +are fast paths when the receiver is either a String, Hash or Array.

+ +

TracePoint

+ +

opt_length can dispatch c_call and c_return events.

+ +

Usage

+ +
"".length
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,9)> (catch: FALSE)
+# 0000 putstring                              ""                        (   1)[Li]
+# 0002 opt_length                             <calldata!mid:length, argc:0, ARGS_SIMPLE>[CcCr]
+# 0004 leave
+
+ +

opt_lt

+ +

Summary

+ +

opt_lt is a specialization of the opt_send_without_block instruction +that occurs when the < operator is used. Fast paths exist within CRuby when +both operands are integers or floats.

+ +

TracePoint

+ +

opt_lt can dispatch both the c_call and c_return events.

+ +

Usage

+ +
3 < 4
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
+# 0000 putobject                              3                         (   1)[Li]
+# 0002 putobject                              4
+# 0004 opt_lt                                 <calldata!mid:<, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_ltlt

+ +

Summary

+ +

opt_ltlt is a specialization of the opt_send_without_block instruction +that occurs when the << operator is used. Fast paths exists when the +receiver is either a String or an Array

+ +

TracePoint

+ +

opt_ltlt can dispatch both the c_call and c_return events.

+ +

Usage

+ +
"" << 2
+
+# == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,7)> (catch: FALSE)
+# 0000 putstring                              ""                        (   1)[Li]
+# 0002 putobject                              2
+# 0004 opt_ltlt                               <calldata!mid:<<, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,7)> (catch: FALSE)
+# 0000 putstring                              ""                        (   1)[Li]
+# 0002 putobject                              2
+# 0004 opt_ltlt                               <calldata!mid:<<, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_minus

+ +

Summary

+ +

opt_minus is a specialization of the opt_send_without_block instruction +that occurs when the - operator is used. In CRuby, there are fast paths +for if both operands are integers or both operands are floats.

+ +

TracePoint

+ +

opt_minus can dispatch both the c_call and c_return events.

+ +

Usage

+ +
3 - 2
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
+# 0000 putobject                              3                         (   1)[Li]
+# 0002 putobject                              2
+# 0004 opt_minus                              <calldata!mid:-, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_mod

+ +

Summary

+ +

opt_mod is a specialization of the opt_send_without_block instruction +that occurs when the % operator is used. In CRuby, there are fast paths +for if both operands are integers or both operands are floats.

+ +

TracePoint

+ +

opt_eq can dispatch both the c_call and c_return events.

+ +

Usage

+ +
4 % 2
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
+# 0000 putobject                              4                         (   1)[Li]
+# 0002 putobject                              2
+# 0004 opt_mod                                <calldata!mid:%, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_mult

+ +

Summary

+ +

opt_mult is a specialization of the opt_send_without_block instruction +that occurs when the * operator is used. In CRuby, there are fast paths +for if both operands are integers or floats.

+ +

TracePoint

+ +

opt_mult can dispatch both the c_call and c_return events.

+ +

Usage

+ +
3 * 2
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
+# 0000 putobject                              3                         (   1)[Li]
+# 0002 putobject                              2
+# 0004 opt_mult                               <calldata!mid:*, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_neq

+ +

Summary

+ +

opt_neq is an optimisation that tests whether two values at the top of +the stack are not equal by testing their equality and performing a logical +NOT on the result.

+ +

This allows opt_neq to use the fast paths optimized in opt_eq when both +operands are Integers, Floats, Symbols or Strings.

+ +

TracePoint

+ +

opt_neq can dispatch both the c_call and c_return events.

+ +

Usage

+ +
2 != 2
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,6)> (catch: FALSE)
+# 0000 putobject                              2                         (   1)[Li]
+# 0002 putobject                              2
+# 0004 opt_neq                                <calldata!mid:==, argc:1, ARGS_SIMPLE>, <calldata!mid:!=, argc:1, ARGS_SIMPLE>[CcCr]
+# 0007 leave
+
+ +

opt_newarray_max

+ +

Summary

+ +

opt_newarray_max is an instruction that represents calling max on an +array literal. It is used to optimize quick comparisons of array elements.

+ +

TracePoint

+ +

opt_newarray_max does not dispatch any events.

+ +

Usage

+ +
[1, x = 2].max
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,14)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] x@0
+# 0000 putobject_INT2FIX_1_                                             (   1)[Li]
+# 0001 putobject                              2
+# 0003 dup
+# 0004 setlocal_WC_0                          x@0
+# 0006 opt_newarray_max                       2
+# 0008 leave
+
+ +

opt_newarray_min

+ +

Summary

+ +

opt_newarray_min is an instruction that represents calling min on an +array literal. It is used to optimize quick comparisons of array elements.

+ +

TracePoint

+ +

opt_newarray_min does not dispatch any events.

+ +

Usage

+ +
[1, x = 2].min
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,14)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] x@0
+# 0000 putobject_INT2FIX_1_                                             (   1)[Li]
+# 0001 putobject                              2
+# 0003 dup
+# 0004 setlocal_WC_0                          x@0
+# 0006 opt_newarray_min                       2
+# 0008 leave
+
+ +

opt_nil_p

+ +

Summary

+ +

opt_nil_p is an optimization applied when the method nil? is called. It +returns true immediately when the receiver is nil and defers to the nil? +method in other cases

+ +

TracePoint

+ +

opt_nil_p can dispatch c_call and c_return events.

+ +

Usage

+ +
"".nil?
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,7)> (catch: FALSE)
+# 0000 putstring                              ""                        (   1)[Li]
+# 0002 opt_nil_p                              <calldata!mid:nil?, argc:0, ARGS_SIMPLE>[CcCr]
+# 0004 leave
+
+ +

opt_not

+ +

Summary

+ +

opt_not negates the value on top of the stack.

+ +

TracePoint

+ +

opt_not can dispatch both the c_call and c_return events.

+ +

Usage

+ +
!true
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
+# 0000 putobject                              true                      (   1)[Li]
+# 0002 opt_not                                <calldata!mid:!, argc:0, ARGS_SIMPLE>[CcCr]
+# 0004 leave
+
+ +

opt_or

+ +

Summary

+ +

opt_or is a specialization of the opt_send_without_block instruction +that occurs when the | operator is used. In CRuby, there are fast paths +for if both operands are integers.

+ +

TracePoint

+ +

opt_or can dispatch both the c_call and c_return events.

+ +

Usage

+ +
2 | 3
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
+# 0000 putobject                              2                         (   1)[Li]
+# 0002 putobject                              3
+# 0004 opt_or                                 <calldata!mid:|, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_plus

+ +

Summary

+ +

opt_plus is a specialization of the opt_send_without_block instruction +that occurs when the + operator is used. In CRuby, there are fast paths +for if both operands are integers, floats, strings, or arrays.

+ +

TracePoint

+ +

opt_plus can dispatch both the c_call and c_return events.

+ +

Usage

+ +
2 + 3
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
+# 0000 putobject                              2                         (   1)[Li]
+# 0002 putobject                              3
+# 0004 opt_plus                               <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_regexpmatch2

+ +

Summary

+ +

opt_regexpmatch2 is a specialization of the opt_send_without_block +instruction that occurs when the =~ operator is used.

+ +

TracePoint

+ +

opt_regexpmatch2 can dispatch both the c_call and c_return events.

+ +

Usage

+ +
/a/ =~ "a"
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,10)> (catch: FALSE)
+# 0000 putobject                              /a/                       (   1)[Li]
+# 0002 putstring                              "a"
+# 0004 opt_regexpmatch2                       <calldata!mid:=~, argc:1, ARGS_SIMPLE>[CcCr]
+# 0006 leave
+
+ +

opt_send_without_block

+ +

Summary

+ +

opt_send_without_block is a specialization of the send instruction that +occurs when a method is being called without a block.

+ +

TracePoint

+ +

opt_send_without_block does not dispatch any events.

+ +

Usage

+ +
puts "Hello, world!"
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,20)> (catch: FALSE)
+# 0000 putself                                                          (   1)[Li]
+# 0001 putstring                              "Hello, world!"
+# 0003 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0005 leave
+
+ +

opt_setinlinecache

+ +

Summary

+ +

opt_setinlinecache is the final instruction after a series of +getconstant instructions that populates the inline cache associated with +an opt_getinlinecache instruction.

+ +

TracePoint

+ +

opt_setinlinecache does not dispatch any events.

+ +

Usage

+ +
Constant
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,8)> (catch: FALSE)
+# 0000 opt_getinlinecache                     9, <is:0>                 (   1)[Li]
+# 0003 putobject                              true
+# 0005 getconstant                            :Constant
+# 0007 opt_setinlinecache                     <is:0>
+# 0009 leave
+
+ +

opt_size

+ +

Summary

+ +

opt_size is a specialization of opt_send_without_block, when the +size method is called on a Ruby type with a known size. In CRuby there +are fast paths when the receiver is either a String, Hash or Array.

+ +

TracePoint

+ +

opt_size can dispatch c_call and c_return events.

+ +

Usage

+ +
"".size
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,7)> (catch: FALSE)
+# 0000 putstring                              ""                        (   1)[Li]
+# 0002 opt_size                               <calldata!mid:size, argc:0, ARGS_SIMPLE>[CcCr]
+# 0004 leave
+
+ +

opt_str_freeze

+ +

Summary

+ +

opt_str_freeze pushes a frozen known string value with no interpolation +onto the stack.

+ +

TracePoint

+ +

opt_str_freeze does not dispatch any events.

+ +

Usage

+ +
"hello".freeze
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,14)> (catch: FALSE)
+# 0000 opt_str_freeze                         "hello", <calldata!mid:freeze, argc:0, ARGS_SIMPLE>(   1)[Li]
+# 0003 leave
+
+ +

opt_str_uminus

+ +

Summary

+ +

opt_str_uminus pushes a frozen known string value with no interpolation +onto the stack.

+ +

TracePoint

+ +

opt_str_uminus does not dispatch any events.

+ +

Usage

+ +
-"string"
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,9)> (catch: FALSE)
+# 0000 opt_str_uminus                         "string", <calldata!mid:-@, argc:0, ARGS_SIMPLE>(   1)[Li]
+# 0003 leave
+
+ +

opt_succ

+ +

Summary

+ +

opt_succ is a specialization of the opt_send_without_block instruction +when the method being called is succ. Fast paths exist within CRuby when +the receiver is either a String or a Fixnum.

+ +

TracePoint

+ +

opt_succ can dispatch c_call and c_return events.

+ +

Usage

+ +
"".succ
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,7)> (catch: FALSE)
+# 0000 putstring                              ""                        (   1)[Li]
+# 0002 opt_succ                               <calldata!mid:succ, argc:0, ARGS_SIMPLE>[CcCr]
+# 0004 leave
+
+ +

pop

+ +

Summary

+ +

pop pops the top value off the stack.

+ +

TracePoint

+ +

pop does not dispatch any events.

+ +

Usage

+ +
a ||= 2
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,7)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] a@0
+# 0000 getlocal_WC_0                          a@0                       (   1)[Li]
+# 0002 dup
+# 0003 branchif                               11
+# 0005 pop
+# 0006 putobject                              2
+# 0008 dup
+# 0009 setlocal_WC_0                          a@0
+# 0011 leave
+
+ +

putnil

+ +

Summary

+ +

putnil pushes a global nil object onto the stack.

+ +

TracePoint

+ +

putnil can dispatch the line event.

+ +

Usage

+ +
nil
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,3)> (catch: FALSE)
+# 0000 putnil                                                           (   1)[Li]
+# 0001 leave
+
+ +

putobject

+ +

Summary

+ +

putobject pushes a known value onto the stack.

+ +

TracePoint

+ +

putobject can dispatch the line event.

+ +

Usage

+ +
5
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,1)> (catch: FALSE)
+# 0000 putobject                              5                         (   1)[Li]
+# 0002 leave
+
+ +

putobject_int2fix_0

+ +

Summary

+ +

putobject_INT2FIX_0_ pushes 0 on the stack. +It is a specialized instruction resulting from the operand +unification optimization. It is the equivalent to putobject 0.

+ +

TracePoint

+ +

putobject can dispatch the line event.

+ +

Usage

+ +
0
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,1)> (catch: FALSE)
+# 0000 putobject_INT2FIX_0_                                             (   1)[Li]
+# 0001 leave
+
+ +

putobject_int2fix_1

+ +

Summary

+ +

putobject_INT2FIX_1_ pushes 1 on the stack. +It is a specialized instruction resulting from the operand +unification optimization. It is the equivalent to putobject 1.

+ +

TracePoint

+ +

putobject can dispatch the line event.

+ +

Usage

+ +
1
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,1)> (catch: FALSE)
+# 0000 putobject_INT2FIX_1_                                             (   1)[Li]
+# 0001 leave
+
+ +

putself

+ +

Summary

+ +

putself pushes the current value of self onto the stack.

+ +

TracePoint

+ +

putself can dispatch the line event.

+ +

Usage

+ +
puts "Hello, world!"
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,20)> (catch: FALSE)
+# 0000 putself                                                          (   1)[Li]
+# 0001 putstring                              "Hello, world!"
+# 0003 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0005 leave
+
+ +

putstring

+ +

Summary

+ +

putstring pushes a string literal onto the stack.

+ +

TracePoint

+ +

putstring can dispatch the line event.

+ +

Usage

+ +
"foo"
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
+# 0000 putstring                              "foo"                     (   1)[Li]
+# 0002 leave
+
+ +

send

+ +

Summary

+ +

send invokes a method with a block.

+ +

TracePoint

+ +

send does not dispatch any events.

+ +

Usage

+ +
"hello".tap { |i| p i }
+
+# == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,23)> (catch: FALSE)
+# 0000 putstring                              "hello"                   (   1)[Li]
+# 0002 send                                   <calldata!mid:tap, argc:0>, block in <main>
+# 0005 leave
+#
+# == disasm: #<ISeq:block in <main>@-e:1 (1,12)-(1,23)> (catch: FALSE)
+# local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] i@0<Arg>
+# 0000 putself                                                          (   1)[LiBc]
+# 0001 getlocal_WC_0                          i@0
+# 0003 opt_send_without_block                 <calldata!mid:p, argc:1, FCALL|ARGS_SIMPLE>
+# 0005 leave                                  [Br]
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,23)> (catch: FALSE)
+# 0000 putstring                              "hello"                   (   1)[Li]
+# 0002 send                                   <calldata!mid:tap, argc:0>, block in <compiled>
+# 0005 leave
+# 
+# == disasm: #<ISeq:block in <compiled>@<compiled>:1 (1,12)-(1,23)> (catch: FALSE)
+# local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] i@0<Arg>
+# 0000 putself                                                          (   1)[LiBc]
+# 0001 getlocal_WC_0                          i@0
+# 0003 opt_send_without_block                 <calldata!mid:p, argc:1, FCALL|ARGS_SIMPLE>
+# 0005 leave                                  [Br]
+
+ +

setglobal

+ +

Summary

+ +

setglobal sets the value of a global variable.

+ +

TracePoint

+ +

setglobal does not dispatch any events.

+ +

Usage

+ +
$global = 5
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,11)> (catch: FALSE)
+# 0000 putobject                              5                         (   1)[Li]
+# 0002 dup
+# 0003 setglobal                              :$global
+# 0005 leave
+
+ +

setlocal

+ +

Summary

+ +

setlocal sets the value of a local variable on a frame determined by the +level and index arguments. The level is the number of frames back to +look and the index is the index in the local table. It pops the value it is +setting off the stack.

+ +

TracePoint

+ +

setlocal does not dispatch any events.

+ +

Usage

+ +
value = 5
+tap { tap { value = 10 } }
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(2,26)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] value@0
+# 0000 putobject                              5                         (   1)[Li]
+# 0002 setlocal_WC_0                          value@0
+# 0004 putself                                                          (   2)[Li]
+# 0005 send                                   <calldata!mid:tap, argc:0, FCALL>, block in <compiled>
+# 0008 leave
+# 
+# == disasm: #<ISeq:block in <compiled>@<compiled>:2 (2,4)-(2,26)> (catch: FALSE)
+# 0000 putself                                                          (   2)[LiBc]
+# 0001 send                                   <calldata!mid:tap, argc:0, FCALL>, block (2 levels) in <compiled>
+# 0004 leave                                  [Br]
+# 
+# == disasm: #<ISeq:block (2 levels) in <compiled>@<compiled>:2 (2,10)-(2,24)> (catch: FALSE)
+# 0000 putobject                              10                        (   2)[LiBc]
+# 0002 dup
+# 0003 setlocal                               value@0, 2
+# 0006 leave                                  [Br]
+
+ +

setlocal_wc_0

+ +

Summary

+ +

setlocal_WC_0 is a specialized version of the setlocal instruction. It +sets the value of a local variable on the current frame to the value at the +top of the stack as determined by the index given as its only argument.

+ +

TracePoint

+ +

setlocal_WC_0 does not dispatch any events.

+ +

Usage

+ +
value = 5
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,9)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] value@0
+# 0000 putobject                              5                         (   1)[Li]
+# 0002 dup
+# 0003 setlocal_WC_0                          value@0
+# 0005 leave
+
+ +

setlocal_wc_1

+ +

Summary

+ +

setlocal_WC_1 is a specialized version of the setlocal instruction. It +sets the value of a local variable on the parent frame to the value at the +top of the stack as determined by the index given as its only argument.

+ +

TracePoint

+ +

setlocal_WC_1 does not dispatch any events.

+ +

Usage

+ +
value = 5
+self.then { value = 10 }
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(2,24)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] value@0
+# 0000 putobject                              5                         (   1)[Li]
+# 0002 setlocal_WC_0                          value@0
+# 0004 putself                                                          (   2)[Li]
+# 0005 send                                   <calldata!mid:then, argc:0, FCALL>, block in <compiled>
+# 0008 leave
+# 
+# == disasm: #<ISeq:block in <compiled>@<compiled>:2 (2,10)-(2,24)> (catch: FALSE)
+# 0000 putobject                              10                        (   2)[LiBc]
+# 0002 dup
+# 0003 setlocal_WC_1                          value@0
+# 0005 leave                                  [Br]
+
+ +

setn

+ +

Summary

+ +

setn is an instruction for set Nth stack entry to stack top

+ +

TracePoint

+ +

# setn does not dispatch any events.

+ +

Usage

+ +
{}[:key] = 'val'
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,16)> (catch: FALSE)
+# 0000 putnil                                                           (   1)[Li]
+# 0001 newhash                                0
+# 0003 putobject                              :key
+# 0005 putstring                              "val"
+# 0007 setn                                   3
+# 0009 opt_aset                               <calldata!mid:[]=, argc:2, ARGS_SIMPLE>[CcCr]
+# 0011 pop
+# 0012 leave
+
+ +

splatarray

+ +

Summary

+ +

splatarray calls to_a on an array to splat.

+ +

It coerces the array object at the top of the stack into Array by calling +to_a. It pushes a duplicate of the array if there is a flag, and the original +array, if there isn’t one.

+ +

TracePoint

+ +

splayarray does not dispatch any events.

+ +

Usage

+ +
x = *(5)
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,8)> (catch: FALSE)
+# local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# [ 1] x@0
+# 0000 putobject                              5                         (   1)[Li]
+# 0002 splatarray                             true
+# 0004 dup
+# 0005 setlocal_WC_0                          x@0
+# 0007 leave
+
+ +

swap

+ +

Summary

+ +

swap swaps the top two elements in the stack.

+ +

TracePoint

+ +

swap does not dispatch any events.

+ +

Usage

+ +
!!defined?([[]])
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,16)> (catch: TRUE)
+# == catch table
+# | catch type: rescue st: 0001 ed: 0003 sp: 0000 cont: 0005
+# | == disasm: #<ISeq:defined guard in <compiled>@<compiled>:0 (0,0)-(-1,-1)> (catch: FALSE)
+# | local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
+# | [ 1] $!@0
+# | 0000 putnil
+# | 0001 leave
+# |------------------------------------------------------------------------
+# 0000 putnil                                                           (   1)[Li]
+# 0001 putobject                              "expression"
+# 0003 swap
+# 0004 pop
+# 0005 opt_not                                <calldata!mid:!, argc:0, ARGS_SIMPLE>[CcCr]
+# 0007 opt_not                                <calldata!mid:!, argc:0, ARGS_SIMPLE>[CcCr]
+# 0009 leave
+
+ +

topn

+ +

Summary

+ +

topn has one argument: n. It gets the nth element from the top of the +stack and pushes it on the stack.

+ +

TracePoint

+ +

topn does not dispatch any events.

+ +

Usage

+ +
case 3
+when 1..5
+  puts "foo"
+end
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,36)> (catch: FALSE)
+# 0000 putobject                              3                         (   1)[Li]
+# 0002 putobject                              1..5
+# 0004 topn                                   1
+# 0006 opt_send_without_block                 <calldata!mid:===, argc:1, FCALL|ARGS_SIMPLE>
+# 0008 branchif                               13
+# 0010 pop
+# 0011 putnil
+# 0012 leave
+# 0013 pop
+# 0014 putself
+# 0015 putstring                              "foo"
+# 0017 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0019 leave
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(4,3)> (catch: FALSE)
+# 0000 putobject                              3                         (   1)[Li]
+# 0002 putobject                              1..5                      (   2)
+# 0004 topn                                   1
+# 0006 opt_send_without_block                 <calldata!mid:===, argc:1, FCALL|ARGS_SIMPLE>
+# 0008 branchif                               13
+# 0010 pop                                                              (   1)
+# 0011 putnil
+# 0012 leave                                                            (   3)
+# 0013 pop                                                              (   2)
+# 0014 putself                                                          (   3)[Li]
+# 0015 putstring                              "foo"
+# 0017 opt_send_without_block                 <calldata!mid:puts, argc:1, FCALL|ARGS_SIMPLE>
+# 0019 leave
+
+ +

toregexp

+ +

Summary

+ +

toregexp is generated when string interpolation is used inside a regex +literal //. It pops a defined number of values from the stack, combines +them into a single string and coerces that string into a Regexp object, +which it pushes back onto the stack

+ +

TracePoint

+ +

toregexp cannot dispatch any TracePoint events.

+ +

Usage

+ +
"/#{true}/"
+
+# == disasm: #<ISeq:<main>@-e:1 (1,0)-(1,9)> (catch: FALSE)
+# 0000 putobject                              ""                        (   1)[Li]
+# 0002 putobject                              true
+# 0004 dup
+# 0005 objtostring                            <calldata!mid:to_s, argc:0, FCALL|ARGS_SIMPLE>
+# 0007 anytostring
+# 0008 toregexp                               0, 2
+# 0011 leave
+
+# == disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,11)> (catch: FALSE)
+# 0000 putobject                              "/"                       (   1)[Li]
+# 0002 putobject                              true
+# 0004 dup
+# 0005 objtostring                            <calldata!mid:to_s, argc:0, FCALL|ARGS_SIMPLE>
+# 0007 anytostring
+# 0008 putobject                              "/"
+# 0010 concatstrings                          3
+# 0012 leave
+
+ + +
+ +
+ + diff --git a/lib/yarv.rb b/lib/yarv.rb deleted file mode 100644 index 689ea17..0000000 --- a/lib/yarv.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -require "stringio" -require "set" -require "syntax_tree" - -require_relative "yarv/call_data" -require_relative "yarv/execution_context" -require_relative "yarv/frame" -require_relative "yarv/instruction" -require_relative "yarv/instruction_sequence" -require_relative "yarv/cfg" -require_relative "yarv/dfg" -require_relative "yarv/soy" -require_relative "yarv/main" -require_relative "yarv/visitor" - -# Require all of the files nested under the lib/yarv/insn directory. -Dir[File.expand_path("yarv/insn/*.rb", __dir__)].each do |filepath| - require_relative "yarv/insn/#{File.basename(filepath, ".rb")}" -end - -# The YARV module is a Ruby runtime that evaluates YARV instructions. -module YARV - # This is the main entry into the project. It accepts a Ruby string that - # represents source code. You can optionally also pass all of the same - # arguments as you would to RubyVM::InstructionSequence.compile. - # - # It compiles the source into an InstructionSequence object. You can then - # execute it - def self.compile( - source, - file = "", - path = "", - lineno = 1, - coverage_enabled: false, - debug_frozen_string_literal: false, - frozen_string_literal: false, - inline_const_cache: true, - instructions_unification: true, - operands_unification: true, - peephole_optimization: true, - specialized_instruction: true, - stack_caching: true, - tailcall_optimization: false, - trace_instruction: false - ) - iseq = - RubyVM::InstructionSequence.compile( - source, - file, - path, - lineno, - coverage_enabled:, - debug_frozen_string_literal:, - frozen_string_literal:, - inline_const_cache:, - instructions_unification:, - operands_unification:, - peephole_optimization:, - specialized_instruction:, - stack_caching:, - tailcall_optimization:, - trace_instruction: - ) - - InstructionSequence.compile(Main.new, iseq.to_a) - end -end diff --git a/lib/yarv/call_data.rb b/lib/yarv/call_data.rb deleted file mode 100644 index f6ca9d6..0000000 --- a/lib/yarv/call_data.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module YARV - # This class represents information about a specific call-site in the code. - class CallData - # stree-ignore - FLAGS = [ - :ARGS_SPLAT, # m(*args) - :ARGS_BLOCKARG, # m(&block) - :FCALL, # m(...) - :VCALL, # m - :ARGS_SIMPLE, # (ci->flag & (SPLAT|BLOCKARG)) && blockiseq == NULL && ci->kw_arg == NULL - :BLOCKISEQ, # has blockiseq - :KWARG, # has kwarg - :KW_SPLAT, # m(**opts) - :TAILCALL, # located at tail position - :SUPER, # super - :ZSUPER, # zsuper - :OPT_SEND, # internal flag - :KW_SPLAT_MUT # kw splat hash can be modified (to avoid allocating a new one) - ] - - attr_reader :mid, :argc, :flag - - def initialize(mid, argc, flag) - @mid = mid - @argc = argc - @flag = flag - end - - def ==(other) - other in CallData[mid: ^(mid), argc: ^(argc), flag: ^(flag)] - end - - def deconstruct_keys(keys) - { mid:, argc:, flag: } - end - - def to_s - "" - end - - private - - def flags - FLAGS - .each_with_index - .each_with_object([]) do |(value, index), result| - result << value if flag & (1 << index) != 0 - end - end - end -end diff --git a/lib/yarv/cfg.rb b/lib/yarv/cfg.rb deleted file mode 100644 index cbb193e..0000000 --- a/lib/yarv/cfg.rb +++ /dev/null @@ -1,139 +0,0 @@ -# frozen_string_literal: true - -module YARV - # Constructs a control-flow-graph, or CFG, of a YARV instruction sequence. - # We use conventional basic-blocks. - class CFG - attr_reader :iseq - attr_reader :blocks - attr_reader :block_map - - def initialize(iseq) - if iseq.throw_handlers.any? - raise "throw handlers not supported in CFG yet" - end - - @iseq = iseq - - block_starts = find_block_starts(iseq) - - # Find basic blocks. - @blocks = - block_starts.map do |start| - stop = - (block_starts.select { |n| n > start } + [iseq.insns.length]).min - length = stop - start - last_insn = iseq.insns[start + length - 1] - BasicBlock.new(self, start, length, last_insn.leaves?) - end - - # Create a map of PCs of basic block starts, to basic block objects. - @block_map = @blocks.map { |block| [block.start, block] }.to_h - - # Connect blocks with the preds and succs. - @blocks.each do |block| - last_insn = iseq.insns[block.start + block.length - 1] - if last_insn.branches? && last_insn.respond_to?(:label) - block.succs << block_map[iseq.labels[last_insn.label]] - end - if (!last_insn.branches? && !last_insn.leaves?) || - last_insn.falls_through? - block.succs << block_map[block.start + block.length] - end - block.succs.each { |succ| succ.preds << block } - end - - # Verify. - verify - end - - def disasm(output = StringIO.new, prefix = "") - output.puts prefix + iseq.disasm_header("cfg") - blocks.each { |block| block.disasm output, prefix } - output.string - end - - private - - # Find all the instructions that start a basic block, because they're - # either the start of an ISEQ, or they're the target of a branch, or - # they are fallen through to from a branch. - def find_block_starts(iseq) - starts = Set.new([0]) - iseq.insns.each_with_index do |insn, insn_pc| - if insn.branches? && insn.respond_to?(:label) - starts.add iseq.labels[insn.label] - end - starts.add insn_pc + 1 if insn.branches? && !insn.is_a?(Leave) - end - starts.to_a.sort - end - - def verify - # Only the last instruction in a block should branch. - blocks.each do |block| - (block.start..block.end - 1).each do |n| - raise if iseq.insns[n].branches? - end - end - end - - class BasicBlock - attr_reader :cfg - attr_reader :start - attr_reader :length - attr_reader :preds - attr_reader :succs - - def initialize(cfg, start, length, leaves) - @cfg = cfg - @start = start - @length = length - @preds = [] - @succs = [] - @leaves = leaves - end - - def disasm(output, prefix) - disasm_block_header output, prefix - disasm_block_body output, prefix - end - - def disasm_block_header(output, prefix) - output.print "#{prefix}block_#{start}:" - unless preds.empty? - output.print " # from: #{preds.map { |b| "block_#{b.start}" }.join(", ")}" - end - output.puts - end - - def disasm_block_body(output, prefix) - cfg.iseq.insns[ - start...start + length - ].each_with_index do |insn, insn_rel_pc| - insn_pc = start + insn_rel_pc - output.print prefix - output.print " " - output.print cfg.iseq.disasm_insn(insn, insn_pc) - output.print yield(insn, insn_rel_pc) if block_given? - output.puts - end - all_succs = succs.map { |b| "block_#{b.start}" } - all_succs.push "leaves" if leaves? - unless all_succs.empty? - output.print prefix - output.print " # to: #{all_succs.join(", ")}" - output.puts - end - end - - def leaves? - @leaves - end - - def end - start + length - 1 - end - end - end -end diff --git a/lib/yarv/dfg.rb b/lib/yarv/dfg.rb deleted file mode 100644 index 21aaccc..0000000 --- a/lib/yarv/dfg.rb +++ /dev/null @@ -1,214 +0,0 @@ -# frozen_string_literal: true - -module YARV - # Constructs a data-flow-graph, or DFG, of a YARV instruction sequence, via - # a CFG. We use basic-blog-arguments. Dataflow is discovered locally and then - # globally. The graph only considers dataflow through the stack - local - # variables anre objects are considered fully escaped in this analysis. - class DFG - attr_reader :cfg - attr_reader :insn_flow - attr_reader :block_flow - - def initialize(cfg) - if cfg.iseq.throw_handlers.any? - raise "throw handlers not supported in DFG yet" - end - - @cfg = cfg - - # Create a side data structure to encode dataflow between instructions. - @insn_flow = {} - cfg.iseq.insns.each_with_index do |insn, insn_pc| - insn_flow[insn_pc] = Dataflow.new - end - - # Create a side data structure to encode dataflow between basic blocks. - @block_flow = {} - cfg.blocks.each { |block| @block_flow[block.start] = Dataflow.new } - - # Discover the dataflow. - local_flow - global_flow - - # Verify. - verify_flow - end - - # Graph dataflow within basic blocks. - def local_flow - # Using an abstract stack, connect from consumers to producers. - cfg.blocks.each do |block| - block_dataflow = block_flow[block.start] - - stack = [] - stack_initial_depth = 0 - - # Go through each instruction in the block... - block - .start - .upto(block.start + block.length - 1) do |insn_pc| - insn = cfg.iseq.insns[insn_pc] - insn_dataflow = insn_flow[insn_pc] - - # How many values will be missing from the local stack to run this - # instruction? - read_off_stack = insn.reads - stack_depth = stack.size - missing_stack_values = read_off_stack - stack_depth - - # For every value the instruction pops off the stack... - insn.reads.times do - # Was the value it pops off from another basic block? - if stack.empty? - # This is a basic block argument. - name = :"in_#{missing_stack_values - 1}" - insn_dataflow.in.unshift name - block_dataflow.in.unshift name - stack_initial_depth += 1 - missing_stack_values -= 1 - else - # Connect this consumer to the producer of the value. - insn_dataflow.in.unshift stack.pop - end - end - - # Record on our abstract stack that this instruction pushed - # this value onto the stack. - insn.writes.times { stack.push insn_pc } - end - - # Values that are left on the stack after going through all - # instructions are arguments to the basic block that we jump to. - stack.reverse.each_with_index do |producer, n| - block_dataflow.out << producer - producer_dataflow = insn_flow[producer] - producer_dataflow.out << :"out_#{n}" - end - end - - # Go backwards and connect from producers to consumers. - cfg.iseq.insns.each_with_index do |insn, insn_pc| - insn_dataflow = insn_flow[insn_pc] - # For every instruction that produced a value used in this - # instruction... - insn_dataflow.in.each do |producer| - # If it's actually another instruction and not a basic block - # argument... - if producer.is_a?(Integer) - # Record in the producing instruction that it produces a value - # used by this construction. - producer_dataflow = insn_flow[producer] - producer_dataflow.out << insn_pc - end - end - end - end - - # Graph dataflow between basic blocks. - def global_flow - # Go through a worklist of all basic blocks... - worklist = cfg.blocks.dup - until worklist.empty? - succ = worklist.pop - succ_flow = block_flow[succ.start] - succ.preds.each do |pred| - pred_flow = block_flow[pred.start] - - # Does a predecessor block have fewer outputs than the successor - # has inputs? - if pred_flow.out.size < succ_flow.in.size - # If so then add arguments to pass data through from the - # predecessor's redecessors. - (succ_flow.in.size - pred_flow.out.size).times do |n| - name = :"pass_#{n}" - pred_flow.in.unshift name - pred_flow.out.unshift name - end - - # Since we modified the predecessor, add it back to the worklist - # so it'll be considered as a successor again, and propogate - # the global dataflow back up the control flow graph. - worklist.push pred - end - end - end - end - - def verify_flow - # Check the first block has no arguments. - first_block = cfg.blocks.first - first_blow_flow = block_flow[first_block.start] - raise unless first_blow_flow.in.size == 0 - - # Check all control flow edges between blocks pass the right number of - # arguments. - cfg.blocks.each do |pred| - pred_flow = block_flow[pred.start] - if pred.succs.empty? - # With no successors, there should be no output arguments. - raise unless pred_flow.out.empty? - else - # Check with successor... - pred.succs.each do |succ| - succ_flow = block_flow[succ.start] - # The predecessor should have as many output arguments as the - # success has input arguments. - raise unless pred_flow.out.size == succ_flow.in.size - end - end - end - end - - def disasm(output = StringIO.new, prefix = "") - output.puts prefix + cfg.iseq.disasm_header("dfg") - cfg.blocks.each do |block| - block_dataflow = block_flow[block.start] - block.disasm_block_header output, prefix - unless block_dataflow.in.empty? - output.print prefix - output.puts " # in: #{disasm_dataflow_connections(block_dataflow.in)}" - end - block.disasm_block_body output, prefix do |insn, rel_pc| - insn_pc = block.start + rel_pc - insn_dataflow = insn_flow[insn_pc] - if insn_dataflow.in.empty? && insn_dataflow.out.empty? - "" - else - annotate = " # " - unless insn_dataflow.in.empty? - annotate += "in: #{disasm_dataflow_connections(insn_dataflow.in)}" - annotate += "; " unless insn_dataflow.out.empty? - end - unless insn_dataflow.out.empty? - annotate += - "out: #{disasm_dataflow_connections(insn_dataflow.out)}" - end - annotate - end - end - unless block_dataflow.out.empty? - output.print prefix - output.puts " # out: #{disasm_dataflow_connections(block_dataflow.out)}" - end - end - output.string - end - - def disasm_dataflow_connections(connections) - connections - .map { |pc| pc.is_a?(Symbol) ? pc : InstructionSequence.disasm_pc(pc) } - .join(", ") - end - - class Dataflow - attr_reader :in - attr_reader :out - - def initialize - @in = [] - @out = [] - end - end - end -end diff --git a/lib/yarv/execution_context.rb b/lib/yarv/execution_context.rb deleted file mode 100644 index db0e0b8..0000000 --- a/lib/yarv/execution_context.rb +++ /dev/null @@ -1,130 +0,0 @@ -# frozen_string_literal: true - -module YARV - # This is the object that gets passed around all of the instructions as they - # are being executed. - class ExecutionContext - # The system stack that tracks values through the execution of the program. - attr_reader :stack - - # The global variables accessible to the program. These mirror the runtime - # global variables if they have not been overridden. - attr_reader :globals - - # The set of methods defined at runtime. - attr_reader :methods - - # This is a stack of frames as they are being executed. - attr_accessor :frames - - # The program counter used to determine which instruction to execute next. - # This is an attr_accessor because it can be modified by instructions being - # executed. - attr_accessor :program_counter - - def initialize - @stack = [] - # Steal the LOAD_PATHS from the host but not LOADED_FEATURES - @globals = { :$: => $:, :"$\"" => [] } - @methods = {} - @frames = [] - @program_counter = 0 - end - - # Calls a method on the given receiver. This is our method dispatch logic. - # First, it looks to see if there is a method defined on the receiver's - # class that we defined explicitly in our runtime. If there is, then it's - # going to save the necessary information and invoke it. Otherwise, it's - # going to call into the parent runtime and let it handle the method call. - # - # Note that the array of arguments coming in here is necessarily the same - # values that align with the parameters to the method being called. They are - # simply the popped values off the top of the stack. It is the - # responsibility of this method to ensure that they get copied into the - # locals table in the correct order. - def call_method(call_data, receiver, arguments, &block) - if (method = methods[[receiver.class, call_data.mid]]) - # We only support a subset of the valid argument permutations. This - # validates each kind to make sure we don't accidentally try to handle a - # method that we currently don't support. - case method.args - in {} - # No arguments, we're good - in { lead_num: ^(arguments.length), **nil } - # Only leading arguments and we line up with the expected number - end - - eval(method) do - # Inside this block, we have already pushed the frame. So now we need - # to establish the correct local variables. - arguments.each_with_index do |argument, index| - current_frame.locals[index] = argument - end - current_frame.set_block(block) if block_given? - end - - stack.last - elsif receiver.is_a?(Main) && call_data.mid == :require - receiver.send(call_data.mid, self, *arguments) - else - receiver.send(call_data.mid, *arguments, &block) - end - end - - # This returns the current execution frame. - def current_frame - frames.last - end - - # This returns the instruction sequence object that is currently being - # executed. In other words, the instruction sequence that is at the top of - # the frame stack. - def current_iseq - current_frame.iseq - end - - # Defines a method on the given object's class keyed by the given name. The - # iseq is an instance of the InstructionSequence class. - def define_method(object, name, iseq) - methods[[object.class, name]] = iseq - end - - # This returns the parent execution frame. - def parent_frame(depth = 1) - frames[-1 - depth] - end - - # This executes the given instruction sequence within a new execution frame. - def with_frame(iseq) - current_program_counter = program_counter - current_stack_length = stack.length - - frames.push(Frame.new(iseq)) - @program_counter = 0 - - begin - yield - ensure - frames.pop - @program_counter = current_program_counter - @stack = @stack[0..current_stack_length] - end - end - - # Pushes a new frame onto the stack, executes the instructions contained - # within this instruction sequence, then pops the frame off the stack. - def eval(iseq) - with_frame(iseq) do - yield if block_given? - - loop do - insn = iseq.insns[program_counter] - self.program_counter += 1 - - insn.call(self) - break if insn in Leave - end - end - end - end -end diff --git a/lib/yarv/frame.rb b/lib/yarv/frame.rb deleted file mode 100644 index 426aa20..0000000 --- a/lib/yarv/frame.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module YARV - # This represents an execution frame. - class Frame - UNDEFINED = Object.new - - attr_reader :iseq, :locals, :block - - def initialize(iseq) - @iseq = iseq - @locals = Array.new(iseq.locals.length) { UNDEFINED } - # TODO: more accurately, this should be in a locals table - # e.g. local table (..., block: -1, ...) - @block = nil - end - - # Fetches the value of a local variable from the frame. If the value has - # not yet been initialized, it will raise an error. - def get_local(index) - local = locals[index] - if local == UNDEFINED - raise NameError, - "undefined local variable or method `#{iseq.locals[index]}' for #{iseq.selfo}" - end - - local - end - - # Sets the value of the local variable on the frame. - def set_local(index, value) - @locals[index] = value - end - - def set_block(block) - @block = block - end - - def execute_block(*arguments) - block.call(*arguments) - end - end -end diff --git a/lib/yarv/insn/adjuststack.rb b/lib/yarv/insn/adjuststack.rb deleted file mode 100644 index 09d3a67..0000000 --- a/lib/yarv/insn/adjuststack.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `adjuststack` accepts a single integer argument and removes that many - # elements from the top of the stack. - # - # ### TracePoint - # - # `adjuststack` cannot dispatch any TracePoint events. - # - # ### Usage - # - # ~~~ruby - # x = [true] - # x[0] ||= nil - # x[0] - # ~~~ - # - class AdjustStack < Instruction - attr_reader :size - - def initialize(size) - @size = size - end - - def ==(other) - other in AdjustStack - end - - def call(context) - context.stack.pop(size) - end - - def deconstruct_keys(keys) - { size: } - end - - def disasm(iseq) - "%-38s %d" % ["adjuststack", size] - end - end -end diff --git a/lib/yarv/insn/anytostring.rb b/lib/yarv/insn/anytostring.rb deleted file mode 100644 index 4b3622d..0000000 --- a/lib/yarv/insn/anytostring.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `anytostring` ensures that the value on top of the stack is a string. - # - # It pops two values off the stack. If the first value is a string it pushes it back on - # the stack. If the first value is not a string, it uses Ruby's built in string coercion - # to coerce the second value to a string and then pushes that back on the stack. - # - # This is used in conjunction with `objtostring` as a fallback for when an object's `to_s` - # method does not return a string - # - # ### TracePoint - # - # `anytostring` cannot dispatch any TracePoint events - # - # ### Usage - # - # ~~~ruby - # "#{5}" - # ~~~ - # - class AnyToString < Instruction - def ==(other) - other in AnyToString - end - - def call(context) - maybe_string, orig_val = context.stack.pop(2) - string = maybe_string.is_a?(String) ? maybe_string : orig_val.to_s - context.stack.push(string) - end - - def disasm(iseq) - "anytostring" - end - end -end diff --git a/lib/yarv/insn/branchif.rb b/lib/yarv/insn/branchif.rb deleted file mode 100644 index bee2c37..0000000 --- a/lib/yarv/insn/branchif.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `branchif` has one argument: the jump index. It pops one value off the stack: - # the jump condition. - # - # If the value popped off the stack is true, `branchif` jumps to - # the jump index and continues executing there. - # - # ### TracePoint - # - # `branchif` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # x = true - # x ||= "foo" - # puts x - # ~~~ - # - class BranchIf < Instruction - attr_reader :label - - def initialize(label) - @label = label - end - - def ==(other) - other in BranchIf # explicitly not comparing labels - end - - def call(context) - condition = context.stack.pop - - if condition - jump_index = context.current_iseq.labels[label] - context.program_counter = jump_index - end - end - - def deconstruct_keys(keys) - { label: } - end - - def branches? - true - end - - def falls_through? - true - end - - def reads - 1 - end - - def writes - 0 - end - - def disasm(iseq) - target = iseq ? iseq.labels[label] : "??" - "%-38s %s (%s)" % ["branchif", label, target] - end - end -end diff --git a/lib/yarv/insn/branchnil.rb b/lib/yarv/insn/branchnil.rb deleted file mode 100644 index eb673b1..0000000 --- a/lib/yarv/insn/branchnil.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `branchnil` has one argument: the jump index. It pops one value off the stack: - # the jump condition. - # - # If the value popped off the stack is nil, `branchnil` jumps to - # the jump index and continues executing there. - # - # ### TracePoint - # - # There is no trace point for `branchnil`. - # - # ### Usage - # - # ~~~ruby - # x = nil - # if x&.to_s - # puts "hi" - # end - # ~~~ - # - class BranchNil < Instruction - attr_reader :label - - def initialize(label) - @label = label - end - - def ==(other) - other in BranchNil # explicitly not comparing labels - end - - def call(context) - condition = context.stack.pop - - if condition.nil? - jump_index = context.current_iseq.labels[label] - context.program_counter = jump_index - end - end - - def deconstruct_keys(keys) - { label: } - end - - def branches? - true - end - - def falls_through? - true - end - - def disasm(iseq) - target = iseq ? iseq.labels[label] : "??" - "%-38s %s (%s)" % ["branchnil", label, target] - end - end -end diff --git a/lib/yarv/insn/branchunless.rb b/lib/yarv/insn/branchunless.rb deleted file mode 100644 index a0e7fc1..0000000 --- a/lib/yarv/insn/branchunless.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `branchunless` has one argument, the jump index - # and pops one value off the stack, the jump condition. - # - # If the value popped off the stack is false or nil, - # `branchunless` jumps to the jump index and continues executing there. - # - # ### TracePoint - # - # `branchunless` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # if 2 + 3 - # puts "foo" - # end - # ~~~ - # - class BranchUnless < Instruction - attr_reader :label - - def initialize(label) - @label = label - end - - def ==(other) - other in BranchUnless # explicitly not comparing labels - end - - def call(context) - condition = context.stack.pop - - unless condition - jump_index = context.current_iseq.labels[label] - context.program_counter = jump_index - end - end - - def deconstruct_keys(keys) - { label: } - end - - def branches? - true - end - - def falls_through? - true - end - - def reads - 1 - end - - def writes - 0 - end - - def disasm(iseq) - target = iseq ? iseq.labels[label] : "??" - "%-38s %s (%s)" % ["branchunless", label, target] - end - end -end diff --git a/lib/yarv/insn/concatarray.rb b/lib/yarv/insn/concatarray.rb deleted file mode 100644 index eaebb2a..0000000 --- a/lib/yarv/insn/concatarray.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `concatarray` concatenates the two Arrays on top of the stack. - # - # It coerces the two objects at the top of the stack into Arrays by calling - # `to_a` if necessary, and makes sure to `dup` the first Array if it was - # already an Array, to avoid mutating it when concatenating. - # - # ### TracePoint - # - # `concatarray` can dispatch the `line` and `call` events. - # - # ### Usage - # - # ~~~ruby - # [1, *2] - # ~~~ - # - class ConcatArray < Instruction - def ==(other) - other in ConcatArray - end - - def call(context) - left, right = context.stack.pop(2) - coerced_left = coerce(left) - coerced_left = left.dup if coerced_left.equal?(left) - coerced_left.concat(coerce(right)) - context.stack.push(coerced_left) - end - - def disasm(iseq) - "concatarray" - end - - private - - def coerce(object) - object.respond_to?(:to_a) ? object.to_a : [object] - end - end -end diff --git a/lib/yarv/insn/concatstrings.rb b/lib/yarv/insn/concatstrings.rb deleted file mode 100644 index 1c25a1e..0000000 --- a/lib/yarv/insn/concatstrings.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `concatstrings` just pops a number of strings from the stack joins them together - # into a single string and pushes that string back on the stack. - # - # This does no coercion and so is always used in conjunction with `objtostring` - # and `anytostring` to ensure the stack contents are always strings - # - # ### TracePoint - # - # `concatstrings` can dispatch the `line` and `call` events. - # - # ### Usage - # - # ~~~ruby - # "#{5}" - # ~~~ - # - class ConcatStrings < Instruction - attr_reader :size - - def initialize(size) - @size = size - end - - def ==(other) - other in ConcatStrings[size: ^(size)] - end - - def call(context) - strings = context.stack.pop(size) - context.stack.push(strings.join) - end - - def deconstruct_keys(keys) - { size: } - end - - def disasm(iseq) - "%-38s %s" % ["concatstrings", size] - end - end -end diff --git a/lib/yarv/insn/defined.rb b/lib/yarv/insn/defined.rb deleted file mode 100644 index 25a3e0d..0000000 --- a/lib/yarv/insn/defined.rb +++ /dev/null @@ -1,107 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `defined` checks if the top value of the stack is defined. If it is, it - # pushes its value onto the stack. Otherwise it pushes `nil`. - # - # ### TracePoint - # - # `defined` cannot dispatch any TracePoint events. - # - # ### Usage - # - # ~~~ruby - # defined?(x) - # ~~~ - # - class Defined < Instruction - DEFINED_TYPE = %i[ - DEFINED_NOT_DEFINED - DEFINED_NIL - DEFINED_IVAR - DEFINED_LVAR - DEFINED_GVAR - DEFINED_CVAR - DEFINED_CONST - DEFINED_METHOD - DEFINED_YIELD - DEFINED_ZSUPER - DEFINED_SELF - DEFINED_TRUE - DEFINED_FALSE - DEFINED_ASGN - DEFINED_EXPR - DEFINED_REF - DEFINED_FUNC - DEFINED_CONST_FROM - ] - - attr_reader :type, :object, :value - - def initialize(type, object, value) - raise if type >= DEFINED_TYPE.length - - @type = type - @object = object - @value = value - end - - def ==(other) - other in Defined[type: ^(type), object: ^(object), value: ^(value)] - end - - def call(context) - predicate = context.stack.pop - context.stack.push(vm_defined?(context, predicate) ? value : nil) - end - - def disasm(iseq) - "%-38s %s, %s, %s" % ["defined", type, object.inspect, value.inspect] - end - - private - - def vm_defined?(context, predicate) - case DEFINED_TYPE[type] - in :DEFINED_NOT_DEFINED - raise "Compilation error" - in :DEFINED_NIL - true - in :DEFINED_IVAR - raise NotImplementedError, "defined?(ivar)" - in :DEFINED_LVAR - raise NotImplementedError, "defined?(lvar)" - in :DEFINED_GVAR - context.globals.key?(object) - in :DEFINED_CVAR - raise NotImplementedError, "defined?(cvar)" - in :DEFINED_CONST - raise NotImplementedError, "defined?(const)" - in :DEFINED_METHOD - raise NotImplementedError, "defined?(method)" - in :DEFINED_YIELD - raise NotImplementedError, "defined?(yield)" - in :DEFINED_ZSUPER - raise NotImplementedError, "defined?(zsuper)" - in :DEFINED_SELF - true - in :DEFINED_TRUE - true - in :DEFINED_FALSE - true - in :DEFINED_ASGN - raise NotImplementedError, "defined?(asgn)" - in :DEFINED_EXPR - raise NotImplementedError, "defined?(expr)" - in :DEFINED_REF - raise NotImplementedError, "defined?(ref)" - in :DEFINED_FUNC - raise NotImplementedError, "defined?(func)" - in :DEFINED_CONST_FROM - raise NotImplementedError, "defined?(const_from)" - end - end - end -end diff --git a/lib/yarv/insn/definemethod.rb b/lib/yarv/insn/definemethod.rb deleted file mode 100644 index 16e7698..0000000 --- a/lib/yarv/insn/definemethod.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `definemethod` defines a method on the class of the current value of `self`. - # It accepts two arguments. The first is the name of the method being defined. - # The second is the instruction sequence representing the body of the method. - # - # ### TracePoint - # - # `definemethod` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # def value = "value" - # ~~~ - # - class DefineMethod < Instruction - attr_reader :name, :iseq - - def initialize(name, iseq) - @name = name - @iseq = iseq - end - - def ==(other) - other in DefineMethod[name: ^(name), iseq: ^(iseq)] - end - - def call(context) - context.define_method(context.current_iseq.selfo, name, iseq) - end - - def deconstruct_keys(keys) - { name:, iseq: } - end - - def reads - 0 - end - - def writes - 0 - end - - def disasm(containing_iseq) - "%-38s %s, %s" % ["definemethod", name.inspect, iseq.name] - end - end -end diff --git a/lib/yarv/insn/dup.rb b/lib/yarv/insn/dup.rb deleted file mode 100644 index 4a0f4ec..0000000 --- a/lib/yarv/insn/dup.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `dup` copies the top value of the stack and pushes it onto the stack. - # - # ### TracePoint - # - # `dup` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # $global = 5 - # ~~~ - # - class Dup < Instruction - def ==(other) - other in Dup - end - - def reads - 1 - end - - def writes - 2 - end - - def side_effects? - false - end - - def call(context) - value = context.stack.pop - context.stack.push(value) - context.stack.push(value) - end - - def disasm(iseq) - "dup" - end - end -end diff --git a/lib/yarv/insn/dup_hash.rb b/lib/yarv/insn/dup_hash.rb deleted file mode 100644 index e2ba938..0000000 --- a/lib/yarv/insn/dup_hash.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `duphash` pushes a hash onto the stack - # - # ### TracePoint - # - # `duphash` can dispatch the line event. - # - # ### Usage - # - # ~~~ruby - # { a: 1 } - # ~~~ - # - class DupHash < Instruction - attr_reader :value - - def initialize(value) - @value = value - end - - def ==(other) - other in DupHash[value: ^(value)] - end - - def call(context) - context.stack.push(value.dup) - end - - def deconstruct_keys(keys) - { value: } - end - - def disasm(iseq) - "%-38s %s" % ["duphash", value.inspect] - end - end -end diff --git a/lib/yarv/insn/duparray.rb b/lib/yarv/insn/duparray.rb deleted file mode 100644 index 06183fc..0000000 --- a/lib/yarv/insn/duparray.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `duparray` copies a literal Array and pushes it onto the stack. - # - # ### TracePoint - # - # `duparray` can dispatch the `line` event. - # - # ### Usage - # - # ~~~ruby - # [true] - # ~~~ - # - class DupArray < Instruction - attr_reader :value - - def initialize(value) - @value = value - end - - def ==(other) - other in DupArray[value: ^(value)] - end - - def call(context) - context.stack.push(value.dup) - end - - def deconstruct_keys(keys) - { value: } - end - - def disasm(iseq) - "%-38s %s" % ["duparray", value.inspect] - end - end -end diff --git a/lib/yarv/insn/dupn.rb b/lib/yarv/insn/dupn.rb deleted file mode 100644 index 1d83628..0000000 --- a/lib/yarv/insn/dupn.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `dupn` duplicates the top `n` stack elements. - # - # ### TracePoint - # - # `dupn` does not dispatch any TracePoint events. - # - # ### Usage - # - # ~~~ruby - # Object::X ||= true - # ~~~ - # - class DupN < Instruction - attr_reader :offset - - def initialize(offset) - @offset = offset - end - - def ==(other) - other in DupN[offset: ^(offset)] - end - - def call(context) - context.stack.concat(context.stack[-offset..].map(&:dup)) - end - - def deconstruct_keys(keys) - { offset: } - end - - def disasm(iseq) - "%-38s %d" % ["dupn", offset] - end - end -end diff --git a/lib/yarv/insn/expandarray.rb b/lib/yarv/insn/expandarray.rb deleted file mode 100644 index 425f95c..0000000 --- a/lib/yarv/insn/expandarray.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `expandarray` looks at the top of the stack, and if the value is an array - # it replaces it on the stack with `num` elements of the array, or `nil` if - # the elements are missing. - # - # ### TracePoint - # - # `expandarray` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # x, = [true, false, nil] - # ~~~ - # - class ExpandArray < Instruction - attr_reader :size, :flag - - def initialize(size, flag) - @size = size - @flag = flag - end - - def ==(other) - other in ExpandArray[size: ^(size), flag: ^(flag)] - end - - def call(context) - raise # see vm_expandarray, it's a little subtle - end - - def reads - 1 - end - - def writes - size - end - - def disasm(iseq) - "%-38s %d, %d" % ["expandarray", size, flag] - end - - def deconstruct_keys(keys) - { size:, flag: } - end - end -end diff --git a/lib/yarv/insn/getconstant.rb b/lib/yarv/insn/getconstant.rb deleted file mode 100644 index 4d93dca..0000000 --- a/lib/yarv/insn/getconstant.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `getconstant` performs a constant lookup and pushes the value of the - # constant onto the stack. - # - # ### TracePoint - # - # `getconstant` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # Constant - # ~~~ - # - class GetConstant < Instruction - attr_reader :name - - def initialize(name) - @name = name - end - - def ==(other) - other in GetConstant[name: ^(name)] - end - - def call(context) - klass, allow_nil = context.stack.pop(2) - - if klass.nil? && !allow_nil - raise NameError, "uninitialized constant #{name}" - end - - # At the moment we're just looking up constants in the parent runtime. In - # the future, we'll want to look up constants in the YARV runtime as well. - context.stack.push((klass || Object).const_get(name)) - end - - def deconstruct_keys(keys) - { name: } - end - - def disasm(iseq) - "%-38s %s" % ["getconstant", name.inspect] - end - end -end diff --git a/lib/yarv/insn/getglobal.rb b/lib/yarv/insn/getglobal.rb deleted file mode 100644 index ad9f56c..0000000 --- a/lib/yarv/insn/getglobal.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `getglobal` pushes the value of a global variables onto the stack. - # - # ### TracePoint - # - # `getglobal` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # $$ - # ~~~ - # - class GetGlobal < Instruction - attr_reader :name - - def initialize(name) - @name = name - end - - def ==(other) - other in GetGlobal[name: ^(name)] - end - - def call(context) - # If we're not currently tracking the global variable, then we're going to - # steal the definition of it from the parent process by eval-ing it. - if !context.globals.key?(name) && global_variables.include?(name) - context.globals[name] = eval(name.to_s) - end - - # If a global variable isn't defined for the given name, this is just - # going to push `nil` onto the stack. - context.stack.push(context.globals[name]) - end - - def deconstruct_keys(keys) - { name: } - end - - def disasm(iseq) - "%-38s %s" % ["getglobal", name.inspect] - end - end -end diff --git a/lib/yarv/insn/getlocal.rb b/lib/yarv/insn/getlocal.rb deleted file mode 100644 index fc624c9..0000000 --- a/lib/yarv/insn/getlocal.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `getlocal` fetches the value of a local variable from a frame determined by - # the level and index arguments. The level is the number of frames back to - # look and the index is the index in the local table. It pushes the value it - # finds onto the stack. - # - # ### TracePoint - # - # `getlocal` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # value = 5 - # tap { tap { value } } - # ~~~ - # - class GetLocal < Instruction - attr_reader :name, :index, :level - - def initialize(name, index, level) - @name = name - @index = index - @level = level - end - - def ==(other) - other in GetLocal[name: ^(name), index: ^(index), level: ^(level)] - end - - def call(context) - value = context.parent_frame(level).get_local(index) - context.stack.push(value) - end - - def deconstruct_keys(keys) - { name:, index:, level: } - end - - def disasm(iseq) - "%-38s %s@%d, %d" % ["getlocal", name, index, level] - end - end -end diff --git a/lib/yarv/insn/getlocal_wc_0.rb b/lib/yarv/insn/getlocal_wc_0.rb deleted file mode 100644 index ec4c258..0000000 --- a/lib/yarv/insn/getlocal_wc_0.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `getlocal_WC_0` is a specialized version of the `getlocal` instruction. It - # fetches the value of a local variable from the current frame determined by - # the index given as its only argument. - # - # ### TracePoint - # - # `getlocal_WC_0` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # value = 5 - # value - # ~~~ - # - class GetLocalWC0 < Instruction - attr_reader :name, :index - - def initialize(name, index) - @name = name - @index = index - end - - def ==(other) - other in GetLocalWC0[name: ^(name), index: ^(index)] - end - - def call(context) - value = context.current_frame.get_local(index) - context.stack.push(value) - end - - def reads - 0 - end - - def writes - 1 - end - - def deconstruct_keys(keys) - { name:, index: } - end - - def disasm(iseq) - "%-38s %s@%d" % ["getlocal_WC_0", name, index] - end - end -end diff --git a/lib/yarv/insn/getlocal_wc_1.rb b/lib/yarv/insn/getlocal_wc_1.rb deleted file mode 100644 index 86e5d79..0000000 --- a/lib/yarv/insn/getlocal_wc_1.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `getlocal_WC_1` is a specialized version of the `getlocal` instruction. It - # fetches the value of a local variable from the parent frame determined by - # the index given as its only argument. - # - # ### TracePoint - # - # `getlocal_WC_1` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # value = 5 - # self.then { value } - # ~~~ - # - class GetLocalWC1 < Instruction - attr_reader :name, :index - - def initialize(name, index) - @name = name - @index = index - end - - def ==(other) - other in GetLocalWC1[name: ^(name), index: ^(index)] - end - - def call(context) - value = context.parent_frame.get_local(index) - context.stack.push(value) - end - - def deconstruct_keys(keys) - { name:, index: } - end - - def disasm(iseq) - "%-38s %s@%d" % ["getlocal_WC_1", name, index] - end - end -end diff --git a/lib/yarv/insn/intern.rb b/lib/yarv/insn/intern.rb deleted file mode 100644 index 8881e4c..0000000 --- a/lib/yarv/insn/intern.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `intern` converts top element from stack to a symbol. - # - # ### TracePoint - # - # There is no trace point for `intern`. - # - # ### Usage - # - # ~~~ruby - # :"#{"foo"}" - # ~~~ - # - class Intern < Instruction - def ==(other) - other in Intern - end - - def call(context) - string = context.stack.pop - context.stack.push(string.to_sym) - end - - def disasm(iseq) - "intern" - end - end -end diff --git a/lib/yarv/insn/invokeblock.rb b/lib/yarv/insn/invokeblock.rb deleted file mode 100644 index 4964603..0000000 --- a/lib/yarv/insn/invokeblock.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `invokeblock` invokes the block passed to a method during a yield. - # - # ### TracePoint - # - # - # ### Usage - # - # ~~~ruby - # def foo; yield; end - # - # # == disasm: #@-e:1 (1,0)-(1,19)> (catch: FALSE) - # # 0000 definemethod :foo, foo ( 1)[Li] - # # 0003 putobject :foo - # # 0005 leave - # # - # # == disasm: # (catch: FALSE) - # # 0000 invokeblock ( 1)[LiCa] - # # 0002 leave [Re] - # # ~~~ - # - class InvokeBlock < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in InvokeBlock[call_data: ^(call_data)] - end - - def call(context) - *arguments = context.stack.pop(call_data.argc) - result = context.current_frame.execute_block(*arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s" % ["invokeblock", call_data] - end - end -end diff --git a/lib/yarv/insn/jump.rb b/lib/yarv/insn/jump.rb deleted file mode 100644 index 84b7232..0000000 --- a/lib/yarv/insn/jump.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `jump` has one argument, the jump index, which it uses to set the next - # instruction to execute. - # - # ### TracePoint - # - # There is no trace point for `jump`. - # - # ### Usage - # - # ~~~ruby - # y = 0 - # if y == 0 - # puts "0" - # else - # puts "2" - # end - # ~~~ - # - class Jump < Instruction - attr_reader :label - - def initialize(label) - @label = label - end - - def ==(other) - other in Jump # explicitly not comparing labels - end - - def call(context) - jump_index = context.current_iseq.labels[label] - context.program_counter = jump_index - end - - def deconstruct_keys(keys) - { label: } - end - - def branches? - true - end - - def reads - 0 - end - - def writes - 0 - end - - def disasm(iseq) - target = iseq ? iseq.labels[label] : "??" - "%-38s %s (%s)" % ["jump", label, target] - end - end -end diff --git a/lib/yarv/insn/leave.rb b/lib/yarv/insn/leave.rb deleted file mode 100644 index e673dbc..0000000 --- a/lib/yarv/insn/leave.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `leave` exits the current frame. - # - # ### TracePoint - # - # `leave` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # ;; - # ~~~ - # - class Leave < Instruction - def ==(other) - other in Leave - end - - def call(context) - # skip for now - end - - def branches? - true - end - - def leaves? - true - end - - def reads - 1 - end - - def writes - 0 - end - - def side_effects? - # Leave doesn't really have a side effects... but we say it does so that - # control flow has somewhere to end up. - true - end - - def disasm(iseq) - "leave" - end - end -end diff --git a/lib/yarv/insn/newarray.rb b/lib/yarv/insn/newarray.rb deleted file mode 100644 index a32773d..0000000 --- a/lib/yarv/insn/newarray.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `newarray` puts a new array initialized with `size` values from the stack. - # - # ### TracePoint - # - # `newarray` dispatches a `line` event. - # - # ### Usage - # - # ~~~ruby - # ["string"] - # ~~~ - # - class NewArray < Instruction - attr_reader :size - - def initialize(size) - @size = size - end - - def ==(other) - other in NewArray[size: ^(size)] - end - - def call(context) - array = context.stack.pop(size) - context.stack.push(array) - end - - def deconstruct_keys(keys) - { size: } - end - - def disasm(iseq) - "%-38s %s" % ["newarray", size] - end - end -end diff --git a/lib/yarv/insn/newhash.rb b/lib/yarv/insn/newhash.rb deleted file mode 100644 index 333cc2d..0000000 --- a/lib/yarv/insn/newhash.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `newhash` puts a new hash onto the stack, using `num` elements from the - # stack. `num` needs to be even. - # - # ### TracePoint - # - # `newhash` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # def foo(key, value) - # { key => value } - # end - # ~~~ - # - class NewHash < Instruction - attr_reader :size - - def initialize(size) - @size = size - end - - def ==(other) - other in NewHash[size: ^(size)] - end - - def call(context) - key_values = context.stack.pop(size) - context.stack.push(Hash[*key_values]) - end - - def deconstruct_keys(keys) - { size: } - end - - def disasm(iseq) - "%-38s %s" % ["newhash", size] - end - end -end diff --git a/lib/yarv/insn/newrange.rb b/lib/yarv/insn/newrange.rb deleted file mode 100644 index fb08224..0000000 --- a/lib/yarv/insn/newrange.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `newrange` creates a Range. It takes one arguments, which is 0 if the end - # is included or 1 if the end value is excluded. - # - # ### TracePoint - # - # `newrange` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # x = 0 - # y = 1 - # p (x..y), (x...y) - # ~~~ - # - class NewRange < Instruction - attr_reader :exclude_end - - def initialize(exclude_end) - unless exclude_end == 0 || exclude_end == 1 - raise ArgumentError, "invalid exclude_end: #{exclude_end.inspect}" - end - - @exclude_end = exclude_end - end - - def ==(other) - other in NewRange[exclude_end: ^(exclude_end)] - end - - def call(context) - range_begin, range_end = context.stack.pop(2) - context.stack.push(Range.new(range_begin, range_end, exclude_end == 1)) - end - - def deconstruct_keys(keys) - { exclude_end: } - end - - def disasm(iseq) - "%-38s %d" % ["newrange", exclude_end] - end - end -end diff --git a/lib/yarv/insn/nop.rb b/lib/yarv/insn/nop.rb deleted file mode 100644 index 58ad512..0000000 --- a/lib/yarv/insn/nop.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `nop` is a no-operation instruction. It is used to pad the instruction - # sequence so there is a place for other instructions to jump to. - # - # ### TracePoint - # - # `nop` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # raise rescue true - # ~~~ - # - class Nop < Instruction - def ==(other) - other in Nop - end - - def reads - 0 - end - - def writes - 0 - end - - def side_effects? - false - end - - def call(context) - end - - def disasm(iseq) - "%-38s" % ["nop"] - end - end -end diff --git a/lib/yarv/insn/objtostring.rb b/lib/yarv/insn/objtostring.rb deleted file mode 100644 index 4f5467b..0000000 --- a/lib/yarv/insn/objtostring.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `objtostring` pops a value from the stack, calls `to_s` on that value and then pushes - # the result back to the stack. - # - # It has fast paths for String, Symbol, Module, Class, Nil, True, False & Number. - # For everything else it calls `to_s` - # - # ### TracePoint - # - # `objtostring` cannot dispatch any TracePoint events. - # - # ### Usage - # - # ~~~ruby - # "#{5}" - # ~~~ - # - class ObjToString < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in ObjToString[call_data: ^(call_data)] - end - - def call(context) - obj = context.stack.pop - result = context.call_method(call_data, obj, []) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s" % ["objtostring", call_data] - end - end -end diff --git a/lib/yarv/insn/opt_and.rb b/lib/yarv/insn/opt_and.rb deleted file mode 100644 index 4b2b7a9..0000000 --- a/lib/yarv/insn/opt_and.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_and` is a specialization of the `opt_send_without_block` instruction - # that occurs when the `&` operator is used. In CRuby, there are fast paths - # for if both operands are integers. - # - # ### TracePoint - # - # `opt_and` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 2 & 3 - # ~~~ - # - class OptAnd < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptAnd[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_and", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_aref.rb b/lib/yarv/insn/opt_aref.rb deleted file mode 100644 index 47991e2..0000000 --- a/lib/yarv/insn/opt_aref.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_aref` is a specialization of the `opt_send_without_block` instruction - # that occurs when the `[]` operator is used. In CRuby, there are fast paths - # if the receiver is an integer, array, or hash. - # - # ### TracePoint - # - # `opt_aref` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 7[2] - # ~~~ - # - class OptAref < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptAref[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_aref", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_aref_with.rb b/lib/yarv/insn/opt_aref_with.rb deleted file mode 100644 index 492912f..0000000 --- a/lib/yarv/insn/opt_aref_with.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_aref_with` is a specialization of the `opt_aref` instruction that - # occurs when the `[]` operator is used with a string argument known at - # compile time. In CRuby, there are fast paths if the receiver is a hash. - # - # ### TracePoint - # - # `opt_aref_with` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # { 'test' => true }['test'] - # ~~~ - # - class OptArefWith < Instruction - attr_reader :key, :call_data - - def initialize(key, call_data) - @key = key - @call_data = call_data - end - - def ==(other) - other in OptArefWith[key: ^(key), call_data: ^(call_data)] - end - - def call(context) - receiver = context.stack.pop - result = context.call_method(call_data, receiver, [key]) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { key: } - end - - def disasm(iseq) - "%-38s %s, %s" % ["opt_aref_with", key, call_data] - end - end -end diff --git a/lib/yarv/insn/opt_aset.rb b/lib/yarv/insn/opt_aset.rb deleted file mode 100644 index 40c9909..0000000 --- a/lib/yarv/insn/opt_aset.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_aset` is an instruction for setting the hash value by the key in `recv[obj] = set` format - # - # ### TracePoint - # - # There is no trace point for `opt_aset`. - # - # ### Usage - # - # ~~~ruby - # {}[:key] = value - # ~~~ - # - class OptAset < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptAset[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_aset", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_aset_with.rb b/lib/yarv/insn/opt_aset_with.rb deleted file mode 100644 index 7d62539..0000000 --- a/lib/yarv/insn/opt_aset_with.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_aset_with` is an instruction for setting the hash value by the known - # string key in the `recv[obj] = set` format. - # - # ### TracePoint - # - # There is no trace point for `opt_aset_with`. - # - # ### Usage - # - # ~~~ruby - # {}["key"] = value - # ~~~ - # - class OptAsetWith < Instruction - attr_reader :key, :call_data - - def initialize(key, call_data) - @key = key - @call_data = call_data - end - - def ==(other) - other in OptAsetWith[key: ^(key), call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc) - result = context.call_method(call_data, receiver, [key, *arguments]) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { key:, call_data: } - end - - def disasm(iseq) - "%-38s %s, %s" % ["opt_aset_with", key.inspect, call_data] - end - end -end diff --git a/lib/yarv/insn/opt_case_dispatch.rb b/lib/yarv/insn/opt_case_dispatch.rb deleted file mode 100644 index 681b203..0000000 --- a/lib/yarv/insn/opt_case_dispatch.rb +++ /dev/null @@ -1,85 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_case_dispatch` is a branch instruction that moves the control flow for - # case statements. - # - # It has two arguments: the cdhash and an else_offset index. It pops one value off - # the stack: a hash key. `opt_case_dispatch` looks up the key in the cdhash - # and jumps to the corresponding index value, if there is one. - # If there is no value in the cdhash, `opt_case_dispatch` jumps to the else_offset index. - # - # The cdhash is a Ruby hash used for handling optimized `case` statements. - # The keys are the conditions of `when` clauses in the `case` statement, - # and the values are the labels to which to jump. This optimization can be - # applied only when the keys can be directly compared. - # - # ### TracePoint - # - # There is no trace point for `opt_case_dispatch`. - # - # ### Usage - # - # ~~~ruby - # case 1 - # when 1 - # puts "foo" - # else - # puts "bar" - # end - # - # # == disasm: #@:1 (1,0)-(1,49)> (catch: FALSE) - # # 0000 putobject_INT2FIX_0_ ( 1)[Li] - # # 0001 dup - # # 0002 opt_case_dispatch , 12 - # # 0005 putobject_INT2FIX_1_ - # # 0006 topn 1 - # # 0008 opt_send_without_block - # # 0010 branchif 19 - # # 0012 pop - # # 0013 putself - # # 0014 putstring "bar" - # # 0016 opt_send_without_block - # # 0018 leave - # # 0019 pop - # # 0020 putself - # # 0021 putstring "foo" - # # 0023 opt_send_without_block - # # 0025 leave - # ~~~ - # - class OptCaseDispatch < Instruction - attr_reader :cdhash, :else_offset - - def initialize(cdhash, else_offset) - @cdhash = Hash[*cdhash] - @else_offset = else_offset - end - - def ==(other) - other in OptCaseDispatch[cdhash: ^(cdhash), else_offset: ^(else_offset)] - end - - def call(context) - hash_key = context.stack.pop - if (label = cdhash[hash_key]) - jump_index = context.current_iseq.labels[label] - context.program_counter = jump_index - else - jump_index = context.current_iseq.labels[else_offset] - context.program_counter = jump_index - end - end - - def deconstruct_keys(keys) - { cdhash:, else_offset: } - end - - def disasm(iseq) - "%-38s %s %s" % - ["opt_case_dispatch", ",", else_offset["label_".length..]] - end - end -end diff --git a/lib/yarv/insn/opt_div.rb b/lib/yarv/insn/opt_div.rb deleted file mode 100644 index f19f6e4..0000000 --- a/lib/yarv/insn/opt_div.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_div` is a specialization of the `opt_send_without_block` instruction - # that occurs when the `/` operator is used. In CRuby, there are fast paths - # for if both operands are integers, or if both operands are floats. - # - # ### TracePoint - # - # `opt_div` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 2 / 3 - # ~~~ - # - class OptDiv < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptDiv[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_div", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_empty_p.rb b/lib/yarv/insn/opt_empty_p.rb deleted file mode 100644 index e547ae5..0000000 --- a/lib/yarv/insn/opt_empty_p.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_empty_p` is an optimization applied when the method `empty?` is called - # on a String, Array or a Hash. This optimization can be applied because Ruby - # knows how to calculate the length of these objects using internal C macros. - # - # ### TracePoint - # - # `opt_empty_p` can dispatch `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # "".empty? - # ~~~ - # - class OptEmptyP < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptEmptyP[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_empty_p", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_eq.rb b/lib/yarv/insn/opt_eq.rb deleted file mode 100644 index 27b0b76..0000000 --- a/lib/yarv/insn/opt_eq.rb +++ /dev/null @@ -1,52 +0,0 @@ -module YARV - # ### Summary - # - # `opt_eq` is a specialization of the `opt_send_without_block` instruction - # that occurs when the == operator is used. Fast paths exist within CRuby when - # both operands are integers, floats, symbols or strings. - # - # ### TracePoint - # - # `opt_eq` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 2 == 2 - # ~~~ - # - class OptEq < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptEq[call_data: ^(call_data)] - end - - def reads - 2 - end - - def writes - 1 - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_eq", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_ge.rb b/lib/yarv/insn/opt_ge.rb deleted file mode 100644 index fff68a8..0000000 --- a/lib/yarv/insn/opt_ge.rb +++ /dev/null @@ -1,44 +0,0 @@ -module YARV - # ### Summary - # - # `opt_ge` is a specialization of the `opt_send_without_block` instruction - # that occurs when the >= operator is used. Fast paths exist within CRuby when - # both operands are integers or floats. - # - # ### TracePoint - # - # `opt_ge` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 4 >= 3 - # ~~~ - # - class OptGe < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptGe[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_ge", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_getinlinecache.rb b/lib/yarv/insn/opt_getinlinecache.rb deleted file mode 100644 index 1d253f3..0000000 --- a/lib/yarv/insn/opt_getinlinecache.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_getinlinecache` is a wrapper around a series of `getconstant` - # instructions that allows skipping past them if the inline cache is currently - # set. - # - # ### TracePoint - # - # `opt_getinlinecache` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # Constant - # ~~~ - # - class OptGetInlineCache < Instruction - attr_reader :label, :cache - - def initialize(label, cache) - @label = label - @cache = cache - end - - def ==(other) - other in OptGetInlineCache[label: ^(label), cache: ^(cache)] - end - - def call(context) - # In CRuby, this is going to check if the cache is populated and then - # potentially jump forward to the label. We're not going to track inline - # caches in YARV, so we'll just always push nil onto the stack as if the - # cache weren't yet populated. - context.stack.push(nil) - end - - def deconstruct_keys(keys) - { label:, cache: } - end - - def disasm(iseq) - "%-38s %s, " % - ["opt_getinlinecache", label["label_".length..], cache] - end - end -end diff --git a/lib/yarv/insn/opt_gt.rb b/lib/yarv/insn/opt_gt.rb deleted file mode 100644 index 572112a..0000000 --- a/lib/yarv/insn/opt_gt.rb +++ /dev/null @@ -1,52 +0,0 @@ -module YARV - # ### Summary - # - # `opt_gt` is a specialization of the `opt_send_without_block` instruction - # that occurs when the > operator is used. Fast paths exist within CRuby when - # both operands are integers or floats. - # - # ### TracePoint - # - # `opt_gt` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 4 > 3 - # ~~~ - # - class OptGt < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptGt[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def reads - 2 - end - - def writes - 1 - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_gt", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_le.rb b/lib/yarv/insn/opt_le.rb deleted file mode 100644 index 824ae17..0000000 --- a/lib/yarv/insn/opt_le.rb +++ /dev/null @@ -1,44 +0,0 @@ -module YARV - # ### Summary - # - # `opt_le` is a specialization of the `opt_send_without_block` instruction - # that occurs when the <= operator is used. Fast paths exist within CRuby when - # both operands are integers or floats. - # - # ### TracePoint - # - # `opt_le` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 3 <= 4 - # ~~~ - # - class OptLe < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptLe[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_le", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_length.rb b/lib/yarv/insn/opt_length.rb deleted file mode 100644 index b76807a..0000000 --- a/lib/yarv/insn/opt_length.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_length` is a specialization of `opt_send_without_block`, when the - # `length` method is called on a Ruby type with a known size. In CRuby there - # are fast paths when the receiver is either a String, Hash or Array. - # - # ### TracePoint - # - # `opt_length` can dispatch `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # "".length - # ~~~ - # - class OptLength < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptLength[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_length", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_lt.rb b/lib/yarv/insn/opt_lt.rb deleted file mode 100644 index 2cfd050..0000000 --- a/lib/yarv/insn/opt_lt.rb +++ /dev/null @@ -1,52 +0,0 @@ -module YARV - # ### Summary - # - # `opt_lt` is a specialization of the `opt_send_without_block` instruction - # that occurs when the < operator is used. Fast paths exist within CRuby when - # both operands are integers or floats. - # - # ### TracePoint - # - # `opt_lt` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 3 < 4 - # ~~~ - # - class OptLt < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptLt[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def reads - 2 - end - - def writes - 1 - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_lt", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_ltlt.rb b/lib/yarv/insn/opt_ltlt.rb deleted file mode 100644 index 67ef4e7..0000000 --- a/lib/yarv/insn/opt_ltlt.rb +++ /dev/null @@ -1,50 +0,0 @@ -module YARV - # ### Summary - # - # `opt_ltlt` is a specialization of the `opt_send_without_block` instruction - # that occurs when the `<<` operator is used. Fast paths exists when the - # receiver is either a String or an Array - # - # ### TracePoint - # - # `opt_ltlt` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # "" << 2 - # - # # == disasm: #@-e:1 (1,0)-(1,7)> (catch: FALSE) - # # 0000 putstring "" ( 1)[Li] - # # 0002 putobject 2 - # # 0004 opt_ltlt [CcCr] - # # 0006 leave - # ~~~ - # - class OptLtLt < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptLtLt[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_ltlt", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_minus.rb b/lib/yarv/insn/opt_minus.rb deleted file mode 100644 index 71c01dc..0000000 --- a/lib/yarv/insn/opt_minus.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_minus` is a specialization of the `opt_send_without_block` instruction - # that occurs when the `-` operator is used. In CRuby, there are fast paths - # for if both operands are integers or both operands are floats. - # - # ### TracePoint - # - # `opt_minus` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 3 - 2 - # ~~~ - # - class OptMinus < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptMinus[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def reads - 2 - end - - def writes - 1 - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_minus", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_mod.rb b/lib/yarv/insn/opt_mod.rb deleted file mode 100644 index f8d318a..0000000 --- a/lib/yarv/insn/opt_mod.rb +++ /dev/null @@ -1,44 +0,0 @@ -module YARV - # ### Summary - # - # `opt_mod` is a specialization of the `opt_send_without_block` instruction - # that occurs when the `%` operator is used. In CRuby, there are fast paths - # for if both operands are integers or both operands are floats. - # - # ### TracePoint - # - # `opt_eq` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 4 % 2 - # ~~~ - # - class OptMod < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptMod[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_mod", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_mult.rb b/lib/yarv/insn/opt_mult.rb deleted file mode 100644 index 887e0d1..0000000 --- a/lib/yarv/insn/opt_mult.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_mult` is a specialization of the `opt_send_without_block` instruction - # that occurs when the `*` operator is used. In CRuby, there are fast paths - # for if both operands are integers or floats. - # - # ### TracePoint - # - # `opt_mult` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 3 * 2 - # ~~~ - # - class OptMult < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptMult[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_mult", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_neq.rb b/lib/yarv/insn/opt_neq.rb deleted file mode 100644 index 31f2295..0000000 --- a/lib/yarv/insn/opt_neq.rb +++ /dev/null @@ -1,49 +0,0 @@ -module YARV - # ### Summary - # - # `opt_neq` is an optimisation that tests whether two values at the top of - # the stack are not equal by testing their equality and performing a logical - # NOT on the result. - # - # This allows `opt_neq` to use the fast paths optimized in `opt_eq` when both - # operands are Integers, Floats, Symbols or Strings. - # - # - # ### TracePoint - # - # `opt_neq` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 2 != 2 - # ~~~ - # - class OptNeq < Instruction - attr_reader :cd_neq, :cd_eq - - def initialize(cd_eq, cd_neq) - @cd_eq = cd_eq - @cd_neq = cd_neq - end - - def ==(other) - other in OptNeq[cd_eq: ^(cd_eq), cd_neq: ^(cd_neq)] - end - - def call(context) - receiver, *arguments = context.stack.pop(cd_neq.argc + 1) - result = context.call_method(cd_neq, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { cd_eq:, cd_neq: } - end - - def disasm(iseq) - "%-38s %s%s%s" % ["opt_neq", cd_eq, cd_neq, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_newarray_max.rb b/lib/yarv/insn/opt_newarray_max.rb deleted file mode 100644 index 829c6db..0000000 --- a/lib/yarv/insn/opt_newarray_max.rb +++ /dev/null @@ -1,45 +0,0 @@ -module YARV - # ### Summary - # - # `opt_newarray_max` is an instruction that represents calling `max` on an - # array literal. It is used to optimize quick comparisons of array elements. - # - # ### TracePoint - # - # `opt_newarray_max` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # [1, x = 2].max - # ~~~ - # - class OptNewArrayMax < Instruction - attr_reader :size - - def initialize(size) - @size = size - end - - def ==(other) - other in OptNewArrayMax[size: ^(size)] - end - - def call(context) - elements = context.stack.pop(size) - call_data = - CallData.new(:max, 0, 1 << CallData::FLAGS.index(:ARGS_SIMPLE)) - - result = context.call_method(call_data, elements, []) - context.stack.push(result) - end - - def deconstruct_keys(keys) - { size: } - end - - def disasm(iseq) - "%-38s %d" % ["opt_newarray_max", size] - end - end -end diff --git a/lib/yarv/insn/opt_newarray_min.rb b/lib/yarv/insn/opt_newarray_min.rb deleted file mode 100644 index 6ca8431..0000000 --- a/lib/yarv/insn/opt_newarray_min.rb +++ /dev/null @@ -1,45 +0,0 @@ -module YARV - # ### Summary - # - # `opt_newarray_min` is an instruction that represents calling `min` on an - # array literal. It is used to optimize quick comparisons of array elements. - # - # ### TracePoint - # - # `opt_newarray_min` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # [1, x = 2].min - # ~~~ - # - class OptNewArrayMin < Instruction - attr_reader :size - - def initialize(size) - @size = size - end - - def ==(other) - other in OptNewArrayMin[size: ^(size)] - end - - def call(context) - elements = context.stack.pop(size) - call_data = - CallData.new(:min, 0, 1 << CallData::FLAGS.index(:ARGS_SIMPLE)) - - result = context.call_method(call_data, elements, []) - context.stack.push(result) - end - - def deconstruct_keys(keys) - { size: } - end - - def disasm(iseq) - "%-38s %d" % ["opt_newarray_min", size] - end - end -end diff --git a/lib/yarv/insn/opt_nil_p.rb b/lib/yarv/insn/opt_nil_p.rb deleted file mode 100644 index 43a3437..0000000 --- a/lib/yarv/insn/opt_nil_p.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_nil_p` is an optimization applied when the method `nil?` is called. It - # returns true immediately when the receiver is `nil` and defers to the `nil?` - # method in other cases - # - # ### TracePoint - # - # `opt_nil_p` can dispatch `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # "".nil? - # ~~~ - # - class OptNilP < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptNilP[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_nil_p", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_not.rb b/lib/yarv/insn/opt_not.rb deleted file mode 100644 index 5356462..0000000 --- a/lib/yarv/insn/opt_not.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_not` negates the value on top of the stack. - # - # ### TracePoint - # - # `opt_not` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # !true - # ~~~ - # - class OptNot < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptNot[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_not", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_or.rb b/lib/yarv/insn/opt_or.rb deleted file mode 100644 index bec328b..0000000 --- a/lib/yarv/insn/opt_or.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_or` is a specialization of the `opt_send_without_block` instruction - # that occurs when the `|` operator is used. In CRuby, there are fast paths - # for if both operands are integers. - # - # ### TracePoint - # - # `opt_or` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 2 | 3 - # ~~~ - # - class OptOr < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptOr[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_or", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_plus.rb b/lib/yarv/insn/opt_plus.rb deleted file mode 100644 index 1e66143..0000000 --- a/lib/yarv/insn/opt_plus.rb +++ /dev/null @@ -1,54 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_plus` is a specialization of the `opt_send_without_block` instruction - # that occurs when the `+` operator is used. In CRuby, there are fast paths - # for if both operands are integers, floats, strings, or arrays. - # - # ### TracePoint - # - # `opt_plus` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # 2 + 3 - # ~~~ - # - class OptPlus < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptPlus[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def reads - 2 - end - - def writes - 1 - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_plus", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_regexpmatch2.rb b/lib/yarv/insn/opt_regexpmatch2.rb deleted file mode 100644 index b1cc1c7..0000000 --- a/lib/yarv/insn/opt_regexpmatch2.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_regexpmatch2` is a specialization of the `opt_send_without_block` - # instruction that occurs when the `=~` operator is used. - # - # ### TracePoint - # - # `opt_regexpmatch2` can dispatch both the `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # /a/ =~ "a" - # ~~~ - # - class OptRegexpMatch2 < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptRegexpMatch2[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_regexpmatch2", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_send_without_block.rb b/lib/yarv/insn/opt_send_without_block.rb deleted file mode 100644 index 7adaf91..0000000 --- a/lib/yarv/insn/opt_send_without_block.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_send_without_block` is a specialization of the send instruction that - # occurs when a method is being called without a block. - # - # ### TracePoint - # - # `opt_send_without_block` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # puts "Hello, world!" - # ~~~ - # - class OptSendWithoutBlock < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptSendWithoutBlock[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def reads - call_data.argc + 1 - end - - def writes - 1 - end - - def disasm(iseq) - "%-38s %s" % ["opt_send_without_block", call_data] - end - end -end diff --git a/lib/yarv/insn/opt_setinlinecache.rb b/lib/yarv/insn/opt_setinlinecache.rb deleted file mode 100644 index 473325f..0000000 --- a/lib/yarv/insn/opt_setinlinecache.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_setinlinecache` is the final instruction after a series of - # `getconstant` instructions that populates the inline cache associated with - # an `opt_getinlinecache` instruction. - # - # ### TracePoint - # - # `opt_setinlinecache` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # Constant - # ~~~ - # - class OptSetInlineCache < Instruction - attr_reader :cache - - def initialize(cache) - @cache = cache - end - - def ==(other) - other in OptSetInlineCache[cache: ^(cache)] - end - - def call(context) - # Since we're not actually populating inline caches in YARV, we don't need - # to do anything in this instruction. - end - - def deconstruct_keys(keys) - { cache: } - end - - def disasm(iseq) - "%-38s " % ["opt_setinlinecache", cache] - end - end -end diff --git a/lib/yarv/insn/opt_size.rb b/lib/yarv/insn/opt_size.rb deleted file mode 100644 index a43ab41..0000000 --- a/lib/yarv/insn/opt_size.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_size` is a specialization of `opt_send_without_block`, when the - # `size` method is called on a Ruby type with a known size. In CRuby there - # are fast paths when the receiver is either a String, Hash or Array. - # - # ### TracePoint - # - # `opt_size` can dispatch `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # "".size - # ~~~ - # - class OptSize < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptSize[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_size", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/opt_str_freeze.rb b/lib/yarv/insn/opt_str_freeze.rb deleted file mode 100644 index c576645..0000000 --- a/lib/yarv/insn/opt_str_freeze.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_str_freeze` pushes a frozen known string value with no interpolation - # onto the stack. - # - # ### TracePoint - # - # `opt_str_freeze` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # "hello".freeze - # ~~~ - # - class OptStrFreeze < Instruction - attr_reader :value, :call_data - - def initialize(value, call_data) - @value = value - @call_data = call_data - end - - def ==(other) - other in OptStrFreeze[value: ^(value), call_data: ^(call_data)] - end - - def call(context) - result = context.call_method(call_data, value, []) - context.stack.push(result) - end - - def deconstruct_keys(keys) - { value:, call_data: } - end - - def disasm(iseq) - "%-38s %s, %s" % ["opt_str_freeze", value.inspect, call_data] - end - end -end diff --git a/lib/yarv/insn/opt_str_uminus.rb b/lib/yarv/insn/opt_str_uminus.rb deleted file mode 100644 index ed8a729..0000000 --- a/lib/yarv/insn/opt_str_uminus.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_str_uminus` pushes a frozen known string value with no interpolation - # onto the stack. - # - # ### TracePoint - # - # `opt_str_uminus` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # -"string" - # ~~~ - # - class OptStrUMinus < Instruction - attr_reader :value, :call_data - - def initialize(value, call_data) - @value = value - @call_data = call_data - end - - def ==(other) - other in OptStrUMinus[value: ^(value), call_data: ^(call_data)] - end - - def call(context) - arguments = context.stack.pop(call_data.argc) - result = context.call_method(call_data, value, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { value:, call_data: } - end - - def disasm(iseq) - "%-38s %s, %s" % ["opt_str_uminus", value.inspect, call_data] - end - end -end diff --git a/lib/yarv/insn/opt_succ.rb b/lib/yarv/insn/opt_succ.rb deleted file mode 100644 index 7acce10..0000000 --- a/lib/yarv/insn/opt_succ.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `opt_succ` is a specialization of the `opt_send_without_block` instruction - # when the method being called is `succ`. Fast paths exist within CRuby when - # the receiver is either a String or a Fixnum. - # - # ### TracePoint - # - # `opt_succ` can dispatch `c_call` and `c_return` events. - # - # ### Usage - # - # ~~~ruby - # "".succ - # ~~~ - # - class OptSucc < Instruction - attr_reader :call_data - - def initialize(call_data) - @call_data = call_data - end - - def ==(other) - other in OptSucc[call_data: ^(call_data)] - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = context.call_method(call_data, receiver, arguments) - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data: } - end - - def disasm(iseq) - "%-38s %s%s" % ["opt_succ", call_data, "[CcCr]"] - end - end -end diff --git a/lib/yarv/insn/pop.rb b/lib/yarv/insn/pop.rb deleted file mode 100644 index f80b301..0000000 --- a/lib/yarv/insn/pop.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `pop` pops the top value off the stack. - # - # ### TracePoint - # - # `pop` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # a ||= 2 - # ~~~ - # - class Pop < Instruction - def ==(other) - other in Pop - end - - def call(context) - context.stack.pop - end - - def reads - 1 - end - - def writes - 0 - end - - def side_effects? - false - end - - def disasm(iseq) - "pop" - end - end -end diff --git a/lib/yarv/insn/putnil.rb b/lib/yarv/insn/putnil.rb deleted file mode 100644 index c797e4f..0000000 --- a/lib/yarv/insn/putnil.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `putnil` pushes a global nil object onto the stack. - # - # ### TracePoint - # - # `putnil` can dispatch the line event. - # - # ### Usage - # - # ~~~ruby - # nil - # ~~~ - # - class PutNil < Instruction - def ==(other) - other in PutNil - end - - def call(context) - context.stack.push(nil) - end - - def reads - 0 - end - - def writes - 1 - end - - def side_effects? - false - end - - def disasm(iseq) - "putnil" - end - end -end diff --git a/lib/yarv/insn/putobject.rb b/lib/yarv/insn/putobject.rb deleted file mode 100644 index 3c9d301..0000000 --- a/lib/yarv/insn/putobject.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `putobject` pushes a known value onto the stack. - # - # ### TracePoint - # - # `putobject` can dispatch the line event. - # - # ### Usage - # - # ~~~ruby - # 5 - # ~~~ - # - class PutObject < Instruction - attr_reader :object - - def initialize(object) - @object = object - end - - def ==(other) - other in PutObject[object: ^(object)] - end - - def call(context) - context.stack.push(object) - end - - def deconstruct_keys(keys) - { object: } - end - - def reads - 0 - end - - def writes - 1 - end - - def side_effects? - false - end - - def disasm(iseq) - "%-38s %s" % ["putobject", object.inspect] - end - end -end diff --git a/lib/yarv/insn/putobject_int2fix_0.rb b/lib/yarv/insn/putobject_int2fix_0.rb deleted file mode 100644 index fbf984b..0000000 --- a/lib/yarv/insn/putobject_int2fix_0.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `putobject_INT2FIX_0_` pushes 0 on the stack. - # It is a specialized instruction resulting from the operand - # unification optimization. It is the equivalent to `putobject 0`. - # - # ### TracePoint - # - # `putobject` can dispatch the line event. - # - # ### Usage - # - # ~~~ruby - # 0 - # ~~~ - # - class PutObjectInt2Fix0 < Instruction - def ==(other) - other in PutObjectInt2Fix0 - end - - def call(context) - context.stack.push(0) - end - - def reads - 0 - end - - def writes - 1 - end - - def side_effects? - false - end - - def disasm(iseq) - "putobject_INT2FIX_0_" - end - end -end diff --git a/lib/yarv/insn/putobject_int2fix_1.rb b/lib/yarv/insn/putobject_int2fix_1.rb deleted file mode 100644 index 5bc3bba..0000000 --- a/lib/yarv/insn/putobject_int2fix_1.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `putobject_INT2FIX_1_` pushes 1 on the stack. - # It is a specialized instruction resulting from the operand - # unification optimization. It is the equivalent to `putobject 1`. - # - # ### TracePoint - # - # `putobject` can dispatch the line event. - # - # ### Usage - # - # ~~~ruby - # 1 - # ~~~ - # - class PutObjectInt2Fix1 < Instruction - def ==(other) - other in PutObjectInt2Fix1 - end - - def call(context) - context.stack.push(1) - end - - def reads - 0 - end - - def writes - 1 - end - - def side_effects? - false - end - - def disasm(iseq) - "putobject_INT2FIX_1_" - end - end -end diff --git a/lib/yarv/insn/putself.rb b/lib/yarv/insn/putself.rb deleted file mode 100644 index e9fabeb..0000000 --- a/lib/yarv/insn/putself.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `putself` pushes the current value of self onto the stack. - # - # ### TracePoint - # - # `putself` can dispatch the line event. - # - # ### Usage - # - # ~~~ruby - # puts "Hello, world!" - # ~~~ - # - class PutSelf < Instruction - attr_reader :object - - def initialize(object) - @object = object - end - - def ==(other) - other in PutSelf[object: ^(object)] - end - - def call(context) - context.stack.push(object) - end - - def reads - 0 - end - - def writes - 1 - end - - def side_effects? - false - end - - def deconstruct_keys(keys) - { object: } - end - - def disasm(iseq) - "putself" - end - end -end diff --git a/lib/yarv/insn/putstring.rb b/lib/yarv/insn/putstring.rb deleted file mode 100644 index c546c6c..0000000 --- a/lib/yarv/insn/putstring.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `putstring` pushes a string literal onto the stack. - # - # ### TracePoint - # - # `putstring` can dispatch the line event. - # - # ### Usage - # - # ~~~ruby - # "foo" - # ~~~ - # - class PutString < Instruction - attr_reader :string - - def initialize(string) - @string = string - end - - def ==(other) - other in PutString[string: ^(string)] - end - - def reads - 0 - end - - def writes - 1 - end - - def call(context) - context.stack.push(string) - end - - def deconstruct_keys(keys) - { string: } - end - - def disasm(iseq) - "%-38s %s" % ["putstring", string.inspect] - end - end -end diff --git a/lib/yarv/insn/send.rb b/lib/yarv/insn/send.rb deleted file mode 100644 index 14de8f9..0000000 --- a/lib/yarv/insn/send.rb +++ /dev/null @@ -1,77 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `send` invokes a method with a block. - # - # ### TracePoint - # - # `send` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # "hello".tap { |i| p i } - # - # # == disasm: #@-e:1 (1,0)-(1,23)> (catch: FALSE) - # # 0000 putstring "hello" ( 1)[Li] - # # 0002 send , block in
- # # 0005 leave - # # - # # == disasm: #@-e:1 (1,12)-(1,23)> (catch: FALSE) - # # local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1]) - # # [ 1] i@0 - # # 0000 putself ( 1)[LiBc] - # # 0001 getlocal_WC_0 i@0 - # # 0003 opt_send_without_block - # # 0005 leave [Br] - # ~~~ - # - class Send < Instruction - attr_reader :call_data, :block_iseq - - def initialize(call_data, block_iseq) - @call_data = call_data - @block_iseq = block_iseq - end - - def ==(other) - other in Send[call_data: ^(call_data), block_iseq: ^(block_iseq)] - end - - def reads - call_data.argc + 1 - end - - def writes - 1 - end - - def call(context) - receiver, *arguments = context.stack.pop(call_data.argc + 1) - result = - if block_iseq.nil? - context.call_method(call_data, receiver, arguments) - else - context.call_method(call_data, receiver, arguments) do |*args| - context.eval(block_iseq) do - args.each_with_index do |arg, index| - context.current_frame.locals[index] = arg - end - end - end - end - - context.stack.push(result) - end - - def deconstruct_keys(keys) - { call_data:, block_iseq: } - end - - def disasm(iseq) - "%-38s %s, %s" % ["send", call_data, block_iseq.name] - end - end -end diff --git a/lib/yarv/insn/setglobal.rb b/lib/yarv/insn/setglobal.rb deleted file mode 100644 index 8ed5d1d..0000000 --- a/lib/yarv/insn/setglobal.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `setglobal` sets the value of a global variable. - # - # ### TracePoint - # - # `setglobal` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # $global = 5 - # ~~~ - # - class SetGlobal < Instruction - attr_reader :name - - def initialize(name) - @name = name - end - - def ==(other) - other in SetGlobal[name: ^(name)] - end - - def call(context) - # If we're not currently tracking the global variable, then we're going to - # steal the definition of it from the parent process by eval-ing it. - if !context.globals.key?(name) && global_variables.include?(name) - context.globals[name] = eval(name.to_s) - end - - context.globals[name] = context.stack.pop - end - - def deconstruct_keys(keys) - { name: } - end - - def disasm(iseq) - "%-38s %s" % ["setglobal", name.inspect] - end - end -end diff --git a/lib/yarv/insn/setlocal.rb b/lib/yarv/insn/setlocal.rb deleted file mode 100644 index db79b69..0000000 --- a/lib/yarv/insn/setlocal.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `setlocal` sets the value of a local variable on a frame determined by the - # level and index arguments. The level is the number of frames back to - # look and the index is the index in the local table. It pops the value it is - # setting off the stack. - # - # ### TracePoint - # - # `setlocal` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # value = 5 - # tap { tap { value = 10 } } - # ~~~ - # - class SetLocal < Instruction - attr_reader :name, :index, :level - - def initialize(name, index, level) - @name = name - @index = index - @level = level - end - - def ==(other) - other in SetLocal[name: ^(name), index: ^(index), level: ^(level)] - end - - def call(context) - value = context.stack.pop - context.parent_frame(level).set_local(index, value) - end - - def deconstruct_keys(keys) - { name:, index:, level: } - end - - def disasm(iseq) - "%-38s %s@%d, %d" % ["setlocal", name, index, level] - end - end -end diff --git a/lib/yarv/insn/setlocal_wc_0.rb b/lib/yarv/insn/setlocal_wc_0.rb deleted file mode 100644 index e8e467f..0000000 --- a/lib/yarv/insn/setlocal_wc_0.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `setlocal_WC_0` is a specialized version of the `setlocal` instruction. It - # sets the value of a local variable on the current frame to the value at the - # top of the stack as determined by the index given as its only argument. - # - # ### TracePoint - # - # `setlocal_WC_0` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # value = 5 - # ~~~ - # - class SetLocalWC0 < Instruction - attr_reader :name, :index - - def initialize(name, index) - @name = name - @index = index - end - - def ==(other) - other in SetLocalWC0[name: ^(name), index: ^(index)] - end - - def call(context) - value = context.stack.pop - context.current_frame.set_local(index, value) - end - - def reads - 1 - end - - def writes - 0 - end - - def disasm(iseq) - "%-38s %s@%d" % ["setlocal_WC_0", name, index] - end - end -end diff --git a/lib/yarv/insn/setlocal_wc_1.rb b/lib/yarv/insn/setlocal_wc_1.rb deleted file mode 100644 index 6bee12e..0000000 --- a/lib/yarv/insn/setlocal_wc_1.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `setlocal_WC_1` is a specialized version of the `setlocal` instruction. It - # sets the value of a local variable on the parent frame to the value at the - # top of the stack as determined by the index given as its only argument. - # - # ### TracePoint - # - # `setlocal_WC_1` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # value = 5 - # self.then { value = 10 } - # ~~~ - # - class SetLocalWC1 < Instruction - attr_reader :name, :index - - def initialize(name, index) - @name = name - @index = index - end - - def ==(other) - other in SetLocalWC1[name: ^(name), index: ^(index)] - end - - def call(context) - value = context.stack.pop - context.parent_frame.set_local(index, value) - end - - def reads - 1 - end - - def writes - 0 - end - - def disasm(iseq) - "%-38s %s@%d" % ["setlocal_WC_1", name, index] - end - end -end diff --git a/lib/yarv/insn/setn.rb b/lib/yarv/insn/setn.rb deleted file mode 100644 index 743a953..0000000 --- a/lib/yarv/insn/setn.rb +++ /dev/null @@ -1,39 +0,0 @@ -module YARV - # ### Summary - # - # `setn` is an instruction for set Nth stack entry to stack top - # - # ### TracePoint - # - # # `setn` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # {}[:key] = 'val' - # ~~~ - # - class SetN < Instruction - attr_reader :index - - def initialize(index) - @index = index - end - - def ==(other) - other in SetN[index: ^(index)] - end - - def call(context) - context.stack[-index - 1] = context.stack.last - end - - def deconstruct_keys(keys) - { index: } - end - - def disasm(iseq) - "%-38s %s" % ["setn", index] - end - end -end diff --git a/lib/yarv/insn/splatarray.rb b/lib/yarv/insn/splatarray.rb deleted file mode 100644 index 01fd9ca..0000000 --- a/lib/yarv/insn/splatarray.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `splatarray` calls to_a on an array to splat. - # - # It coerces the array object at the top of the stack into Array by calling - # `to_a`. It pushes a duplicate of the array if there is a flag, and the original - # array, if there isn't one. - # - # ### TracePoint - # - # `splayarray` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # x = *(5) - # ~~~ - # - class SplatArray < Instruction - attr_reader :flag - - def initialize(flag) - @flag = flag - end - - def ==(other) - other in SplatArray - end - - def call(context) - array = coerce(context.stack.pop) - final_array = flag ? array.dup : array - context.stack.push(final_array) - end - - def disasm(iseq) - "splatarray" - end - - private - - def coerce(object) - object.respond_to?(:to_a) ? object.to_a : [object] - end - end -end diff --git a/lib/yarv/insn/swap.rb b/lib/yarv/insn/swap.rb deleted file mode 100644 index e38dce2..0000000 --- a/lib/yarv/insn/swap.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `swap` swaps the top two elements in the stack. - # - # ### TracePoint - # - # `swap` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # !!defined?([[]]) - # ~~~ - # - class Swap < Instruction - def ==(other) - other in Swap - end - - def call(context) - left, right = context.stack.pop(2) - context.stack.push(right) - context.stack.push(left) - end - - def disasm(iseq) - "swap" - end - end -end diff --git a/lib/yarv/insn/topn.rb b/lib/yarv/insn/topn.rb deleted file mode 100644 index 9f641ff..0000000 --- a/lib/yarv/insn/topn.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `topn` has one argument: `n`. It gets the nth element from the top of the - # stack and pushes it on the stack. - # - # ### TracePoint - # - # `topn` does not dispatch any events. - # - # ### Usage - # - # ~~~ruby - # case 3 - # when 1..5 - # puts "foo" - # end - # - # # == disasm: #@:1 (1,0)-(1,36)> (catch: FALSE) - # # 0000 putobject 3 ( 1)[Li] - # # 0002 putobject 1..5 - # # 0004 topn 1 - # # 0006 opt_send_without_block - # # 0008 branchif 13 - # # 0010 pop - # # 0011 putnil - # # 0012 leave - # # 0013 pop - # # 0014 putself - # # 0015 putstring "foo" - # # 0017 opt_send_without_block - # # 0019 leave - # ~~~ - # - class TopN < Instruction - attr_reader :n - - def initialize(n) - @n = n - end - - def ==(other) - other in TopN[n: ^(n)] - end - - def call(context) - value = context.stack[-n - 1] - context.stack.push(value) - end - - def deconstruct_keys(keys) - { n: } - end - - def disasm(iseq) - "%-38s %d" % ["topn", n] - end - end -end diff --git a/lib/yarv/insn/toregexp.rb b/lib/yarv/insn/toregexp.rb deleted file mode 100644 index 7f0ec0f..0000000 --- a/lib/yarv/insn/toregexp.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -module YARV - # ### Summary - # - # `toregexp` is generated when string interpolation is used inside a regex - # literal `//`. It pops a defined number of values from the stack, combines - # them into a single string and coerces that string into a `Regexp` object, - # which it pushes back onto the stack - # - # ### TracePoint - # - # `toregexp` cannot dispatch any TracePoint events. - # - # ### Usage - # - # ~~~ruby - # "/#{true}/" - # - # # == disasm: #@-e:1 (1,0)-(1,9)> (catch: FALSE) - # # 0000 putobject "" ( 1)[Li] - # # 0002 putobject true - # # 0004 dup - # # 0005 objtostring - # # 0007 anytostring - # # 0008 toregexp 0, 2 - # # 0011 leave - # ~~~ - # - class ToRegexp < Instruction - attr_reader :opts, :cnt - - def initialize(opts, cnt) - @opts = opts - @cnt = cnt - end - - def ==(other) - other in ToRegexp[opts: ^(opts), cnt: ^(cnt)] - end - - def call(context) - re_str = context.stack.pop(cnt).reverse.join - context.stack.push(Regexp.new(re_str, opts)) - end - - def deconstruct_keys(keys) - { opts:, cnt: } - end - - def disasm(iseq) - "%-38s %s, %s" % ["toregexp", opts, cnt] - end - end -end diff --git a/lib/yarv/instruction.rb b/lib/yarv/instruction.rb deleted file mode 100644 index a88f720..0000000 --- a/lib/yarv/instruction.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -module YARV - # Abstract base instruction. - class Instruction - # Whether or not this instruction is a branch instruction. - def branches? - false - end - - # Whether or not this instruction leaves the current frame. - def leaves? - false - end - - # Whether or not this instruction falls through to the next instruction if - # its branching fails. - def falls_through? - false - end - - # How many values are read from the stack. - def reads - raise "not implemented #{self.class}" - end - - # How many values are written to the stack. - def writes - raise "not implemented #{self.class}" - end - - # Does the instruction have side effects? Control-flow counts as a - # side-effect, as do some special-case instructions like Leave - def side_effects? - true - end - - # A hook method to be called when the instruction is being disassembled. The - # child classes will have their respective InstructionSequence passed in. - def to_s - disasm(nil) - end - end -end diff --git a/lib/yarv/instruction_sequence.rb b/lib/yarv/instruction_sequence.rb deleted file mode 100644 index e3ca8ec..0000000 --- a/lib/yarv/instruction_sequence.rb +++ /dev/null @@ -1,456 +0,0 @@ -# frozen_string_literal: true - -module YARV - # This object represents a set of instructions that will be executed. - class InstructionSequence - attr_reader :selfo, :insns, :labels - - # This is the native instruction sequence that we are wrapping. - attr_reader :iseq - - # This is the parent InstructionSequence object if there is one. - attr_reader :parent - - # These handlers handle thrown exceptions. - ThrowHandler = - Struct.new(:type, :iseq, :begin_label, :end_label, :exit_label) - - attr_reader :throw_handlers - - def initialize(selfo, iseq, parent = nil) - @selfo = selfo - @iseq = iseq[...-1] - @parent = parent - - @insns = [] - @labels = {} - - @throw_handlers = - (catch_table || []).map do |handler| - type, child_iseq, begin_label, end_label, exit_label, = handler - throw_iseq = - InstructionSequence.compile(selfo, child_iseq, self) if child_iseq - - ThrowHandler.new(type, throw_iseq, begin_label, end_label, exit_label) - end - end - - class UnimplementedInstruction - attr_reader :name, :args - - def initialize(name, *args) - @name = name - @args = args - end - - def ==(other) - other in UnimplementedInstruction[name: ^(name), args: ^(args)] - end - - def call(context) - raise NotImplementedError, "Unimplemented instruction: #{name}" - end - - def deconstruct_keys(keys) - { name:, args: } - end - - def to_s - name.to_s - end - end - - def self.compile(selfo, iseq, parent = nil) - iseq - .last - .each_with_object(new(selfo, iseq, parent)) do |insn, compiled| - case insn - in Integer | :RUBY_EVENT_LINE - # skip for now - in Symbol - compiled.labels[insn] = compiled.insns.length - in [:adjuststack, size] - compiled << AdjustStack.new(size) - in [:anytostring] - compiled << AnyToString.new - in [:branchif, value] - compiled << BranchIf.new(value) - in [:branchnil, value] - compiled << BranchNil.new(value) - in [:branchunless, value] - compiled << BranchUnless.new(value) - in [:checkkeyword, bits_index, index] - compiled << UnimplementedInstruction.new( - "checkkeyword", - bits_index, - index - ) - in [:checkmatch, type] - compiled << UnimplementedInstruction.new("checkmatch", type) - in [:checktype, type] - compiled << UnimplementedInstruction.new("checktype", type) - in [:concatarray] - compiled << ConcatArray.new - in [:concatstrings, num] - compiled << ConcatStrings.new(num) - in [:defineclass, name, iseq, flags] - compiled << UnimplementedInstruction.new( - "defineclass", - name, - compile(selfo, iseq, compiled), - flags - ) - in [:defined, type, object, value] - compiled << Defined.new(type, object, value) - in [:definemethod, name, iseq] - compiled << DefineMethod.new(name, compile(selfo, iseq, compiled)) - in [:definesmethod, name, iseq] - compiled << UnimplementedInstruction.new( - "definesmethod", - name, - compile(selfo, iseq, compiled) - ) - in [:dup] - compiled << Dup.new - in [:duparray, array] - compiled << DupArray.new(array) - in [:duphash, hash] - compiled << DupHash.new(hash) - in [:dupn, offset] - compiled << DupN.new(offset) - in [:expandarray, size, flag] - compiled << ExpandArray.new(size, flag) - in [:getblockparam, index, level] - compiled << UnimplementedInstruction.new( - "getblockparam", - index, - level - ) - in [:getblockparamproxy, index, level] - compiled << UnimplementedInstruction.new( - "getblockparamproxy", - index, - level - ) - in [:getclassvariable, name, cache] - compiled << UnimplementedInstruction.new( - "getclassvariable", - name, - cache - ) - in [:getconstant, name] - compiled << GetConstant.new(name) - in [:getglobal, value] - compiled << GetGlobal.new(value) - in [:getinstancevariable, name, cache] - compiled << UnimplementedInstruction.new( - "getinstancevariable", - name, - cache - ) - in [:getlocal, offset, level] - current = compiled - level.times { current = current.parent } - - index = current.local_index(offset) - compiled << GetLocal.new(current.locals[index], index, level) - in [:getlocal_WC_0, offset] - index = compiled.local_index(offset) - compiled << GetLocalWC0.new(compiled.locals[index], index) - in [:getlocal_WC_1, offset] - index = parent.local_index(offset) - compiled << GetLocalWC1.new(parent.locals[index], index) - in [:getspecial, key, type] - compiled << UnimplementedInstruction.new("getspecial", key, type) - in [:intern] - compiled << Intern.new - in [:invokeblock, { mid: nil, orig_argc:, flag: }] - compiled << InvokeBlock.new(CallData.new(nil, orig_argc, flag)) - in [:invokesuper, { mid: nil, orig_argc:, flag: }, block_iseq] - block_iseq = compile(selfo, block_iseq, compiled) if block_iseq - compiled << UnimplementedInstruction.new( - "invokesuper", - CallData.new(nil, orig_argc, flag), - block_iseq - ) - in [:jump, value] - compiled << Jump.new(value) - in [:leave] - compiled << Leave.new - in [:newarray, size] - compiled << NewArray.new(size) - in [:newhash, size] - compiled << NewHash.new(size) - in [:newarraykwsplat, size] - compiled << UnimplementedInstruction.new("newarraykwsplat", size) - in [:newrange, exclude_end] - compiled << NewRange.new(exclude_end) - in [:nop] - compiled << Nop.new - in [:objtostring, { mid: :to_s, orig_argc: 0, flag: }] - compiled << ObjToString.new(CallData.new(:to_s, 0, flag)) - in [:once, iseq, cache] - compiled << UnimplementedInstruction.new( - "once", - compile(selfo, iseq, compiled), - cache - ) - in [:opt_and, { mid: :&, orig_argc: 1, flag: }] - compiled << OptAnd.new(CallData.new(:&, 1, flag)) - in [:opt_aref, { mid: :[], orig_argc: 1, flag: }] - compiled << OptAref.new(CallData.new(:[], 1, flag)) - in [:opt_aset, { mid: :[]=, orig_argc: 2, flag: }] - compiled << OptAset.new(CallData.new(:[]=, 2, flag)) - in [:opt_aset_with, key, { mid: :[]=, orig_argc: 2, flag: }] - compiled << OptAsetWith.new(key, CallData.new(:[]=, 2, flag)) - in [:opt_aref_with, key, { mid: :[], orig_argc: 1, flag: }] - compiled << OptArefWith.new(key, CallData.new(:[], 1, flag)) - in [:opt_case_dispatch, cdhash, offset] - compiled << OptCaseDispatch.new(cdhash, offset) - in [:opt_div, { mid: :/, orig_argc: 1, flag: }] - compiled << OptDiv.new(CallData.new(:/, 1, flag)) - in [:opt_empty_p, { mid: :empty?, orig_argc: 0, flag: }] - compiled << OptEmptyP.new(CallData.new(:empty?, 0, flag)) - in [:opt_eq, { mid: :==, orig_argc: 1, flag: }] - compiled << OptEq.new(CallData.new(:==, 1, flag)) - in [:opt_getconstant_path, names] - compiled << UnimplementedInstruction.new( - "opt_getconstant_path", - names - ) - in [:opt_neq, eq_cd, neq_cd] - compiled << OptNeq.new( - CallData.new(:==, 1, eq_cd.fetch(:flag)), - CallData.new(:!=, 1, neq_cd.fetch(:flag)) - ) - in [:opt_ge, { mid: :>=, orig_argc: 1, flag: }] - compiled << OptGe.new(CallData.new(:>=, 1, flag)) - in [:opt_gt, { mid: :>, orig_argc: 1, flag: }] - compiled << OptGt.new(CallData.new(:>, 1, flag)) - in [:opt_le, { mid: :<=, orig_argc: 1, flag: }] - compiled << OptLe.new(CallData.new(:<=, 1, flag)) - in [:opt_lt, { mid: :<, orig_argc: 1, flag: }] - compiled << OptLt.new(CallData.new(:<, 1, flag)) - in [:opt_ltlt, { mid: :<<, orig_argc: 1, flag: }] - compiled << OptLtLt.new(CallData.new(:<<, 1, flag)) - in [:opt_nil_p, { mid: :nil?, orig_argc: 0, flag: }] - compiled << OptNilP.new(CallData.new(:nil?, 0, flag)) - in [:opt_getinlinecache, label, cache] - compiled << OptGetInlineCache.new(label, cache) - in [:opt_length, { mid: :length, orig_argc: 0, flag: }] - compiled << OptLength.new(CallData.new(:length, 0, flag)) - in [:opt_minus, { mid: :-, orig_argc: 1, flag: }] - compiled << OptMinus.new(CallData.new(:-, 1, flag)) - in [:opt_mod, { mid: :%, orig_argc: 1, flag: }] - compiled << OptMod.new(CallData.new(:%, 1, flag)) - in [:opt_mult, { mid: :*, orig_argc: 1, flag: }] - compiled << OptMult.new(CallData.new(:*, 1, flag)) - in [:opt_newarray_max, size] - compiled << OptNewArrayMax.new(size) - in [:opt_newarray_min, size] - compiled << OptNewArrayMin.new(size) - in [:opt_not, { mid: :!, orig_argc: 0, flag: }] - compiled << OptNot.new(CallData.new(:!, 0, flag)) - in [:opt_or, { mid: :|, orig_argc: 1, flag: }] - compiled << OptOr.new(CallData.new(:|, 1, flag)) - in [:opt_plus, { mid: :+, orig_argc: 1, flag: }] - compiled << OptPlus.new(CallData.new(:+, 1, flag)) - in [:opt_regexpmatch2, { mid: :=~, orig_argc: 1, flag: }] - compiled << OptRegexpMatch2.new(CallData.new(:=~, 1, flag)) - in [:opt_send_without_block, { mid:, orig_argc:, flag: }] - compiled << OptSendWithoutBlock.new( - CallData.new(mid, orig_argc, flag) - ) - in [:opt_setinlinecache, cache] - compiled << OptSetInlineCache.new(cache) - in [:opt_size, { mid: :size, orig_argc: 0, flag: }] - compiled << OptSize.new(CallData.new(:size, 0, flag)) - in [:opt_str_freeze, value, { mid: :freeze, orig_argc: 0, flag: }] - compiled << OptStrFreeze.new(value, CallData.new(:freeze, 0, flag)) - in [:opt_str_uminus, value, { mid: :-@, orig_argc: 0, flag: }] - compiled << OptStrUMinus.new(value, CallData.new(:-@, 0, flag)) - in [:opt_succ, { mid: :succ, orig_argc: 0, flag: }] - compiled << OptSucc.new(CallData.new(:succ, 0, flag)) - in [:pop] - compiled << Pop.new - in [:putnil] - compiled << PutNil.new - in [:putobject, object] - compiled << PutObject.new(object) - in [:putobject_INT2FIX_0_] - compiled << PutObjectInt2Fix0.new - in [:putobject_INT2FIX_1_] - compiled << PutObjectInt2Fix1.new - in [:putself] - compiled << PutSelf.new(selfo) - in [:putspecialobject, type] - compiled << UnimplementedInstruction.new("putspecialobject", type) - in [:putstring, string] - compiled << PutString.new(string) - in [:send, { mid:, orig_argc:, flag: }, block_iseq] - block_iseq = - compile(selfo, block_iseq, compiled) unless block_iseq.nil? - - compiled << Send.new(CallData.new(mid, orig_argc, flag), block_iseq) - in [:setblockparam, index, level] - compiled << UnimplementedInstruction.new( - "setblockparam", - index, - level - ) - in [:setclassvariable, name, cache] - compiled << UnimplementedInstruction.new( - "setclassvariable", - name, - cache - ) - in [:setconstant, name] - compiled << UnimplementedInstruction.new("setconstant", name) - in [:setglobal, name] - compiled << SetGlobal.new(name) - in [:setinstancevariable, name, cache] - compiled << UnimplementedInstruction.new( - "setinstancevariable", - name, - cache - ) - in [:setlocal, offset, level] - current = compiled - level.times { current = current.parent } - - index = current.local_index(offset) - compiled << SetLocal.new(current.locals[index], index, level) - in [:setlocal_WC_0, offset] - index = compiled.local_index(offset) - compiled << SetLocalWC0.new(compiled.locals[index], index) - in [:setlocal_WC_1, offset] - index = parent.local_index(offset) - compiled << SetLocalWC1.new(parent.locals[index], index) - in [:setn, index] - compiled << SetN.new(index) - in [:setspecial, key] - compiled << UnimplementedInstruction.new("setspecial", key) - in [:splatarray, flag] - compiled << SplatArray.new(flag) - in [:swap] - compiled << Swap.new - in [:throw, type] - compiled << UnimplementedInstruction.new("throw", type) - in [:topn, n] - compiled << TopN.new(n) - in [:toregexp, opts, cnt] - compiled << ToRegexp.new(opts, cnt) - end - end - end - - def <<(insn) - insns << insn - end - - def ==(other) - other in InstructionSequence[insns: ^(insns), labels: ^(labels.values)] - end - - def deconstruct_keys(keys) - { insns:, labels: labels.values } - end - - def child_iseqs - child_iseqs = [] - insns.each do |insn| - case insn - when DefineMethod - child_iseqs << insn.iseq - when Send - child_iseqs << insn.block_iseq if insn.block_iseq - end - end - child_iseqs - end - - def all_iseqs - [self] + child_iseqs.flat_map(&:all_iseqs) - end - - # Print out this instruction sequence to the given output stream. - def disasm(output = StringIO.new, prefix = "") - output.print("#{prefix}#{disasm_header("disasm")} ") - handled = [] - - if throw_handlers.any? - output.puts("(catch: TRUE)") - output.puts("#{prefix}== catch table") - - throw_handlers.each do |handler| - output.puts("#{prefix}| catch type: #{handler.type}") - - if handler.iseq - handler.iseq.disasm(output, "#{prefix}| ") - handled << handler.iseq - end - end - - output.puts("#{prefix}|#{"-" * 72}") - else - output.puts("(catch: FALSE)") - end - - insns.each_with_index do |insn, insn_pc| - output.puts(prefix + disasm_insn(insn, insn_pc)) - end - - child_iseqs.each do |child_iseq| - output.puts - child_iseq.disasm(output, prefix) - end - - output.string - end - - def disasm_header(tag) - "== #{tag} #" - end - - def disasm_insn(insn, insn_pc) - "#{InstructionSequence.disasm_pc(insn_pc)} #{insn.disasm(self)}" - end - - def self.disasm_pc(pc) - pc.to_s.rjust(4, "0") - end - - # This is the name assigned to this instruction sequence. - def name - iseq[5] - end - - # These are the names of the locals in the instruction sequence. - def locals - iseq[10] - end - - # Indices that are given for getlocal and setlocal instructions are actually - # how far back they are from the top of the stack. So here we do a little - # math to make them a little easier to work with. - def local_index(offset) - (locals.length - (offset - 3)) - 1 - end - - # This is the information about the arguments that should be passed into - # this instruction sequence. - def args - iseq[11] - end - - # These are the various ways the instruction sequence handles raised - # exceptions. - def catch_table - iseq[12] - end - - def eval(context = ExecutionContext.new) - context.eval(self) - end - end -end diff --git a/lib/yarv/main.rb b/lib/yarv/main.rb deleted file mode 100644 index 80cc4e9..0000000 --- a/lib/yarv/main.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module YARV - # This is the self object at the top of the script. - class Main - def ==(other) - other in Main - end - - def inspect - "main" - end - - def require(context, filename) - file_path = - context.globals[:$:].find do |path| - filename += ".rb" unless filename.end_with?(".rb") - - file_path = File.join(path, filename) - next unless File.file?(file_path) && File.readable?(file_path) - - break file_path - end - - raise LoadError, "cannot load such file -- #{filename}" unless file_path - - return false if context.globals[:"$\""].include?(file_path) - - iseq = - File.open(file_path, "r") do |f| - YARV.compile(f.read, file_path, file_path) - end - - context.eval(iseq) - true - end - end -end diff --git a/lib/yarv/soy.rb b/lib/yarv/soy.rb deleted file mode 100644 index a4092a6..0000000 --- a/lib/yarv/soy.rb +++ /dev/null @@ -1,401 +0,0 @@ -# frozen_string_literal: true - -module YARV - # Constructs a sea-of-yarv graph, or SOY graph -- a sea-of-nodes graph, from - # a CFG. - class SOY - attr_reader :dfg - attr_reader :start - attr_reader :nodes - - def initialize(dfg) - if dfg.cfg.iseq.throw_handlers.any? - raise "throw handlers not supported in SOY yet" - end - - @dfg = dfg - @nodes = [] - @id_counter = 999 - - # Create local subgraphs for each basic block. - local_graphs = {} - dfg.cfg.blocks.each do |block| - local_graphs[block.start] = create_local_graph(block) - end - @start = local_graphs[dfg.cfg.blocks.first.start].first - - # Connect global control flow - connect basic blocks. - dfg.cfg.blocks.each do |predecessor| - predecessor_last = local_graphs[predecessor.start].last - predecessor.succs.each_with_index do |successor, n| - connect predecessor_last, - local_graphs[successor.start].first, - :control, - %i[branched fallthrough][n] - end - end - - # Connect global dataflow - connect basic block argument outputs to - # inputs. - dfg.cfg.blocks.each do |predecessor| - predecessor_graph = local_graphs[predecessor.start] - predecessor_graph.out.values.each_with_index do |arg_out, arg_n| - predecessor.succs.each do |successor| - successor_graph = local_graphs[successor.start] - arg_in = successor_graph.in.values[arg_n] - - # We're connecting to a phi node, so we may need a special label. - raise unless arg_in.is_a?(PhiNode) - case arg_out - when InsnNode - # Instructions that go into a phi node are labelled by the PC of - # last instruction in the block that executed them. This way you - # know which value to use for the phi, based on the last - # instruction you executed. - arg_out_block = dfg.cfg.block_map[arg_out.insn_pc] - label = arg_out_block.end - when PhiNode - # Phi nodes to phi nodes are not labelled. - label = nil - else - raise - end - connect arg_out, arg_in, :data, label - end - end - end - - # Run post-build clean up. - post_build - - # Verify. - verify_graph - end - - # Create a sub-graph for a single basic block - block block argument inputs - # and outputs will be left dangling, to be connected later. - def create_local_graph(block) - block_dataflow = dfg.block_flow[block.start] - - # A map of instructions to nodes. - insn_node_map = {} - - # Create a node for each instruction in the block. - block - .start - .upto(block.start + block.length - 1) do |insn_pc| - insn = dfg.cfg.iseq.insns[insn_pc] - node = InsnNode.new(insn, insn_pc) - insn_node_map[insn_pc] = node - nodes.push node - end - - # The first and last node in the sub-graph, and the last fixed node. - previous_fixed = nil - first_fixed = nil - last_fixed = nil - - # If there is more than predecessor, and we have basic block arguments - # coming in, then we need a merge node for the phi nodes to attach to. - if block.preds.size > 1 && !block_dataflow.in.empty? - merge = MergeNode.new(id_counter) - nodes.push merge - previous_fixed = merge - first_fixed = merge - last_fixed = merge - end - - # Connect local control flow (only nodes with side effects.) - block - .start - .upto(block.start + block.length - 1) do |insn_pc| - insn = dfg.cfg.iseq.insns[insn_pc] - if insn.side_effects? - insn_node = insn_node_map[insn_pc] - connect previous_fixed, insn_node, :control if previous_fixed - previous_fixed = insn_node - first_fixed ||= insn_node - last_fixed = insn_node - end - end - - # A graph with only side-effect free instructions will currently have - # no fixed nodes! In that case just use the first instruction's node - # for both first and last. But it's a bug that it'll appear in the - # control flow path! - first = first_fixed || insn_node_map[block.start] - last = last_fixed || insn_node_map[block.start] - - # Connect basic block arguments. - inputs = {} - outputs = {} - block_dataflow.in.each do |arg| - # Each basic block argument gets a phi node. Even if there's only one - # predecessor! We'll tidy this up later. - phi = PhiNode.new(id_counter) - connect phi, merge, :info if merge - nodes.push phi - inputs[arg] = phi - block - .start - .upto(block.start + block.length - 1) do |consumer_pc| - consumer_dataflow = dfg.insn_flow[consumer_pc] - consumer_dataflow.in.each_with_index do |producer, n| - if producer == arg - connect phi, insn_node_map[consumer_pc], :data, n - end - end - end - block_dataflow.out.each { |out| outputs[out] = phi if out == arg } - end - - # Connect local dataflow from consumers back to producers. - block - .start - .upto(block.start + block.length - 1) do |consumer_pc| - consumer_dataflow = dfg.insn_flow[consumer_pc] - - consumer_dataflow.in.each_with_index do |producer, n| - if producer.is_a?(Integer) - producer_pc = producer - connect insn_node_map[producer_pc], - insn_node_map[consumer_pc], - :data, - n - end - end - end - - # Connect dataflow from producers that leaves the block. - block - .start - .upto(block.start + block.length - 1) do |producer_pc| - producer_dataflow = dfg.insn_flow[producer_pc] - producer_dataflow.out.each do |consumer| - unless consumer.is_a?(Integer) - # This is an argument to the successor block - not to an - # instruction here. - outputs[consumer] = insn_node_map[producer_pc] - end - end - end - - SubGraph.new(first, last, inputs, outputs) - end - - # We don't always build things in an optimal way. Go back and fix up some - # mess we left! Ideally we wouldn't create these problems in the first - # place. - def post_build - nodes.dup.each do |node| # dup because we're mutating the list of nodes - case node - when PhiNode - if node.in.size == 1 - # Remove phi nodes with a single input. - remove node, connect_over: true - elsif node.in.map(&:from).uniq.size == 1 - # Remove phi nodes where all inputs are the same. - producer_edge = node.in.first - consumer_edge = node.out.filter { |e| !e.to.is_a?(MergeNode) }.first - connect producer_edge.from, - consumer_edge.to, - :data, - consumer_edge.label - remove node - end - end - end - end - - # Counter for synthetic nodes. - def id_counter - @id_counter += 1 - end - - # Connect one node to another. - def connect(from, to, type, label = nil) - raise if from == to - raise if type == :data && label.nil? unless to.is_a?(PhiNode) - edge = Edge.new(from, to, type, label) - from.out.push edge - to.in.push edge - end - - # Remove a node from the graph, optionally connecting edges that went - # through it. - def remove(node, connect_over: false) - if connect_over - node.in.each do |producer_edge| - node.out.each do |consumer_edge| - connect producer_edge.from, consumer_edge.to, producer_edge.type - end - end - end - - node.in.each do |producer_edge| - producer_edge.from.out.delete_if { |e| e.to == node } - end - - node.out.each do |consumer_edge| - consumer_edge.to.in.delete_if { |e| e.from == node } - end - - nodes.delete node - end - - def verify_graph - # Verify edge labels. - nodes.each do |node| - # Not talking about phi nodes right now. - next if node.is_a?(SOY::PhiNode) - - if node.is_a?(InsnNode) && node.insn.branches? && - !node.insn.is_a?(Leave) - # A branching node must have branched and fallthrough edges - # coming out. - - labels = node.out.map(&:label) - unless labels.sort == %i[branched fallthrough].sort || - labels == [:branched] - raise - end - else - labels = node.in.filter { |e| e.type == :data }.map(&:label) - next if labels.empty? - - # No nil labels - raise if labels.any?(&:nil?) - - # Labels should start at zero. - raise unless labels.min.zero? - - # Labels should be contiguous. - raise unless labels.sort == (labels.min..labels.max).to_a - end - end - end - - def mermaid(output = StringIO.new) - output.puts "flowchart TD" - - nodes.each { |node| output.puts " node_#{node.id}(#{node})" } - - link_counter = 0 - nodes.each do |producer| - producer.out.each do |consumer_edge| - case consumer_edge.type - when :data - edge = "-->" - edge_style = "stroke:green;" - when :control - edge = "-->" - edge_style = "stroke:red;" - when :info - edge = "-.->" - else - raise - end - - if consumer_edge.label - if consumer_edge.to.is_a?(SOY::PhiNode) - # Edges into phi nodes are labelled by the PC of the instruction - # going into the merge. - label = "|#{InstructionSequence.disasm_pc(consumer_edge.label)}| " - else - label = "|#{consumer_edge.label}| " - end - else - label = "" - end - - output.puts " node_#{producer.id} #{edge} #{label}node_#{consumer_edge.to.id}" - output.puts " linkStyle #{link_counter} #{edge_style}" if edge_style - link_counter += 1 - end - end - - output.string - end - - class Node - attr_reader :in - attr_reader :out - - def initialize - @in = [] - @out = [] - end - end - - class InsnNode < Node - attr_reader :insn - attr_reader :insn_pc - - def initialize(insn, insn_pc) - super() - @insn = insn - @insn_pc = insn_pc - end - - def id - insn_pc - end - - def to_s - "#{InstructionSequence.disasm_pc(insn_pc)} #{insn.class.name.split("::").last}" - end - end - - class SynthNode < Node - attr_reader :id - - def initialize(id) - super() - @id = id - end - end - - class PhiNode < SynthNode - def to_s - "#{id} φ" - end - end - - class MergeNode < SynthNode - def to_s - "#{id} ψ" - end - end - - class Edge - TYPES = %i[data control info] - - attr_reader :from - attr_reader :to - attr_reader :type - attr_reader :label - - def initialize(from, to, type, label) - @from = from - @to = to - raise unless TYPES.include?(type) - @type = type - @label = label - end - end - - class SubGraph - attr_reader :first - attr_reader :last - attr_reader :in - attr_reader :out - - def initialize(first, last, inputs, out) - @first = first - @last = last - @in = inputs - @out = out - end - end - end -end diff --git a/lib/yarv/visitor.rb b/lib/yarv/visitor.rb deleted file mode 100644 index 709f700..0000000 --- a/lib/yarv/visitor.rb +++ /dev/null @@ -1,242 +0,0 @@ -# frozen_string_literal: true - -module YARV - class Visitor < SyntaxTree::Visitor - attr_reader :iseq - - def initialize - @iseq = nil - end - - def visit_binary(node) - case node.operator - in :+ - visit(node.left) - visit(node.right) - emit(OptPlus.new(call_data(node.operator, 1, %i[ARGS_SIMPLE]))) - in :- - visit(node.left) - visit(node.right) - emit(OptMinus.new(call_data(node.operator, 1, %i[ARGS_SIMPLE]))) - in :* - visit(node.left) - visit(node.right) - emit(OptMult.new(call_data(node.operator, 1, %i[ARGS_SIMPLE]))) - in :/ - visit(node.left) - visit(node.right) - emit(OptDiv.new(call_data(node.operator, 1, %i[ARGS_SIMPLE]))) - in :"||" - visit(node.left) - emit(Dup.new) - emit_jump(BranchIf) do - emit(Pop.new) - visit(node.right) - end - in :"&&" - visit(node.left) - emit(Dup.new) - emit_jump(BranchUnless) do - emit(Pop.new) - visit(node.right) - end - end - end - - def visit_bodystmt(node) - node.rescue_clause => nil - node.else_clause => nil - node.ensure_clause => nil - visit(node.statements) - end - - def visit_call(node) - visit(node.receiver) - - name = node.message == :call ? :call : node.message.value.to_sym - send = OptSendWithoutBlock.new(call_data(name, 0, %i[ARGS_SIMPLE])) - - if node.operator in SyntaxTree::Op[value: "&."] - emit(Dup.new) - emit_jump(BranchNil) { emit(send) } - else - emit(send) - end - end - - def visit_else(node) - visit(node.statements) - end - - def visit_float(node) - emit(PutObject.new(node.value.to_f)) - end - - def visit_gvar(node) - emit(GetGlobal.new(node.value.to_sym)) - end - - def visit_if(node) - visit(node.predicate) - - branch_offset = current_offset - emit(:branch_placeholder) - visit(node.statements) - - if node.consequent - emit(Pop.new) - emit_jump(Jump) do - iseq.insns[branch_offset] = BranchUnless.new(emit_label) - visit(node.consequent) - emit(Pop.new) - end - else - iseq.insns[branch_offset] = BranchUnless.new(emit_label) - end - end - - def visit_if_mod(node) - visit(node.predicate) - emit_jump(BranchUnless) do - visit(node.statement) - emit(Pop.new) - end - end - - def visit_imaginary(node) - emit(PutObject.new(node.value.to_c)) - end - - def visit_int(node) - case (coerced = node.value.to_i) - when 0 - emit(PutObjectInt2Fix0.new) - when 1 - emit(PutObjectInt2Fix1.new) - else - emit(PutObject.new(coerced)) - end - end - - def visit_paren(node) - visit(node.contents) - end - - def visit_program(node) - @iseq = InstructionSequence.new(Main.new, []) - visit(node.statements) - emit(Leave.new) - iseq - end - - def visit_rational(node) - emit(PutObject.new(node.value.to_r)) - end - - def visit_statements(node) - visit_all(node.body) - end - - def visit_string_concat(node) - visit(node.left) - visit(node.right) - emit(ConcatStrings.new(2)) - end - - def visit_string_dvar(node) - visit(node.variable) - emit(Dup.new) - emit(ObjToString.new(call_data(:to_s, 0, %i[FCALL ARGS_SIMPLE]))) - emit(AnyToString.new) - end - - def visit_string_embexpr(node) - visit(node.statements) - emit(Dup.new) - emit(ObjToString.new(call_data(:to_s, 0, %i[FCALL ARGS_SIMPLE]))) - emit(AnyToString.new) - end - - def visit_string_literal(node) - case node.parts - in [SyntaxTree::TStringContent[value:]] - emit(PutString.new(value)) - in [SyntaxTree::StringDVar => part] - visit(SyntaxTree::TStringContent.new(value: "", location: nil)) - visit(part) - emit(ConcatStrings.new(2)) - in [part] - visit(part) - else - visit_all(node.parts) - emit(ConcatStrings.new(node.parts.length)) - end - end - - def visit_symbol_literal(node) - emit(PutObject.new(node.value.value.to_sym)) - end - - def visit_tstring_content(node) - emit(PutObject.new(node.value)) - end - - def visit_unless_mod(node) - visit(node.predicate) - emit_jump(BranchIf) do - visit(node.statement) - emit(Pop.new) - end - end - - def visit_vcall(node) - cdata = call_data(node.value.value.to_sym, 0, %i[FCALL VCALL ARGS_SIMPLE]) - - emit(PutSelf.new(iseq.selfo)) - emit(OptSendWithoutBlock.new(cdata)) - end - - def visit_void_stmt(node) - end - - def visit_xstring_literal(node) - emit(PutSelf.new(iseq.selfo)) - visit_all(node.parts) - emit(ConcatStrings.new(node.parts.length)) if node.parts.length > 1 - emit(OptSendWithoutBlock.new(call_data(:`, 1, %i[FCALL ARGS_SIMPLE]))) - end - - private - - def call_data(mid, argc, flags) - flag = - flags.inject(0) { |sum, flag| sum | (1 << CallData::FLAGS.index(flag)) } - - CallData.new(mid, argc, flag) - end - - def current_offset - iseq.insns.length - end - - def emit(insn) - iseq << insn - end - - def emit_jump(kind) - offset = current_offset - emit(:placeholder) - - yield - iseq.insns[offset] = kind.new(emit_label) - end - - def emit_label - offset = current_offset - label = :"label_#{offset}" - - iseq.labels[label] = offset - label - end - end -end diff --git a/spec/mspec b/spec/mspec deleted file mode 160000 index 215497e..0000000 --- a/spec/mspec +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 215497e0eb9bb2869096dc76230e06392a96aeba diff --git a/spec/ruby b/spec/ruby deleted file mode 160000 index 3affe1e..0000000 --- a/spec/ruby +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3affe1e54fcd11918a242ad5d4a7ba895ee30c4c diff --git a/doc/style.css b/style.css similarity index 100% rename from doc/style.css rename to style.css diff --git a/test/cfg_test.rb b/test/cfg_test.rb deleted file mode 100644 index 27a3642..0000000 --- a/test/cfg_test.rb +++ /dev/null @@ -1,157 +0,0 @@ -# frozen_string_literal: true - -require_relative "test_helper" - -module YARV - class CFGTest < Test::Unit::TestCase - def test_branch - assert_cfg(<<~DISASM, "foo ? 1 : 2") - == cfg #> - block_0: - 0000 putself - 0001 opt_send_without_block - 0002 branchunless label_7 (5) - # to: block_5, block_3 - block_3: # from: block_0 - 0003 putobject_INT2FIX_1_ - 0004 leave - # to: leaves - block_5: # from: block_0 - 0005 putobject 2 - 0006 leave - # to: leaves - DISASM - end - - def test_simple - assert_cfg(<<~DISASM, "(n < 0 ? -1 : +1) + 100") - == cfg #> - block_0: - 0000 putself - 0001 opt_send_without_block - 0002 putobject_INT2FIX_0_ - 0003 opt_lt [CcCr] - 0004 branchunless label_12 (7) - # to: block_7, block_5 - block_5: # from: block_0 - 0005 putobject -1 - 0006 jump label_13 (8) - # to: block_8 - block_7: # from: block_0 - 0007 putobject_INT2FIX_1_ - # to: block_8 - block_8: # from: block_5, block_7 - 0008 putobject 100 - 0009 opt_plus [CcCr] - 0010 leave - # to: leaves - DISASM - end - - def test_loop - source = <<~SOURCE - n = 10 - sum = 0 - while n > 0 - sum += n - n -= 1 - end - sum - SOURCE - assert_cfg(<<~DISASM, source) - == cfg #> - block_0: - 0000 putobject 10 - 0001 setlocal_WC_0 n@0 - 0002 putobject_INT2FIX_0_ - 0003 setlocal_WC_0 sum@1 - 0004 jump label_28 (16) - # to: block_16 - block_5: - 0005 putnil - 0006 pop - 0007 jump label_28 (16) - # to: block_16 - block_8: # from: block_16 - 0008 getlocal_WC_0 sum@1 - 0009 getlocal_WC_0 n@0 - 0010 opt_plus [CcCr] - 0011 setlocal_WC_0 sum@1 - 0012 getlocal_WC_0 n@0 - 0013 putobject_INT2FIX_1_ - 0014 opt_minus [CcCr] - 0015 setlocal_WC_0 n@0 - # to: block_16 - block_16: # from: block_0, block_5, block_8 - 0016 getlocal_WC_0 n@0 - 0017 putobject_INT2FIX_0_ - 0018 opt_gt , argc:1, ARGS_SIMPLE>[CcCr] - 0019 branchif label_13 (8) - # to: block_8, block_20 - block_20: # from: block_16 - 0020 putnil - 0021 pop - 0022 getlocal_WC_0 sum@1 - 0023 leave - # to: leaves - DISASM - end - - def test_fib - source = <<~SOURCE - def fib(n) - if n < 2 - n - else - fib(n - 1) + fib(n - 2) - end - end - SOURCE - assert_cfg(<<~DISASM, source) - == cfg #> - block_0: - 0000 definemethod :fib, fib - 0001 putobject :fib - 0002 leave - # to: leaves - == cfg # - block_0: - 0000 getlocal_WC_0 n@0 - 0001 putobject 2 - 0002 opt_lt [CcCr] - 0003 branchunless label_11 (6) - # to: block_6, block_4 - block_4: # from: block_0 - 0004 getlocal_WC_0 n@0 - 0005 leave - # to: leaves - block_6: # from: block_0 - 0006 putself - 0007 getlocal_WC_0 n@0 - 0008 putobject_INT2FIX_1_ - 0009 opt_minus [CcCr] - 0010 opt_send_without_block - 0011 putself - 0012 getlocal_WC_0 n@0 - 0013 putobject 2 - 0014 opt_minus [CcCr] - 0015 opt_send_without_block - 0016 opt_plus [CcCr] - 0017 leave - # to: leaves - DISASM - end - - private - - def assert_cfg(expected, source) - string = +"" - compiled = YARV.compile(source) - compiled.all_iseqs.each do |iseq| - cfg = YARV::CFG.new(iseq) - string << cfg.disasm - end - assert_equal(expected, string) - end - end -end diff --git a/test/compile_test.rb b/test/compile_test.rb deleted file mode 100644 index 09373d4..0000000 --- a/test/compile_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -return unless ENV["CI"] -require_relative "test_helper" - -module YARV - class CompileTest < Test::Unit::TestCase - Dir[File.join(RbConfig::CONFIG["libdir"], "**/*.rb")].each do |filepath| - define_method(:"test_compile_#{filepath}") do - YARV.compile(File.read(filepath), filepath, filepath) - rescue SyntaxError - # Skip past any files that have syntax errors. - end - end - end -end diff --git a/test/dfg_test.rb b/test/dfg_test.rb deleted file mode 100644 index 4addb57..0000000 --- a/test/dfg_test.rb +++ /dev/null @@ -1,190 +0,0 @@ -# frozen_string_literal: true - -require_relative "test_helper" - -module YARV - class DFGTest < Test::Unit::TestCase - # Only uses local dataflow. - def test_local - assert_dfg(<<~DISASM, "14 + 2") - == dfg #> - block_0: - 0000 putobject 14 # out: 0002 - 0001 putobject 2 # out: 0002 - 0002 opt_plus [CcCr] # in: 0000, 0001; out: 0003 - 0003 leave # in: 0002 - # to: leaves - DISASM - end - - # Triggers a simple basic block argument - the value for the add may come - # from either side of the ternary. - def test_simple_bbarg - assert_dfg(<<~DISASM, "(14 < 0 ? -1 : +1) + 100") - == dfg #> - block_0: - 0000 putobject 14 # out: 0002 - 0001 putobject_INT2FIX_0_ # out: 0002 - 0002 opt_lt [CcCr] # in: 0000, 0001; out: 0003 - 0003 branchunless label_11 (6) # in: 0002 - # to: block_6, block_4 - block_4: # from: block_0 - 0004 putobject -1 # out: out_0 - 0005 jump label_12 (7) - # to: block_7 - # out: 0004 - block_6: # from: block_0 - 0006 putobject_INT2FIX_1_ # out: out_0 - # to: block_7 - # out: 0006 - block_7: # from: block_4, block_6 - # in: in_0 - 0007 putobject 100 # out: 0008 - 0008 opt_plus [CcCr] # in: in_0, 0007; out: 0009 - 0009 leave # in: 0008 - # to: leaves - DISASM - end - - # Triggers an indirect basic block argument - the 100 value for the add - # comes from block_0, and is used in block_8, but they aren't directly - # connected. This should cause the basic blocks in between to take the - # value as an input and pass it directly as an output. - def test_indirect_bbarg - assert_dfg(<<~DISASM, "100 + (14 < 0 ? -1 : +1)") - == dfg #> - block_0: - 0000 putobject 100 # out: out_0 - 0001 putobject 14 # out: 0003 - 0002 putobject_INT2FIX_0_ # out: 0003 - 0003 opt_lt [CcCr] # in: 0001, 0002; out: 0004 - 0004 branchunless label_13 (7) # in: 0003 - # to: block_7, block_5 - # out: 0000 - block_5: # from: block_0 - # in: pass_0 - 0005 putobject -1 # out: out_0 - 0006 jump label_14 (8) - # to: block_8 - # out: pass_0, 0005 - block_7: # from: block_0 - # in: pass_0 - 0007 putobject_INT2FIX_1_ # out: out_0 - # to: block_8 - # out: pass_0, 0007 - block_8: # from: block_5, block_7 - # in: in_0, in_1 - 0008 opt_plus [CcCr] # in: in_0, in_1; out: 0009 - 0009 leave # in: 0008 - # to: leaves - DISASM - end - - def test_loop - source = <<~SOURCE - n = 10 - sum = 0 - while n > 0 - sum += n - n -= 1 - end - sum - SOURCE - assert_dfg(<<~DISASM, source) - == dfg #> - block_0: - 0000 putobject 10 # out: 0001 - 0001 setlocal_WC_0 n@0 # in: 0000 - 0002 putobject_INT2FIX_0_ # out: 0003 - 0003 setlocal_WC_0 sum@1 # in: 0002 - 0004 jump label_28 (16) - # to: block_16 - block_5: - 0005 putnil # out: 0006 - 0006 pop # in: 0005 - 0007 jump label_28 (16) - # to: block_16 - block_8: # from: block_16 - 0008 getlocal_WC_0 sum@1 # out: 0010 - 0009 getlocal_WC_0 n@0 # out: 0010 - 0010 opt_plus [CcCr] # in: 0008, 0009; out: 0011 - 0011 setlocal_WC_0 sum@1 # in: 0010 - 0012 getlocal_WC_0 n@0 # out: 0014 - 0013 putobject_INT2FIX_1_ # out: 0014 - 0014 opt_minus [CcCr] # in: 0012, 0013; out: 0015 - 0015 setlocal_WC_0 n@0 # in: 0014 - # to: block_16 - block_16: # from: block_0, block_5, block_8 - 0016 getlocal_WC_0 n@0 # out: 0018 - 0017 putobject_INT2FIX_0_ # out: 0018 - 0018 opt_gt , argc:1, ARGS_SIMPLE>[CcCr] # in: 0016, 0017; out: 0019 - 0019 branchif label_13 (8) # in: 0018 - # to: block_8, block_20 - block_20: # from: block_16 - 0020 putnil # out: 0021 - 0021 pop # in: 0020 - 0022 getlocal_WC_0 sum@1 # out: 0023 - 0023 leave # in: 0022 - # to: leaves - DISASM - end - - def test_fib - source = <<~SOURCE - def fib(n) - if n < 2 - n - else - fib(n - 1) + fib(n - 2) - end - end - SOURCE - assert_dfg(<<~DISASM, source) - == dfg #> - block_0: - 0000 definemethod :fib, fib - 0001 putobject :fib # out: 0002 - 0002 leave # in: 0001 - # to: leaves - == dfg # - block_0: - 0000 getlocal_WC_0 n@0 # out: 0002 - 0001 putobject 2 # out: 0002 - 0002 opt_lt [CcCr] # in: 0000, 0001; out: 0003 - 0003 branchunless label_11 (6) # in: 0002 - # to: block_6, block_4 - block_4: # from: block_0 - 0004 getlocal_WC_0 n@0 # out: 0005 - 0005 leave # in: 0004 - # to: leaves - block_6: # from: block_0 - 0006 putself # out: 0010 - 0007 getlocal_WC_0 n@0 # out: 0009 - 0008 putobject_INT2FIX_1_ # out: 0009 - 0009 opt_minus [CcCr] # in: 0007, 0008; out: 0010 - 0010 opt_send_without_block # in: 0006, 0009; out: 0016 - 0011 putself # out: 0015 - 0012 getlocal_WC_0 n@0 # out: 0014 - 0013 putobject 2 # out: 0014 - 0014 opt_minus [CcCr] # in: 0012, 0013; out: 0015 - 0015 opt_send_without_block # in: 0011, 0014; out: 0016 - 0016 opt_plus [CcCr] # in: 0010, 0015; out: 0017 - 0017 leave # in: 0016 - # to: leaves - DISASM - end - - private - - def assert_dfg(expected, source) - string = +"" - compiled = YARV.compile(source) - compiled.all_iseqs.each do |iseq| - cfg = YARV::CFG.new(iseq) - dfg = YARV::DFG.new(cfg) - string << dfg.disasm - end - assert_equal(expected, string) - end - end -end diff --git a/test/disasm_test.rb b/test/disasm_test.rb deleted file mode 100644 index 3a4dcd3..0000000 --- a/test/disasm_test.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require_relative "test_helper" - -module YARV - class DisasmTest < Test::Unit::TestCase - def test_binary_plus - assert_disasm(<<~DISASM, "2 + 3") - == disasm #> (catch: FALSE) - 0000 putobject 2 - 0001 putobject 3 - 0002 opt_plus [CcCr] - 0003 leave - DISASM - end - - def test_branch_plus - assert_disasm(<<~DISASM, "foo ? 1 : 2") - == disasm #> (catch: FALSE) - 0000 putself - 0001 opt_send_without_block - 0002 branchunless label_7 (5) - 0003 putobject_INT2FIX_1_ - 0004 leave - 0005 putobject 2 - 0006 leave - DISASM - end - - private - - def assert_disasm(expected, source) - assert_equal(expected, YARV.compile(source).disasm) - end - end -end diff --git a/test/insn/adjuststack_test.rb b/test/insn/adjuststack_test.rb deleted file mode 100644 index 4acf8e9..0000000 --- a/test/insn/adjuststack_test.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class AdjustStackTest < TestCase - def test_execute - source = <<~RUBY - x = [true] - x[0] ||= nil - x[0] - RUBY - - YARV.compile(source).insns => [ - DupArray, - SetLocalWC0, - GetLocalWC0, - PutObjectInt2Fix0, - DupN, - OptAref, - Dup, - BranchIf, - Pop, - PutNil, - OptAset, - Pop, - Jump, - AdjustStack[size: 3], - GetLocalWC0, - PutObjectInt2Fix0, - OptAref, - Leave - ] - end - end -end diff --git a/test/insn/anytostring_test.rb b/test/insn/anytostring_test.rb deleted file mode 100644 index 5b94292..0000000 --- a/test/insn/anytostring_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class AnyToStringTest < TestCase - def test_execute - assert_insns( - [ - PutObject, - PutObject, - Dup, - ObjToString, - AnyToString, - ConcatStrings, - Leave - ], - '"#{5}"' - ) - assert_stdout("\"5\"\n", 'p "#{5}"') - end - - def test_produces_the_expected_instructions - assert_stdout_for_instructions( - "5\n", - [ - [:putself], - [:putobject, ""], - [:putobject, 5], - [:dup], - [:objtostring, { mid: :to_s, flag: 20, orig_argc: 0 }], - [:anytostring], - [:concatstrings, 2], - [:opt_send_without_block, { mid: :puts, flag: 20, orig_argc: 1 }], - [:leave] - ] - ) - end - end -end diff --git a/test/insn/branchif_test.rb b/test/insn/branchif_test.rb deleted file mode 100644 index fff2228..0000000 --- a/test/insn/branchif_test.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class BranchIfTest < TestCase - def test_branchif_jumps_if_true - source_code = "x = true; x ||= 'foo' ; puts x" - assert_stdout("true\n", source_code) - end - - def test_branchif_doesnt_jump_if_false - source_code = "x = false; x ||= true; puts x" - assert_stdout("true\n", source_code) - end - end -end diff --git a/test/insn/branchnil_test.rb b/test/insn/branchnil_test.rb deleted file mode 100644 index b2ec3a9..0000000 --- a/test/insn/branchnil_test.rb +++ /dev/null @@ -1,49 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class BranchNilTest < TestCase - def test_branchnil_jumps_if_nil - source_code = "x = nil; if x&.to_s; puts 'hi'; end" - assert_insns( - [ - PutNil, - SetLocalWC0, - GetLocalWC0, - Dup, - BranchNil, - OptSendWithoutBlock, - BranchUnless, - PutSelf, - PutString, - OptSendWithoutBlock, - Leave, - PutNil, - Leave - ], - source_code - ) - assert_stdout("", source_code) - end - - def test_branchnil_doesnt_jump_if_not_nil - source_code = "x = true; puts x&.to_s" - assert_insns( - [ - PutObject, - SetLocalWC0, - PutSelf, - GetLocalWC0, - Dup, - BranchNil, - OptSendWithoutBlock, - OptSendWithoutBlock, - Leave - ], - source_code - ) - assert_stdout("true\n", source_code) - end - end -end diff --git a/test/insn/branchunless_test.rb b/test/insn/branchunless_test.rb deleted file mode 100644 index 88ee4cd..0000000 --- a/test/insn/branchunless_test.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class BranchUnlessTest < TestCase - def test_branchunless_jumps_if_false - source_code = "if 2+3; puts 'foo'; end" - assert_insns( - [ - PutObject, - PutObject, - OptPlus, - BranchUnless, - PutSelf, - PutString, - OptSendWithoutBlock, - Leave, - PutNil, - Leave - ], - source_code - ) - assert_stdout("foo\n", source_code) - end - - def test_branchunless_doesnt_jump_if_true - source_code = "if 'bar'.empty?; puts 'foo'; end" - assert_insns( - [ - PutString, - OptEmptyP, - BranchUnless, - PutSelf, - PutString, - OptSendWithoutBlock, - Leave, - PutNil, - Leave - ], - source_code - ) - assert_stdout("", source_code) - end - end -end diff --git a/test/insn/concatarray_test.rb b/test/insn/concatarray_test.rb deleted file mode 100644 index 7499954..0000000 --- a/test/insn/concatarray_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class ConcatarrayTest < TestCase - def test_execute - assert_insns([DupArray, PutObject, ConcatArray, Leave], "[1,*2]") - assert_stdout("[1, 2]\n", "p [1, *2]") - end - - def test_coerces_the_left_element - assert_stdout_for_instructions( - "[2, 3]\n", - [ - [:putself], - [:putobject, 2], - [:putobject, 3], - [:concatarray], - [:opt_send_without_block, { mid: :p, flag: 20, orig_argc: 1 }] - ] - ) - end - - def test_duplicates_the_left_element - assert_stdout_for_instructions( - "[1]\n", - [ - [:putself], - [:duparray, [1]], - [:dup], - [:putobject, 2], - [:concatarray], - [:pop], - [:opt_send_without_block, { mid: :p, flag: 20, orig_argc: 1 }] - ] - ) - end - end -end diff --git a/test/insn/concatstrings_test.rb b/test/insn/concatstrings_test.rb deleted file mode 100644 index d352366..0000000 --- a/test/insn/concatstrings_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class ConcatstringsTest < TestCase - def test_execute - assert_insns( - [ - PutObject, - PutObject, - Dup, - ObjToString, - AnyToString, - ConcatStrings, - Leave - ], - '"#{5}"' - ) - assert_stdout("\"5\"\n", 'p "#{5}"') - end - - def test_produces_the_expected_instructions - assert_stdout_for_instructions( - "5\n", - [ - [:putself], - [:putobject, ""], - [:putobject, 5], - [:dup], - [:objtostring, { mid: :to_s, flag: 20, orig_argc: 0 }], - [:anytostring], - [:concatstrings, 2], - [:opt_send_without_block, { mid: :puts, flag: 20, orig_argc: 1 }], - [:leave] - ] - ) - end - end -end diff --git a/test/insn/defined_test.rb b/test/insn/defined_test.rb deleted file mode 100644 index 2879b69..0000000 --- a/test/insn/defined_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class DefinedTest < TestCase - def test_execute - assert_insns([PutNil, Defined, Leave], "defined?($foo)") - assert_stdout("global-variable\n", "$foo = 1; puts defined?($foo)") - end - end -end diff --git a/test/insn/definemethod_test.rb b/test/insn/definemethod_test.rb deleted file mode 100644 index c27f17a..0000000 --- a/test/insn/definemethod_test.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class DefineMethodTest < TestCase - def test_execute - source = <<~SOURCE - def value = "value" - puts value - SOURCE - - assert_insns( - [ - DefineMethod, - PutSelf, - PutSelf, - OptSendWithoutBlock, - OptSendWithoutBlock, - Leave - ], - source - ) - assert_stdout("value\n", source) - end - - def test_execute_with_leading_arguments - source = <<~SOURCE - def echo(value) = value - puts echo(1) - SOURCE - - assert_stdout("1\n", source) - end - - def test_execute_with_leading_argument_and_other_locals - source = <<~SOURCE - def add2(value) - addition = 2 - addition + value - end - - puts add2(1) - SOURCE - - assert_stdout("3\n", source) - end - - def test_execute_with_leading_arguments_and_other_locals - source = <<~SOURCE - def add3(left, right) - addition = 2 - addition + left + right - end - - puts add3(3, 4) - SOURCE - - assert_stdout("9\n", source) - end - end -end diff --git a/test/insn/dup_hash_test.rb b/test/insn/dup_hash_test.rb deleted file mode 100644 index 3dce113..0000000 --- a/test/insn/dup_hash_test.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class DupHashTest < TestCase - def test_execute - assert_insns([DupHash, Leave], "{ a: 1 }") - assert_stdout("{:a=>1}\n", "p({ a: 1 })") - end - - def test_hash_is_immutable - ruby = <<~RUBY - def foo - { a: 1 } - end - foo.merge!({b: 2}) - p foo - RUBY - assert_stdout("{:a=>1}\n", ruby) - end - end -end diff --git a/test/insn/dup_test.rb b/test/insn/dup_test.rb deleted file mode 100644 index bd3e9fa..0000000 --- a/test/insn/dup_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class DupTest < TestCase - def test_execute - assert_insns([PutObject, Dup, SetGlobal, Leave], "$global = 5") - assert_stdout("5\n", "p $global = 5") - end - end -end diff --git a/test/insn/duparray_test.rb b/test/insn/duparray_test.rb deleted file mode 100644 index d75fe83..0000000 --- a/test/insn/duparray_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class DupArrayTest < TestCase - def test_execute - assert_insns([DupArray, Leave], "[true]") - assert_stdout("true\n", "p [true][0]") - end - - def test_duplicates_the_literal - assert_stdout("[true]\n", "def foo() [true] end; foo.push(false); p foo") - end - end -end diff --git a/test/insn/getglobal_test.rb b/test/insn/getglobal_test.rb deleted file mode 100644 index 824ddee..0000000 --- a/test/insn/getglobal_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class GetGlobalTest < TestCase - def test_execute - assert_insns([GetGlobal, Leave], "$$") - assert_stdout("#{$$}\n", "p $$") - end - end -end diff --git a/test/insn/getlocal_test.rb b/test/insn/getlocal_test.rb deleted file mode 100644 index 6912710..0000000 --- a/test/insn/getlocal_test.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class GetLocalTest < TestCase - def test_execute - YARV.compile("value = 5; tap { tap { value } }").insns => [ - PutObject, - SetLocalWC0, - PutSelf, - Send[ - block_iseq: { - insns: [ - PutSelf, - Send[ - block_iseq: { insns: [GetLocal[name: :value, level: 2], Leave] } - ], - Leave - ] - } - ], - Leave - ] - end - end -end diff --git a/test/insn/getlocal_wc_0_test.rb b/test/insn/getlocal_wc_0_test.rb deleted file mode 100644 index 201213f..0000000 --- a/test/insn/getlocal_wc_0_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class GetLocalWC0Test < TestCase - def test_execute - assert_insns( - [PutObject, SetLocalWC0, GetLocalWC0, Leave], - "value = 5; value" - ) - assert_stdout("5\n", "value = 5; p value") - end - end -end diff --git a/test/insn/getlocal_wc_1_test.rb b/test/insn/getlocal_wc_1_test.rb deleted file mode 100644 index 17ff90b..0000000 --- a/test/insn/getlocal_wc_1_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class GetLocalWC1Test < TestCase - def test_execute - source = "value = 5; print(self.then { value })" - - assert_equal( - [GetLocalWC1, Leave], - YARV.compile(source).insns[4].block_iseq.insns.map(&:class) - ) - end - end -end diff --git a/test/insn/intern_test.rb b/test/insn/intern_test.rb deleted file mode 100644 index 276f39f..0000000 --- a/test/insn/intern_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class InternTest < TestCase - def test_execute - assert_insns([PutString, Intern, Leave], ':"#{"foo"}"') - assert_stdout(":foo\n", 'p :"#{"foo"}"') - end - end -end diff --git a/test/insn/invokeblock_test.rb b/test/insn/invokeblock_test.rb deleted file mode 100644 index 70e3cf9..0000000 --- a/test/insn/invokeblock_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class InvokeBlockTest < TestCase - def test_executes_correctly - source = <<~SOURCE - def foo - yield(10) - end - - foo do |x| - p x + 1 - end - SOURCE - - assert_insns([DefineMethod, PutSelf, Send, Leave], source) - iseq = YARV.compile(source) - assert_equal( - [PutObject, InvokeBlock, Leave], - iseq.insns[0].iseq.insns.map(&:class) - ) - - assert_stdout("11\n", source) - end - end -end diff --git a/test/insn/jump_test.rb b/test/insn/jump_test.rb deleted file mode 100644 index 43780da..0000000 --- a/test/insn/jump_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class JumpTest < TestCase - def test_jump_moves_the_control_flow - source_code = "y = 0; if y == 0 then puts '0' else puts '2' end" - assert_stdout("0\n", source_code, peephole_optimization: false) - end - end -end diff --git a/test/insn/newarray_test.rb b/test/insn/newarray_test.rb deleted file mode 100644 index 285d7db..0000000 --- a/test/insn/newarray_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class NewArrayTest < TestCase - def test_execute - assert_insns([NewArray, Leave], "[]") - assert_stdout(%([1, true, "string"]\n), "p([1, true, 'string'])") - end - end -end diff --git a/test/insn/newhash_test.rb b/test/insn/newhash_test.rb deleted file mode 100644 index bb5f142..0000000 --- a/test/insn/newhash_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class NewHashTest < TestCase - def test_execute - assert_insns([NewHash, Leave], "{}") - assert_stdout("{:a=>2}\n", "k=:a;v=2;p({k=>v})") - end - end -end diff --git a/test/insn/newrange_test.rb b/test/insn/newrange_test.rb deleted file mode 100644 index 5ff81f3..0000000 --- a/test/insn/newrange_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class NewRangeTest < TestCase - def test_execute - assert_insns( - [PutNil, SetLocalWC0, GetLocalWC0, PutNil, NewRange, Leave], - "x=nil;x.." - ) - assert_stdout("nil..nil\nnil...nil\n", "x=nil;p(x..);p(x...)") - end - end -end diff --git a/test/insn/nop_test.rb b/test/insn/nop_test.rb deleted file mode 100644 index fc0acd8..0000000 --- a/test/insn/nop_test.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class NopTest < TestCase - def test_execute - assert_insns( - [PutSelf, OptSendWithoutBlock, Nop, Leave], - "raise rescue true" - ) - end - end -end diff --git a/test/insn/objtostring_test.rb b/test/insn/objtostring_test.rb deleted file mode 100644 index 30c70f3..0000000 --- a/test/insn/objtostring_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class ObjToStringTest < TestCase - def test_execute - assert_insns( - [ - PutObject, - PutObject, - Dup, - ObjToString, - AnyToString, - ConcatStrings, - Leave - ], - '"#{5}"' - ) - assert_stdout("\"5\"\n", 'p "#{5}"') - end - - def test_produces_the_expected_instructions - assert_stdout_for_instructions( - "5\n", - [ - [:putself], - [:putobject, ""], - [:putobject, 5], - [:dup], - [:objtostring, { mid: :to_s, flag: 20, orig_argc: 0 }], - [:anytostring], - [:concatstrings, 2], - [:opt_send_without_block, { mid: :puts, flag: 20, orig_argc: 1 }], - [:leave] - ] - ) - end - end -end diff --git a/test/insn/opt_and_test.rb b/test/insn/opt_and_test.rb deleted file mode 100644 index 50e0452..0000000 --- a/test/insn/opt_and_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptAndTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptAnd, Leave], "2 & 3") - assert_stdout("2\n", "p 2 & 3") - end - end -end diff --git a/test/insn/opt_aref_test.rb b/test/insn/opt_aref_test.rb deleted file mode 100644 index 0bf5727..0000000 --- a/test/insn/opt_aref_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptArefTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptAref, Leave], "7[2]") - assert_stdout("1\n", "p 7[2]") - end - end -end diff --git a/test/insn/opt_aref_with_test.rb b/test/insn/opt_aref_with_test.rb deleted file mode 100644 index 3ccbbc9..0000000 --- a/test/insn/opt_aref_with_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptArefWithTest < TestCase - def test_execute - assert_insns([DupHash, OptArefWith, Leave], "{ 'test' => true }['test']") - assert_stdout("true\n", "p({ 'test' => true }['test'])") - end - end -end diff --git a/test/insn/opt_aset_test.rb b/test/insn/opt_aset_test.rb deleted file mode 100644 index 4030b73..0000000 --- a/test/insn/opt_aset_test.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptAsetTest < TestCase - def test_execute - assert_insns( - [PutNil, NewHash, PutObject, PutString, SetN, OptAset, Pop, Leave], - "{}[:key] = 'val'", - peephole_optimization: false - ) - assert_stdout( - "\"val\"\n", - "p({}[:key] = 'val')", - peephole_optimization: false - ) - end - end -end diff --git a/test/insn/opt_aset_with_test.rb b/test/insn/opt_aset_with_test.rb deleted file mode 100644 index 1de9527..0000000 --- a/test/insn/opt_aset_with_test.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptAsetWithTest < TestCase - def test_execute - YARV.compile("{}[\"true\"] = true").insns => [ - NewHash, - PutObject, - Swap, - TopN, - OptAsetWith[key: "true"], - Pop, - Leave - ] - end - end -end diff --git a/test/insn/opt_case_dispatch_test.rb b/test/insn/opt_case_dispatch_test.rb deleted file mode 100644 index ed6542c..0000000 --- a/test/insn/opt_case_dispatch_test.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptCaseDispatchTest < TestCase - def test_opt_case_dispatch_jumps_to_correct_branch - source_code = "case 1 when 0 then puts 'foo' else puts 'bar' end" - assert_stdout("bar\n", source_code) - end - - def test_opt_case_dispatch_can_use_cd_hash_to_jump_to_correct_branch - source_code = "case 1 when 1 then puts 'foo' else puts 'bar' end" - assert_stdout("foo\n", source_code) - end - end -end diff --git a/test/insn/opt_div_test.rb b/test/insn/opt_div_test.rb deleted file mode 100644 index 4b347c2..0000000 --- a/test/insn/opt_div_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptDivTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptDiv, Leave], "2 / 3") - assert_stdout("0\n", "p 2 / 3") - end - end -end diff --git a/test/insn/opt_empty_p_test.rb b/test/insn/opt_empty_p_test.rb deleted file mode 100644 index cf9f2dd..0000000 --- a/test/insn/opt_empty_p_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptEmptyPTest < TestCase - def test_execute - assert_insns([PutString, OptEmptyP, Leave], "''.empty?") - assert_stdout("true\n", "p ''.empty?") - end - end -end diff --git a/test/insn/opt_eq_test.rb b/test/insn/opt_eq_test.rb deleted file mode 100644 index 106c597..0000000 --- a/test/insn/opt_eq_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptEqTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptEq, Leave], "2 == 2") - assert_stdout("true\n", "p 2 == 2") - end - end -end diff --git a/test/insn/opt_ge_test.rb b/test/insn/opt_ge_test.rb deleted file mode 100644 index 2210171..0000000 --- a/test/insn/opt_ge_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptGeTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptGe, Leave], "4 >= 3") - assert_stdout("true\n", "puts 4 >= 3") - end - end -end diff --git a/test/insn/opt_gt_test.rb b/test/insn/opt_gt_test.rb deleted file mode 100644 index c7b2f59..0000000 --- a/test/insn/opt_gt_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptGtTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptGt, Leave], "4 > 3") - assert_stdout("true\n", "puts 4 > 3") - end - end -end diff --git a/test/insn/opt_le_test.rb b/test/insn/opt_le_test.rb deleted file mode 100644 index b011f3f..0000000 --- a/test/insn/opt_le_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptLeTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptLe, Leave], "3 <= 4") - assert_stdout("true\n", "puts 3 <= 4") - end - end -end diff --git a/test/insn/opt_length_test.rb b/test/insn/opt_length_test.rb deleted file mode 100644 index 44c676d..0000000 --- a/test/insn/opt_length_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptLengthTest < TestCase - def test_execute - assert_insns([PutString, OptLength, Leave], "''.length") - assert_stdout("0\n", "p ''.length") - end - end -end diff --git a/test/insn/opt_lt_test.rb b/test/insn/opt_lt_test.rb deleted file mode 100644 index 60d6d8d..0000000 --- a/test/insn/opt_lt_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptLtTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptLt, Leave], "3 < 4") - assert_stdout("true\n", "puts 3 < 4") - end - end -end diff --git a/test/insn/opt_ltlt_test.rb b/test/insn/opt_ltlt_test.rb deleted file mode 100644 index 71df9d6..0000000 --- a/test/insn/opt_ltlt_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptLtLtTest < TestCase - def test_execute - assert_insns([PutString, PutString, OptLtLt, Leave], "'' << 'a'") - assert_stdout("\"a\"\n", "p (+'') << 'a'") - end - end -end diff --git a/test/insn/opt_minus_test.rb b/test/insn/opt_minus_test.rb deleted file mode 100644 index e1bd3fe..0000000 --- a/test/insn/opt_minus_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptMinusTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptMinus, Leave], "3 - 2") - assert_stdout("1\n", "p 3 - 2") - end - end -end diff --git a/test/insn/opt_mod_test.rb b/test/insn/opt_mod_test.rb deleted file mode 100644 index 0c43958..0000000 --- a/test/insn/opt_mod_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptModTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptMod, Leave], "4 % 2") - assert_stdout("0\n", "puts 4 % 2") - end - end -end diff --git a/test/insn/opt_mult_test.rb b/test/insn/opt_mult_test.rb deleted file mode 100644 index 9514168..0000000 --- a/test/insn/opt_mult_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptMultTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptMult, Leave], "3 * 2") - assert_stdout("6\n", "p 3 * 2") - end - end -end diff --git a/test/insn/opt_neq_test.rb b/test/insn/opt_neq_test.rb deleted file mode 100644 index 069c4f2..0000000 --- a/test/insn/opt_neq_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptNeqTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptNeq, Leave], "2 != 2") - assert_stdout("false\n", "p 2 != 2") - assert_stdout("true\n", "p 1 != 2") - end - end -end diff --git a/test/insn/opt_newarray_max_test.rb b/test/insn/opt_newarray_max_test.rb deleted file mode 100644 index 0c29db6..0000000 --- a/test/insn/opt_newarray_max_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptNewArrayMaxTest < TestCase - def test_execute - assert_insns( - [PutObject, PutObject, Dup, SetLocalWC0, OptNewArrayMax, Leave], - "[2, x = 3].max" - ) - assert_stdout("3\n", "p [2, x = 3].max") - end - end -end diff --git a/test/insn/opt_newarray_min_test.rb b/test/insn/opt_newarray_min_test.rb deleted file mode 100644 index c979247..0000000 --- a/test/insn/opt_newarray_min_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptNewArrayMinTest < TestCase - def test_execute - assert_insns( - [PutObject, PutObject, Dup, SetLocalWC0, OptNewArrayMin, Leave], - "[2, x = 3].min" - ) - assert_stdout("2\n", "p [2, x = 3].min") - end - end -end diff --git a/test/insn/opt_nil_p_test.rb b/test/insn/opt_nil_p_test.rb deleted file mode 100644 index 6acb763..0000000 --- a/test/insn/opt_nil_p_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptNilPTest < TestCase - def test_execute - assert_insns([PutString, OptNilP, Leave], "''.nil?") - assert_stdout("false\n", "p ''.nil?") - end - end -end diff --git a/test/insn/opt_not_test.rb b/test/insn/opt_not_test.rb deleted file mode 100644 index e4ba51d..0000000 --- a/test/insn/opt_not_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptNotTest < TestCase - def test_execute - assert_insns([PutObject, OptNot, Leave], "!true") - assert_stdout("false\n", "p !true") - assert_stdout("true\n", "p !!true") - end - end -end diff --git a/test/insn/opt_or_test.rb b/test/insn/opt_or_test.rb deleted file mode 100644 index 4d6eaa4..0000000 --- a/test/insn/opt_or_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptOrTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptOr, Leave], "2 | 3") - assert_stdout("3\n", "p 2 | 3") - end - end -end diff --git a/test/insn/opt_plus_test.rb b/test/insn/opt_plus_test.rb deleted file mode 100644 index 80b289c..0000000 --- a/test/insn/opt_plus_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptPlusTest < TestCase - def test_execute - assert_insns([PutObject, PutObject, OptPlus, Leave], "2 + 3") - assert_stdout("5\n", "p 2 + 3") - end - end -end diff --git a/test/insn/opt_regexpmatch2_test.rb b/test/insn/opt_regexpmatch2_test.rb deleted file mode 100644 index f1dcc5c..0000000 --- a/test/insn/opt_regexpmatch2_test.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptRegexpMatch2Test < TestCase - def test_execute - assert_insns( - [ - PutObject, - PutString, - OptRegexpMatch2, - Dup, - BranchUnless, - Pop, - GetGlobal, - Leave - ], - "/true/ =~ 'true' && $~" - ) - - assert_stdout("nil\n", "p (/true/ =~ 'true' && $~)") - end - end -end diff --git a/test/insn/opt_send_without_block_test.rb b/test/insn/opt_send_without_block_test.rb deleted file mode 100644 index 6d9e4f4..0000000 --- a/test/insn/opt_send_without_block_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptSendWithoutBlockTest < TestCase - def test_execute - assert_insns( - [PutSelf, PutString, OptSendWithoutBlock, Leave], - "puts 'Hello, world!'" - ) - assert_stdout("Hello, world!\n", "puts 'Hello, world!'") - end - end -end diff --git a/test/insn/opt_size_test.rb b/test/insn/opt_size_test.rb deleted file mode 100644 index e967e15..0000000 --- a/test/insn/opt_size_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptSizeTest < TestCase - def test_execute - assert_insns([PutString, OptSize, Leave], "''.size") - assert_stdout("4\n", "p '1111'.size") - end - end -end diff --git a/test/insn/opt_str_freeze_test.rb b/test/insn/opt_str_freeze_test.rb deleted file mode 100644 index eed01cf..0000000 --- a/test/insn/opt_str_freeze_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptStrFreezeTest < TestCase - def test_execute - assert_insns([OptStrFreeze, Leave], "\"string\".freeze") - assert_stdout("\"string\"\n", "p(-'string')") - end - end -end diff --git a/test/insn/opt_str_uminus_test.rb b/test/insn/opt_str_uminus_test.rb deleted file mode 100644 index 3e2ee90..0000000 --- a/test/insn/opt_str_uminus_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptStrUMinusTest < TestCase - def test_execute - assert_insns([OptStrUMinus, Leave], "-\"string\"") - assert_stdout("\"string\"\n", "p(-'string')") - end - end -end diff --git a/test/insn/opt_succ_test.rb b/test/insn/opt_succ_test.rb deleted file mode 100644 index 1543035..0000000 --- a/test/insn/opt_succ_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class OptSuccTest < TestCase - def test_execute - assert_insns([PutString, OptSucc, Leave], "'a'.succ") - assert_stdout("\"b\"\n", "p 'a'.succ") - end - end -end diff --git a/test/insn/putnil_test.rb b/test/insn/putnil_test.rb deleted file mode 100644 index 11b3c79..0000000 --- a/test/insn/putnil_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class PutNilTest < TestCase - def test_execute - assert_insns([PutNil, Leave], "nil") - assert_stdout("nil\n", "p nil") - end - end -end diff --git a/test/insn/putobject_int2fix_0_test.rb b/test/insn/putobject_int2fix_0_test.rb deleted file mode 100644 index a02dfc1..0000000 --- a/test/insn/putobject_int2fix_0_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class PutObjectInt2Fix0Test < TestCase - def test_execute - assert_insns( - [PutSelf, PutObjectInt2Fix0, OptSendWithoutBlock, Leave], - "puts 0" - ) - assert_stdout("0\n", "puts 0") - end - end -end diff --git a/test/insn/putobject_int2fix_1_test.rb b/test/insn/putobject_int2fix_1_test.rb deleted file mode 100644 index 00e72ae..0000000 --- a/test/insn/putobject_int2fix_1_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class PutObjectInt2Fix1Test < TestCase - def test_execute - assert_insns( - [PutSelf, PutObjectInt2Fix1, OptSendWithoutBlock, Leave], - "puts 1" - ) - assert_stdout("1\n", "puts 1") - end - end -end diff --git a/test/insn/putobject_test.rb b/test/insn/putobject_test.rb deleted file mode 100644 index 9999dc1..0000000 --- a/test/insn/putobject_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class PutObjectTest < TestCase - def test_execute - assert_insns([PutObject, Leave], "5") - assert_stdout("5\n", "puts 5") - end - end -end diff --git a/test/insn/putself_test.rb b/test/insn/putself_test.rb deleted file mode 100644 index aacb5c1..0000000 --- a/test/insn/putself_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class PutSelfTest < TestCase - def test_execute - assert_insns([PutSelf, Leave], "self") - assert_stdout("main\n", "p self") - end - end -end diff --git a/test/insn/putstring_test.rb b/test/insn/putstring_test.rb deleted file mode 100644 index baacd00..0000000 --- a/test/insn/putstring_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class PutStringTest < TestCase - def test_execute - assert_insns([PutString, Leave], "'foo'") - assert_stdout("foo\n", "puts 'foo'") - end - end -end diff --git a/test/insn/send_test.rb b/test/insn/send_test.rb deleted file mode 100644 index 48e3e4b..0000000 --- a/test/insn/send_test.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class SendTest < TestCase - def test_executes_correctly - source = <<~SOURCE - true.tap { |i| p i } - SOURCE - - assert_insns([PutObject, Send, Leave], source) - iseq = YARV.compile(source) - assert_equal( - [PutSelf, GetLocalWC0, OptSendWithoutBlock, Leave], - iseq.insns[1].block_iseq.insns.map(&:class) - ) - - assert_stdout("true\n", source) - end - - def test_executes_without_block_correctly - source = "puts 'hello'" - iseq = YARV.compile(source, specialized_instruction: false) - assert_equal([PutSelf, PutString, Send, Leave], iseq.insns.map(&:class)) - - assert_stdout("hello\n", source) - end - end -end diff --git a/test/insn/setglobal_test.rb b/test/insn/setglobal_test.rb deleted file mode 100644 index d7bd1c0..0000000 --- a/test/insn/setglobal_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class SetGlobalTest < TestCase - def test_execute - assert_insns([PutObject, Dup, SetGlobal, Leave], "$global = 5") - assert_stdout("5\n", "p $global = 5") - end - end -end diff --git a/test/insn/setlocal_test.rb b/test/insn/setlocal_test.rb deleted file mode 100644 index 8f340b5..0000000 --- a/test/insn/setlocal_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class SetLocalTest < TestCase - def test_execute - YARV.compile("value = 5; tap { tap { value = 10 } }").insns => [ - PutObject, - SetLocalWC0, - PutSelf, - Send[ - block_iseq: { - insns: [ - PutSelf, - Send[ - block_iseq: { - insns: [ - PutObject, - Dup, - SetLocal[name: :value, level: 2], - Leave - ] - } - ], - Leave - ] - } - ], - Leave - ] - end - end -end diff --git a/test/insn/setlocal_wc_0_test.rb b/test/insn/setlocal_wc_0_test.rb deleted file mode 100644 index f863d06..0000000 --- a/test/insn/setlocal_wc_0_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class SetLocalWC0Test < TestCase - def test_execute - assert_insns([PutObject, Dup, SetLocalWC0, Leave], "value = 5") - assert_stdout("5\n", "p value = 5") - end - end -end diff --git a/test/insn/setlocal_wc_1_test.rb b/test/insn/setlocal_wc_1_test.rb deleted file mode 100644 index 0c06fd1..0000000 --- a/test/insn/setlocal_wc_1_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class SetLocalWC1Test < TestCase - def test_execute - source = "value = 5; print(self.then { value = 5 })" - - assert_equal( - [PutObject, Dup, SetLocalWC1, Leave], - YARV.compile(source).insns[4].block_iseq.insns.map(&:class) - ) - end - end -end diff --git a/test/insn/splatarray_test.rb b/test/insn/splatarray_test.rb deleted file mode 100644 index a0e2f96..0000000 --- a/test/insn/splatarray_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class SplatArrayTest < TestCase - def test_execute - assert_insns([PutObject, SplatArray, Dup, SetLocalWC0, Leave], "x = *(5)") - assert_stdout("[5]\n", "x = *(5); p x") - end - - def test_coerces_the_element - assert_stdout_for_instructions( - "[2]\n", - [ - [:putself], - [:putobject, 2], - [:splatarray, true], - [:opt_send_without_block, { mid: :p, flag: 20, orig_argc: 1 }] - ] - ) - end - end -end diff --git a/test/insn/swap_test.rb b/test/insn/swap_test.rb deleted file mode 100644 index 47b6cbb..0000000 --- a/test/insn/swap_test.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class SwapTest < TestCase - def test_execute - assert_insns( - [PutNil, PutObject, Swap, Pop, OptNot, OptNot, Leave], - "!!defined?([[]])" - ) - assert_stdout("true\n", "p !!defined?([[]])") - end - end -end diff --git a/test/insn/topn_test.rb b/test/insn/topn_test.rb deleted file mode 100644 index 9d93aa2..0000000 --- a/test/insn/topn_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class TopNTest < TestCase - def test_top_n_gets_nth_element_from_stack - source_code = "case 3 when 1..5 then puts 'foo' end" - assert_stdout("foo\n", source_code) - end - end -end diff --git a/test/insn/toregexp_test.rb b/test/insn/toregexp_test.rb deleted file mode 100644 index 55057a7..0000000 --- a/test/insn/toregexp_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class ToRegexpTest < TestCase - def test_execute - assert_insns( - [PutObject, PutObject, Dup, ObjToString, AnyToString, ToRegexp, Leave], - '/#{true}/' - ) - assert_stdout("\"/true/\"\n", 'p "/#{true}/"') - end - - def test_execute_multiple_interpolations - assert_insns( - [PutObject, PutObject, Dup, ObjToString, AnyToString, ToRegexp, Leave], - '/#{true}/' - ) - assert_stdout("\"/true 5/\"\n", 'p "/#{true} #{5}/"') - end - - def test_produces_the_expected_instructions - assert_stdout_for_instructions( - "/true/\n", - [ - [:putself], - [:putobject, ""], - [:putobject, true], - [:dup], - [:objtostring, { mid: :to_s, flag: 20, orig_argc: 0 }], - [:anytostring], - [:toregexp, 0, 2], - [:opt_send_without_block, { mid: :p, flag: 20, orig_argc: 1 }], - [:leave] - ] - ) - end - end -end diff --git a/test/soy_test.rb b/test/soy_test.rb deleted file mode 100644 index aa9d93d..0000000 --- a/test/soy_test.rb +++ /dev/null @@ -1,331 +0,0 @@ -# frozen_string_literal: true - -require_relative "test_helper" - -module YARV - class SOYTest < Test::Unit::TestCase - def test_local - assert_soy(<<~DISASM, "14 + 2") - flowchart TD - node_0(0000 PutObject) - node_1(0001 PutObject) - node_2(0002 OptPlus) - node_3(0003 Leave) - node_0 --> |0| node_2 - linkStyle 0 stroke:green; - node_1 --> |1| node_2 - linkStyle 1 stroke:green; - node_2 --> node_3 - linkStyle 2 stroke:red; - node_2 --> |0| node_3 - linkStyle 3 stroke:green; - DISASM - end - - def test_simple_bbarg - assert_soy(<<~DISASM, "(14 < 0 ? -1 : +1) + 100") - flowchart TD - node_0(0000 PutObject) - node_1(0001 PutObjectInt2Fix0) - node_2(0002 OptLt) - node_3(0003 BranchUnless) - node_4(0004 PutObject) - node_5(0005 Jump) - node_6(0006 PutObjectInt2Fix1) - node_7(0007 PutObject) - node_8(0008 OptPlus) - node_9(0009 Leave) - node_1000(1000 ψ) - node_1001(1001 φ) - node_0 --> |0| node_2 - linkStyle 0 stroke:green; - node_1 --> |1| node_2 - linkStyle 1 stroke:green; - node_2 --> node_3 - linkStyle 2 stroke:red; - node_2 --> |0| node_3 - linkStyle 3 stroke:green; - node_3 --> |branched| node_6 - linkStyle 4 stroke:red; - node_3 --> |fallthrough| node_5 - linkStyle 5 stroke:red; - node_4 --> |0005| node_1001 - linkStyle 6 stroke:green; - node_5 --> |branched| node_1000 - linkStyle 7 stroke:red; - node_6 --> |branched| node_1000 - linkStyle 8 stroke:red; - node_6 --> |0006| node_1001 - linkStyle 9 stroke:green; - node_7 --> |1| node_8 - linkStyle 10 stroke:green; - node_8 --> node_9 - linkStyle 11 stroke:red; - node_8 --> |0| node_9 - linkStyle 12 stroke:green; - node_1000 --> node_8 - linkStyle 13 stroke:red; - node_1001 -.-> node_1000 - node_1001 --> |0| node_8 - linkStyle 15 stroke:green; - DISASM - end - - def test_indirect_bbarg - assert_soy(<<~DISASM, "100 + (14 < 0 ? -1 : +1)") - flowchart TD - node_0(0000 PutObject) - node_1(0001 PutObject) - node_2(0002 PutObjectInt2Fix0) - node_3(0003 OptLt) - node_4(0004 BranchUnless) - node_5(0005 PutObject) - node_6(0006 Jump) - node_7(0007 PutObjectInt2Fix1) - node_8(0008 OptPlus) - node_9(0009 Leave) - node_1002(1002 ψ) - node_1004(1004 φ) - node_0 --> |0| node_8 - linkStyle 0 stroke:green; - node_1 --> |0| node_3 - linkStyle 1 stroke:green; - node_2 --> |1| node_3 - linkStyle 2 stroke:green; - node_3 --> node_4 - linkStyle 3 stroke:red; - node_3 --> |0| node_4 - linkStyle 4 stroke:green; - node_4 --> |branched| node_7 - linkStyle 5 stroke:red; - node_4 --> |fallthrough| node_6 - linkStyle 6 stroke:red; - node_5 --> |0006| node_1004 - linkStyle 7 stroke:green; - node_6 --> |branched| node_1002 - linkStyle 8 stroke:red; - node_7 --> |branched| node_1002 - linkStyle 9 stroke:red; - node_7 --> |0007| node_1004 - linkStyle 10 stroke:green; - node_8 --> node_9 - linkStyle 11 stroke:red; - node_8 --> |0| node_9 - linkStyle 12 stroke:green; - node_1002 --> node_8 - linkStyle 13 stroke:red; - node_1004 -.-> node_1002 - node_1004 --> |1| node_8 - linkStyle 15 stroke:green; - DISASM - end - - def test_loop - source = <<~SOURCE - n = 10 - sum = 0 - while n > 0 - sum += n - n -= 1 - end - sum - SOURCE - assert_soy(<<~DISASM, source) - flowchart TD - node_0(0000 PutObject) - node_1(0001 SetLocalWC0) - node_2(0002 PutObjectInt2Fix0) - node_3(0003 SetLocalWC0) - node_4(0004 Jump) - node_5(0005 PutNil) - node_6(0006 Pop) - node_7(0007 Jump) - node_8(0008 GetLocalWC0) - node_9(0009 GetLocalWC0) - node_10(0010 OptPlus) - node_11(0011 SetLocalWC0) - node_12(0012 GetLocalWC0) - node_13(0013 PutObjectInt2Fix1) - node_14(0014 OptMinus) - node_15(0015 SetLocalWC0) - node_16(0016 GetLocalWC0) - node_17(0017 PutObjectInt2Fix0) - node_18(0018 OptGt) - node_19(0019 BranchIf) - node_20(0020 PutNil) - node_21(0021 Pop) - node_22(0022 GetLocalWC0) - node_23(0023 Leave) - node_0 --> |0| node_1 - linkStyle 0 stroke:green; - node_1 --> node_3 - linkStyle 1 stroke:red; - node_2 --> |0| node_3 - linkStyle 2 stroke:green; - node_3 --> node_4 - linkStyle 3 stroke:red; - node_4 --> |branched| node_16 - linkStyle 4 stroke:red; - node_5 --> |0| node_6 - linkStyle 5 stroke:green; - node_7 --> |branched| node_16 - linkStyle 6 stroke:red; - node_8 --> node_9 - linkStyle 7 stroke:red; - node_8 --> |0| node_10 - linkStyle 8 stroke:green; - node_9 --> node_10 - linkStyle 9 stroke:red; - node_9 --> |1| node_10 - linkStyle 10 stroke:green; - node_10 --> node_11 - linkStyle 11 stroke:red; - node_10 --> |0| node_11 - linkStyle 12 stroke:green; - node_11 --> node_12 - linkStyle 13 stroke:red; - node_12 --> node_14 - linkStyle 14 stroke:red; - node_12 --> |0| node_14 - linkStyle 15 stroke:green; - node_13 --> |1| node_14 - linkStyle 16 stroke:green; - node_14 --> node_15 - linkStyle 17 stroke:red; - node_14 --> |0| node_15 - linkStyle 18 stroke:green; - node_15 --> |branched| node_16 - linkStyle 19 stroke:red; - node_16 --> node_18 - linkStyle 20 stroke:red; - node_16 --> |0| node_18 - linkStyle 21 stroke:green; - node_17 --> |1| node_18 - linkStyle 22 stroke:green; - node_18 --> node_19 - linkStyle 23 stroke:red; - node_18 --> |0| node_19 - linkStyle 24 stroke:green; - node_19 --> |branched| node_8 - linkStyle 25 stroke:red; - node_19 --> |fallthrough| node_22 - linkStyle 26 stroke:red; - node_20 --> |0| node_21 - linkStyle 27 stroke:green; - node_22 --> node_23 - linkStyle 28 stroke:red; - node_22 --> |0| node_23 - linkStyle 29 stroke:green; - DISASM - end - - def test_fib - source = <<~SOURCE - def fib(n) - if n < 2 - n - else - fib(n - 1) + fib(n - 2) - end - end - SOURCE - assert_soy(<<~DISASM, source) - flowchart TD - node_0(0000 DefineMethod) - node_1(0001 PutObject) - node_2(0002 Leave) - node_0 --> node_2 - linkStyle 0 stroke:red; - node_1 --> |0| node_2 - linkStyle 1 stroke:green; - flowchart TD - node_0(0000 GetLocalWC0) - node_1(0001 PutObject) - node_2(0002 OptLt) - node_3(0003 BranchUnless) - node_4(0004 GetLocalWC0) - node_5(0005 Leave) - node_6(0006 PutSelf) - node_7(0007 GetLocalWC0) - node_8(0008 PutObjectInt2Fix1) - node_9(0009 OptMinus) - node_10(0010 OptSendWithoutBlock) - node_11(0011 PutSelf) - node_12(0012 GetLocalWC0) - node_13(0013 PutObject) - node_14(0014 OptMinus) - node_15(0015 OptSendWithoutBlock) - node_16(0016 OptPlus) - node_17(0017 Leave) - node_0 --> node_2 - linkStyle 0 stroke:red; - node_0 --> |0| node_2 - linkStyle 1 stroke:green; - node_1 --> |1| node_2 - linkStyle 2 stroke:green; - node_2 --> node_3 - linkStyle 3 stroke:red; - node_2 --> |0| node_3 - linkStyle 4 stroke:green; - node_3 --> |branched| node_7 - linkStyle 5 stroke:red; - node_3 --> |fallthrough| node_4 - linkStyle 6 stroke:red; - node_4 --> node_5 - linkStyle 7 stroke:red; - node_4 --> |0| node_5 - linkStyle 8 stroke:green; - node_6 --> |0| node_10 - linkStyle 9 stroke:green; - node_7 --> node_9 - linkStyle 10 stroke:red; - node_7 --> |0| node_9 - linkStyle 11 stroke:green; - node_8 --> |1| node_9 - linkStyle 12 stroke:green; - node_9 --> node_10 - linkStyle 13 stroke:red; - node_9 --> |1| node_10 - linkStyle 14 stroke:green; - node_10 --> node_12 - linkStyle 15 stroke:red; - node_10 --> |0| node_16 - linkStyle 16 stroke:green; - node_11 --> |0| node_15 - linkStyle 17 stroke:green; - node_12 --> node_14 - linkStyle 18 stroke:red; - node_12 --> |0| node_14 - linkStyle 19 stroke:green; - node_13 --> |1| node_14 - linkStyle 20 stroke:green; - node_14 --> node_15 - linkStyle 21 stroke:red; - node_14 --> |1| node_15 - linkStyle 22 stroke:green; - node_15 --> node_16 - linkStyle 23 stroke:red; - node_15 --> |1| node_16 - linkStyle 24 stroke:green; - node_16 --> node_17 - linkStyle 25 stroke:red; - node_16 --> |0| node_17 - linkStyle 26 stroke:green; - DISASM - end - - private - - def assert_soy(expected, source) - string = +"" - compiled = YARV.compile(source) - compiled.all_iseqs.each do |iseq| - cfg = YARV::CFG.new(iseq) - dfg = YARV::DFG.new(cfg) - soy = YARV::SOY.new(dfg) - string << soy.mermaid - end - assert_equal(expected, string) - end - end -end diff --git a/test/static_spec_test.rb b/test/static_spec_test.rb deleted file mode 100644 index 6ff4986..0000000 --- a/test/static_spec_test.rb +++ /dev/null @@ -1,129 +0,0 @@ -# frozen_string_literal: true - -require "test_helper" - -module YARV - class StaticSpecTest < Test::Unit::TestCase - # We're not stupid, we just want to cache results from previous tests for - # use in subsequent tests in COMPILED_CACHE, CFG_CACHE etc. - self.test_order = :defined - - SPEC_FILES = ["#{__dir__}/../spec/ruby/language/and_spec.rb"] - - COMPILED_CACHE = {} - CFG_CACHE = {} - DFG_CACHE = {} - SOY_CACHE = {} - - def test_compile - SPEC_FILES.each do |spec_file| - compiled = - without_warnings do - YARV.compile( - File.read(spec_file), - spec_file, - File.basename(spec_file) - ) - end - COMPILED_CACHE[spec_file] = compiled - end - end - - def test_disasm - SPEC_FILES.each do |spec_file| - compiled = COMPILED_CACHE[spec_file] - next unless compiled - compiled.disasm - end - end - - def test_cfg - SPEC_FILES.each do |spec_file| - compiled = COMPILED_CACHE[spec_file] - next unless compiled - compiled.all_iseqs.each do |iseq| - iseq_key = [spec_file, iseq.object_id] - cfg = CFG.new(iseq) - CFG_CACHE[iseq_key] = cfg - end - end - end - - def test_cfg_disasm - SPEC_FILES.each do |spec_file| - compiled = COMPILED_CACHE[spec_file] - next unless compiled - compiled.all_iseqs.each do |iseq| - iseq_key = [spec_file, iseq.object_id] - cfg = CFG_CACHE[iseq_key] - next unless cfg - cfg.disasm - end - end - end - - def test_dfg - SPEC_FILES.each do |spec_file| - compiled = COMPILED_CACHE[spec_file] - next unless compiled - compiled.all_iseqs.each do |iseq| - iseq_key = [spec_file, iseq.object_id] - cfg = CFG_CACHE[iseq_key] - next unless cfg - dfg = YARV::DFG.new(cfg) - DFG_CACHE[iseq_key] = dfg - end - end - end - - def test_dfg_disasm - SPEC_FILES.each do |spec_file| - compiled = COMPILED_CACHE[spec_file] - next unless compiled - compiled.all_iseqs.each do |iseq| - iseq_key = [spec_file, iseq.object_id] - dfg = DFG_CACHE[iseq_key] - next unless dfg - dfg.disasm - end - end - end - - # def test_soy - # SPEC_FILES.each do |spec_file| - # compiled = COMPILED_CACHE[spec_file] - # next unless compiled - # compiled.all_iseqs.each do |iseq| - # iseq_key = [spec_file, iseq.object_id] - # dfg = DFG_CACHE[iseq_key] - # next unless dfg - # soy = YARV::SOY.new(dfg) - # SOY_CACHE[iseq_key] = soy - # end - # end - # end - - def test_soy_mermaid - SPEC_FILES.each do |spec_file| - compiled = COMPILED_CACHE[spec_file] - next unless compiled - compiled.all_iseqs.each do |iseq| - iseq_key = [spec_file, iseq.object_id] - soy = SOY_CACHE[iseq_key] - next unless soy - soy.mermaid - end - end - end - - def without_warnings - original_verbose = $VERBOSE - $VERBOSE = nil - begin - yield - ensure - $VERBOSE = original_verbose - end - end - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index 34b838d..0000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -# Why is this here!? In the power_assert library, it enables a tracepoint if -# byebug is not defined. This causes a bunch of already-loaded iseqs to be -# recompiled to their tracepoint variants, which means we can't specialize like -# we want to in the tests. -# https://github.com/ruby/power_assert/blob/9132517/lib/power_assert.rb#L5 -module Byebug -end - -$:.unshift(File.expand_path("../lib", __dir__)) -require "yarv" - -require "stringio" -require "test/unit" - -module YARV - class TestCase < Test::Unit::TestCase - private - - def assert_insns(expected, code, **options) - iseq = YARV.compile(code, **options) - assert_equal(expected, iseq.insns.map(&:class)) - end - - def assert_stdout(expected, code, **options) - original = $stdout - $stdout = StringIO.new - - YARV.compile(code, **options).eval - assert_equal(expected, $stdout.string) - ensure - $stdout = original - end - - # Allows to test instructions sequences that the compiler doesn't generate. - def assert_stdout_for_instructions(expected, instructions) - original = $stdout - $stdout = StringIO.new - iseq = RubyVM::InstructionSequence.compile("").to_a - iseq[-1] = [1, :RUBY_EVENT_LINE, *instructions, [:leave]] - InstructionSequence.compile(Main.new, iseq).eval - assert_equal(expected, $stdout.string) - ensure - $stdout = original - end - end -end diff --git a/test/visitor_test.rb b/test/visitor_test.rb deleted file mode 100644 index 003123d..0000000 --- a/test/visitor_test.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -require_relative "test_helper" - -module YARV - class VisitorTest < Test::Unit::TestCase - def test_binary_plus - assert_visit("2 + 3") - end - - def test_binary_minus - assert_visit("3 - 2") - end - - def test_binary_mult - assert_visit("2 * 3") - end - - def test_binary_div - assert_visit("3 / 2") - end - - def test_call - assert_visit("foo.bar") - end - - def test_call_with_call - assert_visit("foo.()") - end - - def test_call_with_lonely - assert_visit("foo&.bar") - end - - def test_float - assert_visit("1.0") - end - - def test_gvar - assert_visit("$foo") - end - - def test_int - assert_visit("3") - end - - def test_int_0 - assert_visit("0") - end - - def test_int_1 - assert_visit("1") - end - - def test_paren - assert_visit("(((0)))") - end - - def test_rational - assert_visit("1r") - end - - def test_string_dvar_gvar - assert_visit("\"\#$foo\"") - end - - def test_string_literal - assert_visit("\"foo \#{bar}\"") - end - - def test_symbol_literal - assert_visit(":foo") - end - - def test_vcall - assert_visit("foo") - end - - def test_xstring_literal - assert_visit("`foo`") - end - - private - - def assert_visit(source) - assert_equal( - YARV.compile(source), - Visitor.new.visit(SyntaxTree.parse(source)) - ) - end - end -end