Talk:Higher Subleq

From Esolang
Jump to navigation Jump to search

Esoteric?

Is this an esoteric language?--TehZ 20:32, 25 December 2010 (UTC)

Probably; even if it isn't, it is on-topic, as it is intended to compile to Subleq, which is definitely esoteric. —ehird 20:59, 25 December 2010 (UTC)

Accessing internal registers

I've found that although the sp (stack top pointer), bp (stack frame base pointer), ra (local return address), and ax (most recent return value) variables aren't directly accessible to the programmer, it's possible to read them and write to them by calling a function and accessing or altering the stack frame. Some C functions that require the use of internal registers are setjmp(), longjmp(), and alloca(). I wrote getter/setter functions for all of the registers as well as working alloca(), setjmp(), and longjmp() functions. These require no modifications to the compiler and seem to work correctly even in complex situations. They sometimes require a few static variables, which begin with a single underscore followed by the function name without any leading underscores. These static variables are so the functions can store data without using stack-relative local variables or arguments, which will become inaccessible when the stack pointer is moved. By tracing bps up the stack, it's possible to alter the sp, bp, or ra of a function further up the stack.

void *_get_ra(int dummy) {return -(&dummy)[1];}
void *_get_bp(int dummy) {return -(&dummy)[2];}
void *_get_sp() {return _get_bp()-2;}
void *_get_ax() {return;}
void *_set_ra(void *val) {return (&val)[1] = -val;}
void *_set_bp(void *val) {return (&val)[2] = -val;}
void *_set_sp_ra;
void *_set_sp_bp;
void *_set_sp(void *val) {
	_set_sp_ra = (&val)[1];
	_set_sp_bp = (&val)[2];
	void *p = _set_bp(val+3);
	(&val)[1] = _set_sp_ra;
	(&val)[2] = _set_sp_bp;
	return p;
}
void *_set_ax(int val) {return val;}
void *_alloca_ra;
void *_alloca_bp;
void *_alloca_ptr;
void *alloca(int n) {
	_alloca_ra = (&n)[1];
	_alloca_bp = (&n)[2];
	_alloca_ptr = _get_bp()-3;
	_set_bp(_get_bp()+n);
	(&n)[1] = _alloca_ra;
	(&n)[2] = _alloca_bp;
	return _alloca_ptr;
}
/* a jmp_buf is an array of 3 cells */
int setjmp(void **env) {
	env[0] = _get_bp(); //sp
	env[1] = (&env)[1]; //ra
	env[2] = (&env)[2]; //bp
	return 0;
}
void **_longjmp_env;
int _longjmp_val;
int longjmp(void **env, int val) {
	_longjmp_env = env;
	_longjmp_val = val?val:1;
	_set_bp(_longjmp_env[0]+1); //sp
	(&env)[1] = _longjmp_env[1]; //ra
	(&env)[2] = _longjmp_env[2]; //bp
	return _longjmp_val;
}

Ian 05:13, 9 August 2011 (UTC)

I made a small automatic-allocation string library based on alloca() and using the _get_bp(), _set_bp(), and _set_sp() functions defined previously. The functions are getsa(), strdupa(), and strndupa(). The last two are identical to the GNU libc functions. The first one is similar to gets(), reading a line until newline or EOF, but automatically allocates the buffer into the caller's automatic storage. They each have their own static variables even though the variables could be shared. Ideally, these would use the do-while loop style, but that's not implemented yet, so an infinite loop with a break is used instead. They can't use plain while because they need space for the null terminator and need to work with zero-length strings. Although getsa()'s prototype has an argument, it should be called with no arguments, since that's just a dummy variable used to provide access to the stack frame. These functions and alloca() can safely be called multiple times (infinite times if the Subleq machine has infinite memory). A longjmp() will corrupt or destroy any data allocated by these functions since the last setjmp(). To explicitly free data, alloca() can be called with a negative number, which will pop that number of cells from the stack. Like pointers to ordinary automatic variables, the pointers returned by these functions can't safely be returned to a previous caller, but it should be possible to make some type of combined memmove()-longjmp() that can move the string into a previous stack frame and then return.
void *_getsa_ra;
void *_getsa_bp;
int _getsa_c;
char *_getsa_ptr;
char *_getsa_ret;
char *getsa(int dummy) {
	_getsa_ra = (&dummy)[1];
	_getsa_bp = (&dummy)[2];
	_getsa_ret = _getsa_ptr = _get_bp()-2;
	while (1) {
		_set_bp(_getsa_ptr+3);
		_set_sp(_getsa_ptr);
		if ((_getsa_c = *_getsa_ptr++ = __in) == '\n' || _getsa_c < 0) break;
	}
	*--_getsa_ptr = '\0';
	(&dummy)[1] = _getsa_ra;
	(&dummy)[2] = _getsa_bp;
	return _getsa_ret;
}
void *_strdupa_ra;
void *_strdupa_bp;
char *_strdupa_str;
char *_strdupa_ptr;
char *_strdupa_ret;
char *strdupa(char* str) {
	_strdupa_ra = (&str)[1];
	_strdupa_bp = (&str)[2];
	_strdupa_str = str;
	_strdupa_ret = _strdupa_ptr = _get_bp()-3;
	while (1) {
		_set_bp(_strdupa_ptr+4);
		_set_sp(_strdupa_ptr);
		if (!(*_strdupa_ptr++ = *_strdupa_str++)) break;
	}
	(&str)[1] = _strdupa_ra;
	(&str)[2] = _strdupa_bp;
	return _strdupa_ret;
}
void *_strndupa_ra;
void *_strndupa_bp;
char *_strndupa_str;
int _strndupa_n;
char *_strndupa_ptr;
char *_strndupa_ret;
char *strndupa(char* str, int n) {
	_strndupa_ra = (&str)[1];
	_strndupa_bp = (&str)[2];
	_strndupa_str = str;
	_strndupa_n = n;
	_strndupa_ret = _strndupa_ptr = _get_bp()-4;
	while (1) {
		_set_bp(_strndupa_ptr+5);
		_set_sp(_strndupa_ptr);
		if (_strndupa_n-- <= 0 || !(*_strndupa_ptr++ = *_strndupa_str++)) break;
	}
	*_strndupa_ptr = '\0';
	(&str)[1] = _strndupa_ra;
	(&str)[2] = _strndupa_bp;
	return _strndupa_ret;
}

Ian 18:12, 9 August 2011 (UTC)

Here's a memdupa() to go with the other functions. This will allocate space for n bytes, copy them from the memory pointed to by mem, and then return a pointer to the new space. Like strdupa() and strndupa(), it is undefined behavior if the source and the newly allocated memory overlap, but this can only happen if the source is above the top of stack (like a dangling pointer). This function is mostly equivalent to memcpy(alloca(n),mem,n), except this implementation allocates and copies one cell at a time.
void *_memdupa_ra;
void *_memdupa_bp;
void *_memdupa_mem;
int _memdupa_n;
void *_memdupa_ptr;
void *_memdupa_ret;
void *memdupa(void* mem, int n) {
	_memdupa_ra = (&mem)[1];
	_memdupa_bp = (&mem)[2];
	_memdupa_mem = mem;
	_memdupa_n = n;
	_memdupa_ret = _memdupa_ptr = _get_bp()-4;
	while (--_memdupa_n >= 0) {
		_set_bp(_memdupa_ptr+5);
		_set_sp(_memdupa_ptr);
		*_memdupa_ptr++ = *_memdupa_mem++;
	}
	(&mem)[1] = _memdupa_ra;
	(&mem)[2] = _memdupa_bp;
	return _memdupa_ret;
}

Ian 22:52, 9 August 2011 (UTC)