From 0a4217a525c59c37f39a6fe5c60f2446cc45fb56 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Tue, 12 Sep 2023 17:00:59 +0900 Subject: [PATCH] feat: native bigint using math/big.Int --- examples/gno.land/p/demo/ufmt/ufmt.gno | 4 ++ examples/gno.land/p/demo/ufmt/ufmt_test.gno | 1 + gno.land/pkg/sdk/vm/convert.go | 13 +++++ gnovm/pkg/gnolang/gonative.go | 9 ++- gnovm/pkg/gnolang/op_binary.go | 6 +- gnovm/pkg/gnolang/op_inc_dec.go | 6 ++ gnovm/pkg/gnolang/values.go | 16 ++++++ gnovm/pkg/gnolang/values_conversions.go | 64 +++++++++++++++++++++ gnovm/tests/files/type40.gno | 61 ++++++++++++++++++++ 9 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 gnovm/tests/files/type40.gno diff --git a/examples/gno.land/p/demo/ufmt/ufmt.gno b/examples/gno.land/p/demo/ufmt/ufmt.gno index 0e6143d6ebd..3820e53169f 100644 --- a/examples/gno.land/p/demo/ufmt/ufmt.gno +++ b/examples/gno.land/p/demo/ufmt/ufmt.gno @@ -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)" } @@ -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)" } diff --git a/examples/gno.land/p/demo/ufmt/ufmt_test.gno b/examples/gno.land/p/demo/ufmt/ufmt_test.gno index 07ba76c55b0..638adaa7129 100644 --- a/examples/gno.land/p/demo/ufmt/ufmt_test.gno +++ b/examples/gno.land/p/demo/ufmt/ufmt_test.gno @@ -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)]"}, diff --git a/gno.land/pkg/sdk/vm/convert.go b/gno.land/pkg/sdk/vm/convert.go index de4db67fb04..b6c41bfd235 100644 --- a/gno.land/pkg/sdk/vm/convert.go +++ b/gno.land/pkg/sdk/vm/convert.go @@ -3,6 +3,7 @@ package vm import ( "encoding/base64" "fmt" + "math/big" "strconv" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" @@ -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())) } diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index 69e468f755a..ba2dd33e22e 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -2,6 +2,7 @@ package gnolang import ( "fmt" + "math/big" "reflect" ) @@ -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: @@ -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: @@ -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", diff --git a/gnovm/pkg/gnolang/op_binary.go b/gnovm/pkg/gnolang/op_binary.go index 13156133e38..f6606567b83 100644 --- a/gnovm/pkg/gnolang/op_binary.go +++ b/gnovm/pkg/gnolang/op_binary.go @@ -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", diff --git a/gnovm/pkg/gnolang/op_inc_dec.go b/gnovm/pkg/gnolang/op_inc_dec.go index 80987707035..e0763eeaf02 100644 --- a/gnovm/pkg/gnolang/op_inc_dec.go +++ b/gnovm/pkg/gnolang/op_inc_dec.go @@ -1,5 +1,7 @@ package gnolang +import "math/big" + func (m *Machine) doOpInc() { s := m.PopStmt().(*IncDecStmt) @@ -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") } @@ -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") } diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 3de74ac0130..24521cc31f8 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -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) @@ -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 { @@ -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 } } diff --git a/gnovm/pkg/gnolang/values_conversions.go b/gnovm/pkg/gnolang/values_conversions.go index 9fc2ce4a567..1efc83c0719 100644 --- a/gnovm/pkg/gnolang/values_conversions.go +++ b/gnovm/pkg/gnolang/values_conversions.go @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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())) + + } case StringKind: bt := baseOf(t) switch cbt := bt.(type) { @@ -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", diff --git a/gnovm/tests/files/type40.gno b/gnovm/tests/files/type40.gno new file mode 100644 index 00000000000..c76b208a923 --- /dev/null +++ b/gnovm/tests/files/type40.gno @@ -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