Skip to content

Commit

Permalink
Merge pull request #36 from deleterium/feat/registerType
Browse files Browse the repository at this point in the history
New keyword 'register' as modifier for variables.
  • Loading branch information
deleterium authored Feb 17, 2024
2 parents bbfb21f + d649728 commit 27184e1
Show file tree
Hide file tree
Showing 18 changed files with 311 additions and 8 deletions.
8 changes: 5 additions & 3 deletions docs/1-Basis.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ This project aims to be as close to C as possible. But given special characteris
As C, can be one line `//` or multi-line `/* .... */`;

### Keywords
Some keywords have the same meaning and use in C: `asm`, `break`, `continue`, `default`, `do`, `else`, `for`, `goto`, `if`, `inline`, `long`, `return`, `struct`, `void`, `while`. Note differences for keywords:
Some keywords have the same meaning and use in C: `asm`, `break`, `continue`, `default`, `do`, `else`, `for`, `goto`, `if`, `inline`, `long`, `register`, `return`, `struct`, `void`, `while`. Note differences for keywords:
* `const`: Actually this will tell compiler to set a value to a variable at the contract creation. No problem setting it a value and then changing it later. It can be used during variable declaration or later, but it can be set only once. Using const can reduce the number of codepages of your program. Examples: `const long i=5;` to seta long; `long a[4]; const a[0]=5;` to set values for array.
* `sizeof`: Usefull to get structs sizes. Return value will be the number of longs that the variable/type needs. One long is 8 bytes. Arrays sizes are one long greater than the length, because the first index is used to store the array starting location (a pointer). Pointers sizes are always 1.
* `switch`: The standard C is fully supported. One addition is that switch expression can be `true` or `false`, and the cases will be evaluated to match the desired result.
Expand All @@ -19,7 +19,7 @@ There are also additional keywords:
* `exit`: Puts the contract in 'stop' mode and set program to restart from main function ('finished' mode). It will be inactive until a new transaction is received. Once a tx is received, it will start execution at `void main()` function. If main function is not defined, the execution will start again from beginning of code, running again all global statements. If the main function is defined, the global statements will be executed only in the first activations of the contract. `exit` takes no argument. If contract activation amount is zero, contract will resume execution on next block (similar to `sleep`).
* `halt`: Puts the contract in 'stop' mode. It will be inactive until a new transaction is received, then it will resume execution at next instruction. It takes no argument. If contract activation amount is zero, contract will resume execution on next block.

Others keyword have no assembly support. They are disabled: `auto`, `double`, `float`, `register`, `volatile`. For future implementation these keywords can be added: `char`, `enum`, `extern`, `int`, `short`, `signed`, `static`, `typedef`, `union`, `unsigned`.
Others keyword have no assembly support. They are disabled: `auto`, `double`, `float`, `volatile`. For future implementation these keywords can be added: `char`, `enum`, `extern`, `int`, `short`, `signed`, `static`, `typedef`, `union`, `unsigned`.

### Preprocessor
Some special features can be enabled/disable via preprocessor directives. Check chapter 1.2.
Expand All @@ -38,10 +38,12 @@ Arrays can be declared using the standart notation, but they only can be initial
Structs use same notation in C. Structs pointers can also be used. To access a member, use `.` or `->` depending if struct is already allocated in memory or if it is a pointer to the memory location. Arrays of structs, arrays inside structs and recursive pointer definition are also supported.

All variables are initialized with value `0` at the first time the contract is executed, unless other value is set by `const` statement.
All variables are similar to `static` in C. So every time a function is called or the smart contract receives a transaction, all variables will keep their last value. To avoid this behavior in functions, declare variables setting them a initial value: `long i=0;`.
Variables defaults are similar to `static` in C. So every time a function is called or the smart contract receives a transaction, all variables will keep their last value. To avoid this behavior in functions, declare variables setting them a initial value: `long i=0;`.
Global variables are available in all functions. Functions variables can only be used inside the function.
Variables declarations can be inside other sentences, like `for (long i; i<10; i++)` or `if (a){ long i=0; ...}`, but their scope can only be 'global' or 'function', in other words, the result is the same as declaring variables at the program start (if global variables) or at the function start (if function variables).

Some variables can be modified with the keyword `register`. The variable must be a single long to fit in one compiler's auxiliary variable. When modified as register, the variable will not be allocated in program memory, but one auxVar will be designated for it in the scope during compilation time. Example: `for (register long i=0; i<10; i++) { ... } i=1;` will raise an error in last assignment because 'i' is used out of declared scope. Keep in mind that the 'parked' auxVar can not be used as temporary values for regular operations, so it is recommended to increase the `#pragma maxAuxVars` to avoid running out of them during compiling process. If a program has many small functions, declaring them as registers will help to reduce the memory. Calling another functions when a register is parked will cause them to be stacked, so the program will need 'user stack pages'.

### Implicit types casting
The compiler will convert numbers and variables between fixed and long if used in binary operators. Most of times long will be converted to fixed, unless there is an assigment and the left side is long. In this case (=, +=, -=, ...) fixed values will be transformed into long. Data can be lost in this transformation, so keep an eye on it!

Expand Down
1 change: 1 addition & 0 deletions docs/1.2-Preprocessor-directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ Special features used by compiler.
* `#pragma reuseAssignedVar [true/false/1/0/]`: When set, compiler will try to use a variable on left side of and `Assignment` as a register. If variable is also used on right side, the compiler will not reuse it. This can save one assembly instruction for every expression used! Default value is `true` and it is highly recomended to maintain it active.
* `#pragma version VALUE`: Informs which compiler's version the code was developed. This is optional but can help future generations. VALUE can be any string or remarks.
* `#pragma verboseAssembly [true/false/1/0/]`: Adds a comment in assembly output with the corresponding line number and the source code. Very usefull for debug.
* `#pragma verboseScope [true/false/1/0/]`: Adds a comment in assembly output with the free register ever begin/end of scope. Also informs when a register in use as another variable name. Very usefull for debug.

### Escaping new line
Just end a line with `\` and it will be joined to the next one. It can be used anywhere, even inside "strings". Usefull for multiline program description, #define for a macro, or in middle of a variable name!
Expand Down
114 changes: 113 additions & 1 deletion src/__tests__/keywords.c.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SmartC } from '../smartc'

describe('More keywords', () => {
describe('Keyword inline', () => {
test('should throw: inline wrong usage', () => {
expect(() => {
const code = 'inline long a;'
Expand Down Expand Up @@ -58,3 +58,115 @@ describe('More keywords', () => {
}).toThrowError(/^At line/)
})
})
describe('Keyword register', () => {
it('should compile: register simple', () => {
const code = '#pragma optimizationLevel 0\nregister long a;a=1;'
const assembly = '^declare r0\n^declare r1\n^declare r2\n\nSET @r2 #0000000000000001\nFIN\n'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
expect(compiler.getAssemblyCode()).toBe(assembly)
})
it('should compile: register simple delim', () => {
const code = '#pragma optimizationLevel 0\nregister long a=1, b=2;'
const assembly = '^declare r0\n^declare r1\n^declare r2\n\nSET @r2 #0000000000000001\nSET @r1 #0000000000000002\nFIN\n'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
expect(compiler.getAssemblyCode()).toBe(assembly)
})
it('should compile: register multi scope', () => {
const code = '#pragma optimizationLevel 0\n#include APIFunctions\nlong G = teste(2, 3);\nlong teste(long arg_a, long arg_b) { register long a=2; if (arg_a) { register long b = Get_A1(); a+=b; } else { register long c = Get_A2(); a+=c; } register long d=0; return a+d; }'
const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare G\n^declare teste_arg_a\n^declare teste_arg_b\n\nSET @teste_arg_b #0000000000000003\nSET @teste_arg_a #0000000000000002\nJSR :__fn_teste\nSET @G $r0\nFIN\n\n__fn_teste:\nSET @r2 #0000000000000002\nBZR $teste_arg_a :__if1_else\n__if1_start:\nFUN @r1 get_A1\nADD @r2 $r1\nJMP :__if1_endif\n__if1_else:\nFUN @r1 get_A2\nADD @r2 $r1\n__if1_endif:\nCLR @r1\nSET @r0 $r2\nADD @r0 $r1\nSET @r0 $r0\nRET\n'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
expect(compiler.getAssemblyCode()).toBe(assembly)
})
it('should compile: register in use and function call (push/pop)', () => {
const code = `#pragma optimizationLevel 0\n#include APIFunctions\n long G = teste(2, 3);
long teste(long arg_a, long arg_b) {
register long a=2;
if (arg_a) {
register long b = Get_A1();
a+=inc(b);
} else {
register long c = Get_A2();
a+=c;
}
return a+inc(a);
}
long inc(long val) { return val+1; }`
const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare G\n^declare teste_arg_a\n^declare teste_arg_b\n^declare inc_val\n\nSET @teste_arg_b #0000000000000003\nSET @teste_arg_a #0000000000000002\nJSR :__fn_teste\nSET @G $r0\nFIN\n\n__fn_teste:\nSET @r2 #0000000000000002\nBZR $teste_arg_a :__if1_else\n__if1_start:\nFUN @r1 get_A1\nPSH $r2\nPSH $r1\nSET @inc_val $r1\nJSR :__fn_inc\nSET @r0 $r0\nPOP @r1\nPOP @r2\nADD @r2 $r0\nJMP :__if1_endif\n__if1_else:\nFUN @r1 get_A2\nADD @r2 $r1\n__if1_endif:\nPSH $r2\nSET @inc_val $r2\nJSR :__fn_inc\nSET @r0 $r0\nPOP @r2\nADD @r0 $r2\nSET @r0 $r0\nRET\n\n__fn_inc:\nSET @r0 $inc_val\nINC @r0\nSET @r0 $r0\nRET\n'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
expect(compiler.getAssemblyCode()).toBe(assembly)
})
it('should compile: register increment on a function call argument (detecting right actual registers vs register variable type', () => {
const code = '#pragma optimizationLevel 0\n#include APIFunctions\nlong G = teste(2, 3); long teste(long arg_a, long arg_b) { register long a=2; return a+inc(a+1); } long inc(long val) { return val+1; }'
const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare G\n^declare teste_arg_a\n^declare teste_arg_b\n^declare inc_val\n\nSET @teste_arg_b #0000000000000003\nSET @teste_arg_a #0000000000000002\nJSR :__fn_teste\nSET @G $r0\nFIN\n\n__fn_teste:\nSET @r2 #0000000000000002\nPSH $r2\nSET @r0 $r2\nINC @r0\nSET @inc_val $r0\nJSR :__fn_inc\nSET @r0 $r0\nPOP @r2\nADD @r0 $r2\nSET @r0 $r0\nRET\n\n__fn_inc:\nSET @r0 $inc_val\nINC @r0\nSET @r0 $r0\nRET\n'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
expect(compiler.getAssemblyCode()).toBe(assembly)
})
test('should throw: register missing type', () => {
expect(() => {
const code = 'register a=2;'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
}).toThrowError(/^At line/)
})
test('should throw: register alone in the dark', () => {
expect(() => {
const code = 'register;'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
}).toThrowError(/^At line/)
})
test('should throw: register wrong type', () => {
expect(() => {
const code = 'struct ASM { long a, b;}; register struct ASM a;'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
}).toThrowError(/^At line/)
})
test('should throw: register on arrays', () => {
expect(() => {
const code = 'register long a[5];'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
}).toThrowError(/^At line/)
})
test('should throw: register on function definition', () => {
expect(() => {
const code = 'long G = teste(2, 3); register teste(long arg_a, long arg_b) { return arg_a+arg_b; }'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
}).toThrowError(/^At line/)
})
test('should throw: register on function arguments', () => {
expect(() => {
const code = 'long G = teste(2, 3); long teste(register long arg_a, long arg_b) { return arg_a+arg_b; }'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
}).toThrowError(/^At line/)
})
test('should throw: register out of scope', () => {
expect(() => {
const code = '#pragma optimizationLevel 0\n#include APIFunctions\n long G = teste(2, 3); long teste(long arg_a, long arg_b) { register long a=2; if (arg_a) { register long b = Get_A1(); a+=b; } return b; }'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
}).toThrowError(/^At line/)
})
test('should throw: running out of register', () => {
expect(() => {
const code = '#pragma maxAuxVars 4\nregister fixed a=1., b=2.;\nregister fixed c=3.;\nregister long d=4;\n'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
}).toThrowError(/^At line: 4/)
})
it('should compile: register multi scope (verbose assembly)', () => {
const code = '#pragma verboseAssembly\n#pragma optimizationLevel 0\n#include APIFunctions\nlong G = teste(2, 3);\nlong teste(long arg_a, long arg_b) {\nregister long a=2;\nif (arg_a) {\nregister long b = Get_A1();\na+=b;\n} else {\nregister long c = Get_A2(); a+=c;\n}\nregister long d=0;\nreturn a+d;\n}\n'
const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare G\n^declare teste_arg_a\n^declare teste_arg_b\n\n^comment scope r0,r1,r2\n^comment line 4 long G = teste(2, 3);\nSET @teste_arg_b #0000000000000003\nSET @teste_arg_a #0000000000000002\nJSR :__fn_teste\nSET @G $r0\n^comment scope r0,r1,r2\nFIN\n\n^comment line 5 long teste(long arg_a, long arg_b) {\n__fn_teste:\n^comment scope r0,r1,r2\n^comment line 6 register long a=2;\n^comment scope r2:teste_a\nSET @r2 #0000000000000002\n^comment line 7 if (arg_a) {\nBZR $teste_arg_a :__if1_else\n__if1_start:\n^comment scope r0,r1\n^comment line 8 register long b = Get_A1();\n^comment scope r1:teste_b\nFUN @r1 get_A1\n^comment line 9 a+=b;\nADD @r2 $r1\n^comment scope r0,r1\nJMP :__if1_endif\n__if1_else:\n^comment scope r0,r1\n^comment line 11 register long c = Get_A2(); a+=c;\n^comment scope r1:teste_c\nFUN @r1 get_A2\nADD @r2 $r1\n__if1_endif:\n^comment scope r0,r1\n^comment line 13 register long d=0;\n^comment scope r1:teste_d\nCLR @r1\n^comment line 14 return a+d;\nSET @r0 $r2\nADD @r0 $r1\nSET @r0 $r0\nRET\n^comment scope r0,r1,r2\nRET\n'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
expect(compiler.getAssemblyCode()).toBe(assembly)
})
})
7 changes: 7 additions & 0 deletions src/__tests__/macros.a.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,13 @@ describe('#pragma', () => {
compiler.compile()
expect(compiler.getAssemblyCode()).toBe(assembly)
})
it('should compile: verboseScope', () => {
const code = '#pragma verboseScope true\n long a;\n if (a) a++;\n a++;\n#pragma optimizationLevel 0\n'
const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare a\n\n^comment scope r0,r1,r2\nBZR $a :__if1_endif\n__if1_start:\n^comment scope r0,r1,r2\nINC @a\n__if1_endif:\n^comment scope r0,r1,r2\nINC @a\n^comment scope r0,r1,r2\nFIN\n'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
expect(compiler.getAssemblyCode()).toBe(assembly)
})
test('should throw: maxAuxVars invalid parameter', () => {
expect(() => {
const code = '#pragma maxAuxVars a\nlong a;'
Expand Down
5 changes: 5 additions & 0 deletions src/codeGenerator/astProcessor/binaryAsnProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ export default function binaryAsnProcessor (
savedDeclaration = AuxVars.isDeclaration
AuxVars.isDeclaration = ''
}
// Clear register declaration before right side evaluation
const prevStateOfIsRegisterSentence = AuxVars.isRegisterSentence
AuxVars.isRegisterSentence = false
// If it is an array item we know, change to the item
if (LGenObj.SolvedMem.type === 'array' &&
LGenObj.SolvedMem.Offset?.type === 'constant') {
Expand All @@ -178,6 +181,8 @@ export default function binaryAsnProcessor (
let RGenObj = assignmentRightSideSolver(LGenObj.SolvedMem)
// Restore isDeclaration value
AuxVars.isDeclaration = savedDeclaration
// Restore isRegisterSentence value
AuxVars.isRegisterSentence = prevStateOfIsRegisterSentence
// Error check for Right side
if (RGenObj.SolvedMem.type === 'void') {
throw new Error(`At line: ${CurrentNode.Operation.line}. ` +
Expand Down
26 changes: 25 additions & 1 deletion src/codeGenerator/astProcessor/endAsnProcessor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { assertNotUndefined } from '../../repository/repository'
import { CONTRACT } from '../../typings/contractTypes'
import { END_ASN, LONG_TYPE_DEFINITION, STRUCT_TYPE_DEFINITION } from '../../typings/syntaxTypes'
import { END_ASN, LONG_TYPE_DEFINITION, MEMORY_SLOT, STRUCT_TYPE_DEFINITION } from '../../typings/syntaxTypes'
import { createSimpleInstruction, createInstruction } from '../assemblyProcessor/createInstruction'
import { GENCODE_AUXVARS, GENCODE_ARGS, GENCODE_SOLVED_OBJECT } from '../codeGeneratorTypes'
import utils from '../utils'
Expand Down Expand Up @@ -75,6 +75,30 @@ export default function endAsnProcessor (
CurrentNode.Token.line,
AuxVars.isDeclaration
)
if (AuxVars.isRegisterSentence) {
return registerProc(retMemObj)
}
return { SolvedMem: retMemObj, asmCode: '' }
}

function registerProc (retMemObj: MEMORY_SLOT) : GENCODE_SOLVED_OBJECT {
const lastFreeRegister = AuxVars.registerInfo.filter(Reg => Reg.inUse === false).reverse()[0]
if (lastFreeRegister === undefined || lastFreeRegister.Template.asmName === 'r0') {
throw new Error(`At line: ${CurrentNode.Token.line}. ` +
'No more registers available. ' +
`Increase the number with '#pragma maxAuxVars ${Program.Config.maxAuxVars + 1}' or try to reduce nested operations.`)
}
lastFreeRegister.inUse = true
AuxVars.scopedRegisters.push(lastFreeRegister.Template.asmName)
const varPrevAsmName = retMemObj.asmName
const motherMemory = assertNotUndefined(Program.memory.find(obj => obj.asmName === retMemObj.asmName), 'Internal error')
retMemObj.address = lastFreeRegister.Template.address
retMemObj.asmName = lastFreeRegister.Template.asmName
motherMemory.address = retMemObj.address
motherMemory.asmName = retMemObj.asmName
if (Program.Config.verboseAssembly) {
return { SolvedMem: retMemObj, asmCode: `^comment scope ${lastFreeRegister.Template.asmName}:${varPrevAsmName}\n` }
}
return { SolvedMem: retMemObj, asmCode: '' }
}

Expand Down
Loading

0 comments on commit 27184e1

Please sign in to comment.