Skip to content

Commit 5581fb4

Browse files
fglockcodex
andcommitted
fix: preserve CPAN runtime compatibility across threads
Implement B::SV address recovery for observed references, carry those addresses through ithread snapshots, account for parser-installed named subroutine stash ownership, and expose POSIX floating-point epsilon constants. Generated with Codex (https://openai.com/codex) Co-Authored-By: Codex <codex@openai.com>
1 parent 13c8504 commit 5581fb4

16 files changed

Lines changed: 186 additions & 8 deletions

src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1854,6 +1854,19 @@ private static List<Object> closureCapturesForMaterialization(
18541854

18551855
private static void installClosureCaptureMetadata(
18561856
RuntimeCode code, List<String> capturedNames, List<Object> capturedValues) {
1857+
// Named subs created through the parser populate an existing stash
1858+
// scalar in place rather than going through RuntimeGlob's CODE-slot
1859+
// assignment path. Account for that persistent stash owner so a
1860+
// temporary copy of \&name cannot release the named CV's pad.
1861+
if (code != null && code.stashRefCount == 0) {
1862+
for (RuntimeScalar installed : GlobalVariable.globalCodeRefs.values()) {
1863+
if (installed != null && installed.value == code) {
1864+
code.hadStashRef = true;
1865+
code.stashRefCount = 1;
1866+
break;
1867+
}
1868+
}
1869+
}
18571870
if (code == null || capturedValues == null || capturedValues.isEmpty()
18581871
|| code.capturedScalars != null) {
18591872
return;

src/main/java/org/perlonjava/runtime/perlmodule/Internals.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public static void initialize() {
3737
// against native Perl. See dev/design/refcount_alignment_plan.md.
3838
internals.registerMethod("jperl_refstate", "jperl_refstate", "$");
3939
internals.registerMethod("jperl_refstate_str", "jperl_refstate_str", "$");
40+
internals.registerMethod("jperl_reference_by_address", "jperlReferenceByAddress", "$");
4041
// Phase 4 (refcount_alignment_plan.md): On-demand reachability
4142
// sweep. Walks Perl-visible roots (globals, stashes, rescued
4243
// objects) and clears weak refs for unreachable objects. Returns
@@ -364,6 +365,13 @@ public static RuntimeList svRefcount(RuntimeArray args, int ctx) {
364365
return new RuntimeScalar(1).getList();
365366
}
366367

368+
/** Return a reference whose displayed address was observed in this runtime. */
369+
public static RuntimeList jperlReferenceByAddress(RuntimeArray args, int ctx) {
370+
long address = args.get(0).getLong();
371+
RuntimeBase value = PerlRuntime.current().resolveReferenceAddress(address);
372+
return value == null ? RuntimeScalarCache.scalarUndef.getList() : value.createReference().getList();
373+
}
374+
367375
/**
368376
* Phase 0 diagnostic: return a hashref describing the full internal
369377
* refcount state of the referent. Intended for differential testing

src/main/java/org/perlonjava/runtime/perlmodule/ScalarUtil.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,9 @@ public static RuntimeList refaddr(RuntimeArray args, int ctx) {
120120
case FORMAT:
121121
case REGEX:
122122
// Return identity of the underlying value object
123-
return new RuntimeScalar(System.identityHashCode(scalar.value)).getList();
123+
RuntimeBase referenced = (RuntimeBase) scalar.value;
124+
PerlRuntime.current().registerReferenceAddress(referenced);
125+
return new RuntimeScalar(PerlRuntime.referenceAddress(referenced)).getList();
124126
case GLOB:
125127
case GLOBREFERENCE:
126128
if (scalar.value instanceof RuntimeGlob glob) {

src/main/java/org/perlonjava/runtime/runtimetypes/PerlRuntime.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import java.util.Set;
2727
import java.util.ArrayDeque;
2828
import java.util.Deque;
29+
import java.lang.ref.WeakReference;
2930
import java.util.concurrent.Callable;
31+
import java.util.concurrent.ConcurrentHashMap;
3032
import java.util.concurrent.locks.ReentrantLock;
3133
import java.util.TreeSet;
3234

@@ -79,6 +81,7 @@ public final class PerlRuntime implements AutoCloseable {
7981
public RuntimeArray libOriginalInc;
8082
public boolean storableLastOpInNetorder;
8183
public final Set<String> xsShimLoadingInProgress = new HashSet<>();
84+
private final Map<Long, WeakReference<RuntimeBase>> referenceAddresses = new ConcurrentHashMap<>();
8285

8386
RuntimeIO ioStdout;
8487
RuntimeIO ioStderr;
@@ -148,6 +151,39 @@ public static PerlRuntime currentOrNull() {
148151
return frame != null ? frame.runtime : null;
149152
}
150153

154+
/** Record the address exposed by Perl reference stringification for B introspection. */
155+
public void registerReferenceAddress(RuntimeBase value) {
156+
registerReferenceAddress(referenceAddress(value), value);
157+
}
158+
159+
public static long referenceAddress(RuntimeBase value) {
160+
return Integer.toUnsignedLong(System.identityHashCode(value));
161+
}
162+
163+
/** Record an inherited address token for the corresponding cloned referent. */
164+
public void registerReferenceAddress(long address, RuntimeBase value) {
165+
referenceAddresses.put(address, new WeakReference<>(value));
166+
}
167+
168+
/** Resolve an address previously exposed in this interpreter instance. */
169+
public RuntimeBase resolveReferenceAddress(long address) {
170+
WeakReference<RuntimeBase> reference = referenceAddresses.get(address);
171+
RuntimeBase value = reference == null ? null : reference.get();
172+
if (reference != null && value == null) {
173+
referenceAddresses.remove(address, reference);
174+
}
175+
return value;
176+
}
177+
178+
Map<Long, RuntimeBase> snapshotReferenceAddresses() {
179+
Map<Long, RuntimeBase> snapshot = new HashMap<>();
180+
referenceAddresses.forEach((address, reference) -> {
181+
RuntimeBase value = reference.get();
182+
if (value != null) snapshot.put(address, value);
183+
});
184+
return snapshot;
185+
}
186+
151187
/** Bind this runtime until the returned scope is closed. */
152188
public Binding bind() {
153189
if (closed) {
@@ -315,6 +351,7 @@ private ThreadSnapshot snapshotCloneInternal(PerlThreadRegistry registry, long t
315351
runtimeCodeState.snapshotCompiledMetadataInto(child.runtimeCodeState);
316352
regexState.snapshotInto(child.regexState);
317353
}
354+
cloner.finishSnapshot();
318355
child.currentDirectory = currentDirectory;
319356
child.initialized = true;
320357
try (Binding ignored = child.bind()) {
@@ -390,6 +427,7 @@ public void close() {
390427
flipFlopState.clear();
391428
scalarGlobState.clear();
392429
pointerPackState.clear();
430+
referenceAddresses.clear();
393431
}
394432
closed = true;
395433
} finally {

src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeArray.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1574,7 +1574,8 @@ public Iterator<RuntimeScalar> iterator() {
15741574
* @return A string representing the array reference.
15751575
*/
15761576
public String toStringRef() {
1577-
String ref = "ARRAY(0x" + Integer.toHexString(this.hashCode()) + ")";
1577+
registerReferenceAddress();
1578+
String ref = "ARRAY(0x" + referenceAddressHex() + ")";
15781579
return (blessId == 0
15791580
? ref
15801581
: NameNormalizer.getBlessStr(blessId) + "=" + ref);

src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeBase.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,9 +442,21 @@ public RuntimeScalar getFirst() {
442442
}
443443

444444
public String toStringRef() {
445+
registerReferenceAddress();
445446
return this.toString();
446447
}
447448

449+
protected final String referenceAddressHex() {
450+
return Long.toHexString(PerlRuntime.referenceAddress(this));
451+
}
452+
453+
protected final void registerReferenceAddress() {
454+
PerlRuntime runtime = PerlRuntime.currentOrNull();
455+
if (runtime != null) {
456+
runtime.registerReferenceAddress(this);
457+
}
458+
}
459+
448460
public double getDoubleRef() {
449461
return this.hashCode();
450462
}

src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5716,7 +5716,8 @@ private static RuntimeList forkOpenResult(ForkOpenCompleteException exception, i
57165716
* @return a string representing the CODE reference
57175717
*/
57185718
public String toStringRef() {
5719-
String ref = "CODE(0x" + Integer.toHexString(this.hashCode()) + ")";
5719+
registerReferenceAddress();
5720+
String ref = "CODE(0x" + referenceAddressHex() + ")";
57205721
return (blessId == 0
57215722
? ref
57225723
: NameNormalizer.getBlessStr(blessId) + "=" + ref);

src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGlob.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1232,7 +1232,8 @@ public String toString() {
12321232
* @return A string representation of the typeglob reference.
12331233
*/
12341234
public String toStringRef() {
1235-
String ref = "GLOB(0x" + Integer.toHexString(this.hashCode()) + ")";
1235+
registerReferenceAddress();
1236+
String ref = "GLOB(0x" + referenceAddressHex() + ")";
12361237
return (blessId == 0
12371238
? ref
12381239
: NameNormalizer.getBlessStr(blessId) + "=" + ref);

src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGraphCloner.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public <T extends RuntimeBase> T cloneGraph(T value) {
5656
try {
5757
return (T) cloneValue(value);
5858
} finally {
59-
if (--publicDepth == 0) finishWeakReferences();
59+
if (--publicDepth == 0) finishCloneBoundary();
6060
}
6161
}
6262

@@ -68,10 +68,37 @@ public List<RuntimeBase> cloneRoots(List<? extends RuntimeBase> roots) {
6868
for (RuntimeBase root : roots) result.add(cloneValue(root));
6969
return result;
7070
} finally {
71-
if (--publicDepth == 0) finishWeakReferences();
71+
if (--publicDepth == 0) finishCloneBoundary();
7272
}
7373
}
7474

75+
private void finishCloneBoundary() {
76+
Map<Long, RuntimeBase> observed = sourceRuntime.snapshotReferenceAddresses();
77+
// A stringified object can remain visible only through a weak Perl
78+
// edge. ithreads still clone that live SV before invoking CLONE, so
79+
// ensure it participates in this graph even when no strong root led
80+
// to it during the ordinary traversal.
81+
for (RuntimeBase source : observed.values()) {
82+
if (!source.threadShared && !clones.containsKey(source)) {
83+
cloneValue(source);
84+
}
85+
}
86+
finishWeakReferences();
87+
for (Map.Entry<Long, RuntimeBase> entry : observed.entrySet()) {
88+
RuntimeBase source = entry.getValue();
89+
RuntimeBase target = source.threadShared
90+
? source : (RuntimeBase) clones.get(source);
91+
if (target != null) {
92+
targetRuntime.registerReferenceAddress(entry.getKey(), target);
93+
}
94+
}
95+
}
96+
97+
/** Complete a runtime snapshot before any child CLONE hooks execute. */
98+
void finishSnapshot() {
99+
finishCloneBoundary();
100+
}
101+
75102
/** Package/runtime snapshot entry point that retains the shared graph map. */
76103
RuntimeBase cloneValue(RuntimeBase value) {
77104
if (value == null) return null;

src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,7 @@ public String dump() {
13061306
* @return A string in the format "HASH(hashCode)".
13071307
*/
13081308
public String toStringRef() {
1309+
registerReferenceAddress();
13091310
// Check if this is a Perl 5.38+ class instance
13101311
String refType = "HASH";
13111312
if (blessId != 0) {
@@ -1315,7 +1316,7 @@ public String toStringRef() {
13151316
}
13161317
}
13171318

1318-
String ref = refType + "(0x" + Integer.toHexString(this.hashCode()) + ")";
1319+
String ref = refType + "(0x" + referenceAddressHex() + ")";
13191320
return (blessId == 0
13201321
? ref
13211322
: NameNormalizer.getBlessStr(blessId) + "=" + ref);

0 commit comments

Comments
 (0)