Script for Parsing jtreg GitHub Actions Results

I was reviewing jtreg test failures in some GitHub actions last week. Since I was only interested in the failures, I decided to write a script to extract the failure details from the log files I had downloaded from GitHub. This looked like another task for the VS Code agent so I wrote the algorithm for extracting the failure details into individual text files, which would be easier to review. Below is the prompt I used.

write a python script that processes all the text files in a user-specified folder. For each text file:
- split it into sections using "--------------------------------------------------" as a separator
- generate a filename for the section from the first line after the separator. To do so, use the text after "TEST: " and replace /, #, and . with underscores and append the ".txt" extension.
- if the section contains the string "test jdk:" then write the content of the entire section to a file with the generated name.

I thought I would need to do at least some debugging of the script but I did not! The script worked flawlessly. I didn’t even need to execute it myself because the flow of using the agent included running the script on my raw folder and then generating a README file! See the script and the README at Add scripts generated by Claude Sonnet 4 VS Code agent · swesonga/scratchpad@f5e8057. The agent was using the Claude Sonnet 4 model.


Categories: OpenJDK, Testing

Running jtreg shell tests on Windows

As part of the jdk11u release process, I needed to run some shell tests on my Windows desktop to determine whether they failed due to a product issue or an environment issue. I defaulted to using my Git Bash environment instead of Cygwin. This post shares some errors I ran into as a result of the different shell environment. The key takeaway was to run such tests in Cygwin whenever I encountered path errors.

rmic Tests

The rmic Tools Reference page describes it as follows:

You use the rmic compiler to generate stub and skeleton class files using the Java Remote Method Protocol (JRMP).

The first test failure I investigated was in jdk11u/test/jdk/sun/rmi/rmic/defaultStubVersion/run.sh at jdk-11.0.28+6 · openjdk/jdk11u. I used my run-jtreg-test.sh script to execute it:

./run-jtreg-test.sh /d/java/ms/openjdk-jdk11u /d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6 /c/java/binaries/jtreg/jtreg-7.4+1/lib/jtreg.jar test/jdk/sun/rmi/rmic/defaultStubVersion/run.sh -nativepath:/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6-test-image/hotspot/jtreg/native

This is the actual command that was executed:

/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/java -Xmx512m -jar /c/java/binaries/jtreg/jtreg-7.4+1/lib/jtreg.jar -agentvm -ignore:quiet -automatic -xml -vmoption:-Xmx512m -timeoutFactor:4 -concurrency:1 -testjdk:/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6 -verbose:fail,error,summary -nativepath:/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6-test-image/hotspot/jtreg/native test/jdk/sun/rmi/rmic/defaultStubVersion/run.sh

The script failed on my local machine with the error below:

STDOUT:
STDERR:
+ defdir=./default_output
+ refdir=./reference_output
+ rm -rf ./default_output ./reference_output
+ mkdir ./default_output ./reference_output
+ /mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/rmic -classpath D:/java/ms/openjdk-jdk11u/JTwork/classes/sun/rmi/rmic/defaultStubVersion/run.d -keep -nowrite -d ./default_output G1Impl
/mnt/d/java/ms/openjdk-jdk11u/test/jdk/sun/rmi/rmic/defaultStubVersion/run.sh: 49: /mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/rmic: not found

I confirmed that rmic.exe exists in the bin directory of the jdk.

$ ls -1 /d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/rmic*
/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/rmic.exe*

Changing the test to directly refer to rmic.exe instead of rmic (as shown in the diff below) resulted in the test passing on my machine. I concluded that this specific issue must therefore be a test bug (i.e. the test should avoid this issue on Windows). See Shell Tests in jtreg for possible ways to fix this. This is one reason why shell scripts are being discouraged for OpenJDK testing.

diff --git a/test/jdk/sun/rmi/rmic/defaultStubVersion/run.sh b/test/jdk/sun/rmi/rmic/defaultStubVersion/run.sh
index 02f71d0c81..0b80015cf4 100644
--- a/test/jdk/sun/rmi/rmic/defaultStubVersion/run.sh
+++ b/test/jdk/sun/rmi/rmic/defaultStubVersion/run.sh
@@ -46,8 +46,8 @@ refdir=./reference_output
 rm -rf $defdir $refdir
 mkdir $defdir $refdir

-${TESTJAVA}/bin/rmic -classpath ${TESTCLASSES:-.} -keep -nowrite -d $defdir G1Impl
-${TESTJAVA}/bin/rmic -classpath ${TESTCLASSES:-.} -keep -nowrite -d $refdir -v1.2 G1Impl
+${TESTJAVA}/bin/rmic.exe -classpath ${TESTCLASSES:-.} -keep -nowrite -d $defdir G1Impl
+${TESTJAVA}/bin/rmic.exe -classpath ${TESTCLASSES:-.} -keep -nowrite -d $refdir -v1.2 G1Impl

 diff -r $defdir $refdir

Notice that the diff(1) – Linux manual page command has an exit code of 0 if the files are identical. This test failed in the test environment with this warning from jdk11u/src/jdk.rmic/share/classes/sun/rmi/rmic/resources/rmic.properties at jdk-11.0.28+6 · openjdk/jdk11u

STDOUT:
Warning: generation and use of skeletons and static stubs for JRMP	
is deprecated. Skeletons are unnecessary, and static stubs have	
been superseded by dynamically generated stubs. Users are	
encouraged to migrate away from using rmic to generate skeletons and static	
stubs. See the documentation for java.rmi.server.UnicastRemoteObject.

This warning is output by the sun.rmi.rmic.Main.compile method. The arguments to rmic.exe are parsed by Main.parseArgs. The -v1.2 argument specifies the non-default generator to use. My local machine must be using a different default generator (or so I thought).

Outstanding Questions at this Point

  1. Why couldn’t rmic be found on my machine?
  2. Why did this test fail in the test environment?

jrunscript Test

The next test failure I dug into was jdk11u/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh at jdk-11.0.28+6 · openjdk/jdk11u. I switched to MSYS2 for this one.

./run-jtreg-test.sh /d/java/ms/openjdk-jdk11u /d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6 /c/java/binaries/jtreg/jtreg-7.4+1/lib/jtreg.jar test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh -nativepath:/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6-test-image/hotspot/jtreg/native

This failed with another path error.

STDOUT:
STDERR:
/mnt/d/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh: 33: .: cannot open D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/common.sh: No such file

My first thought was “where is the TESTSRC set“? It actually appeared in the jtreg output, confirming that it was correctly set:

TESTFILE=D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh \
TESTSRC=D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript \
TESTSRCPATH=D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript \

So what does the failing line do? I asked Copilot: what does this command do in a bash script? . ${TESTSRC-.}/common.sh

  1. . (dot command)
    This is shorthand for source, which executes the contents of a file in the current shell environment rather than in a subshell. This means any variables, functions, or changes made by the script will persist in the current shell.
  2. ${TESTSRC-.}
    This is a parameter expansion. It means:
    • Use the value of the TESTSRC environment variable if it is set.
    • If TESTSRC is not set, use . (the current directory) as the default.
  3. /common.sh
    This appends /common.sh to the path determined above.

This error was a Windows path issue again. Notepad++ could open D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/common.sh so the path itself was fine. I used this workaround:

diff --git a/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh b/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
index 6a3d4e76ff..94001967b0 100644
--- a/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
+++ b/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
@@ -30,10 +30,11 @@
 # @run shell jrunscript-eTest.sh
 # @summary Test that output of 'jrunscript -e' matches the dash-e.out file

-. ${TESTSRC-.}/common.sh
+comn=`/mnt/c/software/msys64/usr/bin/cygpath.exe ${TESTSRC-.}/common.sh`
+. "/mnt$comn"

 setup
-${JAVA} ${TESTVMOPTS} ${TESTJAVAOPTS} -cp ${TESTCLASSES} CheckEngine
+${JAVA}.exe ${TESTVMOPTS} ${TESTJAVAOPTS} -cp ${TESTCLASSES} CheckEngine
 if [ $? -eq 2 ]; then
     echo "No js engine found and engine not required; test vacuously passes."
     exit 0

This got me to the actual test error:

STDOUT:
Output of jrunscript -e differ from expected output. Failed.
STDERR:
Warning: Nashorn engine is planned to be removed from a future JDK release
diff: D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/dash-e.out: No such file or directory

I couldn’t see which command generated the output though, so I added set -ex to the top of the script (like run.sh in the previous test). This was the resulting output:

STDOUT:
STDERR:
+ /mnt/c/software/msys64/usr/bin/cygpath.exe D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/common.sh
+ comn=/d/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/common.sh
+ . /mnt/d/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/common.sh
+ setup
+ [ /mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6 =  ]
+ [ D:/java/ms/openjdk-jdk11u/JTwork/classes/sun/tools/jrunscript/jrunscript-eTest.d =  ]
+ [ D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript =  ]
+ uname -s
+ OS=Linux
+ PS=:
+ FS=/
+ golden_diff=diff
+ JRUNSCRIPT=/mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/jrunscript
+ JAVAC=/mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/javac
+ JAVA=/mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/java
+ /mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/java.exe -Xmx512m -cp D:/java/ms/openjdk-jdk11u/JTwork/classes/sun/tools/jrunscript/jrunscript-eTest.d CheckEngine
Warning: Nashorn engine is planned to be removed from a future JDK release
+ [ 0 -eq 2 ]
+ rm -f jrunscript-eTest.out
+ /mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/jrunscript -J-Dnashorn.args.prepend=--no-deprecation-warning -J-Djava.awt.headless=true -l nashorn -e println('hello')

Aha! Notice the root cause of the filename issues: OS=Linux! This also confirmed that I was using the same diff command. I didn’t get the error message at jdk11u/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh at jdk-11.0.28+5 · openjdk/jdk11u because because of set -ex (the e means exit immediately if any command fails, which does not match the behavior this test requires). After removing the e, I got this output:

STDOUT:
Output of jrunscript -e differ from expected output. Failed.
STDERR:
+ /mnt/c/software/msys64/usr/bin/cygpath.exe D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/common.sh
+ comn=/d/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/common.sh
+ . /mnt/d/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/common.sh
+ setup
+ [ /mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6 =  ]
+ [ D:/java/ms/openjdk-jdk11u/JTwork/classes/sun/tools/jrunscript/jrunscript-eTest.d =  ]
+ [ D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript =  ]
+ uname -s
+ OS=Linux
+ PS=:
+ FS=/
+ golden_diff=diff
+ JRUNSCRIPT=/mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/jrunscript
+ JAVAC=/mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/javac
+ JAVA=/mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/java
+ /mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/java.exe -Xmx512m -cp D:/java/ms/openjdk-jdk11u/JTwork/classes/sun/tools/jrunscript/jrunscript-eTest.d CheckEngine
Warning: Nashorn engine is planned to be removed from a future JDK release
+ [ 0 -eq 2 ]
+ rm -f jrunscript-eTest.out
+ /mnt/d/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/jrunscript -J-Dnashorn.args.prepend=--no-deprecation-warning -J-Djava.awt.headless=true -l nashorn -e println('hello')
+ diff jrunscript-eTest.out D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/dash-e.out
diff: D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/dash-e.out: No such file or directory
+ [ 2 != 0 ]
+ echo Output of jrunscript -e differ from expected output. Failed.
+ rm -f jrunscript-eTest.out
+ exit 1

The ls command confirmed that the file exists:

$ ls -1 `cygpath D:/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/dash-e.out`
/d/java/ms/openjdk-jdk11u/test/jdk/sun/tools/jrunscript/dash-e.out

I patched the script as shown in the next diff:

diff --git a/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh b/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
index 6a3d4e76ff..4c7130857e 100644
--- a/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
+++ b/test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
@@ -30,10 +30,13 @@
 # @run shell jrunscript-eTest.sh
 # @summary Test that output of 'jrunscript -e' matches the dash-e.out file

-. ${TESTSRC-.}/common.sh
+set -x
+
+comn=`/mnt/c/software/msys64/usr/bin/cygpath.exe ${TESTSRC-.}/common.sh`
+. "/mnt$comn"

 setup
-${JAVA} ${TESTVMOPTS} ${TESTJAVAOPTS} -cp ${TESTCLASSES} CheckEngine
+${JAVA}.exe ${TESTVMOPTS} ${TESTJAVAOPTS} -cp ${TESTCLASSES} CheckEngine
 if [ $? -eq 2 ]; then
     echo "No js engine found and engine not required; test vacuously passes."
     exit 0
@@ -44,7 +47,9 @@ fi
 rm -f jrunscript-eTest.out 2>/dev/null
 ${JRUNSCRIPT} -J-Dnashorn.args.prepend=--no-deprecation-warning -J-Djava.awt.headless=true -l nashorn -e "println('hello')" > jrunscript-eTest.out 2>&1

-$golden_diff jrunscript-eTest.out ${TESTSRC}/dash-e.out
+diffarg=`/mnt/c/software/msys64/usr/bin/cygpath.exe ${TESTSRC}/dash-e.out`
+
+$golden_diff jrunscript-eTest.out "/mnt$diffarg"
 if [ $? != 0 ]
 then
   echo "Output of jrunscript -e differ from expected output. Failed."

Avoiding Path Issues

This madness (in the diff above) that made me realize that I needed to fix the path issues and that perhaps Cygwin was the better environment for these tests. Sure enough, the test passed the first time I executed it in Cygwin:

./run-jtreg-test.sh /cygdrive/d/java/ms/dups/openjdk-jdk11u D:/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6 C:/java/binaries/jtreg/jtreg-7.4+1/lib/jtreg.jar test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh -nativepath:D:/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6-test-image/hotspot/jtreg/native
Executing: D:/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/java -Xmx512m -jar C:/java/binaries/jtreg/jtreg-7.4+1/lib/jtreg.jar -agentvm -ignore:quiet -automatic -xml -vmoption:-Xmx512m -timeoutFactor:4 -concurrency:1 -testjdk:D:/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6 -verbose:fail,error,summary -nativepath:D:/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6-test-image/hotspot/jtreg/native test/jdk/sun/tools/jrunscript/jrunscript-eTest.sh
XML output  to D:\java\ms\dups\openjdk-jdk11u\JTwork
Passed: sun/tools/jrunscript/jrunscript-eTest.sh
Test results: passed: 1

Moral of the story: run OpenJDK shell tests on Windows in the Cygwin environment! At this point, the only outstanding question is about the difference between the local machine and the test environment the build was executed in. I thought that the fact that I didn’t get the rmic.jrmp.stubs.deprecated warning meant that a different default generator was used on my machine. However, I realized that the stdout messages were not being displayed! That warning was present in the output but I needed to open JTwork\sun\rmi\rmic\defaultStubVersion\run.jtr to see it!

----------System.out:(11/743)----------
Warning: generation and use of skeletons and static stubs for JRMP	
is deprecated. Skeletons are unnecessary, and static stubs have	
been superseded by dynamically generated stubs. Users are	
encouraged to migrate away from using rmic to generate skeletons and static	
stubs. See the documentation for java.rmi.server.UnicastRemoteObject.
Warning: generation and use of skeletons and static stubs for JRMP	
is deprecated. Skeletons are unnecessary, and static stubs have	
been superseded by dynamically generated stubs. Users are	
encouraged to migrate away from using rmic to generate skeletons and static	
stubs. See the documentation for java.rmi.server.UnicastRemoteObject.
TEST PASSED: default output identical to -v1.2 output
----------System.err:(8/706)----------
+ defdir=./default_output
+ refdir=./reference_output
+ rm -rf ./default_output ./reference_output
+ mkdir ./default_output ./reference_output
+ D:/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/rmic -classpath D:/java/ms/openjdk-jdk11u/JTwork/classes/sun/rmi/rmic/defaultStubVersion/run.d -keep -nowrite -d ./default_output G1Impl
+ D:/java/binaries/jdk/x64/2025-07/windows-jdk11u/jdk-11.0.28+6/bin/rmic -classpath D:/java/ms/openjdk-jdk11u/JTwork/classes/sun/rmi/rmic/defaultStubVersion/run.d -keep -nowrite -d ./reference_output -v1.2 G1Impl
+ diff -r ./default_output ./reference_output
+ echo 'TEST PASSED: default output identical to -v1.2 output'

This was sufficient for me to confirm that the build behaves as expected in this scenario.


Categories: OpenJDK

Building the OpenJDK Zero Variant on Windows

I was recently investigating the behavior of the OpenJDK interpreter on Windows. Demystifying the JVM: JVM Variants, Cppinterpreter and TemplateInterpreter describes two different interpreter implementations. I wanted to try reproducing the bug I was investigating using the simple BytecodeInterpreter. I was wondering how to enable it but the post led me to the realization that it’s not a configurable option. I needed to build the JVM specifically to include this interpreter. We need to get Julian Waters’ Windows/Zero branch with the changes I made to build using Visual C++: swesonga/jdk at swesonga/TheShermanTanker/experimental. The next sections cover the steps for building for x64 and ARM64.

Building Zero for Windows x64

Build libffi for Windows x64 (see Building libffi for Windows x64 with Visual C++) then set up the repo for use by OpenJDK as follows:

cd /c/repos/libffi
mkdir lib
cp x86_64-w64-mingw32/.libs/libffi-8.lib lib/libffi.lib
cp x86_64-w64-mingw32/include/ffi.h include/
cp x86_64-w64-mingw32/include/ffitarget.h include/

Configure the OpenJDK repo using these commands:

git checkout swesonga/TheShermanTanker/experimental

bash configure --with-jvm-variants=zero --with-libffi=/cygdrive/c/repos/libffi --disable-warnings-as-errors --with-debug-level=slowdebug --with-jtreg=/cygdrive/c/java/binaries/jtreg/jtreg-7.5.1+1 --with-gtest=/cygdrive/c/repos/googletest --with-extra-ldflags=-profile --with-boot-jdk=/cygdrive/c/java/binaries/jdk/x64/jdk-24+36

Build OpenJDK:

time /cygdrive/c/repos/scratchpad/scripts/java/cygwin/build-jdk.sh windows x86_64 slowdebug zero

The build will fail with Error: Failed to load D:\java\forks\dups11\openjdk\jdk\build\windows-x86_64-zero-slowdebug\jdk\bin\zero\jvm.dll. However, the build is still usable (all the binaries and symbols should be present). I am saving that investigation for another day. For now, verify that the build works by running this command:

$ build/windows-x86_64-zero-slowdebug/jdk/bin/java.exe -version
java version "25-internal" 2025-09-16
Java Runtime Environment (slowdebug build 25-internal-adhoc.USERsaint.jdk)
Java HotSpot 64-Bit Zero VM (slowdebug build 25-internal-adhoc.USERsaint.jdk, interpreted mode)

Building Zero for Windows AArch64

Once the steps in Building libffi for Windows ARM64 with Visual C++ in MSYS are complete, set up the repo for use by OpenJDK as follows:

cd /c/repos/libffi
mkdir lib
cp aarch64-w64-mingw32/.libs/libffi-8.lib lib/libffi.lib
cp aarch64-w64-mingw32/include/ffi.h include/
cp aarch64-w64-mingw32/include/ffitarget.h include/

Configure the OpenJDK repo using these commands:

git checkout swesonga/TheShermanTanker/experimental

bash configure --with-jvm-variants=zero --with-libffi=/cygdrive/d/repos/dups/libffi --disable-warnings-as-errors --with-debug-level=slowdebug --with-jtreg=/cygdrive/c/java/binaries/jtreg/jtreg-7.5.1+1 --with-gtest=/cygdrive/c/repos/googletest --with-extra-ldflags=-profile --with-boot-jdk=/cygdrive/c/java/binaries/jdk/x64/jdk-24+36

Build OpenJDK:

time /cygdrive/c/repos/scratchpad/scripts/java/cygwin/build-jdk.sh windows aarch64 slowdebug zero

Background Investigation

OpenJDK’s building.md says to use --with-jvm-variants configure argument to specify the “zero” variant:

bash configure --with-jvm-variants=zero --with-debug-level=slowdebug --with-jtreg=/cygdrive/c/java/binaries/jtreg/jtreg-7.5.1+1 --with-gtest=/cygdrive/c/repos/googletest --with-extra-ldflags=-profile --with-boot-jdk=/cygdrive/c/java/binaries/jdk/x64/jdk-24+36

The configure script failed with this error:

checking for --enable-hsdis-bundling... disabled, default
checking what hsdis backend to use... 'none', hsdis will not be built
checking if hsdis should be bundled... no
checking for --enable-libffi-bundling... disabled, default
checking for LIBFFI... checking for ffi.h... no
configure: error: Could not find libffi!
configure exiting with result code 1

I took at look at the source of the error message in jdk/make/autoconf/lib-ffi.m4 and realized that I need to have the repo.

git clone https://github.com/libffi/libffi.git
cd libffi
git checkout v3.4.8

I then added the --with-libffi argument to the configure script.

bash configure --with-jvm-variants=zero --with-debug-level=slowdebug --with-jtreg=/cygdrive/c/java/binaries/jtreg/jtreg-7.5.1+1 --with-gtest=/cygdrive/c/repos/googletest --with-extra-ldflags=-profile --with-boot-jdk=/cygdrive/c/java/binaries/jdk/x64/jdk-24+36 --with-libffi=/cygdrive/d/repos/libffi

This didn’t address the failure but closer inspection of the .m4 led me to realize that I need to build libffi. This was the genesis of the post on Building libffi for Windows x64 with Visual C++. Once I had built libffi and created the .lib file in the expected location of its repo, the configure script succeeded. I started the build with this command:

time /cygdrive/c/repos/scratchpad/scripts/java/cygwin/build-jdk.sh windows x86_64 slowdebug zero

The build failed!

* For target hotspot_variant-zero_libjvm_gtest_objs_BUILD_GTEST_LIBJVM_pch.obj:
BUILD_GTEST_LIBJVM_pch.cpp
d:\java\forks\dups11\openjdk\jdk\src\hotspot\share\runtime/globals.hpp(35): fatal error C1083: Cannot open include file: 'globals_windows_zero.hpp': No such file or directory
   ... (rest of output omitted)

This failure reminded me of the Windows/Zero mail where Julian Waters fixed build zero for windows (although his suggestion to fix this was getting shot down). I was wondering whether this was working at some point in a previous release so I tried openjdk/jdk11u-dev at c5407b6a8464fcc1eed31a2e9e30651e9011dbd3. I got the same error. The next step was to build Julian Waters’ Windows/Zero branch: Windows/Zero Port · TheShermanTanker/jdk@f504cd8. I imported it into my fork as follows:

git remote add TheShermanTanker https://github.com/TheShermanTanker/jdk
git fetch TheShermanTanker
git checkout experimental
git checkout -b swesonga/TheShermanTanker/experimental
git push --set-upstream origin swesonga/TheShermanTanker/experimental

Building this branch failed with this error:

* For target support_gensrc_java.base__SocketOptionRegistry.java:
/*
 * Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved.
 *
...
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
   ... (rest of output omitted)

* All command lines available in /cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/make-support/failure-logs.
=== End of repeated output ===

There was a .log file and a .cmd file in the failure-logs folder. build\windows-x86_64-zero-slowdebug\make-support\failure-logs\support_gensrc_java.base__SocketOptionRegistry.java.log contained this:

/*
 * Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved.
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */
/cygdrive/d/java/forks/dups11/openjdk/jdk/make/scripts/fixpath.sh: line 486: c:\progra~1\mib055~1\2022\enterp~1\vc\tools\msvc\1444~1.352\bin\hostx64\x64\cl.exe: command not found

build\windows-x86_64-zero-slowdebug\make-support\failure-logs\support_gensrc_java.base__SocketOptionRegistry.java.cmdline contained this:

( /usr/bin/gawk '/@@END_COPYRIGHT@@/{exit}1' /cygdrive/d/java/forks/dups11/openjdk/jdk/src/java.base/share/classes/sun/nio/ch/SocketOptionRegistry.java.template && /cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/fixpath exec /cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/fixpath exec /cygdrive/c/progra~1/mib055~1/2022/enterp~1/vc/tools/msvc/1444~1.352/bin/hostx64/x64/cl.exe -E -nologo -I/cygdrive/c/progra~1/mib055~1/2022/enterp~1/vc/tools/msvc/1444~1.352/include -I/cygdrive/c/progra~1/mib055~1/2022/enterp~1/vc/tools/msvc/1444~1.352/atlmfc/include -I/cygdrive/c/progra~1/mib055~1/2022/enterp~1/vc/auxili~1/vs/include -I/cygdrive/c/progra~2/wi3cf2~1/10/include/100261~1.0/ucrt -I/cygdrive/c/progra~2/wi3cf2~1/10/include/100261~1.0/um -I/cygdrive/c/progra~2/wi3cf2~1/10/include/100261~1.0/shared -I/cygdrive/c/progra~2/wi3cf2~1/10/include/100261~1.0/winrt -I/cygdrive/c/progra~2/wi3cf2~1/10/include/100261~1.0/cppwinrt -I/cygdrive/c/progra~2/wi3cf2~1/netfxsdk/4.8/include/um -D_CRT_DECLARE_NONSTDC_NAMES -D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS -nologo -MD -Zc:preprocessor -Zc:inline -Zc:throwingNew -permissive- -volatile:iso -utf-8 -Zc:wchar_t- -DLIBC=default -DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0602 -DWIN32 -DIAL -DWINDOWS -DDEBUG -W3 -Z7 -experimental:deterministic -std:c11 -D_LITTLE_ENDIAN -DARCH='"amd64"' -Damd64 -D_LP64=1 -D_AMD64_ -Damd64 /cygdrive/d/java/forks/dups11/openjdk/jdk/src/java.base/share/classes/sun/nio/ch/SocketOptionRegistry.java.template 2> >(/usr/bin/grep -v '^SocketOptionRegistry.java.template$' >&2) | /usr/bin/gawk '/@@START_HERE@@/,0' | /usr/bin/sed -e 's/@@START_HERE@@/\/\/ AUTOMATICALLY GENERATED FILE - DO NOT EDIT/' -e 's/PREFIX_//' -e 's/^#.*//' ) > /cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/support/gensrc/java.base/sun/nio/ch/SocketOptionRegistry.java

This might be the first time I ever looked up AWK – Wikipedia since I needed to understand what was happening here. The hack I used to address this is Fix ‘command not found’ error · swesonga/jdk@6145ebb.

There were several warnings (e.g. warning C4267: ‘initializing’: conversion from ‘size_t’ to ‘int’, possible loss of data) and a build error that I fixed in Fix MSVC warnings and build errors · swesonga/jdk@bd519ea. I later realized that I could have used the --disable-warnings-as-errors configure argument but it was good for me to be aware of which issues I could run into later when using the zero interpreter. At this point, the build failed with this message:

* For target buildtools_create_symbols_javac__the.COMPILE_CREATE_SYMBOLS_batch:
Error: Failed to load D:\java\forks\dups11\openjdk\jdk\build\windows-x86_64-zero-slowdebug\jdk\bin\zero\jvm.dll

* All command lines available in /cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/make-support/failure-logs.

That error in the only line in the build\windows-x86_64-zero-slowdebug\make-support\failure-logs\buildtools_create_symbols_javac__the.COMPILE_CREATE_SYMBOLS_batch.log file. This was the .cmdline file (next to the .log file):

/cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/fixpath exec /cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/jdk/bin/javac -J-Djava.io.tmpdir=/cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/support/javatmp -g -Xlint:all -source 25 -target 25 -implicit:none -Xprefer:source -XDignore.symbol.file=true -encoding ascii --add-modules jdk.compiler,jdk.jdeps --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED -Werror -Xlint:-options -XDmodifiedInputs=/cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/buildtools/create_symbols_javac/_the.COMPILE_CREATE_SYMBOLS_batch.modfiles.fixed -d /cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/buildtools/create_symbols_javac @/cygdrive/d/java/forks/dups11/openjdk/jdk/build/windows-x86_64-zero-slowdebug/buildtools/create_symbols_javac/_the.COMPILE_CREATE_SYMBOLS_batch.filelist

However, the build process appeared to have actually created a usable JDK!

build/windows-x86_64-zero-slowdebug/jdk/bin/java.exe -version

That meant that I didn’t need to look any further into this error (at least not right away) so I moved on to building for the ARM64 platform. I initially tried cross compiling but this didn’t work. This is the configure command I tried for cross compiling.

bash configure --with-jvm-variants=zero --with-libffi=/cygdrive/d/repos/dups/libffi --openjdk-target=aarch64-unknown-cygwin --disable-warnings-as-errors --with-debug-level=slowdebug --with-jtreg=/cygdrive/c/java/binaries/jtreg/jtreg-7.5.1+1 --with-gtest=/cygdrive/c/repos/googletest --with-extra-ldflags=-profile --with-boot-jdk=/cygdrive/c/java/binaries/jdk/x64/jdk-24+36

The linker failed with a series of 19 unresolved symbols and a warning that the library machine type ‘ARM64’ conflicts with target machine type ‘x64’. Notice that the === Output from failing command(s) repeated here === section did not contain the linker warning LNK4272 since it omits the rest of the input after showing the first 14 lines. Scanning the build log is important in such cases where some of the output is omitted at the end.

...
fallbackLinker.o : error LNK2019: unresolved external symbol __imp_ffi_type_double referenced in function Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1double
fallbackLinker.o : error LNK2019: unresolved external symbol __imp_ffi_type_pointer referenced in function Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1pointer
d:\repos\dups\libffi\lib\libffi.lib : warning LNK4272: library machine type 'ARM64' conflicts with target machine type 'x64'
d:\java\forks\dups11\openjdk\jdk\build\windows-aarch64-zero-slowdebug\buildjdk\support\modules_libs\java.base\fallbackLinker.dll : fatal error LNK1120: 19 unresolved externals

At this point, I switched to my ARM64 machine (to avoid these mismatch issues) and copied the the libffi files onto it (after cloning the libffi repo). The --openjdk-target argument is no longer necessary for a native build on Windows ARM64. The build failed since the CONTEXT (x86 64-bit) struct (defined in C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um\winnt.h is a platform specific struct. This fix was needed for ARM64: Fix build errors on Windows AArch64 · swesonga/jdk@d3ec3c7. OpenJDK now built successfully, well, until the same error about being unable to load jvm.dll.

I moved on to testing the build but this time it didn’t work! I added some debug output in Display GetLastError() on failure to load DLL · swesonga/jdk@ce7143e. GetLastError was 126, i.e. “the specified module could not be found” as per the System Error Codes (0-499) (WinError.h). I verified that the path to jvm.dll was valid then used Process Monitor to record events on the system while running java.exe. Turns out I needed to copy libffi-8.dll into the directory containing jvm.dll.


Code Coverage for OpenJDK Windows Binaries

Build the OpenJDK source code using these commands:

bash configure --with-debug-level=slowdebug --with-jtreg=/cygdrive/c/java/binaries/jtreg/jtreg-7.5.1+1 --with-gtest=/cygdrive/c/repos/googletest --with-extra-ldflags=-profile --with-boot-jdk=/cygdrive/c/java/binaries/jdk/x64/jdk-24+36

time /cygdrive/c/repos/scratchpad/scripts/java/cygwin/build-jdk.sh windows x86_64 slowdebug

The --with-extra-ldflags=-profile argument enables the /PROFILE (Performance Tools Profiler) | Microsoft Learn linker option when building native binaries. build-jdk.sh is a custom script in my scratchpad repo.

Instrumenting the DLLs and EXEs

The Microsoft.CodeCoverage.Console tool can be used to collect code coverage for C++ code. To use it, open the “Developer Command Prompt” – it should be in the path by default. Once the OpenJDK build completes, instrument the DLLs and EXEs as follows:

Microsoft.CodeCoverage.Console.exe instrument attach.dll
Microsoft.CodeCoverage.Console.exe instrument awt.dll
Microsoft.CodeCoverage.Console.exe instrument dt_shmem.dll
Microsoft.CodeCoverage.Console.exe instrument dt_socket.dll
Microsoft.CodeCoverage.Console.exe instrument extnet.dll
Microsoft.CodeCoverage.Console.exe instrument fontmanager.dll
Microsoft.CodeCoverage.Console.exe instrument freetype.dll
Microsoft.CodeCoverage.Console.exe instrument instrument.dll
Microsoft.CodeCoverage.Console.exe instrument j2gss.dll
Microsoft.CodeCoverage.Console.exe instrument j2pcsc.dll
Microsoft.CodeCoverage.Console.exe instrument j2pkcs11.dll
Microsoft.CodeCoverage.Console.exe instrument jaas.dll
Microsoft.CodeCoverage.Console.exe instrument java.dll
Microsoft.CodeCoverage.Console.exe instrument javajpeg.dll
Microsoft.CodeCoverage.Console.exe instrument jawt.dll
Microsoft.CodeCoverage.Console.exe instrument jimage.dll
Microsoft.CodeCoverage.Console.exe instrument jli.dll
Microsoft.CodeCoverage.Console.exe instrument jpackage.dll
Microsoft.CodeCoverage.Console.exe instrument jsound.dll
Microsoft.CodeCoverage.Console.exe instrument jsvml.dll
Microsoft.CodeCoverage.Console.exe instrument lcms.dll
Microsoft.CodeCoverage.Console.exe instrument management.dll
Microsoft.CodeCoverage.Console.exe instrument management_agent.dll
Microsoft.CodeCoverage.Console.exe instrument management_ext.dll
Microsoft.CodeCoverage.Console.exe instrument mlib_image.dll
Microsoft.CodeCoverage.Console.exe instrument net.dll
Microsoft.CodeCoverage.Console.exe instrument nio.dll
Microsoft.CodeCoverage.Console.exe instrument prefs.dll
Microsoft.CodeCoverage.Console.exe instrument rmi.dll
Microsoft.CodeCoverage.Console.exe instrument saproc.dll
Microsoft.CodeCoverage.Console.exe instrument splashscreen.dll
Microsoft.CodeCoverage.Console.exe instrument sspi_bridge.dll
Microsoft.CodeCoverage.Console.exe instrument sunmscapi.dll
Microsoft.CodeCoverage.Console.exe instrument syslookup.dll
Microsoft.CodeCoverage.Console.exe instrument verify.dll
Microsoft.CodeCoverage.Console.exe instrument w2k_lsa_auth.dll
Microsoft.CodeCoverage.Console.exe instrument zip.dll

Microsoft.CodeCoverage.Console.exe instrument jabswitch.exe
Microsoft.CodeCoverage.Console.exe instrument jaccessinspector.exe
Microsoft.CodeCoverage.Console.exe instrument jaccesswalker.exe
Microsoft.CodeCoverage.Console.exe instrument jar.exe
Microsoft.CodeCoverage.Console.exe instrument jarsigner.exe
Microsoft.CodeCoverage.Console.exe instrument java.exe
Microsoft.CodeCoverage.Console.exe instrument javac.exe
Microsoft.CodeCoverage.Console.exe instrument javadoc.exe
Microsoft.CodeCoverage.Console.exe instrument javap.exe
Microsoft.CodeCoverage.Console.exe instrument javaw.exe
Microsoft.CodeCoverage.Console.exe instrument jcmd.exe
Microsoft.CodeCoverage.Console.exe instrument jconsole.exe
Microsoft.CodeCoverage.Console.exe instrument jdb.exe
Microsoft.CodeCoverage.Console.exe instrument jdeprscan.exe
Microsoft.CodeCoverage.Console.exe instrument jdeps.exe
Microsoft.CodeCoverage.Console.exe instrument jfr.exe
Microsoft.CodeCoverage.Console.exe instrument jhsdb.exe
Microsoft.CodeCoverage.Console.exe instrument jimage.exe
Microsoft.CodeCoverage.Console.exe instrument jinfo.exe
Microsoft.CodeCoverage.Console.exe instrument jlink.exe
Microsoft.CodeCoverage.Console.exe instrument jmap.exe
Microsoft.CodeCoverage.Console.exe instrument jmod.exe
Microsoft.CodeCoverage.Console.exe instrument jnativescan.exe
Microsoft.CodeCoverage.Console.exe instrument jpackage.exe
Microsoft.CodeCoverage.Console.exe instrument jps.exe
Microsoft.CodeCoverage.Console.exe instrument jrunscript.exe
Microsoft.CodeCoverage.Console.exe instrument jshell.exe
Microsoft.CodeCoverage.Console.exe instrument jstack.exe
Microsoft.CodeCoverage.Console.exe instrument jstat.exe
Microsoft.CodeCoverage.Console.exe instrument jstatd.exe
Microsoft.CodeCoverage.Console.exe instrument jwebserver.exe
Microsoft.CodeCoverage.Console.exe instrument keytool.exe
Microsoft.CodeCoverage.Console.exe instrument kinit.exe
Microsoft.CodeCoverage.Console.exe instrument klist.exe
Microsoft.CodeCoverage.Console.exe instrument ktab.exe
Microsoft.CodeCoverage.Console.exe instrument rmiregistry.exe
Microsoft.CodeCoverage.Console.exe instrument serialver.exe

The files in the subdirectory named after the HotSpot variant also need to be instrumented:

cd server
Microsoft.CodeCoverage.Console.exe instrument jvm.dll

Collecting Code Coverage

Use the collect command to execute the binary and create a code coverage file (named output.coverage by default):

Microsoft.CodeCoverage.Console.exe collect ".\java -version"

Run start output.coverage to open the code coverage file. You can use the search filter to narrow down the coverage results, e.g. by searching for “interpreter”.

Instrumenting Fails on Windows AArch64

There are warnings when instrumenting some of the DLLs on the AArch64 platform. The warning below is from running Microsoft.CodeCoverage.Console instrument java.dll in the ARM64 Native Tools Command Prompt:

Microsoft.CodeCoverage.Console v17.14.2.0 [arm64 - .NET Framework 4.8.9300.0]

CPEA64::FinalAssignAddressPass: prolog unwind codes mismatch
Proc: Java_java_lang_Class_forName0
Old: ALLOC_S(1) SET_FP SAVE_FPLR_X(1) END
New: NOP ALLOC_S(1) SET_FP SAVE_FPLR_X(1) END
Prolog instructions:
        STP X29 X30 [SP, -0x10]!
        MOV X29 SP
        BL BBlock 00000002 (0x180001000)
        NOP

Although the binaries are instrumented (running the same command again tells you this), OpenJDK with the instrumented binaries fails to execute. I have filed a ticket to ensure this issue gets addressed: “prolog unwind codes mismatch” errors when instrumenting Windows AArch64 OpenJDK DLL with Microsoft.CodeCoverage.Console.exe – Developer Community.

Files Not Instrumented

My list from last year had these 3 files, which are no longer present in the build so keep in mind that you need to ensure your list is current and exhaustive:

javaaccessbridge.dll
jdwp.dll
windowsaccessbridge-64.dll

These files are not instrumented (this message is displayed: Module was not instrumented. Reason: nothing_instrumented.)

jsvml.dll
syslookup.dll

I ignore them for now because they don’t contain the code I’m inspecting.

Listing DLLs and EXEs

I used the ls -1 command to get a list of file names only in Git Bash but they have an asterisk appended to them.

$ ls -1 *.exe
jabswitch.exe*
jaccessinspector.exe*
jaccesswalker.exe*
jar.exe*
jarsigner.exe*
java.exe*
javac.exe*
...

shell – Executables show with character * next to their name in output of ls – Unix & Linux Stack Exchange clarifies that I probably an alias for ls containing the -F option. Sure enough:

$ alias ls
alias ls='ls -F --color=auto --show-control-chars'

$ which ls
/usr/bin/ls

$ /usr/bin/ls -1 *.exe
jabswitch.exe
jaccessinspector.exe
jaccesswalker.exe
...


Categories: Rust

Introduction to Tauri

In the post on Using Stockfish with En Croissant, there was an error (in the En Croissant UI) that mentioned Tauri. I wanted to learn more about what this platform is. A search for Tauri led me to the What is Tauri? | Tauri site. Tauri is a framework for building tiny, fast binaries for all major desktop and mobile platforms. The Learn | Tauri page has a link to the free PDF of the HTML, CSS, JavaScript, and Rust for Beginners: A Guide to Application Development with Tauri book. I went through this book to get a high level idea of what Tauri is and how to create a simple Tauri app.

Installing Tauri Prerequisites

I started by downloading Node.js. The Node.js — Run JavaScript Everywhere page has a link to the JSConf | LF Events page. I have never attended this conference, so it piqued my interest (I made a note to learn more about it later). Node.js informed me that it would install its prerequisites.

====================================================
Tools for Node.js Native Modules Installation Script
====================================================

This script will install Python and the Visual Studio Build Tools, necessary
to compile Node.js native modules. Note that Chocolatey and required Windows
updates will also be installed.

...

Press any key to continue . . .

The Chocolatey script failed to install since I already have a version of Chocolatey installed. Python was upgraded from 3.11 to 3.13.

WARNING: 'choco' was found at 'C:\ProgramData\chocolatey\bin\choco.exe'.
WARNING: An existing Chocolatey installation was detected. Installation will not continue. This script will not
overwrite existing installations.
If there is no Chocolatey installation at 'C:\ProgramData\chocolatey', delete the folder and attempt the installation
again.

Please use choco upgrade chocolatey to handle upgrades of Chocolatey itself.
If the existing installation is not functional or a prior installation did not complete, follow these steps:
 - Backup the files at the path listed above so you can restore your previous installation if needed.
 - Remove the existing installation manually.
 - Rerun this installation script.
 - Reinstall any packages previously installed, if needed (refer to the lib folder in the backup).

Once installation is completed, the backup folder is no longer needed and can be deleted.
Chocolatey v1.2.1
Upgrading the following packages:
python;visualstudio2019-workload-vctools
By upgrading, you accept licenses for the packages.

You have python v3.11.0 installed. Version 3.13.3 is available based on your source(s).
Progress: Downloading python3 3.13.3... 100%
Progress: Downloading python313 3.13.3... 100%
Progress: Downloading python 3.13.3... 100%

python313 v3.13.3 [Approved]
python313 package files upgrade completed. Performing other installation steps.
Installing 64-bit python313...
python313 has been installed.
Added C:\ProgramData\chocolatey\bin\python3.13.exe shim pointed to 'c:\python313\python.exe'.
Python installed to: 'C:\Python313'
WARNING: Environment variable PYTHONHOME points to different version: C:\Python311\
Restricting write permissions to Administrators
  python313 can be automatically uninstalled.
Environment Vars (like PATH) have changed. Close/reopen your shell to
 see the changes (or in powershell/cmd.exe just type `refreshenv`).
 The upgrade of python313 was successful.
  Software installed as 'exe', install location is likely default.

python3 v3.13.3 [Approved]
python3 package files upgrade completed. Performing other installation steps.
 The upgrade of python3 was successful.
  Software installed to 'C:\ProgramData\chocolatey\lib\python3'

python v3.13.3 [Approved]
python package files upgrade completed. Performing other installation steps.
 The upgrade of python was successful.
  Software installed to 'C:\ProgramData\chocolatey\lib\python'
visualstudio2019-workload-vctools v1.0.1 is the latest version available based on your source(s).

Chocolatey upgraded 3/4 packages.
 See the log for details (C:\ProgramData\chocolatey\logs\chocolatey.log).
Type ENTER to exit:

Next, I downloaded and ran the rust installer from Install Rust – Rust Programming Language. I used the standard installation option below.

Welcome to Rust!

This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.

Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:

  C:\Users\saint\.rustup

This can be modified with the RUSTUP_HOME environment variable.

The Cargo home directory is located at:

  C:\Users\saint\.cargo

This can be modified with the CARGO_HOME environment variable.

The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:

  C:\Users\saint\.cargo\bin

This path will then be added to your PATH environment variable by
modifying the PATH registry key at HKEY_CURRENT_USER\Environment.

You can uninstall at any time with rustup self uninstall and
these changes will be reverted.

Current installation options:


   default host triple: x86_64-pc-windows-msvc
     default toolchain: stable (default)
               profile: default
  modify PATH variable: yes

1) Proceed with standard installation (default - just press enter)
2) Customize installation
3) Cancel installation
>
info: profile set to 'default'
info: default host triple is x86_64-pc-windows-msvc
info: syncing channel updates for 'stable-x86_64-pc-windows-msvc'
info: latest update on 2025-05-15, rust version 1.87.0 (17067e9ac 2025-05-09)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-std'
info: downloading component 'rustc'
 73.8 MiB /  73.8 MiB (100 %)  24.2 MiB/s in  3s
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
 20.2 MiB /  20.2 MiB (100 %)   1.8 MiB/s in  8s
info: installing component 'rust-std'
 22.4 MiB /  22.4 MiB (100 %)  20.4 MiB/s in  1s
info: installing component 'rustc'
 73.8 MiB /  73.8 MiB (100 %)  20.8 MiB/s in  3s
info: installing component 'rustfmt'
info: default toolchain set to 'stable-x86_64-pc-windows-msvc'

  stable-x86_64-pc-windows-msvc installed - rustc 1.87.0 (17067e9ac 2025-05-09)


Rust is installed now. Great!

To get started you may need to restart your current shell.
This would reload its PATH environment variable to include
Cargo's bin directory (%USERPROFILE%\.cargo\bin).

Press the Enter key to continue.

Creating a Simple Tauri App

The tutorial says you can use yarn, npm, or bun but npm was the only tool available on my command line after the above installation steps. I followed the instructions to create a Tauri app:

npm create tauri-app@latest

The first prompt was for a project name. I pressed ENTER to use the default name.

$ npm create tauri-app@latest

> npx
> create-tauri-app

? Project name (tauri-app) ›

Next, we need an identifier. ENTER selects the default value:

> npx
> create-tauri-app

✔ Project name · tauri-app
? Identifier (com.tauri-app.app) ›

You then choose the language for the front end.

> npx
> create-tauri-app

✔ Project name · tauri-app
✔ Identifier · com.tauri-app.app
? Choose which language to use for your frontend ›
❯ TypeScript / JavaScript  (pnpm, yarn, npm, deno, bun)
  Rust
  .NET

For TypeScript/JavaScript, you need to specify your package manager. I used npm.

> npx
> create-tauri-app

✔ Project name · tauri-app
✔ Identifier · com.tauri-app.app
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, deno, bun)
? Choose your package manager ›
❯ npm
  pnpm
  yarn
  deno
  bun

I selected React as my UI template. I’m so out of it on front end development that I was surprised to see that Angular is still in use (given that I don’t hear much about it these days).

> npx
> create-tauri-app

✔ Project name · tauri-app
✔ Identifier · com.tauri-app.app
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, deno, bun)
✔ Choose your package manager · npm
? Choose your UI template ›
  Vanilla
  Vue
  Svelte
❯ React  (https://react.dev/)
  Solid
  Angular
  Preact

Finally, I selected TypeScript as my UI flavor:

> npx
> create-tauri-app

✔ Project name · tauri-app
✔ Identifier · com.tauri-app.app
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, deno, bun)
✔ Choose your package manager · npm
✔ Choose your UI template · React - (https://react.dev/)
? Choose your UI flavor ›
❯ TypeScript
  JavaScript

The template was created and instructions on how to run the app were displayed:

> npx
> create-tauri-app

✔ Project name · tauri-app
✔ Identifier · com.tauri-app.app
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm, deno, bun)
✔ Choose your package manager · npm
✔ Choose your UI template · React - (https://react.dev/)
✔ Choose your UI flavor · TypeScript

Template created! To get started run:
  cd tauri-app
  npm install
  npm run tauri android init

For Desktop development, run:
  npm run tauri dev

For Android development, run:
  npm run tauri android dev

I tried running npm run tauri dev without doing anything else, out of curiosity. The command failed (unsurprisingly). npm install is required.

$ cd tauri-app/

$ npm run tauri dev

> tauri-app@0.1.0 tauri
> tauri dev

'tauri' is not recognized as an internal or external command,
operable program or batch file.

$ npm install

added 74 packages, and audited 75 packages in 6s

11 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Running the app takes about 2 minutes (

$ npm run tauri dev

> tauri-app@0.1.0 tauri
> tauri dev

     Running BeforeDevCommand (`npm run dev`)

> tauri-app@0.1.0 dev
> vite


  VITE v6.3.5  ready in 217 ms

  ➜  Local:   http://localhost:1420/
     Running DevCommand (`cargo  run --no-default-features --color always --`)
        Info Watching C:\repos\learning\tauri\tauri-app\src-tauri for changes...
    Updating crates.io index
     Locking 474 packages to latest compatible versions
      Adding proc-macro-crate v2.0.0 (available: v2.0.2)
      Adding serialize-to-javascript v0.1.1 (available: v0.1.2)
      Adding serialize-to-javascript-impl v0.1.1 (available: v0.1.2)
   Compiling proc-macro2 v1.0.95
   Compiling unicode-ident v1.0.18
   Compiling cfg-if v1.0.0
   Compiling windows_x86_64_msvc v0.52.6
   Compiling zerocopy v0.8.25

...

   Compiling keyboard-types v0.7.0
   Compiling serialize-to-javascript v0.1.1
   Compiling embed-resource v3.0.3
   Compiling cargo_toml v0.22.1
   Compiling tauri-utils v2.4.0
   Compiling tauri-winres v0.3.1
   Compiling muda v0.16.1
   Compiling tauri-build v2.2.0
   Compiling tauri-plugin v2.2.0
   Compiling tauri-codegen v2.2.0
   Compiling tauri v2.5.1
   Compiling tauri-plugin-opener v2.2.7
   Compiling tauri-macros v2.2.0
   Compiling tauri-app v0.1.0 (C:\repos\learning\tauri\tauri-app\src-tauri)
   Compiling tao v0.33.0
   Compiling webview2-com v0.37.0
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 35s
     Running `target\debug\tauri-app.exe`

The second execution of that command was much faster since most of the compilation was already complete.

$ npm run tauri dev

> tauri-app@0.1.0 tauri
> tauri dev

     Running BeforeDevCommand (`npm run dev`)

> tauri-app@0.1.0 dev
> vite

     Running DevCommand (`cargo  run --no-default-features --color always --`)

  VITE v6.3.5  ready in 214 ms

  ➜  Local:   http://localhost:1420/
        Info Watching C:\repos\learning\tauri\temp\tauri-app\src-tauri for changes...
   Compiling tauri-app v0.1.0 (C:\repos\learning\tauri\tauri-app\src-tauri)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 16.97s
     Running `target\debug\tauri-app.exe`
Welcome to Tauri + React Desktop Window

Installing pnp

I discovered Tauri in the En Croissant chess program. Per the en-croissant readme, it uses pnpm as the package manager. It is easy to enable as described in the pnpm install instructions.

Since v16.13, Node.js is shipping Corepack for managing package managers. This is an experimental feature, so you need to enable it by running: corepack enable pnpm

Installation | pnpm
$ corepack enable pnpm

$ which pnpm
/c/Program Files/nodejs/pnpm

Building en-croissant

This was easy (compared to some of the other C++ codebases I have been looking at recently). The commands just ran successfully. It’s somewhat unsettling seeing how many packages are downloaded in these npm projects though.

$ pnpm install
! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-10.11.1.tgz
? Do you want to continue? [Y/n] y

! The local project doesn't define a 'packageManager' field. Corepack will now add one referencing pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912.
! For more details about this field, consult the documentation at https://nodejs.org/api/packages.html#packagemanager

Lockfile is up to date, resolution step is skipped
Packages: +528
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Downloading @tabler/icons@2.47.0: 5.57 MB/5.57 MB, done
Downloading @tauri-apps/cli-win32-x64-msvc@2.0.1: 7.66 MB/7.66 MB, done
Downloading @biomejs/cli-win32-x64@1.9.0: 10.83 MB/10.83 MB, done
Downloading typescript@5.4.2: 5.82 MB/5.82 MB, done
Downloading @swc/core-win32-x64-msvc@1.10.0: 16.81 MB/16.81 MB, done
Progress: resolved 528, reused 0, downloaded 528, added 528, done

dependencies:
+ @badrap/result 0.2.13
+ @hello-pangea/dnd 16.5.0
+ @mantine/charts 7.6.2
+ @mantine/core 7.6.2
+ @mantine/dates 7.6.2
+ @mantine/form 7.6.2
+ @mantine/hooks 7.6.2
+ @mantine/notifications 7.6.2
+ @mantine/tiptap 7.6.2
+ @mantine/vanilla-extract 7.6.2
+ @tabler/icons-react 2.47.0
+ @tanstack/react-router 1.20.0
+ @tanstack/react-virtual 3.1.3
+ @tauri-apps/api 2.0.0
+ @tauri-apps/plugin-cli 2.0.0
+ @tauri-apps/plugin-dialog 2.0.0
+ @tauri-apps/plugin-fs 2.0.0
+ @tauri-apps/plugin-http 2.0.0
+ @tauri-apps/plugin-log 2.0.0
+ @tauri-apps/plugin-os 2.0.0
+ @tauri-apps/plugin-process 2.0.0
+ @tauri-apps/plugin-shell 2.0.0
+ @tauri-apps/plugin-updater 2.0.0
+ @tauri-apps/plugin-window-state 2.0.0
+ @tiptap/extension-link 2.2.4
+ @tiptap/extension-placeholder 2.2.4
+ @tiptap/extension-underline 2.2.4
+ @tiptap/pm 2.2.4
+ @tiptap/react 2.2.4
+ @tiptap/starter-kit 2.2.4
+ @types/dom-to-image 2.6.7
+ @vitejs/plugin-react-swc 3.7.2
+ chessground 9.0.4
+ chessops 0.14.0
+ clsx 2.1.0
+ dayjs 1.11.10
+ dom-to-image 2.6.0
+ fast-deep-equal 3.1.3
+ fuse.js 7.0.0
+ i18next 23.11.5
+ immer 10.0.4
+ jotai 2.7.0
+ mantine-contextmenu 7.6.2
+ mantine-datatable 7.6.1
+ mantine-flagpack 4.0.0
+ react 18.3.1
+ react-contenteditable 3.3.7
+ react-dom 18.3.1
+ react-draggable 4.4.6
+ react-helmet 6.1.0
+ react-hotkeys-hook 4.5.0
+ react-i18next 14.1.2
+ react-markdown 9.0.1
+ react-mosaic-component 6.1.0
+ recharts 2.14.1
+ rehype-raw 7.0.0
+ remark-gfm 4.0.0
+ swr 2.2.5
+ tiptap-markdown 0.8.9
+ ts-fsrs 3.5.1
+ ts-pattern 5.0.8
+ vite 5.4.8
+ zod 3.22.4
+ zustand 4.5.2

devDependencies:
+ @biomejs/biome 1.9.0
+ @tanstack/router-vite-plugin 1.20.0
+ @tauri-apps/cli 2.0.1
+ @types/lodash 4.17.10
+ @types/react 18.3.14
+ @types/react-dom 18.3.2
+ @types/react-helmet 6.1.11
+ @types/react-virtualized-auto-sizer 1.0.4
+ @types/react-window 1.8.8
+ @types/react-window-infinite-loader 1.0.9
+ @vanilla-extract/css 1.14.1
+ @vanilla-extract/vite-plugin 4.0.6
+ jsdom 24.0.0
+ typescript 5.4.2
+ vitest 2.1.1

╭ Warning ───────────────────────────────────────────────────────────────────────────────────╮
│                                                                                            │
│   Ignored build scripts: @biomejs/biome, @swc/core, esbuild.                               │
│   Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.   │
│                                                                                            │
╰────────────────────────────────────────────────────────────────────────────────────────────╯

Done in 26.8s using pnpm v10.11.1

Now build the sources:

$ pnpm build

> en-croissant@0.11.1 build C:\repos\en-croissant
> tauri build --no-bundle

    Running beforeBuildCommand `pnpm build-vite`

> en-croissant@0.11.1 build-vite C:\repos\en-croissant
> tsc --noEmit && vite build


♻️  Generating routes...
✅ Processed routes in 167ms
vite v5.4.8 building for production...

♻️  Regenerating routes...
✅ Processed routes in 21ms
✓ 8330 modules transformed.
warnings when minifying css:
▲ [WARNING] Transforming this CSS nesting syntax is not supported in the configured target environment ("chrome87", "edge88", "es2020", "firefox78", "safari14") [unsupported-css-nesting]

    <stdin>:9086:37:
      9086 │   [data-mantine-color-scheme="dark"] & {
           ╵                                      ^

  The nesting transform for this case must generate an ":is(...)" but the configured target environment does not support the ":is" pseudo-class.
...
warning: field `eco` is never read
  --> src\opening.rs:13:5
   |
12 | struct Opening {
   |        ------- field in this struct
13 |     eco: String,
   |     ^^^
   |
   = note: `Opening` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis

warning: `en-croissant` (bin "en-croissant") generated 8 warnings (run `cargo fix --bin "en-croissant"` to apply 4 suggestions)
    Finished `release` profile [optimized] target(s) in 3m 16s
    Built application at: C:\repos\en-croissant\src-tauri\target\release\en-croissant.exe

I like that the output of this entire process is a single executable. Here it is in action after I loaded this FEN: 1r5k/2q2p1p/p2p3B/5PQ1/n1p5/2b4P/PrB3P1/2R1R1K1 w - - 0 0. Something odd is happening here (with the claim that “Black Wins”).

Playing a Game in En Croissant

Although most of the front-end concepts in the HTML, CSS, JavaScript, and Rust for Beginners: A Guide to Application Development with Tauri book were not new to me, the section on Rust was helpful. Seeing it in action in the En Croissant codebase is a good way to get into this new-to-me programming language, which I’ve been meaning to learn for years now.


Building Windows AArch64 OpenJDK using Visual Studio Build Tools

The 8353009: Improve documentation for Windows AArch64 builds PR has a comment stating that “the BuildTools distribution of Visual Studio do not include aarch64-hosted compilers, so to be able to run native builds without the Prism emulation, you need to install the full Visual Studio, including the IDE.” This post describes how I determined this to be false.

I started by looking up how to install Visual Studio Build Tools. The first result I examined was Install Visual Studio Build Tools into a container to support a consistent build system, which at gave me the command line format for installing the build tools. These tools are available on the Visual Studio 2022 Release History page. I wanted a minimal set of components to install so I started with the ARM64 tools and the Windows 11 SDK:

vs_buildtools.exe --quiet --wait --norestart --nocache --installPath "%ProgramFiles(x86)%\Microsoft Visual Studio\2022\BuildTools" --add Microsoft.VisualStudio.Component.VC.Tools.ARM64 --add Microsoft.VisualStudio.Component.Windows11SDK.22621

Running bash configure --with-boot-jdk=<PATH> failed with the error that it could not find a C compiler:

...
checking for cacerts file... default
checking for cacerts source... default
checking for --enable-unlimited-crypto... enabled, default
checking for jni library path... default
configure: Using default toolchain microsoft (Microsoft Visual Studio)
configure: Found Visual Studio installation at /cygdrive/c/progra~2/micros~2/2022/BuildTools using well-known name
configure: Found Microsoft Visual Studio 2022
configure: Trying to extract Visual Studio environment variables for aarch64
configure: using /cygdrive/c/progra~2/micros~2/2022/BuildTools/vc/auxiliary/build/vcvarsarm64.bat
configure: Setting extracted environment variables for aarch64
checking that Visual Studio variables have been correctly extracted... ok
checking for cl... [not found]
configure: error: Could not find a C compiler.
configure exiting with result code 1

The “cl” compiler name it is searching for came from the TOOLCHAIN_DETERMINE_TOOLCHAIN_TYPE macro in jdk/make/autoconf/toolchain.m4. The actual error message (Could not find a C compiler) comes from the TOOLCHAIN_FIND_COMPILER macro, which is invoked by the TOOLCHAIN_DETECT_TOOLCHAIN_CORE macro. The $COMPILER_NAME variable in the error message is the second argument in the TOOLCHAIN_FIND_COMPILER([CC], [C], $TOOLCHAIN_CC_BINARY) call. These macros are invoked from the top level configure.ac file.

The TOOLCHAIN_FIND_COMPILER macro calls the UTIL_LOOKUP_TOOLCHAIN_PROGS macro to find the C compiler. I verified that the last argument is “cl” with an AC_MSG_NOTICE. At this point, I compared the TOOLCHAIN_PATH in config.log with that on a different ARM64 machine with a full VS install. Sure enough, it didn’t contain the bin/hostarm64/arm64 path with the buildtools setup, even though the path exists on disk. TOOLCHAIN_PATH is coming from VS_PATH in toolchain_microsoft.m4. Here is the build\windows-aarch64-server-slowdebug\configure-support\vs-env-aarch64\set-vs-env.sh file.

PATH_BEFORE=$($BASH $TOPDIR/make/scripts/fixpath.sh -i import 'C:\windows\system32;C:\cygwin64\usr\local\bin;C:\cygwin64\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0;C:\Windows\System32\OpenSSH;C:\Program Files\Git\cmd;C:\Users\USER\AppData\Local\Microsoft\WindowsApps ') 
PATH_AFTER=$($BASH $TOPDIR/make/scripts/fixpath.sh -i import 'c:\PROGRA~2\MICROS~2\2022\BuildTools\MSBuild\Current\bin\Roslyn;C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\\arm64;C:\Program Files (x86)\Windows Kits\10\bin\\arm64;c:\PROGRA~2\MICROS~2\2022\BuildTools\\MSBuild\Current\Bin\amd64;C:\Windows\Microsoft.NET\Framework64\v4.0.30319;c:\PROGRA~2\MICROS~2\2022\BuildTools\Common7\IDE\;c:\PROGRA~2\MICROS~2\2022\BuildTools\Common7\Tools\;C:\windows\system32;C:\cygwin64\usr\local\bin;C:\cygwin64\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0;C:\Windows\System32\OpenSSH;C:\Program Files\Git\cmd;C:\Users\USER\AppData\Local\Microsoft\WindowsApps ') 
VS_INCLUDE=$($BASH $TOPDIR/make/scripts/fixpath.sh -i import 'C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\ucrt;C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\um;C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\shared;C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\winrt;C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\cppwinrt ') 
VS_LIB=$($BASH $TOPDIR/make/scripts/fixpath.sh -i import 'C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\ucrt\arm64;C:\Program Files (x86)\Windows Kits\10\\lib\10.0.22621.0\\um\arm64 ') 
VCINSTALLDIR=$($BASH $TOPDIR/make/scripts/fixpath.sh -i import ' ') 
VCToolsRedistDir=$($BASH $TOPDIR/make/scripts/fixpath.sh -i import ' ') 
WindowsSdkDir=$($BASH $TOPDIR/make/scripts/fixpath.sh -i import 'C:\Program Files (x86)\Windows Kits\10\ ') 
WINDOWSSDKDIR=$($BASH $TOPDIR/make/scripts/fixpath.sh -i import 'C:\Program Files (x86)\Windows Kits\10\ ') 

Notice that VS_PATH only has what VS_ENV_CMD added to the PATH! This was a clue that I need to take another step back – I realized that I couldn’t even run cl.exe in the developer command prompt! Then again, the command line for the terminal is:

cmd.exe /k "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\Tools\VsDevCmd.bat" -startdir=none -arch=arm64 -host_arch=x64

Changing the host architecture to arm64 did not help. I launched the VS installer and noticed that the “Desktop development with C++” workload was not installed so I must have been missing additional components.

Visual Studio Build Tools 2022 LTSC 17.12 Workloads

I didn’t want to install the whole workload though, just the necessary individual components. I noticed the C++ Build Tools core features component wasn’t installed so I selected it. The Windows Universal C Runtime component is automatically selected as well:

Visual Studio Build Tools 2022 LTSC 17.12 Individual Components

Once the installation completed, I could run cl.exe in the developer command prompt!

**********************************************************************
** Visual Studio 2022 Developer Command Prompt v17.12.7
** Copyright (c) 2022 Microsoft Corporation
**********************************************************************

C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools>cl
Microsoft (R) C/C++ Optimizing Compiler Version 19.42.34441 for ARM64
Copyright (C) Microsoft Corporation.  All rights reserved.

usage: cl [ option... ] filename... [ /link linkoption... ]

C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools>

The VS installer log in %TEMP% contained these components:

Microsoft.VisualStudio.Component.Roslyn.Compiler,Microsoft.Component.MSBuild,Microsoft.VisualStudio.Component.CoreBuildTools,Microsoft.VisualStudio.Component.Windows10SDK,Microsoft.VisualStudio.Component.VC.CoreBuildTools,Microsoft.VisualStudio.Component.Windows11SDK.22621,Microsoft.VisualStudio.Component.VC.Tools.ARM64EC,Microsoft.VisualStudio.Component.VC.Tools.ARM64

This led me to the minimal set of components that I needed to build OpenJDK on a Windows AArch64 machine with the Visual Studio Build Tools:

vs_buildtools.exe --quiet --wait --norestart --nocache ^
--installPath "%ProgramFiles(x86)%\Microsoft Visual Studio\2022\BuildTools" ^
--add Microsoft.VisualStudio.Component.VC.CoreBuildTools ^
--add Microsoft.VisualStudio.Component.VC.Tools.ARM64 ^
--add Microsoft.VisualStudio.Component.Windows11SDK.22621

Copilot informed me that the caret was the way to split a command across multiple lines in the Windows Command Prompt. This was the final command I used to complete the 8353009: Improve documentation for Windows AArch64 builds PR.


Fixing my VS Code Python Environment

I mentioned in my last post (Learning about Large Language Models) that I recently started going through Sebastian Raschka’s Build a Large Language Model from Scratch book. It had been a while since I ran python code on my laptop so I needed to do some cleanup to restore my environment. I cloned the repo and started executing the first cell in Chapter 2:

from importlib.metadata import version

print("torch version:", version("torch"))
print("tiktoken version:", version("tiktoken"))

I got this error: PackageNotFoundError: No package metadata was found for torch".

{
	"name": "PackageNotFoundError",
	"message": "No package metadata was found for torch",
	"stack": "---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
File /opt/homebrew/Cellar/python@3.11/3.11.1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/importlib/metadata/__init__.py:563, in Distribution.from_name(cls, name)
    562 try:
--> 563     return next(cls.discover(name=name))
    564 except StopIteration:

StopIteration: 

During handling of the above exception, another exception occurred:

PackageNotFoundError                      Traceback (most recent call last)
Cell In[2], line 7
      3 print(sys.version)
      5 from importlib.metadata import version
----> 7 print(\"torch version:\", version(\"torch\"))
      8 print(\"tiktoken version:\", version(\"tiktoken\"))

File /opt/homebrew/Cellar/python@3.11/3.11.1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/importlib/metadata/__init__.py:1008, in version(distribution_name)
   1001 def version(distribution_name):
   1002     \"\"\"Get the version string for the named package.
   1003 
   1004     :param distribution_name: The name of the distribution package to query.
   1005     :return: The version string for the package as defined in the package's
   1006         \"Version\" metadata key.
   1007     \"\"\"
-> 1008     return distribution(distribution_name).version

File /opt/homebrew/Cellar/python@3.11/3.11.1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/importlib/metadata/__init__.py:981, in distribution(distribution_name)
    975 def distribution(distribution_name):
    976     \"\"\"Get the ``Distribution`` instance for the named package.
    977 
    978     :param distribution_name: The name of the distribution package as a string.
    979     :return: A ``Distribution`` instance (or subclass thereof).
    980     \"\"\"
--> 981     return Distribution.from_name(distribution_name)

File /opt/homebrew/Cellar/python@3.11/3.11.1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/importlib/metadata/__init__.py:565, in Distribution.from_name(cls, name)
    563     return next(cls.discover(name=name))
    564 except StopIteration:
--> 565     raise PackageNotFoundError(name)

PackageNotFoundError: No package metadata was found for torch"
}

Here is python setup I had:

saint@MacBookPro LLMs-from-scratch % which python3
/opt/homebrew/bin/python3
saint@MacBookPro LLMs-from-scratch % python3 --version
Python 3.10.9

How is this showing version 3.10.9 but VS Code is using 3.11 from brew (per the file paths in the error messages? This is how to print the version from a python script as per Printing Python version in output – Stack Overflow

import sys
print("Python version")
print(sys.version)
print(sys.path)

Since I have brew installed, this might be what I need: the comment that “If you brew-install Python, but pip is still not in your path, you might need to re-link, like this brew unlink python && brew link python” at python – How do I install pip on macOS or OS X? – Stack Overflow.

saint@MacBookPro LLMs-from-scratch % brew unlink python
Unlinking /opt/homebrew/Cellar/python@3.11/3.11.1... 11 symlinks removed.
saint@MacBookPro LLMs-from-scratch % brew link python
Error: unknown or unsupported macOS version: :dunno

python3 --version still shows 3.10.9.

saint@MacBookPro LLMs-from-scratch % ls -l `which python3`
lrwxr-xr-x  1 saint  admin  40 Jan 27  2023 /opt/homebrew/bin/python3 -> ../Cellar/python@3.10/3.10.9/bin/python3

Is my brew too old?

saint@MacBookPro LLMs-from-scratch % brew --version
Homebrew 4.0.26
Homebrew/homebrew-core (git revision 5b93511fd6b; last commit 2023-03-19)
Homebrew/homebrew-cask (git revision dccc1df2ac; last commit 2023-03-19)

git – How do I update Homebrew? – Stack Overflow says to just run brew update. Look at this list! Scary how much of this outdated stuff is likely to be insecure.

saint@MacBookPro LLMs-from-scratch % brew update
==> Downloading https://ghcr.io/v2/homebrew/portable-ruby/portable-ruby/blobs/sha256:d9faa506c014dedc0b034a68103ba75c9a58242f4d6c67b6ca0f649c39602bcf
######################################################################################################################################################################################################################################### 100.0%
==> Pouring portable-ruby-3.3.7.arm64_big_sur.bottle.tar.gz
==> Homebrew collects anonymous analytics.
Read the analytics documentation (and how to opt-out) here:
  https://docs.brew.sh/Analytics
No analytics have been recorded yet (nor will be during this `brew` run).

==> homebrew/core is old and unneeded, untapping to save space...
Untapping homebrew/core...
Untapped 3 commands and 7398 formulae (7,130 files, 1GB).
==> homebrew/cask is old and unneeded, untapping to save space...
Untapping homebrew/cask...
Untapped 7333 casks (4,415 files, 487.2MB).
==> Downloading https://formulae.brew.sh/api/formula_tap_migrations.jws.json
Updated 4 taps (microsoft/git, homebrew/cask-versions, homebrew/core and homebrew/cask).
==> New Formulae

...<hundreds of lines omitted but included python entries below>

python-argcomplete
python-freethreading
python-gdbm@3.12
python-gdbm@3.13
python-matplotlib
python-packaging
python-setuptools
python-tk@3.12
python-tk@3.13
python@3.12
python@3.13
pyupgrade
...

==> Deleted Installed Formulae
icu4c ✘
==> Deleted Installed Casks
git-credential-manager-core ✘                                                                                            microsoft-openjdk11 ✘
Error: Unexpected method 'appcast' called on Cask adoptopenjdk16.
Follow the instructions here:
  https://github.com/Homebrew/homebrew-cask#reporting-bugs
==> Downloading https://formulae.brew.sh/api/cask_tap_migrations.jws.json
==> Outdated Formulae
aom                 fb303               fribidi             gnuplot             jasper              libheif             libtool             lua                 openblas            pstoedit            sqlite              xorgproto
arpack              fbthrift            gcc                 graphicsmagick      jbig2dec            libidn              libunistring        lz4                 openexr             pyqt@5              suite-sparse        xz
autoconf            fig2dev             gd                  harfbuzz            jpeg-turbo          libidn2             libvmaf             maven               openjdk             python@3.10         sundials            zstd
boost               fizz                gdbm                hdf5                jpeg-xl             liblqr              libx11              mpdecimal           openjpeg            python@3.11         tcl-tk
brotli              flac                gettext             highway             libaec              libomp              libxau              mpfr                openssl@1.1         python@3.9          texinfo
ca-certificates     fltk                ghostscript         hwloc               libavif             libpng              libxcb              mpg123              openssl@3           qscintilla2         wangle
cairo               fmt                 giflib              icu4c@76            libcerf             libraw              libxdmcp            netpbm              opus                qt@5                watchman
cmake               folly               git-gui             imagemagick         libde265            libsndfile          libxext             ninja               pango               readline            webp
double-conversion   fontconfig          glib                imath               libevent            libsodium           libxrender          octave              pcre2               shared-mime-info    wget
edencommon          freetype            gmp                 isl                 libffi              libtiff             little-cms2         open-mpi            pixman              snappy              x265
==> Outdated Casks
git-credential-manager                                                          microsoft-openjdk                                                               microsoft-openjdk@11

You have 113 outdated formulae and 3 outdated casks installed.
You can upgrade them with brew upgrade
or list them with brew outdated.
Error: Unexpected method 'appcast' called on Cask adoptopenjdk16.
Follow the instructions here:
  https://github.com/Homebrew/homebrew-cask#reporting-bugs
==> Migrating cask git-credential-manager-core to git-credential-manager
Error: inreplace failed
/opt/homebrew/Caskroom/git-credential-manager/.metadata/2.1.2/20230703191748.675/Casks/git-credential-manager.rb:
  expected replacement of /\A\s*cask\s+"git\-credential\-manager\-core"/ with "cask \"git-credential-manager\""

python3 --version is still 3.10.9 after this. I tried running pip but zsh sayd command not found. Unfortunately, linking or unlinking of either python or python3 fails with the errors below (despite ls -l `which python3` showing the same path as before).

saint@MacBookPro LLMs-from-scratch % brew unlink python3
Error: Unexpected method 'appcast' called on Cask adoptopenjdk16.
Follow the instructions here:
  https://github.com/Homebrew/homebrew-cask#reporting-bugs
Error: No such keg: /opt/homebrew/Cellar/python3

I decided to install python3 again.

saint@MacBookPro LLMs-from-scratch % brew install python3
==> Downloading https://formulae.brew.sh/api/formula.jws.json
==> Downloading https://formulae.brew.sh/api/cask.jws.json
==> Downloading https://ghcr.io/v2/homebrew/core/python/3.13/manifests/3.13.1
######################################################################################################################################################################################################################################### 100.0%
==> Fetching dependencies for python@3.13: mpdecimal, ca-certificates, openssl@3, readline, sqlite and xz
==> Downloading https://ghcr.io/v2/homebrew/core/mpdecimal/manifests/4.0.0-1
######################################################################################################################################################################################################################################### 100.0%
==> Fetching mpdecimal
==> Downloading https://ghcr.io/v2/homebrew/core/mpdecimal/blobs/sha256:0f5f269bed0e6be2de3edfc4b52867e656f993e5bcff40717f26ee94dd0d2211
######################################################################################################################################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/ca-certificates/manifests/2024-12-31
######################################################################################################################################################################################################################################### 100.0%
==> Fetching ca-certificates
...
<lots of omitted lines>
...
==> Fetching harfbuzz
==> Downloading https://ghcr.io/v2/homebrew/core/harfbuzz/blobs/sha256:2f892566c02b3c8c61aed6f7867b4405e5c814df8500ef4bc4ca91a9e40205a9
######################################################################################################################################################################################################################################### 100.0%
==> Fetching openjdk
==> Downloading https://ghcr.io/v2/homebrew/core/openjdk/blobs/sha256:1285eadf2b5998cda49e4470ee3875e855b0be199765401ad77dc38aea573f49
######################################################################################################################################################################################################################################### 100.0%
Error: can't modify frozen String: "The bottle needs the Xcode Command Line Tools to be installed at /Library/Developer/CommandLineTools.\nDevelopment tools provided by Xcode.app are not sufficient.\n\nYou can install the Xcode Command Line Tools, if desired, with:\n    xcode-select --install\n"

This was the new state of affairs is that command (failed):

saint@MacBookPro LLMs-from-scratch % python3 --version
Python 3.13.1
saint@MacBookPro LLMs-from-scratch % which python3
/opt/homebrew/bin/python3
saint@MacBookPro LLMs-from-scratch % ls -l `which python3`
lrwxr-xr-x  1 saint  admin  40 Feb  4 17:02 /opt/homebrew/bin/python3 -> ../Cellar/python@3.13/3.13.1/bin/python3
saint@MacBookPro LLMs-from-scratch % which pip
pip not found

Ah, all that agonizing and look at this – did I need to be using pip3 all this time?

saint@MacBookPro LLMs-from-scratch % which pip3
/opt/homebrew/bin/pip3
saint@MacBookPro LLMs-from-scratch % ls -l `which pip3`
lrwxr-xr-x  1 saint  admin  37 Feb  4 17:02 /opt/homebrew/bin/pip3 -> ../Cellar/python@3.13/3.13.1/bin/pip3
saint@MacBookPro LLMs-from-scratch % 

Interestingly, I still can’t install pytorch using pip3?

saint@MacBookPro LLMs-from-scratch % pip3 install pytorch

[notice] A new release of pip is available: 24.3.1 -> 25.0
[notice] To update, run: python3.13 -m pip install --upgrade pip
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
    xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a Python library that isn't in Homebrew,
    use a virtual environment:
    
    python3 -m venv path/to/venv
    source path/to/venv/bin/activate
    python3 -m pip install xyz
    
    If you wish to install a Python application that isn't in Homebrew,
    it may be easiest to use 'pipx install xyz', which will manage a
    virtual environment for you. You can install pipx with
    
    brew install pipx
    
    You may restore the old behavior of pip by passing
    the '--break-system-packages' flag to pip, or by adding
    'break-system-packages = true' to your pip.conf file. The latter
    will permanently disable this error.
    
    If you disable this error, we STRONGLY recommend that you additionally
    pass the '--user' flag to pip, or set 'user = true' in your pip.conf
    file. Failure to do this can result in a broken Homebrew installation.
    
    Read more about this behavior here: <https://peps.python.org/pep-0668/>

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

I then tried installing brew as suggested above:

saint@MacBookPro LLMs-from-scratch % brew install pytorch
==> Downloading https://formulae.brew.sh/api/formula.jws.json
==> Downloading https://formulae.brew.sh/api/cask.jws.json
==> Downloading https://ghcr.io/v2/homebrew/core/pytorch/manifests/2.5.1_4
######################################################################################################################################################################################################################################### 100.0%
==> Fetching dependencies for pytorch: abseil, libuv, libyaml, gmp, isl, mpfr, lz4, zstd, make, gcc, openblas, numpy, protobuf, pybind11, sleef and libomp
==> Downloading https://ghcr.io/v2/homebrew/core/abseil/manifests/20240722.1
######################################################################################################################################################################################################################################### 100.0%
==> Fetching abseil
==> Downloading https://ghcr.io/v2/homebrew/core/abseil/blobs/sha256:be7b3373c56a0e1ee2c0c2e85ee4d17e2105ac1d9d6d63011da28d636fec7424
######################################################################################################################################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/libuv/manifests/1.50.0
######################################################################################################################################################################################################################################### 100.0%
==> Fetching libuv
==> Downloading https://ghcr.io/v2/homebrew/core/libuv/blobs/sha256:9a70ed97116c4960f0484159c07145df8e768b1a62be68c071070869ba4c3644
######################################################################################################################################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/libyaml/manifests/0.2.5
######################################################################################################################################################################################################################################### 100.0%
==> Fetching libyaml
==> Downloading https://ghcr.io/v2/homebrew/core/libyaml/blobs/sha256:0ec9bf8082245c008803b42dcae3e6a0c8cd7a67aed589d9b6482b115c0a543b
######################################################################################################################################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/gmp/manifests/6.3.0
######################################################################################################################################################################################################################################### 100.0%
==> Fetching gmp
==> Downloading https://ghcr.io/v2/homebrew/core/gmp/blobs/sha256:6683d73d6677d28e1e8d1b92d6ebfbc068c1d33e19b79114a22a648a99ba5991
######################################################################################################################################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/isl/manifests/0.27
######################################################################################################################################################################################################################################### 100.0%
==> Fetching isl
==> Downloading https://ghcr.io/v2/homebrew/core/isl/blobs/sha256:de143fddb0e20b6b73016ead1e625ebd429db53918200d093e4da98f1e758889
######################################################################################################################################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/mpfr/manifests/4.2.1
######################################################################################################################################################################################################################################### 100.0%
==> Fetching mpfr
==> Downloading https://ghcr.io/v2/homebrew/core/mpfr/blobs/sha256:51f0ca19e897731b928742401c9c8d1d7d93c3c275aa8a66a77b9ac01d0c223c
######################################################################################################################################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/lz4/manifests/1.10.0-1
Already downloaded: /Users/saint/Library/Caches/Homebrew/downloads/8e11e90eb21a06e0f199af9d80e011e3693c77dd353b2477579d95c8471a5802--lz4-1.10.0-1.bottle_manifest.json
==> Fetching lz4
==> Downloading https://ghcr.io/v2/homebrew/core/lz4/blobs/sha256:5bd143b7b784989e549637ea4e484af85ba481e640dde69bc35f3843ae25abc6
Already downloaded: /Users/saint/Library/Caches/Homebrew/downloads/8af6cbc3dc870dba18d9a1b3f311f2d43b56a020a1e565289122cfda703ab791--lz4--1.10.0.arm64_sequoia.bottle.1.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/zstd/manifests/1.5.6
Already downloaded: /Users/saint/Library/Caches/Homebrew/downloads/29403e0df5404d8aeca0e750ac135ec9ef44fc5eeb6df69170ed602acabf0ffb--zstd-1.5.6.bottle_manifest.json
==> Fetching zstd
==> Downloading https://ghcr.io/v2/homebrew/core/zstd/blobs/sha256:487f35700f563b07036cfd429e4e7a4e37f13e22578e688cbfee2fa9484aaf9d
Already downloaded: /Users/saint/Library/Caches/Homebrew/downloads/bcdf6b56ea7b8b23105f1518ddd0830ac5a56c333b0274959c10084ea3a31346--zstd--1.5.6.arm64_sequoia.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/make/manifests/4.4.1-1
######################################################################################################################################################################################################################################### 100.0%
==> Fetching make
==> Downloading https://ghcr.io/v2/homebrew/core/make/blobs/sha256:f361639a5ec1a9355e12f985c511dd6631b6790452a52057032a3a07a690ca4e
######################################################################################################################################################################################################################################### 100.0%
Error: can't modify frozen String: "The bottle needs the Xcode Command Line Tools to be installed at /Library/Developer/CommandLineTools.\nDevelopment tools provided by Xcode.app are not sufficient.\n\nYou can install the Xcode Command Line Tools, if desired, with:\n    xcode-select --install\n"
saint@MacBookPro LLMs-from-scratch % xcode-select --install
xcode-select: note: install requested for command line developer tools

I installed the command line developer tools when prompted below.

Trying to run the first cell in VS Code with the updated setup now gave this error:

{
	"name": "",
	"message": "",
	"stack": "Running cells with 'Python 3.13.1' requires the ipykernel package.
Run the following command to install 'ipykernel' into the Python environment. 
Command: '/opt/homebrew/bin/python3 -m pip install ipykernel -U --user --force-reinstall'"
}
saint@MacBookPro LLMs-from-scratch % /opt/homebrew/bin/python3 -m pip install ipykernel -U --user --force-reinstall

[notice] A new release of pip is available: 24.3.1 -> 25.0
[notice] To update, run: python3.13 -m pip install --upgrade pip
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
    xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a Python library that isn't in Homebrew,
    use a virtual environment:
    
    python3 -m venv path/to/venv
    source path/to/venv/bin/activate
    python3 -m pip install xyz
    
    If you wish to install a Python application that isn't in Homebrew,
    it may be easiest to use 'pipx install xyz', which will manage a
    virtual environment for you. You can install pipx with
    
    brew install pipx
    
    You may restore the old behavior of pip by passing
    the '--break-system-packages' flag to pip, or by adding
    'break-system-packages = true' to your pip.conf file. The latter
    will permanently disable this error.
    
    If you disable this error, we STRONGLY recommend that you additionally
    pass the '--user' flag to pip, or set 'user = true' in your pip.conf
    file. Failure to do this can result in a broken Homebrew installation.
    
    Read more about this behavior here: <https://peps.python.org/pep-0668/>

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.
saint@MacBookPro LLMs-from-scratch % brew install ipykernel
==> Downloading https://formulae.brew.sh/api/formula.jws.json
==> Downloading https://formulae.brew.sh/api/cask.jws.json
Warning: No available formula with the name "ipykernel".
==> Searching for similarly named formulae and casks...
Error: No formulae or casks found for ipykernel.

I override the warning using the --break-system-packages flag and the VS Code notebook now runs.

saint@MacBookPro LLMs-from-scratch % python3 -m pip install ipykernel -U --user --force-reinstall --break-system-package
Collecting ipykernel
  Downloading ipykernel-6.29.5-py3-none-any.whl.metadata (6.3 kB)
Collecting appnope (from ipykernel)
  Downloading appnope-0.1.4-py2.py3-none-any.whl.metadata (908 bytes)
Collecting comm>=0.1.1 (from ipykernel)
  Downloading comm-0.2.2-py3-none-any.whl.metadata (3.7 kB)
Collecting debugpy>=1.6.5 (from ipykernel)
  Downloading debugpy-1.8.12-cp313-cp313-macosx_14_0_universal2.whl.metadata (1.3 kB)
Collecting ipython>=7.23.1 (from ipykernel)
  Downloading ipython-8.32.0-py3-none-any.whl.metadata (5.0 kB)
Collecting jupyter-client>=6.1.12 (from ipykernel)
  Downloading jupyter_client-8.6.3-py3-none-any.whl.metadata (8.3 kB)
Collecting jupyter-core!=5.0.*,>=4.12 (from ipykernel)
  Downloading jupyter_core-5.7.2-py3-none-any.whl.metadata (3.4 kB)
Collecting matplotlib-inline>=0.1 (from ipykernel)
  Downloading matplotlib_inline-0.1.7-py3-none-any.whl.metadata (3.9 kB)
Collecting nest-asyncio (from ipykernel)
  Downloading nest_asyncio-1.6.0-py3-none-any.whl.metadata (2.8 kB)
Collecting packaging (from ipykernel)
  Downloading packaging-24.2-py3-none-any.whl.metadata (3.2 kB)
Collecting psutil (from ipykernel)
  Downloading psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl.metadata (22 kB)
Collecting pyzmq>=24 (from ipykernel)
  Downloading pyzmq-26.2.1-cp313-cp313-macosx_10_15_universal2.whl.metadata (6.2 kB)
Collecting tornado>=6.1 (from ipykernel)
  Downloading tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl.metadata (2.5 kB)
Collecting traitlets>=5.4.0 (from ipykernel)
  Downloading traitlets-5.14.3-py3-none-any.whl.metadata (10 kB)
Collecting decorator (from ipython>=7.23.1->ipykernel)
  Downloading decorator-5.1.1-py3-none-any.whl.metadata (4.0 kB)
Collecting jedi>=0.16 (from ipython>=7.23.1->ipykernel)
  Downloading jedi-0.19.2-py2.py3-none-any.whl.metadata (22 kB)
Collecting pexpect>4.3 (from ipython>=7.23.1->ipykernel)
  Downloading pexpect-4.9.0-py2.py3-none-any.whl.metadata (2.5 kB)
Collecting prompt_toolkit<3.1.0,>=3.0.41 (from ipython>=7.23.1->ipykernel)
  Downloading prompt_toolkit-3.0.50-py3-none-any.whl.metadata (6.6 kB)
Collecting pygments>=2.4.0 (from ipython>=7.23.1->ipykernel)
  Downloading pygments-2.19.1-py3-none-any.whl.metadata (2.5 kB)
Collecting stack_data (from ipython>=7.23.1->ipykernel)
  Downloading stack_data-0.6.3-py3-none-any.whl.metadata (18 kB)
Collecting python-dateutil>=2.8.2 (from jupyter-client>=6.1.12->ipykernel)
  Downloading python_dateutil-2.9.0.post0-py2.py3-none-any.whl.metadata (8.4 kB)
Collecting platformdirs>=2.5 (from jupyter-core!=5.0.*,>=4.12->ipykernel)
  Downloading platformdirs-4.3.6-py3-none-any.whl.metadata (11 kB)
Collecting parso<0.9.0,>=0.8.4 (from jedi>=0.16->ipython>=7.23.1->ipykernel)
  Downloading parso-0.8.4-py2.py3-none-any.whl.metadata (7.7 kB)
Collecting ptyprocess>=0.5 (from pexpect>4.3->ipython>=7.23.1->ipykernel)
  Downloading ptyprocess-0.7.0-py2.py3-none-any.whl.metadata (1.3 kB)
Collecting wcwidth (from prompt_toolkit<3.1.0,>=3.0.41->ipython>=7.23.1->ipykernel)
  Downloading wcwidth-0.2.13-py2.py3-none-any.whl.metadata (14 kB)
Collecting six>=1.5 (from python-dateutil>=2.8.2->jupyter-client>=6.1.12->ipykernel)
  Downloading six-1.17.0-py2.py3-none-any.whl.metadata (1.7 kB)
Collecting executing>=1.2.0 (from stack_data->ipython>=7.23.1->ipykernel)
  Downloading executing-2.2.0-py2.py3-none-any.whl.metadata (8.9 kB)
Collecting asttokens>=2.1.0 (from stack_data->ipython>=7.23.1->ipykernel)
  Downloading asttokens-3.0.0-py3-none-any.whl.metadata (4.7 kB)
Collecting pure-eval (from stack_data->ipython>=7.23.1->ipykernel)
  Downloading pure_eval-0.2.3-py3-none-any.whl.metadata (6.3 kB)
Downloading ipykernel-6.29.5-py3-none-any.whl (117 kB)
Downloading comm-0.2.2-py3-none-any.whl (7.2 kB)
Downloading debugpy-1.8.12-cp313-cp313-macosx_14_0_universal2.whl (2.5 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.5/2.5 MB 36.8 MB/s eta 0:00:00
Downloading ipython-8.32.0-py3-none-any.whl (825 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 825.5/825.5 kB 54.9 MB/s eta 0:00:00
Downloading jupyter_client-8.6.3-py3-none-any.whl (106 kB)
Downloading jupyter_core-5.7.2-py3-none-any.whl (28 kB)
Downloading matplotlib_inline-0.1.7-py3-none-any.whl (9.9 kB)
Downloading pyzmq-26.2.1-cp313-cp313-macosx_10_15_universal2.whl (1.3 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.3/1.3 MB 62.7 MB/s eta 0:00:00
Downloading tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl (436 kB)
Downloading traitlets-5.14.3-py3-none-any.whl (85 kB)
Downloading appnope-0.1.4-py2.py3-none-any.whl (4.3 kB)
Downloading nest_asyncio-1.6.0-py3-none-any.whl (5.2 kB)
Downloading packaging-24.2-py3-none-any.whl (65 kB)
Downloading psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl (248 kB)
Downloading jedi-0.19.2-py2.py3-none-any.whl (1.6 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.6/1.6 MB 79.1 MB/s eta 0:00:00
Downloading pexpect-4.9.0-py2.py3-none-any.whl (63 kB)
Downloading platformdirs-4.3.6-py3-none-any.whl (18 kB)
Downloading prompt_toolkit-3.0.50-py3-none-any.whl (387 kB)
Downloading pygments-2.19.1-py3-none-any.whl (1.2 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 58.1 MB/s eta 0:00:00
Downloading python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
Downloading decorator-5.1.1-py3-none-any.whl (9.1 kB)
Downloading stack_data-0.6.3-py3-none-any.whl (24 kB)
Downloading asttokens-3.0.0-py3-none-any.whl (26 kB)
Downloading executing-2.2.0-py2.py3-none-any.whl (26 kB)
Downloading parso-0.8.4-py2.py3-none-any.whl (103 kB)
Downloading ptyprocess-0.7.0-py2.py3-none-any.whl (13 kB)
Downloading six-1.17.0-py2.py3-none-any.whl (11 kB)
Downloading pure_eval-0.2.3-py3-none-any.whl (11 kB)
Downloading wcwidth-0.2.13-py2.py3-none-any.whl (34 kB)
Installing collected packages: wcwidth, pure-eval, ptyprocess, traitlets, tornado, six, pyzmq, pygments, psutil, prompt_toolkit, platformdirs, pexpect, parso, packaging, nest-asyncio, executing, decorator, debugpy, asttokens, appnope, stack_data, python-dateutil, matplotlib-inline, jupyter-core, jedi, comm, jupyter-client, ipython, ipykernel
  WARNING: The script pygmentize is installed in '/Users/saint/Library/Python/3.13/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
  WARNING: The script debugpy is installed in '/Users/saint/Library/Python/3.13/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
  WARNING: The scripts jupyter, jupyter-migrate and jupyter-troubleshoot are installed in '/Users/saint/Library/Python/3.13/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
  WARNING: The scripts jupyter-kernel, jupyter-kernelspec and jupyter-run are installed in '/Users/saint/Library/Python/3.13/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
  WARNING: The scripts ipython and ipython3 are installed in '/Users/saint/Library/Python/3.13/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.
Successfully installed appnope-0.1.4 asttokens-3.0.0 comm-0.2.2 debugpy-1.8.12 decorator-5.1.1 executing-2.2.0 ipykernel-6.29.5 ipython-8.32.0 jedi-0.19.2 jupyter-client-8.6.3 jupyter-core-5.7.2 matplotlib-inline-0.1.7 nest-asyncio-1.6.0 packaging-24.2 parso-0.8.4 pexpect-4.9.0 platformdirs-4.3.6 prompt_toolkit-3.0.50 psutil-6.1.1 ptyprocess-0.7.0 pure-eval-0.2.3 pygments-2.19.1 python-dateutil-2.9.0.post0 pyzmq-26.2.1 six-1.17.0 stack_data-0.6.3 tornado-6.4.2 traitlets-5.14.3 wcwidth-0.2.13

[notice] A new release of pip is available: 24.3.1 -> 25.0
[notice] To update, run: python3.13 -m pip install --upgrade pip
saint@MacBookPro LLMs-from-scratch % 

First cell now fails because the torch package cannot be found:

{
	"name": "PackageNotFoundError",
	"message": "No package metadata was found for torch",
	"stack": "---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
File /opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/importlib/metadata/__init__.py:407, in Distribution.from_name(cls, name)
    406 try:
--> 407     return next(iter(cls.discover(name=name)))
    408 except StopIteration:

StopIteration: 

During handling of the above exception, another exception occurred:

PackageNotFoundError                      Traceback (most recent call last)
Cell In[1], line 3
      1 from importlib.metadata import version
----> 3 print(\"torch version:\", version(\"torch\"))
      4 print(\"tiktoken version:\", version(\"tiktoken\"))

File /opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/importlib/metadata/__init__.py:987, in version(distribution_name)
    980 def version(distribution_name: str) -> str:
    981     \"\"\"Get the version string for the named package.
    982 
    983     :param distribution_name: The name of the distribution package to query.
    984     :return: The version string for the package as defined in the package's
    985         \"Version\" metadata key.
    986     \"\"\"
--> 987     return distribution(distribution_name).version

File /opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/importlib/metadata/__init__.py:960, in distribution(distribution_name)
    954 def distribution(distribution_name: str) -> Distribution:
    955     \"\"\"Get the ``Distribution`` instance for the named package.
    956 
    957     :param distribution_name: The name of the distribution package as a string.
    958     :return: A ``Distribution`` instance (or subclass thereof).
    959     \"\"\"
--> 960     return Distribution.from_name(distribution_name)

File /opt/homebrew/Cellar/python@3.13/3.13.1/Frameworks/Python.framework/Versions/3.13/lib/python3.13/importlib/metadata/__init__.py:409, in Distribution.from_name(cls, name)
    407     return next(iter(cls.discover(name=name)))
    408 except StopIteration:
--> 409     raise PackageNotFoundError(name)

PackageNotFoundError: No package metadata was found for torch"
}

These are the commands I tried to install pytorch before finding the correct one: pip3 install torch --break-system-packages.

saint@MacBookPro LLMs-from-scratch % brew install torch
==> Downloading https://formulae.brew.sh/api/formula.jws.json
==> Downloading https://formulae.brew.sh/api/cask.jws.json
Warning: No available formula with the name "torch". Did you mean tor, pytorch or orc?
==> Searching for similarly named formulae and casks...
==> Formulae
pytorch ✔                                                    torchvision                                                  tor                                                          orc

To install pytorch ✔, run:
  brew install pytorch ✔
saint@MacBookPro LLMs-from-scratch % brew install pytorch
Warning: pytorch 2.5.1_4 is already installed and up-to-date.
To reinstall 2.5.1_4, run:
  brew reinstall pytorch
saint@MacBookPro LLMs-from-scratch % pip3 install torch

[notice] A new release of pip is available: 24.3.1 -> 25.0
[notice] To update, run: python3.13 -m pip install --upgrade pip
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
    xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a Python library that isn't in Homebrew,
    use a virtual environment:
    
    python3 -m venv path/to/venv
    source path/to/venv/bin/activate
    python3 -m pip install xyz
    
    If you wish to install a Python application that isn't in Homebrew,
    it may be easiest to use 'pipx install xyz', which will manage a
    virtual environment for you. You can install pipx with
    
    brew install pipx
    
    You may restore the old behavior of pip by passing
    the '--break-system-packages' flag to pip, or by adding
    'break-system-packages = true' to your pip.conf file. The latter
    will permanently disable this error.
    
    If you disable this error, we STRONGLY recommend that you additionally
    pass the '--user' flag to pip, or set 'user = true' in your pip.conf
    file. Failure to do this can result in a broken Homebrew installation.
    
    Read more about this behavior here: <https://peps.python.org/pep-0668/>

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.
saint@MacBookPro LLMs-from-scratch % 
saint@MacBookPro LLMs-from-scratch % pip3 install torch --break-system-packages
Collecting torch
  Downloading torch-2.6.0-cp313-none-macosx_11_0_arm64.whl.metadata (28 kB)
Collecting filelock (from torch)
  Downloading filelock-3.17.0-py3-none-any.whl.metadata (2.9 kB)
Collecting typing-extensions>=4.10.0 (from torch)
  Downloading typing_extensions-4.12.2-py3-none-any.whl.metadata (3.0 kB)
Collecting networkx (from torch)
  Downloading networkx-3.4.2-py3-none-any.whl.metadata (6.3 kB)
Collecting jinja2 (from torch)
  Downloading jinja2-3.1.5-py3-none-any.whl.metadata (2.6 kB)
Collecting fsspec (from torch)
  Downloading fsspec-2025.2.0-py3-none-any.whl.metadata (11 kB)
Collecting setuptools (from torch)
  Downloading setuptools-75.8.0-py3-none-any.whl.metadata (6.7 kB)
Collecting sympy==1.13.1 (from torch)
  Downloading sympy-1.13.1-py3-none-any.whl.metadata (12 kB)
Collecting mpmath<1.4,>=1.1.0 (from sympy==1.13.1->torch)
  Downloading mpmath-1.3.0-py3-none-any.whl.metadata (8.6 kB)
Collecting MarkupSafe>=2.0 (from jinja2->torch)
  Downloading MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl.metadata (4.0 kB)
Downloading torch-2.6.0-cp313-none-macosx_11_0_arm64.whl (66.5 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 66.5/66.5 MB 74.3 MB/s eta 0:00:00
Downloading sympy-1.13.1-py3-none-any.whl (6.2 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 6.2/6.2 MB 75.0 MB/s eta 0:00:00
Downloading typing_extensions-4.12.2-py3-none-any.whl (37 kB)
Downloading filelock-3.17.0-py3-none-any.whl (16 kB)
Downloading fsspec-2025.2.0-py3-none-any.whl (184 kB)
Downloading jinja2-3.1.5-py3-none-any.whl (134 kB)
Downloading networkx-3.4.2-py3-none-any.whl (1.7 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.7/1.7 MB 63.7 MB/s eta 0:00:00
Downloading setuptools-75.8.0-py3-none-any.whl (1.2 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 37.0 MB/s eta 0:00:00
Downloading MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl (12 kB)
Downloading mpmath-1.3.0-py3-none-any.whl (536 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 536.2/536.2 kB 27.9 MB/s eta 0:00:00
Installing collected packages: mpmath, typing-extensions, sympy, setuptools, networkx, MarkupSafe, fsspec, filelock, jinja2, torch
Successfully installed MarkupSafe-3.0.2 filelock-3.17.0 fsspec-2025.2.0 jinja2-3.1.5 mpmath-1.3.0 networkx-3.4.2 setuptools-75.8.0 sympy-1.13.1 torch-2.6.0 typing-extensions-4.12.2

[notice] A new release of pip is available: 24.3.1 -> 25.0
[notice] To update, run: python3.13 -m pip install --upgrade pip
saint@MacBookPro LLMs-from-scratch % 

The pytorch import finally works! The next error is also a PackageNotFoundError: "No package metadata was found for tiktoken" which I addressed with the same installation steps:

saint@MacBookPro LLMs-from-scratch % pip3 install tiktoken                     

[notice] A new release of pip is available: 24.3.1 -> 25.0
[notice] To update, run: python3.13 -m pip install --upgrade pip
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install
    xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a Python library that isn't in Homebrew,
    use a virtual environment:
    
    python3 -m venv path/to/venv
    source path/to/venv/bin/activate
    python3 -m pip install xyz
    
    If you wish to install a Python application that isn't in Homebrew,
    it may be easiest to use 'pipx install xyz', which will manage a
    virtual environment for you. You can install pipx with
    
    brew install pipx
    
    You may restore the old behavior of pip by passing
    the '--break-system-packages' flag to pip, or by adding
    'break-system-packages = true' to your pip.conf file. The latter
    will permanently disable this error.
    
    If you disable this error, we STRONGLY recommend that you additionally
    pass the '--user' flag to pip, or set 'user = true' in your pip.conf
    file. Failure to do this can result in a broken Homebrew installation.
    
    Read more about this behavior here: <https://peps.python.org/pep-0668/>

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.
saint@MacBookPro LLMs-from-scratch % 
saint@MacBookPro LLMs-from-scratch % pip3 install tiktoken --break-system-packages
Collecting tiktoken
  Downloading tiktoken-0.8.0-cp313-cp313-macosx_11_0_arm64.whl.metadata (6.6 kB)
Collecting regex>=2022.1.18 (from tiktoken)
  Downloading regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl.metadata (40 kB)
Collecting requests>=2.26.0 (from tiktoken)
  Downloading requests-2.32.3-py3-none-any.whl.metadata (4.6 kB)
Collecting charset-normalizer<4,>=2 (from requests>=2.26.0->tiktoken)
  Downloading charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl.metadata (35 kB)
Collecting idna<4,>=2.5 (from requests>=2.26.0->tiktoken)
  Downloading idna-3.10-py3-none-any.whl.metadata (10 kB)
Collecting urllib3<3,>=1.21.1 (from requests>=2.26.0->tiktoken)
  Downloading urllib3-2.3.0-py3-none-any.whl.metadata (6.5 kB)
Collecting certifi>=2017.4.17 (from requests>=2.26.0->tiktoken)
  Downloading certifi-2025.1.31-py3-none-any.whl.metadata (2.5 kB)
Downloading tiktoken-0.8.0-cp313-cp313-macosx_11_0_arm64.whl (982 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 982.8/982.8 kB 24.2 MB/s eta 0:00:00
Downloading regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl (284 kB)
Downloading requests-2.32.3-py3-none-any.whl (64 kB)
Downloading certifi-2025.1.31-py3-none-any.whl (166 kB)
Downloading charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl (195 kB)
Downloading idna-3.10-py3-none-any.whl (70 kB)
Downloading urllib3-2.3.0-py3-none-any.whl (128 kB)
Installing collected packages: urllib3, regex, idna, charset-normalizer, certifi, requests, tiktoken
Successfully installed certifi-2025.1.31 charset-normalizer-3.4.1 idna-3.10 regex-2024.11.6 requests-2.32.3 tiktoken-0.8.0 urllib3-2.3.0

[notice] A new release of pip is available: 24.3.1 -> 25.0
[notice] To update, run: python3.13 -m pip install --upgrade pip
saint@MacBookPro LLMs-from-scratch % 

Finally, my machine is in a state that can run the code in the Jupyter notebook! This is such a brittle environment. I need to switch to a managed environment to avoid this type of mess.


Categories: Containers, Java

Running my Factorization Java App in Docker

I want to evaluate the OpenJDK serial collector using a Java program I wrote to factorize natural numbers by trial division. This post is about how to set up the app to run in a Docker container on a Linux host. Since the host is a shared machine, I put all my work under ~/swesonga (my own custom home directory). The directory structure for the container will be under ~/swesonga/container/.

Set up the Factorization App

First, log into Linux machine and download the Java binaries to test:

ssh user@IPaddress
mkdir -p ~/swesonga/container/java/binaries/jdk/x64/
cd ~/swesonga/container/java/binaries/jdk/x64/

curl -Lo microsoft-jdk-21.0.5-linux-x64.tar.gz https://aka.ms/download-jdk/microsoft-jdk-21.0.5-linux-x64.tar.gz

tar xzf microsoft-jdk-21.0.5-linux-x64.tar.gz

Clone the factorize repo and set up its dependencies:

cd ~/swesonga/container/
git clone https://github.com/swesonga/factorize

cd ~/swesonga/container/java
curl -Lo commons-cli-1.9.0-bin.tar.gz https://dlcdn.apache.org//commons/cli/binaries/commons-cli-1.9.0-bin.tar.gz
tar xzf commons-cli-1.9.0-bin.tar.gz

Compile the factorization app:

export CLASSPATH=~/swesonga/container/java/commons-cli-1.9.0/commons-cli-1.9.0.jar:.
export JAVA21_HOME=~/swesonga/container/java/binaries/jdk/x64/jdk-21.0.5+11

cd ~/swesonga/container/factorize/java/project/src/main/java/org/swesonga/math

$JAVA21_HOME/bin/javac -d . PrimalityTest.java FactorizationUtils.java Factorize.java ExecutionMode.java

Set up the Docker Environment

Create the Dockerfile

Create a Dockerfile. See Dockerizing a Java Application | Baeldung and How To Dockerize Java Application (Step-by-Step Tutorial) for examples of how to do this. There are some OpenJDK images at Microsoft Artifact Registry (did I need to get my own JDK? maybe not but for now, I know where the JDK is and what is happening).

docker pull mcr.microsoft.com/openjdk/jdk:21-ubuntu

Create the Dockerfile below, substituting the appropriate value for <user>.

FROM mcr.microsoft.com/openjdk/jdk:21-ubuntu
COPY . /swesonga/
WORKDIR /swesonga/factorize/java/project/src/main/java/org/swesonga/math

ENTRYPOINT ["/swesonga/java/binaries/jdk/x64/jdk-21.0.5+11/bin/java", "-cp", "/swesonga/java/commons-cli-1.9.0/commons-cli-1.9.0.jar:.", "-Xint", "-XX:+UseSerialGC", "-XX:+UseCompressedOops", "-XX:HeapBaseMinAddress=0x120000000000", "-Xlog:gc*=debug,safepoint:file=serialgc-jdk21.log::filecount=0", "-Xlog:pagesize=trace:file=pagesize-jdk21.log::filecount=0", "-Xlog:os=trace:file=os-jdk21.log::filecount=0", "org.swesonga.math.Factorize", "-threads", "4", "-number", "7438880205542315091371423981777", "-systemGCFrequency", "1048576"]

If you create the Dockerfile on another machine, you can copy the Dockerfile to the Linux host using scp(1) – Linux manual page:

scp Dockerfile user@IPaddress:/home/<user>/swesonga/container/

Start Docker

Verify that docker is up by running docker version. I got this output:

user@machine:~/swesonga$ docker version
Client: Docker Engine - Community
 Version:           23.0.1
 API version:       1.42
 Go version:        go1.19.5
 Git commit:        a5ee5b1
 Built:             Thu Feb  9 19:46:56 2023
 OS/Arch:           linux/amd64
 Context:           default
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

Run sudo systemctl start docker as described at Start the daemon | Docker Docs.

Build the Docker Image

See docker buildx build | Docker Docs for details on how to build. I only need the -t option to tag the image as swesonga-jdk21-testapp.

cd ~/swesonga/container/
docker build -t swesonga-jdk21-testapp .

Here is some sample output:

user@machine:~/swesonga/container$ docker build -t swesonga-jdk21-testapp .
[+] Building 7.9s (6/6) FINISHED
 => [internal] load build definition from Dockerfile                                                                            0.0s
 => => transferring dockerfile: 669B                                                                                            0.0s
 => [internal] load .dockerignore                                                                                               0.0s
 => => transferring context: 2B                                                                                                 0.0s
 => [internal] load metadata for mcr.microsoft.com/openjdk/jdk:21-ubuntu                                                        0.4s
 => [1/2] FROM mcr.microsoft.com/openjdk/jdk:21-ubuntu@sha256:98b6af6a403a01d476ee579340d624dfaac70409f50080e36eb6d86603f0ed8c  7.2s
 => => resolve mcr.microsoft.com/openjdk/jdk:21-ubuntu@sha256:98b6af6a403a01d476ee579340d624dfaac70409f50080e36eb6d86603f0ed8c  0.0s
 => => sha256:6414378b647780fee8fd903ddb9541d134a1947ce092d08bdeb23a54cb3684ac 29.54MB / 29.54MB                                0.5s
 => => sha256:ac27f5b44782db802c5876054378d16318ba6ab095203e15acc7527778c85370 178.15MB / 178.15MB                              2.3s
 => => sha256:d42e3adbad90b3214756070b3e98acd724228f7e8d08344d7044c0788a185b66 1.38kB / 1.38kB                                  0.2s
 => => sha256:98b6af6a403a01d476ee579340d624dfaac70409f50080e36eb6d86603f0ed8c 683B / 683B                                      0.0s
 => => sha256:ab80e68248a29dec58a531a5ff5a5bb873bb96fe829ac6b17f46c6f2a05cef63 899B / 899B                                      0.0s
 => => sha256:6d6be45eade816f5be7fc8935372e429b035dfee5f6e386dd7f87e6430228554 3.90kB / 3.90kB                                  0.0s
 => => extracting sha256:6414378b647780fee8fd903ddb9541d134a1947ce092d08bdeb23a54cb3684ac                                       1.3s
 => => extracting sha256:ac27f5b44782db802c5876054378d16318ba6ab095203e15acc7527778c85370                                       4.6s
 => => extracting sha256:d42e3adbad90b3214756070b3e98acd724228f7e8d08344d7044c0788a185b66                                       0.0s
 => [2/2] WORKDIR /home/<user>/swesonga/factorize/java/project/src/main/java/org/swesonga/math                                     0.2s
 => exporting to image                                                                                                          0.0s
 => => exporting layers                                                                                                         0.0s
 => => writing image sha256:4c068055cad5759153f3d3677404b9729b8ddee1c7026aa30e88b8c58c228037                                    0.0s
 => => naming to docker.io/library/swesonga-jdk21-testapp

Start the Docker Container

Before starting the container: docker ps | Docker Docs. Then docker run | Docker Docs.

docker ps
docker run -i -t swesonga-jdk21-testapp

Troubleshoot any Docker Errors

One error I ran into initially was that docker was unable to start the container process. I had missed the COPY command in the Dockerfile so the file couldn’t be found:

user@machine:~/swesonga$ docker run -i -t swesonga-jdk21-testapp
docker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "/home/<user>/swesonga/java/binaries/jdk/x64/jdk-21.0.5+11/bin/java": stat /home/<user>/swesonga/java/binaries/jdk/x64/jdk-21.0.5+11/bin/java: no such file or directory: unknown.
ERRO[0000] error waiting for container:

Fix the Dockerfile then docker build again. My ENTRYPOINT path still wasn’t correct so the same error appeared when re-running the application. I didn’t realize this though and thought that since I had changed the entrypoint, a cached build must still be in use. A delete cached docker build – Search pointed to the post on macos – Is there a way to clean docker build cache? – Stack Overflow. This is what I tried in my ignorance:

user@machine:~/swesonga$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          2         2         1.22GB    443.1MB (36%)
Containers      3         0         0B        0B
Local Volumes   0         0         0B        0B
Build Cache     9         0         776.9MB   776.9MB
user@machine:~/swesonga$ docker system df -v
Images space usage:

REPOSITORY               TAG       IMAGE ID       CREATED          SIZE      SHARED SIZE   UNIQUE SIZE   CONTAINERS
swesonga-jdk21-testapp   latest    682fedf54071   11 minutes ago   1.22GB    443.1MB       776.9MB       2
<none>                   <none>    4c068055cad5   26 minutes ago   443.1MB   443.1MB       0B            1

Containers space usage:

CONTAINER ID   IMAGE                    COMMAND                  LOCAL VOLUMES   SIZE      CREATED          STATUS    NAMES
c77b69082a8a   swesonga-jdk21-testapp   "/home/<user>/swesonga/…"   0               0B        6 minutes ago    Created   awesome_chatelet
58c723638dd2   swesonga-jdk21-testapp   "/home/<user>/swesonga/…"   0               0B        8 minutes ago    Created   sharp_lamport
4a3be55725b5   4c068055cad5             "/home/<user>/swesonga/…"   0               0B        17 minutes ago   Created   lucid_tharp

Local Volumes space usage:

VOLUME NAME   LINKS     SIZE

Build cache usage: 776.9MB
...

I tried pruning the build cache as suggested in that post.

user@machine:~/swesonga$ docker builder prune --all
WARNING! This will remove all build cache. Are you sure you want to continue? [y/N] y
ID                                              RECLAIMABLE     SIZE            LAST ACCESSED
te4o8rbj7s6nh6pluquzummzz                       true            0B              8 minutes ago
n2wbz4gf448fluw4uuogiqxdo*                      true    776.9MB         8 minutes ago
...
Total:  1.554GB

I realized that pruning wasn’t what I needed because now the cache was empty but the containers were still there based on the next output:

user@machine:~/swesonga$ docker system df -v
Images space usage:

REPOSITORY               TAG       IMAGE ID       CREATED          SIZE      SHARED SIZE   UNIQUE SIZE   CONTAINERS
swesonga-jdk21-testapp   latest    682fedf54071   18 minutes ago   1.22GB    443.1MB       776.9MB       2
<none>                   <none>    4c068055cad5   33 minutes ago   443.1MB   443.1MB       0B            1

Containers space usage:

CONTAINER ID   IMAGE                    COMMAND                  LOCAL VOLUMES   SIZE      CREATED          STATUS    NAMES
c77b69082a8a   swesonga-jdk21-testapp   "/home/<user>/swesonga/…"   0               0B        13 minutes ago   Created   awesome_chatelet
58c723638dd2   swesonga-jdk21-testapp   "/home/<user>/swesonga/…"   0               0B        15 minutes ago   Created   sharp_lamport
4a3be55725b5   4c068055cad5             "/home/<user>/swesonga/…"   0               0B        24 minutes ago   Created   lucid_tharp

Local Volumes space usage:

VOLUME NAME   LINKS     SIZE

Build cache usage: 0B

CACHE ID   CACHE TYPE   SIZE      CREATED   LAST USED   USAGE     SHARED
user@machine:~/swesonga$

I should have been using docker ps -a instead! The -a shows the existing containers (regardless of whether they are running).

user@machine:~/swesonga$ docker ps -a
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS    PORTS     NAMES
c77b69082a8a   682fedf54071   "/home/<user>/swesonga/…"   16 minutes ago   Created             awesome_chatelet
58c723638dd2   682fedf54071   "/home/<user>/swesonga/…"   18 minutes ago   Created             sharp_lamport
4a3be55725b5   4c068055cad5   "/home/<user>/swesonga/…"   27 minutes ago   Created             lucid_tharp
user@machine:~/swesonga$

I started displaying the Dockerfile before building and one of the errors I ran into was because I hadn’t saved the Dockerfile. Sheesh.

cat Dockerfile
docker build -t swesonga-jdk21-testapp .
docker ps -a
docker run -i -t swesonga-jdk21-testapp

After all this experimentation, we can remove all the broken containers using docker container rm | Docker Docs to remove individual ones or docker container prune | Docker Docs to remove all stopped containers (use with caution)!

docker container prune

Collect Logs from the Container

I used CTRL+C to stop the container after some time. The next question was how to examine the files in the container. linux – Exploring Docker container’s file system – Stack Overflow suggests using docker container cp | Docker Docs. I opened another SSH session to the host running Docker then copied the logs from the container using these commands:

mkdir -p ~/swesonga/logs/tip/
cd ~/swesonga/logs/

docker ps -a
export CONTAINERID=XXXXXXXXXXX

docker cp $CONTAINERID:/swesonga/factorize/java/project/src/main/java/org/swesonga/math/serialgc-jdk21.log ~/swesonga/logs/

docker cp $CONTAINERID:/swesonga/factorize/java/project/src/main/java/org/swesonga/math/pagesize-jdk21.log ~/swesonga/logs/

docker cp $CONTAINERID:/swesonga/factorize/java/project/src/main/java/org/swesonga/math/os-jdk21.log ~/swesonga/logs/

I switched back to the development box and used scp to get the log files back to it.

scp user@IPaddress:/home/<user>/swesonga/logs/*.log .

When gathering multiple rounds of logs, it is convenient to group them into separate folders. I used this command:

export CURRDATE=`date +%Y-%m-%d_%H%M%S`; scp user@IPaddress:/home/<user>/swesonga/logs/*.log ./logs-$CURRDATE/

Setting Container Limits

To see the amount of RAM on the Docker host, run free -h since it’s an Ubuntu host. How do we set a RAM limit for a docker container? Docker – Setting Memory And CPU Limits points me to the --memory limit parameter of docker run | Docker Docs.

docker ps -a
docker run -i --memory 2GB -t swesonga-jdk21-testapp

Observe the head of the jdk21 GC log below. The total memory is now reported as 2048M. The maximum heap size is 25% of this total, as expected. The initial heap is 32MB and the minimum heap is 8MB.

[0.006s][info][gc,init] CardTable entry size: 512
[0.006s][debug][gc,heap] Minimum heap 8388608  Initial heap 33554432  Maximum heap 536870912
[0.006s][info ][gc     ] Using Serial
[0.006s][debug][gc,heap,coops] Protected page at the reserved heap base: 0x0000120000000000 / 2097152 bytes
[0.006s][debug][gc,heap,coops] Heap address: 0x0000120000200000, size: 512 MB, Compressed Oops mode: Non-zero disjoint base: 0x0000120000000000, Oop shift amount: 3
[0.007s][info ][gc,init      ] Version: 21.0.5+11-LTS (release)
[0.007s][info ][gc,init      ] CPUs: 20 total, 20 available
[0.007s][info ][gc,init      ] Memory: 2048M
[0.007s][info ][gc,init      ] Large Page Support: Disabled
[0.007s][info ][gc,init      ] NUMA Support: Disabled
[0.007s][info ][gc,init      ] Compressed Oops: Enabled (Non-zero disjoint base)
[0.007s][info ][gc,init      ] Heap Min Capacity: 8M
[0.007s][info ][gc,init      ] Heap Initial Capacity: 32M
[0.007s][info ][gc,init      ] Heap Max Capacity: 512M
[0.007s][info ][gc,init      ] Pre-touch: Disabled

The number of CPUs can be set using the --cpus option.

cat Dockerfile
docker build -t swesonga-jdktip-testapp-2core .
docker ps -a
docker run -i --cpus 2 --memory 2GB -t swesonga-jdktip-testapp-2core

Viewing Container Stats

One of the questions I need to answer is how much memory is in use and how much is available in the container. A view available memory in a docker container – Google Search pointed me to How to Use the Resource Usage Docker Extension. Turns out docker stats is good enough for my needs right now.

user@machine:~/swesonga$ docker stats

CONTAINER ID   NAME                      CPU %     MEM USAGE / LIMIT   MEM %     NET I/O     BLOCK I/O   PIDS
7f096bd5163d   condescending_goldstine   196.82%   26.03MiB / 2GiB     1.27%     806B / 0B   0B / 0B     13

Categories: Java

Investigating a Windows ZGC Test Failure

[JDK-8334475] UnsafeIntrinsicsTest.java#ZGenerationalDebug assert(!assert_on_failure) failed: Has low-order bits set – Java Bug System (openjdk.org) was brought to my attention a few months ago. I ran the test on commit b3bf31a0a08d on my x64 machine to see what happens:

cd /c/repos/scratchpad/scripts/java/tests

./run-jtreg-tests.sh /d/java/forks/openjdk/jdk /d/java/forks/openjdk/jdk/build/windows-x86_64-server-slowdebug/jdk  /c/java/binaries/jtreg-7.4+1/lib/jtreg.jar

That script shows the exact command line used to invoke the test:

/d/java/forks/openjdk/jdk/build/windows-x86_64-server-slowdebug/jdk/bin/java -Xmx512m -jar /c/java/binaries/jtreg-7.4+1/lib/jtreg.jar -agentvm -ignore:quiet -automatic -xml -vmoption:-Xmx512m -timeoutFactor:4 -concurrency:1 -testjdk:/d/java/forks/openjdk/jdk/build/windows-x86_64-server-slowdebug/jdk -verbose:fail,error,summary  test/hotspot/jtreg/compiler/gcbarriers/UnsafeIntrinsicsTest.java

The test passed on x64 but I wanted to see how to step into the test code itself so I tried this setup in Visual Studio 2022. Unfortunately, it was not straightforward to break into the correct process when launching the test in Visual Studio 2022.

Command: D:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\jdk\bin\java.exe

Arguments: -Xmx512m -jar C:/java/binaries/jtreg-7.4+1/lib/jtreg.jar -agentvm -ignore:quiet -automatic -xml -vmoption:-Xmx512m -timeoutFactor:4 -concurrency:1 -testjdk:D:/java/forks/openjdk/jdk/build/windows-x86_64-server-slowdebug/jdk -verbose:fail,error,summary  test/hotspot/jtreg/compiler/gcbarriers/UnsafeIntrinsicsTest.java

Working Dir: D:/java/forks/openjdk/jdk

Test Execution Sequence

I decided to try to understand the test execution sequence and started by looking at the output generated when running the jtreg test. Notice that the CreateCoredumpOnCrash product flag is disabled. The first thing I did was to enable it so that there is a core dump to examine.

D:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\jdk\bin\java 
-Dtest.vm.opts=-Xmx512m 
-Dtest.tool.vm.opts=-J-Xmx512m 
-Dtest.compiler.opts= 
-Dtest.java.opts= 
-Dtest.jdk=D:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\jdk 
-Dcompile.jdk=D:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\jdk 
-Dtest.timeout.factor=4.0 
-Dtest.root=D:\java\forks\openjdk\jdk\test\hotspot\jtreg 
-Dtest.name=compiler/gcbarriers/UnsafeIntrinsicsTest.java#ZGenerationalDebug 
-Dtest.file=D:\java\forks\openjdk\jdk\test\hotspot\jtreg\compiler\gcbarriers\UnsafeIntrinsicsTest.java 
-Dtest.src=D:\java\forks\openjdk\jdk\test\hotspot\jtreg\compiler\gcbarriers 
-Dtest.src.path=D:\java\forks\openjdk\jdk\test\hotspot\jtreg\compiler\gcbarriers;D:\java\forks\openjdk\jdk\test\lib 
-Dtest.classes=D:\java\forks\openjdk\jdk\JTwork\classes\compiler\gcbarriers\UnsafeIntrinsicsTest_ZGenerationalDebug.d 
-Dtest.class.path=D:\java\forks\openjdk\jdk\JTwork\classes\compiler\gcbarriers\UnsafeIntrinsicsTest_ZGenerationalDebug.d;D:\java\forks\openjdk\jdk\JTwork\classes\test\lib 
-Dtest.class.path.prefix=D:\java\forks\openjdk\jdk\JTwork\classes\compiler\gcbarriers\UnsafeIntrinsicsTest_ZGenerationalDebug.d;D:\java\forks\openjdk\jdk\test\hotspot\jtreg\compiler\gcbarriers;D:\java\forks\openjdk\jdk\JTwork\classes\test\lib 
-Dtest.modules=java.base/jdk.internal.misc:+open --add-modules java.base --add-exports java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED
-Xmx512m
-XX:+UseZGC
-XX:+ZGenerational
-XX:+UnlockDiagnosticVMOptions
-XX:+ZVerifyOops
-XX:ZCollectionInterval=1
-XX:-CreateCoredumpOnCrash
-XX:CompileCommand=dontinline,*::mergeImpl* com.sun.javatest.regtest.agent.MainWrapper D:\java\forks\openjdk\jdk\JTwork\compiler\gcbarriers\UnsafeIntrinsicsTest_ZGenerationalDebug.d\main.0.jta

The core dump wasn’t particularly helpful. I worked on simplifying the test so that only the failing scenarios remained. The log in the JBS issue was interpreted code only so I added the -Xint argument and could still reproduce the failure on Windows AArch64.

I came back to the idea of capturing the full command line for the final java.exe process that runs the test. Is there a way to log process start on Windows to capture all command lines? Copilot cited PowerShell and Command Line Logging | LogRhythm, which suggested enabling the use of Event ID 4688: a new process has been created.

However, the event viewer didn’t have command line arguments when I did this, which is what I needed. I just looked up Event ID 4688 and found 4688(S) A new process has been created. – Windows 10 | Microsoft Learn, which explains that you must enable “Administrative Templates\System\Audit Process Creation\Include command line in process creation events” group policy to include command line in process creation events. Hmm, I definitely didn’t do that when I was investigating this issue. I just tried this and it does the trick!

I went back to deciphering the jtreg test execution output: which class was this com.sun.javatest.regtest.agent.MainWrapper? The only openjdk/jdk match for MainWrapper.java appeared to be jdk/test/hotspot/jtreg/vmTestbase/nsk/share/MainWrapper.java at b3bf31a0a08da679ec2fd21613243fb17b1135a9 · openjdk/jdk (github.com). However, it’s from a different package! Copilot informed me that the MainWrapper class was actually jtreg/src/share/classes/com/sun/javatest/regtest/agent/MainWrapper.java at master · openjdk/jtreg (github.com)

Callstack Investigation

I stepped back to the callstack in the JBS issue and decide to trace it. Here are the links to the sources:

---------------  T H R E A D  ---------------

Current thread (0x0000020efe6a7fb0):  JavaThread "main"             [_thread_in_vm, id=19132, stack(0x0000003c68f00000,0x0000003c69000000) (1024K)]

Stack: [0x0000003c68f00000,0x0000003c69000000]
Native frames: <unavailable>
Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  java.lang.Object.clone()Ljava/lang/Object;+0 java.base
j  java.util.Arrays.copyOfRange([BII)[B+11 java.base
j  java.lang.String.<init>(Ljava/lang/AbstractStringBuilder;Ljava/lang/Void;)V+32 java.base
j  java.lang.StringBuilder.toString()Ljava/lang/String;+16 java.base
j  sun.nio.cs.StandardCharsets.toLower(Ljava/lang/String;)Ljava/lang/String;+121 java.base
j  sun.nio.cs.StandardCharsets.lookup(Ljava/lang/String;)Ljava/nio/charset/Charset;+44 java.base
j  sun.nio.cs.StandardCharsets.charsetForName(Ljava/lang/String;)Ljava/nio/charset/Charset;+6 java.base
j  java.nio.charset.Charset.lookup2(Ljava/lang/String;)Ljava/nio/charset/Charset;+39 java.base
j  java.nio.charset.Charset.lookup(Ljava/lang/String;)Ljava/nio/charset/Charset;+40 java.base
j  java.nio.charset.Charset.isSupported(Ljava/lang/String;)Z+1 java.base
j  java.lang.System.initPhase1()V+37 java.base
v  ~StubRoutines::call_stub 0x0000020e81410180
Lock stack of current Java thread (top to bottom):
LockStack[0]: sun.nio.cs.StandardCharsets 
{0x0000040000018d28} - klass: 'sun/nio/cs/StandardCharsets'
 - ---- fields (total size 5 words):
 - private 'classMap' 'Ljava/util/Map;' @16  null (0x0000000000000000)
 - private 'aliasMap' 'Ljava/util/Map;' @24  null (0x0000000000000000)
 - private 'cache' 'Ljava/util/Map;' @32  null (0x0000000000000000)

There is a generated class, sun.nio.cs.StandardCharsets.charsetForName, in the stack above! I was puzzled about why jdk/src/java.base/share/classes/java/nio/charset/StandardCharsets.java at b3bf31a0a08da679ec2fd21613243fb17b1135a9 · openjdk/jdk (github.com) did not contain that method. This failure reminds me of another failure I was investigating a while back:

$JAVA_HOME/bin/java -jar /c/java/binaries/jtreg-7.4+1/lib/jtreg.jar -agentvm -timeoutFactor:4 -concurrency:4 -verbose:fail,error,summary -testjdk:/d/java/forks/openjdk/jdk/build/windows-x86_64-server-release/jdk -nativepath:/d/java/forks/openjdk/jdk/build/windows-x86_64-server-release/support/test/jdk/jtreg/native/lib test/jdk/java/foreign/TestLinker.java

In the midst of all this wrangling, I decide to write a simple test to spit out the value of the sun.jnu.encoding property. That test runs fine so next step is to use the same flags as the failing jtreg test. However, as I added the ZGC flags to the test command line, I realized that I hadn’t even tried those flags with the java -version. What an oversight! The bug reproduces without running any specific program!

$JAVA_HOME/bin/java -XX:+UseZGC -XX:+ZGenerational -XX:+ZVerifyOops -version

Debugging Minimal Reproducible Failure

Since the assert happens when cloning an array, I decided to find the native implementation of the clone method. Searched for “clone_” and the only relevant hit (from a quick glance) appeared to be in the LinkResolver::check_method_accessability method.

I had been working on the latest commits and decided to try this test on jdk21u. It failed there as well! I tried it on jdk17 but it terminated with “Unrecognized VM option ‘ZGenerational'”. Blaming jdk/src/hotspot/share/gc/shared/gc_globals.hpp at b3bf31a0a08da679ec2fd21613243fb17b1135a9 · openjdk/jdk (github.com) pointed to 8307058: Implementation of Generational ZGC · openjdk/jdk@d20034b (github.com). Looks like jdk21 was the first release with this flag as per [JDK-8307058] Implementation of Generational ZGC – Java Bug System (openjdk.org).

Back to the LinkResolver: it looked like cloning is being dispatched through the Java object but what I had been looking for is a native call. Nothing about cloning at Arrays (Java SE 11 & JDK 11 ) (oracle.com) other than the fact that the method is inherited from the Object class. Nothing helpful in jdk/src/java.base/share/classes/java/util/Arrays.java or jdk/src/java.base/share/classes/java/lang/reflect/Array.java either. jdk/src/java.base/share/native/libjava/Array.c looked promising though. Methods like Java_java_lang_reflect_Array_getByte use the java.lang.reflect.Array JNIEXPORTs declared in jdk/src/hotspot/share/include/jvm.h. Browsing through the corresponding implementations in jdk/src/hotspot/share/prims/jvm.cpp led me to the clone implementation: JVM_Clone! This is the setup I used in the Visual Studio 2022 debugger:

Command: C:/java/forks/openjdk/jdk/build/windows-aarch64-server-slowdebug/jdk/bin/java.exe

Arguments: -XX:+UseZGC -XX:+ZGenerational -XX:+ZVerifyOops -version

Working Dir: C:/java/forks/openjdk/jdk

The failure appeared to be happening in this statement: HeapAccess<>::clone(obj(), new_obj_oop, size). I got this callstack by stepping into the calls in the slowdebug build because the is_valid function is inlined in the fastdebug build, preventing me from setting a breakpoint in it.

jvm.dll!is_valid(zaddress addr, bool assert_on_failure) Line 298	C++
jvm.dll!assert_is_valid(zaddress addr) Line 321	C++
jvm.dll!to_zaddress(unsigned __int64 value) Line 339	C++
jvm.dll!to_zaddress(oopDesc * o) Line 345	C++
jvm.dll!ZBarrierSet::AccessBarrier<270432,ZBarrierSet>::clone_in_heap(oopDesc * src, oopDesc * dst, unsigned __int64 size) Line 433	C++
jvm.dll!AccessInternal::PostRuntimeDispatch<ZBarrierSet::AccessBarrier<270400,ZBarrierSet>,9,270400>::access_barrier(oopDesc * src, oopDesc * dst, unsigned __int64 size) Line 200	C++
jvm.dll!AccessInternal::RuntimeDispatch<270400,oopDesc *,9>::clone_init(oopDesc * src, oopDesc * dst, unsigned __int64 size) Line 349	C++
jvm.dll!AccessInternal::RuntimeDispatch<270400,oopDesc *,9>::clone(oopDesc * src, oopDesc * dst, unsigned __int64 size) Line 533	C++
jvm.dll!AccessInternal::PreRuntimeDispatch::clone<270400>(oopDesc * src, oopDesc * dst, unsigned __int64 size) Line 890	C++
jvm.dll!AccessInternal::clone<262144>(oopDesc * src, oopDesc * dst, unsigned __int64 size) Line 1181	C++
jvm.dll!Access<262144>::clone(oopDesc * src, oopDesc * dst, unsigned __int64 size) Line 212	C++
jvm.dll!JVM_Clone(JNIEnv_ * env, _jobject * handle) Line 698	C++
00000298b2bbf056()	Unknown
00000298a2ee5590()	Unknown
000000c053dfe908()	Unknown

Here’s the stack from stepping into the fastdebug assembly (note that line numbers might be off since this is not slowdebug):

jvm.dll!report_vm_error(const char * file, int line, const char * error_msg, const char * detail_fmt, ...) Line 181	C++
[Inline Frame] jvm.dll!oop::check_oop() Line 89	C++
[Inline Frame] jvm.dll!oop::on_usage() Line 91	C++
[Inline Frame] jvm.dll!oop::obj() Line 103	C++
[Inline Frame] jvm.dll!oop::operator=(const oop &) Line 114	C++
jvm.dll!Copy::pd_conjoint_oops_atomic(const oop * from, oop * to, unsigned __int64 count) Line 120	C++
[Inline Frame] jvm.dll!Copy::pd_conjoint_jlongs_atomic(const __int64 *) Line 106	C++
[Inline Frame] jvm.dll!Copy::conjoint_jlongs_atomic(const __int64 *) Line 142	C++
jvm.dll!AccessInternal::arraycopy_conjoint_atomic<__int64>(__int64 * src, __int64 * dst, unsigned __int64 length) Line 104	C++
jvm.dll!RawAccessBarrier<270432>::clone(oop src, oop dst, unsigned __int64 size) Line 331	C++
[Inline Frame] jvm.dll!BarrierSet::AccessBarrier<270432,ZBarrierSet>::clone_in_heap(oop) Line 313	C++
jvm.dll!ZBarrierSet::AccessBarrier<270432,ZBarrierSet>::clone_in_heap(oop src, oop dst, unsigned __int64 size) Line 451	C++
jvm.dll!AccessInternal::PostRuntimeDispatch<ZBarrierSet::AccessBarrier<270400,ZBarrierSet>,9,270400>::access_barrier(oop src, oop dst, unsigned __int64 size) Line 200	C++
jvm.dll!AccessInternal::RuntimeDispatch<270400,oop,9>::clone_init(oop src, oop dst, unsigned __int64 size) Line 349	C++
[Inline Frame] jvm.dll!AccessInternal::RuntimeDispatch<270400,oop,9>::clone(oop) Line 532	C++
[Inline Frame] jvm.dll!AccessInternal::PreRuntimeDispatch::clone(oop) Line 889	C++
[Inline Frame] jvm.dll!AccessInternal::clone(oop) Line 1180	C++
jvm.dll!Access<262144>::clone(oop src, oop dst, unsigned __int64 size) Line 211	C++
jvm.dll!JVM_Clone(JNIEnv_ * env, _jobject * handle) Line 698	C++
000001a156d3a45c()	Unknown

ZBarrierSet::AccessBarrier<270432,ZBarrierSet>::clone_in_heap is interesting because it verifies that the source is a valid z-address! Let’s take a look at the memory region and see what is there…

I looked at this and searched for UTF-8 byte 0xBD meaning – Search (bing.com) but that didn’t turn up anything meaningful. I noticed that count is 3 on my Aarch64 device so we are copying 3 oops. Was this expected given that the count is 4 on Windows x64? More importantly though, the big question I have now is why doesn’t slowdebug step into the oop checking code when pressing F11 on line 120 in the screenshot? I expected the behavior of oop::on_usage to be different, not that it wouldn’t be called at all! When browsing the sources in VSCode on my x64 desktop, I clicked on the oop type (of the src argument) and it took me to a typedef of class oopDesc*. That’s when I spotted the CHECK_UNHANDLED_OOPS ifndef. The fastdebug build must have this defined! The only non-cpp/hpp file that contains CHECK_UNHANDLED_OOPS is jdk/make/hotspot/lib/JvmFlags.gmk. Sure enough, it is only defined for fastdebug. This means that I should be able to enable it for slowdebug and release and verify whether the behavior is present there. We can therefore configure a slowdebug build with the --with-extra-cflags=-DCHECK_UNHANDLED_OOPS option.

date; time bash configure --with-jtreg=/cygdrive/c/java/binaries/jtreg-7.4+1 --with-gtest=/cygdrive/c/repos/googletest --with-boot-jdk=/cygdrive/c/java/binaries/jdk/x64/jdk-22.0.1+8 --openjdk-target=aarch64-unknown-cygwin --with-debug-level=slowdebug --with-extra-cflags=-DCHECK_UNHANDLED_OOPS

time /cygdrive/c/repos/scratchpad/scripts/java/cygwin/build-jdk.sh windows aarch64 0 slowdebug

Looking at the actual pointer, I noticed that its value is the data “cp1252..”. After further investigation, I conclude that the bug is that we’re calling oops::operator= instead of just copying the values! I test a fix that simply copies the values directly and it works! The test passes even with the -DCHECK_UNHANDLED_OOPS option!

On to the next question: is there anything else using the pd_conjoint_oops_atomic function (and will it be negatively affected by my change)? While searching for “pd_conjoint_oops_atomic”, I notice that some platforms have an assert that oops == long or smth like that. There are 2 users of pd_conjoint_oops_atomic:

  1. Copy::pd_conjoint_oops_atomic > AccessInternal::arraycopy_conjoint_oops > RawAccessBarrierArrayCopy::arraycopy. The last method’s definition is based on templates so I continue with a regex search for “Raw::.*arraycopy”. Many of the results are related to arraycopy_in_heap. Looking at all the barrierSet results makes me realize I could just search JBS for “arraycopy_in_heap”.
  2. pd_arrayof_conjoint_oops in jdk/src/hotspot/os_cpu/windows_aarch64/copy_windows_aarch64.hpp.

One idea is to run Java programs on a build linked with /PROFILE (Performance Tools Profiler) i.e. configured via --with-extra-ldflags=-profile to enable collection of code coverage data then confirming that those functions are executed. That seems cumbersome though so I try to create some array copying code to see if I can get to those functions (using breakpoints but none are hit). After taking a break: I wonder if I can just search from the bottom instead? Looking in jvm.cpp for copy reveals the JVM_ArrayCopy function. Here is my Java program:

public class CopyArray {
    public static void main(String[] args) {
        int length = 0xdeadc0d;
        int srcPos = 0;

        if (args.length > 0) {
            try {
                int userLength = Integer.parseInt(args[0]);
                length = userLength;
            }
            catch (Throwable e) {
                System.err.println("Ignoring invalid user arguments.");
            }
        }

        byte[] src = new byte[length];

        for (int i = 0; i < src.length; i++) {
            src[i] = (byte)(i % 256);
        }
        byte[] dest = new byte[length];
        System.arraycopy(src, srcPos, dest, 0, length);
    }
}

I debugged the JVM running this program on my x64 machine. This hits the target function, JVM_ArrayCopy but there are so many callers. I have to set a condition on the breakpoint (hence the magic value of the length above) before I can step in to see where my call goes. Here are the source paths (note the different commit)

jvm.dll!Copy::pd_conjoint_bytes_atomic(const void * from, void * to, unsigned __int64 count) Line 119	C++
jvm.dll!Copy::conjoint_memory_atomic(const void * from, void * to, unsigned __int64 size) Line 53	C++
jvm.dll!AccessInternal::arraycopy_conjoint_atomic<void>(void * src, void * dst, unsigned __int64 length) Line 164	C++
jvm.dll!RawAccessBarrierArrayCopy::arraycopy<136585312,void>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, void * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, void * dst_raw, unsigned __int64 length) Line 298	C++
jvm.dll!RawAccessBarrier<136585312>::arraycopy<void>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, void * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, void * dst_raw, unsigned __int64 length) Line 308	C++
jvm.dll!AccessInternal::PreRuntimeDispatch::arraycopy<136587328,void>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, void * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, void * dst_raw, unsigned __int64 length) Line 834	C++
jvm.dll!AccessInternal::PreRuntimeDispatch::arraycopy<136585280,void>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, void * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, void * dst_raw, unsigned __int64 length) Line 867	C++
jvm.dll!AccessInternal::arraycopy_reduce_types<136585280,void>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, void * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, void * dst_raw, unsigned __int64 length) Line 1008	C++
jvm.dll!AccessInternal::arraycopy<136577024,void>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, const void * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, void * dst_raw, unsigned __int64 length) Line 1172	C++
jvm.dll!Access<136577024>::arraycopy<void>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, const void * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, void * dst_raw, unsigned __int64 length) Line 147	C++
jvm.dll!ArrayAccess<134217728>::arraycopy<void>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, unsigned __int64 length) Line 301	C++
jvm.dll!TypeArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, JavaThread * __the_thread__) Line 170	C++
jvm.dll!JVM_ArrayCopy(JNIEnv_ * env, _jclass * ignored, _jobject * src, int src_pos, _jobject * dst, int dst_pos, int length) Line 307	C++
00000244ea690702()	Unknown

Copy::conjoint_memory_atomic is interesting because it has a comment indicating that copying bytes is not aligned and so there is no need to be atomic. The if statements in that method indicate that I can change the size of elements in the array to call different paths. Looks like I need to create an array of objects.

/**
export JAVA_HOME=~/java/binaries/jdk/x64/jdk-21.0.2+13
$JAVA_HOME/bin/javac CopyArray.java
$JAVA_HOME/bin/java CopyArray
 */

public class CopyArray {
    public static void main(String[] args) {
        int length = 0xdead;
        int srcPos = 0;

        if (args.length > 0) {
            try {
                int userLength = Integer.parseInt(args[0]);
                length = userLength;
            }
            catch (Throwable e) {
                System.err.println("Ignoring invalid user arguments.");
            }
        }

        Object[] src = new Object[length];

        for (int i = 0; i < src.length; i++) {
            src[i] = new Object();
        }
        Object[] dest = new Object[length];
        System.arraycopy(src, srcPos, dest, 0, length);
    }
}

Now we are closer to the array_oops code I was trying to hit:

jvm.dll!Copy::pd_conjoint_jints_atomic(const int * from, int * to, unsigned __int64 count) Line 52	C++
jvm.dll!Copy::conjoint_oops_atomic(const narrowOop * from, narrowOop * to, unsigned __int64 count) Line 155	C++
jvm.dll!AccessInternal::arraycopy_conjoint_oops(narrowOop * src, narrowOop * dst, unsigned __int64 length) Line 54	C++
jvm.dll!RawAccessBarrierArrayCopy::arraycopy<50331750,HeapWordImpl *>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 234	C++
jvm.dll!RawAccessBarrier<52715622>::arraycopy<enum narrowOop>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, narrowOop * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, narrowOop * dst_raw, unsigned __int64 length) Line 308	C++
jvm.dll!RawAccessBarrier<52715622>::oop_arraycopy<enum narrowOop>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, narrowOop * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, narrowOop * dst_raw, unsigned __int64 length) Line 130	C++
jvm.dll!ModRefBarrierSet::AccessBarrier<35938406,CardTableBarrierSet>::oop_arraycopy_in_heap<enum narrowOop>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, narrowOop * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, narrowOop * dst_raw, unsigned __int64 length) Line 109	C++
jvm.dll!AccessInternal::PostRuntimeDispatch<CardTableBarrierSet::AccessBarrier<35938406,CardTableBarrierSet>,8,35938406>::oop_access_barrier<HeapWordImpl *>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 142	C++
jvm.dll!AccessInternal::RuntimeDispatch<35938374,HeapWordImpl *,8>::arraycopy(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 517	C++
jvm.dll!AccessInternal::PreRuntimeDispatch::arraycopy<35938374,HeapWordImpl *>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 871	C++
jvm.dll!AccessInternal::arraycopy_reduce_types<35938372>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 1018	C++
jvm.dll!AccessInternal::arraycopy<35913732,HeapWordImpl *>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * const * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 1172	C++
jvm.dll!Access<35913728>::oop_arraycopy<HeapWordImpl *>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * const * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 136	C++
jvm.dll!ArrayAccess<33554432>::oop_arraycopy(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, unsigned __int64 length) Line 327	C++
jvm.dll!ObjArrayKlass::do_copy(arrayOop s, unsigned __int64 src_offset, arrayOop d, unsigned __int64 dst_offset, int length, JavaThread * __the_thread__) Line 197	C++
jvm.dll!ObjArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, JavaThread * __the_thread__) Line 282	C++
jvm.dll!JVM_ArrayCopy(JNIEnv_ * env, _jclass * ignored, _jobject * src, int src_pos, _jobject * dst, int dst_pos, int length) Line 307	C++
00000202d3f00502()	Unknown
00000202cc269950()	Unknown

In this call stack, the arraycopy_conjoint_oops(narrowOop* src, narrowOop* dst, size_t length) implementation that is called has narrow oops because of the branch in ObjArrayKlass::copy_array. Launch the application using these arguments instead:

C:\java\forks\openjdk\jdk\build\windows-x86_64-server-slowdebug\jdk\bin\java.exe -XX:-UseCompressedOops CopyArray

Now the code block of interest is hit! Hmm, I’m realizing that I should have been explicit about the collector to use. This was debugging the G1 collector (chosen ergonomically).

Notice that ZGC has different code for copying the array on Aarch64 (my fix-jlongs branch based on openjdk/jdk at ee839b7f0ebe471d3877cddd2c87019ccb8ee5ae).

jvm.dll!ZBarrierSet::AccessBarrier<52715590,ZBarrierSet>::oop_arraycopy_in_heap_no_check_cast(zpointer * dst, zpointer * src, unsigned __int64 length) Line 371	C++
jvm.dll!ZBarrierSet::AccessBarrier<35938374,ZBarrierSet>::oop_arraycopy_in_heap(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, zpointer * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, zpointer * dst_raw, unsigned __int64 length) Line 403	C++
jvm.dll!ZBarrierSet::AccessBarrier<35938374,ZBarrierSet>::oop_arraycopy_in_heap(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, oop * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, oop * dst_raw, unsigned __int64 length) Line 128	C++
jvm.dll!AccessInternal::PostRuntimeDispatch<ZBarrierSet::AccessBarrier<35938374,ZBarrierSet>,8,35938374>::oop_access_barrier<HeapWordImpl *>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 142	C++
jvm.dll!AccessInternal::RuntimeDispatch<35938374,HeapWordImpl *,8>::arraycopy(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 517	C++
jvm.dll!AccessInternal::PreRuntimeDispatch::arraycopy<35938374,HeapWordImpl *>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 871	C++
jvm.dll!AccessInternal::arraycopy_reduce_types<35938372>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 1018	C++
jvm.dll!AccessInternal::arraycopy<35913732,HeapWordImpl *>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * const * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 1172	C++
jvm.dll!Access<35913728>::oop_arraycopy<HeapWordImpl *>(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, HeapWordImpl * const * src_raw, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, HeapWordImpl * * dst_raw, unsigned __int64 length) Line 136	C++
jvm.dll!ArrayAccess<33554432>::oop_arraycopy(arrayOop src_obj, unsigned __int64 src_offset_in_bytes, arrayOop dst_obj, unsigned __int64 dst_offset_in_bytes, unsigned __int64 length) Line 327	C++
jvm.dll!ObjArrayKlass::do_copy(arrayOop s, unsigned __int64 src_offset, arrayOop d, unsigned __int64 dst_offset, int length, JavaThread * __the_thread__) Line 198	C++
jvm.dll!ObjArrayKlass::copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, JavaThread * __the_thread__) Line 290	C++
jvm.dll!JVM_ArrayCopy(JNIEnv_ * env, _jclass * ignored, _jobject * src, int src_pos, _jobject * dst, int dst_pos, int length) Line 308	C++
000002602e8c06a8()	Unknown

I need to turn off compressed oops with the serial collector as well but it looks like there is no check_oop_function for the serial collector. That said, this exploration of array copying code was insightful, showing how the data type sizes determine which path is taken for copying primitives and objects. There didn’t appear to be any red flags about removing the oop::operator= usage so I opened 8334475: UnsafeIntrinsicsTest.java#ZGenerationalDebug assert(!assert_on_failure) failed: Has low-order bits set by swesonga · Pull Request #20390 · openjdk/jdk (github.com) to fix the assertion failure. The most interesting part of this investigation was that the bad address was a data value (cp1252) staring right at me and I missed it. This was quite educational for me though.


Categories: Linux, Programming

SFrame Resources

I have been learning about the SFrame tracing effort and figured I should document the resources I have reviewed. Indu Bhagat has been actively involved in the development of SFrame. This is one of her talks giving an overview of the objectives of SFrame. The overall idea is that profiling tools (e.g. perf) usually need to generate stack traces. She lists some methods used to generate stack traces, e.g. using frame pointers, EH frame, last branch record (LBR), and other heuristics. Each of these have their own advantages and pitfalls. SFrame encodes the minimal info required for stack tracing.

SFrame: The Simple Frame Stack Trace Format – Indu Bhagat, Oracle – YouTube

I found additional videos by searching for sframe indu (there are lots of unrelated sframe results out there). This one by Steven and Indu covers potential issues that need to be addressed for JITted code.

Implementing sframes – Steven Rostedt, Indu Bhagat (youtube.com)

There are various informative discussions about SFrame out there, e.g.

  1. SFrame based stack tracer for user space in the kernel [LWN.net]
  2. Fedora’s tempest in a stack frame [LWN.net]

Indu and Steven also talk about SFrame at the Linux Storage, Filesystem, MM & BPF Summit | LF Events (linuxfoundation.org). This video feels a bit more detailed than the previous one. In this video, it is explicitly outlined that frame pointers require setup in every function and increase register pressure. The cost of this cites the Fedora’s tempest in a stack frame [LWN.net] article. Steven also discusses how the Orc unwinder was created to solve the need for accurate stack unwinding. This is needed, for example, for live kernel patching (see What is Linux kernel live patching? (redhat.com) for more info). SFrame is based on orc unwinder but for user space.

Sframe – Steven Rostedt, Indu Bhagat – YouTube

These resources provide a high-level view of stack tracing/unwinding concerns. Tools like async-profiler/async-profiler: Sampling CPU and HEAP profiler for Java featuring AsyncGetCallTrace + perf_events (github.com) will probably look very different if they switch to using SFrame.