在深入探索 CUDA-Q 編譯器的奇妙世界之前,我想先寫一篇關於抽象語法樹 (AST) 重寫的簡短備忘錄。 抽象語法樹

有人可能會問:AST重寫和編譯有什麼不同?簡而言之,每次編譯都包含AST重寫,但並非每次AST重寫都算編譯。

此外,編譯是將程式碼從一種語言轉換為另一種語言的過程,而抽象語法樹(AST)重寫通常會在同一種語言內進行。

Python抽象語法樹(AST)API Link to heading

Python 提供了一個用於處理抽象語法樹 (AST) 的標準函式庫。 Python AST 中的節點大致分為四大類:

字面量或常數 Link to heading

例如 10、"hello"、True。

Constant(value=10)
Constant(value="hello")
Constant(value=True)

變數 Link to heading

例如 x、y、z。變數既可以以讀取(載入)模式訪問,也可以以寫入(儲存)模式存取。

Name(id='x', ctx=Load())
Name(id='y', ctx=Store())

表達式 Link to heading

例如 1 + 2、x + 2、x > y。表達式是 AST 節點,用於產生值。

BinOp(left=Constant(value=1), op=Add(), right=Constant(value=2))
BinOp(left=Name("x", Load()),op=Add(),right=Constant(value=2))
Compare(left=Name("x", Load()),ops=[Gt()],comparators=[Name(id='y', ctx=Load())])

聲明 Link to heading

例如 if、for、while、return、break、continue。語句是抽象語法樹 (AST) 節點,用於執行操作,並在 Python 程式碼中作為頂層或程式碼區塊層級的結構出現。

例如,以下程式碼:

if x > 1:
pass

相當於:

If(
test=Compare(left=Name("x", Load()),ops=[Gt()],comparators=[Constant(value=1)]),
body=[Pass()],
orelse=[]
)

具體範例:量子核中的條件語句 Link to heading

假設我們要將這段程式碼轉換成:

@kernel
def conditional_play(qubit: Qubit):
iq = qubit.ancilla.readout()
if iq.i > 0.5:
qubit.main.play("waveform")

進入

def kernelized_conditional_play(qubit: Qubit):
iq = qubit.ancilla.readout()
with cc._if(iq.i > 0.5):
qubit.main.play('waveform')

其理念是,iq 是一個運行時 Readout,它不應該由 Python 解釋器進行評估,而應該由量子控制器進行評估。為了實現這一點,需要建立一個 kernel 裝飾器:

class kernel:

def __init__(self, func):
self.func = func

# Uncompile wrapped function (convert it into a string)
source = self.decompile(func)

# Parse the string into an AST
tree = self.parse_to_ast(source)

# Transform the AST, converting the "if" into "with if_()"
tree = Transformer().visit(tree)

# Recompile the AST into a binary
binary = self.recompile(tree)

# Make a namespace for execution
namespace = func.__globals__.copy()
# This does not really executes the function, but rather creates
# a new function based on the modified AST
exec(binary, None, namespace)

# The new function is now available in the namespace
self.kernel  = namespace["kernelized_" + func.__name__]

def __call__(self, *args, **kwargs):
return self.kernel(*args, **kwargs)

decompile、parse_to_ast 和 recompile 只是對標準函式庫函數(inspect、[compile](https://docs.python.org/3/library/comptions.html)的簡單包裝。

def decompile(self, func):
return inspect.getsource(func.__code__)

def parse_to_ast(self, source: str):
return compile(source, filename="<generated>", mode="exec", flags=ast.PyCF_ONLY_AST, dont_inherit=True)

def recompile(self, tree):
return compile(tree, filename="<generated>", mode="exec", dont_inherit=True)

轉換器是 ast.NodeTransformer 的子類,它遍歷抽象語法樹 (AST) 並將其轉換為模型的生成器。這是一種非常通用的設計模式,可用於將任何 AST 轉換為任何其他 AST。


class Transformer(ast.NodeTransformer):
"""
This subclass traverses the AST of the user-written, decorated,
model specification and transforms it into a generator for the
model. Subclassing in this way is the idiomatic way to transform
an AST.

Specifically:

1. rewrite all `if` statements into `with cc._if()` blocks
2. rename the function to `kernelized_` + original function name
3. Remove the @kernel decorator to prevent from recusion
"""

def visit_If(self, node):
self.generic_visit(node)
modified_node = ast.With(
items=[
ast.withitem(
context_expr=ast.Call(
func=ast.Attribute(
value=ast.Name(id="cc", ctx=ast.Load()),
attr="_if",
ctx=ast.Load(),
),
args=[node.test],
keywords=[],
),
optional_vars=None,
)
],
body=node.body,
)

ast.copy_location(modified_node, node)
ast.fix_missing_locations(modified_node)
return modified_node

def visit_FunctionDef(self, node):
modified_node = node
# Rename the function to `kernelized_` + original function name
modified_node.name = "kernelized_" + node.name
# Remove the @kernel decorator to prevent from recusion
modified_node.decorator_list = []

# Copy the source location of the original node
ast.copy_location(modified_node, node)
ast.fix_missing_locations(modified_node)

# Do not forget to visit the children of the node
self.generic_visit(node)

return modified_node

題外話 Link to heading

引入「核心」裝飾器的概念來將 Python 語句轉換為 QCS ISA 可能會令人困惑。如果忘記應用裝飾器會怎樣?由於 Python 解釋器會執行求值操作,核心程式碼將無法運行,這可能會讓開發人員感到非常困惑。

但這個問題有很多解決方案。可以使用靜態分析工具(或程式碼檢查器)來檢查函數是否使用了 kernel 裝飾器。這種方法非常有效,尤其是在開始使用 LLM(生命週期管理)自動化程式碼重寫時。

上下文感知程式碼更改嵌入 (圖片來源:上下文感知程式碼變更嵌入)

# 結論

在這份簡短的備忘錄中,我展示瞭如何以非常簡單的方式重寫 Python 函數的抽象語法樹 (AST)。使用這種方法,編寫可在量子控制堆疊上執行的量子核心就變得容易得多,而無需使用繁瑣的 with _xxx 語法。

當然,有人可能會問,既然業界正在快速轉向基於LLVM的統一技術棧,並採用MLIR方言進行量子計算,那麼在Python中操作抽象語法樹(AST)還有什麼意義呢?的確如此,在接下來的備忘錄中,我將展示如何使用MLIR來實現相同的目標。

我還需要研究一下 M-Labs 的 NAC3 編譯器。與 CUDAQ 相比,NAC3 使用的是基於 Rust 的實作。甚至連 Python 程式碼也是透過 Rust 解析器轉換為抽象語法樹 (AST) 的。不過,至少在他們的 RISC-V 軟核中,程式碼產生是委託給 LLVM 的。

同時,這份備忘錄提供了一種有趣的學習 Python AST 的方式,這對於改進尚未使用 LLVM 的現有基於 Python 的量子電路編譯器非常有用!


# 參考