Path: blob/develop/src/sage_setup/autogen/interpreters/internal/generator.py
7404 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."""121314from collections import defaultdict15from io import StringIO1617from .memory import string_of_addr18from .utils import indent_lines, je19from .utils import reindent_lines as ri2021AUTOGEN_WARN = "Automatically generated by {}. Do not edit!".format(__file__)222324class InterpreterGenerator:25r"""26This class takes an InterpreterSpec and generates the corresponding27C interpreter and Cython wrapper.2829See the documentation for methods get_wrapper and get_interpreter30for more information.31"""3233def __init__(self, spec):34r"""35Initialize an InterpreterGenerator.3637INPUT:3839- ``spec`` -- an InterpreterSpec4041EXAMPLES::4243sage: from sage_setup.autogen.interpreters.internal import *44sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter45sage: interp = RDFInterpreter()46sage: gen = InterpreterGenerator(interp)47sage: gen._spec is interp48True49sage: gen.uses_error_handler50False51"""52self._spec = spec53self.uses_error_handler = False5455def gen_code(self, instr_desc, write):56r"""57Generate code for a single instruction.5859INPUT:6061- instr_desc -- an InstrSpec62- write -- a Python callable6364This function calls its write parameter successively with65strings; when these strings are concatenated, the result is66the code for the given instruction.6768See the documentation for the get_interpreter method for more69information.7071EXAMPLES::7273sage: from sage_setup.autogen.interpreters.internal import *74sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter75sage: interp = RDFInterpreter()76sage: gen = InterpreterGenerator(interp)77sage: from io import StringIO78sage: buff = StringIO()79sage: instrs = dict([(ins.name, ins) for ins in interp.instr_descs])80sage: gen.gen_code(instrs['div'], buff.write)81sage: print(buff.getvalue())82case ...: /* div */83{84double i1 = *--stack;85double i0 = *--stack;86double o0;87o0 = i0 / i1;88*stack++ = o0;89}90break;91<BLANKLINE>92"""9394d = instr_desc95w = write9697if d.uses_error_handler:98self.uses_error_handler = True99100w(je(ri(4, """101case {{ d.opcode }}: /* {{ d.name }} */102{103"""), d=d))104105# If the inputs to an instruction come from the stack,106# then we want to generate code for the inputs in reverse order:107# for instance, the divide instruction, which takes inputs A and B108# and generates A/B, needs to pop B off the stack first.109# On the other hand, if the inputs come from the constant pool,110# then we want to generate code for the inputs in normal order,111# because the addresses in the code stream will be in that order.112# We handle this by running through the inputs in two passes:113# first a forward pass, where we handle non-stack inputs114# (and lengths for stack inputs), and then a reverse pass,115# where we handle stack inputs.116for i in range(len(d.inputs)):117(ch, addr, input_len) = d.inputs[i]118chst = ch.storage_type119if addr is not None:120w(" int ai%d = %s;\n" % (i, string_of_addr(addr)))121if input_len is not None:122w(" int n_i%d = %s;\n" % (i, string_of_addr(input_len)))123if not ch.is_stack():124# Shouldn't hardcode 'code' here125if ch.name == 'code':126w(" %s i%d = %s;\n" % (chst.c_local_type(), i, string_of_addr(ch)))127elif input_len is not None:128w(" %s i%d = %s + ai%d;\n" %129(chst.c_ptr_type(), i, ch.name, i))130else:131w(" %s i%d = %s[ai%d];\n" %132(chst.c_local_type(), i, ch.name, i))133134for i in reversed(range(len(d.inputs))):135(ch, addr, input_len) = d.inputs[i]136chst = ch.storage_type137if ch.is_stack():138if input_len is not None:139w(" %s -= n_i%d;\n" % (ch.name, i))140w(" %s i%d = %s;\n" % (chst.c_ptr_type(), i, ch.name))141else:142w(" %s i%d = *--%s;\n" % (chst.c_local_type(), i, ch.name))143if ch.is_python_refcounted_stack():144w(" *%s = NULL;\n" % ch.name)145146for i in range(len(d.outputs)):147(ch, addr, output_len) = d.outputs[i]148chst = ch.storage_type149if addr is not None:150w(" int ao%d = %s;\n" % (i, string_of_addr(addr)))151if output_len is not None:152w(" int n_o%d = %s;\n" % (i, string_of_addr(output_len)))153if ch.is_stack():154w(" %s o%d = %s;\n" %155(chst.c_ptr_type(), i, ch.name))156w(" %s += n_o%d;\n" % (ch.name, i))157else:158w(" %s o%d = %s + ao%d;\n" %159(chst.c_ptr_type(), i, ch.name, i))160else:161if not chst.cheap_copies():162if ch.is_stack():163w(" %s o%d = *%s++;\n" %164(chst.c_local_type(), i, ch.name))165else:166w(" %s o%d = %s[ao%d];\n" %167(chst.c_local_type(), i, ch.name, i))168else:169w(" %s o%d;\n" % (chst.c_local_type(), i))170w(indent_lines(8, d.code.rstrip('\n') + '\n'))171172stack_offsets = defaultdict(int)173for i in range(len(d.inputs)):174(ch, addr, input_len) = d.inputs[i]175if ch.is_python_refcounted_stack() and not d.handles_own_decref:176if input_len is None:177w(" Py_DECREF(i%d);\n" % i)178stack_offsets[ch] += 1179else:180w(je(ri(8, """181int {{ iter }};182for ({{ iter }} = 0; {{ iter }} < n_i{{ i }}; {{ iter }}++) {183Py_CLEAR(i{{ i }}[{{ iter }}]);184}185"""), iter='_interp_iter_%d' % i, i=i))186187for i in range(len(d.outputs)):188ch = d.outputs[i][0]189chst = ch.storage_type190if chst.python_refcounted():191# We don't yet support code chunks192# that produce multiple Python values, because of193# the way it complicates error handling.194assert i == 0195w(" if (!CHECK(o%d)) {\n" % i)196w(" Py_XDECREF(o%d);\n" % i)197w(" goto error;\n")198w(" }\n")199self.uses_error_handler = True200if chst.cheap_copies():201if ch.is_stack():202w(" *%s++ = o%d;\n" % (ch.name, i))203else:204w(" %s[ao%d] = o%d;\n" % (ch.name, i, i))205206w(je(ri(6,207"""\208}209break;210""")))211212def func_header(self, cython=False):213r"""214Generates the function header for the declaration (in the Cython215wrapper) or the definition (in the C interpreter) of the interpreter216function.217218EXAMPLES::219220sage: from sage_setup.autogen.interpreters.internal import *221sage: from sage_setup.autogen.interpreters.internal.specs.element import ElementInterpreter222sage: interp = ElementInterpreter()223sage: gen = InterpreterGenerator(interp)224sage: print(gen.func_header())225PyObject* interp_el(PyObject** args,226PyObject** constants,227PyObject** stack,228PyObject* domain,229int* code)230sage: print(gen.func_header(cython=True))231object interp_el(PyObject** args,232PyObject** constants,233PyObject** stack,234PyObject* domain,235int* code)236"""237s = self._spec238ret_ty = 'bint' if cython else 'int'239if s.return_type:240ret_ty = s.return_type.c_decl_type()241if cython:242ret_ty = s.return_type.cython_decl_type()243return je(ri(0, """\244{{ ret_ty }} interp_{{ s.name }}(245{%- for ch in s.chunks %}246{% if not loop.first %},247{% endif %}{{ ch.declare_parameter() }}248{%- endfor %})"""), ret_ty=ret_ty, s=s)249250def write_interpreter(self, write):251r"""252Generate the code for the C interpreter.253254This function calls its write parameter successively with255strings; when these strings are concatenated, the result is256the code for the interpreter.257258See the documentation for the get_interpreter method for more259information.260261EXAMPLES::262263sage: from sage_setup.autogen.interpreters.internal import *264sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter265sage: interp = RDFInterpreter()266sage: gen = InterpreterGenerator(interp)267sage: from io import StringIO268sage: buff = StringIO()269sage: gen.write_interpreter(buff.write)270sage: print(buff.getvalue())271/* Automatically generated by ...272"""273s = self._spec274w = write275w(je(ri(0, """276/* {{ warn }} */277#include <Python.h>278279{% print(s.c_header) %}280281{{ myself.func_header() }} {282while (1) {283switch (*code++) {284"""), s=s, myself=self, i=indent_lines, warn=AUTOGEN_WARN))285286for instr_desc in s.instr_descs:287self.gen_code(instr_desc, w)288w(je(ri(0, """289}290}291{% if myself.uses_error_handler %}292error:293return {{ s.err_return }};294{% endif %}295}296297"""), s=s, i=indent_lines, myself=self))298299def write_wrapper(self, write):300r"""301Generate the code for the Cython wrapper.302This function calls its write parameter successively with303strings; when these strings are concatenated, the result is304the code for the wrapper.305306See the documentation for the get_wrapper method for more307information.308309EXAMPLES::310311sage: from sage_setup.autogen.interpreters.internal import *312sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter313sage: interp = RDFInterpreter()314sage: gen = InterpreterGenerator(interp)315sage: from io import StringIO316sage: buff = StringIO()317sage: gen.write_wrapper(buff.write)318sage: print(buff.getvalue())319# Automatically generated by ...320"""321s = self._spec322w = write323types = set()324do_cleanup = False325for ch in s.chunks:326if ch.storage_type is not None:327types.add(ch.storage_type)328do_cleanup = do_cleanup or ch.needs_cleanup_on_error()329for ch in s.chunks:330if ch.name == 'args':331arg_ch = ch332333the_call = je(ri(0, """334{% if s.return_type %}return {% endif -%}335{% if s.adjust_retval %}{{ s.adjust_retval }}({% endif %}336interp_{{ s.name }}({{ arg_ch.pass_argument() }}337{% for ch in s.chunks[1:] %}338, {{ ch.pass_argument() }}339{% endfor %}340){% if s.adjust_retval %}){% endif %}341342"""), s=s, arg_ch=arg_ch)343344the_call_c = je(ri(0, """345{% if s.return_type %}result[0] = {% endif %}346interp_{{ s.name }}(args347{% for ch in s.chunks[1:] %}348, {{ ch.pass_call_c_argument() }}349{% endfor %}350)351352"""), s=s, arg_ch=arg_ch)353354w(je(ri(0, """355# {{ warn }}356{{ s.pyx_header }}357358from cpython.ref cimport PyObject359cdef extern from "Python.h":360void Py_DECREF(PyObject *o)361void Py_INCREF(PyObject *o)362void Py_CLEAR(PyObject *o)363364object PyList_New(Py_ssize_t len)365ctypedef struct PyListObject:366PyObject **ob_item367368ctypedef struct PyTupleObject:369PyObject **ob_item370371from cysignals.memory cimport check_allocarray, sig_free372373from sage.ext.fast_callable cimport Wrapper374375cdef extern from "interp_{{ s.name }}.c":376{{ myself.func_header(cython=true) -}}377378{% if s.err_return != 'NULL' %}379except? {{ s.err_return }}380{% endif %}381382cdef class Wrapper_{{ s.name }}(Wrapper):383# attributes are declared in corresponding .pxd file384385def __init__(self, args):386Wrapper.__init__(self, args, metadata)387cdef int i388cdef int count389{% for ty in types %}390{% print(indent_lines(8, ty.local_declarations)) %}391{% print(indent_lines(8, ty.class_member_initializations)) %}392{% endfor %}393{% for ch in s.chunks %}394{% print(ch.init_class_members()) %}395{% endfor %}396{% print(indent_lines(8, s.extra_members_initialize)) %}397398def __dealloc__(self):399cdef int i400{% for ch in s.chunks %}401{% print(ch.dealloc_class_members()) %}402{% endfor %}403404def __call__(self, *args):405if self._n_args != len(args): raise ValueError406{% for ty in types %}407{% print(indent_lines(8, ty.local_declarations)) %}408{% endfor %}409{% print(indent_lines(8, arg_ch.setup_args())) %}410{% for ch in s.chunks %}411{% print(ch.declare_call_locals()) %}412{% endfor %}413{% if do_cleanup %}414try:415{% print(indent_lines(4, the_call)) %}416except BaseException:417{% for ch in s.chunks %}418{% if ch.needs_cleanup_on_error() %}419{% print(indent_lines(12, ch.handle_cleanup())) %}420{% endif %}421{% endfor %}422raise423{% else %}424{% print(the_call) %}425{% endif %}426{% if not s.return_type %}427return retval428{% endif %}429430{% if s.implement_call_c %}431cdef bint call_c(self,432{{ arg_ch.storage_type.c_ptr_type() }} args,433{{ arg_ch.storage_type.c_reference_type() }} result) except 0:434{% if do_cleanup %}435try:436{% print(indent_lines(4, the_call_c)) %}437except BaseException:438{% for ch in s.chunks %}439{% if ch.needs_cleanup_on_error() %}440{% print(indent_lines(12, ch.handle_cleanup())) %}441{% endif %}442{% endfor %}443raise444{% else %}445{% print(the_call_c) %}446{% endif %}447return 1448{% endif %}449450from sage.ext.fast_callable import CompilerInstrSpec, InterpreterMetadata451metadata = InterpreterMetadata(by_opname={452{% for instr in s.instr_descs %}453'{{ instr.name }}':454(CompilerInstrSpec({{ instr.n_inputs }}, {{ instr.n_outputs }}, {{ instr.parameters }}), {{ instr.opcode }}),455{% endfor %}456},457by_opcode=[458{% for instr in s.instr_descs %}459('{{ instr.name }}',460CompilerInstrSpec({{ instr.n_inputs }}, {{ instr.n_outputs }}, {{ instr.parameters }})),461{% endfor %}462],463ipow_range={{ s.ipow_range }})464"""), s=s, myself=self, types=types, arg_ch=arg_ch,465indent_lines=indent_lines, the_call=the_call,466the_call_c=the_call_c, do_cleanup=do_cleanup,467warn=AUTOGEN_WARN))468469def write_pxd(self, write):470r"""471Generate the pxd file for the Cython wrapper.472473This function calls its write parameter successively with474strings; when these strings are concatenated, the result is475the code for the pxd file.476477See the documentation for the get_pxd method for more478information.479480EXAMPLES::481482sage: from sage_setup.autogen.interpreters.internal import *483sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter484sage: interp = RDFInterpreter()485sage: gen = InterpreterGenerator(interp)486sage: from io import StringIO487sage: buff = StringIO()488sage: gen.write_pxd(buff.write)489sage: print(buff.getvalue())490# Automatically generated by ...491"""492s = self._spec493w = write494types = set()495for ch in s.chunks:496if ch.storage_type is not None:497types.add(ch.storage_type)498for ch in s.chunks:499if ch.name == 'args':500arg_ch = ch501502w(je(ri(0, """503# {{ warn }}504505from cpython.ref cimport PyObject506507from sage.ext.fast_callable cimport Wrapper508{% print(s.pxd_header) %}509510cdef class Wrapper_{{ s.name }}(Wrapper):511{% for ty in types %}512{% print(indent_lines(4, ty.class_member_declarations)) %}513{% endfor %}514{% for ch in s.chunks %}515{% print(ch.declare_class_members()) %}516{% endfor %}517{% print(indent_lines(4, s.extra_class_members)) %}518{% if s.implement_call_c %}519cdef bint call_c(self,520{{ arg_ch.storage_type.c_ptr_type() }} args,521{{ arg_ch.storage_type.c_reference_type() }} result) except 0522{% endif %}523"""), s=s, myself=self, types=types, indent_lines=indent_lines,524arg_ch=arg_ch, warn=AUTOGEN_WARN))525526def get_interpreter(self):527r"""528Return the code for the C interpreter.529530EXAMPLES:531532First we get the InterpreterSpec for several interpreters::533534sage: from sage_setup.autogen.interpreters.internal import *535sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter536sage: from sage_setup.autogen.interpreters.internal.specs.rr import RRInterpreter537sage: from sage_setup.autogen.interpreters.internal.specs.element import ElementInterpreter538sage: rdf_spec = RDFInterpreter()539sage: rr_spec = RRInterpreter()540sage: el_spec = ElementInterpreter()541542Then we get the actual interpreter code::543544sage: rdf_interp = InterpreterGenerator(rdf_spec).get_interpreter()545sage: rr_interp = InterpreterGenerator(rr_spec).get_interpreter()546sage: el_interp = InterpreterGenerator(el_spec).get_interpreter()547548Now we can look through these interpreters.549550Each interpreter starts with a file header; this can be551customized on a per-interpreter basis::552553sage: print(rr_interp)554/* Automatically generated by ... */555...556557Next is the function header, with one argument per memory chunk558in the interpreter spec::559560sage: print(el_interp)561/* ... */ ...562PyObject* interp_el(PyObject** args,563PyObject** constants,564PyObject** stack,565PyObject* domain,566int* code) {567...568569Currently, the interpreters have a very simple structure; just570grab the next instruction and execute it, in a switch571statement::572573sage: print(rdf_interp)574/* ... */ ...575while (1) {576switch (*code++) {577...578579Then comes the code for each instruction. Here is one of the580simplest instructions::581582sage: print(rdf_interp)583/* ... */ ...584case ...: /* neg */585{586double i0 = *--stack;587double o0;588o0 = -i0;589*stack++ = o0;590}591break;592...593594We simply pull the top of the stack into a variable, negate it,595and write the result back onto the stack.596597Let's look at the MPFR-based version of this instruction.598This is an example of an interpreter with an auto-reference599type::600601sage: print(rr_interp)602/* ... */ ...603case ...: /* neg */604{605mpfr_ptr i0 = *--stack;606mpfr_ptr o0 = *stack++;607mpfr_neg(o0, i0, MPFR_RNDN);608}609break;610...611612Here we see that the input and output variables are actually613just pointers into the stack. But due to the auto-reference614trick, the actual code snippet, ``mpfr_neg(o0, i0, MPFR_RNDN);``,615is exactly the same as if i0 and o0 were declared as local616mpfr_t variables.617618For completeness, let's look at this instruction in the619Python-object element interpreter::620621sage: print(el_interp)622/* ... */ ...623case ...: /* neg */624{625PyObject* i0 = *--stack;626*stack = NULL;627PyObject* o0;628o0 = PyNumber_Negative(i0);629Py_DECREF(i0);630if (!CHECK(o0)) {631Py_XDECREF(o0);632goto error;633}634*stack++ = o0;635}636break;637...638639The original code snippet was only ``o0 = PyNumber_Negative(i0);``;640all the rest is automatically generated. For ElementInterpreter,641the CHECK macro actually checks for an exception (makes sure that642o0 is not NULL), tests if the o0 is an element with the correct643parent, and if not converts it into the correct parent. (That is,644it can potentially modify the variable o0.)645"""646647buff = StringIO()648self.write_interpreter(buff.write)649return buff.getvalue()650651def get_wrapper(self):652r"""653Return the code for the Cython wrapper.654655EXAMPLES:656657First we get the InterpreterSpec for several interpreters::658659sage: from sage_setup.autogen.interpreters.internal import *660sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter661sage: from sage_setup.autogen.interpreters.internal.specs.rr import RRInterpreter662sage: from sage_setup.autogen.interpreters.internal.specs.element import ElementInterpreter663sage: rdf_spec = RDFInterpreter()664sage: rr_spec = RRInterpreter()665sage: el_spec = ElementInterpreter()666667Then we get the actual wrapper code::668669sage: rdf_wrapper = InterpreterGenerator(rdf_spec).get_wrapper()670sage: rr_wrapper = InterpreterGenerator(rr_spec).get_wrapper()671sage: el_wrapper = InterpreterGenerator(el_spec).get_wrapper()672673Now we can look through these wrappers.674675Each wrapper starts with a file header; this can be676customized on a per-interpreter basis (some blank lines have been677elided below)::678679sage: print(rdf_wrapper)680# Automatically generated by ...681from cpython.ref cimport PyObject682cdef extern from "Python.h":683void Py_DECREF(PyObject *o)684void Py_INCREF(PyObject *o)685void Py_CLEAR(PyObject *o)686<BLANKLINE>687object PyList_New(Py_ssize_t len)688ctypedef struct PyListObject:689PyObject **ob_item690<BLANKLINE>691ctypedef struct PyTupleObject:692PyObject **ob_item693<BLANKLINE>694from cysignals.memory cimport check_allocarray, sig_free695<BLANKLINE>696from sage.ext.fast_callable cimport Wrapper697...698699We need a way to propagate exceptions back to the wrapper,700even though we only return a double from interp_rdf. The701``except? -1094648009105371`` (that's a randomly chosen702number) means that we will return that number if there's an703exception, but the wrapper still has to check whether that's a704legitimate return or an exception. (Cython does this705automatically.)706707Next comes the actual wrapper class. The member declarations708are in the corresponding pxd file; see the documentation for709get_pxd to see them::710711sage: print(rdf_wrapper)712# ...713cdef class Wrapper_rdf(Wrapper):714# attributes are declared in corresponding .pxd file715...716717Next is the __init__ method, which starts like this::718719sage: print(rdf_wrapper)720# ...721def __init__(self, args):722Wrapper.__init__(self, args, metadata)723cdef int i724cdef int count725...726727To make it possible to generate code for all expression728interpreters with a single code generator, all wrappers729have the same API. The __init__ method takes a single730argument (here called *args*), which is a dictionary holding731all the information needed to initialize this wrapper.732733We call Wrapper.__init__, which saves a copy of this arguments734object and of the interpreter metadata in the wrapper. (This is735only used for debugging.)736737Now we allocate memory for each memory chunk. (We allocate738the memory here, and reuse it on each call of the739wrapper/interpreter. This is for speed reasons; in a fast740interpreter like RDFInterpreter, there are no memory allocations741involved in a call of the wrapper, except for the ones that742are required by the Python calling convention. Eventually743we will support alternate Cython-only entry points that do744absolutely no memory allocation.)745746Basically the same code is repeated, with minor variations, for747each memory chunk; for brevity, we'll only show the code748for 'constants'::749750sage: print(rdf_wrapper)751# ...752val = args['constants']753self._n_constants = len(val)754self._constants = <double*>check_allocarray(self._n_constants, sizeof(double))755for i in range(len(val)):756self._constants[i] = val[i]757...758759Recall that _n_constants is an int, and _constants is a760double*.761762The RRInterpreter version is more complicated, because it has to763call mpfr_init::764765sage: print(rr_wrapper)766# ...767cdef RealNumber rn768...769val = args['constants']770self._n_constants = len(val)771self._constants = <mpfr_t*>check_allocarray(self._n_constants, sizeof(mpfr_t))772for i in range(len(val)):773mpfr_init2(self._constants[i], self.domain.prec())774for i in range(len(val)):775rn = self.domain(val[i])776mpfr_set(self._constants[i], rn.value, MPFR_RNDN)777...778779And as described in the documentation for get_pxd, in780Python-object based interpreters we actually allocate the781memory as a Python list::782783sage: print(el_wrapper)784# ...785val = args['constants']786self._n_constants = len(val)787self._list_constants = PyList_New(self._n_constants)788self._constants = (<PyListObject *>self._list_constants).ob_item789for i in range(len(val)):790self._constants[i] = <PyObject *>val[i]; Py_INCREF(self._constants[i])791...792793Of course, once we've allocated the memory, we eventually have794to free it. (Again, we'll only look at 'constants'.)::795796sage: print(rdf_wrapper)797# ...798def __dealloc__(self):799...800if self._constants:801sig_free(self._constants)802...803804The RRInterpreter code is more complicated again because it has805to call mpfr_clear::806807sage: print(rr_wrapper)808# ...809def __dealloc__(self):810cdef int i811...812if self._constants:813for i in range(self._n_constants):814mpfr_clear(self._constants[i])815sig_free(self._constants)816...817818But the ElementInterpreter code is extremely simple --819it doesn't have to do anything to deallocate constants!820(Since the memory for constants is actually allocated as a821Python list, and Cython knows how to deallocate Python lists.)822823Finally we get to the __call__ method. We grab the arguments824passed by the caller, stuff them in our pre-allocated825argument array, and then call the C interpreter.826827We optionally adjust the return value of the interpreter828(currently only the RDF/float interpreter performs this step;829this is the only place where domain=RDF differs than830domain=float)::831832sage: print(rdf_wrapper)833# ...834def __call__(self, *args):835if self._n_args != len(args): raise ValueError836cdef double* c_args = self._args837cdef int i838for i from 0 <= i < len(args):839self._args[i] = args[i]840return self._domain(interp_rdf(c_args841, self._constants842, self._py_constants843, self._stack844, self._code845))846...847848In Python-object based interpreters, the call to the C849interpreter has to be a little more complicated. We don't850want to hold on to Python objects from an old computation by851leaving them referenced from the stack. In normal operation,852the C interpreter clears out the stack as it runs, leaving the853stack totally clear when the interpreter finishes. However,854this doesn't happen if the C interpreter raises an exception.855In that case, we have to clear out any remnants from the stack856in the wrapper::857858sage: print(el_wrapper)859# ...860try:861return interp_el((<PyListObject*>mapped_args).ob_item862, self._constants863, self._stack864, <PyObject*>self._domain865, self._code866)867except BaseException:868for i in range(self._n_stack):869Py_CLEAR(self._stack[i])870raise871...872873Finally, we define a cdef call_c method, for quickly calling874this object from Cython. (The method is omitted from875Python-object based interpreters.)::876877sage: print(rdf_wrapper)878# ...879cdef bint call_c(self,880double* args,881double* result) except 0:882result[0] = interp_rdf(args883, self._constants884, self._py_constants885, self._stack886, self._code887)888return 1889...890891The method for the RR interpreter is slightly different, because892the interpreter takes a pointer to a result location instead of893returning the value::894895sage: print(rr_wrapper)896# ...897cdef bint call_c(self,898mpfr_t* args,899mpfr_t result) except 0:900interp_rr(args901, result902, self._constants903, self._py_constants904, self._stack905, self._code906, <PyObject*>self._domain907)908return 1909...910911That's it for the wrapper class. The only thing remaining is912the interpreter metadata. This is the information necessary913for the code generator to map instruction names to opcodes; it914also gives information about stack usage, etc. This is fully915documented at InterpreterMetadata; for now, we'll just show916what it looks like.917918Currently, there are three parts to the metadata; the first maps919instruction names to instruction descriptions. The second one920maps opcodes to instruction descriptions. Note that we don't921use InstrSpec objects here; instead, we use CompilerInstrSpec922objects, which are much simpler and contain only the information923we'll need at runtime. The third part says what range the924ipow instruction is defined over.925926First the part that maps instruction names to927(CompilerInstrSpec, opcode) pairs::928929sage: print(rdf_wrapper)930# ...931from sage.ext.fast_callable import CompilerInstrSpec, InterpreterMetadata932metadata = InterpreterMetadata(by_opname={933...934'return':935(CompilerInstrSpec(1, 0, []), 2),936'py_call':937(CompilerInstrSpec(0, 1, ['py_constants', 'n_inputs']), 3),938'pow':939(CompilerInstrSpec(2, 1, []), 4),940'add':941(CompilerInstrSpec(2, 1, []), 5),942...943}, ...)944945There's also a table that maps opcodes to (instruction name,946CompilerInstrSpec) pairs::947948sage: print(rdf_wrapper)949# ...950metadata = InterpreterMetadata(..., by_opcode=[951...952('return',953CompilerInstrSpec(1, 0, [])),954('py_call',955CompilerInstrSpec(0, 1, ['py_constants', 'n_inputs'])),956('pow',957CompilerInstrSpec(2, 1, [])),958('add',959CompilerInstrSpec(2, 1, [])),960...961], ...)962963And then the ipow range::964965sage: print(rdf_wrapper)966# ...967metadata = InterpreterMetadata(...,968ipow_range=(-2147483648, 2147483647))969970And that's it for the wrapper.971"""972973buff = StringIO()974self.write_wrapper(buff.write)975return buff.getvalue()976977def get_pxd(self):978r"""979Return the code for the Cython .pxd file.980981EXAMPLES:982983First we get the InterpreterSpec for several interpreters::984985sage: from sage_setup.autogen.interpreters.internal import *986sage: from sage_setup.autogen.interpreters.internal.specs.rdf import RDFInterpreter987sage: from sage_setup.autogen.interpreters.internal.specs.rr import RRInterpreter988sage: from sage_setup.autogen.interpreters.internal.specs.element import ElementInterpreter989sage: rdf_spec = RDFInterpreter()990sage: rr_spec = RRInterpreter()991sage: el_spec = ElementInterpreter()992993Then we get the corresponding .pxd::994995sage: rdf_pxd = InterpreterGenerator(rdf_spec).get_pxd()996sage: rr_pxd = InterpreterGenerator(rr_spec).get_pxd()997sage: el_pxd = InterpreterGenerator(el_spec).get_pxd()998999Now we can look through these pxd files.10001001Each .pxd starts with a file header; this can be1002customized on a per-interpreter basis (some blank lines have been1003elided below)::10041005sage: print(rdf_pxd)1006# Automatically generated by ...1007from cpython.ref cimport PyObject1008from sage.ext.fast_callable cimport Wrapper1009...1010sage: print(rr_pxd)1011# ...1012from sage.rings.real_mpfr cimport RealField_class, RealNumber1013from sage.libs.mpfr cimport *1014...10151016Next and last is the declaration of the wrapper class, which1017starts off with a list of member declarations::10181019sage: print(rdf_pxd)1020# ...1021cdef class Wrapper_rdf(Wrapper):1022cdef int _n_args1023cdef double* _args1024cdef int _n_constants1025cdef double* _constants1026cdef object _list_py_constants1027cdef int _n_py_constants1028cdef PyObject** _py_constants1029cdef int _n_stack1030cdef double* _stack1031cdef int _n_code1032cdef int* _code1033...10341035Contrast the declaration of ``_stack`` here with the1036ElementInterpreter version. To simplify our handling of1037reference counting and garbage collection, in a Python-object1038based interpreter, we allocate arrays as Python lists,1039and then pull the array out of the innards of the list::10401041sage: print(el_pxd)1042# ...1043cdef object _list_stack1044cdef int _n_stack1045cdef PyObject** _stack1046...10471048Then, at the end of the wrapper class, we declare a cdef method1049for quickly calling the wrapper object from Cython. (This method1050is omitted from Python-object based interpreters.)::10511052sage: print(rdf_pxd)1053# ...1054cdef bint call_c(self,1055double* args,1056double* result) except 01057sage: print(rr_pxd)1058# ...1059cdef bint call_c(self,1060mpfr_t* args,1061mpfr_t result) except 010621063"""10641065buff = StringIO()1066self.write_pxd(buff.write)1067return buff.getvalue()106810691070