diff --git a/.gitignore b/.gitignore index 76f7136..e586fc2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,6 @@ lib*.a embedder -*_n.c \ No newline at end of file +*_n.c + +profiling-results.txt \ No newline at end of file diff --git a/README.md b/README.md index bf8a103..c99f9a8 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,10 @@ The `noja` executable will be generated, which is a CLI that runs Noja code. ## Usage You can run files by doing: ```sh -location/of/noja run +location/of/noja ``` or you can run strings by doing: ```sh -location/of/noja run inline +location/of/noja -i ``` diff --git a/examples/http_server/router.noja b/examples/http_server/router.noja index 65f93ae..a73dbe8 100644 --- a/examples/http_server/router.noja +++ b/examples/http_server/router.noja @@ -5,6 +5,8 @@ Router = {table: Map, plug: Callable, solve: Callable}; fun loadFile(path: String) { + print("path=", path, "\n"); + stream, error = files.openFile(path, files.READ); if error != none: return none, error; @@ -83,8 +85,8 @@ fun new() { if isCallable(method_item): res = method_item(req); else { - cwd = getCurrentWorkingDirectory(); - file = path.join(cwd, method_item); + csd = getCurrentScriptDirectory(); + file = path.join(csd, method_item); data, error = loadFile(file); if error == none: res = respond(200, data); diff --git a/examples/json.noja b/examples/json.noja index e52288f..d7936b5 100644 --- a/examples/json.noja +++ b/examples/json.noja @@ -251,6 +251,43 @@ fun parse(src: String) { return val, err; } +fun readFile(name: String) { + stream, error = files.openFile(name, files.READ); + if error != none: + return error; + + text = ""; + size = 256; + do { + + size = size * 2; + buff = buffer.new(size); + + read_bytes, error = files.read(stream, buff); + if error != none: + return error; + + text2, error = buffer.toString(buff); + if error != none: + return none, error; + assert(text2 != none); + + text = string.cat(text, text2); + + } while count(buff) == read_bytes; + + return text; +} + +fun parseFile(name: String) { + text, error = readFile(name); + if error != none: + return none, error; + assert(text != none); + result, error = parse(text); + return result, error; +} + fun test() { compareAny = import("compare.noja").compareAny; @@ -330,4 +367,4 @@ fun test() { print(" total: ", total, "\n"); } -return {parse: parse, test: test}; \ No newline at end of file +return {parse: parse, parseFile: parseFile, test: test}; \ No newline at end of file diff --git a/examples/scan.noja b/examples/scan.noja index 96d594c..8e87db4 100644 --- a/examples/scan.noja +++ b/examples/scan.noja @@ -8,13 +8,13 @@ Scanner = { consumeSpaces: Callable }; +fun isSpace(c: String) + return c == ' ' + or c == '\t' + or c == '\n'; + fun newScanner(src: String) { - fun isSpace(c: String) - return c == ' ' - or c == '\t' - or c == '\n'; - scan = { src: src, i: 0, diff --git a/src/cli/main.c b/src/cli/main.c index c58d9ab..4cf18d9 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -27,114 +27,170 @@ ** | with The Noja Interpreter. If not, see . | ** +--------------------------------------------------------------------------+ */ - +#include #include #include #include -#include +#include #include "../lib/noja.h" -static const char usage[] = - "Usage patterns:\n" - " $ noja run file.noja\n" - " $ noja run inline \"print('some noja code');\"\n" - " $ noja dis file.noja\n" - " $ noja dis inline \"print('some noja code');\"\n" - " $ noja asm file.noja\n" - " $ noja asm inline \"PUSHINT 5; PUSHFLT 1.0; ADD; POP 1;\"\n"; +static void usage(FILE *stream, const char *name) +{ + fprintf(stream, + "USAGE\n" + " $ %s [-h | -o | -p | -H | {-d | -a}] [--] \n", name); +} + +static void help(FILE *stream, const char *name) +{ + usage(stream, name); + fprintf(stream, + "OPTIONS\n" + " -h, --help Show this message\n" + " -d, --disassembly Output the bytecode associated to noja code\n" + " -i, --inline Execute a string of code instead of a file\n" + " -a, --assembly Specify that the source is bytecode and not noja code\n" + " -p, --profile Profile the execution of the source (can't be used with -d)\n" + " -o, --output Specify the output file of -p or -d\n" + " -H, --heap Heap size\n" + "\n"); +} + +typedef enum { + Mode_DISASSEMBLy, + Mode_ASSEMBLY, + Mode_DEFAULT, + Mode_HELP, +} Mode; int main(int argc, char **argv) { - assert(argc > 0); + Mode mode = Mode_DEFAULT; + bool profile = false; + bool no_file = false; + const char *output = NULL; + const char *input = NULL; + size_t heap = 1024 * 1024; + + for (int i = 1; i < argc; i++) { + + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { - if(argc == 1) - { - // $ noja - fprintf(stderr, "Error: Incorrect usage.\n\n"); - fprintf(stderr, usage); - return -1; - } + mode = Mode_HELP; + + } else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--disassembly")) { - if(!strcmp(argv[1], "run")) - { - if(argc == 2) - { - fprintf(stderr, "Error: Missing source file.\n"); - return -1; - } + mode = Mode_DISASSEMBLy; - _Bool r; + } else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--inline")) { + + no_file = true; - if(!strcmp(argv[2], "inline")) - { - if(argc == 3) - { - fprintf(stderr, "Error: Missing source string.\n"); + } else if (!strcmp(argv[i], "-a") || !strcmp(argv[i], "--assembly")) { + + mode = Mode_ASSEMBLY; + + } else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--profile")) { + + profile = true; + + } else if (!strcmp(argv[i], "-o") || !strcmp(argv[i], "--output")) { + + if (i+1 == argc || argv[i+1][0] == '-') { + fprintf(stderr, "Missing file path after %s option\n", argv[i]); + usage(stderr, argv[0]); return -1; } - r = NOJA_runString(argv[3]); - } - else - r = NOJA_runFile(argv[2]); - return r ? 0 : -1; - } + output = argv[++i]; - if(!strcmp(argv[1], "asm")) - { - if(argc == 2) - { - fprintf(stderr, "Error: Missing source file.\n"); - return -1; - } + } else if (!strcmp(argv[i], "-H") || !strcmp(argv[i], "--heap")) { - _Bool r; - - if(!strcmp(argv[2], "inline")) - { - if(argc == 3) - { - fprintf(stderr, "Error: Missing source string.\n"); + if (i+1 == argc || argv[i+1][0] == '-') { + fprintf(stderr, "Missing byte count after %s option\n", argv[i]); + usage(stderr, argv[0]); return -1; } - r = NOJA_runAssemblyString(argv[3]); - } - else - r = NOJA_runAssemblyFile(argv[2]); - return r ? 0 : -1; - } - - if(!strcmp(argv[1], "dis")) - { - if(argc == 2) - { - fprintf(stderr, "Error: Missing source file.\n"); - return -1; - } - - _Bool r; - - if(!strcmp(argv[2], "inline")) - { - if(argc == 3) - { - fprintf(stderr, "Error: Missing source string.\n"); + heap = atoi(argv[++i]); + if (heap == 0) { + fprintf(stderr, "Invalid heap size\n"); + usage(stderr, argv[0]); return -1; } - - r = NOJA_dumpStringBytecode(argv[3]); + + } else { + input = argv[i]; + break; } - else - r = NOJA_dumpFileBytecode(argv[2]); - return r ? 0 : -1; } - if(!strcmp(argv[1], "help")) - { - fprintf(stdout, usage); - return 0; + int code; + switch (mode) { + + case Mode_HELP: + help(stdout, argv[0]); + code = 0; + break; + + case Mode_DEFAULT: + if (input == NULL) { + fprintf(stderr, "No input file"); + code = -1; + break; + } + if (profile) { + if (no_file) code = NOJA_profileString(input, heap) ? 0 : -1; + else code = NOJA_profileFile(input, heap) ? 0 : -1; + } else { + if (output != NULL) + fprintf(stderr, "Ignoring option -o\n"); + if (no_file) code = NOJA_runString(input, heap) ? 0 : -1; + else code = NOJA_runFile(input, heap) ? 0 : -1; + } + break; + + case Mode_ASSEMBLY: + if (input == NULL) { + fprintf(stderr, "No assembly input file"); + code = -1; + break; + } + if (profile) { + if (no_file) code = NOJA_profileAssemblyString(input, heap) ? 0 : -1; + else code = NOJA_profileAssemblyFile(input, heap) ? 0 : -1; + } else { + if (output != NULL) + fprintf(stderr, "Ignoring option -o\n"); + if (no_file) code = NOJA_runAssemblyString(input, heap) ? 0 : -1; + else code = NOJA_runAssemblyFile(input, heap) ? 0 : -1; + } + break; + case Mode_DISASSEMBLy: + if (input == NULL) { + fprintf(stderr, "No disassembly input file"); + code = -1; + break; + } + if (no_file) code = NOJA_dumpStringBytecode(input) ? 0 : -1; + else code = NOJA_dumpFileBytecode(input) ? 0 : -1; + break; } - fprintf(stderr, "Error: Incorrect usage.\n\n"); - fprintf(stderr, usage); - return -1; + return code; } + +/* + noja [-o | -p | {-d | -a}] [--] + + -o can only be used with -d or -a + + -h --help + -d --disassembly disassembly + -i --inline inline + -a --assembly + -p --profile + -o --output + + noja + noja -i + +*/ \ No newline at end of file diff --git a/src/lib/builtins/basic.c b/src/lib/builtins/basic.c index a2e7ffb..ed76e6d 100644 --- a/src/lib/builtins/basic.c +++ b/src/lib/builtins/basic.c @@ -61,6 +61,38 @@ static int bin_getCurrentWorkingDirectory(Runtime *runtime, Object **argv, unsig return returnValues2(error, runtime, rets, "s", path); } +static int bin_getCurrentScriptDirectory(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error) +{ + UNUSED(argc); + UNUSED(argv); + ASSERT(argc == 0); + const char *path = Runtime_GetCurrentScriptAbsolutePath(runtime); + if (path == NULL) + return returnValues2(error, runtime, rets, "n"); + + size_t len = strlen(path); + + while (path[len-1] != '/') // This underflows if path has len 0 or doesn't contain a "/". + len--; + + Object *obj = Object_FromString(path, len, Runtime_GetHeap(runtime), error); + if (obj == NULL) + return -1; + rets[0] = obj; + return 1; +} + +static int bin_getCurrentScriptLocation(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error) +{ + UNUSED(argc); + UNUSED(argv); + ASSERT(argc == 0); + const char *path = Runtime_GetCurrentScriptAbsolutePath(runtime); + if (path == NULL) + return returnValues2(error, runtime, rets, "n"); + return returnValues2(error, runtime, rets, "s", path); +} + static int bin_typename(Runtime *runtime, Object **argv, unsigned int argc, Object *rets[static MAX_RETS], Error *error) { UNUSED(argc); @@ -364,5 +396,7 @@ StaticMapSlot bins_basic[] = { { "typename", SM_FUNCT, .as_funct = bin_typename, .argc = 1, }, { "keysof", SM_FUNCT, .as_funct = bin_keysof, .argc = 1, }, { "getCurrentWorkingDirectory", SM_FUNCT, .as_funct = bin_getCurrentWorkingDirectory, .argc = 0 }, + { "getCurrentScriptDirectory", SM_FUNCT, .as_funct = bin_getCurrentScriptDirectory, .argc = 0 }, + { "getCurrentScriptLocation", SM_FUNCT, .as_funct = bin_getCurrentScriptLocation, .argc = 0 }, { NULL, SM_END, {}, {} }, }; diff --git a/src/lib/builtins/net.c b/src/lib/builtins/net.c index a27d6ae..fbeb267 100644 --- a/src/lib/builtins/net.c +++ b/src/lib/builtins/net.c @@ -163,7 +163,7 @@ static int bin_accept(Runtime *runtime, Object **argv, unsigned int argc, Object int fd = pargs[0].as_int; struct sockaddr_in new_addr; - socklen_t new_addr_size; + socklen_t new_addr_size = sizeof(new_addr); int new_fd = accept(fd, (struct sockaddr*) &new_addr, &new_addr_size); if (new_fd < 0) return returnValues2(error, runtime, rets, "ns", strerror(errno)); diff --git a/src/lib/builtins/start.noja b/src/lib/builtins/start.noja index 88b30d4..a561866 100644 --- a/src/lib/builtins/start.noja +++ b/src/lib/builtins/start.noja @@ -124,13 +124,6 @@ fun stringFromMap(map: Map, can_use_method=true) { return s; } -fun integerFromDigit(char: String) { - res = ord(char) - ord('0'); - if res < 0 or res > 9: - error("String isn't a digit"); - return res; -} - fun GenericIterator(T) return { set : T, keys : List, @@ -191,6 +184,13 @@ return { error(cat("Don't know how to convert ", typename(value), " to a string")); } + fun integerFromDigit(char: String) { + res = ord(char) - ord('0'); + if res < 0 or res > 9: + error("String isn't a digit"); + return res; + } + fun isCallable(x) return istypeof(Callable, x); diff --git a/src/lib/builtins/string.c b/src/lib/builtins/string.c index 9f4ce9f..1d1079d 100644 --- a/src/lib/builtins/string.c +++ b/src/lib/builtins/string.c @@ -91,11 +91,14 @@ static int bin_cat(Runtime *runtime, Object **argv, unsigned int argc, Object *r Error_Report(error, 0, "Argument #%d is not a string", i+1); return -1; } + + size_t length; + Object_GetString(argv[i], &length); + total_count += length; - total_count += Object_Count(argv[i], error); - - if(error->occurred) - return -1; + // The Object_Count of a string doesn't match the + // byte count, but the second argument of Object_GetString + // does. } char starting[128]; @@ -112,8 +115,6 @@ static int bin_cat(Runtime *runtime, Object **argv, unsigned int argc, Object *r } } - Object *result = NULL; - for(unsigned int i = 0, written = 0; i < argc; i += 1) { size_t length; @@ -125,7 +126,7 @@ static int bin_cat(Runtime *runtime, Object **argv, unsigned int argc, Object *r buffer[total_count] = '\0'; - result = Object_FromString(buffer, total_count, Runtime_GetHeap(runtime), error); + Object *result = Object_FromString(buffer, total_count, Runtime_GetHeap(runtime), error); if(starting != buffer) free(buffer); diff --git a/src/lib/common/executable.c b/src/lib/common/executable.c index dd75a94..1884ab5 100644 --- a/src/lib/common/executable.c +++ b/src/lib/common/executable.c @@ -110,7 +110,7 @@ static const InstrInfo instr_table[] = { INSTR(PUSHTRU) INSTR(PUSHFLS) INSTR(PUSHNNE) - INSTR(PUSHFUN, OPTP_IDX, OPTP_INT) + INSTR(PUSHFUN, OPTP_IDX, OPTP_INT, OPTP_STRING) INSTR(PUSHLST, OPTP_INT) INSTR(PUSHMAP, OPTP_INT) INSTR(PUSHTYP) diff --git a/src/lib/compiler/codegen.c b/src/lib/compiler/codegen.c index 096e618..2271418 100644 --- a/src/lib/compiler/codegen.c +++ b/src/lib/compiler/codegen.c @@ -255,18 +255,19 @@ static void emitInstrForArgumentNode(CodegenContext *ctx, ArgumentNode *arg, int emitInstr_POP1(ctx, arg->base.offset, arg->base.length); } -static void emitInstrForFuncExprNode(CodegenContext *ctx, FuncExprNode *func) +static void emitInstrForFuncExprNode(CodegenContext *ctx, FuncExprNode *func, const char *name) { Label *label_func = Label_New(ctx); Label *label_jump = Label_New(ctx); // Push function. { - Operand ops[2] = { + Operand ops[3] = { { .type = OPTP_PROMISE, .as_promise = Label_ToPromise(label_func) }, { .type = OPTP_INT, .as_int = func->argc }, + { .type = OPTP_STRING, .as_string = name }, }; - CodegenContext_EmitInstr(ctx, OPCODE_PUSHFUN, ops, 2, func->base.base.offset, func->base.base.length); + CodegenContext_EmitInstr(ctx, OPCODE_PUSHFUN, ops, 3, func->base.base.offset, func->base.base.length); } emitInstr_JUMP(ctx, label_jump, func->base.base.offset, func->base.base.length); // Jump after the function code Label_SetHere(label_func, ctx); // This is the function code index. @@ -302,7 +303,7 @@ static void emitInstrForFuncExprNode(CodegenContext *ctx, FuncExprNode *func) static void emitInstrForFuncDeclNode(CodegenContext *ctx, FuncDeclNode *func) { - emitInstrForFuncExprNode(ctx, func->expr); + emitInstrForFuncExprNode(ctx, func->expr, func->name->val); emitInstr_ASS(ctx, func->name->val, func->base.offset, func->base.length); // Assign variable emitInstr_POP1(ctx, func->base.offset, func->base.length); // Pop function object } @@ -557,7 +558,7 @@ static void emitInstrForExprNode(CodegenContext *ctx, ExprNode *expr, Node *key = m->keys; Node *item = m->items; - + while(item) { emitInstrForNode(ctx, key, label_break); @@ -574,7 +575,7 @@ static void emitInstrForExprNode(CodegenContext *ctx, ExprNode *expr, return; case EXPR_FUNC: - emitInstrForFuncExprNode(ctx, (FuncExprNode*) expr); + emitInstrForFuncExprNode(ctx, (FuncExprNode*) expr, "???"); return; case EXPR_SELECT: diff --git a/src/lib/compiler/parse.c b/src/lib/compiler/parse.c index 34aa331..cd24a75 100644 --- a/src/lib/compiler/parse.c +++ b/src/lib/compiler/parse.c @@ -489,12 +489,12 @@ static inline TokenKind next(Context *ctx, const char *file, int line) Token *prev = ctx->token; ctx->token = ctx->token->next; - +/* fprintf(stderr, "NEXT [%.*s] -> [%.*s] from %s:%d\n", prev->length, ctx->src + prev->offset, ctx->token->length, ctx->src + ctx->token->offset, file, line); - +*/ return current(ctx); } diff --git a/src/lib/noja.c b/src/lib/noja.c index b3e8a7c..489ee0b 100644 --- a/src/lib/noja.c +++ b/src/lib/noja.c @@ -5,6 +5,7 @@ #include "compiler/compile.h" #include "assembler/assemble.h" #include "builtins/basic.h" +#include "runtime/timing.h" #include "noja.h" static void print_error(const char *type, Error *error) @@ -33,10 +34,23 @@ static void print_error(const char *type, Error *error) fprintf(stderr, "\n"); } -static _Bool interpret(Executable *exe) -{ - Runtime *runt = Runtime_New(-1, 1024*1024, NULL, NULL); +#include +Runtime *runt = NULL; + +static void signalHandler(int signo) +{ + (void) signo; + if (runt != NULL) + Runtime_Interrupt(runt); +} + +static _Bool interpret(Executable *exe, bool time, size_t heap) +{ + RuntimeConfig config = Runtime_GetDefaultConfigs(); + config.time = time; + + runt = Runtime_New(heap, config); if(runt == NULL) { Error error; @@ -46,6 +60,8 @@ static _Bool interpret(Executable *exe) Error_Free(&error); return 0; } + signal(SIGINT, signalHandler); + signal(SIGTERM, signalHandler); // We use a [RuntimeError] instead of a simple [Error] // because the [RuntimeError] makes a snapshot of the @@ -158,6 +174,37 @@ static _Bool interpret(Executable *exe) RuntimeError_Free(&error); } + TimingTable *table = Runtime_GetTimingTable(runt); + if (table != NULL) { + + const char *file = "profiling-results.txt"; + + FILE *stream = fopen(file, "wb"); + if (stream == NULL) { + fprintf(stderr, "Failed to serialize profiling results\n"); + } else { + + const FunctionExecutionSummary *summary; + size_t count; + + summary = TimingTable_getSummary(table, &count); + assert(summary != NULL); + + for (size_t i = 0; i < count; i++) { + if (summary[i].calls > 0) { + fprintf(stream, "%20s - %s - %ld calls - %.2lfus\n", + summary[i].name, + Source_GetName(summary[i].src), + summary[i].calls, + summary[i].time * 1000000); + } + } + + fclose(stream); + fprintf(stderr, "Wrote profiling result to %s\n", file); + } + } + Runtime_Free(runt); return retc > -1; } @@ -193,7 +240,7 @@ static _Bool disassemble(Source *src) return 1; } -static _Bool interpret_file(const char *file) +static _Bool interpret_file(const char *file, bool time, size_t heap) { Error error; Error_Init(&error); @@ -213,14 +260,14 @@ static _Bool interpret_file(const char *file) return 0; } - _Bool r = interpret(exe); + _Bool r = interpret(exe, time, heap); Executable_Free(exe); Source_Free(src); return r; } -static _Bool interpret_code(const char *code) +static _Bool interpret_code(const char *code, bool time, size_t heap) { Error error; Error_Init(&error); @@ -241,14 +288,14 @@ static _Bool interpret_code(const char *code) return 0; } - _Bool r = interpret(exe); + _Bool r = interpret(exe, time, heap); Executable_Free(exe); Source_Free(src); return r; } -static _Bool interpret_asm_file(const char *file) +static _Bool interpret_asm_file(const char *file, bool time, size_t heap) { Error error; Error_Init(&error); @@ -262,7 +309,7 @@ static _Bool interpret_asm_file(const char *file) Error_Free(&error); return 0; } - + Executable *exe = assemble(src, &error); if(exe == NULL) { print_error("Assemblation", &error); @@ -271,7 +318,7 @@ static _Bool interpret_asm_file(const char *file) return 0; } - _Bool r = interpret(exe); + _Bool r = interpret(exe, time, heap); Executable_Free(exe); Source_Free(src); @@ -279,7 +326,7 @@ static _Bool interpret_asm_file(const char *file) return r; } -static _Bool interpret_asm_code(const char *code) +static _Bool interpret_asm_code(const char *code, bool time, size_t heap) { Error error; Error_Init(&error); @@ -302,7 +349,7 @@ static _Bool interpret_asm_code(const char *code) return 0; } - _Bool r = interpret(exe); + _Bool r = interpret(exe, time, heap); Executable_Free(exe); Source_Free(src); @@ -352,14 +399,14 @@ static _Bool disassemble_code(const char *code) return r; } -_Bool NOJA_runString(const char *str) +_Bool NOJA_runString(const char *str, size_t heap) { - return interpret_code(str); + return interpret_code(str, false, heap); } -_Bool NOJA_runFile(const char *file) +_Bool NOJA_runFile(const char *file, size_t heap) { - return interpret_file(file); + return interpret_file(file, false, heap); } _Bool NOJA_dumpFileBytecode(const char *file) @@ -372,12 +419,32 @@ _Bool NOJA_dumpStringBytecode(const char *str) return disassemble_code(str); } -_Bool NOJA_runAssemblyFile(const char *file) +_Bool NOJA_runAssemblyFile(const char *file, size_t heap) { - return interpret_asm_file(file); + return interpret_asm_file(file, false, heap); } -_Bool NOJA_runAssemblyString(const char *str) +_Bool NOJA_runAssemblyString(const char *str, size_t heap) { - return interpret_asm_code(str); -} \ No newline at end of file + return interpret_asm_code(str, false, heap); +} + +_Bool NOJA_profileString(const char *str, size_t heap) +{ + return interpret_code(str, true, heap); +} + +_Bool NOJA_profileFile(const char *file, size_t heap) +{ + return interpret_file(file, true, heap); +} + +_Bool NOJA_profileAssemblyFile(const char *file, size_t heap) +{ + return interpret_asm_file(file, true, heap); +} + +_Bool NOJA_profileAssemblyString(const char *str, size_t heap) +{ + return interpret_asm_code(str, true, heap); +} diff --git a/src/lib/noja.h b/src/lib/noja.h index a4979f8..ad99f90 100644 --- a/src/lib/noja.h +++ b/src/lib/noja.h @@ -1,9 +1,13 @@ #ifndef NOJA_H #define NOJA_H -_Bool NOJA_runFile (const char *file); -_Bool NOJA_runString(const char *str); -_Bool NOJA_runAssemblyFile(const char *file); -_Bool NOJA_runAssemblyString(const char *str); +_Bool NOJA_runFile (const char *file, size_t heap); +_Bool NOJA_runString(const char *str, size_t heap); +_Bool NOJA_runAssemblyFile(const char *file, size_t heap); +_Bool NOJA_runAssemblyString(const char *str, size_t heap); _Bool NOJA_dumpFileBytecode(const char *file); _Bool NOJA_dumpStringBytecode(const char *str); -#endif /* NOJA_H */ \ No newline at end of file +_Bool NOJA_profileFile(const char *file, size_t heap); +_Bool NOJA_profileString(const char *str, size_t heap); +_Bool NOJA_profileAssemblyFile(const char *file, size_t heap); +_Bool NOJA_profileAssemblyString(const char *str, size_t heap); +#endif /* NOJA_H */ diff --git a/src/lib/objects/heap.c b/src/lib/objects/heap.c index 740c80d..4e68fc0 100644 --- a/src/lib/objects/heap.c +++ b/src/lib/objects/heap.c @@ -299,9 +299,9 @@ void *Heap_RawMalloc(Heap *heap, int size, Error *err) Error_Report(err, 1, "Out of heap"); return NULL; } - + OflowAlloc *oflow = malloc(sizeof(OflowAlloc) + size); - + if(oflow == 0) return 0; @@ -355,68 +355,73 @@ _Bool Heap_StartCollection(Heap *heap, Error *error) return 1; } -_Bool Heap_StopCollection(Heap *heap) +static bool callDestructors(Heap *heap) { - assert(heap->collecting == 1); + int i = 0; - if(heap->collection_failed) + while(i < heap->pend_used) { - free(heap->old_body); - return 0; - } + Object *obj = heap->pend[i].object; - /* Call destructors here */ - { - int i = 0; - - while(i < heap->pend_used) + if(obj->flags & Object_MOVED) { - Object *obj = heap->pend[i].object; - - if(obj->flags & Object_MOVED) - { - heap->pend[i].object = ((MovedObject*) heap->pend[i].object)->new_location; - i += 1; - } - else - { - // We need to call the destructor. - - heap->pend[i].destructor(obj, heap->error); - - if(heap->error->occurred) - return 0; // There will be leaks. - - heap->pend[i] = heap->pend[heap->pend_used-1]; - heap->pend_used -= 1; - } + heap->pend[i].object = ((MovedObject*) heap->pend[i].object)->new_location; + i += 1; } - - if(heap->pend_size / 2 > heap->pend_used) + else { - // Downsize - void *temp = realloc(heap->pend, heap->pend_size / 2 * sizeof(PendingDestruct)); + // We need to call the destructor. - if(temp != NULL) - { - heap->pend = temp; - heap->pend_size /= 2; - } + heap->pend[i].destructor(obj, heap->error); + + if(heap->error->occurred) + return false; // There will be leaks. + + heap->pend[i] = heap->pend[heap->pend_used-1]; + heap->pend_used -= 1; } } + if(heap->pend_size / 2 > heap->pend_used) + { + // Downsize + void *temp = realloc(heap->pend, heap->pend_size / 2 * sizeof(PendingDestruct)); + + if(temp != NULL) + { + heap->pend = temp; + heap->pend_size /= 2; + } + } + + return true; +} + +static void freeOverflowAllocations(Heap *heap) +{ while(heap->old_oflow) { OflowAlloc *prev = heap->old_oflow->prev; free(heap->old_oflow); heap->old_oflow = prev; } +} + +_Bool Heap_StopCollection(Heap *heap) +{ + assert(heap->collecting == 1); + + if(!callDestructors(heap)) + return false; + + freeOverflowAllocations(heap); free(heap->old_body); heap->collecting = 0; heap->objcount = heap->movedcount; - return 1; + + return !heap->collection_failed; } void Heap_CollectExtension(void **referer, unsigned int size, void *userp) diff --git a/src/lib/runtime/o_func.c b/src/lib/runtime/o_func.c index 3ce55aa..6b3cd18 100644 --- a/src/lib/runtime/o_func.c +++ b/src/lib/runtime/o_func.c @@ -28,18 +28,22 @@ ** +--------------------------------------------------------------------------+ */ +#include #include #include #include "../utils/defs.h" #include "../objects/objects.h" +#include "timing.h" #include "runtime.h" typedef struct { Object base; + const char *name; Runtime *runtime; Executable *exe; int index, argc; Object *closure; + TimingID timing_id; } FunctionObject; static _Bool free_(Object *self, Error *error) @@ -108,10 +112,28 @@ static int call(Object *self, Object **argv, unsigned int argc, Object *rets[sta // The right amount of arguments was provided. argv2 = argv; + clock_t begin; + TimingID timing_id; + TimingTable *timing_table = Runtime_GetTimingTable(func->runtime); + if (timing_table != NULL) { + begin = clock(); + + // Need to save the object's member + // before the run function since it + // may trigger a GC cycle invalidating + // the object pointer. + timing_id = func->timing_id; + } + int retc = run(func->runtime, error, func->exe, func->index, func->closure, argv2, expected_argc, rets); - // NOTE: Every object reference is invalidated from here. + if (timing_table != NULL) { + double time = (double) (clock() - begin) / CLOCKS_PER_SEC; + TimingTable_sumCallTime(timing_table, timing_id, time); + } + // NOTE: Every object reference is invalidated from here. + if(argv2 != argv) free(argv2); @@ -157,7 +179,7 @@ static TypeObject t_func = { * The newly created object. If an error occurred, NULL is returned * and information about the error is stored in the [error] argument. */ -Object *Object_FromNojaFunction(Runtime *runtime, Executable *exe, int index, int argc, Object *closure, Heap *heap, Error *error) +Object *Object_FromNojaFunction(Runtime *runtime, const char *name, Executable *exe, int index, int argc, Object *closure, Heap *heap, Error *error) { assert(runtime != NULL); assert(exe != NULL); @@ -180,10 +202,19 @@ Object *Object_FromNojaFunction(Runtime *runtime, Executable *exe, int index, in } func->runtime = runtime; + func->name = name; // Should this be copied? func->exe = exe_copy; func->index = index; func->argc = argc; func->closure = closure; + TimingTable *table = Runtime_GetTimingTable(runtime); + if (table != NULL) { + #warning "TODO: Calculate line number" + size_t line = 0; + Source *src = Executable_GetSource(exe); + func->timing_id = TimingTable_newEntry(table, src, line, name); + } + return (Object*) func; } \ No newline at end of file diff --git a/src/lib/runtime/runtime.c b/src/lib/runtime/runtime.c index be83a56..cd9bdaa 100644 --- a/src/lib/runtime/runtime.c +++ b/src/lib/runtime/runtime.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "runtime.h" #include "../utils/path.h" #include "../utils/defs.h" @@ -49,16 +50,25 @@ struct xFrame { }; struct xRuntime { - void *callback_userp; - _Bool (*callback_addr)(Runtime*, void*); + bool interrupt; + RuntimeCallback callback; _Bool free_heap; Object *builtins; int depth; Frame *frame; Stack *stack; Heap *heap; + TimingTable *timing; }; +RuntimeConfig Runtime_GetDefaultConfigs() +{ + return (RuntimeConfig) { + .stack = 1024, + .callback = { .func = NULL, .data = NULL }, + .time = false, + }; +} // Returns the length written in buff (not considering the zero byte) size_t Runtime_GetCurrentScriptFolder(Runtime *runtime, char *buff, size_t buffsize) @@ -153,50 +163,69 @@ Executable *Runtime_GetCurrentExecutable(Runtime *runtime) return runtime->frame->exe; } -Runtime *Runtime_New2(int stack_size, Heap *heap, _Bool free_heap, void *callback_userp, _Bool (*callback_addr)(Runtime*, void*)) +TimingTable *Runtime_GetTimingTable(Runtime *runtime) { - if(stack_size < 0) - stack_size = 1024; + return runtime->timing; +} +void Runtime_Interrupt(Runtime *runtime) +{ + runtime->interrupt = true; +} + +Runtime *Runtime_New2(Heap *heap, _Bool free_it, RuntimeConfig config) +{ Runtime *runtime = malloc(sizeof(Runtime)); + if (runtime == NULL) + return NULL; - if(runtime != NULL) - { - runtime->heap = heap; - runtime->stack = Stack_New(stack_size); - - if(runtime->stack == NULL) - { - Heap_Free(runtime->heap); - free(runtime); - } - - runtime->free_heap = free_heap; - runtime->callback_userp = callback_userp; - runtime->callback_addr = callback_addr; - runtime->builtins = NULL; - runtime->frame = NULL; - runtime->depth = 0; + runtime->stack = Stack_New(config.stack); + if(runtime->stack == NULL) { + if (free_it) + Heap_Free(heap); + free(runtime); + return NULL; } + TimingTable *timing_table = NULL; + if (config.time) { + timing_table = TimingTable_new(); + if (timing_table == NULL) { + if (free_it) + Heap_Free(heap); + free(runtime); + return NULL; + } + } + + + runtime->interrupt = false; + runtime->timing = timing_table; + runtime->heap = heap; + runtime->free_heap = free_it; + runtime->callback = config.callback; + runtime->builtins = NULL; + runtime->frame = NULL; + runtime->depth = 0; + return runtime; } -Runtime *Runtime_New(int stack_size, int heap_size, void *callback_userp, _Bool (*callback_addr)(Runtime*, void*)) +Runtime *Runtime_New(int heap_size, RuntimeConfig config) { if(heap_size < 0) heap_size = 65536; Heap *heap = Heap_New(heap_size); + if(heap == NULL) return NULL; - if(heap == NULL) - return NULL; - - return Runtime_New2(stack_size, heap, 1, callback_userp, callback_addr); + return Runtime_New2(heap, 1, config); } void Runtime_Free(Runtime *runtime) { + if (runtime->timing != NULL) + TimingTable_free(runtime->timing); if(runtime->free_heap) Heap_Free(runtime->heap); Stack_Free(runtime->stack); @@ -839,6 +868,7 @@ static _Bool step(Runtime *runtime, Error *error) { ASSERT(opc == 1); ASSERT(ops[0].type == OPTP_STRING); + const char *name = ops[0].as_string; if(runtime->frame->used == 0) { @@ -849,7 +879,7 @@ static _Bool step(Runtime *runtime, Error *error) Object *val = Stack_Top(runtime->stack, 0); ASSERT(val != NULL); - Object *key = Object_FromString(ops[0].as_string, -1, runtime->heap, error); + Object *key = Object_FromString(name, -1, runtime->heap, error); if(key == NULL) return 0; @@ -1204,16 +1234,17 @@ static _Bool step(Runtime *runtime, Error *error) case OPCODE_PUSHFUN: { - ASSERT(opc == 2); + ASSERT(opc == 3); ASSERT(ops[0].type == OPTP_IDX); ASSERT(ops[1].type == OPTP_INT); + ASSERT(ops[2].type == OPTP_STRING); Object *closure = Object_NewClosure(runtime->frame->closure, runtime->frame->locals, Runtime_GetHeap(runtime), error); if(closure == NULL) return 0; - Object *obj = Object_FromNojaFunction(runtime, runtime->frame->exe, ops[0].as_int, ops[1].as_int, closure, runtime->heap, error); + Object *obj = Object_FromNojaFunction(runtime, ops[2].as_string, runtime->frame->exe, ops[0].as_int, ops[1].as_int, closure, runtime->heap, error); if(obj == NULL) return 0; @@ -1470,34 +1501,23 @@ int run(Runtime *runtime, Error *error, // Run the code. - if(runtime->callback_addr != NULL) - { - if(!runtime->callback_addr(runtime, runtime->callback_userp)) - Error_Report(error, 0, "Forced abortion"); - else - while(step(runtime, error)) - { - if(!runtime->callback_addr(runtime, runtime->callback_userp)) - { - Error_Report(error, 0, "Forced abortion"); - break; - } - //printf("%2.2f%% percent.\n", Heap_GetUsagePercentage(runtime->heap)); - - if(Heap_GetUsagePercentage(runtime->heap) > 100) - if(!collect(runtime, error)) - break; - } - } + if(runtime->interrupt || (runtime->callback.func != NULL && !runtime->callback.func(runtime, runtime->callback.data))) + Error_Report(error, 0, "Forced abortion"); else while(step(runtime, error)) { + if(runtime->interrupt || (runtime->callback.func != NULL && !runtime->callback.func(runtime, runtime->callback.data))) + { + Error_Report(error, 0, "Forced abortion"); + break; + } + + //printf("%2.2f%% percent.\n", Heap_GetUsagePercentage(runtime->heap)); + if(Heap_GetUsagePercentage(runtime->heap) > 100) if(!collect(runtime, error)) break; - - //printf("%2.2f%% percent.\n", Heap_GetUsagePercentage(runtime->heap)); } // If an error occurred, we want to return NULL. diff --git a/src/lib/runtime/runtime.h b/src/lib/runtime/runtime.h index 8294215..284ba13 100644 --- a/src/lib/runtime/runtime.h +++ b/src/lib/runtime/runtime.h @@ -32,6 +32,7 @@ #define RUNTIME_H #include // meh.. just for the definition of FILE. +#include "timing.h" #include "../utils/error.h" #include "../utils/stack.h" #include "../objects/objects.h" @@ -40,19 +41,34 @@ typedef struct xRuntime Runtime; typedef struct xSnapshot Snapshot; -Runtime* Runtime_New(int stack_size, int heap_size, void *callback_userp, _Bool (*callback_addr)(Runtime*, void*)); -Runtime* Runtime_New2(int stack_size, Heap *heap, _Bool free_heap, void *callback_userp, _Bool (*callback_addr)(Runtime*, void*)); -void Runtime_Free(Runtime *runtime); -_Bool Runtime_Pop(Runtime *runtime, Error *error, unsigned int n); -_Bool Runtime_Push(Runtime *runtime, Error *error, Object *obj); -Heap* Runtime_GetHeap(Runtime *runtime); -Stack* Runtime_GetStack(Runtime *runtime); -Object* Runtime_GetBuiltins(Runtime *runtime); -void Runtime_SetBuiltins(Runtime *runtime, Object *builtins); -int Runtime_GetCurrentIndex(Runtime *runtime); -Executable *Runtime_GetCurrentExecutable(Runtime *runtime); -size_t Runtime_GetCurrentScriptFolder(Runtime *runtime, char *buff, size_t buffsize); -const char *Runtime_GetCurrentScriptAbsolutePath(Runtime *runtime); +typedef struct { + bool (*func)(Runtime*, void*); + void *data; +} RuntimeCallback; + +typedef struct { + bool time; + size_t stack; + RuntimeCallback callback; +} RuntimeConfig; + +Runtime* Runtime_New(int heap_size, RuntimeConfig config); +Runtime* Runtime_New2(Heap *heap, bool free_it, RuntimeConfig config); +void Runtime_Free(Runtime *runtime); +_Bool Runtime_Pop(Runtime *runtime, Error *error, unsigned int n); +_Bool Runtime_Push(Runtime *runtime, Error *error, Object *obj); +void Runtime_Interrupt(Runtime *runtime); +Heap* Runtime_GetHeap(Runtime *runtime); +Stack* Runtime_GetStack(Runtime *runtime); +Object* Runtime_GetBuiltins(Runtime *runtime); +void Runtime_SetBuiltins(Runtime *runtime, Object *builtins); +int Runtime_GetCurrentIndex(Runtime *runtime); +Executable *Runtime_GetCurrentExecutable(Runtime *runtime); +size_t Runtime_GetCurrentScriptFolder(Runtime *runtime, char *buff, size_t buffsize); +const char *Runtime_GetCurrentScriptAbsolutePath(Runtime *runtime); +TimingTable *Runtime_GetTimingTable(Runtime *runtime); +RuntimeConfig Runtime_GetDefaultConfigs(); + Snapshot *Snapshot_New(Runtime *runtime); void Snapshot_Free(Snapshot *snapshot); void Snapshot_Print(Snapshot *snapshot, FILE *fp); @@ -90,8 +106,9 @@ struct StaticMapSlot { }; Object *Object_NewStaticMap(StaticMapSlot slots[], void (*initfn)(StaticMapSlot[]), Runtime *runt, Error *error); -Object *Object_FromNojaFunction(Runtime *runtime, Executable *exe, int index, int argc, Object *closure, Heap *heap, Error *error); +Object *Object_FromNojaFunction(Runtime *runtime, const char *name, Executable *exe, int index, int argc, Object *closure, Heap *heap, Error *error); Object *Object_FromNativeFunction(Runtime *runtime, int (*callback)(Runtime*, Object**, unsigned int, Object*[static MAX_RETS], Error*), int argc, Heap *heap, Error *error); + typedef struct { Error base; Runtime *runtime; diff --git a/src/lib/runtime/timing.c b/src/lib/runtime/timing.c new file mode 100644 index 0000000..0d48b0c --- /dev/null +++ b/src/lib/runtime/timing.c @@ -0,0 +1,76 @@ +#include +#include +#include "timing.h" +#include "../utils/defs.h" + +struct TimingTable { + FunctionExecutionSummary *entries; + size_t size, used; +}; + +TimingTable *TimingTable_new() +{ + TimingTable *table = malloc(sizeof(TimingTable)); + if (table == NULL) + return NULL; + table->entries = NULL; + table->size = 0; + table->used = 0; + return table; +} + +void TimingTable_free(TimingTable *table) +{ + if (table != NULL) { + for(size_t i = 0; i < table->used; i++) { + free(table->entries[i].name); + Source_Free(table->entries[i].src); + } + free(table->entries); + free(table); + } +} + +void TimingTable_sumCallTime(TimingTable *table, TimingID id, double time) +{ + if (id < 0 || id >= (int) table->used) + abort(); + + table->entries[id].calls++; + table->entries[id].time += time; +} + +TimingID TimingTable_newEntry(TimingTable *table, Source *src, size_t line, const char *name) +{ + if (table->size == table->used) { + + size_t new_size = 2 * table->size; + if (new_size == 0) new_size = 8; + + void *temp = realloc(table->entries, new_size * sizeof(FunctionExecutionSummary)); + if (temp == NULL) abort(); + + table->entries = temp; + table->size = new_size; + } + + char *name2 = strdup(name); + if (name2 == NULL) + abort(); + + TimingID id = (int) table->used++; + table->entries[id].src = Source_Copy(src); + table->entries[id].line = line; + table->entries[id].name = name2; + table->entries[id].time = 0; + table->entries[id].calls = 0; + return id; +} + +const FunctionExecutionSummary* +TimingTable_getSummary(TimingTable *table, size_t *count) +{ + ASSERT(table != NULL && count != NULL); + *count = table->used; + return table->entries; +} \ No newline at end of file diff --git a/src/lib/runtime/timing.h b/src/lib/runtime/timing.h new file mode 100644 index 0000000..16b6be6 --- /dev/null +++ b/src/lib/runtime/timing.h @@ -0,0 +1,23 @@ +#ifndef TIMING_H +#define TIMING_H + +#include +#include "../utils/source.h" + +typedef struct { + size_t calls; + double time; + size_t line; + char *name; + Source *src; +} FunctionExecutionSummary; + +typedef int TimingID; +typedef struct TimingTable TimingTable; +TimingTable *TimingTable_new(); +void TimingTable_free(TimingTable *table); +TimingID TimingTable_newEntry(TimingTable *table, Source *src, size_t line, const char *name); +void TimingTable_sumCallTime(TimingTable *table, TimingID id, double time); +TimingTable *TimingTable_getDefaultTable(); +const FunctionExecutionSummary *TimingTable_getSummary(TimingTable *table, size_t *count); +#endif \ No newline at end of file