├── .github └── workflows │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── push-main.yaml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── benchmarks ├── funccall │ ├── Makefile │ ├── README.md │ ├── measure-single-benchmark.sh │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── yasuenag │ │ │ └── ffmasm │ │ │ └── benchmark │ │ │ └── funccall │ │ │ └── FuncCallComparison.java │ │ └── native │ │ └── rdtsc.S └── vectorapi │ ├── README.md │ ├── pom.xml │ └── src │ └── main │ └── java │ └── com │ └── yasuenag │ └── ffmasm │ └── benchmark │ └── vectorapi │ └── VectorOpComparison.java ├── examples ├── aarch64 │ ├── README.md │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ ├── com │ │ └── yasuenag │ │ │ └── ffmasm │ │ │ └── examples │ │ │ └── aarch64 │ │ │ └── Main.java │ │ └── module-info.java ├── cpumodel │ ├── README.md │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ ├── com │ │ └── yasuenag │ │ │ └── ffmasm │ │ │ └── examples │ │ │ └── cpumodel │ │ │ └── Main.java │ │ └── module-info.java ├── disas │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── yasuenag │ │ └── ffmasm │ │ └── examples │ │ └── disas │ │ └── Main.java └── perf │ ├── README.md │ ├── perf.sh │ ├── pom.xml │ └── src │ └── main │ └── java │ └── com │ └── yasuenag │ └── ffmasm │ └── examples │ └── perf │ └── PerfJit.java ├── pom.xml ├── src ├── main │ └── java │ │ ├── com │ │ └── yasuenag │ │ │ └── ffmasm │ │ │ ├── AsmBuilder.java │ │ │ ├── CodeSegment.java │ │ │ ├── JitDump.java │ │ │ ├── NativeRegister.java │ │ │ ├── PlatformException.java │ │ │ ├── UnsupportedPlatformException.java │ │ │ ├── aarch64 │ │ │ ├── AArch64AsmBuilder.java │ │ │ ├── HWShift.java │ │ │ ├── IndexClass.java │ │ │ └── Register.java │ │ │ ├── amd64 │ │ │ ├── AMD64AsmBuilder.java │ │ │ ├── AVXAsmBuilder.java │ │ │ ├── Register.java │ │ │ └── SSEAsmBuilder.java │ │ │ └── internal │ │ │ ├── ExecMemory.java │ │ │ ├── JavaVM.java │ │ │ ├── JniEnv.java │ │ │ ├── JvmtiEnv.java │ │ │ ├── aarch64 │ │ │ └── AArch64NativeRegister.java │ │ │ ├── amd64 │ │ │ ├── AMD64NativeRegister.java │ │ │ └── CallingRegisters.java │ │ │ ├── linux │ │ │ ├── LinuxExecMemory.java │ │ │ └── PerfJitDump.java │ │ │ └── windows │ │ │ └── WindowsExecMemory.java │ │ └── module-info.java └── test │ └── java │ └── com │ └── yasuenag │ └── ffmasm │ └── test │ ├── aarch64 │ ├── AsmTest.java │ └── NativeRegisterTest.java │ ├── amd64 │ ├── AVXAsmTest.java │ ├── AsmTest.java │ ├── NativeRegisterTest.java │ ├── SSEAsmTest.java │ └── TestBase.java │ ├── common │ ├── AsmBuilderTest.java │ ├── CodeSegmentTest.java │ └── JitDumpTest.java │ └── linux │ └── CodeSegmentTest.java └── tools └── disas ├── pom.xml └── src └── main └── java ├── com └── yasuenag │ └── ffmasmtools │ └── disas │ └── Disassembler.java └── module-info.java /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Maven Build & Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | os: 14 | - ubuntu-latest 15 | - windows-latest 16 | - ubuntu-24.04-arm 17 | 18 | runs-on: ${{ matrix.os }} 19 | 20 | name: Run Maven on ${{ matrix.os }} 21 | 22 | steps: 23 | - name: 'Checkout repository' 24 | uses: actions/checkout@v4 25 | 26 | - name: Setup Java 27 | uses: actions/setup-java@v4 28 | with: 29 | distribution: oracle 30 | java-version: 22 31 | cache: maven 32 | 33 | - name: 'Run Maven' 34 | run: mvn -B test 35 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | analyze: 11 | name: Analyze 12 | runs-on: ubuntu-latest 13 | permissions: 14 | actions: read 15 | contents: read 16 | security-events: write 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | language: [ 'java' ] 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v4 26 | 27 | - name: Setup Java 28 | uses: actions/setup-java@v4 29 | with: 30 | distribution: oracle 31 | java-version: 22 32 | cache: maven 33 | 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@v3 36 | with: 37 | languages: ${{ matrix.language }} 38 | queries: security-extended,security-and-quality 39 | 40 | - name: Build 41 | run: mvn -B -DskipTests package 42 | 43 | - name: Perform CodeQL Analysis 44 | uses: github/codeql-action/analyze@v3 45 | -------------------------------------------------------------------------------- /.github/workflows/push-main.yaml: -------------------------------------------------------------------------------- 1 | name: Publish snapshot library 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | concurrency: 10 | group: push-snapshot 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | publish-jar: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | packages: write 18 | contents: read 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | - name: Setup Java 23 | uses: actions/setup-java@v4 24 | with: 25 | distribution: oracle 26 | java-version: 22 27 | cache: maven 28 | - name: Deploy snapshot library to GitHub Packages by Maven 29 | run: mvn -B deploy 30 | env: 31 | GITHUB_TOKEN: ${{ github.token }} 32 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish new release 2 | on: 3 | release: 4 | types: [published] 5 | 6 | concurrency: 7 | group: "publish" 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | upload: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | packages: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Setup Java 18 | uses: actions/setup-java@v4 19 | with: 20 | distribution: oracle 21 | java-version: 22 22 | cache: maven 23 | - name: Publish to GitHub Packages via Apache Maven 24 | run: mvn -B -DskipTests deploy javadoc:javadoc 25 | env: 26 | GITHUB_TOKEN: ${{ github.token }} 27 | - name: Upload artifact 28 | uses: actions/upload-pages-artifact@v3 29 | with: 30 | path: 'target/site/apidocs' 31 | 32 | deploy_page: 33 | needs: upload 34 | permissions: 35 | pages: write 36 | id-token: write 37 | runs-on: ubuntu-latest 38 | environment: 39 | name: github-pages 40 | url: ${{ steps.deployment.outputs.page_url }} 41 | steps: 42 | - name: Deploy to GitHub Pages 43 | id: deployment 44 | uses: actions/deploy-pages@v4 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | jit-*.dump 3 | jitted-*.so 4 | perf.*data* 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ffmasm 2 | =================== 3 | 4 | ![CI result](../../actions/workflows/ci.yml/badge.svg) 5 | ![CodeQL](../../actions/workflows/codeql-analysis.yml/badge.svg) 6 | 7 | ffmasm is an assembler for hand-assembling from Java. 8 | It uses Foreign Function & Memory API, so the application can call assembled code via [MethodHandle](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/invoke/MethodHandle.html). 9 | 10 | * Javadoc: https://yasuenag.github.io/ffmasm/ 11 | * Maven package: https://github.com/YaSuenag/ffmasm/packages/ 12 | * Supported instructions: See builder classes in [com.yasuenag.ffmasm.amd64](https://yasuenag.github.io/ffmasm/com.yasuenag.ffmasm/com/yasuenag/ffmasm/amd64/package-summary.html). 13 | 14 | # Requirements 15 | 16 | Java 22 17 | 18 | # Supported platform 19 | 20 | * Linux AMD64 21 | * Windows AMD64 22 | * Linux AArch64 23 | 24 | # How to build 25 | 26 | ``` 27 | $ mvn package 28 | ``` 29 | 30 | ## Test for ffmasm 31 | 32 | ```bash 33 | $ mvn test 34 | ``` 35 | 36 | # How to use 37 | 38 | See [Javadoc](https://yasuenag.github.io/ffmasm/) and [cpumodel](examples/cpumodel) examples. 39 | 40 | ## 1. Create `CodeSegment` 41 | 42 | [CodeSegment](https://yasuenag.github.io/ffmasm/com.yasuenag.ffmasm/com/yasuenag/ffmasm/CodeSegment.html) is a storage for assembled code. In Linux, it would be allocated by `mmap(2)` with executable bit. 43 | It implements [AutoCloseable](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/AutoCloseable.html), so you can use try-with-resources in below: 44 | 45 | ```java 46 | try(var seg = new CodeSegment()){ 47 | ... 48 | } 49 | ``` 50 | 51 | ## 2. Create `MethodHandle` 52 | 53 | You can assemble the code via inner classes of [com.yasuenag.ffmasm.AsmBuilder](https://yasuenag.github.io/ffmasm/com.yasuenag.ffmasm/com/yasuenag/ffmasm/AsmBuilder.html): 54 | 55 | * [AsmBuilder.AMD64](https://yasuenag.github.io/ffmasm/com.yasuenag.ffmasm/com/yasuenag/ffmasm/AsmBuilder.AMD64.html) 56 | * [AsmBuilder.SSE](https://yasuenag.github.io/ffmasm/com.yasuenag.ffmasm/com/yasuenag/ffmasm/AsmBuilder.SSE.html) 57 | * [AsmBuilder.AVX](https://yasuenag.github.io/ffmasm/com.yasuenag.ffmasm/com/yasuenag/ffmasm/AsmBuilder.AVX.html) 58 | * [AsmBuilder.AArch64](https://yasuenag.github.io/ffmasm/com.yasuenag.ffmasm/com/yasuenag/ffmasm/AsmBuilder.AArch64.html) 59 | 60 | ### AMD64 61 | 62 | You need to use [com.yasuenag.ffmasm.amd64.Register](https://yasuenag.github.io/ffmasm/com.yasuenag.ffmasm/com/yasuenag/ffmasm/amd64/Register.html) to specify registers in assembly code. Following example shows how you can create method `(I)I` (JNI signature) in ffmasm: 63 | 64 | You can get `MethodHandle` in result of `build()`. 65 | 66 | ```java 67 | var desc = FunctionDescriptor.of( 68 | ValueLayout.JAVA_INT, // return value 69 | ValueLayout.JAVA_INT // 1st argument 70 | ); 71 | 72 | var method = new AsmBuilder.AMD64(seg, desc) 73 | /* push %rbp */ .push(Register.RBP) 74 | /* mov %rsp, %rbp */ .movRM(Register.RSP, Register.RBP, OptionalInt.empty()) 75 | /* mov %rdi, %rax */ .movRM(Register.RDI, Register.RAX, OptionalInt.empty()) 76 | /* leave */ .leave() 77 | /* ret */ .ret() 78 | .build(Linker.Option.critical(false)); 79 | ``` 80 | 81 | > [!NOTE] 82 | > [Linker.Option.critical()](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/foreign/Linker.Option.html#critical(boolean)) is recommended to pass `build()` method due to performance, but it might be cause of some issues in JVM (time to synchronize safepoint, memory corruption, etc). See Javadoc of `critical()`. 83 | 84 | ### AArch64 85 | 86 | Most of code is same with AMD64, but it is the only one difference to use `AsmBuilder.AArch64` as builder instance. 87 | 88 | ```java 89 | var desc = FunctionDescriptor.of( 90 | ValueLayout.JAVA_INT, // return value 91 | ValueLayout.JAVA_INT // 1st argument 92 | ); 93 | 94 | var method = new AsmBuilder.AArch64(codeSegment, desc) 95 | /* stp x29, x30, [sp, #-16]! */ .stp(Register.X29, Register.X30, Register.SP, IndexClass.PreIndex, -16) 96 | /* mov x29, sp */ .mov(Register.X29, Register.SP) 97 | /* ldp x29, x30, [sp], #16 */ .ldp(Register.X29, Register.X30, Register.SP, IndexClass.PostIndex, 16) 98 | /* ret */ .ret(Optional.empty()) 99 | .build(); 100 | ``` 101 | 102 | ## 3. Method call 103 | 104 | ```java 105 | int ret = (int)method.invoke(100); // "ret" should be 100 106 | ``` 107 | 108 | # Debugging 109 | 110 | [ffmasm-disassembler](tools/disas) can disassemble the code in [MemorySegment](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/foreign/MemorySegment.html) like generated by ffmasm, and dump assembly code to stdout. 111 | 112 | You can download ffmasm-dissassembler Maven package from GitHub packages: https://github.com/YaSuenag/ffmasm/packages/2370043 113 | 114 | See [examples/disas](examples/disas) for details. 115 | 116 | ## Requirements 117 | 118 | ffmasm-disassembler requires [hsdis](https://github.com/openjdk/jdk/tree/master/src/utils/hsdis). 119 | 120 | ## Generate hsdis 121 | 122 | To generate hsdis for Linux, you can use [hsdis-builder](https://github.com/YaSuenag/hsdis-builder). 123 | 124 | ## Deploy hsdis 125 | 126 | It should be deployed one of following directory (it is documented as source comment in disassembler.cpp in HotSpot): 127 | 128 | 1. `$JAVA_HOME/lib//libhsdis-.so` 129 | 2. `$JAVA_HOME/lib//hsdis-.so` 130 | 3. `$JAVA_HOME/lib/hsdis-.so` 131 | 4. `hsdis-.so` (using `LD_LIBRARY_PATH`) 132 | 133 | If you don't want to deploy hsdis into your JDK, you can specify `hsdis` system property like `-Dhsdis=/path/to/hsdis-amd64.so` 134 | 135 | ## Examples 136 | 137 | ``` 138 | import com.yasuenag.ffmasmtools.disas.Disassembler; 139 | 140 | : 141 | 142 | MemorySegment rdtsc = createRDTSC(); // Generate machine code with ffmasm 143 | Disassembler.dumpToStdout(rdtsc); // Dump assembly code of `rdtsc` to stdout 144 | ``` 145 | 146 | # Play with JNI 147 | 148 | You can bind native method to `MemorySegment` of ffmasm code dynamically. 149 | 150 | You have to construct `MemorySegment` of the machine code with `AMD64AsmBuilder`, and you have to get it from `getMemorySegment()`. Then you can bind it via `NativeRegister`. 151 | 152 | Following example shows native method `test` is binded to the code made by ffmasm. Note that 1st argument in Java is located at arg3 in native function because this is native function (1st arg is `JNIEnv*`, and 2nd arg is `jobject` or `jclass`). 153 | 154 | ```java 155 | public native int test(int arg); 156 | 157 | 158 | 159 | try(var seg = new CodeSegment()){ 160 | var desc = FunctionDescriptor.of( 161 | ValueLayout.JAVA_INT, // return value 162 | ValueLayout.JAVA_INT, // 1st arg (JNIEnv *) 163 | ValueLayout.JAVA_INT, // 2nd arg (jobject) 164 | ValueLayout.JAVA_INT // 3rd arg (arg1 of caller) 165 | ); 166 | var stub = AsmBuilder.AMD64(seg, desc) 167 | /* push %rbp */ .push(Register.RBP) 168 | /* mov %rsp, %rbp */ .movRM(Register.RBP, Register.RSP, OptionalInt.empty()) 169 | /* mov %arg3, retReg */ .movMR(argReg.arg3(), argReg.returnReg(), OptionalInt.empty()) // arg1 in Java is arg3 in native 170 | /* leave */ .leave() 171 | /* ret */ .ret() 172 | .getMemorySegment(); 173 | 174 | var method = this.getClass() 175 | .getMethod("test", int.class); 176 | 177 | var methodMap = Map.of(method, stub); 178 | var register = NativeRegister.create(this.getClass()); 179 | register.registerNatives(methodMap); 180 | 181 | final int expected = 100; 182 | int actual = test(expected); 183 | Assertions.assertEquals(expected, actual); 184 | } 185 | ``` 186 | 187 | # Play with perf tool 188 | 189 | You can record both function name and entry point address as a perf map file. 190 | 191 | ## Record function 192 | 193 | You can pass function name into `build()` method: 194 | 195 | ```java 196 | .build("GeneratedFunc", Linker.Option.critical(true)); 197 | ``` 198 | 199 | Function name would be set to `` if you do not pass function name (includes calling `build(Linker.Option)`). 200 | 201 | ## Write to map file 202 | 203 | perf map file would be written at [shutdown hook](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/Runtime.html#addShutdownHook(java.lang.Thread)) when `CodeSegment` lives. All of functions in all of `CodeSegment`s which are lives would be dumped at the time of shutdown hook. 204 | 205 | You need to enable perf map dumper via `CodeSegment::enablePerfMapDumper`. Call `CodeSegment::disablePerfMapDumper` if you want to cancel the dumper. 206 | 207 | ## Generate jitdump 208 | 209 | perf tool on Linux supports JIT-generated code. ffmasm can dump generated code as a jitdump. See an [example](examples/perf) for details. 210 | 211 | ### Record assembled code as a JIT'ed code 212 | 213 | Pass [JitDump](https://yasuenag.github.io/ffmasm/com.yasuenag.ffmasm/com/yasuenag/ffmasm/JitDump.html) insntace to `build` method. 214 | 215 | ```java 216 | jitdump = JitDump.getInstance(Path.of(".")); 217 | 218 | : 219 | 220 | .build("GeneratedFunc", jitdump); 221 | ``` 222 | 223 | Then you can run `perf record`. Note that you have to set monotonic clock with `-k` option. 224 | 225 | ``` 226 | perf record -k 1 $JAVA_HOME/bin/java ... 227 | ``` 228 | 229 | As a result, you would get `jit-.dump` which includes JIT information. You should keep until run `perf inject`. 230 | 231 | ### Inject JIT'ed code into recording file 232 | 233 | `perf.data` generated by `perf record` would not include JIT'ed code, so you need to inject them via `perf inject` as following. 234 | 235 | ``` 236 | perf inject --jit -i perf.data -o perf.jit.data 237 | ``` 238 | 239 | You will get some `.so` file and `perf.jit.data` as an injected file as a result. 240 | 241 | ### Check with `perf report` 242 | 243 | ``` 244 | perf report -i perf.jit.data 245 | ``` 246 | 247 | # License 248 | 249 | The GNU Lesser General Public License, version 3.0 250 | -------------------------------------------------------------------------------- /benchmarks/funccall/Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | AS = as 3 | ASFLAGS = -g -c --noexecstack 4 | LDFLAGS = -Wl,-z,noexecstack 5 | 6 | TARGET = librdtsc.so 7 | OBJS = target/rdtsc.o 8 | SRCS = src/main/native/rdtsc.S 9 | 10 | all: $(TARGET) 11 | 12 | $(TARGET): $(OBJS) 13 | $(CC) -shared -o $@ $^ $(LDFLAGS) 14 | mv $(TARGET) target/$(TARGET) 15 | 16 | $(OBJS): $(SRCS) 17 | $(AS) $(ASFLAGS) $< -o $@ 18 | -------------------------------------------------------------------------------- /benchmarks/funccall/README.md: -------------------------------------------------------------------------------- 1 | Benchmark for calling function 2 | =================== 3 | 4 | [JMH](https://github.com/openjdk/jmh) benchmark to comparison between JNI and ffmasm. 5 | 6 | This benchmark runs RDTSC instruction. JNI function is written in assembly code in [rdtsc.S](src/main/native/rdtsc.S), and same code is assembled by ffmasm in [FFMComparison.java](src/main/java/com/yasuenag/ffmasm/benchmark/FFMComparison.java). So you can see calling performacne JNI and Foreign Function & Memory API. 7 | 8 | # Requirements 9 | 10 | * Java 22 11 | * Maven 12 | * GNU Make 13 | * GNU assembler 14 | 15 | # How to build 16 | 17 | ``` 18 | cd /path/to/ffasm 19 | mvn install 20 | cd benchmark/funccall 21 | mvn package 22 | ``` 23 | 24 | # Check result from RDTSC 25 | 26 | ``` 27 | mvn exec:exec@single-run 28 | ``` 29 | 30 | # Run benchmark 31 | 32 | ``` 33 | cd target 34 | $JAVA_HOME/bin/java -jar ffmasm-benchmark-funccall-1.0.3.jar 35 | ``` 36 | 37 | JIT log ( `-Xlog:jit+compilation=debug,jit+inlining=debug` ) would be collected into `target` dir with PID. 38 | 39 | # Run single benchmark 40 | 41 | You can use [measure-single-benchmark.sh](measure-single-benchmark.sh) to run single benchmark. 42 | Benchmark should be set from following list: 43 | 44 | * FFM 45 | * FFMCritical 46 | * RegisterNatives 47 | * JNI 48 | 49 | JIT log ( `-Xlog:jit+compilation=debug,jit+inlining=debug` ) would be collected into `target` dir with benchmark name. 50 | 51 | ## Measures iterations or execution time 52 | 53 | ``` 54 | ./measure-single-benchmark.sh run [benchmark] 55 | ``` 56 | 57 | ## Measure iterations or execution time with `perf` 58 | 59 | You have to install perf tool before running. 60 | 61 | ``` 62 | ./measure-single-benchmark.sh perf [benchmark] 63 | ``` 64 | 65 | `perf.data` would be stored into working directory. 66 | -------------------------------------------------------------------------------- /benchmarks/funccall/measure-single-benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MODE=$1 4 | BENCH=$2 5 | BASEDIR=$(dirname $0) 6 | 7 | EXECUTABLE=$JAVA_HOME/bin/java 8 | FUNCCALL_JAR=`ls $BASEDIR/target/original-ffmasm-benchmark-funccall-*.jar` 9 | FFMASM_JAR=`ls $HOME/.m2/repository/com/yasuenag/ffmasm/*-SNAPSHOT/ffmasm-*-SNAPSHOT.jar | tail -n 1` 10 | 11 | JVM_OPTS="-cp $FUNCCALL_JAR:$FFMASM_JAR -Djava.library.path=$BASEDIR/target --enable-native-access=ALL-UNNAMED -Xms4g -Xmx4g -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+AlwaysPreTouch -XX:+PreserveFramePointer -Xlog:jit+compilation=debug,jit+inlining=debug:file=$BASEDIR/target/$BENCH-jit.log::filesize=0" 12 | APP_OPTS="com.yasuenag.ffmasm.benchmark.funccall.FuncCallComparison iterate $BENCH" 13 | 14 | if [ "$MODE" = "perf" ]; then 15 | JVM_OPTS="$JVM_OPTS -XX:+UnlockDiagnosticVMOptions -XX:+DumpPerfMapAtExit" 16 | exec perf record -g -F 99 -- $EXECUTABLE $JVM_OPTS $APP_OPTS 17 | elif [ "$MODE" = "run" ]; then 18 | exec $EXECUTABLE $JVM_OPTS $APP_OPTS 19 | else 20 | echo 'Unknown mode' 21 | fi 22 | -------------------------------------------------------------------------------- /benchmarks/funccall/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 24 | 25 | 4.0.0 26 | 27 | com.yasuenag 28 | ffmasm-benchmark-funccall 29 | jar 30 | 1.0.3 31 | 32 | 33 | UTF-8 34 | 22 35 | 22 36 | 1.37 37 | 38 | 39 | 40 | 41 | org.openjdk.jmh 42 | jmh-core 43 | ${jmh.version} 44 | 45 | 46 | com.yasuenag 47 | ffmasm 48 | 0.7.0-SNAPSHOT 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-compiler-plugin 57 | 3.10.1 58 | 59 | 60 | -Xlint:all 61 | 62 | true 63 | 64 | 65 | org.openjdk.jmh 66 | jmh-generator-annprocess 67 | ${jmh.version} 68 | 69 | 70 | 71 | 72 | 73 | org.codehaus.mojo 74 | exec-maven-plugin 75 | 3.1.0 76 | 77 | 78 | make 79 | compile 80 | 81 | exec 82 | 83 | 84 | make 85 | 86 | 87 | 88 | single-run 89 | exec 90 | 91 | exec 92 | 93 | 94 | ${java.home}/bin/java 95 | ${project.build.directory} 96 | 97 | -classpath 98 | 99 | -Djava.library.path=. 100 | --enable-native-access=ALL-UNNAMED 101 | com.yasuenag.ffmasm.benchmark.funccall.FuncCallComparison 102 | single 103 | 104 | 105 | 106 | 107 | 108 | 109 | maven-shade-plugin 110 | 3.5.1 111 | 112 | false 113 | 114 | 115 | 116 | package 117 | 118 | shade 119 | 120 | 121 | 122 | 123 | org.openjdk.jmh.Main 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /benchmarks/funccall/src/main/java/com/yasuenag/ffmasm/benchmark/funccall/FuncCallComparison.java: -------------------------------------------------------------------------------- 1 | package com.yasuenag.ffmasm.benchmark.funccall; 2 | 3 | import java.lang.foreign.*; 4 | import java.lang.invoke.*; 5 | import java.lang.ref.*; 6 | import java.util.*; 7 | import java.util.concurrent.*; 8 | import java.util.concurrent.atomic.*; 9 | 10 | import com.yasuenag.ffmasm.*; 11 | import com.yasuenag.ffmasm.amd64.*; 12 | 13 | import org.openjdk.jmh.annotations.*; 14 | 15 | 16 | @State(Scope.Benchmark) 17 | @BenchmarkMode(Mode.Throughput) 18 | @Fork(value = 1, jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED", "-Djava.library.path=.", "-Xms4g", "-Xmx4g", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseEpsilonGC", "-XX:+AlwaysPreTouch", "-XX:+PreserveFramePointer", "-Xlog:jit+compilation=debug,jit+inlining=debug:file=jit%p.log::filesize=0"}) 19 | @Warmup(iterations = 1, time = 10, timeUnit = TimeUnit.SECONDS) 20 | @Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.SECONDS) 21 | public class FuncCallComparison{ 22 | 23 | private static final CodeSegment seg; 24 | 25 | private static final MethodHandle ffmRDTSC; 26 | private static final MethodHandle ffmRDTSCCritical; 27 | 28 | static{ 29 | System.loadLibrary("rdtsc"); 30 | 31 | try{ 32 | seg = new CodeSegment(); 33 | var action = new CodeSegment.CleanerAction(seg); 34 | Cleaner.create() 35 | .register(FuncCallComparison.class, action); 36 | 37 | var desc = FunctionDescriptor.of(ValueLayout.JAVA_LONG); 38 | var mem = new AsmBuilder.AMD64(seg, desc) 39 | /* push %rbp */ .push(Register.RBP) 40 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 41 | /* rdtsc */ .rdtsc() 42 | /* shl $32, %rdx */ .shl(Register.RDX, (byte)32, OptionalInt.empty()) 43 | /* or %rdx, %rax */ .orMR(Register.RDX, Register.RAX, OptionalInt.empty()) 44 | /* leave */ .leave() 45 | /* ret */ .ret() 46 | .getMemorySegment(); 47 | 48 | ffmRDTSC = Linker.nativeLinker().downcallHandle(mem, desc); 49 | ffmRDTSCCritical = Linker.nativeLinker().downcallHandle(mem, desc, Linker.Option.critical(false)); 50 | 51 | var register = NativeRegister.create(FuncCallComparison.class); 52 | register.registerNatives(Map.of(FuncCallComparison.class.getMethod("invokeFFMRDTSCRegisterNatives"), mem)); 53 | } 54 | catch(Throwable t){ 55 | throw new RuntimeException(t); 56 | } 57 | } 58 | 59 | @Benchmark 60 | public native long invokeJNI(); 61 | 62 | @Benchmark 63 | public long invokeFFMRDTSC(){ 64 | try{ 65 | return (long)ffmRDTSC.invokeExact(); 66 | } 67 | catch(Throwable t){ 68 | throw new RuntimeException(t); 69 | } 70 | } 71 | 72 | @Benchmark 73 | public long invokeFFMRDTSCCritical(){ 74 | try{ 75 | return (long)ffmRDTSCCritical.invokeExact(); 76 | } 77 | catch(Throwable t){ 78 | throw new RuntimeException(t); 79 | } 80 | } 81 | 82 | @Benchmark 83 | public native long invokeFFMRDTSCRegisterNatives(); 84 | 85 | public void singleRun(){ 86 | long nativeVal = invokeJNI(); 87 | long ffmVal = invokeFFMRDTSC(); 88 | long ffmCriticalVal = invokeFFMRDTSCCritical(); 89 | long ffmRegisterNativesVal = invokeFFMRDTSCRegisterNatives(); 90 | 91 | System.out.println(" JNI: " + nativeVal); 92 | System.out.println(" FFM: " + ffmVal); 93 | System.out.println(" FFM (Critical): " + ffmCriticalVal); 94 | System.out.println("FFM (RegisterNatives): " + ffmRegisterNativesVal); 95 | } 96 | 97 | public void iterate(String benchmark){ 98 | Runnable runner = switch(benchmark){ 99 | case "JNI" -> this::invokeJNI; 100 | case "FFM" -> this::invokeFFMRDTSC; 101 | case "FFMCritical" -> this::invokeFFMRDTSCCritical; 102 | case "RegisterNatives" -> this::invokeFFMRDTSCRegisterNatives; 103 | default -> throw new IllegalArgumentException("Unknown benchmark"); 104 | }; 105 | 106 | final AtomicLong counter = new AtomicLong(); 107 | 108 | var task = new TimerTask(){ 109 | @Override 110 | public void run(){ 111 | System.out.printf("Interrupted: counter = %d\n", counter.get()); 112 | System.exit(0); 113 | } 114 | }; 115 | var timer = new Timer("Benchmark Finisher"); 116 | timer.schedule(task, 30_000); 117 | 118 | long startTime = System.nanoTime(); 119 | while(true){ 120 | runner.run(); 121 | if(counter.incrementAndGet() == Long.MAX_VALUE){ 122 | timer.cancel(); 123 | long endTime = System.nanoTime(); 124 | long elapsedTime = endTime - startTime; 125 | System.out.printf("Finished: %d ns\n", elapsedTime); 126 | } 127 | } 128 | } 129 | 130 | public static void main(String[] args){ 131 | if(args.length < 1){ 132 | System.err.println("You should specify the mode."); 133 | System.exit(1); 134 | } 135 | String mode = args[0]; 136 | 137 | var inst = new FuncCallComparison(); 138 | 139 | if(mode.equals("single")){ 140 | inst.singleRun(); 141 | } 142 | else if(mode.equals("iterate")){ 143 | if(args.length != 2){ 144 | System.err.println("You should specify the benchmark."); 145 | System.exit(2); 146 | } 147 | inst.iterate(args[1]); 148 | } 149 | else{ 150 | System.err.println("Unknown mode."); 151 | System.exit(3); 152 | } 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /benchmarks/funccall/src/main/native/rdtsc.S: -------------------------------------------------------------------------------- 1 | .text 2 | 3 | # jlong Java_com_yasuenag_ffmasm_benchmark_funccall.FuncCallComparison_invokeJNI(JNIEnv *env) 4 | .global Java_com_yasuenag_ffmasm_benchmark_funccall_FuncCallComparison_invokeJNI 5 | .type Java_com_yasuenag_ffmasm_benchmark_funccall_FuncCallComparison_invokeJNI, @function 6 | Java_com_yasuenag_ffmasm_benchmark_funccall_FuncCallComparison_invokeJNI: 7 | push %rbp 8 | mov %rsp, %rbp 9 | rdtsc 10 | shl $32, %rdx 11 | or %rdx, %rax 12 | leave 13 | ret 14 | -------------------------------------------------------------------------------- /benchmarks/vectorapi/README.md: -------------------------------------------------------------------------------- 1 | Benchmark for vector operation 2 | =================== 3 | 4 | [JMH](https://github.com/openjdk/jmh) benchmark to comparison Pure Java code, [Vector API](https://openjdk.org/jeps/426), and ffmasm. 5 | 6 | This benchmark adds with 256bit packed int. Pure Java and Vector API are expected to get benefit from C2. 7 | 8 | # Requirements 9 | 10 | * Java 22 11 | * Maven 12 | 13 | # How to build 14 | 15 | ```sh 16 | $ cd /path/to/ffasm 17 | $ mvn install 18 | $ cd benchmark/vectorapi 19 | $ mvn package 20 | ``` 21 | 22 | # Run benchmark 23 | 24 | ```sh 25 | $ $JAVA_HOME/bin/java -jar target/ffmasm-benchmark-vectorapi-1.0.4.jar 26 | ``` 27 | -------------------------------------------------------------------------------- /benchmarks/vectorapi/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 24 | 25 | 4.0.0 26 | 27 | com.yasuenag 28 | ffmasm-benchmark-vectorapi 29 | jar 30 | 1.0.4 31 | 32 | 33 | UTF-8 34 | 22 35 | 22 36 | 1.37 37 | 38 | 39 | 40 | 41 | org.openjdk.jmh 42 | jmh-core 43 | ${jmh.version} 44 | 45 | 46 | com.yasuenag 47 | ffmasm 48 | 0.7.0-SNAPSHOT 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-compiler-plugin 57 | 3.10.1 58 | 59 | 60 | -Xlint:all 61 | --add-modules 62 | jdk.incubator.vector 63 | --add-exports 64 | jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED 65 | 66 | 67 | 68 | org.openjdk.jmh 69 | jmh-generator-annprocess 70 | ${jmh.version} 71 | 72 | 73 | 74 | 75 | 76 | maven-shade-plugin 77 | 3.4.0 78 | 79 | false 80 | 81 | 82 | 83 | package 84 | 85 | shade 86 | 87 | 88 | 89 | 90 | org.openjdk.jmh.Main 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /benchmarks/vectorapi/src/main/java/com/yasuenag/ffmasm/benchmark/vectorapi/VectorOpComparison.java: -------------------------------------------------------------------------------- 1 | package com.yasuenag.ffmasm.benchmark.vector; 2 | 3 | import java.lang.foreign.*; 4 | import java.lang.invoke.*; 5 | import java.lang.ref.*; 6 | import java.util.*; 7 | import java.util.concurrent.*; 8 | 9 | import jdk.incubator.vector.*; 10 | 11 | import com.yasuenag.ffmasm.*; 12 | import com.yasuenag.ffmasm.amd64.*; 13 | 14 | import org.openjdk.jmh.annotations.*; 15 | 16 | 17 | @State(Scope.Benchmark) 18 | @BenchmarkMode(Mode.Throughput) 19 | @Fork(value = 1, jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED", "--add-modules", "jdk.incubator.vector", "--add-exports", "jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED", "-Xms6g", "-Xmx6g"}) 20 | @Warmup(iterations = 1, time = 3, timeUnit = TimeUnit.SECONDS) 21 | @Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.SECONDS) 22 | public class VectorOpComparison{ 23 | 24 | private static int[] randArray; 25 | private static int[] result; 26 | 27 | private static final CodeSegment seg; 28 | private static final MethodHandle ffm; 29 | private static final MemorySegment srcSeg; 30 | private static final MemorySegment destSeg; 31 | private MemorySegment heapSrcSeg; 32 | private MemorySegment heapDestSeg; 33 | private static final MethodHandle ffmHeap; 34 | 35 | private IntVector vectorSrc; 36 | private IntVector vectorDest; 37 | 38 | static{ 39 | 40 | try{ 41 | seg = new CodeSegment(); 42 | var action = new CodeSegment.CleanerAction(seg); 43 | Cleaner.create() 44 | .register(VectorOpComparison.class, action); 45 | 46 | var desc = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.ADDRESS); 47 | var ffmSeg = new AsmBuilder.AVX(seg, desc) 48 | /* push %rbp */ .push(Register.RBP) 49 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 50 | /* vmovdqu (%rdi), %ymm0 */ .vmovdquRM(Register.YMM0, Register.RDI, OptionalInt.of(0)) 51 | /* vpaddd (%rsi), %ymm0, %ymm0 */ .vpaddd(Register.YMM0, Register.RSI, Register.YMM0, OptionalInt.of(0)) 52 | /* vmovdqu %ymm0, (%rdi) */ .vmovdquMR(Register.YMM0, Register.RDI, OptionalInt.of(0)) 53 | /* leave */ .leave() 54 | /* ret */ .ret() 55 | .getMemorySegment(); 56 | 57 | var linker = Linker.nativeLinker(); 58 | ffm = linker.downcallHandle(ffmSeg, desc); 59 | ffmHeap = linker.downcallHandle(ffmSeg, desc, Linker.Option.critical(true)); 60 | } 61 | catch(PlatformException | UnsupportedPlatformException e){ 62 | throw new RuntimeException(e); 63 | } 64 | 65 | var arena = Arena.ofAuto(); 66 | srcSeg = arena.allocate(32, 32); 67 | destSeg = arena.allocate(32, 32); 68 | } 69 | 70 | @Setup(Level.Iteration) 71 | public void paramSetup(){ 72 | randArray = (new Random()).ints() 73 | .limit(8) 74 | .toArray(); 75 | result = new int[]{0, 0, 0, 0, 0, 0, 0, 0}; 76 | 77 | MemorySegment.copy(randArray, 0, srcSeg, ValueLayout.JAVA_INT, 0, 8); 78 | MemorySegment.copy(result, 0, destSeg, ValueLayout.JAVA_INT, 0, 8); 79 | 80 | heapSrcSeg = MemorySegment.ofArray(randArray); 81 | heapDestSeg = MemorySegment.ofArray(result); 82 | 83 | vectorSrc = IntVector.fromArray(IntVector.SPECIES_256, randArray, 0); 84 | vectorDest = IntVector.fromArray(IntVector.SPECIES_256, result, 0); 85 | } 86 | 87 | @Benchmark 88 | public int[] invokeJava(){ 89 | for(int i = 0; i < 8; i++){ 90 | result[i] += randArray[i]; 91 | } 92 | return result; 93 | } 94 | 95 | @Benchmark 96 | public int[] invokeFFM(){ 97 | try{ 98 | ffm.invoke(destSeg, srcSeg); 99 | return destSeg.toArray(ValueLayout.JAVA_INT); 100 | } 101 | catch(Throwable t){ 102 | throw new RuntimeException(t); 103 | } 104 | } 105 | 106 | @Benchmark 107 | public int[] invokeFFMHeap(){ 108 | try{ 109 | ffmHeap.invoke(heapDestSeg, heapSrcSeg); 110 | return result; 111 | } 112 | catch(Throwable t){ 113 | throw new RuntimeException(t); 114 | } 115 | } 116 | 117 | @Benchmark 118 | public int[] invokeVector(){ 119 | vectorDest.add(vectorSrc).intoArray(result, 0); 120 | return result; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /examples/aarch64/README.md: -------------------------------------------------------------------------------- 1 | ffmasm examples - aarch64 2 | =================== 3 | 4 | This is an example of ffmasm on AArch64 Linux 5 | 6 | # Requirements 7 | 8 | * Java 22 9 | * AArch64 Linux 10 | * Maven 11 | 12 | # How to run 13 | 14 | ## 1. Install ffmasm 15 | 16 | ```bash 17 | $ mvn clean install 18 | ``` 19 | 20 | ## 2. Build 21 | 22 | ```bash 23 | $ cd examples/aarch64 24 | $ mvn clean package 25 | ``` 26 | 27 | ## 3. Run via Maven 28 | 29 | ``` 30 | $ mvn exec:java 31 | ``` 32 | 33 | ## 4. [EXTRA] check assembly in GDB 34 | 35 | Run GDB if you want to check assembled code. 36 | 37 | You can see PID and address of `CodeSegment` on the console when you run `java -jar aarch64-asm-0.1.0.jar --stop` in below. 38 | 39 | ``` 40 | PID: 1847 41 | Addr: 0xffffac5d8000 42 | Result: 100 43 | 44 | Press any key to exit... 45 | ``` 46 | 47 | Attach to JVM process (1847 in this case), and run `disas` on GDB. 48 | 49 | ``` 50 | $ gdb -p 1847 51 | 52 | : 53 | 54 | (gdb) disas 0xffffac5d8000, 0xffffac5d8010 55 | Dump of assembler code from 0xffffac5d8000 to 0xffffac5d8010: 56 | 0x0000ffffac5d8000: stp x29, x30, [sp, #-16]! 57 | 0x0000ffffac5d8004: mov x29, sp 58 | 0x0000ffffac5d8008: ldp x29, x30, [sp], #16 59 | 0x0000ffffac5d800c: ret 60 | End of assembler dump. 61 | ``` 62 | -------------------------------------------------------------------------------- /examples/aarch64/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 4.0.0 24 | 25 | com.yasuenag 26 | aarch64-asm 27 | 0.1.0 28 | jar 29 | 30 | aarch64-asm 31 | 32 | 33 | UTF-8 34 | com.yasuenag.ffmasm.examples.aarch64 35 | com.yasuenag.ffmasm.examples.aarch64.Main 36 | 22 37 | 22 38 | 39 | 40 | 41 | 42 | com.yasuenag 43 | ffmasm 44 | 0.7.0-SNAPSHOT 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.apache.maven.plugins 52 | maven-compiler-plugin 53 | 3.10.1 54 | 55 | 56 | -Xlint:all 57 | 58 | true 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-jar-plugin 64 | 3.4.2 65 | 66 | 67 | 68 | ALL-UNNAMED 69 | 70 | 71 | 72 | 73 | 74 | maven-shade-plugin 75 | 3.6.0 76 | 77 | false 78 | 79 | 80 | 81 | package 82 | 83 | shade 84 | 85 | 86 | 87 | 88 | ${mainClass} 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | org.codehaus.mojo 97 | exec-maven-plugin 98 | 3.5.0 99 | 100 | ${mainClass} 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /examples/aarch64/src/main/java/com/yasuenag/ffmasm/examples/aarch64/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.examples.aarch64; 20 | 21 | import java.lang.foreign.*; 22 | import java.util.*; 23 | 24 | import com.yasuenag.ffmasm.*; 25 | import com.yasuenag.ffmasm.aarch64.*; 26 | 27 | 28 | public class Main{ 29 | 30 | public static void main(String[] args) throws Throwable{ 31 | System.out.println("PID: " + ProcessHandle.current().pid()); 32 | 33 | var desc = FunctionDescriptor.of( 34 | ValueLayout.JAVA_INT, 35 | ValueLayout.JAVA_INT 36 | ); 37 | try(var codeSegment = new CodeSegment()){ 38 | System.out.println("Addr: 0x" + Long.toHexString(codeSegment.getAddr().address())); 39 | var func = new AsmBuilder.AArch64(codeSegment, desc) 40 | /* stp x29, x30, [sp, #-16]! */ .stp(Register.X29, Register.X30, Register.SP, IndexClass.PreIndex, -16) 41 | /* mov x29, sp */ .mov(Register.X29, Register.SP) 42 | /* ldp x29, x30, [sp], #16 */ .ldp(Register.X29, Register.X30, Register.SP, IndexClass.PostIndex, 16) 43 | /* ret */ .ret(Optional.empty()) 44 | .build(); 45 | int result = (int)func.invoke(100); 46 | System.out.println("Result: " + result); 47 | 48 | if((args.length > 0) && args[0].equals("--stop")){ 49 | System.out.println(); 50 | System.out.print("Press any key to exit..."); 51 | System.out.flush(); 52 | System.in.read(); 53 | } 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/aarch64/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | module com.yasuenag.ffmasm.examples.aarch64 { 20 | requires com.yasuenag.ffmasm; 21 | } 22 | -------------------------------------------------------------------------------- /examples/cpumodel/README.md: -------------------------------------------------------------------------------- 1 | ffmasm examples - cpumodel 2 | =================== 3 | 4 | This is an example of ffmasm to obtain CPU model from `CPUID` instruction in AMD64. 5 | 6 | # Requirements 7 | 8 | * Java 25 9 | * AMD64 Linux or Windows 10 | * Maven 11 | 12 | # How to run 13 | 14 | ## 1. Install ffmasm 15 | 16 | ```bash 17 | $ mvn clean install 18 | ``` 19 | 20 | ## 2. Build cpumodel 21 | 22 | ```bash 23 | $ cd examples/cpumodel 24 | $ mvn clean package 25 | ``` 26 | 27 | ## 3. Run via Maven 28 | 29 | ``` 30 | $ mvn exec:java 31 | ``` 32 | 33 | ## 4. [EXTRA] check assembly in GDB 34 | 35 | Run GDB if you want to check assembled code. 36 | 37 | You can see PID and address of `CodeSegment` on the console when you run `java -jar cpumodel-0.1.3.jar --stop` in below. 38 | 39 | ``` 40 | PID: 928 41 | Addr: 0x7f4c42704000 42 | Intel(R) Core(TM) i3-8145U CPU @ 2.10GHz 43 | ``` 44 | 45 | Attach to JVM process (928 in this case), and run `disas` on GDB. 46 | 47 | ``` 48 | $ gdb -p 928 49 | 50 | : 51 | 52 | (gdb) disas 0x7f4c42704000, 0x7f4c42704020 53 | Dump of assembler code from 0x7f4c42704000 to 0x7f4c42704020: 54 | 0x00007f4c42704000: push %rbp 55 | 0x00007f4c42704001: mov %rsp,%rbp 56 | 0x00007f4c42704004: push %rbx 57 | 0x00007f4c42704005: mov %rdi,%rax 58 | 0x00007f4c42704008: mov %rsi,%r11 59 | 0x00007f4c4270400b: cpuid 60 | 0x00007f4c4270400d: mov %eax,(%r11) 61 | 0x00007f4c42704010: mov %ebx,0x4(%r11) 62 | 0x00007f4c42704014: mov %ecx,0x8(%r11) 63 | 0x00007f4c42704018: mov %edx,0xc(%r11) 64 | 0x00007f4c4270401c: pop %rbx 65 | 0x00007f4c4270401e: leave 66 | 0x00007f4c4270401f: ret 67 | End of assembler dump. 68 | ``` 69 | 70 | # Calling convention 71 | 72 | Calling convention is different between Linux AMD64 and Windows x64. Linux AMD64 conform to [System V Application Binary Interface](https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf), on the other hand Windows conform to [here](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention). You have to conform to them when you assemble. 73 | 74 | # Expected assembly code in this example on Linux 75 | 76 | ```assembly 77 | # prologue 78 | push %rbp 79 | mov %rsp, %rbp 80 | 81 | # Evacuate callee-saved register 82 | push %rbx 83 | 84 | # Copy arguments to temporary registers 85 | mov %rdi,%rax 86 | mov %rsi,%r11 87 | 88 | # Call CPUID 89 | cpuid 90 | 91 | # Store result to given memory 92 | mov %eax, (%r11) 93 | mov %ebx, 4(%r11) 94 | mov %ecx, 8(%r11) 95 | mov %edx, 12(%r11) 96 | 97 | # Restore callee-saved register 98 | pop %rbx 99 | 100 | # Epilogue 101 | leave 102 | ret 103 | ``` 104 | 105 | # Play with AOT 106 | 107 | Java 24 introduced [JEP 483: Ahead-of-Time Class Loading & Linking](https://openjdk.org/jeps/483). It can cache InvokeDynamic link, so first call in FFM could be faster. 108 | 109 | [pom.xml](pom.xml) generates AOT cache in `package` phase, so you can feel acceralation with AOT as following. Note that you have to move `target` directory because AOT identifies class path. 110 | 111 | ``` 112 | $ cd target 113 | 114 | 115 | $ time $JAVA_HOME/bin/java -jar cpumodel-0.1.4.jar 116 | PID: 3147 117 | Addr: 0x7f8d1fc37000 118 | AMD Ryzen 3 3300X 4-Core Processor 119 | 120 | real 0m0.140s 121 | user 0m0.196s 122 | sys 0m0.049s 123 | 124 | 125 | $ time $JAVA_HOME/bin/java --enable-native-access=ALL-UNNAMED -XX:AOTCache=ffmasm-cpumodel.aot -jar cpumodel-0.1.4.jar 126 | PID: 3167 127 | Addr: 0x7f9000001000 128 | AMD Ryzen 3 3300X 4-Core Processor 129 | 130 | real 0m0.095s 131 | user 0m0.121s 132 | sys 0m0.041s 133 | ``` 134 | -------------------------------------------------------------------------------- /examples/cpumodel/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 4.0.0 24 | 25 | com.yasuenag 26 | cpumodel 27 | 0.1.4 28 | jar 29 | 30 | cpumodel 31 | 32 | 33 | UTF-8 34 | com.yasuenag.ffmasm.examples.cpumodel 35 | com.yasuenag.ffmasm.examples.cpumodel.Main 36 | 25 37 | 25 38 | 39 | 40 | 41 | 42 | com.yasuenag 43 | ffmasm 44 | 0.7.0-SNAPSHOT 45 | 46 | 47 | 48 | 49 | 50 | 51 | org.apache.maven.plugins 52 | maven-compiler-plugin 53 | 3.10.1 54 | 55 | 56 | -Xlint:all 57 | 58 | true 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-jar-plugin 64 | 3.4.2 65 | 66 | 67 | 68 | ALL-UNNAMED 69 | 70 | 71 | 72 | 73 | 74 | maven-shade-plugin 75 | 3.6.0 76 | 77 | false 78 | 79 | 80 | 81 | package 82 | 83 | shade 84 | 85 | 86 | 87 | 88 | ${mainClass} 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | org.codehaus.mojo 97 | exec-maven-plugin 98 | 3.5.0 99 | 100 | 101 | aot 102 | package 103 | 104 | exec 105 | 106 | 107 | ${project.build.directory} 108 | ${java.home}/bin/java 109 | 110 | --enable-native-access=ALL-UNNAMED 111 | -XX:AOTCacheOutput=ffmasm-cpumodel.aot 112 | -jar 113 | ${project.build.finalName}.jar 114 | 115 | 116 | 117 | 118 | 119 | 120 | org.codehaus.mojo 121 | exec-maven-plugin 122 | 3.5.0 123 | 124 | ${mainClass} 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /examples/cpumodel/src/main/java/com/yasuenag/ffmasm/examples/cpumodel/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.examples.cpumodel; 20 | 21 | import java.lang.foreign.*; 22 | import java.nio.charset.*; 23 | import java.util.*; 24 | 25 | import com.yasuenag.ffmasm.*; 26 | import com.yasuenag.ffmasm.amd64.*; 27 | 28 | 29 | public class Main{ 30 | 31 | public static void main(String[] args) throws Throwable{ 32 | System.out.println("PID: " + ProcessHandle.current().pid()); 33 | 34 | var mem = Arena.ofAuto().allocate(4 * 4 * 3); // 32bit * 4 regs (eax - edx) * 3 calls 35 | var desc = FunctionDescriptor.ofVoid( 36 | ValueLayout.JAVA_INT, // eax 37 | ValueLayout.ADDRESS // memory for eax - edx 38 | ); 39 | try(var codeSegment = new CodeSegment()){ 40 | System.out.println("Addr: 0x" + Long.toHexString(codeSegment.getAddr().address())); 41 | 42 | Register arg1, arg2; 43 | String osName = System.getProperty("os.name"); 44 | if(osName.equals("Linux")){ 45 | arg1 = Register.RDI; 46 | arg2 = Register.RSI; 47 | } 48 | else if(osName.startsWith("Windows")){ 49 | arg1 = Register.RCX; 50 | arg2 = Register.RDX; 51 | } 52 | else{ 53 | throw new RuntimeException("Unsupported OS: " + osName); 54 | } 55 | 56 | var cpuid = new AsmBuilder.AMD64(codeSegment, desc) 57 | /* push %rbp */ .push(Register.RBP) 58 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 59 | /* push %rbx */ .push(Register.RBX) 60 | /* mov , %rax */ .movMR(arg1, Register.RAX, OptionalInt.empty()) 61 | /* mov , %r11 */ .movMR(arg2, Register.R11, OptionalInt.empty()) 62 | /* cpuid */ .cpuid() 63 | /* mov %eax, (%r11) */ .movMR(Register.EAX, Register.R11, OptionalInt.of(0)) 64 | /* mov %ebx, 4(%r11) */ .movMR(Register.EBX, Register.R11, OptionalInt.of(4)) 65 | /* mov %ecx, 8(%r11) */ .movMR(Register.ECX, Register.R11, OptionalInt.of(8)) 66 | /* mov %edx, 12(%r11) */ .movMR(Register.EDX, Register.R11, OptionalInt.of(12)) 67 | /* pop %rbx */ .pop(Register.RBX, OptionalInt.empty()) 68 | /* leave */ .leave() 69 | /* ret */ .ret() 70 | .build(Linker.Option.critical(false)); 71 | 72 | cpuid.invoke(0x80000002, mem); 73 | cpuid.invoke(0x80000003, mem.asSlice(4 * 4)); 74 | cpuid.invoke(0x80000004, mem.asSlice(4 * 4 * 2)); 75 | 76 | String model = mem.getString(0L); 77 | System.out.println(model); 78 | 79 | if((args.length > 0) && args[0].equals("--stop")){ 80 | System.out.println(); 81 | System.out.print("Press any key to exit..."); 82 | System.out.flush(); 83 | System.in.read(); 84 | } 85 | } 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/cpumodel/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | module com.yasuenag.ffmasm.examples.cpumodel { 20 | requires com.yasuenag.ffmasm; 21 | } 22 | -------------------------------------------------------------------------------- /examples/disas/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 22 | 4.0.0 23 | 24 | com.yasuenag 25 | ffmasm-disas-example 26 | 0.1.0 27 | jar 28 | 29 | ffmasm-disas-example 30 | 31 | 32 | UTF-8 33 | com.yasuenag.ffmasm.examples.disas.Main 34 | ${project.artifactId}-${project.version} 35 | 22 36 | 22 37 | 38 | 39 | 40 | 41 | github 42 | https://maven.pkg.github.com/YaSuenag/ffmasm 43 | 44 | 45 | 46 | 47 | 48 | com.yasuenag 49 | ffmasm 50 | 0.6.0-SNAPSHOT 51 | 52 | 53 | com.yasuenag 54 | ffmasm-disassembler 55 | 0.1.1 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-compiler-plugin 64 | 3.13.0 65 | 66 | 67 | -Xlint:all 68 | 69 | true 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-jar-plugin 75 | 3.4.2 76 | 77 | 78 | 79 | ${mainClass} 80 | 81 | 82 | ALL-UNNAMED 83 | 84 | 85 | 86 | 87 | 88 | org.codehaus.mojo 89 | exec-maven-plugin 90 | 3.5.0 91 | 92 | ${mainClass} 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /examples/disas/src/main/java/com/yasuenag/ffmasm/examples/disas/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.examples.disas; 20 | 21 | import java.lang.foreign.*; 22 | import java.util.*; 23 | 24 | import com.yasuenag.ffmasm.*; 25 | import com.yasuenag.ffmasm.amd64.*; 26 | 27 | import com.yasuenag.ffmasmtools.disas.Disassembler; 28 | 29 | 30 | public class Main{ 31 | 32 | public static MemorySegment createRDTSC() throws Exception{ 33 | var seg = new CodeSegment(); 34 | return new AsmBuilder.AMD64(seg) 35 | /* push %rbp */ .push(Register.RBP) 36 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 37 | /* rdtsc */ .rdtsc() 38 | /* shl $32, %rdx */ .shl(Register.RDX, (byte)32, OptionalInt.empty()) 39 | /* or %rdx, %rax */ .orMR(Register.RDX, Register.RAX, OptionalInt.empty()) 40 | /* leave */ .leave() 41 | /* ret */ .ret() 42 | .getMemorySegment(); 43 | } 44 | 45 | public static void main(String[] args) throws Exception{ 46 | var rdtsc = createRDTSC(); 47 | Disassembler.dumpToStdout(rdtsc); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /examples/perf/README.md: -------------------------------------------------------------------------------- 1 | Example of profiling with perf tool 2 | =================== 3 | 4 | This example shows how to use `JitDump` to profile your assembly code generated by ffmasm. [PerfJit.java](src/main/java/com/yasuenag/ffmasm/examples/perf/PerfJit.java) issues `RDRAND` instruction in 10,000,000 times. 5 | 6 | # How to build 7 | 8 | ``` 9 | cd /path/to/ffasm 10 | mvn install 11 | cd examples/perf 12 | mvn package 13 | ``` 14 | 15 | # Run test 16 | 17 | [perf.sh](perf.sh) runs the example with `perf` and injects jitted code (it means assembly code generated by ffmasm) with `perf inject`. 18 | 19 | ``` 20 | ./perf.sh 21 | ``` 22 | 23 | You can see `jit-.dump`, `jitted-*.so`, and `perf.*data*` on working directory of `perf.sh`. Please keep them until you run `perf report`. 24 | 25 | # Profiling with `perf report` 26 | 27 | You need to specify `perf.jit.data` with `-i` option to load injected data. 28 | 29 | ``` 30 | perf report -i perf.jit.data 31 | ``` 32 | -------------------------------------------------------------------------------- /examples/perf/perf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BASEDIR=$(dirname $0) 4 | 5 | perf record -k 1 $JAVA_HOME/bin/java -jar $BASEDIR/target/perf-example-0.1.0.jar && \ 6 | perf inject --jit -i perf.data -o perf.jit.data && \ 7 | echo 'Completed. run "perf report -i perf.jit.data"' 8 | -------------------------------------------------------------------------------- /examples/perf/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 24 | 25 | 4.0.0 26 | 27 | com.yasuenag 28 | perf-example 29 | jar 30 | 0.1.0 31 | 32 | 33 | UTF-8 34 | 22 35 | 22 36 | 37 | 38 | 39 | 40 | com.yasuenag 41 | ffmasm 42 | 0.7.0-SNAPSHOT 43 | 44 | 45 | 46 | 47 | 48 | 49 | org.apache.maven.plugins 50 | maven-compiler-plugin 51 | 3.13.0 52 | 53 | 54 | -Xlint:all 55 | 56 | 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-jar-plugin 61 | 3.4.2 62 | 63 | 64 | 65 | ALL-UNNAMED 66 | 67 | 68 | 69 | 70 | 71 | maven-shade-plugin 72 | 3.6.0 73 | 74 | false 75 | 76 | 77 | 78 | package 79 | 80 | shade 81 | 82 | 83 | 84 | 85 | com.yasuenag.ffmasm.examples.perf.PerfJit 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /examples/perf/src/main/java/com/yasuenag/ffmasm/examples/perf/PerfJit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.examples.perf; 20 | 21 | import java.lang.foreign.*; 22 | import java.lang.invoke.*; 23 | import java.nio.file.*; 24 | import java.util.*; 25 | 26 | import com.yasuenag.ffmasm.*; 27 | import com.yasuenag.ffmasm.amd64.*; 28 | 29 | 30 | public class PerfJit{ 31 | 32 | private static final CodeSegment seg; 33 | 34 | private static final MethodHandle rdtsc; 35 | 36 | private static final JitDump jitdump; 37 | 38 | static{ 39 | try{ 40 | seg = new CodeSegment(); 41 | jitdump = JitDump.getInstance(Path.of(".")); 42 | var desc = FunctionDescriptor.of(ValueLayout.JAVA_LONG); 43 | rdtsc = new AsmBuilder.AMD64(seg, desc) 44 | /* .align 16 */ .alignTo16BytesWithNOP() 45 | /* retry: */ .label("retry") 46 | /* rdrand %rax */ .rdrand(Register.RAX) 47 | /* jae retry */ .jae("retry") 48 | /* ret */ .ret() 49 | .build("ffm_rdtsc", jitdump, Linker.Option.critical(false)); 50 | } 51 | catch(Throwable t){ 52 | throw new RuntimeException(t); 53 | } 54 | } 55 | 56 | public static void main(String[] args) throws Throwable{ 57 | try(jitdump; seg){ 58 | for(int i = 0; i < 10_000_000; i++){ 59 | long _ = (long)rdtsc.invokeExact(); 60 | } 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 4.0.0 24 | 25 | com.yasuenag 26 | ffmasm 27 | 0.7.0-SNAPSHOT 28 | jar 29 | 30 | ffmasm 31 | 32 | 33 | scm:git:git://github.com/YaSuenag/ffmasm.git 34 | scm:git:ssh://github.com:YaSuenag/ffmasm.git 35 | https://github.com/YaSuenag/ffmasm 36 | 37 | 38 | 39 | UTF-8 40 | 22 41 | 22 42 | 43 | 44 | 45 | 46 | org.junit.jupiter 47 | junit-jupiter-api 48 | 5.9.1 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-compiler-plugin 58 | 3.10.1 59 | 60 | 61 | -Xlint:all 62 | 63 | true 64 | 65 | 66 | 67 | org.apache.maven.plugins 68 | maven-javadoc-plugin 69 | 3.4.1 70 | 71 | 72 | org.apache.maven.plugins 73 | maven-surefire-plugin 74 | 3.5.2 75 | 76 | ${java.home}/bin/java 77 | --enable-native-access=ALL-UNNAMED 78 | --enable-native-access=com.yasuenag.ffmasm 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | github 87 | GitHub Packages 88 | https://maven.pkg.github.com/YaSuenag/ffmasm 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/AsmBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm; 20 | 21 | import java.lang.foreign.FunctionDescriptor; 22 | import java.lang.foreign.Linker; 23 | import java.lang.foreign.MemorySegment; 24 | import java.util.function.Consumer; 25 | import java.lang.invoke.MethodHandle; 26 | import java.nio.ByteBuffer; 27 | import java.nio.ByteOrder; 28 | import java.util.HashMap; 29 | import java.util.Map; 30 | import java.util.Set; 31 | 32 | import com.yasuenag.ffmasm.CodeSegment; 33 | import com.yasuenag.ffmasm.JitDump; 34 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 35 | import com.yasuenag.ffmasm.aarch64.AArch64AsmBuilder; 36 | import com.yasuenag.ffmasm.amd64.AMD64AsmBuilder; 37 | import com.yasuenag.ffmasm.amd64.AVXAsmBuilder; 38 | import com.yasuenag.ffmasm.amd64.SSEAsmBuilder; 39 | 40 | 41 | /** 42 | * Base class of assembly builder. 43 | * 44 | * @param Implementation of AsmBuilder 45 | * 46 | * @author Yasumasa Suenaga 47 | */ 48 | public class AsmBuilder{ 49 | 50 | /** 51 | * Builder class for AMD64 52 | */ 53 | public static final class AMD64 extends AMD64AsmBuilder{ 54 | 55 | public AMD64(CodeSegment seg) throws UnsupportedPlatformException{ 56 | this(seg, null); 57 | } 58 | 59 | public AMD64(CodeSegment seg, FunctionDescriptor desc) throws UnsupportedPlatformException{ 60 | super(seg, desc); 61 | } 62 | 63 | } 64 | 65 | /** 66 | * Builder class for SSE 67 | */ 68 | public static final class SSE extends SSEAsmBuilder{ 69 | 70 | public SSE(CodeSegment seg) throws UnsupportedPlatformException{ 71 | this(seg, null); 72 | } 73 | 74 | public SSE(CodeSegment seg, FunctionDescriptor desc) throws UnsupportedPlatformException{ 75 | super(seg, desc); 76 | } 77 | 78 | } 79 | 80 | /** 81 | * Builder class for AVX 82 | */ 83 | public static final class AVX extends AVXAsmBuilder{ 84 | 85 | public AVX(CodeSegment seg) throws UnsupportedPlatformException{ 86 | this(seg, null); 87 | } 88 | 89 | public AVX(CodeSegment seg, FunctionDescriptor desc) throws UnsupportedPlatformException{ 90 | super(seg, desc); 91 | } 92 | 93 | } 94 | 95 | /** 96 | * Builder class for AArch64 97 | */ 98 | public static final class AArch64 extends AArch64AsmBuilder{ 99 | 100 | public AArch64(CodeSegment seg) throws UnsupportedPlatformException{ 101 | this(seg, null); 102 | } 103 | 104 | public AArch64(CodeSegment seg, FunctionDescriptor desc) throws UnsupportedPlatformException{ 105 | super(seg, desc); 106 | } 107 | 108 | } 109 | 110 | private final CodeSegment seg; 111 | 112 | private final MemorySegment mem; 113 | 114 | /** 115 | * ByteBuffer which includes code content. 116 | */ 117 | protected final ByteBuffer byteBuf; 118 | 119 | private final FunctionDescriptor desc; 120 | 121 | // Key: label, Value: position 122 | protected final Map labelMap; 123 | 124 | // Key: label, Value: jump data 125 | public static record PendingJump(Consumer emitOp, int position){} 126 | protected final Map> pendingLabelMap; 127 | 128 | protected AsmBuilder(CodeSegment seg, FunctionDescriptor desc){ 129 | seg.alignTo16Bytes(); 130 | 131 | this.seg = seg; 132 | this.mem = seg.getTailOfMemorySegment(); 133 | this.byteBuf = mem.asByteBuffer().order(ByteOrder.nativeOrder()); 134 | this.desc = desc; 135 | this.labelMap = new HashMap<>(); 136 | this.pendingLabelMap = new HashMap<>(); 137 | } 138 | 139 | /** 140 | * Cast "this" to "T" without unchecked warning. 141 | * 142 | * @return "this" casted to "T" 143 | */ 144 | @SuppressWarnings("unchecked") 145 | protected T castToT(){ 146 | return (T)this; 147 | } 148 | 149 | private void updateTail(){ 150 | if(!pendingLabelMap.isEmpty()){ 151 | throw new IllegalStateException("Label is not defined: " + pendingLabelMap.keySet().toString()); 152 | } 153 | seg.incTail(byteBuf.position()); 154 | } 155 | 156 | /** 157 | * Build as a MethodHandle 158 | * 159 | * @param options Linker options to pass to downcallHandle(). 160 | * @return MethodHandle for this assembly 161 | * @throws IllegalStateException when label(s) are not defined even if they are used 162 | */ 163 | public MethodHandle build(Linker.Option... options){ 164 | return build("", options); 165 | } 166 | 167 | /** 168 | * Build as a MethodHandle 169 | * 170 | * @param name Method name 171 | * @param options Linker options to pass to downcallHandle(). 172 | * @return MethodHandle for this assembly 173 | * @throws IllegalStateException when label(s) are not defined even if they are used 174 | */ 175 | public MethodHandle build(String name, Linker.Option... options){ 176 | return build(name, null, options); 177 | } 178 | 179 | private void storeMethodInfo(String name, JitDump jitdump){ 180 | var top = mem.address(); 181 | var size = byteBuf.position(); 182 | var methodInfo = seg.addMethodInfo(name, top, size); 183 | if(jitdump != null){ 184 | jitdump.writeFunction(methodInfo); 185 | } 186 | } 187 | 188 | /** 189 | * Build as a MethodHandle 190 | * 191 | * @param name Method name 192 | * @param jitdump JitDump instance which should be written. 193 | * @param options Linker options to pass to downcallHandle(). 194 | * @return MethodHandle for this assembly 195 | * @throws IllegalStateException when label(s) are not defined even if they are used 196 | */ 197 | public MethodHandle build(String name, JitDump jitdump, Linker.Option... options){ 198 | updateTail(); 199 | storeMethodInfo(name, jitdump); 200 | return Linker.nativeLinker().downcallHandle(mem, desc, options); 201 | } 202 | 203 | /** 204 | * Get MemorySegment which is associated with this builder. 205 | * 206 | * @return MemorySegment of this builder 207 | * @throws IllegalStateException when label(s) are not defined even if they are used 208 | */ 209 | public MemorySegment getMemorySegment(){ 210 | return getMemorySegment(""); 211 | } 212 | 213 | /** 214 | * Get MemorySegment which is associated with this builder. 215 | * 216 | * @param name Method name 217 | * @return MemorySegment of this builder 218 | * @throws IllegalStateException when label(s) are not defined even if they are used 219 | */ 220 | public MemorySegment getMemorySegment(String name){ 221 | return getMemorySegment(name, null); 222 | } 223 | 224 | /** 225 | * Get MemorySegment which is associated with this builder. 226 | * 227 | * @param name Method name 228 | * @param jitdump JitDump instance which should be written. 229 | * @return MemorySegment of this builder 230 | * @throws IllegalStateException when label(s) are not defined even if they are used 231 | */ 232 | public MemorySegment getMemorySegment(String name, JitDump jitdump){ 233 | updateTail(); 234 | storeMethodInfo(name, jitdump); 235 | long length = byteBuf.position(); 236 | return mem.reinterpret(length); 237 | } 238 | 239 | } 240 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/CodeSegment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm; 20 | 21 | import java.io.IOException; 22 | import java.io.PrintWriter; 23 | import java.io.UncheckedIOException; 24 | import java.lang.foreign.MemorySegment; 25 | import java.lang.invoke.MethodHandle; 26 | import java.nio.file.Files; 27 | import java.nio.file.Path; 28 | import java.util.Comparator; 29 | import java.util.Set; 30 | import java.util.HashSet; 31 | 32 | import com.yasuenag.ffmasm.internal.ExecMemory; 33 | import com.yasuenag.ffmasm.internal.linux.LinuxExecMemory; 34 | import com.yasuenag.ffmasm.internal.windows.WindowsExecMemory; 35 | 36 | 37 | /** 38 | * Memory segment for executables. 39 | * 40 | * @author Yasumasa Suenaga 41 | */ 42 | public class CodeSegment implements AutoCloseable{ 43 | 44 | /** 45 | * Default size of code segment. 46 | */ 47 | public static final long DEFAULT_CODE_SEGMENT_SIZE = 4096L; 48 | 49 | /** 50 | * Holder for method information. This is used for perfmap dumping. 51 | * 52 | * @param name method name 53 | * @param address start address of the method 54 | * @param size method size 55 | */ 56 | public static record MethodInfo(String name, long address, int size){ 57 | @Override 58 | public String toString(){ 59 | return String.format("0x%x 0x%x %s", address, size, name); 60 | } 61 | } 62 | 63 | private final ExecMemory mem; 64 | 65 | private final MemorySegment addr; 66 | 67 | private final long size; 68 | 69 | private final Set methods; 70 | 71 | private long tail; 72 | 73 | private Thread perfMapDumper; 74 | 75 | /** 76 | * Allocate memory for this code segment with default size (4096 bytes). 77 | * @throws PlatformException thrown when native function call failed. 78 | * @throws UnsupportedPlatformException thrown when the platform is not supported. 79 | */ 80 | public CodeSegment() throws PlatformException, UnsupportedPlatformException{ 81 | this(DEFAULT_CODE_SEGMENT_SIZE); 82 | } 83 | 84 | /** 85 | * Allocate memory for this code segment. 86 | * @param size size of code segment. 87 | * @throws PlatformException thrown when native function call failed. 88 | * @throws UnsupportedPlatformException thrown when the platform is not supported. 89 | */ 90 | public CodeSegment(long size) throws PlatformException, UnsupportedPlatformException{ 91 | String osName = System.getProperty("os.name"); 92 | if(osName.equals("Linux")){ 93 | mem = new LinuxExecMemory(); 94 | } 95 | else if(osName.startsWith("Windows")){ 96 | mem = new WindowsExecMemory(); 97 | } 98 | else{ 99 | throw new UnsupportedPlatformException(osName + " is unsupported."); 100 | } 101 | 102 | this.size = size; 103 | this.addr = mem.allocate(size); 104 | this.methods = new HashSet<>(); 105 | this.tail = 0L; 106 | this.perfMapDumper = null; 107 | } 108 | 109 | /** 110 | * Release memory for this code segment. 111 | */ 112 | @Override 113 | public void close() throws Exception{ 114 | if(perfMapDumper != null){ 115 | disablePerfMapDumper(); 116 | } 117 | mem.deallocate(addr, size); 118 | } 119 | 120 | /** 121 | * Class to register calling close() as Cleaner action. 122 | */ 123 | public static class CleanerAction implements Runnable{ 124 | 125 | private final CodeSegment seg; 126 | 127 | /** 128 | * @param seg CodeSegment instance to close. 129 | */ 130 | public CleanerAction(CodeSegment seg){ 131 | this.seg = seg; 132 | } 133 | 134 | /** 135 | * Close associated CodeSegment. 136 | * All of exceptions are ignored during close() operation. 137 | */ 138 | @Override 139 | public void run(){ 140 | try{ 141 | seg.close(); 142 | } 143 | catch(Exception e){ 144 | // ignore 145 | } 146 | } 147 | 148 | } 149 | 150 | /** 151 | * Get slice of this segment from the tail. 152 | * 153 | * @return Slice of this segment from the tail. 154 | */ 155 | public MemorySegment getTailOfMemorySegment(){ 156 | return addr.asSlice(tail); 157 | } 158 | 159 | /** 160 | * Align the tail to 16 bytes 161 | */ 162 | public void alignTo16Bytes(){ 163 | if((tail & 0xf) > 0){ // not aligned 164 | tail = (tail + 0x10) & 0xfffffffffffffff0L; 165 | } 166 | } 167 | 168 | /** 169 | * Get the tail of this segment. 170 | * 171 | * @return the tail of this segment. 172 | */ 173 | public long getTail(){ 174 | return tail; 175 | } 176 | 177 | /** 178 | * Increment the tail with given size. 179 | * @param size value to increment 180 | */ 181 | public void incTail(long size){ 182 | this.tail += size; 183 | } 184 | 185 | /** 186 | * Get MemorySegment which relates to this segment. 187 | * 188 | * @return MemorySegment of this segment. 189 | */ 190 | public MemorySegment getAddr(){ 191 | return addr; 192 | } 193 | 194 | /** 195 | * Add method info. It will be dumped to perf map as related method of this CodeSegment. 196 | * @param name Method name 197 | * @param address Address of the method 198 | * @param size Size of the method (machine code) 199 | * @return MethodInfo of the method info. 200 | * @throws IllegalArgumentException if the address is out of range from this CodeSegment. 201 | */ 202 | public MethodInfo addMethodInfo(String name, long address, int size){ 203 | if((address < addr.address()) || ((addr.address() + this.size) < (address + size))){ 204 | throw new IllegalArgumentException("Address is out of range from CodeSegment."); 205 | } 206 | var methodInfo = new MethodInfo(name, address, size); 207 | methods.add(methodInfo); 208 | return methodInfo; 209 | } 210 | 211 | private void dumpPerfMap(Path path){ 212 | try(var writer = new PrintWriter(Files.newOutputStream(path))){ 213 | methods.stream() 214 | .sorted(Comparator.comparing(MethodInfo::address)) 215 | .map(MethodInfo::toString) 216 | .forEachOrdered(writer::println); 217 | } 218 | catch(IOException e){ 219 | throw new UncheckedIOException(e); 220 | } 221 | } 222 | 223 | /** 224 | * Enable perf map dumper at shutdown hook for dumping all of functions in this CodeSegment. 225 | * @param path Path to dumpfile 226 | */ 227 | public synchronized void enablePerfMapDumper(Path path){ 228 | perfMapDumper = new Thread(() -> dumpPerfMap(path)); 229 | Runtime.getRuntime().addShutdownHook(perfMapDumper); 230 | } 231 | 232 | /** 233 | * Disable perf map dumper 234 | * @throws IllegalStateException if enablePerfMapDumper() is not called before. 235 | */ 236 | public synchronized void disablePerfMapDumper(){ 237 | if(perfMapDumper == null){ 238 | throw new IllegalStateException("PerfMapDumper is not enabled."); 239 | } 240 | Runtime.getRuntime().removeShutdownHook(perfMapDumper); 241 | perfMapDumper = null; 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/JitDump.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm; 20 | 21 | import java.io.IOException; 22 | import java.nio.file.Path; 23 | 24 | import com.yasuenag.ffmasm.internal.linux.PerfJitDump; 25 | 26 | 27 | /** 28 | * Interface of jitdump for perf command on Linux. 29 | * 30 | * @author Yasumasa Suenaga 31 | */ 32 | public interface JitDump extends AutoCloseable{ 33 | 34 | /** 35 | * Get instance of JitDump. 36 | * 37 | * @param dir Base directory which jitdump is generated. 38 | * @throws UnsupportedPlatformException if the call happens on unsupported platform. 39 | */ 40 | public static JitDump getInstance(Path dir) throws UnsupportedPlatformException, PlatformException, IOException{ 41 | var osName = System.getProperty("os.name"); 42 | if(osName.equals("Linux")){ 43 | return new PerfJitDump(dir); 44 | } 45 | 46 | throw new UnsupportedPlatformException("This platform is not supported in JitDump"); 47 | } 48 | 49 | /** 50 | * Write method info to jitdump. 51 | * 52 | * @param method MethodInfo should be written. 53 | */ 54 | public void writeFunction(CodeSegment.MethodInfo method); 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/PlatformException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm; 20 | 21 | 22 | /** 23 | * Thrown when platform functions fail. 24 | * 25 | * @author Yasumasa Suenaga 26 | */ 27 | public class PlatformException extends Exception{ 28 | 29 | private final int errcode; 30 | 31 | /** 32 | * Constructs a new exception with the caused Throwable. 33 | * The cause is not initialized. 34 | * @param cause caused Throwable 35 | */ 36 | public PlatformException(Throwable cause){ 37 | super(cause); 38 | this.errcode = 0; 39 | } 40 | 41 | /** 42 | * Constructs a new exception with the specified detail message and error code from platform. 43 | * The cause is not initialized. 44 | * @param message exception message 45 | * @param errcode error code from platform 46 | */ 47 | public PlatformException(String message, int errcode){ 48 | super(message); 49 | this.errcode = errcode; 50 | } 51 | 52 | /** 53 | * Returns error code which relates to this exception. 54 | * 0 means OS did not return any error (e.g. errcode = 0) - it means some error happen 55 | * in foreign function call in Java. 56 | * 57 | * @return error code which relates to this exception. 58 | */ 59 | public int getErrCode(){ 60 | return errcode; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/UnsupportedPlatformException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm; 20 | 21 | 22 | /** 23 | * Thrown when the platform is unsupported. 24 | * 25 | * @author Yasumasa Suenaga 26 | */ 27 | public class UnsupportedPlatformException extends Exception{ 28 | 29 | /** 30 | * Constructs a new exception with the message. 31 | * @param message exception message 32 | */ 33 | public UnsupportedPlatformException(String message){ 34 | super(message); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/aarch64/HWShift.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.aarch64; 20 | 21 | 22 | /** 23 | * Enum for AArch64 shift value for hw field 24 | * 25 | * @author Yasumasa Suenaga 26 | */ 27 | public enum HWShift{ 28 | None, // 0/16 29 | HW_16, // 16/16 30 | HW_32, // 32/16 31 | HW_48 // 48/16 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/aarch64/IndexClass.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.aarch64; 20 | 21 | 22 | /** 23 | * Enum for AArch64 Index Class 24 | * 25 | * @author Yasumasa Suenaga 26 | */ 27 | public enum IndexClass{ 28 | PostIndex, 29 | PreIndex, 30 | SignedOffset, 31 | UnsignedOffset 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/aarch64/Register.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.aarch64; 20 | 21 | 22 | /** 23 | * Enum for AArch64 CPU register 24 | * 25 | * @author Yasumasa Suenaga 26 | */ 27 | public enum Register{ 28 | 29 | X0(0, 64), 30 | X1(1, 64), 31 | X2(2, 64), 32 | X3(3, 64), 33 | X4(4, 64), 34 | X5(5, 64), 35 | X6(6, 64), 36 | X7(7, 64), 37 | X8(8, 64), 38 | X9(9, 64), 39 | X10(10, 64), 40 | X11(11, 64), 41 | X12(12, 64), 42 | X13(13, 64), 43 | X14(14, 64), 44 | X15(15, 64), 45 | X16(16, 64), 46 | X17(17, 64), 47 | X18(18, 64), 48 | X19(19, 64), 49 | X20(20, 64), 50 | X21(21, 64), 51 | X22(22, 64), 52 | X23(23, 64), 53 | X24(24, 64), 54 | X25(25, 64), 55 | X26(26, 64), 56 | X27(27, 64), 57 | X28(28, 64), 58 | X29(29, 64), 59 | X30(30, 64), 60 | SP(-1, 64); 61 | 62 | private final int encoding; 63 | 64 | private final int width; 65 | 66 | private Register(int encoding, int width){ 67 | this.encoding = encoding & 0b11111; 68 | this.width = width; 69 | } 70 | 71 | /** 72 | * Register encoding 73 | * @return Register encoding 74 | */ 75 | public int encoding(){ 76 | return encoding; 77 | } 78 | 79 | /** 80 | * Register width in bits 81 | * @return Register width 82 | */ 83 | public int width(){ 84 | return width; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/amd64/Register.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.amd64; 20 | 21 | 22 | /** 23 | * Enum for AMD64 CPU register 24 | * 25 | * @author Yasumasa Suenaga 26 | */ 27 | public enum Register{ 28 | 29 | AL(0, 8), 30 | CL(1, 8), 31 | DL(2, 8), 32 | BL(3, 8), 33 | AH(4, 8), 34 | CH(5, 8), 35 | DH(6, 8), 36 | BH(7, 8), 37 | 38 | AX(0, 16), 39 | CX(1, 16), 40 | DX(2, 16), 41 | BX(3, 16), 42 | SP(4, 16), 43 | BP(5, 16), 44 | SI(6, 16), 45 | DI(7, 16), 46 | 47 | EAX(0, 32), 48 | ECX(1, 32), 49 | EDX(2, 32), 50 | EBX(3, 32), 51 | ESP(4, 32), 52 | EBP(5, 32), 53 | ESI(6, 32), 54 | EDI(7, 32), 55 | R8D(8, 32), 56 | R9D(9, 32), 57 | R10D(10, 32), 58 | R11D(11, 32), 59 | R12D(12, 32), 60 | R13D(13, 32), 61 | R14D(14, 32), 62 | R15D(15, 32), 63 | 64 | RAX(0, 64), 65 | RCX(1, 64), 66 | RDX(2, 64), 67 | RBX(3, 64), 68 | RSP(4, 64), 69 | RBP(5, 64), 70 | RSI(6, 64), 71 | RDI(7, 64), 72 | R8(8, 64), 73 | R9(9, 64), 74 | R10(10, 64), 75 | R11(11, 64), 76 | R12(12, 64), 77 | R13(13, 64), 78 | R14(14, 64), 79 | R15(15, 64), 80 | 81 | XMM0(0, 128), 82 | XMM1(1, 128), 83 | XMM2(2, 128), 84 | XMM3(3, 128), 85 | XMM4(4, 128), 86 | XMM5(5, 128), 87 | XMM6(6, 128), 88 | XMM7(7, 128), 89 | XMM8(8, 128), 90 | XMM9(9, 128), 91 | XMM10(10, 128), 92 | XMM11(11, 128), 93 | XMM12(12, 128), 94 | XMM13(13, 128), 95 | XMM14(14, 128), 96 | XMM15(15, 128), 97 | 98 | YMM0(0, 256), 99 | YMM1(1, 256), 100 | YMM2(2, 256), 101 | YMM3(3, 256), 102 | YMM4(4, 256), 103 | YMM5(5, 256), 104 | YMM6(6, 256), 105 | YMM7(7, 256), 106 | YMM8(8, 256), 107 | YMM9(9, 256), 108 | YMM10(10, 256), 109 | YMM11(11, 256), 110 | YMM12(12, 256), 111 | YMM13(13, 256), 112 | YMM14(14, 256), 113 | YMM15(15, 256); 114 | 115 | private final int encoding; 116 | 117 | private final int width; 118 | 119 | private Register(int encoding, int width){ 120 | this.encoding = encoding; 121 | this.width = width; 122 | } 123 | 124 | /** 125 | * Register encoding 126 | * @return Register encoding 127 | */ 128 | public int encoding(){ 129 | return encoding; 130 | } 131 | 132 | /** 133 | * Register width in bits 134 | * @return Register width 135 | */ 136 | public int width(){ 137 | return width; 138 | } 139 | 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/amd64/SSEAsmBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.amd64; 20 | 21 | import java.lang.foreign.FunctionDescriptor; 22 | import java.util.OptionalInt; 23 | 24 | import com.yasuenag.ffmasm.CodeSegment; 25 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 26 | import com.yasuenag.ffmasm.amd64.Register; 27 | 28 | 29 | /** 30 | * Builder for SSE hand-assembling 31 | * 32 | * @author Yasumasa Suenaga 33 | */ 34 | public class SSEAsmBuilder> extends AMD64AsmBuilder{ 35 | 36 | /** 37 | * Constructor. 38 | * 39 | * @param seg CodeSegment which is used by this builder. 40 | * @param desc FunctionDescriptor for this builder. It will be used by build(). 41 | */ 42 | protected SSEAsmBuilder(CodeSegment seg, FunctionDescriptor desc) throws UnsupportedPlatformException{ 43 | super(seg, desc); 44 | } 45 | 46 | private T movdq(Register r, Register m, OptionalInt disp, byte prefix, byte secondOpcode){ 47 | byteBuf.put(prefix); 48 | emitREXOp(r, m); 49 | byteBuf.put((byte)0x0f); // escape opcode 50 | byteBuf.put(secondOpcode); 51 | var mode = emitModRM(r, m, disp); 52 | emitDisp(mode, disp, m); 53 | 54 | return castToT(); 55 | } 56 | 57 | /** 58 | * Move aligned packed integer values from xmm2/mem to xmm1. 59 | * Opcode: 66 0F 6F /r 60 | * Instruction: MOVDQA xmm1, xmm2/m128 61 | * Op/En: A 62 | * 63 | * @param r "r" register 64 | * @param m "r/m" register 65 | * @param disp Displacement. Set "empty" if this operation is reg-reg 66 | * then "r/m" have to be a SIMD register. 67 | * Otherwise it has to be 64 bit GPR because it have to be * a memory operand. 68 | * @return This instance 69 | */ 70 | public T movdqaRM(Register r, Register m, OptionalInt disp){ 71 | return movdq(r, m, disp, (byte)0x66, (byte)0x6f); 72 | } 73 | 74 | /** 75 | * Move aligned packed integer values from xmm1 to xmm2/mem. 76 | * Opcode: 66 0F 7F /r 77 | * Instruction: MOVDQA xmm2/m128, xmm1 78 | * Op/En: B 79 | * 80 | * @param r "r" register 81 | * @param m "r/m" register 82 | * @param disp Displacement. Set "empty" if this operation is reg-reg 83 | * then "r/m" have to be a SIMD register. 84 | * Otherwise it has to be 64 bit GPR because it have to be * a memory operand. 85 | * @return This instance 86 | */ 87 | public T movdqaMR(Register r, Register m, OptionalInt disp){ 88 | return movdq(r, m, disp, (byte)0x66, (byte)0x7f); 89 | } 90 | 91 | /** 92 | * Move unaligned packed integer values from xmm2/mem128 to xmm1. 93 | * Opcode: F3 0F 6F /r 94 | * Instruction: MOVDQU xmm1, xmm2/m128 95 | * Op/En: A 96 | * 97 | * @param r "r" register 98 | * @param m "r/m" register 99 | * @param disp Displacement. Set "empty" if this operation is reg-reg 100 | * then "r/m" have to be a SIMD register. 101 | * Otherwise it has to be 64 bit GPR because it have to be * a memory operand. 102 | * @return This instance 103 | */ 104 | public T movdquRM(Register r, Register m, OptionalInt disp){ 105 | return movdq(r, m, disp, (byte)0xf3, (byte)0x6f); 106 | } 107 | 108 | /** 109 | * Move unaligned packed integer values from xmm1 to xmm2/mem128. 110 | * Opcode: F3 0F 7F /r 111 | * Instruction: MOVDQU xmm2/m128, xmm1 112 | * Op/En: B 113 | * 114 | * @param r "r" register 115 | * @param m "r/m" register 116 | * @param disp Displacement. Set "empty" if this operation is reg-reg 117 | * then "r/m" have to be a SIMD register. 118 | * Otherwise it has to be 64 bit GPR because it have to be * a memory operand. 119 | * @return This instance 120 | */ 121 | public T movdquMR(Register r, Register m, OptionalInt disp){ 122 | return movdq(r, m, disp, (byte)0xf3, (byte)0x7f); 123 | } 124 | 125 | private T movDInternal(Register r, Register m, OptionalInt disp, byte secondOpcode){ 126 | return movDorQ(r, m, disp, secondOpcode, false); 127 | } 128 | 129 | private T movQInternal(Register r, Register m, OptionalInt disp, byte secondOpcode){ 130 | return movDorQ(r, m, disp, secondOpcode, true); 131 | } 132 | 133 | private T movDorQ(Register r, Register m, OptionalInt disp, byte secondOpcode, boolean isQWORD){ 134 | byteBuf.put((byte)0x66); // prefix 135 | emitREXOp(r, m, isQWORD); 136 | byteBuf.put((byte)0x0f); // escape opcode 137 | byteBuf.put(secondOpcode); 138 | var mode = emitModRM(r, m, disp); 139 | emitDisp(mode, disp, m); 140 | 141 | return castToT(); 142 | } 143 | 144 | /** 145 | * Move doubleword from r/m32 to xmm. 146 | * Opcode: 66 0F 6E /r 147 | * Instruction: MOVD xmm, r/m32 148 | * Op/En: A 149 | * 150 | * @param r "r" register 151 | * @param m "r/m" register 152 | * @param disp Displacement. Set "empty" if this operation is reg-reg. 153 | * @return This instance 154 | */ 155 | public T movdRM(Register r, Register m, OptionalInt disp){ 156 | return movDInternal(r, m, disp, (byte)0x6e); 157 | } 158 | 159 | /** 160 | * Move doubleword from xmm register to r/m32. 161 | * Opcode: 66 0F 7E /r 162 | * Instruction: MOVD r/m32, xmm 163 | * Op/En: B 164 | * 165 | * @param r "r" register 166 | * @param m "r/m" register 167 | * @param disp Displacement. Set "empty" if this operation is reg-reg. 168 | * @return This instance 169 | */ 170 | public T movdMR(Register r, Register m, OptionalInt disp){ 171 | return movDInternal(r, m, disp, (byte)0x7e); 172 | } 173 | 174 | /** 175 | * Move quadword from r/m64 to xmm. 176 | * Opcode: 66 REX.W 0F 6E /r 177 | * Instruction: MOVQ xmm, r/m64 178 | * Op/En: A 179 | * 180 | * @param r "r" register 181 | * @param m "r/m" register 182 | * @param disp Displacement. Set "empty" if this operation is reg-reg. 183 | * @return This instance 184 | */ 185 | public T movqRM(Register r, Register m, OptionalInt disp){ 186 | return movQInternal(r, m, disp, (byte)0x6e); 187 | } 188 | 189 | /** 190 | * Move quadword from xmm register to r/m64. 191 | * Opcode: 66 REX.W 0F 7E /r 192 | * Instruction: MOVQ r/m64, xmm 193 | * Op/En: B 194 | * 195 | * @param r "r" register 196 | * @param m "r/m" register 197 | * @param disp Displacement. Set "empty" if this operation is reg-reg. 198 | * @return This instance 199 | */ 200 | public T movqMR(Register r, Register m, OptionalInt disp){ 201 | return movQInternal(r, m, disp, (byte)0x7e); 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/ExecMemory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2023, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal; 20 | 21 | import java.lang.foreign.MemorySegment; 22 | 23 | import com.yasuenag.ffmasm.PlatformException; 24 | 25 | 26 | /** 27 | * Interface for acquiring / releasing memory for execution code. 28 | * Implemented class should manage platform-dependent memory which can execute code in it. 29 | * 30 | * @author Yasumasa Suenaga 31 | */ 32 | public interface ExecMemory{ 33 | 34 | /** 35 | * Allocate memory which can execute code in it. 36 | * 37 | * @param size required size 38 | * @return platform memory address 39 | * @throws PlatformMemoryException thrown when memory allocation fails. 40 | */ 41 | public MemorySegment allocate(long size) throws PlatformException; 42 | 43 | /** 44 | * Deallocate memory which is pointed addr. 45 | * 46 | * @param addr platform memory address 47 | * @param size required size 48 | * @throws PlatformMemoryException thrown when memory deallocation fails. 49 | */ 50 | public void deallocate(MemorySegment addr, long size) throws PlatformException; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/JavaVM.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.foreign.Arena; 23 | import java.lang.foreign.FunctionDescriptor; 24 | import java.lang.foreign.MemorySegment; 25 | import java.lang.foreign.Linker; 26 | import java.lang.foreign.SymbolLookup; 27 | import java.lang.foreign.ValueLayout; 28 | import java.nio.file.Path; 29 | 30 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 31 | 32 | 33 | public class JavaVM{ 34 | 35 | /* Indices from API doc */ 36 | private static final int JavaVM_GetEnv_INDEX = 6; 37 | private static final int JavaVM_AttachCurrentThreadAsDaemon_INDEX = 7; 38 | private static final int JavaVM_MAX_INDEX = JavaVM_AttachCurrentThreadAsDaemon_INDEX; 39 | 40 | /* Keep instance of this class as a singleton */ 41 | private static JavaVM instance; 42 | 43 | private final Arena arena; 44 | private final MemorySegment vm; 45 | private final MemorySegment functionTable; 46 | 47 | /* function handles */ 48 | private MethodHandle GetEnv; 49 | 50 | private SymbolLookup getJvmLookup() throws UnsupportedPlatformException{ 51 | String osName = System.getProperty("os.name"); 52 | Path jvmPath; 53 | if(osName.equals("Linux")){ 54 | jvmPath = Path.of(System.getProperty("java.home"), "lib", "server", "libjvm.so"); 55 | } 56 | else if(osName.startsWith("Windows")){ 57 | jvmPath = Path.of(System.getProperty("java.home"), "bin", "server", "jvm.dll"); 58 | } 59 | else{ 60 | throw new UnsupportedPlatformException(osName + " is unsupported."); 61 | } 62 | 63 | return SymbolLookup.libraryLookup(jvmPath, arena); 64 | } 65 | 66 | private JavaVM() throws Throwable{ 67 | arena = Arena.ofAuto(); 68 | 69 | var JNI_GetCreatedJavaVMs = Linker.nativeLinker() 70 | .downcallHandle(getJvmLookup().find("JNI_GetCreatedJavaVMs").get(), 71 | FunctionDescriptor.of(ValueLayout.JAVA_INT, 72 | ValueLayout.ADDRESS, 73 | ValueLayout.JAVA_INT, 74 | ValueLayout.ADDRESS)); 75 | var vms = arena.allocate(ValueLayout.ADDRESS, 1); 76 | int result = (int)JNI_GetCreatedJavaVMs.invoke(vms, 1, MemorySegment.NULL); 77 | if(result != JniEnv.JNI_OK){ 78 | throw new RuntimeException("JNI_GetCreatedJavaVMs() returns " + result); 79 | } 80 | vm = vms.getAtIndex(ValueLayout.ADDRESS, 0) 81 | .reinterpret(ValueLayout.ADDRESS.byteSize()); 82 | 83 | functionTable = vm.getAtIndex(ValueLayout.ADDRESS, 0) 84 | .reinterpret(ValueLayout.ADDRESS.byteSize() * (JavaVM_MAX_INDEX + 1)); 85 | } 86 | 87 | public static JavaVM getInstance() throws Throwable{ 88 | if(instance == null){ 89 | instance = new JavaVM(); 90 | } 91 | return instance; 92 | } 93 | 94 | public int getEnv(MemorySegment penv, int version) throws Throwable{ 95 | if(GetEnv == null){ 96 | GetEnv = Linker.nativeLinker() 97 | .downcallHandle(functionTable.getAtIndex(ValueLayout.ADDRESS, JavaVM_GetEnv_INDEX), 98 | FunctionDescriptor.of(ValueLayout.JAVA_INT, 99 | ValueLayout.ADDRESS, 100 | ValueLayout.ADDRESS, 101 | ValueLayout.JAVA_INT)); 102 | } 103 | 104 | return (int)GetEnv.invoke(vm, penv, version); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/JniEnv.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.foreign.Arena; 23 | import java.lang.foreign.FunctionDescriptor; 24 | import java.lang.foreign.Linker; 25 | import java.lang.foreign.MemoryLayout; 26 | import java.lang.foreign.MemorySegment; 27 | import java.lang.foreign.StructLayout; 28 | import java.lang.foreign.ValueLayout; 29 | 30 | 31 | public class JniEnv{ 32 | 33 | /* from jni.h */ 34 | public static final int JNI_OK = 0; 35 | private static final int JNI_VERSION_21 = 0x00150000; 36 | 37 | /* Indices from API doc */ 38 | private static final int JNI_RegisterNatives_INDEX = 215; 39 | private static final int JNI_GetPrimitiveArrayCritical_INDEX = 222; 40 | private static final int JNI_ReleasePrimitiveArrayCritical_INDEX = 223; 41 | private static final int JNI_IsVirtualThread_INDEX = 234; 42 | private static final int JNI_MAX_INDEX = JNI_IsVirtualThread_INDEX; 43 | 44 | /* MemoryLayout for JNINativeMethod */ 45 | public static final StructLayout layoutJNINativeMethod; 46 | public static final MemoryLayout.PathElement peName; 47 | public static final MemoryLayout.PathElement peSignature; 48 | public static final MemoryLayout.PathElement peFnPtr; 49 | 50 | /* 51 | * Keep instance of this class. 52 | * JNIEnv* is associated with JavaThread in HotSpot. 53 | * So keep this insntance with ThreadLocal. 54 | */ 55 | private static ThreadLocal instance = new ThreadLocal<>(){ 56 | @Override 57 | protected JniEnv initialValue(){ 58 | try{ 59 | return new JniEnv(); 60 | } 61 | catch(Throwable t){ 62 | throw new RuntimeException(t); 63 | } 64 | } 65 | }; 66 | 67 | private final Arena arena; 68 | private final MemorySegment jniEnv; 69 | private final MemorySegment functionTable; 70 | 71 | /* function addresses / handles */ 72 | private MethodHandle RegisterNatives; 73 | private MemorySegment GetPrimitiveArrayCriticalAddr; 74 | private MemorySegment ReleasePrimitiveArrayCriticalAddr; 75 | 76 | static{ 77 | layoutJNINativeMethod = MemoryLayout.structLayout(ValueLayout.ADDRESS.withName("name"), 78 | ValueLayout.ADDRESS.withName("signature"), 79 | ValueLayout.ADDRESS.withName("fnPtr")); 80 | peName = MemoryLayout.PathElement.groupElement("name"); 81 | peSignature = MemoryLayout.PathElement.groupElement("signature"); 82 | peFnPtr = MemoryLayout.PathElement.groupElement("fnPtr"); 83 | } 84 | 85 | private JniEnv() throws Throwable{ 86 | arena = Arena.ofAuto(); 87 | 88 | var vm = JavaVM.getInstance(); 89 | var env = arena.allocate(ValueLayout.ADDRESS); 90 | int result = vm.getEnv(env, JNI_VERSION_21); 91 | if(result != JniEnv.JNI_OK){ 92 | throw new RuntimeException("GetEnv() returns " + result); 93 | } 94 | jniEnv = env.get(ValueLayout.ADDRESS, 0) 95 | .reinterpret(ValueLayout.ADDRESS.byteSize()); 96 | 97 | functionTable = jniEnv.get(ValueLayout.ADDRESS, 0) // JNIEnv = JNINativeInterface_* 98 | .reinterpret(ValueLayout.ADDRESS.byteSize() * (JNI_MAX_INDEX + 1)); 99 | } 100 | 101 | public static JniEnv getInstance() throws Throwable{ 102 | return instance.get(); 103 | } 104 | 105 | public int registerNatives(MemorySegment clazz, MemorySegment methods, int nMethods) throws Throwable{ 106 | if(RegisterNatives == null){ 107 | RegisterNatives = Linker.nativeLinker() 108 | .downcallHandle(functionTable.getAtIndex(ValueLayout.ADDRESS, JNI_RegisterNatives_INDEX), 109 | FunctionDescriptor.of(ValueLayout.JAVA_INT, 110 | ValueLayout.ADDRESS, 111 | ValueLayout.ADDRESS, 112 | ValueLayout.ADDRESS, 113 | ValueLayout.JAVA_INT)); 114 | } 115 | return (int)RegisterNatives.invoke(jniEnv, clazz, methods, nMethods); 116 | } 117 | 118 | public MemorySegment getPrimitiveArrayCriticalAddr(){ 119 | if(GetPrimitiveArrayCriticalAddr == null){ 120 | GetPrimitiveArrayCriticalAddr = functionTable.getAtIndex(ValueLayout.ADDRESS, JNI_GetPrimitiveArrayCritical_INDEX); 121 | } 122 | return GetPrimitiveArrayCriticalAddr; 123 | } 124 | 125 | public MemorySegment releasePrimitiveArrayCriticalAddr(){ 126 | if(ReleasePrimitiveArrayCriticalAddr == null){ 127 | ReleasePrimitiveArrayCriticalAddr = functionTable.getAtIndex(ValueLayout.ADDRESS, JNI_ReleasePrimitiveArrayCritical_INDEX); 128 | } 129 | return ReleasePrimitiveArrayCriticalAddr; 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/JvmtiEnv.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.foreign.Arena; 23 | import java.lang.foreign.FunctionDescriptor; 24 | import java.lang.foreign.Linker; 25 | import java.lang.foreign.MemorySegment; 26 | import java.lang.foreign.ValueLayout; 27 | 28 | 29 | public class JvmtiEnv{ 30 | 31 | /* from jvmti.h */ 32 | public static final int JVMTI_ERROR_NONE = 0; 33 | private static final int JVMTI_VERSION_21 = 0x30150000; 34 | 35 | /* indices / positions are from API doc */ 36 | private static final int JVMTI_Deallocate_POSITION = 47; 37 | private static final int JVMTI_GetClassSignature_POSITION = 48; 38 | private static final int JVMTI_GetLoadedClasses_POSITION = 78; 39 | private static final int JVMTI_SetHeapSamplingInterval_POSITION = 156; 40 | private static final int JVMTI_MAX_POSITION = JVMTI_SetHeapSamplingInterval_POSITION; 41 | 42 | /* Keep instance of this class as a singleton */ 43 | private static JvmtiEnv instance; 44 | 45 | private final Arena arena; 46 | private final MemorySegment jvmtiEnv; 47 | private final MemorySegment functionTable; 48 | 49 | /* function addresses / handles */ 50 | private MemorySegment GetLoadedClassesAddr; 51 | private MemorySegment DeallocateAddr; 52 | private MethodHandle Deallocate; 53 | private MethodHandle GetClassSignature; 54 | 55 | private JvmtiEnv() throws Throwable{ 56 | arena = Arena.ofAuto(); 57 | 58 | var vm = JavaVM.getInstance(); 59 | var env = arena.allocate(ValueLayout.ADDRESS); 60 | int result = vm.getEnv(env, JVMTI_VERSION_21); 61 | if(result != JniEnv.JNI_OK){ 62 | throw new RuntimeException("GetEnv() returns " + result); 63 | } 64 | jvmtiEnv = env.get(ValueLayout.ADDRESS, 0) 65 | .reinterpret(ValueLayout.ADDRESS.byteSize()); 66 | 67 | functionTable = jvmtiEnv.get(ValueLayout.ADDRESS, 0) 68 | .reinterpret(ValueLayout.ADDRESS.byteSize() * JVMTI_MAX_POSITION); 69 | } 70 | 71 | public static JvmtiEnv getInstance() throws Throwable{ 72 | if(instance == null){ 73 | instance = new JvmtiEnv(); 74 | } 75 | return instance; 76 | } 77 | 78 | public long getRawAddress(){ 79 | return jvmtiEnv.address(); 80 | } 81 | 82 | public MemorySegment getMemorySegment(){ 83 | return jvmtiEnv; 84 | } 85 | 86 | public MemorySegment getLoadedClassesAddr(){ 87 | if(GetLoadedClassesAddr == null){ 88 | GetLoadedClassesAddr = functionTable.getAtIndex(ValueLayout.ADDRESS, JVMTI_GetLoadedClasses_POSITION - 1); 89 | } 90 | return GetLoadedClassesAddr; 91 | } 92 | 93 | public MemorySegment deallocateAddr(){ 94 | if(DeallocateAddr == null){ 95 | DeallocateAddr = functionTable.getAtIndex(ValueLayout.ADDRESS, JVMTI_Deallocate_POSITION - 1); 96 | } 97 | return DeallocateAddr; 98 | } 99 | 100 | public int deallocate(MemorySegment mem) throws Throwable{ 101 | if(Deallocate == null){ 102 | Deallocate = Linker.nativeLinker() 103 | .downcallHandle(deallocateAddr(), 104 | FunctionDescriptor.of(ValueLayout.JAVA_INT, 105 | ValueLayout.ADDRESS, 106 | ValueLayout.ADDRESS)); 107 | } 108 | return (int)Deallocate.invoke(jvmtiEnv, mem); 109 | } 110 | 111 | public int getClassSignature(MemorySegment klass, MemorySegment signature_ptr, MemorySegment generic_ptr) throws Throwable{ 112 | if(GetClassSignature == null){ 113 | GetClassSignature = Linker.nativeLinker() 114 | .downcallHandle(functionTable.getAtIndex(ValueLayout.ADDRESS, JVMTI_GetClassSignature_POSITION - 1), 115 | FunctionDescriptor.of(ValueLayout.JAVA_INT, 116 | ValueLayout.ADDRESS, 117 | ValueLayout.ADDRESS, 118 | ValueLayout.ADDRESS, 119 | ValueLayout.ADDRESS)); 120 | } 121 | return (int)GetClassSignature.invoke(jvmtiEnv, klass, signature_ptr, generic_ptr); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/aarch64/AArch64NativeRegister.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal.aarch64; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.invoke.MethodHandles; 23 | import java.lang.foreign.Arena; 24 | import java.lang.foreign.FunctionDescriptor; 25 | import java.lang.foreign.Linker; 26 | import java.lang.foreign.MemorySegment; 27 | import java.lang.foreign.ValueLayout; 28 | import java.lang.ref.Cleaner; 29 | import java.util.Optional; 30 | 31 | import com.yasuenag.ffmasm.AsmBuilder; 32 | import com.yasuenag.ffmasm.CodeSegment; 33 | import com.yasuenag.ffmasm.NativeRegister; 34 | import com.yasuenag.ffmasm.aarch64.Register; 35 | import com.yasuenag.ffmasm.aarch64.IndexClass; 36 | import com.yasuenag.ffmasm.internal.JvmtiEnv; 37 | 38 | 39 | public final class AArch64NativeRegister extends NativeRegister{ 40 | 41 | private static final Arena arena; 42 | private static final CodeSegment seg; 43 | 44 | private static MemorySegment cbStub; 45 | private static JvmtiEnv jvmtiEnv; 46 | private static MethodHandle registerStub; 47 | 48 | private static void setupRegisterStub() throws Throwable{ 49 | var desc = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, // callbackParam 50 | ValueLayout.ADDRESS, // jvmtiEnv 51 | ValueLayout.ADDRESS, // cbStub 52 | ValueLayout.ADDRESS, // ptr GetLoadedClass() 53 | ValueLayout.ADDRESS); // ptr Deallocate() 54 | 55 | /* S T A C K A L L O C A T I O N 56 | ------------------------------------------------- ---- SP (FP-64) 57 | | Number of jclasses (from GetLoadedClasses()) | 58 | |-------------------------------------------------| 59 | | Pointer to jclass array from GetLoadedClasses() | 60 | |-------------------------------------------------| 61 | | stack alignment (dummy (arg5)) | 62 | |-------------------------------------------------| 63 | | ptr Deallocate() (arg5) | 64 | |-------------------------------------------------| 65 | | ptr GetLoadedClass() (arg4) | 66 | |-------------------------------------------------| 67 | | cbStub (arg3) | 68 | |-------------------------------------------------| 69 | | jvmtiEnv (arg2) | 70 | |-------------------------------------------------| 71 | | callbackParam (arg1) | 72 | |-------------------------------------------------| ---- FP 73 | | Previous FP (X29) | 74 | |-------------------------------------------------| 75 | | Previous LR (X30) | 76 | ------------------------------------------------- 77 | */ 78 | registerStub = new AsmBuilder.AArch64(seg, desc) 79 | // prologue 80 | /* stp x29, x30, [sp, #-16]! */ .stp(Register.X29, Register.X30, Register.SP, IndexClass.PreIndex, -16) 81 | /* mov x29, sp */ .mov(Register.X29, Register.SP) 82 | /* stp x1, x0, [sp, #-16]! */ .stp(Register.X1, Register.X0, Register.SP, IndexClass.PreIndex, -16) 83 | /* stp x3, x2, [sp, #-16]! */ .stp(Register.X3, Register.X2, Register.SP, IndexClass.PreIndex, -16) 84 | /* stp x4, x4, [sp, #-16]! */ .stp(Register.X4, Register.X4, Register.SP, IndexClass.PreIndex, -16) 85 | /* sub sp, sp, #16 */ .subImm(Register.SP, Register.SP, 16, false) 86 | 87 | // call GetLoadedClasses() 88 | /* mov x1, SP */ .mov(Register.X1, Register.SP) // count (arg2) 89 | /* add x2, SP, #8 */ .addImm(Register.SP, Register.X2, 8, false) // classes (arg3) 90 | /* ldr x0, [SP, #48] */ .ldr(Register.X0, Register.SP, IndexClass.UnsignedOffset, 48) // address of jvmtiEnv (arg1) 91 | /* ldr x9, [SP, #32] */ .ldr(Register.X9, Register.SP, IndexClass.UnsignedOffset, 32) // address of GetLoadedClasses() (x9: tmpreg) 92 | /* blr x9 */ .blr(Register.X9) 93 | 94 | // call callback(MemorySegment classes, int class_count, int resultGetLoadedClasses, MemorySegment callbackParam) 95 | /* mov x2, x0 */ .mov(Register.X2, Register.X0) // result of GetLoadedClasses() (arg3) 96 | /* ldp x1, x0, [sp] */ .ldp(Register.X1, Register.X0, Register.SP, IndexClass.SignedOffset, 0) // count (arg2), classes(arg1) 97 | /* ldr x3, [SP, #56] */ .ldr(Register.X3, Register.SP, IndexClass.UnsignedOffset, 56) // callbackParam (arg4) 98 | /* ldr x9, [SP, #40] */ .ldr(Register.X9, Register.SP, IndexClass.UnsignedOffset, 40) // address of callback (x9: tmpreg) 99 | /* blr x9 */ .blr(Register.X9) 100 | 101 | // call Deallocate() 102 | /* ldr x0, [SP, #48] */ .ldr(Register.X0, Register.SP, IndexClass.UnsignedOffset, 48) // address of jvmtiEnv (arg1) 103 | /* ldr x1, [SP, #8] */ .ldr(Register.X1, Register.SP, IndexClass.UnsignedOffset, 8) // classes (arg2) 104 | /* ldr x9, [SP, #24] */ .ldr(Register.X9, Register.SP, IndexClass.UnsignedOffset, 24) // address of Deallocate (x9: tmpreg) 105 | /* blr x9 */ .blr(Register.X9) 106 | 107 | // epilogue 108 | /* add sp, sp, #64 */ .addImm(Register.SP, Register.SP, 64, false) 109 | /* ldp x29, x30, [sp], #16 */ .ldp(Register.X29, Register.X30, Register.SP, IndexClass.PostIndex, 16) 110 | /* ret */ .ret(Optional.empty()) 111 | .build(); 112 | } 113 | 114 | static{ 115 | arena = Arena.ofAuto(); 116 | try{ 117 | seg = new CodeSegment(); 118 | var action = new CodeSegment.CleanerAction(seg); 119 | Cleaner.create() 120 | .register(NativeRegister.class, action); 121 | 122 | var targetMethod = NativeRegister.class 123 | .getDeclaredMethod("callback", 124 | MemorySegment.class, 125 | int.class, 126 | int.class, 127 | MemorySegment.class); 128 | var hndCallback = MethodHandles.lookup() 129 | .unreflect(targetMethod); 130 | cbStub = Linker.nativeLinker() 131 | .upcallStub(hndCallback, 132 | FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, 133 | ValueLayout.JAVA_INT, 134 | ValueLayout.JAVA_INT, 135 | ValueLayout.ADDRESS), 136 | arena); 137 | 138 | jvmtiEnv = JvmtiEnv.getInstance(); 139 | } 140 | catch(Throwable t){ 141 | throw new RuntimeException(t); 142 | } 143 | } 144 | 145 | public AArch64NativeRegister(Class klass){ 146 | super(klass); 147 | } 148 | 149 | @Override 150 | protected void callRegisterStub(MemorySegment callbackParam) throws Throwable{ 151 | if(registerStub == null){ 152 | setupRegisterStub(); 153 | } 154 | registerStub.invoke(callbackParam, jvmtiEnv.getMemorySegment(), cbStub, jvmtiEnv.getLoadedClassesAddr(), jvmtiEnv.deallocateAddr()); 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/amd64/AMD64NativeRegister.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal.amd64; 20 | 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.invoke.MethodHandles; 23 | import java.lang.foreign.Arena; 24 | import java.lang.foreign.FunctionDescriptor; 25 | import java.lang.foreign.Linker; 26 | import java.lang.foreign.MemorySegment; 27 | import java.lang.foreign.ValueLayout; 28 | import java.lang.ref.Cleaner; 29 | import java.util.OptionalInt; 30 | 31 | import com.yasuenag.ffmasm.AsmBuilder; 32 | import com.yasuenag.ffmasm.CodeSegment; 33 | import com.yasuenag.ffmasm.NativeRegister; 34 | import com.yasuenag.ffmasm.amd64.Register; 35 | import com.yasuenag.ffmasm.internal.JvmtiEnv; 36 | 37 | 38 | public final class AMD64NativeRegister extends NativeRegister{ 39 | 40 | private static final Arena arena; 41 | private static final CodeSegment seg; 42 | 43 | private static MethodHandle registerStub; 44 | 45 | private static void setupRegisterStub() throws Throwable{ 46 | var targetMethod = NativeRegister.class 47 | .getDeclaredMethod("callback", 48 | MemorySegment.class, 49 | int.class, 50 | int.class, 51 | MemorySegment.class); 52 | var hndCallback = MethodHandles.lookup() 53 | .unreflect(targetMethod); 54 | var cbStub = Linker.nativeLinker() 55 | .upcallStub(hndCallback, 56 | FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, 57 | ValueLayout.JAVA_INT, 58 | ValueLayout.JAVA_INT, 59 | ValueLayout.ADDRESS), 60 | arena); 61 | 62 | var jvmtiEnv = JvmtiEnv.getInstance(); 63 | var regs = CallingRegisters.getRegs(); 64 | var desc = FunctionDescriptor.ofVoid(ValueLayout.ADDRESS); // callbackParam 65 | 66 | /* S T A C K A L L O C A T I O N 67 | ------------------------------------------------- ---- RSP (RBP-64) 68 | | Shadow space for arg4 | 69 | |-------------------------------------------------| 70 | | Shadow space for arg3 | 71 | |-------------------------------------------------| 72 | | Shadow space for arg2 | 73 | |-------------------------------------------------| 74 | | Shadow space for arg1 | 75 | |-------------------------------------------------| 76 | | Stack alignment | 77 | |-------------------------------------------------| 78 | | Number of jclasses (from GetLoadedClasses()) | 79 | |-------------------------------------------------| 80 | | Pointer to jclass array from GetLoadedClasses() | 81 | |-------------------------------------------------| 82 | | Saved Register 1 | 83 | |-------------------------------------------------| ---- RBP 84 | | Previous RBP | 85 | |-------------------------------------------------| 86 | | Return Address | 87 | ------------------------------------------------- 88 | */ 89 | registerStub = new AsmBuilder.AMD64(seg, desc) 90 | // prologue 91 | /* push %rbp */ .push(Register.RBP) 92 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 93 | /* sub $64, %rsp */ .sub(Register.RSP, 64, OptionalInt.empty()) 94 | /* mov savedReg1, -8(%rbp) */ .movMR(regs.savedReg1(), Register.RBP, OptionalInt.of(-8)) 95 | /* mov arg1, savedReg1 */ .movMR(regs.arg1(), regs.savedReg1(), OptionalInt.empty()) 96 | 97 | // call GetLoadedClasses() 98 | /* lea -24(%rbp), arg2 */ .lea(regs.arg2(), Register.RBP, -24) // count 99 | /* lea -16(%rbp), arg3 */ .lea(regs.arg3(), Register.RBP, -16) // classes 100 | /* mov addr, arg1 */ .movImm(regs.arg1(), jvmtiEnv.getRawAddress()) // address of jvmtiEnv 101 | /* mov addr, tmpReg1 */ .movImm(regs.tmpReg1(), jvmtiEnv.getLoadedClassesAddr().address()) // address of GetLoadedClasses() 102 | /* call tmpReg1 */ .call(regs.tmpReg1()) 103 | 104 | // call callback(jclass *classes, jint class_count) 105 | /* mov -24(%rbp), arg2 */ .movRM(regs.arg2(), Register.RBP, OptionalInt.of(-24)) // count 106 | /* mov -16(%rbp), arg1 */ .movRM(regs.arg1(), Register.RBP, OptionalInt.of(-16)) // classes 107 | /* mov returnReg, arg3 */ .movMR(regs.returnReg(), regs.arg3(), OptionalInt.empty()) // result of GetLoadedClasses() 108 | /* mov savedReg1, arg4 */ .movMR(regs.savedReg1(), regs.arg4(), OptionalInt.empty()) // callbackParam 109 | /* mov addr, tmpReg1 */ .movImm(regs.tmpReg1(), cbStub.address()) // address of callback 110 | /* call tmpReg1 */ .call(regs.tmpReg1()) 111 | 112 | // call Deallocate() 113 | /* mov addr, arg1 */ .movImm(regs.arg1(), jvmtiEnv.getRawAddress()) // address of jvmtiEnv 114 | /* mov -16(%rbp), arg1 */ .movRM(regs.arg1(), Register.RBP, OptionalInt.of(-16)) // classes 115 | /* mov addr, tmpReg1 */ .movImm(regs.tmpReg1(), jvmtiEnv.deallocateAddr().address()) // address of Deallocate() 116 | /* call tmpReg1 */ .call(regs.tmpReg1()) 117 | 118 | // epilogue 119 | /* mov -8(%rbp), savedReg1 */ .movRM(regs.savedReg1(), Register.RBP, OptionalInt.of(-8)) 120 | /* leave */ .leave() 121 | /* ret */ .ret() 122 | .build(); 123 | } 124 | 125 | static{ 126 | arena = Arena.ofAuto(); 127 | try{ 128 | seg = new CodeSegment(); 129 | var action = new CodeSegment.CleanerAction(seg); 130 | Cleaner.create() 131 | .register(NativeRegister.class, action); 132 | } 133 | catch(Throwable t){ 134 | throw new RuntimeException(t); 135 | } 136 | } 137 | 138 | public AMD64NativeRegister(Class klass){ 139 | super(klass); 140 | } 141 | 142 | @Override 143 | protected void callRegisterStub(MemorySegment callbackParam) throws Throwable{ 144 | if(registerStub == null){ 145 | setupRegisterStub(); 146 | } 147 | registerStub.invoke(callbackParam); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/amd64/CallingRegisters.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal.amd64; 20 | 21 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 22 | import com.yasuenag.ffmasm.amd64.Register; 23 | 24 | 25 | public record CallingRegisters( 26 | Register arg1, 27 | Register arg2, 28 | Register arg3, 29 | Register arg4, 30 | Register returnReg, 31 | Register savedReg1, 32 | Register tmpReg1){ 33 | 34 | public static CallingRegisters getRegs() throws UnsupportedPlatformException{ 35 | var arch = System.getProperty("os.arch"); 36 | if(!arch.equals("amd64")){ 37 | throw new UnsupportedPlatformException(arch + " is not supported."); 38 | } 39 | 40 | String osName = System.getProperty("os.name"); 41 | if(osName.equals("Linux")){ 42 | return new CallingRegisters(Register.RDI, 43 | Register.RSI, 44 | Register.RDX, 45 | Register.RCX, 46 | Register.RAX, 47 | Register.R12, 48 | Register.R10); 49 | } 50 | else if(osName.startsWith("Windows")){ 51 | return new CallingRegisters(Register.RCX, 52 | Register.RDX, 53 | Register.R8, 54 | Register.R9, 55 | Register.RAX, 56 | Register.R12, 57 | Register.R10); 58 | } 59 | else{ 60 | throw new UnsupportedPlatformException(osName + " is not supported."); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/linux/LinuxExecMemory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal.linux; 20 | 21 | import java.lang.foreign.Arena; 22 | import java.lang.foreign.FunctionDescriptor; 23 | import java.lang.foreign.Linker; 24 | import java.lang.foreign.MemoryLayout; 25 | import java.lang.foreign.MemorySegment; 26 | import java.lang.foreign.SymbolLookup; 27 | import java.lang.foreign.ValueLayout; 28 | import java.lang.invoke.MethodHandle; 29 | import java.lang.invoke.VarHandle; 30 | import java.util.Map; 31 | 32 | import com.yasuenag.ffmasm.internal.ExecMemory; 33 | import com.yasuenag.ffmasm.PlatformException; 34 | 35 | 36 | /** 37 | * Aquiring / releasing memory for execution code for Linux. 38 | * This class uses mmap(2) and munmap(2) for it. 39 | * 40 | * @author Yasumasa Suenaga 41 | */ 42 | public class LinuxExecMemory implements ExecMemory{ 43 | 44 | private static final Linker nativeLinker; 45 | 46 | private static final SymbolLookup sym; 47 | 48 | private static final Map canonicalLayouts; 49 | 50 | private static final Linker.Option errnoState; 51 | 52 | private static final MemorySegment errnoSeg; 53 | 54 | private static MethodHandle hndMmap = null; 55 | 56 | private static MethodHandle hndMunmap = null; 57 | 58 | private static VarHandle hndErrno = null; 59 | 60 | /** 61 | * page can be read 62 | */ 63 | public static final int PROT_READ = 0x1; 64 | 65 | /** 66 | * page can be written 67 | */ 68 | public static final int PROT_WRITE = 0x2; 69 | 70 | /** 71 | * page can be executed 72 | */ 73 | public static final int PROT_EXEC = 0x4; 74 | 75 | /** 76 | * Changes are private 77 | */ 78 | public static final int MAP_PRIVATE = 0x02; 79 | 80 | /** 81 | * don't use a file 82 | */ 83 | public static final int MAP_ANONYMOUS = 0x20; 84 | 85 | static{ 86 | nativeLinker = Linker.nativeLinker(); 87 | sym = nativeLinker.defaultLookup(); 88 | canonicalLayouts = nativeLinker.canonicalLayouts(); 89 | errnoState = Linker.Option.captureCallState("errno"); 90 | errnoSeg = Arena.global().allocate(Linker.Option.captureStateLayout()); 91 | } 92 | 93 | /** 94 | * Call mmap(2) via FFM. See manpage of mmap(2) for details. 95 | * 96 | * @throws PlatformException if mmap(2) or FFM call failed. 97 | */ 98 | public static MemorySegment mmap(MemorySegment addr, long length, int prot, int flags, int fd, long offset) throws PlatformException{ 99 | if(hndMmap == null){ 100 | var func = sym.find("mmap").get(); 101 | var desc = FunctionDescriptor.of( 102 | ValueLayout.ADDRESS, // return value 103 | ValueLayout.ADDRESS, // addr 104 | canonicalLayouts.get("size_t"), // length 105 | ValueLayout.JAVA_INT, // prot 106 | ValueLayout.JAVA_INT, // flags 107 | ValueLayout.JAVA_INT, // fd 108 | ValueLayout.JAVA_LONG // offset 109 | ); 110 | hndMmap = nativeLinker.downcallHandle(func, desc, errnoState); 111 | } 112 | 113 | try{ 114 | MemorySegment mem = (MemorySegment)hndMmap.invoke(errnoSeg, addr, length, prot, flags, fd, offset); 115 | if(mem.address() == -1L){ // MAP_FAILED 116 | if(hndErrno == null){ 117 | hndErrno = Linker.Option.captureStateLayout().varHandle(MemoryLayout.PathElement.groupElement("errno")); 118 | } 119 | throw new PlatformException("mmap() failed", (int)hndErrno.get(errnoSeg, 0L)); 120 | } 121 | return mem.reinterpret(length); 122 | } 123 | catch(Throwable t){ 124 | throw new PlatformException(t); 125 | } 126 | } 127 | 128 | /** 129 | * Call munmap(2) via FFM. See manpage of munmap(2) for details. 130 | * 131 | * @throws PlatformException if munmap(2) or FFM call failed. 132 | */ 133 | public static int munmap(MemorySegment addr, long length) throws PlatformException{ 134 | if(hndMunmap == null){ 135 | var func = sym.find("munmap").get(); 136 | var desc = FunctionDescriptor.of( 137 | ValueLayout.JAVA_INT, // return value 138 | ValueLayout.ADDRESS, // addr 139 | canonicalLayouts.get("size_t") // length 140 | ); 141 | hndMunmap = nativeLinker.downcallHandle(func, desc, Linker.Option.critical(false)); 142 | } 143 | 144 | try{ 145 | int retval = (int)hndMunmap.invoke(addr, length); 146 | if(retval == -1){ 147 | if(hndErrno == null){ 148 | hndErrno = Linker.Option.captureStateLayout().varHandle(MemoryLayout.PathElement.groupElement("errno")); 149 | } 150 | throw new PlatformException("munmap() failed", (int)hndErrno.get(errnoSeg, 0L)); 151 | } 152 | return retval; // it should be 0 153 | } 154 | catch(Throwable t){ 155 | throw new PlatformException(t); 156 | } 157 | } 158 | 159 | /** 160 | * {@inheritDoc} 161 | */ 162 | @Override 163 | public MemorySegment allocate(long size) throws PlatformException{ 164 | return mmap(MemorySegment.NULL, size, PROT_EXEC | PROT_READ | PROT_WRITE, 165 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 166 | } 167 | 168 | /** 169 | * {@inheritDoc} 170 | */ 171 | @Override 172 | public void deallocate(MemorySegment addr, long size) throws PlatformException{ 173 | munmap(addr, size); 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/linux/PerfJitDump.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal.linux; 20 | 21 | import java.io.IOException; 22 | import java.io.UncheckedIOException; 23 | import java.lang.foreign.Arena; 24 | import java.lang.foreign.FunctionDescriptor; 25 | import java.lang.foreign.Linker; 26 | import java.lang.foreign.MemoryLayout; 27 | import java.lang.foreign.MemorySegment; 28 | import java.lang.foreign.StructLayout; 29 | import java.lang.foreign.ValueLayout; 30 | import java.lang.invoke.MethodHandle; 31 | import java.lang.invoke.VarHandle; 32 | import java.nio.ByteBuffer; 33 | import java.nio.ByteOrder; 34 | import java.nio.channels.FileChannel; 35 | import java.nio.file.Files; 36 | import java.nio.file.NoSuchFileException; 37 | import java.nio.file.Path; 38 | import java.nio.file.StandardOpenOption; 39 | 40 | import com.yasuenag.ffmasm.CodeSegment; 41 | import com.yasuenag.ffmasm.JitDump; 42 | import com.yasuenag.ffmasm.PlatformException; 43 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 44 | 45 | 46 | /** 47 | * Generate jitdump for perf tool on Linux. 48 | * 49 | * @author Yasumasa Suenaga 50 | */ 51 | public class PerfJitDump implements JitDump{ 52 | 53 | // These constants come from tools/perf/util/jitdump.h in Linux Kernel 54 | private static final int JITHEADER_MAGIC = 0x4A695444; 55 | private static final int JITHEADER_VERSION = 1; 56 | private static final int JIT_CODE_LOAD = 0; 57 | private static final int JIT_CODE_CLOSE = 3; 58 | 59 | private static final long PAGE_SIZE = 4096; 60 | 61 | private static final MethodHandle mhGetTid; 62 | 63 | private final FileChannel ch; 64 | 65 | private final int fd; 66 | 67 | private final MemorySegment jitdump; 68 | 69 | private long codeIndex; 70 | 71 | 72 | static{ 73 | var linker = Linker.nativeLinker(); 74 | var lookup = linker.defaultLookup(); 75 | FunctionDescriptor desc; 76 | 77 | desc = FunctionDescriptor.of(ValueLayout.JAVA_INT); 78 | mhGetTid = linker.downcallHandle(lookup.find("gettid").get(), desc); 79 | } 80 | 81 | private static int getTid(){ 82 | try{ 83 | return (int)mhGetTid.invokeExact(); 84 | } 85 | catch(Throwable t){ 86 | throw new RuntimeException("Exception happened at gettid() call.", t); 87 | } 88 | } 89 | 90 | // from tools/perf/util/jitdump.h in Linux Kernel 91 | // struct jitheader { 92 | // uint32_t magic; /* characters "jItD" */ 93 | // uint32_t version; /* header version */ 94 | // uint32_t total_size; /* total size of header */ 95 | // uint32_t elf_mach; /* elf mach target */ 96 | // uint32_t pad1; /* reserved */ 97 | // uint32_t pid; /* JIT process id */ 98 | // uint64_t timestamp; /* timestamp */ 99 | // uint64_t flags; /* flags */ 100 | // }; 101 | private void writeHeader() throws IOException{ 102 | final int headerSize = 40; // sizeof(struct jitheader) 103 | var buf = ByteBuffer.allocate(headerSize).order(ByteOrder.nativeOrder()); 104 | 105 | short elfMach = -1; 106 | try(var chExe = FileChannel.open(Path.of("/proc/self/exe"), StandardOpenOption.READ)){ 107 | var buf2 = ByteBuffer.allocate(2).order(ByteOrder.nativeOrder()); 108 | chExe.read(buf2, 18); // id (16 bytes) + e_type (2 bytes) 109 | buf2.flip(); 110 | elfMach = buf2.getShort(); 111 | } 112 | 113 | // magic 114 | buf.putInt(JITHEADER_MAGIC); 115 | // version 116 | buf.putInt(JITHEADER_VERSION); 117 | // total_size 118 | buf.putInt(headerSize); 119 | // elf_mach 120 | buf.putInt(elfMach); 121 | // pad1 122 | buf.putInt(0); 123 | // pid 124 | buf.putInt((int)ProcessHandle.current().pid()); 125 | // timestamp 126 | buf.putLong(System.nanoTime()); 127 | // flags 128 | buf.putLong(0L); 129 | 130 | buf.flip(); 131 | ch.write(buf); 132 | } 133 | 134 | /** 135 | * Constructor of PerfJitDump. 136 | * This constructer creates dump file named with "jit-.dump" 137 | * into specified directory. Top of page (1 page: 4096 bytes) 138 | * will be mapped as a executable memory - it is mandatory for 139 | * recording in perf tool. 140 | * And also jitdump header will be written at this time. 141 | * 142 | * @param dir Path to base directory to dump. 143 | */ 144 | public PerfJitDump(Path dir) throws UnsupportedPlatformException, PlatformException, IOException{ 145 | // According to jit_detect() in tools/perf/util/jitdump.c in Linux Kernel, 146 | // dump file should be named "jit-.dump". 147 | var jitdumpPath = dir.resolve(String.format("jit-%d.dump", ProcessHandle.current().pid())); 148 | ch = FileChannel.open(jitdumpPath, StandardOpenOption.CREATE_NEW, StandardOpenOption.READ, StandardOpenOption.WRITE); 149 | 150 | // find FD of jitdump file 151 | int fdWork = -1; 152 | try(var links = Files.newDirectoryStream(Path.of("/proc/self/fd"))){ 153 | for(var link : links){ 154 | try{ 155 | if(Files.isSameFile(jitdumpPath, Files.readSymbolicLink(link))){ 156 | fdWork = Integer.parseInt(link.getFileName().toString()); 157 | break; 158 | } 159 | } 160 | catch(NoSuchFileException e){ 161 | // ignore 162 | } 163 | } 164 | } 165 | if(fdWork == -1){ 166 | throw new IllegalStateException("FD of jitdump is not found."); 167 | } 168 | fd = fdWork; 169 | 170 | // from tools/perf/jvmti/jvmti_agent.c in Linux Kernel 171 | jitdump = LinuxExecMemory.mmap(MemorySegment.NULL, PAGE_SIZE, LinuxExecMemory.PROT_READ | LinuxExecMemory.PROT_EXEC, LinuxExecMemory.MAP_PRIVATE, fd, 0); 172 | codeIndex = 0; 173 | 174 | writeHeader(); 175 | } 176 | 177 | // from tools/perf/util/jitdump.h in Linux Kernel 178 | // struct jr_prefix { 179 | // uint32_t id; 180 | // uint32_t total_size; 181 | // uint64_t timestamp; 182 | // }; 183 | // 184 | // struct jr_code_load { 185 | // struct jr_prefix p; 186 | // 187 | // uint32_t pid; 188 | // uint32_t tid; 189 | // uint64_t vma; 190 | // uint64_t code_addr; 191 | // uint64_t code_size; 192 | // uint64_t code_index; 193 | // }; 194 | /** 195 | * {@inheritDoc} 196 | */ 197 | @Override 198 | public synchronized void writeFunction(CodeSegment.MethodInfo method){ 199 | // sizeof(jr_code_load) == 56, null char of method name should be included. 200 | final int totalSize = 56 + method.name().length() + 1 + method.size(); 201 | var buf = ByteBuffer.allocate(totalSize).order(ByteOrder.nativeOrder()); 202 | 203 | // id 204 | buf.putInt(JIT_CODE_LOAD); 205 | // total_size 206 | buf.putInt(totalSize); 207 | // timestamp 208 | buf.putLong(System.nanoTime()); 209 | // pid 210 | buf.putInt((int)ProcessHandle.current().pid()); 211 | // tid 212 | buf.putInt(getTid()); 213 | // vma 214 | buf.putLong(method.address()); 215 | // code_addr 216 | buf.putLong(method.address()); 217 | // code_size 218 | buf.putLong(method.size()); 219 | // code_index 220 | buf.putLong(codeIndex++); 221 | 222 | // method name 223 | buf.put(method.name().getBytes()); 224 | buf.put((byte)0); // NUL 225 | 226 | // code 227 | var seg = MemorySegment.ofAddress(method.address()).reinterpret(method.size()); 228 | buf.put(seg.toArray(ValueLayout.JAVA_BYTE)); 229 | 230 | buf.flip(); 231 | try{ 232 | ch.write(buf); 233 | } 234 | catch(IOException e){ 235 | throw new UncheckedIOException(e); 236 | } 237 | } 238 | 239 | // from tools/perf/util/jitdump.h in Linux Kernel 240 | // struct jr_prefix { 241 | // uint32_t id; 242 | // uint32_t total_size; 243 | // uint64_t timestamp; 244 | // }; 245 | // 246 | // struct jr_code_close { 247 | // struct jr_prefix p; 248 | // }; 249 | /** 250 | * {@inheritDoc} 251 | */ 252 | @Override 253 | public synchronized void close() throws Exception{ 254 | final int headerSize = 16; // sizeof(jr_code_close) 255 | var buf = ByteBuffer.allocate(headerSize).order(ByteOrder.nativeOrder()); 256 | 257 | // id 258 | buf.putInt(JIT_CODE_CLOSE); 259 | // total_size 260 | buf.putInt(headerSize); 261 | // timestamp 262 | buf.putLong(System.nanoTime()); 263 | 264 | buf.flip(); 265 | ch.write(buf); 266 | 267 | LinuxExecMemory.munmap(jitdump, PAGE_SIZE); 268 | ch.close(); 269 | } 270 | 271 | } 272 | -------------------------------------------------------------------------------- /src/main/java/com/yasuenag/ffmasm/internal/windows/WindowsExecMemory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.internal.windows; 20 | 21 | import java.lang.foreign.Arena; 22 | import java.lang.foreign.FunctionDescriptor; 23 | import java.lang.foreign.Linker; 24 | import java.lang.foreign.MemoryLayout; 25 | import java.lang.foreign.MemorySegment; 26 | import java.lang.foreign.SymbolLookup; 27 | import java.lang.foreign.ValueLayout; 28 | import java.lang.invoke.MethodHandle; 29 | import java.lang.invoke.VarHandle; 30 | import java.util.Map; 31 | 32 | import com.yasuenag.ffmasm.internal.ExecMemory; 33 | import com.yasuenag.ffmasm.PlatformException; 34 | 35 | 36 | /** 37 | * Aquiring / releasing memory for execution code for Windows. 38 | * This class uses VirtualAlloc and VirtualFree for it. 39 | * 40 | * @author Yasumasa Suenaga 41 | */ 42 | public class WindowsExecMemory implements ExecMemory{ 43 | 44 | private static final Linker nativeLinker; 45 | 46 | private static final SymbolLookup sym; 47 | 48 | private static final Map canonicalLayouts; 49 | 50 | private static final Linker.Option getLastErrorState; 51 | 52 | private static final MemorySegment getLastErrorSeg; 53 | 54 | private MethodHandle hndVirtualAlloc = null; 55 | 56 | private MethodHandle hndVirtualFree = null; 57 | 58 | private VarHandle hndGetLastError = null; 59 | 60 | public static final int MEM_COMMIT = 0x00001000; 61 | 62 | public static final int MEM_RESERVE = 0x00002000; 63 | 64 | public static final int MEM_RELEASE = 0x00008000; 65 | 66 | public static final int PAGE_EXECUTE_READWRITE = 0x40; 67 | 68 | static{ 69 | sym = SymbolLookup.libraryLookup("Kernel32", Arena.global()); 70 | nativeLinker = Linker.nativeLinker(); 71 | canonicalLayouts = nativeLinker.canonicalLayouts(); 72 | getLastErrorState = Linker.Option.captureCallState("GetLastError"); 73 | getLastErrorSeg = Arena.global().allocate(Linker.Option.captureStateLayout()); 74 | } 75 | 76 | private MemorySegment virtualAlloc(long lpAddress, long dwSize, int flAllocationType, int flProtect) throws PlatformException{ 77 | if(hndVirtualAlloc == null){ 78 | var func = sym.find("VirtualAlloc").get(); 79 | var desc = FunctionDescriptor.of( 80 | ValueLayout.ADDRESS, // return value 81 | ValueLayout.JAVA_LONG, // lpAddress 82 | canonicalLayouts.get("long"), // dwSize 83 | ValueLayout.JAVA_INT, // flAllocationType 84 | ValueLayout.JAVA_INT // flProtect 85 | ); 86 | hndVirtualAlloc = nativeLinker.downcallHandle(func, desc, getLastErrorState); 87 | } 88 | 89 | try{ 90 | MemorySegment mem = (MemorySegment)hndVirtualAlloc.invoke(getLastErrorSeg, 91 | lpAddress, 92 | (int)dwSize, // "long" is 32bit in LLP64 93 | flAllocationType, 94 | flProtect); 95 | if(mem.equals(MemorySegment.NULL)){ 96 | if(hndGetLastError == null){ 97 | hndGetLastError = Linker.Option.captureStateLayout().varHandle(MemoryLayout.PathElement.groupElement("GetLastError")); 98 | } 99 | throw new PlatformException("VirtualAlloc() failed", (int)hndGetLastError.get(getLastErrorSeg, 0L)); 100 | } 101 | return mem.reinterpret(dwSize); 102 | } 103 | catch(Throwable t){ 104 | throw new PlatformException(t); 105 | } 106 | } 107 | 108 | /** 109 | * VirtualFree returns BOOL, it is defined in int. 110 | * https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types 111 | */ 112 | private int virtualFree(MemorySegment lpAddress, long dwSize, int dwFreeType) throws PlatformException{ 113 | if(hndVirtualFree == null){ 114 | var func = sym.find("VirtualFree").get(); 115 | var desc = FunctionDescriptor.of( 116 | ValueLayout.JAVA_INT, // return value 117 | ValueLayout.ADDRESS, // addr 118 | canonicalLayouts.get("long"), // dwSize 119 | ValueLayout.JAVA_INT // dwFreeType 120 | ); 121 | hndVirtualFree = nativeLinker.downcallHandle(func, desc, getLastErrorState); 122 | } 123 | 124 | try{ 125 | int result = (int)hndVirtualFree.invoke(getLastErrorSeg, 126 | lpAddress, 127 | (int)dwSize, // "long" is 32bit in LLP64 128 | dwFreeType); 129 | if(result == 0){ 130 | if(hndGetLastError == null){ 131 | hndGetLastError = Linker.Option.captureStateLayout().varHandle(MemoryLayout.PathElement.groupElement("GetLastError")); 132 | } 133 | throw new PlatformException("VirtualFree() failed", (int)hndGetLastError.get(getLastErrorSeg, 0L)); 134 | } 135 | return result; // it should be true 136 | } 137 | catch(Throwable t){ 138 | throw new PlatformException(t); 139 | } 140 | } 141 | 142 | /** 143 | * {@inheritDoc} 144 | */ 145 | @Override 146 | public MemorySegment allocate(long size) throws PlatformException{ 147 | return virtualAlloc(0, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 148 | } 149 | 150 | /** 151 | * {@inheritDoc} 152 | */ 153 | @Override 154 | public void deallocate(MemorySegment addr, long size) throws PlatformException{ 155 | virtualFree(addr, 0, MEM_RELEASE); 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | 20 | /** 21 | * Core module of ffmasm. 22 | */ 23 | module com.yasuenag.ffmasm { 24 | exports com.yasuenag.ffmasm; 25 | exports com.yasuenag.ffmasm.amd64; 26 | exports com.yasuenag.ffmasm.aarch64; 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/aarch64/NativeRegisterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.aarch64; 20 | 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.condition.EnabledOnOs; 24 | import org.junit.jupiter.api.condition.OS; 25 | 26 | import java.lang.foreign.FunctionDescriptor; 27 | import java.lang.foreign.ValueLayout; 28 | import java.util.Map; 29 | import java.util.Optional; 30 | 31 | import com.yasuenag.ffmasm.AsmBuilder; 32 | import com.yasuenag.ffmasm.CodeSegment; 33 | import com.yasuenag.ffmasm.NativeRegister; 34 | import com.yasuenag.ffmasm.aarch64.IndexClass; 35 | import com.yasuenag.ffmasm.aarch64.Register; 36 | 37 | 38 | @EnabledOnOs(architectures = {"aarch64"}) 39 | public class NativeRegisterTest{ 40 | 41 | public native int test(int arg); 42 | 43 | @Test 44 | @EnabledOnOs({OS.LINUX}) 45 | public void testNativeRegister(){ 46 | try(var seg = new CodeSegment()){ 47 | var desc = FunctionDescriptor.of( 48 | ValueLayout.JAVA_INT, // return value 49 | ValueLayout.JAVA_INT, // 1st arg (JNIEnv *) 50 | ValueLayout.JAVA_INT, // 2nd arg (jobject) 51 | ValueLayout.JAVA_INT // 3rd arg (arg1 of caller) 52 | ); 53 | var stub = new AsmBuilder.AArch64(seg, desc) 54 | /* stp x29, x30, [sp, #-16]! */ .stp(Register.X29, Register.X30, Register.SP, IndexClass.PreIndex, -16) 55 | /* mov x29, sp */ .mov(Register.X29, Register.SP) 56 | /* mov x0, x2 */ .mov(Register.X0, Register.X2) 57 | /* ldp x29, x30, [sp], #16 */ .ldp(Register.X29, Register.X30, Register.SP, IndexClass.PostIndex, 16) 58 | /* ret */ .ret(Optional.empty()) 59 | .getMemorySegment(); 60 | 61 | var method = this.getClass() 62 | .getMethod("test", int.class); 63 | 64 | var methodMap = Map.of(method, stub); 65 | var register = NativeRegister.create(this.getClass()); 66 | register.registerNatives(methodMap); 67 | 68 | final int expected = 100; 69 | int actual = test(expected); 70 | Assertions.assertEquals(expected, actual); 71 | } 72 | catch(Throwable t){ 73 | Assertions.fail(t); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/amd64/NativeRegisterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.amd64; 20 | 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.condition.EnabledOnOs; 24 | import org.junit.jupiter.api.condition.OS; 25 | 26 | import java.lang.foreign.FunctionDescriptor; 27 | import java.lang.foreign.ValueLayout; 28 | import java.util.Map; 29 | import java.util.OptionalInt; 30 | 31 | import com.yasuenag.ffmasm.AsmBuilder; 32 | import com.yasuenag.ffmasm.CodeSegment; 33 | import com.yasuenag.ffmasm.NativeRegister; 34 | import com.yasuenag.ffmasm.amd64.Register; 35 | 36 | 37 | @EnabledOnOs(architectures = {"amd64"}) 38 | public class NativeRegisterTest extends TestBase{ 39 | 40 | public native int test(int arg); 41 | 42 | @Test 43 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 44 | public void testNativeRegister(){ 45 | try(var seg = new CodeSegment()){ 46 | var desc = FunctionDescriptor.of( 47 | ValueLayout.JAVA_INT, // return value 48 | ValueLayout.JAVA_INT, // 1st arg (JNIEnv *) 49 | ValueLayout.JAVA_INT, // 2nd arg (jobject) 50 | ValueLayout.JAVA_INT // 3rd arg (arg1 of caller) 51 | ); 52 | var stub = new AsmBuilder.AMD64(seg, desc) 53 | /* push %rbp */ .push(Register.RBP) 54 | /* mov %rsp, %rbp */ .movRM(Register.RBP, Register.RSP, OptionalInt.empty()) 55 | /* mov %arg3, retReg */ .movMR(argReg.arg3(), argReg.returnReg(), OptionalInt.empty()) // arg1 in Java is arg3 in native 56 | /* leave */ .leave() 57 | /* ret */ .ret() 58 | .getMemorySegment(); 59 | 60 | var method = this.getClass() 61 | .getMethod("test", int.class); 62 | 63 | var methodMap = Map.of(method, stub); 64 | var register = NativeRegister.create(this.getClass()); 65 | register.registerNatives(methodMap); 66 | 67 | final int expected = 100; 68 | int actual = test(expected); 69 | Assertions.assertEquals(expected, actual); 70 | } 71 | catch(Throwable t){ 72 | Assertions.fail(t); 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/amd64/SSEAsmTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.amd64; 20 | 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.condition.EnabledOnOs; 24 | import org.junit.jupiter.api.condition.OS; 25 | 26 | import java.lang.foreign.Arena; 27 | import java.lang.foreign.FunctionDescriptor; 28 | import java.lang.foreign.MemorySegment; 29 | import java.lang.foreign.ValueLayout; 30 | import java.util.OptionalInt; 31 | 32 | import com.yasuenag.ffmasm.AsmBuilder; 33 | import com.yasuenag.ffmasm.CodeSegment; 34 | import com.yasuenag.ffmasm.amd64.Register; 35 | 36 | 37 | @EnabledOnOs(architectures = {"amd64"}) 38 | public class SSEAsmTest extends TestBase{ 39 | 40 | /** 41 | * Tests MOVDQA A/B 42 | */ 43 | @Test 44 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 45 | public void testMOVDQA(){ 46 | try(var seg = new CodeSegment()){ 47 | var desc = FunctionDescriptor.ofVoid( 48 | ValueLayout.ADDRESS, // 1st argument 49 | ValueLayout.ADDRESS // 2nd argument 50 | ); 51 | var method = new AsmBuilder.SSE(seg, desc) 52 | /* push %rbp */ .push(Register.RBP) 53 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 54 | /* movdqa (arg1), %xmm0 */ .movdqaRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0)) 55 | /* movdqa %xmm0, (arg2) */ .movdqaMR(Register.XMM0, argReg.arg2(), OptionalInt.of(0)) 56 | /* leave */ .leave() 57 | /* ret */ .ret() 58 | .build(); 59 | 60 | long[] expected = new long[]{1, 2}; // 64 * 2 = 128 bit 61 | var arena = Arena.ofAuto(); 62 | MemorySegment src = arena.allocate(16, 16); // 128 bit 63 | MemorySegment dest = arena.allocate(16, 16); // 128 bit 64 | MemorySegment.copy(expected, 0, src, ValueLayout.JAVA_LONG, 0, expected.length); 65 | 66 | method.invoke(src, dest); 67 | 68 | Assertions.assertArrayEquals(expected, src.toArray(ValueLayout.JAVA_LONG)); 69 | Assertions.assertArrayEquals(expected, dest.toArray(ValueLayout.JAVA_LONG)); 70 | } 71 | catch(Throwable t){ 72 | Assertions.fail(t); 73 | } 74 | } 75 | 76 | /** 77 | * Tests MOVDQU A/B 78 | */ 79 | @Test 80 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 81 | public void testMOVDQU(){ 82 | try(var seg = new CodeSegment()){ 83 | var desc = FunctionDescriptor.ofVoid( 84 | ValueLayout.ADDRESS, // 1st argument 85 | ValueLayout.ADDRESS // 2nd argument 86 | ); 87 | var method = new AsmBuilder.SSE(seg, desc) 88 | /* push %rbp */ .push(Register.RBP) 89 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 90 | /* movdqu (arg1), %xmm0 */ .movdquRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0)) 91 | /* movdqu %xmm0, (arg2) */ .movdquMR(Register.XMM0, argReg.arg2(), OptionalInt.of(0)) 92 | /* leave */ .leave() 93 | /* ret */ .ret() 94 | .build(); 95 | 96 | long[] expected = new long[]{1, 2}; // 64 * 2 = 128 bit 97 | var arena = Arena.ofAuto(); 98 | MemorySegment src = arena.allocate(16, 16); // 128 bit 99 | MemorySegment dest = arena.allocate(16, 16); // 128 bit 100 | MemorySegment.copy(expected, 0, src, ValueLayout.JAVA_LONG, 0, expected.length); 101 | 102 | method.invoke(src, dest); 103 | 104 | Assertions.assertArrayEquals(expected, src.toArray(ValueLayout.JAVA_LONG)); 105 | Assertions.assertArrayEquals(expected, dest.toArray(ValueLayout.JAVA_LONG)); 106 | } 107 | catch(Throwable t){ 108 | Assertions.fail(t); 109 | } 110 | } 111 | 112 | /** 113 | * Tests MOVD A 114 | */ 115 | @Test 116 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 117 | public void testMOVD_A(){ 118 | try(var seg = new CodeSegment()){ 119 | var desc = FunctionDescriptor.of( 120 | ValueLayout.JAVA_FLOAT, // return value 121 | ValueLayout.ADDRESS // 1st argument 122 | ); 123 | var method = new AsmBuilder.SSE(seg, desc) 124 | /* push %rbp */ .push(Register.RBP) 125 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 126 | /* movd (arg1), %xmm0 */ .movdRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0)) 127 | /* leave */ .leave() 128 | /* ret */ .ret() 129 | .build(); 130 | 131 | float expected = 1.1f; 132 | var arena = Arena.ofAuto(); 133 | MemorySegment src = arena.allocate(ValueLayout.JAVA_FLOAT); 134 | src.set(ValueLayout.JAVA_FLOAT, 0, expected); 135 | 136 | float actual = (float)method.invoke(src); 137 | 138 | Assertions.assertEquals(expected, actual); 139 | } 140 | catch(Throwable t){ 141 | Assertions.fail(t); 142 | } 143 | } 144 | 145 | /** 146 | * Tests MOVD B 147 | */ 148 | @Test 149 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 150 | public void testMOVD_B(){ 151 | try(var seg = new CodeSegment()){ 152 | var arena = Arena.ofAuto(); 153 | MemorySegment dest = arena.allocate(ValueLayout.JAVA_FLOAT); 154 | 155 | var desc = FunctionDescriptor.ofVoid( 156 | ValueLayout.JAVA_FLOAT // 1st argument 157 | ); 158 | var method = new AsmBuilder.SSE(seg, desc) 159 | /* push %rbp */ .push(Register.RBP) 160 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 161 | // Mixed argument order (int, fp) is different between Windows and Linux. 162 | // Thus address is loaded from immediate value. 163 | /* mov addr, %rax */ .movImm(Register.RAX, dest.address()) 164 | /* movd %xmm0, (%rax) */ .movdMR(Register.XMM0, Register.RAX, OptionalInt.of(0)) 165 | /* leave */ .leave() 166 | /* ret */ .ret() 167 | .build(); 168 | 169 | float expected = 1.1f; 170 | 171 | method.invoke(expected); 172 | float actual = dest.get(ValueLayout.JAVA_FLOAT, 0); 173 | 174 | Assertions.assertEquals(expected, actual); 175 | } 176 | catch(Throwable t){ 177 | Assertions.fail(t); 178 | } 179 | } 180 | 181 | /** 182 | * Tests MOVQ A 183 | */ 184 | @Test 185 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 186 | public void testMOVQ_A(){ 187 | try(var seg = new CodeSegment()){ 188 | var desc = FunctionDescriptor.of( 189 | ValueLayout.JAVA_DOUBLE, // return value 190 | ValueLayout.ADDRESS // 1st argument 191 | ); 192 | var method = new AsmBuilder.SSE(seg, desc) 193 | /* push %rbp */ .push(Register.RBP) 194 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 195 | /* movq (arg1), %xmm0 */ .movqRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0)) 196 | /* leave */ .leave() 197 | /* ret */ .ret() 198 | .build(); 199 | 200 | double expected = 1.1d; 201 | var arena = Arena.ofAuto(); 202 | MemorySegment src = arena.allocate(ValueLayout.JAVA_DOUBLE); 203 | src.set(ValueLayout.JAVA_DOUBLE, 0, expected); 204 | 205 | double actual = (double)method.invoke(src); 206 | 207 | Assertions.assertEquals(expected, actual); 208 | } 209 | catch(Throwable t){ 210 | Assertions.fail(t); 211 | } 212 | } 213 | 214 | /** 215 | * Tests MOVQ B 216 | */ 217 | @Test 218 | @EnabledOnOs({OS.LINUX, OS.WINDOWS}) 219 | public void testMOVQ_B(){ 220 | try(var seg = new CodeSegment()){ 221 | var arena = Arena.ofAuto(); 222 | MemorySegment dest = arena.allocate(ValueLayout.JAVA_DOUBLE); 223 | 224 | var desc = FunctionDescriptor.ofVoid( 225 | ValueLayout.JAVA_DOUBLE // 1st argument 226 | ); 227 | var method = new AsmBuilder.SSE(seg, desc) 228 | /* push %rbp */ .push(Register.RBP) 229 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty()) 230 | // Mixed argument order (int, fp) is different between Windows and Linux. 231 | // Thus address is loaded from immediate value. 232 | /* mov addr, %rax */ .movImm(Register.RAX, dest.address()) 233 | /* movq %xmm0, (%rax) */ .movqMR(Register.XMM0, Register.RAX, OptionalInt.of(0)) 234 | /* leave */ .leave() 235 | /* ret */ .ret() 236 | .build(); 237 | 238 | double expected = 1.1d; 239 | 240 | method.invoke(expected); 241 | double actual = dest.get(ValueLayout.JAVA_DOUBLE, 0); 242 | 243 | Assertions.assertEquals(expected, actual); 244 | } 245 | catch(Throwable t){ 246 | Assertions.fail(t); 247 | } 248 | } 249 | 250 | } 251 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/amd64/TestBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.amd64; 20 | 21 | import java.io.IOException; 22 | import java.lang.foreign.Arena; 23 | import java.lang.foreign.FunctionDescriptor; 24 | import java.lang.foreign.ValueLayout; 25 | import java.lang.invoke.MethodHandle; 26 | import java.util.OptionalInt; 27 | 28 | import org.junit.jupiter.api.BeforeAll; 29 | 30 | import com.yasuenag.ffmasm.AsmBuilder; 31 | import com.yasuenag.ffmasm.CodeSegment; 32 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 33 | import com.yasuenag.ffmasm.amd64.Register; 34 | 35 | 36 | public class TestBase{ 37 | 38 | public static record ArgRegister( 39 | Register arg1, 40 | Register arg2, 41 | Register arg3, 42 | Register arg4, 43 | Register returnReg 44 | ){} 45 | 46 | protected static ArgRegister argReg; 47 | 48 | private static boolean isAVX; 49 | 50 | private static boolean isCLFLUSHOPT; 51 | 52 | private static MethodHandle generateCPUID(CodeSegment seg) throws UnsupportedPlatformException{ 53 | var desc = FunctionDescriptor.ofVoid(ValueLayout.JAVA_INT, /* eax */ 54 | ValueLayout.JAVA_INT, /* ecx */ 55 | ValueLayout.ADDRESS /* result */ 56 | ); 57 | return new AsmBuilder.AMD64(seg, desc) 58 | /* push %rbp */ .push(Register.RBP) 59 | /* mov %rsp, %rbp */ .movRM(Register.RBP, Register.RSP, OptionalInt.empty()) 60 | /* push %rbx */ .push(Register.RBX) 61 | /* mov arg1, %rax */ .movRM(Register.RAX, argReg.arg1(), OptionalInt.empty()) 62 | /* mov arg2, %rcx */ .movRM(Register.RCX, argReg.arg2(), OptionalInt.empty()) 63 | /* mov arg3, %r11 */ .movRM(Register.R11, argReg.arg3(), OptionalInt.empty()) 64 | /* cpuid */ .cpuid() 65 | /* mov %eax, (r11) */ .movMR(Register.EAX, Register.R11, OptionalInt.of(0)) 66 | /* mov %ebx, 4(r11) */ .movMR(Register.EBX, Register.R11, OptionalInt.of(4)) 67 | /* mov %ecx, 8(r11) */ .movMR(Register.ECX, Register.R11, OptionalInt.of(8)) 68 | /* mov %ecx, 12(r11) */ .movMR(Register.EDX, Register.R11, OptionalInt.of(12)) 69 | /* pop %rbx */ .pop(Register.RBX, OptionalInt.empty()) 70 | /* leave */ .leave() 71 | /* ret */ .ret() 72 | .build(); 73 | } 74 | 75 | @BeforeAll 76 | public static void init(){ 77 | String osName = System.getProperty("os.name"); 78 | if(osName.equals("Linux")){ 79 | argReg = new ArgRegister(Register.RDI, Register.RSI, Register.RDX, Register.RCX, Register.RAX); 80 | } 81 | else if(osName.startsWith("Windows")){ 82 | argReg = new ArgRegister(Register.RCX, Register.RDX, Register.R8, Register.R9, Register.RAX); 83 | } 84 | else{ 85 | throw new RuntimeException(new UnsupportedPlatformException(osName)); 86 | }; 87 | 88 | try(var seg = new CodeSegment(); 89 | var arena = Arena.ofConfined();){ 90 | var cpuid = generateCPUID(seg); 91 | var cpuidVals = arena.allocate(ValueLayout.JAVA_INT, 4); 92 | 93 | // check AVX 94 | cpuid.invokeExact(1, 0, cpuidVals); 95 | isAVX = ((cpuidVals.getAtIndex(ValueLayout.JAVA_INT, 2) >>> 28) & 0x1) == 1; // ecx 96 | 97 | // check CLFLUSHOPT 98 | cpuid.invokeExact(7, 0, cpuidVals); 99 | isCLFLUSHOPT = ((cpuidVals.getAtIndex(ValueLayout.JAVA_INT, 1) >>> 23) & 0x1) == 1; // ebx 100 | } 101 | catch(Throwable t){ 102 | throw new RuntimeException(t); 103 | } 104 | } 105 | 106 | public static boolean supportAVX(){ 107 | return isAVX; 108 | } 109 | 110 | public static boolean supportCLFLUSHOPT(){ 111 | return isCLFLUSHOPT; 112 | } 113 | 114 | /** 115 | * Show PID, address of CodeSegment, then waits stdin input. 116 | */ 117 | public void showDebugMessage(CodeSegment seg) throws IOException{ 118 | System.out.println("PID: " + ProcessHandle.current().pid()); 119 | System.out.println("Addr: 0x" + Long.toHexString(seg.getAddr().address())); 120 | System.in.read(); 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/common/AsmBuilderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.common; 20 | 21 | import org.junit.jupiter.api.AfterAll; 22 | import org.junit.jupiter.api.Assertions; 23 | import org.junit.jupiter.api.BeforeAll; 24 | import org.junit.jupiter.api.Test; 25 | import org.junit.jupiter.api.condition.EnabledOnOs; 26 | 27 | import com.yasuenag.ffmasm.AsmBuilder; 28 | import com.yasuenag.ffmasm.CodeSegment; 29 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 30 | import com.yasuenag.ffmasm.amd64.AMD64AsmBuilder; 31 | import com.yasuenag.ffmasm.amd64.SSEAsmBuilder; 32 | import com.yasuenag.ffmasm.amd64.AVXAsmBuilder; 33 | import com.yasuenag.ffmasm.aarch64.AArch64AsmBuilder; 34 | 35 | 36 | public class AsmBuilderTest{ 37 | 38 | private static CodeSegment seg; 39 | 40 | @BeforeAll 41 | public static void init(){ 42 | try{ 43 | seg = new CodeSegment(); 44 | } 45 | catch(Exception e){ 46 | throw new Error(e); 47 | } 48 | } 49 | 50 | @AfterAll 51 | public static void tearDown(){ 52 | try{ 53 | seg.close(); 54 | } 55 | catch(Exception e){ 56 | // Do nothing 57 | } 58 | } 59 | 60 | @Test 61 | @EnabledOnOs(architectures = {"amd64"}) 62 | public void testAArch64AsmBuilderOnAMD64() throws UnsupportedPlatformException{ 63 | Assertions.assertThrows(UnsupportedPlatformException.class, () -> new AsmBuilder.AArch64(seg)); 64 | } 65 | 66 | @Test 67 | @EnabledOnOs(architectures = {"aarch64"}) 68 | public void testAMD64AsmBuilderOnAArch64(){ 69 | Assertions.assertThrows(UnsupportedPlatformException.class, () -> new AsmBuilder.AMD64(seg)); 70 | } 71 | 72 | @Test 73 | @EnabledOnOs(architectures = {"aarch64"}) 74 | public void testSSEAsmBuilderOnAArch64(){ 75 | Assertions.assertThrows(UnsupportedPlatformException.class, () -> new AsmBuilder.SSE(seg)); 76 | } 77 | 78 | @Test 79 | @EnabledOnOs(architectures = {"aarch64"}) 80 | public void testAVXAsmBuilderOnAArch64(){ 81 | Assertions.assertThrows(UnsupportedPlatformException.class, () -> new AsmBuilder.AVX(seg)); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/common/CodeSegmentTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.common; 20 | 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import com.yasuenag.ffmasm.CodeSegment; 25 | 26 | 27 | public class CodeSegmentTest{ 28 | 29 | @Test 30 | public void testAlignment(){ 31 | try(var seg = new CodeSegment()){ 32 | seg.alignTo16Bytes(); 33 | Assertions.assertEquals((byte)0, (byte)(seg.getTail() & 0xf), "Memory is not aligned: " + Long.toHexString(seg.getAddr().address() + seg.getTail())); 34 | } 35 | catch(Throwable t){ 36 | Assertions.fail(t); 37 | } 38 | } 39 | 40 | @Test 41 | public void testMethodInfoString(){ 42 | var info = new CodeSegment.MethodInfo("func", 0x1234, 0xff); 43 | Assertions.assertEquals("0x1234 0xff func", info.toString()); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/common/JitDumpTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2024, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.common; 20 | 21 | import java.io.File; 22 | import java.nio.ByteBuffer; 23 | import java.nio.ByteOrder; 24 | import java.nio.channels.FileChannel; 25 | import java.nio.file.Files; 26 | import java.nio.file.Path; 27 | import java.nio.file.StandardOpenOption; 28 | 29 | import org.junit.jupiter.api.Assertions; 30 | import org.junit.jupiter.api.Test; 31 | import org.junit.jupiter.api.condition.EnabledOnOs; 32 | import org.junit.jupiter.api.condition.OS; 33 | 34 | import com.yasuenag.ffmasm.CodeSegment; 35 | import com.yasuenag.ffmasm.JitDump; 36 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 37 | import com.yasuenag.ffmasm.internal.linux.PerfJitDump; 38 | 39 | 40 | public class JitDumpTest{ 41 | 42 | private File getDumpFileInstance(){ 43 | return new File(String.format("/tmp/jit-%d.dump", ProcessHandle.current().pid())); 44 | } 45 | 46 | @Test 47 | @EnabledOnOs(OS.LINUX) 48 | public void testGetInstanceOnLinux(){ 49 | try(var jitdump = JitDump.getInstance(Path.of("/tmp"))){ 50 | Assertions.assertEquals(PerfJitDump.class, jitdump.getClass()); 51 | } 52 | catch(Exception e){ 53 | Assertions.fail(e); 54 | } 55 | 56 | var dumpfile = getDumpFileInstance(); 57 | Assertions.assertTrue(dumpfile.exists()); 58 | 59 | // cleanup 60 | dumpfile.delete(); 61 | } 62 | 63 | @Test 64 | @EnabledOnOs(OS.LINUX) 65 | public void testMemoryPermissions(){ 66 | var dumpfile = getDumpFileInstance(); 67 | String line = null; 68 | 69 | try(var jitdump = JitDump.getInstance(Path.of("/tmp"))){ 70 | try(var lines = Files.lines(Path.of("/proc/self/maps"))){ 71 | line = lines.filter(s -> s.endsWith(dumpfile.getPath())) 72 | .findFirst() 73 | .get(); 74 | } 75 | } 76 | catch(Exception e){ 77 | Assertions.fail(e); 78 | } 79 | 80 | Assertions.assertEquals("r-xp", line.split("\\s+")[1]); 81 | 82 | // cleanup 83 | dumpfile.delete(); 84 | } 85 | 86 | @Test 87 | @EnabledOnOs(OS.LINUX) 88 | public void testJitDumpHeader(){ 89 | var dumpfile = getDumpFileInstance(); 90 | 91 | try(var jitdump = JitDump.getInstance(Path.of("/tmp"))){ 92 | // Create jitdump file. Do nothing at here. 93 | } 94 | catch(Exception e){ 95 | Assertions.fail(e); 96 | } 97 | 98 | try(var ch = FileChannel.open(dumpfile.toPath(), StandardOpenOption.READ)){ 99 | final int headerSize = 40; // sizeof(struct jitheader) 100 | final int closeHeaderSize = 16; // sizeof(jr_code_close) 101 | Assertions.assertEquals(headerSize + closeHeaderSize, ch.size()); 102 | 103 | var buf = ByteBuffer.allocate(headerSize) 104 | .order(ByteOrder.nativeOrder()); 105 | ch.read(buf); 106 | buf.flip(); 107 | 108 | // magic 109 | Assertions.assertEquals(0x4A695444, buf.getInt()); 110 | // version 111 | Assertions.assertEquals(1, buf.getInt()); 112 | // total_size 113 | Assertions.assertEquals(headerSize, buf.getInt()); 114 | // elf_mach (skip) 115 | buf.position(buf.position() + 4); 116 | // pad1 117 | Assertions.assertEquals(0, buf.getInt()); 118 | // pid 119 | Assertions.assertEquals((int)ProcessHandle.current().pid(), buf.getInt()); 120 | // timestamp (skip) 121 | buf.position(buf.position() + 8); 122 | // flag 123 | Assertions.assertEquals(0L, buf.getLong()); 124 | 125 | buf = ByteBuffer.allocate(closeHeaderSize) 126 | .order(ByteOrder.nativeOrder()); 127 | ch.read(buf); 128 | buf.flip(); 129 | 130 | // id 131 | Assertions.assertEquals(3, buf.getInt()); 132 | // total_size 133 | Assertions.assertEquals(closeHeaderSize, buf.getInt()); 134 | } 135 | catch(Exception e){ 136 | Assertions.fail(e); 137 | } 138 | 139 | // cleanup 140 | dumpfile.delete(); 141 | } 142 | 143 | @Test 144 | @EnabledOnOs(OS.LINUX) 145 | public void testJitDumpFunctionEntry(){ 146 | var dumpfile = getDumpFileInstance(); 147 | var info = new CodeSegment.MethodInfo("func", 0x1234, 0); 148 | 149 | try(var jitdump = JitDump.getInstance(Path.of("/tmp"))){ 150 | jitdump.writeFunction(info); 151 | } 152 | catch(Exception e){ 153 | Assertions.fail(e); 154 | } 155 | 156 | try(var ch = FileChannel.open(dumpfile.toPath(), StandardOpenOption.READ)){ 157 | final int fileHeaderSize = 40; // sizeof(struct jitheader) 158 | final int functionEntrySize = 56 + info.name().length() + 1 + info.size(); // sizeof(jr_code_load) == 56, null char of method name should be included. 159 | 160 | var buf = ByteBuffer.allocate(functionEntrySize) 161 | .order(ByteOrder.nativeOrder()); 162 | 163 | // Skip file header 164 | ch.position(fileHeaderSize); 165 | 166 | // Read function entry 167 | ch.read(buf); 168 | buf.flip(); 169 | 170 | // id 171 | Assertions.assertEquals(0, buf.getInt()); 172 | // total_size 173 | Assertions.assertEquals(functionEntrySize, buf.getInt()); 174 | // timestamp (skip) 175 | buf.position(buf.position() + 8); 176 | // pid 177 | Assertions.assertEquals((int)ProcessHandle.current().pid(), buf.getInt()); 178 | // tid (skip) 179 | buf.position(buf.position() + 4); 180 | // vma 181 | Assertions.assertEquals(0x1234L, buf.getLong()); 182 | // code_addr 183 | Assertions.assertEquals(0x1234L, buf.getLong()); 184 | // code_size 185 | Assertions.assertEquals(0L, buf.getLong()); 186 | // code_index 187 | Assertions.assertEquals(0L, buf.getLong()); 188 | // function name 189 | byte[] nameInBytes = new byte[info.name().length()]; 190 | buf.get(nameInBytes); 191 | Assertions.assertEquals("func", new String(nameInBytes)); 192 | Assertions.assertEquals((byte)0, buf.get()); 193 | } 194 | catch(Exception e){ 195 | Assertions.fail(e); 196 | } 197 | 198 | // cleanup 199 | dumpfile.delete(); 200 | } 201 | 202 | @Test 203 | @EnabledOnOs(OS.WINDOWS) 204 | public void testGetInstanceOnWindows(){ 205 | Assertions.assertThrows(UnsupportedPlatformException.class, () -> JitDump.getInstance(Path.of("C:\\tmp"))); 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /src/test/java/com/yasuenag/ffmasm/test/linux/CodeSegmentTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2022, 2023, Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasm.test.linux; 20 | 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.condition.EnabledOnOs; 24 | import org.junit.jupiter.api.condition.OS; 25 | 26 | import java.io.IOException; 27 | import java.lang.ref.Cleaner; 28 | import java.nio.file.Files; 29 | import java.nio.file.Path; 30 | import java.util.NoSuchElementException; 31 | 32 | import com.yasuenag.ffmasm.CodeSegment; 33 | import com.yasuenag.ffmasm.PlatformException; 34 | import com.yasuenag.ffmasm.UnsupportedPlatformException; 35 | 36 | 37 | @EnabledOnOs(OS.LINUX) 38 | public class CodeSegmentTest{ 39 | 40 | private String[] findMemorySegmentFromMaps(long startAddr, long endAddr) throws IOException{ 41 | String startAddrInStr = Long.toHexString(startAddr); 42 | String endAddrInStr = Long.toHexString(endAddr); 43 | try(var stream = Files.lines(Path.of("/proc/self/maps"))){ 44 | return stream.map(l -> l.split(" ")) 45 | .filter(l -> l[0].startsWith(startAddrInStr) || l[0].endsWith(endAddrInStr)) 46 | .findAny() 47 | .get(); 48 | } 49 | } 50 | 51 | @Test 52 | public void testAllocateCodeSegmentWithDefaultSize(){ 53 | try(var seg = new CodeSegment()){ 54 | var addr = seg.getAddr(); 55 | long startAddr = addr.address(); 56 | long endAddr = startAddr + 4096L; 57 | 58 | var entries = findMemorySegmentFromMaps(startAddr, endAddr); 59 | long actualStartAddr = Long.parseLong(entries[0].split("-")[0], 16); 60 | long actualEndAddr = Long.parseLong(entries[0].split("-")[1], 16); 61 | char execBit = entries[1].charAt(2); 62 | 63 | Assertions.assertTrue((actualEndAddr - actualStartAddr) >= 4096L); 64 | Assertions.assertEquals('x', execBit); 65 | } 66 | catch(Throwable t){ 67 | Assertions.fail(t); 68 | } 69 | } 70 | 71 | @Test 72 | public void testAllocateCodeSegmentWithGivenSize(){ 73 | final long size = 8192L; 74 | 75 | try(var seg = new CodeSegment(size)){ 76 | var addr = seg.getAddr(); 77 | long startAddr = addr.address(); 78 | long endAddr = startAddr + size; 79 | 80 | var entries = findMemorySegmentFromMaps(startAddr, endAddr); 81 | long actualStartAddr = Long.parseLong(entries[0].split("-")[0], 16); 82 | long actualEndAddr = Long.parseLong(entries[0].split("-")[1], 16); 83 | char execBit = entries[1].charAt(2); 84 | 85 | Assertions.assertTrue((actualEndAddr - actualStartAddr) >= size); 86 | Assertions.assertEquals('x', execBit); 87 | } 88 | catch(Throwable t){ 89 | Assertions.fail(t); 90 | } 91 | } 92 | 93 | @Test 94 | public void testCloseWithCleaner() throws PlatformException, UnsupportedPlatformException, InterruptedException{ 95 | Object obj = new Object(); 96 | var seg = new CodeSegment(); 97 | var rawAddr = seg.getAddr().address(); 98 | var action = new CodeSegment.CleanerAction(seg); 99 | Cleaner.create() 100 | .register(obj, action); 101 | 102 | // Release obj 103 | obj = null; 104 | System.gc(); 105 | 106 | // Attempt to give opportunity to work Clerner 107 | Thread.yield(); 108 | Thread.sleep(3000); 109 | 110 | // Check memory mapping whether region for CodeSegment is released. 111 | Assertions.assertThrows(NoSuchElementException.class, () -> { 112 | try(var stream = Files.lines(Path.of("/proc/self/maps"))){ 113 | stream.filter(l -> l.startsWith(Long.toHexString(rawAddr))) 114 | .findAny() 115 | .get(); 116 | } 117 | }); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /tools/disas/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 21 | 22 | 23 | 4.0.0 24 | 25 | com.yasuenag 26 | ffmasm-disassembler 27 | 0.1.1 28 | jar 29 | 30 | ffmasm-disassembler 31 | 32 | 33 | scm:git:git://github.com/YaSuenag/ffmasm.git 34 | scm:git:ssh://github.com:YaSuenag/ffmasm.git 35 | https://github.com/YaSuenag/ffmasm 36 | 37 | 38 | 39 | UTF-8 40 | 22 41 | 22 42 | 43 | 44 | 45 | 46 | 47 | org.apache.maven.plugins 48 | maven-compiler-plugin 49 | 3.10.1 50 | 51 | 52 | -Xlint:all 53 | 54 | true 55 | 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-javadoc-plugin 60 | 3.4.1 61 | 62 | 63 | 64 | 65 | 66 | 67 | github 68 | GitHub Packages 69 | https://maven.pkg.github.com/YaSuenag/ffmasm 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /tools/disas/src/main/java/com/yasuenag/ffmasmtools/disas/Disassembler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | package com.yasuenag.ffmasmtools.disas; 20 | 21 | import java.lang.foreign.Arena; 22 | import java.lang.foreign.FunctionDescriptor; 23 | import java.lang.foreign.Linker; 24 | import java.lang.foreign.MemorySegment; 25 | import java.lang.foreign.SymbolLookup; 26 | import java.lang.foreign.ValueLayout; 27 | import java.lang.invoke.MethodHandle; 28 | import java.nio.file.Path; 29 | import java.util.regex.Pattern; 30 | 31 | 32 | public class Disassembler{ 33 | 34 | private static final MethodHandle decode_instructions_virtual; 35 | private static final MemorySegment disasOptions; 36 | 37 | private static Path getHSDISPath(){ 38 | var prop = System.getProperty("hsdis"); 39 | if(prop != null){ 40 | return Path.of(prop); 41 | } 42 | 43 | String libName = "hsdis-" + System.getProperty("os.arch"); 44 | var osName = System.getProperty("os.name"); 45 | if(osName.equals("Linux")){ 46 | libName += ".so"; 47 | } 48 | else if(osName.startsWith("Windows")){ 49 | libName += ".dll"; 50 | } 51 | else{ 52 | throw new RuntimeException(osName); 53 | } 54 | 55 | var javaHome = Path.of(System.getProperty("java.home")); 56 | var vmMatcher = Pattern.compile("^.+ ([^ ]+) VM$").matcher(System.getProperty("java.vm.name")); 57 | vmMatcher.find(); 58 | var vmType = vmMatcher.group(1).toLowerCase(); 59 | // Search order of hsdis: 60 | // 1. /lib//libhsdis-.so 61 | // 2. /lib//hsdis-.so 62 | // 3. /lib/hsdis-.so 63 | // 4. hsdis-.so (using LD_LIBRARY_PATH) 64 | // See src/hotspot/share/compiler/disassembler.cpp in OpenJDK for details. 65 | Path p = javaHome.resolve("lib", vmType, "lib" + libName); 66 | if(p.toFile().exists()){ 67 | return p; 68 | } 69 | else{ 70 | p = javaHome.resolve("lib", vmType, libName); 71 | if(p.toFile().exists()){ 72 | return p; 73 | } 74 | else{ 75 | p = javaHome.resolve("lib", libName); 76 | if(p.toFile().exists()){ 77 | return p; 78 | } 79 | } 80 | } 81 | return Path.of(libName); 82 | } 83 | 84 | static{ 85 | var hsdisPath = getHSDISPath(); 86 | var sym = SymbolLookup.libraryLookup(hsdisPath, Arena.ofAuto()); 87 | var disas = sym.find("decode_instructions_virtual").get(); 88 | var desc = FunctionDescriptor.of(ValueLayout.ADDRESS, // return value 89 | ValueLayout.ADDRESS, // start_va 90 | ValueLayout.ADDRESS, // end_va 91 | ValueLayout.ADDRESS, // buffer 92 | ValueLayout.JAVA_LONG, // length 93 | ValueLayout.ADDRESS, // event_callback 94 | ValueLayout.ADDRESS, // event_stream 95 | ValueLayout.ADDRESS, // printf_callback 96 | ValueLayout.ADDRESS, // printf_stream 97 | ValueLayout.ADDRESS, // options 98 | ValueLayout.JAVA_INT // newline 99 | ); 100 | decode_instructions_virtual = Linker.nativeLinker() 101 | .downcallHandle(disas, desc); 102 | disasOptions = Arena.ofAuto() 103 | .allocateFrom(""); 104 | } 105 | 106 | public static void dumpToStdout(MemorySegment code){ 107 | try{ 108 | decode_instructions_virtual.invoke( 109 | code, // start_va 110 | code.asSlice(code.byteSize()), // end_va 111 | code, // buffer 112 | code.byteSize(), // length 113 | MemorySegment.NULL, // event_callback 114 | MemorySegment.NULL, // event_stream 115 | MemorySegment.NULL, // printf_callback 116 | MemorySegment.NULL, // printf_stream 117 | disasOptions, // options 118 | 1 // newline 119 | ); 120 | } 121 | catch(Throwable t){ 122 | throw new RuntimeException(t); 123 | } 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /tools/disas/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2025 Yasumasa Suenaga 3 | * 4 | * This file is part of ffmasm. 5 | * 6 | * ffmasm is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * ffmasm is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with ffmasm. If not, see . 18 | */ 19 | module com.yasuenag.ffmasmtools.disas { 20 | exports com.yasuenag.ffmasmtools.disas; 21 | } 22 | --------------------------------------------------------------------------------