getArgsForFunc lazily initializes the buffer the first time it is called for a given program (thus, it also needs "program" to run). It will take "needed" elements from the buffer and populate them with vm.pop() in reverse order. Because the estimation can fall short, this function can occasionally
(argsBuf []any, program *Program, needed int)
| 723 | // reverse order. Because the estimation can fall short, this function can |
| 724 | // occasionally make a new allocation. |
| 725 | func (vm *VM) getArgsForFunc(argsBuf []any, program *Program, needed int) (args []any, argsBufOut []any) { |
| 726 | if needed == 0 || program == nil { |
| 727 | return nil, argsBuf |
| 728 | } |
| 729 | |
| 730 | // Step 1: fix estimations and preallocate |
| 731 | if argsBuf == nil { |
| 732 | estimatedFnArgsCount := estimateFnArgsCount(program) |
| 733 | if estimatedFnArgsCount > maxFnArgsBuf { |
| 734 | // put a practical limit to avoid excessive preallocation |
| 735 | estimatedFnArgsCount = maxFnArgsBuf |
| 736 | } |
| 737 | if estimatedFnArgsCount < needed { |
| 738 | // in the case that the first call is for example OpCallN with a large |
| 739 | // number of arguments, then make sure we will be able to serve them at |
| 740 | // least. |
| 741 | estimatedFnArgsCount = needed |
| 742 | } |
| 743 | |
| 744 | // in the case that we are preparing the arguments for the first |
| 745 | // function call of the program, then argsBuf will be nil, so we |
| 746 | // initialize it. We delay this initial allocation here because a |
| 747 | // program could have many function calls but exit earlier than the |
| 748 | // first call, so in that case we avoid allocating unnecessarily |
| 749 | argsBuf = make([]any, estimatedFnArgsCount) |
| 750 | } |
| 751 | |
| 752 | // Step 2: get the final slice that will be returned |
| 753 | var buf []any |
| 754 | if len(argsBuf) >= needed { |
| 755 | // in this case, we are successfully using the single preallocation. We |
| 756 | // use the full slice expression [low : high : max] because in that way |
| 757 | // a function that receives this slice as variadic arguments will not be |
| 758 | // able to make modifications to contiguous elements with append(). If |
| 759 | // they call append on their variadic arguments they will make a new |
| 760 | // allocation. |
| 761 | buf = (argsBuf)[:needed:needed] |
| 762 | argsBuf = (argsBuf)[needed:] // advance the buffer |
| 763 | } else { |
| 764 | // if we have been making calls to something like OpCallN with many more |
| 765 | // arguments than what we estimated, then we will need to allocate |
| 766 | // separately |
| 767 | buf = make([]any, needed) |
| 768 | } |
| 769 | |
| 770 | // Step 3: populate the final slice bulk copying from the stack. This is the |
| 771 | // exact order and copy() is a highly optimized operation |
| 772 | copy(buf, vm.Stack[len(vm.Stack)-needed:]) |
| 773 | vm.Stack = vm.Stack[:len(vm.Stack)-needed] |
| 774 | |
| 775 | return buf, argsBuf |
| 776 | } |
| 777 | |
| 778 | func (vm *VM) Step() { |
| 779 | vm.step <- struct{}{} |
no test coverage detected