如何实现数组 - llvm

时间:2018-06-13 16:48:55

标签: compiler-construction llvm llvm-ir llvm-c++-api

我正在研究使用llvm c ++ api编写的玩具语言,我正在尝试实现数组。我尝试了几种不同的东西,但都没有效果很好。

这就是我的目标:

  • 类似于数组的类型(结构,向量或数组可以工作)
  • 可以传递给函数
  • 并从函数返回
  • 可以拥有无​​限嵌套的数组(例如[8 x [8 x [8 x [...]
  • 可以重新分配
  • 阵列都是相同的类型
  • 阵列在创建时指定的长度有限。

理想情况下,它们会像swift中的数组一样。

当前解决方案

目前我正在使用basic array type。这将检查所有框,除了可以将一维数组传递给函数并从函数返回时,必须通过指针传递多维(嵌套)数组。这意味着我不仅必须将返回的指针bitcast到正确的类型,然后遍历每个元素并将其存储在适当的位置。很快,这变得非常混乱,但也很慢。

载体

据我所知,你不能有嵌套的向量。例如。这不起作用:

<8 x <8 x i32>>

使用malloc

最后,我尝试使用malloc分配适当的空间,然后只使用指针。这与我目前的解决方案有许多相同的问题。

像C ++这样的东西?

采用以下示例:

  %1 = alloca [5 x i32], align 16
  %2 = alloca i32*, align 8
  %3 = getelementptr inbounds [5 x i32], [5 x i32]* %1, i32 0, i32 0
  %4 = call i32* @_Z8getArrayPi(i32* %3)
  store i32* %4, i32** %2, align 8

无法将指针%4存储到%1中,因此必须创建新的分配。否则(使用嵌套数组)需要复制每个元素,并且会出现与当前解决方案相同的问题。另外,理想情况下编译器会处理所有指针,因此它看起来就像是该语言用户的数组。

问题

如果不清楚 - 我的问题是如何实现数组,以便我可以完成上面列出的所有事情(无需复制每个元素)。

1 个答案:

答案 0 :(得分:2)

序言

我认为alloca是正确的方法,如果它们将是固定大小的,但你必须编写相当多的样板来实现保证(C ++样式)省略来优化函数返回。

现在问题在于所有权语义,如果你想完美地完成它,你需要确定它们的生命周期并确定它们是否需要堆分配或者只能逃避堆栈分配。但是现在让我们忽略优化。 Swift / ARC实现在幕后非常混乱,并且使用了大量优化,因此您不一定要对其进行建模,更不用说Swift / ObjC ARC代码之间的差距和“非托管”代码。

简单的解决方案“像Swift”

如果你不想处理它,假设你知道函数的所有可能的退出路径,你可以在堆上使用某种形式的运行时分配你的数组,并提供侵入式引用计数语义。这是Swift使用的模型(顶部有大量优化) - 您堆分配每个数组并实现一个控制块来跟踪数组的引用计数和大小(例如,如果您想要动态边界检查)。在这种情况下,您将使用简单的pass-by-reference语义,如下所示:

  • 数组可以通过对堆对象的引用来表示。
  • 当你输入一个函数时,如果你的特殊数组类型(你可以注释LLVM的Value来跟踪那些)被传入,你就会提高引用数量。
  • 在终止基本块中,您将发出对运行时函数的调用,该函数将为每个被跟踪对象减少函数内的跟踪对象的引用计数(在那里进行优化的大量空间)。
  • 确保上面有一个例外,当从函数返回的数组不会减少引用计数以确保它在返回时保持不变时,您必须确保在弹出调用帧时跟踪这些(为简单起见)我们假设您只能返回一个引用。)
  • 当您无法静态地确定数组的生命周期时,最难的部分是保留/释放语义,这是引用计数变得有用的地方。每次将引用存储在任何类型的数据结构中时,都需要保留运行时(增加引用计数)。在销毁所有者后,其中的任何refcounted对象都会被释放(如果没有refs,则会减少refcount&amp; release)。
  • 当你的数组引用被存储时,它被保留(refcount增加),这假设存储对象,为了简化,存储对其他数组的引用的数组具有静态(在编译器中更难实现)或动态意识它存储的对象类型,并且它知道如果它存储引用计数对象,在它被销毁时它需要释放所有引用。

这几乎是它的基础,是一个非常非常愚蠢的ARC工作方式。现在需要考虑的事情:

  • 非线性控制流程(线程,exeptions,coroutines,closures)。放松是特别有趣的。
  • 在您的语言中使用每个非平凡类型基本上具有相似的特征(refcounted对象),只要您不允许原始指针,这会使事情变得更容易。你可以这样处理闭包(事实上这就是块运行时的作用)。
  • 当您通过非托管代码开始使用指针或封送托管对象时,一切都会变得一团糟。
  • 非拥有引用(指针)需要特别注意,具体取决于您选择设计的方式。
  • 复制对象。

我建议从一个refcounted对象开始,该对象将成为您所用语言管理的所有内容的基础,包括您的数组类型,并随身携带某种类型的信息。 (就像NSObjectlibobjc4的一个复杂得多的版本。