[Transmigration::] Transmigration. The act of moving a package from one Inter tree to another. @ The design of Inter has all been leading up to this: how to move a body of material in one tree, the |migrant| package, into a different tree entirely, where it will become part of the |destination| package. Transmigration is how Inter merges material compiled at different times together. For example, when a Basic Inform project is compiled: (*) //inform7// compiles source text into tree 1 of Inter code. (*) //inter// loads a precompiled copy of BasicInformKit as tree 2. (*) The package |/main/BasicInformKit| in tree 2, really its entire substantive content, is transmigrated to |/main| in tree 1; it has become |/main/BasicInformKit| in tree 1. Tree 2 is left almost empty, and is discarded. (*) //inter// loads a precompiled copy of BasicInformKitExtras as tree 3. (*) A similar transmigration moves its content to become |/main/BasicInformKitExtras| in tree 1, and the remains of tree 3 are discarded. @ Transmigration is a move, not a copy. The destination tree simply makes a node link to the subtree making up |migrant|: so, in principle, this is a fast process, but the devil is in the detail. The |migrant| matter may be full of references to other resources in the origin tree, and those have to be made good. Read //The Wiring// before tackling the algorithms below. Because the operation is a move and not a copy, the origin tree will probably be left with a gaping hole in it: its symbols may be wired to resources which are no longer there. It may be that the origin tree is going to be discarded anyway, so that this doesn't matter. If not, setting |tidy_origin| here will spend some time and effort making it valid again. Note that the |/main| and |/main/connectors| packages of a tree cannot be transmigrated, and nor can the root package |/|. Anything else is fair game to be a |migrant| here. = typedef struct transmigration_details { inter_package *migrant; /* package which is to move between trees */ inter_tree *origin_tree; /* tree it moves from */ inter_tree *destination_tree; /* tree it moves to */ inter_package *origin; /* original parent package of the migrant */ inter_package *destination; /* eventual parent package of the migrant */ inter_bookmark deletion_point; /* where the migrant's head node starts */ inter_bookmark insertion_point; /* where the migrant's head node ends */ inter_bookmark primitives_point; /* where primitives are declared in destination tree */ inter_bookmark ptypes_point; /* where package types are declared in destination tree */ } transmigration_details; @ = void Transmigration::move(inter_package *migrant, inter_package *destination, int tidy_origin) { inter_package *P = migrant; while (P) P = InterPackage::parent(P); /* make names exist */ transmigration_details det; @; @; @; @; @; } @ Both trees will have, at the root level, declarations of the Inter primitives and package types which they use. (See //building: Large-Scale Structure//.) Now, they will probably both declare exactly the same set of each: but just in case the |migrant| package uses primitives or package types not declared at the root of the |destination_tree|, we will need to declare those as extras, and we make the bookmarks |primitives_point| and |ptypes_point| to mark where such extra declarations would go. |deletion_point| and |insertion_point|, more straightforwardly, mark the position of the |migrant|'s head node in the origin tree before transmigration and in the destination tree afterwards. @ = det.migrant = migrant; det.origin = InterPackage::parent(migrant); det.destination = destination; det.origin_tree = InterPackage::tree(migrant); det.destination_tree = InterPackage::tree(destination); det.deletion_point = InterBookmark::after_this_node(migrant->package_head); det.insertion_point = InterBookmark::at_end_of_this_package(destination); inter_tree_node *prims = NULL; LOOP_THROUGH_INTER_CHILDREN(F, destination->package_head->tree->root_node) if (F->W.instruction[ID_IFLD] == PRIMITIVE_IST) prims = F; if (prims == NULL) internal_error("destination has no primitives"); det.primitives_point = InterBookmark::after_this_node(prims); inter_tree_node *ptypes = NULL; LOOP_THROUGH_INTER_CHILDREN(F, destination->package_head->tree->root_node) if (F->W.instruction[ID_IFLD] == PACKAGETYPE_IST) ptypes = F; if (ptypes == NULL) internal_error("destination has no package types"); det.ptypes_point = InterBookmark::after_this_node(ptypes); @ = Transmigration::comment(&det.deletion_point, InterPackage::baseline(migrant), I"Transmigration removed", InterPackage::name(migrant)); Transmigration::comment(&det.insertion_point, InterPackage::baseline(destination) + 1, I"Transmigration inserted", InterPackage::name(migrant)); @ = void Transmigration::comment(inter_bookmark *IBM, int level, text_stream *action, text_stream *content) { inter_ti C = InterWarehouse::create_text(InterBookmark::warehouse(IBM), InterBookmark::package(IBM)); WRITE_TO(InterWarehouse::get_text(InterBookmark::warehouse(IBM), C), "%S %S here", action, content); Inter::Comment::new(IBM, (inter_ti) level, NULL, C); } @ This is the only point anywhere in the Inform tool chain where a node is moved between packages. What makes it tricky is that the symbol giving the name of the |migrant| package, which was in the original parent package's symbols table, must be deleted. A new symbol with the same name must be created in the symbols table for the destination package, and the bytecode for the |package| instruction at the node must be altered to conform with this. But when we are done, all is consistent again. @ = text_stream *migrant_name = Str::duplicate(InterPackage::name(migrant)); inter_symbol *OS = InterSymbolsTable::symbol_from_name_not_following( InterPackage::parent(migrant)->package_scope, migrant_name); if (OS) InterSymbolsTable::remove_symbol(OS); NodePlacement::move_to_moving_bookmark(migrant->package_head, &det.insertion_point); migrant->package_head->tree = det.destination_tree; migrant->package_head->package = destination; inter_symbol *NS = InterSymbolsTable::symbol_from_name_creating( destination->package_scope, migrant_name); InterPackage::set_name_symbol(migrant, NS); if (InterPackage::container(migrant->package_head) != destination) internal_error("transmigration did not take the migrant to the right place"); inter_symbol *S = InterPackage::name_symbol(migrant); if (NS != S) internal_error("transmigration of head node and symbol failed"); @ That was the easy part. The migrant package is now inside the destination tree. Unfortunately: (*) |migrant| may contain symbols |S ~~> O| wired to symbols |O| still in the origin tree, because they lay outside |migrant|. This means the destination tree is now incorrect. (*) The origin tree may contain symbols |O ~~> S| wired to symbols |S| in the migrant, which are therefore not in the origin tree any more. This means the origin tree is now incorrect. @ = InterTree::traverse(det.destination_tree, Transmigration::correct_migrant, &det, migrant, 0); if (tidy_origin) InterTree::traverse(det.origin_tree, Transmigration::correct_origin, &det, NULL, 0); @ A further issue is that the original tree may have offered sockets for some of the definitions in |migrant|. For example, |migrant| might contain some useful function, which the origin tree was offering for linking. We want to make an equivalent socket in the destination tree for the same function. This is important for two reasons: firstly, because after transmigration, the caller will need to use those sockets (see //Wiring::connect_plugs_to_sockets//), and secondly, because this may be only one of a series of transmigrations of Inter kits. All those kits need to see each others' sockets. So we cannot assume that having transmigrated BasicInformKit (say), we no longer need its resources to be socketed. @ = inter_package *origin_connectors = LargeScale::connectors_package_if_it_exists(det.origin_tree); if (origin_connectors) { inter_symbols_table *T = InterPackage::scope(origin_connectors); LOOP_OVER_SYMBOLS_TABLE(S, T) { if (InterSymbol::is_socket(S)) { inter_symbol *target = Wiring::cable_end(S); if (InterSymbol::defined_inside(target, migrant)) @; } } } @ The difficult case here is where the destination already has a socket of the same name. This would happen, for instance, if you transmigrated two kits in turn, and both of them provided a function called |OverlyEagerFn()|: the first time, a socket would be made in the destination tree; but the second time we would find that a socket already existed. This is arguably just a linking error and we should halt. In fact we let it slide, and allow the destination's original socket to remain as it was. We do this because the issues involved in linking property/attribute declarations in WorldModelKit with their Inform 7 counterparts in the main tree are just too awkward to confront here. (Essentially, this is a situation where the same declaration is made twice, once in each tree, an issue to confront later. They will end up meaning the same thing, though, so it's fine to keep using the existing socket here.) @ = LOGIF(INTER_CONNECTORS, "Origin offers socket $3 ~~> $3 in migrant\n", S, target); inter_symbol *equivalent = Wiring::find_socket(det.destination_tree, S->symbol_name); if (equivalent) { inter_symbol *e_target = Wiring::cable_end(equivalent); if (InterSymbol::is_defined(e_target) == FALSE) { LOGIF(INTER_CONNECTORS, "Co-opted undefined socket $3 ~~> $3\n", equivalent, e_target); Wiring::wire_to(equivalent, target); Wiring::wire_to(e_target, target); } else { LOGIF(INTER_CONNECTORS, "There is already a socket %S ~~> $3\n" "We use this rather than continue with %S ~~> $3\n", S->symbol_name, e_target, S->symbol_name, target); } } else { Wiring::socket(det.destination_tree, S->symbol_name, S); } @ Okay, so now for the first cross-referencing fix. The following function traverses every node inside the |migrant| tree. The first thing to do is to correct |P->tree|, which records, for every node, the tree to which it belongs: this is why the traverse needs to visit every node inside |migrant| (including its own head node). But then there are only two cases of interest: primitive invocations, and package head nodes. = void Transmigration::correct_migrant(inter_tree *I, inter_tree_node *P, void *state) { transmigration_details *det = (transmigration_details *) state; P->tree = I; inter_symbol *primitive = Inter::Inv::read_primitive(det->origin_tree, P); if (primitive) @; if (P->W.instruction[ID_IFLD] == PACKAGE_IST) @; } @ Primitive invocations matter because, say, |inv !printnumber| in the migrant will contain a reference to the origin tree's definition of |!printnumber|; this must be converted to a reference to the destination's definition of the same thing. Note that we expect to perform this operation frequently -- there may be, say, 10,000 primitive invocations in the migrant, but always of the same 50 or so primitives round and around -- so we cache the results. @ = inter_symbol *equivalent_primitive = Transmigration::known_equivalent(primitive); if (equivalent_primitive == NULL) { equivalent_primitive = InterSymbolsTable::symbol_from_name( InterTree::global_scope(det->destination_tree), primitive->symbol_name); if (equivalent_primitive == NULL) @; Transmigration::learn_equivalent(primitive, equivalent_primitive); } if (equivalent_primitive) Inter::Inv::write_primitive(det->destination_tree, P, equivalent_primitive); @ In the worst-case scenario, the destination might not even have a declaration of |!printnumber|. (Actually this is unlikely in practice, because we tend to make the same set of primitive declarations in every tree.) In this case, we write a new declaration in the root package of the destination, duplicating the one in the root package of the origin. @ = equivalent_primitive = InterSymbolsTable::symbol_from_name_creating( InterTree::global_scope(det->destination_tree), primitive->symbol_name); inter_tree_node *D = Inode::new_with_1_data_field(&(det->primitives_point), PRIMITIVE_IST, InterSymbolsTable::id_from_symbol(det->destination_tree, NULL, equivalent_primitive), NULL, 0); inter_tree_node *old_D = primitive->definition; for (int i=CAT_PRIM_IFLD; iW.extent; i++) { Inode::extend_instruction_by(D, 1); D->W.instruction[i] = old_D->W.instruction[i]; } inter_error_message *E = InterConstruct::verify_construct( InterBookmark::package(&(det->primitives_point)), D); if (E) { Inter::Errors::issue(E); equivalent_primitive = NULL; } else { NodePlacement::move_to_moving_bookmark(D, &(det->primitives_point)); } @ = inter_package *pack = InterPackage::at_this_head(P); if (InterPackage::is_a_linkage_package(pack)) internal_error("tried to transmigrate /main, /main/connectors or /"); @; @; @ Package types present the exact same issue as primitive invocations: the types are given in terms of declarations in the origin tree, and have to be transferred to matching declarations in the destination. @ = inter_symbol *original_ptype = InterPackage::read_type(det->origin_tree, P); inter_symbol *equivalent_ptype = Transmigration::known_equivalent(original_ptype); if (equivalent_ptype == NULL) { equivalent_ptype = InterSymbolsTable::symbol_from_name( InterTree::global_scope(det->destination_tree), original_ptype->symbol_name); if (equivalent_ptype == NULL) @; Transmigration::learn_equivalent(original_ptype, equivalent_ptype); } InterPackage::write_type(det->destination_tree, P, equivalent_ptype); @ = equivalent_ptype = InterSymbolsTable::symbol_from_name_creating( InterTree::global_scope(det->destination_tree), original_ptype->symbol_name); inter_tree_node *D = Inode::new_with_1_data_field(&(det->ptypes_point), PACKAGETYPE_IST, InterSymbolsTable::id_from_symbol(det->destination_tree, NULL, equivalent_ptype), NULL, 0); inter_error_message *E = InterConstruct::verify_construct( InterBookmark::package(&(det->ptypes_point)), D); if (E) { Inter::Errors::issue(E); equivalent_ptype = NULL; } else { NodePlacement::move_to_moving_bookmark(D, &(det->ptypes_point)); } @ Here |S| is some miscellaneous symbol in our subpackage of |migrant| -- it can't be either a plug or a socket, since the connectors never migrate -- and there are three bad possibilities: @ = inter_symbols_table *T = InterPackage::scope(pack); if (T == NULL) internal_error("package with no symbols"); LOOP_OVER_SYMBOLS_TABLE(S, T) { if (Wiring::is_wired(S)) { inter_symbol *target = Wiring::cable_end(S); if (SymbolAnnotation::get_b(target, ARCHITECTURAL_IANN)) @ else if (InterSymbol::is_plug(target)) @ else if (InterSymbol::defined_inside(S, det->migrant) == FALSE) @ } } @ For example, |S| is wired to |WORD_SIZE| ih the origin tree, which is (let us say) a constant equal to 4. We wire it instead to |WORD_SIZE| in the destination tree, which will also be equal to 4 because we only ever transmigrate between trees with the same Inter architecture. @ = inter_symbol *equivalent = Transmigration::known_equivalent(target); if (equivalent == NULL) { equivalent = LargeScale::find_architectural_symbol(det->destination_tree, target->symbol_name, Produce::kind_to_symbol(NULL)); Transmigration::learn_equivalent(target, equivalent); } Wiring::wire_to(S, equivalent); @ Here |S| is wired to a plug, and it must be a loose plug because |target| is the cable-end from |S|: if that cable ends in a plug, clearly the plug is not wired to a socket. That means it is wired to a name, |target ~~> "some_name"|. We wire |S| instead to a plug seeking |some_name| in the destination tree; note that this may result in a new plug being made there, or may re-use an existing one already looking for (or indeed already having found) |"some_name"|. @ = inter_symbol *equivalent = Transmigration::known_equivalent(target); if (equivalent == NULL) { text_stream *N = Wiring::wired_to_name(target); equivalent = Wiring::plug(det->destination_tree, N); Transmigration::learn_equivalent(target, equivalent); } Wiring::wire_to(S, equivalent); @ Finally |S| may be wired to some ordinary symbol defined in the origin tree but outside of |migrant|. Well, that resource is now outside of the destination tree: and this is exactly what plugs in the destination tree are for. @ = inter_symbol *equivalent = Transmigration::known_equivalent(target); if (equivalent == NULL) { equivalent = Wiring::plug(det->destination_tree, target->symbol_name); Transmigration::learn_equivalent(target, equivalent); } Wiring::wire_to(S, equivalent); @ Now time for the second sort of correction: references from the origin tree into the migrant. If we care about those, then we traverse so that the following visits every node in the origin tree. Note that at this point the head node of |migrant| has been removed from the origin tree -- so this visitor can never visit anything inside |migrant|. Note that we do not correct references from the origin tree's |/main/connectors| package, i.e., plugs and sockets wired to something in |migrant|; we handle those separately (see above). = void Transmigration::correct_origin(inter_tree *I, inter_tree_node *P, void *state) { transmigration_details *det = (transmigration_details *) state; if (P->W.instruction[ID_IFLD] == PACKAGE_IST) { inter_package *pack = InterPackage::at_this_head(P); if (InterPackage::is_a_linkage_package(pack) == FALSE) { inter_symbols_table *T = InterPackage::scope(pack); LOOP_OVER_SYMBOLS_TABLE(S, T) if (Wiring::is_wired(S)) { inter_symbol *target = Wiring::cable_end(S); if (InterSymbol::defined_inside(target, det->migrant)) @; } } } } @ This is now symmetrical to the case above. |S| is wired to what is now a resource in a different tree, so it needs to be wired to a plug instead. @ = inter_symbol *equivalent = Transmigration::known_equivalent(target); if (equivalent == NULL) { equivalent = Wiring::plug(det->origin_tree, S->symbol_name); Transmigration::learn_equivalent(target, equivalent); } Wiring::wire_to(S, equivalent); @ That just leaves the cache. The idea is that for each different act of transmigration, we want to cache the symbol conversions made. The following is fast, but a little wasteful of memory, since it involves storing two fields in every //inter_symbol//: = typedef struct transmigration_data { int valid_on_which_transmigration; struct inter_symbol *cached_equivalent; } transmigration_data; @ = transmigration_data Transmigration::new_transmigration_data(inter_symbol *S) { transmigration_data td; td.valid_on_which_transmigration = 0; td.cached_equivalent = NULL; return td; } @ The scheme is that each different act of transmigration has its own unique ID, counting upwards from 1. = int current_transmigration_count = 1; void Transmigration::begin_cache_session(void) { ++current_transmigration_count; } @ This count is used only to see if the |cached_equivalent| symbol was set during the current transmigration (rather than some previous one). Using this count is quicker, since it saves the time needed to walk through all existing symbols resetting the |cached_equivalent| fields to |NULL|. = void Transmigration::learn_equivalent(inter_symbol *S, inter_symbol *V) { S->transmigration.cached_equivalent = V; S->transmigration.valid_on_which_transmigration = current_transmigration_count; } inter_symbol *Transmigration::known_equivalent(inter_symbol *S) { if (S->transmigration.valid_on_which_transmigration == current_transmigration_count) return S->transmigration.cached_equivalent; return NULL; }