diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 9fd3bba20706..5d2a60fad48c 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -6,6 +6,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-54476): Issue a deprecation warning on first use of a legacy `graal.` prefix (see GR-49960 in [Compiler changelog](../compiler/CHANGELOG.md)). The warning is planned to be replaced by an error in GraalVM for JDK 25. * (GR-48384) Added a GDB Python script (`gdb-debughelpers.py`) to improve the Native Image debugging experience. +* (GR-49517) Add support for emitting Windows x64 unwind info. This enables stack walking in native tooling such as debuggers and profilers. ## GraalVM for JDK 23 (Internal Version 24.1.0) * (GR-51520) The old class initialization strategy, which was deprecated in GraalVM for JDK 22, is removed. The option `StrictImageHeap` no longer has any effect. diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java index 616ab7dd9b6d..734c93a025e1 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java @@ -46,13 +46,12 @@ import java.util.function.Consumer; import java.util.stream.StreamSupport; -import jdk.graal.compiler.debug.DebugContext; - import com.oracle.objectfile.debuginfo.DebugInfoProvider; import com.oracle.objectfile.elf.ELFObjectFile; import com.oracle.objectfile.macho.MachOObjectFile; import com.oracle.objectfile.pecoff.PECoffObjectFile; +import jdk.graal.compiler.debug.DebugContext; import sun.nio.ch.DirectBuffer; /** @@ -254,6 +253,11 @@ public enum RelocationKind { DIRECT_2, DIRECT_4, DIRECT_8, + /** + * The relocation's symbol provides an address whose image-base-relative value (plus addend) + * supplies the fixup bytes. + */ + ADDR32NB_4, /** * The index of the object file section containing the relocation's symbol supplies the * fixup bytes. (used in CodeView debug information) @@ -371,6 +375,7 @@ public static int getRelocationSize(RelocationKind kind) { return 2; case DIRECT_4: case PC_RELATIVE_4: + case ADDR32NB_4: case SECREL_4: return 4; case AARCH64_R_AARCH64_ADR_PREL_PG_HI21: @@ -522,10 +527,13 @@ public interface NobitsSectionImpl extends ElementImpl { // convenience overrides when specifying neither segment nor segment name public Section newUserDefinedSection(String name, ElementImpl impl) { - final Segment segment = getOrCreateSegment(null, name, false, false); final int alignment = getWordSizeInBytes(); - final Section result = newUserDefinedSection(segment, name, alignment, impl); - return result; + return newUserDefinedSection(name, alignment, impl); + } + + public Section newUserDefinedSection(String name, int alignment, ElementImpl impl) { + Segment segment = getOrCreateSegment(null, name, false, false); + return newUserDefinedSection(segment, name, alignment, impl); } public Section newDebugSection(String name, ElementImpl impl) { diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoff.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoff.java index bd7aab82fd1d..a6eadd255272 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoff.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoff.java @@ -195,8 +195,9 @@ enum IMAGE_RELOCATION { * Relocation types */ static final int IMAGE_REL_AMD64_ABSOLUTE = 0x0; - static final int IMAGE_REL_AMD64_ADDR32 = 0x2; static final int IMAGE_REL_AMD64_ADDR64 = 0x1; + static final int IMAGE_REL_AMD64_ADDR32 = 0x2; + static final int IMAGE_REL_AMD64_ADDR32NB = 0x3; static final int IMAGE_REL_AMD64_REL32 = 0x4; static final int IMAGE_REL_AMD64_REL32_1 = 0x5; static final int IMAGE_REL_AMD64_REL32_2 = 0x6; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffMachine.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffMachine.java index eecf4b7291fd..912e4bde6b9e 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffMachine.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffMachine.java @@ -54,6 +54,8 @@ public static PECoffRelocationMethod getRelocation(PECoffMachine m, RelocationKi return PECoffX86_64Relocation.ADDR32; case PC_RELATIVE_4: return PECoffX86_64Relocation.REL32; + case ADDR32NB_4: + return PECoffX86_64Relocation.ADDR32NB; case SECTION_2: return PECoffX86_64Relocation.SECTION; case SECREL_4: @@ -125,10 +127,16 @@ public long toLong() { return IMAGE_RELOCATION.IMAGE_REL_AMD64_ADDR32; } }, - SECREL { + ADDR32NB { @Override public long toLong() { - return IMAGE_RELOCATION.IMAGE_REL_AMD64_SECREL; + return IMAGE_RELOCATION.IMAGE_REL_AMD64_ADDR32NB; + } + }, + REL32 { + @Override + public long toLong() { + return IMAGE_RELOCATION.IMAGE_REL_AMD64_REL32; } }, SECTION { @@ -137,10 +145,10 @@ public long toLong() { return IMAGE_RELOCATION.IMAGE_REL_AMD64_SECTION; } }, - REL32 { + SECREL { @Override public long toLong() { - return IMAGE_RELOCATION.IMAGE_REL_AMD64_REL32; + return IMAGE_RELOCATION.IMAGE_REL_AMD64_SECREL; } }; } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffObjectFile.java index 64a8e4421f84..98904f2cbf76 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffObjectFile.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/PECoffObjectFile.java @@ -126,7 +126,7 @@ protected Segment getOrCreateSegment(String maybeSegmentName, String sectionName @Override public PECoffUserDefinedSection newUserDefinedSection(Segment segment, String name, int alignment, ElementImpl impl) { - PECoffUserDefinedSection userDefined = new PECoffUserDefinedSection(this, name, alignment, impl); + PECoffUserDefinedSection userDefined = new PECoffUserDefinedSection(this, name, alignment, impl, EnumSet.of(PECoffSectionFlag.INITIALIZED_DATA, PECoffSectionFlag.READ)); assert userDefined.getImpl() == impl; if (segment != null) { getOrCreateSegment(segment.getName(), name, true, false).add(userDefined); diff --git a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/FramePointerPhase.java b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/FramePointerPhase.java index d73ce5b8fdc4..86d4de0f4804 100644 --- a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/FramePointerPhase.java +++ b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/FramePointerPhase.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId; import jdk.graal.compiler.asm.amd64.AMD64MacroAssembler; import jdk.graal.compiler.core.common.cfg.BasicBlock; @@ -86,14 +87,13 @@ protected void run(TargetDescription target, LIRGenerationResult lirGenRes, PreA */ buffer.init(instructions); if (block.isExceptionEntry()) { + buffer.append(lirGenRes.getFirstInsertPosition(), new SpillFramePointerOp(true)); buffer.append(lirGenRes.getFirstInsertPosition(), new ReloadFramePointerOp()); } for (int i = 0; i < instructions.size(); i++) { if (instructions.get(i) instanceof AMD64Call.CallOp callOp) { buffer.append(i, new SpillFramePointerOp()); - if (callOp.destroysCallerSavedRegisters()) { - buffer.append(i + 1, new ReloadFramePointerOp()); - } + buffer.append(i + 1, new ReloadFramePointerOp(!callOp.destroysCallerSavedRegisters())); } } buffer.finish(); @@ -121,14 +121,24 @@ private static boolean modifiesStackPointer(LIR lir) { public static class SpillFramePointerOp extends AMD64LIRInstruction { public static final LIRInstructionClass TYPE = LIRInstructionClass.create(SpillFramePointerOp.class); + private final boolean recordMarkOnly; + SpillFramePointerOp() { + this(false); + } + + SpillFramePointerOp(boolean recordMarkOnly) { super(TYPE); + this.recordMarkOnly = recordMarkOnly; } @Override public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) { - var frameMap = (SubstrateAMD64Backend.SubstrateAMD64FrameMap) crb.frameMap; - masm.movq(masm.makeAddress(AMD64.rsp, frameMap.getFramePointerSaveAreaOffset()), AMD64.rbp); + if (!recordMarkOnly) { + var frameMap = (SubstrateAMD64Backend.SubstrateAMD64FrameMap) crb.frameMap; + masm.movq(masm.makeAddress(AMD64.rsp, frameMap.getFramePointerSaveAreaOffset()), AMD64.rbp); + } + crb.recordMark(SubstrateMarkId.FRAME_POINTER_SPILLED); } } @@ -136,14 +146,24 @@ public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) { public static class ReloadFramePointerOp extends AMD64LIRInstruction { public static final LIRInstructionClass TYPE = LIRInstructionClass.create(ReloadFramePointerOp.class); + private final boolean recordMarkOnly; + ReloadFramePointerOp() { + this(false); + } + + ReloadFramePointerOp(boolean recordMarkOnly) { super(TYPE); + this.recordMarkOnly = recordMarkOnly; } @Override public void emitCode(CompilationResultBuilder crb, AMD64MacroAssembler masm) { - var frameMap = (SubstrateAMD64Backend.SubstrateAMD64FrameMap) crb.frameMap; - masm.movq(AMD64.rbp, masm.makeAddress(AMD64.rsp, frameMap.getFramePointerSaveAreaOffset())); + if (!recordMarkOnly) { + var frameMap = (SubstrateAMD64Backend.SubstrateAMD64FrameMap) crb.frameMap; + masm.movq(AMD64.rbp, masm.makeAddress(AMD64.rsp, frameMap.getFramePointerSaveAreaOffset())); + } + crb.recordMark(SubstrateMarkId.FRAME_POINTER_RELOADED); } } } diff --git a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java index 24c80b6815a4..efc5d7005aa3 100644 --- a/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java +++ b/substratevm/src/com.oracle.svm.core.graal.amd64/src/com/oracle/svm/core/graal/amd64/SubstrateAMD64Backend.java @@ -26,6 +26,8 @@ import static com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP; import static com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId.PROLOGUE_END; +import static com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId.PROLOGUE_PUSH_RBP; +import static com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId.PROLOGUE_SET_FRAME_POINTER; import static com.oracle.svm.core.util.VMError.shouldNotReachHere; import static com.oracle.svm.core.util.VMError.unsupportedFeature; import static jdk.graal.compiler.lir.LIRInstruction.OperandFlag.REG; @@ -62,6 +64,7 @@ import com.oracle.svm.core.graal.code.AssignedLocation; import com.oracle.svm.core.graal.code.CGlobalDataInfo; import com.oracle.svm.core.graal.code.PatchConsumerFactory; +import com.oracle.svm.core.graal.code.SharedCompilationResult; import com.oracle.svm.core.graal.code.StubCallingConvention; import com.oracle.svm.core.graal.code.SubstrateBackend; import com.oracle.svm.core.graal.code.SubstrateCallingConvention; @@ -1244,6 +1247,7 @@ protected void maybePushBasePointer(CompilationResultBuilder crb, AMD64MacroAsse SubstrateAMD64FrameMap frameMap = (SubstrateAMD64FrameMap) crb.frameMap; if (frameMap.preserveFramePointer() || isCalleeSaved(rbp, frameMap.getRegisterConfig(), method)) { asm.push(rbp); + crb.recordMark(PROLOGUE_PUSH_RBP); } if (frameMap.preserveFramePointer() && !frameMap.needsFramePointer()) { /* We won't be using rbp as a frame pointer, so we form a frame chain here. */ @@ -1272,6 +1276,7 @@ private static void maybeSetFramePointer(CompilationResultBuilder crb, AMD64Macr /* Set the frame pointer to [rsp]. */ asm.movq(rbp, rsp); } + crb.recordMark(PROLOGUE_SET_FRAME_POINTER); } } @@ -1296,11 +1301,12 @@ public void leave(CompilationResultBuilder crb) { } else { asm.incrementq(rsp, frameMap.frameSize()); } + crb.recordMark(SubstrateMarkId.EPILOGUE_INCD_RSP); + if (frameMap.preserveFramePointer() || isCalleeSaved(rbp, frameMap.getRegisterConfig(), method)) { asm.pop(rbp); + crb.recordMark(SubstrateMarkId.EPILOGUE_POP_RBP); } - - crb.recordMark(SubstrateMarkId.EPILOGUE_INCD_RSP); } @Override @@ -1748,10 +1754,15 @@ public CompilationResultBuilder newCompilationResultBuilder(LIRGenerationResult FrameContext frameContext = createFrameContext(method, stubType, callingConvention); DebugContext debug = lir.getDebug(); Register uncompressedNullRegister = useLinearPointerCompression() ? ReservedRegisters.singleton().getHeapBaseRegister() : Register.None; - CompilationResultBuilder tasm = factory.createBuilder(getProviders(), lirGenResult.getFrameMap(), masm, dataBuilder, frameContext, options, debug, compilationResult, - uncompressedNullRegister, lir); - tasm.setTotalFrameSize(lirGenResult.getFrameMap().totalFrameSize()); - return tasm; + CompilationResultBuilder crb = factory.createBuilder(getProviders(), frameMap, masm, dataBuilder, frameContext, options, debug, compilationResult, uncompressedNullRegister, lir); + crb.setTotalFrameSize(frameMap.totalFrameSize()); + var sharedCompilationResult = (SharedCompilationResult) compilationResult; + var substrateAMD64FrameMap = (SubstrateAMD64FrameMap) frameMap; + sharedCompilationResult.setFrameSize(substrateAMD64FrameMap.frameSize()); + if (substrateAMD64FrameMap.needsFramePointer()) { + sharedCompilationResult.setFramePointerSaveAreaOffset(substrateAMD64FrameMap.getFramePointerSaveAreaOffset()); + } + return crb; } protected AMD64MacroAssembler createAssembler(OptionValues options) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SharedCompilationResult.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SharedCompilationResult.java new file mode 100644 index 000000000000..ca235da4e2cb --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SharedCompilationResult.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024, 2024, 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.core.graal.code; + +import jdk.graal.compiler.code.CompilationResult; +import jdk.graal.compiler.core.common.CompilationIdentifier; + +/** Base class common to both hosted and runtime compilations. */ +public abstract class SharedCompilationResult extends CompilationResult { + private int frameSize = -1; + private int framePointerSaveAreaOffset = -1; + + public SharedCompilationResult(CompilationIdentifier compilationId, String name) { + super(compilationId, name); + } + + public int getFrameSize() { + assert frameSize != -1 : "frame size not set"; + return frameSize; + } + + public void setFrameSize(int frameSize) { + this.frameSize = frameSize; + } + + public int getFramePointerSaveAreaOffset() { + return framePointerSaveAreaOffset; + } + + public void setFramePointerSaveAreaOffset(int framePointerSaveAreaOffset) { + this.framePointerSaveAreaOffset = framePointerSaveAreaOffset; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateBackend.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateBackend.java index f27716cfea47..75b11f7a2be0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateBackend.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateBackend.java @@ -28,6 +28,19 @@ import java.lang.reflect.Method; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; +import org.graalvm.word.LocationIdentity; + +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.graal.nodes.ComputedIndirectCallTargetNode; +import com.oracle.svm.core.graal.snippets.CFunctionSnippets; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.nodes.CFunctionPrologueDataNode; +import com.oracle.svm.core.thread.VMThreads.StatusSupport; +import com.oracle.svm.core.util.VMError; + import jdk.graal.compiler.code.CompilationResult; import jdk.graal.compiler.core.common.CompilationIdentifier; import jdk.graal.compiler.core.common.alloc.RegisterAllocationConfig; @@ -43,19 +56,6 @@ import jdk.graal.compiler.phases.BasePhase; import jdk.graal.compiler.phases.tiers.SuitesProvider; import jdk.graal.compiler.phases.util.Providers; -import org.graalvm.nativeimage.Platform; -import org.graalvm.nativeimage.Platforms; -import org.graalvm.word.LocationIdentity; - -import com.oracle.svm.core.SubstrateUtil; -import com.oracle.svm.core.graal.meta.RuntimeConfiguration; -import com.oracle.svm.core.graal.nodes.ComputedIndirectCallTargetNode; -import com.oracle.svm.core.graal.snippets.CFunctionSnippets; -import com.oracle.svm.core.meta.SharedMethod; -import com.oracle.svm.core.nodes.CFunctionPrologueDataNode; -import com.oracle.svm.core.thread.VMThreads.StatusSupport; -import com.oracle.svm.core.util.VMError; - import jdk.vm.ci.code.CodeCacheProvider; import jdk.vm.ci.code.RegisterConfig; import jdk.vm.ci.code.RegisterValue; @@ -70,11 +70,16 @@ public enum SubstrateMarkId implements CompilationResult.MarkId { * instructions in the compilation. */ PROLOGUE_START(true), + PROLOGUE_PUSH_RBP(true), PROLOGUE_DECD_RSP(true), + PROLOGUE_SET_FRAME_POINTER(true), PROLOGUE_SAVED_REGS(true), PROLOGUE_END(true), + FRAME_POINTER_SPILLED(true), + FRAME_POINTER_RELOADED(true), EPILOGUE_START(false), EPILOGUE_INCD_RSP(true), + EPILOGUE_POP_RBP(true), EPILOGUE_END(true); final boolean isMarkAfter; @@ -149,7 +154,7 @@ public SuitesProvider getSuites() { } public CompilationResult newCompilationResult(CompilationIdentifier compilationIdentifier, String name) { - return new CompilationResult(compilationIdentifier, name) { + return new SharedCompilationResult(compilationIdentifier, name) { @Override public void close(OptionValues options) { /* diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateCompilationResult.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateCompilationResult.java index 85f24b4c49c7..200af49249b1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateCompilationResult.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/graal/code/SubstrateCompilationResult.java @@ -26,11 +26,10 @@ import java.util.List; -import jdk.graal.compiler.code.CompilationResult; import jdk.graal.compiler.core.common.CompilationIdentifier; import jdk.graal.compiler.graph.NodeSourcePosition; -public final class SubstrateCompilationResult extends CompilationResult { +public final class SubstrateCompilationResult extends SharedCompilationResult { private List deoptimizationSourcePositions; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/WindowsUnwindInfoFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/WindowsUnwindInfoFeature.java new file mode 100644 index 000000000000..9502d547c50d --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/WindowsUnwindInfoFeature.java @@ -0,0 +1,548 @@ +/* + * Copyright (c) 2024, 2024, 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.graal.code.SubstrateBackend.SubstrateMarkId.FRAME_POINTER_RELOADED; +import static com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId.FRAME_POINTER_SPILLED; +import static com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP; +import static com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId.PROLOGUE_END; +import static com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId.PROLOGUE_PUSH_RBP; +import static com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId.PROLOGUE_SET_FRAME_POINTER; +import static com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId.PROLOGUE_START; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; + +import org.graalvm.collections.Pair; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.objectfile.BasicProgbitsSectionImpl; +import com.oracle.objectfile.ObjectFile; +import com.oracle.svm.core.FrameAccess; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.graal.code.SharedCompilationResult; +import com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.meta.HostedMethod; + +import jdk.graal.compiler.code.CompilationResult; +import jdk.graal.compiler.code.CompilationResult.CodeMark; +import jdk.graal.compiler.code.CompilationResult.MarkId; +import jdk.graal.compiler.core.common.NumUtil; + +/** + * This feature emits Windows x64 unwind info. + *

+ * The emission of unwind info is driven by {@linkplain CodeMark code marks} recorded during + * compilation. Code marks in this process typically serve one of two roles: they mark ranges for + * which unwind info is emitted or mark points of interest within these ranges. + *

+ * Each range is processed according to the {@linkplain CodeMark#id ID} of the range's start mark, + * with two entries emitted for each range: {@linkplain RUNTIME_FUNCTION} into the .pdata section + * and {@linkplain UNWIND_INFO} into the .xdata section. + *

+ * The following IDs are used to mark different types of ranges: + *

    + *
  • {@linkplain SubstrateMarkId#PROLOGUE_START PROLOGUE_START} - This marks the primary range. + * Each compiled method contains exactly one primary range, which in most cases encompasses the + * entire method's code. The {@linkplain UNWIND_CODE unwind codes} of the emitted unwind info are + * determined by the following prologue marks: {@linkplain SubstrateMarkId#PROLOGUE_PUSH_RBP + * PROLOGUE_PUSH_RBP}, {@linkplain SubstrateMarkId#PROLOGUE_DECD_RSP PROLOGUE_DECD_RSP}, and + * {@linkplain SubstrateMarkId#PROLOGUE_SET_FRAME_POINTER PROLOGUE_SET_FRAME_POINTER}. + * + *
  • {@linkplain SubstrateMarkId#FRAME_POINTER_SPILLED FRAME_POINTER_SPILLED} - This marks a range + * where rbp must be restored from its spill location before continuing unwinding using the primary + * range. The emitted unwind info for this range has fixed unwind codes. + * + *
  • {@linkplain SubstrateMarkId#FRAME_POINTER_RELOADED FRAME_POINTER_RELOADED} - This marks a + * range where rbp was restored after being spilled, allowing immediate continuation of unwinding + * using the primary range. The emitted unwind info for this range has no unwind codes. + *
+ * + * @see + * x64 exception handling + */ +@AutomaticallyRegisteredFeature +@Platforms(Platform.WINDOWS.class) +public class WindowsUnwindInfoFeature implements InternalFeature { + @Override + public void beforeImageWrite(BeforeImageWriteAccess access) { + AbstractImage image = ((FeatureImpl.BeforeImageWriteAccessImpl) access).getImage(); + ObjectFile objectFile = image.getObjectFile(); + + /* Determine the size of .xdata and .pdata sections. */ + AtomicInteger xdataSize = new AtomicInteger(); + AtomicInteger pdataSize = new AtomicInteger(); + image.getCodeCache().getOrderedCompilations().stream().parallel() + .forEach(entry -> visitRanges(entry.getRight(), (range, startMark, end) -> { + var compilation = (SharedCompilationResult) entry.getRight(); + int countOfCodes = switch (startMark.id) { + case PROLOGUE_START -> { + assert RUNTIME_FUNCTION.isPrimary(range) : range; + CodeMark[] prologueMarks = new CodeMark[MAX_PROLOGUE_MARKS]; + int markCount = collectPrologueMarks(compilation, prologueMarks); + yield countOfPrologueCodes(compilation, prologueMarks, markCount); + } + case FRAME_POINTER_SPILLED -> { + assert RUNTIME_FUNCTION.isChained(range) && range % 2 == 1 : range; + yield UNWIND_CODE.UWOP_PUSH_NONVOL.slots + UNWIND_CODE.forAllocation(compilation.getFramePointerSaveAreaOffset()).slots; + } + case FRAME_POINTER_RELOADED -> { + assert RUNTIME_FUNCTION.isChained(range) && range % 2 == 0 : range; + yield 0; + } + default -> + throw VMError.shouldNotReachHere("Unexpected: " + startMark.id); + }; + xdataSize.addAndGet(UNWIND_INFO.size(countOfCodes, RUNTIME_FUNCTION.isChained(range))); + pdataSize.addAndGet(RUNTIME_FUNCTION.SIZE); + })); + + /* Create .xdata and .pdata sections. */ + ByteBuffer xdataBuffer = ByteBuffer.allocate(xdataSize.get()).order(objectFile.getByteOrder()); + BasicProgbitsSectionImpl xdataImpl = new BasicProgbitsSectionImpl(xdataBuffer.array()); + ObjectFile.Section xdataSection = objectFile.newUserDefinedSection(".xdata", 4, xdataImpl); + + ByteBuffer pdataBuffer = ByteBuffer.allocate(pdataSize.get()).order(objectFile.getByteOrder()); + BasicProgbitsSectionImpl pdataImpl = new BasicProgbitsSectionImpl(pdataBuffer.array()); + objectFile.newUserDefinedSection(".pdata", 4, pdataImpl); + + /* Emit the content of .xdata and .pdata sections. */ + for (Pair entry : image.getCodeCache().getOrderedCompilations()) { + visitRanges(entry.getRight(), (range, startMark, end) -> { + if (RUNTIME_FUNCTION.isPrimary(range)) { + pdataBuffer.mark(); /* Mark position of the primary RUNTIME_FUNCTION entry. */ + } + + String symbolName = NativeImage.localSymbolNameForMethod(entry.getLeft()); + + /* Define a symbol for the UNWIND_INFO entry. */ + String unwindInfoSymbolName = RUNTIME_FUNCTION.unwindInfoSymbolPrefixFor(range) + symbolName; + objectFile.createDefinedSymbol(unwindInfoSymbolName, xdataSection, xdataBuffer.position(), 0, false, false); + + /* Emit the UNWIND_INFO entry for the range. */ + var compilation = (SharedCompilationResult) entry.getRight(); + switch (startMark.id) { + case PROLOGUE_START -> { + CodeMark[] prologueMarks = new CodeMark[MAX_PROLOGUE_MARKS]; + int markCount = collectPrologueMarks(compilation, prologueMarks); + + CodeMark lastMark = prologueMarks[markCount - 1]; + int sizeOfProlog = lastMark.pcOffset; + int countOfCodes = countOfPrologueCodes(compilation, prologueMarks, markCount); + byte frameRegister = 0; + int frameOffset = 0; + if (lastMark.id == PROLOGUE_SET_FRAME_POINTER) { + frameRegister = UNWIND_INFO.RBP; + if (SubstrateOptions.PreserveFramePointer.getValue()) { + frameOffset = compilation.getFramePointerSaveAreaOffset(); + } + } + UNWIND_INFO.emit(xdataBuffer, sizeOfProlog, countOfCodes, frameRegister, frameOffset, () -> { + /* Note that we emit unwind codes in reverse order. */ + for (int i = markCount - 1; i >= 0; i--) { + CodeMark mark = prologueMarks[i]; + switch (mark.id) { + case PROLOGUE_SET_FRAME_POINTER -> UNWIND_CODE.UWOP_SET_FPREG.emit(xdataBuffer, mark.pcOffset, 0); + case PROLOGUE_DECD_RSP -> UNWIND_CODE.forAllocation(compilation.getFrameSize()).emit(xdataBuffer, mark.pcOffset, compilation.getFrameSize()); + case PROLOGUE_PUSH_RBP -> UNWIND_CODE.UWOP_PUSH_NONVOL.emit(xdataBuffer, mark.pcOffset, UNWIND_INFO.RBP); + default -> throw VMError.shouldNotReachHere("Unexpected: " + mark.id); + } + } + }); + } + case FRAME_POINTER_SPILLED -> { + int framePointerSaveAreaOffset = compilation.getFramePointerSaveAreaOffset(); + int countOfCodes = UNWIND_CODE.UWOP_PUSH_NONVOL.slots + UNWIND_CODE.forAllocation(framePointerSaveAreaOffset).slots; + ByteBuffer primaryRuntimeFunctionEntry = pdataBuffer.asReadOnlyBuffer().reset().slice().order(pdataBuffer.order()); + UNWIND_INFO.emit(xdataBuffer, 0, countOfCodes, (byte) 0, 0, () -> { + UNWIND_CODE.forAllocation(framePointerSaveAreaOffset).emit(xdataBuffer, 0, framePointerSaveAreaOffset); + UNWIND_CODE.UWOP_PUSH_NONVOL.emit(xdataBuffer, 0, UNWIND_INFO.RBP); + }, () -> { + RUNTIME_FUNCTION.emitCopyOf(xdataImpl, xdataBuffer, symbolName, primaryRuntimeFunctionEntry); + }); + } + case FRAME_POINTER_RELOADED -> { + ByteBuffer primaryRuntimeFunctionEntry = pdataBuffer.asReadOnlyBuffer().reset().slice().order(pdataBuffer.order()); + UNWIND_INFO.emit(xdataBuffer, 0, 0, UNWIND_INFO.RBP, 0, () -> { + /* No unwind codes. */ + }, () -> { + RUNTIME_FUNCTION.emitCopyOf(xdataImpl, xdataBuffer, symbolName, primaryRuntimeFunctionEntry); + }); + } + default -> + throw VMError.shouldNotReachHere("Unexpected: " + startMark.id); + } + + /* Emit the RUNTIME_FUNCTION entry for the range. */ + RUNTIME_FUNCTION.emit(pdataImpl, pdataBuffer, symbolName, range, startMark.pcOffset, end); + }); + } + } + + private static final CodeMark START_MARK = new CodeMark(0, PROLOGUE_START); + + private interface RangeVisitor { + void visit(int range, CodeMark startMark, int end); + } + + /** Visits the ranges derived from marks using the specified visitor. */ + private static void visitRanges(CompilationResult compilation, RangeVisitor visitor) { + if (compilation.getTotalFrameSize() == FrameAccess.returnAddressSize()) { + return; /* No frame, no unwind info needed. */ + } + + int framePointerSaveAreaOffset = ((SharedCompilationResult) compilation).getFramePointerSaveAreaOffset(); + if (framePointerSaveAreaOffset < 0) { + /* There is no frame pointer, so there is only the primary range. */ + visitor.visit(RUNTIME_FUNCTION.PRIMARY_RANGE, START_MARK, compilation.getTargetCodeSize()); + return; + } + + CodeMark startMark = START_MARK; + int range = RUNTIME_FUNCTION.PRIMARY_RANGE; + for (CodeMark endMark : deriveRangeMarks(compilation, markId -> markId == FRAME_POINTER_SPILLED || markId == FRAME_POINTER_RELOADED)) { + visitor.visit(range++, startMark, endMark.pcOffset); + startMark = endMark; + } + visitor.visit(range, startMark, compilation.getTargetCodeSize()); + } + + /** Derives a refined list of range marks, ensuring no empty ranges. */ + private static List deriveRangeMarks(CompilationResult compilation, Predicate isRangeMark) { + /* We expect range marks to be in order of increasing offsets. */ + ArrayList marks = new ArrayList<>(); + for (CodeMark mark : compilation.getMarks()) { + if (!isRangeMark.test(mark.id)) { + continue; + } + assert marks.isEmpty() || marks.getLast().pcOffset <= mark.pcOffset : mark; + if (!marks.isEmpty() && marks.getLast().pcOffset == mark.pcOffset) { + marks.removeLast(); /* Skip empty range. */ + } + if (!marks.isEmpty() && marks.getLast().id == mark.id) { + continue; /* Coalesce same ranges. */ + } + marks.addLast(mark); + } + if (!marks.isEmpty() && marks.getLast().pcOffset == compilation.getTargetCodeSize()) { + marks.removeLast(); /* Skip empty range. */ + } + return marks; + } + + private static final int MAX_PROLOGUE_MARKS = 3; + + /** Collects prologue marks into a given array and returns the number of marks collected. */ + private static int collectPrologueMarks(CompilationResult compilation, CodeMark[] prologueMarks) { + /* + * We expect the prologue marks to be in the following order: [PROLOGUE_PUSH_RBP, + * PROLOGUE_DECD_RSP, PROLOGUE_SET_FRAME_POINTER], and to find at least one of them. + */ + assert prologueMarks.length >= MAX_PROLOGUE_MARKS; + int lastMarkIndex = 0; + loop: for (CodeMark mark : compilation.getMarks()) { + switch (mark.id) { + case PROLOGUE_PUSH_RBP -> { + CodeMark lastMark = prologueMarks[lastMarkIndex]; + assert lastMark == null : lastMark; + prologueMarks[lastMarkIndex] = mark; + } + case PROLOGUE_DECD_RSP -> { + CodeMark lastMark = prologueMarks[lastMarkIndex]; + assert lastMark == null || (lastMark.id == PROLOGUE_PUSH_RBP && lastMark.pcOffset <= mark.pcOffset) : lastMark; + prologueMarks[lastMark == null ? lastMarkIndex : ++lastMarkIndex] = mark; + } + case PROLOGUE_SET_FRAME_POINTER -> { + CodeMark lastMark = prologueMarks[lastMarkIndex]; + assert lastMark == null || ((lastMark.id == PROLOGUE_PUSH_RBP || lastMark.id == PROLOGUE_DECD_RSP) && lastMark.pcOffset <= mark.pcOffset) : lastMark; + prologueMarks[lastMark == null ? lastMarkIndex : ++lastMarkIndex] = mark; + } + case PROLOGUE_END -> { + CodeMark lastMark = prologueMarks[lastMarkIndex]; + assert lastMark != null && lastMark.pcOffset <= mark.pcOffset : lastMark; + break loop; + } + default -> { + } + } + } + assert prologueMarks[lastMarkIndex] != null : "no prologue marks"; + return lastMarkIndex + 1; + } + + /** + * Returns the number of {@linkplain UNWIND_CODE#slots slots} required for unwind codes + * corresponding to the given prologue marks. + */ + private static int countOfPrologueCodes(SharedCompilationResult compilation, CodeMark[] prologueMarks, int markCount) { + assert markCount <= prologueMarks.length; + int sum = 0; + for (int i = 0; i < markCount; i++) { + sum += switch (prologueMarks[i].id) { + case PROLOGUE_PUSH_RBP -> UNWIND_CODE.UWOP_PUSH_NONVOL.slots; + case PROLOGUE_DECD_RSP -> UNWIND_CODE.forAllocation(compilation.getFrameSize()).slots; + case PROLOGUE_SET_FRAME_POINTER -> UNWIND_CODE.UWOP_SET_FPREG.slots; + default -> throw VMError.shouldNotReachHere("Unexpected: " + prologueMarks[i].id); + }; + } + return sum; + } +} + +/** + * Helper class for emitting RUNTIME_FUNCTION structs and related relocations. + * + *
+ * {@code
+ *  typedef struct _RUNTIME_FUNCTION {
+ *      unsigned long BeginAddress;
+ *      unsigned long EndAddress;
+ *      unsigned long UnwindData;
+ *  } RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;
+ * }
+ * 
+ */ +class RUNTIME_FUNCTION { + static final int SIZE = 12; + + static final int PRIMARY_RANGE = 0; + + static boolean isPrimary(int range) { + return range == PRIMARY_RANGE; + } + + static boolean isChained(int range) { + return !isPrimary(range); + } + + static String unwindInfoSymbolPrefixFor(int range) { + /* We use the same prefixes as MSVC. */ + return isPrimary(range) ? "$unwind$" : "$chain$" + (range - 1) + "$"; + } + + static void emitCopyOf(BasicProgbitsSectionImpl section, ByteBuffer buffer, String symbolName, ByteBuffer runtimeFunction) { + int range = runtimeFunction.position() / RUNTIME_FUNCTION.SIZE; + int start = runtimeFunction.getInt(); + int end = runtimeFunction.getInt(); + emit(section, buffer, symbolName, range, start, end); + } + + static void emit(BasicProgbitsSectionImpl section, ByteBuffer buffer, String symbolName, int range, int start, int end) { + String unwindInfoSymbolName = unwindInfoSymbolPrefixFor(range) + symbolName; + emit(section, buffer, symbolName, start, end, unwindInfoSymbolName); + } + + private static void emit(BasicProgbitsSectionImpl section, ByteBuffer buffer, String symbolName, int start, int end, String unwindInfoSymbolName) { + assert buffer.position() % 4 == 0 : "wrong alignment"; + assert start < end : "invalid range"; + int offset = buffer.position(); + section.markRelocationSite(offset, ObjectFile.RelocationKind.ADDR32NB_4, symbolName, start); + section.markRelocationSite(offset + 4, ObjectFile.RelocationKind.ADDR32NB_4, symbolName, end); + section.markRelocationSite(offset + 8, ObjectFile.RelocationKind.ADDR32NB_4, unwindInfoSymbolName, 0); + buffer.position(offset + SIZE); + } +} + +/** + * Helper class for emitting UNWIND_INFO structs. + * + *
+ * {@code
+ *  typedef struct _UNWIND_INFO {
+ *      unsigned char Version       : 3;
+ *      unsigned char Flags         : 5;
+ *      unsigned char SizeOfProlog;
+ *      unsigned char CountOfCodes;
+ *      unsigned char FrameRegister : 4;
+ *      unsigned char FrameOffset   : 4;
+ *      UNWIND_CODE UnwindCode[1];
+ *  //  UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
+ *  //  union {
+ *  //      OPTIONAL unsigned long ExceptionHandler;
+ *  //      OPTIONAL unsigned long FunctionEntry;
+ *  //  };
+ *  //  OPTIONAL unsigned long ExceptionData[];
+ *  } UNWIND_INFO, *PUNWIND_INFO;
+ * }
+ * 
+ */ +class UNWIND_INFO { + private static final int SIZE = 4; + + static int size(int countOfCodes, boolean chained) { + /* Space is always reserved for an even number of unwind codes. */ + int size = SIZE + (countOfCodes + 1 & ~1) * UNWIND_CODE.SIZE; + if (chained) { + size += RUNTIME_FUNCTION.SIZE; + } + return size; + } + + static final byte VERSION = 1; + + static final byte UNW_FLAG_NHANDLER = 0x0; + static final byte UNW_FLAG_CHAININFO = 0x4; + + static final byte RBP = 5; + + interface UnwindCodeEmitter { + void emit(); + } + + interface ChainedInfoEmitter { + void emit(); + } + + static void emit(ByteBuffer buffer, int sizeOfProlog, int countOfCodes, byte frameRegister, int frameOffset, UnwindCodeEmitter unwindCodes) { + emit(buffer, VERSION, UNW_FLAG_NHANDLER, sizeOfProlog, countOfCodes, frameRegister, frameOffset, unwindCodes); + } + + static void emit(ByteBuffer buffer, int sizeOfProlog, int countOfCodes, byte frameRegister, int frameOffset, UnwindCodeEmitter unwindCodes, ChainedInfoEmitter chainedInfo) { + emit(buffer, VERSION, UNW_FLAG_CHAININFO, sizeOfProlog, countOfCodes, frameRegister, frameOffset, unwindCodes); + int chainedInfoPosition = buffer.position(); + chainedInfo.emit(); + assert buffer.position() == chainedInfoPosition + RUNTIME_FUNCTION.SIZE : "wrong chained info emitted"; + } + + private static void emit(ByteBuffer buffer, byte version, byte flags, int sizeOfProlog, int countOfCodes, byte frameRegister, int frameOffset, UnwindCodeEmitter unwindCodes) { + assert buffer.position() % 4 == 0 : "wrong alignment"; + assert version < 8 : version; + assert flags <= UNW_FLAG_CHAININFO : flags; + assert frameRegister < 16 : frameRegister; + assert frameOffset <= 240 && frameOffset % 16 == 0 : frameOffset; + buffer.put(NumUtil.safeToUByte(flags << 3 | version)); + buffer.put(NumUtil.safeToUByte(sizeOfProlog)); + buffer.put(NumUtil.safeToUByte(countOfCodes)); + buffer.put(NumUtil.safeToUByte(frameOffset | frameRegister)); + int firstUnwindCode = buffer.position(); + unwindCodes.emit(); + assert buffer.position() == firstUnwindCode + countOfCodes * UNWIND_CODE.SIZE : "wrong number of unwind codes emitted"; + if ((countOfCodes & 1) != 0) { + buffer.putShort((short) 0); /* Padding. */ + } + } +} + +/** + * Helper class for emitting UNWIND_CODE structs. + * + *
+ * {@code
+ *  typedef union _UNWIND_CODE {
+ *      struct {
+ *          unsigned char CodeOffset;
+ *          unsigned char UnwindOp : 4;
+ *          unsigned char OpInfo   : 4;
+ *      };
+ *      unsigned short FrameOffset;
+ *  } UNWIND_CODE, *PUNWIND_CODE;
+ * }
+ * 
+ */ +enum UNWIND_CODE { + UWOP_PUSH_NONVOL(0, 1) { + @Override + void emit(ByteBuffer buffer, int codeOffset, int info) { + assert info < 16 : info; + super.emit(buffer, codeOffset, info); + } + }, + UWOP_ALLOC_HUGE(1, 3) { + @Override + void emit(ByteBuffer buffer, int codeOffset, int info) { + assert ALLOC_LARGE_LIMIT < info && info % 8 == 0 : info; + super.emit(buffer, codeOffset, 1); + buffer.putInt(NumUtil.safeToUInt(info)); + } + }, + UWOP_ALLOC_LARGE(1, 2) { + @Override + void emit(ByteBuffer buffer, int codeOffset, int info) { + assert ALLOC_SMALL_LIMIT < info && info <= ALLOC_LARGE_LIMIT && info % 8 == 0 : info; + super.emit(buffer, codeOffset, 0); + buffer.putShort(NumUtil.safeToUShort(info / 8)); + } + }, + UWOP_ALLOC_SMALL(2, 1) { + @Override + void emit(ByteBuffer buffer, int codeOffset, int info) { + assert 0 < info && info <= ALLOC_SMALL_LIMIT && info % 8 == 0 : info; + super.emit(buffer, codeOffset, info / 8 - 1); + } + }, + UWOP_ALLOC_ZERO(2, 0) { + @Override + void emit(ByteBuffer buffer, int codeOffset, int info) { + assert info == 0 : info; + /* Nothing to emit. */ + } + }, + UWOP_SET_FPREG(3, 1) { + @Override + void emit(ByteBuffer buffer, int codeOffset, int info) { + assert info == 0 : info; + super.emit(buffer, codeOffset, info); + } + }; + + static final int SIZE = 2; + + static final int ALLOC_SMALL_LIMIT = 128; + static final int ALLOC_LARGE_LIMIT = 512 * 1024 - 8; + + private final int op; + final int slots; + + UNWIND_CODE(int op, int slots) { + this.op = op; + this.slots = slots; + } + + void emit(ByteBuffer buffer, int codeOffset, int info) { + buffer.put(NumUtil.safeToUByte(codeOffset)); + buffer.put(NumUtil.safeToUByte(info << 4 | op)); + } + + static UNWIND_CODE forAllocation(int size) { + assert size >= 0 : size; + if (size == 0) { + return UWOP_ALLOC_ZERO; + } else if (size <= ALLOC_SMALL_LIMIT) { + return UWOP_ALLOC_SMALL; + } else if (size <= ALLOC_LARGE_LIMIT) { + return UWOP_ALLOC_LARGE; + } else { + return UWOP_ALLOC_HUGE; + } + } +}