Re: Custom DSP Code 2 (comunity project)
Posted: Sun Mar 01, 2015 11:29 pm
@exo
Do not add new functions just yet. I will revise, optimize simplify and add new features to ASMfunction class. Most likely the format will change in the near future. I will also write a complete manual with tutorials/examples once it's finished. It you're merely curious how it works, here's an explanation:
Functions and operators are instances of ASMfunction class. The class contains 2 functions that are made public:
test(inputTypeString) - this function tests, whether the function is valid for that number and types of inputs. If it is, it returns an array where at [0] is expected minimal CPU cost and at [1] the amount of registers the function needs for operation (so the compiler may move intermediate results from registers into variables when function needs more registers then are available - in fact it can keep doing so infinately, so no more 8-successive-operations-per-line limit in DSPcode2). It will possibly contain additional informations in the future.
The input is a string where each char represents an input. For example "rvca" means the function has 4 inputs - first is a register, second is a variable, third is a constant and last one is an array (yes, you can supply function with a full array as an argument)
generate(inputTypeString,[inputs],@regs,@uniq) - this function receives the input type string, array, which contains the names of the inputs, @regs array, which contains the real register numbers vs relative ones (whenever you use xmm(0) in your code generator it will be replaced by "xmm"+@regs[0].to_s ) and lastly @uniq, which is a string that changes for each function call, block, etc. basically it is used to make labels and certain types of variables unique for each function call.
It should return an array with 5 entries (in the functions I've written the array is called ret[]) :
ret[0]= an array of variables and constants, that should be added into the code declaration by compiler. Always use BlueprintVar() method to declare these. Simply change the string in the first argument by this scheme:
'var float a=4; local float x=7; const int i=11;'
This would initialize a variable "a" that will be recycled by any function call (will not keep the value to next sample). A local variable "x" which will be made unique for each function call (will keep value to next sample - every myFunction(...) in code will have it's own "x"). And a constant "i", which is "read-only" and will be identical to any integer 11 you use anywhere. Always use this function before you use blueprintCode() method, because it also does some additional stuff in the background, that I have no lust to explain right now.
ret[1]= an initialization code (a string), that will be put into stage(0) (before the actual stage0 code). In all the functions I've added so far, this is empty string, because none of them needs such code.
ret[2]= an iterative code (a string), that will be placed, where you've used the function.
ret[3]= info bar. It returns additional info [expected CPU cost, no. of registers used]. Should return the same as test() method.
ret[4]= the return. This should contain the name of the variable or register, where the return value ends up.
The code may be generated in several different ways, and there are several things you must keep in mind.
1. register numbers are managed by the compiler. Use xmm(i) method when you what to return the real name of the register. Always use registers with the least index. (for example use xmm(0) and xmm(1) when you need two - don't use xmm(0) and xmm(7), because that means the function uses all of the registers). Also don't forget to mention how many registers you've used in ret[3]
2. names of variables and constants you've declared in blueprintVar() are not the real names that will be used by the compiler. To get the real name use @consts and @vars hash. For example @consts["i"] would return the real name of the constant 11 declared in blueprintVar example above.
3. labels for jumps must be made unique for each function call. add @uniq string to their names to do so.
4. input names are in "inputs" array.
Alternatively you may use blueprintCode() method. You simply supply it with your ASM code blueprint and it will automatically change all variable names and labels and return the real code as string. to use input argument in blueprintCode() method use names arg0/arg1/... They serve as placeholders and will be replaced by real input names once function is called.
For example following codes will do the same:
Now, How to create function pack/add new functions?
Go to "functions" module and there to "packs" module. Create a text component. This component will contain your function pack and will be installed once you connect it to the string array. (the new 3rd party function packs may be installed so easily)
The function pack is a ruby code, that will be run via eval() function in the module above (you can see it in the "functions" module).
First you create instance of ASM function class and supply the function name:
next you must create a string, which will contain the ruby code for the "generate()" method. In the method the string is run as code using eval().
The code should return an array of 5 values - explained above.
next you must add this ASMblueprint to the ASM function you've created. That is done using addFunction method.
arguments for the method are:
1- an array of input type strings, which specify for what input types this code generator is valid.
2- the code string mentioned above
3- the info array, which will be returned by test() method. [expected CPU cost, no. of xmm registers the code uses]
You may add multiple different code generators for different input types, so the function may return different code depending on the types of inputs. If you don't supply certain arrangement of input types, the function will not be generated ("unknown function for provided arguments" error will be raised).
Last thing to do is to add the function into the @functions hash, which is used by the compiler:
As I've said The current version of ASM function class is just a prototype. I will add new features and overall change the specification very soon.
Do not add new functions just yet. I will revise, optimize simplify and add new features to ASMfunction class. Most likely the format will change in the near future. I will also write a complete manual with tutorials/examples once it's finished. It you're merely curious how it works, here's an explanation:
Functions and operators are instances of ASMfunction class. The class contains 2 functions that are made public:
test(inputTypeString) - this function tests, whether the function is valid for that number and types of inputs. If it is, it returns an array where at [0] is expected minimal CPU cost and at [1] the amount of registers the function needs for operation (so the compiler may move intermediate results from registers into variables when function needs more registers then are available - in fact it can keep doing so infinately, so no more 8-successive-operations-per-line limit in DSPcode2). It will possibly contain additional informations in the future.
The input is a string where each char represents an input. For example "rvca" means the function has 4 inputs - first is a register, second is a variable, third is a constant and last one is an array (yes, you can supply function with a full array as an argument)
generate(inputTypeString,[inputs],@regs,@uniq) - this function receives the input type string, array, which contains the names of the inputs, @regs array, which contains the real register numbers vs relative ones (whenever you use xmm(0) in your code generator it will be replaced by "xmm"+@regs[0].to_s ) and lastly @uniq, which is a string that changes for each function call, block, etc. basically it is used to make labels and certain types of variables unique for each function call.
It should return an array with 5 entries (in the functions I've written the array is called ret[]) :
ret[0]= an array of variables and constants, that should be added into the code declaration by compiler. Always use BlueprintVar() method to declare these. Simply change the string in the first argument by this scheme:
'var float a=4; local float x=7; const int i=11;'
This would initialize a variable "a" that will be recycled by any function call (will not keep the value to next sample). A local variable "x" which will be made unique for each function call (will keep value to next sample - every myFunction(...) in code will have it's own "x"). And a constant "i", which is "read-only" and will be identical to any integer 11 you use anywhere. Always use this function before you use blueprintCode() method, because it also does some additional stuff in the background, that I have no lust to explain right now.
ret[1]= an initialization code (a string), that will be put into stage(0) (before the actual stage0 code). In all the functions I've added so far, this is empty string, because none of them needs such code.
ret[2]= an iterative code (a string), that will be placed, where you've used the function.
ret[3]= info bar. It returns additional info [expected CPU cost, no. of registers used]. Should return the same as test() method.
ret[4]= the return. This should contain the name of the variable or register, where the return value ends up.
The code may be generated in several different ways, and there are several things you must keep in mind.
1. register numbers are managed by the compiler. Use xmm(i) method when you what to return the real name of the register. Always use registers with the least index. (for example use xmm(0) and xmm(1) when you need two - don't use xmm(0) and xmm(7), because that means the function uses all of the registers). Also don't forget to mention how many registers you've used in ret[3]
2. names of variables and constants you've declared in blueprintVar() are not the real names that will be used by the compiler. To get the real name use @consts and @vars hash. For example @consts["i"] would return the real name of the constant 11 declared in blueprintVar example above.
3. labels for jumps must be made unique for each function call. add @uniq string to their names to do so.
4. input names are in "inputs" array.
Alternatively you may use blueprintCode() method. You simply supply it with your ASM code blueprint and it will automatically change all variable names and labels and return the real code as string. to use input argument in blueprintCode() method use names arg0/arg1/... They serve as placeholders and will be replaced by real input names once function is called.
For example following codes will do the same:
Code: Select all
one way to do it:
ret[2]="movaps "+xmm(0)+","+inputs[0]+";" + "addps "+xmm(0)+","+inputs[1]+";"
second way to do it:
ret[2]=blueprintCode("movaps xmm0,arg0; addps xmm0,arg1;",@name)
Now, How to create function pack/add new functions?
Go to "functions" module and there to "packs" module. Create a text component. This component will contain your function pack and will be installed once you connect it to the string array. (the new 3rd party function packs may be installed so easily)
The function pack is a ruby code, that will be run via eval() function in the module above (you can see it in the "functions" module).
First you create instance of ASM function class and supply the function name:
Code: Select all
func=ASMfunction.new("myFunction")
next you must create a string, which will contain the ruby code for the "generate()" method. In the method the string is run as code using eval().
The code should return an array of 5 values - explained above.
next you must add this ASMblueprint to the ASM function you've created. That is done using addFunction method.
arguments for the method are:
1- an array of input type strings, which specify for what input types this code generator is valid.
2- the code string mentioned above
3- the info array, which will be returned by test() method. [expected CPU cost, no. of xmm registers the code uses]
Code: Select all
#convert integer to float
func=ASMfunction.new("myFunction")
code="ret=[]
ret[0]=blueprintVar('',@name,inputs)
ret[1]=''
ret[2]=blueprintCode(
'cvtdq2ps xmm0,arg0;',@name)
ret[3]=[5,1]
ret[4]=xmm(0)
return ret"
func.addFunction(["v","c","r"],code,[5,1])
You may add multiple different code generators for different input types, so the function may return different code depending on the types of inputs. If you don't supply certain arrangement of input types, the function will not be generated ("unknown function for provided arguments" error will be raised).
Last thing to do is to add the function into the @functions hash, which is used by the compiler:
Code: Select all
@functions["myFunction"]=func
As I've said The current version of ASM function class is just a prototype. I will add new features and overall change the specification very soon.