Skip to content

LHS of Exo Statements #553

Open
Open
@SamirDroubi

Description

@SamirDroubi

Problem:

There are currently two Exo statements that have a LHS: reduce and assign. However, the LHS data is fused within the statement node:

stmt = Assign( sym name, type type, string? cast, expr* idx, expr rhs )
         | Reduce( sym name, type type, string? cast, expr* idx, expr rhs )
         | ...

Which leads to similar behavior of the equivalent cursors at the API:

class AssignCursor(StmtCursor):
    def name(self) -> str:
    def idx(self) -> ExprListCursor:
    def rhs(self) -> ExprCursor:

This generally leads to less-ergonomic code. Consider the following example:

get_symbol_dependencies(proc, cursor) # Gets all the symbols that some cursor depends on

@proc
def foo(a: f32[2], b: f32[2]):
    for i in seq(0, 2):
         a[i] = b[i]

# I want the symbol dependencies of the assign statement
get_symbol_dependencies(proc, assign) # Okay great I gave a cursor that points to the statement and got {'a', 'i', 'b'} back!

# I want the symbol dependencies of the LHS of the assign statement
# but no way for me to point to the LHS!
# I could write the following
{assign.name()} + get_symbol_dependencies(proc, assign.idx()) # Should give me {'a'} + {'i'} = {'a', 'i'}

# But that's less-ergonomic than writing:
get_symbol_dependencies(proc, assign.lhs()) # I get the result {'a', 'i'}
# This final version is clean, direct, and self-explanatory

Consider this other example:

def get_buffer_accesses(proc, cursor, name):
     """
     Returns a list of cursors to all accesses to the buffer named `name` in the subtree of `cursor`
     """
     cursors = get_cursors_in_subtree(proc, cursor)
     check = lambda c: isinstance(c, (ReadCursor, ReduceCursor, AssignCursor)) and c.name() == name 
     # ugh I had to specify three types
     return filter(check, cursors)

# Not only I had to specify three types which seems excessive, 
# I have also had to go into the mental effort of remembering all the types that can access a buffer.
# Alternatively, I would like to write something like the following:
      check = lambda c: isinstance(c, AccessCursor) and c.name() == name

Proposal:
I have a few options below, but these are just ideas I came up with; I am not really a big fan of any of them. I am sure this problem isn't related to Exo in particular and there is a more canonical way of dealing with this if anyone can provide pointers.

Option 1: Extend the expression type

module LoopIR {
    proc = ...
    fnarg  = ...

    stmt = Assign( sym name, type type, string? cast, expr* idx, expr rhs ) # Current
         | Reduce( sym name, type type, string? cast, expr* idx, expr rhs ) # Current
         Assign( expr lhs, string? cast, expr rhs ) # Proposal 
         | Reduce( expr lhs, string? cast, expr rhs ) # Proposal 
         | ....

    expr = Read( sym name, expr* idx ) # Current
                Access(sym, name, expr *idx, read bool, write bool) # Proposal 
         | ...

Advantages:

  1. Packages all types of accesses to buffer under one type

Disadvantages:

  1. This will require some dynamic checking to make sure that LHS is always an Access and not some other random expression.
  2. Expressions were side-effect free, but it might be confusing to have an expressions that can be written to?
  3. It is unclear what the implications of having access as a type of expression on scheduling operation:
    • It might actually make it clearer in some cases: e.g. bind_expr could potentially now bind a LHS.
    • In some other cases, it might not make sense to operate on a LHS which will require the scheduling op to reject LHS nodes

Option 2: Add a LHS type (just decoupling the LHS from the statements)

module LoopIR {
    proc = ...
    fnarg  = ...

    stmt = Assign( sym name, type type, string? cast, expr* idx, expr rhs ) # Current
         | Reduce( sym name, type type, string? cast, expr* idx, expr rhs ) # Current
         Assign( LHS lhs, string? cast, expr rhs ) # Proposal 
         | Reduce( LHS  lhs, string? cast, expr rhs ) # Proposal 
         | ....
    
    lhs = LHS(sym name, type type, expr *idx)  # Proposal 

Advantages:

  1. Leaves expressions untouched

Disadvantages:

  1. Feels hacky
  2. There are still two types that could access a buffer: LHS and Read

Metadata

Metadata

Assignees

No one assigned

    Labels

    C: APIsThe API exposed by the languageC: LanguageThe semantics of the languageS: Needs DiscussionThis needs discussion to decide if important to work

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions