Jolt 论文给出了更多的可分解表格,用于表达 RISC-V 指令的计算过程。
我们首先看一个简单的可分解表格 TEQ,这个表格用来判断 A=?B,如果 A 和 B 相等,那么这个计算返回 1,否则返回 0。下面是一个 2-bit 表格示例:
A00000000010101011010101011111111B00011011000110110001101100011011A=?B1000010000100001
然后我们分析一个 W-bit 长的表格如何分解。对于判等表格来说,我们可以把 W 位的表格拆分成 c 个 W/c-bit 子表格,这些子表格完全相等,因为高位低位运算互不干扰。我们先看子表格的定义:
EQ(W/c)(x,y)=i=0∏(W/c)−1(xi⋅yi+(1−xi)⋅(1−yi))
这个定义是说,如果 xi 和 yi 相等,那么 xi⋅yi+(1−xi)⋅(1−yi)=1,否则为 0。那么 EQ(W/c) 表格的定义就是把 W 位的表格拆分成 c 个 W/c 位的子表格,然后把这些子表格的结果相乘:
EQ(W)(X,Y)=i=0∏c−1EQ(W/c)(Xi,Yi)
这里,
X=X0∥X1∥…∥Xc−1,Y=Y0∥Y1∥…∥Yc−1
接下来我们看一个稍微复杂点的表格,用来计算 X<?Y,表格的索引值为 X∥Y,如果关系成立,那么表格项为 1,否则表格项为 0。
比较两个整数的一般算法是并行扫描两个整数 X, Y 的每一个二进制位。从最高位开始,当遇到第一个不同的位时,停下来比较,并输出结果。
我们引入一个辅助的表格 LTUi,它表示 X 与 Y 第一个不同的 bit 位于第 i 个位置(按照从低位到高位的顺序)
LTUi(W)(x,y)=(1−xi)⋅yi⋅EQ(W−i−1)(x>i,y>i)
例如,LTU1(4)(1101,1110)=1,因为 x1=0,y1=1,并且 EQ(2)(11,11)=1。而 LTU2(4)(1101,1110)=0,因为 x2=1,所以 (1−x2)=0。下面是所有 LTUi(4)(1101,1110) 的计算过程:
i=xiyiEQ(x>i,x>i)(1−xi)yiLTUi311100211100101111010000
利用多个辅助表格的求和,我们可以定义出 LTU(W) 表格:
LTU(W)(x,y)=i=0∑W−1LTUi(W)(x,y)
对上面的例子,我们可以计算 LTU 表格中位于 11011110 这个位置的项,LTU(4)(1101,1110)
LTU(4)(1101,1110)=j≥i∑LTUj(1101,1110)=0+0+1+0=1
假如 W=64,那么 LTU(W) 的尺寸将是 2128,理论上我们无法存储这么大的表格。我们需要把大表格拆分成若干小表格关于一个多项式 G(⋅) 的运算。
接下来,我们分析如何拆分这个表格。假设我们把 W 宽的 bits 拆分为 c 段,每一段为 W/c。操作数 x 和 y 按 c 段拆分后,表示如下:
x(W)=xc−1(W/c)∥xc−2(W/c)∥…∥x0(W/c),y(W)=yc−1(W/c)∥yc−2(W/c)∥…∥y0(W/c)
一个初步的想法是将这 c 组操作数分别输入到 c 个 LTU(W/c) 表格中,然后把这些表格的结果相加。但是这样的计算结果并不是我们想要的,因为这样的计算结果会有多个 1,而我们只需要其中一组出现 1,其余为零,这样我们最后求和的结果才是 1。因此,我们可以考虑从高位开始,当某一段的 LTU(W/c) 计算输出 1 时,我们要求从高位开始的比较过的 W/c 段都应该相等,否则就输出 0。
LTU(W)(x,y)=j=0∑c−1(LTU(W/c)(xj,yj)⋅k=j+1∏c−1EQ(W/c)(xk,yk))
如果要采用 Lasso 框架来证明关于 LTU(W)(x,y) 表格的 Lookup,我们需要定义下给出参数化的多项式 G(⋅):
G(LTU(W/c)[X0∥Y0],EQ(W/c)[X0∥Y0],LTU(W/c)[X1∥Y1],EQ(W/c)[X1∥Y1],…,LTU(W/c)[Xc−1∥Yc−1],EQ(W/c)[Xc−1∥Yc−1])
总共有 c 段,每一段需要两个表格,LTU(W/c) 和 EQ(W/c)
下面是一个不容易切分 bits 的表格,那就是移位运算,比如向左移位(Shift Left Logical, SLL),0011 << 02 = 1100
。移位运算会给表格的切分带来些麻烦,因为移位运算会将低位分段的部分 bits
移动到高位分段。
我们先考虑单个表格的 SLL 运算。下面的 SLLk(X) 表示把长度为 W 的位向量 X 向左移 k 位的运算:
SLLk(W)(X)=j=0∑W−k−12j+k⋅Xj
这个定义比较容易理解,对于 W 个 bits,我们只需要把低 k 位中的每一个 Xi,i∈[0,k),乘上 2k+i,然后对这个 k 个数进行求和。然后高位的 W−k 位会溢出,因此被直接抛弃。这个表格比较容易定义是,因为我们将移位的位数作为一个常数。
接下来,我们考虑左移的位数是一个变量 Y。这时候,我们可以利用一个选择子,来根据 Y 值来选择不同的 SLLk。
SLL(W)(X,Y)=k=0∑W−1EQ(W)(bits(k),Y)⋅SLLk(X)
其中 ∣Y∣=logW,因为 Y 的取值范围是 [0,W−1](在 RISC-V 的规范中,移位操作的位移为 logW)。
例如我们要计算 SLL(4)(1101,10),
k=0k=1k=2k=3EQ(4)(k,Y)EQ(4)(00,10)=0EQ(4)(01,10)=0EQ(4)(10,10)=1EQ(4)(11,10)=0SLLk(X)SLL0(1101)=1101SLL1(1101)=1010SLL2(1101)=0100SLL3(1101)=1010
等式可以展开为:
SLL(4)(1101,10)=EQ(4)(00,10)⋅SLL0(1101)+EQ(4)(01,10)⋅SLL1(1101)+EQ(4)(10,10)⋅SLL2(1101)+EQ(4)(11,10)⋅SLL3(1101)=EQ(4)(10,10)⋅SLL2(1101)=1⋅0100=0100
如果 W=64,我们需要把表格拆分为 c 个 W/c-bit 子表格。我们用一个例子(W=16,c=4,logW=4)来说明,0101 1100 1001 1010 << 0110
,这个移位操作是向左移 6 位,假如我们需要将这个表格拆分为 c=4 个子表格,每个子表格计算的位数为 W/c=4。
这样,每个子表格 SLL(4)(x,y) 长度为 2W/c+logW=24+4=28,每个表格项的位长为 W。那么这个移位操作相当于是 c=4 个移位运算结果的求和:例如我们给出这个子表格的表项示例:
x0000⋮0001⋮0001⋮1001y0000⋮0001⋮0101⋮0011SLL(4)(x,y)0000 0000 0000 0000⋮0000 0000 0000 0010⋮0000 0000 0010 0000⋮0000 0000 0100 1000
SLL(16)((0101110010011010)2,01102) 的计算过程看下表:
0101<<61100<<61001<<61010<<6SLL(16)(x,0110)=====01010100110111000000001000100000010010011000001000100000000000
这里红色标记的部分为溢出 (W=16) 部分。根据 SLL 的语义,我们要抛弃这部分的 bits。剩下的部分,我们把它们错位(乘上 2 的次幂 )相加。由于不同的分段溢出的 bits 数量不等,我们需要先给出一个定义,mi,k 来标记每一个分段中溢出的 bits 数量:
mi,k=min(W/c,max(0,k+(W/c)⋅(i+1)−W)),∀i∈[0,c),k∈[0,logW)
这里 k 为移位的位数,i 为分段的索引。然后 SLLi(W/c) 子表格的定义如下:
SLLi(W/c)(X,Y)=k=0∑W−1EQ(W)(bits(k),Y)⋅j=0∑W/c−mi,k−12j+k⋅Xj
最后我们给出 SLL(W/c) 的分解定义:
SLL(W/c)(Xc−1∥⋯∥X0, Y)=i=0∑c−12i⋅W/c⋅SLLiW/c(Xi,Y)
应用 Lasso 框架来实现移位运算,参数化的多项式 G(⋅) 可以依据上面的等式来定义。