jkenda / aback

A stack-oriented language that uses Polish notation which can be reversed using the ; operator (previously |>)
0 stars 0 forks source link

procedures #3

Open jkenda opened 7 months ago

jkenda commented 7 months ago

additional instructions:

CALL addr (PUSH ip + JMP addr)
RET (jump to the addr at the top of the stack
PUT_BP
TAKE_BP

Have a secondary stack just for storing the return addresses and local variables (taken/peeked values). That way you never have to copy the return values as they're already on the primary stack where they should be.

The secondary stack should start at (initial rsp) / 2. Have rbp point at its base so you can check if they ever overlap.

Add a main proc.

jkenda commented 5 months ago

Here's a conversation I've had about it with ChatGPT:

User

i am developing a programming language that needs two stacks. how would i achieve that in x86 that natively only has 1 stack?

ChatGPT

In x86 assembly language, you can simulate two stacks using a single stack by alternating between the two "logical" stacks within the same physical stack. You would need to manage the stack pointers for each logical stack and ensure proper alignment.

For example, you can use one half of the stack for stack 1 and the other half for stack 2. Keep track of separate stack pointers for each stack and manage the push and pop operations accordingly.

Here's a simplified example in assembly pseudocode:

section .data
    stack_size equ 100  ; Adjust the size as needed

section .bss
    stack resb stack_size

section .text
    global _start

_start:
    ; Initialize stack pointers for both stacks
    mov eax, stack + stack_size  ; Stack pointer for stack 1
    mov ebx, stack              ; Stack pointer for stack 2

    ; Example: Push to stack 1
    mov ecx, 42
    mov [eax], ecx
    sub eax, 4  ; Adjust the stack pointer for stack 1

    ; Example: Pop from stack 2
    add ebx, 4  ; Adjust the stack pointer for stack 2
    mov edx, [ebx]

    ; Continue managing the stacks as needed

This is a basic illustration, and you would need to handle stack overflow and underflow scenarios, manage the stack pointers carefully, and ensure proper separation of the two logical stacks. Keep in mind that this approach might not be the most efficient, so consider the specific requirements of your programming language.

User

you can push and pop to the primary stack with the push and pop instructions, that doesn't need to change. would it be possible for the secondary stack to not be part of the executable, just like the primary isn't?

ChatGPT

Yes, it is possible to have the secondary stack not be a part of the executable itself. Instead of allocating space within the executable, you can dynamically allocate memory for the secondary stack during runtime.

Here's a simplified example in assembly pseudocode:

section .data
    stack_size equ 100  ; Adjust the size as needed

section .bss
    primary_stack resb stack_size

section .text
    global _start

_start:
    ; Initialize the primary stack pointer
    mov esp, primary_stack + stack_size

    ; Dynamically allocate memory for the secondary stack
    mov eax, 4      ; System call number for memory allocation (adjust as needed)
    mov ebx, 0      ; Allocate from the default heap
    mov ecx, stack_size  ; Size of the secondary stack
    mov edx, 7      ; Read, write, and execute permissions (adjust as needed)
    int 0x80        ; Interrupt to invoke the system call

    ; Check for errors in eax and handle accordingly

    ; Set the secondary stack pointer
    mov ebx, eax

    ; Now you can use the primary and secondary stacks as needed
    ; Example: Push to the primary stack
    push 42

    ; Example: Pop from the secondary stack
    mov ecx, [ebx]

Keep in mind that the exact system call and method for dynamic memory allocation may vary depending on the operating system. The example above uses a generic system call (int 0x80), and you would need to replace it with the appropriate mechanism for your target environment. Additionally, error handling and proper stack management are crucial aspects to consider.

User

so basically what you're telling me that the secondary stack has to be part of the heap and it's initialized at the start of the program by requesting memory from the OS with a call to mmap

ChatGPT

Yes, you've understood it correctly. In this approach, the secondary stack is dynamically allocated at runtime from the heap using a system call like mmap (on Unix-like systems) or a similar mechanism depending on the operating system. This allows you to request a block of memory specifically for the secondary stack.

The key steps are:

Requesting Memory: Use a system call to request memory from the operating system, typically using functions like mmap or equivalents.

Setting Stack Pointer: Set the stack pointer (esp or rsp in x86 assembly) to the allocated memory for the secondary stack.

Stack Operations: Perform push and pop operations on both the primary and secondary stacks as needed.

Freeing Memory: Don't forget to release the memory used for the secondary stack when it's no longer needed, typically using munmap or an equivalent mechanism.

Keep in mind that the specific system calls and methods may vary based on the operating system you're targeting. Always handle errors appropriately, and ensure proper stack management throughout the program's execution.

User

i would store a pointer to the secondary stack in a different register, not rsp. otherwise this was a good consult, thank you

ChatGPT

You're welcome! Storing a pointer to the secondary stack in a different register is a sensible approach, allowing you to manage both stacks independently. If you have any more questions or need further assistance, feel free to ask. Happy coding!

User

now, how exactly do i call mmap (i've called read, write open etc. from assembly but those are simpler)? what do i get back?

ChatGPT

Calling mmap involves setting up the appropriate registers with the required parameters and invoking the syscall. In x86 assembly, you typically use eax for the syscall number and other registers for the syscall arguments.

Here's a simplified example for calling mmap:

section .data
    prot_readwrite equ 0x3  ; Read and write permissions
    map_shared equ 0x01     ; Share the mapping with other processes

section .text
    global _start

_start:
    ; Parameters for mmap syscall
    mov eax, 90             ; syscall number for mmap (use 9 on 32-bit systems)
    mov ebx, 0              ; Address hint (usually set to 0)
    mov ecx, stack_size     ; Size of the memory region
    mov edx, prot_readwrite ; Protection flags (read and write)
    mov esi, map_shared     ; Flags (share the mapping with other processes)
    mov edi, -1             ; File descriptor (use -1 for anonymous mapping)
    mov ebp, 0              ; Offset within the file (not applicable for anonymous mapping)

    ; Invoke syscall
    int 0x80  ; Interrupt to invoke the syscall on 32-bit systems

    ; Check eax for errors (eax will contain the return value)
    cmp eax, 0
    jl handle_error

    ; Memory mapping successful, eax contains the pointer to the allocated memory

    ; Continue with your program logic, using the pointer in eax for the secondary stack

    ; To unmap the memory when you're done, you would use the munmap syscall
    ; (similar setup with different syscall number and parameters)

    ; Clean up and exit
    mov eax, 1   ; syscall number for exit
    xor ebx, ebx ; Exit code 0
    int 0x80     ; Invoke syscall

handle_error:
    ; Handle errors appropriately
    ; The specific error code can be determined from the value in eax
    ; You might want to print an error message or take corrective action

In this example, eax is used for the syscall number, and other registers (ebx, ecx, edx, esi, edi, ebp) are used for the syscall arguments. The result of the mmap syscall is stored in eax, and you should check it for errors. If eax is negative, an error occurred.

Please note that the exact values for parameters may vary depending on your specific use case and operating system. This example is for 32-bit x86 systems; for 64-bit systems, the syscall number is typically different, and registers such as rax are used instead of eax. Always refer to the system call documentation for your specific platform.

jkenda commented 2 months ago

in the end i think it's best to first integrate QBE as a backend and then do it