News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Functional programming in assembly

Started by LiaoMi, March 30, 2021, 07:14:45 PM

Previous topic - Next topic

LiaoMi

Hi,

Is Assembly Language considered a functional programming language?
https://www.quora.com/Is-Assembly-Language-considered-a-functional-programming-language

What do you think?

Ex.1
#include "stdafx.h"
#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
// Lambda functions (seit C++11)
// [ capture ] ( params ) -> ret { body }

/*
capture - spezifiziert, welche Symbole erfasst und für den Funktionskörper
sichtbar gemacht werden.

Eine Liste der Symbole kann wie folgt Übergeben werden:

[a,&b], a wird kopiert und b als Referenz erfasst.
[this] erfasst den this-Zeiger als Kopie.
[&] erfasst alle im Funktionskörper verwendeten Symbole als Referenzen.
[=] erfasst alle im Funktionskörper verwendeten Symbole als Kopien.
[] es wird nichts erfasst.

params - Die Liste der Parameter, wie in benannten Funktionen

ret - Der Rückgabetyp. Falls nicht vorhanden, wird er von den return-Statements der Funktion
abgeleitet (oder, falls jene nichts zurückgibt, void)
*/

std::string s = "Hello, Lambda Expressions in C++11!";

for_each(s.begin(), s.end(),
[](char c) { cout << c; }
);

return 0;
}


Ex.2
class Lambda
{
public:
    int compute(int &value){
        auto get = [&value]() -> int {
            return 11 * value;
        };
        return get();
    }
};

int main(){
    Lambda lambda;
    int value = 77;
    return lambda.compute(value);
}


main: # @main
  push rax
  mov dword ptr [rsp + 4], 77
  mov rdi, rsp
  lea rsi, [rsp + 4]
  call Lambda::compute(int&)
  pop rcx
  ret
Lambda::compute(int&): # @Lambda::compute(int&)
  push rax
  mov qword ptr [rsp], rsi
  mov rdi, rsp
  call Lambda::compute(int&)::{lambda()#1}::operator()() const
  pop rcx
  ret
Lambda::compute(int&)::{lambda()#1}::operator()() const: # @Lambda::compute(int&)::{lambda()#1}::operator()() const
  mov rax, qword ptr [rdi]
  mov eax, dword ptr [rax]
  lea ecx, [rax + 4*rax]
  lea eax, [rax + 2*rcx]
  ret

daydreamer

Probably some have old views of assembly as non macro assembler old dos
Specialised critical section coded in asm
, but with lots of helper macros,translated. H files, coinvoke macros for. COM calls,you can do anything
my none asm creations
https://masm32.com/board/index.php?topic=6937.msg74303#msg74303
I am an Invoker
"An Invoker is a mage who specializes in the manipulation of raw and elemental energies."
Like SIMD coding

Vortex

Hi,

I think it's better to keep separated the two worlds. The assemly language should preserve it's essence.

hutch--

Erol,  :thumbsup:

Having a quick read from the article, it sounds like a load of waffle, effectively playing musical chairs with different notions of computer languages. I well understand conventional classic languages, asm, basic, C, pascal as well as COBOL and FORTRAN for the old fellas but most of the rest end up on the trash heap of history and while there is a place for specialised languages for more dedicated tasks, as the tasks change, so does the specialised language.

Just for example, look at modern graphics, GPU processing tends to be brand specific, some stuff runs on Nvidia, other stuff runs on AMD Radeon and similar but as video hardware changes, so too will the libraries that it uses. I am much of the view, learn a classic language (or 3) and branch from their if you have some reason to use a specialised language.

As a humerous aside, my old dev box has an Nvidia 960 which is about 5 years old while the new boxes I have built have Radeon RX 580s and when I moved a video project from my old box to one of the new ones with identical CPU and memory, the speed difference was in the graphics cards and the old Nvidia card was faster than the Radeon.

HSE

Hi LiaoMi!

Look like you are using compiler part of some JWASM family.

Quote from: LiaoMi on March 30, 2021, 07:14:45 PM
main: # @main
  push rax
  mov dword ptr [rsp + 4], 77
  mov rdi, rsp
  lea rsi, [rsp + 4]
  call Lambda::compute(int&)
  pop rcx
  ret
Lambda::compute(int&): # @Lambda::compute(int&)
  push rax
  mov qword ptr [rsp], rsi
  mov rdi, rsp
  call Lambda::compute(int&)::{lambda()#1}::operator()() const
  pop rcx
  ret
Lambda::compute(int&)::{lambda()#1}::operator()() const: # @Lambda::compute(int&)::{lambda()#1}::operator()() const
  mov rax, qword ptr [rdi]
  mov eax, dword ptr [rax]
  lea ecx, [rax + 4*rax]
  lea eax, [rax + 2*rcx]
  ret


How you make that in Assembly? Perhaps you can disassemble that part.

Thanks in advance, HSE
Equations in Assembly: SmplMath

nidud

#5
deleted

nidud

#6
deleted

HSE

  :biggrin:         asmc32 -pe -gui -ws fprog1.asm
         Asmc Macro Assembler Version 2.32.25
         fprog1.asm(22) : error A2114: INVOKE argument type mismatch : 2

         22     .return lambda.compute(value)
Equations in Assembly: SmplMath

nidud

#8
deleted

HSE

Equations in Assembly: SmplMath

LiaoMi

Quote from: daydreamer on March 30, 2021, 11:05:23 PM
Probably some have old views of assembly as non macro assembler old dos
Specialised critical section coded in asm
, but with lots of helper macros,translated. H files, coinvoke macros for. COM calls,you can do anything

Hi daydreamer,

the question is how it should look in assembly language, since functions pass the results in a chain, and nothing is known about the data type. Examples should show how it might look  :tongue:

Quote from: Vortex on March 31, 2021, 12:31:41 AM
Hi,

I think it's better to keep separated the two worlds. The assemly language should preserve it's essence.

Hi Vortex,

I completely agree! Functional programming follows a different logic than low-level programming.

Quote from: hutch-- on March 31, 2021, 12:34:37 AM
Erol,  :thumbsup:

Having a quick read from the article, it sounds like a load of waffle, effectively playing musical chairs with different notions of computer languages. I well understand conventional classic languages, asm, basic, C, pascal as well as COBOL and FORTRAN for the old fellas but most of the rest end up on the trash heap of history and while there is a place for specialised languages for more dedicated tasks, as the tasks change, so does the specialised language.

Just for example, look at modern graphics, GPU processing tends to be brand specific, some stuff runs on Nvidia, other stuff runs on AMD Radeon and similar but as video hardware changes, so too will the libraries that it uses. I am much of the view, learn a classic language (or 3) and branch from their if you have some reason to use a specialised language.

As a humerous aside, my old dev box has an Nvidia 960 which is about 5 years old while the new boxes I have built have Radeon RX 580s and when I moved a video project from my old box to one of the new ones with identical CPU and memory, the speed difference was in the graphics cards and the old Nvidia card was faster than the Radeon.

Hi Hutch,

I think this is a programming paradigm, not the language itself or a property of the language. "In computer science, functional programming is a programming paradigm where programs are constructed by applying and composing functions. It is a declarative programming paradigm in which function definitions are trees of expressions that map values to other values, rather than a sequence of imperative statements which update the running state of the program." - https://en.wikipedia.org/wiki/Functional_programming

Quote from: HSE on March 31, 2021, 01:06:21 AM
Hi LiaoMi!

Look like you are using compiler part of some JWASM family.

Quote from: LiaoMi on March 30, 2021, 07:14:45 PM
main: # @main
  push rax
  mov dword ptr [rsp + 4], 77
  mov rdi, rsp
  lea rsi, [rsp + 4]
  call Lambda::compute(int&)
  pop rcx
  ret
Lambda::compute(int&): # @Lambda::compute(int&)
  push rax
  mov qword ptr [rsp], rsi
  mov rdi, rsp
  call Lambda::compute(int&)::{lambda()#1}::operator()() const
  pop rcx
  ret
Lambda::compute(int&)::{lambda()#1}::operator()() const: # @Lambda::compute(int&)::{lambda()#1}::operator()() const
  mov rax, qword ptr [rdi]
  mov eax, dword ptr [rax]
  lea ecx, [rax + 4*rax]
  lea eax, [rax + 2*rcx]
  ret


How you make that in Assembly? Perhaps you can disassemble that part.

Thanks in advance, HSE

Hi HSE,

this is a clang compiler that can be used in a visual studio. You can see here - https://godbolt.org/ (Clang 11)
#include <stdio.h>

class Lambda
{
public:
    int compute(int &value){
        auto get = [&value]() -> int {
            return 11 * value;
        };
        return get();
    }
};

int main(){
    Lambda lambda;
    int value = 77;
    return lambda.compute(value);
}


Quote from: nidud on March 31, 2021, 03:02:41 AM
I added direct assignment of strings to Asmc.
- this was possible using = &@CStr( ... )

include iostream

    .code

main proc

    .new s:string_t = "Hello, Lambda Expressions in C++11!"

    .for ( rbx = rax : byte ptr [rbx] : rbx++ )

        .new c:char_t = [rbx] ; need signed byte

        cout << c
    .endf
    cout << endl

    .return 0

main endp

    end main



Quote from: nidud on March 31, 2021, 03:15:32 AM

.class Lambda

    .static compute value:abs {
        imul eax,value,11
        }
    .ends

    .code

main proc

    .new lambda:Lambda
    .new value:int_t = 77

    .return lambda.compute(value)

main endp

        push    rbp
        mov     rbp, rsp
        sub     rsp, 48
        mov     dword ptr [rbp-0CH], 77
        imul    eax, dword ptr [rbp-0CH], 11
        leave                               
        ret                                 


Hi nidud,

very cool!  :eusa_clap:  :thumbsup:
I did a couple of tests and realized that this paradigm can only be used at the meta level, i.e. at a high level of language.

#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
// Lambda functions (seit C++11)
// [ capture ] ( params ) -> ret { body }

/*
capture - spezifiziert, welche Symbole erfasst und für den Funktionskörper
sichtbar gemacht werden.

Eine Liste der Symbole kann wie folgt Übergeben werden:

[a,&b], a wird kopiert und b als Referenz erfasst.
[this] erfasst den this-Zeiger als Kopie.
[&] erfasst alle im Funktionskörper verwendeten Symbole als Referenzen.
[=] erfasst alle im Funktionskörper verwendeten Symbole als Kopien.
[] es wird nichts erfasst.

params - Die Liste der Parameter, wie in benannten Funktionen

ret - Der Rückgabetyp. Falls nicht vorhanden, wird er von den return-Statements der Funktion
abgeleitet (oder, falls jene nichts zurückgibt, void)
*/

std::string s = "Hello, Lambda Expressions in C++11!";

for_each(s.begin(), s.end(),
[](char c) { cout << c; }
);

return 0;
}


*.asm https://godbolt.org/

__cxx_global_var_init:                  # @__cxx_global_var_init
        push    rax
        movabs  rdi, offset std::__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        movabs  rdi, offset std::ios_base::Init::~Init() [complete object destructor]
        movabs  rsi, offset std::__ioinit
        movabs  rdx, offset __dso_handle
        call    __cxa_atexit
        pop     rax
        ret
main:                                   # @main
        sub     rsp, 120
        mov     dword ptr [rsp + 116], 0
        lea     rdi, [rsp + 72]
        mov     qword ptr [rsp + 16], rdi       # 8-byte Spill
        call    std::allocator<char>::allocator() [complete object constructor]
        mov     rdx, qword ptr [rsp + 16]       # 8-byte Reload
        mov     esi, offset .L.str
        lea     rdi, [rsp + 80]
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) [complete object constructor]
        jmp     .LBB1_1
.LBB1_1:
        lea     rdi, [rsp + 72]
        call    std::allocator<char>::~allocator() [complete object destructor]
        lea     rdi, [rsp + 80]
        mov     qword ptr [rsp + 8], rdi        # 8-byte Spill
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::begin()
        mov     rdi, qword ptr [rsp + 8]        # 8-byte Reload
        mov     qword ptr [rsp + 48], rax
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::end()
        mov     rcx, rax
        mov     qword ptr [rsp + 40], rcx
        mov     rdi, qword ptr [rsp + 48]
        mov     rsi, qword ptr [rsp + 40]
        call    main::$_0 std::for_each<__gnu_cxx::__normal_iterator<char*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, main::$_0>(__gnu_cxx::__normal_iterator<char*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, __gnu_cxx::__normal_iterator<char*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, main::$_0)
        jmp     .LBB1_2
.LBB1_2:
        mov     dword ptr [rsp + 116], 0
        lea     rdi, [rsp + 80]
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
        mov     eax, dword ptr [rsp + 116]
        add     rsp, 120
        ret
        mov     rcx, rax
        mov     eax, edx
        mov     qword ptr [rsp + 64], rcx
        mov     dword ptr [rsp + 60], eax
        lea     rdi, [rsp + 72]
        call    std::allocator<char>::~allocator() [complete object destructor]
        jmp     .LBB1_5
        mov     rcx, rax
        mov     eax, edx
        mov     qword ptr [rsp + 64], rcx
        mov     dword ptr [rsp + 60], eax
        lea     rdi, [rsp + 80]
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() [complete object destructor]
.LBB1_5:
        mov     rdi, qword ptr [rsp + 64]
        call    _Unwind_Resume@PLT
main::$_0::operator()(char) const:                    # @"main::$_0::operator()(char) const"
        sub     rsp, 24
        mov     al, sil
        mov     qword ptr [rsp + 16], rdi
        mov     byte ptr [rsp + 15], al
        movabs  rdi, offset std::cout
        movsx   esi, byte ptr [rsp + 15]
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char)
        add     rsp, 24
        ret
_GLOBAL__sub_I_example.cpp:             # @_GLOBAL__sub_I_example.cpp
        push    rax
        call    __cxx_global_var_init
        pop     rax
        ret
.L.str:
        .asciz  "Hello, Lambda Expressions in C++11!"

tenkey

Quote from: LiaoMi on March 31, 2021, 08:34:16 AM
"... rather than a sequence of imperative statements which update the running state of the program"

And that pretty much describes assembly language. Assembly code is a sequence of commands (instructions) changing the contents of memory and registers, and interacting with devices.

This is imperative programming, the conventional form of programming.

daydreamer

The C programmers style of using mostly local variables, maybe is more correct to say paradigm?
Ironic ever since non line numbers programming languages,with argument about it should be better with structured programs and later oop,when it's compiled into opcodes it's returns to kinda line numbering in shape of memory addresses  :tongue:  :biggrin:


my none asm creations
https://masm32.com/board/index.php?topic=6937.msg74303#msg74303
I am an Invoker
"An Invoker is a mage who specializes in the manipulation of raw and elemental energies."
Like SIMD coding

jj2007

Everything that can be done in a high level language can be done in assembly, too - for obvious reasons. The question is do I want it? This JavaScript Side-by-side comparison of imperative vs. functional programming looks thrilling:
const numList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let result = 0;
for (let i = 0; i < numList.length; i++) {
  if (numList[i] % 2 === 0) {
    result += numList[i] * 10;
  }
}

const result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
               .filter(n => n % 2 === 0)
               .map(a => a * 10)
               .reduce((a, b) => a + b);


But I'll jump on this train only if a functionally programmed Visual Studio "Express" loads in much less than 3 minutes :cool:

hutch--

I have this disposition that for all of the abstractions, underneath them is good old fashioned procedural programming, any decent compiler converts its notation to mnemonics and CPU hardware only understands mnemonics.

You can encapsulate many forms of binary code, libraries, DLLs or use scripting languages that call their own engines but on x86 - 64 they are just DLL calls, either from the system or dedicated DLLs for the particular scripting language.

Long ago a friend from the PB forum coined an expression in response to OOP, HOP (hardware oriented programming) and if you want the best performance for any given hardware platform, you write it in its native format.

Now portability is another matter, if you live in the land of console apps, C has long been the master of portable languages and this allows Unix apps to run on other platforms as binary code but there is little portability in UI applications. An alternative is JAVA and some gadget hardware uses JAVA as native code.