campctf2023-chall-tis256

Zachtronics TIS100-inspired reversing challenge for CampCTF 2023
git clone https://git.sinitax.com/sinitax/campctf2023-chall-tis256
Log | Files | Refs | Submodules | README | sfeed.txt

tpu.c (15160B)


      1#include "tpu.h"
      2#include "util.h"
      3
      4#include <ctype.h>
      5#include <stdio.h>
      6#include <stdbool.h>
      7#include <stddef.h>
      8#include <string.h>
      9
     10const char *dir_reprs[] = {
     11	"LEFT", "RIGHT", "UP", "DOWN"
     12};
     13
     14const char *status_reprs[] = {
     15	"IDLE", "RUN", "READ", "WRITE"
     16};
     17
     18const char *inst_reprs[] = {
     19	"NOP", "MOV", "SWP", "SAV", "ADD",
     20	"SUB", "NEG", "XOR", "AND", "JMP",
     21	"JEQ", "JNE", "JRO", "SHL", "SHR"
     22};
     23
     24const char *op_reprs[] = {
     25	"ACC", "NIL", "LEFT", "RIGHT", "UP", "DOWN",
     26	"ANY", "LAST", "LIT", "<NAME>"
     27};
     28
     29static bool
     30op_is_dir(enum tpu_inst_op_type op)
     31{
     32	return op >= OP_LEFT && op <= OP_DOWN;
     33}
     34
     35static enum tpu_port_dir
     36op_to_dir(enum tpu_inst_op_type op)
     37{
     38	return op - OP_LEFT;
     39}
     40
     41static enum tpu_port_dir
     42opposite_dir(enum tpu_port_dir dir)
     43{
     44	switch (dir) {
     45	case DIR_UP:
     46		return DIR_DOWN;
     47	case DIR_DOWN:
     48		return DIR_UP;
     49	case DIR_LEFT:
     50		return DIR_RIGHT;
     51	case DIR_RIGHT:
     52		return DIR_LEFT;
     53	}
     54}
     55
     56void
     57tpu_port_init(struct tpu_port *port)
     58{
     59	port->attached = false;
     60	port->dst_port = NULL;
     61	port->dst_tpu = NULL;
     62	port->type = 0;
     63	port->clr_post_run = false;
     64	port->reading = false;
     65	port->writing = false;
     66	port->could_read = false;
     67	port->could_write = false;
     68	port->empty = true;
     69	port->in = -1;
     70	port->out = -1;
     71	port->out_pending = -1;
     72}
     73
     74void
     75tpu_port_deinit(struct tpu_port *port)
     76{
     77	/* empty */
     78}
     79
     80void
     81tpu_init(struct tpu *tpu)
     82{
     83	size_t i;
     84
     85	tpu->status = STATUS_IDLE;
     86
     87	tpu->x = 0;
     88	tpu->y = 0;
     89
     90	tpu->steps = 0;
     91	tpu->idle_steps = 0;
     92
     93	tpu->user = NULL;
     94
     95	tpu->pc = 0;
     96	tpu->acc = 0;
     97	tpu->bak = 0;
     98	tpu->inst_cnt = 0;
     99	memset(tpu->labels, 0, sizeof(char *) * TPU_MAX_INST);
    100	memset(tpu->jmpdst, 0xff, sizeof(int) * TPU_MAX_INST);
    101
    102	tpu->last = -1;
    103	tpu->io_port = -1;
    104	for (i = 0; i < 4; i++)
    105		tpu_port_init(&tpu->ports[i]);
    106}
    107
    108void
    109tpu_deinit(struct tpu *tpu)
    110{
    111	int i;
    112
    113	for (i = 0; i < tpu->inst_cnt; i++) {
    114		if (tpu->insts[i].opcnt >= 1
    115				&& tpu->insts[i].ops[0].type == OP_LABEL)
    116			free(tpu->insts[i].ops[0].val.label);
    117	}
    118	for (i = 0; i < TPU_MAX_INST; i++)
    119		free(tpu->labels[i]);
    120}
    121
    122bool
    123tpu_label_add(struct tpu *tpu, const char *name, size_t pc)
    124{
    125	if (tpu->labels[pc]) return false;
    126	if (tpu_label_get(tpu, name) != TPU_MAX_INST) return false;
    127
    128	tpu->labels[pc] = strdup(name);
    129	if (!tpu->labels[pc]) die("strdup");
    130
    131	return true;
    132}
    133
    134size_t
    135tpu_label_get(struct tpu *tpu, const char *name)
    136{
    137	size_t i;
    138
    139	for (i = 0; i < TPU_MAX_INST; i++) {
    140		if (tpu->labels[i] && !strcasecmp(tpu->labels[i], name))
    141			return i;
    142	}
    143
    144	return TPU_MAX_INST;
    145}
    146
    147void
    148tpu_init_ports(struct tpu *tpu, struct tpu_map *map)
    149{
    150	const int dx[4] = { -1, 1, 0, 0 };
    151	const int dy[4] = { 0, 0, -1, 1 };
    152	enum tpu_port_dir dir, odir;
    153	enum tpu_inst_op_type optype;
    154	struct tpu *neighbor;
    155	int x, y, i, k, l;
    156
    157	for (i = 0; i < tpu->inst_cnt; i++) {
    158		for (k = 0; k < tpu->insts[i].opcnt; k++) {
    159			optype = tpu->insts[i].ops[k].type;
    160			if (op_is_dir(optype)) {
    161				dir = op_to_dir(tpu->insts[i].ops[k].type);
    162				if (k == 0)
    163					tpu->ports[dir].could_read = true;
    164				else
    165					tpu->ports[dir].could_write = true;
    166			} else if (optype == OP_ANY) {
    167				for (l = 0; l < 4; l++) {
    168					if (k == 0)
    169						tpu->ports[l].could_read = true;
    170					else
    171						tpu->ports[l].could_write = true;
    172				}
    173			} else if (optype == OP_LAST && k == 1) {
    174				for (l = 0; l < 4; l++) {
    175					if (tpu->ports[l].could_read)
    176						tpu->ports[l].could_write = true;
    177				}
    178			}
    179		}
    180	}
    181
    182	for (i = 0; i < 4; i++) {
    183		x = tpu->x + dx[i];
    184		y = tpu->y + dy[i];
    185		neighbor = tpu_map_get(map, x, y);
    186		tpu->ports[i].dst_tpu = neighbor;
    187		tpu->ports[i].type = tpu->ports[i].could_read * PORT_IN
    188			| tpu->ports[i].could_write * PORT_OUT;
    189		if (neighbor && tpu->ports[i].type != 0) {
    190			odir = opposite_dir((enum tpu_port_dir) i);
    191			tpu->ports[i].dst_port = &neighbor->ports[odir];
    192		}
    193	}
    194}
    195
    196void
    197tpu_attach_ports(struct tpu *tpu)
    198{
    199	struct tpu *neighbor;
    200	enum tpu_port_dir i, o;
    201
    202	for (i = 0; i < 4; i++) {
    203		neighbor = tpu->ports[i].dst_tpu;
    204		if (!neighbor) continue;
    205		o = opposite_dir(i);
    206		if (tpu->ports[i].type & PORT_IN && neighbor->ports[o].type & PORT_OUT)
    207			tpu->ports[i].attached = true;
    208		if (tpu->ports[i].type & PORT_OUT && neighbor->ports[o].type & PORT_IN)
    209			tpu->ports[i].attached = true;
    210	}
    211}
    212
    213static void
    214tpu_update_ports(struct tpu *tpu)
    215{
    216	struct tpu_port *port;
    217	int i;
    218
    219	for (i = 0; i < 4; i++) {
    220		port = &tpu->ports[i];
    221		if (!port->attached) continue;
    222		if (port->out >= 0 && port->dst_port->in < 0) {
    223			port->dst_port->reading = false;
    224			port->dst_port->in = port->out;
    225			port->writing = false;
    226			port->out = -1;
    227		}
    228		if (port->dst_port->out >= 0 && port->in < 0) {
    229			port->reading = false;
    230			port->in = port->dst_port->out;
    231			port->dst_port->writing = false;
    232			port->dst_port->out = -1;
    233		}
    234		if (tpu->status == STATUS_RUN && port->clr_post_run) {
    235			port->in = -1;
    236			port->clr_post_run = false;
    237		}
    238		port->empty = port->in < 0;
    239	}
    240}
    241
    242bool
    243tpu_set_inst(struct tpu *tpu, uint8_t pc, enum tpu_inst_type inst_type,
    244	unsigned opcnt, struct tpu_inst_op op1, struct tpu_inst_op op2)
    245{
    246	struct tpu_inst *inst;
    247
    248	inst = &tpu->insts[pc];
    249	inst->type = inst_type;
    250	inst->opcnt = opcnt;
    251	inst->ops[0] = op1;
    252	inst->ops[1] = op2;
    253
    254	switch (inst->type) {
    255	case INST_NOP: case INST_SAV:
    256	case INST_SWP: case INST_NEG:
    257		if (inst->opcnt != 0) return false;
    258		break;
    259	case INST_ADD: case INST_SUB: case INST_XOR:
    260	case INST_AND: case INST_SHL: case INST_SHR:
    261		if (inst->opcnt != 1) return false;
    262		if (inst->ops[0].type == OP_LABEL) return false;
    263		break;
    264	case INST_JRO:
    265		if (inst->opcnt != 1) return false;
    266		if (inst->ops[0].type != OP_LIT) return false;
    267		break;
    268	case INST_JMP:
    269		if (inst->opcnt != 1) return false;
    270		if (inst->ops[0].type != OP_LABEL) return false;
    271		break;
    272	case INST_JEQ: case INST_JNE:
    273		if (inst->opcnt != 2) return false;
    274		if (inst->ops[0].type == OP_LABEL) return false;
    275		if (inst->ops[1].type != OP_LABEL) return false;
    276		break;
    277	case INST_MOV:
    278		if (inst->opcnt != 2) return false;
    279		if (inst->ops[0].type == OP_LABEL) return false;
    280		if (inst->ops[1].type == OP_LABEL) return false;
    281		if (inst->ops[1].type == OP_LIT) return false;
    282		break;
    283	}
    284
    285	return true;
    286}
    287
    288bool
    289tpu_add_inst(struct tpu *tpu, enum tpu_inst_type inst_type,
    290	unsigned opcnt, struct tpu_inst_op op1, struct tpu_inst_op op2)
    291{
    292	if (tpu->inst_cnt >= TPU_MAX_INST)
    293		die("tpu_add_inst: tpu X%i Y%i, >= max %i instructions",
    294			tpu->x, tpu->y, TPU_MAX_INST);
    295	return tpu_set_inst(tpu, (uint8_t) tpu->inst_cnt++,
    296		inst_type, opcnt, op1, op2);
    297}
    298
    299static int
    300tpu_port_read(struct tpu *tpu, enum tpu_port_dir dir)
    301{
    302	struct tpu_port *port;
    303
    304	port = &tpu->ports[dir];
    305	if (port->attached && tpu->tis->stdin
    306			&& port->dst_port == &tpu->tis->stdin_port && port->in < 0) {
    307		port->in = getc(tpu->tis->stdin);
    308		port->in = port->in == EOF ? -1 : (uint8_t) port->in;
    309	}
    310	if (port->in < 0) {
    311		port->reading = true;
    312		return -1;
    313	}
    314	port->clr_post_run = true;
    315	return port->in;
    316}
    317
    318static bool
    319tpu_port_write(struct tpu *tpu, enum tpu_port_dir dir, uint8_t lit)
    320{
    321	struct tpu_port *port;
    322
    323	port = &tpu->ports[dir];
    324	if (port->attached && port->dst_port == &tpu->tis->stdout_port) {
    325		if (tpu->tis->stdout) putc(lit, tpu->tis->stdout);
    326		return true;
    327	}
    328	if (!port->attached || !port->dst_port->empty) {
    329		port->out_pending = lit;
    330		port->writing = true;
    331		return false;
    332	}
    333	port->out = lit;
    334	port->out_pending = -1;
    335	return true;
    336}
    337
    338static void
    339tpu_jmp_label(struct tpu *tpu, const char *label)
    340{
    341	size_t pc;
    342
    343	if (tpu->jmpdst[tpu->pc] >= 0) {
    344		tpu->pc = (uint8_t) tpu->jmpdst[tpu->pc];
    345	} else {
    346		pc = tpu_label_get(tpu, label);
    347		if (pc >= TPU_MAX_INST) abort();
    348		tpu->jmpdst[tpu->pc] = (uint8_t) pc;
    349		tpu->pc = (uint8_t) pc;
    350	}
    351}
    352
    353static int
    354tpu_exec_get(struct tpu *tpu, struct tpu_inst_op *op)
    355{
    356	int i, v;
    357
    358	switch (op->type) {
    359	case OP_ACC:
    360		return tpu->acc;
    361	case OP_NIL:
    362		return -1;
    363	case OP_LEFT: case OP_RIGHT: case OP_UP: case OP_DOWN:
    364		return tpu_port_read(tpu, op_to_dir(op->type));
    365	case OP_ANY:
    366		for (i = 0; i < 4; i++) {
    367			v = tpu_port_read(tpu, (enum tpu_port_dir) i);
    368			if (v >= 0) {
    369				tpu->last = i;
    370				return (uint8_t) v;
    371			}
    372		}
    373		return -1;
    374	case OP_LAST:
    375		if (tpu->last < 0) return 0;
    376		return tpu_port_read(tpu, (enum tpu_port_dir) tpu->last);
    377	case OP_LIT:
    378		return op->val.lit;
    379	case OP_LABEL:
    380		abort();
    381	}
    382}
    383
    384static bool
    385tpu_exec_put(struct tpu *tpu, struct tpu_inst_op *op, uint8_t lit)
    386{
    387	int i;
    388
    389	switch (op->type) {
    390	case OP_ACC:
    391		tpu->acc = lit;
    392		return true;
    393	case OP_NIL:
    394		return true;
    395	case OP_LEFT: case OP_RIGHT: case OP_UP: case OP_DOWN:
    396		return tpu_port_write(tpu, op_to_dir(op->type), lit);
    397	case OP_ANY:
    398		for (i = 0; i < 4; i++) {
    399			if (tpu_port_write(tpu, (enum tpu_port_dir) i, lit)) {
    400				tpu->last = i;
    401				return true;
    402			}
    403		}
    404		return false;
    405	case OP_LAST:
    406		if (tpu->last < 0) return false;
    407		return tpu_port_write(tpu, (enum tpu_port_dir) tpu->last, lit);
    408	case OP_LABEL:
    409	case OP_LIT:
    410		abort();
    411	}
    412}
    413
    414static enum tpu_status
    415tpu_exec_mov(struct tpu *tpu, struct tpu_inst *inst)
    416{
    417	int lit;
    418
    419	if (inst->type != INST_MOV) abort();
    420
    421	lit = tpu_exec_get(tpu, &inst->ops[0]);
    422	if (lit < 0) return STATUS_READ;
    423
    424	if (!tpu_exec_put(tpu, &inst->ops[1], (uint8_t) lit))
    425		return STATUS_WRITE;
    426
    427	return STATUS_RUN;
    428}
    429
    430static enum tpu_status
    431tpu_exec(struct tpu *tpu, struct tpu_inst *inst)
    432{
    433	enum tpu_status status;
    434	uint8_t lit;
    435	int val;
    436
    437	switch (inst->type) {
    438	case INST_NOP:
    439		tpu->pc += 1;
    440		return STATUS_RUN;
    441	case INST_MOV:
    442		status = tpu_exec_mov(tpu, inst);
    443		if (status == STATUS_RUN)
    444			tpu->pc += 1;
    445		return status;
    446	case INST_SWP:
    447		lit = tpu->acc;
    448		tpu->acc = tpu->bak;
    449		tpu->bak = lit;
    450		tpu->pc += 1;
    451		return STATUS_RUN;
    452	case INST_SAV:
    453		tpu->bak = tpu->acc;
    454		tpu->pc += 1;
    455		return STATUS_RUN;
    456	case INST_ADD:
    457		val = tpu_exec_get(tpu, &inst->ops[0]);
    458		if (val < 0) return STATUS_READ;
    459		tpu->acc += (uint8_t) val;
    460		tpu->pc += 1;
    461		return STATUS_RUN;
    462	case INST_SUB:
    463		val = tpu_exec_get(tpu, &inst->ops[0]);
    464		if (val < 0) return STATUS_READ;
    465		tpu->acc -= (uint8_t) val;
    466		tpu->pc += 1;
    467		return STATUS_RUN;
    468	case INST_NEG:
    469		tpu->acc = -tpu->acc;
    470		tpu->pc += 1;
    471		return STATUS_RUN;
    472	case INST_XOR:
    473		val = tpu_exec_get(tpu, &inst->ops[0]);
    474		if (val < 0) return STATUS_READ;
    475		tpu->acc ^= (uint8_t) val;
    476		tpu->pc += 1;
    477		return STATUS_RUN;
    478	case INST_AND:
    479		val = tpu_exec_get(tpu, &inst->ops[0]);
    480		if (val < 0) return STATUS_READ;
    481		tpu->acc &= (uint8_t) val;
    482		tpu->pc += 1;
    483		return STATUS_RUN;
    484	case INST_JMP:
    485		tpu_jmp_label(tpu, inst->ops[0].val.label);
    486		return STATUS_RUN;
    487	case INST_JEQ:
    488		val = tpu_exec_get(tpu, &inst->ops[0]);
    489		if (val < 0) return STATUS_READ;
    490		if (tpu->acc == val)
    491			tpu_jmp_label(tpu, inst->ops[1].val.label);
    492		else
    493			tpu->pc += 1;
    494		return STATUS_RUN;
    495	case INST_JNE:
    496		val = tpu_exec_get(tpu, &inst->ops[0]);
    497		if (val < 0) return STATUS_READ;
    498		if (tpu->acc != val)
    499			tpu_jmp_label(tpu, inst->ops[1].val.label);
    500		else
    501			tpu->pc += 1;
    502		return STATUS_RUN;
    503	case INST_JRO:
    504		tpu->pc += inst->ops[0].val.lit;
    505		if (tpu->pc >= tpu->inst_cnt) tpu->pc = 0;
    506		return STATUS_RUN;
    507	case INST_SHL:
    508		val = tpu_exec_get(tpu, &inst->ops[0]);
    509		if (val < 0) abort();
    510		tpu->acc <<= val;
    511		tpu->pc += 1;
    512		return STATUS_RUN;
    513	case INST_SHR:
    514		val = tpu_exec_get(tpu, &inst->ops[0]);
    515		if (val < 0) abort();
    516		tpu->acc >>= val;
    517		tpu->pc += 1;
    518		return STATUS_RUN;
    519	}
    520}
    521
    522enum tpu_status
    523tpu_step(struct tpu *tpu)
    524{
    525	if (tpu->pc < tpu->inst_cnt) {
    526		tpu->status = tpu_exec(tpu, &tpu->insts[tpu->pc]);
    527		if (tpu->pc >= tpu->inst_cnt) tpu->pc = 0;
    528	} else {
    529		tpu->status = STATUS_IDLE;
    530	}
    531
    532	tpu->steps += 1;
    533	if (tpu->status != STATUS_RUN)
    534		tpu->idle_steps += 1;
    535
    536	return tpu->status;
    537}
    538
    539void
    540tpu_vec_init(struct tpu_vec *vec)
    541{
    542	vec->cnt = 0;
    543	vec->cap = 256;
    544	vec->tpus = malloc(vec->cap * sizeof(struct tpu));
    545	if (!vec->tpus) die("malloc:");
    546}
    547
    548void
    549tpu_vec_deinit(struct tpu_vec *vec)
    550{
    551	struct tpu *tpu;
    552	size_t i;
    553
    554	for (tpu = vec->tpus, i = 0; i < vec->cnt; i++, tpu++)
    555		tpu_deinit(tpu);
    556	free(vec->tpus);
    557}
    558
    559void
    560tpu_map_init(struct tpu_map *map)
    561{
    562	memset(map->buckets, 0, sizeof(void *) * TPU_MAP_BUCKETS);
    563}
    564
    565void
    566tpu_map_deinit(struct tpu_map *map)
    567{
    568	struct tpu_map_link *link, *next;
    569	size_t i;
    570
    571	for (i = 0; i < TPU_MAP_BUCKETS; i++) {
    572		link = map->buckets[i];
    573		while (link) {
    574			next = link->next;
    575			free(link);
    576			link = next;
    577		}
    578	}
    579}
    580
    581static struct tpu_map_link **
    582tpu_map_link_pos(struct tpu_map *map, int x, int y)
    583{
    584	struct tpu_map_link **link;
    585	size_t i;
    586
    587	i = (size_t) (x + y) % TPU_MAP_BUCKETS;
    588	link = &map->buckets[i];
    589	while (*link && !((*link)->x == x && (*link)->y == y))
    590		link = &(*link)->next;
    591
    592	return link;
    593}
    594
    595bool
    596tpu_map_add(struct tpu_map *map, struct tpu *tpu)
    597{
    598	struct tpu_map_link **pos, *link;
    599
    600	pos = tpu_map_link_pos(map, tpu->x, tpu->y);
    601	if (*pos) return false;
    602	*pos = link = malloc(sizeof(struct tpu_map_link));
    603	if (!link) die("malloc:");
    604	link->tpu = tpu;
    605	link->x = tpu->x;
    606	link->y = tpu->y;
    607	link->next = NULL;
    608	return true;
    609}
    610
    611struct tpu *
    612tpu_map_get(struct tpu_map *map, int x, int y)
    613{
    614	struct tpu_map_link **link;
    615
    616	link = tpu_map_link_pos(map, x, y);
    617	if (!*link) return NULL;
    618
    619	return (*link)->tpu;
    620}
    621
    622void
    623tis_init(struct tis *tis, FILE *tis_stdin, FILE *tis_stdout)
    624{
    625	tis->steps = 0;
    626	tis->alive = true;
    627	tis->idle = tis->prev_idle = false;
    628	tpu_vec_init(&tis->tpu_vec);
    629	tpu_map_init(&tis->tpu_map);
    630	tis->stdin = tis_stdin;
    631	tpu_port_init(&tis->stdin_port);
    632	tis->stdin_port.type = PORT_OUT;
    633	tis->stdout = tis_stdout;
    634	tpu_port_init(&tis->stdout_port);
    635	tis->stdout_port.type = PORT_IN;
    636}
    637
    638void
    639tis_deinit(struct tis *tis)
    640{
    641	tpu_vec_deinit(&tis->tpu_vec);
    642	tpu_map_deinit(&tis->tpu_map);
    643	if (tis->stdin) fclose(tis->stdin);
    644	tpu_port_deinit(&tis->stdin_port);
    645	if (tis->stdout) fclose(tis->stdout);
    646	tpu_port_deinit(&tis->stdout_port);
    647}
    648
    649bool
    650tis_step(struct tis *tis)
    651{
    652	struct tpu *tpu;
    653	bool running;
    654	size_t i;
    655
    656	running = false;
    657	for (tpu = tis->tpu_vec.tpus, i = 0; i < tis->tpu_vec.cnt; i++, tpu++)
    658		running |= (tpu_step(tpu) == STATUS_RUN);
    659
    660	for (tpu = tis->tpu_vec.tpus, i = 0; i < tis->tpu_vec.cnt; i++, tpu++)
    661		tpu_update_ports(tpu);
    662
    663	tis->prev_idle = tis->idle;
    664	tis->idle = !running;
    665
    666	tis->alive = !tis->idle || !tis->prev_idle
    667		|| tis->stdin_port.reading && !feof(tis->stdin);
    668
    669	tis->steps++;
    670
    671	return tis->alive;
    672}
    673
    674struct tpu *
    675tis_add_tpu(struct tis *tis)
    676{
    677	struct tpu_port *port;
    678	struct tpu_vec *vec;
    679	struct tpu_map_link *link;
    680	struct tpu *tpu;
    681	void *old_arena;
    682	size_t i, k;
    683	ssize_t off;
    684
    685	vec = &tis->tpu_vec;
    686	if (vec->cnt >= vec->cap) {
    687		vec->cap *= 2;
    688		old_arena = vec->tpus;
    689		vec->tpus = realloc(vec->tpus, vec->cap * sizeof(struct tpu));
    690		if (!vec->tpus) die("realloc:");
    691		/* AAAAH, dont looook >.< */
    692		off = (void *) vec->tpus - old_arena;
    693		for (tpu = vec->tpus, i = 0; i < vec->cnt; i++, tpu++) {
    694			for (port = tpu->ports, k = 0; k < 4; k++, port++) {
    695				if (port->dst_tpu)
    696					port->dst_tpu = (void *)port->dst_tpu + off;
    697				if (port->dst_port)
    698					port->dst_port = (void *)port->dst_port + off;
    699			}
    700		}
    701		for (i = 0; i < TPU_MAP_BUCKETS; i++) {
    702			link = tis->tpu_map.buckets[i];
    703			for (; link; link = link->next)
    704				link->tpu = (void *)link->tpu + off;
    705		}
    706	}
    707
    708	return &vec->tpus[vec->cnt++];
    709}