Download presentation
Presentation is loading. Please wait.
1
Design of tacsim - Execute part
Kei Hasegawa
2
Normal backend hcc1 . function(name, type etc)
. parameter scope of function . 3 address code of function . symbol table Repeted several times, the number of functions in a file. intel.dll No problem at all, because normal backend generates assembler code immediately.
3
When should tacsim.dll evaluate function or external variable?
void f(int a) { ... } (*) int x = 5; int main(void) f(3); return 0; hcc1 destroys parameter scope of `f' and 3 address code of `f' at this point. If `f' is declared as `static' function, current implement works well(?), I hope ...
4
It's necessary to evaluate all at once at the end of file.
int main() { f(); g(); } (*) void f(){ ... } void g(){ ... } At this point, it is impossible for tacsim to evaluate 3 address code of `main' function, because tacsim doesn't know about 3 address code of `f' and `g' function.
5
Difference from normal backend
hcc1 hcc1 . function1 . 3 address code of function1 . function2 . 3 address code of function2 ... . symbol table . function . 3 address code ・symbol table Repeated several times, the number of functions in a file. intel.dll tacsim.dll At the end of file, specified subroutine of backend is called.
6
Inter face of hcc1 -> tacsim
vector<info> funcs; struct info { fundef* m_func; vector<tac*> m_code; }; // very similar to static_inline::info &scope::root; // symbol table
7
How to execute `main' function
Call bellow function with argument "main": bool call_direct(string name, pc_t ra) { vector<info>::const_iterator p = find_if(funcs.begin(),funcs.end(),bind2nd(ptr_fun(cmp_name),name)); if ( p == funcs.end() ) return false; return_address.push_back(ra); allocate_stack(p->m_func->m_parameter); const vector<tac*>& code = p->m_code; pc_t pc = code.begin(); while ( pc != code.end() ){ tac* ptr = *pc; pc = exec(pc); if ( *ptr is return3ac ) break; } deallocate_stack(p->m_func->m_parameter); return_address.pop_back(); return true;
8
Before execute `main' function, evaluate static variable in memory not in stack.
Normal backend does the same thing before generating assemble code. None-static memory, i.e. local variables of a function or medium variables are evaluated at the entrance of the function. So, local variables and medium variables of `main' function are evaluated before executing `main' function. For each static variable, assign real memory. If it has initial value, set the value. Normal backend does the same thing.
9
initial value of static memory variable can be address of variable
extern void* q; void* p = (void*)&q; void* q = (void*)&p; (I think) it is impossible to decide the initial value in any implementation. So, it's easy to decide initial value after deciding every variable address. i.e: Decide address of `p' Decide address of `q' Decide initial value of `p' and `q'
10
Operand of 3 address code(1)
Entry of symbol table map<string, vector<usr*> > scope::m_usrs; vector<var*> block::m_vars; For each usr*, var*, if it's necessary, assign memory. ex: For `extern int errno;', not necessary. Assign address 0 for variable which is declared with `extern'. Create table like bellow: typedef map<var*, void*> address_table_t; address_table_t tacsim::memory; stack<address_table_t> tacsim::stack; For constant Allocate memory and set the value there so that it's simple to implement 3 address code execution part. Note that normal backend allocates memory for `long double' and set the value there. For function For a function definition, assign non-zero address ( but not allocate memory ), which enables to call the function when function call operator is applied for this address. For a function declaration, not a definition, assign address 0.
11
Operand of 3 address code(2)
For parameter scope and its children scope, create above table when function is called. And when leveving from a function, free memory and destroy entry from above table. Especially, for parameters, assign continuous memory. ex: For void f(int* p, double d, struct S s){ ... }, assign memory for `p', `d' and `s' where the bellow conditions are satisfied: align(assigned address of `p' + sizeof p, double ) == assigned address of `d' align(assigned address of `d' + sizeof d, struct S) == assigned address of struct S
12
x := y typedef vector<tac*>::const_iterator pc_t;
pc_t assign(pc_t pc) { tac* ptr = *pc; const type* T = ptr->x->m_type; memcpy(getaddr(ptr->x), getaddr(ptr->y), T->size()); return pc + 1; } Not depend if T is scalar or not.
13
getaddr address_table_t tacsim::memory;
stack<address_table_t> tacsim::stack; assign memory map<string, void*> external::table; not assign memory. maybe defined in library. void* getaddr(var* v) { const address_tabl_t& curr = tacim::stack.top(); address_tabl_t ::const_iterator p = curr.find(v); if (p != curr.end()) return p->second; // v is local variable or medium variable p = tacsim::memory.find(v); if (p != tacsim::memory.end()) if ( void* r = p->second ) return r; usr& u = dynamic_cast<usr&>(*v);
14
getaddr (2) // Search the same name entry which is defined at outside of function and assigned memory from tacsim::memory p = find_if(tacsim::memory.begin(), tacsim::memory.end(), bind2nd(ptr_fun(definition),u.m_name)); if ( p != tacsim::memory.end() ) return p->second; map<string, void*>::const_iterator q = external::table.find(u.m_name); if (q != external::table.end()) return q->second; // ex For `errno', `printf' etc throw not_found(u); // just declared but not defined } Register representative symbls into external::table at tacsim compile time
15
x := y + z pc_t add(pc_t pc) { tac* ptr = *pc;
const type* T = ptr->x->m_type; if ( T->real() ){ if ( T->size() == sizeof(float) ) *(float*)getaddr(ptr->x) = *(float*)getaddr (ptr->y) + *(float*)getaddr (ptr->z); else if ( ... ) } else if ( T->integer() ){ integer add else { pointer and integer add return pc + 1;
16
x := y op z point Really do op of type T. T is bellow ( it depends on op ): long double, double, float unsigned long long int, long long int, unsigned long int, long int, unsigned int, int pointer ex: For substract, really do bellow if sizeof(long int) = sizeof(int): long double - long double double - double float - float long long int - long long int int - int pointer - pointer pointer - long long int pointer - int It's necessary to make difference from signed and unsigned according to op. Not defined for real type according to op.
17
x := &y pc_t addr(pc_t pc) { } tac* ptr = *pc;
*(void**)getaddr(ptr->x) = getaddr(ptr->y); return pc + 1; }
18
x := *y pc_t invraddr(pc_t pc) { } tac* ptr = *pc;
const type* T = ptr->x->m_type; memcpy(getaddr(ptr->x), *(void**)getaddr(ptr->y),T->size()); return pc + 1; }
19
x[y] := z pc_t loff(pc_t pc) { } tac* ptr = *pc;
const type* Tz = ptr->z->m_type; char* dst = (char*)getaddr(ptr->x); const type* Ty = ptr->y->m_type; int sy = Ty->size(); int64_t delta = (sy == 4) ? *(int32_t*)getaddr(ptr->y) : *(int64_t*)getaddr(ptr->y); memcpy(dst+delta, getaddr(ptr->z), Tz->size()); return pc + 1; }
20
param y extern vector<pair<void*,const type*> > parameters; pc_t param(pc_t pc) { tac* ptr = *pc; const type* T = ptr->y->m_type; int size = T->size(); void* p = new char[size]; memcpy(p,getaddr(ptr->y),size); parameters.push_back(make_pair(p,T)); return pc + 1; } While calling function, assign continuous memory for whole of parameters, and copy the contents of `parameters[i].first' for each i. After copy, free `parameters[i].first' for each i. and do parameters.clear().
21
x := call y, call y pc_t call(pc_t pc) { } pc_t call_via_name(pc_t pc)
tac* ptr = *pc; const type* T = ptr->y->m_type; return T->scalar() ? call_via_pointer(pc) : call_via_name(pc); } pc_t call_via_name(pc_t pc) string name = ptr->y->m_name; if ( !call_direct(name, pc+1) ) // This function named `name' is not user defined function. ex: printf void* pf = (refer to external::table); const type* T = ptr->y->m_type; // T is func_type const func_type* ft = static_cast<const func_type*>(T); call_common(ptr->x, pf, ft); return pc + 1;
22
function call via pointer to function
pc_t call_via_pointer(pc_t pc) { tac* ptr = *pc; void* pf = *(void**)getaddr(ptr->y); if (pf is points to user-defined function) { string name = ...; call_direct(name, pc+1); return pc + 1; } const type* T = ptr->y->m_type; T = T->unqualified(); const pointer_type* pt = static_cast<const pointer_type*>(T); T = pt->reference_type(); const func_type* ft = static_cast<const func_type*>(T); call_common(ptr->x, pf, ft);
23
call_common(1) The way to specify arguments depends on target processor and compiler which compile tacsim. For general RISC processor, first several arguments are set to specified registers Other arguments are set to relative location from stack pointer ( with loop assembler code) For general CISC processor, just do this
24
call_common(2) enum kind_t { }; union U {
NONE, LD, DOUBLE, FLOAT, U64, S64, U32, S32, U16, S16, U8, S8, VP, REC }; union U { long double ld; double d; float f; uint64_t u64; int64_t s64; uint32_t u32; int32_t s32; uint16_t u16; int16_t s16; uint8_t u8; int8_t s8; void* vp; struct uks { U m_u; kind_t m_kind; int m_size; }; void call_common(var* x, void* pf, const func_type* ft) { const vector<const type*>& param = ft->param(); int nth = (calculate from param. If `ft' takes variable arguments, the number of arguments which are specified. If not, limits<int>::max()); vector<pair<U,kind_t> > Us; transform(parameters.begin(),parameters.end(),back_inserter(Us), conv);
25
call_common(3) for_each(parameters.begin(), parameters.end(), destroy<void*, const type*>()); parameters.clear (); T = ft->return_type(): int size = T->size(); U r; r.vp = T->aggregate() ? new char[size] : 0; kind_t rk = conv_type(T); call_Us(&r, pf, &Us[0], &Us[Us.size()],nth,rk); if ( x ) { void* p = T->aggregate() ? r.vp : &r ; memcpy(getaddr(x), p, size); }
26
call_Us intel x64, Visual Studio version(1)
rsp arguments area for calling `pf' At runtme of `call_Us', calculate bellow: t0 := end - begin t1 := t0 / sizeof(uks) t2 := t1 * 8 and move rsp t2 bytes. local variables and medium variables of `call_Us' rbp
27
call_Us intel x64, Visual Studio version(2)
// void call_Us(U* r, void* pf, uks* begin, uks* end, int nth, kind_t rk) // From here, Microsoft assembler coding move rsp for `pf' arguments area if (r->vp) { // Function return type is aggregate. // Set r->vp to rcx register }
28
call_Us intel x64, Visual Studio version(3)
uks* p = begin; if (p == end) goto LAST; if (p->m_kind <= kind_t::FLOAT) { // 1st argumet is real type // If nth is equal to 0, float is convert to double. If r->vp is equal to 0, set the value to rcx, if not, to rdx // If nth is not equal to 0, set the value to xmm0 } else if (p->m_kind <= kind_t::VP) { // 1st argument is integer or pointer type // If nth is equal to 0, do promotion. If r->vp is equal to 0, set the value rcx, if not, to rdx else { // 1st argument is aggregate type // If r->vp is eqaul to 0, set address of aggregate object to rcx, if not, to rdx // To here, 1st argument is set
29
call_Us intel x64, Visual Studio version(4)
++p; if (p == end) goto LAST; if (p->m_kind <= kind_t::FLOAT) { // 2nd argument is real type // If nth <= 1, float is convert to double. If r->vp is equal to 0, set the value to rdx, if not, to r8 // If nth > 1, set the value to xmm1 } else if (p->m_kind <= kind_t::VP) { // 2nd argument is integer or pointer type // If nth <= 1, do promotion. If r->vp is equal to 0, set the value to rdx, if not, to r8ト else { // 2nd argument is aggregate type // If r->vp is equal 0, set address of aggregate object to rdx, if not, to r8 // To here, 2nd argument is set
30
call_Us intel x64, Visual Studio version(5)
The operation for 1st argument is very similar to that of 2nd argument, so it's reasonable to define macro like bellow: #define SET_REGISTER_MACRO(NNN, GPR1, GPR2, XMMN) ... uks* p = begin; SET_REGISTER_MACRO(0, rcx, rdx, xmm0); // 1st argument ++p; SET_REGISTER_MACRO(1, rdx, r8, xmm1); // 2nd argument SET_REGISTER_MACRO(2, r8, r9, xmm2); // 3rd argument
31
call_Us intel x64, Visual Studio version(6)
++p; // p points to 4th argument int narg = p - begin; // narg expresses which argument is handled. if (!r->vp) { // 4th argument is passed via xmm3 or r9 register like 1st, 2nd and 3rd argument ++p; // p points to 5th argument ++narg; }
32
call_Us intel x64, Visual Studio version(7)
for ( int offset = 32 ; p != end ; ++p, ++narg, offset += 8 ) { // Set the value to rsp + offset location. The value depens on p->m_kind. If nth <= narg, float is convert to double, integer is promoted. } LAST: pf(); // function call via pointer. i.e. for intel x64 // set the value of `pf' to rax // call rax
33
call_Us intel x64, Visual Studio version(8)
// If return type of `pf' is aggregate, nothing to be done. // If return type of `pf' is scalar, the value is in rax or xmm0 if (rk == kind_t::NONE) return; if (rk <= kind_t::FLOAT) { // Return type is real. Set xmm0 to r->ld, r->d or r->f. } else if (rk <= kind_t::VP) { // Return type is integer or pointer type. Set rax to r->u64.
34
call_Us intel x64, g++ version(1)
Difference from Visual Studio version If a parameter's type is `long double', pass the address of copy like structure or union. If `pf' is a function type whose return type is `long double', set the address for return value area to rcx register like structure or union. gcc inline assembler is effective.
35
call_Us intel x64, g++ version(2)
// void call_Us(U* r, void* pf, uks* begin, uks* end, int nth, kind_t rk) move rsp for `pf' arguments area if (rk == kind_t::REC || rk == kind_t:: LD){ // Function return type is aggregate or long double. // If it's aggegate type, set r->vp to rcx register. // If it's long double type, set &r->ld to rcx register. }
36
call_Us intel x64, g++ version(3)
uks* p = begin; if (p == end) goto LAST; if (p->m_kind <= kind_t::FLOAT) { // 1st argumet is real type if ( p->m_kind == kind_t::LD ) { long double tmp = p->m_u.ld; // Especially, `tmp' should be allocated for each argument If rk == kind_t::REC || rk == kind_t::LD , set &tmp to rcx, if not, set to rdx } else { // If nth is equal to 0, float is convert to double. If rk == kind_t::REC || rk == kind_t::LD, set the value to rcx, if not, to rdx // If nth is not equal to 0, set the value to xmm0 else ... // To here, 1st argument is set
37
call_Us intel x64, g++ version(4)
... LAST: pf(); // call via pointer // If rk == kind_t::REC || rk == kind_t::LD, nothing to be done. if (rk == kind_t::NONE) return; if (rk <= kind_t::FLOAT) { // Return type is real. Set xmm0 to r->d or r->f suitably. Especially, don't modify r->ld. } else if (rk <= kind_t::VP) { // Return type is integer or pointer. set rax to r->u64.
38
call_Us intel x86, g++ version (1)
esp Structure argument is passed by copying here Area of arguments for calling `pf' At runtime of call_Us int n = accumulate(begin, end,0,add_size); int add_size(int n, uks* p){ return n + p->m_size; } move esp n bytes Local variables and medium variables of call_Us ebp
39
call_Us intel x86, g++ version (2)
Difference from x64, g++ version All arugments are passed by copying to stack. Return type of `pf' integer or pointer type the result is in eax register real type the result is in top of floating point stack (st0) structure or union type Pass the address as 1st argument. Same with x64, g++ version except for not using rcx register.
40
call_Us intel x86, g++ 版(3) call_Us:
// void call_Us(U* r, void* pf, uks* begin, uks* end, int nth, kind_t rk) move esp int offset = 0; if (rk == kind_t::REC){ // Return type of pf is structure or union // set r->vp to offset 0 from esp offset += sizeof(void*); }
41
call_Us intel x86, g++ 版(4) for ( uks* p = begin ; p != end ; ++p ){ }
According to p->m_kind, reference the member of p->m_u, and set the value to offset `offset' from esp. int size = p->m_size; offset += (size <= sizeof(int)) ? sizeof(int) : size; } switch (rk) { case NONE: case REC: ((void (*)())pf)(); return; case LD: r->ld = ((long double (*)())pf)(); return; ...
42
return y, return extern vector<vector<tac*>::const_iterator> return_address; pc_t return_(pc_t pc) { tac* ptr = *pc; pc = return_address.back(); tac* call = *(pc - 1); // caller if ( ptr->y ){ const type* T = ptr->y->m_type; int size = T->size(); if ( call->x ) memcpy(address[call->x], address[ptr->y],size); } return pc; // Actually, return value is ignore, but just return return address.
43
goto, to pc_t goto(pc_t pc) { } pc_t to(pc_t pc){ return pc + 1; }
tac* ptr = *pc; if ( ptr->m_op ) return ifgoto(pc); const vector<tac*>& code = current_code(); // Get 3 address code of current simulated function pc_t p = find(code.begin(), code.end(), ptr->m_to); assert( p != code.end() ); pc_t = p; } pc_t to(pc_t pc){ return pc + 1; }
44
ifgoto (1) pc_t ifgoto(pc_t pc) { tac* ptr = *pc;
const type* T = ptr->y->m_type; if ( T->real() ){ switch(T->size()){ case sizeof(float): bool (*pred)(float, float) = table[ptr->m_op]; if ( pred(*(float*)getaddr(ptr->y), *(float*)getaddr(ptr->z)){ const vector<tac*>& code = current_code(); return find(code.begin(),code.end(),ptr->m_to); } return pc + 1; ... else {
45
ifgoto (2) Really do compare operation of type T. T is:
long double, double, float, unsigned long long int, long long int, unsigned long int, long int, unsigned int, int, and pointer As far as == and != , not necessary to make difference from signed and unsigned. But on the other hand, 1 byte or 2 byte compare operation is defined (assume that sizeof(int) == 4).
46
alloca x, y pc_t alloc(pc_t pc) { tac* ptr = *pc;
const type* T = ptr->y->m_type; uint64_t size; if (T->size() == 4) size = *(uint32_t*)getaddr(ptr->y); else { assert(T->size()==8); size = *(uint64_t*)getaddr(ptr->y); } address_table_t& curr = tacsim::stack.top(); curr[ptr->x] = new char[size]; return pc + 1;
47
asm "string" Cannot implement bease cannnot evaluate.
48
Function parameters At the entrance of function, copy contents of global variable extern vector<pair<void*,const type*> > parameters; which is used at `param y', to the continuous area: int n = accumulate(params.begin(),params.end(),0,add_size); char* p = n ? new char[n+alpha] : 0; // Continuous area. For 1st argument alignment, allocate +apha accumulate(parameters.begin(),parameters.end(),p,save_param); Do memcpy(p, params[i].first, params[i].second->size()), and go ahead `p' properly, and do again memcpy(p, parameters[i+1].first, parameters[i+1].second->size()). for_each(parameters.begin(), parameters.end(), destroy<void*, const type*>()); parameters.clear (); param_scope* ps = (function parameter scope); const vector<usr*>& order = ps->m_order; accumulate(order.begin(), order.end(), p, decide_address);
49
x := va_start y pc_t vastart(pc_t pc) { } tac* ptr = *pc;
char* p = (char*)getaddr(ptr->y); const type* T = ptr->y->m_type; int size = T->size(); *(char**)getaddr(ptr->x) = p + size; return pc + 1; }
50
x := va_arg y, T tac* ptr = *pc;
pc_t vaarg(pc_t pc) { tac* ptr = *pc; char* src = *(char**)getaddr(ptr->y); va_arg3ac* varg = static_cast<va_arg3ac*>(ptr); const type* T = varg->m_type; src = align(src, T->align()); int size = T->size(); memcpy(getaddr(ptr->x), src, size); *(char**)getaddr(ptr->y) = src + size; return pc + 1; }
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.