Hello Juan, please remember to CC the list when discussing technical problems, ideas and approaches. Others may help you also, perhaps even better than I do.
On Thu, Mar 03 2022, Juan Scerri wrote: > Hi Martin, > > Firstly, thank you for your response. > > I think I have a good knowledge of C. It is part of my course at > university. > > Moreover, I have figured out that the code which I need to change is in > libiberty and more specifically cp-demangle.c and cp-demangle.h. Thank you > for > pointing out that I can view binutils. I will give the repository a look > since > compiling gcc takes a very long time, even with 16 threads. > > I have also read some of the code and I have an idea as to what I can do. > > In the code there are certain functions which use recursion which are very > trivial to convert to an iterative function for example: > > static int > has_return_type (struct demangle_component *dc) > { > if (dc == NULL) > return 0; > switch (dc->type) > { > default: > return 0; > case DEMANGLE_COMPONENT_LOCAL_NAME: > return has_return_type (d_right (dc)); > case DEMANGLE_COMPONENT_TEMPLATE: > return ! is_ctor_dtor_or_conversion (d_left (dc)); > FNQUAL_COMPONENT_CASE: > return has_return_type (d_left (dc)); > } > } > > This can be converted into an iterative function through the introduction > of a > while-loop or a for-loop (whichever one is preferred in the codebase) as > shown > below. > > static int > has_return_type (struct demangle_component *dc) > { > while (1) { > if (dc == NULL) > return 0; > switch (dc->type) > { > default: > return 0; > case DEMANGLE_COMPONENT_LOCAL_NAME: > dc = d_right (dc); > continue; > case DEMANGLE_COMPONENT_TEMPLATE: > return ! is_ctor_dtor_or_conversion (d_left (dc)); > FNQUAL_COMPONENT_CASE: > dc = d_left (dc); > continue; > } > } > } Right, those are the easy cases, the project of course is about the non-trivial ones. > > For the more complicated functions like `d_print_comp` and > `d_print_comp_inner`, there is very large number of cases. Moreover, > different > cases can have multiple recursive calls to `d_print_comp`. My idea to tackle > these is to keep track of the number of calls which would be made if the > algorithm > was recursive. Basically, the count starts at 1 and for: > >> a base case we subtract 1 >> a single call we add 0 >> two calls we add 1 >> three calls we add 2 >> and so on. > > However, this is not enough. We need to deal with the instructions in > between > calls and afterwards. The best structure to keep track of the work which > needs > to be done is a stack (as hinted in the website). This is the implementation > which I came up with (it is heavily inspired by `d_growable_string`): You are correct about the stack, you definitely need a stack to drive the whole process, I am not sure I understand the call counting above or what purpose it would serve. > > #define EMPTY 0 > > /* Stack of operations to do after traversing a full branch. */ > struct d_work_stack { > /* Base pointer. */ > int *bp; > /* Current length of data on the stack. */ > size_t len; > /* Allocated size of buffer. */ > size_t alc; > /* Set to 1 if we had a memory allocation failure. */ > int allocation_failure; > }; > > static void d_work_stack_init(struct d_work_stack *dws, size_t estimate) { > dws->bp = NULL; > dws->len = 0; > dws->alc = 0; > dws->allocation_failure = 0; > > if (estimate > 0) > d_work_stack_resize(dws, estimate); > } > > static inline void d_work_stack_resize(struct d_work_stack *dws, size_t > need) { > size_t newalc; > int *newbp; > > if (dws->allocation_failure) > return; > > /* Start allocation at two bytes to avoid any possibility of confusion > with the special value of 1 used as a retrun to indicate > allocation failures. */ > newalc = dws->alc > 0 ? dws->alc : 2; > while (newalc < need) > newalc <<= 1; > > newbp = (int *)realloc(dws->bp, newalc * sizeof(int)); > if (newbp == NULL) { > free(dws->bp); > dws->bp = NULL; > dws->len = 0; > dws->alc = 0; > dws->allocation_failure = 1; > return; > } > dws->bp = newbp; > dws->alc = newalc; > } > > static void d_work_stack_push(struct d_work_stack *dws, int dw) { > size_t need = dws->len + 1; > if (need > dws->alc) > d_work_stack_resize(dws, need); > > if (dws->allocation_failure) > return; > > dws->bp[(dws->len)++] = dw; > } > > static int d_work_stack_pop(struct d_work_stack *dws) { > if (dws->len == 0) > return EMPTY; > > return dws->bp[--(dws->len)]; > } > > static void d_work_stack_deinit(struct d_work_stack *dws) { > if (dws->bp != NULL) { > free(dws->bp); > dws->bp = NULL; > } > > dws->len = 0; > dws->alc = 0; > dws->allocation_failure = 0; > } Note that the stack will have to hold intermediate data for many kinds of mutually recursive functions in the demangler, so (either directly or conceptually) it will have to hold items - probably structures - of different types. I do not think integers will cut it. In the past when we were discussing the project, it was helpful to look at function d_parmlist. Making the indirect recursion explicit means replacing the recursive calls, such as the call to cplus_demangle_type, with a push on the stack of some structure encoding that a type has to be demangled and immediately returning. But that means that the rest of the work has to be done later (when this and more stuff pushed on the stack gets popped and the driving loop gets back to the same point in the stack) and there is a lot of state - stored in local variables now - which has to be carried over. That state needs to be in the stack (describing the current task) too, hence the need for a richer type. > > The only thing I need to iron out is the best way to use this structure. My > crude idea at the moment is to give code which needs to be executed, after > or in > between recursive calls, an index. Then using a switch the algorithm can > decide > what to do depending on the index (only if the count is still greater than 0 > and the end of a branch was reached). > > Note: that you should think of a `branch` as the branch of the tree formed > if > you visualise the recursive calls until completion. > > However, this might not be the best approach. I might consider using the > stack > to hold function pointers instead. I agree that function pointers are likely to be useful. But note that often you'll need to encode both something to identify the kind of stuff that is being demangled (type, parmlist, component...) and the state how far you already got (from what point you need to continue), so you may want to use both. Hope this is useful, good luck. Martin