docs: def setup_callsite(self, state, ret_addr, args, stack_base=None, alloc_base=None, grow_like_stack=True):
          """
          This function performs the actions of the caller getting ready to jump into a function.
  
          :param state:           The SimState to operate on
          :param ret_addr:        The address to return to when the called function finishes
          :param args:            The list of arguments that that the called function will see
          :param stack_base:      An optional pointer to use as the top of the stack, circa the function entry point
          :param alloc_base:      An optional pointer to use as the place to put excess argument data
          :param grow_like_stack: When allocating data at alloc_base, whether to allocate at decreasing addresses
  
          The idea here is that you can provide almost any kind of python type in `args` and it'll be translated to a
          binary format to be placed into simulated memory. Lists (representing arrays) must be entirely elements of the
          same type and size, while tuples (representing structs) can be elements of any type and size.
          If you'd like there to be a pointer to a given value, wrap the value in a `PointerWrapper`. Any value
          that can't fit in a register will be automatically put in a PointerWrapper.
  
          If stack_base is not provided, the current stack pointer will be used, and it will be updated.
          If alloc_base is not provided, the stack base will be used and grow_like_stack will implicitly be True.
  
          grow_like_stack controls the behavior of allocating data at alloc_base. When data from args needs to be wrapped
          in a pointer, the pointer needs to point somewhere, so that data is dumped into memory at alloc_base. If you
          set alloc_base to point to somewhere other than the stack, set grow_like_stack to False so that sequential
          allocations happen at increasing addresses.
          """
  
          # STEP 0: clerical work
  
          if isinstance(self, SimCCSoot):
              SimEngineSoot.setup_callsite(state, args, ret_addr)
              return
  
          allocator = AllocHelper(self.arch.bits, self.arch.memory_endness == 'Iend_LE')
  
          #
          # STEP 1: convert all values into serialized form
          # this entails creating the vals list of simple values to store and also populating the allocator's
          # understanding of what aux data needs to be stored
          # This is also where we compute arg locations (arg_locs)
          #
  
          if self.func_ty is not None:
              vals = [self._standardize_value(arg, ty, state, allocator.dump) for arg, ty in zip(args, self.func_ty.args)]
          else:
              vals = [self._standardize_value(arg, None, state, allocator.dump) for arg in args]
  
          arg_session = self.arg_session
          arg_locs = [None]*len(args)
          for i, (arg, val) in enumerate(zip(args, vals)):
              if self.is_fp_value(arg) or \
                      (self.func_ty is not None and isinstance(self.func_ty.args[i], SimTypeFloat)):
                  arg_locs[i] = arg_session.next_arg(is_fp=True, size=val.length // state.arch.byte_width)
                  continue
              if val.length > state.arch.bits or (self.func_ty is None and isinstance(arg, (bytes, str, list, tuple))):
                  vals[i] = allocator.dump(val, state)
              elif val.length < state.arch.bits:
                  if self.arch.memory_endness == 'Iend_LE':
                      vals[i] = val.concat(claripy.BVV(0, state.arch.bits - val.length))
                  else:
                      vals[i] = claripy.BVV(0, state.arch.bits - val.length).concat(val)
              arg_locs[i] = arg_session.next_arg(is_fp=False, size=vals[i].length // state.arch.byte_width)
  
          #
          # STEP 2: decide on memory storage locations
          # implement the contract for stack_base/alloc_base/grow_like_stack
          # after this, stack_base should be the final stack pointer, alloc_base should be the final aux storage location,
          # and the stack pointer should be updated
          #
  
          if stack_base is None:
              if alloc_base is None:
                  alloc_size = allocator.size()
                  state.regs.sp -= alloc_size
                  alloc_base = state.regs.sp
                  grow_like_stack = False
  
              state.regs.sp -= self.stack_space(arg_locs)
  
              # handle alignment
              alignment = (state.regs.sp + self.STACKARG_SP_DIFF) % self.STACK_ALIGNMENT
              state.regs.sp -= alignment
  
          else:
              state.regs.sp = stack_base
  
              if alloc_base is None:
                  alloc_base = stack_base + self.stack_space(arg_locs)
                  grow_like_stack = False
  
          if grow_like_stack:
              alloc_base -= allocator.size()
          if type(alloc_base) is int:
              alloc_base = claripy.BVV(alloc_base, state.arch.bits)
  
          for i, val in enumerate(vals):
              vals[i] = allocator.translate(val, alloc_base)
  
          #
          # STEP 3: store everything!
          #
  
          allocator.apply(state, alloc_base)
  
          for loc, val in zip(arg_locs, vals):
              if val.length > loc.size * 8:
                  raise ValueError("Can't fit value {} into location {}".format(repr(val), repr(loc)))
              loc.set_value(state, val, endness='Iend_BE', stack_base=stack_base)
          self.return_addr.set_value(state, ret_addr, stack_base=stack_base)