Updated section on viper function parameters and return types to include ptr return types and more examples and other information

bixb922
2024-05-03 08:53:18 -04:00
parent c4ca8bb9d4
commit 4e0a487218

@@ -382,46 +382,44 @@ Note that since the array element length is 4 bytes, you have to multiply by 4 y
Be aware: Some architectures may reject ptr32 access of pointers that are not multiple of four. Accessing odd bytes will most probably crash the program, no way to trap that as an exception.
# Viper function parameters and return values
From the point of view of the caller, viper functions behave just like any other MicroPython functions. The workings of the viper variables is hidden from the caller.
For integer parameters, use the `int` or `uint` type hint to get automatic conversion to a viper int. The conversion is done internally by MicroPython using the `int()` or `uint()` cast operator respectively.
From the point of view of the caller, viper functions behave just like any other MicroPython functions. The workings of the viper variables is hidden from the caller. The viper data types are not visible outside the viper function.
For arrays and bytearrays, use the ptr32, ptr16 and ptr8 type hints in the function parameters to get fast access to the arrays.
The static analysis that MicroPython does, is viper function by viper function. No type hint information, nor the fact that they are viper functions is carried over from the analysis of one function to another.
If the function returns a viper variable, a return type hint must be supplied, example:
The call overhead for a viper function is substantially lower than call overhead for a undecorated function. For example, for a function with 5 parameters, the call processing time with viper may be 2 times faster than a undecorated function, including the time to convert to and from the viper data types.
## Viper function parameters
For integer parameters, use the `int` or `uint` type hint to get automatic conversion to a viper int. The conversion is done internally by MicroPython using the `int()` or `uint()` cast operator respectively:
```py
@micropython.viper
function returns_integer(param1:int)->int:
return 1
def my_function( x:int, b:uint ):
# now x and b are viper data type variables
....
```
The conversion of the return value back to `builtins.int` is done automatically.
If the value returned by the function is any other object, you do not need a type hint. You can, however, use `object` as return type hint:
For arrays and bytearrays, use the ptr32, ptr16 and ptr8 type hints in the function parameters to get fast access to the arrays. The cast from an array to a pointer is done automatically while processing the call, i.e. a ptr8(), ptr16() or ptr32() cast is applied automatically to the argument.
```py
@micropython.viper
def function_returns_object(x)->object:
return x
h = function_returns_object("a string")
assert isinstance(h,str)
h = function_returns_object((1,2,3))
assert isinstance(h,tuple)
h = function_returns_object([1,2,3])
assert isinstance(h,list)
def my_function( p:ptr32 ):
....
a = array.array("l", (0 for x in range(100)))
my_function( a )
```
Viper functions do not accept keyword arguments nor optional arguments.
Somewhere the docs state a maximum of 4 arguments for a viper function, that seems not to be a restriction anymore.
The static analysis that MicroPython does is viper function by viper function. No information is carried over from the analysis of one function to the other.
Somewhere the docs state that there is a maximum of 4 arguments for a viper function. That seems not to be a restriction anymore.
## Passing a viper variable to a called function
In a viper decorated function, you can certainly call another function. The called function can be @micropython.viper decorated, @micropython.native decorated or plain (undecorated), a bound or unbound method, there is no difference.
However, call overhead for a viper function is lower than call overhead for a undecorated function.
In a viper decorated function, you can certainly call another function. The called function can be `@micropython.viper` decorated, `@micropython.native` decorated or plain (undecorated), a bound or unbound method and you can use a generator (however: no await of an async function inside a viper function).
If you pass a viper variable as argument to a function, it gets converted to a `builtins.int` on the fly:
* A viper `int` is treated as signed.
* A `ptr32`, `ptr16`, `ptr8` and `uint` always leaves a positive result, no sign.
* A `ptr32`, `ptr16`, `ptr8` and `uint` always leave a positive result, no sign, but they are converted also to a `builtins.int` since there are no pointers nor unsigned ints outside viper functions.
```py
@micropython.viper
@@ -431,13 +429,13 @@ def viperfun():
y = uint(0xffffffff)
some_function(y) # some_function will get 0xffffffff == 4294967295
z = int(-1)
some_function(, z) # some_function will get a -1
some_function(z) # some_function will get a -1
ba = bytearray(10)
pba = ptr8(ba)
some_function(pba) # # # some_function will get a number like 1008145600, which is the address of ba, no sign
```
The rationale here is that the viper data types don't make sense outside the viper function, so they are converted to standard MicroPython int. The pointers don't carry information about the type, so they can't be cast back to an array.
The rationale here is that the viper data types don't make sense outside the viper function, so they are converted to standard MicroPython `builtins.int` when passed as parameters. The pointers don't carry information about the type, so they can't be cast back to an array. If you wish to use a returned pointer, you have to cast it back to a pointer explicitly in a viper function or use functions like machine.mem8(), machine.mem16() or machine.mem32().
A nice effect of this is that you can pass a pointer down to a viper function:
```py
@@ -453,9 +451,60 @@ def fun2( mypointer:ptr8 ):
x = mypointer[0]
```
A side effect of this behavior is that `type(viper_variable)` always returns class `builtins.int`
A side effect of this behavior is that `type(viper_variable)` always returns class `builtins.int`, because the viper variable is converted to a `builtins.int` during the call process.
Talking about detecting type: inside a viper function, `isinstance(viper_variable,int)` will give a compile-time error `NotImplementedError: conversion to object`, since `int` is a viper data type, not a MicroPython class. However, `isinstance(viper_variable, builtins.int)` will return `True` since the `viper_variable` will be converted to a MicroPython `builtins.int` automatically during the call process.
## Viper function return values
If the function returns a viper variable, a return type hint must be supplied, for example:
```py
@micropython.viper
function returns_integer(param1:int)->int:
return 1
```
The conversion of the return value back to `builtins.int` is done automatically.
You can return a pointer in a viper function, but you must add the return type hint as ->ptr8, ->ptr16 or ->ptr32. The pointer returned is converted to a `builtins.int` and it's value will be the memory address of the array. The addresses are always byte addresses. The function that uses that returned integer must cast it to a pointer of the correct type to make things work, for example:
```py
@micropython.viper
def function_returning_pointer()->ptr8:
ba = bytearray(10)
pointer_to_ba = ptr8(ba)
pointer_to_ba[0] = 123
# Return a pointer to a bytearray
return pointer_to_ba
@micropython.viper
def function_using_returned_pointer( ):
mypointer = ptr8(function_returning_pointer())
# mypointer is now pointing to the bytearray ba
x = int(mypointer[0])
print(f"x has the value 123: {x=}")
```
Returned pointers can also be used with `machine.mem8` for `ptr8`, `machine.mem16` for `ptr16` and `machine.mem32` for `ptr32` addresses. The `machine.mem` objects are certainly slower than viper pointer operations.
If the value returned by the function is any other object (i.e. if the value returned is not a viper data type), you do not need to specify a type hint. If you wish, you can use `->object` as return type hint, for example:
```py
@micropython.viper
# MicroPython object returned, no return type hint required
def function_returns_something(x):
if x > 10:
return (1,2,3)
if x > 0:
return True
if x < -10:
return None
return x
@micropython.viper
# ->object can be optionally used as return type hint
# for any MicroPython object (except viper data types)
def function_returns_object(x)->object:
return (1,2,3)
```
Talking about detecting type: `isinstance(viper_variable,int)` will give a compile-time error `NotImplementedError: conversion to object`, since `int` is a viper data type, not a MicroPython class. However, `isinstance(viper_variable, builtins.int)` will return `True` since the viper_variable has been converted to a MicroPython int.
# Other topics