2525package com .oracle .svm .core .jdk ;
2626
2727import java .lang .reflect .Field ;
28+ import java .util .ArrayList ;
2829import java .util .Arrays ;
30+ import java .util .EnumSet ;
31+ import java .util .IdentityHashMap ;
32+ import java .util .List ;
33+ import java .util .Set ;
2934import java .util .concurrent .ConcurrentHashMap ;
3035
3136import org .graalvm .nativeimage .Platform ;
3439import com .oracle .svm .core .BuildPhaseProvider .AfterHeapLayout ;
3540import com .oracle .svm .core .feature .AutomaticallyRegisteredImageSingleton ;
3641import com .oracle .svm .core .heap .UnknownObjectField ;
42+ import com .oracle .svm .core .imagelayer .ImageLayerBuildingSupport ;
43+ import com .oracle .svm .core .layeredimagesingleton .ImageSingletonLoader ;
44+ import com .oracle .svm .core .layeredimagesingleton .ImageSingletonWriter ;
45+ import com .oracle .svm .core .layeredimagesingleton .LayeredImageSingletonBuilderFlags ;
46+ import com .oracle .svm .core .layeredimagesingleton .MultiLayeredImageSingleton ;
3747import com .oracle .svm .util .ReflectionUtil ;
3848
3949@ AutomaticallyRegisteredImageSingleton
40- public final class StringInternSupport {
50+ public final class StringInternSupport implements MultiLayeredImageSingleton {
51+
52+ interface SetGenerator {
53+ Set <String > generateSet ();
54+ }
4155
4256 @ Platforms (Platform .HOSTED_ONLY .class )
4357 public static Field getInternedStringsField () {
@@ -59,17 +73,75 @@ public static Field getInternedStringsField() {
5973 */
6074 @ UnknownObjectField (availability = AfterHeapLayout .class ) private String [] imageInternedStrings ;
6175
76+ @ Platforms (Platform .HOSTED_ONLY .class ) private Object priorLayersInternedStrings ;
77+
78+ @ Platforms (Platform .HOSTED_ONLY .class ) private IdentityHashMap <String , String > internedStringsIdentityMap ;
79+
6280 @ Platforms (Platform .HOSTED_ONLY .class )
6381 public StringInternSupport () {
82+ this (Set .of ());
83+ }
84+
85+ private StringInternSupport (Object obj ) {
6486 this .internedStrings = new ConcurrentHashMap <>(16 , 0.75f , 1 );
87+ this .priorLayersInternedStrings = obj ;
6588 }
6689
6790 @ Platforms (Platform .HOSTED_ONLY .class )
6891 public void setImageInternedStrings (String [] newImageInternedStrings ) {
92+ assert !ImageLayerBuildingSupport .buildingImageLayer ();
6993 this .imageInternedStrings = newImageInternedStrings ;
7094 }
7195
72- protected String intern (String str ) {
96+ @ SuppressWarnings ("unchecked" )
97+ @ Platforms (Platform .HOSTED_ONLY .class )
98+ public String [] layeredSetImageInternedStrings (Set <String > layerInternedStrings ) {
99+ assert ImageLayerBuildingSupport .buildingImageLayer ();
100+ /*
101+ * When building a layered image, it is possible that this string has been interned in a
102+ * prior layer. Thus, we must filter the interned string away from this array.
103+ *
104+ * In addition, the hashcode for the string should match across layers.
105+ */
106+ String [] currentLayerInternedStrings ;
107+ Set <String > priorInternedStrings ;
108+ if (priorLayersInternedStrings instanceof SetGenerator generator ) {
109+ priorInternedStrings = generator .generateSet ();
110+ } else {
111+ priorInternedStrings = (Set <String >) priorLayersInternedStrings ;
112+ }
113+ // don't need this anymore
114+ priorLayersInternedStrings = null ;
115+
116+ if (priorInternedStrings .isEmpty ()) {
117+ currentLayerInternedStrings = layerInternedStrings .toArray (String []::new );
118+ } else {
119+ currentLayerInternedStrings = layerInternedStrings .stream ().filter (value -> !priorInternedStrings .contains (value )).toArray (String []::new );
120+ }
121+
122+ Arrays .sort (currentLayerInternedStrings );
123+
124+ this .imageInternedStrings = currentLayerInternedStrings ;
125+
126+ if (ImageLayerBuildingSupport .buildingSharedLayer ()) {
127+ internedStringsIdentityMap = new IdentityHashMap <>(priorInternedStrings .size () + currentLayerInternedStrings .length );
128+ for (var value : priorInternedStrings ) {
129+ String internedVersion = value .intern ();
130+ internedStringsIdentityMap .put (internedVersion , internedVersion );
131+ }
132+ Arrays .stream (currentLayerInternedStrings ).forEach (internedString -> internedStringsIdentityMap .put (internedString , internedString ));
133+ }
134+
135+ return imageInternedStrings ;
136+ }
137+
138+ @ Platforms (Platform .HOSTED_ONLY .class )
139+ public IdentityHashMap <String , String > getInternedStringsIdentityMap () {
140+ assert internedStringsIdentityMap != null ;
141+ return internedStringsIdentityMap ;
142+ }
143+
144+ String intern (String str ) {
73145 String result = internedStrings .get (str );
74146 if (result != null ) {
75147 return result ;
@@ -80,14 +152,49 @@ protected String intern(String str) {
80152
81153 private String doIntern (String str ) {
82154 String result = str ;
83- int imageIdx = Arrays .binarySearch (imageInternedStrings , str );
84- if (imageIdx >= 0 ) {
85- result = imageInternedStrings [imageIdx ];
155+ if (ImageLayerBuildingSupport .buildingImageLayer ()) {
156+ StringInternSupport [] layers = MultiLayeredImageSingleton .getAllLayers (StringInternSupport .class );
157+ for (StringInternSupport layer : layers ) {
158+ String [] layerImageInternedStrings = layer .imageInternedStrings ;
159+ int imageIdx = Arrays .binarySearch (layerImageInternedStrings , str );
160+ if (imageIdx >= 0 ) {
161+ result = layerImageInternedStrings [imageIdx ];
162+ break ;
163+ }
164+ }
165+ } else {
166+ int imageIdx = Arrays .binarySearch (imageInternedStrings , str );
167+ if (imageIdx >= 0 ) {
168+ result = imageInternedStrings [imageIdx ];
169+ }
86170 }
87171 String oldValue = internedStrings .putIfAbsent (result , result );
88172 if (oldValue != null ) {
89173 result = oldValue ;
90174 }
91175 return result ;
92176 }
177+
178+ @ Override
179+ public EnumSet <LayeredImageSingletonBuilderFlags > getImageBuilderFlags () {
180+ return LayeredImageSingletonBuilderFlags .ALL_ACCESS ;
181+ }
182+
183+ @ Override
184+ public PersistFlags preparePersist (ImageSingletonWriter writer ) {
185+ // This can be switched to use constant ids in the future
186+ List <String > newPriorInternedStrings = new ArrayList <>(internedStringsIdentityMap .size ());
187+
188+ newPriorInternedStrings .addAll (internedStringsIdentityMap .keySet ());
189+
190+ writer .writeStringList ("internedStrings" , newPriorInternedStrings );
191+ return PersistFlags .CREATE ;
192+ }
193+
194+ @ SuppressWarnings ("unused" )
195+ public static Object createFromLoader (ImageSingletonLoader loader ) {
196+ SetGenerator gen = (() -> Set .of (loader .readStringList ("internedStrings" ).toArray (new String [0 ])));
197+
198+ return new StringInternSupport (gen );
199+ }
93200}
0 commit comments