├── .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 | 
5 | 
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 |
--------------------------------------------------------------------------------