Open fafalone opened 11 months ago
I think this has been discussed again . I said my opinion in a post here or in Discord ( I don't remember). Inline assembly is a "must", in a language like tB, that tends to cover low and high level applications combining the ease of vb6 and the power of c++. So , IMO , inline assembly should finally included..but not sure if this is a v1.0 target..
It's been mentioned in a few places for sure but I thought it was time it had a form language design proposal issue.
Now granted this is coming from a position of pretty much total ignorance, but it seems like it should be easy to implement... after all, you don't need to do any real processing, nobody is asking for asm intellisense or autocomplete or even syntax highlighting (at least not before like 3.0 maybe)... you would just need to take the contents of that block, feed it through to the last step of compiling (asm->exe) in the right spot. It's not like VB where there's no access to the compiler and linker sources so it has to be hacked in through intercepting and modifying obj files.
Wayne mentioned he's written his own C compiler too... it probably wouldn't be too far out of the way to implement inline C at the same time too. ThunderVB has that feature. In fact it even has example code about the recent OpenGL conversations...
Public Sub InitSDL()
'#c'lib=C:\sdl\lib\SDL.lib
'#c'lib=GLU32.LIB
'#c'lib=GLAUX.LIB
'#c'lib=OPENGL32.LIB
'#c'
'#c' //SDL OpenGL Tutorial.
'#c' //(c) Michael Vance, 2000
'#c' //briareos@lokigames.com
'#c' //
'#c' //Distributed under terms of the LGPL.
'#c' //
'#c' #include <windows.h>
'#c' #include "C:\sdl\include\SDL.h"
'#c' #include <GL/gl.h>
'#c' #include <GL/glu.h>
'#c'
'#c' #include <stdio.h>
'#c' #include <stdlib.h>
'#c' static int exit_app=0;
'#c' static GLboolean should_rotate = GL_TRUE;
'#c'
'#c' static void quit_tutorial( int code )
It goes on with a larger demo of using OpenGL with inline C that imports C headers for OpenGL.
if this kind of features should be support in future, maybe consider it as a extra secondly-develop method/interface for tBers to add any other codes in tB module/class, which will be more convenient for some of the expert coders to add call/callback abilities of other coding language, such as c/cpp/go/java/php/even js, into tB.
function func(p1,p2) dim b as long dim c as longptr '@c_file $apppath$\demo.c //include an c file
'@asm_x64_start ' asmcode '@asm_x64_end b = p1 xor p2
end function
Note you can already use function calls from .c or .asm files by compiling them to .obj or .lib and using tB's static linking ability to build it into the exe. See Samples 17 and 18, or WinDevLib's wdAPIInterlocked.twin for examples.
Note you can already use function calls from .c or .asm files by compiling them to .obj or .lib and using tB's static linking ability to build it into the exe. See Samples 17 and 18, or WinDevLib's wdAPIInterlocked.twin for examples.
but it seems static linking ability can not support mix coding? one line basic code one line asm code one line c code
Just function calls like APIs. But the point is between tBs new language features and that, it covers a lot use cases for inline asm/c, a good alternative until true inline is supported.
What is a low hanging fruit here until inline ASM feature lands (or some inter-language preprocessor implemented) is to just add an Emit(b1, b2, b3, …, bn)
intrinsic like some C/C++ compilers support which directly emits instruction bytes in codegen i.e. similar to db
directive in native assemblers.
This usually coupled with naked
function attribute to skip standard stack-frame prolog/epilog codegen and conditional compilation on Win64
const would allow embedding any ASM thunk as ordinary TB code without separate .obj files.
@wqweto that's low hanging fruit indeed!
@wqweto perhaps EmitBytes(), EmitIntegers(), EmitLongs(), EmitLongLongs()?
Would EmitLongPtrs
also would be useful? 64 bit asm and 32 bit tend to be so different that maybe you wouldn't do it that way.
Probably not so useful, as you're extremely likely to need to write conditionally compiled code to differentiate here anyway.
Having a whole family of functions for something quite obscure might be an overkill as original __emit__
"pseudo-function that injects literal values directly into the object code" in C/C++ supports bytes only.
Some links:
My thinking was that it could make things easier for when working with non hard coded offsets and values. But yes, it might be overkill.
EmitBytes(&H8B, &H81) : EmitLongs(VTableOffsetOf MyProc) ' mov eax, dword [ecx+X]
EmitBytes(&HFF, &HD0) ' call eax
edit: (modified my example due to use of relocation which wouldn't be supported here)
Yes, this seems useful, considering the params had to be literals in original proposal. A lot of folks wouldn't understand why Emit(Idx + 42) would not work for this "function".
Another option is to keep a single pseudo-function name but emit whatever typed literal is passed so this can work for Currency too withoutlittering the global namespace with too much symbols.
Another option is to keep a single pseudo-function name but emit whatever typed literal is passed so this can work for Currency too withoutlittering the global namespace with too much symbols.
Yes, I did consider that, but the problem is that small literals have a natural type of Integer
, e.g. &H42
would then be output as two-bytes. We could say that values <256 are output as a Byte, but that creates further confusion as you might actually intend/need the smaller value to be output as an Integer/Long etc (particularly when using non hard-coded offsets, that would get confusing)
I've currently got this working:
Function DoubleUp Naked(ByVal Value1 As Long) As Long
#If Win64 = False Then
Const Me_StackOffset = 4 ' implicit Me arg for class methods
Const Value1_StackOffset = 8
Const OutPtr_StackOffset = 12 ' implicit OUT arg
Emit(&H8B, &H44, &H24, Value1_StackOffset) ' mov eax, dword ptr [esp + 8] (eax = Value1)
Emit(&HD1, &HE0) ' shl eax, 1 (eax = Value1 * 2)
Emit(&H8B, &H54, &H24, OutPtr_StackOffset) ' mov edx, dword ptr [esp + 12] (edx = ptr OUT)
Emit(&H89, &H02) ' mov dword ptr [edx], eax (*OUT = Value1 * 2)
Emit(&H31, &HC0) ' xor eax, eax (eax = S_OK)
Emit(&HC2, &H0C, &H00) ' ret 12
#Else
' rcx == implicit Me arg for class methods
' rdx == Value1
' r8 == implicit OUT arg
Emit(&HD1, &HE2) ' shl edx, 1 (edx = Value1 * 2)
Emit(&H41, &H89, &H10) ' mov dword ptr [r8], edx (*OUT = Value1 * 2)
Emit(&H31, &HC0) ' xor eax, eax (eax = S_OK)
Emit(&HC3) ' ret
#End If
End Function
And this for the standard-module version:
Function DoubleUp Naked(ByVal Value1 As Long) As Long
#If Win64 = False Then
Const Value1_StackOffset = 4
Emit(&H8B, &H44, &H24, Value1_StackOffset) ' mov eax, dword ptr [esp + 4] (eax = Value1)
Emit(&HD1, &HE0) ' shl eax, 1 (eax = Value1 * 2)
Emit(&HC2, &H04, &H00) ' ret 4 (return value in EAX)
#Else
' rcx == Value1
Emit(&HD1, &HE1) ' shl ecx, 1 (ecx = Value1 * 2)
Emit(&H89, &HC8) ' mov eax, ecx
Emit(&HC3) ' ret (return value in EAX)
#End If
End Function
You can now play around with this, in BETA 605. Tip: to debug the emitted code, attach a native debugger (e.g. VS, or x64dbg) and use Emit(&HCC)
to insert a debugger breakpoint (INT3) in your codegen.
Wondering if it is possible to default to Byte for &H42 suffixless literals and require % for Integer ones (less than 256) only here, the way it's currently impl for small Long literals? Might be a less surprising mode of operandi for this pseudo-function.
Wondering if it is possible to default to Byte for &H42 suffixless literals and require % for Integer ones (less than 256) only here, the way it's currently impl for small Long literals? Might be a less surprising mode of operandi for this pseudo-function.
Might be difficult to achieve without it becoming confusing (e.g. if the value comes from a Const). Needs some thought.
Correction to above: where you see:
Emit(&HC2, &H0C) ' ret 12
Emit(&HC2, &H04) ' ret 4
it should have been
Emit(&HC2, &H0C, &H00) ' ret 12
Emit(&HC2, &H04, &H00) ' ret 4
.
(I will correct the post)
Another example, using the CPUID instruction (in a standard module):
Public Function GetCPUID_ProcessorBrandString() As String
Dim ProcessorBrandStringANSI As String = Space$(24)
GetCPUID_ProcessorBrandStringANSI(ProcessorBrandStringANSI)
Return StrConv(ProcessorBrandStringANSI, vbUnicode)
End Function
Private Sub GetCPUID_ProcessorBrandStringANSI Naked(ByVal PreAllocatedString As String)
' Use the extended CPUID instruction to get the CPU ProcessorBrandString.
' You must pass in a String capable of holding a 48-byte ANSI string
' TODO: checking to ensure the CPU supports the CPUID instruction
#If Win64 = False Then
Emit(&H55) ' push ebp (preserve non-volatile register)
Emit(&H53) ' push ebx (preserve non-volatile register)
Const Value1_StackOffset = 12
Emit(&H8B, &H6C, &H24, Value1_StackOffset) ' mov ebp, [esp+12] (ebp == PreAllocatedString)
Emit(&HB8, &H02, &H00, &H00, &H80) ' mov eax, 0x80000002
Emit(&H0F, &HA2) ' cpuid
Emit(&H89, &H45, &H00) ' mov dword ptr [ebp], eax (ANSI 4 chars)
Emit(&H89, &H5D, &H04) ' mov dword ptr [ebp+4h], ebx (ANSI 4 chars)
Emit(&H89, &H4D, &H08) ' mov dword ptr [ebp+8h], ecx (ANSI 4 chars)
Emit(&H89, &H55, &H0C) ' mov dword ptr [ebp+Ch], edx (ANSI 4 chars)
Emit(&HB8, &H03, &H00, &H00, &H80) ' mov eax, 0x80000003
Emit(&H0F, &HA2) ' cpuid
Emit(&H89, &H45, &H10) ' mov dword ptr [ebp+10h], eax (ANSI 4 chars)
Emit(&H89, &H5D, &H14) ' mov dword ptr [ebp+14h], ebx (ANSI 4 chars)
Emit(&H89, &H4D, &H18) ' mov dword ptr [ebp+18h], ecx (ANSI 4 chars)
Emit(&H89, &H55, &H1C) ' mov dword ptr [ebp+1Ch], edx (ANSI 4 chars)
Emit(&HB8, &H04, &H00, &H00, &H80) ' mov eax, 0x80000004
Emit(&H0F, &HA2) ' cpuid
Emit(&H89, &H45, &H20) ' mov dword ptr [ebp+20h], eax (ANSI 4 chars)
Emit(&H89, &H5D, &H24) ' mov dword ptr [ebp+24h], ebx (ANSI 4 chars)
Emit(&H89, &H4D, &H28) ' mov dword ptr [ebp+28h], ecx (ANSI 4 chars)
Emit(&H89, &H55, &H2C) ' mov dword ptr [ebp+2Ch], edx (ANSI 4 chars)
Emit(&H5B) ' pop ebx (restore non-volatile register)
Emit(&H5D) ' pop ebp (restore non-volatile register)
Emit(&HC2, &H04, &H00) ' ret 4
#Else
Emit(&H53) ' push ebx (preserve non-volatile register)
Emit(&H49, &H89, &HCB) ' mov r11, rcx (rcx will be overwritten by CPUID)
Emit(&HB8, &H02, &H00, &H00, &H80) ' mov eax, 0x80000002
Emit(&H0F, &HA2) ' cpuid
Emit(&H41, &H89, &H43, &H00) ' mov dword ptr [r11], eax (ANSI 4 chars)
Emit(&H41, &H89, &H5B, &H04) ' mov dword ptr [r11+4h], ebx (ANSI 4 chars)
Emit(&H41, &H89, &H4B, &H08) ' mov dword ptr [r11+8h], ecx (ANSI 4 chars)
Emit(&H41, &H89, &H53, &H0C) ' mov dword ptr [r11+Ch], edx (ANSI 4 chars)
Emit(&HB8, &H03, &H00, &H00, &H80) ' mov eax, 0x80000003
Emit(&H0F, &HA2) ' cpuid
Emit(&H41, &H89, &H43, &H10) ' mov dword ptr [r11+10h], eax (ANSI 4 chars)
Emit(&H41, &H89, &H5B, &H14) ' mov dword ptr [r11+14h], ebx (ANSI 4 chars)
Emit(&H41, &H89, &H4B, &H18) ' mov dword ptr [r11+18h], ecx (ANSI 4 chars)
Emit(&H41, &H89, &H53, &H1C) ' mov dword ptr [r11+1Ch], edx (ANSI 4 chars)
Emit(&HB8, &H04, &H00, &H00, &H80) ' mov eax, 0x80000004
Emit(&H0F, &HA2) ' cpuid
Emit(&H41, &H89, &H43, &H20) ' mov dword ptr [r11+20h], eax (ANSI 4 chars)
Emit(&H41, &H89, &H5B, &H24) ' mov dword ptr [r11+24h], ebx (ANSI 4 chars)
Emit(&H41, &H89, &H4B, &H28) ' mov dword ptr [r11+28h], ecx (ANSI 4 chars)
Emit(&H41, &H89, &H53, &H2C) ' mov dword ptr [r11+2Ch], edx (ANSI 4 chars)
Emit(&H5B) ' pop ebx (restore non-volatile register)
Emit(&HC3) ' ret
#End If
End Sub
BETA 607 adds EmitAny() and StackOffset() support. EmitAny is the same as Emit() except that the output length of each element is inferred from the datatype. In other words, if you pass a Long literal value, e.g. 42&, it will output 4 bytes into the codegen stream. StackOffset() allows you to obtain the stack offset of a local variable or parameter, most useful for non-Naked procedures where you don't control local-variable positions in the stack frame, and so need a way to determine this for Emit() purposes.
Two examples:
Public Sub TestWriteToLocalVar1()
Dim Foo As Long = &H12345678
' this assembled code is valid for both X86 and X64
Emit(&HC7, &H84, &H24): EmitAny(StackOffset(Foo), 42&) ' mov dword ptr [esp + StackOffset(Foo)], &H0000002A
MsgBox Foo ' Foo has been populated with the value 42
End Sub
Private Sub TestLocalVar2a(ByVal ParamVar1 As Long)
Dim Foo As Long = &H12345678
' this is valid in both X86 and X64
Emit(&H8B, &H84, &H24): EmitAny(StackOffset(ParamVar1)) ' mov eax, dword ptr [esp + StackOffset(ParamVar1)] (reads input param into EAX)
Emit(&H89, &H84, &H24): EmitAny(StackOffset(Foo)) ' mov dword ptr [esp + StackOffset(Foo)], eax (writes it to our local variable, ABC)
MsgBox Foo ' Foo has been populated with the passed in Something1 value
End Sub
Public Sub TestLocalVar2()
TestLocalVar2a(555)
End Sub
Note that whilst X64 calling convention stipulates that the first 4 arguments are passed in registers (i.e. not on the stack), you shouldn't have to worry about this, as the tB prolog that sets up the stack frame moves the values passed via-registers onto the stack anyway. (Code using Emit() will not be compiled via LLVM, and so this side effect of the tB prolog can be relied upon here).
Tip: if using Emit() like shown above (ie. as part of a regular tB procedure code), then assume that only volatile registers are available for your use (i.e. EAX/ECX/EDX on x86). If you clobber any other registers, ensure you preserve and restore them so that you don't interfere with any other tB codegen that may be relying upon their values. If in doubt, ask :)
Was surprised to see this only discussed in reference as a use for static linking ability; thought there was already a formal request (if I've somehow missed it, apologies, but I searched issues/discussions here and /twinbasic).
I propose tB should support native syntax for inline assembly. tB eliminates some use cases for this, but could never anticipate all of them.
The most appropriate option, I think, it to allow it within functions; whole modules could be covered by static linking. There would be a traditional declaration, followed by function that may also contain tB code, that allows syntax to begin and end a block of assembly.
There's a number of good options for syntax; I propose the best way is to have a line starting with a character that would otherwise be illegal as a first, to avoid conflicts with existing names.
The end syntax matches traditional block end syntax, and if there's still plans to change attribute syntax to @? If attributes inside methods would be too difficult, well the leading character isn't too important so long as it's not valid as a first character for any other use.
While it's certainly 'not BASIC' being an actual different language, I'd again point to the current state of usage: For the rare cases where no suitable alternatives exist, we already have the situation of VBx using assembly through several methods, so this is not introducing something to the language entirely without precedent and current examples. The trick has an inline asm addin for VB6, but more commonly, it's seen as a bunch of inscrutable magic hex values written to an executable location or called with DispCallFunc/CallWindowProc. So there's no avoiding the fact that it will be used in tB just like it's used in VBx-- in fact I tested a project using asm thunks in tB just the other day, it worked fine.\ So it's not a question of whether this can be kept out, it's a question of should it be made friendlier and more accessible, thus increasing tB's power and ease of use for advanced programming? I see no reason why not. Especially given that it's sufficiently difficult only the most skilled and experienced programmers would use it, making it unlikely we'd see a bunch of junk asm code mucking things up for beginners, because it's rare and not even approachable until you have the sense to understand when it should be used.