Ivy to cpp

include ivylang
include cpplang
include hash
include reader
include pass_flat
include pass_typeinfer
include analysis

module generic_to_cpp(ivytype,cpptype,cppclass) = {
    action to_cpp(s:ivytype,st:tocppst) returns (res:cppclass,st:tocppst) = {
        var t : cpptype;
        (t,st) := s.to_cpp_int(st);
        t.ann := s.ann;
        res := t
    }
}


module binary_op_to_cpp(ivytype,cpptype,cppclass) = {
    instantiate generic_to_cpp(ivytype,cpptype,cppclass)
    action to_cpp_int(s:ivytype,st:tocppst) returns (res:cpptype,st:tocppst) = {
        (res.lhs,st) := s.lhs.to_cpp(st);
        (res.rhs,st) := s.rhs.to_cpp(st);
    }
}

object ivy = { ...
The set of names reserved by C++. Ivy names that clash with these have to be changed.

    function cpp_reserved_word(X:str) : bool

    after init {
        cpp_reserved_word("bool") := true;
        cpp_reserved_word("char") := true;
        cpp_reserved_word("int") := true;
        cpp_reserved_word("long") := true;
        cpp_reserved_word("new") := true;
        cpp_reserved_word("and") := true;
        cpp_reserved_word("or") := true;
        cpp_reserved_word("not") := true;
        cpp_reserved_word("float") := true;
        cpp_reserved_word("double") := true;
    }
A 'access_path' is an abstraction of an lvalue. The path of an expression is a sequence of identifiers that gives the name of a root variable, with a sequence of field references appended. For example the path of f.a(x).b(y) is [f,a,b]. If a.b is a function, then the path of a.b(x,y).c is [a.b,c]. On the other hand, (x+y).a is not an lvalue and does not have a path. Paths are used in alias analysis.

    object access_path = {
        type this = struct {
            elems : vector[ident]
        }
    }
Two paths may alias if one is a prefix of the other

    action path_may_alias(v:access_path,w:access_path) returns (res : bool) = {
        res := true;
        var idx := v.elems.begin;
        while res & idx < v.elems.end & idx < w.elems.end {
            res := (v.elems.value(idx) = w.elems.value(idx));
            idx := idx.next
        }
    }
A pair consisting of an lvalue and an alias count

    type lvalue_count = struct {
        lvalue : cpp.expr,
        path : access_path,
        cnt : pos
    }
A path tree is a map from access paths to vector[ident]. It provides traversal from root to leaf.

    object path_tree = {
    object elem = {
        type this = struct {
        present : bool,
        children : vector[ident],
        leaves : vector[ident]
        }
    }
    object undo_rec = {
        type this = struct {
        children : vector[ident],
        leaf : ident
        }
    }
    var v : vector[undo_rec]
    type this = struct {
        value : (ident -> elem),
        undos : vector[undo_rec]
    }
Add an item to the list for a given path

    action add(s:this, path:access_path, item:ident) returns(s:this)= {
        var und : undo_rec;
        var id := path.elems.value(0);
        var idx := path.elems.begin.next;
        while idx < path.elems.end {
        var newid := path.elems.value(idx).prefix(id);
        if ~s.value(newid).present {
            s.value(id).children := s.value(id).children.append(newid);
            s.value(newid).present := true;
            und.children := und.children.append(id);
        }
        id := newid;
        idx := idx.next;
        }
        s.value(id).leaves := s.value(id).leaves.append(item);
        und.leaf := id;
        s.undos := vector[undo_rec].append(s.undos,und);
    }
Undo the last addition of an item

    action undo(s:this) returns(s:this)= {
        var und := s.undos.back;
        var idx := und.children.begin;
        while idx < und.children.end {
        var id := und.children.value(idx);
        var cid := s.value(id).children.back;
        s.value(id).children := s.value(id).children.pop_back;
        s.value(cid).present := false;
        idx := idx.next;
        }
        s.value(und.leaf).leaves := s.value(und.leaf).leaves.pop_back;
        s.undos := s.undos.pop_back;
    }
Collect a list of all items for paths that may alias with the given path.

    action collect(s:this, path:access_path) returns (res:vector[ident]) = {
        var id := path.elems.value(0);
        var idx := path.elems.begin.next;
        while idx < path.elems.end {
        res := res.extend(s.value(id).leaves);
        id := path.elems.value(idx).prefix(id);
        idx := idx.next;
        }
        res := collect_rec(s,id,res);
    }

    action collect_rec(s:this, id:ident, res:vector[ident]) returns (res:vector[ident]) = {
        var elm := s.value(id);
        res := res.extend(elm.leaves);
        var idx := elm.children.begin;
        while idx < elm.children.end {
        res := collect_rec(s,elm.children.value(idx),res);
        idx := idx.next;
        }
    }
    }
This struct holds the state of the borrowing state machine for a given local variable. The meanings of the starte variables are:

  • lvalue: the lvalue that is borrowed by the local;
  • num_paths: the number of access paths occurring in lvalue
  • assigned: the local has been assigned
  • saw_ref: one of the access paths in lvalue may be referenced
  • saw_mod: one of the access paths in lvalue may be modified
  • cancel_const: the local cannot be a const ref
  • cancel_non_const: the local cannot be a non-const ref

The state is updated as code in scope of the borrowing is compiled.

    object borrowing = {
    type this = struct {
        lvalue : expr,
        num_paths : vector[access_path].domain,
        returned : bool,
        saw_ref : bool,
        saw_mod : bool,
        cancel_const : bool,
        cancel_non_const : bool,
        cond_nesting : pos
    }
    }

    instance ident_to_declvec : hash_map(ident,vector[decl])
    instance ident_to_cppclass : hash_map(ident,expr)
    instance ident_to_prototype : hash_map(ident,prototype)
    instance ident_to_borrowing : push_pop_map(ident,borrowing)
    instance cppident_to_cppexpr : push_pop_map(cpp.ident,cpp.expr)

    object tocppst = {
        type this = struct {
Maps types to their members, which includes destructors and member actions.

            members : ident_to_declvec,
Maps types to their C++ superclasses

            cppclasses : ident_to_cppclass,
Identifiers of all objects

            objects : ident_set,
Types of global actions and variables

            globals : global_types,
True if we are emitting a member action

            is_member : bool,
The name of the "this" parameter in a member

            this_ident : ident,
True if we are in a class declaration

        in_class : bool,
True if we are only emitting prototypes

            proto_only : bool,
The subtyping relation

            subtype_rel : subtypes,
True if we are emitting a native C++ name

            native : bool,
True if we are emitting forward declarations

            forward : bool,
Current outputs (lvalues to which expression value will be assigned).

            outputs : vector[expr],
Code to be emitted before the current statement

            code : vector[cpp.stmt],
Counter for temporary variables

            counter : pos,
Map from action names to action prototypes

            protos : ident_to_prototype,
The dead list of the current context. If an lvalue is on this list, then any occurrence is the last reference to the value.

            dead : vector[lvalue_count],
Tracks the local variables in scope

            locals : local_tracker,
The set of constructor functions

        constructors : ident_set,
True is compiling rhs of a dot operator

        dot_rhs : bool,
Path tree giving path conflicts

        conflicts : path_tree,
States of each current borrowing

        borrowings : ident_to_borrowing,
Number of borrowins in above

        num_borrowings : pos,
Map from local variables to borrowed lvalues

        fix_borrow_map : cppident_to_cppexpr,
Depth of nesting of conditionals

        cond_nesting : pos,
Depth of nesting of loops

        loop_nesting : pos,
Annotation of current assignment

            asgn_ann : annot,
Depth of nesting of "inbounds" pragma

            inbounds_nesting : pos

        }

        action add_member(s:this,namesp:ident,member:decl) returns (s:this) = {
            var emp : vector[decl];
            s.members := s.members.set(namesp,s.members.get(namesp,emp).append(member));
        }

        action add_stmt(s:this,code:cpp.stmt) returns (s:this) = {
            s.code := s.code.append(code);
        }
Get the added code and clear it.

    action get_code(s:this,ann:annot) returns (s:this,res:cpp.stmt) = {
            res := cpp.sequence.fold_right(s.code,ann);
            s.code := vector[cpp.stmt].empty;
    }
This adds a statement to the end of the current code, clears the code and returns the result.

        action wrap_stmt(s:this,code:cpp.stmt,ann:annot) returns (s:this,res:cpp.stmt) = {
            s := s.add_stmt(code);
        (s,res) := s.get_code(ann);
        }

    }
Generate a temporay symbol name

    action temp_sym(s:tocppst,ann:annot) returns (s:tocppst,res:cpp.expr) = {
        var name : str;
        name := "__tmp";
        name := name.extend(s.counter.to_str);
        s.counter := s.counter.next;
        res := cpp.symbol.makestr(name,ann);
    }
This declares a temporary variable in the current code context and returns it.

    action make_temp(s:tocppst,ty:expr,ann:annot) returns (s:tocppst,res:cpp.expr) = {
    (s,res) := temp_sym(s,ann);
        var vst : cpp.varst;
        (vst.vtype._type,s) := fix_variant_type(ty,s);
        vst.vtype.name := res;
        vst.ann := ann;
        var vstt : cpp.stmt := vst;
        s := s.add_stmt(vstt);
    }
Given a C++ lvalue, this returns its "path". The path is an identifier that gives the name of the variable, with a sequence of field references appended. For example the path of f.a(x).b(y) is f.(a.b). The path of a.b(x,y) is a.b. On the other hand, (x+y).a is not an lvalue and does not have a path. For such cases, we return ok = false.

    action lvalue_path(s:expr,path:access_path) returns (path:access_path, ok:bool) = {
        if s isa symbol {
            if s.get_verb = verb.none {
                path.elems := path.elems.append(s.get_name);
                ok := true
            }
        } else if s isa app {
            if s.is(verb.colon) {
                (path,ok) := lvalue_path(s.get_arg(0),path)
            } else if s.is(verb.dot) {
                (path,ok) := lvalue_path(s.get_arg(0),path);
                if ok {
                    path.elems := path.elems.append(s.get_arg(1).get_arg(0).get_name);
                }
            } else {
                if s.is(verb.none) {
                    (path,ok) := lvalue_path(s.get_func,path);
                }
            }
        }
    }
Workaround

    var dummy_vector_access_path : vector[access_path]
Get a list of all of the lvalue paths occurring in a C++ expression. The boolean parameters are:

  • ao : arguments only
  • ro : roots only

    action lvalue_paths(s:expr,paths:vector[access_path],ao:bool,ro:bool)
        returns (paths:vector[access_path]) =
    {
        if s.is(verb.colon) {
            paths := lvalue_paths(s.get_arg(0),paths,ao,ro)
        } else if s.is_typed(verb.comma) {
            paths := lvalue_paths(s.get_arg(0),paths,ao,ro);
            paths := lvalue_paths(s.get_arg(1),paths,ao,ro)
        } else {
            if ~ao {
                var path : access_path;
                var ok : bool;
                (path,ok) := lvalue_path(s,path);
                if ok {
                    paths := paths.append(path);
                }
            };
            if ~ro & s isa app {
                if s.is(verb.dot) {
                    paths := lvalue_paths(s.get_arg(0),paths,true,false);
                } else {
                    var args := s.get_args;
                    var idx := args.begin;
                    while idx < args.end {
                        paths := lvalue_paths(args.value(idx),paths,false,false);
                        idx := idx.next;
                    }
                }
            }
        }
    }
This determines whether a C++ lvalue is dead in current context

    action is_dead(e:cpp.expr,st:tocppst,cnt:pos) returns (res:bool) = {
        var idx := st.dead.begin;
        while ~res & idx < st.dead.end {
            var d := st.dead.value(idx);
            res := cpp.expr.eq(e,d.lvalue) & d.cnt <= cnt;
            idx := idx.next
        }
    }
In context of class declarations, identifiers are declared without their namespaces.

    action name_in_context(name:expr,st:tocppst) returns(name:expr) = {
        if st.in_class {
        name := symbol.make(name.get_name.get_member,name.get_ann);
    };
    }

    object ident = { ...
        action to_cpp(s:this,native:bool) returns (res:cpp.ident)
    }
Mangle a C++ identifier. This turns x::y into x__y, which is needed for name subscripts to make legal C++ identifiers.

    action mangle(s:cpp.ident) returns (res:str) = {
        if s isa cpp.dotident {
            res := mangle(s.get_namesp);
            res := res.extend("__");
            res := res.extend(s.get_member.to_str);
        } else {
            res := s.to_str
        }
    }
In translating Ivy names to C++, we have to avoid clashes with C++ reserved words like "char" and "bool". We do this by prepending "". C++ names beginning with "" are reserved for the compiler. If the native argument is true, we treate the identifier as a native C++ name and do not perform this translation. Also non-native names with subscripts need to be mangled. For example, x[t] becomes x__t.

    action strident_to_cpp(s:strident,native:bool) returns (t:cpp.strident) = {
        if ~native & cpp_reserved_word(s.val) {
            t.val := "__";
            t.val := t.val.extend(s.val);
        } else {
            t.val := s.val;
        };
        var idx := s.subscrs.begin;
        while idx < s.subscrs.end {
            var subs := s.subscrs.value(idx).to_cpp(native);
            if native {
                t.subscrs := t.subscrs.append(subs);
            } else {
                t.val := t.val.extend("__");
                t.val := t.val.extend(mangle(subs))
            };
            idx := idx.next
        }
    }

    object strident = { ...
        action to_cpp(s:this,native:bool) returns (res:cpp.ident) = {
            var x := strident_to_cpp(s,native); # workaround
            var y : cpp.ident := x;
            res := y;
        }
    }

    object dotident = { ...
        action to_cpp(s:this,native:bool) returns (res:cpp.ident) = {
            var t : cpp.dotident;
            t.namesp := s.namesp.to_cpp(native);
            t.member := strident_to_cpp(s.member,native);
            res := t
        }
    }

    object expr = { ...
        action to_cpp(s:expr,st:tocppst) returns (res:cpp.expr,st:tocppst)
    }
When translating symbols to C++, we need to take care of name clashes between namespaces (Ivy objects) and classes (Ivy types). If a type has the same identifier as an object, we append .__t to the identifier of the type.

    action fix_object_clash(id:ident,st:tocppst) returns (id:ident) = {
        if st.objects.mem(id) {
            var tok : strident;
            tok.val := "__t";
            id := dotident.make(id,tok);
        }
    }
Also, when compiling a class member function, the first argument is implicit, and is replaced in the C++ by (*this). A few verbs are spelled differently in C++. We fix this here.

    object symbol = { ...
        instantiate generic_to_cpp(ivy.symbol,cpp.symbol,cpp.expr)
        action to_cpp_int(s:symbol,st:tocppst) returns (res:cpp.symbol,st:tocppst) = {
            var vrb := s.get_verb;
            if vrb = verb.not {
                res.name := cpp.strident.make("!")
            } else if vrb = verb.equals {
                res.name := cpp.strident.make("==")
            } else if vrb = verb.notequals {
                res.name := cpp.strident.make("!=")
            } else if vrb = verb.ite {
                res.name := cpp.strident.make("?")
            } else if st.is_member & s.name = st.this_ident & ~st.dot_rhs {
                res.name := cpp.strident.make("(*this)");
            } else {
                var id := s.name;
                if ~st.locals.mem(id) {
                    id := fix_object_clash(s.name,st);
                };
                res.name := id.to_cpp(st.native);
            };
            res.vrb := cpp.str_to_verb(res.name.to_str);
        }
    }

    action make_vector_type(ty:expr) returns (ty:expr) = {
    var vid : strident;
    vid.val := "vector";
    vid.subscrs := vid.subscrs.append(ty.get_name);
    var name := vid.prefix(strident.make("ivy"));
    ty := symbol.make(name,ty.get_ann)
    }

    action fix_tpl_param(s:expr,st:tocppst) returns (res:cpp.expr,st:tocppst) = {
s := symbol.make(fix_object_clash(s.get_name,st),s.get_ann);
        (res,st) := fix_variant_type(s,st)
    }

    action make_md_vector_type(dom:vector[expr],rng:expr,st:tocppst)
        returns (res:cpp.expr,st:tocppst) =
    {
    var vid : cpp.strident;
    vid.val := "vector";
        var crng : cpp.expr;
        (crng,st) := fix_tpl_param(rng,st);
    vid.subscrs := vid.subscrs.append(crng.get_name);
    var idx := dom.begin;
    while idx < dom.end {
            var dty : cpp.expr;
            (dty,st) := fix_tpl_param(dom.value(idx),st);
        vid.subscrs := vid.subscrs.append(dty.get_name);
        idx := idx.next
    };
    var name := vid.prefix(cpp.strident.make("ivy"));
    res := cpp.symbol.make(name,rng.get_ann)
    }
Function types are handled specially, using C++ templates in the standard header. For now we have only a dense representation using std::vector. This works only when the domain types are convertible to size_t. We also need at least a hash table type here for sparse data.

    action function_type(ty:expr,st:tocppst) returns (res:cpp.expr,st:tocppst) = {
    if ty isa app {
var cty := ty.curry; var rng := function_type(cty.get_arg(1)); ty := make_vector_type(rng);
        (res,st) := make_md_vector_type(times.unfold_left(ty.get_arg(0)),ty.get_arg(1),st)
    } else {
            (res,st) := ty.to_cpp(st)
        }
    }
Make a C++ expression of the form ivy::from_str(arg)

    action make_from_string(ty:cpp.expr,arg:cpp.expr,ann:annot) returns(res:cpp.expr) = {
        var id : cpp.strident;
        id.val := "from_str";
        id.subscrs := id.subscrs.append(ty.get_name);
        var name := cpp.dotident.make(cpp.strident.make("ivy"),id);
        res := cpp.app.make1(cpp.symbol.make(name,ann),arg,ann);
    }
Make a C++ expression of the form ivy::from_flt(arg)

    action make_from_float(ty:cpp.expr,arg:cpp.expr,ann:annot) returns(res:cpp.expr) = {
        var id : cpp.strident;
        id.val := "from_flt";
        id.subscrs := id.subscrs.append(ty.get_name);
        var name := cpp.dotident.make(cpp.strident.make("ivy"),id);
        res := cpp.app.make1(cpp.symbol.make(name,ann),arg,ann);
    }
Returns true if the C++ express is *this. This case requires special handling for indirect calls.

    action is_cpp_this(s : cpp.expr) returns (res:bool) = {
    if s isa cpp.symbol {
        res := (s.get_name.to_str = "(*this)");
    }
    }
Make a C++ function call. There are four cases here:

Ordinary function call: prefix.f(a0..an) Method call: a0.f(a1..an) Indirect const method call: a0->f(a1..an) Indirect non-const method call: a0.get().f(a1..an)

The last two cases are used if f : (t * ... -> u) is a member function where type t is a variant type. However, if the first argument is (this) we do not* use this form, as this is a normal C++ pointer, not a special Ivy variant pointer.

    action make_cpp_call(func:expr, args:vector[cpp.expr], ann:annot, st:tocppst, proto : prototype)
        returns (res:cpp.expr, st:tocppst) =
    {
        if func_is_member(func)   {
            var fid := func.get_arg(0).get_name.to_cpp(false);
            var cfunc := cpp.symbol.make(fid.get_member,func.get_ann);
            if is_variant_type(get_dom0(func.get_arg(1)),st) & ~is_cpp_this(args.value(0)) {
        var arg := proto.args.value(0);
        if arg.is_const {
                    cfunc := cpp.arrow.make(args.value(0),cfunc,ann)
        } else {
            cfunc := cpp.dot.make(cpp.app.make0(cpp.dot.make(args.value(0),cpp.symbol.makestr("get",ann),ann),ann),cfunc,ann)
        }
            } else {
                cfunc := cpp.dot.make(args.value(0),cfunc,ann)
            };
            res := cpp.app.make(cfunc,args.segment(1,args.end),ann);
        } else {
            var cfunc : cpp.expr;
            (cfunc,st) := func.to_cpp(st);
            res := cpp.app.make(cfunc,args,ann);
        }
    }
After executing a function call, the call gives up ownership of its arguments, possibly allowing the arguments to be side-effected by future calls. A good example is this:

a := a.resize(size(a) - 1);

Here, the call to size has ownership over a which would prevent resize from side-effecting a. However, since the call to size executes first, it gives up its ownership before resize executes, so resize can side-effect a.

    action unown_func_args(args:vector[expr],st:tocppst) returns (st:tocppst) = {
        var idx := args.begin;
        while idx < args.end {
            var arg := args.value(idx);
            var path : access_path;
            var ok : bool;
            (path,ok) := lvalue_path(arg,path);
            if ok {
                st := unown_path(path,st);
            };
            idx := idx.next
        }
    }
A dead lvalue can be converted to an rvalue, which may allow the C++ compiler to use a move rather than a copy.

    action make_rvalue_if_dead(st:tocppst,inp:cpp.expr) returns (inp:cpp.expr) = {
    if is_dead(inp,st,1) {
        inp := cpp.app.make1(cpp.symbol.makestr("std::move",inp.get_ann),inp,inp.get_ann);
    }
    }
Translate an action call to C++. This is complicated, because we have to apply the action prototype and deal with call-by-reference.

In-place optimization. There are two cases when a call can modify an argument in-place:

(1) Borrowing. This is an in/out parameter given an lvalue that is returned by the expression and has exactly one remaining alias in the expression.

(2) Return by reference. This is an out parameter given an lvalue that is returned by the expression and has no aliases in the expression.

In both of these cases, we simply pass the lvalue by non-const reference. Any other non-const reference parameter is copied to/from a temporary to preserve value semantics.

If the call is return-by-value, then we return the translated function call. If return-by-reference, we emit the function call and return the lvalue passed to the output parameter, which may be a temporary. If the call is the expression root and it is return-by-value, then we we assign the outputs as a side effect, by copying any temporaries back to the original lvalues, and we return ().

Note that any remaining inputs may be either by const ref or by value. We need not make this distinction here, however, as call-by-value is the same as call-by-const-ref with an internal copy operation.

    action call_to_cpp (func:expr, inputs:vector[expr], ann:annot, st:tocppst)
        returns (res:cpp.expr,st:tocppst) =
    {
        var proto := st.protos.value(func.get_arg(0).get_name);
First evaluate all of the inputs.

        var inpvals : vector[cpp.expr];
        var idx := proto.args.begin;
        while idx < proto.args.end {
            var inp : cpp.expr;
            var parg := proto.args.value(idx);
            if parg.is_input {
                var save_outputs := st.outputs;
                st.outputs := vector[expr].empty;
                (inp,st) := inputs.value(parg.inpos).to_cpp(st);
                st.outputs := save_outputs;
            };
            inpvals := inpvals.append(inp);
            idx := idx.next
        };
Evaluate all of the arguments

        var args : vector[cpp.expr];
        idx := proto.args.begin;
        var rets : vector[cpp.stmt];
        var ret_vals : vector[cpp.expr];
        var kdx := inpvals .begin;
        while idx < proto.args.end {
            var parg := proto.args.value(idx);
            var inp := inpvals.value(kdx);
            var out : cpp.expr;
            if parg.is_output & st.outputs.end > 0 {
                (out,st) := st.outputs.value(parg.outpos).to_cpp(st);
                if ~is_dead(out,st, 1 if parg.is_input else 0) {
            var orig := out;
                    (st,out) := make_temp(st,parg.name.get_arg(1),ann);
                    rets := rets.append(cpp.asgn.make(orig,out,ann));
                }
            } else if parg.is_ref & ~parg.is_const {
                if is_dead(inp,st,1) {
                    out := inp;  # if input is a dead lvalue, side-effect it
                } else {
                    (st,out) := make_temp(st,parg.name.get_arg(1),ann);
                };
        if parg.is_output {
            ret_vals := ret_vals.append(out);
        }
            } else if parg.is_input & ~parg.is_ref {
        inp := make_rvalue_if_dead(st,inp);
        }
        if parg.is_input & parg.is_ref & ~parg.is_const & ~cpp.expr.eq(inp,out) {
                st := st.add_stmt(cpp.asgn.make(out,inp,ann));
                inp := out;
            } else if parg.is_output {
                inp := out
            };
            args := args.append(inp);
            idx := idx.next;
            kdx := kdx.next
        };
        var cann := st.asgn_ann if st.outputs.end > 0 else ann;
        (res,st) := make_cpp_call(func,args,cann,st,proto);
        st := unown_func_args(inputs,st);
        if ret_vals.end > 0 | rets.end > 0 | ~proto.has_ret {
            st := st.add_stmt(cpp.asgn.make(cpp.empty.make(ann),res,ann));
        var jdx := rets.begin;
        while jdx < rets.end {
                st := st.add_stmt(rets.value(jdx));
        jdx := jdx.next;
        };
            res := cpp.comma.fold_left(ret_vals,ann);
        }
    }

    action make_cast_to_bool(s:cpp.expr) returns (s:cpp.expr) = {
    s := cpp.app.make1(cpp.symbol.makestr("ivy::native_bool",s.get_ann),s,s.get_ann);
    }

    action make_isa(s:cpp.expr,ty:cpp.expr) returns (s:cpp.expr) = {
    s := cpp.app.make1(cpp.symbol.makestr1("ivy::isa",ty.get_name,s.get_ann),s,s.get_ann);
    }
Translate a function application to C++.

    object app = { ...
        action to_cpp(s:this,st:tocppst) returns (res:cpp.expr,st:tocppst) = {

        if s.is(verb.colon) {
Generally, type decorations are not emitted to C++. However, in case of a numeral, we need to cast the numeral to the appropriate type. If a string literal, we use the template function ivy::from_str to convert to type t.

If a symbol is an action and its type is not a function type, we have to add () for C++.

                var arg := s.args.value(0);
        (res,st) := arg.to_cpp(st);
                if (arg isa symbol) {
            if arg.get_verb = verb.truev | arg.get_verb = verb.falsev {
                        res := cpp.app.make1(cpp.symbol.makestr("ivy::native_bool",s.get_ann),res,s.get_ann);
            } else if arg.get_verb = verb.numeral | st.constructors.mem(arg.get_name) {
                        var ty : cpp.expr;
                (ty,st) := s.args.value(1).to_cpp(st);
                        res := cpp.app.make1(ty,res,s.get_ann);
                    } else if arg.get_verb = verb.string {
                        var ty : cpp.expr;
                        (ty,st) := s.args.value(1).to_cpp(st);
                        res := make_from_string(ty,res,s.get_ann);
                    } else if arg.get_verb = verb.fltnum {
                        var ty : cpp.expr;
                        (ty,st) := s.args.value(1).to_cpp(st);
                        res := make_from_float(ty,res,s.get_ann);
                    } else if st.globals.is_action.mem(arg.get_name) {
                        if ~(s.args.value(1).is(verb.arrow)) {
                            var args : vector[cpp.expr];
                            res := cpp.app.make(res,args,res.get_ann);
                        }
                    }
                }
            } else if s.is(verb.arrow) {
Function types need special translation to C++

        var tmp : expr := s;  # workaround
        (res,st) := function_type(tmp,st)
            } else if s.is(verb.isav) {
x isa t is translated to x.isa()

        var arg0 : cpp.expr;
        var arg1 : cpp.expr;
        (arg0,st) := s.args.value(0).to_cpp(st);
        (arg1,st) := s.args.value(1).to_cpp(st);
        if is_cpp_this(arg0) {
            res := make_isa(arg0,arg1);
        } else {
            var isam : cpp.strident;
            isam.val := "isa";
            isam.subscrs := isam.subscrs.append(arg1.get_name);
            var id : cpp.ident := isam;
            res := cpp.symbol.make(id,s.ann);
            res := cpp.dot.make(arg0,res,s.ann);
            res := cpp.app.make0(res,s.ann);
        }
        } else if s.func.is(verb.colon) & s.func.get_arg(0).get_verb = verb.castv {
A cast operator looks like (cast : (t->u))(x). This may indicate a need for an upcast in C++.

                var ty := s.func.get_arg(1).get_arg(1);  # the range type of the cast operator
                (res,st) := upcast(ty,s.args.value(0),st)
        } else {
Detect whether a function application is an action call.

                var func : expr;
                var args : vector[expr];
                (func,args) := get_app(s);

                if st.globals.is_action.mem(func.get_arg(0).get_name) {
An action call needs special treatment

                    (res,st) := call_to_cpp(func,args,s.ann,st);


                } else {
This is the normal case of function application. We translate the function and all the arguments and then apply.

                    var capp : cpp.app;
                    (capp.func,st) := s.func.to_cpp(st);
                    {
                        var save_outputs := st.outputs;
                        st.outputs := vector[expr].empty;
                        var idx := s.args.begin;
                        while idx < s.args.end {
                            var arg : cpp.expr;
                if s.func.get_verb = verb.dot & idx = 1 { st.dot_rhs := true; };
                            (arg,st) := s.args.value(idx).to_cpp(st);
                st.dot_rhs := false;
                            capp.args := capp.args.append(arg);
                            idx := idx.next
                        };
                        st.outputs := save_outputs;
For ternary operators, we need to permute the arguments

                        if capp.is(cpp.verb.ite) {
                            var tmp := capp.args.value(0);
                            capp.args := capp.args.set(0,capp.args.value(1));
                            capp.args := capp.args.set(1,tmp);
                        };
For inbounds array accesses, we use the member function __inp rather than operator(). This does an ordinary unchecked vector access.

                        if st.inbounds_nesting > 0 {
                            var is_data := s.func.is(verb.dot);
                            if s.func.is(verb.colon) {
                                var arg := s.func.get_arg(0);
                                is_data := arg isa symbol & arg.get_verb = verb.none;
                            };
                            if is_data {
                                var inb := cpp.symbol.makestr("__inb",capp.func.get_ann);
                                capp.func := cpp.dot.make(capp.func,inb,s.ann);
                            }
                        }
If of the form (x:t).f, where t is a variant type, convert to x->f, since variant types are represented with smart pointers.

                        if s.func.get_verb = verb.dot {
                            var lhs := s.args.value(0);
                            if lhs.is(verb.colon) {
                                var ty := lhs.get_arg(1);
                                if is_variant_type(ty,st) & ~is_cpp_this(capp.args.value(0)){
                                    capp.func := cpp.symbol.makestr("->",capp.func.get_ann)
                                }
                            }
                        }
                    };
                    capp.ann := s.ann;
                    res := capp;
                    st := unown_func_args(s.args,st);
If of the form x.f where is a member action of input arity zero, add empty parentheses for C++.

if s.is(verb.dot) { var lhs := s.args.value(0); var ty := lhs.get_type; var fid := s.args.value(1).get_arg(0).get_name.prefix(ty.get_name); if st.globals.is_action.mem(fid) { if ~(s.args.value(1).get_arg(1).get_arg(1).is(verb.arrow)) { var args : vector[cpp.expr]; res := cpp.app.make(res,args,res.get_ann); } } }

                }
        }
        }
    }

    object stmt = { ...
        action to_cpp(s:stmt,st:tocppst) returns (res:cpp.stmt,st:tocppst)
    }

    object skipst = { ...
instantiate generic_to_cpp(skipst,cpp.skipst,cpp.stmt)
        action to_cpp(s:this,st:tocppst) returns (res:cpp.stmt,st:tocppst) = {
            var t : cpp.skipst;
            t.ann := s.ann;
            res := t
    }
    }

    object cpp.stmt = { ...
        action fix_borrow(s:this,st:ivy.tocppst) returns (res:cpp.stmt,st:ivy.tocppst) = {
        res := s;
    }
        action setup_fix_borrow(s:this,st:ivy.tocppst) returns (st:ivy.tocppst,ok:bool) = {
        ok := false;
    }
    }

    action upcast(lhsty:expr,rhs:expr,st:tocppst) returns (res:cpp.expr,st:tocppst) = {
        (res,st) := rhs.to_cpp(st);
        if false & rhs.is(verb.colon) {
            var rhsty := rhs.get_arg(1);
            if st.subtype_rel.is_subtype(rhsty,lhsty) {
        var crhsty : cpp.expr;
        (crhsty,st) := rhsty.to_cpp(st);
                res := cpp.new.make(cpp.app.make1(crhsty,res,rhs.get_ann),rhs.get_ann);
        var clhsty : cpp.expr;
        (clhsty,st) := fix_variant_type(lhsty,st);
        res := cpp.app.make1(clhsty,res,rhs.get_ann)
            }
        }
    }

    action unown_path(path:access_path,st:tocppst) returns (st:tocppst) = {
        var idx := st.dead.begin;
        while idx < st.dead.end {
            var lc := st.dead.value(idx);
            if path_may_alias(path,lc.path) {
                lc.cnt := lc.cnt.prev;
                st.dead := st.dead.set(idx,lc);
            };
            idx := idx.next
        }
    }

    action kill_lvalue(e:expr,st:tocppst,paths:vector[access_path]) returns (st:tocppst) = {
        var path : access_path;
        var ok : bool;
        (path,ok) := lvalue_path(e,path);
        if ok {
            var alias_count : pos := 0;
            var idx := paths.begin;
            while idx < paths.end {
                if path_may_alias(path,paths.value(idx)) {
                    alias_count := alias_count.next;
                };
                idx := idx.next
            };
            var lc : lvalue_count;
            (lc.lvalue,st) := e.to_cpp(st);
            lc.path := path;
            lc.cnt := alias_count;
            st.dead := st.dead.append(lc);
        }
    }
Here, we find all of the lvalues that are dead after evaluation of an expression that references a given list of access paths. The paramer es lists the lvalues that are assigned after evaluation (and thus are dead even if they are globals). We add to this all of the locals that are referenced in the expression and known to be dead because they are not subsequently referenced.

For each dead lvalue, we store a count in st.dead of the the number of path references in the expression that may alias with the lvalue. When compiling the expression, this count allows us to determine when the last possible reference that may alias with the dead lvalue as been reached. At this point the lvalue may be passed by non-const reference or moved, since its value is no longer needed.

Note: any local that is referenced inside a loop but declared outside the loop is considered live (i.e., it may be referenced in the next loop iteration, and we do not keep track of this).

    action kill_lvalues(es:vector[expr],st:tocppst,paths:vector[access_path])
        returns (st:tocppst) =
    {
        var idx := es.begin;
        while idx < es.end {
            st := kill_lvalue(es.value(idx),st,paths);
            idx := idx.next
        }

    var seen : (ident -> bool);
    var jdx := paths.begin;
    while jdx < paths.end {
        var id := paths.value(jdx).elems.value(0);
        if st.locals.mem(id) & ~seen(id) {
        seen(id) := true;
        var ann : annot;
        var li := st.locals.value(id);
        if ~li.is_live & ~li.is_ref & li.loop_nesting >= st.loop_nesting {
            st := kill_lvalue(symbol.make(id,ann),st,paths);
        }
        }
        jdx := jdx.next;
    }
    }
If the "this" argument of a member function is assign to a variable or passed as an argument, and if "this" is of a supertype, then we have to use the virtual __upcast method of the object to get a C-o-W pointer to the object. Otherwise C++ will slice the object.

    action fix_variant_arg(s:expr,rhs:cpp.expr,st:tocppst) returns (rhs:cpp.expr) = {
    if ~s.is_typed(verb.castv) & is_cpp_this(rhs) {
        var ty := s.get_type;
        if is_variant_type(ty,st) {
        var ann := rhs.get_ann;
        rhs := cpp.dot.make(rhs,cpp.app.make0(cpp.symbol.makestr("__upcast",ann),ann),ann);
        }
    }
    }
Given a list of referenced paths, update the live locals.

    action update_live(paths:vector[access_path],st:tocppst) returns (st:tocppst) = {
    var idx := paths.begin;
    while idx < paths.end {
        var id := paths.value(idx).elems.value(0);
        if st.locals.mem(id) {
        var li := st.locals.value(id);
        li.is_live := true;
        st.locals := st.locals.set(id,li);
        }
        idx := idx.next;
    }
    }
Handling of assignments. For each lvalue on the lhs, we count the number of possibly aliasing references on the rhs, and record this information in st.outputs. We then translate the rhs. If the rhs is a call to an action with return by reference, then this will have the side effect of assigning the outputs. If so, the return value will be (). Otherwise, we generate an assigment of the return values to the outputs. Finally, we assemble the generated code and restore the context.

    object asgn = { ...
        action to_cpp(s:this,st:tocppst) returns (resd:cpp.stmt,st:tocppst) = {
            var paths : vector[access_path];
            paths := lvalue_paths(s.rhs,paths,false,false);
            var res : cpp.asgn;
            res.ann := s.ann;
            (res.lhs,st) := s.lhs.to_cpp(st);
            st.outputs := comma.unfold_left(s.lhs);
            st.asgn_ann := s.ann;
            st := kill_lvalues(st.outputs,st,paths);
        (res.rhs,st) := s.rhs.to_cpp(st);
        if res.rhs.get_verb ~= cpp.verb.empty {
res.rhs := fix_variant_arg(s.rhs,res.rhs,st);
        res.rhs := make_rvalue_if_dead(st,res.rhs);
        st := st.add_stmt(res);  # Don't generate () = ();
        };
        (st,resd) := st.get_code(s.ann);
            st.outputs := vector[expr].empty;
            st.dead := vector[lvalue_count].empty;
        paths := lvalue_paths(s.lhs,paths,true,false);
        st := update_live(paths,st);
        if st.num_borrowings > 0 {
        var apaths : vector[access_path];
        apaths := lvalue_paths(s.lhs,apaths,false,true);
        st := update_borrowings(apaths,true,st);
        st := check_borrowing_return(s,st);
        st := update_borrowings(paths,false,st);
        }
        }
    }
When borrowing, we sometimes need to strip off "std::move", indicating that an expression should be treated as an lvalue.

    action strip_move(s:cpp.expr) returns (s:cpp.expr) = {
    if s isa cpp.app {
        if s.get_func.get_name.to_str = "std::move" {
        s := s.get_arg(0);
        }
    }
    }
When fixing up borrows, if an assignment is a return of a borrowed value, we replace it with skip (i.e., since the lhs and rhs are the same address).

    object cpp.asgn = {...
        action fix_borrow(s:this,st:ivy.tocppst) returns (res:cpp.stmt,st:ivy.tocppst) = {
        res := s;
        var rhs := ivy.strip_move(s.rhs);
        if rhs isa symbol {
        var id := rhs.get_name;
        if st.fix_borrow_map.mem(id) {
            var lvalue := st.fix_borrow_map.value(id);
            if expr.eq(lvalue,s.lhs) {
            res := cpp.skipst.make(s.ann);
            }
        }
        }
    }
    }
Set up to borrow an lvalue. A potential borrowing is an assignment of the form x := y, where x is a local and y is an lvalue, provided x is dead at then end of the current scope and no action calls occur in y. A const borrowing is allowed if no lvalue occurring in the term y is modified before the last occurrence of x. A non-const borrowing is allowed if no lvalue occurring in y is referenced before the x is returned. A return of x is an assignment y := x.

To set up a potential borrowing, we add entry to st.borrowings for local. We also the item x in the conflict map to every access path in y. This allows us mark failed borrowings in subsequent code.

Returns ok=true if a borrowing was set up.

Note: a variable referenced in a loop but declared outside the loop is considered to be live outside the current scope, since it may be live at the beginning of the next iteration and we do not track this. It might actually be dead (i.e., if the first reference in the loop is always a full assignment) but in this case the programmer can be punished for not declaring the variable inside the loop.

    action setup_borrowing(s:stmt,st:tocppst) returns (st:tocppst,ok:bool,id:ident) = {
    if s isa asgn {
        var x := s.get_lhs;
        if x.is(verb.colon) {
        x := x.get_arg(0);
        }
        if x isa symbol {
        id := x.get_name;
        if st.locals.mem(id) & ~st.borrowings.mem(id) {
            var y := s.get_rhs;
            var path : access_path;
            path, var is_lvalue := lvalue_path(y,path);
            var li := st.locals.value(id);
            if is_lvalue & ~li.is_live & ~li.is_ref & li.loop_nesting >= st.loop_nesting {
            if path.elems.value(0) ~= x.get_name & is_functional(y,st.globals) {
                st.borrowings := st.borrowings.push;
                st.num_borrowings := st.num_borrowings.next;
                var br : borrowing;
                br.lvalue := y;
                var paths : vector[access_path];
                paths := lvalue_paths(y,paths,false,false);
                br.num_paths := paths.end;
                br.cond_nesting := st.cond_nesting;
                st.borrowings := st.borrowings.set(id,br);
                var idx := paths.begin;
                while idx < paths.end {
                st.conflicts := st.conflicts.add(paths.value(idx),id);
                idx := idx.next;
                }
                ok := true;
            }
            }
        }
        }
    }
    }
This undoes the effect of setup_borrowing, where id is the identifier returned by setup_borrowing.

    action unsetup_borrowing(id:ident,st:tocppst) returns (st:tocppst) = {
    var br := st.borrowings.value(id);
    var idx := 0  : (vector[access_path].domain);
    while idx < br.num_paths {
        st.conflicts := st.conflicts.undo;
        idx := idx.next;
    }
    st.borrowings := st.borrowings.pop;
    st.num_borrowings := st.num_borrowings.prev;
    }
Whenver we access paths, we update the state of any conflicting borrowings. We note in the state of any conficting borrowings whether we've see a mod or a ref. Note, a mod is any modification of a conflicting path, while a ref is only a ref of the value of the path. Further, if a borrow variable is assigned, we note this in the borrowing state and cancel const reference. Any reference to the borrow variable after a modification of a conflicting path cancels all borrowing. Any reference to the value of the borrowed lvalue after the variable assigned cancels all borrowing.

    action update_borrowings(paths:vector[access_path], is_mod:bool, st:tocppst) returns (st:tocppst) = {
    var idx := paths.begin;
    while idx < paths.end {
        var path := paths.value(idx);
        var ids := st.conflicts.collect(path);
        var jdx := ids.begin;
        while jdx < ids.end {
        var id := ids.value(jdx);
        if st.borrowings.mem(id) {
            var br := st.borrowings.value(id);
            var lpath : access_path;
            lpath, var ok := lvalue_path(br.lvalue,lpath);
            if path_may_alias(path,lpath) {
            if br.returned {
                br.cancel_non_const := true;
            }
            }
            if is_mod & br.saw_ref {
            br.cancel_non_const := true;
            br.cancel_const := true;
            }
            st.borrowings.map := st.borrowings.map.set(id,br);
        }
        jdx := jdx.next;
        }
        var root := path.elems.value(0);
        if st.borrowings.mem(root) {
        var br := st.borrowings.value(root);
        if is_mod {
            br.cancel_const := true;
            if ~br.returned {
            br.cancel_non_const := true;
            }
        }
        br.saw_ref := true;
        st.borrowings.map := st.borrowings.map.set(root,br);
        }
        idx := idx.next;
    }
    }
When we see an assignment y := x, we look to see if x is borrowing y. If so, we mark x unassigned. This means that x and y are now semantically equal again, so is is safe to reference 'y'. In this case, we return ok = true, meaning that the assignment should be skipped, as the lhs and rhs are the same lvalue.

    action check_borrowing_return(s:asgn, st:tocppst) returns (st:tocppst) = {
    if s.rhs.is(verb.colon) {
        var rhs := s.rhs.get_arg(0);
        if rhs isa symbol {
        var id := rhs.get_name;
        if st.borrowings.mem(id) {
            var br := st.borrowings.value(id);
            if expr.eq(br.lvalue,s.lhs) & st.cond_nesting = br.cond_nesting {
            br.returned := true;
            }
            st.borrowings.map := st.borrowings.map.set(id,br);
        }
        }
    }
    }
Turn a local assignment into a reference

    action make_local_ref(typing:expr,lhs:cpp.stmt,is_const:bool,st:tocppst) returns (res:cpp.stmt,st:tocppst) = {
    var vst : cpp.varst;
    (vst.vtype._type,st) := fix_variant_type(typing.get_arg(1),st);
        vst.vtype.name := lhs.get_lhs;
        vst.ann := lhs.get_ann;
    vst.has_initval := true;
    vst.initval := strip_move(lhs.get_rhs);
    vst.vtype.is_const := is_const;
    vst.vtype.is_borrow := true;
    vst.vtype.is_ref := true;
    res := vst;
    }
Open or close scope of a pragma. Pragmas:

  • inbounds: promises array references in the following code are within the allocated bounds, turns of bounds checking

    action pragma_stmt(s:stmt,start:bool,st:tocppst) returns(st:tocppst) = {
        if s isa pragmast {
            var ok : bool := false;
            var e := s.get_expr;
            if e isa symbol {
                var name := e.get_name.to_str;
                if name = "inbounds" {
                    ok := true;
                    if start {
                        st.inbounds_nesting := st.inbounds_nesting.next;
                    } else {
                        st.inbounds_nesting := st.inbounds_nesting.prev;
                    }
                }
            }
            if ~ok {
                report_error(bad_syntax.make(e),e.get_ann);
            }
        }
    }
Notice here that for a sequence s1;s2, we compile s2 first and then s1. This allows the compilation of s1 to depend on whether locals are live in s2.

    object sequence = { ...
        action to_cpp(s:this,st:tocppst) returns (resd:cpp.stmt,st:tocppst) = {
            var res : cpp.sequence;
            res.ann := s.ann;
            st.locals := st.locals.push_stmt(s.lhs,st.loop_nesting);
        st, var ok, var bid := setup_borrowing(s.lhs,st);
            st := pragma_stmt(s.lhs,true,st);
            (res.rhs,st) := s.rhs.to_cpp(st);
            st := pragma_stmt(s.lhs,false,st);
        var br:borrowing;
        if ok {
        br := st.borrowings.value(bid);
        st := unsetup_borrowing(bid,st);
        }
            (res.lhs,st) := s.lhs.to_cpp(st);
            st.locals := st.locals.pop;
        if ok {
        if ~br.cancel_const | ~br.cancel_non_const {
            res.lhs,st := make_local_ref(s.lhs.get_lhs,res.lhs,~br.cancel_const,st);
            resd := cpp.sequence.make(res,cpp.skipst.make(s.ann),s.ann);
        } else {resd := res}
        } else {resd := res;}
        }
    }

    object cpp.sequence = { ...
    action fix_borrow(s:this,st:ivy.tocppst) returns (resd:cpp.stmt,st:ivy.tocppst) = {
            var res : cpp.sequence;
            res.ann := s.ann;
            (res.lhs,st) := s.lhs.fix_borrow(st);
        st, var ok := s.lhs.setup_fix_borrow(st);
            (res.rhs,st) := s.rhs.fix_borrow(st);
        if ok {
        st.fix_borrow_map := st.fix_borrow_map.pop;
        }
        resd := res;
        }
    }
Translate "while" statement. We have to account for any side effects in the condition evaluation. For example, suppose we have if f(x) (s1) else {s2} where f(x) is return-by-reference. This will translate to:

bool __tmpXXX = x;
f(__tmpXXX);
if (__tmpXXX) {s1} else {s2}

    object ifst = { ...
        action to_cpp(s:this,st:tocppst) returns (resd:cpp.stmt,st:tocppst) = {
        var res : cpp.ifst;
        res.ann := s.ann;
        var paths : vector[access_path];
        paths := lvalue_paths(s.cond,paths,false,false);
        if st.num_borrowings > 0 {
        st := update_borrowings(paths,false,st);
        }
            (res.cond,st) := s.cond.to_cpp(st);
            var code := st.code;  # side effects of cond evaluation
        st.code := vector[cpp.stmt].empty;
        st.cond_nesting := st.cond_nesting.next;
            (res.thenst,st) := s.thenst.to_cpp(st);
            (res.elsest,st) := s.elsest.to_cpp(st);
        st.cond_nesting := st.cond_nesting.prev;
        resd := res;
            st.code := code;
        (st,resd) := st.wrap_stmt(resd,s.ann);
        st := update_live(paths,st);
        }
    }

    object cpp.ifst = { ...
    action fix_borrow(s:this,st:ivy.tocppst) returns (resd:cpp.stmt,st:ivy.tocppst) = {
        var res : cpp.ifst;
        res.ann := s.ann;
        res.cond := s.cond;
            (res.thenst,st) := s.thenst.fix_borrow(st);
            (res.elsest,st) := s.elsest.fix_borrow(st);
        resd := res

    }
    }
Translate "while" statement. We have to account for any side effects in the condition evaluation. For example, suppose we have while f(x) {s1} where f(x) is return-by-reference. This will translate to:

while (true) {
    bool __tmpXXX = x;
    f(__tmpXXX);
    if (!__tmpXXX) break;
    s1
}

    object whilest = { ...
        action to_cpp(s:this,st:tocppst) returns (resd:cpp.stmt,st:tocppst) = {
        var res : cpp.whilest;
        res.ann := s.ann;
        var paths : vector[access_path];
        paths := lvalue_paths(s.cond,paths,false,false);
        if st.num_borrowings > 0 {
        st := update_borrowings(paths,false,st);
        }
            (res.cond,st) := s.cond.to_cpp(st);
            var code := st.code;  # side effects of cond evaluation
        st.code := vector[cpp.stmt].empty;
        st.cond_nesting := st.cond_nesting.next;
        st.loop_nesting := st.loop_nesting.next;
            (res.body,st) := s.body.to_cpp(st);
        st.cond_nesting := st.cond_nesting.prev;
        st.loop_nesting := st.loop_nesting.prev;
        if code.end > 0 {
        st.code := code;
        var brkcond := cpp.not.make(res.cond,s.ann);
        var brkif := cpp.breakst.make(s.ann);
        var brkelse := cpp.skipst.make(s.ann);
        var brkst := cpp.ifst.make(brkcond,brkif,brkelse,s.ann);
        st := st.add_stmt(brkst);
        res.cond := cpp.symbol.makestr("true",s.ann);
        (st,res.body) := st.wrap_stmt(res.body,s.ann);
        };
        resd := res;
        (st,resd) := st.wrap_stmt(resd,s.ann);
        st := update_live(paths,st);
        }
    }

    object cpp.whilest = { ...
    action fix_borrow(s:this,st:ivy.tocppst) returns (resd:cpp.stmt,st:ivy.tocppst) = {
        var res : cpp.whilest;
        res.ann := s.ann;
        res.cond := s.cond;
            (res.body,st) := s.body.fix_borrow(st);
        resd := res
    }
    }

    object varst = { ...
instantiate generic_to_cpp(ivy.varst,cpp.varst,cpp.stmt)
        action to_cpp(s:this,st:tocppst) returns (resd:cpp.stmt,st:tocppst) = {
            var res : cpp.varst;
            res.ann := s.ann;
            (res.vtype._type,st) := fix_variant_type(s.name.get_arg(1),st);
            (res.vtype.name,st) := s.name.get_arg(0).to_cpp(st);
            resd := res;
        }
    }

    object cpp.varst = {
        action setup_fix_borrow(s:this,st:ivy.tocppst) returns (st:ivy.tocppst,ok:bool) = {
        if s.vtype.is_borrow {
        st.fix_borrow_map := st.fix_borrow_map.push;
        st.fix_borrow_map := st.fix_borrow_map.set(s.vtype.name.get_name,s.initval);
        ok := true;
        }
    }
    }

    object decl = { ...
        action to_cpp(s:this,st:tocppst) returns (res:cpp.decl,st:tocppst)
        action reg_member(s:this,st:tocppst) returns (st:tocppst)
        action emitted(s:this,st:tocppst) returns (res:bool) = {
            res := true;
        }
    action record_prototypes(s:this,st:tocppst) returns (st:tocppst)
    }
The full name of a C++ function depends on whether it is a member function. If so, we find take the full name of the class and append the member name. The full name of the class can differ from the name of Ivy type because of object name clashes.

    action full_action_name(name:expr,is_member : bool,st:tocppst)
        returns (res:cpp.expr,st:tocppst) =
    {
        if is_member {
            var pref : ident := name.get_name.get_namesp;
            var clsid := fix_object_clash(pref,st);
            var funid := name.get_name.get_member.prefix(clsid);
            res := cpp.symbol.make(funid.to_cpp(false),name.get_ann);
        } else {
            (res,st) := name.to_cpp(st)
        }
    }

    action is_input_param(s:actdc,p:expr) returns (res:bool) = {
        var idx := s.inputs.begin;
        while ~res & idx < s.inputs.end {
            res := expr.eq(s.inputs.value(idx),p);
            idx := idx.next
        }
    }
Make a variable declaration with an initializer

    action make_initializer(typing:expr,initval:cpp.expr,st:tocppst) returns (res:cpp.stmt,st:tocppst) = {
    var vd : cpp.varst;
    (vd.vtype.name,st) := typing.get_arg(0).to_cpp(st);
    (vd.vtype._type,st) := typing.get_arg(1).to_cpp(st);
    vd.has_initval := true;
    vd.initval := initval;
    vd.ann := typing.get_ann;
    res := vd;
    }
Create a temporary name for an argument that will be copied into a local variable.

    object actdc = { ...

    action record_prototypes(s:this,st:tocppst) returns (st:tocppst) = {
            var proto := s.get_proto(st.globals,st.subtype_rel);
            st.protos := st.protos.set(s.name.get_name,proto);
    }
Here, we translate an Ivy action declaration to a C++ function declaration. The C++ function declarations depends on the action prototype. If the action is a member action, we drop the first input and replace it with *this.

        action to_cpp(s:this,st:tocppst) returns (resd:cpp.decl,st:tocppst) = {
            var res : cpp.funcdecl;
            res.ann := s.ann;
        var proto := st.protos.value(s.name.get_name);
            var has_output := proto.has_ret;
            if has_output {
                (res.ftype.base._type,st) := fix_variant_type(s.outputs.value(proto.ret.outpos).get_arg(1),st);
            } else {
                res.ftype.base._type := cpp.voidtype(s.ann);
            };
            var cprot := st.proto_only;
            var is_member := s.is_member;
            var full_name : cpp.expr;
            (full_name,st) := full_action_name(s.name,is_member,st);
        res.ftype.base.name := member_name(full_name) if (cprot | st.in_class) else full_name;
            var idx := proto.args.begin;
            st.is_member := is_member;
        var argasgns : vector[cpp.stmt];
            if st.is_member {
                idx := idx.next;   # skip the first input "this" if a member
        var parg := proto.args.value(0);
        if ~cprot & parg.is_copy {
            var vd,st := make_initializer(parg.name,cpp.symbol.makestr("(*this)",s.ann),st);
            argasgns := argasgns.append(vd);
        } else {
                    st.this_ident := parg.name.get_arg(0).get_name;
        };
                res.ftype.is_const := proto.args.value(0).is_const;
        if cprot & is_variant_type(proto.args.value(0).name.get_arg(1),st) {
            res.is_virtual := true
        }
            };
            st.locals := st.locals.push;
            while idx < proto.args.end {
                var parg := proto.args.value(idx);
                var arg := parg.name;
                var argt : cpp.simpletype;
                argt.is_const := parg.is_const;
                argt.is_ref := parg.is_ref;
                (argt._type,st) := fix_variant_type(arg.get_arg(1),st);
                st.locals := st.locals.add_var(arg,parg.is_ref,0);
                (argt.name,st) := arg.get_arg(0).to_cpp(st);
        if ~cprot & parg.is_copy {
            (st,argt.name) := temp_sym(st,s.ann);
            var vd,st := make_initializer(parg.name,argt.name,st);
            argasgns := argasgns.append(vd);
        }
                res.ftype.args := res.ftype.args.append(argt);
                idx := idx.next
            };
            res.has_body := ~cprot;
        if res.has_body {
        if s.has_body {
            var body :cpp.stmt;
            (body,st) := s.body.to_cpp(st);
            (body,st) := body.fix_borrow(st);
            res.body := body;
        } else {
            res.body := cpp.skipst.make(s.ann);
        };
        if has_output {
            var rvar : cpp.expr;
            (rvar,st) := s.outputs.value(0).get_arg(0).to_cpp(st);
            var ret := cpp.retst.make(rvar,s.ann);
            res.body := cpp.sequence.make(res.body,ret,s.ann);
            if ~is_input_param(s,s.outputs.value(0)) {
            var vs := cpp.varst.make(res.ftype.base._type,rvar,s.ann);
            res.body := cpp.sequence.make(vs,res.body,s.ann);
            }
        };
        var kdx := argasgns.end;
        while kdx > argasgns.begin {
            kdx := kdx.prev;
            res.body := cpp.sequence.make(argasgns.value(kdx),res.body,s.ann);
        }
        };
            st.locals := st.locals.pop;
            st.is_member := false;
            resd := res;
            if ~st.in_class & cprot {
                resd := add_namespaces(resd,s.name.get_name);
            }
        st.this_ident := 0;
        }
Here, we register actions as members of types. These will be emitted as members of the corresponding C++ class.

        action reg_member(s:this,st:tocppst) returns (st:tocppst) = {
            if s.is_member {
                var actd := s;
                actd.has_body := false;
                st := st.add_member(s.member_type,actd);
            }
        }
An action declaration is not emitted if it is a member and does not have a body. External actions are never emitted.

        action emitted(s:this,st:tocppst) returns (res:bool) = {
            res := (~s.is_member | ~st.proto_only) & s.kind ~= action_kind.external;
        }
If a virtual action has this as an in-out parameter, we don't have an appropriate prototype. The easiest way to handle this is to eliminate the in-out parameter by replacing the output parameter with a fresh name and copying the value. For example, suppose we have this action:

action t.f(s:t,...) returns (s:t,...) { ... }

We replace this by the following equivalent action:

action t.f(__this:t,...) returns (s:t,...) { s := __this; ... }

This results in a copy of s that might be unnecessary if its value is dead. However, it gets us aroud the problem that assigning a value of a subtype to *this will result in the value being sliced.

    action fix_action(s : this, st:tocppst) returns (res : decl) = {
        if s.is_member {
        var outs := param_set(s.outputs);
        var origthis := s.inputs.value(0).get_arg(0);
        var thisty := s.inputs.value(0).get_arg(1);
        var id := origthis.get_name;
        if outs.mem(id) {
            if is_virtual_action(s,st.globals,st.subtype_rel) {
            var resa := s;
            var newthis := symbol.makestr("__this",s.ann);
            var newthisarg := colon.make(newthis,thisty,s.ann);
            resa.inputs := resa.inputs.set(0,newthisarg);
            if resa.has_body {
                resa.body := sequence.make(asgn.make(origthis,newthis,s.ann),resa.body,s.ann);
            };
            res := resa;
            } else {res := s}
        } else {res := s}
        } else {res := s}
    }


    }
Init declarations are not emitted

    object initdc = { ...

        action emitted(s:this,st:tocppst) returns (res:bool) = {
            res := false;
        }
    }

    action add_namespaces_rec(d:cpp.decl,id:ident) returns(d:cpp.decl) = {
        var nd : cpp.namespacedecl;
        nd.ann := d.get_ann;
        var name := id.get_member if (id isa dotident) else id;
        nd.name := cpp.symbol.make(name.to_cpp(false),d.get_ann);
        nd.members := nd.members.append(d);
        d := nd;
        if id isa dotident {
            d := add_namespaces_rec(d,id.get_namesp);
        }
    }

    action add_namespaces(d:cpp.decl,id:ident) returns(d:cpp.decl) = {
        if id isa dotident {
            d := add_namespaces_rec(d,id.get_namesp);
        }
    }

    action member_name(s:cpp.expr) returns(s:cpp.expr) = {
        if (s.get_name) isa cpp.dotident {
            s := cpp.symbol.make(s.get_name.get_member,s.get_ann)
        }
    }
Instantiate a standard template. This produces a type name of the form ivy.tpl[ty].

    action make_std_tpl(tpl:str, ty:str, ann:annot) returns (res:cpp.expr) = {
    var vid : cpp.strident;
    vid.val := tpl;
    vid.subscrs := vid.subscrs.append(cpp.strident.make(ty));
    var tname := cpp.dotident.make(cpp.strident.make("ivy"),vid);
        res := cpp.symbol.make(tname,ann);
    }
Make the name of the C++ enum that corresponds to an Ivy enumerated type. The name is __enum_name where `name is the Ivy name.

    action enum_name(name : cpp.expr) returns (res:str) = {
        res := "__enum_";
        res := res.extend(name.get_name.to_str)
    }
Unfortunately, enumerated types can't be classes in C++. To represent one an enumerated type t, we declared a C++ enum named __enum_t. We then instantiate a template ivy::native_enum<__enum_t> defined in the standard header. This wraps the enum in a class with the necessary standard traits. The form of an enum type name in the C++ code is:

enum __enum_name  {elem1,...elemN};

struct name : ivy::native_enum< __enum_name > {
    name() : ivy::native_enum< __enum_name >() {}
    name(long long value) :
    name(__enum_name value) : ivy::native_enum< __enum_name >(value) {}
};

Note we have to promote the constructors of the base class to the derived class. The parameter sd is the struct declaration. This action prepends the C++ enum declaration.

    action enum_to_cpp(name:cpp.expr,spec:typespec,sd:cpp.decl,st:tocppst)
        returns(res:cpp.decl,st:tocppst) =
    {
Delcare the enum __enum_t.

        var ed : cpp.enumdecl;
        ed.ann := name.get_ann;
        ed.name := cpp.symbol.makestr(enum_name(name),ed.ann);
        var cnstrs := spec.get_elems;
        var idx := cnstrs.begin;
        while idx < cnstrs.end {
            var e : cpp.expr;
            (e,st) := cnstrs.value(idx).to_cpp(st);
            ed.elems := ed.elems.append(member_name(e));
            idx := idx.next
        };
        var gd : cpp.groupdc;
        gd.ann := name.get_ann;
        gd.decls := gd.decls.append(ed);
        gd.decls := gd.decls.append(sd);
        res := gd;
    }
All the delcared classes have to have certain standard traits. These are:

(1) A default constructor (1a) A default copy constructor (1b) A default move constructor (2) A constructor from (signed or unsigned) long long. (3) A conversion to size_t (4) A static predicate __is_seq (5) A == overload (6) A != overload (7) A predicate __is_zero (8) A class __hash of hashers

Here, we add the standard traits to a C++ structure.

Make a C++ constructor for a struct Default constructor does nothing: name() {};

    action make_cpp_cons(t:cpp.structdecl) returns (s:cpp.funcdecl) = {
        s.ftype.base.name := t.name;
        s.has_body := true;
        s.body := cpp.skipst.make(t.ann);
    }

    action make_virt_destr(t:cpp.structdecl) returns (s:cpp.funcdecl) = {
    var name : str;
    name := name.extend("~");
    name := name.extend(t.name.get_name.to_str);
        s.ftype.base.name := cpp.symbol.makestr(name,t.ann);
        s.has_body := true;
        s.body := cpp.skipst.make(t.ann);
    s.is_virtual := true;
    }

    action make_upcast_method(t:cpp.structdecl) returns (s:cpp.funcdecl) = {
        s.ftype.base.name := cpp.symbol.makestr("__upcast",t.ann);
    var ty := t.super if t.has_super else t.name;
    s.ftype.base._type := cpp.symbol.makestr1("ivy::ptr",ty.get_name,t.ann);
        s.has_body := true;
        s.body := cpp.retst.make(cpp.symbol.makestr("(*this)",t.ann),t.ann);
    s.is_virtual := true;
    s.ftype.is_const := true;
    }
Add a default constructor to a class. The default constructor looks like this:

name(\[const\] name & \[&\]) = default;

    action add_default_cons(s:cpp.structdecl,is_const:bool,is_rvalue:bool) returns (s:cpp.structdecl) = {
    var ty : cpp.simpletype;
    ty._type := s.name;
    ty.is_ref := true;
    ty.is_const := is_const;
    ty.is_rvalue := is_rvalue;
    var ncons : cpp.funcdecl;
        ncons.ftype.base.name := s.name;
    ncons.ftype.args := ncons.ftype.args.append(ty);
    ncons.is_default := true;
        s.members := s.members.append(ncons);
    }
Add a default assignment operator to a class. The default operator looks like this:

name & operator = (\[const\] name & \[&\]) = default;

    action add_default_asgn(s:cpp.structdecl,is_const:bool,is_rvalue:bool) returns (s:cpp.structdecl) = {
    var ty : cpp.simpletype;
    ty._type := s.name;
    ty.is_ref := true;
    ty.is_const := is_const;
    ty.is_rvalue := is_rvalue;
    var ncons : cpp.funcdecl;
    ncons.ftype.base._type := s.name;
        ncons.ftype.base.name := cpp.symbol.makestr("operator =",s.ann);
    ncons.ftype.base.is_ref := true;
    ncons.ftype.args := ncons.ftype.args.append(ty);
    ncons.is_default := true;
        s.members := s.members.append(ncons);
    }
Numeric constructor does nothing (so that 0 gives default value): name(long long) {};

    action add_numeric_cons(s:cpp.structdecl) returns (s:cpp.structdecl) = {
        var ncons := make_cpp_cons(s);
        var nconsarg0 : cpp.simpletype;
        nconsarg0._type := cpp.symbol.makestr("long long",s.ann);
        nconsarg0.name := cpp.symbol.makestr("value",ncons.ann);
        ncons.ftype.args := ncons.ftype.args.append(nconsarg0);
        s.members := s.members.append(ncons);
    }
Conversion to size_t returns zero: operator size_t() const { return 0; }

    action add_sizet_conv(s:cpp.structdecl) returns (s:cpp.structdecl) = {

        var tosizet : cpp.funcdecl;
        tosizet.ftype.base.name := cpp.symbol.makestr("operator std::size_t",s.ann);
        tosizet.ftype.is_const := true;
        tosizet.has_body := true;
        tosizet.body := cpp.retst.make(cpp.symbol.makestr("0",s.ann),s.ann);
        s.members := s.members.append(tosizet);
    }
__is_seq returns zero: static bool __is_seq() {return false;}

    action add_is_seq_pred(s:cpp.structdecl) returns (s:cpp.structdecl) = {
        var isseq : cpp.funcdecl;
        isseq.ftype.base._type := cpp.symbol.makestr("bool",s.ann);
        isseq.ftype.base.name := cpp.symbol.makestr("__is_seq",s.ann);
        isseq.is_static := true;
        isseq.has_body := true;
        isseq.body := cpp.retst.make(cpp.symbol.makestr("false",s.ann),s.ann);
        s.members := s.members.append(isseq);
    }
Equality comparison yields member-wise equality: bool operator==(const name &other) const { return f1==other.f1 & ...; }

    action add_eq_pred(s:cpp.structdecl) returns (s:cpp.structdecl) = {
        var eq : cpp.funcdecl;
        eq.ftype.base._type := cpp.symbol.makestr("ivy::native_bool",s.ann);
        eq.ftype.base.name := cpp.symbol.makestr("operator ==",s.ann);
        eq.ftype.is_const := true;
        var eqarg0 : cpp.simpletype;
        eqarg0._type := s.name;
        eqarg0.is_const := true;
        eqarg0.is_ref := true;
        eqarg0.name := cpp.symbol.makestr("other",eq.ann);
        eq.ftype.args := eq.ftype.args.append(eqarg0);
        eq.has_body := true;
        var eqs : vector[cpp.expr];
        var idx := s.members.begin;
        while idx < s.members.end {
            var d := s.members.value(idx);
            if d isa cpp.vardecl {
                var f0 := d.get_name;
                var f1 := cpp.dot.make(eqarg0.name,d.get_name,s.ann);
                var e := cpp.equals.make(f0,f1,s.ann);
                eqs := eqs.append(e);
            };
            idx := idx.next
        };
        var retexp := cpp.symbol.makestr("true",s.ann);
        if eqs.end > 0 {
            retexp := cpp.and.fold_left(eqs,s.ann);
        };
        eq.body := cpp.retst.make(retexp,s.ann);
        s.members := s.members.append(eq);
    }
Disequality is just negation of equality bool operator!=(const name &other) const { return !((*this)==other); }

    action add_diseq_pred(s:cpp.structdecl) returns (s:cpp.structdecl) = {
        var diseq : cpp.funcdecl;
        diseq.ftype.base._type := cpp.symbol.makestr("ivy::native_bool",s.ann);
        diseq.ftype.base.name := cpp.symbol.makestr("operator !=",s.ann);
        diseq.ftype.is_const := true;
        var diseqarg0 : cpp.simpletype;
        diseqarg0._type := s.name;
        diseqarg0.is_const := true;
        diseqarg0.is_ref := true;
        diseqarg0.name := cpp.symbol.makestr("other",diseq.ann);
        diseq.ftype.args := diseq.ftype.args.append(diseqarg0);
        diseq.has_body := true;
        var f0 := cpp.symbol.makestr("(*this)",s.ann);
        var diseqret := cpp.not.make(cpp.equals.make(f0,diseqarg0.name,s.ann),s.ann);
        diseq.body := cpp.retst.make(diseqret,s.ann);
        s.members := s.members.append(diseq);
    }
__is_zero is conjunction of __is_zero for members. bool __is_zero() const { return f1.is_zero() & ...; }

    action add_is_zero_pred(s:cpp.structdecl) returns (s:cpp.structdecl) = {
        var iszero : cpp.funcdecl;
        iszero.ftype.base._type := cpp.symbol.makestr("bool",s.ann);
        iszero.ftype.base.name := cpp.symbol.makestr("__is_zero",s.ann);
        iszero.ftype.is_const := true;
        iszero.has_body := true;
        var iszeros : vector[cpp.expr];
        var idx := s.members.begin;
        while idx < s.members.end {
            var d := s.members.value(idx);
            if d isa cpp.vardecl {
                var f0 := d.get_name;
                var f1 := cpp.dot.make(d.get_name,iszero.ftype.base.name,s.ann);
                var f2 : vector[cpp.expr];
                var e := cpp.app.make(f1,f2,s.ann);
                iszeros := iszeros.append(e);
            };
            idx := idx.next
        };
        var iszeroret := cpp.symbol.makestr("true",s.ann);
        if iszeros.end > 0 {
            iszeroret := cpp.and.fold_left(iszeros,s.ann);
        };
        iszero.body := cpp.retst.make(iszeroret,s.ann);
        s.members := s.members.append(iszero);
    }

    action add_hasher(s:cpp.structdecl) returns (s:cpp.structdecl) = {
Hash function adds hashes of members: struct __hash { size_t operator()(const name &x) {returns t1::__hash(f1) + ...; } };

        var hashstr : cpp.structdecl;
        hashstr.ann := s.ann;
        hashstr.name := cpp.symbol.makestr("__hash",s.ann);
        hashstr.has_members := true;
        var hash : cpp.funcdecl;
        hash.ftype.base._type := cpp.symbol.makestr("std::size_t",s.ann);
        hash.ftype.base.name := cpp.symbol.makestr("operator ()",s.ann);
        hash.ftype.is_const := true;
        var hasharg0 : cpp.simpletype;
        hasharg0._type := s.name;
        hasharg0.is_const := true;
        hasharg0.is_ref := true;
        hasharg0.name := cpp.symbol.makestr("x",hash.ann);
        hash.ftype.args := hash.ftype.args.append(hasharg0);
        hash.has_body := true;
        var hashs : vector[cpp.expr];
        var idx := s.members.begin;
        while idx < s.members.end {
            var d := s.members.value(idx);
            if d isa cpp.vardecl {
                var f0 := d.get_name;
                var f1 := hashstr.name.prefix(d.get_type.get_name);
                var f2 := cpp.dot.make(hasharg0.name,f0,s.ann);
                var e := cpp.app.make1(cpp.app.make0(f1,s.ann),f2,s.ann);
                hashs := hashs.append(e);
            };
            idx := idx.next
        };
        var hashret := cpp.symbol.makestr("0",s.ann);
        if hashs.end > 0 {
            hashret := cpp.plus.fold_left(hashs,s.ann);
        };
        hash.body := cpp.retst.make(hashret,s.ann);
        hashstr.members := hashstr.members.append(hash);
        s.members := s.members.append(hashstr);
    }
Add the standard traits to a struct.

    action add_standard_traits(s:cpp.structdecl) returns (s:cpp.structdecl) = {

        s.members := s.members.append(make_cpp_cons(s));
    s := add_default_cons(s,true,false);
    s := add_default_cons(s,false,true);
    s := add_default_asgn(s,true,false);
    s := add_default_asgn(s,false,true);
        s := add_numeric_cons(s);
        s := add_sizet_conv(s);
        s := add_is_seq_pred(s);
        s := add_eq_pred(s);
        s := add_diseq_pred(s);
        s := add_is_zero_pred(s);
        s := add_hasher(s);
    }
Add a virtual upcast and destructor to a base class.

    action add_virtual_destructor(s:cpp.structdecl) returns (s:cpp.structdecl) = {
    s.members := s.members.append(make_upcast_method(s));
        s.members := s.members.append(make_virt_destr(s));
    }
Add an virtual upcast derived class.

    action add_upcast_method(s:cpp.structdecl) returns (s:cpp.structdecl) = {
    s.members := s.members.append(make_upcast_method(s));
    }
Add a constructor that uses the base class constructor.

derived(t value) : base(value) {}
derived(const t& value) : base(value) {}

The latter is produced if the constref parameter is true

    action add_derived_cons(s:cpp.structdecl,t:cpp.expr,constref:bool) returns (s:cpp.structdecl) = {
        var dcons := make_cpp_cons(s);
        var dconsarg0 : cpp.simpletype;
        dconsarg0._type := t;
        dconsarg0.name := cpp.symbol.makestr("value",dcons.ann);
        dconsarg0.is_const := constref;
        dconsarg0.is_ref := constref;
        dcons.ftype.args := dcons.ftype.args.append(dconsarg0);
        dcons.ftype.has_initializer := true;
        dcons.ftype.initializer := cpp.app.make1(s.super,dconsarg0.name,s.ann);
        s.members := s.members.append(dcons);
    }
Add a conversion of a derived struct to its base struct

operator base () const { return (*this); }

    action add_base_conversion(s:cpp.structdecl) returns (s:cpp.structdecl) = {

        var abc : cpp.funcdecl;
        var op : str;
        op := op.extend("operator ");
        op := op.extend(s.super.get_name.to_str);
        abc.ftype.base.name := cpp.symbol.makestr(op,s.ann);
        abc.ftype.is_const := true;
        abc.has_body := true;
        abc.body := cpp.retst.make(cpp.symbol.makestr("(*this)",s.ann),s.ann);
        s.members := s.members.append(abc);
    }
Subclasses of native c++ classes inherit the standard traits from those classes. In order to allow them to use the overloads of the native classes, we add this boilerplate to the class:

struct ivytype : cpptype { ivytype(long long value) : cpptype(value) {} ivytype(const cpptype &x) : cpptype(x) {} operator cpptype() { return (*this); } };

That is, we provide a constructor from the base class and a conversion to the base class. THe latte is used by the C++ compiler when interpreting an expression like x+y, where x and y are of the derived class. Since the only available conversion is to the base class, the + operator of the base class is used. On assignment, the constructor is used.

    action add_derived_traits(s:cpp.structdecl) returns (s:cpp.structdecl) = {

        s.members := s.members.append(make_cpp_cons(s));
        s := add_derived_cons(s,cpp.symbol.makestr("long long",s.ann),false);
        s := add_derived_cons(s,s.super,true);
        s := add_base_conversion(s);
    }
Native types occur on the right-hand side of interpret declarations. They need special processing, because we do not want to avoid clashes with C++ reserved words.

    action native_type_to_cpp(ty:expr,st:tocppst) returns (res:cpp.expr,st:tocppst) = {
        st.native := true;
        (res,st) := ty.to_cpp(st);
        st.native := false;
    }
Translate a type declaration to C++. All Ivy types translate to C++ structs.

    object typedc = { ...
        action to_cpp(s:this,st:tocppst) returns (resd:cpp.decl,st:tocppst) = {
            var res : cpp.structdecl;
            res.ann := s.ann;
            var cls : cpp.expr;
            (cls,st) := s.sort.to_cpp(st);
            res.name := member_name(cls);
            if ~st.forward {
                if s.has_super {
                    res.has_super := true;
                    (res.super,st) := s.super.to_cpp(st)
                } else if s.has_spec & (s.spec isa enumspec) {
                    res.has_super := true;
                    res.super := make_std_tpl("native_enum",enum_name(res.name),res.ann);
                } else if st.cppclasses.mem(s.sort.get_name) {
                    res.has_super := true;
                    var itype := st.cppclasses.value(s.sort.get_name);
                    (res.super,st) := native_type_to_cpp(itype,st);
                };
                res.has_members := true;  # This is not a forward declaration
                var members := st.members.get(s.sort.get_name,vector[decl].empty);
                var idx := members.begin;
                st.in_class := true;
                while idx < members.end {
                    var d : cpp.decl;
                    (d,st) := members.value(idx).to_cpp(st);
                    res.members := res.members.append(d);
                    idx := idx.next
                };
                st.in_class := false;
                if res.has_super & ~s.has_super {  # derived from native
                    res := add_derived_traits(res);
                } else {
                    res := add_standard_traits(res);
                };
        if s.has_super {
            res := add_upcast_method(res);
        };
        if is_variant_type(s.sort,st) {
                    res := add_virtual_destructor(res);
        };
                resd := res;
                if s.has_spec & (s.spec isa enumspec) {
                    (resd,st) := enum_to_cpp(res.name,s.spec,resd,st);
                };
            } else {
                resd := res;
            };
            resd := add_namespaces(resd,fix_object_clash(s.sort.get_name,st));
    }
Here, we register the constructors of enumerated types

    import action tocpp_show_str(s:str)

    action reg_member(s:this,st:tocppst) returns (st:tocppst) = {
        if s.has_spec {
        if s.spec isa enumspec {
            var conss := s.spec.get_elems;
            var idx := conss.begin;
            while idx < conss.end {
            var cons := conss.value(idx);
            st.constructors := st.constructors.set(cons.get_name,true);
            idx := idx.next
            }
        }
        }
        }


    }

    action is_variant_type(t:expr,st:tocppst) returns (res:bool) = {
        if t isa symbol {
            if st.subtype_rel.subtypes_of.mem(t.get_name) {
        res := true;
        }
    }
    }
When storing values of variant types we use smart pointers. That is, type t becomes ivy.ptr.

    action fix_variant_type(t:expr,st:tocppst) returns (res:cpp.expr,st:tocppst) = {
        if is_variant_type(t,st) {
        var cppty : cpp.expr;
        (cppty,st) := t.to_cpp(st);
            var s : cpp.strident;
            s.val := "ptr";
            s.subscrs := s.subscrs.append(cppty.get_name);
            res := cpp.symbol.make(s.prefix(cpp.strident.make("ivy")),t.get_ann);
        } else {
        (res,st) := t.to_cpp(st);
    }
    }
Translate a function definition to an action, and fix actions we can't handle.

    object decl = { ...
        action func_to_action(s : this) returns (res : decl) = {
            res := s;
        }
        action fix_action(s : this, st:tocppst) returns (res : decl) = {
            res := s;
        }
    }

    object vardc = { ...
        action func_to_action(s : vardc) returns (dres : decl) = {
            if s.has_def {
                var res : actdc;
                res.ann := s.ann;
                var lhs := s.typing;
                var ty : expr;
                if lhs isa app {
                    res.name := lhs.get_func.get_arg(0);
                    res.inputs := lhs.get_args;
                    ty := lhs.get_func.get_arg(1).get_arg(1);
                } else if lhs isa symbol {
                    res.name := lhs.get_arg(0);
                    ty := lhs.get_arg(1);
                };
                var retv := symbol.makestr("__out",s.ann);
                var retty := colon.make(retv,ty,s.ann);
                res.outputs := res.outputs.append(retty);
                res.has_body := true;
                res.body := asgn.make(retty,s.def,s.ann);
                dres := res;
            } else {
                dres := s;
            }
        }
    }
Translate a local variable declaration to C++.

    object vardc = { ...
        action to_cpp(s:this,st:tocppst) returns (resd:cpp.decl,st:tocppst) = {
            var res : cpp.vardecl;
            res.ann := s.ann;
            var ty := s.typing.get_arg(1);
            if s.is_destructor {
                ty := ty.curry.get_arg(1);
            };
        (res.vtype._type,st) := fix_variant_type(ty,st);
            var name : cpp.expr;
            (name,st) := s.typing.get_arg(0).to_cpp(st);
            res.vtype.name := member_name(name);
            resd := res; # workaround
            if ~st.in_class {
                resd := add_namespaces(resd,s.typing.get_arg(0).get_name);
            }
    }
A type destructor is translated to a data member of the class;

        action reg_member(s:this,st:tocppst) returns (st:tocppst) = {
            if s.is_destructor {
                var dom := times.unfold_left(s.typing.get_arg(1).get_arg(0));
                var type_ident := dom.value(0).get_name;
                st := st.add_member(type_ident,s);
            }
        }
An variable declaration is not emitted if it is a destructor.

        action emitted(s:this,st:tocppst) returns (res:bool) = {
            res := ~s.is_destructor;
        }

    }
Translate a header declaration to C++

    object header = { ...
        instantiate generic_to_cpp(this,cpp.header,cpp.decl)

    action to_cpp_int(s:this,st:tocppst) returns (res:cpp.header,st:tocppst) = {
            res.filename := s.filename
    }
    }
Translate an interpret declaration to C++.

    object interpdc = { ...
An interpret declararation determines the C++ superclass of the type.

        action reg_member(s:this,st:tocppst) returns (st:tocppst) = {
            st.cppclasses := st.cppclasses.set(s.itype.get_name,s.ctype);
        }
Interpret declarations are never emitted

        action emitted(s:this,st:tocppst) returns (res:bool) = {
            res := false;
        }

    }
Translate an object declaration to C++.

    object objectdc = { ...
An object declaration corresponds to a namespace in C++. However, we emit namespace declarations for the object members, not here. Here, we just record the object identifiers, so we know when they clash with type identifiers.

        action reg_member(s:this,st:tocppst) returns (st:tocppst) = {
            st.objects := st.objects.set(s.name.get_name,true);
        }
Object declarations are never emitted

        action emitted(s:this,st:tocppst) returns (res:bool) = {
            res := false;
        }

    }
Translate an Ivy program to C++.

    object prog = { ...
    action to_cpp(sp:this) returns (res:cpp.prog) = {
            var s := sp;  # workaround
            var st:tocppst;
Get the subtyping relation

            st.subtype_rel := s.get_subtypes;
A pass to convert function defs to actions

            var idx := s.decls.begin;
        while idx < s.decls.end {
                var d := s.decls.value(idx).func_to_action;
                s.decls := s.decls.set(idx,d);
        idx := idx.next
        };
Get the global typing

            st.globals := s.get_global_types(true);
A pass to register all of the type members

            idx := s.decls.begin;
        while idx < s.decls.end {
                st := s.decls.value(idx).reg_member(st);
        idx := idx.next
        };
A pass to emit the forward declarations of classes

            st.forward := true;
            idx := s.decls.begin;
        while idx < s.decls.end {
                var ivyd := s.decls.value(idx);
                if ivyd isa typedc {
                    var d : cpp.decl;
                    (d,st) := ivyd.to_cpp(st);
            res.decls := res.decls.append(d);
                };
        idx := idx.next
        };
            st.forward := false;
A pass to rewrite actions with problematic signatures

            idx := s.decls.begin;
        while idx < s.decls.end {
                var d := s.decls.value(idx).fix_action(st);
                s.decls := s.decls.set(idx,d);
        idx := idx.next
        };
A pass to record all of the C++ function prototypes

            idx := s.decls.begin;
        while idx < s.decls.end {
        st := s.decls.value(idx).record_prototypes(st);
        idx := idx.next
        };
A pass to emit the C++ prototypes and class declarations

            st.proto_only := true;
            idx := s.decls.begin;
        while idx < s.decls.end {
                if s.decls.value(idx).emitted(st) {
                    var d : cpp.decl;
                    (d,st) := s.decls.value(idx).to_cpp(st);
            res.decls := res.decls.append(d);
                };
        idx := idx.next
        };
A pass to emit the C++ function implementations

            st.proto_only := false;
            idx := s.decls.begin;
        while idx < s.decls.end {
                var ivyd := s.decls.value(idx);
                if (ivyd isa actdc) & s.decls.value(idx).emitted(st) {
                    var d : cpp.decl;
                    (d,st) := ivyd.to_cpp(st);
            res.decls := res.decls.append(d);
                };
        idx := idx.next
        };
Emit a main function by concatenating the init declaration bodies.

            var main : cpp.funcdecl;
            main.ftype.base._type := cpp.inttype(main.ann);
            main.ftype.base.name := cpp.symbol.makestr("main",main.ann);
            var mainarg0 : cpp.simpletype;
            var mainarg1 : cpp.simpletype;
            mainarg0._type := cpp.inttype(main.ann);
            mainarg0.name := cpp.symbol.makestr("argc",main.ann);
            mainarg1._type := cpp.symbol.makestr("char **",main.ann);
            mainarg1.name := cpp.symbol.makestr("argv",main.ann);
            main.ftype.args := main.ftype.args.append(mainarg0);
            main.ftype.args := main.ftype.args.append(mainarg1);
            main.has_body := true;
            main.body := cpp.retst.make(cpp.symbol.makestr("0",main.ann),main.ann);
            idx := s.decls.end;
        while idx > s.decls.begin {
                idx := idx.prev;
                var decl := s.decls.value(idx);
                if decl isa initdc {
                    var cbody : cpp.stmt;
                    (cbody,st) := decl.get_body.to_cpp(st);
                    main.body := cpp.sequence.make(cbody,main.body,decl.get_ann);
                }
            };
        var argv := cpp.asgn.make(cpp.symbol.makestr("ivy::__argv",main.ann),cpp.symbol.makestr("argv",main.ann),main.ann);
        main.body := cpp.sequence.make(argv,main.body,main.ann);
        var argc := cpp.asgn.make(cpp.symbol.makestr("ivy::__argc",main.ann),cpp.symbol.makestr("argc",main.ann),main.ann);
        main.body := cpp.sequence.make(argc,main.body,main.ann);
            res.decls := res.decls.append(main);
    }

    import action show_prog(s:str)

        action file_to_cpp(name:str) = {
Read the program

            var p := prog.read_file(name);
var p := prog.dec(name);

Flattening pass

            if errors.end = 0 {
                p := p.flat;
            };
call show_prog(p.enc);

Type inference pass

            if errors.end = 0 {
            p := p.typeinfer;
            };
Translate to C++

            var cpp_prog : cpp.prog;
            if errors.end = 0 {
                 cpp_prog := p.to_cpp;
            };
Pretty-print the C++

            if errors.end = 0 {
                var cpp_name := path.change_extension(name,"cpp");
                call write_file(cpp_name,cpp_prog.enc);
            }
        }
    }



}