【译】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

单片机毕业设计-基于STM32单片机的智能镜系统-仿真设计

可通过MQ-2监测当前烟雾浓度可通过温湿度传感器监测当前温湿度可通过光照强度传感器监测光照强度可通过蜂鸣器进行烟雾报警可通过语音识别模块进行反馈播报状态可通过按键设置阈值三个继电器控制灯、加湿、风扇标签:STM32单片机、OLED112864、DHT11、BH1750、继电器、SU-03T、MQ-2、蜂鸣器题目扩展:智能家居安防、智能大棚控制系统、智能环境检测系统项目编号:ZL-32-240604-FZ。

bsdog11的博客 967

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

YOLOv8环境搭建+训练自己的数据详细项目

直接按照路径会有问题,找到自己 requirements.txt 文件路径,我这里是pip install -r D:\ultralytics-main\ultralytics-main\requirements.txt。首先去自己的anaconda的安装的envs(虚拟环境),在导航栏输入cmd,进入命令窗口。到官方网站下载yolo模型 ,下载好后解压,里面有个文件requirements.txt。然后安装ultralytics ,这是必须的。

m0_63172128的博客 2万+

LLVM Type

LLVM的Type

TomOfJerry的博客 325

遥感数据下载——FIRM:VIIRS火点数据、MODIS火点数据简介及下载

一、FIRMS(Fire Information for Resoutce Management System) 网站链接:https://firms.modaps.eosdis.nasa.gov/map/ 这个网站上对VIIRS及MODIS的火灾数据进行了显示,可以进行按日期、产品类别等动态的展示火点数据或火烧迹地,同时也提供了数据下载控件。高级选择中还可以进行数据统计结果展示。 二、VIIR...

knkn123的博客 3万+

【HSPiP应用指南】从理论到实践:如何利用汉森溶解度参数优化配方设计

本文深入解析了汉森溶解度参数(HSP)理论及其在配方设计中的核心价值,并详细介绍了如何利用HSPiP软件工具从理论走向实践。文章通过匹配溶解度参数、计算汉森距离(Ra),系统阐述了如何解决溶剂选择、颜料分散稳定性及扩散行为预测等关键问题,并结合界面气相色谱(IGC)等实验方法提升预测精度,旨在帮助配方师实现配方设计的可视化、可量化与高效优化。

weixin_29305313的博客 1649

【译】LLVM 的设计

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

fcsfcsfcs的博客 524

UG NX二次开发(C#)-VS2022配置NX二次开发模板不能正常显示的问题

随着VS2022的发布,使用VS2022进行UG NX二次开发的人员也会越来越多,但是由于VS2022和以前的VS版本采用代码结构不同,而UG NX的二次开发编程模板还没有适应于VS2022的模板配置,所以不能正常的创建一个完整的工程。本文采用曲线救国的方式来实现VS2022下的UG NX二次开发。如果有读者能很好的解决模板配置,不吝赐教。

GimiGimmy 4275

【AI系统】LLVM IR 详解

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

ZOMI酱 1374

深度学习技巧应用26-CNN中多种注意力机制的嵌入方法,终于搞懂注意力机制了

大家好,我是微学AI,今天给大家介绍一下深度学习技巧应用26-CNN中多种注意力机制的嵌入方法,终于搞懂注意力机制了。CNN是一种能够有效处理图像和其他二维数据的深度学习模型。在传统的CNN中,每个卷积核都会对输入的所有位置进行相同的操作,这可能导致网络无法针对特定区域或特征进行更加关注和重点处理。 注意力机制被引入卷积神经网络中,旨在使网络能够自动地学习和聚焦于输入中的重要区域或特征。通过引入注意力机制,可以增强网络对不同区域或特征的关注度,提取更有意义和相关的特征,从而提高模型的性能和表达能力。

微学AI的博客 3313

【Claude】Invalid model / Opus not available 错误:模型不可用的原因 bug报错已解决

使用 Claude API 时,指定错误或不可用的模型 ID 会收到 Invalid model 错误。本文从模型命名规则出发,给出模型降级链与动态选择方案。

博客 7915

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

AD-Copilot 论文解读:把“大模型看图”升级为“对照检验”的工业异常检测助手

本文提出AD-Copilot,一个面向工业异常检测的视觉语言助手,通过视觉上下文比较解决现有方法的不足。针对工业场景中多模态大模型表现不佳的问题,作者指出核心原因在于缺乏工业数据和比较能力不足。为此,论文提出三个关键贡献:构建Chat-AD数据集,设计Comparison Encoder显式建模图像差异,以及采用多阶段训练策略。实验表明,该方法在MMAD基准上达到82.29%准确率,定位任务提升3.35倍,并展现出良好的泛化能力。消融实验验证了比较模块的有效性,表明其成功源于高质量比较数据与显式比较模块的协

blanokvaffy的博客 333

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

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

3067

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

【译】LLVM 类型相等判断

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

fcsfcsfcs的博客 676

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

[译] LLVM IR中间码指令集

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

bluehire的专栏 2258

认识 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、付费专栏及课程。

余额充值