diff --git a/notebook/src/hooks/repl-context.tsx b/notebook/src/hooks/repl-context.tsx index 330717a..a4c5aef 100644 --- a/notebook/src/hooks/repl-context.tsx +++ b/notebook/src/hooks/repl-context.tsx @@ -56,7 +56,15 @@ export default function ReplProvider({children}: {children: ReactNode}) { const dram = useMemory('DRAM') const bluetooth = useRef(new Bluetooth()) - let onExecutionComplete = useRef((executionTime: number) => {}) + + // To use these variables in callbacks + const latestCellRef = useRef(latestCell) + latestCellRef.current = latestCell + const iramRef = useRef(iram) + const dramRef = useRef(dram) + iramRef.current = iram + dramRef.current = dram + useEffect(() => { bluetooth.current.setNotificationHandler(onReceiveNotification); @@ -75,10 +83,9 @@ export default function ReplProvider({children}: {children: ReactNode}) { } const executeLatestCell = async () => { - let updatedLatestCell:CellT = {...latestCell, compileError: '', state: 'compiling'} - setLatestCell(updatedLatestCell) + setLatestCell({...latestCell, compileError: '', state: 'compiling'}) try { - const compileResult = useJIT ? await network.compileWithProfiling(updatedLatestCell.code) : await network.compile(updatedLatestCell.code) + const compileResult = useJIT ? await network.compileWithProfiling(latestCell.code) : await network.compile(latestCell.code) const iramBuffer = Buffer.from(compileResult.iram.data, "hex") const dramBuffer = Buffer.from(compileResult.dram.data, "hex") const flashBuffer = Buffer.from(compileResult.flash.data, "hex") @@ -89,26 +96,12 @@ export default function ReplProvider({children}: {children: ReactNode}) { .loadToFlash(compileResult.flash.address, flashBuffer) .jump(compileResult.entryPoint) .generate() - updatedLatestCell = {...updatedLatestCell, state: 'sending'} - setLatestCell(updatedLatestCell) + setLatestCell({...latestCell, compileError: '', state: 'sending'}) iram.actions.setUsedSegment(compileResult.iram.address, iramBuffer.length) dram.actions.setUsedSegment(compileResult.dram.address, dramBuffer.length) const bluetoothTime = await bluetooth.current.sendBuffers(bytecodeBuffer) const compileTime = compileResult.compileTime - updatedLatestCell = {...updatedLatestCell, state: 'executing', time: {compile: compileTime, bluetooth: bluetoothTime}} - setLatestCell(updatedLatestCell) - onExecutionComplete.current = (executionTime: number) => { - if (updatedLatestCell.time !== undefined && updatedLatestCell.time?.execution === undefined) { - updatedLatestCell.time.execution = executionTime - updatedLatestCell.state = 'done' - const nextCellId = updatedLatestCell.id + 1 - setPostExecutionCells((cells) => - [...cells, updatedLatestCell] - ) - setLatestCell({id: nextCellId, code: '', state: 'user-writing'}) - } - - } + setLatestCell({...latestCell, state: 'executing', time: {compile: compileTime, bluetooth: bluetoothTime}}) } catch (error: any) { if (error instanceof CompileError) { setLatestCell({...latestCell, state: 'user-writing', compileError: error.toString()}) @@ -145,13 +138,29 @@ export default function ReplProvider({children}: {children: ReactNode}) { }); } + const onExecutionComplete = (executionTime: number) => { + if (latestCellRef.current.time !== undefined && latestCellRef.current.time?.execution === undefined) { + latestCellRef.current.time.execution = executionTime + latestCellRef.current.state = 'done' + const nextCellId = latestCellRef.current.id + 1 + setPostExecutionCells((cells) => + [...cells, latestCellRef.current] + ) + setLatestCell({id: nextCellId, code: '', state: 'user-writing'}) + } + } + const jitCompile = (fid: number, paramtypes: string[]) => { network.jitCompile(fid, paramtypes).then((compileResult) => { console.log(compileResult) + const iramBuffer = Buffer.from(compileResult.iram.data, "hex") + const dramBuffer = Buffer.from(compileResult.dram.data, "hex") + iramRef.current.actions.setUsedSegment(compileResult.iram.address, iramBuffer.length) + dramRef.current.actions.setUsedSegment(compileResult.dram.address, dramBuffer.length) const bytecodeBuffer = new BytecodeBufferBuilder(MAX_MTU) - .loadToRAM(compileResult.iram.address, Buffer.from(compileResult.iram.data, "hex")) - .loadToRAM(compileResult.dram.address, Buffer.from(compileResult.dram.data, "hex")) + .loadToRAM(compileResult.iram.address, iramBuffer) + .loadToRAM(compileResult.dram.address, dramBuffer) .loadToFlash(compileResult.flash.address, Buffer.from(compileResult.flash.data, "hex")) .jump(compileResult.entryPoint) .generate() @@ -175,7 +184,7 @@ export default function ReplProvider({children}: {children: ReactNode}) { onDeviceResetComplete(parseResult.meminfo) break; case BYTECODE.RESULT_EXECTIME: - onExecutionComplete.current(parseResult.exectime) + onExecutionComplete(parseResult.exectime) break; case BYTECODE.RESULT_PROFILE: { console.log("receive profile", parseResult.fid, parseResult.paramtypes); @@ -186,7 +195,6 @@ export default function ReplProvider({children}: {children: ReactNode}) { } } - return ( {}, + reset: (size: number, address: number) => {}, setUsedSegment: (start: number, size: number) => {}, } } diff --git a/server/src/jit/jit-code-generator.ts b/server/src/jit/jit-code-generator.ts index 8dc149a..5bada34 100644 --- a/server/src/jit/jit-code-generator.ts +++ b/server/src/jit/jit-code-generator.ts @@ -72,7 +72,7 @@ function specializedFunctionBodyName(name: string) { // returns '(' or '(' // '(' is returned if the type cannot be checked. -function checkType(type?: StaticType) { +function checkType(env: VariableEnv, type?: StaticType): string|undefined { if (type instanceof ArrayType) { if (type.elementType === Integer) return 'gc_is_intarray('; @@ -83,7 +83,7 @@ function checkType(type?: StaticType) { if (type.elementType === Any) return 'gc_is_anyarray('; else - throw new JITCompileError('Unknown array type.'); + return `gc_is_instance_of(&${env.useArrayType(type)[0]}.clazz, `; } if (type instanceof InstanceType) { @@ -98,13 +98,12 @@ function checkType(type?: StaticType) { case BooleanT: return 'is_bool_value('; case StringT: - return `gc_is_string_object(`; + return 'gc_is_string_object('; default: return undefined; } } - export class JitCodeGenerator extends CodeGenerator{ private profiler: Profiler; private bsSrc: string[]; @@ -250,6 +249,9 @@ export class JitCodeGenerator extends CodeGenerator{ this.result.write(') {') this.result.right().nl() + // For test + this.result.write(`#ifdef TEST64`).nl().write('puts("Execute specialized function");').nl().write('#endif').nl() + this.result.write('return '); this.functionCall(node, fenv, specializedFuncName, specializedType, funcType.paramTypes, 'self') @@ -257,6 +259,9 @@ export class JitCodeGenerator extends CodeGenerator{ this.result.write('} else {') this.result.right().nl() + // For test + this.result.write(`#ifdef TEST64`).nl().write('puts("Execute original function");').nl().write('#endif').nl() + this.result.write('return '); this.functionCall(node, fenv, originalFuncName, funcType, funcType.paramTypes, 'self') this.signatures += this.makeFunctionStruct(originalFuncName, funcType, false) @@ -310,7 +315,7 @@ export class JitCodeGenerator extends CodeGenerator{ const paramName = (node.params[i] as AST.Identifier).name const info = fenv.table.lookup(paramName) if (info !== undefined) { - const check = checkType(targetParamTypes[i]); + const check = checkType(fenv, targetParamTypes[i]); if (check) { const name = info.transpiledName(paramName) paramSig.push(`${check}${name})`); diff --git a/server/src/jit/utils.ts b/server/src/jit/utils.ts index dbbaa20..520c5e8 100644 --- a/server/src/jit/utils.ts +++ b/server/src/jit/utils.ts @@ -30,8 +30,8 @@ export function typeStringToStaticType(typeString: string, gvnt?: GlobalVariable return new ArrayType('float') } else if (typeString === 'Array') { return new ArrayType('boolean') - } else if (typeString === 'Array') { - return 'any' + } else if (isArray(typeString)) { + return getArrayType(typeString, gvnt) } else if (typeString === 'Function') { return 'any' } else { @@ -42,6 +42,30 @@ export function typeStringToStaticType(typeString: string, gvnt?: GlobalVariable } } +function isArray(typeString: string) { + return /\[\]$/.test(typeString) +} + +function getArrayType(typeString: string, gvnt?: GlobalVariableNameTable):StaticType { + const matches = typeString.match(/(\[\])+$/); + let ndim = matches ? matches[0].length / 2 : 0; + const className = typeString.replace(/(\[\])+$/, ""); + let arr: StaticType|undefined; + if (className === 'string') + arr = 'string' + else { + arr = gvnt === undefined ? undefined : gvnt.lookup(className)?.type + if (arr === undefined || !(arr instanceof InstanceType)) + throw new ProfileError(`Cannot find the profiled class: ${className}`) + } + + while (ndim > 0) { + arr = new ArrayType(arr) + ndim -= 1 + } + return arr +} + function staticTypeToTSType(type: StaticType): AST.TSType { if (type === Integer || type === Float) return tsTypeReference(identifier(type)); @@ -76,7 +100,6 @@ function staticTypeToTSType(type: StaticType): AST.TSType { return tsAnyKeyword(); } - function staticTypeToNode(type: StaticType):AST.TSTypeAnnotation { return tsTypeAnnotation(staticTypeToTSType(type)); } diff --git a/server/src/server/session.ts b/server/src/server/session.ts index 2960a84..ce8939c 100644 --- a/server/src/server/session.ts +++ b/server/src/server/session.ts @@ -61,7 +61,6 @@ export default class Session { public executeWithProfiling(tsString: string) { this.currentCodeId += 1; - const ast = runBabelParser(tsString, 1) const codeGenerator = (initializerName: string, codeId: number, moduleId: number) => { return new JitCodeGenerator(initializerName, codeId, moduleId, this.profiler, tsString); @@ -72,7 +71,10 @@ export default class Session { } const start = performance.now(); + // Transpile + const ast = runBabelParser(tsString, 1); + convertAst(ast, this.profiler); const tResult = jitTranspile(this.currentCodeId, ast, typeChecker, codeGenerator, this.nameTable, undefined) const entryPointName = tResult.main; const cString = cProlog + tResult.code; @@ -105,11 +107,9 @@ export default class Session { } // Transpile - const ast = runBabelParser(func.src, 1); const start = performance.now(); - + const ast = runBabelParser(func.src, 1); convertAst(ast, this.profiler); - // const tResult = transpile(0, func.src, this.nameTable, undefined, -1, ast, codeGenerator); const tResult = jitTranspile(this.currentCodeId, ast, typeChecker, codeGenerator, this.nameTable, undefined) const entryPointName = tResult.main; const cString = cProlog + tResult.code; diff --git a/server/tests/jit/jit-code-generator.test.ts b/server/tests/jit/jit-code-generator.test.ts index 0f18bc2..16e263e 100644 --- a/server/tests/jit/jit-code-generator.test.ts +++ b/server/tests/jit/jit-code-generator.test.ts @@ -140,6 +140,41 @@ print(area(rect)); .toEqual("Rectangle, undefined, undefined, undefined\n44\n") }) +test('profile: class array', () => { + const src = ` +class Rectangle { + x: integer + y: integer + + constructor(x:integer, y:integer) { + this.x = x; + this.y = y; + } +} + +function areas(rects) { + let sum = 0 + for (let i = 0; i < rects.length; i++) { + sum += rects[i].x * rects[i].y + } + return sum +} + +let rects = [new Rectangle(11, 4), new Rectangle(11, 4), new Rectangle(11, 4)] +for(let i = 0; i < 15; i++) { + areas(rects); +} +print(areas(rects)); + ` + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src, profiler, file1, result0.names) + expect(execute([file0, file1], [result1.main], tempCFilePath('file2'), tempExecutableFilePath('bscript'))) + .toEqual("Rectangle[], undefined, undefined, undefined\n132\n") +}) + test('profile: not profile function with function return type', () => { const src = ` function func(a:()=>integer) { @@ -253,7 +288,7 @@ print(add(1, 5.5, false)) const func = profiler.getFunctionProfileById(0); if (func === undefined) throw new Error(`Cannot fine func.`) - profiler.setFuncSpecializedType(0, ["integer", "integer", "undefined", "undefined"].map(t => typeStringToStaticType(t, result1.names))) + profiler.setFuncSpecializedType(0, ["integer", "float", "boolean", "undefined"].map(t => typeStringToStaticType(t, result1.names))) const file2 = tempCFilePath('file2') const result2 = compile(1, func.src, profiler, file2, result1.names) @@ -261,7 +296,7 @@ print(add(1, 5.5, false)) const file3 = tempCFilePath('file3') const result3 = compile(2, src3, profiler, file3, result2.names) expect(execute([file0, file1, file2, file3], [result1.main, result2.main, result3.main], tempCFilePath('file4'), tempExecutableFilePath('bscript'))) - .toEqual(`5.400000\n12.000000\n`) + .toEqual(`5.400000\nExecute specialized function\n12.000000\n`) }) test('jit compile: string', () => { @@ -294,7 +329,7 @@ printStr("world"); const file3 = tempCFilePath('file3') const result3 = compile(2, src3, profiler, file3, result2.names) expect(execute([file0, file1, file2, file3], [result1.main, result2.main, result3.main], tempCFilePath('file4'), tempExecutableFilePath('bscript'))) - .toEqual(`hello\nworld\n`) + .toEqual(`hello\nExecute specialized function\nworld\n`) }) test('jit compile: intarray, floatarray, boolarray', () => { @@ -327,7 +362,7 @@ print(add0([1, 3], [1.1, 4.4], [false, false])); const file3 = tempCFilePath('file3') const result3 = compile(2, src3, profiler, file3, result2.names) expect(execute([file0, file1, file2, file3], [result1.main, result2.main, result3.main], tempCFilePath('file4'), tempExecutableFilePath('bscript'))) - .toEqual(`2.100000\n3.200000\n`) + .toEqual(`2.100000\nExecute specialized function\n3.200000\n`) }) test('jit compile: anyarray, array', () => { @@ -355,7 +390,7 @@ print(aarr0(aarr, arr)); const func = profiler.getFunctionProfileById(0); if (func === undefined) throw new Error(`Cannot fined func.`) - profiler.setFuncSpecializedType(0, ["Array", "Array", "undefined", "undefined"].map(t => typeStringToStaticType(t, result1.names))) + profiler.setFuncSpecializedType(0, ["Array", "string[]", "undefined", "undefined"].map(t => typeStringToStaticType(t, result1.names))) const file2 = tempCFilePath('file2') const result2 = compile(1, func.src, profiler, file2, result1.names) @@ -363,7 +398,7 @@ print(aarr0(aarr, arr)); const file3 = tempCFilePath('file3') const result3 = compile(2, src3, profiler, file3, result2.names) expect(execute([file0, file1, file2, file3], [result1.main, result2.main, result3.main], tempCFilePath('file4'), tempExecutableFilePath('bscript'))) - .toEqual(`1\n1\n`) + .toEqual(`1\nExecute specialized function\n1\n`) }) test('jit compile: class', () => { @@ -407,7 +442,55 @@ print(area(new Rectangle(3, 4))) const file3 = tempCFilePath('file3') const result3 = compile(2, src3, profiler, file3, result2.names) expect(execute([file0, file1, file2, file3], [result1.main, result2.main, result3.main], tempCFilePath('file4'), tempExecutableFilePath('bscript'))) - .toEqual(`12\n12\n`) + .toEqual(`12\nExecute specialized function\n12\n`) +}) + +test('jit compile: class array', () => { + const src1 = ` + +class Rectangle { + x: integer + y: integer + + constructor(x:integer, y:integer) { + this.x = x; + this.y = y; + } +} + +function areas(rects) { + let sum = 0 + for (let i = 0; i < rects.length; i++) { + sum += rects[i].x * rects[i].y + } + return sum +} + +print(areas([new Rectangle(11, 4), new Rectangle(11, 4), new Rectangle(11, 4)])) + ` + + const src3 = ` +print(areas([new Rectangle(11, 4), new Rectangle(11, 4), new Rectangle(11, 4)])) + ` + + const profiler = new Profiler() + const file0 = tempCFilePath('file0') + const result0 = initialCompile(file0); + const file1 = tempCFilePath('file1') + const result1 = compile(0, src1, profiler, file1, result0.names) + + const func = profiler.getFunctionProfileById(0); + if (func === undefined) + throw new Error(`Cannot fined func.`) + profiler.setFuncSpecializedType(0, ["Rectangle[]", "undefined", "undefined", "undefined"].map(t => typeStringToStaticType(t, result1.names))) + + const file2 = tempCFilePath('file2') + const result2 = compile(1, func.src, profiler, file2, result1.names) + + const file3 = tempCFilePath('file3') + const result3 = compile(2, src3, profiler, file3, result2.names) + expect(execute([file0, file1, file2, file3], [result1.main, result2.main, result3.main], tempCFilePath('file4'), tempExecutableFilePath('bscript'))) + .toEqual(`132\nExecute specialized function\n132\n`) }) test('jit compile: function redefinition after jit compile', () => { @@ -450,5 +533,5 @@ print(add(4, 5)) const file4 = tempCFilePath('file4') const result4 = compile(3, src4, profiler, file4, result3.names) expect(execute([file0, file1, file2, file3, file4], [result1.main, result2.main, result3.main, result4.main], tempCFilePath('file5'), tempExecutableFilePath('bscript'))) - .toEqual(`9\n9\n11\n`) + .toEqual(`9\nExecute specialized function\n9\nExecute specialized function\n11\n`) }) \ No newline at end of file diff --git a/server/tests/jit/test-jit-utils.ts b/server/tests/jit/test-jit-utils.ts index 37868c7..efae49a 100644 --- a/server/tests/jit/test-jit-utils.ts +++ b/server/tests/jit/test-jit-utils.ts @@ -20,6 +20,7 @@ function performance_now(): integer { return 0 } const prologCcode = `/* To compile, cc -DTEST64 this_file.c c-runtime.c */ #include "../../microcontroller/core/include/c-runtime.h" #include "../../microcontroller/core/include/profiler.h" +#include ` const prologCcode2 = ` diff --git a/server/tests/transpiler/transpile.test.ts b/server/tests/transpiler/transpile.test.ts index 52abfe5..03a51af 100644 --- a/server/tests/transpiler/transpile.test.ts +++ b/server/tests/transpiler/transpile.test.ts @@ -10,17 +10,7 @@ test("transpile", () => { }) const bsSrc = ` -class GPIO { - pinNum:integer; - - constructor(pinNum: integer) { - this.pinNum = pinNum; - } - - set(level: integer) { - this.pinNum + 1 - } -} +let sarr: string[] = ['hello'] ` test("playground", () => {