My dyngen for Plan 9 is now capable of emitting the right kind of code for a Plan 9 QEMU. In addition to some tables and ancillary functions, the core of dyngen's output is an enormous switch/case statement for copying individual micro-op functions. Here are examples of the output for both Dyngen/UNIX and Dyngen/Plan 9: Dyngen/UNIX: case INDEX_op_sqrtps: { long param1, param2; extern void op_sqrtps(); extern char float32_sqrt; extern char float32_sqrt; extern char float32_sqrt; extern char float32_sqrt; memcpy(gen_code_ptr, (void *)((char *)&op_sqrtps+0), 129); param1 = *opparam_ptr++; param2 = *opparam_ptr++; *(uint32_t *)(gen_code_ptr + 24) = (int32_t)param2 + 0; *(uint32_t *)(gen_code_ptr + 35) = (int32_t)param1 + 0; *(uint32_t *)(gen_code_ptr + 40) = (long)(&float32_sqrt) - (long)(gen_code_ptr + 40) + -4; *(uint32_t *)(gen_code_ptr + 62) = (long)(&float32_sqrt) - (long)(gen_code_ptr + 62) + -4; *(uint32_t *)(gen_code_ptr + 84) = (long)(&float32_sqrt) - (long)(gen_code_ptr + 84) + -4; *(uint32_t *)(gen_code_ptr + 106) = (long)(&float32_sqrt) - (long)(gen_code_ptr + 106) + -4; gen_code_ptr += 129; } break; Dyngen/Plan 9: case INDEX_sqrtps: { extern uchar __op_p9_push[]; memcpy(gen_code_ptr, __op_p9_push, 5); dyngen_itab[51]->addr = (ulong)(gen_code_ptr + 178); dynreloc(gen_code_ptr - 0, 1, 1, dyngen_itab, dyngen_nimport); gen_code_ptr += 5; } { extern uchar op_sqrtps[]; memcpy(gen_code_ptr, op_sqrtps, 173); ulong param1 = *opparam_ptr++; ulong param2 = *opparam_ptr++; dynreloc(gen_code_ptr - 69183, 69341, 2, dyngen_itab, dyngen_nimport); /* ri=27, ro=0xfff6239f */ dynreloc(gen_code_ptr - 69183, 69327, 1, dyngen_itab, dyngen_nimport); /* ri=7, ro=0x0 */ dynreloc(gen_code_ptr - 69183, 69304, 2, dyngen_itab, dyngen_nimport); /* ri=27, ro=0xfff623c4 */ dynreloc(gen_code_ptr - 69183, 69290, 1, dyngen_itab, dyngen_nimport); /* ri=7, ro=0x0 */ dynreloc(gen_code_ptr - 69183, 69267, 2, dyngen_itab, dyngen_nimport); /* ri=27, ro=0xfff623e9 */ dynreloc(gen_code_ptr - 69183, 69253, 1, dyngen_itab, dyngen_nimport); /* ri=7, ro=0x0 */ dynreloc(gen_code_ptr - 69183, 69231, 2, dyngen_itab, dyngen_nimport); /* ri=27, ro=0xfff6240d */ dyngen_itab[52]->addr = param2; dynreloc(gen_code_ptr - 69183, 69206, 1, dyngen_itab, dyngen_nimport); /* ri=52, ro=0x0 */ dyngen_itab[51]->addr = param1; dynreloc(gen_code_ptr - 69183, 69195, 1, dyngen_itab, dyngen_nimport); /* ri=51, ro=0x0 */ dynreloc(gen_code_ptr - 69183, 69188, 1, dyngen_itab, dyngen_nimport); /* ri=7, ro=0x0 */ gen_code_ptr += 173; } break; The first set of { }'s are the machinery for the push/op/push/op layout. The second set is the code to relocate our example micro-op, sqrtps, which, as you can see takes two parameters. The ri=... value provides (for debugging) the index into the import table. The ro=... is the offset from that symbol; the 0xfff... are being used for PC-indirect references. Relevant import indicies are /* Index 7: env */ /* Index 27: float32_sqrt */ /* Index 51: __op_param1 [cfolder 1] */ /* Index 52: __op_param2 [cfolder 2] */ Our accesses to the environment are because we don't have explicit register allocation. Otherwise, it's a 1-for-1 match. Oh yeah... the somewhat funky 69183 and friends... op_sqrtps starts 69183 bytes into the dlm. Since dynreloc() operates assuming a full dlm, we just back its pointer up to pretend that we're still doing the dlm-at-once. Hoorah. In fairness, we can probably not alter the base and just fiddle with the offset... this is just the first thing that I thought of about a month ago and haven't taken the time to ensure that it works "the other way."