compiler-construction clang llvm debug-symbols llvm-ir

compiler construction - Obtención del nombre de la variable original para un valor LLVM



compiler-construction clang (4)

Los operandos para un llvm::User (por ejemplo, instrucción) son llvm::Value s.

Después del paso mem2reg , las variables están en forma de SSA , y sus nombres correspondientes al código fuente original se pierden. Value::getName() solo se establece para algunas cosas; Para la mayoría de las variables, que son intermediarios, no se establece.

El pase de instnamer se puede ejecutar para dar a todas las variables nombres como tmp1 y tmp2 , pero esto no captura de dónde provienen originalmente. Aquí hay algunos LLVM IR al lado del código C original:

Estoy creando una página html simple para visualizar y depurar algunas optimizaciones en las que estoy trabajando, y quiero mostrar las variables SSA como notación de nombre y nombre , en lugar de solo nombres temporales de instnamer. Es sólo para ayudar a mi legibilidad.

Estoy obteniendo mi LLVM IR de clang con una línea de comandos como:

clang -g3 -O1 -emit-llvm -o test.bc -c test.c

Hay llamadas a llvm.dbg.declare y llvm.dbg.value en el IR; ¿Cómo se convierten los nombres de código fuente originales y los números de versión de SSA?

Entonces, ¿cómo puedo determinar la variable original (o el nombre de la constante con nombre) de un llvm::Value ? Los depuradores deben poder hacer esto, entonces, ¿cómo puedo?


Dado un Value , se puede obtener un nombre de variable a través de todas las llamadas llvm.dbg.declare y llvm.dbg.value en la función de envolvente, verificando si alguna se refiere a ese valor, y si es así, devuelva la DIVariable asociada con El valor por esa llamada intrínseca.

Por lo tanto, el código debería tener un aspecto similar a (aproximadamente, no probado ni compilado):

const Function* findEnclosingFunc(const Value* V) { if (const Argument* Arg = dyn_cast<Argument>(V)) { return Arg->getParent(); } if (const Instruction* I = dyn_cast<Instruction>(V)) { return I->getParent()->getParent(); } return NULL; } const MDNode* findVar(const Value* V, const Function* F) { for (const_inst_iterator Iter = inst_begin(F), End = inst_end(F); Iter != End; ++Iter) { const Instruction* I = &*Iter; if (const DbgDeclareInst* DbgDeclare = dyn_cast<DbgDeclareInst>(I)) { if (DbgDeclare->getAddress() == V) return DbgDeclare->getVariable(); } else if (const DbgValueInst* DbgValue = dyn_cast<DbgValueInst>(I)) { if (DbgValue->getValue() == V) return DbgValue->getVariable(); } } return NULL; } StringRef getOriginalName(const Value* V) { // TODO handle globals as well const Function* F = findEnclosingFunc(V); if (!F) return V->getName(); const MDNode* Var = findVar(V, F); if (!Var) return "tmp"; return DIVariable(Var).getName(); }

Puede ver más arriba que era demasiado perezoso para agregar el manejo de globales, pero en realidad no es tan importante, esto requiere iterar sobre todos los globales enumerados en la información de depuración de la unidad de compilación actual (use M.getNamedMetadata("llvm.dbg.cu") para obtener una lista de todas las unidades de compilación en el módulo actual, luego verifique qué coincide con su variable (a través del método getGlobal ) y devuelva su nombre.

Sin embargo , tenga en cuenta que lo anterior solo funcionará para los valores directamente asociados con las variables originales. Cualquier valor que sea el resultado de cualquier cálculo no se nombrará correctamente de esta manera; y, en particular, los valores que representan los accesos a los campos no se nombrarán con el nombre del campo. Esto es factible, pero requiere un procesamiento más complejo. Tendrá que identificar el número de campo del GEP, y luego profundizar en la información de depuración de tipo para que la estructura recupere el nombre del campo. Los depuradores hacen eso, sí, pero ningún depurador opera en LLVM IR land, por lo que sé, incluso el LLDB de LLVM funciona de manera diferente, al analizar el DWARF en el archivo objeto en tipos de Clang.


Esto es parte de la información de depuración que se adjunta a LLVM IR en forma de metadatos. La documentación está aquí . También está disponible una antigua publicación de blog con algunos antecedentes.

$ cat > z.c long fact(long arg, long farg, long bart) { long foo = farg + bart; return foo * arg; } $ clang -emit-llvm -O3 -g -c z.c $ llvm-dis z.bc -o -

Produce esto:

define i64 @fact(i64 %arg, i64 %farg, i64 %bart) #0 { entry: tail call void @llvm.dbg.value(metadata !{i64 %arg}, i64 0, metadata !10), !dbg !17 tail call void @llvm.dbg.value(metadata !{i64 %farg}, i64 0, metadata !11), !dbg !17 tail call void @llvm.dbg.value(metadata !{i64 %bart}, i64 0, metadata !12), !dbg !17 %add = add nsw i64 %bart, %farg, !dbg !18 tail call void @llvm.dbg.value(metadata !{i64 %add}, i64 0, metadata !13), !dbg !18 %mul = mul nsw i64 %add, %arg, !dbg !19 ret i64 %mul, !dbg !19 }

Con -O0 lugar de -O3 , no verá llvm.dbg.value , pero verá llvm.dbg.declare .


Si está utilizando una versión reciente de Clang, algunos de los otros enfoques no funcionarán. En su lugar, use la marca -fno-discard-value-names para clang. Esto hará que llvm :: Values ​​conserve sus nombres originales.