NASM how to return an array of pointers?

I call a NASM dll from ctypes. In my NASM dll code, I create three arrays using malloc. Their pointers are assigned to final1_ptr, final2_ptr and final3_ptr. If I return any of the pointers individually, I get the correct results.

But I want to return an array of all three pointers. For that, I also have an array initialized in the .data section: Return_Pointer_Array: dq 0, 0, 0.

At the end I assign the pointers this way:

mov rdi,Return_Pointer_Array
mov rax,qword[final1_ptr]
mov qword [rdi],rax
mov rax,qword[final2_ptr]
mov qword [rdi+8],rax
mov rax,qword[final3_ptr]
mov qword [rdi+16],rax
mov rax,rdi
ret

However, what ctypes gets is an array of three extremely small decimals, not valid pointer addresses.

The actual program listing is very long, so I hope the code above will be enough to understand the problem.

Thanks very much for any help.

EDIT: if I return the Return_Pointer_Array without assigning any values, it returns double (floating point) values. The array is initialized as integer, so I don't understand why it defaults to floating point when it's initialized as dq 0, 0, 0.

EDIT #2: Per request, here is the Python code:

def Test_Data_Read():

#Note:  in production, X is a list of 50,000 random floats read from file; 
#For simplicity, I have reduced it to a short simple list:  

X = [11.0,1.0,2.0,7.0,4.0,4.0,4.0,6.0,7.0,6.0,11.0,4.0,10.0,7.0,8.0,4.0,9.0,4.0,5.0,3.0,4.0,1.0,2.0,5.0,3.0,5.0,11.0,10.0,11.0,9.0,3.0,12.0]

PyGram_Test_01_asm(X)

#__________

def PyGram_Test_01_asm(X):

Input_Length_Array = []
Input_Length_Array.append(len(X)*8)

CA_X = (ctypes.c_double * len(X))(*X)
length_array_out = (ctypes.c_double * len(Input_Length_Array))(*Input_Length_Array)

hDLL = ctypes.WinDLL("C:/NASM_Test_Projects/Nested_For_Loops/Nested_For_Loops.dll")
CallName = hDLL.Main_Entry_fn
CallName.argtypes = [ctypes.POINTER(ctypes.c_double), ctypes.POINTER(ctypes.c_double)]
CallName.restype = ctypes.POINTER(ctypes.c_double)

start_time = timeit.default_timer()

ret_ptr = CallName(CA_X,length_array_out)

a = ret_ptr[:3]

Here is the entire NASM listing (sometimes minimal and complete are at odds with each other)

; Header Section
[BITS 64]

[default rel]

export Main_Entry_fn

extern malloc, realloc, free

section .data align=16
out_array_pointer: dq 0
call_var_length: dq 0
c_square: dq 0
final1_ptr: dq 0
final1_ctr: dq 0
final2_ptr: dq 0
final2_ctr: dq 0
final3_ptr: dq 0
final3_ctr: dq 0
float_temp_var: dq 0.0
temp_math_var: dq 0
Bool_0: dq 0.0
Bool_1: dq 1.0
data_master_ptr: dq 0
initial_dynamic_length: dq 0
X_ptr: dq 0
X_ctr: dq 0
X: dq 0
n: dq 0
i: dq 0
a: dq 0.0
range_loop_start_a: dq 0
range_loop_end_a: dq 0
b: dq 0.0
range_loop_start_b: dq 0
range_loop_end_b: dq 0
c: dq 0
X_length: dq 0
Input_Length_Array: dq 0,
Return_Pointer_Array: dq 0, 0, 0

section .text

PyGram_Test_01_fn:
xor rcx,rcx
mov [X_ctr],rcx
label_401:
lea rdi,[rel X_ptr]
mov rbp,qword [rdi]
mov rcx,[X_ctr]
cmp rcx,[X_length]
jge exit_label_for_PyGram_Test_01_fn
movsd xmm0,[rbp+rcx]
movsd [n],xmm0
add rcx,8
mov [X_ctr],rcx
movsd xmm0,qword[n]
cvttsd2si rax,xmm0
mov [n],rax
mov rax,[n]
mov rdx,1
add rax,rdx
mov [range_loop_end_a],rax
mov rax,1
sub rax,1
mov [range_loop_start_a],rax
mov [a],rax
label_801:
mov rcx,[a]
inc rcx
cmp rcx,[range_loop_end_a]
jge label_401
mov [a],rcx
mov rax,[a]
sub rax,1
mov [range_loop_start_b],rax
mov [b],rax
mov rax,[n]
mov [range_loop_end_b],rax
label_1201:
mov rcx,[b]
inc rcx
cmp rcx,[range_loop_end_b]
jge label_801
mov [b],rcx
mov rax,[a]
mov r8,[a]
xor rcx,rcx
add rcx,1
Exponent_Label_0:
mul r8
inc rcx
mov rdx,2
cmp rcx,rdx
jl Exponent_Label_0
mov[temp_math_var],rax
mov rax,[b]
mov r8,[b]
xor rcx,rcx
add rcx,1
Exponent_Label_1:
mul r8
inc rcx
mov rdx,2
cmp rcx,rdx
jl Exponent_Label_1
mov rdx,rax
mov rax,[temp_math_var]
add rax,rdx
mov [c_square],rax
mov rax,[c_square]
mov rax,[c_square]
cvtsi2sd xmm1,rax
sqrtsd xmm0,xmm1
cvttsd2si rax,xmm0
mov [c],rax
label_1601:
mov rax,[c]
mov rax,[c]
mov r8,[c]
xor rcx,rcx
add rcx,1
Exponent_Label_2:
mul r8
inc rcx
mov rdx,2
cmp rcx,rdx
jl Exponent_Label_2
mov [temp_math_var],rax
mov rax,[c_square]
mov rdx,[temp_math_var]
sub rax,rdx
mov rdx,0
cmp rax,rdx
jne label_1201
mov rdi,[final1_ptr]
mov rcx,[final1_ctr]
mov rax,[a]
cvtsi2sd xmm0,rax
movsd [rdi + rcx],xmm0
add rcx,8
mov [final1_ctr],rcx
mov rdi,[final2_ptr]
mov rcx,[final2_ctr]
mov rax,[b]
cvtsi2sd xmm0,rax
movsd [rdi + rcx],xmm0
add rcx,8
mov [final2_ctr],rcx
mov rdi,[final3_ptr]
mov rcx,[final3_ctr]
mov rax,[c]
cvtsi2sd xmm0,rax
movsd [rdi + rcx],xmm0
add rcx,8
mov [final3_ctr],rcx
jmp label_1201
label_900:
exit_label_for_PyGram_Test_01_fn:
mov rdi,Return_Pointer_Array
mov rax,qword[final1_ptr]
mov qword [rdi],rax
mov rax,qword[final2_ptr]
mov qword [rdi+8],rax
mov rax,qword[final3_ptr]
mov qword [rdi+16],rax
mov rax,rdi
ret

; __________
; Main Entry

Main_Entry_fn:
push rdi
push rbp
mov [X_ptr],rcx
mov [data_master_ptr],rdx
; Now assign lengths
lea rdi,[data_master_ptr]
mov rbp,[rdi]
xor rcx,rcx
movsd xmm0,qword[rbp+rcx]
cvttsd2si rax,xmm0
mov [X_length],rax
add rcx,8
; __________
; malloc for dynamic arrays
lea rdi,[data_master_ptr]
mov rbp,[rdi]
movsd xmm0,qword[rbp]
cvttsd2si rax,xmm0
mov [initial_dynamic_length],rax
mov rax,3529984
mov [initial_dynamic_length],rax
mov rcx,qword[initial_dynamic_length] ; Initial size
xor rax,rax
sub rsp,40
call malloc
mov qword [final1_ptr],rax
add rsp,40
mov rcx,qword[initial_dynamic_length] ; Initial size
xor rax,rax
sub rsp,40
call malloc
mov qword [final2_ptr],rax
add rsp,40
mov rcx,qword[initial_dynamic_length] ; Initial size
xor rax,rax
sub rsp,40
call malloc
mov qword [final3_ptr],rax
add rsp,40
; __________
call PyGram_Test_01_fn
exit_label_for_Main_Entry_fn:
pop rbp
pop rdi
ret

1 answer

  • answered 2018-03-14 17:58 RTC222

    I am posting this as an answer because others may need this information. With help from Michael Petch (above) I have resolved this.

    To return an array of pointers from a dll to ctypes:

    1. Declare an array in the dll (for NASM, it's declared in the .data section, e.g. Return_Pointer_Array: dq 0, 0, 0).

    2. On exit, assign the pointers to the array (for assembler; in C, use C assignments):

      mov rdi,Return_Pointer_Array
      mov rax,qword[final1_ptr]
      mov [rdi],rax
      mov rax,qword[final2_ptr]
      mov [rdi+8],rax
      mov rax,qword[final3_ptr]
      mov [rdi+16],rax
      mov rax,rdi
      
    3. Set the return type to CallName.restype = ctypes.POINTER(ctypes.c_int64).

    4. Cast each of the returned pointers and convert the data to a Python array (in this example, I know in advance how many elements there are in each array; for a dynamic array, the length can be passed back as an element of the return array):

      ret_ptr = CallName(CA_X,length_array_out)
      a = ret_ptr[:3]
      n1 = ctypes.cast(a[0], ctypes.POINTER(ctypes.c_double))
      x1 = n1[:50000]
      n2 = ctypes.cast(a[1], ctypes.POINTER(ctypes.c_double))
      x2 = n2[:50000]
      n3 = ctypes.cast(a[2], ctypes.POINTER(ctypes.c_double))
      x3 = n3[:50000]
      

    Alternatively, set the return type to CallName.restype = ctypes.POINTER(ctypes.POINTER(ctypes.c_double)), as Michael Petch said above, and extract the values like this:

        ret_ptr = CallName(CA_X,length_array_out)
        a = ret_ptr[:3]
        n1 = a[0]
        x1 = n1[:50000]
        etc