Skip to content

Support Python AST parser frontend - #1

Merged
ShangkunLi merged 3 commits into
mainfrom
support-ast-parser-pr
Jun 22, 2026
Merged

Support Python AST parser frontend#1
ShangkunLi merged 3 commits into
mainfrom
support-ast-parser-pr

Conversation

@ShangkunLi

Copy link
Copy Markdown
Collaborator

This PR supports the initial synapse Python frontend infrastructure.

It adds:

  • Minimal Python package configuration with editable install support
  • A source parser that captures ordinary Python functions and converts them to Python AST
  • A plain Python GEMM example used as the first frontend smoke case
  • Parser tests covering source capture, function metadata, and expected AST node kinds
  • GitHub Actions workflow for running pytest
  • Updated README with setup, example, and test instructions

This is intentionally limited to the first frontend milestone:

ordinary Python function
  -> source capture
  -> Python AST dump

Later PRs will build on this by resolving the Python AST into SYNAPSE compute IR and then lowering to Taskflow MLIR.

@ShangkunLi ShangkunLi self-assigned this Jun 18, 2026
@ShangkunLi
ShangkunLi requested review from guosran and tancheng and removed request for guosran and tancheng June 18, 2026 16:38
@tancheng
tancheng requested review from tancheng and yyan7223 and removed request for tancheng and yyan7223 June 18, 2026 17:23
@tancheng

Copy link
Copy Markdown

Can we remove the limitation of number of reviewers? It can only choose 1 reviewer for now.

@ShangkunLi

Copy link
Copy Markdown
Collaborator Author

Can we remove the limitation of number of reviewers? It can only choose 1 reviewer for now.

Just make this repo public. It only supports multiple reviewers for the public repo of a free account.

If we want to make it private while keeping multiple reviewers, we need a subscription.

@tancheng

Copy link
Copy Markdown

We need discuss or a design doc first. Would we go through static way (Mojo) or JiT way?

@ShangkunLi

Copy link
Copy Markdown
Collaborator Author

We need discuss or a design doc first. Would we go through static way (Mojo) or JiT way?

My suggestion is to make the initial SYNAPSE path static/AOT-first, while keeping the compiler pipeline reusable for a future JIT frontend.

The main reason is scope control. Our immediate goal is to define the programming model and IR boundary:

Python program
-> SYNAPSE AST through frontend
-> Taskflow MLIR
-> backend artifact (LLVM IR, NEURA IR, CIRCUIT IR, etc.)

For the first milestone, I think we should focus on making this pipeline deterministic, inspectable, and testable. This is especially important for the CPU + multi-CGRA SoC path and for future hardware-IR generation.

JIT can be added later as a wrapper around the same compiler pipeline. It would infer signature information from runtime arguments, call the compiler, cache the artifact, and launch it. But I would avoid making JIT the first implementation target, because that would force us to design runtime caching, launch semantics, and dynamic specialization before the core IR/compiler story is stable.

So my proposal for the design doc is:

  1. Define the static/AOT path first:
    Python Program
    -> SYNAPSE AST through frontend
    -> Taskflow MLIR
    -> backend artifact (LLVM IR, NEURA IR, CIRCUIT IR, etc.)

  2. Define the function signature:
    what shape/dtype/memory/layout information the compiler needs, and whether it is provided explicitly or inferred.

  3. Keep the core compiler API reusable:
    compile(fn_or_graph, signature, target)

  4. Treat JIT as a future wrapper:
    infer signature from runtime args,
    call the same compile pipeline,
    cache the artifact,
    then launch.

This lets us move forward with a clean static compiler first, while keeping a clear path to GPU/CGRA/ASIC JIT later.

tancheng
tancheng previously approved these changes Jun 21, 2026
Comment thread tests/frontend/test_parser.py
@tancheng

Copy link
Copy Markdown

We need discuss or a design doc first. Would we go through static way (Mojo) or JiT way?

My suggestion is to make the initial SYNAPSE path static/AOT-first, while keeping the compiler pipeline reusable for a future JIT frontend.

The main reason is scope control. Our immediate goal is to define the programming model and IR boundary:

Python program -> SYNAPSE AST through frontend -> Taskflow MLIR -> backend artifact (LLVM IR, NEURA IR, CIRCUIT IR, etc.)

For the first milestone, I think we should focus on making this pipeline deterministic, inspectable, and testable. This is especially important for the CPU + multi-CGRA SoC path and for future hardware-IR generation.

JIT can be added later as a wrapper around the same compiler pipeline. It would infer signature information from runtime arguments, call the compiler, cache the artifact, and launch it. But I would avoid making JIT the first implementation target, because that would force us to design runtime caching, launch semantics, and dynamic specialization before the core IR/compiler story is stable.

So my proposal for the design doc is:

  1. Define the static/AOT path first:
    Python Program
    -> SYNAPSE AST through frontend
    -> Taskflow MLIR
    -> backend artifact (LLVM IR, NEURA IR, CIRCUIT IR, etc.)
  2. Define the function signature:
    what shape/dtype/memory/layout information the compiler needs, and whether it is provided explicitly or inferred.
  3. Keep the core compiler API reusable:
    compile(fn_or_graph, signature, target)
  4. Treat JIT as a future wrapper:
    infer signature from runtime args,
    call the same compile pipeline,
    cache the artifact,
    then launch.

This lets us move forward with a clean static compiler first, while keeping a clear path to GPU/CGRA/ASIC JIT later.

Sounds good to me. I am wondering how is the ast determines the data type now though.

@ShangkunLi

Copy link
Copy Markdown
Collaborator Author

We need discuss or a design doc first. Would we go through static way (Mojo) or JiT way?

My suggestion is to make the initial SYNAPSE path static/AOT-first, while keeping the compiler pipeline reusable for a future JIT frontend.
The main reason is scope control. Our immediate goal is to define the programming model and IR boundary:
Python program -> SYNAPSE AST through frontend -> Taskflow MLIR -> backend artifact (LLVM IR, NEURA IR, CIRCUIT IR, etc.)
For the first milestone, I think we should focus on making this pipeline deterministic, inspectable, and testable. This is especially important for the CPU + multi-CGRA SoC path and for future hardware-IR generation.
JIT can be added later as a wrapper around the same compiler pipeline. It would infer signature information from runtime arguments, call the compiler, cache the artifact, and launch it. But I would avoid making JIT the first implementation target, because that would force us to design runtime caching, launch semantics, and dynamic specialization before the core IR/compiler story is stable.
So my proposal for the design doc is:

  1. Define the static/AOT path first:
    Python Program
    -> SYNAPSE AST through frontend
    -> Taskflow MLIR
    -> backend artifact (LLVM IR, NEURA IR, CIRCUIT IR, etc.)
  2. Define the function signature:
    what shape/dtype/memory/layout information the compiler needs, and whether it is provided explicitly or inferred.
  3. Keep the core compiler API reusable:
    compile(fn_or_graph, signature, target)
  4. Treat JIT as a future wrapper:
    infer signature from runtime args,
    call the same compile pipeline,
    cache the artifact,
    then launch.

This lets us move forward with a clean static compiler first, while keeping a clear path to GPU/CGRA/ASIC JIT later.

Sounds good to me. I am wondering how is the ast determines the data type now though.

At the current parser stage, the AST does not determine data types. It only captures syntax. Type information will come from a separate signature.

@tancheng

Copy link
Copy Markdown

At the current parser stage, the AST does not determine data types. It only captures syntax. Type information will come from a separate signature.

What do you mean by "signature"?

@ShangkunLi

Copy link
Copy Markdown
Collaborator Author

At the current parser stage, the AST does not determine data types. It only captures syntax. Type information will come from a separate signature.

What do you mean by "signature"?

By "signature", I mean the compiler-level description of the function boundary: the argument names, shapes, dtypes, memory/layout information, and possibly input/output roles.

At the current parser stage, for a plain Python function like:

def gemm(A, B, C):
    ...

the Python AST only tells us that the function has three arguments named A, B, and C. It does not tell us whether A/B/C are f32 arrays, i32 arrays, their shapes, or their memory layout.

So type information has to come from somewhere else. There are two possible ways:

  1. Use type annotations in the function definition, e.g. something like:
def gemm(
    A: f32[128, 128],
    B: f32[128, 128],
    C: f32[128, 128],
):
    ...

Then the frontend can parse these annotations and bind A/B/C to compiler types.

Allo mostly follows this style for its Python DSL: function argument annotations provide the initial dtype/shape bindings, and then its TypeInferer propagates types inside the function body.

  1. Keep the Python function unannotated, and pass the signature separately to the compiler, e.g.:
compile(
    gemm,
    signature={
        "A": memref((128, 128), "f32"),
        "B": memref((128, 128), "f32"),
        "C": memref((128, 128), "f32"),
    },
)

triton.compiler.compile follows this style for its Triton DSL:
https://github.com/triton-lang/triton/blob/1f097c85358be982f6dc41e86c7a3652fb708c82/python/triton/compiler/compiler.py#L226

For SYNAPSE, we need to decide which signature mechanism we want first.

@ShangkunLi
ShangkunLi merged commit 3ab7316 into main Jun 22, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants