aboutsummaryrefslogtreecommitdiff
blob: 6106a65279a2366d3c10f5d071dbcd476f62d0ea (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
import sys
from itertools import izip
from collections import OrderedDict

from rpython.translator.tool.cbuild import ExternalCompilationInfo
from rpython.rlib.rfile import FILEP
from rpython.rtyper.lltypesystem import rffi, lltype
from rpython.rtyper.tool import rfficache, rffi_platform
from rpython.flowspace.model import Constant, const
from rpython.flowspace.specialcase import register_flow_sc
from rpython.flowspace.flowcontext import FlowingError

from . import model
from .cparser import Parser


CNAME_TO_LLTYPE = {
    'char': rffi.CHAR,
    'double': rffi.DOUBLE, 'long double': rffi.LONGDOUBLE,
    'float': rffi.FLOAT, 'FILE': FILEP.TO}

def add_inttypes():
    for name in rffi.TYPES:
        if name.startswith('unsigned'):
            rname = 'u' + name[9:]
        else:
            rname = name
        rname = rname.replace(' ', '').upper()
        CNAME_TO_LLTYPE[name] = rfficache.platform.types[rname]

add_inttypes()
CNAME_TO_LLTYPE['int'] = rffi.INT_real
CNAME_TO_LLTYPE['wchar_t'] = lltype.UniChar
if 'ssize_t' not in CNAME_TO_LLTYPE:  # on Windows
    CNAME_TO_LLTYPE['ssize_t'] = rffi.SIGNED

def cname_to_lltype(name):
    return CNAME_TO_LLTYPE[name]

class DelayedStruct(object):
    def __init__(self, name, fields, TYPE):
        self.struct_name = name
        self.type_name = None
        self.fields = fields
        self.TYPE = TYPE

    def get_type_name(self):
        if self.type_name is not None:
            return self.type_name
        elif not self.struct_name.startswith('$'):
            return 'struct %s' % self.struct_name
        else:
            raise ValueError('Anonymous struct')

    def __repr__(self):
        return "<struct {struct_name}>".format(**vars(self))


class CTypeSpace(object):
    def __init__(self, parser=None, definitions=None, macros=None,
                 headers=None, includes=None):
        self.definitions = definitions if definitions is not None else {}
        self.macros = macros if macros is not None else {}
        self.structs = {}
        self.ctx = parser if parser else Parser()
        self.headers = headers if headers is not None else ['sys/types.h']
        self.parsed_headers = []
        self.sources = []
        self._config_entries = OrderedDict()
        self.includes = []
        self.struct_typedefs = {}
        self._handled = set()
        self._frozen = False
        self._cdecl_type_cache = {}  # {cdecl: TYPE} cache
        if includes is not None:
            for header in includes:
                self.include(header)

    def include(self, other):
        self.ctx.include(other.ctx)
        self.structs.update(other.structs)
        self.includes.append(other)

    def parse_source(self, source, configure=True):
        self.sources.append(source)
        self.ctx.parse(source)
        if configure:
            self.configure_types()

    def parse_header(self, header_path, configure=True):
        self.headers.append(str(header_path))
        self.parsed_headers.append(header_path)
        self.ctx.parse(header_path.read())
        if configure:
            self.configure_types()

    def add_typedef(self, name, obj, quals):
        assert name not in self.definitions
        tp = self.convert_type(obj, quals)
        if isinstance(tp, DelayedStruct):
            if tp.type_name is None:
                tp.type_name = name
            tp = self.realize_struct(tp)
            self.structs[obj.realtype] = tp
        self.definitions[name] = tp

    def add_macro(self, name, value):
        assert name not in self.macros
        self.macros[name] = value

    def add_struct(self, name, obj, quals):
        tp = self.convert_type(obj, quals)
        if isinstance(tp, DelayedStruct):
            tp = self.realize_struct(tp)
        self.structs[obj] = tp

    def new_struct(self, obj):
        if obj.name == '_IO_FILE':  # cffi weirdness
            return cname_to_lltype('FILE')
        struct = DelayedStruct(obj.name, None, lltype.ForwardReference())
        # Cache it early, to avoid infinite recursion
        self.structs[obj] = struct
        if obj.fldtypes is not None:
            struct.fields = zip(
                obj.fldnames,
                [self.convert_field(field) for field in obj.fldtypes])
        return struct

    def convert_field(self, obj):
        tp = self.convert_type(obj)
        if isinstance(tp, DelayedStruct):
            tp = tp.TYPE
        elif isinstance(tp, type) and issubclass(tp, Enum):
            tp = rffi.INT_real
        return tp

    def realize_struct(self, struct):
        type_name = struct.get_type_name()
        if struct.fields is None:
            raise ValueError('Missing definition for %s' % type_name)
        entry = rffi_platform.Struct(type_name, struct.fields)
        self._config_entries[entry] = struct.TYPE
        return struct.TYPE

    def build_eci(self):
        all_sources = []
        for cts in self.includes:
            all_sources.extend(cts.sources)
        all_sources.extend(self.sources)
        all_headers = self.headers
        for x in self.includes:
            for hdr in x.headers:
                if hdr not in all_headers:
                    all_headers.append(hdr)
        if sys.platform == 'win32':
            if sys.maxint > 2**32:
                compile_extra = ['-Dssize_t=__int64']
            else:
                compile_extra = ['-Dssize_t=long']
        else:
            compile_extra = []
        return ExternalCompilationInfo(
            post_include_bits=all_sources, includes=all_headers,
            compile_extra=compile_extra)

    def configure_types(self):
        for name, (obj, quals) in self.ctx._declarations.iteritems():
            if obj in self.ctx._included_declarations:
                continue
            if name in self._handled:
                continue
            self._handled.add(name)
            if name.startswith('typedef '):
                name = name[8:]
                self.add_typedef(name, obj, quals)
            elif name.startswith('macro '):
                name = name[6:]
                self.add_macro(name, obj)
            elif name.startswith('struct '):
                name = name[7:]
                self.add_struct(name, obj, quals)
        if not self._config_entries:
            return
        eci = self.build_eci()
        result = rffi_platform.configure_entries(list(self._config_entries), eci)
        for entry, TYPE in izip(self._config_entries, result):
            # hack: prevent the source from being pasted into common_header.h
            del TYPE._hints['eci']
            self._config_entries[entry].become(TYPE)
        self._config_entries.clear()

    def convert_type(self, obj, quals=0):
        if isinstance(obj, model.DefinedType):
            return self.convert_type(obj.realtype, obj.quals)
        if isinstance(obj, model.PrimitiveType):
            return cname_to_lltype(obj.name)
        elif isinstance(obj, model.StructType):
            if obj in self.structs:
                return self.structs[obj]
            return self.new_struct(obj)
        elif isinstance(obj, model.PointerType):
            TO = self.convert_type(obj.totype)
            if TO is lltype.Void:
                return rffi.VOIDP
            elif isinstance(TO, DelayedStruct):
                TO = TO.TYPE
            if isinstance(TO, lltype.ContainerType):
                return lltype.Ptr(TO)
            else:
                if obj.quals & model.Q_CONST:
                    return lltype.Ptr(lltype.Array(
                        TO, hints={'nolength': True, 'render_as_const': True}))
                else:
                    return rffi.CArrayPtr(TO)
        elif isinstance(obj, model.FunctionPtrType):
            if obj.ellipsis:
                raise NotImplementedError
            args = [self.convert_type(arg) for arg in obj.args]
            res = self.convert_type(obj.result)
            return lltype.Ptr(lltype.FuncType(args, res))
        elif isinstance(obj, model.VoidType):
            return lltype.Void
        elif isinstance(obj, model.ArrayType):
            return rffi.CFixedArray(self.convert_type(obj.item), obj.length)
        elif isinstance(obj, model.EnumType):
            enum = type(obj.forcename, (Enum,), {})
            for key, value in zip(obj.enumerators, obj.enumvalues):
                setattr(enum, key, value)
            return enum
        else:
            raise NotImplementedError

    def gettype(self, cdecl):
        try:
            return self._cdecl_type_cache[cdecl]
        except KeyError:
            result = self._real_gettype(cdecl)
            self._cdecl_type_cache[cdecl] = result
            return result

    def _real_gettype(self, cdecl):
        obj = self.ctx.parse_type(cdecl)
        result = self.convert_type(obj)
        if isinstance(result, DelayedStruct):
            result = result.TYPE
        return result

    def cast(self, cdecl, value):
        return rffi.cast(self.gettype(cdecl), value)

    def parse_func(self, cdecl):
        cdecl = cdecl.strip()
        if cdecl[-1] != ';':
            cdecl += ';'
        ast, _, _ = self.ctx._parse(cdecl)
        decl = ast.ext[-1]
        tp, quals = self.ctx._get_type_and_quals(decl.type, name=decl.name)
        return FunctionDeclaration(decl.name, tp)

    def _freeze_(self):
        if self._frozen:
            return True

        @register_flow_sc(self.cast)
        def sc_cast(ctx, v_decl, v_arg):
            if not isinstance(v_decl, Constant):
                raise FlowingError(
                    "The first argument of cts.cast() must be a constant.")
            TP = self.gettype(v_decl.value)
            return ctx.appcall(rffi.cast, const(TP), v_arg)

        @register_flow_sc(self.gettype)
        def sc_gettype(ctx, v_decl):
            if not isinstance(v_decl, Constant):
                raise FlowingError(
                    "The argument of cts.gettype() must be a constant.")
            return const(self.gettype(v_decl.value))

        self._frozen = True
        return True

class Enum(object):
    pass

class FunctionDeclaration(object):
    def __init__(self, name, tp):
        self.name = name
        self.tp = tp

    def get_llargs(self, cts):
        return [cts.convert_type(arg) for arg in self.tp.args]

    def get_llresult(self, cts):
        return cts.convert_type(self.tp.result)

def parse_source(source, includes=None, headers=None, configure_now=True):
    cts = CTypeSpace(headers=headers, includes=includes)
    cts.parse_source(source)
    return cts