Skip to content

Feat: hello-awtfree draws Compose through the GraalVM window's own surface - #58

Merged
b-re-w merged 11 commits into
extendedfrom
feat/awtfree-compose-scene
Oct 18, 2026
Merged

b-re-w merged 11 commits into
extendedfrom
feat/awtfree-compose-scene

Conversation

@b-re-w

@b-re-w b-re-w commented Oct 17, 2026

Copy link
Copy Markdown
Member

Supersedes #42 and answers thisisthepy/python-multiplatform#191 §5 item 2 (a demo that draws Compose on the GraalVM path).

b-re-w added 11 commits October 18, 2026 05:41
…rface

The one-executable gate's sample is now a Compose application with no AWT, built from #42:
text, a field and a clickable control, hosted by the core fork's ComposeWindowHost in the
GraalVM window and drawn through appKitWindowSurface (Metal) or x11WindowSurface (GL), from
core JetBrains#138. With HELLO_SELF_CHECK it still checks Korean with no ICU data file and the inked
drawing, and now also presents 30 frames of the Compose content, none at a size other than the
window's, which check-one-executable.sh requires.

A run that does not end within 120 s is stopped with every thread's stack printed (sample on
macOS, gdb on Linux). The GraalVM macOS cell hung after its text check once on #57 (run
39058147045, attempt 1, between drawing the PNG and the window report); the next one names where.

The window modules are built with the scene, against the core fork's Compose, which the window
module jobs now take first. The core fork is pinned at 8130daf5, which has the surfaces.
Run 39067773624: with the scene, checkNativeImageNoAwt fails on Linux (libawt.so,
libawt_xawt.so, libfontmanager.so, libjavajpeg.so and liblcms.so written beside the image) and
on macOS (Java_sun_font_, libawt, libjawt, libosxui and libfontmanager named inside it), while
none of the forbidden root types was reached. The types whose loading pulls in AWT's natives
without a window (the font manager, Color, Font, Cursor, ColorModel, Clipboard) are now
forbidden too, so the analysis names the path; and, for this PR only, the call tree is written
and uploaded.
…arm64

The forbidden AWT types were passed as one comma-joined -H:ReportAnalysisForbiddenType value
(since #52), which the option does not split, so nothing was forbidden: run 39070735944's
macOS call tree has UIManager reached from com.apple.laf class initialisers with no report.
Each type now has its own option, so the analysis stops and names the path.

Linux arm64's link failed on eglGetProcAddress, eglQueryString and eglGetCurrentDisplay once
the scene made Skia's GL backend reachable: JetBrains' Skia for that platform finds GL through
EGL.
… images

With the forbidden types working, run 39073283082 stopped on java.awt.Toolkit reached from
androidx.compose.ui.input.key.Key.toString (KeyEvent.getKeyText). The core fork guards that,
the system clipboard, the Transferable clipboard fallback and skiko's Swing dispatcher behind
ExtendedAwt.available, read from -Dcompose.awt=false, but only a class initialised while the
image is built makes the guard a constant. The plugin passed the property and left the class to
run time, so every guarded branch stayed reachable. ExtendedAwt is now initialised at build time
alongside AwtSwitch whenever it is on the classpath, as compose-rust's renderer does.
Run 39074824167 builds hello-awtfree with no AWT on all three cells; the only thing left beside
the executable was the call tree's reports folder this PR turned on to find the path. The
sample keeps NATIVE_IMAGE_CALL_TREE for the next time a path has to be found.
Run 39079050531 caught the macOS hang with the new thread dump. The process main thread is in
_pthread_join, and another thread waits in dispatch_sync for the main queue (the AppKit window's
dxc_on_main), which nothing serves, so it never returns. Every frame in the image was unnamed;
for this PR the image keeps its local symbols, so the next sample names them.

On Linux x64 and arm64 the scene came up and presented a frame with 0 at the wrong size. It
presented only one because nothing in the content changes; the self-check now asks for a frame
each turn, so 30 are drawn and presented.
…en main is off the first thread

The symbolised sample of the macOS hang (run 39082357989) shows it: the process's first thread
is in JavaMainWrapper.doRunInNewThread, joining the thread main runs on, while that thread waits
in dxc_native_frame_end for the main queue, which only the first thread serves. The sample gave
-H:-RunMainInNewThread after the plugin had locked experimental options again; it now unlocks
them first. The self-check also asks pthread_main_np before opening the window and exits with a
message instead of hanging, so whether main is on the first thread is a line in the report.
… runs main on the first thread

Run 39084384117 settled the macOS hang: with -H:-RunMainInNewThread given and unlocked, main
still reports pthread_main_np() == 0 under Liberica NIK 25.0.4.1, and the symbolised sample before
it has the first thread in JavaMainWrapper.doRunInNewThread joining main while main waits in
dispatch_sync for the main queue. NIK runs main on a new thread on macOS whatever the option
says. The AWT-free samples use nothing of NIK's, so the macOS cell takes upstream GraalVM
25.0.1, which honours the option. This is also the intermittent hang of hello-awtfree-link:
it stops at the first AppKit call that crosses threads, and that call is not on every path.
…stop keeping symbols in the image

The macOS hang is traced and fixed; the image goes back to deleting its local symbols.
@b-re-w
b-re-w merged commit 0405a75 into extended Oct 18, 2026
15 checks passed
@b-re-w
b-re-w deleted the feat/awtfree-compose-scene branch October 18, 2026 02:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant