/*
 * UAE - The Un*x Amiga Emulator
 *
 * cpuopti.c - Small optimizer for cpu*.s files
 *             Based on work by Tauno Taipaleenmaki
 *
 * Copyright 1996 Bernd Schmidt
 */

#include "sysconfig.h"
#include "sysdeps.h"
#include <ctype.h>

#include "options.h"

struct line {
    struct line *next, *prev;
    int delete;
    char *data;
};

struct func {
    struct line *first_line, *last_line;
    int initial_offset;
};

static void oops(void)
{
    fprintf(stderr, "Corrupted assembly file!\n");
    abort();
}

static char * match(struct line *l, const char *m)
{
    char *str = l->data;
    int len = strlen(m);
    while (isspace(*str))
	str++;
    
    if (strncmp(str, m, strlen(m)) != 0)
	return NULL;
    return str + len;
}

static void do_function(struct func *f)
{
    int pops_at_end = 0;
    struct line *l, *fl;
    char *s, *s2;
    int in_pop_area = 1;
    
    f->initial_offset = 0;
    
    l = f->last_line;
    fl = f->first_line;

    if (!match(l,"ret"))
	oops();
    l = l->prev;
    
    while (!match(fl, "op_"))
	fl = fl->next;
    fl = fl->next;
    
    s = match(l, "addl $");
    s2 = match(fl, "subl $");
    if (s && s2 && strcmp(s,s2) == 0) {
	int v = 0;
	while (isdigit(*s)) {
	    v = v * 10 + (*s) - '0';
	    s++;
	}
	if (strcmp(s, ",%esp") == 0) {
	    f->initial_offset = v;
	    l->delete = 2;
	    fl->delete = 3;
	    l = l->prev;
	    fl = fl->next;
	} else 
	    in_pop_area = 0;
    }
    while (in_pop_area) {
	char *popm, *pushm;
	popm = match(l, "popl %");
	pushm = match(fl, "pushl %");
	if (popm && pushm && strcmp(pushm, popm) == 0) {
	    pops_at_end++;
	    fl->delete = l->delete = 1;
	} else
	    in_pop_area = 0;
	l = l->prev;
	fl = fl->next;
    }
    if (f->initial_offset)
	f->initial_offset += 4 * pops_at_end;
}

static void output_function(struct func *f)
{
    struct line *l = f->first_line;
    
    while (l) {
	switch (l->delete) {
	 case 1:
	    break;
	 case 0:
	    printf("%s\n", l->data);
	    break;
	 case 2:
	    if (f->initial_offset)
		printf("\taddl $%d,%%esp\n", f->initial_offset);
	    break;
	 case 3:
	    if (f->initial_offset)
		printf("\tsubl $%d,%%esp\n", f->initial_offset);
	    break;
	}
	l = l->next;
    }
}

int main(int argc, char **argv)
{
    char tmp[4096];

    for(;;) {
	char *s;
	
	if ((fgets(tmp, 4095, stdin)) == NULL)
	    break;

	s = strchr (tmp, '\n');
	if (s != NULL)
	    *s = 0;

	if (strncmp(tmp, ".globl op_", 10) == 0) {
	    struct line *first_line = NULL, *prev = NULL;
	    struct line **nextp = &first_line;
	    struct func f;
	    
	    do {
		struct line *current;

		if (strcmp (tmp, "#APP") != 0 && strcmp (tmp, "#NO_APP") != 0) {
		    current = *nextp = (struct line *)malloc(sizeof (struct line));
		    nextp = &current->next;
		    current->prev = prev; prev = current;
		    current->next = NULL;
		    current->delete = 0;
		    current->data = my_strdup (tmp);
		}
		if ((fgets(tmp, 4095, stdin)) == NULL) 
		    oops();
		s = strchr (tmp, '\n');
		if (s != NULL)
		    *s = 0;
	    } while (strncmp (tmp,".Lfe", 4) != 0);

	    f.first_line = first_line;
	    f.last_line = prev;

	    do_function(&f);
	    output_function(&f);
	}
	printf("%s\n", tmp);
    }
    return 0;
}
