Skip to content

Commit 53a410e

Browse files
[EVM] Add C API to compile files with debug info separated
This patch introduces LLVMTargetMachineEmitToMemoryBufferWithDbg, extending LLVMTargetMachineEmitToMemoryBuffer with an extra output buffer for debug information. For inputs with debug info, all .debug sections are emitted into a separate object. For inputs without debug info, a minimal DWARF object containing only the symbol table is generated. When emitting assembly output, the debug buffer is empty. Signed-off-by: Vladimir Radosavljevic <vr@matterlabs.dev>
1 parent 613af2a commit 53a410e

File tree

4 files changed

+238
-0
lines changed

4 files changed

+238
-0
lines changed

llvm/include/llvm-c/TargetMachine.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,15 @@ LLVM_C_ABI LLVMBool LLVMTargetMachineEmitToMemoryBuffer(
228228
LLVMTargetMachineRef T, LLVMModuleRef M, LLVMCodeGenFileType codegen,
229229
char **ErrorMessage, LLVMMemoryBufferRef *OutMemBuf);
230230

231+
// EVM local begin
232+
/** Compile the LLVM IR stored in \p M and store the result in \p OutMemBuf.
233+
Additionally, if debug information is emitted, store it in \p DbgMemBuf. */
234+
LLVM_C_ABI LLVMBool LLVMTargetMachineEmitToMemoryBufferWithDbg(
235+
LLVMTargetMachineRef T, LLVMModuleRef M, LLVMCodeGenFileType codegen,
236+
char **ErrorMessage, LLVMMemoryBufferRef *OutMemBuf,
237+
LLVMMemoryBufferRef *DbgMemBuf);
238+
// EVM local end
239+
231240
/*===-- Triple ------------------------------------------------------------===*/
232241
/** Get a triple for the host machine as a string. The result needs to be
233242
disposed with LLVMDisposeMessage. */

llvm/lib/Target/TargetMachineC.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,63 @@ LLVMBool LLVMTargetMachineEmitToMemoryBuffer(LLVMTargetMachineRef T,
349349
return Result;
350350
}
351351

352+
// EVM local begin
353+
static LLVMBool
354+
LLVMTargetMachineEmitWithDbg(LLVMTargetMachineRef T, LLVMModuleRef M,
355+
raw_pwrite_stream &OS, raw_pwrite_stream &DOS,
356+
LLVMCodeGenFileType codegen, char **ErrorMessage) {
357+
TargetMachine *TM = unwrap(T);
358+
Module *Mod = unwrap(M);
359+
360+
legacy::PassManager pass;
361+
362+
std::string error;
363+
364+
Mod->setDataLayout(TM->createDataLayout());
365+
366+
CodeGenFileType ft = CodeGenFileType::Null;
367+
switch (codegen) {
368+
case LLVMAssemblyFile:
369+
ft = CodeGenFileType::AssemblyFile;
370+
break;
371+
default:
372+
ft = CodeGenFileType::ObjectFile;
373+
break;
374+
}
375+
if (TM->addPassesToEmitFile(pass, OS, &DOS, ft)) {
376+
error = "TargetMachine can't emit a file of this type";
377+
*ErrorMessage = strdup(error.c_str());
378+
return true;
379+
}
380+
381+
pass.run(*Mod);
382+
383+
OS.flush();
384+
DOS.flush();
385+
return false;
386+
}
387+
388+
LLVMBool LLVMTargetMachineEmitToMemoryBufferWithDbg(
389+
LLVMTargetMachineRef T, LLVMModuleRef M, LLVMCodeGenFileType codegen,
390+
char **ErrorMessage, LLVMMemoryBufferRef *OutMemBuf,
391+
LLVMMemoryBufferRef *DbgMemBuf) {
392+
SmallString<0> CodeString;
393+
raw_svector_ostream OStream(CodeString);
394+
SmallString<0> DbgString;
395+
raw_svector_ostream DOS(DbgString);
396+
bool Result =
397+
LLVMTargetMachineEmitWithDbg(T, M, OStream, DOS, codegen, ErrorMessage);
398+
399+
StringRef Data = OStream.str();
400+
*OutMemBuf =
401+
LLVMCreateMemoryBufferWithMemoryRangeCopy(Data.data(), Data.size(), "");
402+
StringRef DbgData = DOS.str();
403+
*DbgMemBuf = LLVMCreateMemoryBufferWithMemoryRangeCopy(DbgData.data(),
404+
DbgData.size(), "");
405+
return Result;
406+
}
407+
// EVM local end
408+
352409
char *LLVMGetDefaultTargetTriple(void) {
353410
return strdup(sys::getDefaultTargetTriple().c_str());
354411
}

llvm/unittests/Target/EVM/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ set(LLVM_LINK_COMPONENTS
1919

2020
add_llvm_target_unittest(EVMTests
2121
BytecodeSize.cpp
22+
EmitToMemoryBufferWithDbg.cpp
2223
SpillRegion.cpp
2324
StackShuffler.cpp
2425
StackModel.cpp
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
//===- EmitToMemoryBufferWithDbg.cpp --------------------------------------===//
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+
#include "llvm-c/Core.h"
10+
#include "llvm-c/IRReader.h"
11+
#include "llvm-c/Target.h"
12+
#include "llvm-c/TargetMachine.h"
13+
#include "llvm/ADT/StringRef.h"
14+
#include "llvm/IR/LLVMContext.h"
15+
#include "llvm/IRReader/IRReader.h"
16+
#include "llvm/Object/ObjectFile.h"
17+
#include "llvm/Support/ErrorHandling.h"
18+
#include "llvm/Support/raw_ostream.h"
19+
#include "gtest/gtest.h"
20+
21+
using namespace llvm;
22+
23+
namespace {
24+
25+
class EmitToMemoryBufferWithDbgTest : public testing::Test {
26+
public:
27+
static const char *IRStringWithDbg;
28+
static const char *IRStringWithoutDbg;
29+
LLVMTargetMachineRef TM = nullptr;
30+
LLVMContextRef Ctx = nullptr;
31+
LLVMModuleRef M = nullptr;
32+
LLVMMemoryBufferRef OutBuf = nullptr;
33+
LLVMMemoryBufferRef DbgBuf = nullptr;
34+
35+
void createModuleAndCompile(StringRef IR, LLVMCodeGenFileType codegen) {
36+
LLVMMemoryBufferRef Buf = LLVMCreateMemoryBufferWithMemoryRange(
37+
IR.data(), IR.size(), "test", true);
38+
39+
char *ErrMsg = nullptr;
40+
if (LLVMParseIRInContext(Ctx, Buf, &M, &ErrMsg))
41+
report_fatal_error("Failed to parse IR: " + StringRef(ErrMsg));
42+
43+
if (LLVMTargetMachineEmitToMemoryBufferWithDbg(TM, M, codegen, &ErrMsg,
44+
&OutBuf, &DbgBuf))
45+
report_fatal_error("EmitToMemoryBufferWithDbg failed: " +
46+
StringRef(ErrMsg));
47+
}
48+
49+
static void SetUpTestSuite() {
50+
LLVMInitializeEVMTargetInfo();
51+
LLVMInitializeEVMTarget();
52+
LLVMInitializeEVMTargetMC();
53+
LLVMInitializeEVMAsmPrinter();
54+
}
55+
56+
void SetUp() override {
57+
LLVMTargetRef T = nullptr;
58+
char *ErrMsg = nullptr;
59+
if (LLVMGetTargetFromTriple("evm", &T, &ErrMsg))
60+
report_fatal_error("Failed to get target for triple 'evm': " +
61+
StringRef(ErrMsg));
62+
63+
TM = LLVMCreateTargetMachine(T, "evm",
64+
/*CPU=*/"",
65+
/*Features=*/"", LLVMCodeGenLevelDefault,
66+
LLVMRelocDefault, LLVMCodeModelDefault);
67+
if (!TM)
68+
report_fatal_error("Failed to create TargetMachine for EVM target");
69+
Ctx = LLVMContextCreate();
70+
}
71+
72+
void TearDown() override {
73+
LLVMDisposeMemoryBuffer(OutBuf);
74+
LLVMDisposeMemoryBuffer(DbgBuf);
75+
LLVMDisposeModule(M);
76+
LLVMContextDispose(Ctx);
77+
LLVMDisposeTargetMachine(TM);
78+
}
79+
};
80+
81+
const char *EmitToMemoryBufferWithDbgTest::IRStringWithDbg = R"IR(
82+
target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256"
83+
target triple = "evm"
84+
85+
define i256 @foo(i256 %0, i256 %1) !dbg !4 {
86+
%add1 = add nsw i256 %0, 5, !dbg !7
87+
%add2 = add nsw i256 %1, 3, !dbg !8
88+
%mul = mul nsw i256 %add2, %add1, !dbg !9
89+
%add3 = add nsw i256 %add2, %add1, !dbg !10
90+
%add4 = add nsw i256 %add3, %mul, !dbg !11
91+
ret i256 %add4, !dbg !12
92+
}
93+
94+
!llvm.dbg.cu = !{!0}
95+
!llvm.module.flags = !{!2, !3}
96+
97+
!0 = distinct !DICompileUnit(language: DW_LANG_Assembly, file: !1, isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false)
98+
!1 = !DIFile(filename: "Test.sol", directory: "/tmp")
99+
!2 = !{i32 7, !"Dwarf Version", i32 5}
100+
!3 = !{i32 2, !"Debug Info Version", i32 3}
101+
!4 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, flags: DIFlagPrototyped | DIFlagAllCallsDescribed, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0)
102+
!5 = !DISubroutineType(types: !6)
103+
!6 = !{}
104+
!7 = !DILocation(line: 2, column: 7, scope: !4)
105+
!8 = !DILocation(line: 3, column: 7, scope: !4)
106+
!9 = !DILocation(line: 4, column: 17, scope: !4)
107+
!10 = !DILocation(line: 5, column: 17, scope: !4)
108+
!11 = !DILocation(line: 6, column: 16, scope: !4)
109+
!12 = !DILocation(line: 6, column: 5, scope: !4)
110+
)IR";
111+
112+
const char *EmitToMemoryBufferWithDbgTest::IRStringWithoutDbg = R"IR(
113+
target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256"
114+
target triple = "evm"
115+
116+
define i256 @foo(i256 %0, i256 %1) {
117+
%add1 = add nsw i256 %0, 5
118+
%add2 = add nsw i256 %1, 3
119+
%mul = mul nsw i256 %add2, %add1
120+
%add3 = add nsw i256 %add2, %add1
121+
%add4 = add nsw i256 %add3, %mul
122+
ret i256 %add4
123+
}
124+
)IR";
125+
126+
bool objHasDbgSection(LLVMMemoryBufferRef B) {
127+
StringRef ObjBytes(LLVMGetBufferStart(B), LLVMGetBufferSize(B));
128+
auto Obj =
129+
object::ObjectFile::createObjectFile(MemoryBufferRef(ObjBytes, "dbg"));
130+
if (!Obj)
131+
report_fatal_error("Failed to parse object file from debug buffer");
132+
133+
for (const auto &S : (*Obj)->sections()) {
134+
Expected<StringRef> Name = S.getName();
135+
if (Name && Name->contains(".debug"))
136+
return true;
137+
}
138+
return false;
139+
}
140+
141+
TEST_F(EmitToMemoryBufferWithDbgTest, ObjWithDbg) {
142+
createModuleAndCompile(IRStringWithDbg, LLVMObjectFile);
143+
144+
EXPECT_TRUE(LLVMGetBufferSize(OutBuf) > 0) << "Output buffer is empty";
145+
EXPECT_TRUE(LLVMGetBufferSize(DbgBuf) > 0) << "Debug output buffer is empty";
146+
EXPECT_FALSE(objHasDbgSection(OutBuf))
147+
<< "Output buffer unexpectedly contained .debug sections";
148+
EXPECT_TRUE(objHasDbgSection(DbgBuf))
149+
<< "Debug buffer did not appear to contain any .debug sections";
150+
}
151+
152+
TEST_F(EmitToMemoryBufferWithDbgTest, ObjWithoutDbg) {
153+
createModuleAndCompile(IRStringWithoutDbg, LLVMObjectFile);
154+
155+
EXPECT_TRUE(LLVMGetBufferSize(OutBuf) > 0) << "Output buffer is empty";
156+
EXPECT_TRUE(LLVMGetBufferSize(DbgBuf) > 0) << "Debug output buffer is empty";
157+
EXPECT_FALSE(objHasDbgSection(OutBuf))
158+
<< "Output buffer unexpectedly contained .debug sections";
159+
EXPECT_FALSE(objHasDbgSection(DbgBuf))
160+
<< "Debug buffer unexpectedly contained .debug sections";
161+
}
162+
163+
TEST_F(EmitToMemoryBufferWithDbgTest, Asm) {
164+
createModuleAndCompile(IRStringWithDbg, LLVMAssemblyFile);
165+
166+
EXPECT_TRUE(LLVMGetBufferSize(OutBuf) > 0) << "Output buffer is empty";
167+
EXPECT_FALSE(LLVMGetBufferSize(DbgBuf) > 0)
168+
<< "Debug output buffer is not empty";
169+
}
170+
171+
} // anonymous namespace

0 commit comments

Comments
 (0)