diff options
author | Jo-Philipp Wich <jo@mein.io> | 2022-08-07 00:24:53 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-07 00:24:53 +0200 |
commit | d0ae9106307343298c926ed065c19609aa37a001 (patch) | |
tree | 1ccab2a62017db277b5de73e0ce6a4ed93aa751a | |
parent | f1e393873a17571ada80c189fbedef020d89cdad (diff) | |
parent | fcc49e6944ab29ab48e8363d2d72e9ca10d3fb76 (diff) |
Merge pull request #98 from jow-/dynlink-support
Add import statement support for dynamic extensions
-rw-r--r-- | compiler.c | 76 | ||||
-rw-r--r-- | include/ucode/lib.h | 1 | ||||
-rw-r--r-- | include/ucode/types.h | 1 | ||||
-rw-r--r-- | include/ucode/vm.h | 3 | ||||
-rw-r--r-- | lib.c | 21 | ||||
-rw-r--r-- | main.c | 14 | ||||
-rw-r--r-- | tests/cram/test_basic.t | 3 | ||||
-rw-r--r-- | tests/custom/04_modules/06_export_errors | 1 | ||||
-rw-r--r-- | vm.c | 96 |
9 files changed, 194 insertions, 22 deletions
@@ -187,10 +187,8 @@ uc_compiler_syntax_error(uc_compiler_t *compiler, size_t off, const char *fmt, . off = uc_program_function_srcpos(compiler->function, uc_compiler_current_chunk(compiler)->count); - if (off) { - byte = off; - line = uc_source_get_line(source, &byte); - } + byte = off; + line = uc_source_get_line(source, &byte); va_start(ap, fmt); len = xvasprintf(&s, fmt, ap); @@ -3332,6 +3330,68 @@ uc_compiler_acquire_source(uc_compiler_t *compiler, const char *path) } static bool +uc_compiler_compile_dynload(uc_compiler_t *compiler, const char *name, uc_value_t *imports) +{ + uc_value_t *modname = ucv_string_new(name); + size_t i, n_imports; + uc_value_t *import; + + for (i = 0, n_imports = 0; i < ucv_array_length(imports); i++) { + import = ucv_array_get(imports, i); + + if (ucv_boolean_get(import)) { + uc_compiler_emit_insn(compiler, 0, I_DYNLOAD); + uc_compiler_emit_u32(compiler, 0, 0); + uc_compiler_emit_constant_index(compiler, 0, modname); + } + else { + n_imports++; + } + } + + if (n_imports > 0) { + uc_compiler_emit_insn(compiler, 0, I_DYNLOAD); + uc_compiler_emit_u32(compiler, 0, n_imports | ((compiler->upvals.count - n_imports) << 16)); + uc_compiler_emit_constant_index(compiler, 0, modname); + + for (i = 0; i < ucv_array_length(imports); i++) { + import = ucv_get(ucv_array_get(imports, i)); + + if (!import) + import = ucv_string_new("default"); + + if (!ucv_boolean_get(import)) + uc_compiler_emit_constant_index(compiler, 0, import); + + ucv_put(import); + } + } + + ucv_put(modname); + + return true; +} + +static bool +uc_compiler_is_dynlink_module(uc_compiler_t *compiler, const char *name, const char *path) +{ + uc_search_path_t *dynlink_list = &compiler->parser->config->force_dynlink_list; + size_t i; + char *p; + + for (i = 0; i < dynlink_list->count; i++) + if (!strcmp(dynlink_list->entries[i], name)) + return true; + + if (!path) + return false; + + p = strrchr(path, '.'); + + return (p && !strcmp(p, ".so")); +} + +static bool uc_compiler_compile_module(uc_compiler_t *compiler, const char *name, uc_value_t *imports) { uc_source_t *source; @@ -3343,7 +3403,10 @@ uc_compiler_compile_module(uc_compiler_t *compiler, const char *name, uc_value_t path = uc_compiler_resolve_module_path(compiler, name); - if (path) { + if (uc_compiler_is_dynlink_module(compiler, name, path)) { + res = uc_compiler_compile_dynload(compiler, name, imports); + } + else if (path) { source = uc_compiler_acquire_source(compiler, path); if (source) { @@ -3363,6 +3426,8 @@ uc_compiler_compile_module(uc_compiler_t *compiler, const char *name, uc_value_t res = false; } + + uc_source_put(source); } else { uc_compiler_syntax_error(compiler, compiler->parser->curr.pos, @@ -3371,7 +3436,6 @@ uc_compiler_compile_module(uc_compiler_t *compiler, const char *name, uc_value_t return false; } - uc_source_put(source); free(path); return res; diff --git a/include/ucode/lib.h b/include/ucode/lib.h index 4c7a3b0..416fea5 100644 --- a/include/ucode/lib.h +++ b/include/ucode/lib.h @@ -34,6 +34,7 @@ uc_cfn_ptr_t uc_stdlib_function(const char *name); __hidden bool uc_source_context_format(uc_stringbuf_t *buf, uc_source_t *src, size_t off, bool compact); __hidden bool uc_error_context_format(uc_stringbuf_t *buf, uc_source_t *src, uc_value_t *stacktrace, size_t off); +__hidden uc_value_t *uc_require_library(uc_vm_t *vm, uc_value_t *nameval, bool so_only); /* vm helper */ diff --git a/include/ucode/types.h b/include/ucode/types.h index 636d6e4..e20f3d6 100644 --- a/include/ucode/types.h +++ b/include/ucode/types.h @@ -227,6 +227,7 @@ typedef struct { bool strict_declarations; bool raw_mode; uc_search_path_t module_search_path; + uc_search_path_t force_dynlink_list; } uc_parse_config_t; extern uc_parse_config_t uc_default_parse_config; diff --git a/include/ucode/vm.h b/include/ucode/vm.h index cc57fdb..8562524 100644 --- a/include/ucode/vm.h +++ b/include/ucode/vm.h @@ -97,7 +97,8 @@ __insn(NEXTK) \ __insn(NEXTKV) \ __insn(DELETE) \ __insn(IMPORT) \ -__insn(EXPORT) +__insn(EXPORT) \ +__insn(DYNLOAD) #undef __insn @@ -1674,7 +1674,7 @@ uc_require_ucode(uc_vm_t *vm, const char *path, uc_value_t *scope, uc_value_t ** } static bool -uc_require_path(uc_vm_t *vm, const char *path_template, const char *name, uc_value_t **res) +uc_require_path(uc_vm_t *vm, const char *path_template, const char *name, uc_value_t **res, bool so_only) { uc_stringbuf_t *buf = xprintbuf_new(); const char *p, *q, *last; @@ -1715,7 +1715,7 @@ uc_require_path(uc_vm_t *vm, const char *path_template, const char *name, uc_val if (!strcmp(p + 1, ".so")) rv = uc_require_so(vm, buf->buf, res); - else if (!strcmp(p + 1, ".uc")) + else if (!strcmp(p + 1, ".uc") && !so_only) rv = uc_require_ucode(vm, buf->buf, NULL, res, true); if (rv) @@ -1727,18 +1727,17 @@ out: return rv; } -static uc_value_t * -uc_require(uc_vm_t *vm, size_t nargs) +uc_value_t * +uc_require_library(uc_vm_t *vm, uc_value_t *nameval, bool so_only) { - uc_value_t *val = uc_fn_arg(0); uc_value_t *search, *se, *res; size_t arridx, arrlen; const char *name; - if (ucv_type(val) != UC_STRING) + if (ucv_type(nameval) != UC_STRING) return NULL; - name = ucv_string_get(val); + name = ucv_string_get(nameval); search = ucv_property_get(uc_vm_scope_get(vm), "REQUIRE_SEARCH_PATH"); if (ucv_type(search) != UC_ARRAY) { @@ -1754,7 +1753,7 @@ uc_require(uc_vm_t *vm, size_t nargs) if (ucv_type(se) != UC_STRING) continue; - if (uc_require_path(vm, ucv_string_get(se), name, &res)) + if (uc_require_path(vm, ucv_string_get(se), name, &res, so_only)) return res; } @@ -1765,6 +1764,12 @@ uc_require(uc_vm_t *vm, size_t nargs) } static uc_value_t * +uc_require(uc_vm_t *vm, size_t nargs) +{ + return uc_require_library(vm, uc_fn_arg(0), false); +} + +static uc_value_t * uc_iptoarr(uc_vm_t *vm, size_t nargs) { uc_value_t *ip = uc_fn_arg(0); @@ -90,7 +90,8 @@ print_usage(const char *app) "-c[flag,flag,...]\n" " Compile the given source file(s) to bytecode instead of executing them.\n" " Supported flags: no-interp (omit interpreter line), interp=... (over-\n" - " ride interpreter line with ...)\n\n" + " ride interpreter line with ...), dynlink=... (force import from ... to\n" + " be treated as shared extensions loaded at runtime).\n\n" "-o path\n" " Output file path when compiling. If omitted, the compiled byte code\n" @@ -204,7 +205,7 @@ parse_template_modeflags(char *opt, uc_parse_config_t *config) } static void -parse_compile_flags(char *opt, char **interp) +parse_compile_flags(char *opt, char **interp, uc_search_path_t *dynlink_list) { char *p, *k, *v; @@ -230,6 +231,12 @@ parse_compile_flags(char *opt, char **interp) else *interp = v; } + else if (!strcmp(k, "dynlink")) { + if (!v) + fprintf(stderr, "Compile flag \"%s\" requires a value, ignoring\n", k); + else + uc_vector_push(dynlink_list, v); + } else { fprintf(stderr, "Unrecognized -c flag \"%s\", ignoring\n", k); } @@ -577,7 +584,7 @@ main(int argc, char **argv) case 'c': outfile = "./uc.out"; - parse_compile_flags(optarg, &interp); + parse_compile_flags(optarg, &interp, &config.force_dynlink_list); break; case 's': @@ -640,6 +647,7 @@ main(int argc, char **argv) out: uc_search_path_free(&config.module_search_path); + uc_vector_clear(&config.force_dynlink_list); uc_source_put(source); diff --git a/tests/cram/test_basic.t b/tests/cram/test_basic.t index 7308002..5911ac9 100644 --- a/tests/cram/test_basic.t +++ b/tests/cram/test_basic.t @@ -61,7 +61,8 @@ check that ucode provides exepected help: -c[flag,flag,...] Compile the given source file(s) to bytecode instead of executing them. Supported flags: no-interp (omit interpreter line), interp=... (over- - ride interpreter line with ...) + ride interpreter line with ...), dynlink=... (force import from ... to + be treated as shared extensions loaded at runtime). -o path Output file path when compiling. If omitted, the compiled byte code diff --git a/tests/custom/04_modules/06_export_errors b/tests/custom/04_modules/06_export_errors index 5c9f676..83227b1 100644 --- a/tests/custom/04_modules/06_export_errors +++ b/tests/custom/04_modules/06_export_errors @@ -10,6 +10,7 @@ export let x = 1; -- Expect stderr -- Syntax error: Exports may only appear at top level of a module +In line 1, byte 1: `export let x = 1;` ^-- Near here @@ -75,7 +75,8 @@ static const int8_t insn_operand_bytes[__I_MAX] = { [I_QMCALL] = 4, [I_IMPORT] = 4, - [I_EXPORT] = 4 + [I_EXPORT] = 4, + [I_DYNLOAD] = 4 }; static const char *exception_type_strings[] = { @@ -902,11 +903,11 @@ uc_vm_capture_stacktrace(uc_vm_t *vm, size_t i) static uc_value_t * uc_vm_get_error_context(uc_vm_t *vm) { + size_t offset, i, byte, line; uc_value_t *stacktrace; uc_callframe_t *frame; uc_stringbuf_t *buf; uc_chunk_t *chunk; - size_t offset, i; /* skip to first non-native function call frame */ for (i = vm->callframes.count; i > 1; i--) @@ -924,7 +925,10 @@ uc_vm_get_error_context(uc_vm_t *vm) buf = ucv_stringbuf_new(); - if (offset) + byte = offset; + line = uc_source_get_line(uc_program_function_source(frame->closure->function), &byte); + + if (line) uc_error_context_format(buf, uc_vm_frame_source(frame), stacktrace, offset); else if (frame->ip != chunk->entries) ucv_stringbuf_printf(buf, "At instruction %zu", (frame->ip - chunk->entries) - 1); @@ -2422,6 +2426,88 @@ uc_vm_insn_export(uc_vm_t *vm, uc_vm_insn_t insn) ucv_get(&ref->header); } +static void +uc_vm_insn_dynload(uc_vm_t *vm, uc_vm_insn_t insn) +{ + uc_callframe_t *frame = uc_vm_current_frame(vm); + uc_value_t *name, *export, *modscope, *modobj; + uint16_t count = vm->arg.u32 & 0xffff; + uint16_t to = vm->arg.u32 >> 16; + uint32_t cidx; + bool found; + + /* instruction is followed by u32 containing the constant index of the + * module name string to import and `count` times u32 values containing + * the import name constant indexes */ + + cidx = ( + frame->ip[0] * 0x1000000UL + + frame->ip[1] * 0x10000UL + + frame->ip[2] * 0x100UL + + frame->ip[3] + ); + + frame->ip += 4; + + /* push module name onto stack, then attempt to load module and pop + * name value again. Will raise exception on error */ + name = uc_program_get_constant(uc_vm_current_program(vm), cidx); + modscope = uc_require_library(vm, name, true); + ucv_put(name); + + if (!modscope) + return; + + /* If count is zero, we're doing a wildcard import. Shallow copy module + * object, mark it constant and patch into the target upvalue. */ + if (count == 0) { + modobj = ucv_object_new(vm); + + ucv_object_foreach(modscope, k, v) + ucv_object_add(modobj, k, ucv_get(v)); + + ucv_set_constant(modobj, true); + + uc_vm_stack_push(vm, modobj); + } + + /* ... otherwise we're importing a specific list of names */ + else { + while (count > 0) { + cidx = ( + frame->ip[0] * 0x1000000UL + + frame->ip[1] * 0x10000UL + + frame->ip[2] * 0x100UL + + frame->ip[3] + ); + + frame->ip += 4; + + name = uc_program_get_constant(uc_vm_current_program(vm), cidx); + export = ucv_object_get(modscope, ucv_string_get(name), &found); + + if (!found) { + uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, + "Module does not export %s", + ucv_string_get(name)); + + ucv_put(name); + + return; + } + + ucv_put(name); + + frame->closure->upvals[to] = (uc_upvalref_t *)ucv_upvalref_new(0); + frame->closure->upvals[to]->closed = true; + frame->closure->upvals[to]->value = ucv_get(export); + + count--; + to++; + } + } +} + static uc_value_t * uc_vm_callframe_pop(uc_vm_t *vm) { @@ -2717,6 +2803,10 @@ uc_vm_execute_chunk(uc_vm_t *vm) uc_vm_insn_export(vm, insn); break; + case I_DYNLOAD: + uc_vm_insn_dynload(vm, insn); + break; + default: uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "unknown opcode %d", insn); break; |