Source code for curry.backends.cxx.compiler

from ...exceptions import CompileError, CurryTypeError
from ..generic import compiler, renderer
from ... import common, icurry
from . import cyrtbindings as cyrt
from . import passthrough
from ...utility import formatDocstring, strings, visitation
from ...utility.showflags import showflags
import os

__all__ = ['compile', 'write_module', 'FORMAT_VERSION']

# The format of the generated C++.  vEmitHeader writes it into every file as
# "// FORMAT: N".  The toolchain refuses a cached file with another stamp, or
# none (Cpp2So.is_stale): such a file was written for another runtime, and the
# runtime would read its static data with the wrong layout.  A file without a
# stamp is format 1.  Raise the number when the generated code, or a layout it
# depends on, changes.  Format 3: the optimizer replaces calls of alias
# functions (interpreter.optimize.inline_aliases), so a cached file of format
# 2 is correct but slower.  Format 4: the bill of materials is plain data
# (cyrt/bom.hpp; vEmitMetadata and vEmitModuleDefinition).  A file of format 3
# defines a ModuleBOM object, which the loader no longer reads.  Format 5: a
# step writes its result into the redex when it fits (Node::rewrite,
# vEmit_compileS_IReturn); a file of format 4 forwards every result, which is
# correct but slower.  Format 6: a literal outside the tables of the runtime
# is a node of the module, made once at load (literal_node,
# internLiteralNode); a file of format 5 allocates such a literal on every
# execution, which is correct but slower.  Format 7: an argument that a step
# only passes on is a plain Node * (passthrough.py, vEmit_compileS_IVarDecl);
# a file of format 6 builds a Variable for it, which is correct but slower.
# Format 8: a partial application without arguments is a node of the module,
# made once at load (partial_node, internPartialNode); a file of format 7
# allocates one on every execution, which is correct but slower.  Format 11:
# the optimizer collapses an apply chain on a known head into the call it
# builds (interpreter.optimize.saturate_applies), a chain through Prelude.$
# included, and records the unfolding of a nullary function in its metadata;
# a file of format 8 is correct but slower, and it tells the modules that
# import it no unfoldings.  Formats 9 and 10 were earlier forms of the same
# pass in the working tree, never committed.  Format 12: the optimizer
# inlines calls of small non-recursive functions and calls of a single-case
# function on a known constructor (interpreter.optimize.inline_calls), and
# records the body of such a function in its metadata; a file of format 11
# is correct but slower, and it tells the modules that import it no bodies.
# Format 13: an object names no path.  The header carries the full name of
# the module ("// MODULE: N"), from which the compile step forms the SONAME
# of the object (toolchain.soname); the object names the objects of its
# imports by their SONAMEs, and the loader imports those modules before it
# opens the object (loader.load_module).  The record names a source outside
# the installation relative to the directory of the object, "../../N.curry"
# (_source_file_name), as it names a source under the installation relative
# to it since format 12.  A file of format 12 names the objects of its
# imports and its source by absolute path, so it serves one tree.
FORMAT_VERSION = 13

# The runtime headers every generated file includes, as vEmitHeader writes
# them: one directive per name.  The toolchain digests the include closure
# of these headers into the ABI stamp of an object (toolchain.runtime_headers
# and runtime_digest): a header the closure does not reach belongs to the
# runtime library alone, and an edit to it compiles no module again.
RUNTIME_INCLUDES = ('cyrt/cyrt.hpp',)

[docs] def compile(interp, imodule): compileM = CxxCompiler(interp, imodule) return compileM.compile()
SINGLETONS = { 'Node::create(&CyI7Prelude4_K_k)' : 'nil()' , 'Node::create(&CyI7Prelude4_Y_y)' : 'unit()' , 'Node::create(&CyI7Prelude4True)' : 'true_()' , 'Node::create(&CyI7Prelude5False)': 'false_()' , 'Node::create(&CyI7Prelude4Fail)' : 'fail()' } def replace_singletons(f): def emitter(*args, **kwds): result = f(*args, **kwds) return SINGLETONS.get(result, result) return emitter # The pinned nullary constructors, the info tables behind SINGLETONS. # Node::create returns the static object for one of these, and a step forwards # the redex to it. A heap copy of a pinned node would never be marked by the # collector, so the generator never writes one into a redex (see # Node::rewrite in cyrt/graph/node.hxx). PINNED_INFOS = frozenset(key[len('Node::create(&'):-1] for key in SINGLETONS) # The size of the block of a node, in bytes, as the generated info tables # spell it: sizeof(Head) + sizeof(Arg[max(1, arity)]), one word each. The # generator compares the block of a result with the block of the redex, so # that a result that fits is written into the redex (vEmit_compileS_IReturn). # InfoTable asserts that every block is at least this large. WORD = 8 def alloc_size(arity): return WORD * (1 + max(1, arity)) class CxxCompiler(compiler.CompilerBase): CODE_TYPE = 'C++' EXCLUDED_METADATA = set(['cxx.material', 'cxx.shlib']) def __init__(self, interp, iroot): super(CxxCompiler, self).__init__(interp, iroot) self.cxxmodule = cyrt.Module.find_or_create(iroot.modulename) \ if isinstance(iroot, icurry.IModule) \ else None # The size of the block of the redex of the step under compilation, and # the variables of the step that are plain pointers. See # vEmitStepfuncHeader. self.redex_alloc_size = None self.plain_vars = frozenset() self.free_vars = frozenset() def vIsBuiltin(self, iobj): if self.cxxmodule is not None: if isinstance(iobj, (icurry.IFunction, icurry.IConstructor)): return self.cxxmodule.get_builtin_symbol(iobj.name) is not None elif isinstance(iobj, icurry.IDataType): return self.cxxmodule.get_builtin_type(iobj.name) is not None else: assert False return False def vIsSynthesized(self, ifun): return False def vBackendFunctionKey(self, ifun): assert False def vEmitHeader(self): # The compile step reads the three lines: the imports it links against, # the format it accepts, and the module name of the SONAME (Cpp2So). yield '// IMPORTS: ' + ' '.join(str(mod) for mod in self.iroot.imports) yield '// FORMAT: %d' % FORMAT_VERSION yield '// MODULE: ' + self.iroot.fullname for name in RUNTIME_INCLUDES: yield '#include "%s"' % name yield '' yield 'using namespace cyrt;' yield '' yield 'extern "C" {' def vEmitFooter(self): yield '} // extern "C"' def vEmitImported(self, modulename): return [] def vEmitStepfuncLink(self, ifun, h_stepfunc): yield 'tag_type %s(RuntimeState *, Configuration *);' % h_stepfunc def vEmitInfotabLink(self, isym, h_info): yield 'extern InfoTable const %s;' % h_info def vEmitDataTypeLink(self, itype, h_datatype): yield 'extern DataType const %s;' % h_datatype def vEmitStepfuncHeader(self, ifun, h_stepfunc): # The redex of this step is a node of the function's own info table, # whose block vEmitFunctionInfotab sizes by the arity. self.redex_alloc_size = alloc_size(ifun.arity) self.plain_vars = passthrough.plain_variables(ifun) self.free_vars = passthrough.free_variables(ifun) yield '/****** %s ******/' % ifun.fullname yield 'tag_type %s(RuntimeState * rts, Configuration * C)' % h_stepfunc def vEmitStepfuncEntry(self): yield 'Cursor _0 = C->cursor();' def vEmitSynthesizedStepfunc(self, ibuiltin, h_stepfunc): assert False def vEmitFunctionInfotab(self, ifun, h_info, h_stepfunc): flags = ifun.metadata.get('all.flags', 0) | common.F_STATIC_OBJECT yield 'InfoTable const %s{' % h_info yield ' /*tag*/ T_FUNC' yield ' , /*arity*/ %s' % ifun.arity yield ' , /*alloc_size*/ sizeof(Head) + sizeof(Arg[%s])' % max(1, ifun.arity) yield ' , /*flags*/ %s' % showflags(flags) yield ' , /*name*/ %s' % _dquote(ifun.name) yield ' , /*format*/ "%s"' % ('p' * ifun.arity) yield ' , /*step*/ %s' % h_stepfunc yield ' , /*type*/ nullptr' yield ' };' yield '' def vEmitConstructorInfotab(self, ictor, h_info, h_datatype): flags = ictor.metadata.get('all.flags', 0) | common.F_STATIC_OBJECT yield 'InfoTable const %s{' % h_info yield ' /*tag*/ T_CTOR + %r' % ictor.index yield ' , /*arity*/ %s' % ictor.arity yield ' , /*alloc_size*/ sizeof(Head) + sizeof(Arg[%s])' % max(1, ictor.arity) yield ' , /*flags*/ %s' % showflags(flags) yield ' , /*name*/ %s' % _dquote(ictor.name) yield ' , /*format*/ "%s"' % ('p' * ictor.arity) yield ' , /*step*/ nullptr' yield ' , /*type*/ &%s' % h_datatype yield ' };' yield '' def vEmitDataType(self, itype, h_datatype, ctor_handles): h_ctortable = self.next_private_symbolname(compiler.CONSTRUCTOR_TABLE) self.symtab.insert( h_ctortable, compiler.CONSTRUCTOR_TABLE , 'constructor table for %r' % itype.fullname ) yield 'static InfoTable const * %s[] = { %s };' % ( h_ctortable, ', '.join('&%s' % h for h in ctor_handles) ) self.symtab.make_defined(h_ctortable) yield 'DataType const %s { %s, %r, %r, F_STATIC_OBJECT, %s };' % ( h_datatype, h_ctortable, len(itype.constructors), 't' , _dquote(itype.name) ) yield '' def vEmitStringLiteral(self, string, h_string): yield 'static char const * %s = %s;' % (h_string, _dquote(string)) def vEmitValueSetLiteral(self, values, h_valueset, h_valueset_data): if len(values) == 0 or isinstance(values[0], int): dt_code = 'i' elif isinstance(values[0], float): dt_code = 'f' elif isinstance(values[0], str): dt_code = 'c' # The runtime reads the values through an Arg pointer, so each element # has the size of an Arg. A char value is a char32_t literal. yield 'static Arg const %s[] = {%s};' % ( h_valueset_data, ', '.join(_cxxshow(v, use_char=True) for v in values) ) yield 'static ValueSet const %s{(Arg *) %s, %r, %r};' % ( h_valueset, h_valueset_data, len(values), dt_code ) def vEmitMetadata(self, md, h_md): # A metadata object as plain data (cyrt/bom.hpp): an array of entries, in # key order, and the record that names it. The entries of an empty # object are a null pointer. if not md: yield 'static bom::Metadata const %s = {nullptr, 0};' % h_md return h_entries = self.next_private_symbolname(compiler.MODULE_DATA, 'md') self.symtab.insert(h_entries, compiler.MODULE_DATA, '<metadata entries>') yield 'static bom::Entry const %s[] = {%s};' % ( h_entries, ', '.join(_bom_entry(key, md[key]) for key in sorted(md)) ) self.symtab.make_defined(h_entries) yield 'static bom::Metadata const %s = {%s, %d};' % ( h_md, h_entries, len(md) ) def vEmitModuleDefinition(self, imodule, h_module): # The module record _bom_ and its tables, as plain data (cyrt/bom.hpp). # The loader finds the record by its name, checks its version, and # decodes it. A table comes before the table that points to it. cxx = renderer.PY_RENDERER lines = [] def table(ctype, suffix, descr, rows): ''' Appends a static array of ``ctype`` that holds ``rows``. Returns the name of the array and its length; an empty table is null. ''' rows = list(rows) if not rows: return 'nullptr', 0 h_table = self.next_private_symbolname(compiler.MODULE_DATA, suffix) self.symtab.insert(h_table, compiler.MODULE_DATA, descr) lines.append('static %s %s[] = {' % (ctype, h_table)) for prefix, row in cxx.prettylist(rows, level=0): lines.append(prefix + row) lines.append(' };') self.symtab.make_defined(h_table) return h_table, len(rows) def with_length(name_and_length): return '%s, %d' % name_and_length imports = table( 'char const * const', 'imports', 'imports of %r' % imodule.fullname , (_dquote(name) for name in imodule.imports) ) h_md = self.internMetadata(imodule.metadata) aliases = table( 'bom::Alias const', 'aliases', 'aliases of %r' % imodule.fullname , ( '{%s, %s}' % (_dquote(k), _dquote(v)) for k, v in imodule.aliases.items() ) ) type_rows = [] for itype in imodule.types.values(): h_type = self.vGetSymbolName(itype, compiler.DATA_TYPE) type_md = self.internMetadata(itype.metadata) ctor_mds = [ self.internMetadata(ictor.metadata) for ictor in itype.constructors ] ctors, _ = table( 'bom::Metadata const * const', 'ctors' , 'constructor metadata of %r' % itype.fullname , ('&%s' % md for md in ctor_mds) ) type_rows.append('{&%s, %s, &%s}' % (type_md, ctors, h_type)) types = table( 'bom::Type const', 'types', 'types of %r' % imodule.fullname, type_rows ) function_rows = [] for ifun in imodule.functions.values(): vis = 'PRIVATE' if ifun.is_private else 'PUBLIC ' h_info = self.vGetSymbolName(ifun, compiler.INFO_TABLE) fun_md = self.internMetadata(ifun.metadata) function_rows.append('{%s, &%s, &%s}' % (vis, fun_md, h_info)) functions = table( 'bom::Function const', 'functions' , 'functions of %r' % imodule.fullname, function_rows ) # A source under the installation is named relative to SPRITE_HOME, and # another source relative to the directory of the object; the loader # resolves the name (see _source_file_name and loader.source_file). filename = 'nullptr' if imodule.filename is None \ else _dquote(_source_file_name(imodule.filename)) # A const object at namespace scope has internal linkage unless it is # declared extern; the loader looks the record up by name. lines.append('extern bom::Module const _bom_;') lines.append('bom::Module const _bom_ = {') lines.append(' /*version */ bom::VERSION') lines.append(' , /*fullname */ %s' % _dquote(imodule.fullname)) lines.append(' , /*filename */ %s' % filename) lines.append(' , /*imports */ %s' % with_length(imports)) lines.append(' , /*metadata */ &%s' % h_md) lines.append(' , /*aliases */ %s' % with_length(aliases)) lines.append(' , /*types */ %s' % with_length(types)) lines.append(' , /*functions*/ %s' % with_length(functions)) lines.append(' };') return lines def vEmitModuleImport(self, imodule, h_module): return [] def vEmit_compileS_IVarDecl(self, vardecl, varname): # A variable that the step only passes on is a plain pointer; the rule # is in passthrough.py. It starts null: a recursive let builds a forward # reference from a variable not yet assigned, and an unassigned Variable # yields null as well (Variable::rvalue). A variable that a case # scrutinizes, or that is the base of a path or of a node assignment, is # a Variable. if vardecl.vid in self.plain_vars: yield 'Node * %s = nullptr;' % varname else: yield 'Variable %s;' % varname def vEmit_compileS_IFreeDecl(self, vardecl, varname): yield 'auto %s = rts->freshvar();' % varname def vEmit_compileS_IVarAssign(self, assign, lhs, rhs): if assign.vid in self.plain_vars: # A plain pointer takes the rendered right side: the plain read of a # successor (vEmit_compileE_IVarAccess), a new node, a literal, or # another plain variable. yield '%s = %s;' % (lhs, rhs) elif isinstance(assign.expr, icurry.IVarAccess): # A Variable takes the indexer, which records the path and the guards. yield '%s = %s;' % (lhs, self.variableAccess(assign.expr)) elif isinstance(assign.expr, icurry.IVar) \ and assign.expr.vid not in self.free_vars: # An alias copies the Variable. Both sides of an alias have one kind # (passthrough.py), so the right side is a Variable as well. A free # variable on the right side is a plain pointer to a node the step # built, and takes the path below. yield '%s = %s;' % (lhs, rhs) else: # A node the step builds or names: a call, a partial application, a # choice, a literal, or a string. The Variable targets a cell of the # frame that holds the node, with no path and no guard. The decision # rests on the kind of the expression, not on the rendered text: a # pinned constructor (false_()), a literal of the tables (int_(2)), a # literal node or a partial node of the module (CyL..., CyP...) are # nodes as much as Node::create(...) is, and a Variable has no # assignment from a Node * (issue #59). tmpname = 'tmp%s' % lhs yield 'Node * %s = %s;' % (tmpname, rhs) yield '%s.target = %s;' % (lhs, tmpname) def variableAccess(self, ivaraccess): ''' The indexer of Variable for a path: one subscript per entry. Variable::operator[] takes one index; a comma list such as _1[0,1] would be the C++ comma operator. ''' var = self.vEmit_compileE_IVar(ivaraccess.var) return var + ''.join('[%s]' % i for i in ivaraccess.path) def vEmit_compileS_INodeAssign(self, assign, lhs, rhs): # Patch a forward reference of a recursive let. Index to the parent of # the slot, then write the slot through Variable::set_successor. The # rendered ``lhs`` is not used: ``_1[1] = _2`` would assign to a temporary # Variable and never write the successor. path = list(assign.path) assert path var = self.vEmit_compileE_IVar(assign.lhs.var) parent = var + ''.join('[%s]' % i for i in path[:-1]) yield '%s.set_successor(%s, %s);' % (parent, path[-1], rhs) def vEmit_compileS_IExempt(self, exempt): yield 'return _0->make_failure();' def vEmit_compileS_IReturn(self, iret, expr): # The result is written into the redex when it fits the block of the # redex (Node::rewrite), as the Python backend rewrites every result in # place. Otherwise the redex is forwarded to a new node. A reference # result is forwarded as well, unless it is a primitive value # (Node::forward_or_copy). if isinstance(iret.expr, (icurry.IVar, icurry.IVarAccess)): yield 'return _0->forward_or_copy(%s);' % expr elif self.resultFits(iret.expr): if isinstance(iret.expr, icurry.ILit): # A literal is a reference in ICurry, so the generic compiler rendered # it as a new node (int_(0)). The info table and the value go into # the redex instead. expr = self.compileE(iret.expr, primary=False) yield 'return _0->rewrite(%s);' % expr else: yield '_0->forward_to(%s);' % expr yield 'return T_FWD;' def resultFits(self, expr): ''' Whether the node that ``expr`` denotes fits the block of the redex. A pinned constructor never does (see PINNED_INFOS). A partial application and a value of another kind are forwarded. ''' assert self.redex_alloc_size is not None if isinstance(expr, icurry.IPartialCall): return False elif isinstance(expr, icurry.ICall): # A call is saturated: one successor per argument. if self.importSymbol(expr.symbolname) in PINNED_INFOS: return False size = alloc_size(len(expr.exprs)) elif isinstance(expr, icurry.IOr): # The node is Prelude.? applied to both sides. size = alloc_size(2) else: # A boxed literal or a string: a head and one word. lit = expr.lit if isinstance(expr, icurry.ILit) else expr if not isinstance( lit, (icurry.IInt, icurry.IChar, icurry.IFloat, icurry.IString) ): return False size = alloc_size(1) return size <= self.redex_alloc_size def vEmit_compileS_ICaseCons(self, icase, h_datatype, varident): yield 'auto tag = rts->hnf(C, &%s, &%s);' % (varident, h_datatype) yield 'switch(tag)' switchbody = [] for branch in icase.branches: value = self.interp.symbol(branch.symbolname).info.tag switchbody.append(('case %s:' % value, branch.symbolname)) switchbody.append(list(self.compileS(branch.block))) switchbody.append('default: return tag;') yield switchbody def vEmit_compileS_ICaseLit(self, icase, h_sel, h_values): yield 'auto tag = rts->hnf(C, &%s, &%s);' % (h_sel, h_values) yield 'if(tag < T_CTOR) return tag;' assert icase.branches br = icase.branches[0] if isinstance(br.lit, icurry.IFloat): yield 'auto switch_value = NodeU{%s.target}.float_->value;' % h_sel for branch in icase.branches: yield 'if(switch_value == %r)' % branch.lit.value yield list(self.compileS(branch.block)) yield 'else return _0->make_failure();' else: if isinstance(br.lit, icurry.IChar): yield 'switch(NodeU{%s.target}.char_->value)' % h_sel elif isinstance(br.lit, icurry.IInt): for branch in icase.branches: _check_int_literal(branch.lit.value) yield 'switch(NodeU{%s.target}.int_->value)' % h_sel else: raise CompileError('bad switch type: %r' % type(br.lit)) switchbody = [] for branch in icase.branches: switchbody.append('case %s:' % _cxxshow(branch.lit.value, use_char=True)) switchbody.append(list(self.compileS(branch.block))) switchbody.append('default: return _0->make_failure();') yield switchbody def vEmit_compileE_IVar(self, ivar): return '_%s' % ivar.vid def vEmit_compileE_IVarAccess(self, ivaraccess, var): # An access in an expression passes its value on (an argument, or the # result of the step), so a path of one entry is read as a plain node: a # successor of the redex through Node::successor_node, which keeps a set # guard in the slot, and a successor of a Variable through # Variable::successor_node, which wraps the value in the guards the # Variable crossed. A longer path takes the indexer. An assignment to a # Variable takes the indexer as well (vEmit_compileS_IVarAssign). if len(ivaraccess.path) == 1: arrow = '->' if ivaraccess.vid == 0 else '.' return '%s%ssuccessor_node(%s)' % (var, arrow, ivaraccess.path[0]) return self.variableAccess(ivaraccess) LIT_CONSTRUCTOR = { 'CyI7Prelude3Int' : 'int_' , 'CyI7Prelude5Float': 'float_' , 'CyI7Prelude4Char' : 'char_' } def vEmit_compileE_ILiteral(self, iliteral, h_ctor, primary): # A primary literal is a node. The runtime keeps one node for each # small integer and each ASCII character (int_ and char_ in # cyrt/builtins.hpp return them), and the module keeps one node for each # of its other literals (internLiteralNode). So a step allocates nothing # for a literal. A non-primary literal is the info table and the value, # for a rewrite of the redex. if isinstance(iliteral, icurry.IInt): _check_int_literal(iliteral.value) shown = _cxxshow(iliteral.value, use_char=True) if not primary: return '&%s, Arg(%s)' % (h_ctor, shown) elif self.isSmallValue(iliteral): return '%s(%s)' % (self.LIT_CONSTRUCTOR[h_ctor], shown) else: return self.internLiteralNode(h_ctor, shown) def isSmallValue(self, iliteral): ''' Whether the runtime keeps a shared node for the literal: an Int from SMALL_INT_MIN to SMALL_INT_MAX or a Char up to SMALL_CHAR_MAX. ''' if isinstance(iliteral, icurry.IInt): return cyrt.SMALL_INT_MIN <= iliteral.value <= cyrt.SMALL_INT_MAX elif isinstance(iliteral, icurry.IChar): return ord(iliteral.value) <= cyrt.SMALL_CHAR_MAX else: return False def internLiteralNode(self, h_ctor, shown): ''' The symbol of the node of a literal of this module. The node is made when the module loads and lives as long as the process (literal_node in cyrt/builtins.hpp). One node serves every occurrence of the literal in the module. The key is the spelling: -0.0 and 0.0 are two literals. ''' key = compiler.LITERAL_NODE, h_ctor, shown existing = self.intern_store.get(key) if existing is not None: return existing h_lit = self.next_private_symbolname(compiler.LITERAL_NODE) self.symtab.insert( h_lit, compiler.LITERAL_NODE, '<literal node: %s>' % shown ) self.target_object['.strings'].append( 'static Node * const %s = literal_node(&%s, Arg(%s));' % (h_lit, h_ctor, shown) ) self.symtab.make_defined(h_lit) self.intern_store[key] = h_lit return h_lit def vEmit_compileE_IString(self, istring, h_string, primary): text = '&_biString_Info, Arg(%s)' % h_string return 'Node::create(%s)' % text if primary else text def vEmit_compileE_IUnboxedLiteral(self, iunboxed, primary): return repr(iunboxed) @replace_singletons def vEmit_compileE_ICall(self, icall, h_info, args, primary): text = '&%s%s' % (h_info, ''.join(', ' + e for e in args)) return 'Node::create(%s)' % text if primary else text def vEmit_compileE_IPartialCall(self, ipcall, h_info, args, primary): # 'primary' intentionally ignored: a partial application is always a # node. One without arguments is a node of the module, made once at # load (see internPartialNode); one with arguments is allocated with # the arguments inline (Node::create_partial). if not ipcall.exprs: return self.internPartialNode(h_info, ipcall.missing) text = '&%s%s' % (h_info, ''.join(', ' + e for e in args)) return 'Node::create_partial(%s)' % text def internPartialNode(self, h_info, missing): ''' The symbol of the node of a partial application without arguments: the symbol ``h_info`` as a value, as in ``map f xs``. The node is made when the module loads and lives as long as the process (partial_node in cyrt/builtins.hpp); one node serves every occurrence in the module. The missing count is spelled, because the info table of a head of this module is not yet initialized when the node is made. ''' key = compiler.PARTIAL_NODE, h_info existing = self.intern_store.get(key) if existing is not None: return existing h_partial = self.next_private_symbolname(compiler.PARTIAL_NODE) self.symtab.insert( h_partial, compiler.PARTIAL_NODE, '<partial node: %s>' % h_info ) self.target_object['.strings'].append( 'static Node * const %s = partial_node(&%s, %d);' % (h_partial, h_info, missing) ) self.symtab.make_defined(h_partial) self.intern_store[key] = h_partial return h_partial def vEmit_compileE_IOr(self, ior, lhs, rhs, primary): h_choice = self.importSymbol('Prelude.?') text = "&%s, %s, %s" % (h_choice, lhs, rhs) return 'Node::create(%s)' % text if primary else text # The bytes that a C++ string literal spells with a symbolic escape. _CXX_ESCAPES = { ord('"') : '\\"' , ord('\\'): '\\\\' , ord('\n'): '\\n' , ord('\r'): '\\r' , ord('\t'): '\\t' } def _source_file_name(filename): ''' The name of the source file of a module for the record of the module. A file under the installation is named relative to it (SPRITE_HOME; toolchain._filenames.installed_relpath), as "curry/Prelude.curry". Any other file is named relative to the directory where the toolchain places the object of that source, the product directory beside the source (".curry/<subdir>/"), so the name is "../../M.curry" and begins with a parent reference. The loader tells the two forms apart by that prefix (loader.source_file). So the record names no tree, and an object loaded from a copy of its tree names the source of the copy. ''' from ...toolchain import _filenames relpath = _filenames.installed_relpath(filename) if relpath is not None: return relpath objdir = os.path.dirname(_filenames.icurryfilename(filename)) relpath = os.path.relpath(filename, objdir) if not relpath.startswith((os.pardir + os.sep, os.curdir + os.sep)): relpath = os.path.join(os.curdir, relpath) return relpath def _dquote(string): ''' A C++ string literal that holds the UTF-8 encoding of ``string``. Printable ASCII is written as is. Every other byte is written as a three-digit octal escape, which no following character can extend. The question mark is escaped as well, so that no trigraph can form. ''' parts = ['"'] for byte in strings.ensure_binary(string): escape = _CXX_ESCAPES.get(byte) if escape is not None: parts.append(escape) elif 0x20 <= byte < 0x7f and byte != ord('?'): parts.append(chr(byte)) else: parts.append('\\%03o' % byte) parts.append('"') return ''.join(parts) def _char_literal(char): ''' A char32_t literal that holds the code point of ``char``. Printable ASCII is written as is; every other code point is written as a hex escape. ''' assert len(char) == 1 if char in '\\\'': return "U'\\%s'" % char codepoint = ord(char) if 0x20 <= codepoint < 0x7f: return "U'%s'" % char return "U'\\x%x'" % codepoint def _bom_entry(key, value): ''' One bom::Entry (cyrt/bom.hpp): the key, the kind, the number, and the text of a metadata entry. MDValue holds a string, an int, or a bool; another value is an error here, not in the C++ compiler. ''' if isinstance(value, bool): return '{%s, bom::BOOLEAN, %d, nullptr}' % (_dquote(key), int(value)) elif isinstance(value, int): if not -2 ** 31 <= value < 2 ** 31: raise CompileError( 'metadata %r holds %r, which does not fit a C++ int' % (key, value) ) return '{%s, bom::INTEGER, %d, nullptr}' % (_dquote(key), value) elif isinstance(value, str): return '{%s, bom::STRING, 0, %s}' % (_dquote(key), _dquote(value)) else: raise CompileError( 'metadata %r holds a value of type %r; the C++ backend stores str, ' 'int, and bool' % (key, type(value).__name__) ) @visitation.dispatch.on('arg') def _cxxshow(arg, use_char=False): assert False # The range of Int (issue #105). The generated code spells an Int literal # as a C++ constant, which the C++ compiler narrows in silence when it does # not fit, so a literal outside the range is refused here, with the message # of the node (the materializer refuses the same literal at load). The # minimum has no literal of its own in C++: it is spelled as an expression. INT_MIN = -(1 << 63) INT_MAX = (1 << 63) - 1 def _check_int_literal(value): if not INT_MIN <= value <= INT_MAX: raise CurryTypeError( '%d is outside the range of Int (%d to %d)' % (value, INT_MIN, INT_MAX) ) @_cxxshow.when(bool) def _cxxshow(bit, use_char=False): return 'true' if bit else 'false' @_cxxshow.when((int, float)) def _cxxshow(i, use_char=False): if i == INT_MIN and type(i) is int: return '(-9223372036854775807LL - 1)' return repr(i) @_cxxshow.when(str) def _cxxshow(string, use_char=False): if use_char: return _char_literal(string) else: return _dquote(string)
[docs] def write_module( target_object, stream, goal=None, section_headers=True, module_main=True , goalscheme=None ): # goalscheme, the type of the goal, serves the footer of the Python # backend; the entry point written here runs no goal. render = renderer.CXX_RENDERER.renderLines for section_name in compiler.TargetObject.SECTIONS: if section_headers: stream.write('/* SECTION: %s */\n' % section_name) section_data = target_object[section_name] section_text = render(section_data) stream.write(section_text) if section_text: stream.write('\n\n') if module_main: for line in _generate_main(target_object, goal): stream.write(line) stream.write('\n') stream.write('\n\n')
def _generate_main(target_object, goal): # The entry point of a program is a stub (the Quickstart says so); the # object is loaded by the runtime, never run by the dynamic loader, so # the text names no program interpreter (an .interp section named the # loader of the build machine until pothole batch 4 of 2026-10-10, and # nothing read it). yield '#include <iostream>' yield '' yield 'extern "C" void entry()' yield '{' yield ' std::cout << "Entry for %r" << std::endl;' % target_object.unitname yield ' std::exit(0);' yield '}'