Skip to content

Commit 618927c

Browse files
authored
Add GC support for OCaml (#21)
1 parent bc5ed56 commit 618927c

File tree

13 files changed

+317
-16
lines changed

13 files changed

+317
-16
lines changed

clang/lib/CodeGen/BackendUtil.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
#include "llvm/Transforms/Scalar/GVN.h"
8585
#include "llvm/Transforms/Scalar/JumpThreading.h"
8686
#include "llvm/Transforms/Scalar/LowerMatrixIntrinsics.h"
87+
#include "llvm/Transforms/Scalar/RewriteStatepointsForGC.h"
8788
#include "llvm/Transforms/Utils.h"
8889
#include "llvm/Transforms/Utils/CanonicalizeAliases.h"
8990
#include "llvm/Transforms/Utils/Debugify.h"
@@ -954,6 +955,17 @@ void EmitAssemblyHelper::RunOptimizationPipeline(
954955
});
955956
}
956957

958+
// TODO: Do this in a location that is more appropriate (LLVM instead of
959+
// Clang). Also, determine a better place for this in the pipeline, since
960+
// we don't want other transformations to treat values that may be relocated
961+
// by the GC in an unsound way.
962+
PB.registerOptimizerLastEPCallback(
963+
[](ModulePassManager &MPM, OptimizationLevel Level) {
964+
if (Level != OptimizationLevel::O0) {
965+
MPM.addPass(RewriteStatepointsForGC());
966+
}
967+
});
968+
957969
// Register callbacks to schedule sanitizer passes at the appropriate part
958970
// of the pipeline.
959971
if (LangOpts.Sanitize.has(SanitizerKind::LocalBounds))

llvm/include/llvm/CodeGen/AsmPrinter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ class AsmPrinter : public MachineFunctionPass {
524524
void emitGlobalGOTEquivs();
525525

526526
/// Emit the stack maps.
527-
void emitStackMaps();
527+
void emitStackMaps(Module &M);
528528

529529
//===------------------------------------------------------------------===//
530530
// Overridable Hooks

llvm/include/llvm/CodeGen/GCMetadataPrinter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class GCMetadataPrinter {
6464
/// Called when the stack maps are generated. Return true if
6565
/// stack maps with a custom format are generated. Otherwise
6666
/// returns false and the default format will be used.
67-
virtual bool emitStackMaps(StackMaps &SM, AsmPrinter &AP) { return false; }
67+
virtual bool emitStackMaps(Module &M, StackMaps &SM, AsmPrinter &AP) { return false; }
6868
};
6969

7070
} // end namespace llvm

llvm/include/llvm/CodeGen/LinkAllAsmWriterComponents.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ namespace {
3232

3333
llvm::linkOcamlGCPrinter();
3434
llvm::linkErlangGCPrinter();
35+
llvm::linkOxCamlGCPrinter();
3536

3637
}
3738
} ForceAsmWriterLinking; // Force link by creating a global definition.

llvm/include/llvm/CodeGen/StackMaps.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,23 +310,29 @@ class StackMaps {
310310
using ConstantPool = MapVector<uint64_t, uint64_t>;
311311

312312
struct FunctionInfo {
313+
uint64_t StaticStackSize = 0;
313314
uint64_t StackSize = 0;
314315
uint64_t RecordCount = 1;
315316

316317
FunctionInfo() = default;
317-
explicit FunctionInfo(uint64_t StackSize) : StackSize(StackSize) {}
318+
explicit FunctionInfo(uint64_t StaticStackSize, uint64_t StackSize)
319+
: StaticStackSize(StaticStackSize), StackSize(StackSize) {}
318320
};
319321

320322
struct CallsiteInfo {
323+
const MCSymbol *CSLabel = nullptr;
321324
const MCExpr *CSOffsetExpr = nullptr;
325+
const FunctionInfo CSFunctionInfo;
322326
uint64_t ID = 0;
323327
LocationVec Locations;
324328
LiveOutVec LiveOuts;
325329

326330
CallsiteInfo() = default;
327-
CallsiteInfo(const MCExpr *CSOffsetExpr, uint64_t ID,
331+
CallsiteInfo(const MCSymbol *CSLabel, const MCExpr *CSOffsetExpr,
332+
const FunctionInfo CSFunctionInfo, uint64_t ID,
328333
LocationVec &&Locations, LiveOutVec &&LiveOuts)
329-
: CSOffsetExpr(CSOffsetExpr), ID(ID), Locations(std::move(Locations)),
334+
: CSLabel(CSLabel), CSOffsetExpr(CSOffsetExpr),
335+
CSFunctionInfo(CSFunctionInfo), ID(ID), Locations(std::move(Locations)),
330336
LiveOuts(std::move(LiveOuts)) {}
331337
};
332338

llvm/include/llvm/IR/BuiltinGCs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ void linkOcamlGCPrinter();
2828
/// Creates an erlang-compatible metadata printer.
2929
void linkErlangGCPrinter();
3030

31+
/// Creates an oxcaml-compatible metadata printer.
32+
void linkOxCamlGCPrinter();
33+
3134
} // namespace llvm
3235

3336
#endif // LLVM_IR_BUILTINGCS_H

llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
#include "llvm/BinaryFormat/ELF.h"
4040
#include "llvm/CodeGen/GCMetadata.h"
4141
#include "llvm/CodeGen/GCMetadataPrinter.h"
42+
#include "llvm/IR/BuiltinGCs.h"
43+
#include "llvm/CodeGen/LinkAllAsmWriterComponents.h"
4244
#include "llvm/CodeGen/MachineBasicBlock.h"
4345
#include "llvm/CodeGen/MachineConstantPool.h"
4446
#include "llvm/CodeGen/MachineDominators.h"
@@ -2187,7 +2189,7 @@ bool AsmPrinter::doFinalization(Module &M) {
21872189
// text sections come after debug info has been emitted. This matters for
21882190
// stack maps as they are arbitrary data, and may even have a custom format
21892191
// through user plugins.
2190-
emitStackMaps();
2192+
emitStackMaps(M);
21912193

21922194
// Finalize debug and EH information.
21932195
for (const HandlerInfo &HI : Handlers) {
@@ -3857,7 +3859,7 @@ GCMetadataPrinter *AsmPrinter::getOrCreateGCPrinter(GCStrategy &S) {
38573859
report_fatal_error("no GCMetadataPrinter registered for GC: " + Twine(Name));
38583860
}
38593861

3860-
void AsmPrinter::emitStackMaps() {
3862+
void AsmPrinter::emitStackMaps(Module &M) {
38613863
GCModuleInfo *MI = getAnalysisIfAvailable<GCModuleInfo>();
38623864
assert(MI && "AsmPrinter didn't require GCModuleInfo?");
38633865
bool NeedsDefault = false;
@@ -3867,7 +3869,7 @@ void AsmPrinter::emitStackMaps() {
38673869
else
38683870
for (const auto &I : *MI) {
38693871
if (GCMetadataPrinter *MP = getOrCreateGCPrinter(*I))
3870-
if (MP->emitStackMaps(SM, *this))
3872+
if (MP->emitStackMaps(M, SM, *this))
38713873
continue;
38723874
// The strategy doesn't have printer or doesn't emit custom stack maps.
38733875
// Use the default format.

llvm/lib/CodeGen/AsmPrinter/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ add_llvm_component_library(LLVMAsmPrinter
2121
EHStreamer.cpp
2222
ErlangGCPrinter.cpp
2323
OcamlGCPrinter.cpp
24+
OxCamlGCPrinter.cpp
2425
PseudoProbePrinter.cpp
2526
WinCFGuard.cpp
2627
WinException.cpp
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
//===- OxCamlGCPrinter.cpp - OxCaml frametable emitter --------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This file implements printing the assembly code for an OxCaml frametable.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "llvm/ADT/STLExtras.h"
14+
#include "llvm/ADT/SmallString.h"
15+
#include "llvm/ADT/Twine.h"
16+
#include "llvm/CodeGen/AsmPrinter.h"
17+
#include "llvm/CodeGen/GCMetadata.h"
18+
#include "llvm/CodeGen/GCMetadataPrinter.h"
19+
#include "llvm/CodeGen/StackMaps.h"
20+
#include "llvm/IR/BuiltinGCs.h"
21+
#include "llvm/IR/DataLayout.h"
22+
#include "llvm/IR/Function.h"
23+
#include "llvm/IR/Mangler.h"
24+
#include "llvm/IR/Module.h"
25+
#include "llvm/IR/Statepoint.h"
26+
#include "llvm/MC/MCContext.h"
27+
#include "llvm/MC/MCDirectives.h"
28+
#include "llvm/MC/MCStreamer.h"
29+
#include "llvm/Support/ErrorHandling.h"
30+
#include "llvm/Target/TargetLoweringObjectFile.h"
31+
#include <array>
32+
#include <cctype>
33+
#include <cstddef>
34+
#include <cstdint>
35+
#include <string>
36+
37+
using namespace llvm;
38+
39+
namespace {
40+
41+
class OxCamlGCMetadataPrinter : public GCMetadataPrinter {
42+
public:
43+
void beginAssembly(Module &M, GCModuleInfo &Info, AsmPrinter &AP) override;
44+
void finishAssembly(Module &M, GCModuleInfo &Info, AsmPrinter &AP) override;
45+
bool emitStackMaps(Module &M, StackMaps &SM, AsmPrinter &AP) override;
46+
};
47+
48+
} // end anonymous namespace
49+
50+
static GCMetadataPrinterRegistry::Add<OxCamlGCMetadataPrinter>
51+
Y("oxcaml", "OxCaml frametable printer");
52+
53+
void llvm::linkOxCamlGCPrinter() {}
54+
55+
static std::string camlGlobalSymName(const Module &M, const char *Id) {
56+
if (Metadata *ModuleMD = M.getModuleFlag(StringRef("oxcaml_module"))) {
57+
if (MDString *Str = dyn_cast<MDString>(ModuleMD)) {
58+
StringRef ModuleName = Str->getString();
59+
60+
std::string SymName;
61+
SymName += "caml";
62+
SymName += ModuleName;
63+
SymName += "__";
64+
SymName += Id;
65+
66+
return SymName;
67+
}
68+
}
69+
70+
report_fatal_error("Module name not provided for OxCaml GC!");
71+
}
72+
73+
static void emitCamlGlobal(const Module &M, MCStreamer &OS, const char *Id) {
74+
std::string SymName = camlGlobalSymName(M, Id);
75+
76+
SmallString<128> TmpStr;
77+
Mangler::getNameWithPrefix(TmpStr, SymName, M.getDataLayout());
78+
79+
MCSymbol *Sym = OS.getContext().getOrCreateSymbol(TmpStr);
80+
81+
OS.emitSymbolAttribute(Sym, MCSA_Global);
82+
OS.emitLabel(Sym);
83+
}
84+
85+
void OxCamlGCMetadataPrinter::beginAssembly(Module &M, GCModuleInfo &Info,
86+
AsmPrinter &AP) {
87+
AP.OutStreamer->switchSection(AP.getObjFileLowering().getTextSection());
88+
emitCamlGlobal(M, *(AP.OutStreamer), "code_begin");
89+
90+
AP.OutStreamer->switchSection(AP.getObjFileLowering().getDataSection());
91+
emitCamlGlobal(M, *(AP.OutStreamer), "data_begin");
92+
}
93+
94+
95+
void OxCamlGCMetadataPrinter::finishAssembly(Module &M, GCModuleInfo &Info,
96+
AsmPrinter &AP) {
97+
AP.OutStreamer->switchSection(AP.getObjFileLowering().getTextSection());
98+
emitCamlGlobal(M, *(AP.OutStreamer), "code_end");
99+
100+
AP.OutStreamer->switchSection(AP.getObjFileLowering().getDataSection());
101+
emitCamlGlobal(M, *(AP.OutStreamer), "data_end");
102+
}
103+
104+
/// Map LLVM DWARF register numbers to OxCaml register map.
105+
/// * See llvm/lib/Target/X86/X86RegisterInfo.td for DWARF register numbers.
106+
/// * See backend/amd64/proc.ml for the OxCaml register map.
107+
108+
// TODO: This is target-specific and should probably live in a
109+
// target-specific location.
110+
111+
// Directly taken from [Reg_class.gpr_dwarf_reg_numbers]:
112+
// https://github.com/oxcaml/oxcaml/blob/main/backend/amd64/reg_class.ml#L26
113+
// Note that R14 and R15 are added for completeness
114+
static constexpr std::array<unsigned, 16> GPR_OxCamlToDwarf =
115+
{ 0, 3, 5, 4, 1, 2, 8, 9, 12, 13, 10, 11, 6, 14, 15 };
116+
117+
static constexpr auto GPR_DwarfToOxCaml = []() {
118+
std::array<unsigned, 16> result{};
119+
for (size_t ocaml_idx = 0; ocaml_idx < GPR_OxCamlToDwarf.size(); ++ocaml_idx) {
120+
unsigned dwarf_reg = GPR_OxCamlToDwarf[ocaml_idx];
121+
if (dwarf_reg < result.size()) {
122+
result[dwarf_reg] = ocaml_idx;
123+
}
124+
}
125+
return result;
126+
}();
127+
128+
static const unsigned XMMBeginOxCaml = 100;
129+
static const unsigned XMMBeginDwarf = 17;
130+
static const unsigned XMMEndDwarf = 32;
131+
132+
static unsigned mapLLVMDwarfRegToOxCamlIndex(unsigned DwarfRegNum) {
133+
if (DwarfRegNum < GPR_DwarfToOxCaml.size()) {
134+
return GPR_DwarfToOxCaml[DwarfRegNum];
135+
} else if (XMMBeginDwarf <= DwarfRegNum && DwarfRegNum <= XMMEndDwarf) {
136+
return DwarfRegNum - XMMBeginDwarf + XMMBeginOxCaml;
137+
} else {
138+
report_fatal_error("Unrecognised DWARF register for use in OxCaml frametable: "
139+
+ Twine(DwarfRegNum));
140+
}
141+
}
142+
143+
bool OxCamlGCMetadataPrinter::emitStackMaps(Module &M, StackMaps &SM, AsmPrinter &AP) {
144+
MCStreamer &OS = *AP.OutStreamer;
145+
unsigned PtrSize = M.getDataLayout().getPointerSize(); // Can only be 8 for now
146+
147+
OS.switchSection(AP.getObjFileLowering().getDataSection());
148+
149+
emitCamlGlobal(M, OS, "frametable");
150+
151+
// Number of records
152+
OS.emitInt64(SM.getCSInfos().size());
153+
154+
for (const auto &CSI : SM.getCSInfos()) {
155+
// From runtime/frame_descriptors.h:
156+
// https://github.com/oxcaml/oxcaml/blob/main/runtime/caml/frame_descriptors.h#L63
157+
//
158+
// typedef struct {
159+
// int32_t retaddr_rel; /* offset of return address from &retaddr_rel */
160+
// uint16_t frame_data; /* frame size and various flags */
161+
// uint16_t num_live;
162+
// uint16_t live_ofs[num_live];
163+
// } frame_descr;
164+
165+
// retaddr_rel
166+
MCSymbol *Here = OS.getContext().createTempSymbol();
167+
OS.emitLabel(Here);
168+
const MCExpr *RelativeAddr = MCBinaryExpr::createSub(
169+
MCSymbolRefExpr::create(CSI.CSLabel, OS.getContext()),
170+
MCSymbolRefExpr::create(Here, OS.getContext()),
171+
OS.getContext());
172+
OS.emitValue(RelativeAddr, 4);
173+
174+
// frame_data
175+
uint64_t FrameSize = CSI.CSFunctionInfo.StaticStackSize;
176+
if (CSI.ID != StatepointDirectives::DefaultStatepointID)
177+
FrameSize += CSI.ID; // Stack offset from OxCaml
178+
FrameSize += PtrSize; // Return address
179+
180+
if (FrameSize >= 1 << 16)
181+
report_fatal_error("Long frames not supported for OxCaml GC: FrameSize = "
182+
+ Twine(FrameSize));
183+
OS.emitInt16(FrameSize);
184+
185+
// num_live
186+
uint64_t LiveCount = 0;
187+
for (const auto &Loc : CSI.Locations) {
188+
if (Loc.Type == StackMaps::Location::Register ||
189+
Loc.Type == StackMaps::Location::Direct ||
190+
Loc.Type == StackMaps::Location::Indirect) {
191+
LiveCount++;
192+
}
193+
}
194+
LiveCount += CSI.LiveOuts.size();
195+
196+
if (LiveCount >= 1 << 16) {
197+
// Very rude!
198+
report_fatal_error("Long frames not supported for OxCaml GC: LiveCount = "
199+
+ Twine(LiveCount));
200+
}
201+
OS.emitInt16(LiveCount);
202+
203+
// live_ofs
204+
for (const auto &Loc : CSI.Locations) {
205+
if (Loc.Type == StackMaps::Location::Register) {
206+
// Register indices are tagged (2n+1) and follow the OxCaml register
207+
// map (see `mapLLVMDwarfRegToOxCamlIndex`)
208+
unsigned DwarfRegNum = Loc.Reg;
209+
unsigned OxCamlIndex = mapLLVMDwarfRegToOxCamlIndex(DwarfRegNum);
210+
uint16_t EncodedReg = (OxCamlIndex << 1) + 1;
211+
OS.emitInt16(EncodedReg);
212+
} else if (Loc.Type == StackMaps::Location::Direct ||
213+
Loc.Type == StackMaps::Location::Indirect) {
214+
// For stack locations (Direct/Indirect): emit offset directly
215+
int64_t Offset = Loc.Offset;
216+
217+
// BP-relative addressing -> SP
218+
if (Offset < 0) {
219+
int64_t TempFrameSize =
220+
FrameSize - PtrSize /* return address */ - PtrSize /* pushed BP */;
221+
Offset += TempFrameSize;
222+
}
223+
224+
if (Offset < -(1 << 15) || Offset >= (1 << 15)) {
225+
// Very rude!
226+
report_fatal_error("Stack offset too large for OxCaml frametable: "
227+
+ Twine(Offset));
228+
}
229+
OS.emitInt16(static_cast<uint16_t>(Offset));
230+
} else {
231+
// TODO: Do we need anything else here?
232+
}
233+
}
234+
235+
for (const auto &LO : CSI.LiveOuts) {
236+
unsigned OxCamlIndex = mapLLVMDwarfRegToOxCamlIndex(LO.DwarfRegNum);
237+
uint16_t EncodedReg = (OxCamlIndex << 1) + 1;
238+
OS.emitInt16(EncodedReg);
239+
}
240+
241+
OS.emitValueToAlignment(Align(PtrSize));
242+
}
243+
244+
OS.addBlankLine();
245+
return true;
246+
}

0 commit comments

Comments
 (0)