Some more bytecode questions :)

Started by
15 comments, last by wodinoneeye 16 years, 11 months ago
I have some more questions about bytecode. I think I am probably starting to annoy everyone by now with all my questions, but eventually I will end up with a Nobel Prize winning language, so, without further ado, here goes: Right. Functions. When I make functions in my script, does it make sense to compile it as an actual proper function, or is it alright just to make it more like an inline function? After all, it wouldn't be taking up any more time to run, maybe a little more memory though. Take this for example...

function sqrt(int num)
{
   if (num >= 0)
   {
      return num^0.5;
   }
}

function main()
{
   print sqrt(4);
   print sqrt(9);
   print sqrt(16);
   print sqrt(25);
}


might turn into this bytecode (just a pseudo thing just now):

open     // {

open      // { so variables are deletes after scope closes
int num
rset 4     //puts 4 in temp register
setv 0     // sets var 0 to register data
push 0
pushv 0    //pushes var 0 (num) onto stack
more_eq
if         //if result of the more_eq bit is 1
open
pushv 0
push 0.5
power      // ^
rset      //sets temp register to the result of the above
close       // }
print      //displays register data
close      // }.. vars and other stack things are deleted (local variables!)

//do this another 3 times :P

close     // }


I hope you can understand what I mean by that. Heheh. I had some more questions.. but I forgot them typing that one out.. :/ I'll get back to you guys with any more. Cheers
Advertisement
inlining all functions is a bad idea. As function get larger, inlining them will produce a huge memory overhead. Plus, inlined functions cannot be recursive (well, you could identify tail-recursion and compile it to iterative code, but that would be very restrictive).
Hmm.. well what do you recommend? I thought another possible way would be to.. uh.. hard to explain, look at this then:

1. open   //this is where the sqrt function starts          //its not shown that its a function, but its skipped anyway          //because the entry point is where main is2.     //just do stuff in here...10. close11. open //main12.   //..    //put some data on the stack, that will count as parameters15.   //16. goto 1  //go to where the sqrt function starts. it will take all stack            //items it needs and create variables from them17. print18. close
Quote:Original post by Zotoaster
Hmm.. well what do you recommend? I thought another possible way would be to.. uh.. hard to explain, look at this then:

*** Source Snippet Removed ***


That is a simplified of how it's done in assembly (the instruction is just called "call" in x86 assembly language). Inlining would be an extremely bad idea. How would you do the following:
function exponentation(int base,int exponent){  if( exponent == 0 )  {    return 1;  }  if( exponent >= 0)  {    return base*exponentation(base,exponent-1);  }}

How would that be translated to inlined byte code?

It would also be horrible for performance. It would increase the number of page faults (hurts performance) and you wouldn't use the cache optimally.
That's a good point CTar, I totally forgot about recursiveness!

So you're saying I should just go with the goto idea? And it just uses the stack for parameters? I guess I could use the register as the return value. Never thought of that heheh. Yeah, I think I will go with that method.


I remembered my other question btw. It's not exactly about bytecode, but meh.

I have my memory buffer, _mem. I knew I had to make it store all types of data, int, char, or any other UDT, so I just made a struct, where the data variable is an integer, and it has a type variable, which, obviously defines the type of data used. So, an array with my name might look like:

_mem[0].type = 0 (char); _mem[0].data = 'Z';
_mem[1].type = 0 (char); _mem[1].data = 'o';
etc...

That works perfectly. I was just wondering how floating point numbers were stored. I know that basically, it has an exponent and a mantissa, but I can't figure out how to make it fit here.
Quote:Original post by Zotoaster
So you're saying I should just go with the goto idea? And it just uses the stack for parameters? I guess I could use the register as the return value. Never thought of that heheh. Yeah, I think I will go with that method.

Usually that is the best method. As you learn more you'll find other methods and you might have to choose between several different methods, but for now I suggest you just go with "goto" and you can always add more mechanisms if it's needed. On a sidenote I suggest you get another name for it since goto usually means to go somewhere, but there is no promise of returning.

Quote:I have my memory buffer, _mem. I knew I had to make it store all types of data, int, char, or any other UDT, so I just made a struct, where the data variable is an integer, and it has a type variable, which, obviously defines the type of data used. So, an array with my name might look like:

_mem[0].type = 0 (char); _mem[0].data = 'Z';
_mem[1].type = 0 (char); _mem[1].data = 'o';
etc...

That works perfectly. I was just wondering how floating point numbers were stored. I know that basically, it has an exponent and a mantissa, but I can't figure out how to make it fit here.

What exactly do you mean fit? You could normally fit a floating point number in one data variable by doing some simple bit manipulation. Are you having trouble figuring out the mantissa, exponent and sign or are you having trouble fitting them in memory?
Well as I said, the data is stored as an int. Does this mean I have to have two bits of memory? An int for the mantissa, and an int for for exponent? Even so, I would have no clue then how to turn a regular float into these two forms..


[edit]

Also, I just remembered another question, that's been bugging me for a while. What happens with parameters passed by reference? I know what happens, I just don't know how! :p
Quote:Also, I just remembered another question, that's been bugging me for a while. What happens with parameters passed by reference? I know what happens, I just don't know how! :p


Instead of passing a parameter by value, as in pushing it on the stack, you instead push its memory location onto the stack. The callee will then be able to read from or write to this location, and changes will be kept after returning.

In your case it would be something like:
stack.push( PointerToInt ); // as in _stack[0].type = PointerToIntstack.push( & myInt ); // as in _stack[0].data = int( & myInt );


It should be noted that storing a pointer in an integer could result in loss of data (for example on non 32-bit systems), so it's probably wiser to use other means to implement a data store. For example, you could map data to memory directly:
*reinterpret_cast<int *>( baseMem + offset ) = myInt;


Quote:Well as I said, the data is stored as an int. Does this mean I have to have two bits of memory? An int for the mantissa, and an int for for exponent? Even so, I would have no clue then how to turn a regular float into these two forms..


A 32-bit floating-point number can be stored (and consequently restored) perfectly in a 32-bit integer data type. In C++, this could go like so:
*reinterpret_cast<float *>( & myInt ) = myFloat; // storemyFloat = *reinterpret_cast<float *>( & myInt ); // restore


Please, correct me if I made any syntactical mistakes. I'm in a bit of a hurry :-).
Well, I don't know how I didn't think about pushing the pointer to an object onto a stack. Luckily enough, I've already implemented that just for variables going on the stack. I really am an idiot, I think I will go an have something to eat too and see if that helps :P

As for the float thing, I'm not using templates or anything. This is what the memory array type is:
	struct tMem	{		tMem() : type(0), taken(false), obj(0), data(0)		{		}		int data;			//Data to be used		int obj;			//Object at memory location		int type;			//Type of object		bool taken;			//If memory is currently used	};

Don't view it as a template, reinterpret_cast is simply the C++ equivalent to the C cast-operator:
reinterpret_cast<float *>( & myInt )

does exactly the same as:
(float *)& myInt

e.a. reinterpret bits

So, again:
_mem[0].type = typeid(float);*(float *)( &_mem[0].data ) = 3.1415f;

This topic is closed to new replies.

Advertisement