docs: private long search_ml(int request_size)
      throws IOException
      {
          Block best_candidate = null;
          Block best_candidate_pred = null;
          Block block = ql_heads[last_ql_index].first_block;
          long block_addr;
          boolean found = false;
          Block prev_block = null;
          int ql_index;
          int ql_size;
          int rem;
          int rem2;
  
          large_requests++;
          while (block != null) {
              ml_blocks_searched++;
              if (block.size >= request_size) {
                  rem = block.size - request_size;
                  if (rem <= acceptable_waste) {
                      // allocate the block without splitting
                      if (prev_block != null) {
                          prev_block.next = block.next;
                      } else {
                          ql_heads[last_ql_index].first_block = block.next;
                      }
                      ql_heads[last_ql_index].length--;
                      if (ql_heads[last_ql_index].length == 0) {
                          nonempty_lists--;
                      }
  
                      allocated_blocks++;
                      free_blocks--;
                      ml_hits++;
                      allocated_words += block.size;
                      free_words -= block.size;
  
                      seek_and_count(block.address);
                      writeInt(- block.size);
  
                      return(block.address + HDR_SIZE);
                  } else {
                      if (best_candidate == null) {
                          best_candidate = block;
                          best_candidate_pred = prev_block;
                      } else {
                          if (best_candidate.size >= block.size) {
                              best_candidate = block;
                              best_candidate_pred = prev_block;
                          }
                      }
                  }
              }
              prev_block = block;
              block = block.next;
          }
          if (best_candidate == null) {
              // have to allocate from tail
              return allocate_from_tail(request_size);
          } else {
              // we have to split a block
              rem = best_candidate.size - request_size;
              block_addr = best_candidate.address;
              if (rem <= last_quick_size_block) {
                  rem2 = rem % grain_size;
                  rem -= rem2;
                  ql_index = calculate_list_index_for_dealloc(rem);
                  request_size += rem2;
                  if (best_candidate_pred != null) {
                      best_candidate_pred.next = best_candidate.next;
                  } else {
                      ql_heads[last_ql_index].first_block = best_candidate.next;
                  }
                  add_block_to_freelist(best_candidate, ql_index);
              }
              best_candidate.size = rem;
              best_candidate.address += request_size;
              return split_block(block_addr, request_size, rem);
          }
  
      }