【译】LLVM 类型系统

LLVM IR:2.IR类型体系(Type System) llvm 的类型体系是IR语法中很重要的一部分,中间代码类型化可以使得许多优化在IR层面进行,不需要在转换之前做额外的分析,强类型系统可以更加轻松地读取生成的代码,并支持对正常的三地址码表示以及新颖的分析和转换。 阅读详情

LLVM IR(intermediate representation) 的类型系统,在 LLVM 3.0 的时候被重写了,本文将介绍其原因,以及新类型系统如何运作。

内容来源: http://blog.llvm.org/2011/11/llvm-30-type-system-rewrite.html
原文标题:LLVM 3.0 Type System Rewrite
作者:Chris Lattner
译者:流左沙
注:翻译较原文会有一定的精简、重排和添删,主要是为了提取重要内容以达到更好的理解,请以原文为准。

1、基本类型

The LLVM IR type system is a fairly straight-forward part of the IR.

LLVM IR 的类型系统是 IR 的一部分。

The type system consists of three major parts: 它包含有三个主要部分

  • primitive types (like 'double' and integer types), 原始类型
  • derived types (like structs, arrays and vectors), 派生类型
  • and a mechanism for handling forward declarations of types ('opaque'). 提前声明的类型,不明确的

2、类型系统的目标

  • 使用高效的指针,来判断结构体类型是否一致
  • 类型可以提前声明,而后续可能在其他地方补全信息
  • 可以表示不同语言的源码
  • 简单、可预测行为的

3、旧类型系统的运作

对于不明确类型的,当其后来被解析了(可能在链接时),名叫类型补全(type refinement)的进程,会把所有指向该不明确类型的指针修正。

%T1 = type opaque
%T2 = type %T1*

T2 is a PointerType to an OpaqueType. If we resolve %T1 to {} (an empty struct), then %T2 mutates to be a PointerType to the empty StructType.

T2 是不明确类型的指针类型,如果把 T1 解析为空结构体类型,那么 T2 就能改变指针指向空结构体类型。

3.1、产生的问题

  • In order to guarantee that pointer equality checks work for structural type equivalence checks, VMCore is required to re-unique types whenever they are mutated during type resolution. 为了保证指针判同的工作——判断结构体类型是否一致,VMCore 需要在类型解析后,又重新处理其类型。
  • Another problem is that more than just types need to be updated: anything that contains a pointer to a type has to be updated, or it gets a dangling pointer. This issue manifested in a number of ways: for example every Value has a pointer to a type. In order to make the system a bit more efficient, Value::getType() actually performed a lazy "union find" step to ensure that it always returned a canonicalized and uniqued type. This made Value::getType() (a very common call) more expensive than it should be. 所有指向类型的指针都需要更新,不然它就是个野指针。这加剧了类型系统任务的繁重。
  • An even worse problem that this "type updating" problem caused is when you were manipulating and building IR through the LLVM APIs. Because types could move, it was very easy to get dangling pointers, causing a lot of confusion and a lot of broken clients of the LLVM API. 当我们通过 LLVM API 去编译 IR 时,因为类型会变化,很容易就会导致野指针,导致 API 带来很多的困扰,以及对稳定性的破坏。

3.2、类型统一的怪异行为

type names were not part of the type system, they were an "on the side" data structure. The names were not taking into consideration during type uniquing, and you could have multiple names for one type (which led to a lot of confusion). 类型名字不是类型系统的一部分,它们是数据结构。在类型统一时没有考虑类型名字,导致一个类型可能会有多个名字的困扰。

%T1 = type opaque
@G1 = external global %T1*

%T2 = type {}
@G2 = external global %T2*

If %T1 was later resolved to {}, then %T1 and %T2 would both become names for the same empty structure type, the type formerly known as "%T1" would be unified with the type formerly known as "%T2", and now the IR would print out as: 如果 T1 被解析为空,那么 T1 和 T2 都是相同的空结构体类型名字,%T1* 和 %T2 会是一致的。

%T1 = type {}
@G1 = external global %T1*

%T2 = type {}
@G2 = external global %T1*

... note that G2 now has type "%T1*"! This is because the names in the type system was just a hash table on the side, so that asmprinter would pick one of the arbitrarily large number of names for a type when printing. This was "correct", but highly confusing the folks who did not know the ins and outs of the type system, and not helpful behavior. It also made reading .ll dumps from a C++ compiler very difficult, because it is very common to have many structurally identical types with different names. 注意 G2 现在类型是 %T1* ,因为名字在类型系统里只是一个哈希表,汇编器会随意地获取类型的其中一个名字。这可能是正确的,但会扰乱不清楚类型系统输入输出的人,而且这是毫无帮助的行为。导致阅读 IR 异常困难。

3.3、类型重定向

A final problem (that I don't want to dwell on too much) is that we previously could have the situation where a type existed that had no name at all. While this is fine from the type system graph's perspective, this made printing types impossible if they were cyclic and had no names. The solution to this problem was a system known as type up-references. 类型可能还存在没有名字存在的问题。对于类型系统的图结构没什么问题,但对于类型输出是灾难。一个解决方法就是类型重定向。

An "up reference" allows you to refer to a lexically enclosing type without requiring it to have a name. For instance, a structure declaration may contain a pointer to any of the types it is lexically a member of. Example of up references (with their equivalent as named type declarations) include: 类型重定向让你指向词法闭包类型,而不需要名字。

{ \2 * }                %x = type { %x* } 
{ \2 }*                 %y = type { %y }*
\1*                     %z = type %z*        // Self-referential pointer.

An up reference is needed by the asmprinter for printing out cyclic types when there is no declared name for a type in the cycle. Because the asmprinter does not want to print out an infinite type string, it needs a syntax to handle recursive types that have no names (all names are optional in llvm IR). 类型重定向对于没有名字的循环类型而言,是必需的。

Type up-references were an elegant solution that allowed the asmprinter (and parser) to be able to represent an arbitrary recursive type in finite space, without requiring names. For example, the %intlist example above could be represented as "{\2, i32}". It also allowed for construction of some nice (but surprising) types like "\1" which was a pointer to itself! 类型重定向可谓是一种优雅的解决方式。它允许指针指向其自身。

Despite having some amount of beauty and elegance, type up-references were never well understood by most people and caused a lot of confusion. It is important to be able to strip the names out of an LLVM IR module (e.g. the -strip pass), but it is also important for compiler hackers to be able to understand the system! 但类型重定向是难以理解的。

4、LLVM 3.0 新类型系统

the primitive and derived types are the same, only OpaqueType has been removed and StructType has been enhanced. 原始类型和派生类型保持不变,而不明确类型被移除了,以及结构体类型被优化了。

LLVM 3.0 uses a type system very similar to what C has, based on type completion. Basically, instead of creating an opaque type and replacing it later, you now create an StructType with no body, then specify the body later. To create the %intlist type, you'd now write something like this: LLVM 3.0 的新类型系统和 C 比较类似,基于类型补全。现在新的类型系统不会创建不明确类型,而是会创建一个没有实现体的结构体类型,然后之后再去补全其实现。

StructType *IntList = StructType::create(SomeLLVMContext, "intlist");
Type *Elts[] = { PointerType::getUnqual(IntList), Int32Type };
IntList->setBody(Elts);

... which is simple and to the point, much better than the 2.9 way. There are a few non-obvious ramifications to this design though.

4.1、Only struct types can be recursive

In the previous type system, an OpaqueType could be resolved to any arbitrary type, allowing such oddities as "%t1 = type %t1*", which is a pointer to itself. In the new type system, only IR structure types can have their body missing, so it is impossible to create a recursive type that doesn't involve a struct. 新类型系统只允许结构体类型,才可以有循环类型,比如指针指向其自身。

4.2、Literal and Identified structs

In the new type system, there are actually two different kinds of structure type: a "literal" structure (e.g. "{i32, i32}") and an "identified" structure (e.g. "%ty = type {i32, i32}"). 新类型系统当中,只有两种不同的结构体类型:字面结构体和已识别结构体。

Identified structures are the kind we are talking about: they can have a name, and can have their body specified after the type is created. The identified structure is not uniqued with other structure types, which is why they are produced with StructType::create(...). Because identified types are potentially recursive, the asmprinter always prints them by their name (or a number like %42 if the identified struct has no name). 已识别结构体是可以有类型名字的,其实现体可以在类型被创建时补全的。

Literal structure types work similarly to the old IR structure types: they never have names and are uniqued by structural identity: this means that they must have their body elements available at construction time, and they can never be recursive. When printed by the asmprinter, they are always printed inline without a name. Literal structure types are created by the StructType::get(...) methods, reflecting that they are uniqued (the call may or may not actually allocate a new StructType). 字面结构体不会有类型名字,在构建时实现体必须被补全了,不能是循环类型。

已识别结构体和其类型名字是一一对应的。

Because StructType::create always returns a new identified type, we need some behavior for when you try to create two types with the same name. The solution is that VMCore detects the conflict and autorenames the later request by adding a suffix to the type: when you request a "foo" type, you may actually get back a type named "foo.42". This is consistent with other IR objects like instructions and functions, and the names are uniqued at the LLVMContext level.

但这也导致了链接器的任务更加困难。

x.ll:
  %A = type { i32 }
  @G = external global %A
y.ll:
  %A = type { i32 }
  @G = global %A zeroinitializer

两个模块都被加载到 LLVMContext 当中,但它们都有类型名字 A,我们需要保证类型名字的唯一,所以在内存中实际上是这样的:

x.ll module:
  %A = type { i32 }
  @G = external global %A
y.ll module:
  %A.1 = type { i32 }
  @G = global %A.1 zeroinitializer

... and now it is quite clear that the @G objects have different types. When linking these two global variables, it is now up to the linker to remap the types of IR objects into a consistent set of types, and rewrite things into a consistent state. This requires the linker to compute the set of identical types and solve the same graph isomorphism problems that VMCore used to (see the "remapType" logic in lib/Linker/LinkModules.cpp if you're interested). 这样的话会导致 @G 类型不一致,所以需要链接器去修正。

4.3、这种改变的优势

类型名字在进行 pass 优化时,也是一个很好的传播信息。

5、概念

LLVM Language Reference Manual

5.1、Opaque Structure Types

Opaque structure types are used to represent named structure types that do not have a body specified. This corresponds (for example) to the C notion of a forward declared structure.
不明确类型用来表示未补全实现体的结构体类型。与 C 的提前声明的结构体一致。

参考

LLVM 3.0 Type System Rewrite

LLVM系列第一章:编译LLVM源码 详细介绍如何利用CMake和Ninja工具来编译及安装LLVM。 阅读详情

相关推荐

LLVM安装和LLVM包括LLVM tablegen学习笔记

LLVM 是一个跨平台(可在 Linux、Windows 和 Mac 上使用)C/C++ 编译器工具集,像 GCC一样。LLVM 可以编译用 C、C++ 和 Objective-C 编写的代码。LLVM 通过 libc++ 和 libc++ ABI 项目支持 C++11、C++14 和 C++17。LLVM 还部分支持最新的 C++20 和 C++2b 标准。**你可以基于LLVM提供的功能开发自己的模块,并集成在LLVM系统上,增加它的功能,或者就单纯自己开发软件工具,而利用LLVM来支撑底层实现。

outbreakrmb的博客 1460

LLVM 核心类简明示例

《带有LLVM的eBPF组件》 《The LLVM Compiler Infrastructure | LLVM编译器基础设施》 《LLVM每日谈 | 知乎》 《LLVM 核心类简明示例》 目录 LLVM 核心类简明示例 : llvm::Value && llvm::Type && llvm::Constant llvm::Value llvm:Type llvm::Constant LLVM 核心类简明示例 : llvm::Value &&a.

RToax 1531

LLVM - 学习笔记一

1. 工具和库 LLVM中的独立工具: opt:在IR级对程序进行优化的工具,输入必须是LLVM的bitcode,生成的输出文件必须具有相同的类型。 llc:通过特定后端将LLVM bitcode转换成目标汇编或目标问价的工具。 llvm-mc:能够汇编指令并生成像ELF、MachO、PE等对象格式的目标文件,也可以反汇编相同的对象,从而转存这些指令的相应汇编信息和内部LLVM机器指令数据结构。 lli:LLVM IP的解释器和JIT编译器。 llvm-link:将几个LLVM bitcode链接在一起,

zhugl的博客 4593

25 | 后端技术的重用:LLVM不仅仅让你高效

在编译器后端,做代码优化和为每个目标平台生成汇编代码,工作量是很大的。那么,有什么办法能降低这方面的工作量,提高我们的工作效率呢?。在前端部分,我就带你使用 Antlr 生成了词法分析器和语法分析器。那么在后端部分,我们也可以获得类似的帮助,比如利用 LLVM 和 GCC 这两个后端框架。相比前端的编译器工具,如 Lex(Flex)、Yacc(Bison)和 Antlr 等,对于后端工具,了解的人比较少,资料也更稀缺,如果你是初学者,那么上手的确有一些难度。

qq_37756660的博客 2101

LLVM语言参考手册_llvm 手册

这个调用约定试图使调用尽可能地快速(例如通过寄存器传递)。这种调用约定允许目标使用任何它想要为目标生成快速代码的技巧,而不必遵守外部指定的ABI(应用程序二进制接口,Application Binary Interface)。只有在使用GHC或HiPE约定时,才能优化Tail调用。这个调用约定不支持变长参数,且要求所有被调用对象的原型与函数定义的原型精确匹配。 这个调用约定尝试在假定调用通常不执行的情况下,使调用方中的代码尽可能高效。因此,这些调用通常保存所有寄存器,以便调用不会破坏调用方中的任何活动范围。

2401_89284515的博客 928

【Tableau】解决订阅报表PDF中文显示为乱码(方框)问题

一、问题描述 在tableau server网页端报表中文字符能正常显示,但在订阅后发送到邮箱时,以图片格式呈现的内容中中文字符显示为方框。 网页端报表能正常显示中文字符: 邮件中的报表不能正常显示中文字符: 二、问题分析 网页端字符显示情况是由html编码决定的,而tableau生成图片时是通过所部署的服务器的字体文件显示字体。因此可能是由于Linux服务器缺失相关的字体文件导致网页端选择的中文字体在服务器上缺失对应的字体文件导致的。则考虑在服务器引入对应的字体文件。 三、问题解决 1、查看服务器现有

why_not_study的博客 2833

LLVM IR(三)——IR类型体系(Type System)

3.1 Void Type Void类型不代表任何值,也没有大小。跟c、java中的void一样,在需要的地方起占位作用。 3.2 函数类型(Function Type) Function 类型可以看做是函数的签名,它包括了函数的返回类型和形参列表。 语法: <returntype> (<parameter list>) returntype函数的返回类型,必须是一个void类型或者first class类型(不包含label和metadata类型)。 Parameter list

yelvens的博客 4816

process插件_利用SPSS 插件MLmed检验多水平中介的调节模型

本文介绍一个SPSS插件,用于检验多水平中介的调节,及该方法与Mplus的比较。1 Mlmed 简介及安装Mlmed 简介: Mlmed 由Rockwood 2017年开发,基于SPSS插件计算Multilevel moderated mediation. 可以提供multilevel mediation Monte Carlo 置信区间比如(模拟20000次),以及多水平调节的...

weixin_39976081的博客 5775

LLVM Type

LLVM的Type

TomOfJerry的博客 325

DDR内存设计必看:DFI接口协议详解与实战避坑指南(附时序图解析)

本文深入解析了DDR内存设计中连接内存控制器与物理层的DFI接口协议,聚焦于DDR4/DDR5设计的实际痛点。文章详细阐述了协议版本兼容性、时钟架构(匹配频率与频率比系统)、PCB信号完整性设计以及上电初始化与调试等关键环节,为硬件工程师提供了一套从理论到实战的完整避坑指南,并附有时序图解析以助理解。

weixin_29256771的博客 331

光学遥感显著目标检测初探笔记总结

一张图片里最吸引注意力的部分就是显著性物体,其实是模拟人的一个注意力机制。目标是希望通过计算机的方法让我们自动定位和模拟人的这种感知能力,从而去定位场景中的一个让人感兴趣的目标。

醋酸洋红的博客 2077

【译】LLVM 的设计

LLVM 是开源的编译工具链项目,用 C++ 编写,包含一系列模块化的编译器组件和工具链,用于开发编译器前端和后端。 内容来源:https://aosabook.org/en/llvm.html 原文标题:LLVM 作者:Chris Lattner 译者:流左沙 注:翻译较原文会有一定的精简、重排和添删,主要是为了提取重要内容以达到更好的理解,请以原文为准。 Classical Compiler Design Implications of this Design The most import

fcsfcsfcs的博客 524

Arduino读取串口接收到的数据并发送数据

Arduino读取串口接收到的数据并发送数据 源程序: void setup() { Serial.begin(115200); } void loop() { String comdata ; while (Serial.available() > 0) { comdata += char(Serial.read()); delay(2); } if (comdata.length() > 0) { Serial.print("串口接收到的数据:"); Serial.println(comd

perseverance51博客 1万+

【AI系统】LLVM IR 详解

在上一篇文章中,我们已经简要介绍了 LLVM 的基本概念和架构,我们现在将更深入地研究 LLVM 的 IR(中间表示)的概念。了解 LLVM IR 的重要性是为了能够更好地理解编译器的运作原理,以及在编译过程中 IR 是如何被使用的。LLVM IR 提供了一种抽象程度适中的表示形式,同时能够涵盖绝大多数源代码所包含的信息,这使得编译器能够更为灵活地操作和优化代码。本文将进一步探究 LLVM IR 的不同表示形式,将有助于我们更好地理解代码在编译器中是如何被处理和转换的。

ZOMI酱 1374

HCIE-Datacom笔试题库

关于HCIE Datacom笔试考试题目整理!

坏坏-5的博客 611

Oracle EBS R12 主 / 辅助分类帐(主 / 辅助帐套)落地

本文提供了Oracle EBS R12主/辅助分类帐实施的全套文档模板,包含主帐套(法定帐套)和辅助帐套的完整配置清单、科目映射规则模板、4类典型调整分录示例(存货跌价、折旧差异、往来重分类、外币折算)以及税会/IFRS案例分录。重点阐述了主/辅助帐套的配置要点、SLA子分类帐关键配置、科目映射规则(一对一、多对一、重分类等类型)以及调整分录的自动生成逻辑。文档可直接用于项目实施,包含总体架构图、科目映射矩阵表、会计规则集配置等交付物,并附有Excel版模板便于直接使用。适用于需要实现多准则并行记账、税务调

金牌架构师 386

LLVM语言参考手册

LLVM语言参考手册1. 摘要(Abstract)2. 简介(Introduction)2.1 结构良好性(Well-Formedness)3. 标识符(Identifiers)4. 高级结构(High Level Structure)4.1 模块结构(Module Structure)4.2 链接类型(Linkage Types)4.3 调用约定(Calling Conventions)4.4 ...

梦在哪里的博客 5687

odom反算成经纬度

这个转换系统能够将局部坐标(相对于原点的X, Y, Z坐标)转换回全球坐标系统。将局部里程计数据反算成经纬度坐标的过程涉及几个关键步骤,主要是将相对于某个局部参考点的坐标(例如在机器人、无人车或无人机的导航系统中使用的坐标)转换回全球GPS坐标系统(WGS-84经纬度)。为了能够将局部里程计数据转换为经纬度,首先需要知道局部坐标系统的原点(即参考点)在全球坐标系统中的位置。对于从里程计获得的每个局部坐标点,使用步骤2中设置的坐标转换系统将其转换为经纬度。使用转换模型将局部坐标转换为全球坐标。

如果想成为中心,那么就到中心去吧。 650

论文分享 | LLVM: 用于程序分析和转换的编译框架

当深入研究模糊测试时,发现很多方案会用到LLVM,一种用于程序分析和转换的编译器框架,基于该框架进行代码插桩、污点追踪等操作。分享一篇发表于2004年CGO会议的LLVM经典论文,结合相关博客和教程从基本原理和入门实践中记录学习笔记。

3067

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

【译】LLVM 类型相等判断

在 LLVM 的类型系统中,进行两个类型是否等价的判断,对于做代码分析而言是很重要的,本文将介绍遇到的难题以及解决的方法。 内容来源:https://lowlevelbits.org/type-equality-in-llvm/ 原文标题:TYPE EQUALITY IN LLVM 作者:AlexDenisov 译者:流左沙 注:翻译较原文会有一定的精简、重排和添删,主要是为了提取重要内容以达到更好的理解,请以原文为准。 1、LLVM 类型系统的简述 关于 LLVM 类型系统的背景,可以看我之前写的一

fcsfcsfcs的博客 676

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

[译] LLVM IR中间码指令集

最近在研究这个东西, 实在百度不到, 最后在谷歌上找到英文手册. 英文手册地址:https://llvm.org/docs/LangRef.html 先说点废话: 国内的IT水平离世界顶级水平差距实在有点大, 个人感觉 有些因素导致这个结果: 1.化身广告商的百度, 想找点专业文章, 没戏. 2.墙. 有用的文档在墙外,不翻墙看不到. 3...

bluehire的专栏 2258

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

认识 LLVM

简介 LLVM是一套提供编译器基础设施的开源项目,是用 C++ 编写,包含一系列模块化的编译器组件和工具链,用来开发编译器前端和后端。它是为了任意一种编程语言而写成的程序,利用虚拟技术创造出编译时期、链接时期、执行时期以及“闲置时期”的优化。 LLVM的命名源自于底层虚拟机(Low Level Virtual Machine)的首字母缩写,导致不了解它的人以为它是类似于 JVM(Java Virtual Machine) 的虚拟机,实际上这个项目的范围并不局限于创建一个虚拟机,而是包括 LLVM 中介码(L

面向人生编程 1170

LLVM 3.0类型系统重写

原文地址:http://blog.llvm.org/2011/11/llvm-30-type-system-rewrite.html LLVM 3.0中最为普遍的IR(因此编译器API)改变之一是完全重新实现LLVMIR类型。这个改变拖延了很久(最初的类型系统自LLVM1.0延续),它使得编译器更快,极大地简化VMCore的一个关键子系统,并消除了IR某些通常导致混淆与不方便的设计点。本文解释为

wuhui_gdnt的专栏 2228

Labrys:基于LLVM的系统Fω类型系统的探索之旅

Labrys:基于LLVM的系统Fω类型系统的探索之旅 项目介绍 在编程语言的璀璨星河中,有一颗独特的星星——Labrys。这不仅仅是一个普通的玩具语言,它是一次深入TypeSystem领域的探险,专为那些对函数式编程和编译原理充满好奇的开发者打造。Labrys基于强大的LLVM基础设施,实现了先进的系统Fω(F-super)类型系统,为程序员提供了全新的代码设计和错误管理工具。 项目技术分析 ...

gitblog_00809的博客 371

llvm后端之SDNode设计

DAG节点的类型(也就是操作类型,对应于指令类型)是定义在llvm::ISD::NodeType枚举类型中;EVT是对MVT的封装,此外还提供了对MVT类型的扩展。当表示MVT之外的类型时,其V.SimpleTy为INVALID_SIMPLE_VALUE_TYPE。蓝色表示指令序列化时两个节点中间可以插入其他节点对应的指令,而红色节点表示两个节点生成的指令中间不能插入其他节点的指令。llvm后端DAG的类型系统分为三个层级,从外到内为:EVT、MVT、SimpleValueType。

fs3296的博客 2392

LLVM(还没译完, 回头再译)

This chapter discusses some of the design decisions that shaped LLVM1, an umbrella project that hosts and develops a set of close-knit low-level toolchain components (e.g., assemblers, compilers, de

Kreats的专栏 971
上一篇: 代码优化利器 LTO 介绍
下一篇: 跨语言调 C 库:DragonFFI
流左沙
博客等级 码龄11年 46粉丝 55原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值