{"id":13647121,"url":"https://github.com/Sipkab/jvm-tail-recursion","last_synced_at":"2025-04-21T21:32:45.381Z","repository":{"id":167636849,"uuid":"255314974","full_name":"Sipkab/jvm-tail-recursion","owner":"Sipkab","description":"Optimizer library for tail recursive calls in Java bytecode","archived":false,"fork":false,"pushed_at":"2023-03-21T16:03:53.000Z","size":446,"stargazers_count":122,"open_issues_count":4,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-08-02T01:26:54.743Z","etag":null,"topics":["jvm-bytecode","optimization-tools","saker-build","tail-recursion"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Sipkab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-04-13T12:07:32.000Z","updated_at":"2024-07-17T06:02:28.000Z","dependencies_parsed_at":"2023-07-07T16:32:48.596Z","dependency_job_id":null,"html_url":"https://github.com/Sipkab/jvm-tail-recursion","commit_stats":null,"previous_names":["sipkab/jvm-tail-recursion"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sipkab%2Fjvm-tail-recursion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sipkab%2Fjvm-tail-recursion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sipkab%2Fjvm-tail-recursion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Sipkab%2Fjvm-tail-recursion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Sipkab","download_url":"https://codeload.github.com/Sipkab/jvm-tail-recursion/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223880452,"owners_count":17219125,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["jvm-bytecode","optimization-tools","saker-build","tail-recursion"],"created_at":"2024-08-02T01:03:21.196Z","updated_at":"2024-11-09T20:31:04.597Z","avatar_url":"https://github.com/Sipkab.png","language":"Java","readme":"# jvm-tail-recursion\n\n[![Build status](https://img.shields.io/azure-devops/build/sipkab/cebd28c3-5a6a-462e-8750-516bfbe96bb4/4/master)](https://dev.azure.com/sipkab/jvm-tail-recursion/_build) [![Latest version](https://mirror.nest.saker.build/badges/sipka.jvm.tailrec/version.svg)](https://nest.saker.build/package/sipka.jvm.tailrec \"sipka.jvm.tailrec | saker.nest\")\n\nJava library performing tail recursion optimizations on Java bytecode. It simply replaces the final recursive method calls in a function to a goto to the start of the same function.\n\nThe project uses [ASM](https://asm.ow2.io/) to perform bytecode manipulation.\n\n\n  * [Examples](#examples)\n    + [Count down to zero](#count-down-to-zero)\n    + [List numbers in a string](#list-numbers-in-a-string)\n    + [Enumerate all interfaces that a class implements](#enumerate-all-interfaces-that-a-class-implements)\n    + [Side effect free instruction removal](#side-effect-free-instruction-removal)\n    + [this instance change](#this-instance-change)\n  * [Limitations](#limitations)\n    + [Recommendations](#recommendations)\n  * [Usage](#usage)\n    + [Part of the build process](#part-of-the-build-process)\n      - [ZIP transformer](#zip-transformer)\n      - [Class directory optimization](#class-directory-optimization)\n      - [Optimize an existing archive](#optimize-an-existing-archive)\n    + [Command line usage](#command-line-usage)\n      - [With saker.build](#with-sakerbuild)\n  * [Benchmarks](#benchmarks)\n    + [Optimized](#optimized)\n    + [Unoptimized](#unoptimized)\n  * [Building the project](#building-the-project)\n  * [Repository structure](#repository-structure)\n  * [Should I use this?](#should-i-use-this)\n  * [License](#license)\n\n\n## Examples\n\n### Count down to zero\n\nA simple tail recursive function that counts down to zero:\n\n\u003ctable\u003e\u003ctr\u003e\n\u003cth\u003eBefore\u003c/th\u003e\n\u003cth\u003eAfter\u003c/th\u003e\n\u003c/tr\u003e\u003ctr\u003e\u003ctd\u003e\n\n\n```java\nstatic void count(int n) {\n\n    if (n == 0) {\n        return;\n    }\n    count(n - 1);\n    \n}\n```\n\n\n\u003c/td\u003e\u003ctd\u003e\n    \n```java\nstatic void count(int n) {\n    while (true) {\n        if (n == 0) {\n            return;\n        }\n        n = n - 1;\n    }\n}\n```\n\n\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\n### List numbers in a string\n\nIf you've ever wanted a sequence of numbers in a string:\n\n\u003ctable\u003e\u003ctr\u003e\n\u003cth\u003eBefore\u003c/th\u003e\n\u003cth\u003eAfter\u003c/th\u003e\n\u003c/tr\u003e\u003ctr\u003e\u003ctd\u003e\n\n```java\nstatic String numbers(int n, String s) {\n\n    if (n == 0) {\n        return s + \"0\";\n    }\n    return numbers(n - 1, s + n + \",\");\n    \n    \n}\n```\n\n\n\u003c/td\u003e\u003ctd\u003e\n    \n```java\nstatic String numbers(int n, String s) {\n    while (true) {\n        if (n == 0) {\n            return s + \"0\";\n        }\n        s = s + n + \",\";\n        n = n - 1;\n    }\n}\n```\n\n\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\n### Enumerate all interfaces that a class implements\n\nTail recursive class can also be optimized inside an if-else condition. Note that here only the recursive call at the end is optimized, not the one in the loop!\n\n\u003ctable\u003e\u003ctr\u003e\n\u003cth\u003eBefore\u003c/th\u003e\n\u003cth\u003eAfter\u003c/th\u003e\n\u003c/tr\u003e\u003ctr\u003e\u003ctd\u003e\n\n\n```java\nstatic void collectInterfaces(\n        Class\u003c?\u003e clazz, \n        Set\u003cClass\u003c?\u003e\u003e result) {\n        \n    for (Class\u003c?\u003e itf : clazz.getInterfaces()) {\n        if (result.add(itf)) \n            collectInterfaces(itf, result);\n    }\n    Class\u003c?\u003e sclass = clazz.getSuperclass();\n    if (sclass != null) {\n        collectInterfaces(sclass, result);\n        \n    }\n    \n    \n}\n```\n\n\n\u003c/td\u003e\u003ctd\u003e\n    \n```java\nstatic void collectInterfaces(\n        Class\u003c?\u003e clazz, \n        Set\u003cClass\u003c?\u003e\u003e result) {\n    while (true) {\n        for (Class\u003c?\u003e itf : clazz.getInterfaces()) {\n            if (result.add(itf)) \n                collectInterfaces(itf, result);\n        }\n        Class\u003c?\u003e sclass = clazz.getSuperclass();\n        if (sclass != null) {\n            clazz = sclass;\n            continue;\n        }\n        return;\n    }\n}\n```\n\n\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\n### Side effect free instruction removal\n\nIf there are some instructions in the return path of a tail recursive call which can be removed, the library will do so. Some of these are:\n\n* Unused variables\n* Unused allocated arrays\n* Unused field or array accesses\n\n\u003ctable\u003e\u003ctr\u003e\n\u003cth\u003eBefore\u003c/th\u003e\n\u003cth\u003eAfter\u003c/th\u003e\n\u003c/tr\u003e\u003ctr\u003e\u003ctd\u003e\n\n\n```java\nfinal void count(int n) {\n\n    if (n == 0) {\n        return;\n    }\n    count(n - 1);\n    Object b = new int[Integer.MAX_INT];\n    Object f = this.myField;\n    Object a = this.myArray[30];\n}\n```\n\n\n\u003c/td\u003e\u003ctd\u003e\n    \n```java\nfinal void count(int n) {\n    while (true) {\n        if (n == 0) {\n            return;\n        }\n        n = n - 1;\n    }\n\n    \n}\n```\n\n\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\nNote that this causes some exceptions to not be thrown in case of programming errors. E.g. No `OutOfMemoryError` will be thrown in the optimized code as the `new int[Integer.MAX_INT]` instruction is optimized out, and no `NullPointerException`s are thrown if the `myArray` field is `null`.\n\n### this instance change\n\nThe optimization can be performed even if the tail recursive call is done on a different instance: (See limitations)\n\n\u003ctable\u003e\u003ctr\u003e\n\u003cth\u003eBefore\u003c/th\u003e\n\u003cth\u003eAfter\u003c/th\u003e\n\u003c/tr\u003e\u003ctr\u003e\u003ctd\u003e\n\n\n```java\npublic class MyClass {\n    public final void count(int n) {\n\n        if (n == 0) {\n            return;\n        }\n        new MyClass().count(n - 1);\n        \n        \n    }\n}\n```\n\n\n\u003c/td\u003e\u003ctd\u003e\n    \n```java\npublic class MyClass {\n    public final void count(int n) {\n        while (true) {\n            if (n == 0) {\n                return;\n            }\n            this = new MyClass();\n            n = n - 1;\n        }\n    }\n}\n```\n\n\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\nNote that setting the `this` variable is not valid Java code, but it is possible in bytecode.\n\n## Limitations\n\nThere are some limitations to the optimization:\n\n1. The method must not be virtual. A virtual method is called based on the dynamic type of the object. This means that instance methods which are virtual cannot be optimized, as if the object on which the method is being invoked on changes, the recursive call cannot be simplified to a jump.\n    * Another reason is why virtual methods cannot be optimized is that they can be called from the subclass using the `super.method(...)` expression. If a subclass calls the method then the tail recursive calls would need to be dispatched back to the subclass. This causes virtual methods to not be optimizable.\n2. Synchronized instance methods cannot be optimized. See the previous point for the reasons.\n    * `static` method **can** be synchronized.\n3. If you throw an exception in the method, the stacktrace will only show the method once, as the tail recursive calls are optimized.\n\n### Recommendations\n\nThe methods you want to be subject to optimization should be any of the following:\n\n* `static` method.\n* `private` instance method.\n* `final` instance method.    \n\n## Usage\n\nThe project is released as the [sipka.jvm.tailrec](https://nest.saker.build/package/sipka.jvm.tailrec?tab=bundles) package on the [saker.nest repository](https://nest.saker.build/).\\\nYou can [**download the latest release using this link**](https://api.nest.saker.build/bundle/download/sipka.jvm.tailrec-v0.8.2) or by selecting a version and clicking *Download* on the *Bundles* tab on the [sipka.jvm.tailrec](https://nest.saker.build/package/sipka.jvm.tailrec?tab=bundles) package page.\n\nIt can be used in the following ways:\n\n### Part of the build process\n\nThe project integrates with the [saker.build system](https://github.com/sakerbuild/saker.build) in the following ways:\n\n#### ZIP transformer\n\nUsing the `sipka.jvm.tailrec.zip.transformer()` task to retrieve a ZIP transformer when creating your JAR or ZIP archive will cause each `.class` file to be optimized by the library.\n\n```sakerscript\n$javac = saker.java.compile(src)\nsaker.jar.create(\n    Resources: {\n        Directory: $javac[ClassDirectory],\n        Resources: **,\n    },\n    Transformers: sipka.jvm.tailrec.zip.transformer(),\n)\n```\n\nThe above is an example for compiling all Java sources in the `src` directory, and creating a JAR file with the compiled and optimized classes.\n\n#### Class directory optimization\n\nYou can use the `sipka.jvm.tailrec.optimize()` task to optimize a directory with the `.class` files in it.\n\n```sakerscript\n$javac = saker.java.compile(src)\n\n$path = sipka.jvm.tailrec.optimize($javac[ClassDirectory])\n```\n\nThe above will simply optimize all the `.class` files that are the output of the Java compilation. The optimized classes are written into the build directory, and a path to it is returned by the task. (`$path`)\n\n#### Optimize an existing archive\n\nIf you already have an archive that you want to optimize, use the ZIP transformer as seen previously, but specify the inputs as your archive:\n\n```sakerscript\nsaker.jar.create(\n    Include: my_jar_to_optimize.jar,\n    Transformers: sipka.jvm.tailrec.zip.transformer(),\n)\n```\n\nThis will result in a new archive being created that contains everything from the included JAR, and each `.class` file will be optimized.\n\n### Command line usage\n\nThe optimization can also be performed on the command line:\n\n```plaintext\njava -jar sipka.jvm.tailrec.jar -output my_jar_opt.jar my_jar.jar\n```\n\nThe above will optimize `my_jar.jar` and create the output of `my_jar_opt.jar`. You can also overwrite the input:\n\n```plaintext\njava -jar sipka.jvm.tailrec.jar -overwrite my_jar.jar\n```\n\nWhich causes the input JAR to be overwritten with the result.\n\nThe input can also be a class directory.\n\nSee `--help` for more usage information.\n\n#### With saker.build\n\nIf you already have the [saker.build system](https://github.com/sakerbuild/saker.build) at hand, you don't have to bother with downloading. You can use the `main` action of saker.nest to invoke the library:\n\n```plaintext\njava -jar saker.build.jar action main sipka.jvm.tailrec --help\n```\n\n## Benchmarks\n\nOur results are the following: (**Higher values are better**)\n\nSee the [benchmark](/benchmark) directory for more information.\n\n### Optimized\n\n```plaintext\nBenchmark                            Mode  Cnt        Score      Error  Units\nTailRecursionBenchmark.countTest    thrpt   25   436354,616 ? 2208,882  ops/s\nTailRecursionBenchmark.factTest     thrpt   25  1201126,490 ? 8081,594  ops/s\nTailRecursionBenchmark.numbersTest  thrpt   25     2183,977 ?   62,684  ops/s\n```\n\n### Unoptimized\n\n```plaintext\nBenchmark                            Mode  Cnt        Score      Error  Units\nTailRecursionBenchmark.countTest    thrpt   25   257429,802 ? 1501,296  ops/s\nTailRecursionBenchmark.factTest     thrpt   25   831008,693 ? 9108,785  ops/s\nTailRecursionBenchmark.numbersTest  thrpt   25     2083,716 ?   14,563  ops/s\n```\n\n## Building the project\n\nThe project uses the [saker.build system](https://github.com/sakerbuild/saker.build) for building.\n\nUse the following command, or do build it inside an IDE:\n\n```plaintext\njava -jar saker.build.jar -build-directory build export\n```\n\nSee the [build script](/saker.build) for the executable build targets.\n\n## Repository structure\n\n* `src`: The source files for the project\n    * Sources for the ASM library are under the package `sipka.jvm.tailrec.thirdparty`.\n* `resources`: Resource files for the created JAR files\n* `test/src`: Test Java sources\n* `test/resources`: Resource files for test cases which need them\n\n## Should I use this?\n\nYou should use it, but you **should not** rely on it.\\\nIn general, when you're writing production code, you'll most likely already optimize your methods in ways that it already avoids issues that are solvable with tail recursion optimization.\n\nMy recommendation is that in general you shouldn't rely on a specific optimization being performed for you. They are subject to the circumstances, and can easily break without the intention of breaking it. For example, by not paying attention and accidentally adding a new instruction after the tail recursive call that you want to optimize, will cause the optimization to not be performed. This could cause your code to break unexpectedly. \\\nIf you want an optimization done for you, you should do it yourself, or be extremely explicit about it. Make sure to add tests for the scenarios that you want to work in a specific way.\n\nThis project serves mainly educational purposes, and is also fun as you can write infinite loops like this:\n\n```java\npublic static void loopForever() {\n    System.out.println(\"Hello world!\");\n    \n    loopForever();\n}\n```\n\nMagnificent!\n\n## License\n\nThe source code for the project is licensed under *GNU General Public License v3.0 only*.\n\nShort identifier: [`GPL-3.0-only`](https://spdx.org/licenses/GPL-3.0-only.html).\n\nOfficial releases of the project (and parts of it) may be licensed under different terms. See the particular releases for more information.\n","funding_links":[],"categories":["Java"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSipkab%2Fjvm-tail-recursion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSipkab%2Fjvm-tail-recursion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSipkab%2Fjvm-tail-recursion/lists"}