├── .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
├── 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
│ │ │ ├── CodeSegment.java
│ │ │ ├── JitDump.java
│ │ │ ├── NativeRegister.java
│ │ │ ├── PlatformException.java
│ │ │ ├── UnsupportedPlatformException.java
│ │ │ ├── amd64
│ │ │ ├── AMD64AsmBuilder.java
│ │ │ ├── AVXAsmBuilder.java
│ │ │ ├── Register.java
│ │ │ └── SSEAsmBuilder.java
│ │ │ └── internal
│ │ │ ├── ExecMemory.java
│ │ │ ├── JavaVM.java
│ │ │ ├── JniEnv.java
│ │ │ ├── JvmtiEnv.java
│ │ │ ├── amd64
│ │ │ ├── AMD64NativeRegister.java
│ │ │ └── CallingRegisters.java
│ │ │ ├── linux
│ │ │ ├── LinuxExecMemory.java
│ │ │ └── PerfJitDump.java
│ │ │ └── windows
│ │ │ └── WindowsExecMemory.java
│ │ └── module-info.java
└── test
│ └── java
│ └── com
│ └── yasuenag
│ └── ffmasm
│ └── test
│ ├── amd64
│ ├── AVXAsmTest.java
│ ├── AsmTest.java
│ ├── NativeRegisterTest.java
│ ├── SSEAsmTest.java
│ └── TestBase.java
│ ├── common
│ ├── 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: [ ubuntu-latest, windows-latest ]
14 |
15 | runs-on: ${{ matrix.os }}
16 |
17 | name: Run Maven on ${{ matrix.os }}
18 |
19 | steps:
20 | - name: 'Checkout repository'
21 | uses: actions/checkout@v4
22 |
23 | - name: Setup Java
24 | uses: actions/setup-java@v4
25 | with:
26 | distribution: oracle
27 | java-version: 22
28 | cache: maven
29 |
30 | - name: 'Run Maven'
31 | run: mvn -B test
32 |
--------------------------------------------------------------------------------
/.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 |
23 | # How to build
24 |
25 | ```
26 | $ mvn package
27 | ```
28 |
29 | ## Test for ffmasm
30 |
31 | ```bash
32 | $ mvn test
33 | ```
34 |
35 | # How to use
36 |
37 | See [Javadoc](https://yasuenag.github.io/ffmasm/) and [cpumodel](examples/cpumodel) examples.
38 |
39 | ## 1. Create `CodeSegment`
40 |
41 | `CodeSegment` is a storage for assembled code. In Linux, it would be allocated by `mmap(2)` with executable bit.
42 | 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:
43 |
44 | ```java
45 | try(var seg = new CodeSegment()){
46 | ...
47 | }
48 | ```
49 |
50 | ## 2. Create `MethodHandle` via `AMD64AsmBuilder`
51 |
52 | You can assemble the code via `AMD64AsmBuilder`. It would be instanciated via `create()`, and it should be passed both `CodeSegment` and [FunctionDescriptor](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/foreign/FunctionDescriptor.html).
53 |
54 | In following example, the method is defined as `(I)I` (JNI signature) in `FunctionDescriptor`.
55 | `AMD64AsmBuilder` is builder pattern, so you can add instruction in below. Following example shows method argument (`int`) would be returned straightly.
56 |
57 | You can get `MethodHandle` in result of `build()`.
58 |
59 | ```java
60 | var desc = FunctionDescriptor.of(
61 | ValueLayout.JAVA_INT, // return value
62 | ValueLayout.JAVA_INT // 1st argument
63 | );
64 |
65 | var method = AMD64AsmBuilder.create(seg, desc)
66 | /* push %rbp */ .push(Register.RBP)
67 | /* mov %rsp, %rbp */ .movRM(Register.RSP, Register.RBP, OptionalInt.empty())
68 | /* mov %rdi, %rax */ .movRM(Register.RDI, Register.RAX, OptionalInt.empty())
69 | /* leave */ .leave()
70 | /* ret */ .ret()
71 | .build(Linker.Option.critical(false));
72 | ```
73 |
74 | NOTE: [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()`.
75 |
76 | ## 3. Method call
77 |
78 | ```java
79 | int ret = (int)method.invoke(100); // "ret" should be 100
80 | ```
81 |
82 | # Debugging
83 |
84 | [ffmasm-disassembler](tools/disas) can disassemble the code in `MemorySegment` like generated by ffmasm, and dump assembly code to stdout.
85 |
86 | You can download ffmasm-dissassembler Maven package from GitHub packages: https://github.com/YaSuenag/ffmasm/packages/2370043
87 |
88 | See [examples/disas](examples/disas) for details.
89 |
90 | ## Requirements
91 |
92 | ffmasm-disassembler requires [hsdis](https://github.com/openjdk/jdk/tree/master/src/utils/hsdis).
93 |
94 | ## Generate hsdis
95 |
96 | To generate hsdis for Linux, you can use [hsdis-builder](https://github.com/YaSuenag/hsdis-builder).
97 |
98 | ## Deploy hsdis
99 |
100 | It should be deployed one of following directory (it is documented as source comment in disassembler.cpp in HotSpot):
101 |
102 | 1. `$JAVA_HOME/lib//libhsdis-.so`
103 | 2. `$JAVA_HOME/lib//hsdis-.so`
104 | 3. `$JAVA_HOME/lib/hsdis-.so`
105 | 4. `hsdis-.so` (using `LD_LIBRARY_PATH`)
106 |
107 | If you don't want to deploy hsdis into your JDK, you can specify `hsdis` system property like `-Dhsdis=/path/to/hsdis-amd64.so`
108 |
109 | ## Examples
110 |
111 | ```
112 | import com.yasuenag.ffmasmtools.disas.Disassembler;
113 |
114 | :
115 |
116 | MemorySegment rdtsc = createRDTSC(); // Generate machine code with ffmasm
117 | Disassembler.dumpToStdout(rdtsc); // Dump assembly code of `rdtsc` to stdout
118 | ```
119 |
120 | # Play with JNI
121 |
122 | You can bind native method to `MemorySegment` of ffmasm code dynamically.
123 |
124 | 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`.
125 |
126 | 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`).
127 |
128 | ```java
129 | public native int test(int arg);
130 |
131 |
132 |
133 | try(var seg = new CodeSegment()){
134 | var desc = FunctionDescriptor.of(
135 | ValueLayout.JAVA_INT, // return value
136 | ValueLayout.JAVA_INT, // 1st arg (JNIEnv *)
137 | ValueLayout.JAVA_INT, // 2nd arg (jobject)
138 | ValueLayout.JAVA_INT // 3rd arg (arg1 of caller)
139 | );
140 | var stub = AMD64AsmBuilder.create(AMD64AsmBuilder.class, seg, desc)
141 | /* push %rbp */ .push(Register.RBP)
142 | /* mov %rsp, %rbp */ .movRM(Register.RBP, Register.RSP, OptionalInt.empty())
143 | /* mov %arg3, retReg */ .movMR(argReg.arg3(), argReg.returnReg(), OptionalInt.empty()) // arg1 in Java is arg3 in native
144 | /* leave */ .leave()
145 | /* ret */ .ret()
146 | .getMemorySegment();
147 |
148 | var method = this.getClass()
149 | .getMethod("test", int.class);
150 |
151 | var methodMap = Map.of(method, stub);
152 | var register = NativeRegister.create(this.getClass());
153 | register.registerNatives(methodMap);
154 |
155 | final int expected = 100;
156 | int actual = test(expected);
157 | Assertions.assertEquals(expected, actual);
158 | }
159 | ```
160 |
161 | # Play with perf tool
162 |
163 | You can record both function name and entry point address as a perf map file.
164 |
165 | ## Record function
166 |
167 | You can pass function name into `build()` method:
168 |
169 | ```java
170 | .build("GeneratedFunc", Linker.Option.critical(true));
171 | ```
172 |
173 | Function name would be set to `` if you do not pass function name (includes calling `build(Linker.Option)`).
174 |
175 | ## Write to map file
176 |
177 | 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.
178 |
179 | You need to enable perf map dumper via `CodeSegment::enablePerfMapDumper`. Call `CodeSegment::disablePerfMapDumper` if you want to cancel the dumper.
180 |
181 | ## Generate jitdump
182 |
183 | perf tool on Linux supports JIT-generated code. ffmasm can dump generated code as a jitdump. See an [example](examples/perf) for details.
184 |
185 | ### Record assembled code as a JIT'ed code
186 |
187 | Pass `JitDump` insntace to `build` method.
188 |
189 | ```java
190 | jitdump = JitDump.getInstance(Path.of("."));
191 |
192 | :
193 |
194 | .build("GeneratedFunc", jitdump);
195 | ```
196 |
197 | Then you can run `perf record`. Note that you have to set monotonic clock with `-k` option.
198 |
199 | ```
200 | perf record -k 1 $JAVA_HOME/bin/java ...
201 | ```
202 |
203 | As a result, you would get `jit-.dump` which includes JIT information. You should keep until run `perf inject`.
204 |
205 | ### Inject JIT'ed code into recording file
206 |
207 | `perf.data` generated by `perf record` would not include JIT'ed code, so you need to inject them via `perf inject` as following.
208 |
209 | ```
210 | perf inject --jit -i perf.data -o perf.jit.data
211 | ```
212 |
213 | You will get some `.so` file and `perf.jit.data` as an injected file as a result.
214 |
215 | ### Check with `perf report`
216 |
217 | ```
218 | perf report -i perf.jit.data
219 | ```
220 |
221 | # License
222 |
223 | The GNU Lesser General Public License, version 3.0
224 |
--------------------------------------------------------------------------------
/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.6.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 = AMD64AsmBuilder.create(AMD64AsmBuilder.class, 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.6.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 = AMD64AsmBuilder.create(AVXAsmBuilder.class, seg, desc)
48 | /* push %rbp */ .push(Register.RBP)
49 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty())
50 | .cast(AVXAsmBuilder.class)
51 | /* vmovdqu (%rdi), %ymm0 */ .vmovdquRM(Register.YMM0, Register.RDI, OptionalInt.of(0))
52 | /* vpaddd (%rsi), %ymm0, %ymm0 */ .vpaddd(Register.YMM0, Register.RSI, Register.YMM0, OptionalInt.of(0))
53 | /* vmovdqu %ymm0, (%rdi) */ .vmovdquMR(Register.YMM0, Register.RDI, OptionalInt.of(0))
54 | /* leave */ .leave()
55 | /* ret */ .ret()
56 | .getMemorySegment();
57 |
58 | var linker = Linker.nativeLinker();
59 | ffm = linker.downcallHandle(ffmSeg, desc);
60 | ffmHeap = linker.downcallHandle(ffmSeg, desc, Linker.Option.critical(true));
61 | }
62 | catch(PlatformException | UnsupportedPlatformException e){
63 | throw new RuntimeException(e);
64 | }
65 |
66 | var arena = Arena.ofAuto();
67 | srcSeg = arena.allocate(32, 32);
68 | destSeg = arena.allocate(32, 32);
69 | }
70 |
71 | @Setup(Level.Iteration)
72 | public void paramSetup(){
73 | randArray = (new Random()).ints()
74 | .limit(8)
75 | .toArray();
76 | result = new int[]{0, 0, 0, 0, 0, 0, 0, 0};
77 |
78 | MemorySegment.copy(randArray, 0, srcSeg, ValueLayout.JAVA_INT, 0, 8);
79 | MemorySegment.copy(result, 0, destSeg, ValueLayout.JAVA_INT, 0, 8);
80 |
81 | heapSrcSeg = MemorySegment.ofArray(randArray);
82 | heapDestSeg = MemorySegment.ofArray(result);
83 |
84 | vectorSrc = IntVector.fromArray(IntVector.SPECIES_256, randArray, 0);
85 | vectorDest = IntVector.fromArray(IntVector.SPECIES_256, result, 0);
86 | }
87 |
88 | @Benchmark
89 | public int[] invokeJava(){
90 | for(int i = 0; i < 8; i++){
91 | result[i] += randArray[i];
92 | }
93 | return result;
94 | }
95 |
96 | @Benchmark
97 | public int[] invokeFFM(){
98 | try{
99 | ffm.invoke(destSeg, srcSeg);
100 | return destSeg.toArray(ValueLayout.JAVA_INT);
101 | }
102 | catch(Throwable t){
103 | throw new RuntimeException(t);
104 | }
105 | }
106 |
107 | @Benchmark
108 | public int[] invokeFFMHeap(){
109 | try{
110 | ffmHeap.invoke(heapDestSeg, heapSrcSeg);
111 | return result;
112 | }
113 | catch(Throwable t){
114 | throw new RuntimeException(t);
115 | }
116 | }
117 |
118 | @Benchmark
119 | public int[] invokeVector(){
120 | vectorDest.add(vectorSrc).intoArray(result, 0);
121 | return result;
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/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 24
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.3.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 -XX:AOTCache=ffmasm-cpumodel.aot -jar cpumodel-0.1.3.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.3
28 | jar
29 |
30 | cpumodel
31 |
32 |
33 | UTF-8
34 | com.yasuenag.ffmasm.examples.cpumodel
35 | com.yasuenag.ffmasm.examples.cpumodel.Main
36 | 24
37 | 24
38 |
39 |
40 |
41 |
42 | com.yasuenag
43 | ffmasm
44 | 0.6.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-record
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:AOTMode=record
112 | -XX:AOTConfiguration=ffmasm-cpumodel.aotconf
113 | -jar
114 | ${project.build.finalName}.jar
115 |
116 |
117 |
118 |
119 | aot-create
120 | package
121 |
122 | exec
123 |
124 |
125 | ${project.build.directory}
126 | ${java.home}/bin/java
127 |
128 | -XX:AOTMode=create
129 | -XX:AOTConfiguration=ffmasm-cpumodel.aotconf
130 | -XX:AOTCache=ffmasm-cpumodel.aot
131 | -cp
132 | ${project.build.finalName}.jar
133 |
134 |
135 |
136 |
137 |
138 |
139 | org.codehaus.mojo
140 | exec-maven-plugin
141 | 3.5.0
142 |
143 | ${mainClass}
144 |
145 |
146 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/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 = AMD64AsmBuilder.create(AMD64AsmBuilder.class, 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 AMD64AsmBuilder.create(AMD64AsmBuilder.class, 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.6.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 | package com.yasuenag.ffmasm.examples.perf;
2 |
3 | import java.lang.foreign.*;
4 | import java.lang.invoke.*;
5 | import java.nio.file.*;
6 | import java.util.*;
7 |
8 | import com.yasuenag.ffmasm.*;
9 | import com.yasuenag.ffmasm.amd64.*;
10 |
11 |
12 | public class PerfJit{
13 |
14 | private static final CodeSegment seg;
15 |
16 | private static final MethodHandle rdtsc;
17 |
18 | private static final JitDump jitdump;
19 |
20 | static{
21 | try{
22 | seg = new CodeSegment();
23 | jitdump = JitDump.getInstance(Path.of("."));
24 | var desc = FunctionDescriptor.of(ValueLayout.JAVA_LONG);
25 | rdtsc = AMD64AsmBuilder.create(AMD64AsmBuilder.class, seg, desc)
26 | /* .align 16 */ .alignTo16BytesWithNOP()
27 | /* retry: */ .label("retry")
28 | /* rdrand %rax */ .rdrand(Register.RAX)
29 | /* jae retry */ .jae("retry")
30 | /* ret */ .ret()
31 | .build("ffm_rdtsc", jitdump, Linker.Option.critical(false));
32 | }
33 | catch(Throwable t){
34 | throw new RuntimeException(t);
35 | }
36 | }
37 |
38 | public static void main(String[] args) throws Throwable{
39 | try(jitdump; seg){
40 | for(int i = 0; i < 10_000_000; i++){
41 | long _ = (long)rdtsc.invokeExact();
42 | }
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
21 |
22 |
23 | 4.0.0
24 |
25 | com.yasuenag
26 | ffmasm
27 | 0.6.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/CodeSegment.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;
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 | public static record MethodInfo(String name, long address, int size){
53 | @Override
54 | public String toString(){
55 | return String.format("0x%x 0x%x %s", address, size, name);
56 | }
57 | }
58 |
59 | private final ExecMemory mem;
60 |
61 | private final MemorySegment addr;
62 |
63 | private final long size;
64 |
65 | private final Set methods;
66 |
67 | private long tail;
68 |
69 | private Thread perfMapDumper;
70 |
71 | /**
72 | * Allocate memory for this code segment with default size (4096 bytes).
73 | * @throws PlatformException thrown when native function call failed.
74 | * @throws UnsupportedPlatformException thrown when the platform is not supported.
75 | */
76 | public CodeSegment() throws PlatformException, UnsupportedPlatformException{
77 | this(DEFAULT_CODE_SEGMENT_SIZE);
78 | }
79 |
80 | /**
81 | * Allocate memory for this code segment.
82 | * @param size size of code segment.
83 | * @throws PlatformException thrown when native function call failed.
84 | * @throws UnsupportedPlatformException thrown when the platform is not supported.
85 | */
86 | public CodeSegment(long size) throws PlatformException, UnsupportedPlatformException{
87 | String osName = System.getProperty("os.name");
88 | if(osName.equals("Linux")){
89 | mem = new LinuxExecMemory();
90 | }
91 | else if(osName.startsWith("Windows")){
92 | mem = new WindowsExecMemory();
93 | }
94 | else{
95 | throw new UnsupportedPlatformException(osName + " is unsupported.");
96 | }
97 |
98 | this.size = size;
99 | this.addr = mem.allocate(size);
100 | this.methods = new HashSet<>();
101 | this.tail = 0L;
102 | this.perfMapDumper = null;
103 | }
104 |
105 | /**
106 | * Release memory for this code segment.
107 | */
108 | @Override
109 | public void close() throws Exception{
110 | if(perfMapDumper != null){
111 | disablePerfMapDumper();
112 | }
113 | mem.deallocate(addr, size);
114 | }
115 |
116 | /**
117 | * Class to register calling close() as Cleaner action.
118 | */
119 | public static class CleanerAction implements Runnable{
120 |
121 | private final CodeSegment seg;
122 |
123 | /**
124 | * @param seg CodeSegment
instance to close.
125 | */
126 | public CleanerAction(CodeSegment seg){
127 | this.seg = seg;
128 | }
129 |
130 | /**
131 | * Close associated CodeSegment
.
132 | * All of exceptions are ignored during close()
operation.
133 | */
134 | @Override
135 | public void run(){
136 | try{
137 | seg.close();
138 | }
139 | catch(Exception e){
140 | // ignore
141 | }
142 | }
143 |
144 | }
145 |
146 | /**
147 | * Get slice of this segment from the tail.
148 | *
149 | * @return Slice of this segment from the tail.
150 | */
151 | public MemorySegment getTailOfMemorySegment(){
152 | return addr.asSlice(tail);
153 | }
154 |
155 | /**
156 | * Align the tail to 16 bytes
157 | */
158 | public void alignTo16Bytes(){
159 | if((tail & 0xf) > 0){ // not aligned
160 | tail = (tail + 0x10) & 0xfffffffffffffff0L;
161 | }
162 | }
163 |
164 | /**
165 | * Get the tail of this segment.
166 | *
167 | * @return the tail of this segment.
168 | */
169 | public long getTail(){
170 | return tail;
171 | }
172 |
173 | /**
174 | * Increment the tail with given size.
175 | * @param size value to increment
176 | */
177 | public void incTail(long size){
178 | this.tail += size;
179 | }
180 |
181 | /**
182 | * Get MemorySegment which relates to this segment.
183 | *
184 | * @return MemorySegment of this segment.
185 | */
186 | public MemorySegment getAddr(){
187 | return addr;
188 | }
189 |
190 | /**
191 | * Add method info. It will be dumped to perf map as related method of this CodeSegment.
192 | * @param name Method name
193 | * @param address Address of the method
194 | * @param size Size of the method (machine code)
195 | * @return MethodInfo of the method info.
196 | * @throws IllegalArgumentException if the address is out of range from this CodeSegment.
197 | */
198 | public MethodInfo addMethodInfo(String name, long address, int size){
199 | if((address < addr.address()) || ((addr.address() + this.size) < (address + size))){
200 | throw new IllegalArgumentException("Address is out of range from CodeSegment.");
201 | }
202 | var methodInfo = new MethodInfo(name, address, size);
203 | methods.add(methodInfo);
204 | return methodInfo;
205 | }
206 |
207 | private void dumpPerfMap(Path path){
208 | try(var writer = new PrintWriter(Files.newOutputStream(path))){
209 | methods.stream()
210 | .sorted(Comparator.comparing(MethodInfo::address))
211 | .map(MethodInfo::toString)
212 | .forEachOrdered(writer::println);
213 | }
214 | catch(IOException e){
215 | throw new UncheckedIOException(e);
216 | }
217 | }
218 |
219 | /**
220 | * Enable perf map dumper at shutdown hook for dumping all of functions in this CodeSegment.
221 | * @param path Path to dumpfile
222 | */
223 | public synchronized void enablePerfMapDumper(Path path){
224 | perfMapDumper = new Thread(() -> dumpPerfMap(path));
225 | Runtime.getRuntime().addShutdownHook(perfMapDumper);
226 | }
227 |
228 | /**
229 | * Disable perf map dumper
230 | * @throws IllegalStateException if enablePerfMapDumper() is not called before.
231 | */
232 | public synchronized void disablePerfMapDumper(){
233 | if(perfMapDumper == null){
234 | throw new IllegalStateException("PerfMapDumper is not enabled.");
235 | }
236 | Runtime.getRuntime().removeShutdownHook(perfMapDumper);
237 | perfMapDumper = null;
238 | }
239 |
240 | }
241 |
--------------------------------------------------------------------------------
/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/NativeRegister.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;
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.ValueLayout;
28 | import java.lang.reflect.Method;
29 | import java.util.Map;
30 |
31 | import com.yasuenag.ffmasm.internal.JniEnv;
32 | import com.yasuenag.ffmasm.internal.JvmtiEnv;
33 | import com.yasuenag.ffmasm.internal.amd64.AMD64NativeRegister;
34 |
35 |
36 | /**
37 | * Dynamic native method register.
38 | *
39 | * This class binds memory address to arbitrary native methods dynamically.
40 | * Note that 1st and 2nd arguments of JNI function are reserved.
41 | * (1st is JNIEnv*
, 2nd is jclass
or jobject
)
42 | * You need to get instance of this class from {@link #create(Class>)}.
43 | *
44 | * We can get JNIEnv*
via GetEnv()
JNI function.
45 | * However we have to get JavaVM*
to call it.
46 | * So we call JNI_GetCreatedJavaVMs()
which is exported function
47 | * from JVM at first, then we get a pointer of GetEnv()
from
48 | * JavaVM
function table.
49 | *
50 | * To register native functions dynamically, we need to call
51 | * RegisterNatives()
JNI function, we need to know jclass of
52 | * the holder class to do it.
53 | * We can use FindClass()
JNI function normally for this purpose,
54 | * but we cannot do that because FindClass()
checks caller, and
55 | * it attempts to find only from system class loader when the caller is
56 | * MethodHandle
.
57 | * (See JavaThread::security_get_caller_class()
in HotSpot)
58 | * ffmasm would be loaded from application class loader, so we need to get
59 | * jclass
by another method.
60 | *
61 | * We can get jclass
es of all of loaded classes via
62 | * GetLoadedClasses()
JVMTI function. Get jvmtiEnv*
63 | * with same procedure with JNIEnv*
, and get a pointer of
64 | * GetLoadedClasses()
from the function table.
65 | * However lifecycle of jclass
es is JNI local, it means they will
66 | * be invalid when function call (via ffmasm) are ended (return to the app).
67 | * So we need to create stub code to call GetLoadedClasses()
and
68 | * to find out a target jclass
at once.
69 | *
70 | * Stub code calls GetLoadedClasses()
at first, then passes
71 | * jclass
es and number of them to callback function.
72 | * Callback function iterates jclass
es, and compare class signature
73 | * from GetClassSignature()
JVMTI function with
74 | * strcmp()
. The reason of using it is that we do not need to
75 | * create new Java instance for String
, and don't need to
76 | * reinterpret MemorySegment
of class signature with its length.
77 | * We would release class signature with Deallocate()
JVMTI
78 | * function of course when it can be dispose.
79 | *
80 | * When we find a target jclass
at the callback, we register
81 | * executable memory of native method via RegisterNatives()
JNI
82 | * function. Then callback would be finished.
83 | *
84 | * Finally, stub code would release memory of jclass
es, then
85 | * all of register processes are done!
86 | *
87 | * @author Yasumasa Suenaga
88 | */
89 | public abstract class NativeRegister{
90 |
91 | private static final MemoryLayout layoutCallbackParam;
92 | private static final MemoryLayout.PathElement peClassSig;
93 | private static final MemoryLayout.PathElement peMethods;
94 | private static final MemoryLayout.PathElement peNumMethods;
95 |
96 | private static final MethodHandle strcmp;
97 |
98 | private final Class> klass;
99 |
100 | static{
101 | layoutCallbackParam = MemoryLayout.structLayout(ValueLayout.ADDRESS.withName("class_sig"),
102 | ValueLayout.ADDRESS.withName("methods"),
103 | ValueLayout.JAVA_INT.withName("num_methods"));
104 | peClassSig = MemoryLayout.PathElement.groupElement("class_sig");
105 | peMethods = MemoryLayout.PathElement.groupElement("methods");
106 | peNumMethods = MemoryLayout.PathElement.groupElement("num_methods");
107 |
108 | var linker = Linker.nativeLinker();
109 | strcmp = linker.downcallHandle(linker.defaultLookup()
110 | .find("strcmp")
111 | .get(),
112 | FunctionDescriptor.of(ValueLayout.JAVA_INT,
113 | ValueLayout.ADDRESS,
114 | ValueLayout.ADDRESS));
115 | }
116 |
117 | protected static void callback(MemorySegment classes, int class_count, int resultGetLoadedClasses, MemorySegment callbackParam){
118 | if(resultGetLoadedClasses != JvmtiEnv.JVMTI_ERROR_NONE){
119 | throw new RuntimeException("GetLoadedClasses() returns " + resultGetLoadedClasses);
120 | }
121 |
122 | classes = classes.reinterpret(ValueLayout.ADDRESS.byteSize() * class_count);
123 | callbackParam = callbackParam.reinterpret(layoutCallbackParam.byteSize());
124 | try(var arena = Arena.ofConfined()){
125 | var sigPtr = arena.allocate(ValueLayout.ADDRESS);
126 | var targetSig = (MemorySegment)layoutCallbackParam.varHandle(peClassSig)
127 | .get(callbackParam, 0L);
128 | var jvmtiEnv = JvmtiEnv.getInstance();
129 | for(int idx = 0; idx < class_count; idx++){
130 | var clazz = classes.getAtIndex(ValueLayout.ADDRESS, idx);
131 | int result = jvmtiEnv.getClassSignature(clazz, sigPtr, MemorySegment.NULL);
132 | if(result != JvmtiEnv.JVMTI_ERROR_NONE){
133 | throw new RuntimeException("GetClassSignature() returns " + result);
134 | }
135 |
136 | var sig = sigPtr.get(ValueLayout.ADDRESS, 0);
137 | int cmp = (int)strcmp.invoke(sig, targetSig);
138 | jvmtiEnv.deallocate(sig);
139 | if(cmp == 0){
140 | MemorySegment methods = (MemorySegment)layoutCallbackParam.varHandle(peMethods)
141 | .get(callbackParam, 0L);
142 | int nMethods = (int)layoutCallbackParam.varHandle(peNumMethods)
143 | .get(callbackParam, 0L);
144 | result = JniEnv.getInstance()
145 | .registerNatives(clazz, methods, nMethods);
146 | if(result != JniEnv.JNI_OK){
147 | throw new RuntimeException("RegisterNatives() returns " + result);
148 | }
149 | return;
150 | }
151 | }
152 | }
153 | catch(Throwable t){
154 | throw new RuntimeException(t);
155 | }
156 |
157 | }
158 |
159 | protected NativeRegister(Class> klass){
160 | this.klass = klass;
161 | }
162 |
163 | private String convertToJNITypeSignature(Class> clazz){
164 | String signature = clazz.isArray() ? "[" : "";
165 | if(clazz == boolean.class){
166 | signature += "Z";
167 | }
168 | else if(clazz == byte.class){
169 | signature += "B";
170 | }
171 | else if(clazz == char.class){
172 | signature += "C";
173 | }
174 | else if(clazz == short.class){
175 | signature += "S";
176 | }
177 | else if(clazz == int.class){
178 | signature += "I";
179 | }
180 | else if(clazz == long.class){
181 | signature += "J";
182 | }
183 | else if(clazz == float.class){
184 | signature += "F";
185 | }
186 | else if(clazz == double.class){
187 | signature += "D";
188 | }
189 | else if(clazz == void.class){
190 | signature += "V";
191 | }
192 | else{
193 | signature += "L" + clazz.getCanonicalName().replace('.', '/') + ";";
194 | };
195 | return signature;
196 | }
197 |
198 | private String getJNIMethodSignature(Method method){
199 | String signature = "(";
200 | for(var argType : method.getParameterTypes()){
201 | signature += convertToJNITypeSignature(argType);
202 | }
203 | signature += ")" + convertToJNITypeSignature(method.getReturnType());
204 |
205 | return signature;
206 | }
207 |
208 | protected abstract void callRegisterStub(MemorySegment callbackParam) throws Throwable;
209 |
210 | /**
211 | * Register executable memory to native methods.
212 | *
213 | * @param methods Map of Method
and MemorySegment
. You have to set Method
object what you want to set to key of map.
214 | * @throws Throwable when some error happened.
215 | */
216 | public void registerNatives(Map methods) throws Throwable{
217 | var classSig = "L" + klass.getCanonicalName().replace('.', '/') + ";";
218 | var peNameHandle = JniEnv.layoutJNINativeMethod.arrayElementVarHandle(JniEnv.peName);
219 | var peSignatureHandle = JniEnv.layoutJNINativeMethod.arrayElementVarHandle(JniEnv.peSignature);
220 | var peFnPtrHandle = JniEnv.layoutJNINativeMethod.arrayElementVarHandle(JniEnv.peFnPtr);
221 |
222 | try(var arena = Arena.ofConfined()){
223 | var nativeMethods = arena.allocate(JniEnv.layoutJNINativeMethod, methods.size());
224 | int idx = 0;
225 | for(var es : methods.entrySet()){
226 | Method method = es.getKey();
227 | MemorySegment mem = es.getValue();
228 |
229 | peNameHandle.set(nativeMethods, 0L, (long)idx, arena.allocateFrom(method.getName()));
230 | peSignatureHandle.set(nativeMethods, 0L, (long)idx, arena.allocateFrom(getJNIMethodSignature(method)));
231 | peFnPtrHandle.set(nativeMethods, 0L, (long)idx, mem);
232 |
233 | idx++;
234 | }
235 |
236 | var callbackParam = arena.allocate(layoutCallbackParam);
237 | layoutCallbackParam.varHandle(peClassSig).set(callbackParam, 0L, arena.allocateFrom(classSig));
238 | layoutCallbackParam.varHandle(peMethods).set(callbackParam, 0L, nativeMethods);
239 | layoutCallbackParam.varHandle(peNumMethods).set(callbackParam, 0L, methods.size());
240 |
241 | callRegisterStub(callbackParam);
242 | }
243 | }
244 |
245 | /**
246 | * Create new instance of NativeRegister.
247 | *
248 | * @param klass Class
what you want to set native methods.
249 | * @throws UnsupportedPlatformException when this method is called on unsupported platform.
250 | */
251 | public static NativeRegister create(Class> klass) throws UnsupportedPlatformException{
252 | var arch = System.getProperty("os.arch");
253 | if(arch.equals("amd64")){
254 | return new AMD64NativeRegister(klass);
255 | }
256 | else{
257 | throw new UnsupportedPlatformException(arch + " is not supported");
258 | }
259 | }
260 |
261 | }
262 |
--------------------------------------------------------------------------------
/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/amd64/AVXAsmBuilder.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 |
27 |
28 | /**
29 | * Builder for AVX hand-assembling
30 | *
31 | * @author Yasumasa Suenaga
32 | */
33 | public class AVXAsmBuilder extends SSEAsmBuilder{
34 |
35 | /**
36 | * Constructor.
37 | *
38 | * @param seg CodeSegment which is used by this builder.
39 | * @param desc FunctionDescriptor for this builder. It will be used by build().
40 | */
41 | protected AVXAsmBuilder(CodeSegment seg, FunctionDescriptor desc){
42 | super(seg, desc);
43 | }
44 |
45 | private static enum PP{
46 | None((byte)0b00),
47 | H66((byte)0b01),
48 | HF3((byte)0b10),
49 | HF2((byte)0b11);
50 |
51 | private final byte prefix;
52 |
53 | private PP(byte prefix){
54 | this.prefix = prefix;
55 | }
56 |
57 | public byte prefix(){
58 | return prefix;
59 | }
60 | }
61 |
62 | private static enum LeadingBytes{
63 | H0F((byte)0b00001),
64 | H0F38((byte)0b00010),
65 | H0F3A((byte)0b00011);
66 |
67 | private final byte bytes;
68 |
69 | private LeadingBytes(byte bytes){
70 | this.bytes = bytes;
71 | }
72 |
73 | public byte bytes(){
74 | return bytes;
75 | }
76 | }
77 |
78 | private void emit2ByteVEXPrefix(Register src1, PP simdPrefix){
79 | byte VEXvvvv = (byte)((~src1.encoding()) & 0b1111);
80 | emit2ByteVEXPrefixWithVVVV(VEXvvvv, src1.width() == 256, simdPrefix);
81 | }
82 |
83 | private void emit2ByteVEXPrefixWithVVVV(byte VEXvvvv, boolean is256bit, PP simdPrefix){
84 | byte rexr = (byte)((VEXvvvv >> 3) & 1);
85 | byte vecLength = is256bit ? (byte)1 : (byte)0;
86 | byteBuf.put((byte)0xC5); // 2-byte VEX
87 | byteBuf.put((byte)( (rexr << 7) | // REX.R
88 | (VEXvvvv << 3) | // VEX.vvvv
89 | (vecLength << 2) | // Vector Length
90 | simdPrefix.prefix() // opcode extension (SIMD prefix)
91 | ));
92 | }
93 |
94 | private void emit3ByteVEXPrefix(Register r, Register m, PP simdPrefix, LeadingBytes bytes){
95 | byte VEXvvvv = (byte)((~r.encoding()) & 0b1111);
96 | byte invMem = (byte)((~m.encoding()) & 0b1111);
97 | byte rexr = (byte)((VEXvvvv >> 3) & 1);
98 | byte rexb = (byte)((invMem >> 3) & 1);
99 | byte is256Bit = (r.width() == 256) ? (byte)1 : (byte)0;
100 | byteBuf.put((byte)0xC4); // 3-byte VEX
101 | byteBuf.put((byte)( (rexr << 7) | // REX.R
102 | ( 1 << 6) | // inverse of REX.X
103 | (rexb << 5) | // REX.B
104 | bytes.bytes() // leading opcode bytes
105 | ));
106 | byteBuf.put((byte)( (VEXvvvv << 3) | // VEX.vvvv
107 | (is256Bit << 2) | // Vector Length
108 | simdPrefix.prefix() // opcode extension (SIMD prefix)
109 | ));
110 | }
111 |
112 | private AVXAsmBuilder vmovdq(Register r, Register m, OptionalInt disp, PP pp, byte opcode){
113 | if(m.encoding() > 7){
114 | emit3ByteVEXPrefix(Register.YMM0 /* unused */, m, pp, LeadingBytes.H0F);
115 | }
116 | else{
117 | emit2ByteVEXPrefix(Register.YMM0 /* unused */, pp);
118 | }
119 |
120 | byteBuf.put(opcode); // MOVDQA
121 |
122 | byte mode = emitModRM(r, m, disp);
123 | if(mode == 0b01){ // reg-mem disp8
124 | byteBuf.put((byte)disp.getAsInt());
125 | }
126 | else if(mode == 0b10){ // reg-mem disp32
127 | byteBuf.putInt(disp.getAsInt());
128 | }
129 |
130 | return this;
131 | }
132 |
133 | /**
134 | * Move aligned packed integer values from r/m to r.
135 | * NOTES: This method supports YMM register only now.
136 | * Opcode: VEX.256.66.0F.WIG 6F /r (256 bit)
137 | * Instruction: VMOVDQA r, r/m
138 | * Op/En: A
139 | *
140 | * @param r "r" register
141 | * @param m "r/m" register
142 | * @param disp Displacement. Set "empty" if this operation is reg-reg
143 | * then "r/m" have to be a SIMD register.
144 | * Otherwise it has to be 64 bit GPR because it have to be
145 | * a memory operand..
146 | * @return This instance
147 | */
148 | public AVXAsmBuilder vmovdqaRM(Register r, Register m, OptionalInt disp){
149 | return vmovdq(r, m, disp, PP.H66, (byte)0x6f);
150 | }
151 |
152 | /**
153 | * Move aligned packed integer values from r to r/m.
154 | * NOTES: This method supports YMM register only now.
155 | * Opcode: VEX.256.66.0F.WIG 7F /r (256 bit)
156 | * Instruction: VMOVDQA r/m, r
157 | * Op/En: B
158 | *
159 | * @param r "r" register
160 | * @param m "r/m" register
161 | * @param disp Displacement. Set "empty" if this operation is reg-reg
162 | * then "r/m" have to be a SIMD register.
163 | * Otherwise it has to be 64 bit GPR because it have to be
164 | * a memory operand..
165 | * @return This instance
166 | */
167 | public AVXAsmBuilder vmovdqaMR(Register r, Register m, OptionalInt disp){
168 | return vmovdq(r, m, disp, PP.H66, (byte)0x7f);
169 | }
170 |
171 | /**
172 | * Move unaligned packed integer values from r/m to r.
173 | * NOTES: This method supports YMM register only now.
174 | * Opcode: VEX.256.F3.0F.WIG 6F /r (256 bit)
175 | * Instruction: VMOVDQU r, r/m
176 | * Op/En: A
177 | *
178 | * @param r "r" register
179 | * @param m "r/m" register
180 | * @param disp Displacement. Set "empty" if this operation is reg-reg
181 | * then "r/m" have to be a SIMD register.
182 | * Otherwise it has to be 64 bit GPR because it have to be
183 | * a memory operand..
184 | * @return This instance
185 | */
186 | public AVXAsmBuilder vmovdquRM(Register r, Register m, OptionalInt disp){
187 | return vmovdq(r, m, disp, PP.HF3, (byte)0x6f);
188 | }
189 |
190 | /**
191 | * Move unaligned packed integer values from r to r/m.
192 | * NOTES: This method supports YMM register only now.
193 | * Opcode: VEX.256.F3.0F.WIG 7F /r (256 bit)
194 | * Instruction: VMOVDQU r/m, r
195 | * Op/En: B
196 | *
197 | * @param r "r" register
198 | * @param m "r/m" register
199 | * @param disp Displacement. Set "empty" if this operation is reg-reg
200 | * then "r/m" have to be a SIMD register.
201 | * Otherwise it has to be 64 bit GPR because it have to be
202 | * a memory operand..
203 | * @return This instance
204 | */
205 | public AVXAsmBuilder vmovdquMR(Register r, Register m, OptionalInt disp){
206 | return vmovdq(r, m, disp, PP.HF3, (byte)0x7f);
207 | }
208 |
209 | /**
210 | * Bitwise XOR of r and r/m.
211 | * NOTES: This method supports YMM register only now.
212 | * Opcode: VEX.256.66.0F.WIG EF /r (256 bit)
213 | * Instruction: VPXOR dest, r, r/m
214 | * Op/En: B
215 | *
216 | * @param r "r" register
217 | * @param m "r/m" register
218 | * @param dest "dest" register
219 | * @param disp Displacement. Set "empty" if this operation is reg-reg
220 | * then "r/m" have to be a SIMD register.
221 | * Otherwise it has to be 64 bit GPR because it have to be
222 | * a memory operand.
223 | * @return This instance
224 | */
225 | public AVXAsmBuilder vpxor(Register r, Register m, Register dest, OptionalInt disp){
226 | if(m.encoding() > 7){
227 | emit3ByteVEXPrefix(r, m, PP.H66, LeadingBytes.H0F);
228 | }
229 | else{
230 | emit2ByteVEXPrefix(r, PP.H66);
231 | }
232 |
233 | byteBuf.put((byte)0xef); // VPXOR
234 |
235 | byte mode = emitModRM(dest, m, disp);
236 | if(mode == 0b01){ // reg-mem disp8
237 | byteBuf.put((byte)disp.getAsInt());
238 | }
239 | else if(mode == 0b10){ // reg-mem disp32
240 | byteBuf.putInt(disp.getAsInt());
241 | }
242 |
243 | return this;
244 | }
245 |
246 | /**
247 | * Add packed doubleword integers from r/m, r and store in dest.
248 | * NOTES: This method supports YMM register only now.
249 | * Opcode: VEX.256.66.0F.WIG FE /r (256 bit)
250 | * Instruction: VPADDD dest, r, r/m
251 | * Op/En: B
252 | *
253 | * @param r "r" register
254 | * @param m "r/m" register
255 | * @param dest "dest" register
256 | * @param disp Displacement. Set "empty" if this operation is reg-reg
257 | * then "r/m" have to be a SIMD register.
258 | * Otherwise it has to be 64 bit GPR because it have to be
259 | * a memory operand.
260 | * @return This instance
261 | */
262 | public AVXAsmBuilder vpaddd(Register r, Register m, Register dest, OptionalInt disp){
263 | if(m.encoding() > 7){
264 | emit3ByteVEXPrefix(r, m, PP.H66, LeadingBytes.H0F);
265 | }
266 | else{
267 | emit2ByteVEXPrefix(r, PP.H66);
268 | }
269 |
270 | byteBuf.put((byte)0xfe); // VPADDD
271 |
272 | byte mode = emitModRM(dest, m, disp);
273 | if(mode == 0b01){ // reg-mem disp8
274 | byteBuf.put((byte)disp.getAsInt());
275 | }
276 | else if(mode == 0b10){ // reg-mem disp32
277 | byteBuf.putInt(disp.getAsInt());
278 | }
279 |
280 | return this;
281 | }
282 |
283 | /**
284 | * Logical compare r with r/m.
285 | * NOTES: This method supports YMM register only now.
286 | * Opcode: VEX.256.66.0F38.WIG 17 /r (256 bit)
287 | * Instruction: VPTEST r, r/m
288 | * Op/En: RM
289 | *
290 | * @param r "r" register
291 | * @param m "r/m" register
292 | * @param disp Displacement. Set "empty" if this operation is reg-reg
293 | * then "r/m" have to be a SIMD register.
294 | * Otherwise it has to be 64 bit GPR because it have to be
295 | * a memory operand.
296 | * @return This instance
297 | */
298 | public AVXAsmBuilder vptest(Register r, Register m, OptionalInt disp){
299 | emit3ByteVEXPrefix(Register.YMM0 /* unused */, m, PP.H66, LeadingBytes.H0F38);
300 | byteBuf.put((byte)0x17); // PTEST
301 | byte mode = emitModRM(r, m, disp);
302 | if(mode == 0b01){ // reg-mem disp8
303 | byteBuf.put((byte)disp.getAsInt());
304 | }
305 | else if(mode == 0b10){ // reg-mem disp32
306 | byteBuf.putInt(disp.getAsInt());
307 | }
308 |
309 | return this;
310 | }
311 |
312 | /**
313 | * Zero bits in positions 128 and higher of some YMM and ZMM registers.
314 | * Opcode: VEX.128.0F.WIG 77
315 | * Instruction: VZEROUPPER
316 | * Op/En: ZO
317 | *
318 | * @return This instance
319 | */
320 | public AVXAsmBuilder vzeroupper(){
321 | emit2ByteVEXPrefixWithVVVV((byte)0b1111, false, PP.None);
322 | byteBuf.put((byte)0x77); // VZEROUPPER
323 | return this;
324 | }
325 |
326 | }
327 |
--------------------------------------------------------------------------------
/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, 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.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 |
27 |
28 | /**
29 | * Builder for SSE hand-assembling
30 | *
31 | * @author Yasumasa Suenaga
32 | */
33 | public class SSEAsmBuilder extends AMD64AsmBuilder{
34 |
35 | /**
36 | * Constructor.
37 | *
38 | * @param seg CodeSegment which is used by this builder.
39 | * @param desc FunctionDescriptor for this builder. It will be used by build().
40 | */
41 | protected SSEAsmBuilder(CodeSegment seg, FunctionDescriptor desc){
42 | super(seg, desc);
43 | }
44 |
45 | private SSEAsmBuilder movdq(Register r, Register m, OptionalInt disp, byte prefix, byte secondOpcode){
46 | byteBuf.put(prefix);
47 | emitREXOp(r, m);
48 | byteBuf.put((byte)0x0f); // escape opcode
49 | byteBuf.put(secondOpcode);
50 | var mode = emitModRM(r, m, disp);
51 | emitDisp(mode, disp, m);
52 |
53 | return this;
54 | }
55 |
56 | /**
57 | * Move aligned packed integer values from xmm2/mem to xmm1.
58 | * Opcode: 66 0F 6F /r
59 | * Instruction: MOVDQA xmm1, xmm2/m128
60 | * Op/En: A
61 | *
62 | * @param r "r" register
63 | * @param m "r/m" register
64 | * @param disp Displacement. Set "empty" if this operation is reg-reg
65 | * then "r/m" have to be a SIMD register.
66 | * Otherwise it has to be 64 bit GPR because it have to be * a memory operand.
67 | * @return This instance
68 | */
69 | public SSEAsmBuilder movdqaRM(Register r, Register m, OptionalInt disp){
70 | return movdq(r, m, disp, (byte)0x66, (byte)0x6f);
71 | }
72 |
73 | /**
74 | * Move aligned packed integer values from xmm1 to xmm2/mem.
75 | * Opcode: 66 0F 7F /r
76 | * Instruction: MOVDQA xmm2/m128, xmm1
77 | * Op/En: B
78 | *
79 | * @param r "r" register
80 | * @param m "r/m" register
81 | * @param disp Displacement. Set "empty" if this operation is reg-reg
82 | * then "r/m" have to be a SIMD register.
83 | * Otherwise it has to be 64 bit GPR because it have to be * a memory operand.
84 | * @return This instance
85 | */
86 | public SSEAsmBuilder movdqaMR(Register r, Register m, OptionalInt disp){
87 | return movdq(r, m, disp, (byte)0x66, (byte)0x7f);
88 | }
89 |
90 | /**
91 | * Move unaligned packed integer values from xmm2/mem128 to xmm1.
92 | * Opcode: F3 0F 6F /r
93 | * Instruction: MOVDQU xmm1, xmm2/m128
94 | * Op/En: A
95 | *
96 | * @param r "r" register
97 | * @param m "r/m" register
98 | * @param disp Displacement. Set "empty" if this operation is reg-reg
99 | * then "r/m" have to be a SIMD register.
100 | * Otherwise it has to be 64 bit GPR because it have to be * a memory operand.
101 | * @return This instance
102 | */
103 | public SSEAsmBuilder movdquRM(Register r, Register m, OptionalInt disp){
104 | return movdq(r, m, disp, (byte)0xf3, (byte)0x6f);
105 | }
106 |
107 | /**
108 | * Move unaligned packed integer values from xmm1 to xmm2/mem128.
109 | * Opcode: F3 0F 7F /r
110 | * Instruction: MOVDQU xmm2/m128, xmm1
111 | * Op/En: B
112 | *
113 | * @param r "r" register
114 | * @param m "r/m" register
115 | * @param disp Displacement. Set "empty" if this operation is reg-reg
116 | * then "r/m" have to be a SIMD register.
117 | * Otherwise it has to be 64 bit GPR because it have to be * a memory operand.
118 | * @return This instance
119 | */
120 | public SSEAsmBuilder movdquMR(Register r, Register m, OptionalInt disp){
121 | return movdq(r, m, disp, (byte)0xf3, (byte)0x7f);
122 | }
123 |
124 | private SSEAsmBuilder movDInternal(Register r, Register m, OptionalInt disp, byte secondOpcode){
125 | return movDorQ(r, m, disp, secondOpcode, false);
126 | }
127 |
128 | private SSEAsmBuilder movQInternal(Register r, Register m, OptionalInt disp, byte secondOpcode){
129 | return movDorQ(r, m, disp, secondOpcode, true);
130 | }
131 |
132 | private SSEAsmBuilder movDorQ(Register r, Register m, OptionalInt disp, byte secondOpcode, boolean isQWORD){
133 | byteBuf.put((byte)0x66); // prefix
134 | emitREXOp(r, m, isQWORD);
135 | byteBuf.put((byte)0x0f); // escape opcode
136 | byteBuf.put(secondOpcode);
137 | var mode = emitModRM(r, m, disp);
138 | emitDisp(mode, disp, m);
139 |
140 | return this;
141 | }
142 |
143 | /**
144 | * Move doubleword from r/m32 to xmm.
145 | * Opcode: 66 0F 6E /r
146 | * Instruction: MOVD xmm, r/m32
147 | * Op/En: A
148 | *
149 | * @param r "r" register
150 | * @param m "r/m" register
151 | * @param disp Displacement. Set "empty" if this operation is reg-reg.
152 | * @return This instance
153 | */
154 | public SSEAsmBuilder movdRM(Register r, Register m, OptionalInt disp){
155 | return movDInternal(r, m, disp, (byte)0x6e);
156 | }
157 |
158 | /**
159 | * Move doubleword from xmm register to r/m32.
160 | * Opcode: 66 0F 7E /r
161 | * Instruction: MOVD r/m32, xmm
162 | * Op/En: B
163 | *
164 | * @param r "r" register
165 | * @param m "r/m" register
166 | * @param disp Displacement. Set "empty" if this operation is reg-reg.
167 | * @return This instance
168 | */
169 | public SSEAsmBuilder movdMR(Register r, Register m, OptionalInt disp){
170 | return movDInternal(r, m, disp, (byte)0x7e);
171 | }
172 |
173 | /**
174 | * Move quadword from r/m64 to xmm.
175 | * Opcode: 66 REX.W 0F 6E /r
176 | * Instruction: MOVQ xmm, r/m64
177 | * Op/En: A
178 | *
179 | * @param r "r" register
180 | * @param m "r/m" register
181 | * @param disp Displacement. Set "empty" if this operation is reg-reg.
182 | * @return This instance
183 | */
184 | public SSEAsmBuilder movqRM(Register r, Register m, OptionalInt disp){
185 | return movQInternal(r, m, disp, (byte)0x6e);
186 | }
187 |
188 | /**
189 | * Move quadword from xmm register to r/m64.
190 | * Opcode: 66 REX.W 0F 7E /r
191 | * Instruction: MOVQ r/m64, xmm
192 | * Op/En: B
193 | *
194 | * @param r "r" register
195 | * @param m "r/m" register
196 | * @param disp Displacement. Set "empty" if this operation is reg-reg.
197 | * @return This instance
198 | */
199 | public SSEAsmBuilder movqMR(Register r, Register m, OptionalInt disp){
200 | return movQInternal(r, m, disp, (byte)0x7e);
201 | }
202 |
203 | }
204 |
--------------------------------------------------------------------------------
/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 getLoadedClassesAddr(){
83 | if(GetLoadedClassesAddr == null){
84 | GetLoadedClassesAddr = functionTable.getAtIndex(ValueLayout.ADDRESS, JVMTI_GetLoadedClasses_POSITION - 1);
85 | }
86 | return GetLoadedClassesAddr;
87 | }
88 |
89 | public MemorySegment deallocateAddr(){
90 | if(DeallocateAddr == null){
91 | DeallocateAddr = functionTable.getAtIndex(ValueLayout.ADDRESS, JVMTI_Deallocate_POSITION - 1);
92 | }
93 | return DeallocateAddr;
94 | }
95 |
96 | public int deallocate(MemorySegment mem) throws Throwable{
97 | if(Deallocate == null){
98 | Deallocate = Linker.nativeLinker()
99 | .downcallHandle(deallocateAddr(),
100 | FunctionDescriptor.of(ValueLayout.JAVA_INT,
101 | ValueLayout.ADDRESS,
102 | ValueLayout.ADDRESS));
103 | }
104 | return (int)Deallocate.invoke(jvmtiEnv, mem);
105 | }
106 |
107 | public int getClassSignature(MemorySegment klass, MemorySegment signature_ptr, MemorySegment generic_ptr) throws Throwable{
108 | if(GetClassSignature == null){
109 | GetClassSignature = Linker.nativeLinker()
110 | .downcallHandle(functionTable.getAtIndex(ValueLayout.ADDRESS, JVMTI_GetClassSignature_POSITION - 1),
111 | FunctionDescriptor.of(ValueLayout.JAVA_INT,
112 | ValueLayout.ADDRESS,
113 | ValueLayout.ADDRESS,
114 | ValueLayout.ADDRESS,
115 | ValueLayout.ADDRESS));
116 | }
117 | return (int)GetClassSignature.invoke(jvmtiEnv, klass, signature_ptr, generic_ptr);
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/com/yasuenag/ffmasm/internal/amd64/AMD64NativeRegister.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 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.CodeSegment;
32 | import com.yasuenag.ffmasm.NativeRegister;
33 | import com.yasuenag.ffmasm.amd64.AMD64AsmBuilder;
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 = AMD64AsmBuilder.create(AMD64AsmBuilder.class, 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, 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 | // from bits/time.h
60 | private static final int CLOCK_MONOTONIC = 1;
61 |
62 | private static final long PAGE_SIZE = 4096;
63 |
64 |
65 | private static final MethodHandle mhGetTid;
66 | private static final StructLayout structTimespec;
67 | private static final VarHandle hndSec;
68 | private static final VarHandle hndNSec;
69 | private static final MethodHandle mhClockGettime;
70 |
71 |
72 | private final FileChannel ch;
73 |
74 | private final int fd;
75 |
76 | private final MemorySegment jitdump;
77 |
78 | private long codeIndex;
79 |
80 |
81 | static{
82 | var linker = Linker.nativeLinker();
83 | var lookup = linker.defaultLookup();
84 | FunctionDescriptor desc;
85 |
86 | desc = FunctionDescriptor.of(ValueLayout.JAVA_INT);
87 | mhGetTid = linker.downcallHandle(lookup.find("gettid").get(), desc);
88 |
89 | structTimespec = MemoryLayout.structLayout(
90 | ValueLayout.JAVA_LONG.withName("tv_sec"),
91 | ValueLayout.JAVA_LONG.withName("tv_nsec")
92 | );
93 | hndSec = structTimespec.varHandle(MemoryLayout.PathElement.groupElement("tv_sec"));
94 | hndNSec = structTimespec.varHandle(MemoryLayout.PathElement.groupElement("tv_nsec"));
95 | // __CLOCKID_T_TYPE is defined as __S32_TYPE in bits/typesizes.h
96 | desc = FunctionDescriptor.of(ValueLayout.JAVA_INT,
97 | ValueLayout.JAVA_INT,
98 | ValueLayout.ADDRESS);
99 | mhClockGettime = linker.downcallHandle(lookup.find("clock_gettime").get(), desc);
100 | }
101 |
102 | private static int getTid(){
103 | try{
104 | return (int)mhGetTid.invokeExact();
105 | }
106 | catch(Throwable t){
107 | throw new RuntimeException("Exception happened at gettid() call.", t);
108 | }
109 | }
110 |
111 | private static long getTimestamp(){
112 | try(var arena = Arena.ofConfined()){
113 | var timespec = arena.allocate(structTimespec);
114 | int _ = (int)mhClockGettime.invokeExact(CLOCK_MONOTONIC, timespec);
115 |
116 | return ((long)hndSec.get(timespec, 0L) * 1_000_000_000) + (long)hndNSec.get(timespec, 0L);
117 | }
118 | catch(Throwable t){
119 | throw new RuntimeException("Exception happened at gettid() call.", t);
120 | }
121 | }
122 |
123 | // from tools/perf/util/jitdump.h in Linux Kernel
124 | // struct jitheader {
125 | // uint32_t magic; /* characters "jItD" */
126 | // uint32_t version; /* header version */
127 | // uint32_t total_size; /* total size of header */
128 | // uint32_t elf_mach; /* elf mach target */
129 | // uint32_t pad1; /* reserved */
130 | // uint32_t pid; /* JIT process id */
131 | // uint64_t timestamp; /* timestamp */
132 | // uint64_t flags; /* flags */
133 | // };
134 | private void writeHeader() throws IOException{
135 | final int headerSize = 40; // sizeof(struct jitheader)
136 | var buf = ByteBuffer.allocate(headerSize).order(ByteOrder.nativeOrder());
137 |
138 | short elfMach = -1;
139 | try(var chExe = FileChannel.open(Path.of("/proc/self/exe"), StandardOpenOption.READ)){
140 | var buf2 = ByteBuffer.allocate(2).order(ByteOrder.nativeOrder());
141 | chExe.read(buf2, 18); // id (16 bytes) + e_type (2 bytes)
142 | buf2.flip();
143 | elfMach = buf2.getShort();
144 | }
145 |
146 | // magic
147 | buf.putInt(JITHEADER_MAGIC);
148 | // version
149 | buf.putInt(JITHEADER_VERSION);
150 | // total_size
151 | buf.putInt(headerSize);
152 | // elf_mach
153 | buf.putInt(elfMach);
154 | // pad1
155 | buf.putInt(0);
156 | // pid
157 | buf.putInt((int)ProcessHandle.current().pid());
158 | // timestamp
159 | buf.putLong(getTimestamp());
160 | // flags
161 | buf.putLong(0L);
162 |
163 | buf.flip();
164 | ch.write(buf);
165 | }
166 |
167 | /**
168 | * Constructor of PerfJitDump.
169 | * This constructer creates dump file named with "jit-.dump"
170 | * into specified directory. Top of page (1 page: 4096 bytes)
171 | * will be mapped as a executable memory - it is mandatory for
172 | * recording in perf tool.
173 | * And also jitdump header will be written at this time.
174 | *
175 | * @param dir Path to base directory to dump.
176 | */
177 | public PerfJitDump(Path dir) throws UnsupportedPlatformException, PlatformException, IOException{
178 | // According to jit_detect() in tools/perf/util/jitdump.c in Linux Kernel,
179 | // dump file should be named "jit-.dump".
180 | var jitdumpPath = dir.resolve(String.format("jit-%d.dump", ProcessHandle.current().pid()));
181 | ch = FileChannel.open(jitdumpPath, StandardOpenOption.CREATE_NEW, StandardOpenOption.READ, StandardOpenOption.WRITE);
182 |
183 | // find FD of jitdump file
184 | int fdWork = -1;
185 | try(var links = Files.newDirectoryStream(Path.of("/proc/self/fd"))){
186 | for(var link : links){
187 | try{
188 | if(Files.isSameFile(jitdumpPath, Files.readSymbolicLink(link))){
189 | fdWork = Integer.parseInt(link.getFileName().toString());
190 | break;
191 | }
192 | }
193 | catch(NoSuchFileException e){
194 | // ignore
195 | }
196 | }
197 | }
198 | if(fdWork == -1){
199 | throw new IllegalStateException("FD of jitdump is not found.");
200 | }
201 | fd = fdWork;
202 |
203 | // from tools/perf/jvmti/jvmti_agent.c in Linux Kernel
204 | jitdump = LinuxExecMemory.mmap(MemorySegment.NULL, PAGE_SIZE, LinuxExecMemory.PROT_READ | LinuxExecMemory.PROT_EXEC, LinuxExecMemory.MAP_PRIVATE, fd, 0);
205 | codeIndex = 0;
206 |
207 | writeHeader();
208 | }
209 |
210 | // from tools/perf/util/jitdump.h in Linux Kernel
211 | // struct jr_prefix {
212 | // uint32_t id;
213 | // uint32_t total_size;
214 | // uint64_t timestamp;
215 | // };
216 | //
217 | // struct jr_code_load {
218 | // struct jr_prefix p;
219 | //
220 | // uint32_t pid;
221 | // uint32_t tid;
222 | // uint64_t vma;
223 | // uint64_t code_addr;
224 | // uint64_t code_size;
225 | // uint64_t code_index;
226 | // };
227 | /**
228 | * {@inheritDoc}
229 | */
230 | @Override
231 | public synchronized void writeFunction(CodeSegment.MethodInfo method){
232 | // sizeof(jr_code_load) == 56, null char of method name should be included.
233 | final int totalSize = 56 + method.name().length() + 1 + method.size();
234 | var buf = ByteBuffer.allocate(totalSize).order(ByteOrder.nativeOrder());
235 |
236 | // id
237 | buf.putInt(JIT_CODE_LOAD);
238 | // total_size
239 | buf.putInt(totalSize);
240 | // timestamp
241 | buf.putLong(getTimestamp());
242 | // pid
243 | buf.putInt((int)ProcessHandle.current().pid());
244 | // tid
245 | buf.putInt(getTid());
246 | // vma
247 | buf.putLong(method.address());
248 | // code_addr
249 | buf.putLong(method.address());
250 | // code_size
251 | buf.putLong(method.size());
252 | // code_index
253 | buf.putLong(codeIndex++);
254 |
255 | // method name
256 | buf.put(method.name().getBytes());
257 | buf.put((byte)0); // NUL
258 |
259 | // code
260 | var seg = MemorySegment.ofAddress(method.address()).reinterpret(method.size());
261 | buf.put(seg.toArray(ValueLayout.JAVA_BYTE));
262 |
263 | buf.flip();
264 | try{
265 | ch.write(buf);
266 | }
267 | catch(IOException e){
268 | throw new UncheckedIOException(e);
269 | }
270 | }
271 |
272 | // from tools/perf/util/jitdump.h in Linux Kernel
273 | // struct jr_prefix {
274 | // uint32_t id;
275 | // uint32_t total_size;
276 | // uint64_t timestamp;
277 | // };
278 | //
279 | // struct jr_code_close {
280 | // struct jr_prefix p;
281 | // };
282 | /**
283 | * {@inheritDoc}
284 | */
285 | @Override
286 | public synchronized void close() throws Exception{
287 | final int headerSize = 16; // sizeof(jr_code_close)
288 | var buf = ByteBuffer.allocate(headerSize).order(ByteOrder.nativeOrder());
289 |
290 | // id
291 | buf.putInt(JIT_CODE_CLOSE);
292 | // total_size
293 | buf.putInt(headerSize);
294 | // timestamp
295 | buf.putLong(getTimestamp());
296 |
297 | buf.flip();
298 | ch.write(buf);
299 |
300 | LinuxExecMemory.munmap(jitdump, PAGE_SIZE);
301 | ch.close();
302 | }
303 |
304 | }
305 |
--------------------------------------------------------------------------------
/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 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 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/com/yasuenag/ffmasm/test/amd64/NativeRegisterTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 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.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.CodeSegment;
32 | import com.yasuenag.ffmasm.NativeRegister;
33 | import com.yasuenag.ffmasm.amd64.AMD64AsmBuilder;
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 = AMD64AsmBuilder.create(AMD64AsmBuilder.class, 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, 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.CodeSegment;
33 | import com.yasuenag.ffmasm.amd64.AMD64AsmBuilder;
34 | import com.yasuenag.ffmasm.amd64.Register;
35 | import com.yasuenag.ffmasm.amd64.SSEAsmBuilder;
36 |
37 |
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 = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc)
52 | /* push %rbp */ .push(Register.RBP)
53 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty())
54 | .cast(SSEAsmBuilder.class)
55 | /* movdqa (arg1), %xmm0 */ .movdqaRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0))
56 | /* movdqa %xmm0, (arg2) */ .movdqaMR(Register.XMM0, argReg.arg2(), OptionalInt.of(0))
57 | /* leave */ .leave()
58 | /* ret */ .ret()
59 | .build();
60 |
61 | long[] expected = new long[]{1, 2}; // 64 * 2 = 128 bit
62 | var arena = Arena.ofAuto();
63 | MemorySegment src = arena.allocate(16, 16); // 128 bit
64 | MemorySegment dest = arena.allocate(16, 16); // 128 bit
65 | MemorySegment.copy(expected, 0, src, ValueLayout.JAVA_LONG, 0, expected.length);
66 |
67 | method.invoke(src, dest);
68 |
69 | Assertions.assertArrayEquals(expected, src.toArray(ValueLayout.JAVA_LONG));
70 | Assertions.assertArrayEquals(expected, dest.toArray(ValueLayout.JAVA_LONG));
71 | }
72 | catch(Throwable t){
73 | Assertions.fail(t);
74 | }
75 | }
76 |
77 | /**
78 | * Tests MOVDQU A/B
79 | */
80 | @Test
81 | @EnabledOnOs({OS.LINUX, OS.WINDOWS})
82 | public void testMOVDQU(){
83 | try(var seg = new CodeSegment()){
84 | var desc = FunctionDescriptor.ofVoid(
85 | ValueLayout.ADDRESS, // 1st argument
86 | ValueLayout.ADDRESS // 2nd argument
87 | );
88 | var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc)
89 | /* push %rbp */ .push(Register.RBP)
90 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty())
91 | .cast(SSEAsmBuilder.class)
92 | /* movdqu (arg1), %xmm0 */ .movdquRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0))
93 | /* movdqu %xmm0, (arg2) */ .movdquMR(Register.XMM0, argReg.arg2(), OptionalInt.of(0))
94 | /* leave */ .leave()
95 | /* ret */ .ret()
96 | .build();
97 |
98 | long[] expected = new long[]{1, 2}; // 64 * 2 = 128 bit
99 | var arena = Arena.ofAuto();
100 | MemorySegment src = arena.allocate(16, 16); // 128 bit
101 | MemorySegment dest = arena.allocate(16, 16); // 128 bit
102 | MemorySegment.copy(expected, 0, src, ValueLayout.JAVA_LONG, 0, expected.length);
103 |
104 | method.invoke(src, dest);
105 |
106 | Assertions.assertArrayEquals(expected, src.toArray(ValueLayout.JAVA_LONG));
107 | Assertions.assertArrayEquals(expected, dest.toArray(ValueLayout.JAVA_LONG));
108 | }
109 | catch(Throwable t){
110 | Assertions.fail(t);
111 | }
112 | }
113 |
114 | /**
115 | * Tests MOVD A
116 | */
117 | @Test
118 | @EnabledOnOs({OS.LINUX, OS.WINDOWS})
119 | public void testMOVD_A(){
120 | try(var seg = new CodeSegment()){
121 | var desc = FunctionDescriptor.of(
122 | ValueLayout.JAVA_FLOAT, // return value
123 | ValueLayout.ADDRESS // 1st argument
124 | );
125 | var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc)
126 | /* push %rbp */ .push(Register.RBP)
127 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty())
128 | .cast(SSEAsmBuilder.class)
129 | /* movd (arg1), %xmm0 */ .movdRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0))
130 | /* leave */ .leave()
131 | /* ret */ .ret()
132 | .build();
133 |
134 | float expected = 1.1f;
135 | var arena = Arena.ofAuto();
136 | MemorySegment src = arena.allocate(ValueLayout.JAVA_FLOAT);
137 | src.set(ValueLayout.JAVA_FLOAT, 0, expected);
138 |
139 | float actual = (float)method.invoke(src);
140 |
141 | Assertions.assertEquals(expected, actual);
142 | }
143 | catch(Throwable t){
144 | Assertions.fail(t);
145 | }
146 | }
147 |
148 | /**
149 | * Tests MOVD B
150 | */
151 | @Test
152 | @EnabledOnOs({OS.LINUX, OS.WINDOWS})
153 | public void testMOVD_B(){
154 | try(var seg = new CodeSegment()){
155 | var arena = Arena.ofAuto();
156 | MemorySegment dest = arena.allocate(ValueLayout.JAVA_FLOAT);
157 |
158 | var desc = FunctionDescriptor.ofVoid(
159 | ValueLayout.JAVA_FLOAT // 1st argument
160 | );
161 | var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc)
162 | /* push %rbp */ .push(Register.RBP)
163 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty())
164 | // Mixed argument order (int, fp) is different between Windows and Linux.
165 | // Thus address is loaded from immediate value.
166 | /* mov addr, %rax */ .movImm(Register.RAX, dest.address())
167 | .cast(SSEAsmBuilder.class)
168 | /* movd %xmm0, (%rax) */ .movdMR(Register.XMM0, Register.RAX, OptionalInt.of(0))
169 | /* leave */ .leave()
170 | /* ret */ .ret()
171 | .build();
172 |
173 | float expected = 1.1f;
174 |
175 | method.invoke(expected);
176 | float actual = dest.get(ValueLayout.JAVA_FLOAT, 0);
177 |
178 | Assertions.assertEquals(expected, actual);
179 | }
180 | catch(Throwable t){
181 | Assertions.fail(t);
182 | }
183 | }
184 |
185 | /**
186 | * Tests MOVQ A
187 | */
188 | @Test
189 | @EnabledOnOs({OS.LINUX, OS.WINDOWS})
190 | public void testMOVQ_A(){
191 | try(var seg = new CodeSegment()){
192 | var desc = FunctionDescriptor.of(
193 | ValueLayout.JAVA_DOUBLE, // return value
194 | ValueLayout.ADDRESS // 1st argument
195 | );
196 | var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc)
197 | /* push %rbp */ .push(Register.RBP)
198 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty())
199 | .cast(SSEAsmBuilder.class)
200 | /* movq (arg1), %xmm0 */ .movqRM(Register.XMM0, argReg.arg1(), OptionalInt.of(0))
201 | /* leave */ .leave()
202 | /* ret */ .ret()
203 | .build();
204 |
205 | double expected = 1.1d;
206 | var arena = Arena.ofAuto();
207 | MemorySegment src = arena.allocate(ValueLayout.JAVA_DOUBLE);
208 | src.set(ValueLayout.JAVA_DOUBLE, 0, expected);
209 |
210 | double actual = (double)method.invoke(src);
211 |
212 | Assertions.assertEquals(expected, actual);
213 | }
214 | catch(Throwable t){
215 | Assertions.fail(t);
216 | }
217 | }
218 |
219 | /**
220 | * Tests MOVQ B
221 | */
222 | @Test
223 | @EnabledOnOs({OS.LINUX, OS.WINDOWS})
224 | public void testMOVQ_B(){
225 | try(var seg = new CodeSegment()){
226 | var arena = Arena.ofAuto();
227 | MemorySegment dest = arena.allocate(ValueLayout.JAVA_DOUBLE);
228 |
229 | var desc = FunctionDescriptor.ofVoid(
230 | ValueLayout.JAVA_DOUBLE // 1st argument
231 | );
232 | var method = AMD64AsmBuilder.create(SSEAsmBuilder.class, seg, desc)
233 | /* push %rbp */ .push(Register.RBP)
234 | /* mov %rsp, %rbp */ .movMR(Register.RSP, Register.RBP, OptionalInt.empty())
235 | // Mixed argument order (int, fp) is different between Windows and Linux.
236 | // Thus address is loaded from immediate value.
237 | /* mov addr, %rax */ .movImm(Register.RAX, dest.address())
238 | .cast(SSEAsmBuilder.class)
239 | /* movq %xmm0, (%rax) */ .movqMR(Register.XMM0, Register.RAX, OptionalInt.of(0))
240 | /* leave */ .leave()
241 | /* ret */ .ret()
242 | .build();
243 |
244 | double expected = 1.1d;
245 |
246 | method.invoke(expected);
247 | double actual = dest.get(ValueLayout.JAVA_DOUBLE, 0);
248 |
249 | Assertions.assertEquals(expected, actual);
250 | }
251 | catch(Throwable t){
252 | Assertions.fail(t);
253 | }
254 | }
255 |
256 | }
257 |
--------------------------------------------------------------------------------
/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.CodeSegment;
31 | import com.yasuenag.ffmasm.UnsupportedPlatformException;
32 | import com.yasuenag.ffmasm.amd64.AMD64AsmBuilder;
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 AMD64AsmBuilder.create(AMD64AsmBuilder.class, 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/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 |
--------------------------------------------------------------------------------