方舟编译器源码过一遍流程

不管是被带节奏还是啥,在年初放出方舟编译器的消息后,我真的很期待的,毕竟这是我本科一直很想去的华为编译器部门出品的,并且迫不及待地更新了最新的EMUI,体验一波所谓的方舟编译器。不过目前确实,没看到有啥实质性的、明眼可以看的东西。

跨语言编译的事,有一个比较成熟的graal在做了,其实也不算什么新思想。不过放在移动端,甚至是IoT领域,确实是前无古人。

昨天下载了代码,但是在火车上还没看,今天大致看了一下。

其实该吐槽的别人都吐槽了。

  • 文档啥的确实写得不怎么样,看完文档确实没懂应该怎么做才能跑起来;
  • 为啥是引用计数呢?
  • 后端的东西可能和华为的麒麟关系很大,需要脱敏可以理解,但是Java的前端有啥不好开源的?
  • 我对LLVM比较熟悉,说实话,我真的觉得二者好像啊,Phase和Pass的概念(感觉方舟的Phase开发没有Pass开发方便)、IR的设计等;(为什么不基于LLVM呢?小声BB……)
  • ……

目前开源的东西实在太少了,我其实很想知道以下是怎么做的(如果有内部人士提前透露一下就好了):

  1. 静态编译后如何保持动态特性(reflection,Java的invokedynamic等),特别是如果要支持JS的话,元信息等怎么处理;
  2. 内存管理部分,肯定不是单纯的引用计数吧;
  3. 多语言联合编译的话,是有一个统一的runtime提供所有语言的功能,还是用到一种语言就链接这个语言的runtime子集呢?还是两种结合的方式(一个提供基本功能的runtime,多个language-specific的runtime);
  4. 代码用方舟编译后在安卓运行,是私有格式,还是类似Xamarin的方式?(更新:这个可以参阅华为的方舟编译器主要修改了zygote

大概看了一些代码了,头晕。

可能因为我是C++菜鸡,有一些地方我实在看不懂为啥这么写……

源码结构

  • bin:编译好的可执行文件和一些脚本,很奇怪为什么要放在这里;
  • deplibs:因为有很多东西还没有开源,所以在这里提供了静态库;
  • huawei_secure_c:华为自己实现的xxx_s函数,很多只是简单的vxxx_s的封装(不是很理解为什么这么做);
  • maple_driver:编译器的驱动,就是把编译->各种Phase的转换和优化->链接->生成可执行文件这个流程串起来,目前好像不完整啊。在该目录下的defs/phases.def有所使用到的Phase的定义,有一些可以在src/mpl2mplsrc/maple_me中可以找到Phase的实现,有一些还没找到,不知道是没开源还是我漏了。
  • maple_ipa:ipa的实现,没细看,感觉是各种Phase的组合;这个部分应该不完整;
  • maple_ir:这次开源的重点,IR的生成、解析等;有一个手写的Lexer和一个手写的递归下降的Parser(牛批!将近3000行,写这个的兄弟写完估计眼睛就瞎了);primitive type很有意思;具体实现还没细看;
  • maple_me:me是啥的简写很迷啊,是maple emiting吗?功能应该和LLVM的emit类似;有几个Phase的实现在这里;一些中端优化;(更新:me是middle end的缩写);
  • maple_phase:Phase框架的实现,没有开源,只有三个头文件;
  • maple_util:工具类,没有开源;
  • mempool:内存池的实现,没有开源;有意思的是,定义了一个MapleString(果然每一个C++项目都会有自己的字符串实现啊hhh);
  • mpl2mpl:一些Phase的实现,主要是一些analysis、异常处理啥的,针对Java有一个native方法的stub的生成;有对Reflection分析的Phase,提取类型信息、方法信息、类字段信息(不限于Java,看定义可以支持C++、Python等),然后放在maple的metadata(定义在src/maple_ir/include/metadata_layout.h)中,metadata_layout.h中有一句说“metadata layout is shared between maple compiler and runtime”,所以Reflection需要runtime的支持是肯定的;有一些Phase的缩写太奇怪了,没看懂要干啥;
  • third_party:第三方库,只有一个zlib;应该是jbc2mpl用到了,用来解压jar文件;

MIR

看完MIR的文档,确实感觉有点不太舒服,和我印象中的IR应该有的样子不太一样,我一直觉得LLVM IR才是IR应该有的样子(太天真了点)。

看到有答主提到了MIR有师出同门的Open64的身影,我特意去了解了一下Open64的Whirl IR(Open64 Compiler Whirl Intermediate Representation),发现确实是的,可能这个就是Fred Chow老爷子的设计风格。

可以看到,MIR有很多和语言特性相关的opcode(例如Java的类声明、virtualcall等),甚至有if、while这种特别high level的opcode。如果参照Whirl IR的设计,其实这个是合理的。在Whirl IR中,IR是分为多种不同的level的,虽然都叫Whirl IR,但是囊括了从高级语言到底层机器码的过程中的所有可能需要的不同level的表示形式,可以方便进行不同层级的优化。high level的IR可以进行和语言特性相关的优化,low level进行和硬件特性相关的优化,类似这样。不过MIR的文档最好说明一下这么设计的原因,给个类似Whirl IR的这种流程图也可以,不然很容易被喷的。

如图,每一个转换过程都会将更high level的IR翻译为lower level的IR。

还没细看目前开源的实现中Phase的实现,不过像if、while这种high level、层级化的控制流opcode,经过Phase的转换、优化后,应该会变成goto、brfalse、brtrue等扁平化的控制流opcode(更新:在src/maple_ir/src/mir_lower.cpp有具体实现)。整体结构可以算是Open64的翻版,可能确实是老爷子的怨念吧hhh。

和Whirl IR不太一样的地方,就是MIR中有一些直接和前端语言特性强相关的opcode,目前看到的有类型里包含JS的数据类型、opcode中的Java Call和Java Class and Interface Declaration。这部分可能之后添加对JS等的支持后,还会有更多的opcode。

Java Class and Interface Declaration这个部分没啥好说的,Java Call应该是直接和Java Byte Code的invoke系列字节码(invokedynamic、invokespecial、invokeinterface、invokestatic、invokevirtual)对应了,不过按理来说适用其他语言。invokevirtual对应virtualcall、supercall,invokeinterface对应interfacecall,invokespecial和invokestatic编译之后进行name mangling可以当做普通函数(类似C++),对应的应该是MIR中Call系列。invokedynamic没有对应的,所以目前估计是没有直接解决这个问题,看后续的开源吧。

关于invokedynamic指令,我个人的一种猜测是,可能是runtime提供Callsite的构建,类似于在Java 7之前groovy的做法。不过这种方法效率挺低的,需要一些黑科技了。

更新:

关于invokedynamic指令,我觉得我之前肯定傻逼了,既然IR没有提供直接的支持,那就说明一个问题:在前端把invokedynamic翻译为了IR中有的指令。经过我的测试以及对jbc2mpl的逆向,确实是这样的,不过目前还不怎么清楚具体的机制,这里就不细说了。

上手

很惭愧,这么久了其实还没自己编译一遍,也没有上手试一下,只是大致看了代码。

试了一下,可以明显感觉开源的东西太少了,想要跑起来是不可能的,因为无论是编译期Runtime和执行时的Runtime都没有提供,这次开源的东西,确实只能看一下转换出来的IR(直接从Java字节码转换过来的,而这个jbc2mpl还没有开源)。

很多答主都说无法运行,这是因为没有提供Java的Runtime,所以想要能够生成.mpl,任何涉及到Java基本库中的类的都不要出现(所以不能System.out.println、不能测试异常,甚至不能出现main函数,因为main函数的参数有String,摊手.jpg)。

按照文档的指导,配置好Clang、gn、ninja之后,可以正常编译出来。这里要提一句,有人说开源的代码只有声明没有实现,这是不对的,那部分实现只是还没开源而已,提供了静态库,所以编译是没有问题的。

1.准备测试代码

因为不能涉及任何基本库,所以基本算是残废的Java,这里我们就主要看Java字节码和MIR的对应关系。

以下面的斐波那契为例:

 public class HelloWorld {
	 public static int fib(int n) {
	     return n <= 2 ? 1 : fib(n - 1) + fib( n - 2);
	 }
 }
//编译//javac HelloWorld.java//反编译得到Java字节码
//javap -verbose  HelloWorld.class > HelloWorld.jbc
2.生成对应的Maple IR

如果没有涉及基本库,是可以生成IR的:

//jbc2mpl -inclass HelloWorld.class -o HelloWorld.mpl

这个过程中会打印一个警告信息:

"Warn  20: method Ljava_2Flang_2FObject_3B_7C_3Cinit_3E_7C_28_29V is undefined"

因为父类Object的构造函数没有定义(还是因为没有Runtime)。

同时生成了两个文件:HelloWorld.mpl和HelloWorld.mplt。

.mpl是IR,.mplt是符号表(?)

3.分析

根据官网的演示界面上的流程,后面还要跑maple优化IR和mplcg进行汇编生成的,但是我无论怎么跑都segmentation fault,遂放弃。

和Java字节码对比一下会发现基本是直译过来了(生成的IR还用注释给出了原始的Java字节码指令是啥,良心了)。

之前警告的方法是name mangling后的Object构造函数,这里将invokespecial指令翻译为了MIR的superclasscallassigned,和上面的猜测基本一致。这个地方要依赖Runtime,可能就是后续的步骤无法继续下去的原因吧。

曲线救国

按照官网的流程来说,需要先用maple进行一些中端优化的,但是maple实在跑不起来,所以想了一下,干脆直接进行汇编生成,测试一下整体的流程。

还是以上面的fib为例,用Maple IR写出来(其实是把上面的.mpl删了很多东西直接得到的),然后进行测试。

  1. fib.mpl

2. 使用mplcg生成汇编,直接看命令行参数可以发现mplcg的功能还是挺多的:

mplcg  fib.mpl

汇编大概如下:

//fib.s
//    .file    "fib.mpl"
//    .text
 //   .align 2
//    .globl    fib
//    .hidden    fib
//    .type    fib, %function
//fib:
//.Label.fib3:
 //   .cfi_startproc
 //   stp    x29, x30, [sp,#-48]!
 //   .cfi_def_cfa_offset 48
 //   .cfi_offset 29, -48
 //   .cfi_offset 30, -40
 //   mov    x29, sp
 //   .cfi_def_cfa_register 29
 //   str    w0, [x29,#40]
 //   mov    w1, #2
 //   str    w1, [x29,#16]
 //   ldr    w1, [x29,#40]
 //   ldr    w2, [x29,#16]
 //   cmp    w1, w2
 //   bgt    .Label.fib1
//    mov    w1, #1
//    str    w1, [x29,#16]
//    ldr    w1, [x29,#16]
//    str    w1, [x29,#20]
//    b    .Label.fib2
//.Label.fib1:
//    mov    w1, #1
//    str    w1, [x29,#16]
//    ldr    w1, [x29,#40]
//    ldr    w2, [x29,#16]
//    sub    w1, w1, w2
 //   str    w1, [x29,#16]
 //   ldr    w0, [x29,#16]
//    mov    w0, w0
//    bl    fib
//    str    w0, [x29,#16]
//    mov    w1, #2
//    str    w1, [x29,#24]
 //   ldr    w1, [x29,#40]
 //   ldr    w2, [x29,#24]
//    sub    w1, w1, w2
//    str    w1, [x29,#24]
//    ldr    w0, [x29,#24]
//    mov    w0, w0
 //   bl    fib
//    str    w0, [x29,#24]
//    ldr    w1, [x29,#16]
 //   ldr    w2, [x29,#24]
//    add    w1, w1, w2
//    str    w1, [x29,#16]
//    ldr    w1, [x29,#16]
//    str    w1, [x29,#20]
//.Label.fib2:
 //   ldr    w0, [x29,#20]
  //  str    w0, [x29,#16]
 //   ldr    w0, [x29,#16]
//    b    .Label.fib6
//.Label.fib6:
//    ldp    x29, x30, [sp], #48
  //  .cfi_restore 29
//    .cfi_restore 30
//    .cfi_def_cfa 31, 0
//    ret
//.Label.fib5:
//.Label.fib4:
 //   .cfi_endproc
  //  .size    fib, .-fib
   // .section  .__muid_conststr,"aw",%progbits
//__muid_conststr_begin:
//__muid_conststr_end:
 //   .section  .__muid_classmetadata,"aw",%progbits
//__muid_classmetadata_begin:
//__muid_classmetadata_end:
  //  .section  .__muid_itab,"aw",%progbits
//__muid_itab_begin:
//__muid_itab_end:
 //   .section  .__muid_vtab,"aw",%progbits
//__muid_vtab_begin:
//__muid_vtab_end:
  //  .section  .__muid_vtab_offset_tab,"aw",%progbits
//__muid_vtab_offset_tab_begin:
//__muid_vtab_offset_tab_end:
//    .section  .__muid_field_offset_tab,"aw",%progbits
//__muid_field_offset_tab_begin:
//__muid_field_offset_tab_end:
//    .section  .__muid_offset_value_table,"aw",%progbits
//__muid_offset_value_table_begin:
//__muid_offset_value_table_end:
 //   .section  .__muid_local_classinfo_tab,"aw",%progbits
//__muid_local_classinfo_tab_begin:
//__muid_local_classinfo_tab_end:
//    .section  .__muid_superclass,"aw",%progbits
//__muid_superclass_begin:
//__muid_superclass_end:
 //   .hidden DW.ref.__mpl_personality_v0
//    .weak DW.ref.__mpl_personality_v0
//    .section .data.DW.ref.__mpl_personality_v0,"awG",%progbits,DW.ref.__mpl_personality_v0,comdat
//    .align 3
 //   .type DW.ref.__mpl_personality_v0, %object
//    .size DW.ref.__mpl_personality_v0,8
//DW.ref.__mpl_personality_v0:
//    .xword __mpl_personality_v0

一堆的load/store指令,生成的应该是aarch64的汇编。

同时还会生成一个.groots.txt文件和.primordials.txt文件,不知道干啥的,内容是空的。

自己测试了一下优化级别,用-O2来生成汇编:

mplcg -O2 fib.mpl

diff比对一下,确实有不同,应该进行了尾递归优化(ARM汇编快忘了,没细看。更新:恶补了一下ARM汇编之后,发现并没有尾递归优化)。

3. 测试程序

生成了汇编以后,写个程序测试一下:

main.c

#include<stdio.h>

extern int fib(int n);

int main(int argc, char** argv) {
    printf("%d\n", fib(10));
    return 0;
}

4. 交叉编译

用Android NDK的工具链编译(按照我以前的经验,要加-static静态编译才行):

aarch64-linux-android21-clang -static fib.s main.c -o fib
会报错。

undefined reference to `__mpl_personality_v0‘

可以看到上面的汇编的下半部分,都是一些虚表、构造函数啥的section(很奇怪为什么一个fib函数会生成这些东西,这个应该是和对象结构相关的东西),目前没有用处,从".section .__muid_conststr,"aw",%progbits"开始直接全部删了。

再次编译成功。

5. 运行

不知道为啥qemu运行出错了,所以用adb连接安卓模拟器,然后把文件push到安卓模拟器之后再运行,这样是没有问题。

运行结果:55

整个流程就彻底算跑通了(但还并不是直接编译Java源码),概念上验证方舟是可行的(其实mplcg生成了aarch64汇编之后,后面的都是可以用传统的toolchain来,方舟可能也是这么做的)。

更新:

因为没有提供libjava-core.mplt,我想是不是可以把整个Java运行时用jbc2mpl转换一下呢(方舟团队肯定也不是手动编写这个libjava-core.mplt吧)?

于是下载了Java 5(怕太新的maple不支持,不过Java字节码也就在Java 7增加了一条invokedynamic指令)的JDK,提取出rt.jar,然后用jbc2mpl转换整个rt.jar。但是这种方法不行,会报错很奇怪的错:

Tid(7292): CHECK/CHECK_FATAL failure: false at [../../../jbc2mpl/src/jbcOpcode.cpp:138] Reversed opcode jsr

(Reversed应该是typo,应该是Reserved吧)

其实仔细想一想,这种方法肯定会有问题,因为Java基本库并不是自洽的(基本库里调用了一些和具体虚拟机实现相关、私有的代码,com.sun包下的那些东西等等)。而且谷歌有先例在,Oracle肯定不放过,也不知道华为怎么避免专利问题。

不想折腾了,等开源更多的东西后一切都会明了吧。

更新:今天(2019.9.5)发现上面转换rt.jar获得libjava-core.mplt的方法已经有人做了,并且取得了一些成果(链接:贴吧用户wconly的帖子)。这个是苦力活,辛苦这位兄弟了。不过大家不要再尝试这种方法了,即使可以把rt.jar和相关的jar都通过jbc2mpl转换为了.mpl和.mplt,也是跑不起来的。以System.out.println为例,它最终会落在一个native方法上,而这个native方法会调用某个IO系统调用,这个并不是Java基本库本身提供的。要想跑起来,需要自己写这些Java基本库中的native方法的stub的实现,可能等研究完源码、自己写了stub的实现,华为已经把方舟都完整开源了。

2019.9.6最后一更(雾)

我先直接下一个结论:目前没办法不借助任何辅助手段直接通过开源的东西跑起来哪怕一个简单的HelloWorld。这里不借助任何辅助手段是指自己不写代码、也不使用任何自己找到的Runtime。

首先介绍方舟用到的两种重要文件格式.mplt和.mpl:

  • .mplt是声明(符号表),可以理解为.h头文件;
  • .mpl是定义,是具体实现,可以理解为.c实现,.mpl都会import相应的.mplt,相当于就是#include了;

因为Runtime是方舟很重要的一部分(很大概率不会开源的),这么设计,华为可以只给应用开发者提供.mplt文件,不给具体实现,最后链接到Runtime就好了。

而目前开源的东西里,没有给Runtime的实现,甚至连头文件(libjava-core.mplt)都没给。各位大大都想尽办法构建这个头文件:

通过这种方式,确实可以按照流程跑起来,并且最终得到汇编(但这个还是有限制)。

但是问题在于:

  • Java的基本库不是单纯由Java编写的,还有很多native方法;
  • 而HelloWorld中一个System.out.println涉及的模块很多,调用链错综复杂;(这个也是Java 9 的module系统解决的一个问题)

就以System.out.println为例(来源:How System.out.println() really works?):

整个的调用链最终落在了一个JNI调用,调用native方法访问操作系统提供的IO系统调用(打印出"Hello World!")。

问题就在于,这个native方法本身应该是在JVM中实现的,而现在如果我们要跑起来,我们就要自己实现这个方法。

而除了这些基本库本身的native方法外,方舟编译器其实还有一些自己私有的函数、symbol,仔细阅读生成的汇编,会发现__mpl_personality_v0(编译fib.s时出错的部分)这个方舟编译器插入的symbol,这个肯定不是Java基本库或者我们写的。还有很多类似的函数和symbol。

当然这个不是大问题,我们有时间的话,可以把涉及的native调用和能发现的方舟编译器的私有函数和symbol都自己实现了,最后链接进去。

但是还有一个问题:main函数入口。

回想一下C语言程序的链接过程,简单来说,在最终生成可执行文件时,是会链接进去一个crt0.o(或者其他类似的),包含一个_start入口,进行C语言Runtime的初始化,最终再跳转到我们写的main函数。

而在传统的Java程序的运行方式中,环境准备、跳转到main函数,这个是JVM干的事,但是现在方舟静态编译了Java,也就是说,需要我们进行main函数入口的指定和Runtime的初始化工作。

以一个不做任何事的HelloWorld.java为例:

public class HelloWorld {
    public static void main(String[] args) {

    }
}

借助前面两位大大的方法,它生成的.mpl如下:

//flavor 1
//srclang 3
//id 65535
//numfuncs 2
//import "HelloWorld.mplt"
//import "../standard/core.mplt"
//entryfunc &LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V
//fileinfo {
//  @INFO_filename "HelloWorld.class"}
//srcfileinfo {
//  1 "HelloWorld.java"}
//javaclass $LHelloWorld_3B <$LHelloWorld_3B> public
//func &LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V public constructor (var %_this <* <$LHelloWorld_3B>>) void
//func &LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V public static (var %Reg0_R542 <* <[] <* <$Ljava_2Flang_2FString_3B>>>>) void
//var $__cinf_Ljava_2Flang_2FString_3B <$__class_meta__>
//func &MCC_GetOrInsertLiteral () <* <$Ljava_2Flang_2FString_3B>>
//func &LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V public constructor (var %_this <* <$LHelloWorld_3B>>) void {
//  funcid 180174
//  var %Reg1_R163412 <* <$LHelloWorld_3B>>
//  var %Reg1_R78 <* <$Ljava_2Flang_2FObject_3B>>

//  dassign %Reg1_R163412 0 (dread ref %_this)
//  #INSTIDX : 0||0000:  aload_0
//  #INSTIDX : 1||0001:  invokespecial
//  dassign %Reg1_R78 0 (retype ref <* <$Ljava_2Flang_2FObject_3B>> (dread ref %Reg1_R163412))
//  callassigned &Ljava_2Flang_2FObject_3B_7C_3Cinit_3E_7C_28_29V (dread ref %Reg1_R78) {}
 // #INSTIDX : 4||0004:  return
// return ()
//}
//func &LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V public static (var %Reg0_R542 <* <[] <* <$Ljava_2Flang_2FString_3B>>>>) void {
//  funcid 180175
//  intrinsiccallwithtype <$LHelloWorld_3B> JAVA_CLINIT_CHECK ()
//  #INSTIDX : 0||0000:  return
//  return ()
//}

因为JAVA_CLINIT_CHECK ()没有实现,直接mplcg生成汇编会有问题:

Tid(15952): CHECK/CHECK_FATAL failure: false at [../../../mapleall/maplebe/src/cg/aarch64/aarch64cgfunc.cpp:8813] Intrinsic 53: JAVA_CLINIT_CHECK not implemented by the AArch64 CG.

把.mpl的倒数第四行删除后,可以生成有效的汇编(测试了一下,经过mplme和mpl2mpl转换之后可以不用删除,在src/mpl2mpl/src/class_init.cpp中有lower过程):

    .file    "HelloWorld.mpl"

    .section    .rodata
    .align    2
//__method_desc__LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V:
    .word .Label.name.LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V - .
    .short 40
    .short 0
    .section  .__muid_java_text,"aw"
    .align 2
    .globl    LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V
    .hidden    LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V
    .type    LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V, %function
 //   .word __method_desc__LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V-.
LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V:
.Label.LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V1:
    .cfi_startproc
    .cfi_personality 155, DW.ref.__mpl_personality_v0
    stp    x29, x30, [sp,#-48]!
    .cfi_def_cfa_offset 48
    .cfi_offset 29, -48
    .cfi_offset 30, -40
    mov    x29, sp
    .cfi_def_cfa_register 29
    str    x0, [x29,#40]
    ldr    wzr, [x19]
    ldr    x1, [x29,#40]
    str    x1, [x29,#16]
    ldr    x1, [x29,#16]
    str    x1, [x29,#24]
    ldr    x0, [x29,#24]
    mov    x0, x0
    bl    Ljava_2Flang_2FObject_3B_7C_3Cinit_3E_7C_28_29V
    ldr    wzr, [x19]
    b    .Label.LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V4
.Label.LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V4:
    ldp    x29, x30, [sp], #48
    .cfi_restore 29
    .cfi_restore 30
    .cfi_def_cfa 31, 0
    ret
.Label.LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V3:
.Label.LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V2:
    .cfi_endproc
.Label.end.LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V:
    .size    LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V, .-LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V
    .word 0xFFFFFFFF
    .word .Label.LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V4-.Label.LHelloWorld_3B_7C_3Cinit_3E_7C_28_29V1

    .section    .rodata
    .align    2
//__method_desc__LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V:
    .word .Label.name.LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V - .
    .short 24
    .short 0
    .section  .__muid_java_text,"aw"
    .align 2
    .globl    LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V
    .hidden    LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V
    .type    LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V, %function
    .word __method_desc__LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V-.
LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V:
.Label.LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V1:
    .cfi_startproc
    .cfi_personality 155, DW.ref.__mpl_personality_v0
    stp    x29, x30, [sp,#-32]!
    .cfi_def_cfa_offset 32
    .cfi_offset 29, -32
    .cfi_offset 30, -24
    mov    x29, sp
    .cfi_def_cfa_register 29
    str    x0, [x29,#24]
    ldr    wzr, [x19]
    ldr    wzr, [x19]
    b    .Label.LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V4
.Label.LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V4:
    ldp    x29, x30, [sp], #32
    .cfi_restore 29
    .cfi_restore 30
    .cfi_def_cfa 31, 0
    ret
.Label.LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V3:
.Label.LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V2:
    .cfi_endproc
.Label.end.LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V:
    .size    LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V, .-LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V
    .word 0xFFFFFFFF
    .word .Label.LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V4-.Label.LHelloWorld_3B_7Cmain_7C_28ALjava_2Flang_2FString_3B_29V1
    .type MCC_GCTIB__PTN_0, %object
    .data
    .align 3
    .global MCC_GCTIB__PTN_0
MCC_GCTIB__PTN_0:
    .long 0x0
    .long 0
    .section  .__muid_conststr,"aw",%progbits
//__muid_conststr_begin:
//__muid_conststr_end:
//    .section  .__muid_classmetadata,"aw",%progbits
//__muid_classmetadata_begin:
//__muid_classmetadata_end:
    .section  .__muid_itab,"aw",%progbits
//__muid_itab_begin:
//__muid_itab_end:
    .section  .__muid_vtab,"aw",%progbits
//__muid_vtab_begin:
//__muid_vtab_end:
    .section  .__muid_vtab_offset_tab,"aw",%progbits
//__muid_vtab_offset_tab_begin:
//__muid_vtab_offset_tab_end:
    .section  .__muid_field_offset_tab,"aw",%progbits
//__muid_field_offset_tab_begin:
//__muid_field_offset_tab_end:
    .section  .__muid_offset_value_table,"aw",%progbits
//__muid_offset_value_table_begin:
//__muid_offset_value_table_end:
    .section  .__muid_local_classinfo_tab,"aw",%progbits
//__muid_local_classinfo_tab_begin:
//__muid_local_classinfo_tab_end:
    .section  .__muid_superclass,"aw",%progbits
//__muid_superclass_begin:
//__muid_superclass_end:
    .hidden DW.ref.__mpl_personality_v0
    .weak DW.ref.__mpl_personality_v0
    .section .data.DW.ref.__mpl_personality_v0,"awG",%progbits,DW.ref.__mpl_personality_v0,comdat
    .align 3
    .type DW.ref.__mpl_personality_v0, %object
    .size DW.ref.__mpl_personality_v0,8
DW.ref.__mpl_personality_v0:
    .xword __mpl_personality_v0

可以看到Java中的main方法,只是进行了name mangling然后当做普通方法处理了,并没有我们喜闻乐见的main函数。

其实从这里可以知道,方舟编译器用的链接器应该是改写过的(mplcg命令行参数也有一个--maplelinker参数),至少也需要处理找Java的main函数这个问题(目前也没看到有对main函数进行标记)。

不能编译为可执行文件,但是也可以生成.so库文件,这个没有任何问题的,不过这个需要用上面提到的,自己写main.c调用。但是,C调用Java的方法的调用约定是怎么样的呢?这个也是个问题。

总之,我已经放弃用常规方式跑起来HelloWorld了,这是个很费劲的事,而这本来应该是方舟编译器做的事。

不过用几位大大的.mplt可以把生成.s这个流程完整跑下来了,把重点放在研究那几个Phase吧。

总结

  • 开源的东西太少了,只有一个IR是不行的;
  • 方舟编译器肯定不是PPT,整体是可以跑通的;
  • 需要等进一步的开源才能知道一些细节的东西(动态特性的处理、Runtime等等);

说实话,靠这样猜、看源码来摸清方舟的思路,是有点累的。

还是等进一步的开源吧。

https://www.zhihu.com/question/343431810

https://zhuanlan.zhihu.com/openarkcompiler

原文地址:https://www.cnblogs.com/ation/p/12050110.html

时间: 2024-10-29 18:06:37

方舟编译器源码过一遍流程的相关文章

HBase1.0.0源码分析之请求处理流程分析以Put操作为例(二)

HBase1.0.0源码分析之请求处理流程分析以Put操作为例(二) 1.通过mutate(put)操作,将单个put操作添加到缓冲操作中,这些缓冲操作其实就是Put的父类的一个List的集合.如下: private List<Row> writeAsyncBuffer = new LinkedList<>(); writeAsyncBuffer.add(m); 当writeAsyncBuffer满了之后或者是人为的调用backgroundFlushCommits操作促使缓冲池中的

openVswitch(OVS)源码分析之工作流程(哈希桶结构体的解释)

这篇blog是专门解决前篇openVswitch(OVS)源码分析之工作流程(哈希桶结构体的疑惑)中提到的哈希桶结构flex_array结构体成员变量含义的问题. 引用下前篇blog中分析讨论得到的flex_array结构体成员变量的含义结论: struct { int element_size; // 这是flex_array_part结构体存放的哈希头指针的大小 int total_nr_elements; // 这是全部flex_array_part结构体中的哈希头指针的总个数 int e

Spark SQL源码分析之核心流程

自从去年Spark Submit 2013 Michael Armbrust分享了他的Catalyst,到至今1年多了,Spark SQL的贡献者从几人到了几十人,而且发展速度异常迅猛,究其原因,个人认为有以下2点: 1.整合:将SQL类型的查询语言整合到 Spark 的核心RDD概念里.这样可以应用于多种任务,流处理,批处理,包括机器学习里都可以引入Sql. 2.效率:因为Shark受到hive的编程模型限制,无法再继续优化来适应Spark模型里. 前一段时间测试过Shark,并且对Spark

Linux学习之源码1:入口流程

有地方看到,启动流程是arch/arm/boot/compressed/head.s ----->调用arch/arm/boot/compressed/misc.c的decompress_kernel()函数解压内核.---->arch/arm/kernel/head-common.S初始化 ---->init/main.c的asmlinkage void __init start_kernel(void) 注意在arch/arm/kernel/smp.c文件中有一个启动多核处理器的函数

ThinkPHP源码阅读1-------访问流程

ThinkPHP访问流程在手册1.11系统流程里就有介绍,我阅读的ThinkPHP的版本是3.1.3,大家可以看下手册,基本的流程也有,现在就是详细去介绍一下ThinkPHP的访问流程.(调试模式下的,在部署模式下,会把中间的URL解析,文件加载之类的,都封装到一个文件里) 1.入口文件(index.php) 入口文件最常见得就是index.php,而在这里可以定义项目名称,路径,缓存文件存放路径之类的.最后要加载Thinkphp/ThinkPHP.php 指向下一个文件的地方是require

leveldb源码分析--插入删除流程

由于网络上对leveldb的分析文章都比较丰富,一些基础概念和模型都介绍得比较多,所以本人就不再对这些概念以专门的篇幅进行介绍,本文主要以代码流程注释的方式. 首先我们从db的插入和删除开始以对整个体系有一个感性的认识,首先看插入: Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) { WriteBatch batch; //leveldb中不管单个插入还是多个插入都是以Wri

MYC编译器源码之词法分析

前文  .NET框架源码解读之MYC编译器 和 MYC编译器源码分析之程序入口 分别讲解了 SSCLI 里示例编译器的架构和程序入口,本文接着分析它的词法分析部分的代码. 词法解析的工作都由Tok类处理,其构造函数接受一个Io对象做文件处理,下面是Tok构造函数的源码: public Tok(Io ihandle) { io = ihandle; // 初始化Token(字符归类)字典 InitHash(); // initialize the tokens hashtable // 读入文件的

Linux学习之源码2:start_kernel流程

一.X86的流程可以参考http://www.kerneltravel.net/kernel-book/第十三章%20启动系统/13.5.htm 二.arm的流程,在http://www.cnblogs.com/gangsaleisi/archive/2013/01/09/2851734.html基础上进行分析. 并且是在3.9.7版本上进行分析的,差别不是太大. 1.lockdep_init():lockdep哈希表初始化,lockdep是linux内核的一个调试模块,用来检查内核互斥机制尤其

Okhttp源码分析--基本使用流程分析

Okhttp源码分析--基本使用流程分析 一. 使用 同步请求 OkHttpClient okHttpClient=new OkHttpClient(); Request request=new Request.Builder() .get() .url("www.baidu.com") .build(); Call call =okHttpClient.newCall(request).execute(); 异步请求 OkHttpClient okHttpClient=new OkH