-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Viper: problem updating nonlocal variables #8086
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
Comments
Thanks for the report. I can reproduce the issue. Closures are almost working in viper. But it's storing a raw integer value instead of an integer object when assigning to the closed-over variable. As a workaround you can explicitly create the integer-object (a small int) via: def foo():
x : int = 0
@micropython.viper
def inner() -> int:
nonlocal x
q : int = int(x)
q += 1
x = q << 1 | 1 # <- workaround!
return int(x)
return inner
bar = foo()
bar() # 1
bar() # 2 |
As I see it, this would make it possible to program generators with @micropython.viper. |
In Python everything is an object, and the system needs to know how to extract the object type. For integer objects the low bit is set. So, the integer value Viper works with raw integer values, not integer objects, so to communicate with Python code it must convert between objects and raw values. The bug here is that it's not done automatically. |
It's perhaps worth noting that if the bug is fixed the workround will produce unexpected results. I opted for the integer array approach to avoid this hazard. Array indexing is fast and there doesn't seem to be a performance penalty. |
I know this is an old issue, but there is still another solution which I think is a bit more readable:
Tested on MicroPython v1.23.0-preview.47.g16c6bc47c on 2024-01-17; Generic ESP32S3 module with Octal-SPIRAM with ESP32S3. |
Another idea does not work but yields a new error:
|
@bixb922 Please can you explain how this works? :) |
I'm still in the process of finding out how this all works. I'll ramble a bit about this topic here :-), please bear with me for the long post. Also: viper has improved since the OP. I could not reproduce all examples given. The viper int data type and the MicroPython int are two very different beasts. The viper data type refers to a raw memory location on the stack and is not an object, just a raw variable (much like a C stack variable). The MicroPython int data type has an object descriptor, and may be (AFAIK) allocated on the stack for local variables and on the heap for global variables and instance variables (is this last statement correct?). Inside a viper function, int always means viper int. If you want to access the MicroPython int, you can import builtins and use builtins.int. So I think it's best to refer to these two data types as "viper int" and "builtins.int". The viper code emitter (which is the same as the native code emitter plus the viper data types) detects at compile-time which local variables are of one of the viper types. It does this by taking the first statement where the variable is created (assigned). If it gets assigned a viper int, then it is flagged as such, if not, it's an "object". This is why viper ints only can local variables (edit: nonlocal being a special case). Once a variable is detected as "viper int", the type cannot be changed and trying to do this will cause the compile-time error "ViperTypeError: local 'x' has type 'int' but source is 'object'". Due to these differences, interoperability between these two int data types cannot be entirely transparent. These are cases where the transition has to be explict, with a type cast or explicit conversion:
These are cases where the transition seems to be automatic:
For the closure example, the simplest code would be: def nonlocal_test_inner_viper():
x = 1
@micropython.viper
def inner_function():
nonlocal x
# Next line gives "ViperTypeError: can't do binary op between 'object' and 'int'"
x = x + 1
inner_function()
return inner_function The message means: "x is an object and 1 is a viper int, can't add them" (At first I read this the other way around and got stumped). When you convert the I am writing all this stuff down to a document to be put soon somewhere on github. I'd love to get some feedback. |
Thanks for that, very informative.
This is the MicroPython object model - I suggest you read this. |
Thanks!! |
Here is still another twist. And yes, there is a problem somewhere, but a bit different from the OP: def nonlocal_test():
x = 1
y = None
z = None
@micropython.viper
def internal_viper():
nonlocal x, y, z
# Operation of viper int with nonlocal builtins.int needs
# converting all terms to builtins.int, if not we get a ViperTypeError
x = x + builtins.int(1)
viperx:int = 111
# The next statement does *not* convert from viper int to builtins.int
y = viperx
# This works:
z = builtins.int(viperx*2)
internal_viper()
print(f"nonlocal test {x=}, expected 2")
print(f"nonlocal test {y=}, expected 222")
print(f"nonlocal test {z=}, expected 111")
nonlocal_test() The output is:
With the way objects are stored now in mind, it seems that So nonlocal int operations only work if the nonlocal was previously created as int. Other data types seem not to be affected. |
As this is very interesting, moving back to discussions probably is best here, as new posts in issues are mailed to all core developers and thus make a lot of noise. I appreciate the discussion and think it would be great if you/we could copy your/our contribution over to the original discussion too, for others searching viper. No offence meant, just my feeling:-) |
I am trying to write a Viper function which retains state between calls, preferably using a closure. The following produces an unexpected result (all testing on a Pyboard 1.1 running V1.17):
Oddly similar code using a global works:
Attempting this with a class produces a type error
To date my "best" solution is to use a closure with an integer array to contain state:
It would be good if
nonlocal
worked as expected and if there were a solution to the problem of accessing integer bound variables.Incidentally the speedup using Viper in my actual application is phenomenal :)
The text was updated successfully, but these errors were encountered: