From 4cf1e2eb5bbfa90f9ee5aade01cb32c6842ef5bf Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 2 Feb 2024 09:49:17 +0100 Subject: [PATCH] refactor(gnovm): handle assignment operations in preprocess --- gnovm/pkg/gnolang/machine.go | 52 +------ gnovm/pkg/gnolang/nodes.go | 47 ++++++ gnovm/pkg/gnolang/op_assign.go | 247 -------------------------------- gnovm/pkg/gnolang/op_exec.go | 24 +--- gnovm/pkg/gnolang/op_string.go | 23 +-- gnovm/pkg/gnolang/preprocess.go | 22 +-- 6 files changed, 72 insertions(+), 343 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index e9e8eba8adc..ea77ecbd40d 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -915,21 +915,10 @@ const ( OpMaybeNativeType Op = 0x79 // maybenative{X} /* Statement operators */ - OpAssign Op = 0x80 // Lhs = Rhs - OpAddAssign Op = 0x81 // Lhs += Rhs - OpSubAssign Op = 0x82 // Lhs -= Rhs - OpMulAssign Op = 0x83 // Lhs *= Rhs - OpQuoAssign Op = 0x84 // Lhs /= Rhs - OpRemAssign Op = 0x85 // Lhs %= Rhs - OpBandAssign Op = 0x86 // Lhs &= Rhs - OpBandnAssign Op = 0x87 // Lhs &^= Rhs - OpBorAssign Op = 0x88 // Lhs |= Rhs - OpXorAssign Op = 0x89 // Lhs ^= Rhs - OpShlAssign Op = 0x8A // Lhs <<= Rhs - OpShrAssign Op = 0x8B // Lhs >>= Rhs - OpDefine Op = 0x8C // X... := Y... - OpInc Op = 0x8D // X++ - OpDec Op = 0x8E // X-- + OpAssign Op = 0x80 // Lhs = Rhs + OpDefine Op = 0x81 // X... := Y... + OpInc Op = 0x82 // X++ + OpDec Op = 0x83 // X-- /* Decl operators */ OpValueDecl Op = 0x90 // var/const ... @@ -1335,39 +1324,6 @@ func (m *Machine) Run() { case OpAssign: m.incrCPU(OpCPUAssign) m.doOpAssign() - case OpAddAssign: - m.incrCPU(OpCPUAddAssign) - m.doOpAddAssign() - case OpSubAssign: - m.incrCPU(OpCPUSubAssign) - m.doOpSubAssign() - case OpMulAssign: - m.incrCPU(OpCPUMulAssign) - m.doOpMulAssign() - case OpQuoAssign: - m.incrCPU(OpCPUQuoAssign) - m.doOpQuoAssign() - case OpRemAssign: - m.incrCPU(OpCPURemAssign) - m.doOpRemAssign() - case OpBandAssign: - m.incrCPU(OpCPUBandAssign) - m.doOpBandAssign() - case OpBandnAssign: - m.incrCPU(OpCPUBandnAssign) - m.doOpBandnAssign() - case OpBorAssign: - m.incrCPU(OpCPUBorAssign) - m.doOpBorAssign() - case OpXorAssign: - m.incrCPU(OpCPUXorAssign) - m.doOpXorAssign() - case OpShlAssign: - m.incrCPU(OpCPUShlAssign) - m.doOpShlAssign() - case OpShrAssign: - m.incrCPU(OpCPUShrAssign) - m.doOpShrAssign() case OpDefine: m.incrCPU(OpCPUDefine) m.doOpDefine() diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 8f2c5054a8a..78f9fc83f53 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -47,6 +47,9 @@ const ( SHR // >> BAND_NOT // &^ + // NOTE: keep following combined assignment operations in sync with + // min/maxAssignmentOperationed + ADD_ASSIGN // += SUB_ASSIGN // -= MUL_ASSIGN // *= @@ -108,6 +111,50 @@ const ( VAR ) +const ( + minAssignmentOperation = ADD_ASSIGN + maxAssignmentOperation = BAND_NOT_ASSIGN +) + +func (w Word) isAssignmentOperation() bool { + return w >= minAssignmentOperation && w <= maxAssignmentOperation +} + +// convertAssignemntOperation converts w to its equivalent binary operation in +// the case of an assignemnt operation; for instance, +// ADD_ASSIGN.convertAssignmentOperation() returns ADD. +// +// If w is not an assignment operation, ILLEGAL is returned. +func (w Word) convertAssignmentOperation() Word { + // XXX: is this inlined? if not, would lookup table make it inlined / better? + switch w { + case ADD_ASSIGN: + return ADD + case SUB_ASSIGN: + return SUB + case MUL_ASSIGN: + return MUL + case QUO_ASSIGN: + return QUO + case REM_ASSIGN: + return REM + case BAND_ASSIGN: + return BAND + case BOR_ASSIGN: + return BOR + case XOR_ASSIGN: + return XOR + case SHL_ASSIGN: + return SHL + case SHR_ASSIGN: + return SHR + case BAND_NOT_ASSIGN: + return BAND_NOT + default: + return ILLEGAL + } +} + type Name string // ---------------------------------------- diff --git a/gnovm/pkg/gnolang/op_assign.go b/gnovm/pkg/gnolang/op_assign.go index 0cc30861355..7bf36015316 100644 --- a/gnovm/pkg/gnolang/op_assign.go +++ b/gnovm/pkg/gnolang/op_assign.go @@ -44,250 +44,3 @@ func (m *Machine) doOpAssign() { lv.Assign2(m.Alloc, m.Store, m.Realm, rvs[i], true) } } - -func (m *Machine) doOpAddAssign() { - s := m.PopStmt().(*AssignStmt) - rv := m.PopValue() // only one. - lv := m.PopAsPointer(s.Lhs[0]) - if debug { - assertSameTypes(lv.TV.T, rv.T) - } - - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } - // add rv to lv. - addAssign(m.Alloc, lv.TV, rv) - if lv.Base != nil { - m.Realm.DidUpdate(lv.Base.(Object), nil, nil) - } -} - -func (m *Machine) doOpSubAssign() { - s := m.PopStmt().(*AssignStmt) - rv := m.PopValue() // only one. - lv := m.PopAsPointer(s.Lhs[0]) - if debug { - assertSameTypes(lv.TV.T, rv.T) - } - - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } - // sub rv from lv. - subAssign(lv.TV, rv) - if lv.Base != nil { - m.Realm.DidUpdate(lv.Base.(Object), nil, nil) - } -} - -func (m *Machine) doOpMulAssign() { - s := m.PopStmt().(*AssignStmt) - rv := m.PopValue() // only one. - lv := m.PopAsPointer(s.Lhs[0]) - if debug { - assertSameTypes(lv.TV.T, rv.T) - } - - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } - // lv *= rv - mulAssign(lv.TV, rv) - if lv.Base != nil { - m.Realm.DidUpdate(lv.Base.(Object), nil, nil) - } -} - -func (m *Machine) doOpQuoAssign() { - s := m.PopStmt().(*AssignStmt) - rv := m.PopValue() // only one. - lv := m.PopAsPointer(s.Lhs[0]) - if debug { - assertSameTypes(lv.TV.T, rv.T) - } - - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } - // lv /= rv - quoAssign(lv.TV, rv) - if lv.Base != nil { - m.Realm.DidUpdate(lv.Base.(Object), nil, nil) - } -} - -func (m *Machine) doOpRemAssign() { - s := m.PopStmt().(*AssignStmt) - rv := m.PopValue() // only one. - lv := m.PopAsPointer(s.Lhs[0]) - if debug { - assertSameTypes(lv.TV.T, rv.T) - } - - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } - // lv %= rv - remAssign(lv.TV, rv) - if lv.Base != nil { - m.Realm.DidUpdate(lv.Base.(Object), nil, nil) - } -} - -func (m *Machine) doOpBandAssign() { - s := m.PopStmt().(*AssignStmt) - rv := m.PopValue() // only one. - lv := m.PopAsPointer(s.Lhs[0]) - if debug { - assertSameTypes(lv.TV.T, rv.T) - } - - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } - // lv &= rv - bandAssign(lv.TV, rv) - if lv.Base != nil { - m.Realm.DidUpdate(lv.Base.(Object), nil, nil) - } -} - -func (m *Machine) doOpBandnAssign() { - s := m.PopStmt().(*AssignStmt) - rv := m.PopValue() // only one. - lv := m.PopAsPointer(s.Lhs[0]) - if debug { - assertSameTypes(lv.TV.T, rv.T) - } - - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } - // lv &^= rv - bandnAssign(lv.TV, rv) - if lv.Base != nil { - m.Realm.DidUpdate(lv.Base.(Object), nil, nil) - } -} - -func (m *Machine) doOpBorAssign() { - s := m.PopStmt().(*AssignStmt) - rv := m.PopValue() // only one. - lv := m.PopAsPointer(s.Lhs[0]) - if debug { - assertSameTypes(lv.TV.T, rv.T) - } - - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } - // lv |= rv - borAssign(lv.TV, rv) - if lv.Base != nil { - m.Realm.DidUpdate(lv.Base.(Object), nil, nil) - } -} - -func (m *Machine) doOpXorAssign() { - s := m.PopStmt().(*AssignStmt) - rv := m.PopValue() // only one. - lv := m.PopAsPointer(s.Lhs[0]) - if debug { - assertSameTypes(lv.TV.T, rv.T) - } - - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } - // lv ^= rv - xorAssign(lv.TV, rv) - if lv.Base != nil { - m.Realm.DidUpdate(lv.Base.(Object), nil, nil) - } -} - -func (m *Machine) doOpShlAssign() { - s := m.PopStmt().(*AssignStmt) - rv := m.PopValue() // only one. - lv := m.PopAsPointer(s.Lhs[0]) - - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } - // lv <<= rv - shlAssign(lv.TV, rv) - if lv.Base != nil { - m.Realm.DidUpdate(lv.Base.(Object), nil, nil) - } -} - -func (m *Machine) doOpShrAssign() { - s := m.PopStmt().(*AssignStmt) - rv := m.PopValue() // only one. - lv := m.PopAsPointer(s.Lhs[0]) - - // XXX HACK (until value persistence impl'd) - if m.ReadOnly { - if oo, ok := lv.Base.(Object); ok { - if oo.GetIsReal() { - panic("readonly violation") - } - } - } - // lv >>= rv - shrAssign(lv.TV, rv) - if lv.Base != nil { - m.Realm.DidUpdate(lv.Base.(Object), nil, nil) - } -} diff --git a/gnovm/pkg/gnolang/op_exec.go b/gnovm/pkg/gnolang/op_exec.go index 300303135ad..b3f2cc517b7 100644 --- a/gnovm/pkg/gnolang/op_exec.go +++ b/gnovm/pkg/gnolang/op_exec.go @@ -436,28 +436,6 @@ EXEC_SWITCH: switch cs.Op { case ASSIGN: m.PushOp(OpAssign) - case ADD_ASSIGN: - m.PushOp(OpAddAssign) - case SUB_ASSIGN: - m.PushOp(OpSubAssign) - case MUL_ASSIGN: - m.PushOp(OpMulAssign) - case QUO_ASSIGN: - m.PushOp(OpQuoAssign) - case REM_ASSIGN: - m.PushOp(OpRemAssign) - case BAND_ASSIGN: - m.PushOp(OpBandAssign) - case BOR_ASSIGN: - m.PushOp(OpBorAssign) - case XOR_ASSIGN: - m.PushOp(OpXorAssign) - case SHL_ASSIGN: - m.PushOp(OpShlAssign) - case SHR_ASSIGN: - m.PushOp(OpShrAssign) - case BAND_NOT_ASSIGN: - m.PushOp(OpBandnAssign) case DEFINE: m.PushOp(OpDefine) default: @@ -473,7 +451,7 @@ EXEC_SWITCH: m.PushExpr(rx) m.PushOp(OpEval) } - if cs.Op != DEFINE { + if cs.Op == ASSIGN { // For each Lhs, push eval operation if needed. for i := len(cs.Lhs) - 1; 0 <= i; i-- { lx := cs.Lhs[i] diff --git a/gnovm/pkg/gnolang/op_string.go b/gnovm/pkg/gnolang/op_string.go index db52b4ff67b..367b4352ee5 100644 --- a/gnovm/pkg/gnolang/op_string.go +++ b/gnovm/pkg/gnolang/op_string.go @@ -90,20 +90,9 @@ func _() { _ = x[OpStructType-120] _ = x[OpMaybeNativeType-121] _ = x[OpAssign-128] - _ = x[OpAddAssign-129] - _ = x[OpSubAssign-130] - _ = x[OpMulAssign-131] - _ = x[OpQuoAssign-132] - _ = x[OpRemAssign-133] - _ = x[OpBandAssign-134] - _ = x[OpBandnAssign-135] - _ = x[OpBorAssign-136] - _ = x[OpXorAssign-137] - _ = x[OpShlAssign-138] - _ = x[OpShrAssign-139] - _ = x[OpDefine-140] - _ = x[OpInc-141] - _ = x[OpDec-142] + _ = x[OpDefine-129] + _ = x[OpInc-130] + _ = x[OpDec-131] _ = x[OpValueDecl-144] _ = x[OpTypeDecl-145] _ = x[OpSticky-208] @@ -123,7 +112,7 @@ const ( _Op_name_3 = "OpEvalOpBinary1OpIndex1OpIndex2OpSelectorOpSliceOpStarOpRefOpTypeAssert1OpTypeAssert2OpStaticTypeOfOpCompositeLitOpArrayLitOpSliceLitOpSliceLit2OpMapLitOpStructLitOpFuncLitOpConvert" _Op_name_4 = "OpArrayLitGoNativeOpSliceLitGoNativeOpStructLitGoNativeOpCallGoNative" _Op_name_5 = "OpFieldTypeOpArrayTypeOpSliceTypeOpPointerTypeOpInterfaceTypeOpChanTypeOpFuncTypeOpMapTypeOpStructTypeOpMaybeNativeType" - _Op_name_6 = "OpAssignOpAddAssignOpSubAssignOpMulAssignOpQuoAssignOpRemAssignOpBandAssignOpBandnAssignOpBorAssignOpXorAssignOpShlAssignOpShrAssignOpDefineOpIncOpDec" + _Op_name_6 = "OpAssignOpDefineOpIncOpDec" _Op_name_7 = "OpValueDeclOpTypeDecl" _Op_name_8 = "OpStickyOpBodyOpForLoopOpRangeIterOpRangeIterStringOpRangeIterMapOpRangeIterArrayPtrOpReturnCallDefers" ) @@ -135,7 +124,7 @@ var ( _Op_index_3 = [...]uint8{0, 6, 15, 23, 31, 41, 48, 54, 59, 72, 85, 99, 113, 123, 133, 144, 152, 163, 172, 181} _Op_index_4 = [...]uint8{0, 18, 36, 55, 69} _Op_index_5 = [...]uint8{0, 11, 22, 33, 46, 61, 71, 81, 90, 102, 119} - _Op_index_6 = [...]uint8{0, 8, 19, 30, 41, 52, 63, 75, 88, 99, 110, 121, 132, 140, 145, 150} + _Op_index_6 = [...]uint8{0, 8, 16, 21, 26} _Op_index_7 = [...]uint8{0, 11, 21} _Op_index_8 = [...]uint8{0, 8, 14, 23, 34, 51, 65, 84, 102} ) @@ -159,7 +148,7 @@ func (i Op) String() string { case 112 <= i && i <= 121: i -= 112 return _Op_name_5[_Op_index_5[i]:_Op_index_5[i+1]] - case 128 <= i && i <= 142: + case 128 <= i && i <= 131: i -= 128 return _Op_name_6[_Op_index_6[i]:_Op_index_6[i+1]] case 144 <= i && i <= 145: diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index c86edb0e515..e6d4d044eaa 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -207,8 +207,20 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { if !defined { panic(fmt.Sprintf("nothing defined in assignment %s", n.String())) } - } else { - // nothing defined. + } else if n.Op.isAssignmentOperation() { + // convert operation and simplify to simple assignment stmt + expr. + if len(n.Lhs) != 1 || len(n.Rhs) != 1 { + panic(fmt.Sprintf("assignment operation %s requires exactly 1 expression on left and right hand side", n.Op)) + } + if nx, ok := n.Lhs[0].(*NameExpr); ok && nx.Name == "_" { + panic(fmt.Sprintf("assignment operation %s cannot have blank identifier on left hand side", n.Op)) + } + n.Rhs[0] = &BinaryExpr{ + Left: n.Lhs[0], + Op: n.Op.convertAssignmentOperation(), + Right: n.Rhs[0], + } + n.Op = ASSIGN } // TRANS_ENTER ----------------------- @@ -1636,12 +1648,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { default: panic("should not happen") } - } else if n.Op == SHL_ASSIGN || n.Op == SHR_ASSIGN { - if len(n.Lhs) != 1 || len(n.Rhs) != 1 { - panic("should not happen") - } - // Special case if shift assign <<= or >>=. - checkOrConvertType(store, last, &n.Rhs[0], UintType, false) } else { // General case: a, b = x, y. for i, lx := range n.Lhs {