结构化文本语言编译器的虚拟机指令设计与优化
2018-05-16,
,
(南京南瑞继保电气有限公司,南京 211102)
引 言
IEC61131是国际电工委员会(IEC)颁布的可编程控制器(PLC)国际标准,用于规范可编程控制器编程工具和应用程序的开发,目的是便于各厂家的应用程序移植,降低用户的使用难度和维护成本[1-5]。其中IEC61131-3为软件设计提供了标准化的编程概念和编程方法,定义了指令表IL、结构化文本ST、梯形图LD、功能块图FBD、顺序功能图SFC几种语言的规范,国内外工控厂家、研究机构已经开始提供基于该标准的产品[6-8]。参考文献[9-10]介绍了嵌入式软PLC系统的架构,参考文献[11]提出一种将ST语言转换为IL指令的方法,参考文献[12]设计了ST的文法分析器,上述资料文献推进了IEC61131在国内的研究进程。ST有标准的关键字和语法结构,可用于复杂逻辑设计实现,通常有将ST语句直接编译为硬件相关的目标可执行文件或者将ST转换为C语言后再调用第三方编译的处理方法,这种方法执行效率较高,但无法满足以程序组织单元(POU)为单位的在线无扰更新的需求。
在一些工控应用场合中,当装置处于运行状态时,需要能实现部分程序的下载更新和生效,不能将整个程序编译为1个目标文件,而是需要编译为与机器无关的虚拟机指令,在嵌入式装置侧的非实时任务中进行指令文件更新,在实时任务中解释执行。针对上述需求,需要定义一套指令集,开发自主编译器,本文介绍了指令集设计和对应的ST主要语句的处理表达模式、指令优化方法。
1 虚拟机指令设计
1.1 ST语言简介
IEC61131定义的软件模型包括配置、资源、任务、全局变量、访问路径,在任务中可运行程序实例。程序划分为若干组织单元(POU)管理。ST语言是类似于PASCAL的高级语言,用文本编写的代码由语句和表达式组成,每行用分号结束,用ST语言编写的POU包括变量声明、赋值语句、流程控制语句(选择、循环、返回),形成中间指令时需翻译转换这些语句。以CASE选择语句为例,其示例代码如下:
CASE PARA OF
1,2: a:=1;
3..5: a:=2;
ELSE a:=3;
END_CASE;
CASE语句中表达式的值必须是整数,并可采用连续符号..表示连续的多个整数,不同整数值用逗号分隔,用于执行相同的语句组。
1.2 整体架构设计
为了满足工控程序在线无扰更新的需求,将ST程序编译为虚拟机解释型指令。如图1所示,编译器分前端和后端,前端读取ST源程序,借助于flex完成词法分析,借助bison完成语法分析,其中flex是词法分析工具、bison是语法分析工具[13]。自主开发的编译器在语法分析过程中创建符号表保存数据,形成语法树,遍历语法树进行语义分析,形成中间数据结构,并输出中间指令。解释器在初始化或指令文件更新后,读取指令文件,形成指令数组,在周期或中断任务中执行相关指令。
图1 ST语言编译解释架构
1.3 指令集设计
在编译后采用虚拟机指令表示,可以适当隔离不同机器平台的特点,便于解释器系统的移植。本文的中间指令以三地址码为基础,三地址码包含一个操作符、源操作数和一个目的操作数。目的操作数用来存放源操作数经过操作符对应操作处理后产生的结果,或者对于转移操作,目的操作数代表要转移的目的地址。三地址码中源操作数的数目可以小于两个,三地址码基于两个基本概念:地址和指令[14]。地址可以为指向符号表条目的常量、编译器生成的临时变量。本文使用变量在数据区的序号作为地址标号,每个变量在数据区对应1个相同大小的结构体,记录变量类型、初始值、是否保持、是否边沿触发等IEC61131定义的属性。
本文在指令设计时参考了VCODE和CIL指令集的部分理念[15-16]:使执行频繁的部分保持高效,使其它部分保持正确。根据ST语言的规范和特性,支持可变形参,可内置调用更丰富的库函数接口。表1为指令集。
表1 ST虚拟机指令集
在存储时指令类型占用1字节,指令存在二元运算(rd,rs1,rs2)、一元运算(rd,rs)、跳转指令jmp、函数调用scall等模式。采用紧凑型存储,根据指令类型动态输出形参个数。
2 ST语句编译与优化
当定义指令集后,编译器的主要工作是将ST语句编译为指令序列。需要设计各语句对应的编译处理方案,这也是ST语言编译的关键步骤。
2.1 IF语句的处理
IF语句带有可选的ELSEIF、ELSE部分。这两个可选部分的组合产生两种形式。以带有ELSEIF语句的为例,其语句结构为:
IF
ELSEIF
…
ELSEIF
ELSE
END_IF;
对应的编译方案如下所示:
{Evaluate
(jz,
{ code for (jmp EndLabel) ELS1Label:(lab, ELS1Label) {Evaluate (jz, { code for jmp EndLabel) … ELSN-1Label:(lab, ELSN-1Label) {Evaluate (jz, { code for (jmp EndLabel) ELSNLabel:(lab, ELSNLabel) {code for EndLabel: (lab, EndLabel) IF语句翻译处理的关键步骤为: ① 在每个布尔表达式后,需要根据表达式的值生成一个条件跳转; ② 在THEN部分后,如果有相应的ELSEIF、ELSE部分,则需生成跳转语句跳过; ③ 标识 ELSEIF、ELSE部分的正确标号,以及IF语句的结尾须由构造jmp元组的动作例程产生,相应的LABEL元组须放在元组序列的正确位置上。基于标签唯一分配机制可避免目标元组序号回填。 ST语言的FOR语句的特点如下: ① 要求控制变量、初始值、终值是相同的整数类型(SINT、INT、DINT)的表达式,不能被任何重复的语句改变(在循环语句内作为只读加以保护)。 ② FOR结构中有两个表达式,即“循环初始表达式”、“循环终止表达式”,这两个表示的计算结果类型必须是有序类型。 ③ 标准ST规定FOR有2种形式,即FOR-TO-BY-DO、FOR-TO-DO。FOR 语句将控制变量从初始值向上或向下增加到终值,其增量由表达式的值决定。如果没有BY关键字,则缺省值是1。终止的条件检测在每个迭代开始时进行,初始值超过终值时,退出语句序列。FOR语句的文法格式如下所示: FOR语句带BY关键字 : FOR END_FOR; FOR语句带未BY关键字自动默认增量为1 : FOR END_FOR; 其中expr1是循环索引赋值表达式,expr2是终止值表达式,expr3是增量表达式,实际应用中,expr3多是常量表达式。为了提高运行效率,当expr3是常量值时,可预先判定FOR语句是“向上计数”或“向下计数”类型。当该常量值大于0时,使用向上计数的元组序列,常量值小于0时,使用向下计数的元组序列。通常的FOR语句代码序列将会在循环地步增值索引变量,然后再跳转回循环上部以测试新值是否上溢出。这种方法缺点是:如果上界是最大整数的话,这个增值可能在最后的测试前引起上溢[17]。为避免这个问题,在参考文献[17]的基础上,本文设计了ST 语言的FOR语句的安全编译方案,如图2所示。 图2 向上/下计数的FOR语句元组序列 图2的语句序列可能看起来有些费解,因为每一个序列都包括2个不停的终止测试。这是由于:如果迭代的上界是给定机器所能表示的最大整数,则这种结构是必要的,确保了向上计数的FOR的正确终止。在ST语言中,循环索引变量是一个从循环外可见的变量,当索引变量是子界类型并且循环迭代在整个子界范围时,上述的代码序列能确保该变量绝不被赋值为超出范围的值,并保证该循环体代码除非至少执行一次,否则永不执行。 当expr3是常规表达式时,不能预先判定是上行或下行,则先计算expr1、expr2,并判断表达式值的大小,动态确定上下限。如图3所示,在循环执行的指令段中,先执行FOR中语句,之后计算expr3,更新索引变量,判断是否大于上界或小于下界,若是,则跳出循环。 图3 动态确定上下限FOR语句元组序列 大部分情况下,应用程序使用CASE语句时,判断变量都是单个常数,故先检测CASE是否为单变量-全常量分支,符合条件时,形成基于跳转表的优化翻译模式[18]。当CASE的分支表达式是形如a,b,c..d的形式,可形成短路求值后跳转指令,其编译方案如图4所示。 图4 多分支CASE语句元组序列 在处理CASE分支的表达式时,可直接输出表达式对应的三地址码,不必临时构建boolean-expr表达式后再翻译,例如: ① 对于CASE a单变量,输出: (je, Var, a, LabelX); ② 对于CASE a,b多变量,输出: (je, Var, a, LabelX) (je, Var, b, LabelX) ③ 对于 CASE a..b区间变量,输出: (ge, Temp1, Var, a) (le, Temp2, Var, b) (and, Temp3, Temp1, Temp2) (jz, Temp3, LabelX) 当CASE存在多个逗号时,以逗号为间隔,形成上述的指令序列(其中..分配3个临时变量),最后通过临时变量的逻辑或运算,由于已经预先分配分支标号,可在OR运算时加入短路求值跳转的指令,提高运行效率。 ST中的EXIT语句类似于C的break语句,在条件结束前终止循环。当EXIT语句位于嵌套的循环结构内时,从EXIT所在的最内层循环突出,即在跟随EXIT语句的第一循环的终止符后(END_FOR、END_WHILE、END_REPEAT),EXIT语句可以用无条件跳转指令jmp来表示,它和IF/WHILE语句的区别在于跳转目标标签是由其它节点生成的。针对多层嵌套循环,为了确定跳转语句的跳转目标,采用图5所示的栈操作机制。 图5 基于栈确定跳转目标标签 图5的算法思路如下: ① 基于栈的数据结构管理标签; ② 在遍历语法树时遇到可以用EXIT跳出的语句(例如WHILE),将跳转目标的标签压入栈(EndLabel); ③ 将WHILE语句的本体转换为中间代码; ④ 如果在本体中发现EXIT语句,将栈顶的标签作为跳转目标; ⑤ 本体的转换结束后,将栈顶的标签弹出栈。 当前EXIT语句的跳转目标的标签始终位于栈顶。 在嵌入式装置解释执行二进制指令时,对实时性要求很高。常用的编译器通常是在编译阶段进行优化,但由于局限于局部函数的优化,欠缺整体的考虑,而且采用的优化方法是个黑盒子,验证对比分析相对困难,所以需要一种安全、可靠的指令优化方法,本文对编译后的指令文件进行常规优化,该方法透明,可通过反汇编验证跟踪优化的正确性,优化步骤如下: ① 解析指令文件,读取文件头,获取数据区、指令区内容。 ② 分析数据区,获取变量序号、变量类型、是否为常量、临时变量等标志属性,其中临时变量可进行增加、删除操作,常量变量可以直接运算。 ③ 第1次遍历指令区,处理常量运算,并优化临时变量赋值指令。其中对于右值变量都为常量类型的指令,则直接计算常量表达式,并将运算指令修改为赋值指令。对于赋值指令,进行临时变量上下文的优化,例如对于 “add tmp1, var2,var3;asgn var1, tmp1”的指令,可优化为“add var1, var2, var3”。 ④ 第2次遍历指令区,进行代数简化[19]。例如直接删除与立即数0的加减法、与立即数1的乘法运算,将与立即数0的乘法运算转化成0的赋值操作,简化与立即数TRUE、FALSE相关的逻辑运算等。 ⑤ 第3次遍历指令区,进行变量引用点分析,去除未使用变量和无效指令。统计各条指令的左值变量在后续指令中被作为右值变量的计数,若引用计数为0,则删除该条指令,若优化后某临时变量未被引用,则可从数据区删除。 经过100余个包含多层结构体、数组变量的复杂语句测试用例统计,指令优化前后整体效率提升了10%~25%左右。 参考文献 [1] Karl-Heinz JohnMichael Tiegelkamp.IEC61131-3:Programming Industrial Automation System[M]. Berlin:Springer,2000. [2] InternationalElectrotechnical Commission.IEC61131-3 Programmable controllers-Part1:General information[S].2nd ed.2003. [3] InternationalElectrotechnical Commission.IEC61131-3 Programmable controllers-Part3:Programming language[S].2nd ed.2003. [4] InternationalElectrotechnical Commission.IEC61131-8 Guidelines for the application and implementation of programming languages[S].2003. [5] 中国国家标准管理委员会.GB/T 15969.3-2006可编程控制器-第3部分:编程语言[S]. [6] 彭瑜,何衍庆.IEC61131-3编程语言及应用基础 [M].北京:机械工业出版社,2008. [7] 任向阳.开放式IEC61131控制系统设计[M].北京:机械工业出版社,2016. [8] Phonenix Contact Software GmbH.MULTIPROG help documents[EB/OL].[2018-02].http://www. phonenixcontact-software.com. [9] 未庆超,蔡启仲,谢从涩,等.基于ARM的PLC编译系统设计[J].计算机测量与控制,2014,22(4):1225-1229. [10] 姜娟,冯萍,康继昌.嵌入式软PLC开发系统研究[J].科学技术与工程,2011,11(3):494-498. [11] 张玉娇,卓怀忠,沈开奎,等.基于IEC61131-3标准的ST转化为IL语言的设计与实现[J].自动化与仪表,2016(9):74-76. [12] 梁世武,李加恒,朱立国,等.基于IEC61131-3标准的ST语言文法分析器的实现与应用[J].仪器仪表标准化与计量,2015(5):26-29. [13] Jobn Levine.flex与bision[M].南京:东南大学出版社,2011. [14] Alfred V Aho,Monica S Lam,Ravi Sethi,et al.Compilers Principles,Techniques&Tools[M].龙书,译.北京:机械工业出版社,2009. [15] 姜玲燕,梁阿磊,管海兵.动态二进制翻译中的中间表示[J].计算机工程,2009,5(9):283-285. [16] Microsoft Corporation.Common Language Infrastructure(CIL),Partition III,CIL Instruction Set,2001. [17] Charles NFischer,Richard J LeBlanc.Crafting A Compiler with C[M].Boston:Addison Wesley,1991. [18] 裘魏.编译器设计之路[M].北京:机械工业出版社,2011. [19] 青木峰郎.自制编译器[M].北京:人民邮电出版社,2016.2.2 FOR语句的处理
2.3 CASE语句的处理
2.4 EXIT语句的处理
2.5 指令优化
结 语