Skip to content

Commit 5373664

Browse files
committed
[GR-21865] Introduce copy-on-write image heap provider on Windows.
PullRequest: graal/9699
2 parents 68540b7 + fb89576 commit 5373664

File tree

10 files changed

+588
-39
lines changed

10 files changed

+588
-39
lines changed

substratevm/src/com.oracle.svm.core.genscavenge/src/com/oracle/svm/core/genscavenge/AbstractImageHeapLayouter.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ public T[] getPartitions() {
7777
return partitions;
7878
}
7979

80+
private T getLastPartition() {
81+
return getPartitions()[PARTITION_COUNT - 1];
82+
}
83+
8084
public AbstractImageHeapLayouter() {
8185
this.partitions = createPartitionsArray(PARTITION_COUNT);
8286
this.partitions[READ_ONLY_PRIMITIVE] = createPartition("readOnlyPrimitive", false, false, false);
@@ -111,6 +115,12 @@ public ImageHeapLayoutInfo layout(ImageHeap imageHeap, int pageSize) {
111115
} else if (partition == getWritableHuge()) {
112116
endAlignment = pageSize;
113117
}
118+
119+
/* Make sure the image heap size is a multiple of the page size. */
120+
if (partition == getLastPartition()) {
121+
endAlignment = pageSize;
122+
}
123+
114124
partition.setStartAlignment(startAlignment);
115125
partition.setEndAlignment(endAlignment);
116126
}
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/*
2+
* Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.core.windows;
26+
27+
import static com.oracle.svm.core.Isolates.IMAGE_HEAP_BEGIN;
28+
import static com.oracle.svm.core.Isolates.IMAGE_HEAP_RELOCATABLE_BEGIN;
29+
import static com.oracle.svm.core.Isolates.IMAGE_HEAP_RELOCATABLE_END;
30+
import static org.graalvm.nativeimage.c.function.CFunction.Transition.NO_TRANSITION;
31+
32+
import org.graalvm.nativeimage.ImageSingletons;
33+
import org.graalvm.nativeimage.StackValue;
34+
import org.graalvm.nativeimage.c.function.CFunctionPointer;
35+
import org.graalvm.nativeimage.c.function.InvokeCFunctionPointer;
36+
import org.graalvm.nativeimage.c.type.CCharPointer;
37+
import org.graalvm.nativeimage.c.type.WordPointer;
38+
import org.graalvm.nativeimage.hosted.Feature;
39+
import org.graalvm.word.LocationIdentity;
40+
import org.graalvm.word.Pointer;
41+
import org.graalvm.word.PointerBase;
42+
import org.graalvm.word.UnsignedWord;
43+
import org.graalvm.word.WordFactory;
44+
45+
import com.oracle.svm.core.annotate.AutomaticFeature;
46+
import com.oracle.svm.core.annotate.Uninterruptible;
47+
import com.oracle.svm.core.c.CGlobalData;
48+
import com.oracle.svm.core.c.CGlobalDataFactory;
49+
import com.oracle.svm.core.c.function.CEntryPointActions;
50+
import com.oracle.svm.core.c.function.CEntryPointErrors;
51+
import com.oracle.svm.core.os.AbstractCopyingImageHeapProvider;
52+
import com.oracle.svm.core.os.ImageHeapProvider;
53+
import com.oracle.svm.core.os.VirtualMemoryProvider;
54+
import com.oracle.svm.core.os.VirtualMemoryProvider.Access;
55+
import com.oracle.svm.core.windows.headers.FileAPI;
56+
import com.oracle.svm.core.windows.headers.LibC;
57+
import com.oracle.svm.core.windows.headers.LibC.WCharPointer;
58+
import com.oracle.svm.core.windows.headers.LibLoaderAPI;
59+
import com.oracle.svm.core.windows.headers.MemoryAPI;
60+
import com.oracle.svm.core.windows.headers.WinBase;
61+
import com.oracle.svm.core.windows.headers.WinBase.HANDLE;
62+
import com.oracle.svm.core.windows.headers.WinBase.HMODULE;
63+
64+
@AutomaticFeature
65+
class WindowsImageHeapProviderFeature implements Feature {
66+
@Override
67+
public void duringSetup(DuringSetupAccess access) {
68+
if (!ImageSingletons.contains(ImageHeapProvider.class)) {
69+
ImageSingletons.add(ImageHeapProvider.class, new WindowsImageHeapProvider());
70+
}
71+
}
72+
}
73+
74+
/**
75+
* An image heap provider for Windows that creates image heaps that are copy-on-write clones of the
76+
* loaded image heap.
77+
*/
78+
public class WindowsImageHeapProvider extends AbstractCopyingImageHeapProvider {
79+
@Override
80+
@Uninterruptible(reason = "Called during isolate initialization.")
81+
protected int commitAndCopyMemory(Pointer loadedImageHeap, UnsignedWord imageHeapSize, Pointer newImageHeap) {
82+
HANDLE imageHeapFileMapping = getImageHeapFileMapping();
83+
if (imageHeapFileMapping.isNull()) {
84+
/* Fall back to copying from memory. */
85+
return super.commitAndCopyMemory(loadedImageHeap, imageHeapSize, newImageHeap);
86+
}
87+
88+
/* Map a copy-on-write view of the image heap. */
89+
if (VirtualMemoryProvider.get().mapFile(newImageHeap, imageHeapSize, imageHeapFileMapping, getImageHeapFileOffset(),
90+
Access.READ | Access.WRITE).isNull()) {
91+
/* Fall back to copying from memory. */
92+
return super.commitAndCopyMemory(loadedImageHeap, imageHeapSize, newImageHeap);
93+
}
94+
95+
/* Copy relocatable pages. */
96+
return copyMemory(IMAGE_HEAP_RELOCATABLE_BEGIN.get(), IMAGE_HEAP_RELOCATABLE_END.get().subtract(IMAGE_HEAP_RELOCATABLE_BEGIN.get()),
97+
newImageHeap.add(IMAGE_HEAP_RELOCATABLE_BEGIN.get().subtract(IMAGE_HEAP_BEGIN.get())));
98+
}
99+
100+
@Override
101+
@Uninterruptible(reason = "Called during isolate initialization.")
102+
protected int copyMemory(Pointer loadedImageHeap, UnsignedWord imageHeapSize, Pointer newImageHeap) {
103+
LibC.memcpy(newImageHeap, loadedImageHeap, imageHeapSize);
104+
return CEntryPointErrors.NO_ERROR;
105+
}
106+
107+
/**
108+
* The pseudovariable __ImageBase provided by the MSVC linker represents the DOS header of the
109+
* module, which is what a Win32 module begins with. In other words, it is the base address of
110+
* the module and is the same as its HMODULE.
111+
*/
112+
private static final CGlobalData<PointerBase> IMAGE_BASE = CGlobalDataFactory.forSymbol("__ImageBase");
113+
114+
/** The cached handle of the image heap file mapping that closes when the process exits. */
115+
private static final CGlobalData<WordPointer> IMAGE_HEAP_FILE_MAPPING = CGlobalDataFactory.createWord(WindowsUtils.UNINITIALIZED_HANDLE);
116+
117+
@Uninterruptible(reason = "Called during isolate initialization.", mayBeInlined = true)
118+
private static HANDLE getImageHeapFileMapping() {
119+
HANDLE value = IMAGE_HEAP_FILE_MAPPING.get().read();
120+
if (value.equal(WindowsUtils.UNINITIALIZED_HANDLE)) {
121+
HANDLE fileMapping = createImageHeapFileMapping();
122+
HANDLE existingMapping = (HANDLE) ((Pointer) IMAGE_HEAP_FILE_MAPPING.get()).compareAndSwapWord(0,
123+
WindowsUtils.UNINITIALIZED_HANDLE, fileMapping, LocationIdentity.ANY_LOCATION);
124+
125+
if (existingMapping.equal(WindowsUtils.UNINITIALIZED_HANDLE)) {
126+
value = fileMapping;
127+
} else {
128+
/* Another thread has already created the mapping, so use that. */
129+
value = existingMapping;
130+
WinBase.CloseHandle(fileMapping);
131+
}
132+
}
133+
return value;
134+
}
135+
136+
/** Returns a handle to the image heap file mapping or the null pointer in case of an error. */
137+
@Uninterruptible(reason = "Called during isolate initialization.", mayBeInlined = true)
138+
private static HANDLE createImageHeapFileMapping() {
139+
/* Get the path of the file that contains the image heap. */
140+
WCharPointer filePath = StackValue.get(WinBase.MAX_PATH, WCharPointer.class);
141+
int length = LibLoaderAPI.GetModuleFileNameW((HMODULE) IMAGE_BASE.get(), filePath, WinBase.MAX_PATH);
142+
if (length == 0 || length == WinBase.MAX_PATH) {
143+
return WordFactory.nullPointer();
144+
}
145+
146+
/* Open the file for mapping. */
147+
HANDLE fileHandle = FileAPI.CreateFileW(filePath, FileAPI.GENERIC_READ(), FileAPI.FILE_SHARE_READ() | FileAPI.FILE_SHARE_DELETE(),
148+
WordFactory.nullPointer(), FileAPI.OPEN_EXISTING(), 0, WordFactory.nullPointer());
149+
if (fileHandle.equal(WinBase.INVALID_HANDLE_VALUE())) {
150+
return WordFactory.nullPointer();
151+
}
152+
153+
/* Create the mapping and close the file. */
154+
HANDLE fileMapping = MemoryAPI.CreateFileMappingW(fileHandle, WordFactory.nullPointer(), MemoryAPI.PAGE_READONLY(),
155+
0, 0, WordFactory.nullPointer());
156+
WinBase.CloseHandle(fileHandle);
157+
return fileMapping;
158+
}
159+
160+
private static final CGlobalData<WordPointer> IMAGE_HEAP_FILE_OFFSET = CGlobalDataFactory.createWord();
161+
162+
@Uninterruptible(reason = "Called during isolate initialization.", mayBeInlined = true)
163+
private static UnsignedWord getImageHeapFileOffset() {
164+
/*
165+
* The file offset of a relative virtual address (RVA) in a PE image can be determined by
166+
* inspecting the PE image headers.
167+
*
168+
* We do this using two helper functions from ntdll.dll: RtlImageNtHeader and
169+
* RtlAddressInSectionTable. Unfortunately, they are not publicly documented, but I think it
170+
* is unlikely that this will pose a problem (and it would definitely not go unnoticed).
171+
*
172+
* Alternatively, we could use equivalent functions from dbghelp.dll (ImageNtHeader and
173+
* ImageRvaToVa), but this would introduce an additional DLL dependency (which we would like
174+
* to avoid). Or we could even do it manually. The tricky part would be finding the header
175+
* of the section containing the RVA.
176+
*/
177+
UnsignedWord value = IMAGE_HEAP_FILE_OFFSET.get().read();
178+
if (value.equal(0)) {
179+
/* Get the NT header of the module that contains the image heap. */
180+
PointerBase ntHeader = invokeRtlImageNtHeader(IMAGE_BASE.get());
181+
/* Compute the RVA of the image heap. */
182+
int rva = (int) (IMAGE_HEAP_BEGIN.get().rawValue() - IMAGE_BASE.get().rawValue());
183+
/* Get the file offset of the image heap. */
184+
value = invokeRtlAddressInSectionTable(ntHeader, rva);
185+
IMAGE_HEAP_FILE_OFFSET.get().write(value);
186+
}
187+
return value;
188+
}
189+
190+
private static final CGlobalData<CCharPointer> NTDLL_DLL = CGlobalDataFactory.createCString("ntdll.dll");
191+
private static final CGlobalData<CCharPointer> RTL_IMAGE_NT_HEADER = CGlobalDataFactory.createCString("RtlImageNtHeader");
192+
private static final CGlobalData<CCharPointer> RTL_ADDRESS_IN_SECTION_TABLE = CGlobalDataFactory.createCString("RtlAddressInSectionTable");
193+
194+
private static final int ERROR_BAD_EXE_FORMAT = 0xC1;
195+
196+
/** Locates the IMAGE_NT_HEADERS structure in a PE image and returns a pointer to the data. */
197+
@Uninterruptible(reason = "Called during isolate initialization.", mayBeInlined = true)
198+
private static PointerBase invokeRtlImageNtHeader(PointerBase imageBase) {
199+
RtlImageNtHeader rtlImageNtHeader = WindowsUtils.getFunctionPointer(NTDLL_DLL.get(), RTL_IMAGE_NT_HEADER.get(), true);
200+
PointerBase ntHeader = rtlImageNtHeader.invoke(imageBase);
201+
if (ntHeader.isNull()) {
202+
CEntryPointActions.failFatally(ERROR_BAD_EXE_FORMAT, RTL_IMAGE_NT_HEADER.get());
203+
}
204+
return ntHeader;
205+
}
206+
207+
private interface RtlImageNtHeader extends CFunctionPointer {
208+
@InvokeCFunctionPointer(transition = NO_TRANSITION)
209+
PointerBase invoke(PointerBase imageBase);
210+
}
211+
212+
/**
213+
* Locates a relative virtual address (RVA) within the image header of a file that is mapped as
214+
* a file and returns the offset of the corresponding byte in the file.
215+
*/
216+
@Uninterruptible(reason = "Called during isolate initialization.", mayBeInlined = true)
217+
private static UnsignedWord invokeRtlAddressInSectionTable(PointerBase ntHeader, int rva) {
218+
RtlAddressInSectionTable rtlAddressInSectionTable = WindowsUtils.getFunctionPointer(NTDLL_DLL.get(), RTL_ADDRESS_IN_SECTION_TABLE.get(), true);
219+
UnsignedWord offset = (UnsignedWord) rtlAddressInSectionTable.invoke(ntHeader, WordFactory.nullPointer(), rva);
220+
if (offset.equal(0)) {
221+
CEntryPointActions.failFatally(ERROR_BAD_EXE_FORMAT, RTL_ADDRESS_IN_SECTION_TABLE.get());
222+
}
223+
return offset;
224+
}
225+
226+
private interface RtlAddressInSectionTable extends CFunctionPointer {
227+
@InvokeCFunctionPointer(transition = NO_TRANSITION)
228+
PointerBase invoke(PointerBase ntHeader, PointerBase base, int rva);
229+
}
230+
}

substratevm/src/com.oracle.svm.core.windows/src/com/oracle/svm/core/windows/WindowsUtils.java

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@
3030
import java.io.IOException;
3131

3232
import org.graalvm.nativeimage.PinnedObject;
33-
import org.graalvm.nativeimage.Platform;
34-
import org.graalvm.nativeimage.Platforms;
3533
import org.graalvm.nativeimage.StackValue;
34+
import org.graalvm.nativeimage.c.function.CFunctionPointer;
35+
import org.graalvm.nativeimage.c.struct.CPointerTo;
3636
import org.graalvm.nativeimage.c.type.CCharPointer;
3737
import org.graalvm.nativeimage.c.type.CIntPointer;
3838
import org.graalvm.nativeimage.c.type.CLongPointer;
39+
import org.graalvm.word.PointerBase;
3940
import org.graalvm.word.UnsignedWord;
4041
import org.graalvm.word.WordFactory;
4142

@@ -45,13 +46,15 @@
4546
import com.oracle.svm.core.annotate.RecomputeFieldValue.CustomFieldValueComputer;
4647
import com.oracle.svm.core.annotate.TargetClass;
4748
import com.oracle.svm.core.annotate.Uninterruptible;
49+
import com.oracle.svm.core.c.function.CEntryPointActions;
4850
import com.oracle.svm.core.windows.headers.FileAPI;
51+
import com.oracle.svm.core.windows.headers.LibLoaderAPI;
4952
import com.oracle.svm.core.windows.headers.WinBase;
53+
import com.oracle.svm.core.windows.headers.WinBase.HMODULE;
5054

5155
import jdk.vm.ci.meta.MetaAccessProvider;
5256
import jdk.vm.ci.meta.ResolvedJavaField;
5357

54-
@Platforms(Platform.WINDOWS.class)
5558
public class WindowsUtils {
5659

5760
@TargetClass(className = "java.lang.ProcessImpl")
@@ -182,4 +185,53 @@ public static long getNanoCounter() {
182185
double freq = performanceFrequency;
183186
return (long) ((current / freq) * NANOSECS_PER_SEC);
184187
}
188+
189+
/** Sentinel value denoting the uninitialized kernel handle. */
190+
public static final PointerBase UNINITIALIZED_HANDLE = WordFactory.pointer(1);
191+
192+
@CPointerTo(nameOfCType = "void*")
193+
interface CFunctionPointerPointer<T extends CFunctionPointer> extends PointerBase {
194+
T read();
195+
196+
void write(T value);
197+
}
198+
199+
/** Sentinel value denoting the uninitialized pointer. */
200+
static final PointerBase UNINITIALIZED_POINTER = WordFactory.pointer(0xBAD);
201+
202+
/**
203+
* Retrieves and caches the address of an exported function from an already loaded DLL if the
204+
* cached function pointer is {@linkplain #UNINITIALIZED_POINTER uninitialized}, otherwise it
205+
* returns the cached value.
206+
*/
207+
@Uninterruptible(reason = "May be called from uninterruptible code.", mayBeInlined = true)
208+
static <T extends CFunctionPointer> T getAndCacheFunctionPointer(CFunctionPointerPointer<T> cachedFunctionPointer,
209+
CCharPointer dllName, CCharPointer functionName) {
210+
T functionPointer = cachedFunctionPointer.read();
211+
if (functionPointer.equal(UNINITIALIZED_POINTER)) {
212+
functionPointer = getFunctionPointer(dllName, functionName, false);
213+
cachedFunctionPointer.write(functionPointer);
214+
}
215+
return functionPointer;
216+
}
217+
218+
/** Retrieves the address of an exported function from an already loaded DLL. */
219+
@SuppressWarnings("unchecked")
220+
@Uninterruptible(reason = "May be called from uninterruptible code.", mayBeInlined = true)
221+
static <T extends CFunctionPointer> T getFunctionPointer(CCharPointer dllName, CCharPointer functionName, boolean failOnError) {
222+
PointerBase functionPointer = LibLoaderAPI.GetProcAddress(getDLLHandle(dllName), functionName);
223+
if (functionPointer.isNull() && failOnError) {
224+
CEntryPointActions.failFatally(WinBase.GetLastError(), functionName);
225+
}
226+
return (T) functionPointer;
227+
}
228+
229+
@Uninterruptible(reason = "May be called from uninterruptible code.", mayBeInlined = true)
230+
private static HMODULE getDLLHandle(CCharPointer dllName) {
231+
HMODULE dllHandle = LibLoaderAPI.GetModuleHandleA(dllName);
232+
if (dllHandle.isNull()) {
233+
CEntryPointActions.failFatally(WinBase.GetLastError(), dllName);
234+
}
235+
return dllHandle;
236+
}
185237
}

0 commit comments

Comments
 (0)