协同程序的编译器优化的制作方法

文档序号:15626986发布日期:2018-10-09 23:13阅读:316来源:国知局

在计算中,协同程序可以被视为通用例程实体,对于该通用例程实体除了通常由例程支持的调用和返回操作之外,其还支持挂起和恢复操作。也就是说,协同程序可以被调用(call)或被激活(invoke),并且在完成时,可以向调用方返回值,并且在这个意义上像其他种类的例程实体(例如,函数、过程)相似地进行操作。但是,除此之外,协同程序可以挂起执行,将控制传递给另一代码片段(也可能产生值),然后在挂起点恢复执行。一些协同程序具有多个挂起点。一些挂起点是重入的,即,在完成较早调用之前,它们可以再次被调用(以便创建另一实例)。例如,在实现协同任务、异常、事件循环、迭代器、延续、无限列表和管道时,可以使用协同程序。



技术实现要素:

一些实施例涉及减少协同程序使用的存储器的技术问题。在一些情形下,通过协同程序进行存储器分配的常规途径将耗尽可用存储器,这使得在给定系统上运行给定程序是不切实际或不可能的,但是使用本文中所描述的一个或多个优化有助于减少存储器使用,足以允许优化版本的相同程序在同一系统上运行。

一些实施例标识作为优化其激活帧可以在调用方的栈上被分配而非如常规那样在堆上分配帧的协同程序。例如,当编译器确定协同程序c的寿命(lifespan)不能超过首先调用协同程序c的例程r的寿命时,编译器生成代码以在r的栈上分配c的激活帧,而非生成代码以从堆存储器分配c的帧。在一些情况下,作为另一优化,协同程序c的代码也与调用c的例程r的代码内联。

所给出的示例仅是说明性的。本发明内容不旨在标识所要求保护的主题的关键特征或必要特征,也不旨在用于限制所要求保护的主题的范围。相反,提供本发明内容以简化形式介绍下文在具体实施方式中进一步描述的一些技术概念。创新由权利要求限定,并且在本发明内容与权利要求冲突的情况下,应当以权利要求为准。

附图说明

参考附图给出更具体的描述。这些附图仅仅说明所选择的方面,因此不完全确定覆盖范围或范围。

图1是图示了具有在软件控制下彼此交互的至少一个处理器和至少一个存储器的计算机系统,以及可能存在于多个网络节点上的操作环境中的其他项,并且还图示了所配置的存储介质(与纯信号相反)实施例的框图;

图2是图示了示例体系架构中的协同程序、协同程序激活帧内容以及协同程序编译和/或运行时间环境的方面的框图;

图3是图示了一些过程的步骤和所配置的存储介质的流程图;

图4是图示了协同程序帧分配的存储器优化的数据流程图;

图5图示了协同程序的初始化和恢复点的示例元组列表;

图6和图7共同示出了示例元组列表,其图示了使用编译器内嵌原语函数来标识协同程序恢复点;

图8图示了被注释成以图示使用定义链接的示例元组列表;

图9是图示了协同程序激活帧存储器布局和访问的示例的图;以及

图10是图示了栈布局的图。

具体实施方式

缩略词

下文对一些缩略词进行定义,但是其他缩略语可以在本文中别处定义,或者不需要定义就可以被技术人员所理解。

abi:应用二进制接口

alu:算术和逻辑单元

api:应用程序接口

be:后端(编译器的后端,负责优化和降低到特定体系架构机器代码的编译器部件)

cd:光盘

cfg:控制流保护

cil:通用中间语言(以前的msil-microsoft中间语言)

cpu:中央处理单元

cx:c++扩展

dvd:数字通用盘或数字视频盘

ebx:x86体系架构寄存器

eh:异常处理,异常处理程序

fpga:现场可编程门阵列

fpu:浮点处理单元

gpu:图形处理单元

gui:图形用户接口

ide:集成开发环境,有时也被称之为“交互式开发环境”

il:中间语言

ir:中间表示(例如,以cil或另一il)

jit:准时制

pc:程序计数器(也称之为指令指针)

ppl:并行模式库

ram:随机存取存储器

rbp:x64体系架构寄存器

rom:只读存储器

ssa:静态单赋值(ir形式,以该形式每个变量被精确赋值一次并且每个变量在使用前被定义)

std:标准c++命名空间

stl:标准模板库

udt:用户定义类型

utc:通用元组编译器

关于超链接的注释

本公开的部分包含可以被认为是浏览器可执行代码的url、超链接和/或其他项。为了帮助描述一些实施例,这些项被包括在本公开中,而不是被包括以引用它们标识的网站的内容。申请人不打算将这些url、超链接或其他此类代码作为活动链接。这些项都不旨在通过引用位于本披露文件之外的材料,来用作并入。因此,本文中不应反对包括这些项。在这些项尚未被禁用的情况下,专利局将在准备将本文档的文本加载到其官方web数据库时禁用它们(即,使它们作为链接无效)。mpep§608.01(vii)。

概述

在一些实施例中,通过允许多个入口点在某些位置处挂起和恢复执行,协同程序将子例程一般化以用于协同(即,非抢占式)多任务处理。协同程序可以用于实现协同任务、异常、事件循环、迭代器、延续、无限列表和管道。协同程序也可以在c++/cx中替换“.then”,这极大减少代码大小。本文中的一个示例示出了使用新c++关键字“yield”的代码生成器。还提供了一个“await”的c++关键字示例。还讨论了编译器中的从前端读入的il,到寄存器分配以及代码的最终发出中的步骤。还讨论了安全性和优化。所给出的一些细节是用于amd64处理器体系架构,但是可以为其他处理器体系架构提供类似的功能和优化,这些其他处理器体系架构包括例如x86、amd64、arm32和arm64体系架构。

可以在更广泛的上下文中查看本文中所描述的一些实施例。比如,诸如分配存储器、调用例程、编译代码和恢复执行之类的概念可能与具体实施例相关。然而,从广泛的上下文的可用性来看,本文中并不寻求抽象概念的排他性权利;它们并非抽象概念。相反,本公开集中于提供适当特定的实施例,其技术效果完全或部分地解决具体技术问题,例如,有限堆存储器空间、协同程序不安全性。涉及分配存储器、调用例程、编译代码和恢复执行之类的其他介质、系统和方法在本范围之外。因而,在适当理解本公开内容的情况下,还避免了模糊性、纯粹的抽象性、缺乏技术性和伴随的证明问题。

本文中所描述的实施例的技术特点对于本领域普通技术人员将是显而易见的,并且对于广泛的关注读者而言也将以几种方式显而易见。首先,一些实施例解决了技术问题,诸如由于使用其激活帧在堆上分配的大量协同程序而耗尽堆存储器,以及如何确定协同程序激活帧何时能够安全地在堆以外的别处上分配。第二,一些实施例包括技术部件,诸如以与通用计算机内的典型交互之外的方式与软件进行交互的计算硬件。例如,除了诸如通常存储器分配、通常存储器读取和写入、通常指令执行以及某种i/o之类的正常交互之外,本文中所描述的一些实施例根据协同程序的寿命的范围在协同程序的编译时生成栈分配或堆分配代码。第三,一些实施例提供的技术效果,包括在一些情况下以两倍、或三倍,或五倍、甚至更多增加可以在给定系统上运行的协同程序的数目,而不会由于存储器不足而崩溃。第四,一些实施例包括技术适应性,诸如实现并发过程的协同(即,非抢占式)调度的yield语句和/或await表达式。第五,一些实施例基于诸如使用定义分析之类的技术考虑,通过添加协同程序激活帧分配优化来修改编译器的技术功能。第六,一些实施例的技术优点包括并行处理的可靠性提高、使用协同程序的程序的开发简化以及存储器硬件要求减少。

现在将参考示例性实施例,诸如附图中所示的那些实施例,并且特定语言将在本文中用于描述它们。但是,在本文中所图示的特征的更改和进一步的修改,以及一个或多个相关领域的技术人员可能想到的、并且拥有本公开内容的本文中的具体实施例所图示的抽象原理的附加技术应用,都应当被考虑在权利要求的范围内。

术语的含义在本公开内容中被阐明,因此对于这些澄清应当仔细留意阅读权利要求。给出了特定的示例,但是一个或多个相关领域的技术人员将理解,其他示例也可以落入所使用的术语的含义内,以及落入一个或多个权利要求的范围内。术语在这里并不一定具有它们在一般使用(特别地,在非技术性使用的情况下)、或特定行业中使用、或特定字典或字典集合中具有的相同含义。附图标记可以用各种短语来使用,以帮助显示术语的广度。在给定文本片段中省略附图标记并不一定意味着图中的内容没有通过文本进行讨论。发明人声称并且行使他们对他们自己的词典编纂的权利。所引用的术语被明确定义,但是当隐含定义术语时,没有使用引号。可以在具体实施方式和/或申请文件的其他地方明确或隐含地定义术语。

如本文中所使用的,“计算机系统”可以包括例如一个或多个服务器、主板、处理节点、个人计算机(便携式或非便携式)、个人数字助理、智能电话、智能手表、智能带、小区或移动电话、至少具有处理器和存储器的其他移动设备和/或提供至少部分地由指令控制的一个或多个处理器的一个或多个其他设备。指令可以是存储器和/或专用电路中的固件或其他软件的形式。特别地,尽管可能出现许多实施例在工作站或膝上型计算机上运行,但是其他实施例可以在其他计算设备上运行,并且任何一个或多个这样的设备可以是给定实施例的一部分。

“多线程”计算机系统是支持多个执行线程的计算机系统。术语“线程”应当被理解为包括能够或可能进行调度(并且可能同步)的任何代码,并且本领域技术人员还可以通过另一名称(诸如例如,“任务”、“过程”或者甚至“协同程序”)而得知,尽管他们还将认识到“协同程序”在本文中的更特定意义,这是因为本文中所讨论的协同程序是经由恢复和挂起来进行协同调度,该协同程序是可恢复并且除了初始入口点之外还具有至少一个恢复点。通常线程通常是抢先调度的;除非另有说明,否则本文中的协同程序是非抢占式的。本文中所讨论的协同程序和其他线程可以并行运行、顺序运行或以并行执行(例如,多任务处理)和顺序执行(例如,时间分片)的组合运行。多线程环境已经被设计成各种配置。执行线程可以并行运行,或者线程可以被组织用于并行执行,但是实际上依次顺序执行。可以以多种方式来实现多线程,例如,通过在多处理器环境中的不同核上运行不同线程、通过对单个处理器核上的不同线程进行时间分片或者通过时间分片和多处理器线程的某种组合。线程上下文切换可以例如由内核的线程调度器、用户空间信号,或者由用户空间和内核操作的组合来发起。例如,线程可以依次在共享数据上操作,或者每个线程可以在其自己的数据上操作。

“逻辑处理器”或“处理器”是单个独立硬件线程处理单元,诸如同时多线程实现方式中的核。作为另一示例,每个核运行两个线程的超线程四核芯片具有八个逻辑处理器。逻辑处理器包括硬件。术语“逻辑”用于防止给定芯片最多具有一个处理器的错误结论;“逻辑处理器”和“处理器”在本文中可互换使用。处理器可以是通用的,或者它们可以针对诸如图形处理、信号处理、浮点算术处理、加密、i/o处理等之类的特定用途而被定制。通用处理器的目的根据其正在执行的代码而改变。特别地,当处理器正在执行程序的编译时,它可以被视为编译处理器,并且当同一处理器正在执行把处理器的特定体系架构作为目标的编译代码时,可以稍后将其视为目标处理器。编译处理器和目标处理器还可以是不同的处理器,例如,可以具有处理器p的一台机器上编译代码,然后在具有不同处理器q的不同的机器上运行编译后的代码。

“多处理器”计算机系统是具有多个逻辑处理器的计算机系统。多处理器环境出现在各种配置中。在给定配置中,所有处理器可以在功能上等同,而在另一配置中,由于具有不同的硬件能力、不同的软件赋值或两者,一些处理器可能与其他处理器不同。根据配置,处理器可以在单个总线上彼此紧密地耦合,或者它们可以松散地耦合。在一些配置中,处理器共享中央存储器,在某些配置中,它们各自具有它们自己的本地存储器,并且在一些配置中,存在共享存储器和本地存储器。

“内核”包括操作系统、系统管理程序、虚拟机、bios代码和类似的硬件接口软件。

“代码”是指处理器指令、数据(其包括常数、变量和数据结构)、或指令和数据两者。

“协同程序”是提供非抢占式多任务处理的软件部件。它们具有多个入口点,从而允许其执行被挂起并且在其主体内的一个或多个指定位置处恢复。

“可恢复函数”是协同程序,它也是函数,即,在完成时它返回值。协同程序还可以被实现为具有副作用但不直接向调用方返回值的过程。

在所提出的可以被查看(例如,www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4134.pdf)的名为“resumablefunctionsv.2”的编号为n4134的标准文档中,“可恢复函数”被定义为“所提出的实现无栈协同程序的c++语言机制”。然而,出于本公开的目的,并且如本文中所使用的,可恢复函数(a)不一定限于c++语言环境,(b)不一定限于无栈的实现方式。如本文中所使用的,无栈的实现方式是其中(i)可恢复函数本身不为在恢复时需要可用的任何本地数据,分配栈存储器或者(ii)只有协同程序本身可以被挂起-从协同程序内调用的子协同程序不能挂起的那些无栈实现方式。可以使用任一定义;在没有明确定义选择的情况下,推定为定义(ii)。在某些情形下,由于可恢复函数的行为像函数,因此可以使用栈,以从调用方获取参数,存储返回地址等等。然而,该函数使用的本地数据不会存储在栈激活帧上,因为当函数返回到处于挂起下的调用方时,该帧将被释放。“栈式”实现方式与非栈式实现方式相反。附带地,上文以“n4134.pdf”结尾的查看位置是禁用超链接的示例,并且这些不旨在通过引用而被并入。

“例程”是指经由跳转和上下文保存来接收控制的功能、过程、异常处理程序、中断处理程序或另一指令块。上下文保存在栈上推送返回地址或以其他方式保存返回地址,并且还可以在从例程返回时,保存要还原的寄存器内容。协同程序是一种具体例程。

“优化”是指要改善而不一定使之完美。例如,可以对已经优化的程序进行进一步改进。

“程序”在本文中广泛使用,以包括程序员(还被称为开发人员)编写的应用程序、内核、驱动程序、中断处理程序、固件、状态机、库和其他代码。

“iot”或“物联网”是指可寻址嵌入式计算节点的任何联网集合。这样的节点是如本文中所定义的计算机系统的示例,但是它们还具有以下特点中的至少两个特点:(a)没有本地人类可读显示;(b)没有本地键盘;(c)主要输入源是追踪非语言数据源的传感器;(d)没有本地旋转磁盘存储装置,即ram芯片或rom芯片提供唯一的本地存储器;(e)没有cd或dvd驱动;(f)在家用电器中嵌入;(g)在植入式医疗设备中嵌入;(h)在车辆中嵌入;(i)在过程自动化控制系统中嵌入;或(j)集中在以下各项中的一项的设计:环境监测、市政基础设施监测、工业设备监测、能源使用监测、人类或动物健康监测或物理运输系统监测。

如本文中所使用的,除非另有说明,否则“包括”允许附加元件(即,包括装置,该装置包括)。“由…组成”是指基本上由...组成或完全由...组成。当x的非y部分(如果有的话)可以被自由地更改、移除和/或添加,而不更改所要求保护的实施例的功能时,则x基本上由y组成。

“过程”有时在本文中用作计算科学领域的术语,并且在该技术意义上,例如涵盖资源用户,即,协同程序、线程、任务、中断处理程序、应用过程、内核过程、过程和对象方法。“过程”在本文中还用作专利法领域术语,例如在描述与系统权利要求或制品(所配置的存储介质)权利要求相反的过程权利要求中。类似地,“方法”有时用作计算科学领域的技术术语(一种“例程”),还用作专利法领域术语(“过程”)。技术人员将理解在具体实例中意图是什么含义,并且还将理解,给定的所要求保护的过程或方法(在专利法意义上)有时可以使用一种或多种过程或方法(在计算科学意义上)来实现。

“自动地”是指通过使用自动化(例如,由用于本文中所讨论的特定操作和技术效果的由软件配置的通用计算硬件),这与没有自动化相反。特别地,“自动地”执行的步骤不是用手在纸上或在人脑中执行,尽管它们可以由人类发起或者由人交互引导。使用机器执行自动步骤,以便获得一种或多种技术效果,该技术效果在没有如此提供的技术交互的情况下是不能实现的。

技术人员理解,技术效果是技术实施例的推定目的。例如在实施例中涉及计算并且还可以在没有技术部件的情况下(例如,通过纸和笔,甚至作为意识步骤)执行一些计算的纯粹事实,不会去除技术效果的存在或更改实施例的特定和技术性质。例如,某些熟悉的设备执行平衡计算以维护其平衡;一些示例包括移动机器人和轮式个人移动设备(segway,inc.的标志)。这些设备不是本文中所描述的实施例的一部分,而是它们说明技术效果通过技术部件而非通过纯粹意识步骤而被提供。平衡计算根本无法仅通过意识步骤或纸和笔,来足够快地执行以提供许多移动机器人或轮式个人移动设备中存在的平衡。因此,具有动态平衡设备的技术效果由包括处理器和与平衡控制软件相互作用的存储器的技术部件而被提供。

“计算上”同样是指正在使用的计算设备(至少处理器加存储器),并且排除单独通过纯粹人类思想或纯粹人类行为而获得的结果。例如,用纸和笔做算术不是如本文中所理解的那样在计算上做算术。计算结果更快,更广泛、更深入、更准确、更一致、更全面、和/或以其他方式提供超过人类绩效范围的技术效果。“计算步骤”是在计算上执行的步骤。“自动地”与“计算上”均不是必然意味着“立即”。“计算上”和“自动地”在本文中可互换使用。

“主动地”意味着没有来自用户的直接请求。实际上,用户可能甚至没有实现实施例的主动步骤是可能的,直到步骤的结果已经呈现给用户为止。除非另有说明,否则本文中所描述的任何计算和/或自动步骤也可以主动进行。

“语言上”意味着通过使用经常用于面对面的人与人之间的沟通的自然语言或其他形式的通信。语言上的沟通包括例如使用手指、手、脸和/或身体的说话、打字或手势。

在整个本文档中,使用可选复数意味着存在所指示的特征中的一个或多个特征。例如,“处理器”意味着“一个或多个处理器”或等同地“至少一个处理器”。

在整个本文档中,除非另有明确说明,否则对过程中的步骤的任何引用假设该步骤可以由感兴趣方直接执行和/或由该方通过中间机构和/或中介实体间接执行,以及仍然在步骤的范围之内。也就是说,除非直接执行是明确陈述的要求,否则不要求感兴趣方直接执行该步骤。例如,涉及感兴趣方关于目的地或其他对象的动作的步骤(例如,正在分配、调用、确定、执行、生成、内联、优化、设置、恢复、返回、设置、挂起、使用、验证、或分配、已分配、调用、被调用等)可以涉及某一其他方的中间动作,诸如转发、复制、上传、下载、编码、解码、压缩、解压缩、加密、解密、认证、调用等,但是它们仍可以被理解为由感兴趣方直接执行。

无论何时引用数据或指令,都应当理解这些项配置计算机可读存储器和/或计算机可读存储介质,从而将其变换为特定物品,这例如与简单地存在于纸上、存在于人的思想中、或者作为在电线上传播的纯信号相反。除非在权利要求中另有明确说明,否则权利要求不涵盖信号本身。出于在美国专利保护的目的,存储器或其他计算机可读存储介质不是在美国专利商标局对inrenuijten案例解释下的可获得专利的主题范围之外的传播信号或载波,并且基于本公开来对美国专利申请中的存储介质的要求保护不应当被解释为针对信号本身。

而且,尽管存在与在本文中别处明显相反的任何内容,但是要理解一方面(a)计算机可读存储介质和计算机可读存储器与另一方面(b)传输介质(还被称为信号介质)之间的明显区别。传输介质是传播信号或载波计算机可读介质。相反,计算机可读存储介质和计算机可读存储器是不传播信号或载波计算机可读介质。除非另有明确说明,否则“计算机可读介质”是指计算机可读存储介质,而非传播信号本身。

本文中的“实施例”是个示例。术语“实施例”与“本发明”不可互换。即使本文中本身没有明确描述所得方面组合,实施例也可以自由地共享或借用方面来创建其他实施例(只要结果是可操作的)。对于本领域技术人员而言,要求明确描述每个和每一所准许的组合是不必要的,并且可能与认识到为本领域的读者所撰写的专利说明书的策略相反。关于从甚至少量可组合特征产生的可能组合的数目的正式组合计算和非正式公共直觉还将指示存在用于本文中所描述的方面的大量方面组合。因而,要求对每个和每一组合进行明确叙述可能与要求专利说明书简明扼要并且让读者在相关技术领域有所了解的策略相反。

本文中所给出的备选定义表示备选实施例集合。除了将这些备选定义视为不适当和不可调和的不一致性之外,所描述的总实施例集合应当被理解为与相应定义相对应的各个实施例组的设定操作并集。这适用于明确定义和隐含定义两者。例如,如果一个定义陈述协同程序具有特性a和b,另一定义陈述协同程序具有特性a和c,则不能因此推断术语“协同程序”是无限的。相反,术语“协同程序”应当被理解为在一些实施例中涵盖具有特性a和b的那些协同程序,以及在其他实施例中涵盖具有特性a和c的那些协同程序。其他所定义的术语应该被类似地处理,以避免不确定性。

操作环境

参考图1,实施例的操作环境100可以包括计算机系统102。计算机系统102可以是多处理器计算机系统,或不是多处理器计算机系统。操作环境可以包括给定计算机系统中的一个或多个机器,其可以是群集的、客户端服务器联网的和/或对等网络的。各个机器是计算机系统,合作机器组也是计算机系统。给定计算机系统102可以被配置成用于例如具有应用的最终用户、用于管理员、作为分布式处理节点和/或以其他方式配置。

人类用户104可以经由所键入的文本、触摸、话音、移动、计算机视觉、手势和/或其他形式的i/o,通过使用显示器、键盘和其他外围设备106来与计算机系统102交互。用户接口可以支持实施例与一个或多个人类用户之间的交互。用户接口可以包括命令行接口、图形用户接口(gui)、自然用户接口(nui)、话音命令接口和/或其他接口呈现。例如,用户接口可以在本地台式计算机上或在智能电话上生成,或者它可以从web服务器生成并且发送给客户端。用户接口可以作为服务的一部分生成,并且可以与诸如社交联网服务之类的其他服务整合。给定的操作环境包括设备和基础架构,这些设备和基础架构支持这些不同用户接口生成选项和使用。

自然用户接口(nui)操作可以例如使用语音识别、触摸和触笔识别、屏幕上和屏幕邻近的手势识别、空中手势、头部和眼睛追踪、话音和语音识别、视觉、触摸、手势和/或机器智能。nui技术的一些示例包括触敏显示、话音和语音识别、意图和目标理解、使用深度相机(诸如立体相机系统、红外相机系统、rgb相机系统及其组合)的运动手势检测、使用加速度计/陀螺仪的运动手势检测、面部识别,3d显示、头部、眼睛以及注视追踪、沉浸式增强现实和虚拟现实系统,以及使用电场感应电极(脑电图和相关工具)感测大脑活动的技术,所有这些自然用户接口操作都提供更自然的接口。

本领域技术人员将领会,本文中在“操作环境”下呈现的前述方面和其他方面还可以形成给定实施例的一部分。本文档的标题不旨在向特征的严格分类,提供实施例和非实施例特征类。

作为另一示例,游戏可以驻留在microsoft服务器(microsoftcorporation的标记)上。游戏可以从控制台购买,并且可以全部或部分地在服务器、控制台或两者上执行。多个用户可以使用标准控制器、空中手势、话音或使用诸如智能电话或平板电脑之类的伴随设备与游戏来进行交互。给定的操作环境包括支持这些不同使用场景的设备和基础架构。

系统管理员、开发人员、工程师和最终用户每个都是特定类型的用户104。代表一个或多个人行动的自动代理、脚本、回放软件等还可以是用户104。存储设备和/或联网设备在一些实施例中可以被认为是外围设备。图1中未示出的其他计算机系统可以以技术方式与计算机系统102进行交互,或者经由网络接口设备使用到网络108的一个或多个连接与另一系统实施例进行交互。

计算机系统102包括至少一个逻辑处理器110。与其他合适的系统一样,计算机系统102还包括一个或多个计算机可读存储介质112。介质112可以具有不同的物理类型。介质112可以是易失性存储器、非易失性存储器、原地固定介质、可移除介质、磁性介质、光学介质、固态介质和/或其他类型的物理耐久存储介质(仅与传播的信号相反)。特别地,诸如便携式(即,外部的)硬盘驱动器、cd、dvd、记忆棒或其他可移除非易失性存储介质之类的配置的介质114,可以在被插入或以其他方式安装时,在功能上成为计算机系统的技术部分,从而使得其内容可与处理器110进行交互以及被处理器110来使用。可移除配置介质114是计算机可读存储介质112的示例。计算机可读存储介质112的一些其他示例包括内置ram、rom、硬盘、以及不容易被用户104移除的其他存储器存储设备。为了符合当前的美国专利要求,计算机可读介质和计算机可读存储介质以及计算机可读存储器都不是信号本身。

介质114被配置有可由处理器110执行的指令116;在本文中广义上使用“可执行文件”来包括例如在虚拟机上运行的机器代码、可解释代码、字节代码和/或代码。介质114还配置有通过执行指令116来创建、修改、引用和/或以其他方式用于实现技术效果的数据118。指令116和数据118配置它们所驻留其中的存储器或其他存储介质114;当该存储器或其他计算机可读存储介质是给定计算机系统的功能部分时,指令116和数据118还配置该计算机系统。在一些实施例中,数据118的一部分代表真实世界项,诸如产品特点、库存、物理测量、设置、图像、读数、目标、体积等。这类数据还通过备份、还原、提交、中止、重新格式化和/或其他技术操作而进行变换。

尽管实施例可以被描述为被实现为由计算设备(例如,通用计算机、移动电话或游戏控制台)中的一个或多个处理器执行的软件指令,但这种描述并不意味着排除所有可能的实施例。技术人员将理解,相同或相似的功能还可以全部或部分地直接在硬件逻辑中实现,以提供相同或相似的技术效果。可替代地或除了软件实现方式之外,本文中所描述的技术功能可以至少部分地由一个或多个硬件逻辑部件来执行。在不排除其他实现方式的情况下,例如实施例可以包括硬件逻辑部件,诸如现场可编程门阵列(fpga)、专用集成电路(asic)、专用标准产品(assp)、片上系统部件(soc)、复杂可编程逻辑器件(cpld)和类似部件。例如,实施例的部件可以基于它们的输入、输出和/或其技术效果而被分组为交互功能模块。

在一些环境中,一个或多个应用程序120具有源代码122,其包括用于调用协同程序c的例程r的代码;在这种实例中,r和c分别是例程124的示例。诸如编译器128之类的软件开发工具126,例如通过将源代码例如变换为中间代码,来辅助软件开发,该中间代码然后例如被变换为可执行代码。这些编译器产生的代码通常被标识为代码138。在所示的示例中,编译器128包括前端130,其执行词法分析和解析;以及后端132,其生成特定于目标体系架构(例如,特定虚拟机体系架构或处理器110体系架构)的代码138。被称为内嵌原语144的某些函数是编译器所固有的,而非仅仅在不同于编译器的库中实现。可以使用调试器134来标识程序中的错误。在运行时间/内核136的辅助下,执行编译后的代码138。在一些实施例中,运行时间是内核的一部分,而在其他实施例中,几个不同语言特定运行时间在给定硬件特定内核的顶部上操作。

文本中所讨论的代码122、138、工具126和其他项可以每一个都部分地或全部地驻留在一个或多个硬件存储介质112内,从而配置超过所有硬件软件合作操作中固有的“正常”(即,最小公分母)交互的技术效果的那些介质。

除了处理器110(cpu、alu、fpu和/或gpu)、存储器/存储介质112、一个或多个显示器140和一个或多个电池之外,操作环境还可以包括其他硬件,诸如例如总线、电源、有线和无线网络接口卡以及加速器,本文中在对本领域技术人员尚不显而易见的范围内对这些硬件相应的操作进行描述。显示器可以包括一个或多个触摸屏、响应于来自笔或平板电脑的输入的屏幕、或操作仅用于输出的屏幕。cpu是中央处理单元,alu是算术和逻辑单元,fpu是浮点处理单元,gpu是图形处理单元。

给定操作环境可以包括集成开发环境(ide)142,其向开发者提供协调软件开发工具126的集合,诸如编译器、源代码编辑器、分析器、调试器等等。特别地,一些实施例的一些合适操作环境包括或帮助创建被配置成支持程序开发的microsoftvisual开发环境(microsoftcorporation的标记)。一些合适操作环境包括环境(oracleamerica,inc.的标记),一些包括利用诸如c++或c#(“c-sharp”)之类语言的环境,但本文中的教导适用于广泛多种的编程语言、编程模型和程序,并且也适用于软件开发领域本身以外的技术工作。

所讨论的项不一定是任何具体操作环境的一部分或所有实施例的一部分,而是可能与操作环境中的项或本文所讨论的某些实施例进行互操作。

系统

图2图示了适用于与一些实施例一起使用的体系架构的方面。在程序120内,一个或多个例程124递归地调用它们自己和/或调用一个或多个其他程序。因此,该程序包括一个或多个调用方202,其使得对一个或多个例程做出调用204。在至少一个实例中,对协同程序206做出调用。协同程序可以是函数208,这是因为协同程序在包含调用的表达式(例如,“x=foo()+1”)向调用方202返回值,其中,“协同程序被命名为foo,包含协同程序调用的表达式是“foo()+1”。或者,协同程序可能是一过程,该过程不会在其被调用的点处返回值,但是仍然可以例如更改全局变量。

所图示的协同程序206包括具有参数列表的报头210,如其他例程以及主体212,其在执行时,通常使用一个或多个参数并且经常使用其范围在协同程序本身内的局部变量执行操作。其他例程还具有报头和主体。与一些其他例程不同,协同程序主体包括由对应的代码220实现的至少一个挂起点之前的、具有对应的初始化代码216的初始化点214,以及具有当协同程序在挂起之后恢复时被执行的代码224的一个或多个恢复点222。

所图示的协同程序部分地由激活帧226实现,该激活帧226包含由系统102使用以追踪协同程序的代码的位置以及协同程序例程实例的执行状态的数据。激活帧226还可以被简称为“帧”,但是应当注意不要将协同程序激活帧与当调用任何类型的例程(不一定是协同程序)时创建的栈帧混淆。除非另有说明,否则本文中的“帧”是指协同程序激活帧。如本文中所解释的,有时协同程序激活帧存储在栈228(其是存储器112的一部分),但是常规协同程序激活帧已经被存储在堆230存储器112中,而不是被存储在栈上。如本文中所描述的操作的合适的编译器生成栈激活帧代码232或堆激活帧代码234,其在目标系统上被执行时将分别在栈228或堆230上分配激活帧226。

所图示的激活帧226包含或可以包含用于保证(promise)236、保存区域238、恢复地址240、恢复索引242和平台特定区域244的数据。在给定实施例中,这些项在存储器中的实际布局不一定匹配它们在图2中的附图标记的顺序和/或图表布局。例如,图9示出了一个实现方式的布局,其中帧指针902(也称之为图6和图7iv_resumable_frame_ptr)指向位于分配给激活帧226的存储器块内部的恢复地址240(即,包含存储器地址)。在其他实现方式中,恢复地址240被代替地定位在分配给激活帧226的存储器的一个边缘。

在一些实现方式中,协同程序保证236是被设计和被分配成包含实现由协同程序206所暴露的更高级别抽象所要求的库特定数据的数据区域。例如,在一些环境中,实现经由std::future<t>提供最终值的类任务的语义的协同程序很可能具有包含std::promise<t>的协同程序保证。实现生成器的协同程序206可以具有存储要产生的当前值和生成器的状态(活动/取消/关闭)的保证236。

在一些实现方式中,保存区域238是被设计和被分配成包含所讨论的协同程序206的局部变量的数据区域。它还可以被称为指定用于临时存储单元和/或溢出区域的区域。

在一些实现方式中,恢复地址240指向代码224,当由该激活帧表示的协同程序实例下次恢复执行时将向该代码224传递控制。在一些实现方式中,恢复地址240是基准地址,执行恢复的实际地址取决于恢复索引242的值,例如,在第一恢复点222处向代码224传递控制,或者在第二恢复点222处向代码224的传递控制,或者在第三恢复点222处向代码224传递控制等等。在一些实现方式中,执行验证代码252以确保恢复地址240是有效的,以防止可能具有向恶意软件的控制传递的改变。

在一些实现方式中,平台特定区域244是被设计和被分配成包含诸如寄存器值或指向全局数据结构的指针之类的平台特定数据的数据区域。

所图示的协同程序具有寿命246,即,其中可以存在协同程序的给定实例的范围。范围根据表达式和其他代码122来限定,其包含对协同程序的调用204。将帧226放置在堆还是栈上的选择可以至少部分地基于对应协同程序的寿命的范围,该寿命可以使用静态单赋值(ssa)使用定义链250进行评估。

至少部分地使用元组248或另一个中间表示来实现所图示的协同程序,该中间表示通常比源代码122更接近于给定机器体系架构,但是本身不能在处理器110上直接执行。一种元组248的一些示例如图5至图8所示。

在一些实现方式中,协同程序用于帮助实现yield表达式254和/或await表达式256,但是还可以在没有这样的语句的情况下使用协同程序。

一些实施例为计算机系统102提供逻辑处理器110和由电路、固件和/或软件所配置的存储介质112,以通过使用如本文中所描述的协同程序帧分配的编译器优化来扩展功能提供技术效果,诸如针对诸如堆存储器用尽之类的技术问题的协同程序激活帧的调用方的栈分配。

在一些实施例中,诸如人类用户i/o设备(屏幕、键盘、鼠标、平板电脑、麦克风、扬声器、运动传感器等)之类的外围设备106将以与一个或多个处理器110和存储器可操作地通信的形式存在。然而,实施例还可以深入地嵌入诸如物联网的一部分之类的技术系统中,以使得人类用户104不会直接与该实施例交互。软件过程可以是用户104。

在一些实施例中,系统包括由网络连接的多台计算机。联网接口设备可以使用诸如例如分组交换网络接口卡、无线收发器或电话网络接口之类的部件来提供对网络108的访问,这些部件可以存在于给定计算机系统中。但是,实施例还可以通过直接存储器访问、可移除非易失性介质或其他信息存储检索和/或传输方案来传递技术数据和/或技术指令,或者计算机系统中的实施例可以在不与其他计算机系统通信的情况下进行操作。

一些实施例在“云计算”环境和/或“云”存储环境中操作,其中计算服务不是被所有的,而是按需被提供的。例如,程序的源代码可能在联网云中的多个设备/系统102上,编译器可能会在云内的其他设备上编译源代码,并且所得到的可执行代码可以在其他一个或多个云设备/一个或多个系统上运行并且配置显示器。

一些实施例提供计算机系统102,其包括编译处理器110、与编译处理器可操作地通信的存储器112以及程序源代码122,该程序源代码驻留在存储器中。程序源代码包括调用方202对协同程序206的调用204。驻留在存储器中的静态单赋值使用定义链250是基于协同程序206在程序源代码122中的定义,并且还基于协同程序206在程序源代码122中的至少一个使用,例如,对协同程序206的调用204。驻留在存储器中的编译器128具有协同程序编译代码,例如,前端130、后端132、实现内嵌原语函数的代码144和/或在编译协同程序中使用的编译器128的其他代码146的部分。编译器126的协同程序编译代码146与编译处理器110和存储器112交互,以在执行时执行过程,该过程包括:(a)利用静态单赋值使用定义链250的分析确定协同程序206是否具有当由目标处理器110执行调用时,将不超过调用204的寿命246;(b)当协同程序寿命不会超过调用时,编译处理器生成代码232,其在目标处理器执行时分配来自调用方202的本地栈存储器228的协同程序的激活帧226;以及(c)当协同程序寿命可以超过调用时,编译处理器生成代码234,其在目标处理器执行时从堆存储器230分配激活帧226。

在一些示例中,激活帧226包括恢复索引242,即,其值区分协同程序中的多个执行恢复点222的序数。在其他示例中,它不包括恢复索引。

在一些示例中,激活帧包括保证部分236,其中目标处理器在执行从程序源代码编译的代码138时进行初始化,并且平台特定寄存器保存区域244通过运行时间136进行初始化。在其他示例中,保证236和/或平台特定区域244不是激活帧的一部分。

一些示例包括驻留在存储器中的至少第一协同程序片段和第二协同程序片段,例如初始化代码216和恢复代码224。协同程序片段由协同程序的初始挂起点218界定。第一协同程序片段包括目标处理器执行到初始挂起点的协同程序的初始化部分,而第二协同程序片段包括目标处理器在协同程序的至少一个挂起点之后执行的恢复部分。

在一些示例中,协同程序编译代码146通过使用从程序源代码122产生的中间语言中的内嵌原语144,来分别标识协同程序初始化点214、挂起点218和恢复点222。比如,如图6和图7所图示的,编译器的解析前端可以使用元组248中的内嵌原语144,以便向生成后端的编译器的目标处理器代码标识点214、218和/或222。

在一些示例中,在一个或多个目标处理器110执行时,编译后的协同程序代码138、232从调用方的本地栈存储器228分配协同程序的激活帧226,并且还执行与协同程序的调用方内联的协同程序的至少一部分。在其他示例中,激活帧位于栈上,但协同程序主体212不与调用方202内联。

在一些示例中,协同程序包括以下各项中的至少一项:yield语句254、await表达式256。特别地,在一些示例中,在迭代器中使用形式为“yield-keyword><expression>的yield语句一次返回<expression>的值;每次执行yield语句时,都会返回下一个值。比如,根据编程语言,yield-keyword可以是“yield”或一些其他关键字。在一些示例中,await表达式被应用于异步方法中的任务以挂起方法的执行,直到等待的任务完成为止。该任务表示正在进行的工作。用于实现await表达式的实际关键字可能会因编程语言而异。

过程

图3以流程图300图示了一些过程实施例。在一些实施例中,可以在编译或构建脚本的控制下或者以其他方式需要很少或没有同时即时用户输入的情况下,来自动执行附图中所示或以其他方式公开的技术过程。除非另有说明,否则还可以部分自动地或部分手动地执行过程。在给定实施例中,过程的零个或多个所图示的步骤可以重复,或许通过不同的参数或数据,以进行操作。实施例中的步骤还可以以与图3中布置的自顶向下顺序不同的顺序进行。步骤可以以部分重叠的方式串行地或完全并行地执行。流程图300被遍历以指示在过程期间执行的步骤的顺序可以从过程的一个执行变化到该过程的另一执行。流程图遍历顺序还可以从一个过程实施例到另一过程实施例而变化。步骤还可以被省略、组合、重新命名、重新分组或以其他方式偏离所图示的流程,只要所执行的过程可操作并且符合至少一个权利要求。特别地,图4中的数据流程图图示了与图3所图示的流程相重叠的步骤流程,但省略了被示为图3中的选项的一些步骤,并且添加了未在图3中示出的其他步骤。

因此,本文中提供了示例来帮助说明该技术的方面,但是在该文档内给出的示例没有描述所有可能的实施例。实施例不限于本文中所提供的特定实现方式、布置、显示、特征、途径或场景。例如,给定实施例可以包括附加的或不同的技术特征、机构和/或数据结构,并且可以以其他方式偏离本文中所提供的示例。

一些示例提供了一种用于实现可恢复函数(也称之为函数的协同程序)的方法(也称之为技术过程,算法)。在一些示例中,该方法包括:编译器在编译402时确定302可恢复函数是否具有寿命,该寿命将不超过由计算系统中的调用方做出的对可恢复函数的调用。当编译器确定302可恢复函数寿命将不超过调用时,编译器生成304代码,其在执行时从计算系统中的调用方的本地栈存储器,而非从堆230分配308可恢复函数的激活帧,从而通过减少堆存储器使用来优化346该堆存储器使用,而不需要从程序120中移除协同程序的功能。相比之下,当编译器确定302可恢复函数寿命可以超过调用(因此其涵盖确定302寿命确实这样延伸,或者确定302它可能延伸),编译器生成306代码,其在执行340时从计算系统中的堆存储器分配310激活帧。编译器可以至少部分地通过使用316静态单赋值使用定义链250,来确定302可恢复函数寿命的范围。

一些方法包括:编译器生成350代码138,其在执行340时将恢复地址240放置312在激活帧中。恢复地址位置是可恢复函数206、208的函数主体212的可恢复部分224的存储器地址。

一些方法包括:编译器生成352代码138,其在执行时,将恢复索引242设置314在激活帧中。恢复索引是其值区分可恢复函数的函数主体中的多个执行恢复点222的序数。

在一些方法中,可恢复函数包括318以下各项中的至少一项:yield语句254、await表达式256。

在一些方法中,在编译402期间,编译器将可恢复函数的代码内联入320可恢复函数208的调用方。例如,可以使用各种准则来选择是否内联函数,诸如从内联代码提高执行340速度,是否被可执行代码138的大小增加或需要更频繁地换出寄存器所抵消(offset)。更一般地,尽管内联可以改善程序在运行时间处的时间和空间使用,从而优化348目标处理器的使用,内联还可以增加程序的二进制文件的大小。内联倾向于提高代码执行速度,但还可能降低性能。例如,插入例程的多个副本可能会足够增加代码大小,以使代码不再适合高速缓存,从而导致更多的高速缓存丢失。在嵌入式系统中,具有较小代码尺寸可能比具有较快的代码执行更为重要,因此使得该特定系统的内联不具吸引力。在一些系统中,从内联添加的变量可以足够增加处理器110的寄存器使用,以使导致额外的ram112访问,从而降低程序执行速度。此外,一些例程(诸如一些递归例程)不能被内联。

在一些示例中,该方法包括:编译器生成322代码,其在执行340时初始化324激活帧中的保证数据结构。

在一些示例中,编译器包括前端130和后端132。在编译402期间,前端执行特定编程语言的词法分析和解析,并且后端将来自前端的输出转换成元组248,该元组248包括与机器无关的操作码和符号操作数。前端可以向后端标识326以下各项:(a)可恢复函数的初始化点214、(b)可恢复函数的至少一个挂起点218、以及(c)针对每个挂起点的恢复点222,其指示执行已经在该挂起点处被挂起342之后,可恢复函数的执行340将恢复344的位置。特别地,在一些方法中,编译器前端使用328内嵌原语向编译器后端标识326可恢复函数初始化点、挂起点和恢复点。

配置的介质

一些实施例包括所配置的计算机可读存储介质112。介质112可以包括磁盘(磁性、光学或其他)、ram、eeproms或其他rom和/或其他可配置存储器,该介质112特别地包括计算机可读介质(与仅传播信号相反)。特别地,所配置的存储介质可以是可移除存储介质114,诸如cd、dvd或闪存。通用存储器可以是可移除或不可移除并且可以是易失性或不可易失性,该通用存储器可以使用数据118和指令116形式的诸如使用定义链之类的项被配置到实施例中,从可移除介质和/或诸如网络连接之类的另一源读取,以形成所配置的介质。如本文中所公开的,所配置的介质112能够使得计算机系统在编译402期间执行用于协同程序优化的技术过程步骤。因此,图1至图4和图9有助于图示所配置的存储介质实施例和过程实施例、以及系统和过程实施例。特别地,图3和/或图4中所图示的过程步骤,或者本文中以其他方式教导的过程步骤中的任一过程步骤可以用于帮助配置存储介质,以形成所配置的介质实施例。

使用定义链(也称之为曲线图)表示变量的使用以及该变量的所有定义,其可以在没有任何其他中间定义的情况下达实现上述使用。

在一些实施例中,计算机可读存储介质114配置有数据和指令,其在被编译计算系统102中的至少一个处理器执行时,将使得至少一个处理器110执行协同程序编译402的技术过程。该过程(也称之为技术方法)包括:在编译时确定302协同程序是否具有寿命,该寿命不会超过由目标计算系统中的调用方做出的对协同程序的调用。当协同程序的寿命不会超过调用时,该过程包括生成304代码,其在目标计算系统中的处理器被执行340时,从目标计算系统102中的调用方的本地栈存储器分配协同程序的激活帧。当协同程序寿命可以超过调用时(因此包括当它清楚地确实这样超过该调用时),该过程包括:生成306代码,其在目标计算系统中被执行时从目标计算系统中的堆存储器分配激活帧。该过程还包括:生成350代码,其在目标计算系统中被执行时将恢复地址放置312在激活帧中,该恢复地址是协同程序206的可恢复部分的地址。

在一些实施例中,该过程还包括:生成322代码,其在目标计算系统中被执行340时,初始化324激活帧中的保证数据结构。

在一些实施例中,该过程还包括以下各项中的至少一项:生成330代码252,其在目标计算系统中被执行时,验证332在激活帧中指定的恢复地址值是在基于该恢复地址值来传递控制之前的有效值;生成330代码252,其在目标计算系统中被执行时,验证334在激活帧中指定的恢复索引值是在基于该恢复索引值来传递控制之前的有效值。比如,编译器可以生成枚举用于恢复点222的有效值的列表或其他数据结构,并且代码252可以对照该有效值列表来检查给定恢复地址值。类似地,编译器可以追踪给定协同程序中的挂起恢复点对的数目,并且生成代码252以将该值用作恢复索引值的上限。

计算机可读存储介质可以被配置成执行本文中所描述的任何过程,包括在本文中别处讨论的图3或图4的范围内的那些过程。比如,在一些实施例中,计算机可读存储介质过程至少部分地通过使用316静态单赋值使用定义链250,来确定协同程序寿命的范围。

附加示例

下文提供了附加细节和设计考虑。与本文中的其他示例一样,所描述的特征可以在给定实施例中单独使用和/或组合使用或完全不使用。

本领域技术人员将理解,实现细节可以涉及特定代码,诸如特定api和特定示例程序,因此这些实现细节可以不需要出现在每个实施例中。技术人员还将理解,在讨论细节时使用的程序标识符和一些其他术语是针对实现方式所特定的,因此这些术语不需要涉及每一个实施例。但是,尽管它们并不一定需要存在于此,但是之所以提供这些细节是因为它们可以通过提供上下文,来帮助一些读者和/或可以说明本文中所讨论的技术的多种可能的实现方式中的一些。

以下讨论从microsoftvisual2015文档(microsoftcorporation的标记)中导出。visual工具包括由microsoftcorporation实现的软件。visual软件和/或文档的方面与本文中所描述的实施例的方面一致,或者visual软件和/或文档的方面以其他方式说明上述方面。然而,应当理解,visual文档和/或实现方式的选择不一定限于这些实施例的范围,同样应当理解,visual软件和/或其文档还可能包含在这些实施例的范围之外的特征。还应当理解,下文的讨论内容是部分地作为对非本领域普通技术的读者的帮助而被提供,并且因此可以包含和/或省略一些细节,为了支持本公开的细节,上述细节在下文的记载不会被严格要求。

作为协同程序使用的示例,考虑用于添加到c++2017而提出的yield关键字(或以不同名称的类似功能,例如,“__yield_value”)。协同程序可以在代码122中的yield语句254中使用,如下所示:

在被编译和执行时,该代码会打印串文字中的每个字符“hello,andwelcometothisworld\n”,一次一个字符。在h()中,系统将在循环的后边缘处挂起执行,并且将单个字符返回给main()。所提出的c++yield语句触发代码生成,以保存可恢复代码地址并且将即时寄存器保存在utc在堆上创建的(即,在编译时,utc生成的代码,以在运行时间期间在堆上创建帧)帧数据结构226中。一旦寄存器和要恢复的位置已被保存,剩下的utc生成的代码就会执行带有结尾的正常返回338序列。在代码正在main的循环主体中执行时,针对h()的帧仍然存在于堆中,并且main()将指针保持指向它,以使它可以在h()中的某个位置处恢复。用于该代码生成的优化346是要在main的栈上分配308可恢复帧226,而非在堆上分配308可恢复帧226,并且相关的优化348要内联320可恢复代码的主体。

可以类似地优化所提出的c++await表达式。可以在代码122中使用await表达式256(或者,以另一关键字的类似功能),如下所示:

在该示例中,_await表达式不会阻止它正在其上执行的线程。相反,它在源代码中的存在使得编译器生成将tcp_reader()的其余部分签名为延续的代码。然后控制返回给异步方法的调用方。当任务完成时,它调用它的延续,该延续是作为可恢复pc存储的回叫函数,并且该方法恢复到它停止的地方。

从编程语言用户(软件开发者)104的视角来看,await非常具有值。但是在一些实施例中,在使用await表达式进行编译器前端之后,该表达式变得像编译器的后端中的yield一样。

从根本上,除了正常调用336和返回操作338之外,协同程序还支持挂起342和恢复344操作的函数。编译器前端130将await和yield(作为示例)转换为由后端(be)132观察到的挂起和恢复操作。因此,出于当前目的,yield和await仅是许多可能的示例中的两个可能的示例,其示出了协同程序如何可以向语言的用户进行显现。be处理挂起和恢复。前端处理在源代码中提供协同程序的yield、await和/或其他语言特征。同样,尽管以c++2017为例,但是其他编程语言也支持协同程序,或其可以扩展为支持协同程序,所述编程语言包括例如dart(所提出的)、d、hack、javascript(所提出的)、modula-2、perl、python、ruby等。如本文中所描述的那些优化之类的优化,还可以用于具有类协同程序的语言设施的其他语言的编译器128(例如,jit编译器)中。

协同程序可以在单核心系统102或多核心系统102上运行。它们可以在单个机器上运行,或者在分布式系统102内的不同机器上运行。

在一些实施例中,协同程序可以通过将它们与子例程进行比较而被理解。当子例程被调用336时,执行从子例程的起始处开始,一旦子例程退出338,则完成。在录入实例之后,子例程的实例只返回一次控制,并且子例程在子例程的调用之间不保持任何状态信息。相比之下,协同程序可以通过调用其他协同程序来退出,其稍后可能返回到它们在原始协同程序中被调用的位置。从协同程序的视角来看,它不是退出,而是调用另一协同程序。因此,协同程序实例保持状态,并且在程序的执行期间可以立刻成为给定协同程序的多个实例。在通过对其产生控制来(a)调用另一协同程序和(b)调用另一例程(其最终将控制返回到调用的原始点)之间的一个差异是:在控制转移(a)下,以与前一协同程序相同的延续方式,来录入后一协同程序。彼此产生的两个协同程序之间的关系可以被看作不是调用方与被叫者的关系,而是对称的。

在一些实施例中,协同程序通过延续实现。延续是数据结构,其表示在过程执行中的给定点处的计算过程。但是,可以在编程语言内访问延续数据,而非隐藏在运行时间中。例如,一级延续会节省程序的执行上下文。例如,可以使用激活帧226来实现延续,以将执行状态信息保存在恢复地址240中,或者在恢复地址240将恢复索引242中。

在一些实施例中,编译器前端对编译器后端进行注释,其中每个挂起点进入协同程序的以下位置,即协同程序的初始化部分所在的位置以及恢复部分所位于的位置。特别地,在一些实现方式中,编译器前端和be通过使用328作为注释的内嵌原语来进行通信。作为示例,以下内嵌原语144可以用于解释高级代码生成:

_resumable_save()-存储所有即时寄存器,以及可恢复程序计数器(pc)240

_resumable_suspend()-跳转到函数的结尾并且返回

_resumable_cancel()-拆除帧结构226并且跳转到结尾

一些实施例包括内嵌原语,这些内嵌原语诸如是以下各项中的一些或所有项:

/***

*可恢复

*版权(c)microsoftcorporation。保留所有权利。

*目的:非栈式可恢复函数的库支持

*http://isocpp.org/files/papers/n4134.pdf

*[public]

****/

#pragmaonce

#ifndef_resumable_

#define_resumable_

#ifndefrc_invoked

#include<new>

//#pragmapack(push,_crt_packing)

#pragmapush_macro("new")

#undefnew

#if1

extern"c"size_t_coro_resume(void*);

extern"c"void_coro_destroy(void*);

extern"c"size_t_coro_done(void*);

#pragmaintrinsic(_coro_resume)

#pragmaintrinsic(_coro_destroy)

#pragmaintrinsic(_coro_done)

#endif

_std_begin

本领域技术人员将认识到,本段文本不是源代码本身的一部分,而是对其的注释。提供该评论是为了帮助读者了解在本段文本中并入的源代码,并且帮助本公开内容符合包括源代码列表的、关于披露格式和内容的规定。尽管在这个源代码中使用了实验名称空间,但是在产生实施例中,还可以使用其他命名空间,包括作为生产等级而非实验的命名空间。

本领域技术人员将认识到,不同的格式化样式可以在源代码中使用,而并非一旦所得代码被编译就更改它的功能。例如,如接下来的两个函数所示,函数的主体的左大括号可以被放置在函数名称和参数列表之后的行上,或者函数的主体的左大括号可以被放置在与函数的名称和参数列表相同的行上:

本领域技术人员将认识到,许多编程语言中的某些函数是内置的(本来就是支持的或经由标准库支持的)。一个示例是sizeof()函数,其返回数据类型的存储器存储大小:

技术人员将认识到,“微不足道的”不一定意味着不重要,但是可以反而意味着“容易的”或“简单直接的”。

本领域技术人员将认识到,在该源代码中,“struct”是指数据结构定义。

本领域技术人员将认识到,使用reinterpret_cast涉及将原始数据解释为数据类型,该数据类型表示可能与先前表示的原始数据的数据类型不同。

本领域技术人员将认识到,代码功能不需要特定标识符,在某种意义上,将一个标识符x的所有实例重命名为另一标识符y,通常产生在功能上相同的代码,从而通过代码的另一部分来禁止与标识符y的名称冲突。

//todo:rename_resumable_xxx=>_coro_xxx

extern"c"size_t_coro_frame_size();

extern"c"void*_coro_frame_ptr();

extern"c"void_coro_init_block();

extern"c"void*_coro_resume_addr();

extern"c"void_coro_init_frame(void*);

extern"c"void_coro_save(size_t);

extern"c"void_coro_suspend(size_t);

extern"c"void_coro_cancel();

extern"c"void_coro_resume_block();

#pragmaintrinsic(_coro_frame_size)

#pragmaintrinsic(_coro_frame_ptr)

#pragmaintrinsic(_coro_init_block)

#pragmaintrinsic(_coro_resume_addr)

#pragmaintrinsic(_coro_init_frame)

#pragmaintrinsic(_coro_save)

#pragmaintrinsic(_coro_suspend)

#pragmaintrinsic(_coro_cancel)

#pragmaintrinsic(_coro_resume_block)

#pragmapop_macro("new")

//#pragmapack(pop)

#endif/*rc_invoked*/

#endif/*_resumable_*/

例如,源代码

…=yield<expr>;

可以扩展为等于如下的表达式:

作为另一示例,考虑用于定义返回整数的生成器的以下c++源代码:

该fib()功能可能被如下调用:

在一些实施例中,编译器后端132将把协同程序fib(intn)概括成两个函数:

__fib$init

__fib$resume

(__fib$destroy在一些情况下可以被创建为后期优化)

__fib$init的主体用于分配310并且初始化堆中的栈帧(本文中也被称为激活帧)。用户的原始代码放在__fib$resume(以eh包装),并且它将具有与yield点一样多的恢复点

函数main()将被扩展为c++源代码122(或等价的代码),其类似于以下代码:

生成器结构“g”将覆写运算符“*”\“!=”和“++”并且在迭代器方法中,调用内嵌原语begin()和end()。

一个实现方式使用这些可迭代函数:

这里是原始生成器数据结构(其特定于fib()),该数据结构仅返回整数并且依赖于三个内嵌原语。技术人员将理解,这可以通过模板和库代码进行通用化和定制化

图5示出了用于使用上述信息的main()的元组248的示例。

一般而言,元组248最初来自il(几乎一对一地匹配它),并且最终通过编译402变换成机器代码。

在该示例中,fib$init()函数将创建帧226、初始化它、创建udt结构、解构formals以及挂起。在一个实现方式中,用于fib$init()的元组看起来像图6和图7所示的列表。

fib$resume()将使用帧结构226中的索引位置(slot)242来确定要从哪个点恢复。回想一下,协同程序可以具有n个可恢复点222。该函数还用于该实现方式中以适当地解构以下各项:(i)c++协同程序中的对象,该对象在该点处存在、(ii)帧数据结构的c++可定制部分(在可恢复pc位置的上方)、以及(iii)帧数据结构的utc部分(在可恢复pc位置和索引的下方)。用于fib$resume()的伪代码看起来像:

//使用所索引的恢复点的所概述的函数

在下面的部分中,该代码将被传递给帧指针902以及用于通过值传回聚合的隐藏参数:

在一些实施例中,为了使协同程序安全,utc将插入/guard间接调用检查252或通过该函数的流属性,并且确定通过后端已经概述的例程的确切名称,协同程序可以被直接调用以供恢复。间接调用检查252确定332间接调用的目标是否有效。如果目标无效,则控制不会被传递给目标地址处的代码。如果目标有效,则控制被传递给目标地址处的代码。

在一些实施例中,实现以下过程(算法)以确定302协同验证的寿命是否是局部的以及如何优化该场景。这支持以下两项:(i)满足安全性要求;以及(ii)满足类迭代器场景的严格性能目标。协同程序寿命确定算法有三个步骤:

步骤1.播种协同程序属性和可恢复函数名称的点。

步骤2.在元组流中使用ssa使用定义边缘来流传这些{对}。

步骤3.从函数exit向后退,进行以下操作:

true标志指示栈省略(即,生成304代码以将协同程序激活帧226放置在协同程序调用方的栈上,而非堆上)。当主例程仅在调用方202的本地范围内使用协同程序时,如本文中所教导的那样,省略可以执行的优化346。除了通过栈省略来更有效地使用存储器之外,可以优化348代码生成,以放置协同程序主体与调用方内联。

作为协同程序寿命确定算法的说明,考虑以下源代码:

在编译器阶段性处理之后,该源代码是完整的并且代码被发布,这个示例中的机器代码如下所示:

在这一点上,rcx具有将帧ptr放入的局部地址,以使得main可以使用它来恢复协同程序。函数指针是帧指针指向的第一词,随后是该协同程序中的恢复点的整数索引。继续机器代码,该示例现在将调用该帧的分配器,并且返回驻留在main上的栈上的局部$s2$1[rsp]中的帧指针。

在没有优化的情况下,main可能总是通过[rbx],进行间接调用来恢复协同程序。这一点是如上文所示出的。指令“callqwordptr[rbx]”是恢复的实现方式,并且x64汇编语言是通过间接的调用,即,[rbx]。寄存器rbx直接指向协同程序帧的中间,并且(按照惯例)在这一点,其地址为“__fib$_cororesume”,该地址是协同程序的概述函数主体。其内部逻辑运行出在紧靠可恢复地址之后的帧位置中的索引,即,[rbx+8]。如果这个索引位置在所恢复的协同程序主体的逻辑中被设置为零,则这意味着协同程序最后一次被恢复(被预期的),并且该帧应该被释放。

在运行优化算法之后,可以看到所有的调用都将协同程序恢复为直接调用。传递给分配器的参数“call?fib”仍然被设置为假(在默认情况下)。这被视为“movr8,0”。

在这一点上,rcx具有将帧ptr放入的局部地址,以使main可以使用它来恢复协同程序。函数指针是帧指针指向的第一词,随后是该协同程序中的恢复点的整数索引。继续机器代码,现在该示例将调用该帧的分配器,并且返回驻留在main上的栈上的局部$s2$1[rsp]中的帧指针。现在,在分配器调用处继续(pickup),示例代码如下所示:

通过将循环中的间接调用变成直接调用,该优化消除该间接调用(这里标签$ll33@main处于循环的顶部处)。这是个益处,但是对性能和安全性可能会有更大影响。一些示例内联并且避免在堆中分配栈帧。在这一点上,这个示例将内联并且确保协同程序主体(被叫者)中的变量被添加到针对main(调用方)中的局部变量而分配的栈空间。

一旦上文所示的分析阶段已经确定协同程序的寿命将仅跨越调用函数的寿命,执行优化就是安全的。这种优化(a)用直接调用替换间接调用;(b)在新的直接调用位置,内联协同程序函数主体;(c)变换调用方的主体以消除协同程序帧的堆分配;(d)变换内联主体以避免返回,并且寄存器在其周围进行保存和还原;以及(e)将协同程序主体(被调用方)中的所有局部变量变换成调用函数(调用方)中的新的局部变量。分析阶段将用对编译器已经生成的可恢复函数主体的直接调用,来替换所有恢复内嵌原语。这将用直接函数调用来替换间接函数调用。

一旦恢复点变为直接调用,编译器就可以将该协同程序调用站点变换成内联函数主体,该内联函数主体在这种情况下是该协同程序的内联函数主体。在这个示例中,这涉及:(a)去掉所有结尾代码;(b)去掉所发出的所有指令以在每个yield之前保存寄存器;(c)去掉发出的所有指令,以在每个yield点之后恢复之后还原寄存器;(d)去掉对库代码的调用,以分配(用户指定的)堆中的协同程序帧;以及(e)去掉对库代码的调用,以释放堆中的协同程序帧。通过这样做,实施例可以将协同程序(被调用方)中的每个局部变量改变为被添加到调用函数(调用方)的栈帧的局部变量。

一些实施例使用一个属性aaex_resumable(或另一名称),该属性指示函数主体要被编译器作为协同程序进行处理。在一些实施例中下,没有使用其他协同程序特定属性。

例如,在图8中,所注释的示例元组流、#1以及相关联的箭头指示对于协同程序(即,指向协同程序激活帧的指针)的寿命的开始的发现。“种子”表示类型信息产生指向协同程序的指针的位置。在元组中,t66表示临时变量;元组列表采用ssa(静态单赋值)形式。i64表示64位整数类型。因此,“t66.i64”表示在对fib()调用中,作为参数传递的64位整数变量。在示例元组流中,#2和相关联的箭头表示在使用定义分析中使用该$,该使用定义分析示出了协同程序fib()的寿命至少延伸到这样远。#3和相关联的箭头表示fib()恢复点。生成代码以恢复协同程序并且传递帧指针,以指示在该实例中恢复fib()执行时,将执行协同程序的哪个指令。

关于栈省略,在一些实施例中,协同程序激活帧226本身不从堆移动到栈。相反,在编译器内,将针对已经将帧建模为驻留在堆230上(而不是驻留在调用方的栈上)的代码生成的默认建模进行修改(优化),从而对帧进行建模。因此,生成304在调用方栈上分配308协同程序帧的代码,而非生成306在堆上分配310帧的代码。

在一些实施例中,栈省略中的阶段利用了上文呈现的对于?fib()的两个版本的调用的优点。该示例示出了?fib()函数主体的主体,因为它可能使用其内部表示(ir)而在编译器内部出现。当编译器创建其中概述init()和resume()主体的fib()的新主体时,编译器也将使分配条件化。这是用于fib的init函数,其设置可恢复函数的地址和包含opassign的前两行所示的索引:

block0:vout(1)

entry?fib$_initcoro(_$s1$~~,___$returnudt$~~,_n$)

block1:vin(0)out(3,2)

[$s1$(gr0)]=opassign&?fib$resumecoro

[$s1$(gr0)+4]=opassign2(0x2)

tv163(gr0)-=lea&[_$s1$(gr0)-8]

t138=opcmptv163(gr0)-,0(0x0)

opbranch(eq)&$ln5,t138!

block2:vin(1)out(3)

t140=oparg4(0x4)

t141=oparg0(0x0)

t142=opargtv163(gr0)!-

rpas(7)=opintrinsic(iv_memset)t142!,t141!,t140!

(4|0=4,4,4)#15

block3:vin(1,2)out(4)

r$ln5@fib$_initc:;uses=1#15

rt72=

opintrinsic(iv_coro_frame_size)(4|4=n)#15

r[_$s1$(gr0)+t72!]=opassign[_n$(gr0)](4|4=4,4,4,4)

#15

r[___$returnudt$(gr0)]=opassign

_$s1$(gr0)!(4|4=4,4)#15

block4:vin(3)

exit#15

block

如果?fib()的第一参数为假,则代码将在堆中分配帧。如果?fib()的第一参数为真,则代码调用_alloca并且增加当前栈的尺寸,即,帧的尺寸。这是针对将?fib内联回到main()调用方而设置的。一旦代码内联?fib(),则stackelision参数(其是简单的布尔值)将成为从main传播到if语句的备份。

block0out(1)

entry?fib(stackelision,___$returnudt$,_n$)#6

block1:cin(0)out(2)

当内联时,可以在此替换stackelision参数的调用方的值。然后,可以消除if的上半部分,然后_alloca将在调用方的上下文中发生。这将是用于协同程序帧的空间:

if(!stackelision){

rt66=opintrinsic(iv_coro_frame_size)

rt91=opaddt66!,12(0xc)

rt85=opargt91!

rt87=oparg&__realalloc$3

rt89,pas(7)=

opcall&?allocate@?$allocator@d@std@@qaepadi@z,t87!,

t85!,pas(7)

r__ptr$4=opassignt89!

rt170=oparg&_n$

rt169=oparg___$returnudt$

rt70=opadd__ptr$4!,8(0x8)

rt168=opargt70!

rpas(7)=opcall&?fib$_initcoro,t168!,t169!,t170!,pas(7)

ropret___$returnudt$!

else{

rt100=opintrinsic(iv_coro_frame_size)

rt101=opaddt100!,12(0xc)

t102=opargt101

t103=opintrinsic(iv_alloca)t65//用来在栈上进行分配的系统调用

r__ptr$4=opassignt103

rt104=oparg&_n$

rt105=oparg___$returnudt$

rt106=opadd__ptr$4!,8(0x8)

rt107=opargt70!

rpas(7)=opcall&?fib$_initcoro,t107!,t105!,t104!,

pas(7)

ropret__ptr$4

}

block2in(1)

exit

block

以下代码示例提供了进一步说明。首先,示出了main()调用函数。它具体包括以下调用:

rt65=oparg35(0x23)(4|4=4)#21

rt66=oparg&_$s2$1(4|4=4)#21

rt64=oparg0(4|4=4)#21

rpas(10)=opcall&?fib,t66!,t65!,pas(10)(4|0=4,4,4,0)#21

这个调用变成下文的代码。由于系统调用_alloca(),所以栈帧现在栈上被分配:

rt65=oparg35(0x23)(4|4=4)#21

rt66=oparg&_$s2$1(4|4=4)#21

rt64=oparg0(4|4=4)#21

rpas(10)=opcall&?fib,t66!,t65!,pas(10)(4|0=4,4,4,0)

#21

rt100=opintrinsic(iv_coro_frame_size)

rt101=opaddt100!,12(0xc)

t102=opargt101

t103=opintrinsic(iv_alloca)t65//用来在栈上进行分配的系统调用

r__ptr$4=opassignt103

rt170=oparg&_n$

rt169=oparg___$returnudt$

rt70=opadd__ptr$4!,8(0x8)

rt168=opargt70!

rpas(7)=opcall&?fib$_initcoro,t168!,t169!,t170!,pas(7)

_$s2$1(gr0)=opassign__ptr$4

这里是main()代码,在栈帧在栈上被分配和并且在做出上文所指出的改变之前:

block0out(1)

entry_main()#17

block1:cin(0)out(4,2)

这是对在栈上分配48个字节的调用。该函数是已经被概述成fib_init()//函数的原始fib,并且fib_resume()函数主体_$s2$1是用于指示局部栈偏移的汇编器符号常量,所以地址&_$s2$1只是简单的rsp+value(_$s2$1)。继续,可以有:

r_count$(gr0)=opassign0(0x0)(4|4=4)#19

rt65=oparg35(0x23)(4|4=4)#21

rt66=oparg&_$s2$1(4|4=4)#21

rt64=oparg0(4|4=4)#21

rpas(10)=opcall&?fib,t66!,t65!,pas(10)(4|0=4,4,4,0)#21

rt94=opcmp_$s2$1(gr0),0(0x0)(4|0=4,4)#21

ropbranch(eq)&$ln9,t94!(?|n=0,0)#21

block2in(1)out(4,3)

rt152=oparg_$s2$1(gr0)(4|4=4)#21

rpas(10)=opintrinsic(iv_coro_resume)t152!,pas(10)(4|0=4,0)#21

rt141=opcmp[_$s2$1(gr0)+4],

0(0x0)(4|0=4,4,4)#21

ropbranch(eq')&$ln79,t141!(?|n=0,0)#21

block3in(2)out(10)

block4in(2,1)out(12,5)

r$ln9@main:;uses=1#21

r_<begin>$l0$3(gr0)=opassign

_$s2$1(gr0)(4|4=4)#21

rt77=opcmp_$s2$1(gr0),0(0x0)(4|0=4,4)#21

ropbranch(eq)&$ln67,t77!(?|n=0,0)#21

block5in(4)out(6)

rtv296(gr0)=lea&[_$s2$1(gr0)-8](4|4=4,4)

rtv302(gr0)=lea&[_$s2$1(gr0)+4](4|4=4,4)

block6:lin(5)out(7)

block7:cin(6,7)out(8,7)loop:0nest:1

r$ll46@main:;uses=1#21[loop]

t260=opassign[tv296(gr0)](4|4=4,4)#21

rt81=oparg[t260](4|4=4,4)#22

rt82=oparg&??_c@_04egbkggki@?$cfd?5?6?$aa@(4|4=4)#22

rpas(10)=opcall&_printf,t82!,t81!,pas(10)(4|0=4,4,4,0)#22

_count$(gr0)=add_count$(gr0),1(0x1)(4|4=4,4)#23

rt180=oparg_<begin>$l0$3(gr0)(4|4=4)#21

rpas(10)=opintrinsic(iv_coro_resume)t180!,pas(10)(4|0=4,0)#21

rt174=opcmp[tv302(gr0)],0(0x0)(4|0=4,4,4)#21

ropbranch(ne)&$ll46,t174!(?|n=0,0)#21

block8:sin(7)out(9)

block9in(8)out(10)

block10in(3,9)out(12,11)

$ln79@main:;uses=1

rt222=opcmp_$s2$1(gr0),0(0x0)(4|0=4,4)#24

ropbranch(eq)&$ln67,t222!(?|n=0,0)#24

block11in(10)out(12)

rt233=oparg_$s2$1(gr0)!(4|4=4)#24

ropintrinsic(iv_coro_destroy)

t233!(0|n=4)#24

block12:cin(10,4,11)out(13)

r$ln67@main:;uses=2#24

rt84=oparg_count$(gr0)!(4|4=4)#26

rt85=

oparg&??_c@_0l@fmcnpoej@count?3?5?$cfd?6?$aa@(4|4=4)#26

rpas(7)=opcall&_printf,t85!,t84!,pas(7)(4|0=4,4,4,0)#26

opret0(0x0)(4|n=4)#27

block13in(12)

exit#27

这是对_alloca()将在栈上置入空间的位置的解释。

图10示出了函数a调用非叶函数b的栈布局的示例。注意,栈上的alloca空间。函数a的序言已经针对栈底部的b所需的所有寄存器和栈参数分配了空间。该调用推送返回地址,b的序言针对其局部变量、非易失性寄存器以及其调用函数所需的空间分配空间。如果b使用alloca,则在本地变量性/非易失性寄存器保存区域以及参数栈区域之间分配空间。

现在剩下的是,要内联init()和resume()函数主体的所有直接调用,并且在已使用_alloca()在栈上创建的新空间上,分配它们所有的局部变量。

特定地,每个调用:

rpas(10)=opintrinsic(iv_coro_resume)t180!,pas(10)(4|0=4,0)#21

将被变成直接调用

opcall&?fib$_resumecoro

这是内联之前的示例代码:

block0out(1)

entry_main()#17

block1:cin(0)out(4,2)

特别地,这是用来在栈上分配48个字节的调用。该函数是已经被概述成fib_init()函数和fib_resume()函数主体中的原始fib。

r_count$(gr0)=opassign0(0x0)(4|4=4)#19

rt100=opintrinsic(iv_coro_frame_size)

rt101=opaddt100!,12(0xc)

t102=opargt101

t103=opintrinsic(iv_alloca)t65//用来在栈上进行分配的系统调用

r__ptr$4=opassignt103

rt170=oparg&_n$

rt169=oparg___$returnudt$

rt70=opadd__ptr$4!,8(0x8)

rt168=opargt70!

rpas(7)=opcall&?fib$_initcoro,t168!,t169!,t170!,

pas(7)

_$s2$1(gr0)=opassign__ptr$4

rt94=opcmp_$s2$1(gr0),0(0x0)(4|0=4,4)#21

ropbranch(eq)&$ln9,t94!(?|n=0,0)#21

block2in(1)out(4,3)

rt152=oparg_$s2$1(gr0)(4|4=4)#21

特别地,在下行中的opintrinsic代码在内联后将会不同:

rpas(10)=opintrinsic(iv_coro_resume)t152!,pas(10)(4|0=4,0)#21

内联之后,将成为如下:

rpas(10)=opcall&?fib$_resumecorot152,pas(10)

继续在内联前或内联后后是一样的代码,具有:

rt141=opcmp[_$s2$1(gr0)+4],

0(0x0)(4|0=4,4,4)#21

ropbranch(eq')&$ln79,t141!(?|n=0,0)#21

block3in(2)out(10)

block4in(2,1)out(12,5)

r$ln9@main:;uses=1#21

r_<begin>$l0$3(gr0)=opassign

_$s2$1(gr0)(4|4=4)#21

rt77=opcmp_$s2$1(gr0),0(0x0)(4|0=4,4)#21

ropbranch(eq)&$ln67,t77!(?|n=0,0)#21

block5in(4)out(6)

rtv296(gr0)=lea&[_$s2$1(gr0)-8](4|4=4,4)

rtv302(gr0)=lea&[_$s2$1(gr0)+4](4|4=4,4)

block6:lin(5)out(7)

block7:cin(6,7)out(8,7)loop:0nest:1

r$ll46@main:;uses=1#21

[loop]

t260=opassign[tv296(gr0)](4|4=4,4)#21

rt81=oparg[t260](4|4=4,4)#22

rt82=oparg&??_c@_04egbkggki@?$cfd?5?6?$aa@(4|4=4)#22

rpas(10)=opcall&_printf,t82!,t81!,pas(10)(4|0=4,4,4,0)#22

_count$(gr0)=add_count$(gr0),1(0x1)(4|4=4,4)#23

rt180=oparg_<begin>$l0$3(gr0)(4|4=4)#21

下行中的opintrinsic代码在内联之后还会不同:

rpas(10)=opintrinsic(iv_coro_resume)t180!,pas(10)(4|0=4,0)#21

内联之后,如下:

pas(10)=opcall&?fib$_resumecorot180!,pas(10)

继续在内联前或内联后是一样的代码,具有:

rt174=opcmp[tv302(gr0)],0(0x0)(4|0=4,4,4)#21

ropbranch(ne)&$ll46,t174!(?|n=0,0)#21

block8:sin(7)out(9)

block9in(8)out(10)

block10in(3,9)out(12,11)

$ln79@main:;uses=1

rt222=opcmp_$s2$1(gr0),

0(0x0)(4|0=4,4)#2all4

ropbranch(eq)&$ln67,t222!(?|n=0,0)#24

block11in(10)out(12)

rt233=oparg_$s2$1(gr0)!(4|4=4)#24

ropintrinsic(iv_coro_destroy)

t233!(0|n=4)#24

block12:cin(10,4,11)out(13)

r$ln67@main:;uses=2#24

rt84=oparg_count$(gr0)!(4|4=4)#26

rt85=

oparg&??_c@_0l@fmcnpoej@count?3?5?$cfd?6?$aa@

(4|4=4)#26

rpas(7)=opcall&_printf,t85!,t84!,pas(7)(4|0=4,4,4,0)#26

opret0(0x0)(4|n=4)#27

block13in(12)

exit#27

现在,内联对从原始?fib()函数而概述的编译器所生成的函数的两个调用:

opcall?fib$resumecoro

opcall?fib$initcoro

在这些函数中使用的所有局部变量现在将从栈中播放。附加地,因为一切被暴露,即栈帧中的索引和使用该索引的opswitch,所以可以去掉opswitch,该opswitch可能从每次对&?fib$_resumecoro的调用而被内联。

在一些实施例中,存储器中的协同程序布局具有采用所陈述的顺序的以下各部分以及方面:保证部分(由库使用的)236、填充部分(x64上16字节、x86上8字节)、填充后的保证的内边缘处(存储在x64上的rbp寄存器,x86上的ebx寄存器中)的协同程序帧指针目标地址、恢复序言部分(由库初始化和使用的,但be保留位置)、be使用的非易失性寄存器保存区域部分244、be使用的恢复ip部分、be使用的ehtrylevel部分(仅限x86)、be使用的ba_dynamic变量部分。恢复代码可以是类似于:

488bc1movrax,rcx

488bd1movrdx,rcx

33c9xorecx,ecx

ff10callqwordptr[rax]

在一些实施例中,存储器中的协同程序布局具有采用所陈述的顺序的以下各部分:协同程序保证236、恢复序言地址240、恢复索引242、平台特定寄存器/保存区域244、局部变量和临时变量溢出区域。一些实施例省略保证236。一些实施例省略了溢出区域238。在一些实施例中,恢复索引提供与恢复序言地址或其他指示的偏移,以指定在恢复执行协同程序时,协同程序的哪个恢复点222将接收处理器控制。

在一些并行编程语言中,“保证”是用于同步的构造。在一些并行编程语言中,“未来”是变量的只读占位符视图,而保证是可写入的单赋值容器,其设置未来的值。一些实施例省略了协同程序中的保证部分。

在一些实施例中,帧226布局符合以下各项。任何可恢复协同程序仅存在一个入口点。在索引中存在有效整数或者执行将命中int3。frame_ptr902指向包含_resumefptr()的帧内位置,随后是恢复点索引242。在这对之后(入口点地址和索引),该帧包含寄存器和目标abi特定数据。来自优化这种情况的更多安全性是来自于进行“堆省略”(即,在调用方的本地栈上进行分配),然后进行对可恢复代码224的直接调用。还可以保护对于可恢复主体的间接调用,该直接调用只是来自操作系统的回叫。

一些实施例执行或提供优化346,该优化将代码从通过间接函数指针(存储在从堆分配的帧中)恢复协同程序变换为“通过名称”直接调用协同程序主体并且在本地栈上分配其帧。

本示例中的“本地”与“局部变量”中的局部相同,即,在协同程序实例的调用方202的栈228上。堆分配是昂贵的,并且可以避免它们以及替换调用方的栈存储器的优化可以显著节省性能。

在一些实施例中,在可能调用“可恢复函数”的代码中,协同程序被表示为针对堆中的帧结构的基本句柄。所指向的第一词包含可恢复函数主体的地址240。第二词(索引)242指向(或保持)表示其中执行要恢复的可恢复函数主体中的特定点(n中的一个)的序数。给定堆中的帧226和表示恢复点的序数,调用方可以以允许编译器影响异步编程模型的方式,来调用协同程序。一些实施例支持:用通常约为48个字节的协同程序帧,替换在每个实例的.text段中负责约5k字节的.then()构造。这节省了大量的存储器。

在一些实施例中,协同程序帧指针902(按照惯例)指向帧的中间的某处。它正指向包含可恢复函数指针的位置240。保证类具有默认实现方式,如果库编写者遵循被描述为前端和stl库文档的一部分的api,则这些实现方式可以由这些库编写者来自定义。回到调用方,在x86上恢复344协同程序是通过帧指针的间接调用,该间接调用传递frame_ptr以作为这个指针。可恢复代码加载索引,并且执行切换到正确恢复点。在产生之后,如果存在另一恢复点,来自前端的代码生成将导致utc发布代码,以便适当地增加索引。

以下两列说明c++关键字,然后说明一个前端130可能生成的cil:

通过编译器内嵌原语144,前端绑定到后端代码生成。在一些实现方式中,这些内嵌原语包括本文中所示出内嵌原语和/或通过已经在stl扩展中构造具有定义的api的类而在库中示出的内嵌原语中的一个或多个。

作为寿命示例,在该代码中:

对于(autov:fib(35))printf(“%d\n”,v)

协同程序被创建和解构,并且不会在分号外存活。

但是,在这个示例中:

dosomething通过引用来采用协同程序。dosomething可能已经在某个全局变量或某个堆块中存放了协同程序,该块(注释为“scope-ends”)将保持该协同程序在大括号外活动。因此,在此示例中,因为该地址可能从本地调用逃逸,所以我们无法进行堆省略。

一些实施例包括代码,其在具有与处理器可操作地通信的存储器的系统中的处理器执行时,将执行以下方法中的一种或多种方法。

在一些情况下,编译器128生成代码以分配激活帧、生成代码以设置帧中的恢复地址以及生成代码以设置帧中的恢复索引。比如,一种用于实现无栈可恢复函数的方法包括:编译器生成代码,其在执行时,从计算系统中的存储器分配可恢复函数激活帧;编译器还生成代码,其在执行时,将恢复地址放置在激活帧中,恢复地址是函数主体的可恢复部分的地址;以及编译器还生成代码,其在执行时,在激活帧中设置恢复索引,该恢复索引是其值区分函数主体中的多个执行恢复点的序数。

在一些情况下,激活帧226包括保证部分。一种方法包括:编译器还生成代码,其在执行时,初始化激活帧中的保证数据结构。

在一些情况下,可恢复函数主体被编译成402分成两的片段。一个片段是将在第一挂起点之前执行的初始化段。另一片段是在任何挂起结束之后执行的恢复段。在一种方法中,编译器至少在第一片段和第二片段中编辑函数主体,这些块均有函数主体的初始挂起点界定,第一片段包括函数主体的初始化部分,其执行直到初始挂起点,第二片段包括在函数主体的任何挂起点之后执行的恢复部分。

在该示例中,编译器前端进行语言特定解析并且产生语法树和符号表。编译器后端将这些转换成元组248。对于可恢复函数,前端告知后端(a)初始点、(b)每个挂起点以及(c)每个恢复点。在一种方法中,编译器包括前端和后端,前端执行特定编程语言的词法分析和解析,后端将前端的输出转换成包括机器无关操作码和符号操作数的元组,前端向后端标识以下内容:(a)可恢复函数的初始化点、(b)可恢复函数的每个挂起点、(c)每个挂起点的恢复点,其指示在挂起之后将执行恢复的位置。在一种这样的方法中,前端使用内嵌原语向后端标识326可恢复函数初始化点、挂起点和恢复点。

一些情况下,编译器检查激活帧是否可以放置在调用方的栈中而非堆中。在一种方法中,编译器还确定可恢复函数是否具有不超过由调用方做出的对可恢复函数的调用的寿命;当编译器确定可恢复函数的寿命不会超过调用时,编译器生成代码,其在执行时,将从调用方的本地栈存储器分配激活帧;当编译器确定可恢复函数的寿命可以超过调用时,编译器生成代码,其在执行时,将从堆存储器中分配激活帧。在一些这样的方法中,编译器至少部分地通过使用316静态单赋值使用定义链,来确定可恢复函数的寿命的范围。

yield示例和await示例是非限制性的。本文中的教导并不限于以给定编程语言中定义该功能的特定关键字,也不限于作为可以通过栈省略而优化的协同程序的使用的特定功能。

有时,编译器将添加安全性252,以防止利用可恢复函数激活帧。在一种方法中,编译器还生成代码,其在执行时,验证由可恢复函数激活帧指定的地址是在将向该地址传递控制之前的有效地址。

在一个实现方式中,后端首先进行用于机器独立优化的变换,其可以被概括为:(i)概述协同程序函数主体的部分、(ii)用switch创建机器独立控制流,用内嵌原语隐藏细节。例如,foo的主体将被剪切成三部分,如下所示:

关于代码生成,考虑这些函数中的任一函数的编译402。作为定制机制的一部分,在一些示例中,库编写者被允许选择它们是否想要进行初始挂起和最终挂起,该初始挂起和最终挂起是由编译器引入的,而不是由用户编写的。

在这个示例中,utc中有五个阶段负责降低该函数。

阶段1.例如通过布尔coroutineoutlinetransform(pfuncfunc)实现。这将概述_init()和_resume()函数两者。_init()将分配和设置帧结构226。原始函数将成为调用_init()的小shell。该阶段挂起编译原始函数,并且将所有三个例程放在编译器的工作列表中。将创建相依性边缘,以使得首先编译_resume()。

阶段2.例如通过voidcoroutineprocessresume(pfuncfunc)实现。在每个_yield成插入一对标签,并且在opswitch语句上收集0到n排序的标签。奇数将是运行析构函数并且取消的代码。偶数将是当在该点处恢复时运行的代码。在进行该操作的同时,将suspend变成goto取消,并且在每次保存时,将索引存储回帧。完成在switch语句附近工作的用于恢复函数的所有代码生成。在每个解构标签的末尾处插入代码,该代码跳转到cancel代码序列。该序列拆除帧的promise部分和帧的utc特定部分。在resume()主体的末尾处,创建这个代码序列。在默认标签之后插入调试中断的调用。在调试中断之后,默认插入“done”标签,该标签在结尾之前应该是正确的。

阶段2.例如通过lower()实现。在这个协同程序的实现方式中,前端将针对语法树中示出的操作,生成内嵌原语。c2阅读器将它们变成以下后端内嵌原语144:

1.opintrinsic(iv_resumable_resume_addr)

2.opintrinsic(iv_resumable_frame_size)

3.opintrinsic(iv_resumable_init_frame)

4.opintrinsic(iv_resumable_destroy_formals)

5.opintrinsic(iv_resumable_frame_ptr)

6.opintrinsic(iv_resumable_save)

7.opintrinsic(iv_resumable_suspend)

阶段4.例如通过coromdrestoreandsave(pfuncfunc)实现。在coroutinemd.cpp文件中,将插入针对所有被叫者保存寄存器而保存以及还原代码。还原代码在切换之前触发一次。适当的保存代码会在函数被挂起的每个位置处触发。

阶段5.例如通过coromdinsertfinalmovs().cleanup实现。

关于编译器调度,在一个实现方式中,编译顺序将是:_resume(),_init(),然后是原始的function()。

在一些实施例中,被编译402为协同程序的函数具有创建帧226(有时在堆中)的代码生成,以使得可以无限地(即,任意大量的次数)停顿并且恢复该函数。为了使一些场景更快并且更安全,一些编译器的实施例执行分析和优化346,348,这导致帧被移动到本地栈,并且生成器协同程序主体在调用方中被内联。

一些实施例使用不同内嵌原语144。一个实现方式提供以下内嵌原语:

opintrinsic(iv_resumable_resume_addr)

opintrinsic(iv_resumable_frame_size)

opintrinsic(iv_resumable_init_frame)

opintrinsic(iv_resumable_destroy_formals)

opintrinsic(iv_resumable_frame_ptr)

opintrinsic(iv_resumable_save)

opintrinsic(iv_resumable_suspend)

opintrinsic(iv_resumable_cancel)

一些附加的组合和变化。

代码、数据结构、逻辑、部件、信号、信号定时和/或其功能等同物的这些组合中的任一个组合还可以与上文所描述的系统及其变型中的任一系统及其变型进行组合。过程可以包括本文中以可操作的任何子集或组合或序列描述的任何步骤。每个变体可以单独发生,或者与其他变体中的任一种或多种变体组合发生。每个变体可以与过程中的任一过程一起发生,并且每个过程可以与其他过程中的任一个或多个过程相组合。每个过程或多个过程(包括变体)的组合可以是上文所描述的介质组合和变体中的任一个。

一些实现方式的存在可以通过创建具有满足调用方栈228上,而非堆230中的激活帧226的准则的协同程序的程序,然后通过调试器134逐步通过代码138,以及查看是否帧通过编译器所生成的代码被放在堆上(常规昂贵的方法)或调用方栈(创新的方法)来检测。这种创新可以作为编译选项的一部分,而被呈现给应用程序开发人员,或者可以完全作为编译器优化在后台来操作。该创新可以在单个设备中或跨多个设备实现。

结论

虽然具体实施例在本文中被明确地表达且描述为过程、所配置的介质或系统,但是应当领会,一种类型的实施例的讨论通常还延伸到其他实施例类型。比如,关于图3和图4的过程的描述还有助于描述所配置的介质,并且帮助描述如所讨论的那些系统和制品那样的系统或制品的技术效果和操作。并一个实施例的限制并非必然被读入另一实施例。特别地,过程不一定限于在讨论诸如所配置的存储器之类的系统或制品时所呈现的数据结构和布置。

除非在此明确地说明,否则本文中对具有某个特征x的实施例的引用以及在本文中别处对具有某个特征y的实施例的引用,不排除具有特征x和特征y两者的本公开实施例。所有可能的否定权利要求限制在本公开的范围内,在某种意义上,即使本文中在任何示例中没有给出该特定排除,被表述为实施例的一部分的任何特征也可以被明确地从包括另一实施例中移除。术语“实施例”在本文中仅用作“以与可适用法律相一致的方式而应用的过程、系统、制品、所配置的计算机可读介质和/或本文中教导的其他示例”的更方便形式。因而,只要该实施例与至少一个权利要求一致,则给定“实施例”可以包括本文中所公开的特征的任何组合。

每个在文本中讨论的或在附图中示出的项,并非在每个实施例中都需要存在。相反,实施例可以包含未在特定示例中被明确讨论的或在未在给定图中示出的一个或多个项。尽管通过特定示例在文本和附图中图示了一些可能性,但是实施例可以偏离这些示例。比如,示例的特定技术效果或技术特征可以被省略、重命名、不同地分组、重复、在硬件和/或软件中不同地实例化,或者示例的特定技术效果或技术特征是示例中的两个或多个示例中出现的效果或特征的混合。在一些实施例中,在一个位置示出的函数还可以在不同的位置处提供。本领域技术人员认识到,从整体来看,在给定实现方式中,可以以各种方式定义功能模块,而不必从交互模块的集合中省略所需的技术效果。

通过附图标记参考所有附图。附图或文本中与给定附图标记相关联的短语中的任何明显的不一致性应当被理解为简单地扩大了该标记所引用的范围。即使使用了相同的附图标记,但是给定附图标记的不同实例也可以指不同的实施例。

如本文中所使用的,诸如“一个”和“所述”之类的术语包括所指示的项或步骤中的一个或多个项或步骤。特别地,在权利要求中,对项的引用通常意味着存在至少一个这样的项,并且对步骤的引用意味着执行该步骤的至少一个实例。

标题仅为方便起见;关于给定主题的信息也可以在其标题指示该主题的章节之外找到。

所有权利要求书和摘要在提交时都是说明书的一部分。

虽然示例性实施例在附图中已经被示出,并且在上文被描述,但是对于本领域普通技术人员来说显而易见的是,在不背离权利要求中所阐述的原理和概念的情况下,可以做出许多修改,并且这些修改不需要涵盖整个抽象概念。尽管主题以特定于结构特征和/或过程动作的语言而被描述,但是应当理解,所附权利要求中限定的主题不一定限于上文在权利要求中所描述的特定技术特征或动作。在给定定义或示例中所标识的每个手段或方面或技术效果,不一定存在于每个实施例或在每个实施例中被利用。相反,所描述的特定特征和动作和效果,被公开为在实现权利要求时要考虑的示例。

所有不包括整个抽象概念但属于权利要求的等同物的含义和范围内的改变,将被涵盖在由法律所准许的权利要求的最大范围内。

当前第1页1 2 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1