Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion compiler-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ Instructions are fetched and evaluated one at the time until they are finished (

Instructions can move data by manipulating registers. Registers banks are separated by type (so that we don't have to have a single heap-allocated value, nor unsafe pointers or manually handled unsafe memory). With "type" here we mean the internal representation of data, which isn't the same as numscript types (there isn't necessarily a 1-1 relationship). For example, both strings, assets and accounts are represented via the golang `string` type. The `bool` bank is the other direction: it has no numscript counterpart at all, and exists so that a branch condition can't be a monetary quantity.

> Note: we'll probably change accounts' representation when adding scopes
> Note: scopes turned out not to need a representation change. `scoped(account, "scope")`
> compiles to a plain second `string` register carried alongside the account's own, using
> the same nilable-optional-operand idiom `Color`/`Overdraft` already use on `PullAccount` —
> exactly the same non-bank treatment `Monetary` gets as a `(str, int)` pair. See
> `compiler.compileAccountExpr`/`accountValue` and `ir.AssertValidScope`.

A simple example of an instruction is:

Expand Down
1 change: 1 addition & 0 deletions internal/builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ const (
Overdraft = "overdraft"
GetAsset = "get_asset"
GetAmount = "get_amount"
Scoped = "scoped"
)
47 changes: 16 additions & 31 deletions internal/cmd/bytecode_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,55 +41,48 @@ type BytecodeRunArgs struct {
OutFormatOpt string
}

// vmMetaKey identifies one metadata slot: account, scope and key.
type vmMetaKey struct {
account string
scope string
key string
}

// vmStore is a vm.Store over the rows of an inputs file.
type vmStore struct {
balances map[runtime.PairKey]*big.Int
meta map[string]map[string]string
meta map[vmMetaKey]string
}

func (s vmStore) GetBalance(_ context.Context, account, asset, color string) (*big.Int, error) {
func (s vmStore) GetBalance(_ context.Context, account, scope, asset, color string) (*big.Int, error) {
// the caller owns what it gets: the run state mutates balances in place
if v, ok := s.balances[runtime.PairKey{Account: account, Asset: asset, Color: color}]; ok {
if v, ok := s.balances[runtime.PairKey{Account: account, Scope: scope, Asset: asset, Color: color}]; ok {
return new(big.Int).Set(v), nil
}
return new(big.Int), nil
}

func (s vmStore) GetMetadata(_ context.Context, account, key string) (string, bool, error) {
v, ok := s.meta[account][key]
func (s vmStore) GetMetadata(_ context.Context, account, scope, key string) (string, bool, error) {
v, ok := s.meta[vmMetaKey{account: account, scope: scope, key: key}]
return v, ok, nil
}

func newVmStore(inputsPath string, inputs BytecodeInputsFile) (vmStore, error) {
store := vmStore{
balances: make(map[runtime.PairKey]*big.Int, len(inputs.Balances)),
meta: make(map[string]map[string]string),
meta: make(map[vmMetaKey]string, len(inputs.Meta)),
}

for _, row := range inputs.Balances {
// the vm never sets a scope, so a scoped row could never be read back
if row.Scope != "" {
return vmStore{}, fmt.Errorf("invalid inputs file '%s': scoped balances are not supported by the vm (account=%q scope=%q)", inputsPath, row.Account, row.Scope)
}

amount := row.Amount
if amount == nil {
amount = new(big.Int)
}
store.balances[runtime.PairKey{Account: row.Account, Asset: row.Asset, Color: row.Color}] = amount
store.balances[runtime.PairKey{Account: row.Account, Scope: row.Scope, Asset: row.Asset, Color: row.Color}] = amount
}

for _, row := range inputs.Meta {
if row.Scope != "" {
return vmStore{}, fmt.Errorf("invalid inputs file '%s': scoped metadata is not supported by the vm (account=%q scope=%q)", inputsPath, row.Account, row.Scope)
}

byKey, ok := store.meta[row.Account]
if !ok {
byKey = map[string]string{}
store.meta[row.Account] = byKey
}
byKey[row.Key] = row.Value
store.meta[vmMetaKey{account: row.Account, scope: row.Scope, key: row.Key}] = row.Value
}

return store, nil
Expand Down Expand Up @@ -211,15 +204,7 @@ func showBytecodePretty(result runtime.ExecutionResult) error {

if len(result.AccountsMetadata) != 0 {
fmt.Println("Accounts meta:")
accounts := make([]string, 0, len(result.AccountsMetadata))
for account := range result.AccountsMetadata {
accounts = append(accounts, account)
}
sort.Strings(accounts)
for _, account := range accounts {
fmt.Printf("@%s\n", account)
fmt.Print(prettyPrintStringMeta(result.AccountsMetadata[account]))
}
fmt.Print(result.AccountsMetadata.PrettyPrint())
}

return nil
Expand Down
38 changes: 27 additions & 11 deletions internal/cmd/bytecode_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func TestVmStoreReturnsACopyOfTheBalance(t *testing.T) {
})
require.NoError(t, err)

got, err := store.GetBalance(context.Background(), "src", "USD/2", "")
got, err := store.GetBalance(context.Background(), "src", "", "USD/2", "")
require.NoError(t, err)
require.Zero(t, got.Cmp(big.NewInt(100)))

Expand All @@ -222,27 +222,43 @@ func TestVmStoreUnknownAccountIsZeroNotAnError(t *testing.T) {
store, err := newVmStore("in.json", BytecodeInputsFile{})
require.NoError(t, err)

got, err := store.GetBalance(context.Background(), "nobody", "USD/2", "")
got, err := store.GetBalance(context.Background(), "nobody", "", "USD/2", "")
require.NoError(t, err)
require.Zero(t, got.Sign())

_, ok, err := store.GetMetadata(context.Background(), "nobody", "k")
_, ok, err := store.GetMetadata(context.Background(), "nobody", "", "k")
require.NoError(t, err)
require.False(t, ok)
}

func TestVmStoreRejectsScopedRows(t *testing.T) {
_, err := newVmStore("in.json", BytecodeInputsFile{
func TestVmStoreSupportsScopedRows(t *testing.T) {
store, err := newVmStore("in.json", BytecodeInputsFile{
Balances: interpreter.Balances{
{Account: "src", Asset: "USD/2", Amount: big.NewInt(1), Scope: "reserve"},
{Account: "src", Asset: "USD/2", Amount: big.NewInt(100)},
},
})
require.ErrorContains(t, err, "scoped balances are not supported by the vm")

_, err = newVmStore("in.json", BytecodeInputsFile{
Meta: interpreter.AccountsMetadata{
{Account: "src", Key: "k", Value: "v", Scope: "reserve"},
{Account: "src", Key: "k", Value: "scoped", Scope: "reserve"},
{Account: "src", Key: "k", Value: "unscoped"},
},
})
require.ErrorContains(t, err, "scoped metadata is not supported by the vm")
require.NoError(t, err)

scopedBal, err := store.GetBalance(context.Background(), "src", "reserve", "USD/2", "")
require.NoError(t, err)
require.Zero(t, scopedBal.Cmp(big.NewInt(1)))

unscopedBal, err := store.GetBalance(context.Background(), "src", "", "USD/2", "")
require.NoError(t, err)
require.Zero(t, unscopedBal.Cmp(big.NewInt(100)))

scopedMeta, ok, err := store.GetMetadata(context.Background(), "src", "reserve", "k")
require.NoError(t, err)
require.True(t, ok)
require.Equal(t, "scoped", scopedMeta)

unscopedMeta, ok, err := store.GetMetadata(context.Background(), "src", "", "k")
require.NoError(t, err)
require.True(t, ok)
require.Equal(t, "unscoped", unscopedMeta)
}
9 changes: 5 additions & 4 deletions internal/compiler/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ type benchStore struct {
balances map[runtime.PairKey]*big.Int
}

func (s benchStore) GetBalance(ctx context.Context, account, asset, color string) (*big.Int, error) {
if v, ok := s.balances[runtime.PairKey{Account: account, Asset: asset, Color: color}]; ok {
func (s benchStore) GetBalance(ctx context.Context, account, scope, asset, color string) (*big.Int, error) {
if v, ok := s.balances[runtime.PairKey{Account: account, Scope: scope, Asset: asset, Color: color}]; ok {
return v, nil
}
return new(big.Int), nil
}

func (benchStore) GetMetadata(ctx context.Context, k, v string) (string, bool, error) {
func (benchStore) GetMetadata(ctx context.Context, account, scope, key string) (string, bool, error) {
return "", false, nil
}

Expand All @@ -34,10 +34,11 @@ type runtimeStoreAdapter struct {

func (s runtimeStoreAdapter) GetBalance(
account string,
scope string,
asset string,
color string,
) (*big.Int, error) {
return s.store.GetBalance(context.Background(), account, asset, color)
return s.store.GetBalance(context.Background(), account, scope, asset, color)
}

// Both benchmarks run the SAME program with the same starting balance; only the
Expand Down
Loading