-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multistage programming #618
Conversation
Python is the metalanguage and the Exo base language has not been changed in any breaking way Python unquote blocks are evaluated using exec. Each unquote behaves like a new scope in python. Both unquote and quote blocks have parent scopes that look at the nearest enclosing block of the same type. For example:
When this procedure is evaluated, it will print "1", and then result in a procedure that is equivalent to:
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #618 +/- ##
==========================================
+ Coverage 88.07% 88.23% +0.15%
==========================================
Files 92 93 +1
Lines 21303 21625 +322
==========================================
+ Hits 18763 19080 +317
- Misses 2540 2545 +5 ☔ View full report in Codecov by Sentry. |
Some relatively minor things are still broken:
|
571006e
to
cf2593a
Compare
This branch is only a subset of the desired features for metaprogramming. In future PRs, consider:
|
Thanks Kenneth! I will review this PR more carefully, but meanwhile can you write a documentation for metaprogramming features and limitations? (see #729 for sample documentation for other features). |
y: f32 | ||
y = sin(x) | ||
|
||
|
||
def test_sin2(): | ||
@proc | ||
def sin(x: f32): | ||
def sin_proc(x: f32): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this change necessary? Is it because the procedure name is conflicting with the extern name?
I think procedure definitions should be able to overwrite whatever other global and local variables out there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change is necessary because the sin function call inside the proc is parsed as an implicit unquote in the metalanguage, which means it is looked up like a normal python variable named sin would be looked up at that point. But, if the proc is named sin, it is registered as a local variable in the scope which shadows the sin extern that is imported as a global variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmm I see what you mean but this seems quite cryptic.... It's way too opaque to users... How bad is it going to be if we don't support implicit unquotes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the code becomes pretty cluttered if there are no implicit unquotes because you will have to put braces around every variable you reference from the metalanguage.
Also, I think it should be expected that if you name a proc "sin" it will shadow any other "sin" inside its scope. In most other languages, you can refer to the function you are currently inside by its name (e.g. recursion), so it doesn't seem very surprising.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a side note, if you try compiling this exo:
@proc
def sin(x: f32):
x = sin(x)
the result is:
#include <math.h>
// sin(
// x : f32 @DRAM
// )
void sin( void *ctxt, float* x ) {
*x = sin((float)*x);
}
which is actually invalid C because the sin
inside the function will refer to the proc sin
not the function from math.h
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see okay, this makes sense. Can you add a test case of declaring a proc named sin
that shadows the extern sin, and call the proc sin
from another procedure foo
, and see if that works as expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
complete
with exo: | ||
a = {bar(~{a + 1})} | ||
|
||
c_file, _ = compile_procs_to_strings([foo], "test.h") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you compiling the proc into C for every test? I think you should also sanity-check the ExoIR printing.
Can modify the asserts to sth like:
assert str(foo) + c_file == golden
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, you know that you can update all the golden tests by running pytest tests/test_metaprogramming.py --update-golden
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
complete
@@ -15,6 +15,9 @@ | |||
from ..core.prelude import * | |||
from ..core.extern import Extern | |||
|
|||
from typing import Any, Callable, Union, NoReturn, Optional | |||
import copy | |||
from dataclasses import dataclass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are those imports? Are they necessary? If so, you should add them as dependencies to requirements.txt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The imports are parts of the python standard library just to give some type annotations and reduce some clutter
@@ -35,54 +38,106 @@ def str_to_mem(name): | |||
return getattr(sys.modules[__name__], name) | |||
|
|||
|
|||
@dataclass | |||
class SourceInfo: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you need a wrapper for SrcInfo
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to make a wrapper because before we just passed a getsrcinfo function into Parser, which I thought was confusing because then you don't know where the behavior of getsrcinfo is defined. I think it is neater if it is wrapped in an object with a defined get_src_info method that you can jump to.
srcinfo = self.getsrcinfo(e) | ||
else: | ||
srcinfo = self.getsrcinfo(node) | ||
if isinstance(e, pyast.Slice): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add tests to cover the lines Codecov is reporting?
|
||
|
||
@dataclass | ||
class QuoteReplacer(pyast.NodeTransformer): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should definitely add docstrings to each Class and functions you defined
3082687
to
00b0d51
Compare
if isinstance(unquoted, str) and unquoted in _prim_types: | ||
return _prim_types[unquoted] | ||
else: | ||
self.err(node, "Unquote computation did not yield valid type") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need else: on if len(unquote_eval_result) == 1:
and raise error
) | ||
else: | ||
return tuple() | ||
|
||
def eval_expr(self, expr): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add tests for
mems=[DRAM, ...]
x : f32 @ mems[0]
826bd6d
to
91c62b2
Compare
admittedly the docs could use better motivated examples, but I found it difficult to include any without overly complicating what should also serve as a reference that is easily skimmable. I think the first limitation (lack of quote outside of exo procs) also holds metaprogramming back by a lot more than i originally anticipated, but we should try to merge the existing features first and then hopefully reduce the limitations later. I considered documenting how metaprogramming works internally in pyparser, but besides my existing comments, it doesn't really seem like there is a space for a more in-depth explanation. |
Implicit quote/unquote lookup now progressively looks up enclosing scopes without discerning whether it is a Python or Exo scope. Thus, Python variables can shadow Exo variables if the enclosing Python scope is closer to the reference. I found that this change is easier to generalize if I also allow implicit unquotes on the left hand side of assignments. For example, in this program: @proc
def foo(a: i32):
with python:
with exo:
a += 1
@proc
def foo(a: i32, b: i32):
with python:
syms = [a, b]
for sym in syms:
with exo:
sym += 1 |
No description provided.