Skip to content

WASM_X64: Support Data Segment #1585

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 24, 2023
Merged

Conversation

ubaidsk
Copy link
Collaborator

@ubaidsk ubaidsk commented Mar 16, 2023

towards #1539

@ubaidsk
Copy link
Collaborator Author

ubaidsk commented Mar 23, 2023

Example:

(lp) lpython$ cat integration_tests/test_complex_01.py 
from ltypes import i32, i64, f32, f64, c32, c64

def test_real_imag():
    x: c64
    x = c64(2) + 3j
    a: f64
    b: f64
    eps: f64
    eps = 1e-12
    a = x.real
    b = x.imag
    assert abs(a - 2.0) <= eps
    assert abs(b - 3.0) <= eps

    print(x)

def test_complex():
    x: c64
    x = complex(4.5, 6.7)
    eps: f64
    eps = 1e-12
    assert abs(x.real - 4.5) <= eps
    assert abs(x.imag - 6.7) <= eps

    x = complex(-4, 2)
    assert abs(x.real - (-4.0)) <= eps
    assert abs(x.imag - 2.0) <= eps

    x = complex(4, 7.89)
    assert abs(x.real - 4.0) <= eps
    assert abs(x.imag - 7.89) <= eps

    x = complex(5.6, 0)
    assert abs(x.real - 5.6) <= eps
    assert abs(x.imag - 0.0) <= eps

    a: f64
    a = 534.6
    x = complex(a, -a) # (f64, f64)

    assert abs(x.real - 534.60000000000002274) <= eps
    assert abs(x.imag - (-534.60000000000002274)) <= eps

    a2: f32
    a2 = -f32(423.5430806348152437)
    a3: f32
    a3 = f32(34.5)
    x2: c32
    x2 = c32(complex(a2, a3)) # (f32, f32)

    assert f64(abs(x2.imag - f32(34.5))) <= eps

    i1: i32
    i1 = -5
    i2: i64
    i2 = -i64(6)

    x = complex(a3, a) # (f32, f64)
    x = complex(a, a3) # (f64, f32)
    x = complex(i1, i2) # (i32, i64)
    x = complex(i1, -i1) # (i32, i32)
    x = complex(-i2, -i2) # (i64, i64)
    x = complex(i2, -i1) # (i64, i32)

    print(x)

def test_complex_unary_minus():
    c: c32
    c = c32(complex(3, 4.5))
    _c: c32
    _c = -c
    assert abs(f64(_c.real) - (-3.0)) <= 1e-12
    assert abs(f64(_c.imag) - (-4.5)) <= 1e-12
    _c = c32(complex(5, -78))
    _c = -_c
    assert abs(f64(_c.real) - (-5.0)) <= 1e-12
    assert abs(f64(_c.imag) - 78.0) <= 1e-12
    c2: c64
    c2 = complex(-4.5, -7.8)
    c2 = -c2
    assert abs(c2.real - 4.5) <= 1e-12
    assert abs(c2.imag - 7.8) <= 1e-12
    c2 = c64(3) + 4j
    c2 = -c2
    assert abs(c2.real - (-3.0)) <= 1e-12
    assert abs(c2.imag - (-4.0)) <= 1e-12

    print(c, _c, c2)

def test_complex_not():
    c: c32
    c = c32(complex(4, 5))
    b: bool
    b = not c
    assert not b

    c2: c64
    c2 = complex(0, 0)
    b = not c2
    assert b

    print(c,c2, b)

def check():
    test_real_imag()
    test_complex()
    test_complex_unary_minus()
    test_complex_not()

check()

ASM Binary Format:

(lp) lpython$ lpython integration_tests/test_complex_01.py --backend wasm_x64 -o tmp > main.asm
(lp) lpython$ ./tmp
(2.000000000,3.000000000)
(-6.000000000,5.000000000)
(3.000000000,4.50000000) (-5.000000000,78.000000000) (-3.000000000,-4.000000000)
(4.000000000,5.000000000) (0.000000000,0.000000000) 1
(lp) lpython$ readelf -h tmp
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x804e2ea
  Start of program headers:          64 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         3
  Size of section headers:           0 (bytes)
  Number of section headers:         0
  Section header string table index: 0
(lp) lpython$ readelf -l tmp

Elf file type is EXEC (Executable file)
Entry point 0x804e2ea
There are 3 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000008048000 0x0000000008048000
                 0x0000000000001000 0x0000000000001000  R      0x1000
  LOAD           0x0000000000001000 0x0000000008049000 0x0000000008049000
                 0x0000000000006425 0x0000000000006425  R E    0x1000
  LOAD           0x0000000000008000 0x0000000008050000 0x0000000008050000
                 0x0000000000000100 0x0000000000000100  RW     0x1000
(lp) lpython$ 

ASM Text Format:

(wasm_asm) lpython$ nasm -fbin main.asm && chmod +x main
(wasm_asm) lpython$ ./main
(2.000000000,3.000000000)
(-6.000000000,5.000000000)
(3.000000000,4.50000000) (-5.000000000,78.000000000) (-3.000000000,-4.000000000)
(4.000000000,5.000000000) (0.000000000,0.000000000) 1
(wasm_asm) lpython$ readelf -h main
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x804cba9
  Start of program headers:          64 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         3
  Size of section headers:           0 (bytes)
  Number of section headers:         0
  Section header string table index: 0
(wasm_asm) lpython$ readelf -l main

Elf file type is EXEC (Executable file)
Entry point 0x804cba9
There are 3 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000008048000 0x0000000008048000
                 0x0000000000001000 0x0000000000001000  R      0x1000
  LOAD           0x0000000000001000 0x0000000008049000 0x0000000008049000
                 0x00000000000047c9 0x00000000000047c9  R E    0x1000
  LOAD           0x00000000000063a4 0x000000000804e3a4 0x000000000804e3a4
                 0x0000000000000100 0x0000000000000100  RW     0x1000
(wasm_asm) lpython$ 

for (size_t i = 0; i < padding_size; i++) {
m_code.push_back(m_al, 0);
}
EMIT("times " + std::to_string(padding_size) + " db 0");
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 473: I think the padding_size computed here is valid only for the binary that we generate. For assembly text format, while encoding the text_segment, since nasm can encode instructions using different number of bytes than our encoding, the padding size required by nasm binary could be different from that of our binary.

I am looking into the possibilities of computing the padding size dynamically in assembly text format.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For NASM use the $ and $$ trick that we use to compute the size of the binary for the header.

@ubaidsk ubaidsk marked this pull request as ready for review March 24, 2023 06:42
@ubaidsk ubaidsk requested a review from certik March 24, 2023 06:42
@ubaidsk
Copy link
Collaborator Author

ubaidsk commented Mar 24, 2023

This is ready. Please review and share feedback.

Copy link
Contributor

@certik certik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks great!

Should we add some tests for this new feature?

@ubaidsk
Copy link
Collaborator Author

ubaidsk commented Mar 24, 2023

Should we add some tests for this new feature?

Sure, we can add tests. I think the test integration_tests/test_complex_01.py tests reading as well as writing data to our new data segment (It reads and writes to global variables which in assembly are located/allocated in the new data segment).

(lp) lpython$ lpython integration_tests/test_complex_01.py --backend wasm_x64 -o tmp > main.asm
(lp) lpython$ ./tmp
(2.000000000,3.000000000)
(-6.000000000,5.000000000)
(3.000000000,4.50000000) (-5.000000000,78.000000000) (-3.000000000,-4.000000000)
(4.000000000,5.000000000) (0.000000000,0.000000000) 1
(lp) lpython$ 
(wasm_asm) lpython$ nasm -fbin main.asm && chmod +x main
(wasm_asm) lpython$ ./main
(2.000000000,3.000000000)
(-6.000000000,5.000000000)
(3.000000000,4.50000000) (-5.000000000,78.000000000) (-3.000000000,-4.000000000)
(4.000000000,5.000000000) (0.000000000,0.000000000) 1
(wasm_asm) lpython$ 

(In the above example, I added some print statements to the test integration_tests/test_complex_01.py so that we have some output and can verify that the assembly binary and text generated actually works.)

PS: The integration_tests/test_complex_01.py is already enabled for wasm_x64 backend.

@ubaidsk
Copy link
Collaborator Author

ubaidsk commented Mar 24, 2023

As this PR seems approved, I am merging this. I will work on further changes/suggestions (if any) in another PR.

@ubaidsk ubaidsk merged commit 1dc74c6 into lcompilers:main Mar 24, 2023
@ubaidsk ubaidsk deleted the x64_data_section branch March 24, 2023 18:29
@certik
Copy link
Contributor

certik commented Mar 24, 2023

I see, perfect!

I don't see any global variables in https://github.com/lcompilers/lpython/blob/1dc74c6200cac7c983f3ae15d3dce25f37c55f3f/integration_tests/test_complex_01.py, which line is a global variable?

What capability does this new RW data segment allows us to do? One is that the code segment becomes read-only, which is good. The other might be that now we should be able to resize it, thus allow arbitrary length heap allocation. Anything else?

@ubaidsk
Copy link
Collaborator Author

ubaidsk commented Mar 24, 2023

enum GLOBAL_VAR {
cur_mem_loc = 0,
tmp_reg_f32 = 1,
tmp_reg_f64 = 2,
global_vars_cnt = 3
};

We currently use global variable for temporary operations like stack manipulation (for example: to swap the top and top-second stack elements). (The cur_mem_loc global variable is not used yet I think.)

For example, the global get/set operations are used here

if (f->m_kind == 4) {
this->visit_expr(*x.m_arg);
wasm::emit_f32_neg(m_code_section, m_al);
wasm::emit_set_global(m_code_section, m_al, m_global_var_name_idx_map[tmp_reg_f32]);
wasm::emit_f32_neg(m_code_section, m_al);
wasm::emit_get_global(m_code_section, m_al, m_global_var_name_idx_map[tmp_reg_f32]);
} else if (f->m_kind == 8) {
this->visit_expr(*x.m_arg);
wasm::emit_f64_neg(m_code_section, m_al);
wasm::emit_set_global(m_code_section, m_al, m_global_var_name_idx_map[tmp_reg_f64]);
wasm::emit_f64_neg(m_code_section, m_al);
wasm::emit_get_global(m_code_section, m_al, m_global_var_name_idx_map[tmp_reg_f64]);

and here

wasm::emit_set_global(m_code_section, m_al,
(a_kind == 4) ? m_global_var_name_idx_map[tmp_reg_f32]
: m_global_var_name_idx_map[tmp_reg_f64]);
wasm::emit_drop(m_code_section, m_al);
wasm::emit_get_global(m_code_section, m_al,
(a_kind == 4) ? m_global_var_name_idx_map[tmp_reg_f32]
: m_global_var_name_idx_map[tmp_reg_f64]);

The integration_tests/test_complex_01.py utilizes the above code lines. Thus, it tests getting and setting globals.

@ubaidsk
Copy link
Collaborator Author

ubaidsk commented Mar 24, 2023

The other might be that now we should be able to resize it, thus allow arbitrary length heap allocation

I think it can allow us compile-time memory allocation (For example: we can have a label and can emit 0 bytes for as much space we require. This way I think we have a zero initialised memory of the required size. But this could increase the file size.)

Using the above approach, I think we can now support arrays by initializing memory for them in the data segment. For dynamic/runtime memory allocation (which could be needed for allocatable arrays), I think we should try to see/use system calls for memory allocation.

@certik
Copy link
Contributor

certik commented Mar 24, 2023

I see, perfect, thanks for the explanation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants