Path: blob/develop/src/sage_setup/autogen/interpreters/internal/generator.py
4086 views
# ***************************************************************************1# Copyright (C) 2009 Carl Witty <[email protected]>2# Copyright (C) 2015 Jeroen Demeyer <[email protected]>3#4# This program is free software: you can redistribute it and/or modify5# it under the terms of the GNU General Public License as published by6# the Free Software Foundation, either version 2 of the License, or7# (at your option) any later version.8# https://www.gnu.org/licenses/9# ***************************************************************************1011"""Implements the generic interpreter generator."""1213from __future__ import print_function, absolute_import1415from collections import defaultdict16from io import StringIO1718from .memory import string_of_addr19from .utils import je, indent_lines, reindent_lines as ri202122AUTOGEN_WARN = "Automatically generated by {}. Do not edit!".format(__file__)232425class InterpreterGenerator:26r"""27This class takes an InterpreterSpec and generates the corresponding28C interpreter and Cython wrapper.2930See the documentation for methods get_wrapper and get_interpreter31for more information.32"""3334def __init__(self, spec):35r"""36Initialize an InterpreterGenerator.3738INPUT:3940- ``spec`` -- an InterpreterSpec4142EXAMPLES::4344sage: from sage_setup.autogen.interpreters.internal import *45sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter46sage: interp = RDFInterpreter()47sage: gen = InterpreterGenerator(interp)48sage: gen._spec is interp49True50sage: gen.uses_error_handler51False52"""53self._spec = spec54self.uses_error_handler = False5556def gen_code(self, instr_desc, write):57r"""58Generate code for a single instruction.5960INPUT:6162- instr_desc -- an InstrSpec63- write -- a Python callable6465This function calls its write parameter successively with66strings; when these strings are concatenated, the result is67the code for the given instruction.6869See the documentation for the get_interpreter method for more70information.7172EXAMPLES::7374sage: from sage_setup.autogen.interpreters.internal import *75sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter76sage: interp = RDFInterpreter()77sage: gen = InterpreterGenerator(interp)78sage: from io import StringIO79sage: buff = StringIO()80sage: instrs = dict([(ins.name, ins) for ins in interp.instr_descs])81sage: gen.gen_code(instrs['div'], buff.write)82sage: print(buff.getvalue())83case ...: /* div */84{85double i1 = *--stack;86double i0 = *--stack;87double o0;88o0 = i0 / i1;89*stack++ = o0;90}91break;92<BLANKLINE>93"""9495d = instr_desc96w = write9798if d.uses_error_handler:99self.uses_error_handler = True100101w(je(ri(4, """102case {{ d.opcode }}: /* {{ d.name }} */103{104"""), d=d))105106# If the inputs to an instruction come from the stack,107# then we want to generate code for the inputs in reverse order:108# for instance, the divide instruction, which takes inputs A and B109# and generates A/B, needs to pop B off the stack first.110# On the other hand, if the inputs come from the constant pool,111# then we want to generate code for the inputs in normal order,112# because the addresses in the code stream will be in that order.113# We handle this by running through the inputs in two passes:114# first a forward pass, where we handle non-stack inputs115# (and lengths for stack inputs), and then a reverse pass,116# where we handle stack inputs.117for i in range(len(d.inputs)):118(ch, addr, input_len) = d.inputs[i]119chst = ch.storage_type120if addr is not None:121w(" int ai%d = %s;\n" % (i, string_of_addr(addr)))122if input_len is not None:123w(" int n_i%d = %s;\n" % (i, string_of_addr(input_len)))124if not ch.is_stack():125# Shouldn't hardcode 'code' here126if ch.name == 'code':127w(" %s i%d = %s;\n" % (chst.c_local_type(), i, string_of_addr(ch)))128elif input_len is not None:129w(" %s i%d = %s + ai%d;\n" %130(chst.c_ptr_type(), i, ch.name, i))131else:132w(" %s i%d = %s[ai%d];\n" %133(chst.c_local_type(), i, ch.name, i))134135for i in reversed(range(len(d.inputs))):136(ch, addr, input_len) = d.inputs[i]137chst = ch.storage_type138if ch.is_stack():139if input_len is not None:140w(" %s -= n_i%d;\n" % (ch.name, i))141w(" %s i%d = %s;\n" % (chst.c_ptr_type(), i, ch.name))142else:143w(" %s i%d = *--%s;\n" % (chst.c_local_type(), i, ch.name))144if ch.is_python_refcounted_stack():145w(" *%s = NULL;\n" % ch.name)146147for i in range(len(d.outputs)):148(ch, addr, output_len) = d.outputs[i]149chst = ch.storage_type150if addr is not None:151w(" int ao%d = %s;\n" % (i, string_of_addr(addr)))152if output_len is not None:153w(" int n_o%d = %s;\n" % (i, string_of_addr(output_len)))154if ch.is_stack():155w(" %s o%d = %s;\n" %156(chst.c_ptr_type(), i, ch.name))157w(" %s += n_o%d;\n" % (ch.name, i))158else:159w(" %s o%d = %s + ao%d;\n" %160(chst.c_ptr_type(), i, ch.name, i))161else:162if not chst.cheap_copies():163if ch.is_stack():164w(" %s o%d = *%s++;\n" %165(chst.c_local_type(), i, ch.name))166else:167w(" %s o%d = %s[ao%d];\n" %168(chst.c_local_type(), i, ch.name, i))169else:170w(" %s o%d;\n" % (chst.c_local_type(), i))171w(indent_lines(8, d.code.rstrip('\n') + '\n'))172173stack_offsets = defaultdict(int)174for i in range(len(d.inputs)):175(ch, addr, input_len) = d.inputs[i]176if ch.is_python_refcounted_stack() and not d.handles_own_decref:177if input_len is None:178w(" Py_DECREF(i%d);\n" % i)179stack_offsets[ch] += 1180else:181w(je(ri(8, """182int {{ iter }};183for ({{ iter }} = 0; {{ iter }} < n_i{{ i }}; {{ iter }}++) {184Py_CLEAR(i{{ i }}[{{ iter }}]);185}186"""), iter='_interp_iter_%d' % i, i=i))187188for i in range(len(d.outputs)):189ch = d.outputs[i][0]190chst = ch.storage_type191if chst.python_refcounted():192# We don't yet support code chunks193# that produce multiple Python values, because of194# the way it complicates error handling.195assert i == 0196w(" if (!CHECK(o%d)) {\n" % i)197w(" Py_XDECREF(o%d);\n" % i)198w(" goto error;\n")199w(" }\n")200self.uses_error_handler = True201if chst.cheap_copies():202if ch.is_stack():203w(" *%s++ = o%d;\n" % (ch.name, i))204else:205w(" %s[ao%d] = o%d;\n" % (ch.name, i, i))206207w(je(ri(6,208"""\209}210break;211""")))212213def func_header(self, cython=False):214r"""215Generates the function header for the declaration (in the Cython216wrapper) or the definition (in the C interpreter) of the interpreter217function.218219EXAMPLES::220221sage: from sage_setup.autogen.interpreters.internal import *222sage: from sage_setup.autogen.interpreters.internal.specs.element import ElementInterpreter223sage: interp = ElementInterpreter()224sage: gen = InterpreterGenerator(interp)225sage: print(gen.func_header())226PyObject* interp_el(PyObject** args,227PyObject** constants,228PyObject** stack,229PyObject* domain,230int* code)231sage: print(gen.func_header(cython=True))232object interp_el(PyObject** args,233PyObject** constants,234PyObject** stack,235PyObject* domain,236int* code)237"""238s = self._spec239ret_ty = 'bint' if cython else 'int'240if s.return_type:241ret_ty = s.return_type.c_decl_type()242if cython:243ret_ty = s.return_type.cython_decl_type()244return je(ri(0, """\245{{ ret_ty }} interp_{{ s.name }}(246{%- for ch in s.chunks %}247{% if not loop.first %},248{% endif %}{{ ch.declare_parameter() }}249{%- endfor %})"""), ret_ty=ret_ty, s=s)250251def write_interpreter(self, write):252r"""253Generate the code for the C interpreter.254255This function calls its write parameter successively with256strings; when these strings are concatenated, the result is257the code for the interpreter.258259See the documentation for the get_interpreter method for more260information.261262EXAMPLES::263264sage: from sage_setup.autogen.interpreters.internal import *265sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter266sage: interp = RDFInterpreter()267sage: gen = InterpreterGenerator(interp)268sage: from io import StringIO269sage: buff = StringIO()270sage: gen.write_interpreter(buff.write)271sage: print(buff.getvalue())272/* Automatically generated by ...273"""274s = self._spec275w = write276w(je(ri(0, """277/* {{ warn }} */278#include <Python.h>279280{% print(s.c_header) %}281282{{ myself.func_header() }} {283while (1) {284switch (*code++) {285"""), s=s, myself=self, i=indent_lines, warn=AUTOGEN_WARN))286287for instr_desc in s.instr_descs:288self.gen_code(instr_desc, w)289w(je(ri(0, """290}291}292{% if myself.uses_error_handler %}293error:294return {{ s.err_return }};295{% endif %}296}297298"""), s=s, i=indent_lines, myself=self))299300def write_wrapper(self, write):301r"""302Generate the code for the Cython wrapper.303This function calls its write parameter successively with304strings; when these strings are concatenated, the result is305the code for the wrapper.306307See the documentation for the get_wrapper method for more308information.309310EXAMPLES::311312sage: from sage_setup.autogen.interpreters.internal import *313sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter314sage: interp = RDFInterpreter()315sage: gen = InterpreterGenerator(interp)316sage: from io import StringIO317sage: buff = StringIO()318sage: gen.write_wrapper(buff.write)319sage: print(buff.getvalue())320# Automatically generated by ...321"""322s = self._spec323w = write324types = set()325do_cleanup = False326for ch in s.chunks:327if ch.storage_type is not None:328types.add(ch.storage_type)329do_cleanup = do_cleanup or ch.needs_cleanup_on_error()330for ch in s.chunks:331if ch.name == 'args':332arg_ch = ch333334the_call = je(ri(0, """335{% if s.return_type %}return {% endif -%}336{% if s.adjust_retval %}{{ s.adjust_retval }}({% endif %}337interp_{{ s.name }}({{ arg_ch.pass_argument() }}338{% for ch in s.chunks[1:] %}339, {{ ch.pass_argument() }}340{% endfor %}341){% if s.adjust_retval %}){% endif %}342343"""), s=s, arg_ch=arg_ch)344345the_call_c = je(ri(0, """346{% if s.return_type %}result[0] = {% endif %}347interp_{{ s.name }}(args348{% for ch in s.chunks[1:] %}349, {{ ch.pass_call_c_argument() }}350{% endfor %}351)352353"""), s=s, arg_ch=arg_ch)354355w(je(ri(0, """356# {{ warn }}357{{ s.pyx_header }}358359from cpython.ref cimport PyObject360cdef extern from "Python.h":361void Py_DECREF(PyObject *o)362void Py_INCREF(PyObject *o)363void Py_CLEAR(PyObject *o)364365object PyList_New(Py_ssize_t len)366ctypedef struct PyListObject:367PyObject **ob_item368369ctypedef struct PyTupleObject:370PyObject **ob_item371372from cysignals.memory cimport check_allocarray, sig_free373374from sage.ext.fast_callable cimport Wrapper375376cdef extern from "interp_{{ s.name }}.c":377{{ myself.func_header(cython=true) -}}378379{% if s.err_return != 'NULL' %}380except? {{ s.err_return }}381{% endif %}382383cdef class Wrapper_{{ s.name }}(Wrapper):384# attributes are declared in corresponding .pxd file385386def __init__(self, args):387Wrapper.__init__(self, args, metadata)388cdef int i389cdef int count390{% for ty in types %}391{% print(indent_lines(8, ty.local_declarations)) %}392{% print(indent_lines(8, ty.class_member_initializations)) %}393{% endfor %}394{% for ch in s.chunks %}395{% print(ch.init_class_members()) %}396{% endfor %}397{% print(indent_lines(8, s.extra_members_initialize)) %}398399def __dealloc__(self):400cdef int i401{% for ch in s.chunks %}402{% print(ch.dealloc_class_members()) %}403{% endfor %}404405def __call__(self, *args):406if self._n_args != len(args): raise ValueError407{% for ty in types %}408{% print(indent_lines(8, ty.local_declarations)) %}409{% endfor %}410{% print(indent_lines(8, arg_ch.setup_args())) %}411{% for ch in s.chunks %}412{% print(ch.declare_call_locals()) %}413{% endfor %}414{% if do_cleanup %}415try:416{% print(indent_lines(4, the_call)) %}417except BaseException:418{% for ch in s.chunks %}419{% if ch.needs_cleanup_on_error() %}420{% print(indent_lines(12, ch.handle_cleanup())) %}421{% endif %}422{% endfor %}423raise424{% else %}425{% print(the_call) %}426{% endif %}427{% if not s.return_type %}428return retval429{% endif %}430431{% if s.implement_call_c %}432cdef bint call_c(self,433{{ arg_ch.storage_type.c_ptr_type() }} args,434{{ arg_ch.storage_type.c_reference_type() }} result) except 0:435{% if do_cleanup %}436try:437{% print(indent_lines(4, the_call_c)) %}438except BaseException:439{% for ch in s.chunks %}440{% if ch.needs_cleanup_on_error() %}441{% print(indent_lines(12, ch.handle_cleanup())) %}442{% endif %}443{% endfor %}444raise445{% else %}446{% print(the_call_c) %}447{% endif %}448return 1449{% endif %}450451from sage.ext.fast_callable import CompilerInstrSpec, InterpreterMetadata452metadata = InterpreterMetadata(by_opname={453{% for instr in s.instr_descs %}454'{{ instr.name }}':455(CompilerInstrSpec({{ instr.n_inputs }}, {{ instr.n_outputs }}, {{ instr.parameters }}), {{ instr.opcode }}),456{% endfor %}457},458by_opcode=[459{% for instr in s.instr_descs %}460('{{ instr.name }}',461CompilerInstrSpec({{ instr.n_inputs }}, {{ instr.n_outputs }}, {{ instr.parameters }})),462{% endfor %}463],464ipow_range={{ s.ipow_range }})465"""), s=s, myself=self, types=types, arg_ch=arg_ch,466indent_lines=indent_lines, the_call=the_call,467the_call_c=the_call_c, do_cleanup=do_cleanup,468warn=AUTOGEN_WARN))469470def write_pxd(self, write):471r"""472Generate the pxd file for the Cython wrapper.473474This function calls its write parameter successively with475strings; when these strings are concatenated, the result is476the code for the pxd file.477478See the documentation for the get_pxd method for more479information.480481EXAMPLES::482483sage: from sage_setup.autogen.interpreters.internal import *484sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter485sage: interp = RDFInterpreter()486sage: gen = InterpreterGenerator(interp)487sage: from io import StringIO488sage: buff = StringIO()489sage: gen.write_pxd(buff.write)490sage: print(buff.getvalue())491# Automatically generated by ...492"""493s = self._spec494w = write495types = set()496for ch in s.chunks:497if ch.storage_type is not None:498types.add(ch.storage_type)499for ch in s.chunks:500if ch.name == 'args':501arg_ch = ch502503w(je(ri(0, """504# {{ warn }}505506from cpython.ref cimport PyObject507508from sage.ext.fast_callable cimport Wrapper509{% print(s.pxd_header) %}510511cdef class Wrapper_{{ s.name }}(Wrapper):512{% for ty in types %}513{% print(indent_lines(4, ty.class_member_declarations)) %}514{% endfor %}515{% for ch in s.chunks %}516{% print(ch.declare_class_members()) %}517{% endfor %}518{% print(indent_lines(4, s.extra_class_members)) %}519{% if s.implement_call_c %}520cdef bint call_c(self,521{{ arg_ch.storage_type.c_ptr_type() }} args,522{{ arg_ch.storage_type.c_reference_type() }} result) except 0523{% endif %}524"""), s=s, myself=self, types=types, indent_lines=indent_lines,525arg_ch=arg_ch, warn=AUTOGEN_WARN))526527def get_interpreter(self):528r"""529Return the code for the C interpreter.530531EXAMPLES:532533First we get the InterpreterSpec for several interpreters::534535sage: from sage_setup.autogen.interpreters.internal import *536sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter537sage: from sage_setup.autogen.interpreters.internal.specs.rr import RRInterpreter538sage: from sage_setup.autogen.interpreters.internal.specs.element import ElementInterpreter539sage: rdf_spec = RDFInterpreter()540sage: rr_spec = RRInterpreter()541sage: el_spec = ElementInterpreter()542543Then we get the actual interpreter code::544545sage: rdf_interp = InterpreterGenerator(rdf_spec).get_interpreter()546sage: rr_interp = InterpreterGenerator(rr_spec).get_interpreter()547sage: el_interp = InterpreterGenerator(el_spec).get_interpreter()548549Now we can look through these interpreters.550551Each interpreter starts with a file header; this can be552customized on a per-interpreter basis::553554sage: print(rr_interp)555/* Automatically generated by ... */556...557558Next is the function header, with one argument per memory chunk559in the interpreter spec::560561sage: print(el_interp)562/* ... */ ...563PyObject* interp_el(PyObject** args,564PyObject** constants,565PyObject** stack,566PyObject* domain,567int* code) {568...569570Currently, the interpreters have a very simple structure; just571grab the next instruction and execute it, in a switch572statement::573574sage: print(rdf_interp)575/* ... */ ...576while (1) {577switch (*code++) {578...579580Then comes the code for each instruction. Here is one of the581simplest instructions::582583sage: print(rdf_interp)584/* ... */ ...585case ...: /* neg */586{587double i0 = *--stack;588double o0;589o0 = -i0;590*stack++ = o0;591}592break;593...594595We simply pull the top of the stack into a variable, negate it,596and write the result back onto the stack.597598Let's look at the MPFR-based version of this instruction.599This is an example of an interpreter with an auto-reference600type::601602sage: print(rr_interp)603/* ... */ ...604case ...: /* neg */605{606mpfr_ptr i0 = *--stack;607mpfr_ptr o0 = *stack++;608mpfr_neg(o0, i0, MPFR_RNDN);609}610break;611...612613Here we see that the input and output variables are actually614just pointers into the stack. But due to the auto-reference615trick, the actual code snippet, ``mpfr_neg(o0, i0, MPFR_RNDN);``,616is exactly the same as if i0 and o0 were declared as local617mpfr_t variables.618619For completeness, let's look at this instruction in the620Python-object element interpreter::621622sage: print(el_interp)623/* ... */ ...624case ...: /* neg */625{626PyObject* i0 = *--stack;627*stack = NULL;628PyObject* o0;629o0 = PyNumber_Negative(i0);630Py_DECREF(i0);631if (!CHECK(o0)) {632Py_XDECREF(o0);633goto error;634}635*stack++ = o0;636}637break;638...639640The original code snippet was only ``o0 = PyNumber_Negative(i0);``;641all the rest is automatically generated. For ElementInterpreter,642the CHECK macro actually checks for an exception (makes sure that643o0 is not NULL), tests if the o0 is an element with the correct644parent, and if not converts it into the correct parent. (That is,645it can potentially modify the variable o0.)646"""647648buff = StringIO()649self.write_interpreter(buff.write)650return buff.getvalue()651652def get_wrapper(self):653r"""654Return the code for the Cython wrapper.655656EXAMPLES:657658First we get the InterpreterSpec for several interpreters::659660sage: from sage_setup.autogen.interpreters.internal import *661sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter662sage: from sage_setup.autogen.interpreters.internal.specs.rr import RRInterpreter663sage: from sage_setup.autogen.interpreters.internal.specs.element import ElementInterpreter664sage: rdf_spec = RDFInterpreter()665sage: rr_spec = RRInterpreter()666sage: el_spec = ElementInterpreter()667668Then we get the actual wrapper code::669670sage: rdf_wrapper = InterpreterGenerator(rdf_spec).get_wrapper()671sage: rr_wrapper = InterpreterGenerator(rr_spec).get_wrapper()672sage: el_wrapper = InterpreterGenerator(el_spec).get_wrapper()673674Now we can look through these wrappers.675676Each wrapper starts with a file header; this can be677customized on a per-interpreter basis (some blank lines have been678elided below)::679680sage: print(rdf_wrapper)681# Automatically generated by ...682from cpython.ref cimport PyObject683cdef extern from "Python.h":684void Py_DECREF(PyObject *o)685void Py_INCREF(PyObject *o)686void Py_CLEAR(PyObject *o)687<BLANKLINE>688object PyList_New(Py_ssize_t len)689ctypedef struct PyListObject:690PyObject **ob_item691<BLANKLINE>692ctypedef struct PyTupleObject:693PyObject **ob_item694<BLANKLINE>695from cysignals.memory cimport check_allocarray, sig_free696<BLANKLINE>697from sage.ext.fast_callable cimport Wrapper698...699700We need a way to propagate exceptions back to the wrapper,701even though we only return a double from interp_rdf. The702``except? -1094648009105371`` (that's a randomly chosen703number) means that we will return that number if there's an704exception, but the wrapper still has to check whether that's a705legitimate return or an exception. (Cython does this706automatically.)707708Next comes the actual wrapper class. The member declarations709are in the corresponding pxd file; see the documentation for710get_pxd to see them::711712sage: print(rdf_wrapper)713# ...714cdef class Wrapper_rdf(Wrapper):715# attributes are declared in corresponding .pxd file716...717718Next is the __init__ method, which starts like this::719720sage: print(rdf_wrapper)721# ...722def __init__(self, args):723Wrapper.__init__(self, args, metadata)724cdef int i725cdef int count726...727728To make it possible to generate code for all expression729interpreters with a single code generator, all wrappers730have the same API. The __init__ method takes a single731argument (here called *args*), which is a dictionary holding732all the information needed to initialize this wrapper.733734We call Wrapper.__init__, which saves a copy of this arguments735object and of the interpreter metadata in the wrapper. (This is736only used for debugging.)737738Now we allocate memory for each memory chunk. (We allocate739the memory here, and reuse it on each call of the740wrapper/interpreter. This is for speed reasons; in a fast741interpreter like RDFInterpreter, there are no memory allocations742involved in a call of the wrapper, except for the ones that743are required by the Python calling convention. Eventually744we will support alternate Cython-only entry points that do745absolutely no memory allocation.)746747Basically the same code is repeated, with minor variations, for748each memory chunk; for brevity, we'll only show the code749for 'constants'::750751sage: print(rdf_wrapper)752# ...753val = args['constants']754self._n_constants = len(val)755self._constants = <double*>check_allocarray(self._n_constants, sizeof(double))756for i in range(len(val)):757self._constants[i] = val[i]758...759760Recall that _n_constants is an int, and _constants is a761double*.762763The RRInterpreter version is more complicated, because it has to764call mpfr_init::765766sage: print(rr_wrapper)767# ...768cdef RealNumber rn769...770val = args['constants']771self._n_constants = len(val)772self._constants = <mpfr_t*>check_allocarray(self._n_constants, sizeof(mpfr_t))773for i in range(len(val)):774mpfr_init2(self._constants[i], self.domain.prec())775for i in range(len(val)):776rn = self.domain(val[i])777mpfr_set(self._constants[i], rn.value, MPFR_RNDN)778...779780And as described in the documentation for get_pxd, in781Python-object based interpreters we actually allocate the782memory as a Python list::783784sage: print(el_wrapper)785# ...786val = args['constants']787self._n_constants = len(val)788self._list_constants = PyList_New(self._n_constants)789self._constants = (<PyListObject *>self._list_constants).ob_item790for i in range(len(val)):791self._constants[i] = <PyObject *>val[i]; Py_INCREF(self._constants[i])792...793794Of course, once we've allocated the memory, we eventually have795to free it. (Again, we'll only look at 'constants'.)::796797sage: print(rdf_wrapper)798# ...799def __dealloc__(self):800...801if self._constants:802sig_free(self._constants)803...804805The RRInterpreter code is more complicated again because it has806to call mpfr_clear::807808sage: print(rr_wrapper)809# ...810def __dealloc__(self):811cdef int i812...813if self._constants:814for i in range(self._n_constants):815mpfr_clear(self._constants[i])816sig_free(self._constants)817...818819But the ElementInterpreter code is extremely simple --820it doesn't have to do anything to deallocate constants!821(Since the memory for constants is actually allocated as a822Python list, and Cython knows how to deallocate Python lists.)823824Finally we get to the __call__ method. We grab the arguments825passed by the caller, stuff them in our pre-allocated826argument array, and then call the C interpreter.827828We optionally adjust the return value of the interpreter829(currently only the RDF/float interpreter performs this step;830this is the only place where domain=RDF differs than831domain=float)::832833sage: print(rdf_wrapper)834# ...835def __call__(self, *args):836if self._n_args != len(args): raise ValueError837cdef double* c_args = self._args838cdef int i839for i from 0 <= i < len(args):840self._args[i] = args[i]841return self._domain(interp_rdf(c_args842, self._constants843, self._py_constants844, self._stack845, self._code846))847...848849In Python-object based interpreters, the call to the C850interpreter has to be a little more complicated. We don't851want to hold on to Python objects from an old computation by852leaving them referenced from the stack. In normal operation,853the C interpreter clears out the stack as it runs, leaving the854stack totally clear when the interpreter finishes. However,855this doesn't happen if the C interpreter raises an exception.856In that case, we have to clear out any remnants from the stack857in the wrapper::858859sage: print(el_wrapper)860# ...861try:862return interp_el((<PyListObject*>mapped_args).ob_item863, self._constants864, self._stack865, <PyObject*>self._domain866, self._code867)868except BaseException:869for i in range(self._n_stack):870Py_CLEAR(self._stack[i])871raise872...873874Finally, we define a cdef call_c method, for quickly calling875this object from Cython. (The method is omitted from876Python-object based interpreters.)::877878sage: print(rdf_wrapper)879# ...880cdef bint call_c(self,881double* args,882double* result) except 0:883result[0] = interp_rdf(args884, self._constants885, self._py_constants886, self._stack887, self._code888)889return 1890...891892The method for the RR interpreter is slightly different, because893the interpreter takes a pointer to a result location instead of894returning the value::895896sage: print(rr_wrapper)897# ...898cdef bint call_c(self,899mpfr_t* args,900mpfr_t result) except 0:901interp_rr(args902, result903, self._constants904, self._py_constants905, self._stack906, self._code907, <PyObject*>self._domain908)909return 1910...911912That's it for the wrapper class. The only thing remaining is913the interpreter metadata. This is the information necessary914for the code generator to map instruction names to opcodes; it915also gives information about stack usage, etc. This is fully916documented at InterpreterMetadata; for now, we'll just show917what it looks like.918919Currently, there are three parts to the metadata; the first maps920instruction names to instruction descriptions. The second one921maps opcodes to instruction descriptions. Note that we don't922use InstrSpec objects here; instead, we use CompilerInstrSpec923objects, which are much simpler and contain only the information924we'll need at runtime. The third part says what range the925ipow instruction is defined over.926927First the part that maps instruction names to928(CompilerInstrSpec, opcode) pairs::929930sage: print(rdf_wrapper)931# ...932from sage.ext.fast_callable import CompilerInstrSpec, InterpreterMetadata933metadata = InterpreterMetadata(by_opname={934...935'return':936(CompilerInstrSpec(1, 0, []), 2),937'py_call':938(CompilerInstrSpec(0, 1, ['py_constants', 'n_inputs']), 3),939'pow':940(CompilerInstrSpec(2, 1, []), 4),941'add':942(CompilerInstrSpec(2, 1, []), 5),943...944}, ...)945946There's also a table that maps opcodes to (instruction name,947CompilerInstrSpec) pairs::948949sage: print(rdf_wrapper)950# ...951metadata = InterpreterMetadata(..., by_opcode=[952...953('return',954CompilerInstrSpec(1, 0, [])),955('py_call',956CompilerInstrSpec(0, 1, ['py_constants', 'n_inputs'])),957('pow',958CompilerInstrSpec(2, 1, [])),959('add',960CompilerInstrSpec(2, 1, [])),961...962], ...)963964And then the ipow range::965966sage: print(rdf_wrapper)967# ...968metadata = InterpreterMetadata(...,969ipow_range=(-2147483648, 2147483647))970971And that's it for the wrapper.972"""973974buff = StringIO()975self.write_wrapper(buff.write)976return buff.getvalue()977978def get_pxd(self):979r"""980Return the code for the Cython .pxd file.981982EXAMPLES:983984First we get the InterpreterSpec for several interpreters::985986sage: from sage_setup.autogen.interpreters.internal import *987sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter988sage: from sage_setup.autogen.interpreters.internal.specs.rr import RRInterpreter989sage: from sage_setup.autogen.interpreters.internal.specs.element import ElementInterpreter990sage: rdf_spec = RDFInterpreter()991sage: rr_spec = RRInterpreter()992sage: el_spec = ElementInterpreter()993994Then we get the corresponding .pxd::995996sage: rdf_pxd = InterpreterGenerator(rdf_spec).get_pxd()997sage: rr_pxd = InterpreterGenerator(rr_spec).get_pxd()998sage: el_pxd = InterpreterGenerator(el_spec).get_pxd()9991000Now we can look through these pxd files.10011002Each .pxd starts with a file header; this can be1003customized on a per-interpreter basis (some blank lines have been1004elided below)::10051006sage: print(rdf_pxd)1007# Automatically generated by ...1008from cpython.ref cimport PyObject1009from sage.ext.fast_callable cimport Wrapper1010...1011sage: print(rr_pxd)1012# ...1013from sage.rings.real_mpfr cimport RealField_class, RealNumber1014from sage.libs.mpfr cimport *1015...10161017Next and last is the declaration of the wrapper class, which1018starts off with a list of member declarations::10191020sage: print(rdf_pxd)1021# ...1022cdef class Wrapper_rdf(Wrapper):1023cdef int _n_args1024cdef double* _args1025cdef int _n_constants1026cdef double* _constants1027cdef object _list_py_constants1028cdef int _n_py_constants1029cdef PyObject** _py_constants1030cdef int _n_stack1031cdef double* _stack1032cdef int _n_code1033cdef int* _code1034...10351036Contrast the declaration of ``_stack`` here with the1037ElementInterpreter version. To simplify our handling of1038reference counting and garbage collection, in a Python-object1039based interpreter, we allocate arrays as Python lists,1040and then pull the array out of the innards of the list::10411042sage: print(el_pxd)1043# ...1044cdef object _list_stack1045cdef int _n_stack1046cdef PyObject** _stack1047...10481049Then, at the end of the wrapper class, we declare a cdef method1050for quickly calling the wrapper object from Cython. (This method1051is omitted from Python-object based interpreters.)::10521053sage: print(rdf_pxd)1054# ...1055cdef bint call_c(self,1056double* args,1057double* result) except 01058sage: print(rr_pxd)1059# ...1060cdef bint call_c(self,1061mpfr_t* args,1062mpfr_t result) except 010631064"""10651066buff = StringIO()1067self.write_pxd(buff.write)1068return buff.getvalue()106910701071