Wamcc: Compile Prolog into C (No. 7-3)

Source: Internet
Author: User

Iii. Existing logic programming translators

This section describes how to process control flow in Janus, kl1, Erlang, and wamcc. This presentation is inspired by [5] and uses the goal of a stacked model. However, we do not follow abstraction similar to actual execution. The consequence of this choice clearly describes the correlation between C code and WAM commands. Due to space limitations, we will only discuss control issues here. The first reason is that the WAM used by wamcc is traditional but not optimized. As a result, the code written for other commands is now known as [1]. Second, the key to effective control is to translate the code into C, because the WAM code is flat and performs the conversion through the branch. This is a more suitable high-level control structure, such as functions, and does not provide more for low-level control. Therefore, the main problem is to find a suitable solution for the translation of the WAM branch. In our demonstration, there is only one clause and one fact in the following example:

p:  allocate   /* p:- q, r.   */     call(q)     deallocate     execute(r)q:  proceed    /* q.  */

However, this simple example shows the commands used by Prolog to control certainty. The call and execution of translation methods will be particularly prominent. How to manage direct branches (that is, when the target address is a known tag ), command Translation solves the problem of indirect branch (that is, when the target address is the content of some variables, register CP in this case ).
But there is a problem, because the indirect branch is not a standard (ANSI) C (so it must be simulated), and because the Goto command can only process code in the same function. This solution leads to a unique feature of a C program and a switch command to simulate indirect goto statements. After this method, the previous example will be converted:

fct_switch(){    label_switch:        switch(PC){            case p:                     /* p:- q,r  .  */            label_p:                push(CP);               /* allocate */                CP=p1;                  /* call(q) */                goto label_q;           /*   "   */            case p1:                pop(CP);                /* deallocate */                goto label_r;           /* execute(r) */            case q:                     /* q. */            label_q:                  PC=CP;                /* proceed */                  goto label_switch;    /*   :   */            .            .            .      }}

This method costs a lot on the server of the RISC because the switch statement costs about 10 machine commands (including boundary checks ). However, the main drawback of this method is that a program is upgraded to a single function. Therefore, apart from the example of toys, it will generate a huge function, and the C compiler cannot handle it within a reasonable period of time. If this is set, it is not easy to implement modularization. It requires each predicate to call a consulting dynamic table to control the switch function through this module. In addition, in order to support a complete Prolog, we also focus on correct backtracking when context changes. Therefore, modularity is a penalty, and an additional module call will be much more costly than a module call.

3.1 Janus

The implementation of Janus is based on a simple idea. It translates a C branch into a wam branch, that is, a goto command. Similar methods, as described in [11], are used in the Prolog Compiler. But there is a problem, because the introduction branch is not available in Standard C (ansi c) (so it must be simulated), and because the Goto command can only process the same code function. This solution leads to a unique feature of a C program and a switch command to simulate indirect goto statements. After this method, the previous example will be converted:

fct_switch(){  label_switch:      switch(PC){          case p:                 /* p:- q,r   */          label_p:              push(CP);           /* allocate */              CP=p1;              /* call(q) */              goto label_q;       /*   :  */          case p1:              pop(CP);            /* deallocate */              goto label_r;       /* execute(r) */          case q:                 /* q.  */          label_q:                PC=CP;              /* proceed */              goto label_switch;  /*   :  */          .          .          .      }}

This method costs a lot on the server of the server defined as the switch statement costs about 10 machine commands (including boundary checks ). However, the main drawback is that this method is a program that is upgraded to a single function. Therefore, apart from the example of toys, it will generate a huge function, and the C compiler cannot handle it at a reasonable time. In this setting, it is not easy to cope with modularization. It requires each predicate to call a consulting dynamic table to control the switch function through this module. In addition, in order to support a complete PROLOG and take care of environment changes, rollback is handled correctly. Therefore, modularity is punitive, and an additional module call is much more expensive than a module call.

3.2 kl1

It is unrealistic to compile a C function, which means that the WAM code of the C program is sliced into several functions. Each Prolog predicate looks so natural to translate into a C function. Wam branch will give rise to function call. Such a function calls another nested function (Branch) before returning, and so on; in this way, it will never return the previous ending program. Therefore, the data accumulated in the C Control Stack is useless, which can cause memory overflow. The solution is to get the result from any function before executing a branch, and have a process supervisor to make the branch continue. This leads to the following code example:

fct_supervisor(){    while(PC)        (*PC)();}void fct_p()        /* p:-  q,r,  */{    push(CP);       /* allocate */    CP=fct_p1;      /* call(q) */    PC=fct_q;       /*   :   */}void fct_p1{    pop(CP);        /* deallocate */    PC=fct_r;       /* execute(r) */}void fct_q()        /* q.  */{    PC=CP;          /* proceed */}

The above code can suppress PC register optimization and return its information. Therefore, when the transfer control and branch are required, each function implements row computing and terminal address return. This method shows that a WAM branch is implemented by a return to the supervisor after a function is called. This cost is obviously higher than the simple jump command and will generate a local code compiler. However, there may be no additional fees for calling additional modules. The first implementation of wamcc used this technology and was about twice slower than the simulation of sicstus. Kl1 balances the call and return of functions: the predicates of the same module are translated into a single function. Therefore, when there is only one module, kl1 acts like Janus. The supervision function only needs to call context switching for the additional module, so the cost exceeds the internal module call.

Finally, let's state that this approach (whether or not to improve kl1 recommendations) is the best solution for ansi c 100%.

3.3 Erlang

Erlang is also translated into a C function predicate. However, to avoid the overhead of function calls and return, Erlang's advantage is the new possibility provided by the gnu c compiler (GCC. In fact, GCC considers the (jump command) label as the first class object, so that it can store the label in a pointer variable and point out such a variable in the indirect jump of the subsequent execution value. Therefore, our idea is to jump the WAM branch of the translation to the introduction to call the internal C function to avoid extra costs. Then all the addresses of a global table need to be stored. The first call of each function must be initialized. Back to our unavoidable example, this will produce:

void fct_p()                      /* p:- q,r.  */{    jmp_tbl[p]=&&label_p;         /* (initialization) */    jmp-tbl[p1]=&&labe_p1;    return; label_p:    push(CP);                     /* allocate */    CP=&&label_p1;                /* call(q) */    goto *jum-tbl[q];             /*   :   */  label_p1:    pop(CP);                      /* deallocate */    goto *jmp-tbl[r];             /* execute(r) */}void fct_q()                      /* q.  */{    jmp_tbl[q]=&&label_1;         /* (initialization) */    return; label_q:    goto *CP;                     /* proceed */}

All branches are indirectly goto through a global address table. To eliminate the overhead of indirect substitution for direct jump, Erlang is like kl1 or Janus. All predicates of a given module are compiled into a single function. Therefore, only the calling of additional modules requires consultation on the global address table, and the calling of this internal module is more expensive.
Observe that the branch is directly inside the function, avoiding the preference to make it unable to use local variables (no reserved space in the C stack), which means that only local variables are used. Note that any command must not move the previous tag, which is quite difficult to guarantee. Let's take into account the access to elements in the global table. This load is compiled into an address table, followed by the address used to access the table of the specified element. The compiler can optimize table access at will and place the address for loading the table at the beginning of the function. It assumes that it will always be executed. When jump in the function, this will cause a problem and will try to use uninitialized registers.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.