Skip to content

Commit 9feb396

Browse files
Copilotfduminy
andcommitted
Revert build.xml changes and add new cycle-breaking strategy document
- Revert all/build.xml to original state (before classlib.jar changes) - Add CYCLE_BREAKING_STRATEGY.md with detailed plan for breaking cycles while respecting constraints: 1. Doesn't break compilation 2. Preserves security manager 3. Keeps getRefSize() API 4. Actually breaks circular dependencies Proposed approach uses dependency inversion via system properties or build-time constants to eliminate org.vmmagic → org.jnode.vm.core cycle without removing APIs or breaking existing code. Co-authored-by: fduminy <143904+fduminy@users.noreply.github.com>
1 parent 682ce3f commit 9feb396

2 files changed

Lines changed: 269 additions & 22 deletions

File tree

CYCLE_BREAKING_STRATEGY.md

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
# New Cycle-Breaking Strategy
2+
3+
**Date:** 2025-11-12
4+
**Status:** PROPOSED - Not Yet Implemented
5+
6+
## Problem Statement
7+
8+
The original PR attempted to break circular dependencies but all changes were reverted due to:
9+
1. **Compilation errors** - Type changes broke existing code
10+
2. **Security requirements** - JNode needs AccessController.doPrivileged calls
11+
3. **API preservation** - getRefSize() method must remain
12+
13+
**Current State:** No circular dependencies are broken. Build.xml changes also reverted.
14+
15+
## Constraints
16+
17+
Based on feedback, any solution must:
18+
1.**Not break compilation** - All existing code must continue to compile
19+
2.**Preserve security manager** - Keep AccessController.doPrivileged calls
20+
3.**Keep getRefSize()** - API compatibility required
21+
4.**Break cycles** - Actually eliminate circular dependencies
22+
23+
## Proposed Approach: Dependency Inversion
24+
25+
Instead of removing dependencies, we can break cycles using **dependency inversion** and **interface extraction**.
26+
27+
### Strategy 1: Extract VmArchitecture Interface
28+
29+
**Target:** Break `org.vmmagic → org.jnode.vm.core` cycle in MagicUtils
30+
31+
**Current Problem:**
32+
```java
33+
// MagicUtils.java depends on VmUtils
34+
private static final int getRefSize() {
35+
if (refSize == 0) {
36+
refSize = VmUtils.getVm().getArch().getReferenceSize();
37+
}
38+
return refSize;
39+
}
40+
```
41+
42+
**Solution:**
43+
1. Create a new minimal interface `IArchInfo` in `org.vmmagic` package:
44+
```java
45+
package org.vmmagic.unboxed;
46+
47+
public interface IArchInfo {
48+
int getReferenceSize();
49+
}
50+
```
51+
52+
2. Add a static setter in MagicUtils:
53+
```java
54+
private static IArchInfo archInfo;
55+
56+
public static void setArchInfo(IArchInfo info) {
57+
archInfo = info;
58+
}
59+
60+
private static final int getRefSize() {
61+
if (refSize == 0) {
62+
refSize = archInfo.getReferenceSize();
63+
}
64+
return refSize;
65+
}
66+
```
67+
68+
3. In VmArchitecture (or boot sequence), initialize it:
69+
```java
70+
// During VM initialization
71+
MagicUtils.setArchInfo(new IArchInfo() {
72+
public int getReferenceSize() {
73+
return VmUtils.getVm().getArch().getReferenceSize();
74+
}
75+
});
76+
```
77+
78+
**Benefits:**
79+
- ✅ No compilation errors (getRefSize() still exists)
80+
- ✅ MagicUtils no longer imports from org.jnode.vm
81+
- ✅ Breaks the cycle: org.vmmagic → org.jnode.vm.core
82+
- ✅ Minimal code changes
83+
- ✅ Runtime behavior unchanged
84+
85+
**Risks:**
86+
- Low - Interface is simple and contained
87+
- Need to ensure setArchInfo() is called early in boot sequence
88+
89+
### Strategy 2: Use System Properties for Reference Size
90+
91+
**Alternative approach** - even simpler:
92+
93+
1. Set a system property during VM initialization:
94+
```java
95+
// In VM bootstrap
96+
System.setProperty("jnode.arch.refsize",
97+
String.valueOf(VmUtils.getVm().getArch().getReferenceSize()));
98+
```
99+
100+
2. Read it in MagicUtils:
101+
```java
102+
private static final int getRefSize() {
103+
if (refSize == 0) {
104+
String prop = System.getProperty("jnode.arch.refsize");
105+
refSize = (prop != null) ? Integer.parseInt(prop) : 8; // default 64-bit
106+
}
107+
return refSize;
108+
}
109+
```
110+
111+
**Benefits:**
112+
- ✅ Even simpler - no new interfaces
113+
- ✅ No compilation errors
114+
- ✅ Breaks the cycle completely
115+
- ✅ Easy to understand and maintain
116+
117+
**Risks:**
118+
- Very low - System properties available very early in boot
119+
120+
### Strategy 3: Static Initialization in Build
121+
122+
**Most robust approach** - determine at build time:
123+
124+
1. Modify the build process to detect target architecture
125+
2. Generate a simple constants class:
126+
```java
127+
// Generated during build
128+
package org.vmmagic.unboxed;
129+
130+
public final class ArchConstants {
131+
public static final int REFERENCE_SIZE = 8; // or 4 for 32-bit
132+
}
133+
```
134+
135+
3. Use it in MagicUtils:
136+
```java
137+
private static final int getRefSize() {
138+
if (refSize == 0) {
139+
refSize = ArchConstants.REFERENCE_SIZE;
140+
}
141+
return refSize;
142+
}
143+
```
144+
145+
**Benefits:**
146+
- ✅ Zero runtime overhead
147+
- ✅ No dynamic dependencies at all
148+
- ✅ Completely breaks the cycle
149+
- ✅ Type-safe (compile-time constant)
150+
151+
**Risks:**
152+
- Medium - Requires build system modification
153+
- Need to handle multi-architecture builds
154+
155+
## Recommended Implementation Order
156+
157+
### Phase 1: Quick Win (Strategy 2 - System Properties)
158+
**Effort:** 1-2 hours
159+
**Risk:** Very Low
160+
**Impact:** Breaks 1 cycle (org.vmmagic → org.jnode.vm.core)
161+
162+
**Steps:**
163+
1. Modify MagicUtils.java to read from system property
164+
2. Find VM initialization code and add property setting
165+
3. Test build and verify cycle is broken with analyze_source_cycles.py
166+
4. Update documentation
167+
168+
### Phase 2: Long-term (Strategy 3 - Build-time Constants)
169+
**Effort:** 4-8 hours
170+
**Risk:** Medium
171+
**Impact:** Cleaner, more maintainable solution
172+
173+
**Steps:**
174+
1. Enhance build.xml to detect architecture
175+
2. Generate ArchConstants.java during build
176+
3. Update MagicUtils.java to use constants
177+
4. Test on both 32-bit and 64-bit builds
178+
5. Document the approach
179+
180+
### Phase 3: Other Cycles
181+
After proving the approach works, apply similar patterns to other dependencies.
182+
183+
## Other Potential Cycles to Address
184+
185+
### org.jnode.plugin → rt.vm
186+
**Current blocker:** Need to preserve AccessController.doPrivileged
187+
188+
**Possible approach:**
189+
- Keep the security calls but use standard JDK classes
190+
- Or accept this dependency as it's using standard security APIs
191+
192+
**Decision:** May not be breakable without security compromise - **SKIP for now**
193+
194+
### Analysis Required
195+
Need to run analyze_source_cycles.py after changes to identify:
196+
1. Which cycles were actually broken
197+
2. What new cycles (if any) were introduced
198+
3. Net reduction in circular dependencies
199+
200+
## Success Criteria
201+
202+
A successful implementation will:
203+
1. ✅ Break at least 1 circular dependency
204+
2. ✅ Pass all existing tests
205+
3. ✅ Build successfully with `./build.sh clean cd-x86-lite`
206+
4. ✅ Show reduction in analyze_source_cycles.py output
207+
5. ✅ Not introduce compilation errors
208+
6. ✅ Preserve all public APIs (including getRefSize())
209+
7. ✅ Maintain security manager functionality
210+
211+
## Testing Plan
212+
213+
1. **Build Test:** `./build.sh clean cd-x86-lite` must succeed
214+
2. **Cycle Analysis:** Run analyze_source_cycles.py before and after
215+
3. **Unit Tests:** All existing tests must pass
216+
4. **Integration Test:** Boot the ISO and verify basic functionality
217+
5. **Security Test:** Verify security manager still works
218+
219+
## Next Steps
220+
221+
1. Get approval for Strategy 2 (System Properties) approach
222+
2. Implement the changes
223+
3. Run full test suite
224+
4. Analyze results with analyze_source_cycles.py
225+
5. Document actual vs expected results
226+
6. If successful, consider Phase 2 (build-time constants)
227+
228+
## Questions for Review
229+
230+
1. Is Strategy 2 (System Properties) acceptable?
231+
2. When in the boot sequence should we set the property?
232+
3. Should we add a fallback default if property not set?
233+
4. Are there other cycles we should prioritize?
234+
235+
---
236+
237+
**Implementation Note:** This strategy respects all constraints while still achieving the goal of breaking circular dependencies. The key insight is to invert the dependency rather than remove it entirely.

all/build.xml

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@
271271
<if>
272272
<or>
273273
<not>
274-
<uptodate srcfile="${root.dir}/all/lib/ftp.properties" targetfile="${classlib.jar}"/>
274+
<uptodate srcfile="${root.dir}/all/lib/ftp.properties" targetfile="${root.dir}/all/lib/classlib.pack.gz"/>
275275
</not>
276276
<not>
277277
<uptodate srcfile="${root.dir}/all/lib/ftp.properties" targetfile="${root.dir}/all/lib/classlib-src.jar.bz2"/>
@@ -299,27 +299,24 @@
299299
</then>
300300
<else>
301301
<!-- classlib update from the FTP server -->
302-
<delete file="${classlib.jar}"/>
302+
<delete file="${root.dir}/all/lib/classlib.pack.gz"/>
303303
<delete file="${root.dir}/all/lib/classlib-src.jar.bz2"/>
304-
<get src="${classlib.url}/classlib.jar" dest="${classlib.jar}"/>
305-
<get src="${classlib.url}/classlib-src.jar.bz2" dest="${root.dir}/all/lib/classlib-src.jar.bz2" ignoreerrors="true"/>
304+
<get src="${classlib.url}/classlib.pack.gz" dest="${root.dir}/all/lib/classlib.pack.gz"/>
305+
<get src="${classlib.url}/classlib-src.jar.bz2" dest="${root.dir}/all/lib/classlib-src.jar.bz2"/>
306306
<!-- verify checksum for downloaded files -->
307307
<if>
308308
<not>
309-
<checksum algorithm="MD5" file="${classlib.jar}" property="${classlib.md5}"/>
309+
<checksum algorithm="MD5" file="${root.dir}/all/lib/classlib.pack.gz" property="${classlib.md5}"/>
310310
</not>
311311
<then>
312-
<delete file="${classlib.jar}"/>
313-
<fail message="checksum failed for classlib.jar"/>
312+
<delete file="${root.dir}/all/lib/classlib.pack.gz"/>
313+
<fail message="checksum failed for classlib.pack.gz"/>
314314
</then>
315315
</if>
316316
<if>
317-
<and>
318-
<available file="${root.dir}/all/lib/classlib-src.jar.bz2"/>
319-
<not>
320-
<checksum algorithm="MD5" file="${root.dir}/all/lib/classlib-src.jar.bz2" property="${classlib-src.md5}"/>
321-
</not>
322-
</and>
317+
<not>
318+
<checksum algorithm="MD5" file="${root.dir}/all/lib/classlib-src.jar.bz2" property="${classlib-src.md5}"/>
319+
</not>
323320
<then>
324321
<delete file="${root.dir}/all/lib/classlib-src.jar.bz2"/>
325322
<fail message="checksum failed for classlib-src.jar.bz2"/>
@@ -330,15 +327,28 @@
330327
</then>
331328
</if>
332329
<if>
333-
<and>
334-
<available file="${root.dir}/all/lib/classlib-src.jar.bz2"/>
335-
<or>
336-
<not>
337-
<available file="${classlib-src.jar}" />
338-
</not>
339-
<uptodate srcfile="${classlib-src.jar}" targetfile="${root.dir}/all/lib/classlib-src.jar.bz2"/>
340-
</or>
341-
</and>
330+
<or>
331+
<not>
332+
<available file="${classlib.jar}" />
333+
</not>
334+
<uptodate srcfile="${classlib.jar}" targetfile="${root.dir}/all/lib/classlib.pack.gz"/>
335+
</or>
336+
<then>
337+
<delete file="${classlib.jar}"/>
338+
<echo message="Creating ${classlib.jar}"/>
339+
<exec executable="${java.home}/bin/unpack200">
340+
<arg file="${root.dir}/all/lib/classlib.pack.gz"/>
341+
<arg file="${classlib.jar}"/>
342+
</exec>
343+
</then>
344+
</if>
345+
<if>
346+
<or>
347+
<not>
348+
<available file="${classlib-src.jar}" />
349+
</not>
350+
<uptodate srcfile="${classlib-src.jar}" targetfile="${root.dir}/all/lib/classlib-src.jar.bz2"/>
351+
</or>
342352
<then>
343353
<delete file="${classlib-src.jar}"/>
344354
<echo message="Creating ${classlib-src.jar}"/>

0 commit comments

Comments
 (0)