diff --git a/docs/1-Basis.md b/docs/1-Basis.md index 6c4cff7..8cea72e 100644 --- a/docs/1-Basis.md +++ b/docs/1-Basis.md @@ -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. @@ -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. @@ -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! diff --git a/docs/1.2-Preprocessor-directives.md b/docs/1.2-Preprocessor-directives.md index d875b32..c75b043 100644 --- a/docs/1.2-Preprocessor-directives.md +++ b/docs/1.2-Preprocessor-directives.md @@ -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! diff --git a/src/__tests__/keywords.c.spec.ts b/src/__tests__/keywords.c.spec.ts index d66076c..906c1da 100644 --- a/src/__tests__/keywords.c.spec.ts +++ b/src/__tests__/keywords.c.spec.ts @@ -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;' @@ -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) + }) +}) diff --git a/src/__tests__/macros.a.spec.ts b/src/__tests__/macros.a.spec.ts index 7f9fd05..ee3df16 100644 --- a/src/__tests__/macros.a.spec.ts +++ b/src/__tests__/macros.a.spec.ts @@ -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;' diff --git a/src/codeGenerator/astProcessor/binaryAsnProcessor.ts b/src/codeGenerator/astProcessor/binaryAsnProcessor.ts index fbf724b..ba69dd1 100644 --- a/src/codeGenerator/astProcessor/binaryAsnProcessor.ts +++ b/src/codeGenerator/astProcessor/binaryAsnProcessor.ts @@ -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') { @@ -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}. ` + diff --git a/src/codeGenerator/astProcessor/endAsnProcessor.ts b/src/codeGenerator/astProcessor/endAsnProcessor.ts index 8f8227b..1925f8b 100644 --- a/src/codeGenerator/astProcessor/endAsnProcessor.ts +++ b/src/codeGenerator/astProcessor/endAsnProcessor.ts @@ -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' @@ -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: '' } } diff --git a/src/codeGenerator/astProcessor/setupGenCode.ts b/src/codeGenerator/astProcessor/setupGenCode.ts index 82a0db4..1115aa8 100644 --- a/src/codeGenerator/astProcessor/setupGenCode.ts +++ b/src/codeGenerator/astProcessor/setupGenCode.ts @@ -17,7 +17,9 @@ export default function setupGenCode ( isDeclaration: '', isLeftSideOfAssignment: false, isConstSentence: false, + isRegisterSentence: false, hasVoidArray: false, + scopedRegisters: Globals.scopedRegisters, warnings: [], isTemp: auxvarsIsTemp, getNewRegister: auxvarsGetNewRegister, @@ -32,12 +34,18 @@ export default function setupGenCode ( CodeGenInfo.InitialAST = assertNotUndefined(CodeGenInfo.InitialAST) CodeGenInfo.initialIsReversedLogic = CodeGenInfo.initialIsReversedLogic ?? false // Create registers array - AuxVars.memory.filter(OBJ => /^r\d$/.test(OBJ.asmName)).forEach(MEM => { + AuxVars.memory.filter(OBJ => /^r\d$/.test(OBJ.asmName) && OBJ.type === 'register').forEach(MEM => { AuxVars.registerInfo.push({ inUse: false, Template: MEM }) }) + // Mark in use registers (keyword 'register') + AuxVars.registerInfo.forEach(Mem => { + if (AuxVars.scopedRegisters.find(item => item === Mem.Template.asmName)) { + Mem.inUse = true + } + }) const code = genCode(Globals.Program, AuxVars, { RemAST: CodeGenInfo.InitialAST, logicalOp: CodeGenInfo.initialJumpTarget !== undefined, @@ -81,6 +89,10 @@ export default function setupGenCode ( if (id === undefined) { return false } + if (AuxVars.scopedRegisters.find(items => items === id.Template.asmName)) { + // It is a register, but scoped. Do not mess!!! + return false + } return true } @@ -126,6 +138,9 @@ export default function setupGenCode ( if (MemFound === undefined) { throw new Error(`At line: ${line}. Using variable '${varName}' before declaration.`) } + if (MemFound.toBeRegister && MemFound.asmName === '') { + throw new Error(`At line: ${line}. Using variable '${varName}' out of scope!`) + } if (!MemFound.isSet) { detectAndSetNotInitialized(MemFound, line, varDeclaration !== '') } diff --git a/src/codeGenerator/astProcessor/unaryAsnProcessor.ts b/src/codeGenerator/astProcessor/unaryAsnProcessor.ts index 43c59fe..e6a9f06 100644 --- a/src/codeGenerator/astProcessor/unaryAsnProcessor.ts +++ b/src/codeGenerator/astProcessor/unaryAsnProcessor.ts @@ -307,6 +307,9 @@ export default function unaryAsnProcessor ( return sleepKeyProc() case 'sizeof': return sizeofKeyProc() + case 'register': + AuxVars.isRegisterSentence = true + return traverseNotLogical() case 'struct': // nothing to do here return { SolvedMem: utils.createVoidMemObj(), asmCode: '' } diff --git a/src/codeGenerator/codeGenerator.ts b/src/codeGenerator/codeGenerator.ts index 5d5ab57..f7f335b 100644 --- a/src/codeGenerator/codeGenerator.ts +++ b/src/codeGenerator/codeGenerator.ts @@ -1,3 +1,4 @@ +import { assertNotUndefined } from '../repository/repository' import { CONTRACT } from '../typings/contractTypes' import { MEMORY_SLOT, SENTENCES } from '../typings/syntaxTypes' import optimizer from './assemblyProcessor/optimizer' @@ -20,6 +21,7 @@ export default function codeGenerator (Program: CONTRACT) { errors: '', currFunctionIndex: -1, currSourceLine: 0, + scopedRegisters: [], getNewJumpID: function () { // Any changes here, also change function auxvarsGetNewJumpID this.jumpId++ @@ -37,6 +39,38 @@ export default function codeGenerator (Program: CONTRACT) { } return previous }, '') + }, + printFreeRegisters () { + let registers = 'r0' + for (let i = 1; i < Program.Config.maxAuxVars; i++) { + if (this.scopedRegisters.findIndex(item => item === `r${i}`) === -1) { + registers += `,r${i}` + } + } + GlobalCodeVars.assemblyCode += `^comment scope ${registers}\n` + }, + startScope: function (scopeName: string) { + this.scopedRegisters.push(scopeName) + if (Program.Config.verboseScope) { + this.printFreeRegisters() + } + }, + stopScope: function (scopeName: string) { + let liberationNeeded: string + do { + liberationNeeded = assertNotUndefined(this.scopedRegisters.pop(), 'Internal error') + if (/^r\d$/.test(liberationNeeded)) { + const motherMemory = assertNotUndefined(Program.memory.find(obj => + obj.asmName === liberationNeeded && + obj.type !== 'register' + ), 'Internal error') + motherMemory.address = -1 + motherMemory.asmName = '' + } + } while (liberationNeeded !== scopeName) + if (Program.Config.verboseScope) { + this.printFreeRegisters() + } } } @@ -53,7 +87,9 @@ export default function codeGenerator (Program: CONTRACT) { } // Add code for global sentences GlobalCodeVars.currFunctionIndex = -1 + GlobalCodeVars.startScope('global') Program.Global.sentences.forEach(compileSentence) + GlobalCodeVars.stopScope('global') // jump to main function, or program ends. if (Program.functions.find(obj => obj.name === 'main') === undefined) { writeAsmLine('FIN') @@ -74,7 +110,9 @@ export default function codeGenerator (Program: CONTRACT) { functionHeaderGenerator() // add code for functions sentences. if (currentFunction.sentences !== undefined) { + GlobalCodeVars.startScope(currentFunction.name) currentFunction.sentences.forEach(compileSentence) + GlobalCodeVars.stopScope(currentFunction.name) } functionTailGenerator() }) @@ -105,7 +143,9 @@ export default function codeGenerator (Program: CONTRACT) { const EndOfPreviousCode = GlobalCodeVars.assemblyCode.length // add code for functions sentences. if (func.sentences !== undefined) { + GlobalCodeVars.startScope(`inline_${inlineId}`) func.sentences.forEach(compileSentence) + GlobalCodeVars.stopScope(`inline_${inlineId}`) } // Function code is in the end of assembly code, it will be substituded in the middle. const functionCode = GlobalCodeVars.assemblyCode.slice(EndOfPreviousCode) @@ -241,8 +281,10 @@ export default function codeGenerator (Program: CONTRACT) { }, Sentence.line) writeAsmCode(assemblyCode, Sentence.line) writeAsmLine(sentenceID + '_start:') + GlobalCodeVars.startScope(`scope_${sentenceID}`) Sentence.trueBlock.forEach(compileSentence) writeAsmLine(sentenceID + '_endif:') + GlobalCodeVars.stopScope(`scope_${sentenceID}`) break case 'ifElse': sentenceID = '__if' + GlobalCodeVars.getNewJumpID() @@ -253,14 +295,19 @@ export default function codeGenerator (Program: CONTRACT) { }, Sentence.line) writeAsmCode(assemblyCode, Sentence.line) writeAsmLine(sentenceID + '_start:') + GlobalCodeVars.startScope(`scope_${sentenceID}`) Sentence.trueBlock.forEach(compileSentence) + GlobalCodeVars.stopScope(`scope_${sentenceID}`) writeAsmLine('JMP :' + sentenceID + '_endif') writeAsmLine(sentenceID + '_else:') + GlobalCodeVars.startScope(`scope2_${sentenceID}`) Sentence.falseBlock.forEach(compileSentence) writeAsmLine(sentenceID + '_endif:') + GlobalCodeVars.stopScope(`scope2_${sentenceID}`) break case 'while': sentenceID = '__loop' + GlobalCodeVars.getNewJumpID() + GlobalCodeVars.startScope(`scope_${sentenceID}`) writeAsmLine(sentenceID + '_continue:', Sentence.line) assemblyCode = setupGenCode(GlobalCodeVars, { InitialAST: Sentence.ConditionAST, @@ -274,9 +321,11 @@ export default function codeGenerator (Program: CONTRACT) { GlobalCodeVars.latestLoopId.pop() writeAsmLine('JMP :' + sentenceID + '_continue') writeAsmLine(sentenceID + '_break:') + GlobalCodeVars.stopScope(`scope_${sentenceID}`) break case 'do': sentenceID = '__loop' + GlobalCodeVars.getNewJumpID() + GlobalCodeVars.startScope(`scope2_${sentenceID}`) writeAsmLine(sentenceID + '_continue:', Sentence.line) GlobalCodeVars.latestLoopId.push(sentenceID) Sentence.trueBlock.forEach(compileSentence) @@ -289,9 +338,11 @@ export default function codeGenerator (Program: CONTRACT) { }, Sentence.line) writeAsmCode(assemblyCode) writeAsmLine(sentenceID + '_break:') + GlobalCodeVars.stopScope(`scope2_${sentenceID}`) break case 'for': sentenceID = '__loop' + GlobalCodeVars.getNewJumpID() + GlobalCodeVars.startScope(`scope_${sentenceID}`) assemblyCode = setupGenCode(GlobalCodeVars, { InitialAST: Sentence.threeSentences[0].CodeAST }, Sentence.line) @@ -314,9 +365,11 @@ export default function codeGenerator (Program: CONTRACT) { writeAsmCode(assemblyCode, Sentence.line) writeAsmLine('JMP :' + sentenceID + '_condition') writeAsmLine(sentenceID + '_break:') + GlobalCodeVars.stopScope(`scope_${sentenceID}`) break case 'switch': { sentenceID = '__switch' + GlobalCodeVars.getNewJumpID() + GlobalCodeVars.startScope(`scope_${sentenceID}`) let jumpTgt = sentenceID jumpTgt += Sentence.hasDefault ? '_default' : '_break' assemblyCode = setupGenCode(GlobalCodeVars, { @@ -330,6 +383,7 @@ export default function codeGenerator (Program: CONTRACT) { Sentence.block.forEach(compileSentence) GlobalCodeVars.latestLoopId.pop() writeAsmLine(sentenceID + '_break:') + GlobalCodeVars.stopScope(`scope_${sentenceID}`) break } case 'case': diff --git a/src/codeGenerator/codeGeneratorTypes.ts b/src/codeGenerator/codeGeneratorTypes.ts index 6639d56..b1ba7c6 100644 --- a/src/codeGenerator/codeGeneratorTypes.ts +++ b/src/codeGenerator/codeGeneratorTypes.ts @@ -16,12 +16,20 @@ export type GLOBAL_AUXVARS = { currFunctionIndex: number /** Line counter for source code */ currSourceLine: number + /** Handle register allocation and liberation in each scope */ + scopedRegisters: string[] /** Get a new jump id according to current Configs (global scope) */ getNewJumpID(): string /** Query the value of last loop id */ getLatestLoopID(): string /** Query the value of last loop id that is a pure loop (excluding 'switch' ids) */ getLatestPureLoopID(): string + /** Helper for debugger to know what are the free registers. */ + printFreeRegisters(): void + /** Operations to start a new scope for registers */ + startScope(arg :string): void + /** Operations to close a scope for registers */ + stopScope(arg :string): void } export type SETUPGENCODE_ARGS = { @@ -64,10 +72,13 @@ export type GENCODE_AUXVARS = { isLeftSideOfAssignment: boolean /** Flag to inform lower level AST that it is const declaration sentence */ isConstSentence: boolean + /** Flag to inform lower level AST that it is register declaration sentence */ + isRegisterSentence: boolean /** Flag to inform lower level AST that there are an void array assignment */ hasVoidArray: boolean /** Warnings found */ warnings: string[] + scopedRegisters: string[] /** Verifies if a variable at loc address is register or temporary reused var */ isTemp(loc: number): boolean /** Get a new register variable */ diff --git a/src/shaper/memoryProcessor.ts b/src/shaper/memoryProcessor.ts index 251a877..85f7c8b 100644 --- a/src/shaper/memoryProcessor.ts +++ b/src/shaper/memoryProcessor.ts @@ -19,6 +19,7 @@ export default function memoryProcessor ( programTD: TYPE_DEFINITIONS[], AuxVars: SHAPER_AUXVARS, phraseCode: TOKEN [], structPrefix: string = '' ): MEMORY_SLOT[] { let tokenCounter = 0 + let isRegister = false type LFV = 'long'|'fixed'|'void' @@ -39,6 +40,13 @@ export default function memoryProcessor ( case 'struct': retMem.push(...structProcessControl()) break + case 'register': + if (AuxVars.isFunctionArgument) { + throw new Error(`At line: ${phraseCode[tokenCounter].line}. Arguments for functions cannot be register type.`) + } + tokenCounter++ + isRegister = true + break default: tokenCounter++ } @@ -107,11 +115,15 @@ export default function memoryProcessor ( } header.isDeclared = AuxVars.isFunctionArgument header.isSet = AuxVars.isFunctionArgument + header.toBeRegister = isRegister // If is not an array, just send the header if (dimensions.length === 0) { return [header] } // But if it IS an array, update header + if (isRegister) { + throw new Error(`At line: ${phraseCode[tokenCounter].line}. 'register' modifier on arrays is not implemented.`) + } header.type = 'array' header.typeDefinition = structPrefix + header.asmName header.ArrayItem = { diff --git a/src/shaper/shaper.ts b/src/shaper/shaper.ts index d8ea700..d70973e 100644 --- a/src/shaper/shaper.ts +++ b/src/shaper/shaper.ts @@ -84,6 +84,7 @@ export default function shaper (Program: CONTRACT, tokenAST: TOKEN[]): void { throw new Error(`At line: ${tokenAST[tokenIndex].line}.` + ' Function returning a struct currently not implemented.') } + validateFunctionReturnType(tokenAST[tokenIndex]) Program.functions.push({ argsMemObj: [], sentences: [], @@ -104,6 +105,7 @@ export default function shaper (Program: CONTRACT, tokenAST: TOKEN[]): void { tokenAST[tokenIndex + 2]?.type === 'Variable' && tokenAST[tokenIndex + 3]?.type === 'Function' && tokenAST[tokenIndex + 4]?.type === 'CodeDomain') { + validateFunctionReturnType(tokenAST[tokenIndex]) Program.functions.push({ argsMemObj: [], sentences: [], @@ -139,6 +141,17 @@ export default function shaper (Program: CONTRACT, tokenAST: TOKEN[]): void { } } + function validateFunctionReturnType (Tkn: TOKEN) { + switch (Tkn.value) { + case 'void': + case 'long': + case 'fixed': + case 'struct': + return + } + throw new Error(`At line: ${Tkn.line}. Invalid function declaration type. Expecting 'void', 'long', 'fixed' or 'struct'`) + } + /** Reads/verifies one macro token and add it into Program.Config object */ function processMacroControl (Token: SC_MACRO) : void { let boolVal: boolean | undefined @@ -222,6 +235,9 @@ export default function shaper (Program: CONTRACT, tokenAST: TOKEN[]): void { case 'verboseAssembly': Program.Config.verboseAssembly = bool return true + case 'verboseScope': + Program.Config.verboseScope = bool + return true default: throw new Error(`At line: ${MacroToken.line}.` + ` Unknow macro property: '#${MacroToken.type} ${MacroToken.property}'.` + @@ -516,6 +532,14 @@ export default function shaper (Program: CONTRACT, tokenAST: TOKEN[]): void { case 'label': return default: + if (CurrMem.toBeRegister) { + // do not allocate variables modified as register + if (Program.Config.verboseAssembly) { + // It will be needed by Simulator + Program.Config.verboseScope = true + } + return + } CurrMem.address = memoryAddress memoryAddress++ } diff --git a/src/shaper/templates.ts b/src/shaper/templates.ts index c165830..2a78af4 100644 --- a/src/shaper/templates.ts +++ b/src/shaper/templates.ts @@ -69,6 +69,7 @@ const longArg : MEMORY_SLOT = { scope: 'dummy', declaration: 'long', isSet: false, + toBeRegister: false, size: 1, isDeclared: true } @@ -81,6 +82,7 @@ const longPtrArg : MEMORY_SLOT = { scope: 'dummy', declaration: 'long_ptr', isSet: false, + toBeRegister: false, size: 1, isDeclared: true } @@ -93,6 +95,7 @@ const fixedArg : MEMORY_SLOT = { scope: 'dummy', declaration: 'fixed', isSet: false, + toBeRegister: false, size: 1, isDeclared: true } @@ -102,6 +105,7 @@ export const autoCounterTemplate : MEMORY_SLOT = { asmName: '_counterTimestamp', declaration: 'long', isDeclared: true, + toBeRegister: false, isSet: false, name: '_counterTimestamp', scope: '', @@ -148,6 +152,7 @@ export const BuiltInTemplate: SC_FUNCTION[] = [ scope: 'memcopy', declaration: 'void_ptr', isSet: false, + toBeRegister: false, size: 1, isDeclared: true }, @@ -159,6 +164,7 @@ export const BuiltInTemplate: SC_FUNCTION[] = [ scope: 'memcopy', declaration: 'void_ptr', isSet: false, + toBeRegister: false, size: 1, isDeclared: true } @@ -622,6 +628,7 @@ export function getMemoryTemplate (memType: MEMORY_BASE_TYPES) : MEMORY_SLOT { isDeclared: false, declaration: '', isSet: false, + toBeRegister: false, address: -1, name: '', scope: '', @@ -635,6 +642,7 @@ export const fixedBaseTemplate : MEMORY_SLOT = { declaration: 'fixed', isDeclared: false, isSet: true, + toBeRegister: false, name: 'f100000000', hexContent: '0000000005f5e100', scope: '', diff --git a/src/smartc.ts b/src/smartc.ts index d0f34ce..dd72264 100644 --- a/src/smartc.ts +++ b/src/smartc.ts @@ -66,7 +66,8 @@ export class SmartC { PUserStackPages: 0, PCodeStackPages: 0, PCodeHashId: '', - verboseAssembly: false + verboseAssembly: false, + verboseScope: false }, warnings: [] } diff --git a/src/syntaxProcessor/createTree.ts b/src/syntaxProcessor/createTree.ts index 257891d..bad7e02 100644 --- a/src/syntaxProcessor/createTree.ts +++ b/src/syntaxProcessor/createTree.ts @@ -242,12 +242,32 @@ function KeywordToAST (tokens: TOKEN[], keywordLoc: number) : AST { Operation: tokens[0], Center: createTree(tokens.slice(1)) } + case 'register': + if (tokens.length === 1) { + throw new Error(`At line: ${tokens[0].line}. Missing the variable type for 'register' use.`) + } + validateRegisterNextToken(tokens[1]) + return { + type: 'unaryASN', + Operation: tokens[0], + Center: createTree(tokens.slice(1)) + } default: // Never throw new Error(`Internal error at line: ${tokens[0].line}. Keyword '${tokens[0].value}' shown up.`) } } +function validateRegisterNextToken (nextToken: TOKEN) { + if (nextToken.type !== 'Keyword') { + throw new Error(`At line: ${nextToken.line}. Missing the variable type for 'register' use.`) + } + if (nextToken.value !== 'long' && nextToken.value !== 'fixed' && nextToken.value !== 'void') { + throw new Error(`At line: ${nextToken.line}. 'registers' can be only types: 'long', 'fixed' or 'void'. ` + + `Found '${nextToken.value}'.`) + } +} + function UnaryOperatorToAST (tokens: TOKEN[], operatorLoc: number) : AST { if (operatorLoc !== 0) { throw new Error(`At line: ${tokens[operatorLoc].line}.` + diff --git a/src/tokenizer/tokenizerV3.ts b/src/tokenizer/tokenizerV3.ts index 1bd4f3d..e0c5b1f 100644 --- a/src/tokenizer/tokenizerV3.ts +++ b/src/tokenizer/tokenizerV3.ts @@ -73,7 +73,7 @@ export default function tokenizer (inputSourceCode: string): PRE_TOKEN[] { /* Not all here, just the easy */ const easyKeywordTokens = [ 'break', 'case', 'const', 'continue', 'default', 'do', 'else', 'exit', 'fixed', 'for', 'goto', - 'halt', 'if', 'inline', 'long', 'return', 'sleep', 'sizeof', 'switch', 'void', 'while' + 'halt', 'if', 'inline', 'long', 'register', 'return', 'sleep', 'sizeof', 'switch', 'void', 'while' ] function tokenizerMain () { diff --git a/src/typings/contractTypes.ts b/src/typings/contractTypes.ts index a7b8ce2..799f8ce 100644 --- a/src/typings/contractTypes.ts +++ b/src/typings/contractTypes.ts @@ -33,6 +33,8 @@ export type SC_CONFIG = { PCodeHashId: string, /** Adds a comment in generated assembly code with source code line number and content */ verboseAssembly: boolean, + /** Adds a comment when new scope start/ends with the free registers, and warns when one register is locked */ + verboseScope: boolean, } export type SC_MACRO = { diff --git a/src/typings/syntaxTypes.ts b/src/typings/syntaxTypes.ts index bd1d720..5c620c8 100644 --- a/src/typings/syntaxTypes.ts +++ b/src/typings/syntaxTypes.ts @@ -66,6 +66,8 @@ export type MEMORY_SLOT = { declaration: DECLARATION_TYPES /** Control warning if using variables before setting it. */ isSet: boolean + /** Control if a specific register to be used in this variable */ + toBeRegister: boolean /** Offset in memory. -1 if this slot is not in memory */ address: number /** Variable name */