com.oracle.svm.hosted.image.NativeBootImage Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of svm Show documentation
Show all versions of svm Show documentation
SubstrateVM image builder components
/*
* Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.svm.hosted.image;
import static com.oracle.svm.core.SubstrateUtil.mangleName;
import static com.oracle.svm.core.util.VMError.shouldNotReachHere;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.graalvm.collections.Pair;
import org.graalvm.compiler.code.CompilationResult;
import org.graalvm.compiler.core.common.CompressEncoding;
import org.graalvm.compiler.core.common.NumUtil;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.Indent;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.c.function.CFunctionPointer;
import com.oracle.graal.pointsto.meta.AnalysisMethod;
import com.oracle.graal.pointsto.meta.AnalysisType;
import com.oracle.objectfile.BasicProgbitsSectionImpl;
import com.oracle.objectfile.BuildDependency;
import com.oracle.objectfile.LayoutDecision;
import com.oracle.objectfile.LayoutDecisionMap;
import com.oracle.objectfile.ObjectFile;
import com.oracle.objectfile.ObjectFile.Element;
import com.oracle.objectfile.ObjectFile.ProgbitsSectionImpl;
import com.oracle.objectfile.ObjectFile.RelocationKind;
import com.oracle.objectfile.ObjectFile.Section;
import com.oracle.objectfile.SectionName;
import com.oracle.objectfile.macho.MachOObjectFile;
import com.oracle.svm.core.FrameAccess;
import com.oracle.svm.core.Isolates;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.c.CConst;
import com.oracle.svm.core.c.CGlobalDataImpl;
import com.oracle.svm.core.c.CHeader;
import com.oracle.svm.core.c.CHeader.Header;
import com.oracle.svm.core.c.CUnsigned;
import com.oracle.svm.core.c.function.CEntryPointOptions.Publish;
import com.oracle.svm.core.c.function.GraalIsolateHeader;
import com.oracle.svm.core.config.ConfigurationValues;
import com.oracle.svm.core.graal.code.CGlobalDataInfo;
import com.oracle.svm.core.graal.code.CGlobalDataReference;
import com.oracle.svm.core.meta.SubstrateObjectConstant;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.NativeImageOptions;
import com.oracle.svm.hosted.c.CGlobalDataFeature;
import com.oracle.svm.hosted.c.NativeLibraries;
import com.oracle.svm.hosted.c.codegen.CSourceCodeWriter;
import com.oracle.svm.hosted.c.codegen.QueryCodeWriter;
import com.oracle.svm.hosted.code.CEntryPointCallStubMethod;
import com.oracle.svm.hosted.code.CEntryPointCallStubSupport;
import com.oracle.svm.hosted.code.CEntryPointData;
import com.oracle.svm.hosted.image.NativeImageHeap.HeapPartition;
import com.oracle.svm.hosted.image.NativeImageHeap.ObjectInfo;
import com.oracle.svm.hosted.image.RelocatableBuffer.Info;
import com.oracle.svm.hosted.meta.HostedMetaAccess;
import com.oracle.svm.hosted.meta.HostedMethod;
import com.oracle.svm.hosted.meta.HostedUniverse;
import com.oracle.svm.hosted.meta.MethodPointer;
import jdk.vm.ci.code.site.ConstantReference;
import jdk.vm.ci.code.site.DataSectionReference;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.ResolvedJavaMethod.Parameter;
import jdk.vm.ci.meta.ResolvedJavaType;
public abstract class NativeBootImage extends AbstractBootImage {
public static final long RWDATA_CGLOBALS_PARTITION_OFFSET = 0;
@Override
public Section getTextSection() {
assert textSection != null;
return textSection;
}
@Override
public abstract String[] makeLaunchCommand(NativeImageKind k, String imageName, Path binPath, Path workPath, java.lang.reflect.Method method);
protected final void write(Path outputFile) {
try {
Files.createDirectories(outputFile.normalize().getParent());
FileChannel channel = FileChannel.open(outputFile, StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
objectFile.write(channel);
} catch (Exception ex) {
throw shouldNotReachHere(ex);
}
resultingImageSize = (int) outputFile.toFile().length();
if (NativeImageOptions.PrintImageElementSizes.getValue()) {
for (Element e : objectFile.getElements()) {
System.out.printf("PrintImageElementSizes: size: %15d name: %s\n", e.getMemSize(objectFile.getDecisionsByElement()), e.getElementName());
}
}
}
void writeHeaderFiles(Path outputDir, String imageName, boolean dynamic) {
/* Group methods by header files. */
Map extends Class extends Header>, List> hostedMethods = uniqueEntryPoints.stream()
.filter(this::shouldWriteHeader)
.map(m -> Pair.create(cHeader(m), m))
.collect(Collectors.groupingBy(Pair::getLeft, Collectors.mapping(Pair::getRight, Collectors.toList())));
hostedMethods.forEach((headerClass, methods) -> {
methods.sort(NativeBootImage::sortMethodsByFileNameAndPosition);
Header header = headerClass == Header.class ? defaultCHeaderAnnotation(imageName) : instantiateCHeader(headerClass);
writeHeaderFile(outputDir, header, methods, dynamic);
});
}
private void writeHeaderFile(Path outDir, CHeader.Header header, List methods, boolean dynamic) {
CSourceCodeWriter writer = new CSourceCodeWriter(outDir.getParent());
String imageHeaderGuard = "__" + header.name().toUpperCase().replaceAll("[^A-Z0-9]", "_") + "_H";
String dynamicSuffix = dynamic ? "_dynamic.h" : ".h";
writer.appendln("#ifndef " + imageHeaderGuard);
writer.appendln("#define " + imageHeaderGuard);
writer.appendln();
QueryCodeWriter.writeCStandardHeaders(writer);
List dependencies = header.dependsOn().stream()
.map(NativeBootImage::instantiateCHeader)
.map(depHeader -> "<" + depHeader.name() + dynamicSuffix + ">").collect(Collectors.toList());
writer.includeFiles(dependencies);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintWriter printWriter = new PrintWriter(baos);
header.writePreamble(printWriter);
printWriter.flush();
for (String line : baos.toString().split("\\r?\\n")) {
writer.appendln(line);
}
if (methods.size() > 0) {
writer.appendln();
writer.appendln("#if defined(__cplusplus)");
writer.appendln("extern \"C\" {");
writer.appendln("#endif");
writer.appendln();
methods.forEach(m -> writeMethodHeader(m, writer, dynamic));
writer.appendln("#if defined(__cplusplus)");
writer.appendln("}");
writer.appendln("#endif");
}
writer.appendln("#endif");
String fileName = outDir.getFileName().resolve(header.name() + dynamicSuffix).toString();
writer.writeFile(fileName, false);
}
/**
* Looks up the corresponding {@link CHeader} annotation for the {@link HostedMethod}. Returns
* {@code null} if no annotation was found.
*/
private static Class extends CHeader.Header> cHeader(HostedMethod entryPointStub) {
/* check if method is annotated */
AnalysisMethod entryPoint = CEntryPointCallStubSupport.singleton().getMethodForStub((CEntryPointCallStubMethod) entryPointStub.wrapped.wrapped);
CHeader methodAnnotation = entryPoint.getDeclaredAnnotation(CHeader.class);
if (methodAnnotation != null) {
return methodAnnotation.value();
}
/* check if enclosing classes are annotated */
AnalysisType enclosingType = entryPoint.getDeclaringClass();
while (enclosingType != null) {
CHeader enclosing = enclosingType.getDeclaredAnnotation(CHeader.class);
if (enclosing != null) {
return enclosing.value();
}
enclosingType = enclosingType.getEnclosingType();
}
return CHeader.Header.class;
}
private static Header instantiateCHeader(Class extends CHeader.Header> header) {
try {
Constructor> constructor = header.getDeclaredConstructor();
constructor.setAccessible(true);
return (CHeader.Header) constructor.newInstance();
} catch (NoSuchMethodException e) {
throw UserError.abort("CHeader " + header.getName() + " can't be instantiated. Please make sure that it has a nullary constructor.");
} catch (InstantiationException e) {
throw UserError.abort("CHeader " + header.getName() + " can't be instantiated. Make sure that " + header.getSimpleName() + " is not abstract.");
} catch (IllegalAccessException e) {
throw VMError.shouldNotReachHere("We set the constructor to accessible.");
} catch (InvocationTargetException e) {
throw UserError.abort("CHeader " + header.getName() + " can't be instantiated. The constructor threw and exception: " + e.getTargetException().getMessage());
}
}
private static CHeader.Header defaultCHeaderAnnotation(String defaultHeaderName) {
return new CHeader.Header() {
@Override
public String name() {
return defaultHeaderName;
}
@Override
public List> dependsOn() {
return Collections.singletonList(GraalIsolateHeader.class);
}
};
}
private static int sortMethodsByFileNameAndPosition(HostedMethod stub1, HostedMethod stub2) {
ResolvedJavaMethod rm1 = CEntryPointCallStubSupport.singleton().getMethodForStub((CEntryPointCallStubMethod) stub1.wrapped.wrapped).wrapped;
ResolvedJavaMethod rm2 = CEntryPointCallStubSupport.singleton().getMethodForStub((CEntryPointCallStubMethod) stub2.wrapped.wrapped).wrapped;
int fileComparison = rm1.getDeclaringClass().getSourceFileName().compareTo(rm2.getDeclaringClass().getSourceFileName());
if (fileComparison != 0) {
return fileComparison;
} else if (rm1.getLineNumberTable() != null && rm2.getLineNumberTable() != null) {
return rm1.getLineNumberTable().getLineNumber(0) - rm2.getLineNumberTable().getLineNumber(0);
}
return 0;
}
private void writeMethodHeader(HostedMethod m, CSourceCodeWriter writer, boolean dynamic) {
assert Modifier.isStatic(m.getModifiers()) : "Published methods that go into the header must be static.";
CEntryPointData cEntryPointData = (CEntryPointData) m.getWrapped().getEntryPointData();
String docComment = cEntryPointData.getDocumentation();
if (docComment != null && !docComment.isEmpty()) {
writer.appendln("/*");
Arrays.stream(docComment.split("\n")).forEach(l -> writer.appendln(" * " + l));
writer.appendln(" */");
}
if (dynamic) {
writer.append("typedef ");
}
writer.append(CSourceCodeWriter.toCTypeName(m,
(ResolvedJavaType) m.getSignature().getReturnType(m.getDeclaringClass()),
false,
false, // GR-9242
metaAccess,
nativeLibs));
writer.append(" ");
assert !cEntryPointData.getSymbolName().isEmpty();
if (dynamic) {
writer.append("(*").append(cEntryPointData.getSymbolName()).append("_fn_t)");
} else {
writer.append(cEntryPointData.getSymbolName());
}
writer.append("(");
String sep = "";
Parameter[] parameterInfo = m.getParameters();
for (int i = 0; i < m.getSignature().getParameterCount(false); i++) {
writer.append(sep);
sep = ", ";
writer.append(CSourceCodeWriter.toCTypeName(m,
(ResolvedJavaType) m.getSignature().getParameterType(i, m.getDeclaringClass()),
parameterInfo != null && parameterInfo[i].getDeclaredAnnotation(CConst.class) != null,
parameterInfo != null && parameterInfo[i].getDeclaredAnnotation(CUnsigned.class) != null,
metaAccess, nativeLibs));
if (parameterInfo != null && parameterInfo[i].isNamePresent()) {
writer.append(" ");
writer.append(parameterInfo[i].getName());
}
}
writer.appendln(");");
writer.appendln();
}
private boolean shouldWriteHeader(HostedMethod method) {
Object data = method.getWrapped().getEntryPointData();
return data instanceof CEntryPointData && ((CEntryPointData) data).getPublishAs() == Publish.SymbolAndHeader;
}
private ObjectFile.Symbol defineDataSymbol(String name, Element section, long position) {
return objectFile.createDefinedSymbol(name, section, position, wordSize, false, true);
}
/**
* Create the image sections for code, constants, and the heap.
*/
@Override
@SuppressWarnings("try")
public void build(DebugContext debug) {
try (DebugContext.Scope buildScope = debug.scope("NativeBootImage.build")) {
final CGlobalDataFeature cGlobals = CGlobalDataFeature.singleton();
final int textSectionSize = codeCache.getCodeCacheSize();
final int roConstantsSize = codeCache.getAlignedConstantsSize();
final int cglobalsSize = ConfigurationValues.getObjectLayout().alignUp(cGlobals.getSize());
if (SubstrateOptions.SpawnIsolates.getValue()) {
heap.alignRelocatablePartition(objectFile.getPageSize());
}
long roSectionSize = roConstantsSize;
long rwSectionSize = cglobalsSize;
if (!SubstrateOptions.SpawnIsolates.getValue()) {
roSectionSize += heap.getReadOnlySectionSize();
rwSectionSize += heap.getWritableSectionSize();
}
// Text section (code)
final RelocatableBuffer textBuffer = RelocatableBuffer.factory("text", textSectionSize, objectFile.getByteOrder());
final NativeTextSectionImpl textImpl = NativeTextSectionImpl.factory(textBuffer, objectFile, codeCache);
final String textSectionName = SectionName.TEXT.getFormatDependentName(objectFile.getFormat());
textSection = objectFile.newProgbitsSection(textSectionName, objectFile.getPageSize(), false, true, textImpl);
// Read-only data section
final RelocatableBuffer roDataBuffer = RelocatableBuffer.factory("roData", roSectionSize, objectFile.getByteOrder());
final ProgbitsSectionImpl roDataImpl = new BasicProgbitsSectionImpl(roDataBuffer.getBytes());
final String roDataSectionName = SectionName.RODATA.getFormatDependentName(objectFile.getFormat());
roDataSection = objectFile.newProgbitsSection(roDataSectionName, objectFile.getPageSize(), false, false, roDataImpl);
// Read-write data section
final RelocatableBuffer rwDataBuffer = RelocatableBuffer.factory("rwData", rwSectionSize, objectFile.getByteOrder());
final ProgbitsSectionImpl rwDataImpl = new BasicProgbitsSectionImpl(rwDataBuffer.getBytes());
final String rwDataSectionName = SectionName.DATA.getFormatDependentName(objectFile.getFormat());
rwDataSection = objectFile.newProgbitsSection(rwDataSectionName, objectFile.getPageSize(), true, false, rwDataImpl);
// Define symbols for the sections.
objectFile.createDefinedSymbol(textSection.getName(), textSection, 0, 0, false, false);
objectFile.createDefinedSymbol("__svm_text_end", textSection, textSectionSize, 0, false, true);
objectFile.createDefinedSymbol(roDataSection.getName(), roDataSection, 0, 0, false, false);
objectFile.createDefinedSymbol(rwDataSection.getName(), rwDataSection, 0, 0, false, false);
final long constantsPartitionOffset = 0L;
final long roConstantsEndOffset = constantsPartitionOffset + roConstantsSize;
final long rwGlobalsEndOffset = RWDATA_CGLOBALS_PARTITION_OFFSET + ConfigurationValues.getObjectLayout().alignUp(cGlobals.getSize());
final RelocatableBuffer heapSectionBuffer;
final ProgbitsSectionImpl heapSectionImpl;
if (SubstrateOptions.SpawnIsolates.getValue()) {
boolean writable = !SubstrateOptions.SpawnIsolates.getValue();
final long heapSize = heap.getReadOnlySectionSize() + heap.getWritableSectionSize();
heapSectionBuffer = RelocatableBuffer.factory("heap", heapSize, objectFile.getByteOrder());
heapSectionImpl = new BasicProgbitsSectionImpl(heapSectionBuffer.getBytes());
final String heapSectionName = SectionName.SVM_HEAP.getFormatDependentName(objectFile.getFormat());
heapSection = objectFile.newProgbitsSection(heapSectionName, objectFile.getPageSize(), writable, false, heapSectionImpl);
objectFile.createDefinedSymbol(heapSection.getName(), heapSection, 0, 0, false, true);
heap.setReadOnlySection(heapSection.getName(), 0);
long writableSectionOffset = heap.getReadOnlySectionSize();
heap.setWritableSection(heapSection.getName(), writableSectionOffset);
defineDataSymbol(Isolates.IMAGE_HEAP_BEGIN_SYMBOL_NAME, heapSection, 0);
defineDataSymbol(Isolates.IMAGE_HEAP_END_SYMBOL_NAME, heapSection, heapSize);
defineDataSymbol(Isolates.IMAGE_HEAP_WRITABLE_BEGIN_SYMBOL_NAME, heapSection, writableSectionOffset);
defineDataSymbol(Isolates.IMAGE_HEAP_WRITABLE_END_SYMBOL_NAME, heapSection, writableSectionOffset + heap.getWritableSectionSize());
final long relocatableOffset = heap.getReadOnlyRelocatablePartitionOffset();
final long relocatableSize = heap.getReadOnlyRelocatablePartitionSize();
defineDataSymbol(Isolates.IMAGE_HEAP_RELOCATABLE_BEGIN_SYMBOL_NAME, heapSection, relocatableOffset);
defineDataSymbol(Isolates.IMAGE_HEAP_RELOCATABLE_END_SYMBOL_NAME, heapSection, relocatableOffset + relocatableSize);
} else {
heapSectionBuffer = null;
heapSectionImpl = null;
heap.setReadOnlySection(roDataSection.getName(), roConstantsEndOffset);
heap.setWritableSection(rwDataSection.getName(), rwGlobalsEndOffset);
}
// Write the section contents and record relocations.
// - The code goes in the text section, by itself.
textImpl.writeTextSection(debug, textSection, entryPoints);
// - The constants go at the beginning of the read-only data section.
codeCache.writeConstants(roDataBuffer);
// - Non-heap global data goes at the beginning of the read-write data section.
cGlobals.writeData(rwDataBuffer, (offset, symbolName) -> defineDataSymbol(symbolName, rwDataSection, offset + RWDATA_CGLOBALS_PARTITION_OFFSET));
defineDataSymbol(CGlobalDataInfo.CGLOBALDATA_BASE_SYMBOL_NAME, rwDataSection, RWDATA_CGLOBALS_PARTITION_OFFSET);
// - Write the heap, either to its own section, or to the ro and rw data sections.
if (SubstrateOptions.SpawnIsolates.getValue()) {
heap.writeHeap(debug, heapSectionBuffer, heapSectionBuffer);
long firstRelocOffset = heap.getFirstRelocatablePointerOffsetInSection();
defineDataSymbol(Isolates.IMAGE_HEAP_RELOCATABLE_FIRST_RELOC_POINTER_NAME, heapSection, firstRelocOffset);
assert castToByteBuffer(heapSectionBuffer).getLong((int) firstRelocOffset) == 0;
} else {
assert heapSectionBuffer == null;
heap.writeHeap(debug, roDataBuffer, rwDataBuffer);
}
// Mark the sections with the relocations from the maps.
// - "null" as the objectMap is because relocations from text are always to constants.
markRelocationSitesFromMaps(textBuffer, textImpl, heap.objects);
markRelocationSitesFromMaps(roDataBuffer, roDataImpl, heap.objects);
markRelocationSitesFromMaps(rwDataBuffer, rwDataImpl, heap.objects);
if (heapSectionBuffer != null) {
markRelocationSitesFromMaps(heapSectionBuffer, heapSectionImpl, heap.objects);
}
}
// [Footnote 1]
//
// Subject: Re: Do you know why text references can only be to constants?
// Date: Fri, 09 Jan 2015 12:51:15 -0800
// From: Christian Wimmer
// To: Peter B. Kessler
//
// Code (i.e. the text section) needs to load the address of objects. So
// the read-only section contains a 8-byte slot with the address of the
// object that you actually want to load. A RIP-relative move instruction
// is used to load this 8-byte slot. The relocation for the move ensures
// the offset of the move is patched. And then a relocation from the
// read-only section to the actual native image heap ensures the 8-byte slot
// contains the actual address of the object to be loaded.
//
// Therefore, relocations in .text go only to things in .rodata; and
// relocations in .rodata go to .data in the current implementation
//
// It might be possible to have a RIP-relative load-effective-address (LEA)
// instruction to go directly from .text to .data, eliminating the memory
// access to load the address of an object. So I agree that allowing
// relocation from .text only to .rodata is an arbitrary restriction that
// could prevent future optimizations.
//
// -Christian
}
/**
* Covariant return type overrides added by https://bugs.openjdk.java.net/browse/JDK-4774077
* make the cast below unnecessary as of JDK 9.
*/
@SuppressWarnings("cast")
private static ByteBuffer castToByteBuffer(final RelocatableBuffer heapSectionBuffer) {
return (ByteBuffer) heapSectionBuffer.getBuffer().asReadOnlyBuffer().position(0);
}
private void markRelocationSitesFromMaps(RelocatableBuffer relocationMap, ProgbitsSectionImpl sectionImpl, Map