LLVM Cpu0 新后端6

 想好好熟悉一下llvm开发一个新后端都要干什么,于是参考了老师的系列文章:

LLVM 后端实践笔记

代码在这里(还没来得及准备,先用网盘暂存一下):

链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?pwd=vd6s 提取码: vd6s 

之前的章节只实现了 int 和 32 位的 long 类型数据,这一章会新增一些更复杂的数据类型,比如 char, bool, short, long long,还会增加结构体,浮点,和向量类型。这一部分内容相对比较简单,其实这些类型也都是标准语言都支持的类型,所以 LLVM 自身已经实现了很大一部分功能,只要我们的后端不那么奇怪,就很容易填补缺失的内容。

目录

一、修改的文件

1.1 Cpu0ISelDAGToDAG.cpp

1.2 Cpu0ISelLowering.cpp/.h

1.3 Cpu0InstrInfo.td

1.4 Cpu0SEISelDAGToDAG.cpp/.h

1.5 MCTargetDesc/Cpu0InstPrinter.cpp/.h

二、实现结果

2.1 局部指针

2.2 char类型

2.3 bool类型

2.4 short

2.5 long long 类型

2.6 局部数组、结构体

2.7 全局数组、结构体

2.8 向量

2.9 cl指令


一、修改的文件

1.1 Cpu0ISelDAGToDAG.cpp

在SelectAddr接口内增加对于基址+常量偏移这种地址形式的处理,对于全局基址加常量偏移的情况,提取其基址和偏移。。

1.2 Cpu0ISelLowering.cpp/.h

有关于对 bool 类型的处理,这里增加了一些对 i1 类型 Promote 的合法化描述,告诉 LLVM 在遇到对 i1 类型的 extend 时要做 Promote。Promote 是将较小宽度的数据类型扩展成对应的能够支持的更宽的数据宽度类型,在指令选择的类型合法化阶段会起到作用。在 long long 实现中,在 Lowering 的位置还需要增加对 long long 类型的移位操作合法化。

覆盖一个函数 isOffsetFoldingLegal(),直接返回 false,避免带偏移的全局地址展开,Cpu0 和 Mips 一样无法处理这种情况。我们实现的 getAddrNonPIC() 方法中,将全局符号地址展开成一条加法指令,对地址的高低位做加法运算。所以实际上我们会将一条全局地址带偏移的寻址展开成加法运算 base,然后再把结果与 offset 相加的 DAG(在 Cpu0ISelDAGToDAG.cpp 中的 SelectAddr 中提取这种情况的 node Value,此时就已经是两个 add node 了)。

最后,还需要对向量类型的支持做一小部分改动,覆盖 getSetCCResultType() 方法,如果是向量类型,使用 VT.changeVectorElementTypeToInteger() 方法返回 CC 值。

1.3 Cpu0InstrInfo.td

到目前,因为我们添加数据类型的很多实现代码已经在公共 LLVM 代码中实现,所以实际上大多数修改都在 td 文件中。

新增一个 mem_ea 的操作数类型,这是一个 complexpattern,会定义其 encoding 操作和 printinst 等操作,它用来描述指令 pattern 中的地址表示;然后要定义一个 LEA_ADDiu 的模式,这是一个不会输出成指令的模式,它实际上是计算地址+偏移的结果,这和 sparc 处理器中的 LEA_ADDRi 是一样的效果。

新增 i8 和 i16 相关的 extend 类型以及对应的 ld/st,命名为 LB, LBu, SB, LH, LHu, SH。LB, LH 处理有符号的 i8/i16 类型 load,LBu, LHu 处理无符号的 i8/i16 类型 load,SB, SH 处理i8/i16 类型的store。

新增 CountLeading0 和 CountLeading1 的 pattern,用来选择到计算前导 0 和计算前导 1 的指令,llvm 内置了 ctlz 的 node(count leading zero),可以直接把 clz 指令接过去,不过对于 count leading 1 是没有对应的 node 的,不过可以通过先对值取反然后求前导 0 的方式实现前导 1 的计算,即 ctlz (not RC:$rb)。

因为 C 语言没有对求前导 0 和前导 1 的原生语法,所以实际上会使用 builtin 接口来实现,也就是说,在 C 语言描述中,为了实现这种功能,需要调用 __builtin_clz() 函数(ctls 就是先对参数取反再调用 ctlz 的 builtin),因为我们使用了内置的 node,所以这部分是 llvm 帮我们实现了。

1.4 Cpu0SEISelDAGToDAG.cpp/.h

定义了一个 selectAddESubE() 方法,用来处理带进位的加减法运算的指令选择。在 trySelect() 方法中,将对 ISD::SUBE, ISD::ADDE 的情况选择用 selectAddESubE() 来处理。

selectAddESubE() 方法为符合条件的 node 新增了一个操作数节点,该节点会读取状态字中进位是否是 1,并将结果叠加到运算中;在 Cpu032I 处理器中,使用 CMP 指令和 ANDi 指令来获取进位状态,在 Cpu032II 处理器中,则使用 SLT 指令直接判断进位。

另外,还要处理 SMUL_LOHI 和 UMUL_LOHI 节点,这是能够直接返回两个运算结果的节点(高低位)。

1.5 MCTargetDesc/Cpu0InstPrinter.cpp/.h

增加mem_ea 的printinst操作的实现。

二、实现结果

2.1 局部指针

int test_local_pointer() {int b = 3;int *p = &b;return *p;
}
	addiu	$sp, $sp, -8   # 扩栈addiu	$2, $zero, 3   # 将3存到寄存器st	$2, 4($sp)         # 将其存到栈上addiu	$2, $sp, 4     # 读出栈中局部变量的地址st	$2, 0($sp)         # 将这个地址存到栈上ld	$2, 0($sp)         # 读出这个地址ld	$2, 0($2)          # 读出地址里的内容addiu	$sp, $sp, 8    # 回栈ret	$lr                # 返回

2.2 char类型

struct Date
{short year;char month;char day;char hour;char minute;char second;
};unsigned char b[4] = {'a', 'b', 'c', '\0'};int test_char()
{unsigned char a = b[1];char c = (char)b[1];struct Date date1 = {2021, (char)2, (char)27, (char)17, (char)22, (char)10};char m = date1.month;char s = date1.second;return 0;
}
	addiu	$sp, $sp, -24lui	$2, %got_hi(b)addu	$2, $2, $gpld	$2, %got_lo(b)($2)  # 与上边搭配加载全局变量b的首地址lbu	$3, 1($2)           # 计算b[1]的地址,存到寄存器3sb	$3, 20($sp)         # 将寄存器3内的地址存到栈上lbu	$2, 1($2)           # 再次计算b[1]的地址,存到寄存器2sb	$2, 16($sp)         # 将寄存器2内的地址存到栈上ld	$2, %got($__const.test_char.date1)($gp)ori	$2, $2, %lo($__const.test_char.date1)  # 获取要写入局部变量对象的常量的地址lhu	$3, 6($2)           # 将偏移6处的内容load到寄存器3中,lhu是i16的,就是load范围是2字节lhu	$4, 4($2)           # 将偏移4处的内容load到寄存器4中,lhu是i16的,就是load范围是2字节shl	$4, $4, 16         or	$3, $4, $3          # 这两条是将上述load出来的两个2字节的内容拼成一个4字节的st	$3, 12($sp)         # 将这4字节存到栈上,也就是存放hour, minute, second到 date1lhu	$3, 2($2)           # 这里是相同的逻辑lhu	$2, 0($2)shl	$2, $2, 16or	$2, $2, $3st	$2, 8($sp)          # 将这4字节存到栈上,也就是存放year, month, day到 date1lbu	$2, 10($sp)         # 从偏移10的位置读出date1.month(我们知道year, month, day在偏移8的位置,year两个字节,因此month在偏移10的位置,这里很正确)sb	$2, 4($sp)          # 将其存到栈上(m)lbu	$2, 14($sp)         # 从偏移14的位置读出date1.secondsb	$2, 0($sp)          # 将其存到栈上(s)addiu	$2, $zero, 0addiu	$sp, $sp, 24ret	$lr

2.3 bool类型

bool test_load_bool()
{int a = 1;if (a < 0)return false;return true;
}

这里涉及到跳转我们当前可能编不过,下一节的内容加上之后我们就可以编过了,我们先提前看一下效果。

_Z14test_load_boolv:
# %bb.0:addiu	$sp, $sp, -8addiu	$2, $zero, 1st	$2, 0($sp)ld	$2, 0($sp)addiu	$3, $zero, -1slt	$2, $3, $2bne	$2, $zero, $BB0_2nop
# %bb.1:addiu	$2, $zero, 0sb	$2, 7($sp)       # 使用 sb 将 bool 类型的 0 写入栈jmp	$BB0_3
$BB0_2:addiu	$2, $zero, 1sb	$2, 7($sp)       # 使用 sb 将 bool 类型的 1 写入栈
$BB0_3:lbu	$2, 7($sp)addiu	$sp, $sp, 8ret	$lr

2.4 short

int test_signed_char()
{char a = 0x80;int i = (signed int)a;i = i + 2; // i = (-128 + 2) = -126return i;
}int test_unsigned_char()
{unsigned char c = 0x80;unsigned int ui = (unsigned int)c;ui = ui + 2; // ui = (128 + 2) = 130return (int)ui;
}int test_signed_short()
{short a = 0x8000;int i = (signed int)a;i = i + 2; // i = (-32768 + 2) = -32766return i;
}int test_unsigned_short()
{unsigned short c = 0x8000;unsigned int ui = (unsigned int)c;ui = ui + 2; // ui = (32768 + 2) = 32770return (int)ui;
}
st_signed_short
...addiu	$sp, $sp, -8ori	$2, $zero, 32768sh	$2, 4($sp)lh	$2, 4($sp)st	$2, 0($sp)ld	$2, 0($sp)addiu	$2, $2, 2st	$2, 0($sp)ld	$2, 0($sp)addiu	$sp, $sp, 8ret	$lr
...
test_unsigned_short:
...addiu	$sp, $sp, -8ori	$2, $zero, 32768sh	$2, 4($sp)lhu	$2, 4($sp)st	$2, 0($sp)ld	$2, 0($sp)addiu	$2, $2, 2st	$2, 0($sp)ld	$2, 0($sp)addiu	$sp, $sp, 8ret	$lr
...

汇编还是很好理解的,这里就不进行详细的分析了。

2.5 long long 类型

long long test_longlong()
{long long a = 0x300000002;long long b = 0x100000001;int a1 = 0x30010000;int b1 = 0x20010000;long long c = a + b;    // c = 0x00000004,00000003long long d = a - b;    // d = 0x00000002,00000001long long e = a * b;    // e = 0x00000005,00000002long long f = (long long)a1 * (long long)b1;    // f = 0x00060050,01000000return (c+d+e+f);       // (0x0006005b,01000006) = (393307,16777222)
}
	addiu	$sp, $sp, -56addiu	$2, $zero, 2st	$2, 52($sp)          # a的低位addiu	$2, $zero, 3st	$2, 48($sp)          # a的高位addiu	$2, $zero, 1st	$2, 44($sp)          # b的低位st	$2, 40($sp)          # b的高位lui	$2, 12289st	$2, 36($sp)          # a1lui	$2, 8193st	$2, 32($sp)          # b1ld	$2, 52($sp)          # a的低位ld	$3, 48($sp)          # a的高位ld	$4, 44($sp)          # b的低位ld	$5, 40($sp)          # b的高位addu	$3, $3, $5       # 高位相加addu	$4, $2, $4       # 低位相加sltu	$2, $4, $2       # 判断低位加法是否有进位addu	$2, $3, $2       # 将进位与高位结果相加st	$4, 28($sp)          # 下同st	$2, 24($sp)ld	$2, 48($sp)ld	$3, 52($sp)ld	$4, 40($sp)ld	$5, 44($sp)sltu	$6, $3, $5subu	$2, $2, $4subu	$2, $2, $6subu	$3, $3, $5st	$3, 20($sp)st	$2, 16($sp)ld	$2, 48($sp)ld	$3, 52($sp)ld	$4, 44($sp)ld	$5, 40($sp)mul	$5, $3, $5multu	$3, $4mflo	$3mfhi	$6addu	$5, $6, $5mul	$2, $2, $4addu	$2, $5, $2st	$3, 12($sp)st	$2, 8($sp)ld	$2, 36($sp)ld	$3, 32($sp)mult	$2, $3mflo	$2mfhi	$3st	$2, 4($sp)st	$3, 0($sp)ld	$2, 28($sp)ld	$3, 24($sp)ld	$4, 20($sp)ld	$5, 16($sp)addu	$3, $3, $5addu	$4, $2, $4sltu	$2, $4, $2addu	$2, $3, $2ld	$3, 8($sp)ld	$5, 12($sp)addu	$5, $4, $5sltu	$4, $5, $4addu	$2, $2, $3addu	$2, $2, $4ld	$3, 4($sp)ld	$4, 0($sp)addu	$2, $2, $4addu	$3, $5, $3sltu	$4, $3, $5addu	$2, $2, $4addiu	$sp, $sp, 56ret	$lr

2.6 局部数组、结构体

与2.2中的局部结构体类似。

2.7 全局数组、结构体

struct Date
{int year;int month;int day;
};struct Date date = {2021, 2, 27};
int a[3] = {2021, 2, 27};int test_struct()
{int day = date.day;int i = a[1];return (i+day);  // 2 + 27 = 29
}
	addiu	$sp, $sp, -8lui	$2, %got_hi(date)addu	$2, $2, $gpld	$2, %got_lo(date)($2)  # 从got表中取全局变量date的地址ld	$2, 8($2)              # 从偏移8的地方load出date.day,(year和month各占4字节)st	$2, 4($sp)             # 存到栈中(day)lui	$2, %got_hi(a)         # 下同addu	$2, $2, $gpld	$2, %got_lo(a)($2)ld	$2, 4($2)st	$2, 0($sp)ld	$2, 0($sp)ld	$3, 4($sp)addu	$2, $2, $3addiu	$sp, $sp, 8ret	$lr

2.8 向量

typedef long vector8long __attribute__((__vector_size__(32)));
typedef long vector8short __attribute__((__vector_size__(16)));int test_cmplt_short()
{volatile vector8short a0 = {0, 1, 2, 3};volatile vector8short b0 = {2, 2, 2, 2};volatile vector8short c0;c0 = a0 < b0;return (int)(c0[0] + c0[1] + c0[2] + c0[3]);
}int test_cmplt_long()
{volatile vector8long a0 = {2, 2, 2, 2, 1, 1, 1, 1};volatile vector8long b0 = {1, 1, 1, 1, 2, 2, 2, 2};volatile vector8long c0;c0 = a0 < b0;return (c0[0] + c0[1] + c0[2] + c0[3] + c0[4] + c0[5] + c0[6] + c0[7]);
}

下述是test_cmplt_short函数的汇编,我们看个稍微短一点的:

	addiu	$sp, $sp, -64st	$10, 60($sp)                    # 4-byte Folded Spillst	$9, 56($sp)                     # 4-byte Folded Spilladdiu	$2, $zero, 3st	$2, 44($sp)addiu	$3, $zero, 2st	$3, 40($sp)addiu	$2, $zero, 1st	$2, 36($sp)addiu	$2, $zero, 0st	$2, 32($sp)st	$3, 28($sp)st	$3, 24($sp)st	$3, 20($sp)st	$3, 16($sp)ld	$3, 44($sp)ld	$4, 40($sp)ld	$5, 36($sp)ld	$6, 32($sp)ld	$7, 28($sp)ld	$8, 24($sp)ld	$9, 20($sp)ld	$10, 16($sp)slt	$6, $6, $10subu	$6, $2, $6slt	$5, $5, $9subu	$5, $2, $5slt	$4, $4, $8subu	$4, $2, $4slt	$3, $3, $7subu	$2, $2, $3st	$2, 12($sp)st	$4, 8($sp)st	$5, 4($sp)st	$6, 0($sp)ld	$2, 12($sp)ld	$2, 8($sp)ld	$2, 4($sp)ld	$2, 0($sp)ld	$3, 12($sp)ld	$3, 8($sp)ld	$3, 0($sp)ld	$3, 4($sp)addu	$2, $2, $3ld	$3, 12($sp)ld	$3, 4($sp)ld	$3, 0($sp)ld	$3, 8($sp)addu	$2, $2, $3ld	$3, 8($sp)ld	$3, 4($sp)ld	$3, 0($sp)ld	$3, 12($sp)addu	$2, $2, $3ld	$9, 56($sp)                     # 4-byte Folded Reloadld	$10, 60($sp)                    # 4-byte Folded Reloadaddiu	$sp, $sp, 64ret	$lr

其实整体逻辑是很简单的。

2.9 cl指令

int countLeadingZero() {int a, b;b = __builtin_clz(a);return b;
}int countLeadingOne() {int a, b;b = __builtin_clz(~a);return b;
}
countLeadingZero:addiu	$sp, $sp, -8ld	$2, 4($sp)clz	$2, $2st	$2, 0($sp)ld	$2, 0($sp)addiu	$sp, $sp, 8ret	$lrcountLeadingOne:addiu	$sp, $sp, -8ld	$2, 4($sp)clo	$2, $2st	$2, 0($sp)ld	$2, 0($sp)addiu	$sp, $sp, 8ret	$lr

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/25583.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】 深入了解 MySQL 存储过程:定义、优势及示例

我已经从你的 全世界路过 像一颗流星 划过命运 的天空 很多话忍住了 不能说出口 珍藏在 我的心中 只留下一些回忆 &#x1f3b5; 牛奶咖啡《从你的全世界路过》 在数据库管理系统中&#xff0c;存储过程&#xff08;Stored Procedure&#xff09;是一种重…

探究IOC容器刷新环节初始化前的预处理

目录 一、IOC容器的刷新环节快速回顾 二、初始化前的预处理prepareRefresh源码分析 三、初始化属性源 &#xff08;一&#xff09;GenericWebApplicationContext初始化属性源 &#xff08;二&#xff09;StaticWebApplicationContext初始化属性源 四、初始化早期事件集合…

汇川CodeSysPLC教程03-2-4 RS485

前情回顾 在前面介绍串口通讯的视频当中&#xff0c;我们提到了RS232&#xff0c;也顺带提到了RS485&#xff0c;本期将会对RS485做比较全面的比较和盘点。 什么是RS485&#xff1f; RS485是一种用于串行通信的标准&#xff0c;通常用于工业自动化和远距离数据传输。它在电气…

3.大模型高效微调PEFT

大模型高效微调(PEFT)技术 预训练模型的背景 预训练与微调:传统的微调方法通常涉及对整个预训练模型的参数进行再训练,以适应特定任务。这虽然有效,但计算成本高,且需要大量的标记数据。模型结构:像BERT或GPT这样的模型通常包含数亿甚至数十亿个参数,构成一个深层次的…

Qt——升级系列(Level Four):控件概述、QWidget 核心属性、按钮类控件

目录 控件概述 QWidget 核心属性 核心属性概览 enabled geometry windowTitle windowIcon windowOpacity cursor font toolTip focusPolicy styleSheet 按钮类控件 Push Button Radio Buttion Check Box Tool Button 控件概述 Widget 是 Qt 中的核⼼概念. 英⽂原义是 "…

西门子学习笔记11 - PTO脉冲指令的使用

1、使用指令前的设置 1、打开一个脉冲发生器&#xff0c;并启用 2、选择使用PTO(脉冲A和方向B) 3、硬件设置输出 4、这样前期的准备工作就完成了 2、指令的使用 1、添加指令CTRL_PTO 2、配置如下 3、方向控制程序如下 4、最后进行测试即可

C语言之存储类、作用域、生命周期、链接属性

一 &#xff1a;概念解析 1&#xff1a; 存储类 &#xff08;1&#xff09;存储类就是存储类型&#xff0c;就是描述C语言变量存储在什么地方 &#xff08;2&#xff09;内存有多种管理方法&#xff1a;栈、堆数据段、bss段、.text段......一个变量的存储类属性就是描述…

html--万年历

<!DOCTYPE html> <html lang"zh_CN"><head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8" /><meta charset"utf-8" /><title>万年历</title><link rel"styles…

C语言 | Leetcode C语言题解之第142题环形链表II

题目&#xff1a; 题解&#xff1a; struct ListNode* detectCycle(struct ListNode* head) {struct ListNode *slow head, *fast head;while (fast ! NULL) {slow slow->next;if (fast->next NULL) {return NULL;}fast fast->next->next;if (fast slow) {s…

LLVM Cpu0 新后端9 objdump readelf

想好好熟悉一下llvm开发一个新后端都要干什么&#xff0c;于是参考了老师的系列文章&#xff1a; LLVM 后端实践笔记 代码在这里&#xff08;还没来得及准备&#xff0c;先用网盘暂存一下&#xff09;&#xff1a; 链接: https://pan.baidu.com/s/1yLAtXs9XwtyEzYSlDCSlqw?…

C-Linux: 题集

1 C语言 1 简述 全局变量 和 局部变量的区别: 存储位置:全局变量存储在静态区,局部变量存储在栈上 作用范围:全局变量对整个程序可见,局部变量仅对其定义的函数可见 生命周期:全局变量在程序执行过程中都存在,局部变量的生命周期仅在其所在函数执行期间存在 2 死锁…

EE trade:如何在A股市场中有效设定止盈止损点

A股市场充满机遇和风险&#xff0c;很多投资者在这里实现了财富增长&#xff0c;也有投资者在这里遭受损失。如何在波动性较大的市场中&#xff0c;控制风险&#xff0c;保护利润和本金?止盈止损是关键。 什么是止盈止损? 止盈止损是指在交易中&#xff0c;根据预先设定的条…

【人工智能】AIGC是什么?

AIGC是“人工智能生成内容”&#xff08;Artificial Intelligence Generated Content&#xff09;的缩写&#xff0c;它指的是利用人工智能技术自动创建文本、图像、音频、视频等不同类型的内容。AIGC技术的发展&#xff0c;使得计算机能够模拟人类的创作过程&#xff0c;生成具…

如何稳定高效地进行 TiDB 数据导入导出?

对于在数据库行业中摸爬滚打多年的老鸟 DBA 来说&#xff0c;TiDB 可是一点也不陌生&#xff0c;作为 PingCAP 公司自主研发的真开源分布式数据库&#xff0c;其先进的设计理念以及丰富的生态工具&#xff0c;可算得上是业界自主创新和性能领先的代名词。 TiDB 是谁&#xff1…

python一点通: Async异步函数很好,但是如何有效执行阻塞任务?

当使用 asyncio.create_task(function1) 和 asyncio.create_task(function2) 时&#xff0c;你正在创建两个将在同一个事件循环中并发运行的异步任务。这些任务运行是否高效取决于它们本身的性质。 理解异步任务 如果 function1 和 function2 是异步函数&#xff08;即&#…

探索PostgreSQL的多模型世界:灵活存储,无限可能

在数据库的世界里&#xff0c;有一种神器&#xff0c;它以其无与伦比的灵活性和强大的功能&#xff0c;赢得了全球开发者的青睐。它就是——PostgreSQL&#xff0c;一个真正的多模型数据库管理系统。 为什么选择PostgreSQL&#xff1f; 可靠性和稳定性&#xff1a;PostgreSQL…

Rust anyhow 简明教程

anyhow 是 Rust 中的一个库&#xff0c;旨在提供灵活的、具体的错误处理能力&#xff0c;建立在 std::error::Error 基础上。它主要用于那些需要简单错误处理的应用程序和原型开发中&#xff0c;尤其是在错误类型不需要被严格区分的场景下。 以下是 anyhow 的几个关键特性&…

MAVEN架构项目管理工具

1、什么是maven Maven是跨平台的项目管理工具。主要服务于基于Java平台的项目构建&#xff0c;依赖管理和项目信息管理。 2、maven的目标&#xff1a;Maven的主要目标是为了使开发人员在最短的时间内领会项目的所有状态 3、使用maven不需要考虑各个依赖的版本&#xff0c;因…

Follow Carl To Grow|【LeetCode】530.二叉搜索树的最小绝对差,501.二叉搜索树中的众数,236. 二叉树的最近公共祖先

【LeetCode】530.二叉搜索树的最小绝对差 题意&#xff1a;给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 思路&#xff1a;中序遍历拿到递增序列&#xff0c;然后求相邻…

【将xml文件转yolov5训练数据txt标签文件】连classes.txt都可以生成

将xml文件转yolov5训练数据txt标签文件 前言一、代码解析 二、使用方法总结 前言 找遍全网&#xff0c;我觉得写得最详细的就是这个博文⇨将xml文件转yolov5训练数据txt标签文件 虽然我还是没有跑成功。那个正则表达式我不会改QWQ&#xff0c;但是不妨碍我会训练ai。 最终成功…