Skip to content

Commit

Permalink
Merge pull request #35 from deleterium/feat/inlineFunctions
Browse files Browse the repository at this point in the history
Included new keyword 'inline' to use in functions definitions.
  • Loading branch information
deleterium authored Feb 12, 2024
2 parents 06b1a2c + ccc0c6e commit d46f302
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 5 deletions.
11 changes: 9 additions & 2 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`, `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`, `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 Down Expand Up @@ -45,9 +45,16 @@ The compiler will convert numbers and variables between fixed and long if used i
Same as in C `fixedVariable = (fixed)longVariable`. The compiler will make the transformation. It is also possible to use some built-in functions if the desired transformation is just to copy the value in memory (memcopy) or use in arguments for functions (bcftol). Check chapter 1.5 Built-in functions.

### Functions
As avaliable in C, the developer can make use of functions to make coding easier or reuse code from other projects. There is no need to put function prototypes at the beginning, the function can be used before it is declared, because their definitions are collected a step before the compiling process. Functions arguments and return values are passed using user stack. Recursive functions are allowed but developer must set manually and carefully a new size for "user stack pages" thru preprocessor directives. There are two special functions: `void main()` explained before and `void catch()` explained at **Contract states** topic. It is not obligatory to use them.
As avaliable in C, the developer can make use of functions to make coding easier or reuse code from other projects. There is no need to put function prototypes at the beginning, the function can be used before it is declared, because their definitions are collected a step before the compiling process.

Functions arguments are set by caller and the function returns value thru 'r0', the first auxiliary variable. Recursive functions are allowed but developer must set manually and carefully a new size for "user stack pages" thru preprocessor directives, because they store all functions variables in the stack before recursion.

There are two special functions: `void main()` explained before and `void catch()` explained at **Contract states** topic. It is not obligatory to use them.

Functions can return also arrays and structs; the returning values can be used directly: example `if ( arrFn(a)[2] == 25 )` or `b = structFn(a)->value;`

Inline functions, the ones declared as `inline long myFunction() { code; }`, will be inserted into caller function. This saves instructions to jump subroutine and return from subroutine. It is very useful for functions that are called only once and for small programs. If all functions are inline, the program will not use the 'code stack page', thus reducing the deployment cost by 1 page.

### Built-in functions
From SmartC version 2.0 built-in functions were introduced.
No declaration is needed to use them.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "smartc-signum-compiler",
"version": "9999.9.0",
"version": "9999.9.1",
"description": "C Compiler for smart contracts on Signum network",
"main": "dist/smartc.js",
"types": "dist/smartc.d.ts",
Expand Down
60 changes: 60 additions & 0 deletions src/__tests__/keywords.c.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { SmartC } from '../smartc'

describe('More keywords', () => {
test('should throw: inline wrong usage', () => {
expect(() => {
const code = 'inline long a;'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
}).toThrowError(/^At line/)
})
it('should compile: inline simple', () => {
const code = '#pragma optimizationLevel 0\n long a, b; a = inc(b); inline long inc (long num) { return num+1; }'
const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare a\n^declare b\n^declare inc_num\n\nSET @inc_num $b\n__inline1_start:\nSET @r0 $inc_num\nINC @r0\nSET @r0 $r0\nJMP :__inline1_end\n__inline1_end:\nSET @a $r0\nFIN\n'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
expect(compiler.getAssemblyCode()).toBe(assembly)
})
it('should compile: inline call inside inline function', () => {
const code = '#pragma optimizationLevel 0\nlong a, b; a = inc(b); inline long inc (long num) { return add2(num)-1; } inline long add2(long newnum) { return newnum+2; }'
const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare a\n^declare b\n^declare inc_num\n^declare add2_newnum\n\nSET @inc_num $b\n__inline1_start:\nSET @add2_newnum $inc_num\n__inline2_start:\nSET @r0 $add2_newnum\nINC @r0\nINC @r0\nSET @r0 $r0\nJMP :__inline2_end\n__inline2_end:\nSET @r0 $r0\nDEC @r0\nSET @r0 $r0\nJMP :__inline1_end\n__inline1_end:\nSET @a $r0\nFIN\n'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
expect(compiler.getAssemblyCode()).toBe(assembly)
})
it('should compile: calling two times same inline function', () => {
const code = '#pragma optimizationLevel 0\nlong a, b; a = inc(b); b = inc(a); inline long inc (long num) { if (num) return num+1; return -1; }'
const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare a\n^declare b\n^declare inc_num\n\nSET @inc_num $b\n__inline1_start:\nBZR $inc_num :__if2_endif\n__if2_start:\nSET @r0 $inc_num\nINC @r0\nSET @r0 $r0\nJMP :__inline1_end\n__if2_endif:\nSET @r0 #ffffffffffffffff\nSET @r0 $r0\nJMP :__inline1_end\n__inline1_end:\nSET @a $r0\nSET @inc_num $a\n__inline3_start:\nBZR $inc_num :__if4_endif\n__if4_start:\nSET @r0 $inc_num\nINC @r0\nSET @r0 $r0\nJMP :__inline3_end\n__if4_endif:\nSET @r0 #ffffffffffffffff\nSET @r0 $r0\nJMP :__inline3_end\n__inline3_end:\nSET @b $r0\nFIN\n'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
expect(compiler.getAssemblyCode()).toBe(assembly)
})
it('should compile: inline call inside argument of inline function', () => {
const code = '#pragma optimizationLevel 0\n long a, b; a = inc(1+inc(b)); inline long inc (long num) { if (num) return num+1; return -1; }'
const assembly = '^declare r0\n^declare r1\n^declare r2\n^declare a\n^declare b\n^declare inc_num\n\nSET @inc_num $b\n__inline1_start:\nBZR $inc_num :__if2_endif\n__if2_start:\nSET @r0 $inc_num\nINC @r0\nSET @r0 $r0\nJMP :__inline1_end\n__if2_endif:\nSET @r0 #ffffffffffffffff\nSET @r0 $r0\nJMP :__inline1_end\n__inline1_end:\nSET @a $r0\nINC @a\nSET @inc_num $a\n__inline3_start:\nBZR $inc_num :__if4_endif\n__if4_start:\nSET @r0 $inc_num\nINC @r0\nSET @r0 $r0\nJMP :__inline3_end\n__if4_endif:\nSET @r0 #ffffffffffffffff\nSET @r0 $r0\nJMP :__inline3_end\n__inline3_end:\nSET @a $r0\nFIN\n'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
expect(compiler.getAssemblyCode()).toBe(assembly)
})
test('should throw: inline circular loop', () => {
expect(() => {
const code = '#pragma optimizationLevel 0\n long a, b; a = inc(1); inline long inc (long num) { return add2(num)-1; } inline long add2(long newnum) { return inc(newnum)+1; }'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
}).toThrowError(/^At line/)
})
test('should throw: inline main function', () => {
expect(() => {
const code = '#pragma optimizationLevel 0\n long a, b; inline void main(void) {a = 1/}'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
}).toThrowError(/^At line/)
})
test('should throw: inline catch function', () => {
expect(() => {
const code = '#pragma optimizationLevel 0\n long a, b; a++; inline void catch (void) {a = 1;}'
const compiler = new SmartC({ language: 'C', sourceCode: code })
compiler.compile()
}).toThrowError(/^At line/)
})
})
6 changes: 5 additions & 1 deletion src/codeGenerator/astProcessor/functionSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ export default function functionSolver (
AuxVars.freeRegister(ArgGenObj.SolvedMem.address)
}
// Create instruction
returnAssemblyCode += createSimpleInstruction('Function', FunctionToCall.name)
if (FunctionToCall.isInline) {
returnAssemblyCode += `%inline.${FunctionToCall.name}%\n`
} else {
returnAssemblyCode += createSimpleInstruction('Function', FunctionToCall.name)
}
// Pop return value from stack
if (FunctionToCall.declaration === 'void') {
FnRetObj = utils.createVoidMemObj()
Expand Down
32 changes: 32 additions & 0 deletions src/codeGenerator/codeGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ export default function codeGenerator (Program: CONTRACT) {
}
// For every function:
Program.functions.forEach((currentFunction, index) => {
if (currentFunction.isInline) {
if (currentFunction.name === 'main' || currentFunction.name === 'catch') {
throw new Error(`At line: ${currentFunction.line}.` +
" Functions 'main' and 'catch' cannot be inline.")
}
return
}
GlobalCodeVars.currFunctionIndex = index
writeAsmLine('') // blank line to be nice to debugger!
functionHeaderGenerator()
Expand All @@ -71,6 +78,15 @@ export default function codeGenerator (Program: CONTRACT) {
}
functionTailGenerator()
})
let calls = 0
while (/^%inline\.(\w+)%$/m.test(GlobalCodeVars.assemblyCode)) {
calls++
GlobalCodeVars.assemblyCode = GlobalCodeVars.assemblyCode.replace(/^%inline\.(\w+)%$/m, substituteInlineFunction)
if (calls > 200) {
throw new Error('At line: unknow. Maximum number of inline substitutions. ' +
'Inline cannot be used in recursive functions neither have circular dependency of each other.')
}
}
// Inspect if there were errros and throw now
if (GlobalCodeVars.errors.length !== 0) {
throw new Error(GlobalCodeVars.errors + Program.warnings)
Expand All @@ -82,6 +98,22 @@ export default function codeGenerator (Program: CONTRACT) {
)
}

function substituteInlineFunction (match: string, g1: string) {
GlobalCodeVars.currFunctionIndex = Program.functions.findIndex(fn => fn.name === g1)
const func = Program.functions[GlobalCodeVars.currFunctionIndex]
const inlineId = GlobalCodeVars.getNewJumpID()
const EndOfPreviousCode = GlobalCodeVars.assemblyCode.length
// add code for functions sentences.
if (func.sentences !== undefined) {
func.sentences.forEach(compileSentence)
}
// Function code is in the end of assembly code, it will be substituded in the middle.
const functionCode = GlobalCodeVars.assemblyCode.slice(EndOfPreviousCode)
return `__inline${inlineId}_start:\n` +
functionCode.replace(/RET/g, `JMP :__inline${inlineId}_end`) +
`__inline${inlineId}_end:`
}

function writeAsmLine (lineContent: string, sourceCodeLine: number = 0) {
if (Program.Config.verboseAssembly === true &&
sourceCodeLine !== 0 &&
Expand Down
2 changes: 2 additions & 0 deletions src/codeGenerator/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default {
size: param.length / 16,
declaration: 'long',
isDeclared: true,
isSet: true,
hexContent: param
}
},
Expand All @@ -60,6 +61,7 @@ export default {
scope: '',
size: 0,
declaration: 'void',
isSet: true,
isDeclared: true
}
},
Expand Down
14 changes: 14 additions & 0 deletions src/shaper/shaper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,14 @@ export default function shaper (Program: CONTRACT, tokenAST: TOKEN[]): void {
function splitCode () : void {
Program.Global.code = []
let tokenIndex = 0
let isInline = false
while (tokenIndex < tokenAST.length) {
if (tokenAST[tokenIndex].type === 'Keyword' &&
tokenAST[tokenIndex].value === 'inline') {
tokenIndex++
isInline = true
continue
}
if (tokenAST[tokenIndex].type === 'Keyword' &&
tokenAST[tokenIndex + 1]?.type === 'Variable' &&
tokenAST[tokenIndex + 2]?.type === 'Function' &&
Expand All @@ -81,12 +88,14 @@ export default function shaper (Program: CONTRACT, tokenAST: TOKEN[]): void {
argsMemObj: [],
sentences: [],
declaration: tokenAST[tokenIndex].value as DECLARATION_TYPES,
isInline,
line: tokenAST[tokenIndex + 1].line,
name: tokenAST[tokenIndex + 1].value,
arguments: tokenAST[tokenIndex + 2].params,
code: tokenAST[tokenIndex + 3].params
})
tokenIndex += 4
isInline = false
continue
}
if (tokenAST[tokenIndex].type === 'Keyword' &&
Expand All @@ -99,13 +108,15 @@ export default function shaper (Program: CONTRACT, tokenAST: TOKEN[]): void {
argsMemObj: [],
sentences: [],
declaration: (tokenAST[tokenIndex].value + '_ptr') as DECLARATION_TYPES,
isInline,
typeDefinition: tokenAST[tokenIndex].extValue,
line: tokenAST[tokenIndex + 2].line,
name: tokenAST[tokenIndex + 2].value,
arguments: tokenAST[tokenIndex + 3].params,
code: tokenAST[tokenIndex + 4].params
})
tokenIndex += 5
isInline = false
continue
}
if (tokenAST[tokenIndex].type === 'Macro') {
Expand All @@ -119,6 +130,9 @@ export default function shaper (Program: CONTRACT, tokenAST: TOKEN[]): void {
tokenIndex++
continue
}
if (isInline) {
throw new Error(`At line: ${tokenAST[tokenIndex].line}. Invalid use for inline keyword. Expecting a type and a function definition.`)
}
// Not function neither macro, so it is global statement
Program.Global.code.push(tokenAST[tokenIndex])
tokenIndex++
Expand Down
Loading

0 comments on commit d46f302

Please sign in to comment.