Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
__pycache__/
*.py[cod]
*$py.class

.DS_Store
# Distribution / packaging
.Python
build/
Expand Down
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
- 既存コードへの余計な修正禁止。Set 単体で完結する API は `caten/isl/specs/set.py` 内で完結させる。UnionSet 依存 API は関数名だけ置いて保留可。自動生成手法は禁止。
- 進捗と作業計画を常に本ファイルに記録し更新すること(型ごとに完了状況や今後の順番を明記)。最新の計画がここに存在する状態を保つ。

## Polyhedral DSL Guidelines
- Prefer using Mixin operator overloads (e.g., `A | B` instead of `A.union(B)`) for cleaner code in user scripts and DSL implementations.

## 作業計画と進捗 (2025-11-16)
直近のギャップ集計: `docs/ISL_missing_apis.md`(2025-11-16 再生成、欠落API 2047件)。map 残 2 件(tuple_name系シンボル未提供のみ、libisl非存在)。
優先順とステータス(✅完了 / 🚧着手中 / ⏳未着手)
Expand Down
2 changes: 2 additions & 0 deletions caten/polyhedral/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .schedule_tree.filter import filter
from .schedule_tree.mark import mark
from .schedule_tree.sequence import sequence
from .stmt import stmt

__all__ = [
"domain",
Expand All @@ -16,4 +17,5 @@
"schedule",
"compute_flow",
"to_c",
"stmt",
]
3 changes: 2 additions & 1 deletion caten/polyhedral/context.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import contextvars
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Any, Optional

if TYPE_CHECKING:
import caten.isl as I
Expand All @@ -10,6 +10,7 @@ class ScheduleBuilder:
def __init__(self) -> None:
self.current_node: Optional["I.ScheduleNode"] = None
self.schedule: Optional["I.Schedule"] = None
self.current_domain: Any = None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The type hint for current_domain is Any, which is not very specific. To improve type safety and code clarity, consider using a more specific type. A forward reference Optional["domain"] would be more appropriate here, assuming it refers to the domain class from caten.polyhedral.schedule_tree.domain. You will need to add the import under a TYPE_CHECKING block to avoid circular dependencies.

Suggested change
self.current_domain: Any = None
self.current_domain: Optional["domain"] = None


_builder_ctx: contextvars.ContextVar[Optional[ScheduleBuilder]] = contextvars.ContextVar("schedule_builder", default=None)

Expand Down
65 changes: 65 additions & 0 deletions caten/polyhedral/poly_schedule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from typing import Optional

import caten.isl as I
from caten.polyhedral.analysis import compute_dependence_relation, schedule_is_legal_p
from caten.polyhedral.codegen import to_c


class PolyhedralSchedule:
def __init__(self, schedule: "I.Schedule", reads: Optional["I.UnionMap"] = None, writes: Optional["I.UnionMap"] = None) -> None:
self.isl_schedule = schedule
self.reads = reads
self.writes = writes
self.raw_dep: Optional["I.UnionMap"] = None
self.total_dep: Optional["I.UnionMap"] = None

if reads and writes:
self.compute_dependencies()

def compute_dependencies(self) -> None:
if not self.reads or not self.writes:
return
total, raw, waw, war = compute_dependence_relation(self.reads, self.writes, self.isl_schedule)
self.raw_dep = raw
self.total_dep = total

def is_legal(self) -> bool:
# Check legality against RAW dependencies
if self.raw_dep:
return schedule_is_legal_p(self.isl_schedule, self.raw_dep)
return True
Comment on lines +26 to +30

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The is_legal method currently checks for schedule legality only against Read-After-Write (RAW) dependencies. For a schedule to be truly legal, it must respect all data dependencies, including Write-After-Write (WAW) and Write-After-Read (WAR). The compute_dependencies method already calculates self.total_dep, which is the union of all dependencies. You should use self.total_dep for a correct legality check.

Suggested change
def is_legal(self) -> bool:
# Check legality against RAW dependencies
if self.raw_dep:
return schedule_is_legal_p(self.isl_schedule, self.raw_dep)
return True
def is_legal(self) -> bool:
# Check legality against all dependencies
if self.total_dep:
return schedule_is_legal_p(self.isl_schedule, self.total_dep)
return True


def get_root(self) -> "I.ScheduleNode":
return self.isl_schedule.get_root()

def to_c(self) -> str:
return to_c(self.isl_schedule)

def __str__(self) -> str:
return str(self.isl_schedule)

def update(self, node: "I.ScheduleNode") -> None:
"""Update the internal schedule from a modified schedule node."""
self.isl_schedule = node.get_schedule()

def sequence(self, other: "PolyhedralSchedule") -> "PolyhedralSchedule":
"""Combine this schedule with another using isl_schedule_sequence."""
new_sched = self.isl_schedule.sequence(other.isl_schedule)

new_reads = None
if self.reads and other.reads:
new_reads = self.reads.union(other.reads)
elif self.reads:
new_reads = self.reads
elif other.reads:
new_reads = other.reads

new_writes = None
if self.writes and other.writes:
new_writes = self.writes.union(other.writes)
elif self.writes:
new_writes = self.writes
elif other.writes:
new_writes = other.writes
Comment on lines +49 to +63

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for combining reads and writes maps is verbose and can be simplified. Additionally, the newly added guideline in AGENTS.md recommends using operator overloads like | instead of the .union() method for better readability. This can be refactored to be more concise and adhere to the new guideline.

        new_reads = self.reads | other.reads if self.reads and other.reads else self.reads or other.reads
        new_writes = self.writes | other.writes if self.writes and other.writes else self.writes or other.writes


return PolyhedralSchedule(new_sched, reads=new_reads, writes=new_writes)
30 changes: 27 additions & 3 deletions caten/polyhedral/schedule_tree/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,14 +157,38 @@ def __enter__(self) -> "domain":
# We set current_node to the child of Domain (the Leaf)
builder.current_node = sched.get_root().child(0)

self._prev_domain = builder.current_domain
builder.current_domain = self

return self

def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
builder = get_builder()
if builder.current_node:
self.schedule = builder.current_node.get_schedule()
builder.current_node = None
builder.current_domain = self._prev_domain

def finalize(self, read: Optional[Union[str, "I.UnionMap"]] = None, write: Optional[Union[str, "I.UnionMap"]] = None) -> Any:
from ..poly_schedule import PolyhedralSchedule

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The import of PolyhedralSchedule is performed inside the finalize method. While this can be a strategy to avoid circular imports, it doesn't seem necessary here as poly_schedule.py does not import domain.py. Moving this import to the top of the file would follow standard Python conventions and improve readability.


if self.schedule is None:
if self.domain_set:
uset = self.domain_set
if isinstance(uset, str):
uset = I.UnionSet(uset)
elif isinstance(uset, I.Set):
uset = I.UnionSet.from_set(uset)
self.schedule = I.Schedule.from_domain(uset)
Comment on lines +176 to +182

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This block of code, which converts a string or I.Set into an I.UnionSet, is duplicated in caten/polyhedral/stmt.py (lines 26-29). To adhere to the DRY (Don't Repeat Yourself) principle, this logic should be extracted into a shared helper function or a method.

else:
raise RuntimeError("No domain set for schedule.")

r = read if read else self.reads_map
if isinstance(r, str):
r = I.UnionMap(r)

w = write if write else self.writes_map
if isinstance(w, str):
w = I.UnionMap(w)

def finalize(self, op_context: Any = None) -> Any:
# Placeholder for Kernel creation logic
return self.schedule
return PolyhedralSchedule(self.schedule, reads=r, writes=w)
77 changes: 77 additions & 0 deletions caten/polyhedral/stmt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import re
from typing import List, Optional, Tuple

import caten.isl as I

from .context import get_builder


def stmt(expr: str) -> None:
dom = get_builder().current_domain
if dom is None:
raise RuntimeError("stmt() must be used within a P.domain context")

if "=" not in expr:
raise ValueError(f"Invalid statement expression (must contain assignment '='): {expr}")

lhs_str, rhs_str = expr.split("=", 1)

def extract_accesses(s: str) -> List[Tuple[str, str]]:
return re.findall(r"([a-zA-Z_]\w*)\s*\[(.*?)\]", s)

writes = extract_accesses(lhs_str)
reads = extract_accesses(rhs_str)

uset = dom.domain_set
if isinstance(uset, str):
uset = I.UnionSet(uset)
elif isinstance(uset, I.Set):
uset = I.UnionSet.from_set(uset)

new_reads: Optional["I.UnionMap"] = None
new_writes: Optional["I.UnionMap"] = None

def process_set(s: "I.Set") -> None:
nonlocal new_reads, new_writes
s_str = str(s)
if ":" in s_str:
tuple_part = s_str.split(":")[0].strip()
if tuple_part.startswith("{"):
tuple_part = tuple_part[1:].strip()
else:
tuple_part = s_str.strip()
if tuple_part.startswith("{") and tuple_part.endswith("}"):
tuple_part = tuple_part[1:-1].strip()
Comment on lines +36 to +44

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Extracting the tuple part by parsing the string representation of an isl.Set is fragile. This implementation relies on the specific format of isl.Set.__str__, which could change in future versions of the underlying library, breaking your code. If the caten.isl API provides a more robust way to achieve this (e.g., by inspecting the set's space and dimensions programmatically), it would be better to use that. If not, it would be good to add a comment here highlighting the fragility of this approach.


for (name, indices) in writes:
m_str = f"{{ {tuple_part} -> {name}[{indices}] }}"
m = I.UnionMap(m_str)
if new_writes is None:
new_writes = m
else:
new_writes = new_writes.union(m)

for (name, indices) in reads:
m_str = f"{{ {tuple_part} -> {name}[{indices}] }}"
m = I.UnionMap(m_str)
if new_reads is None:
new_reads = m
else:
new_reads = new_reads.union(m)

set_list = uset.get_set_list()
n = set_list.n_set()
for i in range(n):
process_set(set_list.get_at(i))

if new_reads:
if dom.reads_map:
dom.reads_map = dom.reads_map.union(new_reads)
else:
dom.reads_map = new_reads

if new_writes:
if dom.writes_map:
dom.writes_map = dom.writes_map.union(new_writes)
else:
dom.writes_map = new_writes
Comment on lines +67 to +77

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for updating dom.reads_map and dom.writes_map is duplicated. This can be refactored into a small helper function to reduce repetition. Also, according to the new guideline in AGENTS.md, you should prefer the | operator over the .union() method.

Suggested change
if new_reads:
if dom.reads_map:
dom.reads_map = dom.reads_map.union(new_reads)
else:
dom.reads_map = new_reads
if new_writes:
if dom.writes_map:
dom.writes_map = dom.writes_map.union(new_writes)
else:
dom.writes_map = new_writes
if new_reads:
dom.reads_map = dom.reads_map | new_reads if dom.reads_map else new_reads
if new_writes:
dom.writes_map = dom.writes_map | new_writes if dom.writes_map else new_writes

Loading