aboutsummaryrefslogtreecommitdiff
path: root/pysmob.c
blob: 98c26f497e22747257e303557a0906c95efa11de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// pysmob implementation
//
////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2008 Omer Zak.
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library, in a file named COPYING; if not, write to the
// Free Software Foundation, Inc., 59 Temple Place, Suite 330,
// Boston, MA  02111-1307  USA
//
// For licensing issues, contact <w1@zak.co.il>.
//
////////////////////////////////////////////////////////////////////////
//
// Implements the Guile SMOB type, which encapsulates Python objects.
// While those SMOBs can be used to encapsulate any PyObject* object,
// they are typically used to encapsulate only class instances.
//
////////////////////////////////////////////////////////////////////////
//
// Note about verbosity handling:
//
// This module uses printf() rather than scm_simple_format & Co. for
// PYGUILE_VERBOSE_GC and PYGUILE_VERBOSE_GC_DETAILED.
// The reason is that scm_* functions cannot be used during garbage
// collection.
//
////////////////////////////////////////////////////////////////////////
#include "pysmob.h"
#include "verbose.h"

#ifndef Py_ssize_t
#define Py_ssize_t int
#endif /* Py_ssize_t */

static scm_t_bits tag_pysmob;

////////////////////////////////////////////////////////////////////////
// Wrapped PyObject memory management
////////////////////////////////////////////////////////////////////////

static PyObject *pdict_wrapped_pyobjects;
// Pointer at Dict, whose keys are addresses of wrapped PyObjects.
// (The keys have to be addresses, because the objects themselves
// can be mutable and hence unsuitable for use as Dict keys.)

//scm_t_c_hook_function clear_pdict_values;
//scm_t_c_hook_function delete_unmarked_pdict_keys;

void *
clear_pdict_values(void *hook_data, void *func_data, void *data)
{
  if (pyguile_verbosity_test(PYGUILE_VERBOSE_GC)) {
    //scm_simple_format(scm_current_output_port(),scm_makfrom0str("# Starting garbage collection (mark phase) - entered clear_pdict_values\n"),SCM_EOL);
    printf("# Starting garbage collection (mark phase) - entered clear_pdict_values\n");
  }
  PyObject *pdict = (PyObject *)func_data;
  if (!PyDict_CheckExact(pdict)) {
    scm_misc_error("clear_pdict_values","invalid pysmobs Dict",SCM_EOL);  // NOT COVERED BY TESTS
  }

  PyObject *key;
  PyObject *value;
  Py_ssize_t pos = 0;
  while(PyDict_Next(pdict,&pos,&key,&value)) {
    Py_INCREF(Py_False);
    if (0 != PyDict_SetItem(pdict,
			    key,
			    Py_False)) {
      Py_DECREF(Py_False);  // NOT COVERED BY TESTS
      scm_misc_error("clear_pdict_values","failed to clear pysmobs Dict",SCM_EOL);  // NOT COVERED BY TESTS
    }
  }
  return(NULL);
}

void *
delete_unmarked_pdict_keys(void *hook_data, void *func_data, void *data)
{
  if (pyguile_verbosity_test(PYGUILE_VERBOSE_GC)) {
    //scm_simple_format(scm_current_output_port(),scm_makfrom0str("# Going to finish garbage collection (sweep phase) - entered delete_unmarked_pdict_keys\n"),SCM_EOL);
    printf("# Going to finish garbage collection (sweep phase) - entered delete_unmarked_pdict_keys\n");
  }
  PyObject *pdict = (PyObject *)func_data;
  if (!PyDict_CheckExact(pdict)) {
    scm_misc_error("delete_unmarked_pdict_keys","invalid pysmobs Dict",SCM_EOL);  // NOT COVERED BY TESTS
  }

  PyObject *pdict_clone = PyDict_Copy(pdict);
  if (NULL == pdict_clone) {
    PyErr_Clear();  // NOT COVERED BY TESTS
    scm_misc_error("delete_ummarked_pdict_keys","failed to prepare for deleting from Dict",SCM_EOL);  // NOT COVERED BY TESTS
  }

  PyObject *key;
  PyObject *value;
  Py_ssize_t pos = 0;
  while(PyDict_Next(pdict_clone,&pos,&key,&value)) {
    if (Py_False == value) {
      Py_INCREF(key);
      if (0 != PyDict_DelItem(pdict,key)) {
	PyErr_Clear();  // NOT COVERED BY TESTS
	Py_DECREF(key);  // NOT COVERED BY TESTS
	Py_DECREF(pdict_clone);  // NOT COVERED BY TESTS
	scm_misc_error("delete_ummarked_pdict_keys","failed to delete item from Dict",SCM_EOL);  // NOT COVERED BY TESTS
      }
      long pptr = PyLong_AsLong(key);
      Py_DECREF((PyObject *)pptr);  // Actually delete (if necessary) the formerly-wrapped PyObject.
      Py_DECREF(key);
      if (pyguile_verbosity_test(PYGUILE_VERBOSE_GC_DETAILED)) {
	//scm_simple_format(scm_current_output_port(),scm_makfrom0str("# delete_unmarked_pdict_keys: deleting ~S\n"),scm_list_1(scm_long2num(pptr)));
	printf("# delete_unmarked_pdict_keys: deleting 0x%08lX\n",pptr);
      }
    }
  }
  Py_DECREF(pdict_clone);
  return(NULL);
}


////////////////////////////////////////////////////////////////////////
// Private functions
////////////////////////////////////////////////////////////////////////

static SCM
mark_pysmob(SCM psmob)
{
  // New system of garbage collection:
  // Each time we wrap a PyObject, we add its address, as a key, to
  // a Python Dict, which will serve as hash table.
  //
  // Python facilities will be used for this purpose.
  //
  // When starting the mark phase, we clear all values in the
  // aforementioned Python Dict.
  //
  // For each pysmob to be marked, we mark the corresponding Python
  // Dict value.
  //
  // At end of the sweep phase, we delete from the Python Dict any
  // items not marked.
  // The reference counting will be maintained by ownership by the
  // Python Dict.  Guile will not deal with reference counting
  // related to pysmobs at all.

  long key = (long) unwrap_pysmob(psmob);
  PyObject *pkey = PyLong_FromLong(key);
  if (NULL == pkey) {
    PyErr_Clear();  // NOT COVERED BY TESTS
    scm_memory_error("mark_pysmob");  // NOT COVERED BY TESTS
  }
  int ret = PyDict_Contains(pdict_wrapped_pyobjects,pkey);
  if (-1 == ret) {
    PyErr_Clear();  // NOT COVERED BY TESTS
    Py_DECREF(pkey);  // NOT COVERED BY TESTS
    scm_misc_error("mark_pysmob","error when checking pysmobs Dict for ~S",scm_list_1(psmob));  // NOT COVERED BY TESTS
  }
  if (0 == ret) {
    Py_DECREF(pkey);
    scm_misc_error("mark_pysmob","smob not found in pysmobs Dict: ~S",scm_list_1(psmob));
  }
  Py_INCREF(Py_True);
  if (0 != PyDict_SetItem(pdict_wrapped_pyobjects,
			  pkey,
			  Py_True)) {
    Py_DECREF(pkey);
    scm_misc_error("mark_pysmob","failed to mark pysmob ~S",scm_list_1(psmob));
  }
  if (pyguile_verbosity_test(PYGUILE_VERBOSE_GC_DETAILED)) {
    //scm_simple_format(scm_current_output_port(),scm_makfrom0str("# mark_pysmob: marking ~S\n"),scm_list_1(scm_long2num(key)));
    printf("# mark_pysmob: marking 0x%08lX\n",key);
  }

  // scm_gc_mark(any SCM object referred to by the Python object);
  // One SCM object is returned to the caller (who will mark it).
  return(SCM_UNSPECIFIED);  // No need to mark any SCM object for now
  // (until PyObjects learn to hold wrapped SCM objects).
}


//static size_t
//free_pysmob(SCM psmob)
//{
//  PyObject *pobj = unwrap_pysmob(psmob);
//  if (pyguile_verbosity_test(PYGUILE_VERBOSE_GC_DETAILED)) {
//    scm_simple_format(scm_current_output_port(),scm_makfrom0str("# free_pysmob: freeing object ~S\n"),scm_list_1(scm_long2num((long)pobj)));
//  }
//  Py_XDECREF(pobj);
//  return(0);
//}

static int
print_pysmob(SCM smob, SCM port, scm_print_state* prstate)
{
  if (!SCM_SMOB_PREDICATE(tag_pysmob,smob)) {
    scm_wrong_type_arg("print-pysmob",SCM_ARG1,smob);  // NOT COVERED BY TESTS
  }
  if (!SCM_PORTP(port)) {
    scm_wrong_type_arg("print-pysmob",SCM_ARG2,port);  // NOT COVERED BY TESTS
  }
  // I don't know how to validate the 3rd argument.
  PyObject *prepr = PyObject_Repr(unwrap_pysmob(smob));
  if (NULL != prepr) {
    char *pstr = PyString_AsString(prepr);
    scm_puts("(python-eval ",port);
    scm_puts(pstr, port);
    scm_puts(" #t)",port);
    Py_DECREF(prepr);       // also invalidates pstr.
  }
  else {
    scm_misc_error("print-pysmob","repr(~S) failure",scm_list_1(smob));  // NOT COVERED BY TESTS
    //scm_puts("*nil*", port);
  }
  return(1);  // Nonzero means success.
}

static SCM
equalp_pysmob(SCM smob1, SCM smob2)
{
  if (!SCM_SMOB_PREDICATE(tag_pysmob,smob1)) {
    scm_wrong_type_arg("equalp-pysmob",SCM_ARG1,smob1);
  }
  if (!SCM_SMOB_PREDICATE(tag_pysmob,smob2)) {
    scm_wrong_type_arg("equalp-pysmob",SCM_ARG2,smob2);
  }
  PyObject *pobj1 = unwrap_pysmob(smob1);
  if (NULL == pobj1) {
    scm_misc_error("equalp-pysmob","argument 1 (~S) unwrapping failure",  // NOT COVERED BY TESTS
		   scm_list_1(smob1));
  }
  PyObject *pobj2 = unwrap_pysmob(smob2);
  if (NULL == pobj2) {
    scm_misc_error("equalp-pysmob","argument 2 (~S) unwrapping failure",  // NOT COVERED BY TESTS
		   scm_list_1(smob2));
  }
  switch (PyObject_RichCompareBool(pobj1,pobj2,Py_EQ)) {
  case 0:
    return(SCM_BOOL_F);
  case 1:
    return(SCM_BOOL_T);
  case -1:
  default:
    // Error.
    scm_misc_error("equalp-pysmob","comparison failure ~S vs. ~S",
		   scm_list_2(smob1,smob2));
    return(SCM_UNDEFINED);
  }
}

////////////////////////////////////////////////////////////////////////
// Public functions
////////////////////////////////////////////////////////////////////////


// Return nonzero if sobj is of type pysmob.
int
IS_PYSMOBP(SCM sobj)
{
  return(SCM_SMOB_PREDICATE(tag_pysmob,sobj));
}

// Create a pysmob corresponding to a PyObject.
SCM
wrap_pyobject(PyObject *pobj)
{
  if (NULL == pobj) {
    scm_misc_error("wrap-pyobject","NULL PyObject",SCM_EOL);  // NOT COVERED BY TESTS
    //return(SCM_UNSPECIFIED);
  }
  else {
    if (pyguile_verbosity_test(PYGUILE_VERBOSE_NEW_PYSMOB)) {
      scm_simple_format(scm_current_output_port(),scm_makfrom0str("# wrap_pyobject: new wrapped object ~S\n"),scm_list_1(scm_long2num((long)pobj)));
    }
    Py_INCREF(pobj);
    Py_INCREF(Py_True);
    PyDict_SetItem(pdict_wrapped_pyobjects,
		   PyLong_FromLong((long)pobj),
		   Py_True);
    SCM_RETURN_NEWSMOB(tag_pysmob,pobj);
  }
}

// Provide reference to PyObject embedded in a pysmob.
// No ownership transfer is implied.
PyObject *
unwrap_pysmob(SCM sobj)
{
  if (!SCM_SMOB_PREDICATE(tag_pysmob,sobj)) {
    scm_wrong_type_arg("unwrap-pysmob",SCM_ARG1,sobj);  // NOT COVERED BY TESTS
  }
  PyObject *pobj = (PyObject *)SCM_SMOB_DATA(sobj);
  if (pyguile_verbosity_test(PYGUILE_VERBOSE_UNWRAP_PYSMOB)) {
    // The scm_simple_format code can safely be invoked if we know
    // that we are not inside garbage collection.
    // TODO:  invoke a flag for this purpose.
    //scm_simple_format(scm_current_output_port(),scm_makfrom0str("# unwrap_pysmob: accessing object ~S\n"),scm_list_1(scm_long2num((long)pobj)));
    printf("# unwrap_pysmob: accessing object 0x%08lX\n",(long)pobj);
  }
  return(pobj);
}

////////////////////////////////////////////////////////////////////////

void finalize_pysmob_type(void)
{
  Py_DECREF(pdict_wrapped_pyobjects);
}

void init_pysmob_type(void)
{
  tag_pysmob = scm_make_smob_type("pysmob",0);
  scm_set_smob_mark (tag_pysmob, mark_pysmob);
  //scm_set_smob_free(tag_pysmob,free_pysmob);  // does nothing in the new scheme of managing PyObjects during garbage collection.
  scm_set_smob_print(tag_pysmob,print_pysmob);
  scm_set_smob_equalp(tag_pysmob,equalp_pysmob);

  // Py_Initialize must have already been invoked
  // by init_pysmob_type()'s caller.
  pdict_wrapped_pyobjects = PyDict_New();
  if (atexit(finalize_pysmob_type)) {
    fprintf(stderr,"cannot set pysmob finalization function\n");  // NOT COVERED BY TESTS
    exit(1);  // NOT COVERED BY TESTS
  }

  // Register garbage collection hooks.
  scm_c_hook_add(&scm_before_mark_c_hook,&clear_pdict_values,
		 (void *)pdict_wrapped_pyobjects,0);
  scm_c_hook_add(&scm_after_sweep_c_hook,&delete_unmarked_pdict_keys,
		 (void *)pdict_wrapped_pyobjects,0);
}

// End of pysmob.c