Skip to content

Commit

Permalink
feat: native bigint using math/big.Int
Browse files Browse the repository at this point in the history
  • Loading branch information
r3v4s committed Sep 12, 2023
1 parent 207d66d commit 0a4217a
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 5 deletions.
4 changes: 4 additions & 0 deletions examples/gno.land/p/demo/ufmt/ufmt.gno
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ func Sprintf(format string, args ...interface{}) string {
buf += v.String()
case string:
buf += v
case bigint:
buf += string(v)
default:
buf += "(unhandled)"
}
Expand All @@ -70,6 +72,8 @@ func Sprintf(format string, args ...interface{}) string {
buf += strconv.FormatUint(uint64(v), 10)
case uint64:
buf += strconv.FormatUint(v, 10)
case bigint:
buf += string(v)
default:
buf += "(unhandled)"
}
Expand Down
1 change: 1 addition & 0 deletions examples/gno.land/p/demo/ufmt/ufmt_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestSprintf(t *testing.T) {
{"uint [%d]", []interface{}{uint(42)}, "uint [42]"},
{"int64 [%d]", []interface{}{int64(42)}, "int64 [42]"},
{"uint64 [%d]", []interface{}{uint64(42)}, "uint64 [42]"},
{"bigint [%d]", []interface{}{bigint(42)}, "bigint [42]"}
{"bool [%t]", []interface{}{true}, "bool [true]"},
{"bool [%t]", []interface{}{false}, "bool [false]"},
{"invalid bool [%t]", []interface{}{"invalid"}, "invalid bool [(unhandled)]"},
Expand Down
13 changes: 13 additions & 0 deletions gno.land/pkg/sdk/vm/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package vm
import (
"encoding/base64"
"fmt"
"math/big"
"strconv"

gno "github.com/gnolang/gno/gnovm/pkg/gnolang"
Expand Down Expand Up @@ -153,6 +154,18 @@ func convertArgToGno(arg string, argT gno.Type) (tv gno.TypedValue) {
}
tv.SetUint64(u64)
return
case gno.BigintType:
if arg[0] == '+' {
panic("numbers cannot start with +")
}
bi, ok := big.NewInt(0).SetString(arg, 10)
if !ok {
panic(fmt.Sprintf(
"error parsing bigint %v", arg))
}

tv.SetBigInt(bi)
return
default:
panic(fmt.Sprintf("unexpected primitive type %s", bt.String()))
}
Expand Down
9 changes: 7 additions & 2 deletions gnovm/pkg/gnolang/gonative.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gnolang

import (
"fmt"
"math/big"
"reflect"
)

Expand Down Expand Up @@ -508,7 +509,9 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv
tv.SetFloat64(rv.Float())
}
case BigintKind:
panic("not yet implemented")
if lvl != 0 {
tv.SetBigInt(rv.Interface().(*big.Int))
}
case BigdecKind:
panic("not yet implemented")
case ArrayKind:
Expand Down Expand Up @@ -828,7 +831,7 @@ func gno2GoType(t Type) reflect.Type {
case Float64Type:
return reflect.TypeOf(float64(0))
case BigintType, UntypedBigintType:
panic("not yet implemented")
return reflect.TypeOf(big.NewInt(0))
case BigdecType, UntypedBigdecType:
panic("not yet implemented")
default:
Expand Down Expand Up @@ -1113,6 +1116,8 @@ func gno2GoValue(tv *TypedValue, rv reflect.Value) (ret reflect.Value) {
rv.SetFloat(float64(tv.GetFloat32()))
case Float64Type:
rv.SetFloat(tv.GetFloat64())
case BigintType, UntypedBigintType:
rv.Set(reflect.ValueOf(tv.GetBigInt()))
default:
panic(fmt.Sprintf(
"unexpected type %s",
Expand Down
6 changes: 3 additions & 3 deletions gnovm/pkg/gnolang/op_binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -1109,9 +1109,9 @@ func shlAssign(lv, rv *TypedValue) {
case Uint64Type:
lv.SetUint64(lv.GetUint64() << rv.GetUint())
case BigintType, UntypedBigintType:
lb := lv.GetBigInt()
lb = big.NewInt(0).Lsh(lb, rv.GetUint())
lv.V = BigintValue{V: lb}
z := big.NewInt(0)
z.Lsh(lv.GetBigInt(), rv.GetUint())
lv.V = BigintValue{V: z}
default:
panic(fmt.Sprintf(
"operators << and <<= not defined for %s",
Expand Down
6 changes: 6 additions & 0 deletions gnovm/pkg/gnolang/op_inc_dec.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package gnolang

import "math/big"

func (m *Machine) doOpInc() {
s := m.PopStmt().(*IncDecStmt)

Expand Down Expand Up @@ -42,6 +44,8 @@ func (m *Machine) doOpInc() {
lv.SetUint32(lv.GetUint32() + 1)
case Uint64Type:
lv.SetUint64(lv.GetUint64() + 1)
case BigintType:
lv.GetBigInt().Add(lv.GetBigInt(), big.NewInt(1))
default:
panic("unexpected type in in operation")
}
Expand Down Expand Up @@ -94,6 +98,8 @@ func (m *Machine) doOpDec() {
lv.SetUint32(lv.GetUint32() - 1)
case Uint64Type:
lv.SetUint64(lv.GetUint64() - 1)
case BigintType:
lv.GetBigInt().Sub(lv.GetBigInt(), big.NewInt(1))
default:
panic("unexpected type in in operation")
}
Expand Down
16 changes: 16 additions & 0 deletions gnovm/pkg/gnolang/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,7 @@ func (tv TypedValue) Copy(alloc *Allocator) (cp TypedValue) {
case BigintValue:
cp.T = tv.T
cp.V = cv.Copy(alloc)
cp.N = tv.N
case *ArrayValue:
cp.T = tv.T
cp.V = cv.Copy(alloc)
Expand Down Expand Up @@ -1423,6 +1424,17 @@ func (tv *TypedValue) GetFloat64() float64 {
return *(*float64)(unsafe.Pointer(&tv.N))
}

func (tv *TypedValue) SetBigInt(bi *big.Int) {
if debug {
if tv.T.Kind() != BigintKind || isNative(tv.T) {
panic(fmt.Sprintf(
"TypedValue.SetBigInt() on type %s",
tv.T.String()))
}
}
tv.V = BigintValue{bi}
}

func (tv *TypedValue) GetBigInt() *big.Int {
if debug {
if tv.T != nil && tv.T.Kind() != BigintKind {
Expand Down Expand Up @@ -2430,6 +2442,10 @@ func defaultValue(alloc *Allocator, t Type) Value {
)
}
default:
switch t.Kind() {
case BigintKind:
return BigintValue{V: big.NewInt(0)}
}
return nil
}
}
Expand Down
64 changes: 64 additions & 0 deletions gnovm/pkg/gnolang/values_conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ GNO_CASE:
x := float64(tv.GetInt()) // XXX determinism?
tv.T = t
tv.SetFloat64(x)
case BigintKind:
tv.V = BigintValue{V: big.NewInt(tv.GetInt64())}
tv.T = t
case StringKind:
tv.V = alloc.NewString(string(rune(tv.GetInt())))
tv.T = t
Expand Down Expand Up @@ -315,6 +318,9 @@ GNO_CASE:
x := float64(tv.GetInt32()) // XXX determinism?
tv.T = t
tv.SetFloat64(x)
case BigintKind:
tv.V = BigintValue{V: big.NewInt(tv.GetInt64())}
tv.T = t
case StringKind:
tv.V = alloc.NewString(string(tv.GetInt32()))
tv.T = t
Expand Down Expand Up @@ -374,6 +380,9 @@ GNO_CASE:
x := float64(tv.GetInt64()) // XXX determinism?
tv.T = t
tv.SetFloat64(x)
case BigintKind:
tv.V = BigintValue{V: big.NewInt(tv.GetInt64())}
tv.T = t
case StringKind:
tv.V = alloc.NewString(string(rune(tv.GetInt64())))
tv.T = t
Expand Down Expand Up @@ -433,6 +442,9 @@ GNO_CASE:
x := float64(tv.GetUint()) // XXX determinism?
tv.T = t
tv.SetFloat64(x)
case BigintKind:
tv.V = BigintValue{V: big.NewInt(tv.GetInt64())}
tv.T = t
case StringKind:
tv.V = alloc.NewString(string(rune(tv.GetUint())))
tv.T = t
Expand Down Expand Up @@ -669,6 +681,9 @@ GNO_CASE:
x := float64(tv.GetUint64()) // XXX determinism?
tv.T = t
tv.SetFloat64(x)
case BigintKind:
tv.V = BigintValue{V: big.NewInt(tv.GetInt64())}
tv.T = t
case StringKind:
tv.V = alloc.NewString(string(rune(tv.GetUint64())))
tv.T = t
Expand Down Expand Up @@ -788,6 +803,41 @@ GNO_CASE:
"cannot convert %s to %s",
tvk.String(), k.String()))
}
case BigintKind:
switch k {
case IntKind, Int8Kind, Int16Kind, Int32Kind, UintKind, Uint8Kind, Uint16Kind, Uint32Kind:
panic(fmt.Sprintf(
"cannot convert %s to %s",
tvk.String(), k.String()))
case Int64Kind:
x := tv.GetBigInt()
if x.IsInt64() {
tv.T = t
tv.SetInt64(x.Int64())
} else {
panic(fmt.Sprintf(
"cannot convert %s to %s",
tvk.String(), k.String()))
}
case Uint64Kind:
x := tv.GetBigInt()
if x.IsUint64() {
tv.T = t
tv.SetUint64(x.Uint64())
} else {
panic(fmt.Sprintf(
"cannot convert %s to %s",
tvk.String(), k.String()))
}
case StringKind:
tv.T = t
tv.SetString(StringValue(tv.GetBigInt().String()))
default:
panic(fmt.Sprintf(
"cannot convert %s to %s",
tvk.String(), k.String()))

Check failure on line 839 in gnovm/pkg/gnolang/values_conversions.go

View workflow job for this annotation

GitHub Actions / lint

unnecessary trailing newline (whitespace)
}
case StringKind:
bt := baseOf(t)
switch cbt := bt.(type) {
Expand All @@ -809,6 +859,20 @@ GNO_CASE:
"cannot convert %s to %s",
tvk.String(), t.String()))
}
case PrimitiveType:
switch k {
case BigintKind:
bi := new(big.Int)
bi, ok := bi.SetString(tv.GetString(), 10)
if !ok {
panic(fmt.Sprintf(
"cannot convert %s to %s",
tvk.String(), k.String(),
))
}
tv.V = BigintValue{V: bi}
tv.T = t
}
default:
panic(fmt.Sprintf(
"cannot convert %s to %s",
Expand Down
61 changes: 61 additions & 0 deletions gnovm/tests/files/type40.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package main

var (
mi64 int64 = 123
mui64 uint64 = 123

mbi bigint = 123

max_uint64_plus_1 bigint = 18446744073709551616
max_int64_plus_1 bigint = 9223372036854775808
)

func main() {
// default value
var _zeroBig bigint
shouldEQ(_zeroBig, bigint(0))

// int64 > bigint
shouldEQ(bigint(mi64), mbi)

// uint64 > bigint
shouldEQ(bigint(mui64), mbi)

// bigint > int64
shouldEQ(int64(mbi), mi64)

// bigint(too big) > int64
// int64(max_int64_plus_1) // cannot convert BigintKind to Int64Kin

// bigint > uint64
shouldEQ(uint64(mbi), mui64)

// bigint(too big) > uint64
// uint64(max_uint64_plus_1) // cannot convert BigintKind to Int64Kind

// OpInc, OpDec
var _oneBig bigint = 1
_oneBig++
shouldEQ(_oneBig, bigint(2))

_oneBig--
shouldEQ(_oneBig, bigint(1))
}

func shouldEQ(a, b interface{}) bool {
if a == b {
println("true")
} else {
println("false")
}
return a == b
}

// Output:
// true
// true
// true
// true
// true
// true
// true

0 comments on commit 0a4217a

Please sign in to comment.