Skip to content

feat(gnovm): interrealm spec; cross/crossing; readonly; defer/recover/panic fixes; blank identifier fixes.... #4060

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

Merged
merged 61 commits into from
Apr 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
8f2adb2
added Readonly...
jaekwon Mar 27, 2025
222122c
...
jaekwon Mar 27, 2025
e4d39cd
Readonly is a property of TypedValue, rather than ReadonlyValue wrapper
jaekwon Mar 28, 2025
b59c858
fix tests
jaekwon Mar 28, 2025
e93bbd3
Machine.IsReadonly() also checks realm mismatch
jaekwon Mar 30, 2025
663bfc3
nil interface is undefined
jaekwon Mar 31, 2025
8f932fe
fix IsReadonly to use IsImmutable; half of convert check done
jaekwon Mar 31, 2025
a007c95
enable second opconvert check
jaekwon Mar 31, 2025
a7abc9d
...
jaekwon Mar 31, 2025
84e34c7
...
jaekwon Mar 31, 2025
7e99a91
first commit of interrealm_withswitch
jaekwon Apr 1, 2025
489cc84
fix zrealm_crossrealm11.gno test
jaekwon Apr 2, 2025
75a4625
intermediate
jaekwon Apr 2, 2025
d8db4b3
fix tests...
jaekwon Apr 3, 2025
e0459b9
fix gnovm tests
jaekwon Apr 3, 2025
dd30de5
...
jaekwon Apr 3, 2025
e7d65cb
heapitems_for_3693
jaekwon Apr 5, 2025
f182d38
update tests
jaekwon Apr 5, 2025
6b8b41d
fix heap return issues
jaekwon Apr 5, 2025
c1d3652
...
jaekwon Apr 5, 2025
afe5578
cleanup and fixes
jaekwon Apr 7, 2025
44f0a0b
heap item all package vars
jaekwon Apr 7, 2025
872b6a5
update golden tests
jaekwon Apr 7, 2025
f026885
a funcvalue is an object; a func closure has no parent; find heap cap…
jaekwon Apr 8, 2025
b6dc789
update golden tests
jaekwon Apr 8, 2025
cdef2e3
fix one example test
jaekwon Apr 8, 2025
425b919
update golden tests
jaekwon Apr 8, 2025
636b7c3
fix script tests
jaekwon Apr 8, 2025
66213d4
add gno-memory-model
jaekwon Apr 9, 2025
95a06f4
replace IsUndefined(); remove defaultValue() for safety
jaekwon Apr 9, 2025
768cda4
Add interrealm spec
jaekwon Apr 9, 2025
d772663
add note on closures
jaekwon Apr 9, 2025
22506cd
update gno-interrealm.gno
jaekwon Apr 10, 2025
b2d0c97
replace callerat with current/previous realm()
jaekwon Apr 10, 2025
19c159e
borrrow realm -> switchrealm() test
jaekwon Apr 10, 2025
6b6ecee
borrow test; fix tests
jaekwon Apr 10, 2025
fff210b
fix #4019
jaekwon Apr 10, 2025
d9f5925
fix stdlibs/std tests
jaekwon Apr 11, 2025
af10d50
Create exploit.gno
piux2 Apr 11, 2025
274e397
Create realm_exploiter.gno
piux2 Apr 11, 2025
b912d9f
fix blank identifier multiple name valuedecl bug; make faux blocks mo…
jaekwon Apr 11, 2025
e7d85ba
fix realm exploiter test
jaekwon Apr 11, 2025
96c3524
docs: update EOA sections in realms.md
moul Apr 11, 2025
16a37b4
Create defer1.gno
piux2 Apr 11, 2025
328d282
Create recover1.gno
piux2 Apr 11, 2025
c1be915
more recover tests
piux2 Apr 11, 2025
85b0a93
chore(examples): update one example for new interrealm spec
moul Apr 11, 2025
bc6954b
negtive test, cannot capture name in type assert
ltzmaxwell Apr 12, 2025
86d998a
switchrealm -> crossing; withswitch -> cross
jaekwon Apr 12, 2025
9b02f1f
...
jaekwon Apr 12, 2025
2c18fae
add simple return & capture tests
ltzmaxwell Apr 12, 2025
f799a24
Merge commit 'refs/pull/4060/head' of github.com:gnolang/gno into pr-…
ltzmaxwell Apr 12, 2025
452c5a8
add no borrow
ltzmaxwell Apr 14, 2025
0a79c98
Merge branch 'master' into interrealm
moul Apr 17, 2025
8dc7d83
Merge remote-tracking branch 'origin/interrealm' into heapitems_for_3693
moul Apr 18, 2025
25bf34c
chore: update missing generated tests
moul Apr 18, 2025
63134d3
chore: regenerate + fmt
moul Apr 18, 2025
e2d6eb9
fill pointer with heap item base
jaekwon Apr 19, 2025
385720b
fix copy issues; fix blank identifier regression; return after Panic(…
jaekwon Apr 19, 2025
6d241bd
fix RunFiles DidUpdate for heap item
ltzmaxwell Apr 19, 2025
6fa2f69
fix(gnovm): various defer/recover/panic issues (#4188)
jaekwon Apr 20, 2025
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
108 changes: 108 additions & 0 deletions docs/resources/gno-interrealm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Interrealm Specification

Gno extends Go's type system with a interrealm rules. These rules can be
checked during the static type-checking phase (but at the moment they are
partially dependent on runtime checks).

All functions in Gno execute under a realm context as determined by the call
stack. Objects that reside in a realm can only be modified if the realm context
matches.

A function declared in p packages when called:

* inherits the last realm for package declared functions and closures.
* inherits the last realm when a method is called on unreal receiver.
* implicitly crosses to the receiver's resident realm when a method of the
receiver is called. The receiver's realm is also called the "borrow realm".

A function declared in a realm package when called:

* explicitly crosses to the realm in which the function is declared if the
function begins with a `crossing()` statement. The new realm is called the
"current realm".
* otherwise follows the same rules as for p packages.

The `crossing()` statement must be the first statement of a function's body.
It is illegal to use anywhere else, and cannot be used in p packages. Functions
that begin with the `crossing()` statement are called "crossing" functions".

A crossing function declared in a realm different than the last explicitly
crossed realm *must* be called like `cross(fn)(...)`. That is, functions of
calls that result in explicit realm crossings must be wrapped with `cross()`.

`std.CurrentRealm()` returns the current realm last explicitly crossed to.

`std.PreviousRealm()` returns the realm explicitly crossed to before that.

A crossing function declared in the same realm package as the callee may be
called normally OR like `cross(fn)(...)`. When called normally there will be no
realm crossing, but when called like `cross(fn)(...)` there is technically a
realm crossing and the current realm and previous realm returned are the same.

The current realm and previous realm do not depend on any implicit crossing to
the receiver's borrowed/storage realm even if the borrowed realm is the last
realm of the call stack equal to `m.Realm`. In other words `std.CurrentRealm()`
may be different than `m.Realm` (the borrow realm) when a receiver is called on
a foreign object.

Calls of methods on receivers residing in realms different than the current
realm must not be called like `cross(fn)(...)` if the method is not a
crossing function itself, and vice versa. Or it could be said that implicit
crossing is not real realm crossing. (When you sign a document with someone
else's pen it is still your signature; signature:pen :: current:borrowed.

A crossing method declared in a realm cannot modify the receiver if the object
resides in a different realm. However not all methods are required to be
crossing methods, and crossing methods may still read the state of the
receiver (and in general anything reacheable is readible).

New unreal objects reachable from the borrowed realm (or current realm if there
was no method call that borrowed) become persisted in the borrowed realm (or
current realm) upon finalization of the foreign object's method (or function).
(When you put an unlabeled photo in someone else's scrap book the photo now
belongs to the other person). In the future we will introduce an `attach()`
function to prevent a new unreal object from being taken.

MsgCall can only call (realm) crossing functions.

MsgRun will run a file's `main()` function in the user's realm and may call
both crossing functions and non-crossing functions.

A realm package's initialization (including init() calls) execute with current
realm of itself, and it `std.PreviousRealm()` will panic unless the call stack
includes a crossing function called like `cross(fn)(...)`.

### Justifications

P package code should behave the same even when copied verbatim in a realm
package.

Realm crossing with respect to `std.CurrentRealm()` and `std.PreviousRealm()`
is important enough to be explicit and warrants type-checking.

A crossing function of a realm should be able to call another crossing function
of the same realm without necessarily explicitly crossing realms.

Sometimes the previous realm and current realm must be the same realm, such as
when a realm consumes a service that it offers to external realms and users.

A method should be able to modify the receiver and associated objects of the
same borrowed realm.

A method should be able to create new objects that reside in the same realm by
default in order to maintain storage realm consistency and encapsulation and
reduce fragmentation.

In the future an object may be migrated from one realm to another when it loses
all references in one realm and gains references in another. The behavior of
the object should not change after migration because this type of migration is
implicit and generally not obvious without more language features.

Code declared in p packages (or declared in "immutable" realm packages) can
help different realms enforce contracts trustlessly, even those that involve
the caller's current realm. Otherwise two mutable (upgreadeable) realms cannot
export trust unto the chain because functions declared in those two realms can
be upgraded.

Both `crossing()` and `cross(fn)(...)` statements may become special syntax in
future Gno versions.
218 changes: 218 additions & 0 deletions docs/resources/gno-memory-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
# Gno Memory Model

## The Typed Value

```go
type TypedValue struct {
T Type
V Value
N [8]byte
}
```

Both `Type` and `Value` are Go interface values. Go does not support union
types, so primitive values like bools and ints are stored in the N field for
performance.

All values in Gno are stored represented as (type, value) tuples. Vars in
scope blocks, fields in structs, elements in arrays, keys and values of maps
are all respresented by the same `TypedValue` struct.

This tuple representation lets the Gno VM implementation logic be simpler with
less code. Reading and writing values are the same whether the static type of
the value is an interface or something concrete with no special logic for
memory optimizations which is less relevant in a massive multi-user
transactional operating system where most of the data resides in disk anyways.

Another benefit is that it promotes the development of new types of client
interfaces that can make use of the type information for object display and
interaction. The original vision of Tim Burners Lee's HTML DOM based World Wide
Web with restful HTTP requests like GET and POST has been steamrolled over by
continuous developments in HTML, CSS, Javascript, and the browser. The internet
is alive but the World Wide Web is dead. Gno is a reboot of the original vision
of the Web but better because everything is integrated based on a singular
well designed object-oriented language. Instead of POST requests Gno has
typed method calls. All values are annotated with their types making the
environment REST-ful to the core.

> REST (Representational State Transfer) is a software architectural style that
> was created to describe the design and guide the development of the
> architecture for the World Wide Web. REST defines a set of constraints for
> how the architecture of a distributed, Internet-scale hypermedia system, such
> as the Web, should behave. - wikipedia

While the Gno VM implements the memory model described here in the most
straightforward way, future alternative implementations may represent values
differently in machine memory even while conforming to the spec as implemented
by the Gno VM.


## Objects and Values

There are many types of Values, and some of these value types are also Objects.
The types below are all values and those that are bolded are also objects.

* Primitive // bool, uint, int, uint8, ... int64
* StringValue
* BigintValue // only used for constant expressions
* BigdecValue // only used for constant expressions
* DataByteValue // invisible type for byte array optimization
* PointerValue // base is always an object
* **ArrayValue**
* SliceValue
* **StructValue**
* **FuncValue**
* **MapValue**
* **BoundMethodValue** // func & receiver
* TypeValue
* PackageValue
* **BlockValue** // for package, file, if, range, switch, func
* RefValue // reference to an object stored in disk
* **HeapItemValue** // invisible type for loopvars and closure captures


## Pointers

```go
type PointerValue struct {
TV *TypedValue // escape val if pointer to var.
Base Value // array/struct/block, or heapitem.
Index int // list/fields/values index, or -1 or -2 (see below).
}
```

The pointer is a reference to a typed value slot in an array, struct, block,
or heap item. It is also used internally in the VM for assigning to slots.
Even internally the Base is used to tell the realm finalizer when the base
has been updated.

## Blocks and Heap Items

All statements enclosed in {} parentheses will allocate a new block
and push it onto the block stack of the VM. The size of the block
is determined by the number of variables declared in the block statement.

```
type BlockValue struct {
ObjectInfo
Source BlockNode
Values []TypedValue
Parent Value // Parent block if any, or RefValue{} to one.
Blank TypedValue // Captures "_" underscore names.
bodyStmt bodyStmt // Holds a pointer to the current statement.
}
```

The following Gno AST nodes when executed will create a new block:

* FuncLitStmt
* BlockStmt // a list of statements wrapped in {}
* ForStmt
* IfCaseStmt
* RangeStmt
* SwitchCaseStmt
* FuncDecl
* FileNode
* PackageNode

`IfStmt`s and `SwitchStmt`s also produce faux blocks that get merged onto the
following `IfCaseStmt` and `SwitchCaseStmt` respectively, but this is an
invisible implementation detail and the behavior may change.

Heap items are only used in blocks. Conceptually they are an object container
around a singleton typed value slot. It is not visible to the gno developer
but it is important to understand how they work when inspecting the block
space.

```go
func Example(arg int) (res *int) {
var x int = arg + 1
return &x
}
```

The above code when executed will first produce the following block:

```
BlockValue{
...
Source: <*FuncDecl node>,
Values: [
{T: nil, V: nil}, // 'arg' parameter
{T: nil, V: nil}, // 'res' result
{T: HeapItemType{},
V: &HeapItemValue{{T: nil, V: nil}}, // 'x' variable
],
...
}
```

In the above example the third slot for `x` is not initialized to the zero
value of a typed value slot, but rather it is prefilled with a heap item.

Variables declared in a closure or passed by reference are first discovered and
marked as such from the preprocessor, and NewBlock() will prepopulate these
slots with `*HeapItemValues`. When a `*HeapItemValue` is present in a block
slot it is not written over but instead the value is written into the heap
item's slot.

When the example code executes `return &x` instead of returning a
`PointerValue` with `.Base` set to the `BlockValue` and `.Index` of 2, it sets
`.Base` to the `*HeapItemValue` with `.Index` of 0 since a heap item only
contains one slot. The pointer's `.TV` is set to the single slot of of the heap
item. This way the when the pointer is used later in another transaction there
is no need to load the whole original block value, but rather a single heap
item object. If `Example()` returned only `x` rather than a pointer `&x` it
would not be initialized with a heap item for the slot.

```go
func Example2(arg int) (res func()) {
var x int = arg + 1
return func() {
println(x)
}
}
```

The above example illustrates another use for heap items. Here we don't
reference `x`, but it is captured by the anonymous function literal (closure).
At runtime the closure `*FuncValue` captures the heap item object such that the
closure does not depend on the block at all.

Variables declared at the package (global) level may also be referred to by
pointer in anonymous functions. In the future we will allow limited upgrading
features for mutable realm packages (e.g. the ability to add new functions or
replace or "swizzle" existing ones), so all package level declared variables
are wrapped in heap item objects.

Since all global package values referenced closures can be captured as heap
objects, the execution and persistence of a closure function value does not
depend on any parent blocks. (Omitted here is how references to package
level declared functions and methods are replaced by a selector expression
on the package itself; otherwise closures would still in general depend
on their parent blocks).

## Loopvars

Go1.22 introduced loopvars to reduce programmer errors. Gno uses
heap items to implement loopvars.

```go
for _, v := range values {
saveClosure(func() {
fmt.Println(v)
})
}
```

The Gno VM does something special for loopvars. Instead of assigning the new
value `v` to the same slot, or even the same heap item object's slot, it
replaces the existing heap item object with a new one. This allows the closure
to capture a new heap item object with every iteration. This is called
a heap definition.

The behavior is applied for implicit loops with `goto` statements. The
preprocessor first identifies all such variable definitions whether explicit in
range statements or implicit via `goto` statements that are captured by
closures or passed by pointer reference, and directs the VM to execute the
define statement by replacing the existing heap item object with a new one.
Loading
Loading