Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
24517c5
Give ParparVM real weak and soft references
shai-almog Sep 7, 2026
8a28f0e
Make reference field access atomic, and decide aliases together
shai-almog Sep 7, 2026
b5f53d8
Release soft referents on allocation failure, and stop losing touches
shai-almog Sep 7, 2026
d012f91
Retain a referent discovery could not record, and load it once
shai-almog Sep 7, 2026
67b1bdf
Process references to a fixpoint, and register the get() barrier
shai-almog Sep 7, 2026
4cc739b
Let the emergency clear soft referents without the discovery list
shai-almog Sep 7, 2026
67f8b86
Hold the SATB registration across the referent load
shai-almog Sep 7, 2026
b343356
Finish the referent's atomic accesses, and stop clearing after a drop…
shai-almog Sep 7, 2026
aa8cdaf
Count take-side SATB losses, and stop clearing when SATB is compiled out
shai-almog Sep 7, 2026
99504d0
Stop the verifier being blind to referents, and close a heap overflow
shai-almog Sep 7, 2026
be618fc
Retain discovered referents when a dropped log abandons the clear pass
shai-almog Sep 8, 2026
8bffa85
Measure what the Reference.get() load barrier actually costs
shai-almog Sep 8, 2026
00c02d4
Close the remaining reference-clearing races, and fix the no-BiBOP build
shai-almog Sep 8, 2026
a12f741
Define the GC epoch mirror unconditionally instead of working around it
shai-almog Sep 8, 2026
c27e1e7
Serialize emergency recovery slots, and stop the epoch mirror going s…
shai-almog Sep 8, 2026
d4bbc9b
Retain rather than clear on the capped SATB termination path
shai-almog Sep 8, 2026
653713d
Recover references when the final SATB take loses its batch
shai-almog Sep 8, 2026
ffb8d6f
Re-arm the SATB barrier before tracing recovered referents
shai-almog Sep 8, 2026
ba514a7
Track emergency slots when deciding the retain fixpoint
shai-almog Sep 8, 2026
79a2b66
Consume the touch stamp when the emergency path retains an unrecorded…
shai-almog Sep 8, 2026
9dccb84
Export SoftReference to the supported API, and retain under the barri…
shai-almog Sep 8, 2026
3065b6a
Resolve unrecorded referents before reading them, and stop self-test3…
shai-almog Sep 8, 2026
a64e25a
Delete the duplicated retain walk, and make the self-test rebuild fai…
shai-almog Sep 8, 2026
a41b4a4
Let drop recovery reclaim what the emergency condemned, and delete th…
shai-almog Sep 8, 2026
4c0292a
Quiesce before reading touch stamps in drop recovery, and spare live …
shai-almog Sep 8, 2026
a11dae2
Guard the recovery's header read, and record what it clears
shai-almog Sep 8, 2026
6d3cf63
Do no reference work in a program that has no references
shai-almog Sep 8, 2026
1c8ab03
Nursery promotion, two silent gates, and a baseline the rename broke
shai-almog Sep 8, 2026
7385cc9
The emergency reclaim retained everything it condemned
shai-almog Sep 8, 2026
8d59483
Record why the porting layer still hands back a WeakReference
shai-almog Sep 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java
Original file line number Diff line number Diff line change
Expand Up @@ -5607,6 +5607,17 @@ public void run() {
///
/// a caching object or null if caching isn't supported
public Object createSoftWeakRef(Object o) {
// STILL A WeakReference, DELIBERATELY, and not an oversight now that ParparVM has
// a real SoftReference with a ranked retention policy behind it.
//
// Switching this method -- and deleting the iOS override, which pins every entry
// in a Hashtable until a memory warning replaces the whole map -- changes the
// lifetime of every decoded image, gradient and resource cache in every app on
// every platform. That wants its own change and its own bisect point, because
// each call site also needs deciding individually rather than mechanically: a
// rebuildable cache wants a SoftReference, while a lifetime tracker such as
// JavascriptContext reads a null extract as PROOF the wrapper was collected and
// would leak, or worse, under one that outlives its referent.
return new WeakReference(o);
}

Expand Down
39 changes: 39 additions & 0 deletions Ports/CLDC11/src/java/lang/ref/SoftReference.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2012, Codename One 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. Codename One 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 Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package java.lang.ref;
/// A reference the collector keeps while the referent is being used and memory allows, and
/// clears before the process runs out.
///
/// This directory is the SUPPORTED API SURFACE, not an implementation: `maven/java-runtime`
/// compiles it and `BytecodeComplianceMojo` indexes the result as the set of types an
/// application is allowed to touch. A class implemented in `vm/JavaAPI` but missing here is
/// therefore invisible to user code -- javac resolves it against the host JDK and the
/// compliance check then rejects it as forbidden API, which is a confusing way to learn a
/// feature was never exported. The bodies are deliberately inert for the same reason the
/// neighbouring stubs are.
public class SoftReference extends java.lang.ref.Reference{
/// Creates a new soft reference that refers to the given object.
public SoftReference(java.lang.Object ref){
}

}
2 changes: 1 addition & 1 deletion scripts/cast-semantics-baseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ com/codename1/impl/android/AndroidImplementation#scheduleBackgroundWork(Lcom/cod
com/codename1/impl/android/AndroidImplementation#signingCertificatesViaReflection(Landroid/content/pm/PackageManager;Ljava/lang/String;)[Landroid/content/pm/Signature;|cast to [Landroid.content.pm.Signature; inside catch(java.lang.Throwable)
com/codename1/impl/android/AndroidImplementation#signingCertificatesViaReflection(Landroid/content/pm/PackageManager;Ljava/lang/String;)[Landroid/content/pm/Signature;|cast to java.lang.Boolean inside catch(java.lang.Throwable)
com/codename1/impl/android/AndroidImplementation#vibrate(I)V|cast to android.os.Vibrator inside catch(java.lang.Throwable)
com/codename1/impl/android/AndroidImplementation$46#onReceive(Landroid/content/Context;Landroid/content/Intent;)V|cast to android.content.ComponentName inside catch(java.lang.Throwable)
com/codename1/impl/android/AndroidImplementation$47#onReceive(Landroid/content/Context;Landroid/content/Intent;)V|cast to android.content.ComponentName inside catch(java.lang.Throwable)
com/codename1/impl/android/AndroidImplementation$SetCurrentFormImpl#run()V|cast to [Ljava.lang.Class; inside catch(java.lang.Throwable)
com/codename1/impl/android/AndroidImplementation$SetCurrentFormImpl#run()V|cast to [Ljava.lang.Object; inside catch(java.lang.Throwable)
com/codename1/impl/android/AndroidImplementation$SetCurrentFormImpl#run()V|cast to android.graphics.Bitmap inside catch(java.lang.Throwable)
Expand Down
143 changes: 143 additions & 0 deletions vm/ByteCodeTranslator/src/cn1_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -1401,6 +1401,18 @@ static inline JAVA_BOOLEAN cn1InNursery(void* p) {
// heap ref store), which thread-pausing structurally cannot.
extern volatile int gcSatbActive;
extern void cn1SatbEnqueue(JAVA_OBJECT old);
// DECLARED HERE, not beside the write barrier, because that copy sits in the #else of
// the CN1_NURSERY split and these four have callers that are not conditional on it:
// nativeMethods' arraycopy/cloneArray bulk barrier and CN1_REF_LOAD_BEGIN/END. With the
// declarations behind the nursery #else, -DCN1_NURSERY compiled those calls as implicit
// C89 declarations returning int, which clang has rejected outright since C99 became the
// default -- so the nursery build did not compile at all, and nothing noticed because no
// gate builds that arm. Duplicating an extern is legal and keeps the two halves honest.
extern volatile int gcSatbTerminating;
extern JAVA_BOOLEAN cn1SatbBulkBegin(void);
extern void cn1SatbEnqueueRangeLocked(JAVA_ARRAY_OBJECT* refs, int count);
extern void cn1SatbBulkEnd(void);
extern void cn1SatbBulkQuiesce(void);
#if defined(CN1_DISABLE_SATB)
// Escape hatch to A/B the barrier cost or fall back if a regression appears. When
// disabled, gcSatbActive is never armed (see codenameOneGCMark) AND the per-store
Expand All @@ -1414,6 +1426,26 @@ extern void cn1SatbEnqueue(JAVA_OBJECT old);
} } while(0)
#endif

// ---- java.lang.ref support -------------------------------------------------
// A WeakReference's referent is NOT traced by the generated mark function. That
// function calls cn1GcDiscoverReference instead (see ByteCodeClass), handing over
// the addresses of the reference's fields, and the collector decides for itself
// whether the referent lives.
//
// Field pointers rather than the object, because this file is a fixed template
// compiled beside whatever the translator emitted: `struct obj__java_lang_ref_Reference`
// does not exist in a program that never uses a reference, so naming it here would
// break the build for those. The layout stays on the generated side.
//
// `strength` is CN1_REF_WEAK or CN1_REF_SOFT, taken from a field the subclass
// constructor sets. Deliberately not a class-pointer comparison: the dead-code pass
// is entitled to remove a class symbol this file would then fail to link against,
// and a user-written subclass of either would compare unequal to both.
#define CN1_REF_WEAK 0
#define CN1_REF_SOFT 1
// cn1TouchAge value written by get_field_java_lang_ref_Reference_objReference on
// every read. The collector turns it back into an age in cycles.
#define CN1_REF_TOUCHED (-1)
extern const char* volatile cn1LastNamSetter; // diagnosis: last bracket toucher
#ifdef CN1_CONSERVATIVE_GC_ROOTS
// The bracket's purpose was to suppress GC interaction while native C code
Expand Down Expand Up @@ -3073,6 +3105,117 @@ void codenameOneGcFree(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj);

extern int currentGcMarkValue;
extern void gcMarkObject(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_BOOLEAN force);
// Drop every soft referent at the next collection, whatever the retention policy would
// otherwise have decided. Called when an allocation has actually failed: SoftReference's
// one hard guarantee is that all of them are cleared before the VM gives up, and the
// retention ladder cannot see that coming on a platform with no per-process budget probe.
extern void cn1RefDropAllSoftReferents(void);
extern void cn1GcDiscoverReference(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT ref, JAVA_BOOLEAN force,
JAVA_OBJECT* referentField, JAVA_INT* touchAgeField,
JAVA_INT* agedCycleField, JAVA_INT strength);


// ---- the Reference.get() load barrier --------------------------------------
// Emitted into get_field_java_lang_ref_Reference_objReference, and the reason the
// clear pass is allowed to run with mutators still going.
//
// A thread whose stack was scanned and released early can pull the referent out of a
// reference and hold it in a local the collector has already walked past. That referent
// is then neither marked nor fresh, which is the one case the sweep's "already marked or
// FRESH" invariant does not cover, so without this it is freed under a live pointer.
// Enqueuing puts it in the snapshot: the trial clear of gcSatbActive finds a non-empty
// log, re-arms, marks it, and the reference is left alone.
//
// THE FILTER IS NOT AN OPTIMIZATION, it is what makes this affordable. cn1SatbEnqueue
// takes a mutex per accepted reference, and get() on a hot cache is called far more often
// than anything the per-store barrier sees -- measured on RefPolicy before this filter
// existed, a 400,000-access run put over 10,000 entries into the log per cycle and drove
// the SATB termination loop into its CN1_SATB_MAX_REOPENS cap on every single cycle,
// which is the collector failing to converge rather than a cost.
//
// It skips exactly the referents the clear pass would refuse to clear anyway: already
// marked this epoch, or fresh and therefore kept by the sweep's grace rule. Deliberately
// STRICTER than the clear pass's own test, which also spares mark == epoch - 1 (last
// cycle's slack): bibopGcEpoch is only exactly equal to currentGcMarkValue once
// cn1BibopBeginGcCycle has published it, and a barrier must not depend on a mirror being
// current. Skipping less is always safe; skipping more is not.
//
// A retained soft reference costs nothing here at all, because its referent is marked as
// an ordinary strong edge by cn1GcDiscoverReference before any get() can reach it.
// REGISTERS AROUND THE LOAD, via the same handshake the bulk copies use, and the
// registration is what the caller must hold ACROSS its load -- hence the awkward shape:
// the accessor calls cn1RefLoadBegin(), loads, calls this, then cn1RefLoadEnd().
//
// Checking gcSatbActive and then enqueuing is not enough on this path, and the reason it
// is enough for the per-store barrier does not carry over. cn1SatbEnqueue takes a mutex,
// so a thread can pass a flag check and then be delayed long enough for the collector to
// clear the field, finish its empty final take, lower gcSatbTerminating and quiesce -- and
// the entry then lands in a log nothing will ever drain, or is skipped entirely, while the
// sweep frees the referent the caller is about to return. The per-store barrier tolerates
// that window because a reference STORED after the fixpoint is already marked or FRESH and
// the sweep keeps both; a weak REFERENT handed out by get() is neither.
//
// AN OUTER "FAST PATH" FLAG CHECK BREAKS THIS, and did: gating entry to
// cn1SatbBulkBegin() on a prior read of the same flags reintroduces the race one level
// out, because the thread can be descheduled between that read and the registration. The
// whole value of cn1SatbBulkBegin is that it registers FIRST and reports afterwards, so
// nothing may be sampled before it.
//
// With the registration held across the load, a false answer is safe rather than merely
// unlikely: the collector cannot be mid-termination (its quiesce waits for this
// registration), so either no mark is running -- and one starting later scans this thread
// with the value already in a register -- or reference processing is complete, in which
// case a field still holding a pointer was not condemned and its referent is marked.
// The deletion barrier for the referent field, with an ATOMIC load.
//
// CN1_SATB_DELETE next door reads through a plain JAVA_OBJECT volatile*, which is right
// for every ordinary field because nothing else writes them concurrently. The referent is
// the exception: cn1GcProcessReferences stores JAVA_NULL into it atomically from the
// collector while Reference.clear() runs here, so the plain read would leave that pair a
// mixed atomic/non-atomic access -- undefined in C, and the same defect that was fixed for
// the getter and for this setter's own store. Making the store atomic and leaving the
// barrier's read plain fixes half a race.
#if defined(CN1_DISABLE_SATB)
#define CN1_SATB_DELETE_REF(fieldAddr) do { } while(0)
#else
#define CN1_SATB_DELETE_REF(fieldAddr) \
do { if(__builtin_expect(gcSatbActive, 0)) { \
JAVA_OBJECT cn1__old = __atomic_load_n((JAVA_OBJECT*)(fieldAddr), __ATOMIC_RELAXED); \
if(cn1__old != JAVA_NULL && !CN1_IS_TAGGED(cn1__old)) cn1SatbEnqueue(cn1__old); \
} } while(0)
#endif

#if defined(CN1_DISABLE_SATB)
#define CN1_REF_LOAD_BEGIN() JAVA_FALSE
#define CN1_REF_LOAD_END() do { } while(0)
#define CN1_SATB_REF_KEEP(active, refVal) do { (void)(active); (void)(refVal); } while(0)
Comment thread
shai-almog marked this conversation as resolved.
#else
// -DCN1_REF_NO_LOAD_BARRIER compiles the registration and the enqueue out, leaving the
// touch stamp and the load. It is UNSOUND -- it is the arm that measures what the barrier
// costs, not a configuration to ship -- and exists because "is get() too expensive?" has to
// be answered with a number rather than an intuition.
#if defined(CN1_REF_NO_LOAD_BARRIER)
#define CN1_REF_LOAD_BEGIN() JAVA_FALSE
#define CN1_REF_LOAD_END() do { } while(0)
#else
#define CN1_REF_LOAD_BEGIN() cn1SatbBulkBegin()
#define CN1_REF_LOAD_END() cn1SatbBulkEnd()
#endif
#ifdef CN1_GC_CONFORM
extern _Atomic long cn1RefGets;
#define CN1_REF_COUNT_GET() atomic_fetch_add_explicit(&cn1RefGets, 1, memory_order_relaxed)
#else
#define CN1_REF_COUNT_GET() do { } while(0)
#endif
#define CN1_SATB_REF_KEEP(active, refVal) \
do { CN1_REF_COUNT_GET(); JAVA_OBJECT cn1__r = (refVal); \
if((active) && cn1__r != JAVA_NULL && !CN1_IS_TAGGED(cn1__r)) { \
int cn1__m = __atomic_load_n(&cn1__r->__codenameOneGcMark, __ATOMIC_RELAXED); \
int cn1__e = atomic_load_explicit(&bibopGcEpoch, memory_order_relaxed); \
if(cn1__m != -1 && cn1__m != cn1__e) cn1SatbEnqueue(cn1__r); \
} } while(0)
#endif

extern void gcMarkArrayObject(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT obj, JAVA_BOOLEAN force);
extern JAVA_BOOLEAN removeObjectFromHeapCollection(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT o);

Expand Down
Loading
Loading