LLVM Backend Practices - Part 1

LLVM Backend Practices - Part 1 PostEncoderMethod Background 一般情况下我们对于指令encoding采取传统的在指令定义的tablegen文件里,设置好指令的field mapping即可,如果新一代指令集有新指令,则定义新的Inst和fieldmap类即可。 在实际项目中,我们遇到过这种情况:架构演进过程中,每代之间指令功能变动不大,但指令encoding变动频繁,此外encoding采取的并不是顺序编码,而是逐bit的映射,目的是为了获取一定的指令shrink机会,即可变长指令。这里先不展开讨论shrink,而是着重讨论我们是如何解决encoding问题的。 Solution 解决方案整体上可以一句话概括:自动代码生成 + LLVM基础设施中的PostencodeMethod hook Code Auto-gen 针对每一代架构指令集,定义一张大表,可以是csv表格或其他便于非研发人员编辑与研发人员读取的格式均可,这个表格中定义每一个指令field对应encoding的比特位序列。 读取表格,针对每一类相同编码规则的指令,自动生成形似下述代码的encoder methods。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 unsigned XXXInst1PostEncoder(const MCInst &MI, unsigned EncodedValue, const MCSubtargetInfo &STI) { // code to transform EncodedValue ... return EncodedValue; } unsigned XXXInst2PostEncoder(const MCInst &MI, unsigned EncodedValue, const MCSubtargetInfo &STI) { // code to transform EncodedValue ... return EncodedValue; } // other post encoder methods LLVM Infrastrcture - PostEncoderMethod LLVM tablegen类Instruction中包含成员PostEncoderMethod,对需要使用postencoder的指令类绑定相应的method,即可完成绑定,例如ARM架构中的类似代码: 1 2 3 4 5 6 7 8 class NDataI<dag oops, dag iops, Format f, InstrItinClass itin, string opc, string dt, string asm, string cstr, list<dag> pattern> : NeonI<oops, iops, AddrModeNone, IndexModeNone, f, itin, opc, dt, asm, cstr, pattern> { let Inst{31-25} = 0b1111001; let PostEncoderMethod = "NEONThumb2DataIPostEncoder"; let DecoderNamespace = "NEONData"; } 这里为NDataI这类指令,绑定了一个Post encoder method,用于在code emitting时对encoding进行修改。 Shrink Shrink操作并不少见,很多可变长指令集都有对encoding的shrink操作,即在指令编码阶段,根据指令集编码的定义,允许按照一定规则将指令编码进一步缩短。根据指令集特点,会有不同的shrink策略。当然也有一些架构代码中定义了类似shrink的pass,会做一些target specific的指令替换或立即数优化,这与我们工作中遇到的shrink不相同,以下会称之为“某架构”。 某架构最长支持128bit,最短32bit编码,指令各个field根据一些经验和profiler数据,将编码的bit位分布在不同的dword上,并且会给这些域定义一个缺省值,这样就可以根据128bit中4个dword的u32值来判断某条指令实际是否会占据更高32bit的bit位,从而帮助编译器判断是否可以做shrink。 根据以上描述,编译器会根据某条指令初始编码中的4个u32值,是否为默认值,来判断最短可以shrink到几个dword,并且在实际占据的若干个dword的最后一个的最后一位上,设置一个endbit,即简单设为1,舍去后续的encoding,即可完成shrink。 Inlined ptx/asm Impl llvm有支持inline对应架构的asm汇编的基础设施,具体是定义一个继承MCAsmInfo类,做一些简单的配置和注册即可初步使能inline asm,当然前提是指令定义是tablegen中要定义好每个指令对应的汇编格式。以AMDGPU为例: 定义并配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // definition and configurations AMDGPUMCAsmInfo::AMDGPUMCAsmInfo(const Triple &TT, const MCTargetOptions &Options) { CodePointerSize = (TT.getArch() == Triple::amdgcn) ? 8 : 4; StackGrowsUp = true; HasSingleParameterDotFile = false; //===------------------------------------------------------------------===// MinInstAlignment = 4; // This is the maximum instruction encoded size for gfx10. With a known // subtarget, it can be reduced to 8 bytes. MaxInstLength = (TT.getArch() == Triple::amdgcn) ? 20 : 16; SeparatorString = "\n"; CommentString = ";"; InlineAsmStart = ";#ASMSTART"; InlineAsmEnd = ";#ASMEND"; //===--- Data Emission Directives -------------------------------------===// UsesELFSectionDirectiveForBSS = true; //===--- Global Variable Emission Directives --------------------------===// HasAggressiveSymbolFolding = true; COMMDirectiveAlignmentIsInBytes = false; HasNoDeadStrip = true; //===--- Dwarf Emission Directives -----------------------------------===// SupportsDebugInformation = true; UsesCFIWithoutEH = true; DwarfRegNumForCFI = true; UseIntegratedAssembler = false; } 注册 1 2 3 4 5 extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeAMDGPUTargetMC() { ... RegisterMCAsmInfo<AMDGPUMCAsmInfo> X(*T); ... } 那么inline ptx怎么做呢?其实也可以利用该机制,将ptx视为某个非ptx target所能识别的汇编,但要自定义ptx汇编语句的lexer、parser,并将inlined ptx codegen成llvm ir,所以这就要求将这个特殊的inlined asm处理的pass加在llvm ir阶段。 由于ptx其实功能非常繁多,直接generate成llvm ir,ir builder的开发量会比较大,并且有一些ptx指令功能其实是比较复杂的,因此我们也可以在llvm ir生成过程中,通过将ptx指令逻辑用c语言实现,放进libdevice库中,而在ir builder时直接生成libdevice function的call inst即可一定程度上提高实现的效率。同样,这也要求我们把pass加在llvm ir阶段,并且在always inliner之前,这样可以让libdevice function call自动inline。 这个实现方案有一个限制,就是编译器不太好区分是inline ptx还是inline native asm,此时需要牺牲掉inline ptx的语法检查功能,将无法解析的inline汇编语法认为是inline native asm,交给下一步inline native asm去处理。

2025/4/15
articleCard.readMore

How to support debug on GPU - WIP

How to support debug on GPU - WIP Before the Sharing First time to write a blog in English, worthy to memorize. Recently I’m interested by the debugging tech on heterogeneous computing system, CPU with GPU for example, especially the micro-arch design. I’m lucky that I was part of a developper toolchain team, I know a bunch of guys who are really familiar with debugging tech. In the beginning of 2018, our team were involved in the first NPU chip project of HUAWEI and we developed logging system(focusing on the software implementation), trace tool, IDE and the prototype of the debugger. Why was just a prototype, which is complicated and no comments here. Anyway, some guys were experts in that team, like my memtor Zheng. We communicated these days and concluded a brief design about the debugging system on GPU/NPU system. Background of Debugging Basic debug actions include: setting breakpoints, running by step, veiwing variables information(type, address, temporary value). In this blog, I will focus on the hardware support requirements to the “breakpoint setting” and “running by step”. In CPU system, OS provides various of system interruptions to support debugging user’s applications. For example INT3 instruction of X86 help trigger a interrupt for the debugger, debugger will catch the signal and process following debugging action, like viewing the value of temporary variable. This is a software level breakpoint. And also there is a hardware way to set a breakpoint. Hardware may design several register for storing breakpoint address, which is the PC address. When polling the breakpoint registers and meeting the PC is equal to the value stored in the breakpoint register, the PC issue module will stop issuing, which implements break action of user’s program. GPU Design for supporting Debugging Both software and hardware ways to set a breakpoint, the hardware should support instruction level interrupt in the micro arch design.

2025/2/14
articleCard.readMore

一篇不知道多久前翻译的译文--30年之后,QBasic依然是最棒的编程启蒙语言

译文:《30年之后,QBasic依然是最棒的编程启蒙语言》 前言 现在是北京时间2024年9月13日0点18分,本来是想随便聊聊最近的工作和生活,突然发现drafts目录里有一篇存货,仔细一看原来是不知道什么时候翻译的一篇英文博客。仔细回忆了一下,应该是某个技术自媒体的网友说可以尝试翻译一下投稿,过审了会有稿费。当然,既然是在自己的博客出现,显然是没有过审,看来自己的翻译能力还有待提高,请读者海涵。 译文 原文链接:http://www.nicolasbize.com/blog/30-years-later-qbasic-is-still-the-best/ translated by Ti May 5, 2016 by Nicolas Bize 我的大儿子Noah三个月前刚满7岁。在他这个年龄,让他和家人们玩两个小时的《我的世界》也能让他激动得心跳加速。除了《我的世界》之外,他最爱的游戏还有《超级马里奥制造》。看到孩子玩这款游戏也常让我想起自己在这个年龄时的样子,真是令人激动啊。大约5个月之前,我远离家人和朋友去参加一年一度的ludum dare[1]挑战。和往常一样,我会远离朋友和家人,甚至回到原始的穴居人状态,历时48小时不眠不休地从零开发一款游戏(文章末尾提供了链接,欢迎体验)。当我自豪地向我爱人展示我的“史诗级AAA大作”时,Noah也对编程世界产生了兴趣。于是我向这个不到7岁的孩子简单演示了这些简单的英文单词是如何构建起一个复杂游戏的。从那天起,Noah就开始反复地求我教他如何制作属于他自己的电子游戏。从那天开始,在之后5个月的时间里,我不断地寻找儿童编程语言和IDE的圣杯,渴望着能够将孩子的这种兴趣的火花,变成一段难忘的经历…… 为了达成这个目标,我把目光延伸到了无穷无尽的技术社区。在技术论坛和社区里,技术爱好者们向我提出了不少建议,我也尝试了无数种“儿童编程语言”,例如:SmallBasic,Pico-8,Smalltalk,Scratch等等。我甚至还求助了“万能”的StackOverflow,却都一无所获。5个月之后,我寻找儿童编程语言圣杯的愿望无疾而终,我被迫接受一个非常悲观的事实:30年过去了,QBasic仍然是最适合于编程启蒙的语言,且没有之一。 “天呐,请别教他GOTO!” 1 2 10 PRINT “OH NO, WHAT ARE YOU DOING?!!!” 20 GOTO 10 是的,QBasic确实是一门相当糟糕的过程式编程语言。它将被广泛认为不好的语言特性介绍给所有学习这门语言的人,包括隐式声明语法,不区分大小写,使用non-zero-based[2]计数等等。学习编程,最好的做法是从一开始就养成良好的习惯,而非多年之后再来纠正那些不良实践。按照这个逻辑,可能应该从我最爱的Ruby语言开始学习编程。然而,即便QBasic有许多在现在看来不是很恰当的语言特性,但QBasic具备的这些特性,都遵循着一个非常明确的设计目标:保持语言的简单性和易用性,而其他语言常常为了保证灵活性、一定的复杂度和逻辑性抛弃了这两个目标。 我在Noah的11’’ HP Stream上安装了QBasic,这个过程中还不得不使用了一些DosBox的破解手段。接着Noah双击图标,进入了IDE的介绍界面,这勾起了我很多回忆: 接下来,我向Noah介绍了每一位编程初学者的“神圣的仪式“:开发一个向这个圈子里其他同伴问好的程序。他慢慢地摸索着每一个按键,用右手手指小心翼翼地敲出了一串字符:PRINT "hello world" 他按下了F5键,当他看到自己的代码被编译、渲染到一片漆黑的屏幕上时,Noah显得非常惊喜。他欢笑着给了他老爹一个high-five,然后赶紧把电脑中刚刚敲下的代码抄录到了自己的笔记本上,为了防止自己忘记。 我们继续学习了其他指令:CLS, COLOR, PLAY, INPUT和IF。全程无需任何多余的解释,没有任何复杂度,没有生涩的操作符,没有抽象概念,没有必须仔细阅读的文档和手册,没有object/class/method等高级概念,不用安装任何开发框架,IDE中也没有大片的菜单和按钮,也没有特殊的单词和括号。一切仅仅是格式简单的纯代码。 不到1个小时,Noah就独立地写出了一个程序——一个交互式的,非常微妙的小程序,这个程序让你明白了计算机对你作为一个有情感的人类个体的感受。 随后他立马邀请来他最好的朋友Christian,并把程序运行给他看,极其自豪。 Noah甚至还给好朋友简单地介绍了这个程序的是如何运行的,以及这些代码的含义! 在这一个小时中,我这年龄才7岁的儿子不仅学会了写第一个纯文本交互游戏,还体验了创造的乐趣和激动,编译并运行了他那个小程序。更值得表演的是,他还将全部的学习成果记录在了笔记本上,这个习惯非常不错: 我很开心的是,今天Noah终于体会到了他爸爸一直在说的“我做着全世界最棒的工作”。而我唯一感到遗憾的是认识到了一个悲观的现实,经过了30多年,我们仍然没有创造出对于儿童编程更适合的编程语言:QBasic只有非常有限的关键字(全部的帮助文档只需一个F1屏,并且还能附上简单的示例),没有任何让程序员分心的可视化组件,具有非常紧凑的开发环境,它能尽可能早地报错,使用简单的一键式编译和运行。这么多年来,我们创造出了更稳定、更复杂的语言/框架/IDE。诚然,对于现实真正的应用开发而言,足够的稳定性和复杂度是必须的,但我们还是没能创造出比QBasic更简单、更容易让初学者感受到编程的快乐的语言。而且在“现代化”的软件生态和环境中,让新手用现代计算机(Mac/PC/Linux)去运行QBasic也变得让人感到恐惧,放在当年,只需要将一个3.5英寸的磁盘插入硬盘驱动即可······ 不论如何,今天值得庆祝的是,又有一位新朋友发现了编程的快乐与美丽!欢呼~ (按照文章开头的约定,欢迎体验我的AAA大作,而我,就等着EA来向我够买版权了) 译者注: [1] Ludam Dare:Ludam Dare是一个世界级的在线、快速游戏开发挑战活动。要求参与者利用一个周末的时间,基于赛前社区票选出的主题,从零开始制作一款游戏。 [2] Non-zero-based:Zero-based是一种从0开始的计数方法,相反Non-zero-based计数法是从非零数开始计数,例如从1开始。严格来讲从0开始或从1开始计数并没有对和错之分,但在计算机领域尤其是在编程语言领域,从0开始计数是一个比较常见的惯例,大部分具有一定影响力的编程语言也是这么设计的。

2024/9/13
articleCard.readMore

LLVM Note - Instruction Selection In LLVM

Instruction Selection In LLVM Instruction Selection Method FastISel SelectionDAGISel GlobalISel related hooks, like how to specify which isel method to use in TargetConfig::addCoreISelPasses will choose a valid isel method FastISel llvm ir based fast used in O0 workflow llvm ir -> machine instruction with virtual register how to impl define a class inherit from FastISel override a virtual function fastSelectInstruction, then emit each opcode in llvm ir which is target dependent SelectionDAG based ISel should lowering to DAG first from llvm ir will do several times legalize and combine in instruction selecting, includes: type legalize then combine vector legalize then combine dag legalize then combine will do heuristic schdeule on DAG after selected to MachineDAG workflow llvm ir -> lowering to DAG -> initialize DAG -> combine -> type legalize and combine -> vector legalize and type legalize and combine -> dag legalize and combine -> insturction selection -> instruction scheduling(will introduce later) how to impl define a class inherit from SelectionDAGISel override the entry of selection isel Select() override most of all selectXXX virtual function to select instruction, usually named as <TargetName>DAGToDAGISel() what is lowering lowering define some legalization info and other rules for DAG builder when SelectionDAG is initialized GlobalISel TODO

2022/3/17
articleCard.readMore

REPL技术分析——Python的交互式

概要 Python是一种解释型语言,通过解释器对代码进行逐行执行,一般的解释器也是这样实现,当然也存在一些优化方法,对代码进行JIT编译,提高执行速度。所以Python的REPL可以说是原生支持的。 Python语言有多种解释器,例如: CPython:C语言实现的Python解释器,一般情况下在Terminal中执行命令python,就会调用CPython解释器执行代码 PyPy:前面提到的通过JIT技术提升Python代码执行速度 IPython:Python的交互式解释器,底层也是通过调用CPython对代码进行解释执行 回到主题REPL,我们可以以IPython为入口进行分析,进一步对CPython进行分析 IPython 个人习惯,从源码出发分析。IPtyhon的github源码仓,链接,交互式开发的mainloop的代码在这个interactiveshell.py中,我们可以看到,IPython支持一个完整的代码块的交互式运行,采用异步的方式运行以保证一定的用户体验。一个完整的代码块,由用户输入,可以是一行完整的python代码,也可以是多行语法的代码块。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def _run_cell(self, raw_cell:str, store_history:bool, silent:bool, shell_futures:bool): """Internal method to run a complete IPython cell.""" coro = self.run_cell_async( raw_cell, store_history=store_history, silent=silent, shell_futures=shell_futures, ) # run_cell_async is async, but may not actually need an eventloop. # when this is the case, we want to run it using the pseudo_sync_runner # so that code can invoke eventloops (for example via the %run , and # `%paste` magic. if self.trio_runner: runner = self.trio_runner elif self.should_run_async(raw_cell): runner = self.loop_runner else: runner = _pseudo_sync_runner try: return runner(coro) except BaseException as e: info = ExecutionInfo(raw_cell, store_history, silent, shell_futures) result = ExecutionResult(info) result.error_in_exec = e self.showtraceback(running_compiled_code=True) return result return 继续分析这个run_cell_async: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 async def run_cell_async(self, raw_cell: str, store_history=False, silent=False, shell_futures=True) -> ExecutionResult: info = ExecutionInfo( raw_cell, store_history, silent, shell_futures) result = ExecutionResult(info) ... # If any of our input transformation (input_transformer_manager or # prefilter_manager) raises an exception, we store it in this variable # so that we can display the error after logging the input and storing # it in the history. try: cell = self.transform_cell(raw_cell) ... # Store raw and processed history ... # Display the exception if input processing failed. ... # Our own compiler remembers the __future__ environment. If we want to # run code with a separate __future__ environment, use the default # compiler compiler = self.compile if shell_futures else CachingCompiler() _run_async = False with self.builtin_trap: cell_name = self.compile.cache(cell, self.execution_count) with self.display_trap: # Compile to bytecode try: if sys.version_info < (3,8) and self.autoawait: if _should_be_async(cell): # the code AST below will not be user code: we wrap it # in an `async def`. This will likely make some AST # transformer below miss some transform opportunity and # introduce a small coupling to run_code (in which we # bake some assumptions of what _ast_asyncify returns. # they are ways around (like grafting part of the ast # later: # - Here, return code_ast.body[0].body[1:-1], as well # as last expression in return statement which is # the user code part. # - Let it go through the AST transformers, and graft # - it back after the AST transform # But that seem unreasonable, at least while we # do not need it. code_ast = _ast_asyncify(cell, 'async-def-wrapper') _run_async = True else: code_ast = compiler.ast_parse(cell, filename=cell_name) else: code_ast = compiler.ast_parse(cell, filename=cell_name) ... # Apply AST transformations try: code_ast = self.transform_ast(code_ast) ... # Execute the user code interactivity = "none" if silent else self.ast_node_interactivity if _run_async: interactivity = 'async' has_raised = await self.run_ast_nodes(code_ast.body, cell_name, interactivity=interactivity, compiler=compiler, result=result) ... # Reset this so later displayed values do not modify the # ExecutionResult self.displayhook.exec_result = None ... return result 我把一些异常处理的代码省略了,不关键的跳过。删除不关键的处理流程后我们可以分析下源码: 首先是将cell代码块(raw_cell),执行历史(store_history),以及一些设置运行模式的参数(silent和shell_futures)来示例化成ExecutionInfo,然后将它塞到ExecutionResult这个方法中,做进一步的封装,方便后续进行执行过程中关键信息的存储 tramsform_cell的工作主要是做一些行的分割以及其他处理,例如确保每一个输入cell最后有一个空行,这个也是编译器的常规操作,方便parsing的时候计算报错行号 _ast_asyncify是一个异步方法,将输入cell(源码)解析为ast,同样其他两个分支都是将输入解析为ast,这里的分支是为了区分版本,解决版本兼容性。这里的compiler我们没有在IPython源码中找到定义,推测是CPython的封装,后续再分析 run_ast_nodes也是调用了compiler的能力,下面我们就可以去CPython中进一步分析了 IPython主要对CPython进行封装,将ast导入给CPython进行执行。并且,部分情况下并没有调用compiler封装的run_code方法,而是直接使用Python内置的exec()方法执行python代码,处理也比较简单。 CPython CPython是python解释器的c语言实现,也是Python的官方解释器。按照惯例我们还是从源码入手,cpython托管在github上,项目链接。 源码分析 首先从main函数出发,找到Programs/python.c中的main函数,在进入到repl loop之前,我们快速过一下执行流程。当然,对于c/c++项目而言,最万能的方式还是通过调试,一步一步地借助断点和查看调用栈来分析。在只关注一个具体的功能的时候,个人还是比较偏向于直接看源码,聚焦关键的函数。 执行流程: Programs/python.c:16 => Py_BytesMain Modules/main.c:679 => pymain_main Modules/main.c:627 Py_RunMain => pymain_run_python 到pymain_run_python()函数,我们可以具体看一下这个函数里的构成: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 static void pymain_run_python(int *exitcode) { ... if (config->run_command) { *exitcode = pymain_run_command(config->run_command, &cf); } else if (config->run_module) { *exitcode = pymain_run_module(config->run_module, 1); } else if (main_importer_path != NULL) { *exitcode = pymain_run_module(L"__main__", 0); } else if (config->run_filename != NULL) { *exitcode = pymain_run_file(config, &cf); } else { *exitcode = pymain_run_stdin(config, &cf); } pymain_repl(config, &cf, exitcode); goto done; ... } 在这个函数中,通过函数最开始构建的运行环境配置(config),来决定后续的分支: run_command分支:调用pymain_run_command()函数,来执行命令 run_module分支:调用pymain_run_module()函数,运行一个python模块 run_filename分支:调用pymain_run_file()函数,运行一个python文件 其他:调用pymain_run_stdin()函数,来执行一个标准输入 最后:调用pymain_repl()函数,启动repl 上述执行分支中,估计大家对于最后两个分支(其他和最后)会感到十分疑惑,看起来逻辑有重复,其他分支中,调用pymain_run_stdin()函数后,再启动repl。实际上最后两个分支最终调用的函数都是一样的: pymain_run_stdin(): 1 2 3 4 5 6 7 static int pymain_run_stdin(PyConfig *config, PyCompilerFlags *cf) { ... int run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, cf); return (run != 0); } pymain_repl(): 1 2 3 4 5 6 7 static void pymain_repl(PyConfig *config, PyCompilerFlags *cf, int *exitcode) { ... int res = PyRun_AnyFileFlags(stdin, "<stdin>", cf); *exitcode = (res != 0); } 实际上,在pymain_repl()中调用的PyRun_AnyFileFlags(),在include/pythonrun.h中定义为: 1 2 #define PyRun_AnyFileFlags(fp, name, flags) \ PyRun_AnyFileExFlags(fp, name, 0, flags) 是一毛一样的呢。最终就执行到了我们的重头戏:Python/pythonrun.c:91 PyRun_InteractiveLoopFlags() interactive loop PyRun_InteractiveLoopFlags(stdin, "<stdin>", 0, cf)中,从stdin标准输入流中读取用户输入,进行执行: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags) { ... err = 0; do { ret = PyRun_InteractiveOneObjectEx(fp, filename, flags); if (ret == -1 && PyErr_Occurred()) { ... PyErr_Print(); flush_io(); } else { nomem_count = 0; } } while (ret != E_EOF); Py_DECREF(filename); return err; } 调用PyRun_InteractiveOneObjectEx()函数执行用户输入。 我们可以看到对于一个Python Object的执行流程如下: _PyUnicode_FromId:造一个modulename _PySys_GetObjectId:从stdin中读取用户输入 PyImport_AddModuleObject:加载import模块 run_mod:运行module run_mod()中: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static PyObject * run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals, PyCompilerFlags *flags, PyArena *arena) { PyCodeObject *co; PyObject *v; co = PyAST_CompileObject(mod, filename, flags, -1, arena); if (co == NULL) return NULL; if (PySys_Audit("exec", "O", co) < 0) { Py_DECREF(co); return NULL; } v = run_eval_code_obj(co, globals, locals); Py_DECREF(co); return v; } 先将python moduleParse成AST(调用PyAST_CompileObject()函数),再编译成Python的ByteCode,最后塞给run_eval_code_obj()函数进行执行。 基本上repl的执行流程就讲完了,有点困了,有(bu)时(xiang)间(nong)再细化补充,欢迎留言。 源码分析地比较粗糙,找到一片详细debug,介绍cpython中的编译执行流程的博客,见最后一片参考文章(Internals of CPython),写得比较详细,甚至还简单介绍了gdb的使用方式,很贴心。 Reference Python解释器 interactiveshell.py Modules/main.c Python/pythonrun.c Internals of CPython

2020/2/12
articleCard.readMore

REPL技术分析——Swift REPL模式

技术路线 在IR层面支持REPL,提供swift解释器,swift提供了编译器runtime,提供基础的词法分析、语法分析、IR生成能力,并可基于llvm ir进行表达式eval,通过JIT的方式在解释器中支持REPL。 源码分析 头文件:include/swift/Immediate/Immediate.h,包含两个接口 1 2 3 4 5 int RunImmediately(CompilerInstance &CI, const ProcessCmdLine &CmdLine, const IRGenOptions &IRGenOpts, const SILOptions &SILOpts, std::unique_ptr<SILModule> &&SM); void runREPL(CompilerInstance &CI, const ProcessCmdLine &CmdLine, bool ParseStdlib); RunImmediately方法用于基于SIL(Swift Intermediate Language),立即Eval当前IR Module,相当于是解释器 runREPL方法提供给FrontendTool.cpp前端逻辑进行调用,作为REPL的main loop 源码文件:Immediate.cpp 实现几个功能: 1 2 3 4 5 6 7 8 9 10 11 12 void *loadSwiftRuntime(ArrayRef<std::string> runtimeLibPaths); bool tryLoadLibraries(ArrayRef<LinkLibrary> LinkLibraries, SearchPathOptions SearchPathOpts, DiagnosticEngine &Diags); bool linkLLVMModules(llvm::Module *Module, std::unique_ptr<llvm::Module> SubModule); bool autolinkImportedModules(ModuleDecl *M, const IRGenOptions &IRGenOpts); int swift::RunImmediately(CompilerInstance &CI, const ProcessCmdLine &CmdLine, const IRGenOptions &IRGenOpts, const SILOptions &SILOpts, std::unique_ptr<SILModule> &&SM); 加载swift编译器runtime:swiftCore llvm ir module间合并 import module的支持 RunImmediately:将input翻译成llvm ir,调用llvm::ExecutionEngine直接执行llvm ir 源码文件:REPL.cpp REPL的主函数,从Frontend.cpp进来,主要由一个读取用户input的main loop,对每个输入进行处理。依赖histedit的支持 Reference: What’s REPL Source Code Immediate.cpp Source Code REPL.cpp histedit.h: Line editor and history interface.

2020/2/7
articleCard.readMore

基于Hexo和github page搭建个人博客

倒腾过好几次个人主页,但个人原创文章并不多,总是要么在换模板的路上,要不就是在换框架的路上,终于乐此而疲了。这次换个人主页之后还是要静下心来多写文章才是。这篇文章用于答谢Hexo和Archer主题的作者,hexo是到目前为止用过的最好用的博客框架,archer主题模板是目前个人比较喜欢的一款模版。 废话不多说,按照个人习惯,分四段简单介绍下如何基于Hexo和Github Page搭建个人主页。 本文不严格区分*nix系统和windows系统,所有命令均可在terminal或者gitbash中执行。 0x00 准备 hexo是一个静态站点生成工具,集创建(初始化站点)、开发(指的是写博客)、发布博客文章功能于一体,十分方便。 搭建博客站点先准备以下平台的账号和开发环境: 注册github账户,链接 安装git,并在github账户中配置sshkey:*nix系统一般自带git,windows系统安装gitbash,下载地址 配置sshkey: 生成sshkey:*nix系统中打开terminal,windows系统中打开gitbash,参考教程,执行以下命令, 1 cd ~ && ssh-keygen -t rsa -b 4096 -C "your_email@example.com" # your_email@example.com替换为你注册github的邮箱,可以一直按回车生成默认的文件名,方便阅读后续的步骤 执行以下命令,拷贝出你的public key,将屏幕中输出的公钥内容ctrl+c进行拷贝: 1 cd ~/.ssh/ && cat id_rsa.pub # 这里的id_rsa.pub为上一步生成的密钥对中的公钥 打开github中配置sshkey的界面,链接,选择New SSH key,把拷贝的公钥配置上去即可。 安装Nodejs,下载地址,安装成功后会同时安装好node和npm两个工具,并为npm配置国内源,例如配置淘宝源可以执行下述命令: 1 npm config set registry https://registry.npm.taobao.org 安装hexo,执行下述命令 1 2 npm install hexo-cli -g # install hexo hexo -v # check whether successfully installed 0x01 初始化站点 执行以下命令来初始化博客站点,会创建一个新的目录,以blog-dev为例,执行: 1 hexo init blog-dev 待命令执行完毕,会新建一个blog-dev目录,进入该目录中,我们可以看到以下目录树: 1 2 3 4 5 6 7 8 blog-dev ├── _config.yml #---------------- 站点配置文件 ├── node_modules #---------------- npm安装包本地保存目录 ├── package-lock.json ├── package.json #---------------- 依赖包配置文件 ├── scaffolds ├── source #---------------- 博客文章保存目录 └── themes #---------------- 博客主题保存目录 此时,进入到blog-dev目录,可以执行hexo的相关参数及命令,可以进行站点的管理,简单介绍如下: 1 2 3 4 hexo clean # 清除缓存 hexo generate # 生成网站静态文件 hexo server # 启动本地调试模式,默认可以打开localhost:4000查看网站效果 hexo deploy # 发布到GitHub Page,第0x11节会具体介绍 hexo还有一个非常方便的点是主题套用十分方便,将想要的模板下载下来,放到themes目录下,然后修改_config.yml文件中的theme字段,改为下载下来的主题目录名即可。主题可以在官方网站下载,也可以自行从各种渠道获取。 0x10 创建github page 本节内容大家应该都比较熟悉,创建作为个人主页用的github page,必须将仓名命名为username.github.com,经过测试,仓名中的username与你的github账号名可以不区分大小写。 这部分教程我想偷懒跳过了,可以自行查看官方教程。 0x11 预览和发布 在blog-dev目录下执行hexo new '博客标题'即可创建一篇新的博客,使用markdown语法进行编辑。 编辑完毕之后,执行下述命令,可以启动一个本地的临时服务器进行预览: 1 hexo server # 启动本地预览服务 在浏览器中打开localhost:4000,即可预览你的博客。 配置blog-dev的发布路径,编辑blog-dev/_config.yml文件,一般在文件末尾,有一个deploy字段,把第三节创建的github page仓库路径配置上去即可,如果deploy字段下只有一个type,则手动添加其他字段(注意yml文件遵从yaml格式缩紧)。例如我的配置就是: 1 2 3 4 deploy: type: git repo: https://github.com/ChinoMars/chinomars.github.io.git branch: master 依次执行下述命令,即可发布你的静态站点: 1 2 3 hexo clean # 清除缓存 hexo generate # 生成站点的静态文件 hexo deploy # 自动push到github page所在的master分支,进行发布 最后,可以打开网址:https://username.github.io即可查看个人的站点。

2020/1/29
articleCard.readMore