Skip to content

Commit a77b823

Browse files
Reduce memory overhead for debug info generation.
Seal debug info entries after debug info generation. Restructure DwarfDebugInfo. Avoid unnecessary Cursors. Remove ImageSingleton for SourceManager Use synchronized lists instead of ConcurrentSets if possible. Use synchronized EconomicMaps instead of ConcurrentHashMaps if possible.
1 parent 80d7490 commit a77b823

26 files changed

+965
-800
lines changed

substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java

Lines changed: 101 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@
2626

2727
package com.oracle.objectfile.debugentry;
2828

29+
import java.util.ArrayList;
2930
import java.util.Comparator;
30-
import java.util.HashMap;
3131
import java.util.List;
3232
import java.util.Map;
33-
import java.util.concurrent.ConcurrentSkipListSet;
33+
import java.util.stream.Collectors;
34+
import java.util.stream.IntStream;
3435

3536
import com.oracle.objectfile.debugentry.range.Range;
3637

@@ -53,26 +54,32 @@ public sealed class ClassEntry extends StructureTypeEntry permits EnumClassEntry
5354
/**
5455
* Details of methods located in this instance.
5556
*/
56-
private final ConcurrentSkipListSet<MethodEntry> methods;
57+
private List<MethodEntry> methods;
5758
/**
5859
* A list recording details of all normal compiled methods included in this class sorted by
5960
* ascending address range. Note that the associated address ranges are disjoint and contiguous.
6061
*/
61-
private final ConcurrentSkipListSet<CompiledMethodEntry> compiledMethods;
62+
private List<CompiledMethodEntry> compiledMethods;
6263

6364
/**
64-
* A list of all files referenced from info associated with this class, including info detailing
65-
* inline method ranges.
65+
* A map of all files referenced from info associated with this class, including info detailing
66+
* inline method ranges. Each unique file is mapped to its 1-based index in this class.
6667
*/
67-
private final ConcurrentSkipListSet<FileEntry> files;
68-
private final Map<FileEntry, Integer> indexedFiles = new HashMap<>();
68+
private Map<FileEntry, Integer> indexedFiles = null;
69+
/**
70+
* The list of all files referenced from this class.
71+
*/
72+
private List<FileEntry> files;
6973

7074
/**
71-
* A list of all directories referenced from info associated with this class, including info
72-
* detailing inline method ranges.
75+
* A map of all directories referenced from info associated with this class, including info
76+
* detailing inline method ranges. Each unique dir is mapped to its 1-based index in this class.
77+
*/
78+
private Map<DirEntry, Integer> indexedDirs = null;
79+
/**
80+
* The list of all dirs referenced from this class.
7381
*/
74-
private final ConcurrentSkipListSet<DirEntry> dirs;
75-
private final Map<DirEntry, Integer> indexedDirs = new HashMap<>();
82+
private List<DirEntry> dirs;
7683

7784
public ClassEntry(String typeName, int size, long classOffset, long typeSignature,
7885
long compressedTypeSignature, long layoutTypeSignature,
@@ -81,20 +88,60 @@ public ClassEntry(String typeName, int size, long classOffset, long typeSignatur
8188
this.superClass = superClass;
8289
this.fileEntry = fileEntry;
8390
this.loader = loader;
84-
this.methods = new ConcurrentSkipListSet<>(Comparator.comparingInt(MethodEntry::getModifiers).thenComparingInt(MethodEntry::getLine).thenComparing(MethodEntry::getSymbolName));
85-
this.compiledMethods = new ConcurrentSkipListSet<>(Comparator.comparing(CompiledMethodEntry::primary));
86-
this.files = new ConcurrentSkipListSet<>(Comparator.comparing(FileEntry::fileName).thenComparing(file -> file.dirEntry().path()));
87-
this.dirs = new ConcurrentSkipListSet<>(Comparator.comparing(DirEntry::path));
91+
this.methods = new ArrayList<>();
92+
this.compiledMethods = new ArrayList<>();
93+
this.files = new ArrayList<>();
94+
this.dirs = new ArrayList<>();
8895

8996
addFile(fileEntry);
9097
}
9198

99+
@Override
100+
public void seal() {
101+
super.seal();
102+
assert methods instanceof ArrayList<MethodEntry> && compiledMethods instanceof ArrayList<CompiledMethodEntry> &&
103+
files instanceof ArrayList<FileEntry> && indexedFiles == null && dirs instanceof ArrayList<DirEntry> &&
104+
indexedDirs == null : "ClassEntry should only be sealed once";
105+
methods = List.copyOf(methods);
106+
methods.forEach(MethodEntry::seal);
107+
108+
compiledMethods = compiledMethods.stream().sorted(Comparator.comparing(CompiledMethodEntry::primary)).toList();
109+
compiledMethods.forEach(CompiledMethodEntry::seal);
110+
compiledMethods.stream()
111+
.flatMap(cm -> cm.topDownRangeStream(true).map(Range::getFileEntry))
112+
.distinct()
113+
.forEach(this::addFile);
114+
115+
files = files.stream().distinct().toList();
116+
indexedFiles = IntStream.range(0, files.size())
117+
.boxed()
118+
.collect(Collectors.toUnmodifiableMap(i -> files.get(i), i -> i + 1));
119+
120+
dirs = dirs.stream().distinct().toList();
121+
indexedDirs = IntStream.range(0, dirs.size())
122+
.boxed()
123+
.collect(Collectors.toUnmodifiableMap(i -> dirs.get(i), i -> i + 1));
124+
}
125+
126+
/**
127+
* Adds and indexes a file entry and the corresponding dir entry for this class entry.
128+
* <p>
129+
* This is only called during debug info generation. No more files are added to this
130+
* {@code ClassEntry} when writing debug info to the object file.
131+
*
132+
* @param addFileEntry the file entry to add
133+
*/
92134
private void addFile(FileEntry addFileEntry) {
135+
assert files instanceof ArrayList<FileEntry> && dirs instanceof ArrayList<DirEntry> : "Can only add files and dirs before a ClassEntry is sealed.";
93136
if (addFileEntry != null && !addFileEntry.fileName().isEmpty()) {
94-
files.add(addFileEntry);
137+
synchronized (files) {
138+
files.add(addFileEntry);
139+
}
95140
DirEntry addDirEntry = addFileEntry.dirEntry();
96141
if (addDirEntry != null && !addDirEntry.getPathString().isEmpty()) {
97-
dirs.add(addDirEntry);
142+
synchronized (dirs) {
143+
dirs.add(addDirEntry);
144+
}
98145
}
99146
}
100147
}
@@ -112,26 +159,34 @@ public void addField(FieldEntry field) {
112159

113160
/**
114161
* Add a method to the class entry and store its file entry.
162+
* <p>
163+
* This is only called during debug info generation. No more methods are added to this
164+
* {@code ClassEntry} when writing debug info to the object file.
115165
*
116166
* @param methodEntry the {@code MethodEntry} to add
117167
*/
118168
public void addMethod(MethodEntry methodEntry) {
169+
assert methods instanceof ArrayList<MethodEntry> : "Can only add methods before a ClassEntry is sealed.";
119170
addFile(methodEntry.getFileEntry());
120-
methods.add(methodEntry);
171+
synchronized (methods) {
172+
methods.add(methodEntry);
173+
}
121174
}
122175

123176
/**
124177
* Add a compiled method to the class entry and store its file entry and the file entries of
125178
* inlined methods.
179+
* <p>
180+
* This is only called during debug info generation. No more compiled methods are added to this
181+
* {@code ClassEntry} when writing debug info to the object file.
126182
*
127183
* @param compiledMethodEntry the {@code CompiledMethodEntry} to add
128184
*/
129185
public void addCompiledMethod(CompiledMethodEntry compiledMethodEntry) {
130-
addFile(compiledMethodEntry.primary().getFileEntry());
131-
for (Range range : compiledMethodEntry.topDownRangeStream().toList()) {
132-
addFile(range.getFileEntry());
186+
assert compiledMethods instanceof ArrayList<CompiledMethodEntry> : "Can only add compiled methods before a ClassEntry is sealed.";
187+
synchronized (compiledMethods) {
188+
compiledMethods.add(compiledMethodEntry);
133189
}
134-
compiledMethods.add(compiledMethodEntry);
135190
}
136191

137192
public String getFileName() {
@@ -173,30 +228,19 @@ public int getDirIdx() {
173228

174229
/**
175230
* Returns the file index of a given file entry within this class entry.
176-
*
177231
* <p>
178-
* The first time a file entry is fetched, this produces a file index that is used for further
179-
* index lookups. The file index is only created once. Therefore, this method must be used only
180-
* after debug info generation is finished and no more file entries can be added to this class
181-
* entry.
182-
*
232+
* This method is only called once all debug info entries are produced, the class entry and the
233+
* file index was generated.
234+
*
183235
* @param file the given file entry
184236
* @return the index of the file entry
185237
*/
186238
public int getFileIdx(FileEntry file) {
187-
if (file == null || files.isEmpty() || !files.contains(file)) {
239+
assert indexedFiles != null : "Can only request file index after a ClassEntry is sealed.";
240+
if (file == null || !indexedFiles.containsKey(file)) {
188241
return 0;
189242
}
190243

191-
// Create a file index for all files in this class entry
192-
if (indexedFiles.isEmpty()) {
193-
int index = 1;
194-
for (FileEntry f : getFiles()) {
195-
indexedFiles.put(f, index);
196-
index++;
197-
}
198-
}
199-
200244
return indexedFiles.get(file);
201245
}
202246

@@ -214,30 +258,19 @@ public int getDirIdx(FileEntry file) {
214258

215259
/**
216260
* Returns the dir index of a given dir entry within this class entry.
217-
*
218261
* <p>
219-
* The first time a dir entry is fetched, this produces a dir index that is used for further
220-
* index lookups. The dir index is only created once. Therefore, this method must be used only
221-
* after debug info generation is finished and no more dir entries can be added to this class
222-
* entry.
262+
* This method is only called once all debug info entries are produced, the class entry and the
263+
* dir index was generated.
223264
*
224265
* @param dir the given dir entry
225266
* @return the index of the dir entry
226267
*/
227268
public int getDirIdx(DirEntry dir) {
228-
if (dir == null || dir.getPathString().isEmpty() || dirs.isEmpty() || !dirs.contains(dir)) {
269+
assert indexedDirs != null : "Can only request dir index after a ClassEntry is sealed.";
270+
if (dir == null || !indexedDirs.containsKey(dir)) {
229271
return 0;
230272
}
231273

232-
// Create a dir index for all dirs in this class entry
233-
if (indexedDirs.isEmpty()) {
234-
int index = 1;
235-
for (DirEntry d : getDirs()) {
236-
indexedDirs.put(d, index);
237-
index++;
238-
}
239-
}
240-
241274
return indexedDirs.get(dir);
242275
}
243276

@@ -246,15 +279,17 @@ public String getLoaderId() {
246279
}
247280

248281
/**
249-
* Retrieve a list of all compiled method entries for this class.
282+
* Retrieve a list of all compiled method entries for this class, sorted by start address.
250283
*
251-
* @return a list of all compiled method entries for this class.
284+
* @return a {@code List} of all compiled method entries for this class
252285
*/
253286
public List<CompiledMethodEntry> compiledMethods() {
254-
return List.copyOf(compiledMethods);
287+
assert !(compiledMethods instanceof ArrayList<CompiledMethodEntry>) : "Can only access compiled methods after a ClassEntry is sealed.";
288+
return compiledMethods;
255289
}
256290

257291
public boolean hasCompiledMethods() {
292+
assert !(compiledMethods instanceof ArrayList<CompiledMethodEntry>) : "Can only access compiled methods after a ClassEntry is sealed.";
258293
return !compiledMethods.isEmpty();
259294
}
260295

@@ -263,7 +298,8 @@ public ClassEntry getSuperClass() {
263298
}
264299

265300
public List<MethodEntry> getMethods() {
266-
return List.copyOf(methods);
301+
assert !(methods instanceof ArrayList<MethodEntry>) : "Can only access methods after a ClassEntry is sealed.";
302+
return methods;
267303
}
268304

269305
/**
@@ -274,7 +310,7 @@ public List<MethodEntry> getMethods() {
274310
*/
275311
public long lowpc() {
276312
assert hasCompiledMethods();
277-
return compiledMethods.first().primary().getLo();
313+
return compiledMethods.getFirst().primary().getLo();
278314
}
279315

280316
/**
@@ -284,9 +320,10 @@ public long lowpc() {
284320
*
285321
* @return the highest code section offset for compiled method code belonging to this class
286322
*/
323+
@SuppressWarnings("unused")
287324
public long hipc() {
288325
assert hasCompiledMethods();
289-
return compiledMethods.last().primary().getHi();
326+
return compiledMethods.getLast().primary().getHi();
290327
}
291328

292329
/**
@@ -296,7 +333,8 @@ public long hipc() {
296333
* @return a list of all referenced files
297334
*/
298335
public List<FileEntry> getFiles() {
299-
return List.copyOf(files);
336+
assert !(files instanceof ArrayList<FileEntry>) : "Can only access files after a ClassEntry is sealed.";
337+
return files;
300338
}
301339

302340
/**
@@ -306,6 +344,7 @@ public List<FileEntry> getFiles() {
306344
* @return a list of all referenced directories
307345
*/
308346
public List<DirEntry> getDirs() {
309-
return List.copyOf(dirs);
347+
assert !(dirs instanceof ArrayList<DirEntry>) : "Can only access dir after a ClassEntry is sealed.";
348+
return dirs;
310349
}
311350
}

substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ public record CompiledMethodEntry(PrimaryRange primary, List<FrameSizeChangeEntr
4949
*
5050
* @return the stream of all ranges
5151
*/
52-
public Stream<Range> topDownRangeStream() {
52+
public Stream<Range> topDownRangeStream(boolean includePrimary) {
5353
// skip the root of the range stream which is the primary range
54-
return primary.rangeStream().skip(1);
54+
return primary.rangeStream().skip(includePrimary ? 0 : 1);
5555
}
5656

5757
/**
@@ -62,7 +62,7 @@ public Stream<Range> topDownRangeStream() {
6262
* @return the stream of leaf ranges
6363
*/
6464
public Stream<Range> leafRangeStream() {
65-
return topDownRangeStream().filter(Range::isLeaf);
65+
return topDownRangeStream(false).filter(Range::isLeaf);
6666
}
6767

6868
/**
@@ -73,6 +73,11 @@ public Stream<Range> leafRangeStream() {
7373
* @return the stream of call ranges
7474
*/
7575
public Stream<Range> callRangeStream() {
76-
return topDownRangeStream().filter(range -> !range.isLeaf());
76+
return topDownRangeStream(false).filter(range -> !range.isLeaf());
77+
}
78+
79+
public void seal() {
80+
// Seal the primary range. Also seals all subranges recursively.
81+
primary.seal();
7782
}
7883
}

substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,6 @@ public abstract class DebugInfoBase {
104104
* Handle on type entry for header structure.
105105
*/
106106
private HeaderTypeEntry headerType;
107-
/**
108-
* Handle on type entry for void type.
109-
*/
110-
private TypeEntry voidType;
111107
/**
112108
* Handle on class entry for java.lang.Object.
113109
*/
@@ -172,11 +168,6 @@ public abstract class DebugInfoBase {
172168
* address translation.
173169
*/
174170
public static final String COMPRESSED_PREFIX = "_z_.";
175-
/**
176-
* A prefix used for type signature generation to generate unique type signatures for type
177-
* layout type units.
178-
*/
179-
public static final String LAYOUT_PREFIX = "_layout_.";
180171

181172
/**
182173
* The name of the type for header field hub which needs special case processing to remove tag
@@ -259,9 +250,6 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
259250
compiledMethods.addAll(debugInfoProvider.compiledMethodEntries());
260251
debugInfoProvider.typeEntries().forEach(typeEntry -> {
261252
types.add(typeEntry);
262-
if (typeEntry.getTypeName().equals("void")) {
263-
voidType = typeEntry;
264-
}
265253
switch (typeEntry) {
266254
case ArrayTypeEntry arrayTypeEntry -> arrayTypes.add(arrayTypeEntry);
267255
case PrimitiveTypeEntry primitiveTypeEntry -> primitiveTypes.add(primitiveTypeEntry);
@@ -289,12 +277,6 @@ public HeaderTypeEntry lookupHeaderType() {
289277
return headerType;
290278
}
291279

292-
public TypeEntry lookupVoidType() {
293-
// this should only be looked up after all types have been notified
294-
assert voidType != null;
295-
return voidType;
296-
}
297-
298280
public ClassEntry lookupObjectClass() {
299281
// this should only be looked up after all types have been notified
300282
assert objectClass != null;

0 commit comments

Comments
 (0)