Skip to content

Commit 2e6e405

Browse files
[GR-68055] Memory spike during debug info generation for Layered Micronaut Pegasus build.
PullRequest: graal/21648
2 parents 14eb222 + a77b823 commit 2e6e405

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)