Skip to content
This repository was archived by the owner on Mar 23, 2023. It is now read-only.

Commit f1446cd

Browse files
authored
Make __dict__ modifiable (#331)
This requires serializing access to Object.dict via atomic operations. From some quick benchmarking, this does not seem to make a significant difference to attribute access times.
1 parent 0bb0709 commit f1446cd

File tree

11 files changed

+129
-40
lines changed

11 files changed

+129
-40
lines changed

runtime/builtin_types.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -342,19 +342,13 @@ func builtinDir(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException) {
342342
}
343343
d := NewDict()
344344
o := args[0]
345-
if o.dict != nil {
346-
raised := seqForEach(f, o.dict.ToObject(), func(k *Object) *BaseException {
347-
return d.SetItem(f, k, None)
348-
})
349-
if raised != nil {
345+
if dict := o.Dict(); dict != nil {
346+
if raised := d.Update(f, dict.ToObject()); raised != nil {
350347
return nil, raised
351348
}
352349
}
353350
for _, t := range o.typ.mro {
354-
raised := seqForEach(f, t.dict.ToObject(), func(k *Object) *BaseException {
355-
return d.SetItem(f, k, None)
356-
})
357-
if raised != nil {
351+
if raised := d.Update(f, t.Dict().ToObject()); raised != nil {
358352
return nil, raised
359353
}
360354
}

runtime/builtin_types_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func TestBuiltinDelAttr(t *testing.T) {
5353

5454
func TestBuiltinFuncs(t *testing.T) {
5555
f := NewRootFrame()
56-
objectDir := ObjectType.dict.Keys(f)
56+
objectDir := ObjectType.Dict().Keys(f)
5757
objectDir.Sort(f)
5858
fooType := newTestClass("Foo", []*Type{ObjectType}, newStringDict(map[string]*Object{"bar": None}))
5959
fooTypeDir := NewList(objectDir.elems...)

runtime/core_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,20 @@ func TestToNative(t *testing.T) {
11641164
}
11651165
}
11661166

1167+
func BenchmarkGetAttr(b *testing.B) {
1168+
f := NewRootFrame()
1169+
attr := NewStr("bar")
1170+
fooType := newTestClass("Foo", []*Type{ObjectType}, NewDict())
1171+
foo := newObject(fooType)
1172+
if raised := SetAttr(f, foo, attr, NewInt(123).ToObject()); raised != nil {
1173+
panic(raised)
1174+
}
1175+
b.ResetTimer()
1176+
for i := 0; i < b.N; i++ {
1177+
mustNotRaise(GetAttr(f, foo, attr, nil))
1178+
}
1179+
}
1180+
11671181
// SetAttr is tested in TestObjectSetAttr.
11681182

11691183
func exceptionsAreEquivalent(e1 *BaseException, e2 *BaseException) bool {

runtime/frame.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ func (f *Frame) release() {
7070
// TODO: Track cache depth and release memory.
7171
f.frameCache, f.back = f, f.frameCache
7272
// Clear pointers early.
73-
f.dict = nil
73+
f.setDict(nil)
7474
f.globals = nil
7575
f.code = nil
7676
} else if f.back != nil {

runtime/native.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ func getNativeType(rtype reflect.Type) *Type {
434434
d[name] = newNativeField(name, i, t)
435435
}
436436
}
437-
t.dict = newStringDict(d)
437+
t.setDict(newStringDict(d))
438438
// This cannot fail since we're defining simple classes.
439439
if err := prepareType(t); err != "" {
440440
logFatal(err)

runtime/native_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -479,11 +479,11 @@ func TestNewNativeFieldChecksInstanceType(t *testing.T) {
479479
}
480480

481481
// When its field property is assigned to a different type
482-
property, raised := native.typ.dict.GetItemString(f, "foo")
482+
property, raised := native.typ.Dict().GetItemString(f, "foo")
483483
if raised != nil {
484484
t.Fatal("Unexpected exception:", raised)
485485
}
486-
if raised := IntType.dict.SetItemString(f, "foo", property); raised != nil {
486+
if raised := IntType.Dict().SetItemString(f, "foo", property); raised != nil {
487487
t.Fatal("Unexpected exception:", raised)
488488
}
489489

runtime/object.go

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package grumpy
1717
import (
1818
"fmt"
1919
"reflect"
20+
"sync/atomic"
2021
"unsafe"
2122
)
2223

@@ -39,7 +40,7 @@ var (
3940
// Object represents Python 'object' objects.
4041
type Object struct {
4142
typ *Type `attr:"__class__"`
42-
dict *Dict `attr:"__dict__"`
43+
dict *Dict
4344
ref *WeakRef
4445
}
4546

@@ -50,7 +51,7 @@ func newObject(t *Type) *Object {
5051
}
5152
o := (*Object)(unsafe.Pointer(reflect.New(t.basis).Pointer()))
5253
o.typ = t
53-
o.dict = dict
54+
o.setDict(dict)
5455
return o
5556
}
5657

@@ -66,7 +67,13 @@ func (o *Object) Call(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseExcepti
6667

6768
// Dict returns o's object dict, aka __dict__.
6869
func (o *Object) Dict() *Dict {
69-
return o.dict
70+
p := (*unsafe.Pointer)(unsafe.Pointer(&o.dict))
71+
return (*Dict)(atomic.LoadPointer(p))
72+
}
73+
74+
func (o *Object) setDict(d *Dict) {
75+
p := (*unsafe.Pointer)(unsafe.Pointer(&o.dict))
76+
atomic.StorePointer(p, unsafe.Pointer(d))
7077
}
7178

7279
// String returns a string representation of o, e.g. for debugging.
@@ -109,8 +116,9 @@ func objectDelAttr(f *Frame, o *Object, name *Str) *BaseException {
109116
}
110117
}
111118
deleted := false
112-
if o.dict != nil {
113-
deleted, raised = o.dict.DelItem(f, name.ToObject())
119+
d := o.Dict()
120+
if d != nil {
121+
deleted, raised = d.DelItem(f, name.ToObject())
114122
if raised != nil {
115123
return raised
116124
}
@@ -138,7 +146,7 @@ func objectGetAttribute(f *Frame, o *Object, name *Str) (*Object, *BaseException
138146
}
139147
}
140148
// Look in the object's dict.
141-
if d := o.dict; d != nil {
149+
if d := o.Dict(); d != nil {
142150
value, raised := d.GetItem(f, name.ToObject())
143151
if value != nil || raised != nil {
144152
return value, raised
@@ -208,8 +216,8 @@ func objectSetAttr(f *Frame, o *Object, name *Str, value *Object) *BaseException
208216
return typeSet.Fn(f, typeAttr, o, value)
209217
}
210218
}
211-
if o.dict != nil {
212-
if raised := o.dict.SetItem(f, name.ToObject(), value); raised == nil || !raised.isInstance(KeyErrorType) {
219+
if d := o.Dict(); d != nil {
220+
if raised := d.SetItem(f, name.ToObject(), value); raised == nil || !raised.isInstance(KeyErrorType) {
213221
return nil
214222
}
215223
}
@@ -220,6 +228,7 @@ func initObjectType(dict map[string]*Object) {
220228
ObjectType.typ = TypeType
221229
dict["__reduce__"] = objectReduceFunc
222230
dict["__reduce_ex__"] = newBuiltinFunction("__reduce_ex__", objectReduceEx).ToObject()
231+
dict["__dict__"] = newProperty(newBuiltinFunction("_get_dict", objectGetDict).ToObject(), newBuiltinFunction("_set_dict", objectSetDict).ToObject(), nil).ToObject()
223232
ObjectType.slots.DelAttr = &delAttrSlot{objectDelAttr}
224233
ObjectType.slots.GetAttribute = &getAttributeSlot{objectGetAttribute}
225234
ObjectType.slots.Hash = &unaryOpSlot{objectHash}
@@ -306,8 +315,8 @@ func objectReduceCommon(f *Frame, args Args) (*Object, *BaseException) {
306315
newArgs = append(newArgs, toTupleUnsafe(extraNewArgs).elems...)
307316
}
308317
dict := None
309-
if o.dict != nil {
310-
dict = o.dict.ToObject()
318+
if d := o.Dict(); d != nil {
319+
dict = d.ToObject()
311320
}
312321
// For proto >= 2 include list and dict items.
313322
listItems := None
@@ -334,3 +343,29 @@ func objectReduceCommon(f *Frame, args Args) (*Object, *BaseException) {
334343
}
335344
return NewTuple5(newFunc, NewTuple(newArgs...).ToObject(), dict, listItems, dictItems).ToObject(), nil
336345
}
346+
347+
func objectGetDict(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
348+
if raised := checkMethodArgs(f, "_get_dict", args, ObjectType); raised != nil {
349+
return nil, raised
350+
}
351+
o := args[0]
352+
d := o.Dict()
353+
if d == nil {
354+
format := "'%s' object has no attribute '__dict__'"
355+
return nil, f.RaiseType(AttributeErrorType, fmt.Sprintf(format, o.typ.Name()))
356+
}
357+
return args[0].Dict().ToObject(), nil
358+
}
359+
360+
func objectSetDict(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
361+
if raised := checkMethodArgs(f, "_set_dict", args, ObjectType, DictType); raised != nil {
362+
return nil, raised
363+
}
364+
o := args[0]
365+
if o.Type() == ObjectType {
366+
format := "'%s' object has no attribute '__dict__'"
367+
return nil, f.RaiseType(AttributeErrorType, fmt.Sprintf(format, o.typ.Name()))
368+
}
369+
o.setDict(toDictUnsafe(args[1]))
370+
return None, nil
371+
}

runtime/object_test.go

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func TestObjectDelAttr(t *testing.T) {
105105
})
106106
dellerType := newTestClass("Deller", []*Type{ObjectType}, newStringDict(map[string]*Object{
107107
"__get__": newBuiltinFunction("__get__", func(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException) {
108-
attr, raised := args[1].dict.GetItemString(f, "attr")
108+
attr, raised := args[1].Dict().GetItemString(f, "attr")
109109
if raised != nil {
110110
return nil, raised
111111
}
@@ -115,7 +115,7 @@ func TestObjectDelAttr(t *testing.T) {
115115
return attr, nil
116116
}).ToObject(),
117117
"__delete__": newBuiltinFunction("__delete__", func(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException) {
118-
deleted, raised := args[1].dict.DelItemString(f, "attr")
118+
deleted, raised := args[1].Dict().DelItemString(f, "attr")
119119
if raised != nil {
120120
return nil, raised
121121
}
@@ -127,7 +127,7 @@ func TestObjectDelAttr(t *testing.T) {
127127
}))
128128
fooType := newTestClass("Foo", []*Type{ObjectType}, newStringDict(map[string]*Object{"deller": newObject(dellerType)}))
129129
foo := newObject(fooType)
130-
if raised := foo.dict.SetItemString(NewRootFrame(), "attr", NewInt(123).ToObject()); raised != nil {
130+
if raised := foo.Dict().SetItemString(NewRootFrame(), "attr", NewInt(123).ToObject()); raised != nil {
131131
t.Fatal(raised)
132132
}
133133
cases := []invokeTestCase{
@@ -180,13 +180,13 @@ func TestObjectGetAttribute(t *testing.T) {
180180
"barsetter": setter,
181181
}))
182182
foo := newObject(fooType)
183-
if raised := foo.dict.SetItemString(NewRootFrame(), "fooattr", True.ToObject()); raised != nil {
183+
if raised := foo.Dict().SetItemString(NewRootFrame(), "fooattr", True.ToObject()); raised != nil {
184184
t.Fatal(raised)
185185
}
186-
if raised := foo.dict.SetItemString(NewRootFrame(), "barattr", NewInt(-1).ToObject()); raised != nil {
186+
if raised := foo.Dict().SetItemString(NewRootFrame(), "barattr", NewInt(-1).ToObject()); raised != nil {
187187
t.Fatal(raised)
188188
}
189-
if raised := foo.dict.SetItemString(NewRootFrame(), "barsetter", NewStr("NOT setter").ToObject()); raised != nil {
189+
if raised := foo.Dict().SetItemString(NewRootFrame(), "barsetter", NewStr("NOT setter").ToObject()); raised != nil {
190190
t.Fatal(raised)
191191
}
192192
cases := []invokeTestCase{
@@ -205,6 +205,52 @@ func TestObjectGetAttribute(t *testing.T) {
205205
}
206206
}
207207

208+
func TestObjectGetDict(t *testing.T) {
209+
fooType := newTestClass("Foo", []*Type{ObjectType}, NewDict())
210+
foo := newObject(fooType)
211+
if raised := SetAttr(NewRootFrame(), foo, NewStr("bar"), NewInt(123).ToObject()); raised != nil {
212+
panic(raised)
213+
}
214+
fun := wrapFuncForTest(func(f *Frame, o *Object) (*Object, *BaseException) {
215+
return GetAttr(f, o, NewStr("__dict__"), nil)
216+
})
217+
cases := []invokeTestCase{
218+
{args: wrapArgs(newObject(ObjectType)), wantExc: mustCreateException(AttributeErrorType, "'object' object has no attribute '__dict__'")},
219+
{args: wrapArgs(newObject(fooType)), want: NewDict().ToObject()},
220+
{args: wrapArgs(foo), want: newStringDict(map[string]*Object{"bar": NewInt(123).ToObject()}).ToObject()},
221+
}
222+
for _, cas := range cases {
223+
if err := runInvokeTestCase(fun, &cas); err != "" {
224+
t.Error(err)
225+
}
226+
}
227+
}
228+
229+
func TestObjectSetDict(t *testing.T) {
230+
fooType := newTestClass("Foo", []*Type{ObjectType}, NewDict())
231+
testDict := newStringDict(map[string]*Object{"bar": NewInt(123).ToObject()})
232+
fun := wrapFuncForTest(func(f *Frame, o, val *Object) (*Object, *BaseException) {
233+
if raised := SetAttr(f, o, NewStr("__dict__"), val); raised != nil {
234+
return nil, raised
235+
}
236+
d := o.Dict()
237+
if d == nil {
238+
return None, nil
239+
}
240+
return d.ToObject(), nil
241+
})
242+
cases := []invokeTestCase{
243+
{args: wrapArgs(newObject(ObjectType), NewDict()), wantExc: mustCreateException(AttributeErrorType, "'object' object has no attribute '__dict__'")},
244+
{args: wrapArgs(newObject(fooType), testDict), want: testDict.ToObject()},
245+
{args: wrapArgs(newObject(fooType), 123), wantExc: mustCreateException(TypeErrorType, "'_set_dict' requires a 'dict' object but received a 'int'")},
246+
}
247+
for _, cas := range cases {
248+
if err := runInvokeTestCase(fun, &cas); err != "" {
249+
t.Error(err)
250+
}
251+
}
252+
}
253+
208254
func TestObjectNew(t *testing.T) {
209255
foo := makeTestType("Foo", ObjectType)
210256
foo.flags &= ^typeFlagInstantiable
@@ -335,7 +381,7 @@ func TestObjectSetAttr(t *testing.T) {
335381
})
336382
setterType := newTestClass("Setter", []*Type{ObjectType}, newStringDict(map[string]*Object{
337383
"__get__": newBuiltinFunction("__get__", func(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException) {
338-
item, raised := args[1].dict.GetItemString(f, "attr")
384+
item, raised := args[1].Dict().GetItemString(f, "attr")
339385
if raised != nil {
340386
return nil, raised
341387
}
@@ -345,7 +391,7 @@ func TestObjectSetAttr(t *testing.T) {
345391
return item, nil
346392
}).ToObject(),
347393
"__set__": newBuiltinFunction("__set__", func(f *Frame, args Args, kwargs KWArgs) (*Object, *BaseException) {
348-
if raised := args[1].dict.SetItemString(f, "attr", NewTuple(args.makeCopy()...).ToObject()); raised != nil {
394+
if raised := args[1].Dict().SetItemString(f, "attr", NewTuple(args.makeCopy()...).ToObject()); raised != nil {
349395
return nil, raised
350396
}
351397
return None, nil

runtime/super.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func superGetAttribute(f *Frame, o *Object, name *Str) (*Object, *BaseException)
7474
}
7575
// Now do normal mro lookup from the successor type.
7676
for ; i < n; i++ {
77-
dict := mro[i].dict
77+
dict := mro[i].Dict()
7878
res, raised := dict.GetItem(f, name.ToObject())
7979
if raised != nil {
8080
return nil, raised

runtime/type.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ func prepareBuiltinType(typ *Type, init builtinTypeInit) {
172172
}
173173
}
174174
}
175-
typ.dict = newStringDict(dict)
175+
typ.setDict(newStringDict(dict))
176176
if err := prepareType(typ); err != "" {
177177
logFatal(err)
178178
}
@@ -287,7 +287,7 @@ func (t *Type) Name() string {
287287

288288
// FullName returns t's fully qualified name including the module.
289289
func (t *Type) FullName(f *Frame) (string, *BaseException) {
290-
moduleAttr, raised := t.dict.GetItemString(f, "__module__")
290+
moduleAttr, raised := t.Dict().GetItemString(f, "__module__")
291291
if raised != nil {
292292
return "", raised
293293
}
@@ -313,7 +313,7 @@ func (t *Type) isSubclass(super *Type) bool {
313313

314314
func (t *Type) mroLookup(f *Frame, name *Str) (*Object, *BaseException) {
315315
for _, t := range t.mro {
316-
v, raised := t.dict.GetItem(f, name.ToObject())
316+
v, raised := t.Dict().GetItem(f, name.ToObject())
317317
if v != nil || raised != nil {
318318
return v, raised
319319
}

0 commit comments

Comments
 (0)