diff --git a/Makefile b/Makefile index d4cb1a599..078709e0a 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ BUILDDIR = _build all: build test build: - dune b src runtime runtime32 stdlib tutorial + dune b src runtime runtime32 stdlib tutorial virtual_machine install: all dune b @install --profile=release diff --git a/README.md b/README.md index d6faee5c2..2ef2146ce 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ # Lama -![lama](lama.svg) is a programming language (initualy developed by JetBrains Research) for educational purposes as an exemplary language to introduce the domain of programming languages, compilers, and tools. +![lama](lama.svg) is a programming language (initially developed by JetBrains Research) for educational purposes as an exemplary language to introduce the domain of programming languages, compilers, and tools. Its general characteristics are: * procedural with first-class functions - functions can be passed as arguments, placed in data structures, @@ -27,10 +27,10 @@ The lack of a type system (a vital feature for a real-world language for software engineering) is an intensional decision that allows showing the unchained diversity of runtime behaviors, including those that a typical type system is called to prevent. On the other hand the language can be used in the future as a raw substrate to apply various ways of software verification (including type systems). -The current implementation contains a native code compiler for **x86-64**, written in **OCaml**, a runtime library with garbage-collection support, written in **C**, and a small standard library, written in ![lama](lama.svg) itself. +The current implementation contains a native code compiler for **x86-64**, written in **OCaml**, a runtime library with garbage-collection support and a bytecode virtual machine written in **C**, and a small standard library, written in ![lama](lama.svg) itself. -In addition, a source-level reference interpreter is implemented as well as a compiler to a small stack machine. -The stack machine code can in turn be either interpreted on a stack machine interpreter, or used as an intermediate representation by the native code compiler. +In addition, a source-level reference interpreter is implemented as well as a compiler to a small abstract stack machine. +This abstract stack machine code can in turn be serialized as bytecode for execution by the virtual machine, or used as an intermediate representation by the native code compiler. ## Language Specification diff --git a/byterun/.gitignore b/byterun/.gitignore deleted file mode 100644 index 68995d9a0..000000000 --- a/byterun/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/byterun.exe \ No newline at end of file diff --git a/byterun/byterun.c b/byterun/byterun.c deleted file mode 100644 index adb86afb9..000000000 --- a/byterun/byterun.c +++ /dev/null @@ -1,351 +0,0 @@ -/* Lama SM Bytecode interpreter */ - -#include -#include -#include -#include -#include "../runtime32/runtime.h" - -void *__start_custom_data; -void *__stop_custom_data; - -/* The unpacked representation of bytecode file */ -typedef struct -{ - char *string_ptr; /* A pointer to the beginning of the string table */ - char *public_ptr; /* A pointer to the beginning of publics table */ - char *code_ptr; /* A pointer to the bytecode itself */ - int *global_ptr; /* A pointer to the global area */ - int stringtab_size; /* The size (in bytes) of the string table */ - int global_area_size; /* The size (in words) of global area */ - int public_symbols_number; /* The number of public symbols */ - char buffer[0]; -} bytefile; - -/* Gets a string from a string table by an index */ -char *get_string(bytefile *f, int pos) -{ - return &f->string_ptr[pos]; -} - -/* Each public symbol entry: int32 name_offset, int32 code_offset, uint8 flag */ -#define PUBLIC_ENTRY_SIZE 9 - -/* Gets a name for a public symbol */ -char *get_public_name(bytefile *f, int i) -{ - return get_string(f, *(int *)(f->public_ptr + i * PUBLIC_ENTRY_SIZE)); -} - -/* Gets an offset for a public symbol */ -int get_public_offset(bytefile *f, int i) -{ - return *(int *)(f->public_ptr + i * PUBLIC_ENTRY_SIZE + sizeof(int)); -} - -/* Gets a flag for a public symbol (0 = function, 1 = global) */ -char get_public_flag(bytefile *f, int i) -{ - return f->public_ptr[i * PUBLIC_ENTRY_SIZE + 2 * sizeof(int)]; -} - -/* Reads a binary bytecode file by name and unpacks it */ -bytefile *read_file(char *fname) -{ - FILE *f = fopen(fname, "rb"); - long size; - bytefile *file; - - if (f == 0) - { - failure("%s\n", strerror(errno)); - } - - if (fseek(f, 0, SEEK_END) == -1) - { - failure("%s\n", strerror(errno)); - } - - file = (bytefile *)malloc(sizeof(void *) * 4 + (size = ftell(f))); - - if (file == 0) - { - failure("*** FAILURE: unable to allocate memory.\n"); - } - - rewind(f); - - if (size != fread(&file->stringtab_size, 1, size, f)) - { - failure("%s\n", strerror(errno)); - } - - fclose(f); - - file->string_ptr = &file->buffer[file->public_symbols_number * PUBLIC_ENTRY_SIZE]; - file->public_ptr = file->buffer; - file->code_ptr = &file->string_ptr[file->stringtab_size]; - file->global_ptr = (int *)malloc(file->global_area_size * sizeof(int)); - - return file; -} - -/* Disassembles the bytecode pool */ -void disassemble(FILE *f, bytefile *bf) -{ - -#define INT (ip += sizeof(int), *(int *)(ip - sizeof(int))) -#define BYTE *ip++ -#define STRING get_string(bf, INT) -#define FAIL failure("ERROR: invalid opcode %d-%d\n", h, l) - - char *ip = bf->code_ptr; - char *ops[] = {"+", "-", "*", "/", "%", "<", "<=", ">", ">=", "==", "!=", "&&", "!!"}; - char *pats[] = {"=str", "#string", "#array", "#sexp", "#ref", "#val", "#fun"}; - char *lds[] = {"LD", "LDA", "ST"}; - do - { - char x = BYTE, - h = (x & 0xF0) >> 4, - l = x & 0x0F; - - fprintf(f, "0x%.8x:\t", ip - bf->code_ptr - 1); - - switch (h) - { - case 15: - goto stop; - - /* BINOP */ - case 0: - fprintf(f, "BINOP\t%s", ops[l - 1]); - break; - - case 1: - switch (l) - { - case 0: - fprintf(f, "CONST\t%d", INT); - break; - - case 1: - fprintf(f, "STRING\t%s", STRING); - break; - - case 2: - fprintf(f, "SEXP\t%s ", STRING); - fprintf(f, "%d", INT); - break; - - case 3: - fprintf(f, "STI"); - break; - - case 4: - fprintf(f, "STA"); - break; - - case 5: - fprintf(f, "JMP\t0x%.8x", INT); - break; - - case 6: - fprintf(f, "END"); - break; - - case 7: - fprintf(f, "RET"); - break; - - case 8: - fprintf(f, "DROP"); - break; - - case 9: - fprintf(f, "DUP"); - break; - - case 10: - fprintf(f, "SWAP"); - break; - - case 11: - fprintf(f, "ELEM"); - break; - - default: - FAIL; - } - break; - - case 2: - case 3: - case 4: - fprintf(f, "%s\t", lds[h - 2]); - switch (l) - { - case 0: - fprintf(f, "G(%d)", INT); - break; - case 1: - fprintf(f, "L(%d)", INT); - break; - case 2: - fprintf(f, "A(%d)", INT); - break; - case 3: - fprintf(f, "C(%d)", INT); - break; - default: - FAIL; - } - break; - - case 5: - switch (l) - { - case 0: - fprintf(f, "CJMPz\t0x%.8x", INT); - break; - - case 1: - fprintf(f, "CJMPnz\t0x%.8x", INT); - break; - - case 2: - fprintf(f, "BEGIN\t%d ", INT); - fprintf(f, "%d", INT); - break; - - case 3: - fprintf(f, "CBEGIN\t%d ", INT); - fprintf(f, "%d", INT); - break; - - case 4: - fprintf(f, "CLOSURE\t0x%.8x", INT); - { - int n = INT; - for (int i = 0; i < n; i++) - { - switch (BYTE) - { - case 0: - fprintf(f, "G(%d)", INT); - break; - case 1: - fprintf(f, "L(%d)", INT); - break; - case 2: - fprintf(f, "A(%d)", INT); - break; - case 3: - fprintf(f, "C(%d)", INT); - break; - default: - FAIL; - } - } - }; - break; - - case 5: - fprintf(f, "CALLC\t%d", INT); - break; - - case 6: - fprintf(f, "CALL\t0x%.8x ", INT); - fprintf(f, "%d", INT); - break; - - case 7: - fprintf(f, "TAG\t%s ", STRING); - fprintf(f, "%d", INT); - break; - - case 8: - fprintf(f, "ARRAY\t%d", INT); - break; - - case 9: - fprintf(f, "FAIL\t%d", INT); - fprintf(f, "%d", INT); - break; - - case 10: - fprintf(f, "LINE\t%d", INT); - break; - - default: - FAIL; - } - break; - - case 6: - fprintf(f, "PATT\t%s", pats[l]); - break; - - case 7: - { - switch (l) - { - case 0: - fprintf(f, "CALL\tread"); - break; - - case 1: - fprintf(f, "CALL\twrite"); - break; - - case 2: - fprintf(f, "CALL\tlength"); - break; - - case 3: - fprintf(f, "CALL\tstring"); - break; - - case 4: - fprintf(f, "CALL\t.array\t%d", INT); - break; - - default: - FAIL; - } - } - break; - - default: - FAIL; - } - - fprintf(f, "\n"); - } while (1); -stop: - fprintf(f, "\n"); -} - -/* Dumps the contents of the file */ -void dump_file(FILE *f, bytefile *bf) -{ - int i; - - fprintf(f, "String table size : %d\n", bf->stringtab_size); - fprintf(f, "Global area size : %d\n", bf->global_area_size); - fprintf(f, "Number of public symbols: %d\n", bf->public_symbols_number); - fprintf(f, "Public symbols :\n"); - - for (i = 0; i < bf->public_symbols_number; i++) - fprintf(f, " 0x%.8x: %s (%s)\n", get_public_offset(bf, i), get_public_name(bf, i), - get_public_flag(bf, i) == 0 ? "function" : "global"); - - fprintf(f, "Code:\n"); - disassemble(f, bf); -} - -int main(int argc, char *argv[]) -{ - bytefile *f = read_file(argv[1]); - dump_file(stdout, f); - return 0; -} diff --git a/byterun/dune b/byterun/dune deleted file mode 100644 index 6b8730c28..000000000 --- a/byterun/dune +++ /dev/null @@ -1,31 +0,0 @@ -(rule - (target byterun.exe) - (deps - (:main byterun.c) - (:runtime ../runtime/runtime.a) - mac-specific-flags.txt) - (mode - (promote (until-clean))) - (action - (run - gcc - %{read-lines:mac-specific-flags.txt} - -g - %{main} - %{runtime} - -o - %{target}))) - -(rule - (target mac-specific-flags.txt) - (enabled_if - (= %{system} "linux")) - (action - (write-file %{target} ""))) - -(rule - (target mac-specific-flags.txt) - (enabled_if - (= %{ocaml-config:system} macosx)) - (action - (write-file %{target} "-arch\nx86_64\n-ld_classic"))) diff --git a/regression/gen.ml b/regression/gen.ml index f8e34841c..1eb135326 100644 --- a/regression/gen.ml +++ b/regression/gen.ml @@ -32,4 +32,32 @@ let () = dprintfn " (deps %s %s))" !lama_file !input_file; Out_channel.with_open_text !cram_file (fun ch -> output_string ch (Buffer.contents cram_buf))) + done); + ignore (Sys.command "mkdir -p vm"); + Out_channel.with_open_text "vm/dune" (fun dunech -> + let dprintfn fmt = Format.kasprintf (Printf.fprintf dunech "%s\n") fmt in + dprintfn "; This file was autogenerated\n"; + dprintfn "(cram (deps ../../src/Driver.exe ../../runtime/Std.i ../../virtual_machine/lama.exe))\n"; + + for i = 0 to count - 1 do + let cram_buf = Buffer.create 100 in + let cram_printfn fmt = + Format.kasprintf (Printf.bprintf cram_buf "%s\n") fmt + in + let cram_file = Printf.sprintf "vm/test%03d.t" i in + let lama_file = Printf.sprintf "test%03d.lama" i in + let input_file = Printf.sprintf "test%03d.input" i in + + if Sys.file_exists lama_file && i <> 803 then ( + cram_printfn + " $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test%03d.lama" + i; + cram_printfn + " $ ../../virtual_machine/lama.exe test%03d.bc < ../test%03d.input" + i i; + + dprintfn "(cram (applies_to test%03d)" i; + dprintfn " (deps ../%s ../%s))" lama_file input_file; + Out_channel.with_open_text cram_file (fun ch -> + output_string ch (Buffer.contents cram_buf))) done) diff --git a/regression/vm/dune b/regression/vm/dune new file mode 100644 index 000000000..89ae6932b --- /dev/null +++ b/regression/vm/dune @@ -0,0 +1,160 @@ +; This file was autogenerated + +(cram (deps ../../src/Driver.exe ../../runtime/Std.i ../../virtual_machine/lama.exe)) + +(cram (applies_to test001) + (deps ../test001.lama ../test001.input)) +(cram (applies_to test002) + (deps ../test002.lama ../test002.input)) +(cram (applies_to test003) + (deps ../test003.lama ../test003.input)) +(cram (applies_to test004) + (deps ../test004.lama ../test004.input)) +(cram (applies_to test005) + (deps ../test005.lama ../test005.input)) +(cram (applies_to test006) + (deps ../test006.lama ../test006.input)) +(cram (applies_to test007) + (deps ../test007.lama ../test007.input)) +(cram (applies_to test008) + (deps ../test008.lama ../test008.input)) +(cram (applies_to test009) + (deps ../test009.lama ../test009.input)) +(cram (applies_to test010) + (deps ../test010.lama ../test010.input)) +(cram (applies_to test011) + (deps ../test011.lama ../test011.input)) +(cram (applies_to test012) + (deps ../test012.lama ../test012.input)) +(cram (applies_to test013) + (deps ../test013.lama ../test013.input)) +(cram (applies_to test014) + (deps ../test014.lama ../test014.input)) +(cram (applies_to test015) + (deps ../test015.lama ../test015.input)) +(cram (applies_to test016) + (deps ../test016.lama ../test016.input)) +(cram (applies_to test017) + (deps ../test017.lama ../test017.input)) +(cram (applies_to test018) + (deps ../test018.lama ../test018.input)) +(cram (applies_to test019) + (deps ../test019.lama ../test019.input)) +(cram (applies_to test020) + (deps ../test020.lama ../test020.input)) +(cram (applies_to test021) + (deps ../test021.lama ../test021.input)) +(cram (applies_to test022) + (deps ../test022.lama ../test022.input)) +(cram (applies_to test023) + (deps ../test023.lama ../test023.input)) +(cram (applies_to test024) + (deps ../test024.lama ../test024.input)) +(cram (applies_to test025) + (deps ../test025.lama ../test025.input)) +(cram (applies_to test026) + (deps ../test026.lama ../test026.input)) +(cram (applies_to test027) + (deps ../test027.lama ../test027.input)) +(cram (applies_to test028) + (deps ../test028.lama ../test028.input)) +(cram (applies_to test029) + (deps ../test029.lama ../test029.input)) +(cram (applies_to test034) + (deps ../test034.lama ../test034.input)) +(cram (applies_to test036) + (deps ../test036.lama ../test036.input)) +(cram (applies_to test040) + (deps ../test040.lama ../test040.input)) +(cram (applies_to test041) + (deps ../test041.lama ../test041.input)) +(cram (applies_to test042) + (deps ../test042.lama ../test042.input)) +(cram (applies_to test045) + (deps ../test045.lama ../test045.input)) +(cram (applies_to test046) + (deps ../test046.lama ../test046.input)) +(cram (applies_to test050) + (deps ../test050.lama ../test050.input)) +(cram (applies_to test054) + (deps ../test054.lama ../test054.input)) +(cram (applies_to test059) + (deps ../test059.lama ../test059.input)) +(cram (applies_to test063) + (deps ../test063.lama ../test063.input)) +(cram (applies_to test072) + (deps ../test072.lama ../test072.input)) +(cram (applies_to test073) + (deps ../test073.lama ../test073.input)) +(cram (applies_to test074) + (deps ../test074.lama ../test074.input)) +(cram (applies_to test077) + (deps ../test077.lama ../test077.input)) +(cram (applies_to test078) + (deps ../test078.lama ../test078.input)) +(cram (applies_to test079) + (deps ../test079.lama ../test079.input)) +(cram (applies_to test080) + (deps ../test080.lama ../test080.input)) +(cram (applies_to test081) + (deps ../test081.lama ../test081.input)) +(cram (applies_to test082) + (deps ../test082.lama ../test082.input)) +(cram (applies_to test083) + (deps ../test083.lama ../test083.input)) +(cram (applies_to test084) + (deps ../test084.lama ../test084.input)) +(cram (applies_to test085) + (deps ../test085.lama ../test085.input)) +(cram (applies_to test086) + (deps ../test086.lama ../test086.input)) +(cram (applies_to test088) + (deps ../test088.lama ../test088.input)) +(cram (applies_to test089) + (deps ../test089.lama ../test089.input)) +(cram (applies_to test090) + (deps ../test090.lama ../test090.input)) +(cram (applies_to test091) + (deps ../test091.lama ../test091.input)) +(cram (applies_to test092) + (deps ../test092.lama ../test092.input)) +(cram (applies_to test093) + (deps ../test093.lama ../test093.input)) +(cram (applies_to test094) + (deps ../test094.lama ../test094.input)) +(cram (applies_to test095) + (deps ../test095.lama ../test095.input)) +(cram (applies_to test096) + (deps ../test096.lama ../test096.input)) +(cram (applies_to test097) + (deps ../test097.lama ../test097.input)) +(cram (applies_to test098) + (deps ../test098.lama ../test098.input)) +(cram (applies_to test099) + (deps ../test099.lama ../test099.input)) +(cram (applies_to test100) + (deps ../test100.lama ../test100.input)) +(cram (applies_to test101) + (deps ../test101.lama ../test101.input)) +(cram (applies_to test102) + (deps ../test102.lama ../test102.input)) +(cram (applies_to test103) + (deps ../test103.lama ../test103.input)) +(cram (applies_to test104) + (deps ../test104.lama ../test104.input)) +(cram (applies_to test105) + (deps ../test105.lama ../test105.input)) +(cram (applies_to test106) + (deps ../test106.lama ../test106.input)) +(cram (applies_to test107) + (deps ../test107.lama ../test107.input)) +(cram (applies_to test110) + (deps ../test110.lama ../test110.input)) +(cram (applies_to test111) + (deps ../test111.lama ../test111.input)) +(cram (applies_to test112) + (deps ../test112.lama ../test112.input)) +(cram (applies_to test801) + (deps ../test801.lama ../test801.input)) +(cram (applies_to test802) + (deps ../test802.lama ../test802.input)) diff --git a/regression/vm/test001.t b/regression/vm/test001.t new file mode 100644 index 000000000..3ac915720 --- /dev/null +++ b/regression/vm/test001.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test001.lama + $ ../../virtual_machine/lama.exe test001.bc < ../test001.input + > > 90 diff --git a/regression/vm/test002.t b/regression/vm/test002.t new file mode 100644 index 000000000..92af9f60e --- /dev/null +++ b/regression/vm/test002.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test002.lama + $ ../../virtual_machine/lama.exe test002.bc < ../test002.input + > > 41 diff --git a/regression/vm/test003.t b/regression/vm/test003.t new file mode 100644 index 000000000..c38684950 --- /dev/null +++ b/regression/vm/test003.t @@ -0,0 +1,5 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test003.lama + $ ../../virtual_machine/lama.exe test003.bc < ../test003.input + > > 7 + 3 + 1 diff --git a/regression/vm/test004.t b/regression/vm/test004.t new file mode 100644 index 000000000..ff0a6e401 --- /dev/null +++ b/regression/vm/test004.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test004.lama + $ ../../virtual_machine/lama.exe test004.bc < ../test004.input + > > 10 diff --git a/regression/vm/test005.t b/regression/vm/test005.t new file mode 100644 index 000000000..c5be28154 --- /dev/null +++ b/regression/vm/test005.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test005.lama + $ ../../virtual_machine/lama.exe test005.bc < ../test005.input + > > 11 diff --git a/regression/vm/test006.t b/regression/vm/test006.t new file mode 100644 index 000000000..857cba182 --- /dev/null +++ b/regression/vm/test006.t @@ -0,0 +1,8 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test006.lama + $ ../../virtual_machine/lama.exe test006.bc < ../test006.input + > > 1 + 1 + 0 + 1 + 0 + 0 diff --git a/regression/vm/test007.t b/regression/vm/test007.t new file mode 100644 index 000000000..eb81b7656 --- /dev/null +++ b/regression/vm/test007.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test007.lama + $ ../../virtual_machine/lama.exe test007.bc < ../test007.input + -4 diff --git a/regression/vm/test008.t b/regression/vm/test008.t new file mode 100644 index 000000000..03b109bb8 --- /dev/null +++ b/regression/vm/test008.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test008.lama + $ ../../virtual_machine/lama.exe test008.bc < ../test008.input + -45 diff --git a/regression/vm/test009.t b/regression/vm/test009.t new file mode 100644 index 000000000..d49d231be --- /dev/null +++ b/regression/vm/test009.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test009.lama + $ ../../virtual_machine/lama.exe test009.bc < ../test009.input + 1024 diff --git a/regression/vm/test010.t b/regression/vm/test010.t new file mode 100644 index 000000000..175255f73 --- /dev/null +++ b/regression/vm/test010.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test010.lama + $ ../../virtual_machine/lama.exe test010.bc < ../test010.input + 499950 diff --git a/regression/vm/test011.t b/regression/vm/test011.t new file mode 100644 index 000000000..5c925cd4c --- /dev/null +++ b/regression/vm/test011.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test011.lama + $ ../../virtual_machine/lama.exe test011.bc < ../test011.input + 2 diff --git a/regression/vm/test012.t b/regression/vm/test012.t new file mode 100644 index 000000000..b25571fec --- /dev/null +++ b/regression/vm/test012.t @@ -0,0 +1,10 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test012.lama + $ ../../virtual_machine/lama.exe test012.bc < ../test012.input + > 0 + 0 + 0 + 1 + 1 + 0 + 1 + 1 diff --git a/regression/vm/test013.t b/regression/vm/test013.t new file mode 100644 index 000000000..7caf4a96a --- /dev/null +++ b/regression/vm/test013.t @@ -0,0 +1,10 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test013.lama + $ ../../virtual_machine/lama.exe test013.bc < ../test013.input + > 10 + 11 + 10 + 11 + 3 + 2 + 1 + 0 diff --git a/regression/vm/test014.t b/regression/vm/test014.t new file mode 100644 index 000000000..f7b04726a --- /dev/null +++ b/regression/vm/test014.t @@ -0,0 +1,33 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test014.lama + $ ../../virtual_machine/lama.exe test014.bc < ../test014.input + > 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 diff --git a/regression/vm/test015.t b/regression/vm/test015.t new file mode 100644 index 000000000..b75dda75f --- /dev/null +++ b/regression/vm/test015.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test015.lama + $ ../../virtual_machine/lama.exe test015.bc < ../test015.input + > 7919 diff --git a/regression/vm/test016.t b/regression/vm/test016.t new file mode 100644 index 000000000..09141d268 --- /dev/null +++ b/regression/vm/test016.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test016.lama + $ ../../virtual_machine/lama.exe test016.bc < ../test016.input + > 3628800 diff --git a/regression/vm/test017.t b/regression/vm/test017.t new file mode 100644 index 000000000..88a828a03 --- /dev/null +++ b/regression/vm/test017.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test017.lama + $ ../../virtual_machine/lama.exe test017.bc < ../test017.input + > 6765 diff --git a/regression/vm/test018.t b/regression/vm/test018.t new file mode 100644 index 000000000..b32d6823e --- /dev/null +++ b/regression/vm/test018.t @@ -0,0 +1,16 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test018.lama + $ ../../virtual_machine/lama.exe test018.bc < ../test018.input + > 2 + 0 + 3 + 4 + 5 + 0 + 7 + 0 + 11 + 0 + 13 + 0 + 17 + 2 diff --git a/regression/vm/test019.t b/regression/vm/test019.t new file mode 100644 index 000000000..6f7cc3a8a --- /dev/null +++ b/regression/vm/test019.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test019.lama + $ ../../virtual_machine/lama.exe test019.bc < ../test019.input + 499950 diff --git a/regression/vm/test020.t b/regression/vm/test020.t new file mode 100644 index 000000000..549d2f2c4 --- /dev/null +++ b/regression/vm/test020.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test020.lama + $ ../../virtual_machine/lama.exe test020.bc < ../test020.input + > 7919 diff --git a/regression/vm/test021.t b/regression/vm/test021.t new file mode 100644 index 000000000..ffca90ec5 --- /dev/null +++ b/regression/vm/test021.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test021.lama + $ ../../virtual_machine/lama.exe test021.bc < ../test021.input + > 3628800 diff --git a/regression/vm/test022.t b/regression/vm/test022.t new file mode 100644 index 000000000..bd46c8a28 --- /dev/null +++ b/regression/vm/test022.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test022.lama + $ ../../virtual_machine/lama.exe test022.bc < ../test022.input + > 6765 diff --git a/regression/vm/test023.t b/regression/vm/test023.t new file mode 100644 index 000000000..66251e008 --- /dev/null +++ b/regression/vm/test023.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test023.lama + $ ../../virtual_machine/lama.exe test023.bc < ../test023.input + > > > > > > 35 diff --git a/regression/vm/test024.t b/regression/vm/test024.t new file mode 100644 index 000000000..d42ee36a8 --- /dev/null +++ b/regression/vm/test024.t @@ -0,0 +1,4 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test024.lama + $ ../../virtual_machine/lama.exe test024.bc < ../test024.input + > 3 + 8 diff --git a/regression/vm/test025.t b/regression/vm/test025.t new file mode 100644 index 000000000..9d1f05cfa --- /dev/null +++ b/regression/vm/test025.t @@ -0,0 +1,14 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test025.lama + $ ../../virtual_machine/lama.exe test025.bc < ../test025.input + > 1 + 100 + 200 + 300 + 2 + 100 + 200 + 300 + 3 + 100 + 200 + 300 diff --git a/regression/vm/test026.t b/regression/vm/test026.t new file mode 100644 index 000000000..e59a2f833 --- /dev/null +++ b/regression/vm/test026.t @@ -0,0 +1,23 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test026.lama + $ ../../virtual_machine/lama.exe test026.bc < ../test026.input + > 1 + 100 + 200 + 300 + 100 + 200 + 300 + 2 + 100 + 200 + 300 + 100 + 200 + 300 + 3 + 100 + 200 + 300 + 100 + 200 + 300 diff --git a/regression/vm/test027.t b/regression/vm/test027.t new file mode 100644 index 000000000..e908e764a --- /dev/null +++ b/regression/vm/test027.t @@ -0,0 +1,37 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test027.lama + $ ../../virtual_machine/lama.exe test027.bc < ../test027.input + > 1 + 100 + 200 + 300 + 1 + 2 + 100 + 200 + 300 + 3 + 100 + 200 + 300 + 3 + 4 + 100 + 200 + 300 + 5 + 100 + 200 + 300 + 5 + 100 + 200 + 300 + 100 + 200 + 300 + 100 + 200 + 300 + 100 + 200 + 300 diff --git a/regression/vm/test028.t b/regression/vm/test028.t new file mode 100644 index 000000000..eb0d81994 --- /dev/null +++ b/regression/vm/test028.t @@ -0,0 +1,16 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test028.lama + $ ../../virtual_machine/lama.exe test028.bc < ../test028.input + > 7 + 5040 + 6 + 720 + 5 + 120 + 4 + 24 + 3 + 6 + 2 + 2 + 1 + 1 diff --git a/regression/vm/test029.t b/regression/vm/test029.t new file mode 100644 index 000000000..037deb913 --- /dev/null +++ b/regression/vm/test029.t @@ -0,0 +1,20 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test029.lama + $ ../../virtual_machine/lama.exe test029.bc < ../test029.input + > 9 + 55 + 8 + 34 + 7 + 21 + 6 + 13 + 5 + 8 + 4 + 5 + 3 + 3 + 2 + 2 + 1 + 1 diff --git a/regression/vm/test034.t b/regression/vm/test034.t new file mode 100644 index 000000000..5e9403398 --- /dev/null +++ b/regression/vm/test034.t @@ -0,0 +1,18 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test034.lama + $ ../../virtual_machine/lama.exe test034.bc < ../test034.input + > 97 + 98 + 99 + 100 + 101 + 102 + 103 + 104 + 99 + 100 + 101 + 102 + 103 + 104 + 105 + 106 diff --git a/regression/vm/test036.t b/regression/vm/test036.t new file mode 100644 index 000000000..05719a59c --- /dev/null +++ b/regression/vm/test036.t @@ -0,0 +1,18 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test036.lama + $ ../../virtual_machine/lama.exe test036.bc < ../test036.input + > 97 + 98 + 99 + 100 + 101 + 102 + 103 + 104 + 97 + 97 + 97 + 97 + 97 + 97 + 97 + 97 diff --git a/regression/vm/test040.t b/regression/vm/test040.t new file mode 100644 index 000000000..6df032abd --- /dev/null +++ b/regression/vm/test040.t @@ -0,0 +1,6 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test040.lama + $ ../../virtual_machine/lama.exe test040.bc < ../test040.input + > 1 + 2 + 3 + 4 diff --git a/regression/vm/test041.t b/regression/vm/test041.t new file mode 100644 index 000000000..fc90179c5 --- /dev/null +++ b/regression/vm/test041.t @@ -0,0 +1,4 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test041.lama + $ ../../virtual_machine/lama.exe test041.bc < ../test041.input + > 600 + 1800 diff --git a/regression/vm/test042.t b/regression/vm/test042.t new file mode 100644 index 000000000..5088e77fd --- /dev/null +++ b/regression/vm/test042.t @@ -0,0 +1,12 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test042.lama + $ ../../virtual_machine/lama.exe test042.bc < ../test042.input + > 0 + 1 + 2 + 3 + 4 + 4 + 4 + 4 + 4 + 4 diff --git a/regression/vm/test045.t b/regression/vm/test045.t new file mode 100644 index 000000000..4452aed39 --- /dev/null +++ b/regression/vm/test045.t @@ -0,0 +1,42 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test045.lama + $ ../../virtual_machine/lama.exe test045.bc < ../test045.input + > 49 + 34 + 97 + 98 + 99 + 34 + 91 + 93 + 91 + 49 + 44 + 32 + 50 + 44 + 32 + 51 + 93 + 67 + 111 + 110 + 115 + 32 + 40 + 49 + 44 + 32 + 67 + 111 + 110 + 115 + 32 + 40 + 50 + 44 + 32 + 78 + 105 + 108 + 41 + 41 diff --git a/regression/vm/test046.t b/regression/vm/test046.t new file mode 100644 index 000000000..c3c4cc89c --- /dev/null +++ b/regression/vm/test046.t @@ -0,0 +1,15 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test046.lama + $ ../../virtual_machine/lama.exe test046.bc < ../test046.input + > 3 + 3 + 3 + 1 + 2 + 3 + 5 + 5 + 1 + 2 + 3 + 4 + 5 diff --git a/regression/vm/test050.t b/regression/vm/test050.t new file mode 100644 index 000000000..3b9a2e1c5 --- /dev/null +++ b/regression/vm/test050.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test050.lama + $ ../../virtual_machine/lama.exe test050.bc < ../test050.input + > 2 diff --git a/regression/vm/test054.t b/regression/vm/test054.t new file mode 100644 index 000000000..dd8e244cc --- /dev/null +++ b/regression/vm/test054.t @@ -0,0 +1,6 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test054.lama + Fatal error: exception Failure("Indirect assignment is not supported yet: If (Var (\"z\"), Scope ([], Ref (\"x\")), Scope ([], Ref (\"y\")))") + [2] + $ ../../virtual_machine/lama.exe test054.bc < ../test054.input + Failed to load unit 'test054' + [1] diff --git a/regression/vm/test059.t b/regression/vm/test059.t new file mode 100644 index 000000000..d3a19e041 --- /dev/null +++ b/regression/vm/test059.t @@ -0,0 +1,5 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test059.lama + $ ../../virtual_machine/lama.exe test059.bc < ../test059.input + > 0 + 1 + 2 diff --git a/regression/vm/test063.t b/regression/vm/test063.t new file mode 100644 index 000000000..f496da825 --- /dev/null +++ b/regression/vm/test063.t @@ -0,0 +1,4 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test063.lama + $ ../../virtual_machine/lama.exe test063.bc < ../test063.input + > 100 + 200 diff --git a/regression/vm/test072.t b/regression/vm/test072.t new file mode 100644 index 000000000..5adde0af4 --- /dev/null +++ b/regression/vm/test072.t @@ -0,0 +1,20 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test072.lama + $ ../../virtual_machine/lama.exe test072.bc < ../test072.input + > 9 + 55 + 8 + 34 + 7 + 21 + 6 + 13 + 5 + 8 + 4 + 5 + 3 + 3 + 2 + 2 + 1 + 1 diff --git a/regression/vm/test073.t b/regression/vm/test073.t new file mode 100644 index 000000000..80f200212 --- /dev/null +++ b/regression/vm/test073.t @@ -0,0 +1,16 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test073.lama + $ ../../virtual_machine/lama.exe test073.bc < ../test073.input + > 7 + 5040 + 6 + 720 + 5 + 120 + 4 + 24 + 3 + 6 + 2 + 2 + 1 + 1 diff --git a/regression/vm/test074.t b/regression/vm/test074.t new file mode 100644 index 000000000..a93eb9666 --- /dev/null +++ b/regression/vm/test074.t @@ -0,0 +1,38 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test074.lama + $ ../../virtual_machine/lama.exe test074.bc < ../test074.input + > 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 3 + 5 + 7 + 9 + 11 + 13 + 15 + 17 + 19 + 5 + 13 + 29 + 61 + 125 + 253 + 509 + 1021 + 2045 diff --git a/regression/vm/test077.t b/regression/vm/test077.t new file mode 100644 index 000000000..aec55f95c --- /dev/null +++ b/regression/vm/test077.t @@ -0,0 +1,8 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test077.lama + $ ../../virtual_machine/lama.exe test077.bc < ../test077.input + > 5 + 6 + 7 + 8 + 9 + 10 diff --git a/regression/vm/test078.t b/regression/vm/test078.t new file mode 100644 index 000000000..225de8ab2 --- /dev/null +++ b/regression/vm/test078.t @@ -0,0 +1,14 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test078.lama + $ ../../virtual_machine/lama.exe test078.bc < ../test078.input + > 1 + 2 + 3 + 4 + 1 + 2 + 3 + 4 + 3 + 4 + 1 + 2 diff --git a/regression/vm/test079.t b/regression/vm/test079.t new file mode 100644 index 000000000..80eae619a --- /dev/null +++ b/regression/vm/test079.t @@ -0,0 +1,8 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test079.lama + $ ../../virtual_machine/lama.exe test079.bc < ../test079.input + > 1 + 1 + 1 + 1 + 0 + 0 diff --git a/regression/vm/test080.t b/regression/vm/test080.t new file mode 100644 index 000000000..1d003313f --- /dev/null +++ b/regression/vm/test080.t @@ -0,0 +1,5 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test080.lama + $ ../../virtual_machine/lama.exe test080.bc < ../test080.input + > 0 + 100 + 300 diff --git a/regression/vm/test081.t b/regression/vm/test081.t new file mode 100644 index 000000000..e346cea15 --- /dev/null +++ b/regression/vm/test081.t @@ -0,0 +1,8 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test081.lama + $ ../../virtual_machine/lama.exe test081.bc < ../test081.input + > 1 + 2 + 3 + 100 + 200 + 300 diff --git a/regression/vm/test082.t b/regression/vm/test082.t new file mode 100644 index 000000000..c0ba37558 --- /dev/null +++ b/regression/vm/test082.t @@ -0,0 +1,19 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test082.lama + $ ../../virtual_machine/lama.exe test082.bc < ../test082.input + > 1 + 1 + 1 + 1 + 1 + 2 + 3 + 100 + 3 + 2 + 1 + 6 + 5 + 4 + 3 + 2 + 1 diff --git a/regression/vm/test083.t b/regression/vm/test083.t new file mode 100644 index 000000000..8e6c83398 --- /dev/null +++ b/regression/vm/test083.t @@ -0,0 +1,5 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test083.lama + $ ../../virtual_machine/lama.exe test083.bc < ../test083.input + > 7 + 7 + 28 diff --git a/regression/vm/test084.t b/regression/vm/test084.t new file mode 100644 index 000000000..d0f4ba91f --- /dev/null +++ b/regression/vm/test084.t @@ -0,0 +1,5 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test084.lama + $ ../../virtual_machine/lama.exe test084.bc < ../test084.input + > 55 + 310 + 310 diff --git a/regression/vm/test085.t b/regression/vm/test085.t new file mode 100644 index 000000000..f3152ea97 --- /dev/null +++ b/regression/vm/test085.t @@ -0,0 +1,10 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test085.lama + $ ../../virtual_machine/lama.exe test085.bc < ../test085.input + > 0 + 15 + 15 + 1 + 2 + 3 + 4 + 5 diff --git a/regression/vm/test086.t b/regression/vm/test086.t new file mode 100644 index 000000000..a3a8a4422 --- /dev/null +++ b/regression/vm/test086.t @@ -0,0 +1,5 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test086.lama + $ ../../virtual_machine/lama.exe test086.bc < ../test086.input + > 1 + 2 + 3 diff --git a/regression/vm/test088.t b/regression/vm/test088.t new file mode 100644 index 000000000..98f1c1828 --- /dev/null +++ b/regression/vm/test088.t @@ -0,0 +1,4 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test088.lama + $ ../../virtual_machine/lama.exe test088.bc < ../test088.input + 0 + 3 diff --git a/regression/vm/test089.t b/regression/vm/test089.t new file mode 100644 index 000000000..7b866c040 --- /dev/null +++ b/regression/vm/test089.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test089.lama + $ ../../virtual_machine/lama.exe test089.bc < ../test089.input + > > > 8 diff --git a/regression/vm/test090.t b/regression/vm/test090.t new file mode 100644 index 000000000..996aa7bc7 --- /dev/null +++ b/regression/vm/test090.t @@ -0,0 +1,5 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test090.lama + $ ../../virtual_machine/lama.exe test090.bc < ../test090.input + > 6 + 7 + 8 diff --git a/regression/vm/test091.t b/regression/vm/test091.t new file mode 100644 index 000000000..cb9cd0086 --- /dev/null +++ b/regression/vm/test091.t @@ -0,0 +1,11 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test091.lama + $ ../../virtual_machine/lama.exe test091.bc < ../test091.input + > 1 + 2 + 3 + 2 + 3 + 4 + 3 + 4 + 5 diff --git a/regression/vm/test092.t b/regression/vm/test092.t new file mode 100644 index 000000000..94222ef7a --- /dev/null +++ b/regression/vm/test092.t @@ -0,0 +1,7 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test092.lama + $ ../../virtual_machine/lama.exe test092.bc < ../test092.input + > 1 + 1 + 1 + 1 + 0 diff --git a/regression/vm/test093.t b/regression/vm/test093.t new file mode 100644 index 000000000..3f1e0a6fc --- /dev/null +++ b/regression/vm/test093.t @@ -0,0 +1,4 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test093.lama + $ ../../virtual_machine/lama.exe test093.bc < ../test093.input + > 11 + 18 diff --git a/regression/vm/test094.t b/regression/vm/test094.t new file mode 100644 index 000000000..f7d455aa3 --- /dev/null +++ b/regression/vm/test094.t @@ -0,0 +1,6 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test094.lama + $ ../../virtual_machine/lama.exe test094.bc < ../test094.input + > 5 + 7 + 12 + -2 diff --git a/regression/vm/test095.t b/regression/vm/test095.t new file mode 100644 index 000000000..73ad459b8 --- /dev/null +++ b/regression/vm/test095.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test095.lama + $ ../../virtual_machine/lama.exe test095.bc < ../test095.input + > 5 diff --git a/regression/vm/test096.t b/regression/vm/test096.t new file mode 100644 index 000000000..cedf73ac4 --- /dev/null +++ b/regression/vm/test096.t @@ -0,0 +1,4 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test096.lama + $ ../../virtual_machine/lama.exe test096.bc < ../test096.input + > 2 + 1 diff --git a/regression/vm/test097.t b/regression/vm/test097.t new file mode 100644 index 000000000..97ff38e0a --- /dev/null +++ b/regression/vm/test097.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test097.lama + $ ../../virtual_machine/lama.exe test097.bc < ../test097.input + > 35 diff --git a/regression/vm/test098.t b/regression/vm/test098.t new file mode 100644 index 000000000..d86a99129 --- /dev/null +++ b/regression/vm/test098.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test098.lama + $ ../../virtual_machine/lama.exe test098.bc < ../test098.input + > 12 diff --git a/regression/vm/test099.t b/regression/vm/test099.t new file mode 100644 index 000000000..9ad4bb758 --- /dev/null +++ b/regression/vm/test099.t @@ -0,0 +1,5 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test099.lama + $ ../../virtual_machine/lama.exe test099.bc < ../test099.input + > 1 + 800 + 800 diff --git a/regression/vm/test100.t b/regression/vm/test100.t new file mode 100644 index 000000000..54b0bfae7 --- /dev/null +++ b/regression/vm/test100.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test100.lama + $ ../../virtual_machine/lama.exe test100.bc < ../test100.input + > 0 diff --git a/regression/vm/test101.t b/regression/vm/test101.t new file mode 100644 index 000000000..c4015064e --- /dev/null +++ b/regression/vm/test101.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test101.lama + $ ../../virtual_machine/lama.exe test101.bc < ../test101.input + > 0 diff --git a/regression/vm/test102.t b/regression/vm/test102.t new file mode 100644 index 000000000..8cde8fbdd --- /dev/null +++ b/regression/vm/test102.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test102.lama + $ ../../virtual_machine/lama.exe test102.bc < ../test102.input + > 5 diff --git a/regression/vm/test103.t b/regression/vm/test103.t new file mode 100644 index 000000000..cf1f3796d --- /dev/null +++ b/regression/vm/test103.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test103.lama + $ ../../virtual_machine/lama.exe test103.bc < ../test103.input + > > > 5 diff --git a/regression/vm/test104.t b/regression/vm/test104.t new file mode 100644 index 000000000..ed3b771ad --- /dev/null +++ b/regression/vm/test104.t @@ -0,0 +1,12 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test104.lama + $ ../../virtual_machine/lama.exe test104.bc < ../test104.input + > 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 diff --git a/regression/vm/test105.t b/regression/vm/test105.t new file mode 100644 index 000000000..763962afa --- /dev/null +++ b/regression/vm/test105.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test105.lama + $ ../../virtual_machine/lama.exe test105.bc < ../test105.input + > 3 diff --git a/regression/vm/test106.t b/regression/vm/test106.t new file mode 100644 index 000000000..479d4716f --- /dev/null +++ b/regression/vm/test106.t @@ -0,0 +1,4 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test106.lama + $ ../../virtual_machine/lama.exe test106.bc < ../test106.input + > 1 + 2 diff --git a/regression/vm/test107.t b/regression/vm/test107.t new file mode 100644 index 000000000..35a7b89fa --- /dev/null +++ b/regression/vm/test107.t @@ -0,0 +1,3 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test107.lama + $ ../../virtual_machine/lama.exe test107.bc < ../test107.input + > 0 diff --git a/regression/vm/test110.t b/regression/vm/test110.t new file mode 100644 index 000000000..847aaeed3 --- /dev/null +++ b/regression/vm/test110.t @@ -0,0 +1,6 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test110.lama + Fatal error: exception Failure("Indirect assignment is not supported yet: If (Const (1), Scope ([], ElemRef (Var (\"x\"), Const (0))), Scope ([], ElemRef (Var (\"y\"), Const (0))))") + [2] + $ ../../virtual_machine/lama.exe test110.bc < ../test110.input + Failed to load unit 'test110' + [1] diff --git a/regression/vm/test111.t b/regression/vm/test111.t new file mode 100644 index 000000000..4a1e8d780 --- /dev/null +++ b/regression/vm/test111.t @@ -0,0 +1,8 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test111.lama + $ ../../virtual_machine/lama.exe test111.bc < ../test111.input + 97 + 98 + 99 + 100 + 97 + 98 diff --git a/regression/vm/test112.t b/regression/vm/test112.t new file mode 100644 index 000000000..a2795af36 --- /dev/null +++ b/regression/vm/test112.t @@ -0,0 +1,13 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test112.lama + $ ../../virtual_machine/lama.exe test112.bc < ../test112.input + 1 + 2 + 5 + 6 + 7 + 8 + 5 + 6 + 7 + 8 + 3 diff --git a/regression/vm/test801.t b/regression/vm/test801.t new file mode 100644 index 000000000..3b3ff3368 --- /dev/null +++ b/regression/vm/test801.t @@ -0,0 +1,7 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test801.lama + $ ../../virtual_machine/lama.exe test801.bc < ../test801.input + 1 + 2 + 3 + 4 + 5 diff --git a/regression/vm/test802.t b/regression/vm/test802.t new file mode 100644 index 000000000..e1fc5266a --- /dev/null +++ b/regression/vm/test802.t @@ -0,0 +1,12 @@ + $ ../../src/Driver.exe -runtime ../../runtime -I ../../stdlib/x64 -b ../test802.lama + $ ../../virtual_machine/lama.exe test802.bc < ../test802.input + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 diff --git a/runtime/Makefile b/runtime/Makefile index f71efd909..5876c8568 100644 --- a/runtime/Makefile +++ b/runtime/Makefile @@ -7,25 +7,37 @@ else ifeq ($(UNAME_S),Darwin) ARCH = -arch x86_64 endif +LAMA_ENV ?= 1 +BUILD_DIR ?= . + +# Virtual machine doesn't work well with this parameter +ifeq ($(LAMA_ENV), 1) + ENV_FLAGS := -DLAMA_ENV +else + ENV_FLAGS := +endif + DISABLE_WARNINGS=-Wno-shift-negative-value -COMMON_FLAGS=$(DISABLE_WARNINGS) -g -fstack-protector-all $(ARCH) --std=c11 -PROD_FLAGS=$(COMMON_FLAGS) -DLAMA_ENV +COMMON_FLAGS=$(DISABLE_WARNINGS) -O3 -g -fstack-protector-all $(ARCH) --std=c11 +PROD_FLAGS=$(COMMON_FLAGS) $(ENV_FLAGS) TEST_FLAGS=$(COMMON_FLAGS) -DDEBUG_VERSION UNIT_TESTS_FLAGS=$(TEST_FLAGS) INVARIANTS_CHECK_FLAGS=$(TEST_FLAGS) -DFULL_INVARIANT_CHECKS -all: gc.o runtime.o printf.o - ar rc runtime.a runtime.o gc.o printf.o +all: $(BUILD_DIR)/gc.o $(BUILD_DIR)/runtime.o $(BUILD_DIR)/printf.o + ar rc $(BUILD_DIR)/runtime.a $(BUILD_DIR)/runtime.o $(BUILD_DIR)/gc.o $(BUILD_DIR)/printf.o -gc.o: gc.c gc.h - $(CC) $(PROD_FLAGS) -c gc.c -o gc.o +$(BUILD_DIR): + mkdir -p $@ -runtime.o: runtime.c runtime.h - $(CC) $(PROD_FLAGS) -c runtime.c -o runtime.o +$(BUILD_DIR)/gc.o: gc.c gc.h | $(BUILD_DIR) + $(CC) $(PROD_FLAGS) -c gc.c -o $(BUILD_DIR)/gc.o -printf.o: printf.S - $(CC) $(PROD_FLAGS) -Wa,--noexecstack -x assembler-with-cpp -c -g printf.S -o printf.o +$(BUILD_DIR)/runtime.o: runtime.c runtime.h | $(BUILD_DIR) + $(CC) $(PROD_FLAGS) -c runtime.c -o $(BUILD_DIR)/runtime.o -clean: - $(RM) *.a *.o *~ negative_scenarios/*.err +$(BUILD_DIR)/printf.o: printf.S | $(BUILD_DIR) + $(CC) $(PROD_FLAGS) -Wa,--noexecstack -x assembler-with-cpp -c -g printf.S -o $(BUILD_DIR)/printf.o +clean: + $(RM) $(BUILD_DIR)/*.a $(BUILD_DIR)/*.o *~ negative_scenarios/*.err diff --git a/runtime/runtime.c b/runtime/runtime.c index 956536d60..e7e0e7cc9 100644 --- a/runtime/runtime.c +++ b/runtime/runtime.c @@ -7,6 +7,7 @@ extern size_t __gc_stack_top, __gc_stack_bottom; +#ifdef LAMA_ENV #define PRE_GC() \ bool flag = false; \ flag = __gc_stack_top == 0; \ @@ -14,10 +15,13 @@ extern size_t __gc_stack_top, __gc_stack_bottom; assert(__gc_stack_top != 0); \ assert((__gc_stack_top & 0xF) == 0); \ assert(__builtin_frame_address(0) <= (void *)__gc_stack_top); - #define POST_GC() \ assert(__builtin_frame_address(0) <= (void *)__gc_stack_top); \ if (flag) { __gc_stack_top = 0; } +#else +#define PRE_GC() (void)0 +#define POST_GC() (void)0 +#endif _Noreturn static void vfailure (char *s, va_list args) { fprintf(stderr, "*** FAILURE: "); @@ -1266,7 +1270,7 @@ extern aint Lread () { // int result = BOX(0); aint result = BOX(0); - printf("> "); + printf(" > "); fflush(stdout); scanf("%" SCNdAI, &result); @@ -1288,7 +1292,7 @@ extern aint Lwrite (aint n) { printf("%" PRIdAI "\n", UNBOX(n)); fflush(stdout); - return 0; + return BOX(0); } extern aint Lrandom (aint n) { diff --git a/runtime32/runtime.c b/runtime32/runtime.c index 14dcda540..37dd4a3b7 100644 --- a/runtime32/runtime.c +++ b/runtime32/runtime.c @@ -1474,7 +1474,7 @@ extern void* Ltl (void *v) { extern int Lread () { int result = BOX(0); - printf ("> "); + printf (" > "); fflush (stdout); scanf ("%d", &result); diff --git a/src/SM.ml b/src/SM.ml index 34463e32c..884e7a0d4 100644 --- a/src/SM.ml +++ b/src/SM.ml @@ -165,6 +165,8 @@ module ByteCode = struct (* Public symbol flags *) let pub_flag_function = 0 let pub_flag_global = 1 + let magic = "LaMa" + let format_version = 1 let compile cmd insns = let code = Buffer.create 256 in @@ -173,88 +175,139 @@ module ByteCode = struct let pubs = Stdlib.ref [] in let imports = Stdlib.ref [] in let globals = Hashtbl.create 16 in - let glob_count = Stdlib.ref 0 in + let extern_globals = Stdlib.ref S.empty in + let extern_funcs = Stdlib.ref S.empty in let fixups = Stdlib.ref [] in + let func_fixups = Stdlib.ref [] in let add_lab l = Hashtbl.replace lmap l (Buffer.length code) in + let add_global name = + try Hashtbl.find globals name + with Not_found -> + let i = Hashtbl.length globals in + Hashtbl.add globals name i; + i + in + let add_extern name is_fun = + if is_fun then + extern_funcs := S.add name !extern_funcs + else + extern_globals := S.add name !extern_globals + in let add_public name is_fun = let flag = if is_fun then pub_flag_function else pub_flag_global in pubs := (name, flag) :: !pubs in let add_import l = imports := l :: !imports in let add_fixup l = fixups := (Buffer.length code, l) :: !fixups in - let add_bytes = List.iter (fun x -> Buffer.add_char code @@ Char.chr x) in - let add_ints = - List.iter (fun x -> Buffer.add_int32_ne code @@ Int32.of_int x) + let add_func_fixup l = func_fixups := (Buffer.length code, l) :: !func_fixups in + let add_bytes buffer = + List.iter (fun x -> Buffer.add_uint8 buffer x) in + let add_ints buffer = + List.iter (fun x -> Buffer.add_int32_le buffer @@ Int32.of_int x) + in + let add_code_bytes = add_bytes code in + let add_code_ints = add_ints code in let add_strings = - List.iter (fun x -> - Buffer.add_int32_ne code @@ Int32.of_int @@ StringTab.add st x) + let unescape x = + let n = String.length x in + let buf = Buffer.create n in + let rec iterate i = + if i < n then + match x.[i] with + | '"' -> + Buffer.add_char buf '"'; + iterate (i + 1) + | '\\' -> ( + if i + 1 >= n then + Buffer.add_char buf '\\' + else + match x.[i + 1] with + | 'n' -> + Buffer.add_char buf '\n'; + iterate (i + 2) + | 't' -> + Buffer.add_char buf '\t'; + iterate (i + 2) + | 'r' -> + Buffer.add_char buf '\r'; + iterate (i + 2) + | _ -> + Buffer.add_char buf '\\'; + iterate (i + 1)) + | c -> + Buffer.add_char buf c; + iterate (i + 1) + in + iterate 0; + Buffer.contents buf + in + List.iter (fun x -> add_code_ints [ StringTab.add st @@ unescape x ]) in let add_designations n = let b x = match n with None -> x | Some b -> (b * 16) + x in List.iter (function | Value.Global s -> - let i = - try Hashtbl.find globals s - with Not_found -> - let i = !glob_count in - incr glob_count; - Hashtbl.add globals s i; - i - in - add_bytes [ b 0 ]; - add_ints [ i ] + if S.mem s !extern_globals then begin + let str_off = StringTab.add st s in + add_code_bytes [ b 0 ]; + add_code_ints [ -str_off - 1 ] + end else begin + let i = add_global s in + add_code_bytes [ b 0 ]; + add_code_ints [ i ] + end | Value.Local n -> - add_bytes [ b 1 ]; - add_ints [ n ] + add_code_bytes [ b 1 ]; + add_code_ints [ n ] | Value.Arg n -> - add_bytes [ b 2 ]; - add_ints [ n ] + add_code_bytes [ b 2 ]; + add_code_ints [ n ] | Value.Access n -> - add_bytes [ b 3 ]; - add_ints [ n ] + add_code_bytes [ b 3 ]; + add_code_ints [ n ] | _ -> failwith (Printf.sprintf "Unexpected pattern: %s: %d" __FILE__ __LINE__)) in let insn_code = function (* 0x0s *) - | BINOP s -> add_bytes [ opnum s ] + | BINOP s -> add_code_bytes [ opnum s ] (* 0x10 n:32 *) | CONST n -> - add_bytes [ (1 * 16) + 0 ]; - add_ints [ n ] + add_code_bytes [ (1 * 16) + 0 ]; + add_code_ints [ n ] (* 0x11 s:32 *) | STRING s -> - add_bytes [ (1 * 16) + 1 ]; + add_code_bytes [ (1 * 16) + 1 ]; add_strings [ s ] (* 0x12 s:32 n:32 *) | SEXP (s, n) -> - add_bytes [ (1 * 16) + 2 ]; + add_code_bytes [ (1 * 16) + 2 ]; add_strings [ s ]; - add_ints [ n ] + add_code_ints [ n ] (* 0x13 *) - | STI -> add_bytes [ (1 * 16) + 3 ] + | STI -> add_code_bytes [ (1 * 16) + 3 ] (* 0x14 *) - | STA -> add_bytes [ (1 * 16) + 4 ] + | STA -> add_code_bytes [ (1 * 16) + 4 ] | LABEL s | FLABEL s | SLABEL s -> add_lab s (* 0x15 l:32 *) | JMP s -> - add_bytes [ (1 * 16) + 5 ]; + add_code_bytes [ (1 * 16) + 5 ]; add_fixup s; - add_ints [ 0 ] + add_code_ints [ 0 ] (* 0x16 *) - | END -> add_bytes [ (1 * 16) + 6 ] + | END -> add_code_bytes [ (1 * 16) + 6 ] (* 0x17 *) - | RET -> add_bytes [ (1 * 16) + 7 ] + | RET -> add_code_bytes [ (1 * 16) + 7 ] (* 0x18 *) - | DROP -> add_bytes [ (1 * 16) + 8 ] + | DROP -> add_code_bytes [ (1 * 16) + 8 ] (* 0x19 *) - | DUP -> add_bytes [ (1 * 16) + 9 ] + | DUP -> add_code_bytes [ (1 * 16) + 9 ] (* 0x1a *) - | SWAP -> add_bytes [ (1 * 16) + 10 ] + | SWAP -> add_code_bytes [ (1 * 16) + 10 ] (* 0x1b *) - | ELEM -> add_bytes [ (1 * 16) + 11 ] + | ELEM -> add_code_bytes [ (1 * 16) + 11 ] (* 0x2d n:32 *) | LD d -> add_designations (Some 2) [ d ] (* 0x3d n:32 *) @@ -263,69 +316,65 @@ module ByteCode = struct | ST d -> add_designations (Some 4) [ d ] (* 0x50 l:32 *) | CJMP ("z", s) -> - add_bytes [ (5 * 16) + 0 ]; + add_code_bytes [ (5 * 16) + 0 ]; add_fixup s; - add_ints [ 0 ] + add_code_ints [ 0 ] (* 0x51 l:32 *) | CJMP ("nz", s) -> - add_bytes [ (5 * 16) + 1 ]; + add_code_bytes [ (5 * 16) + 1 ]; add_fixup s; - add_ints [ 0 ] - (* 0x70 *) - | CALL ("read", _, _) -> add_bytes [ (7 * 16) + 0 ] - (* 0x71 *) - | CALL ("write", _, _) -> add_bytes [ (7 * 16) + 1 ] - (* 0x72 *) - | CALL ("length", _, _) -> add_bytes [ (7 * 16) + 2 ] - (* 0x73 *) - | CALL ("string", _, _) -> add_bytes [ (7 * 16) + 3 ] + add_code_ints [ 0 ] (* 0x74 *) | CALL (".array", n, _) -> - add_bytes [ (7 * 16) + 4 ]; - add_ints [ n ] + add_code_bytes [ (7 * 16) + 4 ]; + add_code_ints [ n ] (* 0x52 n:32 n:32 *) | BEGIN (_, a, l, [], _, _) -> - add_bytes [ (5 * 16) + 2 ]; - add_ints [ a; l ] (* with no closure *) - (* 0x53 n:32 n:32 *) - | BEGIN (_, a, l, _, _, _) -> - add_bytes [ (5 * 16) + 3 ]; - add_ints [ a; l ] (* with a closure *) + add_code_bytes [ (5 * 16) + 2 ]; + add_code_ints [ a; l ] (* with no closure *) + (* 0x53 n:32 n:32 n:32 *) + | BEGIN (_, a, l, ds, _, _) -> + add_code_bytes [ (5 * 16) + 3 ]; + add_code_ints [ a; l; List.length ds ] (* with a closure *) (* 0x54 l:32 n:32 d*:32 *) | CLOSURE (s, ds) -> - add_bytes [ (5 * 16) + 4 ]; - add_fixup s; - add_ints [ 0; List.length ds ]; + add_code_bytes [ (5 * 16) + 4 ]; + add_func_fixup s; + add_code_ints [ 0; List.length ds ]; add_designations None ds (* 0x55 n:32 *) | CALLC (n, _) -> - add_bytes [ (5 * 16) + 5 ]; - add_ints [ n ] + add_code_bytes [ (5 * 16) + 5 ]; + add_code_ints [ n ] (* 0x56 l:32 n:32 *) | CALL (fn, n, _) -> - add_bytes [ (5 * 16) + 6 ]; - add_fixup fn; - add_ints [ 0; n ] + add_code_bytes [ (5 * 16) + 6 ]; + add_func_fixup fn; + add_code_ints [ 0; n ] (* 0x57 s:32 n:32 *) | TAG (s, n) -> - add_bytes [ (5 * 16) + 7 ]; + add_code_bytes [ (5 * 16) + 7 ]; add_strings [ s ]; - add_ints [ n ] + add_code_ints [ n ] (* 0x58 n:32 *) | ARRAY n -> - add_bytes [ (5 * 16) + 8 ]; - add_ints [ n ] + add_code_bytes [ (5 * 16) + 8 ]; + add_code_ints [ n ] (* 0x59 n:32 n:32 *) - | FAIL ((l, c), _) -> - add_bytes [ (5 * 16) + 9 ]; - add_ints [ l; c ] - (* 0x5a n:32 *) + | FAIL ((l, c), false) -> + add_code_bytes [ (5 * 16) + 9 ]; + add_code_ints [ l; c ] + (* 0x5a n:32 n:32 *) + | FAIL ((l, c), true) -> + add_code_bytes [ (5 * 16) + 10 ]; + add_code_ints [ l; c ] + (* 0x5b n:32 *) | LINE n -> - add_bytes [ (5 * 16) + 10 ]; - add_ints [ n ] + add_code_bytes [ (5 * 16) + 11 ]; + add_code_ints [ n ] (* 0x6p *) - | PATT p -> add_bytes [ (6 * 16) + enum patt p ] - | EXTERN _ -> () + | PATT p -> add_code_bytes [ (6 * 16) + enum patt p ] + | EXTERN (name, is_fun) -> add_extern name is_fun | PUBLIC (name, is_fun) -> add_public name is_fun | IMPORT s -> add_import s | _ -> @@ -333,11 +382,25 @@ module ByteCode = struct (Printf.sprintf "Unexpected pattern: %s: %d" __FILE__ __LINE__) in List.iter insn_code insns; - add_bytes [ 255 ]; + add_code_bytes [ 255 ]; let code = Buffer.to_bytes code in + List.iter + (fun (addr_ofs, l) -> + let resolved_addr = + try Hashtbl.find lmap l + with Not_found -> + if S.mem l !extern_funcs then + (* External function: use negative string offset *) + let str_off = StringTab.add st l in + -(str_off + 1) + else + failwith (Printf.sprintf "ERROR: undefined function '%s'" l) + in + Bytes.set_int32_le code addr_ofs (Int32.of_int resolved_addr)) + !func_fixups; List.iter (fun (ofs, l) -> - Bytes.set_int32_ne code ofs + Bytes.set_int32_le code ofs (Int32.of_int @@ try Hashtbl.find lmap l @@ -346,7 +409,7 @@ module ByteCode = struct !fixups; let pubs_resolved = List.rev_map (fun (name, flag) -> - let pos = + let pos = if flag = pub_flag_global then try Hashtbl.find globals name with Not_found -> @@ -356,21 +419,29 @@ module ByteCode = struct with Not_found -> failwith (Printf.sprintf "ERROR: undefined label of public '%s'" name) in - (Int32.of_int @@ StringTab.add st name, Int32.of_int pos, flag)) + (StringTab.add st name, pos, flag)) !pubs in + let imports = List.rev_map (fun l -> StringTab.add st l) !imports in let st = Buffer.to_bytes st.StringTab.buffer in let file = Buffer.create 1024 in - Buffer.add_int32_ne file (Int32.of_int @@ Bytes.length st); - Buffer.add_int32_ne file (Int32.of_int @@ !glob_count); - Buffer.add_int32_ne file (Int32.of_int @@ List.length pubs_resolved); + let add_file_bytes = add_bytes file in + let add_file_ints = add_ints file in + Buffer.add_string file magic; + add_file_ints + [ format_version + ; Bytes.length st + ; Hashtbl.length globals + ; List.length imports + ; List.length pubs_resolved + ]; + Buffer.add_bytes file st; + add_file_ints imports; List.iter (fun (n, o, f) -> - Buffer.add_int32_ne file n; - Buffer.add_int32_ne file o; - Buffer.add_uint8 file f) + add_file_ints [ n; o ]; + add_file_bytes [ f ]) pubs_resolved; - Buffer.add_bytes file st; Buffer.add_bytes file code; let f = open_out_bin (Printf.sprintf "%s.bc" cmd#basename) in Buffer.output_buffer f file; @@ -1663,9 +1734,12 @@ let compile cmd ((imports, _), p) = ] env in + let top_public = + match cmd#get_mode with `BC -> [] | _ -> [ PUBLIC (topname, true) ] + in let prg = List.map (fun i -> IMPORT i) imports - @ [ PUBLIC (topname, true) ] @ env#get_decls @ List.flatten prg + @ top_public @ env#get_decls @ List.flatten prg in (*Printf.eprintf "Before propagating closures:\n"; Printf.eprintf "%s\n%!" env#show_funinfo; diff --git a/stdlib/Makefile b/stdlib/Makefile index 5d4766162..52efebbc7 100644 --- a/stdlib/Makefile +++ b/stdlib/Makefile @@ -1,4 +1,4 @@ -.PHONY: all +.PHONY: all obj bytecode SHELL := /bin/bash SRCDIR ?= . @@ -8,12 +8,17 @@ $(info FILES = $(FILES)) OFILES = $(FILES:$(SRCDIR)/%=%) OFILES := $(OFILES:.lama=.o) $(info OFILES = $(OFILES)) +BCFILES = $(OFILES:.o=.bc) ALL := $(sort $(OFILES)) LAMAC ?= ../src/lamac BDIR ?= . -all: $(addprefix $(BDIR)/,$(ALL)) +all: obj + +obj: $(addprefix $(BDIR)/,$(ALL)) $(info ALL = $(ALL), SRCDIR = $(SRCDIR)) + +bytecode: obj $(addprefix $(BDIR)/,$(BCFILES)) $(BDIR)/Fun.o: $(BDIR)/Ref.o $(BDIR)/Data.o: $(BDIR)/Ref.o $(BDIR)/Collection.o @@ -33,7 +38,10 @@ $(BDIR)/STM.o: $(BDIR)/List.o $(BDIR)/Fun.o $(BDIR)/%.o: $(SRCDIR)/%.lama $(LAMAC) -g -I . -c $< #-o $@ +$(BDIR)/%.bc: $(SRCDIR)/%.lama obj + $(LAMAC) -I . -b $< + clean: - $(RM) -r *.s *.o *.i *~ + $(RM) -r *.s *.o *.i *.bc *~ pushd regression && make clean && popd diff --git a/stdlib/regression/gen.ml b/stdlib/regression/gen.ml index 824fef158..abca85cb1 100644 --- a/stdlib/regression/gen.ml +++ b/stdlib/regression/gen.ml @@ -53,4 +53,37 @@ let () = Out_channel.with_open_text !cram_file (fun ch -> output_string ch "This file was autogenerated.\n"; output_string ch (Buffer.contents cram_buf))) + done); + ignore (Sys.command "mkdir -p vm"); + Out_channel.with_open_text "vm/dune" (fun dunech -> + let dprintfn fmt = Format.kasprintf (Printf.fprintf dunech "%s\n") fmt in + dprintfn "; This file was autogenerated\n"; + dprintfn "(cram (deps ../../../src/Driver.exe ../../../runtime/Std.i ../../../virtual_machine/lama.exe %s))\n" + (String.concat " " (List.concat_map (fun s -> + [sprintf "../../../stdlib/x64/%s.i" s + ;sprintf "../../../stdlib/x64/%s.bc" s + ]) + stdlib)); + + for i = 0 to count - 1 do + let cram_buf = Buffer.create 100 in + let cram_printfn fmt = + Format.kasprintf (Printf.bprintf cram_buf "%s\n") fmt + in + let cram_file = Printf.sprintf "vm/test%02d.t" i in + let lama_file = Printf.sprintf "test%02d.lama" i in + + if Sys.file_exists lama_file then ( + cram_printfn + " $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test%02d.lama" + i; + cram_printfn + " $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test%02d.bc" + i; + + dprintfn "(cram (applies_to test%02d)" i; + dprintfn " (deps ../%s))" lama_file; + Out_channel.with_open_text cram_file (fun ch -> + output_string ch "This file was autogenerated.\n"; + output_string ch (Buffer.contents cram_buf))) done) diff --git a/stdlib/regression/vm/dune b/stdlib/regression/vm/dune new file mode 100644 index 000000000..792e8e7e3 --- /dev/null +++ b/stdlib/regression/vm/dune @@ -0,0 +1,68 @@ +; This file was autogenerated + +(cram (deps ../../../src/Driver.exe ../../../runtime/Std.i ../../../virtual_machine/lama.exe ../../../stdlib/x64/Array.i ../../../stdlib/x64/Array.bc ../../../stdlib/x64/Buffer.i ../../../stdlib/x64/Buffer.bc ../../../stdlib/x64/Collection.i ../../../stdlib/x64/Collection.bc ../../../stdlib/x64/Data.i ../../../stdlib/x64/Data.bc ../../../stdlib/x64/Fun.i ../../../stdlib/x64/Fun.bc ../../../stdlib/x64/Lazy.i ../../../stdlib/x64/Lazy.bc ../../../stdlib/x64/List.i ../../../stdlib/x64/List.bc ../../../stdlib/x64/Matcher.i ../../../stdlib/x64/Matcher.bc ../../../stdlib/x64/Ostap.i ../../../stdlib/x64/Ostap.bc ../../../stdlib/x64/Random.i ../../../stdlib/x64/Random.bc ../../../stdlib/x64/Ref.i ../../../stdlib/x64/Ref.bc ../../../stdlib/x64/STM.i ../../../stdlib/x64/STM.bc ../../../stdlib/x64/Timer.i ../../../stdlib/x64/Timer.bc)) + +(cram (applies_to test01) + (deps ../test01.lama)) +(cram (applies_to test02) + (deps ../test02.lama)) +(cram (applies_to test03) + (deps ../test03.lama)) +(cram (applies_to test04) + (deps ../test04.lama)) +(cram (applies_to test05) + (deps ../test05.lama)) +(cram (applies_to test06) + (deps ../test06.lama)) +(cram (applies_to test07) + (deps ../test07.lama)) +(cram (applies_to test08) + (deps ../test08.lama)) +(cram (applies_to test09) + (deps ../test09.lama)) +(cram (applies_to test10) + (deps ../test10.lama)) +(cram (applies_to test11) + (deps ../test11.lama)) +(cram (applies_to test12) + (deps ../test12.lama)) +(cram (applies_to test13) + (deps ../test13.lama)) +(cram (applies_to test14) + (deps ../test14.lama)) +(cram (applies_to test15) + (deps ../test15.lama)) +(cram (applies_to test16) + (deps ../test16.lama)) +(cram (applies_to test17) + (deps ../test17.lama)) +(cram (applies_to test18) + (deps ../test18.lama)) +(cram (applies_to test20) + (deps ../test20.lama)) +(cram (applies_to test21) + (deps ../test21.lama)) +(cram (applies_to test22) + (deps ../test22.lama)) +(cram (applies_to test23) + (deps ../test23.lama)) +(cram (applies_to test24) + (deps ../test24.lama)) +(cram (applies_to test25) + (deps ../test25.lama)) +(cram (applies_to test26) + (deps ../test26.lama)) +(cram (applies_to test27) + (deps ../test27.lama)) +(cram (applies_to test28) + (deps ../test28.lama)) +(cram (applies_to test29) + (deps ../test29.lama)) +(cram (applies_to test30) + (deps ../test30.lama)) +(cram (applies_to test32) + (deps ../test32.lama)) +(cram (applies_to test33) + (deps ../test33.lama)) +(cram (applies_to test34) + (deps ../test34.lama)) diff --git a/stdlib/regression/vm/test01.t b/stdlib/regression/vm/test01.t new file mode 100644 index 000000000..9b06ee5f0 --- /dev/null +++ b/stdlib/regression/vm/test01.t @@ -0,0 +1,312 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test01.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test01.bc + Set internal structure: MNode (63, 1, 0, MNode (31, 1, 0, MNode (15, 1, 0, MNode (7, 1, 0, MNode (3, 1, 0, MNode (1, 1, 0, MNode (0, 1, 0, 0, 0), MNode (2, 1, 0, 0, 0)), MNode (5, 1, 0, MNode (4, 1, 0, 0, 0), MNode (6, 1, 0, 0, 0))), MNode (11, 1, 0, MNode (9, 1, 0, MNode (8, 1, 0, 0, 0), MNode (10, 1, 0, 0, 0)), MNode (13, 1, 0, MNode (12, 1, 0, 0, 0), MNode (14, 1, 0, 0, 0)))), MNode (23, 1, 0, MNode (19, 1, 0, MNode (17, 1, 0, MNode (16, 1, 0, 0, 0), MNode (18, 1, 0, 0, 0)), MNode (21, 1, 0, MNode (20, 1, 0, 0, 0), MNode (22, 1, 0, 0, 0))), MNode (27, 1, 0, MNode (25, 1, 0, MNode (24, 1, 0, 0, 0), MNode (26, 1, 0, 0, 0)), MNode (29, 1, 0, MNode (28, 1, 0, 0, 0), MNode (30, 1, 0, 0, 0))))), MNode (47, 1, 0, MNode (39, 1, 0, MNode (35, 1, 0, MNode (33, 1, 0, MNode (32, 1, 0, 0, 0), MNode (34, 1, 0, 0, 0)), MNode (37, 1, 0, MNode (36, 1, 0, 0, 0), MNode (38, 1, 0, 0, 0))), MNode (43, 1, 0, MNode (41, 1, 0, MNode (40, 1, 0, 0, 0), MNode (42, 1, 0, 0, 0)), MNode (45, 1, 0, MNode (44, 1, 0, 0, 0), MNode (46, 1, 0, 0, 0)))), MNode (55, 1, 0, MNode (51, 1, 0, MNode (49, 1, 0, MNode (48, 1, 0, 0, 0), MNode (50, 1, 0, 0, 0)), MNode (53, 1, 0, MNode (52, 1, 0, 0, 0), MNode (54, 1, 0, 0, 0))), MNode (59, 1, 0, MNode (57, 1, 0, MNode (56, 1, 0, 0, 0), MNode (58, 1, 0, 0, 0)), MNode (61, 1, 0, MNode (60, 1, 0, 0, 0), MNode (62, 1, 0, 0, 0)))))), MNode (79, 1, -1, MNode (71, 1, 0, MNode (67, 1, 0, MNode (65, 1, 0, MNode (64, 1, 0, 0, 0), MNode (66, 1, 0, 0, 0)), MNode (69, 1, 0, MNode (68, 1, 0, 0, 0), MNode (70, 1, 0, 0, 0))), MNode (75, 1, 0, MNode (73, 1, 0, MNode (72, 1, 0, 0, 0), MNode (74, 1, 0, 0, 0)), MNode (77, 1, 0, MNode (76, 1, 0, 0, 0), MNode (78, 1, 0, 0, 0)))), MNode (87, 1, -1, MNode (83, 1, 0, MNode (81, 1, 0, MNode (80, 1, 0, 0, 0), MNode (82, 1, 0, 0, 0)), MNode (85, 1, 0, MNode (84, 1, 0, 0, 0), MNode (86, 1, 0, 0, 0))), MNode (95, 1, 0, MNode (91, 1, 0, MNode (89, 1, 0, MNode (88, 1, 0, 0, 0), MNode (90, 1, 0, 0, 0)), MNode (93, 1, 0, MNode (92, 1, 0, 0, 0), MNode (94, 1, 0, 0, 0))), MNode (97, 1, -1, MNode (96, 1, 0, 0, 0), MNode (98, 1, -1, 0, MNode (99, 1, 0, 0, 0))))))) + Set elements: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99} + Testing 0 => 1 + Testing 100 => 0 + Testing 1 => 1 + Testing 101 => 0 + Testing 2 => 1 + Testing 102 => 0 + Testing 3 => 1 + Testing 103 => 0 + Testing 4 => 1 + Testing 104 => 0 + Testing 5 => 1 + Testing 105 => 0 + Testing 6 => 1 + Testing 106 => 0 + Testing 7 => 1 + Testing 107 => 0 + Testing 8 => 1 + Testing 108 => 0 + Testing 9 => 1 + Testing 109 => 0 + Testing 10 => 1 + Testing 110 => 0 + Testing 11 => 1 + Testing 111 => 0 + Testing 12 => 1 + Testing 112 => 0 + Testing 13 => 1 + Testing 113 => 0 + Testing 14 => 1 + Testing 114 => 0 + Testing 15 => 1 + Testing 115 => 0 + Testing 16 => 1 + Testing 116 => 0 + Testing 17 => 1 + Testing 117 => 0 + Testing 18 => 1 + Testing 118 => 0 + Testing 19 => 1 + Testing 119 => 0 + Testing 20 => 1 + Testing 120 => 0 + Testing 21 => 1 + Testing 121 => 0 + Testing 22 => 1 + Testing 122 => 0 + Testing 23 => 1 + Testing 123 => 0 + Testing 24 => 1 + Testing 124 => 0 + Testing 25 => 1 + Testing 125 => 0 + Testing 26 => 1 + Testing 126 => 0 + Testing 27 => 1 + Testing 127 => 0 + Testing 28 => 1 + Testing 128 => 0 + Testing 29 => 1 + Testing 129 => 0 + Testing 30 => 1 + Testing 130 => 0 + Testing 31 => 1 + Testing 131 => 0 + Testing 32 => 1 + Testing 132 => 0 + Testing 33 => 1 + Testing 133 => 0 + Testing 34 => 1 + Testing 134 => 0 + Testing 35 => 1 + Testing 135 => 0 + Testing 36 => 1 + Testing 136 => 0 + Testing 37 => 1 + Testing 137 => 0 + Testing 38 => 1 + Testing 138 => 0 + Testing 39 => 1 + Testing 139 => 0 + Testing 40 => 1 + Testing 140 => 0 + Testing 41 => 1 + Testing 141 => 0 + Testing 42 => 1 + Testing 142 => 0 + Testing 43 => 1 + Testing 143 => 0 + Testing 44 => 1 + Testing 144 => 0 + Testing 45 => 1 + Testing 145 => 0 + Testing 46 => 1 + Testing 146 => 0 + Testing 47 => 1 + Testing 147 => 0 + Testing 48 => 1 + Testing 148 => 0 + Testing 49 => 1 + Testing 149 => 0 + Testing 50 => 1 + Testing 150 => 0 + Testing 51 => 1 + Testing 151 => 0 + Testing 52 => 1 + Testing 152 => 0 + Testing 53 => 1 + Testing 153 => 0 + Testing 54 => 1 + Testing 154 => 0 + Testing 55 => 1 + Testing 155 => 0 + Testing 56 => 1 + Testing 156 => 0 + Testing 57 => 1 + Testing 157 => 0 + Testing 58 => 1 + Testing 158 => 0 + Testing 59 => 1 + Testing 159 => 0 + Testing 60 => 1 + Testing 160 => 0 + Testing 61 => 1 + Testing 161 => 0 + Testing 62 => 1 + Testing 162 => 0 + Testing 63 => 1 + Testing 163 => 0 + Testing 64 => 1 + Testing 164 => 0 + Testing 65 => 1 + Testing 165 => 0 + Testing 66 => 1 + Testing 166 => 0 + Testing 67 => 1 + Testing 167 => 0 + Testing 68 => 1 + Testing 168 => 0 + Testing 69 => 1 + Testing 169 => 0 + Testing 70 => 1 + Testing 170 => 0 + Testing 71 => 1 + Testing 171 => 0 + Testing 72 => 1 + Testing 172 => 0 + Testing 73 => 1 + Testing 173 => 0 + Testing 74 => 1 + Testing 174 => 0 + Testing 75 => 1 + Testing 175 => 0 + Testing 76 => 1 + Testing 176 => 0 + Testing 77 => 1 + Testing 177 => 0 + Testing 78 => 1 + Testing 178 => 0 + Testing 79 => 1 + Testing 179 => 0 + Testing 80 => 1 + Testing 180 => 0 + Testing 81 => 1 + Testing 181 => 0 + Testing 82 => 1 + Testing 182 => 0 + Testing 83 => 1 + Testing 183 => 0 + Testing 84 => 1 + Testing 184 => 0 + Testing 85 => 1 + Testing 185 => 0 + Testing 86 => 1 + Testing 186 => 0 + Testing 87 => 1 + Testing 187 => 0 + Testing 88 => 1 + Testing 188 => 0 + Testing 89 => 1 + Testing 189 => 0 + Testing 90 => 1 + Testing 190 => 0 + Testing 91 => 1 + Testing 191 => 0 + Testing 92 => 1 + Testing 192 => 0 + Testing 93 => 1 + Testing 193 => 0 + Testing 94 => 1 + Testing 194 => 0 + Testing 95 => 1 + Testing 195 => 0 + Testing 96 => 1 + Testing 196 => 0 + Testing 97 => 1 + Testing 197 => 0 + Testing 98 => 1 + Testing 198 => 0 + Testing 99 => 1 + Testing 199 => 0 + Set internal structure: MNode (63, 0, 0, MNode (31, 1, 0, MNode (15, 1, 0, MNode (7, 1, 0, MNode (3, 1, 0, MNode (1, 1, 0, MNode (0, 1, 0, 0, 0), MNode (2, 1, 0, 0, 0)), MNode (5, 1, 0, MNode (4, 1, 0, 0, 0), MNode (6, 1, 0, 0, 0))), MNode (11, 1, 0, MNode (9, 1, 0, MNode (8, 1, 0, 0, 0), MNode (10, 1, 0, 0, 0)), MNode (13, 1, 0, MNode (12, 1, 0, 0, 0), MNode (14, 1, 0, 0, 0)))), MNode (23, 1, 0, MNode (19, 1, 0, MNode (17, 1, 0, MNode (16, 1, 0, 0, 0), MNode (18, 1, 0, 0, 0)), MNode (21, 1, 0, MNode (20, 1, 0, 0, 0), MNode (22, 1, 0, 0, 0))), MNode (27, 1, 0, MNode (25, 1, 0, MNode (24, 1, 0, 0, 0), MNode (26, 1, 0, 0, 0)), MNode (29, 1, 0, MNode (28, 1, 0, 0, 0), MNode (30, 1, 0, 0, 0))))), MNode (47, 1, 0, MNode (39, 1, 0, MNode (35, 1, 0, MNode (33, 1, 0, MNode (32, 1, 0, 0, 0), MNode (34, 1, 0, 0, 0)), MNode (37, 1, 0, MNode (36, 1, 0, 0, 0), MNode (38, 1, 0, 0, 0))), MNode (43, 1, 0, MNode (41, 1, 0, MNode (40, 1, 0, 0, 0), MNode (42, 1, 0, 0, 0)), MNode (45, 1, 0, MNode (44, 1, 0, 0, 0), MNode (46, 1, 0, 0, 0)))), MNode (55, 0, 0, MNode (51, 0, 0, MNode (49, 1, 0, MNode (48, 1, 0, 0, 0), MNode (50, 0, 0, 0, 0)), MNode (53, 0, 0, MNode (52, 0, 0, 0, 0), MNode (54, 0, 0, 0, 0))), MNode (59, 0, 0, MNode (57, 0, 0, MNode (56, 0, 0, 0, 0), MNode (58, 0, 0, 0, 0)), MNode (61, 0, 0, MNode (60, 0, 0, 0, 0), MNode (62, 0, 0, 0, 0)))))), MNode (79, 0, -1, MNode (71, 0, 0, MNode (67, 0, 0, MNode (65, 0, 0, MNode (64, 0, 0, 0, 0), MNode (66, 0, 0, 0, 0)), MNode (69, 0, 0, MNode (68, 0, 0, 0, 0), MNode (70, 0, 0, 0, 0))), MNode (75, 0, 0, MNode (73, 0, 0, MNode (72, 0, 0, 0, 0), MNode (74, 0, 0, 0, 0)), MNode (77, 0, 0, MNode (76, 0, 0, 0, 0), MNode (78, 0, 0, 0, 0)))), MNode (87, 0, -1, MNode (83, 0, 0, MNode (81, 0, 0, MNode (80, 0, 0, 0, 0), MNode (82, 0, 0, 0, 0)), MNode (85, 0, 0, MNode (84, 0, 0, 0, 0), MNode (86, 0, 0, 0, 0))), MNode (95, 0, 0, MNode (91, 0, 0, MNode (89, 0, 0, MNode (88, 0, 0, 0, 0), MNode (90, 0, 0, 0, 0)), MNode (93, 0, 0, MNode (92, 0, 0, 0, 0), MNode (94, 0, 0, 0, 0))), MNode (97, 0, -1, MNode (96, 0, 0, 0, 0), MNode (98, 0, -1, 0, MNode (99, 0, 0, 0, 0))))))) + Set elements: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49} + Testing 0 => 1 + Testing 1 => 1 + Testing 2 => 1 + Testing 3 => 1 + Testing 4 => 1 + Testing 5 => 1 + Testing 6 => 1 + Testing 7 => 1 + Testing 8 => 1 + Testing 9 => 1 + Testing 10 => 1 + Testing 11 => 1 + Testing 12 => 1 + Testing 13 => 1 + Testing 14 => 1 + Testing 15 => 1 + Testing 16 => 1 + Testing 17 => 1 + Testing 18 => 1 + Testing 19 => 1 + Testing 20 => 1 + Testing 21 => 1 + Testing 22 => 1 + Testing 23 => 1 + Testing 24 => 1 + Testing 25 => 1 + Testing 26 => 1 + Testing 27 => 1 + Testing 28 => 1 + Testing 29 => 1 + Testing 30 => 1 + Testing 31 => 1 + Testing 32 => 1 + Testing 33 => 1 + Testing 34 => 1 + Testing 35 => 1 + Testing 36 => 1 + Testing 37 => 1 + Testing 38 => 1 + Testing 39 => 1 + Testing 40 => 1 + Testing 41 => 1 + Testing 42 => 1 + Testing 43 => 1 + Testing 44 => 1 + Testing 45 => 1 + Testing 46 => 1 + Testing 47 => 1 + Testing 48 => 1 + Testing 49 => 1 + Testing 50 => 0 + Testing 51 => 0 + Testing 52 => 0 + Testing 53 => 0 + Testing 54 => 0 + Testing 55 => 0 + Testing 56 => 0 + Testing 57 => 0 + Testing 58 => 0 + Testing 59 => 0 + Testing 60 => 0 + Testing 61 => 0 + Testing 62 => 0 + Testing 63 => 0 + Testing 64 => 0 + Testing 65 => 0 + Testing 66 => 0 + Testing 67 => 0 + Testing 68 => 0 + Testing 69 => 0 + Testing 70 => 0 + Testing 71 => 0 + Testing 72 => 0 + Testing 73 => 0 + Testing 74 => 0 + Testing 75 => 0 + Testing 76 => 0 + Testing 77 => 0 + Testing 78 => 0 + Testing 79 => 0 + Testing 80 => 0 + Testing 81 => 0 + Testing 82 => 0 + Testing 83 => 0 + Testing 84 => 0 + Testing 85 => 0 + Testing 86 => 0 + Testing 87 => 0 + Testing 88 => 0 + Testing 89 => 0 + Testing 90 => 0 + Testing 91 => 0 + Testing 92 => 0 + Testing 93 => 0 + Testing 94 => 0 + Testing 95 => 0 + Testing 96 => 0 + Testing 97 => 0 + Testing 98 => 0 + Testing 99 => 0 + List set: MNode (2, 1, -1, MNode (1, 1, 0, 0, 0), MNode (4, 1, 0, MNode (3, 1, 0, 0, 0), MNode (5, 1, 0, 0, 0))) + Set union: MNode (4, 1, -1, MNode (2, 1, 0, MNode (1, 1, 0, 0, 0), MNode (3, 1, 0, 0, 0)), MNode (33, 1, 0, MNode (11, 1, 0, MNode (5, 1, 0, 0, 0), MNode (22, 1, 0, 0, 0)), MNode (44, 1, -1, 0, MNode (55, 1, 0, 0, 0)))) + Elements: {1, 2, 3, 4, 5, 11, 22, 33, 44, 55} + Set difference: MNode (4, 1, -1, MNode (2, 1, 0, MNode (1, 0, 0, 0, 0), MNode (3, 0, 0, 0, 0)), MNode (33, 1, 0, MNode (11, 1, 0, MNode (5, 0, 0, 0, 0), MNode (22, 0, 0, 0, 0)), MNode (44, 0, -1, 0, MNode (55, 1, 0, 0, 0)))) + Elements: {2, 4, 11, 33, 55} diff --git a/stdlib/regression/vm/test02.t b/stdlib/regression/vm/test02.t new file mode 100644 index 000000000..ba88c5a36 --- /dev/null +++ b/stdlib/regression/vm/test02.t @@ -0,0 +1,4 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test02.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test02.bc + Assn ("x", Dec ("3")) diff --git a/stdlib/regression/vm/test03.t b/stdlib/regression/vm/test03.t new file mode 100644 index 000000000..3b9a6efb5 --- /dev/null +++ b/stdlib/regression/vm/test03.t @@ -0,0 +1,21 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test03.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test03.bc + -1 + 1 + 0 + -1 + 1 + 0 + -1 + 1 + 0 + 0 + -1 + 1 + -1 + 1 + 0 + 0 + 1 + -1 diff --git a/stdlib/regression/vm/test04.t b/stdlib/regression/vm/test04.t new file mode 100644 index 000000000..9f3f6bdda --- /dev/null +++ b/stdlib/regression/vm/test04.t @@ -0,0 +1,308 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test04.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test04.bc + Map internal structure: MNode (63, {630}, 0, MNode (31, {310}, 0, MNode (15, {150}, 0, MNode (7, {70}, 0, MNode (3, {30}, 0, MNode (1, {10}, 0, MNode (0, {0}, 0, 0, 0), MNode (2, {20}, 0, 0, 0)), MNode (5, {50}, 0, MNode (4, {40}, 0, 0, 0), MNode (6, {60}, 0, 0, 0))), MNode (11, {110}, 0, MNode (9, {90}, 0, MNode (8, {80}, 0, 0, 0), MNode (10, {100}, 0, 0, 0)), MNode (13, {130}, 0, MNode (12, {120}, 0, 0, 0), MNode (14, {140}, 0, 0, 0)))), MNode (23, {230}, 0, MNode (19, {190}, 0, MNode (17, {170}, 0, MNode (16, {160}, 0, 0, 0), MNode (18, {180}, 0, 0, 0)), MNode (21, {210}, 0, MNode (20, {200}, 0, 0, 0), MNode (22, {220}, 0, 0, 0))), MNode (27, {270}, 0, MNode (25, {250}, 0, MNode (24, {240}, 0, 0, 0), MNode (26, {260}, 0, 0, 0)), MNode (29, {290}, 0, MNode (28, {280}, 0, 0, 0), MNode (30, {300}, 0, 0, 0))))), MNode (47, {470}, 0, MNode (39, {390}, 0, MNode (35, {350}, 0, MNode (33, {330}, 0, MNode (32, {320}, 0, 0, 0), MNode (34, {340}, 0, 0, 0)), MNode (37, {370}, 0, MNode (36, {360}, 0, 0, 0), MNode (38, {380}, 0, 0, 0))), MNode (43, {430}, 0, MNode (41, {410}, 0, MNode (40, {400}, 0, 0, 0), MNode (42, {420}, 0, 0, 0)), MNode (45, {450}, 0, MNode (44, {440}, 0, 0, 0), MNode (46, {460}, 0, 0, 0)))), MNode (55, {550}, 0, MNode (51, {510}, 0, MNode (49, {490}, 0, MNode (48, {480}, 0, 0, 0), MNode (50, {500}, 0, 0, 0)), MNode (53, {530}, 0, MNode (52, {520}, 0, 0, 0), MNode (54, {540}, 0, 0, 0))), MNode (59, {590}, 0, MNode (57, {570}, 0, MNode (56, {560}, 0, 0, 0), MNode (58, {580}, 0, 0, 0)), MNode (61, {610}, 0, MNode (60, {600}, 0, 0, 0), MNode (62, {620}, 0, 0, 0)))))), MNode (79, {790}, -1, MNode (71, {710}, 0, MNode (67, {670}, 0, MNode (65, {650}, 0, MNode (64, {640}, 0, 0, 0), MNode (66, {660}, 0, 0, 0)), MNode (69, {690}, 0, MNode (68, {680}, 0, 0, 0), MNode (70, {700}, 0, 0, 0))), MNode (75, {750}, 0, MNode (73, {730}, 0, MNode (72, {720}, 0, 0, 0), MNode (74, {740}, 0, 0, 0)), MNode (77, {770}, 0, MNode (76, {760}, 0, 0, 0), MNode (78, {780}, 0, 0, 0)))), MNode (87, {870}, -1, MNode (83, {830}, 0, MNode (81, {810}, 0, MNode (80, {800}, 0, 0, 0), MNode (82, {820}, 0, 0, 0)), MNode (85, {850}, 0, MNode (84, {840}, 0, 0, 0), MNode (86, {860}, 0, 0, 0))), MNode (95, {950}, 0, MNode (91, {910}, 0, MNode (89, {890}, 0, MNode (88, {880}, 0, 0, 0), MNode (90, {900}, 0, 0, 0)), MNode (93, {930}, 0, MNode (92, {920}, 0, 0, 0), MNode (94, {940}, 0, 0, 0))), MNode (97, {970}, -1, MNode (96, {960}, 0, 0, 0), MNode (98, {980}, -1, 0, MNode (99, {990}, 0, 0, 0))))))) + Map elements: {[0, 0], [1, 10], [2, 20], [3, 30], [4, 40], [5, 50], [6, 60], [7, 70], [8, 80], [9, 90], [10, 100], [11, 110], [12, 120], [13, 130], [14, 140], [15, 150], [16, 160], [17, 170], [18, 180], [19, 190], [20, 200], [21, 210], [22, 220], [23, 230], [24, 240], [25, 250], [26, 260], [27, 270], [28, 280], [29, 290], [30, 300], [31, 310], [32, 320], [33, 330], [34, 340], [35, 350], [36, 360], [37, 370], [38, 380], [39, 390], [40, 400], [41, 410], [42, 420], [43, 430], [44, 440], [45, 450], [46, 460], [47, 470], [48, 480], [49, 490], [50, 500], [51, 510], [52, 520], [53, 530], [54, 540], [55, 550], [56, 560], [57, 570], [58, 580], [59, 590], [60, 600], [61, 610], [62, 620], [63, 630], [64, 640], [65, 650], [66, 660], [67, 670], [68, 680], [69, 690], [70, 700], [71, 710], [72, 720], [73, 730], [74, 740], [75, 750], [76, 760], [77, 770], [78, 780], [79, 790], [80, 800], [81, 810], [82, 820], [83, 830], [84, 840], [85, 850], [86, 860], [87, 870], [88, 880], [89, 890], [90, 900], [91, 910], [92, 920], [93, 930], [94, 940], [95, 950], [96, 960], [97, 970], [98, 980], [99, 990]} + Testing 0 => Some (0) + Testing 100 => None + Testing 1 => Some (10) + Testing 101 => None + Testing 2 => Some (20) + Testing 102 => None + Testing 3 => Some (30) + Testing 103 => None + Testing 4 => Some (40) + Testing 104 => None + Testing 5 => Some (50) + Testing 105 => None + Testing 6 => Some (60) + Testing 106 => None + Testing 7 => Some (70) + Testing 107 => None + Testing 8 => Some (80) + Testing 108 => None + Testing 9 => Some (90) + Testing 109 => None + Testing 10 => Some (100) + Testing 110 => None + Testing 11 => Some (110) + Testing 111 => None + Testing 12 => Some (120) + Testing 112 => None + Testing 13 => Some (130) + Testing 113 => None + Testing 14 => Some (140) + Testing 114 => None + Testing 15 => Some (150) + Testing 115 => None + Testing 16 => Some (160) + Testing 116 => None + Testing 17 => Some (170) + Testing 117 => None + Testing 18 => Some (180) + Testing 118 => None + Testing 19 => Some (190) + Testing 119 => None + Testing 20 => Some (200) + Testing 120 => None + Testing 21 => Some (210) + Testing 121 => None + Testing 22 => Some (220) + Testing 122 => None + Testing 23 => Some (230) + Testing 123 => None + Testing 24 => Some (240) + Testing 124 => None + Testing 25 => Some (250) + Testing 125 => None + Testing 26 => Some (260) + Testing 126 => None + Testing 27 => Some (270) + Testing 127 => None + Testing 28 => Some (280) + Testing 128 => None + Testing 29 => Some (290) + Testing 129 => None + Testing 30 => Some (300) + Testing 130 => None + Testing 31 => Some (310) + Testing 131 => None + Testing 32 => Some (320) + Testing 132 => None + Testing 33 => Some (330) + Testing 133 => None + Testing 34 => Some (340) + Testing 134 => None + Testing 35 => Some (350) + Testing 135 => None + Testing 36 => Some (360) + Testing 136 => None + Testing 37 => Some (370) + Testing 137 => None + Testing 38 => Some (380) + Testing 138 => None + Testing 39 => Some (390) + Testing 139 => None + Testing 40 => Some (400) + Testing 140 => None + Testing 41 => Some (410) + Testing 141 => None + Testing 42 => Some (420) + Testing 142 => None + Testing 43 => Some (430) + Testing 143 => None + Testing 44 => Some (440) + Testing 144 => None + Testing 45 => Some (450) + Testing 145 => None + Testing 46 => Some (460) + Testing 146 => None + Testing 47 => Some (470) + Testing 147 => None + Testing 48 => Some (480) + Testing 148 => None + Testing 49 => Some (490) + Testing 149 => None + Testing 50 => Some (500) + Testing 150 => None + Testing 51 => Some (510) + Testing 151 => None + Testing 52 => Some (520) + Testing 152 => None + Testing 53 => Some (530) + Testing 153 => None + Testing 54 => Some (540) + Testing 154 => None + Testing 55 => Some (550) + Testing 155 => None + Testing 56 => Some (560) + Testing 156 => None + Testing 57 => Some (570) + Testing 157 => None + Testing 58 => Some (580) + Testing 158 => None + Testing 59 => Some (590) + Testing 159 => None + Testing 60 => Some (600) + Testing 160 => None + Testing 61 => Some (610) + Testing 161 => None + Testing 62 => Some (620) + Testing 162 => None + Testing 63 => Some (630) + Testing 163 => None + Testing 64 => Some (640) + Testing 164 => None + Testing 65 => Some (650) + Testing 165 => None + Testing 66 => Some (660) + Testing 166 => None + Testing 67 => Some (670) + Testing 167 => None + Testing 68 => Some (680) + Testing 168 => None + Testing 69 => Some (690) + Testing 169 => None + Testing 70 => Some (700) + Testing 170 => None + Testing 71 => Some (710) + Testing 171 => None + Testing 72 => Some (720) + Testing 172 => None + Testing 73 => Some (730) + Testing 173 => None + Testing 74 => Some (740) + Testing 174 => None + Testing 75 => Some (750) + Testing 175 => None + Testing 76 => Some (760) + Testing 176 => None + Testing 77 => Some (770) + Testing 177 => None + Testing 78 => Some (780) + Testing 178 => None + Testing 79 => Some (790) + Testing 179 => None + Testing 80 => Some (800) + Testing 180 => None + Testing 81 => Some (810) + Testing 181 => None + Testing 82 => Some (820) + Testing 182 => None + Testing 83 => Some (830) + Testing 183 => None + Testing 84 => Some (840) + Testing 184 => None + Testing 85 => Some (850) + Testing 185 => None + Testing 86 => Some (860) + Testing 186 => None + Testing 87 => Some (870) + Testing 187 => None + Testing 88 => Some (880) + Testing 188 => None + Testing 89 => Some (890) + Testing 189 => None + Testing 90 => Some (900) + Testing 190 => None + Testing 91 => Some (910) + Testing 191 => None + Testing 92 => Some (920) + Testing 192 => None + Testing 93 => Some (930) + Testing 193 => None + Testing 94 => Some (940) + Testing 194 => None + Testing 95 => Some (950) + Testing 195 => None + Testing 96 => Some (960) + Testing 196 => None + Testing 97 => Some (970) + Testing 197 => None + Testing 98 => Some (980) + Testing 198 => None + Testing 99 => Some (990) + Testing 199 => None + Map internal structure: MNode (63, 0, 0, MNode (31, {310}, 0, MNode (15, {150}, 0, MNode (7, {70}, 0, MNode (3, {30}, 0, MNode (1, {10}, 0, MNode (0, {0}, 0, 0, 0), MNode (2, {20}, 0, 0, 0)), MNode (5, {50}, 0, MNode (4, {40}, 0, 0, 0), MNode (6, {60}, 0, 0, 0))), MNode (11, {110}, 0, MNode (9, {90}, 0, MNode (8, {80}, 0, 0, 0), MNode (10, {100}, 0, 0, 0)), MNode (13, {130}, 0, MNode (12, {120}, 0, 0, 0), MNode (14, {140}, 0, 0, 0)))), MNode (23, {230}, 0, MNode (19, {190}, 0, MNode (17, {170}, 0, MNode (16, {160}, 0, 0, 0), MNode (18, {180}, 0, 0, 0)), MNode (21, {210}, 0, MNode (20, {200}, 0, 0, 0), MNode (22, {220}, 0, 0, 0))), MNode (27, {270}, 0, MNode (25, {250}, 0, MNode (24, {240}, 0, 0, 0), MNode (26, {260}, 0, 0, 0)), MNode (29, {290}, 0, MNode (28, {280}, 0, 0, 0), MNode (30, {300}, 0, 0, 0))))), MNode (47, {470}, 0, MNode (39, {390}, 0, MNode (35, {350}, 0, MNode (33, {330}, 0, MNode (32, {320}, 0, 0, 0), MNode (34, {340}, 0, 0, 0)), MNode (37, {370}, 0, MNode (36, {360}, 0, 0, 0), MNode (38, {380}, 0, 0, 0))), MNode (43, {430}, 0, MNode (41, {410}, 0, MNode (40, {400}, 0, 0, 0), MNode (42, {420}, 0, 0, 0)), MNode (45, {450}, 0, MNode (44, {440}, 0, 0, 0), MNode (46, {460}, 0, 0, 0)))), MNode (55, 0, 0, MNode (51, 0, 0, MNode (49, {490}, 0, MNode (48, {480}, 0, 0, 0), MNode (50, 0, 0, 0, 0)), MNode (53, 0, 0, MNode (52, 0, 0, 0, 0), MNode (54, 0, 0, 0, 0))), MNode (59, 0, 0, MNode (57, 0, 0, MNode (56, 0, 0, 0, 0), MNode (58, 0, 0, 0, 0)), MNode (61, 0, 0, MNode (60, 0, 0, 0, 0), MNode (62, 0, 0, 0, 0)))))), MNode (79, 0, -1, MNode (71, 0, 0, MNode (67, 0, 0, MNode (65, 0, 0, MNode (64, 0, 0, 0, 0), MNode (66, 0, 0, 0, 0)), MNode (69, 0, 0, MNode (68, 0, 0, 0, 0), MNode (70, 0, 0, 0, 0))), MNode (75, 0, 0, MNode (73, 0, 0, MNode (72, 0, 0, 0, 0), MNode (74, 0, 0, 0, 0)), MNode (77, 0, 0, MNode (76, 0, 0, 0, 0), MNode (78, 0, 0, 0, 0)))), MNode (87, 0, -1, MNode (83, 0, 0, MNode (81, 0, 0, MNode (80, 0, 0, 0, 0), MNode (82, 0, 0, 0, 0)), MNode (85, 0, 0, MNode (84, 0, 0, 0, 0), MNode (86, 0, 0, 0, 0))), MNode (95, 0, 0, MNode (91, 0, 0, MNode (89, 0, 0, MNode (88, 0, 0, 0, 0), MNode (90, 0, 0, 0, 0)), MNode (93, 0, 0, MNode (92, 0, 0, 0, 0), MNode (94, 0, 0, 0, 0))), MNode (97, 0, -1, MNode (96, 0, 0, 0, 0), MNode (98, 0, -1, 0, MNode (99, 0, 0, 0, 0))))))) + Map elements: {[0, 0], [1, 10], [2, 20], [3, 30], [4, 40], [5, 50], [6, 60], [7, 70], [8, 80], [9, 90], [10, 100], [11, 110], [12, 120], [13, 130], [14, 140], [15, 150], [16, 160], [17, 170], [18, 180], [19, 190], [20, 200], [21, 210], [22, 220], [23, 230], [24, 240], [25, 250], [26, 260], [27, 270], [28, 280], [29, 290], [30, 300], [31, 310], [32, 320], [33, 330], [34, 340], [35, 350], [36, 360], [37, 370], [38, 380], [39, 390], [40, 400], [41, 410], [42, 420], [43, 430], [44, 440], [45, 450], [46, 460], [47, 470], [48, 480], [49, 490]} + Testing 0 => Some (0) + Testing 1 => Some (10) + Testing 2 => Some (20) + Testing 3 => Some (30) + Testing 4 => Some (40) + Testing 5 => Some (50) + Testing 6 => Some (60) + Testing 7 => Some (70) + Testing 8 => Some (80) + Testing 9 => Some (90) + Testing 10 => Some (100) + Testing 11 => Some (110) + Testing 12 => Some (120) + Testing 13 => Some (130) + Testing 14 => Some (140) + Testing 15 => Some (150) + Testing 16 => Some (160) + Testing 17 => Some (170) + Testing 18 => Some (180) + Testing 19 => Some (190) + Testing 20 => Some (200) + Testing 21 => Some (210) + Testing 22 => Some (220) + Testing 23 => Some (230) + Testing 24 => Some (240) + Testing 25 => Some (250) + Testing 26 => Some (260) + Testing 27 => Some (270) + Testing 28 => Some (280) + Testing 29 => Some (290) + Testing 30 => Some (300) + Testing 31 => Some (310) + Testing 32 => Some (320) + Testing 33 => Some (330) + Testing 34 => Some (340) + Testing 35 => Some (350) + Testing 36 => Some (360) + Testing 37 => Some (370) + Testing 38 => Some (380) + Testing 39 => Some (390) + Testing 40 => Some (400) + Testing 41 => Some (410) + Testing 42 => Some (420) + Testing 43 => Some (430) + Testing 44 => Some (440) + Testing 45 => Some (450) + Testing 46 => Some (460) + Testing 47 => Some (470) + Testing 48 => Some (480) + Testing 49 => Some (490) + Testing 50 => None + Testing 51 => None + Testing 52 => None + Testing 53 => None + Testing 54 => None + Testing 55 => None + Testing 56 => None + Testing 57 => None + Testing 58 => None + Testing 59 => None + Testing 60 => None + Testing 61 => None + Testing 62 => None + Testing 63 => None + Testing 64 => None + Testing 65 => None + Testing 66 => None + Testing 67 => None + Testing 68 => None + Testing 69 => None + Testing 70 => None + Testing 71 => None + Testing 72 => None + Testing 73 => None + Testing 74 => None + Testing 75 => None + Testing 76 => None + Testing 77 => None + Testing 78 => None + Testing 79 => None + Testing 80 => None + Testing 81 => None + Testing 82 => None + Testing 83 => None + Testing 84 => None + Testing 85 => None + Testing 86 => None + Testing 87 => None + Testing 88 => None + Testing 89 => None + Testing 90 => None + Testing 91 => None + Testing 92 => None + Testing 93 => None + Testing 94 => None + Testing 95 => None + Testing 96 => None + Testing 97 => None + Testing 98 => None + Testing 99 => None + List map: MNode (2, {20}, -1, MNode (1, {10}, 0, 0, 0), MNode (4, {40}, 0, MNode (3, {30}, 0, 0, 0), MNode (5, {50}, 0, 0, 0))) diff --git a/stdlib/regression/vm/test05.t b/stdlib/regression/vm/test05.t new file mode 100644 index 000000000..fcb0eb29e --- /dev/null +++ b/stdlib/regression/vm/test05.t @@ -0,0 +1,12 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test05.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test05.bc + Cached: 1 + Cached: 1 + Cached: 1 + Cached: 1 + Cached: 1 + Cached: 1 + Cached: 1 + Cached: 1 + Cached: 1 diff --git a/stdlib/regression/vm/test06.t b/stdlib/regression/vm/test06.t new file mode 100644 index 000000000..640d06b25 --- /dev/null +++ b/stdlib/regression/vm/test06.t @@ -0,0 +1,10 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test06.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test06.bc + Flattening: 0 + Flattening: {0, 0, 0, 0} + Flattening: 0 + Flattening: {1, 2, 3} + Flattening: {1, 2, 3, 4, 5, 6, 7, 8, 9} + List to array: [1, 2, 3, 4, 5] + Array to list: {1, 2, 3, 4, 5} diff --git a/stdlib/regression/vm/test07.t b/stdlib/regression/vm/test07.t new file mode 100644 index 000000000..b4ae234bc --- /dev/null +++ b/stdlib/regression/vm/test07.t @@ -0,0 +1,9 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test07.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test07.bc + HashTab internal structure: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {[{1, 2, 3}, 100]}, 0, 0, 0] + HashTab internal structure: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {[{1, 2, 3}, 200], [{1, 2, 3}, 100]}, 0, 0, 0] + Searching: Some (200) + Searching: Some (200) + Replaced: Some (800) + Restored: Some (200) diff --git a/stdlib/regression/vm/test08.t b/stdlib/regression/vm/test08.t new file mode 100644 index 000000000..e8300dc25 --- /dev/null +++ b/stdlib/regression/vm/test08.t @@ -0,0 +1,6 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test08.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test08.bc + 6 + 120 + 5040 diff --git a/stdlib/regression/vm/test09.t b/stdlib/regression/vm/test09.t new file mode 100644 index 000000000..6d70f7c2a --- /dev/null +++ b/stdlib/regression/vm/test09.t @@ -0,0 +1,8 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test09.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test09.bc + Parsing a*| against "aa"... Succ ({"a", "a"}) + Parsing a+| against "aa"... Succ ({"a", "a"}) + Parsing list(a)| against "a"... Succ ({"a"}) + Parsing list(a)| against "a,a"... Succ ({"a", "a"}) + Parsing list0(a)| against ""... Succ (0) diff --git a/stdlib/regression/vm/test10.t b/stdlib/regression/vm/test10.t new file mode 100644 index 000000000..86eccc486 --- /dev/null +++ b/stdlib/regression/vm/test10.t @@ -0,0 +1,5 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test10.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test10.bc + Parsing "aaa" with many ... Succ ({"a", "a", "a"}) + Parsing "ab" with bad_alter ... Succ ("ab") diff --git a/stdlib/regression/vm/test11.t b/stdlib/regression/vm/test11.t new file mode 100644 index 000000000..774d82b7e --- /dev/null +++ b/stdlib/regression/vm/test11.t @@ -0,0 +1,9 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test11.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test11.bc + Succ ("a") + Succ (Add ("a", "a")) + Succ (Sub ("a", "a")) + Succ (Sub (Add ("a", "a"), "a")) + Succ (Add ("a", Mul ("a", "a"))) + Succ (Sub (Mul ("a", "a"), Div ("a", "a"))) diff --git a/stdlib/regression/vm/test12.t b/stdlib/regression/vm/test12.t new file mode 100644 index 000000000..71783661d --- /dev/null +++ b/stdlib/regression/vm/test12.t @@ -0,0 +1,4 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test12.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test12.bc + Succ (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul (Mul ("a", "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a"), "a")) diff --git a/stdlib/regression/vm/test13.t b/stdlib/regression/vm/test13.t new file mode 100644 index 000000000..02be8d6c1 --- /dev/null +++ b/stdlib/regression/vm/test13.t @@ -0,0 +1,6 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test13.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test13.bc + Succ (Add ("a", Sub ("a", "a"))) + Succ (Mul (Div (Mul ("a", "a"), "a"), "a")) + Succ (Add (Mul ("a", "a"), Sub (Div ("a", "a"), Mul ("a", "a")))) diff --git a/stdlib/regression/vm/test14.t b/stdlib/regression/vm/test14.t new file mode 100644 index 000000000..237b053b1 --- /dev/null +++ b/stdlib/regression/vm/test14.t @@ -0,0 +1,6 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test14.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test14.bc + Succ (Add ("a", Sub ("a", "a"))) + Succ (Mul (Div (Mul ("a", "a"), "a"), "a")) + Succ (Add (Mul ("a", "a"), Sub (Div ("a", "a"), Mul ("a", "a")))) diff --git a/stdlib/regression/vm/test15.t b/stdlib/regression/vm/test15.t new file mode 100644 index 000000000..d355cac35 --- /dev/null +++ b/stdlib/regression/vm/test15.t @@ -0,0 +1,7 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test15.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test15.bc + Succ (Eq ("a", "a")) + Succ (Eq (Mul ("a", "a"), Mul ("a", "a"))) + Succ (Eq (Add (Mul ("a", "a"), Sub (Div ("a", "a"), Mul ("a", "a"))), Sub (Mul ("a", "a"), "a"))) + Fail ({""*" expected at"}, 1, 2) diff --git a/stdlib/regression/vm/test16.t b/stdlib/regression/vm/test16.t new file mode 100644 index 000000000..fcfc67646 --- /dev/null +++ b/stdlib/regression/vm/test16.t @@ -0,0 +1,9 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test16.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test16.bc + Succ (Eq ("a", "a")) + Succ (Eq ("b", "b")) + Succ (Eq (Mul ("a", "a"), Mul ("a", "a"))) + Succ (Eq (Mul ("b", "b"), Mul ("b", "b"))) + Succ (Eq (Add (Mul ("a", "a"), Sub (Div ("a", "a"), Mul ("a", "a"))), Sub (Mul ("a", "a"), "a"))) + Succ (Eq (Add (Mul ("b", "b"), Sub (Div ("b", "b"), Mul ("b", "b"))), Sub (Mul ("b", "b"), "b"))) diff --git a/stdlib/regression/vm/test17.t b/stdlib/regression/vm/test17.t new file mode 100644 index 000000000..c15e2d648 --- /dev/null +++ b/stdlib/regression/vm/test17.t @@ -0,0 +1,15 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test17.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test17.bc + Lazy body: 0 + Lazy body: 1 + Lazy body: 2 + Lazy body: 3 + Lazy body: 4 + Lazy body: 5 + Lazy body: 6 + Lazy body: 7 + Lazy body: 8 + Lazy body: 9 + First force: 100 + Second force: 100 diff --git a/stdlib/regression/vm/test18.t b/stdlib/regression/vm/test18.t new file mode 100644 index 000000000..fe12ada17 --- /dev/null +++ b/stdlib/regression/vm/test18.t @@ -0,0 +1,35 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test18.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test18.bc + 1 =?= 1 = 0 + symmetricity: ok + 1 =?= 10 = -1 + symmetricity: ok + "abc" =?= "abc" = 0 + symmetricity: ok + "abc" =?= "def" = -1 + symmetricity: ok + 1 =?= "abc" = 1 + symmetricity: ok + S (1) =?= S (1) = 0 + symmetricity: ok + S (2) =?= S (1) = 1 + symmetricity: ok + S (1, 2, 3) =?= S (1, 3, 2) = -1 + symmetricity: ok + S (1, 2, 3) =?= D (5, 6) = 1 + symmetricity: ok + 1 =?= S (5) = 1 + symmetricity: ok + "abs" =?= S (5, 6) = -1 + symmetricity: ok + [1, 2, 3] =?= S (1, 2, 3) = -1 + symmetricity: ok + "abc" =?= [1, 2, 3] = -1 + symmetricity: ok + 1 =?= [1, 2, 3] = 1 + symmetricity: ok + 0 + 0 + 0 + 0 diff --git a/stdlib/regression/vm/test20.t b/stdlib/regression/vm/test20.t new file mode 100644 index 000000000..0e712c562 --- /dev/null +++ b/stdlib/regression/vm/test20.t @@ -0,0 +1,15 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test20.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test20.bc + Empty + Node (0, Empty, Empty) + Node (0, Empty, Node (1, Empty, Empty)) + Node (0, Empty, Node (1, Empty, Node (2, Empty, Empty))) + Node (0, Empty, Node (1, Empty, Node (2, Empty, Node (3, Empty, Empty)))) + Node (0, Empty, Node (1, Empty, Node (2, Empty, Node (3, Empty, Node (4, Empty, Empty))))) + Node (0, Empty, Node (1, Empty, Node (2, Empty, Node (3, Empty, Node (4, Empty, Node (5, Empty, Empty)))))) + Node (0, Empty, Node (1, Empty, Node (2, Empty, Node (3, Empty, Node (4, Empty, Node (5, Empty, Node (6, Empty, Empty))))))) + Node (0, Empty, Node (1, Empty, Node (2, Empty, Node (3, Empty, Node (4, Empty, Node (5, Empty, Node (6, Empty, Node (7, Empty, Empty)))))))) + Node (0, Empty, Node (1, Empty, Node (2, Empty, Node (3, Empty, Node (4, Empty, Node (5, Empty, Node (6, Empty, Node (7, Empty, Node (8, Empty, Empty))))))))) + Node (0, Empty, Node (1, Empty, Node (2, Empty, Node (3, Empty, Node (4, Empty, Node (5, Empty, Node (6, Empty, Node (7, Empty, Node (8, Empty, Node (9, Empty, Empty)))))))))) + Node (0, Empty, Node (1, Empty, Node (2, Empty, Node (3, Empty, Node (4, Empty, Node (5, Empty, Node (6, Empty, Node (7, Empty, Node (8, Empty, Node (9, Empty, Node (10, Empty, Empty))))))))))) diff --git a/stdlib/regression/vm/test21.t b/stdlib/regression/vm/test21.t new file mode 100644 index 000000000..5d06fb611 --- /dev/null +++ b/stdlib/regression/vm/test21.t @@ -0,0 +1,12 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test21.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test21.bc + 1 + 1 + 1 + 1 + 1 + 2 + 3 + 100 + Cons (3, Cons (2, Cons (1, Cons (6, Cons (5, Cons (4, Cons (3, Cons (2, Cons (1, Nil))))))))) diff --git a/stdlib/regression/vm/test22.t b/stdlib/regression/vm/test22.t new file mode 100644 index 000000000..3ddad17db --- /dev/null +++ b/stdlib/regression/vm/test22.t @@ -0,0 +1,7 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test22.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test22.bc + 0 + {1, 2, 3, 4} + {{1}, {2, 3}, {4, {5, 6}}} + {1, 2, 3, 4} diff --git a/stdlib/regression/vm/test23.t b/stdlib/regression/vm/test23.t new file mode 100644 index 000000000..69f308513 --- /dev/null +++ b/stdlib/regression/vm/test23.t @@ -0,0 +1,6 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test23.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test23.bc + 1 + {2, 3, 4} + 2 diff --git a/stdlib/regression/vm/test24.t b/stdlib/regression/vm/test24.t new file mode 100644 index 000000000..e75c84dff --- /dev/null +++ b/stdlib/regression/vm/test24.t @@ -0,0 +1,6 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test24.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test24.bc + 3 + {1} + {1} diff --git a/stdlib/regression/vm/test25.t b/stdlib/regression/vm/test25.t new file mode 100644 index 000000000..5f1c27f29 --- /dev/null +++ b/stdlib/regression/vm/test25.t @@ -0,0 +1,8 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test25.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test25.bc + Cloning int: 5 + Cloning string: abc + Cloning array: [1, 2, 3, 4, 5] + Cloning sexp: A (1, 2, 3, 4, 5) + Cloning closure: address ok, 5, 6 diff --git a/stdlib/regression/vm/test26.t b/stdlib/regression/vm/test26.t new file mode 100644 index 000000000..a6eb4d104 --- /dev/null +++ b/stdlib/regression/vm/test26.t @@ -0,0 +1,5 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test26.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test26.bc + Number of commands-line arguments: 1 + arg [0 ] = "test26.bc" diff --git a/stdlib/regression/vm/test27.t b/stdlib/regression/vm/test27.t new file mode 100644 index 000000000..81874487e --- /dev/null +++ b/stdlib/regression/vm/test27.t @@ -0,0 +1,4 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test27.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test27.bc + Yes diff --git a/stdlib/regression/vm/test28.t b/stdlib/regression/vm/test28.t new file mode 100644 index 000000000..c03f15938 --- /dev/null +++ b/stdlib/regression/vm/test28.t @@ -0,0 +1,7 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test28.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test28.bc + Succ (Seq ("a", "b")) + Succ (Alt ("a")) + Succ (Alt ("b")) + Succ (Rep ({"a", "a", "a"})) diff --git a/stdlib/regression/vm/test29.t b/stdlib/regression/vm/test29.t new file mode 100644 index 000000000..dc7e55d9e --- /dev/null +++ b/stdlib/regression/vm/test29.t @@ -0,0 +1,7 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test29.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test29.bc + Succ (Seq ("a", "b")) + Succ (Alt ("a")) + Succ (Alt ("b")) + Succ (Rep ({"a", "a", "a"})) diff --git a/stdlib/regression/vm/test30.t b/stdlib/regression/vm/test30.t new file mode 100644 index 000000000..23ee7e0c1 --- /dev/null +++ b/stdlib/regression/vm/test30.t @@ -0,0 +1,203 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test30.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test30.bc + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 diff --git a/stdlib/regression/vm/test32.t b/stdlib/regression/vm/test32.t new file mode 100644 index 000000000..c71cd6114 --- /dev/null +++ b/stdlib/regression/vm/test32.t @@ -0,0 +1,5 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test32.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test32.bc + Flattening: 0 + Flattening: {A, B, C, D} diff --git a/stdlib/regression/vm/test33.t b/stdlib/regression/vm/test33.t new file mode 100644 index 000000000..fab652baf --- /dev/null +++ b/stdlib/regression/vm/test33.t @@ -0,0 +1,5 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test33.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test33.bc + {}.string: 0 + {}.stringcat: diff --git a/stdlib/regression/vm/test34.t b/stdlib/regression/vm/test34.t new file mode 100644 index 000000000..46746af93 --- /dev/null +++ b/stdlib/regression/vm/test34.t @@ -0,0 +1,5 @@ +This file was autogenerated. + $ ../../../src/Driver.exe -runtime ../../../runtime -I ../../../runtime -I ../../../stdlib/x64 -b ../test34.lama + $ ../../../virtual_machine/lama.exe -I ../../../stdlib/x64 test34.bc + ' " ` % \ + \h @ $ # ; [ ] diff --git a/stdlib/x64/dune b/stdlib/x64/dune index de85adfc9..bcb4009ea 100644 --- a/stdlib/x64/dune +++ b/stdlib/x64/dune @@ -18,30 +18,43 @@ %{project_root}/src/Driver.exe) (targets Array.i + Array.bc Array.o Buffer.i + Buffer.bc Buffer.o Collection.i + Collection.bc Collection.o Data.i + Data.bc Data.o Fun.i + Fun.bc Fun.o Lazy.i + Lazy.bc Lazy.o List.i + List.bc List.o Matcher.i + Matcher.bc Matcher.o Ostap.i + Ostap.bc Ostap.o Random.i + Random.bc Random.o Ref.i + Ref.bc Ref.o STM.i + STM.bc STM.o Timer.i + Timer.bc Timer.o) (mode (promote (until-clean))) @@ -56,10 +69,13 @@ (setenv LAMAC "../../src/Driver.exe -64 -I ../../runtime" - (run make -j2 -f ../Makefile all))))))) + (progn + (run make -j2 -f ../Makefile obj) + (run make -j2 -f ../Makefile bytecode)))))))) (install (section share) (files (glob_files (*.i with_prefix x64)) + (glob_files (*.bc with_prefix x64)) (glob_files (*.o with_prefix x64)))) diff --git a/virtual_machine/Makefile b/virtual_machine/Makefile new file mode 100644 index 000000000..a7a9e0237 --- /dev/null +++ b/virtual_machine/Makefile @@ -0,0 +1,34 @@ +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -O3 +LIBS = -lffi -ldl +LDFLAGS = -rdynamic +BUILD_DIR = .vm-build +TARGET = lama.exe +SOURCES = lama.c converter.c vm.c bytecode.c ffi.c loader.c symbols.c ops.c memory.c opcodes.c disasm.c +OBJECTS = $(addprefix $(BUILD_DIR)/,$(SOURCES:.c=.o)) + +RUNTIME_DIR = ../runtime +RUNTIME_LIB = $(BUILD_DIR)/runtime/runtime.a + +all: $(TARGET) + +debug: CFLAGS += -DDEBUG_PRINT -g3 -Og -O0 -fsanitize=address +debug: $(TARGET) + +$(TARGET): $(OBJECTS) $(RUNTIME_LIB) | $(BUILD_DIR) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJECTS) -Wl,--whole-archive $(RUNTIME_LIB) -Wl,--no-whole-archive $(LIBS) + +$(RUNTIME_LIB): + $(MAKE) -C $(RUNTIME_DIR) LAMA_ENV=0 BUILD_DIR=$(abspath $(BUILD_DIR)/runtime) + +$(BUILD_DIR): + mkdir -p $@ + +$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR) + $(CC) $(CFLAGS) -c -o $@ $< + +clean: + $(MAKE) -C $(RUNTIME_DIR) BUILD_DIR=$(abspath $(BUILD_DIR)/runtime) clean + $(RM) -r $(BUILD_DIR) $(TARGET) + +.PHONY: all debug clean diff --git a/virtual_machine/README.md b/virtual_machine/README.md new file mode 100644 index 000000000..c8a42156f --- /dev/null +++ b/virtual_machine/README.md @@ -0,0 +1,67 @@ +# ![Lama](../lama.svg) virtual machine + +This directory contains the implementation of the virtual machine for the ![Lama](../lama.svg) programming language. + +Documentation is split as follows: + +* [`SPEC.md`](SPEC.md) - bytecode file format and instruction reference. +* [`README.md`](README.md) - implementation overview, build instructions, and command-line usage. + +## Build + +```bash +# Release +make + +# Debug +make debug + +# Remove build artifacts +make clean +``` + +## Usage + +The input can be a path to the main `.bc` file: + +```bash +./lama.exe Main.bc +``` + +or a unit name: + +```bash +./lama.exe Main +``` + +When a `.bc` path is used, its directory is added as the first unit search path. +When a unit name is used, the VM searches only the paths passed with `-I`. + +You can also add directories to the list of searched paths for imported modules: + +```bash +./lama.exe -I stdlib/ -I lib/ Main +``` + +Program arguments are passed after the unit name or `.bc` path: + +```bash +./lama.exe -I stdlib/ Main arg1 arg2 +./lama.exe Main.bc arg1 arg2 +``` + +To print bytecode metadata and instructions without executing the program: + +```bash +./lama.exe --disassemble Main.bc +``` + +Run `./lama.exe --help` for the full list of command-line options. + +## Architecture + +![VM Architecture](arch.svg) + +The figure shows the main components of the virtual machine and the relationships between them. The command-line interface (CLI) is the external entry point: it receives the parameters and passes control to the virtual machine facade. The facade coordinates the remaining components: it loads bytecode files, decodes and links them and prepares its garbage collector. + +The virtual machine follows a stack-based architecture where operands are pushed onto the operand stack, and operations consume these operands and push their results back onto it. diff --git a/virtual_machine/SPEC.md b/virtual_machine/SPEC.md new file mode 100644 index 000000000..f37e69a3b --- /dev/null +++ b/virtual_machine/SPEC.md @@ -0,0 +1,1164 @@ +# ![Lama](../lama.svg) Bytecode and VM Instruction Reference + +This document describes two representations: + + 1. __Bytecode format__: the serialized instructions stored in `.bc` files. + 2. __VM representation__: the decoded (threaded code) representation executed by the VM. + +## Table of contents + +* [Data types](#data-types) +* [Bytecode file layout](#bytecode-file-layout) +* [External references](#external-references) +* [Instruction reference conventions](#instruction-reference-conventions) +* [BINOP](#binop) +* [CONST](#const) +* [STRING](#string) +* [SEXP](#sexp) +* [STA](#sta) +* [JMP](#jmp) +* [END](#end) +* [DROP](#drop) +* [DUP](#dup) +* [SWAP](#swap) +* [ELEM](#elem) +* [LD](#ld) +* [ST](#st) +* [CJMP](#cjmp) +* [BEGIN](#begin) +* [BEGIN_CLOSURE](#begin_closure) +* [CLOSURE](#closure) +* [CALLC](#callc) +* [CALL](#call) +* [TAG](#tag) +* [ARRAY](#array) +* [FAIL](#fail) +* [LINE](#line) +* [PATT](#patt) +* [BARRAY](#barray) +* [EOF](#eof) + +## Data types + +Types use fixed-width, little-endian, two's complement integer encodings. The following convention is being followed in the document: + +| Type | Width | Range | +|:--|:--|:--| +| `uint8` | 1 byte | 0 to 255 | +| `int32` | 4 bytes | −231 to 231−1 | + +The `opcode` field of every bytecode instruction is `uint8`. + +# Bytecode file layout + +1. Header (24 bytes) +2. String table (`string_table_size` bytes) +3. Imports (`imports_count * 4` bytes) +4. Public symbols (`public_symbols_count * 9` bytes) +5. Code section (until `0xFF`) + +### Header + +| Field | Type | Description | +|:--|:--|:--| +| `magic` | `uint8[4]` | ASCII bytes `LaMa` | +| `version` | `int32` | bytecode format version, currently `1` | +| `string_table_size` | `int32` | size of the string table (in bytes) | +| `globals_count` | `int32` | number of global variables | +| `imports_count` | `int32` | number of imports | +| `public_symbols_count` | `int32` | number of public symbols | + +### Imports + +| Field | Type | Description | +|:--|:--|:--| +| `name_offset` | `int32` | offset into the string table for the module name | + +### Public symbols + +| Field | Type | Description | +|:--|:--|:--| +| `name_offset` | `int32` | offset into the string table for the name of a public symbol | +| `code_offset` | `int32` | bytecode offset for functions, global index for globals | +| `flag` | `uint8` | `0` = function, `1` = global | + +## External references + +`CALL` (`0x56`) and `CLOSURE` (`0x54`) instructions use negative values for external function references. `LD_GLO` (`0x20`) and `ST_GLO` (`0x40`) instructions use negative values for external global references. + +The encoding is the same for both: + +* `value >= 0` : local references (bytecode offset for functions, global index for globals) +* `value < 0`: `string_table_offset = -value -1` + +The string at that offset is looked up to resolve the external symbol at load time. + +## Instruction reference conventions + +* __Operation__ - a short description of what the instruction does. +* __Format__ - the bytecode mnemonic and operand types. +* __Forms__ - concrete opcode variants and their opcode values. +* __Operand stack__ - written as `before` → `after`. The top of stack is rightmost. +* __Description__ - bytecode-level meaning of the instruction and its operands. +* __Implementation notes__ - VM-internal representation after decoding, and the effect on registers ``. + +The VM uses the following virtual registers: + +* `ip` - instruction pointer into the decoded (threaded-code) stream +* `sp` - operand stack pointer. The operand stack grows downwards: a push decrements `sp`, and a pop increments it. +* `bp` - base pointer of the current call frame + +## BINOP + +### Operation + +Apply a binary operator to the top two stack values. + +### Format + +```text +BINOP +``` + +### Forms + +```text +ADD = 0x01 +SUB = 0x02 +MUL = 0x03 +DIV = 0x04 +MOD = 0x05 +LT = 0x06 +LE = 0x07 +GT = 0x08 +GE = 0x09 +EQ = 0x0A +NE = 0x0B +AND = 0x0C +OR = 0x0D +``` + +### Operand stack + +`..., x, y` → `..., result` + +### Description + +Each binary operator pops its two operands from the stack, applies the corresponding runtime operation, and pushes the result. + +The operand order is `x` then `y`, so the topmost stack value is the right operand. + +### Implementation notes + +```text +ADD -> [op_add] +SUB -> [op_sub] +MUL -> [op_mul] +DIV -> [op_div] +MOD -> [op_mod] +LT -> [op_lt] +LE -> [op_le] +GT -> [op_gt] +GE -> [op_ge] +EQ -> [op_eq] +NE -> [op_ne] +AND -> [op_and] +OR -> [op_or] +``` + +All binary operator handlers pop `y`, then `x`, call `runtime_fn(x, y)`, and push the resulting value. + +`` → `` + +## CONST + +### Operation + +Push a constant on the stack. + +### Format + +```text +CONST value:int32 +``` + +### Forms + +```text +CONST = 0x10 +``` + +### Operand stack + +`...` → `..., value` + +### Description + +`value` is a signed 32-bit integer literal encoded directly in the bytecode. + +### Implementation notes + +```text +[op_const][value] +``` + +Pushes `BOX(value)` onto the stack. + +`` → `` + +## STRING + +### Operation + +Push a string on the stack. + +### Format + +```text +STRING string_offset:int32 +``` + +### Forms + +```text +STRING = 0x11 +``` + +### Operand stack + +`...` → `..., value` + +### Description + +`string_offset` is an offset into the string table pointing to the literal bytes of the string. + +### Implementation notes + +```text +[op_string][str] +``` + +`string_offset` is resolved during decoding to a direct C string pointer `str`. + +`` → `` + +## SEXP + +### Operation + +Create an S-expression + +### Format + +```text +SEXP tag_offset:int32 n_fields:int32 +``` + +### Forms + +```text +SEXP = 0x12 +``` + +### Operand stack + +`..., field_1, ..., field_n` → `..., sexp` + +### Description + +`tag_offset` is an offset into the string table pointing to the S-expression tag name. `n_fields` is the number of fields consumed from the top of the operand stack. + +The fields are taken from the stack and used as the contents of the constructed S-expression. + +### Implementation notes + +```text +[op_sexp][tag_hash][n_fields] +``` + +`tag_offset` is resolved beforehand during decoding to `tag_hash` (using runtime `LtagHash`). + +`` → `` + +## STA + +### Operation + +Store a value into an aggregate. + +### Format + +```text +STA +``` + +### Forms + +```text +STA = 0x14 +``` + +### Operand stack + +`..., aggregate, index, value` → `..., value` + +### Description + +`aggregate` represents an array, S-expression, or string. + +`STA` stores `value` into `aggregate[index]`. + +The target aggregate and index are taken from the operand stack. The updated value remains on the stack after the store. + +### Implementation notes + +```text +[op_sta] +``` + +Calls `Bsta(aggregate, index, value)` and leaves `value` on the stack. + +`` → `` + +## JMP + +### Operation + +Jump unconditionally to a target instruction. + +### Format + +```text +JMP target:int32 +``` + +### Forms + +```text +JMP = 0x15 +``` + +### Operand stack + +`...` → `...` + +### Description + +`target` is a bytecode offset naming the jump destination within the code section. `target` must not be outside of the function's body. All control flow paths reaching the same jump target must agree on operand stack's depth. + +### Implementation notes + +```text +[op_jmp][target] +``` + +`target` is resolved during decoding to a threaded-code instruction pointer. + +`` → `` + +## END + +### Operation + +Return from the current function. + +### Format + +```text +END +``` + +### Forms + +```text +END = 0x16 +``` + +### Operand stack + +`..., return_value` → `..., return_value` + +### Description + +`END` terminates the current function body and returns the top stack value to the caller. `END` is only valid when the current operand-stack depth is exactly `1`, i.e. the function body leaves exactly one return value. + +### Implementation notes + +```text +[op_end] +``` + +Restores the caller frame and pushes the return value onto the caller stack. + +`` → `` + +## DROP + +### Operation + +Drop the top stack value. + +### Format + +```text +DROP +``` + +### Forms + +```text +DROP = 0x18 +``` + +### Operand stack + +`..., value` → `...` + +### Description + +`DROP` removes the top value from the operand stack. + +### Implementation notes + +```text +[op_drop] +``` + +`` → `` + +## DUP + +### Operation + +Duplicate the top stack value. + +### Format + +```text +DUP +``` + +### Forms + +```text +DUP = 0x19 +``` + +### Operand stack + +`..., value` → `..., value, value` + +### Description + +`DUP` copies the top value of the operand stack. + +### Implementation notes + +```text +[op_dup] +``` + +`` → `` + +## SWAP + +### Operation + +Swap the top two stack values. + +### Format + +```text +SWAP +``` + +### Forms + +```text +SWAP = 0x1A +``` + +### Operand stack + +`..., x, y` → `..., y, x` + +### Description + +`SWAP` exchanges the top two values on the operand stack. + +### Implementation notes + +```text +[op_swap] +``` + +`` → `` + +## ELEM + +### Operation + +Load a value from an aggregate. + +### Format + +```text +ELEM +``` + +### Forms + +```text +ELEM = 0x1B +``` + +### Operand stack + +`..., aggregate, index` → `..., value` + +### Description + +`aggregate` represents an array, S-expression, or string. + +`ELEM` loads the value stored at `aggregate[index]`. + +### Implementation notes + +```text +[op_elem] +``` + +Calls `Belem(aggregate, index)` and pushes the loaded value. + +`` → `` + +## LD + +### Operation + +Load a variable to the stack. + +### Format + +```text +LD operand:int32 +``` + +### Forms + +```text +LD_GLO = 0x20 +LD_LOC = 0x21 +LD_ARG = 0x22 +LD_CLO = 0x23 +``` + +### Operand stack + +`...` → `..., value` + +### Description + +`LD_GLO`: + +* `operand` field uses the same external global reference encoding described in [external references](#external-references). + +`LD_LOC`: + +* `operand` is a local slot index. +* `operand` must satisfy `0 <= operand < n_locals`. + +`LD_ARG`: + +* `operand` is an argument slot index. +* `operand` must satisfy `0 <= operand < n_args`. + +`LD_CLO`: + +* `operand` is a closure capture index. +* `operand` must satisfy `0 <= operand < n_captured`. + +### Implementation notes + +```text +LD_GLO -> [op_ld_glo][global_ptr] +LD_LOC -> [op_ld_loc][local] +LD_ARG -> [op_ld_arg][arg] +LD_CLO -> [op_ld_clo][capture] +``` + +`LD_GLO` resolves `operand` during decoding to `global_ptr`. +`LD_CLO` reads captured value `operand` from `closure[operand + 1]`. + +`` → `` + +## ST + +### Operation + +Store a value into a variable. + +### Format + +```text +ST operand:int32 +``` + +### Forms + +```text +ST_GLO = 0x40 +ST_LOC = 0x41 +ST_ARG = 0x42 +ST_CLO = 0x43 +``` + +### Operand stack + +`..., value` → `..., value` + +### Description + +`ST_GLO`: + +* `operand` field uses the same external global reference encoding described in [external references](#external-references). + +`ST_LOC`: + +* `operand` is a local slot index. +* `operand` must satisfy `0 <= operand < n_locals`. + +`ST_ARG`: + +* `operand` is an argument slot index. +* `operand` must satisfy `0 <= operand < n_args`. + +`ST_CLO`: + +* `operand` is a closure capture index. +* `operand` must satisfy `0 <= operand < n_captured`. + +### Implementation notes + +```text +ST_GLO -> [op_st_glo][global_ptr] +ST_LOC -> [op_st_loc][local] +ST_ARG -> [op_st_arg][arg] +ST_CLO -> [op_st_clo][capture] +``` + +`ST_GLO` resolves `operand` during decoding to `global_ptr`. +`ST_CLO` stores captured value `operand` in `closure[operand + 1]`. + +`` → `` + +## CJMP + +### Operation + +Jump conditionally to a target instruction. + +### Format + +```text +CJMP target:int32 +``` + +### Forms + +```text +CJMP_Z = 0x50 +CJMP_NZ = 0x51 +``` + +### Operand stack + +`..., value` → `...` + +### Description + +`CJMP_Z` jumps when `value == 0`. `CJMP_NZ` jumps when `value != 0`. `target` is a bytecode offset naming the jump destination within the code section. `target` must not be outside of the function's body. All control flow paths reaching the same jump target must agree on operand stack's depth. + +### Implementation notes + +```text +CJMP_Z -> [op_cjmp_z][target] +CJMP_NZ -> [op_cjmp_nz][target] +``` + +`target` is resolved during decoding to a threaded-code instruction pointer. + +`` → `<(target | ip + 2), sp + 1, bp>` + +## BEGIN + +### Operation + +Enter a function body and allocate local slots. + +### Format + +```text +BEGIN n_args:int32 n_locals:int32 +``` + +### Forms + +```text +BEGIN = 0x52 +``` + +### Operand stack + +`..., arg_1, ..., arg_n` → `..., arg_1, ..., arg_n, local_1, ..., local_m` + +### Description + +`n_args` is the number of function arguments and `n_locals` is the number of local slots allocated for the function body. + +Each local slot is initialized to `BOX(0)`. + +### Implementation notes + +```text +[op_begin][n_args][n_locals] +``` + +`` → `` + +## BEGIN_CLOSURE + +### Operation + +Enter a closure body and allocate local slots. + +### Format + +```text +BEGIN_CLOSURE n_args:int32 n_locals:int32 n_captured:int32 +``` + +### Forms + +```text +BEGIN_CLOSURE = 0x53 +``` + +### Operand stack + +`..., arg_1, ..., arg_n, closure` → `..., arg_1, ..., arg_n, closure, local_1, ..., local_m` + +### Description + +`n_args` is the number of call arguments, `n_locals` is the number of local slots allocated for the closure body, and `n_captured` is the number of captured values expected by the closure entry point. + +Each local slot is initialized to `BOX(0)`. Every `CLOSURE` that targets that entry point must use the same `n_captured` value. + +### Implementation notes + +```text +[op_begin_closure][n_args][n_locals] +``` + +`n_captured` is validated during decoding and is not carried into the threaded representation. + +`` → `` + +## CLOSURE + +### Operation + +Create a closure object and push it on the stack. + +### Format + +```text +CLOSURE target:int32 n_captured:int32 (kind:uint8 index:int32)* +``` + +### Forms + +```text +CLOSURE = 0x54 +``` + +### Operand stack + +`...` → `..., closure` + +### Description + +`target` is a bytecode offset naming the closure entry point. `n_captured` is the number of captured values stored in the closure. + +Each capture designation is encoded as a `(kind:uint8 index:int32)` pair, where: + +* `kind = 0` denotes a global variable +* `kind = 1` denotes a local variable +* `kind = 2` denotes a function argument +* `kind = 3` denotes a captured closure variable + +The `target` operand uses the same external function reference encoding described in [external references](#external-references). Internal `target` values must be in range. Internal and inter-unit `target` must point to a function entry `BEGIN` or `BEGIN_CLOSURE`. For each capture designation, the referenced index must be valid for its kind: +* `0 <=` local index `< n_locals`. +* `0 <=` argument index `< n_args`. +* `0 <=` closure capture index `< current function n_captured`. +For a given internal closure entry point, all `CLOSURE` instructions targeting it must agree on `n_captured`. + + +### Implementation notes + +```text +capture_* -> LD +[op_closure][target][n_captured] +``` + +Each capture designation is translated during decoding into a corresponding [LD](#ld) instruction. `target` is then resolved to a threaded-code instruction pointer. + +`op_closure` consumes the already loaded captured values from the stack. The runtime closure layout is: + +* `closure[0]` = entry point +* `closure[i + 1]` = captured value `i` + +`` → `` + +## CALLC + +### Operation + +Call a closure value. + +### Format + +```text +CALLC n_args:int32 +``` + +### Forms + +```text +CALLC = 0x55 +``` + +### Operand stack + +`..., arg_1, ..., arg_n, closure` → `..., result` + +### Description + +`n_args` is the number of call arguments. The closure value is taken from the top of the stack. + +### Implementation notes + +```text +[op_callc][n_args] +``` + +Reads the entry point from `closure[0]`, pushes a new call frame, and transfers control to it. + +`` → `` + +## CALL + +### Operation + +Call a function. + +### Format + +```text +CALL target:int32 n_args:int32 +``` + +### Forms + +```text +CALL = 0x56 +``` + +### Operand stack + +`..., arg_1, ..., arg_n` → `..., result` + +### Description + +`target` is a bytecode offset naming the call target. `n_args` is the number of call arguments. + +The `target` operand uses the same external function reference encoding described in [external references](#external-references). Internal `target` values must be in range. Internal and inter-unit `CALL` must point to a function entry `BEGIN`. + +### Implementation notes + +```text +[op_call][target][n_args] +``` + +`target` is resolved during decoding to a threaded-code instruction pointer. + +`` → `` + +## TAG + +### Operation + +Check whether a value is an S-expression with a given tag and arity. + +### Format + +```text +TAG tag_offset:int32 n_fields:int32 +``` + +### Forms + +```text +TAG = 0x57 +``` + +### Operand stack + +`..., value` → `..., result` + +### Description + +`tag_offset` is an offset into the string table naming the expected S-expression tag. `n_fields` is the expected number of fields. + +### Implementation notes + +```text +[op_tag][tag_hash][n_fields] +``` + +`tag_offset` is resolved during decoding to `tag_hash` (using runtime `LtagHash`). + +`` → `` + +## ARRAY + +### Operation + +Check whether a value is an array of a given length. + +### Format + +```text +ARRAY n:int32 +``` + +### Forms + +```text +ARRAY = 0x58 +``` + +### Operand stack + +`..., value` → `..., result` + +### Description + +`n` is the expected array length. + +### Implementation notes + +```text +[op_array][n] +``` + +`` → `` + +## FAIL + +### Operation + +Raise a pattern-match failure. + +### Format + +```text +FAIL line:int32 col:int32 +``` + +### Forms + +```text +FAIL = 0x59 +FAIL_KEEP = 0x5A +``` + +### Operand stack + +`..., value` → `...` for `FAIL` + +`..., value` → `..., value` for `FAIL_KEEP` + +### Description + +`line` and `col` identify the source position reported for the match failure. + +`FAIL` consumes the top value before reporting the failure. `FAIL_KEEP` reports the failure while keeping the top value. + +### Implementation notes + +```text +[op_fail][line][col][drop_value][module_name] +``` + +`` → `⊥` + +## LINE + +### Operation + +Emit a line marker. + +### Format + +```text +LINE line:int32 +``` + +### Forms + +```text +LINE = 0x5B +``` + +### Operand stack + +`...` → `...` + +### Description + +`line` is a source line number associated with the following bytecode position. + +### Implementation notes + +```text +[op_line][line] +``` + +In non-`DEBUG_PRINT` builds, `LINE` is skipped during decoding and does not appear in the threaded representation. + +`` → `` + +## PATT + +### Operation + +Apply a pattern predicate. + +### Format + +```text +PATT +``` + +### Forms + +```text +PATT_STR_CMP = 0x60 +PATT_STRING = 0x61 +PATT_ARRAY = 0x62 +PATT_SEXP = 0x63 +PATT_BOXED = 0x64 +PATT_UNBOXED = 0x65 +PATT_CLOSURE = 0x66 +``` + +### Operand stack + +`..., x, y` → `..., result` for `PATT_STR_CMP` + +`..., value` → `..., result` for all other forms + +### Description + +Each `PATT_*` instruction applies a runtime predicate used by pattern matching and pushes a boolean(-like) result. + +`PATT_STR_CMP` compares two values. The remaining forms test whether a single value matches the corresponding runtime shape. + +### Implementation notes + +```text +PATT_STR_CMP -> [op_patt_str_cmp] +PATT_STRING -> [op_patt_string] +PATT_ARRAY -> [op_patt_array] +PATT_SEXP -> [op_patt_sexp] +PATT_BOXED -> [op_patt_boxed] +PATT_UNBOXED -> [op_patt_unboxed] +PATT_CLOSURE -> [op_patt_closure] +``` + +`PATT_STR_CMP` behaves like a binary operator. All other `PATT_*` forms behave like unary operators. + +`` → `` for `PATT_STR_CMP` + +`` → `` for all other forms + +## BARRAY + +### Operation + +Construct an array and push it onto the stack. + +### Format + +```text +BARRAY n:int32 +``` + +### Forms + +```text +BARRAY = 0x74 +``` + +### Operand stack + +`..., elem_1, ..., elem_n` → `..., array` + +### Description + +`n` is the number of array elements consumed from the top of the operand stack. + +### Implementation notes + +```text +[op_barray][n] +``` + +`op_barray` consumes `n` stack values, reverses them into array order, allocates a runtime array with `Barray`, and pushes the resulting array value. + +`` → `` + +## EOF + +### Operation + +Mark the end of the bytecode stream. + +### Format + +```text +EOF +``` + +### Forms + +```text +EOF = 0xFF +``` + +### Operand stack + +`...` → `...` + +### Description + +`EOF` terminates the bytecode stream. It must appear at the end of the code section and outside any function body. + +### Implementation notes + +```text +[op_eof] +``` + +`` → `⊥` diff --git a/virtual_machine/arch.svg b/virtual_machine/arch.svg new file mode 100644 index 000000000..9b238f7b2 --- /dev/null +++ b/virtual_machine/arch.svg @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/virtual_machine/bytecode.c b/virtual_machine/bytecode.c new file mode 100644 index 000000000..a7023965d --- /dev/null +++ b/virtual_machine/bytecode.c @@ -0,0 +1,204 @@ +#define _POSIX_C_SOURCE 200809L +#include "bytecode.h" +#include "memory.h" +#include "opcodes.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAGIC "LaMa" +#define MAGIC_SIZE 4 +#define BYTECODE_VERSION 1 +#define HEADER_SIZE (MAGIC_SIZE + 20) +#define PUB_ENTRY_SIZE 9 +#define IMPORT_ENTRY_SIZE 4 + +bytecode *bytecode_load_fd(int fd) { + bytecode *bc = NULL; + const uint8_t *data = MAP_FAILED; + size_t file_size = 0; + struct stat st; + if (fstat(fd, &st) < 0) { + perror("bytecode_load: fstat"); + goto out; + } + + file_size = (size_t)st.st_size; + + if (file_size == 0) { + fprintf(stderr, "bytecode_load: empty file\n"); + goto out; + } + + data = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (data == MAP_FAILED) { + perror("bytecode_load: mmap"); + goto out; + } + + byte_reader reader; + reader_init(&reader, data, file_size); + + if (file_size < HEADER_SIZE) { + fprintf(stderr, "bytecode_load: file too small for header (%zu bytes)\n", + file_size); + goto out; + } + + if (memcmp(data, MAGIC, MAGIC_SIZE) != 0) { + fprintf(stderr, "bytecode_load: invalid magic number\n"); + goto out; + } + + reader_skip(&reader, MAGIC_SIZE); + + int32_t version = reader_i32(&reader); + int32_t string_table_size = reader_i32(&reader); + int32_t globals_count = reader_i32(&reader); + int32_t num_imports = reader_i32(&reader); + int32_t num_pubs = reader_i32(&reader); + + if (version != BYTECODE_VERSION) { + fprintf(stderr, "bytecode_load: unsupported bytecode version %d\n", + version); + goto out; + } + + if (string_table_size < 0 || globals_count < 0 || num_imports < 0 || + num_pubs < 0) { + fprintf(stderr, "bytecode_load: negative header field\n"); + goto out; + } + + size_t st_offset = HEADER_SIZE; + size_t imports_offset = st_offset + (size_t)string_table_size; + size_t pubs_offset = imports_offset + (size_t)num_imports * IMPORT_ENTRY_SIZE; + size_t code_offset = pubs_offset + (size_t)num_pubs * PUB_ENTRY_SIZE; + + if (code_offset > file_size) { + fprintf(stderr, + "bytecode_load: sections exceed file size (code_offset=%zu, " + "file_size=%zu)\n", + code_offset, file_size); + goto out; + } + + size_t code_size = file_size - code_offset; + + if (code_size == 0 || data[code_offset + code_size - 1] != OP_EOF) { + fprintf(stderr, "bytecode_load: bytecode must end with EOF opcode\n"); + goto out; + } + + const char *string_table = (const char *)data + st_offset; + + bc = ALLOC(bytecode); + + bc->map_base = data; + bc->map_size = file_size; + bc->version = version; + + bc->string_table = string_table; + bc->string_table_size = (size_t)string_table_size; + bc->code = data + code_offset; + bc->code_size = code_size; + bc->globals_count = (size_t)globals_count; + + bc->pubs = data + pubs_offset; + bc->pubs_len = (size_t)num_pubs; + + bc->imports = data + imports_offset; + bc->imports_len = (size_t)num_imports; + + // will be set later + bc->name = NULL; + +out: + close(fd); + if (!bc && data != MAP_FAILED) { + munmap((void *)data, file_size); + } + return bc; +} + +bytecode *bytecode_load(const char *filename) { + int fd = open(filename, O_RDONLY); + if (fd < 0) { + perror("bytecode_load: open"); + return NULL; + } + + return bytecode_load_fd(fd); +} + +void bytecode_pubs_init(bytecode_iterator *iter, const bytecode *bc) { + reader_init(&iter->reader, bc->pubs, bc->pubs_len * PUB_ENTRY_SIZE); + iter->string_table = bc->string_table; + iter->string_table_size = bc->string_table_size; + iter->len = bc->pubs_len; + iter->curr = 0; +} + +bool bytecode_pubs_next(bytecode_iterator *iter, public_symbol *out) { + if (iter->curr >= iter->len) { + return false; + } + int32_t name_offset = reader_i32(&iter->reader); + if (name_offset < 0 || (size_t)name_offset >= iter->string_table_size) { + fprintf(stderr, "bytecode_pubs_next: name_offset %d out of range\n", + name_offset); + return false; + } + out->name = iter->string_table + name_offset; + out->code_offset = reader_i32(&iter->reader); + out->flag = reader_u8(&iter->reader); + + iter->curr++; + return true; +} + +void bytecode_imports_init(bytecode_iterator *it, const bytecode *bc) { + reader_init(&it->reader, bc->imports, bc->imports_len * IMPORT_ENTRY_SIZE); + it->string_table = bc->string_table; + it->string_table_size = bc->string_table_size; + it->len = bc->imports_len; + it->curr = 0; +} + +bool bytecode_imports_next(bytecode_iterator *it, const char **out_name) { + if (it->curr >= it->len) { + return false; + } + int32_t name_offset = reader_i32(&it->reader); + if (name_offset < 0 || (size_t)name_offset >= it->string_table_size) { + fprintf(stderr, "bytecode_imports_next: name_offset %d out of range\n", + name_offset); + return false; + } + *out_name = it->string_table + name_offset; + + it->curr++; + return true; +} + +size_t bytecode_count_globals(bytecode **bc_arr, size_t n) { + size_t total = 0; + for (size_t i = 0; i < n; i++) { + total += bc_arr[i]->globals_count; + } + return total; +} + +void bytecode_free(bytecode *bc) { + if (!bc) { + return; + } + munmap((void *)bc->map_base, bc->map_size); + free((void *)bc->name); + free(bc); +} diff --git a/virtual_machine/bytecode.h b/virtual_machine/bytecode.h new file mode 100644 index 000000000..defb82b67 --- /dev/null +++ b/virtual_machine/bytecode.h @@ -0,0 +1,74 @@ +#ifndef BYTECODE_H +#define BYTECODE_H + +#include "reader.h" +#include +#include +#include + +#define PUB_FLAG_FUNCTION 0 +#define PUB_FLAG_GLOBAL 1 + +typedef struct { + const char *name; // Direct pointer to string + int32_t code_offset; // Offset into bytecode section (for functions) or global + // index + uint8_t flag; // PUB_FLAG_FUNCTION or PUB_FLAG_GLOBAL +} public_symbol; + +typedef struct { + // Memory-mapped file + const uint8_t *map_base; + size_t map_size; + + int32_t version; + + const char *string_table; + size_t string_table_size; + + const uint8_t *code; + size_t code_size; + + const uint8_t *pubs; + size_t pubs_len; + + const uint8_t *imports; + size_t imports_len; + + size_t globals_count; + + const char *name; +} bytecode; + +bytecode *bytecode_load_fd(int fd); +bytecode *bytecode_load(const char *filename); + +void bytecode_free(bytecode *bc); + +typedef struct { + byte_reader reader; + const char *string_table; + size_t string_table_size; + size_t len; + size_t curr; +} bytecode_iterator; + +size_t bytecode_count_globals(bytecode **bc_arr, size_t n); + +void bytecode_pubs_init(bytecode_iterator *iter, const bytecode *bc); +bool bytecode_pubs_next(bytecode_iterator *iter, public_symbol *out); + +void bytecode_imports_init(bytecode_iterator *iter, const bytecode *bc); +bool bytecode_imports_next(bytecode_iterator *iter, const char **out_name); +/* + * Get string from string table by offset + */ +static inline const char *bytecode_get_string(const bytecode *bc, + int32_t offset) { + if (!bc || offset < 0 || (size_t)offset >= bc->string_table_size) { + return NULL; + } + return bc->string_table + offset; +} + +#endif // BYTECODE_H diff --git a/virtual_machine/converter.c b/virtual_machine/converter.c new file mode 100644 index 000000000..ba9e312a6 --- /dev/null +++ b/virtual_machine/converter.c @@ -0,0 +1,1256 @@ +#include "converter.h" +#include "bytecode.h" +#include "da.h" +#include "debug.h" +#include "ffi.h" +#include "memory.h" +#include "opcodes.h" +#include "ops.h" +#include "symbols.h" +#include +#include +#include +#include +#include +#include +#include + +extern aint LtagHash(const char *s); + +/* + * Sentinel value for external references (both functions and globals). + * Address = -index - 1, so index 0 becomes -1, index 1 becomes -2, etc. + */ +#define TO_EXT_REF(idx) (-(idx) - 1) +#define IS_EXT_REF(addr) ((addr) < 0) +#define EXT_REF_INDEX(addr) (-(addr) - 1) + +#define GLOBAL_PREFIX "global_" + +#define FFI_STUB_SIZE 2 + +typedef enum { + INTERNAL, // internal call + UNIT, // inter-unit call + FFI, // FFI call +} reloc_kind; + +typedef enum { + TARGET_JUMP, // must not land on function entry or EOF + TARGET_CALL, // must land on OP_BEGIN + TARGET_CLOSURE, // must land on OP_BEGIN or OP_BEGIN_CLOSURE +} target_kind; + +typedef struct { + size_t patch_idx; + const char *name; + reloc_kind kind; +} reloc; + +typedef struct fixup_node { + struct fixup_node *next; + size_t insn_idx; // Index in code array that needs the jump target + size_t origin_bc_off; // Source bytecode offset +} fixup_node; + +// Metadata for each bytecode offset +typedef struct { + insn *insn; // NULL if not visited + fixup_node *fixups; // Linked list of forward jumps pointing here + int32_t resolved_idx; // Index in generated code array (-1 if not visited) + int32_t stack_depth; // Expected stack depth (-1 if not visited yet) + int32_t n_captured; // n_captured for CLOSURE targets (-1 if not a target) + int32_t func_idx; // Current function entry offset (-1 outside any function) +} meta_info; + +typedef struct { + insn *code; + size_t code_len; + int32_t *bc_to_insn_map; + reloc *relocs; + size_t relocs_len; +} decoded; + +/* + * Cache for external C globals.. + * Also used as GC root table. + */ +typedef struct { + const char *name; + void *ptr; +} ext_global_entry; + +typedef struct { + struct { + ext_global_entry *data; + size_t len; + size_t cap; + } entries; +} ext_global_cache; + +/* + * Different states of reachability for stack validation: + * LIVE: currently decoding sequentially, reachable from previous instruction + * BARRIER: just emitted JMP or END, so next instruction is reachable but not + * from previous instruction + */ +typedef enum { LIVE, BARRIER } reach_state; + +typedef struct { + int32_t depth; + reach_state state; +} stack_validation; + +typedef struct { + int32_t n_locals; + int32_t n_args; + int32_t n_captured; // 0 for BEGIN, >0 for BEGIN_CLOSURE +} func_ctx; + +typedef struct { + const bytecode *bc; + + struct { + insn *data; + size_t len; + size_t cap; + } code; + + byte_reader reader; + aint *globals; + size_t global_offset; + + struct { + reloc *data; + size_t len; + size_t cap; + } relocs; + + int32_t *bc_to_insn_map; + symbol_table *st; + ffi_call_table *ffi; + ext_global_cache *ext_globals; + + stack_validation sv; + + func_ctx func; + int32_t func_idx; // Current function entry offset (-1 outside any function) +} decode_ctx; + +static void decode_ctx_init(decode_ctx *ctx, const bytecode *bc, + symbol_table *st, ffi_call_table *ffi, + ext_global_cache *ext_globals, aint *globals, + size_t global_offset) { + ctx->bc = bc; + + ctx->globals = globals; + ctx->global_offset = global_offset; + ctx->bc_to_insn_map = ALLOC_ARRAY(int32_t, bc->code_size); + + da_init(ctx->code); + da_init(ctx->relocs); + + ctx->st = st; + ctx->ffi = ffi; + ctx->ext_globals = ext_globals; + + ctx->sv = (stack_validation){0}; + ctx->func = (func_ctx){.n_captured = -1}; + ctx->func_idx = -1; + + reader_init(&ctx->reader, bc->code, bc->code_size); +} + +static void free_decoded_arr(decoded *arr, size_t n) { + for (size_t i = 0; i < n; i++) { + free(arr[i].code); + free(arr[i].bc_to_insn_map); + free(arr[i].relocs); + } +} + +static void add_reloc(decode_ctx *ctx, size_t patch_idx, const char *name, + reloc_kind kind) { + reloc s = {.patch_idx = patch_idx, .name = name, .kind = kind}; + da_append(ctx->relocs, s); +} + +static fixup_node *add_fixup(meta_info *meta, size_t target_off, + size_t insn_idx, size_t origin_bc_off) { + fixup_node *node = ALLOC(fixup_node); + node->insn_idx = insn_idx; + node->origin_bc_off = origin_bc_off; + node->next = meta[target_off].fixups; + meta[target_off].fixups = node; + return node; +} + +/* + * Validate that an internal target is valid: in range, and has a correct + * opcode. + */ +static bool validate_target_off(const bytecode *bc, int32_t target_off, + size_t current_bc_off, target_kind kind) { + if (target_off < 0 || target_off >= (int32_t)bc->code_size) { + fprintf(stderr, "Error: target_off=%d out of range at bc_off=%zu\n", + target_off, current_bc_off); + return false; + } + + uint8_t got = bc->code[target_off]; + bool bad; + switch (kind) { + case TARGET_JUMP: + bad = opcode_is_func_begin(got) || got == OP_EOF; + break; + case TARGET_CALL: + bad = got != OP_BEGIN; + break; + case TARGET_CLOSURE: + bad = !opcode_is_func_begin(got); + break; + } + if (bad) { + fprintf(stderr, "Error: bad target %s at bc_off=%zu, target=%d\n", + opcode_to_string(got), current_bc_off, target_off); + return false; + } + return true; +} + +/* + * Resolve an external C global -- prefix with "global_", dlsym, cache. + */ +static void *resolve_ext_global_ptr(ext_global_cache *cache, const char *name) { + for (size_t i = 0; i < cache->entries.len; i++) { + if (strcmp(cache->entries.data[i].name, name) == 0) { + return cache->entries.data[i].ptr; + } + } + + size_t nlen = strlen(name); + char prefixed[sizeof(GLOBAL_PREFIX) + nlen]; + memcpy(prefixed, GLOBAL_PREFIX, sizeof(GLOBAL_PREFIX) - 1); + memcpy(prefixed + sizeof(GLOBAL_PREFIX) - 1, name, nlen + 1); + + void *ptr = dlsym(RTLD_DEFAULT, prefixed); + if (!ptr) { + fprintf(stderr, "Error: unresolved global '%s' (tried '%s')\n", name, + prefixed); + return NULL; + } + + ext_global_entry entry = {.name = name, .ptr = ptr}; + da_append(cache->entries, entry); + return ptr; +} + +static aint *resolve_global_ptr(decode_ctx *ctx, int32_t idx, + size_t global_base) { + if (!IS_EXT_REF(idx)) { + return &ctx->globals[global_base + idx]; + } + + int str_offset = EXT_REF_INDEX(idx); + const char *glob_name = bytecode_get_string(ctx->bc, str_offset); + VM_DEBUG("DECODE: external global '%s'\n", glob_name); + + resolved_symbol *sym = symbol_table_find_global(ctx->st, glob_name); + if (sym) { + return &ctx->globals[sym->idx]; + } + + return (aint *)resolve_ext_global_ptr(ctx->ext_globals, glob_name); +} + +/* + * Code emission macros - append to code array in context + */ +#define EMIT_FUNC(f) da_append(ctx->code, ((insn){.func = (f)})) +#define EMIT_NUM(n) da_append(ctx->code, ((insn){.num = (n)})) +#define EMIT_ANUM(n) da_append(ctx->code, ((insn){.anum = (n)})) +#define EMIT_STR(s) da_append(ctx->code, ((insn){.str = (s)})) +#define EMIT_TARGET(t) da_append(ctx->code, ((insn){.target = (t)})) +#define EMIT_GLOBAL_PTR(p) da_append(ctx->code, ((insn){.global_ptr = (p)})) +#define EMIT_PTR(p) da_append(ctx->code, ((insn){.ptr = (p)})) + +#define ENTRY_STEP_SLOTS 4 + +static void emit_entry_step(insn *slot, insn *main_begin) { + slot[0].func = op_call; + slot[1].target = main_begin; + slot[2].num = 0; + // Pop the result of the main unit's BEGIN since we don't do anything + // with it. + slot[3].func = op_drop; +} + +static bool emit_glo(decode_ctx *ctx, int32_t idx, size_t global_base, fn op) { + aint *ptr = resolve_global_ptr(ctx, idx, global_base); + if (!ptr) { + return false; + } + + EMIT_FUNC(op); + EMIT_GLOBAL_PTR(ptr); + return true; +} + +/* + * Emit target slot for CALL/CLOSURE, handles external and internal targets. + */ +static bool emit_target(decode_ctx *ctx, meta_info *meta, int32_t target_off, + size_t current_bc_off, target_kind kind) { + const bytecode *bc = ctx->bc; + size_t target_slot = ctx->code.len; + + if (IS_EXT_REF(target_off)) { + int str_offset = EXT_REF_INDEX(target_off); + const char *name = bytecode_get_string(bc, str_offset); + VM_DEBUG("DECODE: %s external target '%s' at bc_off=%zu\n", + opcode_to_string(bc->code[current_bc_off]), name, current_bc_off); + + resolved_symbol *sym = symbol_table_find_function(ctx->st, name); + if (sym) { + add_reloc(ctx, target_slot, name, UNIT); + EMIT_NUM( + sym->idx); // placeholder, will be resolved to inter-unit function + } else { + size_t idx = ffi_call_table_intern(ctx->ffi, name); + add_reloc(ctx, target_slot, name, FFI); + EMIT_NUM(idx); // placeholder, will be resolved to FFI call + } + } else { + if (!validate_target_off(bc, target_off, current_bc_off, kind)) + return false; + + EMIT_NUM(0); // placeholder — will hold code index + + meta_info *tm = &meta[target_off]; + if (target_off < (int32_t)current_bc_off) { + assert(tm->resolved_idx != -1); + ctx->code.data[target_slot].num = tm->resolved_idx; + add_reloc(ctx, target_slot, NULL, INTERNAL); + } else { + add_fixup(meta, target_off, target_slot, current_bc_off); + } + } + return true; +} + +/* + * Handle jump target resolution (intra-unit only — these are always local) + */ +static bool handle_jump(decode_ctx *ctx, meta_info *meta, + size_t current_bc_off) { + int32_t target_off = reader_i32(&ctx->reader); + int32_t depth = ctx->sv.depth; + + if (!validate_target_off(ctx->bc, target_off, current_bc_off, TARGET_JUMP)) { + return false; + } + + size_t my_idx = ctx->code.len; + EMIT_NUM(0); // placeholder — will hold code index + + meta_info *tm = &meta[target_off]; + if (target_off < (int32_t)current_bc_off) { + // Backward jump — target was already visited by sequential decode + assert(tm->resolved_idx != -1 && + "backward jump target must have been visited"); + if (tm->func_idx != ctx->func_idx) { + fprintf( + stderr, + "Error: backward jump escapes function at bc_off=%zu, target=%d\n", + current_bc_off, target_off); + return false; + } + ctx->code.data[my_idx].num = tm->resolved_idx; + + add_reloc(ctx, my_idx, NULL, INTERNAL); + VM_DEBUG(" JUMP: backward to bc_off=%d, (depth=%d, target_depth=%d)\n", + target_off, depth, tm->stack_depth); + assert(tm->stack_depth != -1 && + "backward jump target must have known stack depth"); + if (tm->stack_depth != depth) { + fprintf(stderr, + "Error: Jump stack mismatch at bc_off=%zu (exptected %d, " + "actual %d)\n", + current_bc_off, depth, tm->stack_depth); + return false; + } + } else { + // Forward jump — add fixup + add_fixup(meta, target_off, my_idx, current_bc_off); + VM_DEBUG(" JUMP: forward to bc_off=%d, (depth=%d, target_depth=%d)\n", + target_off, depth, tm->stack_depth); + if (tm->stack_depth == -1) { + tm->stack_depth = depth; + } else if (tm->stack_depth != depth) { + fprintf(stderr, + "Error: Jump stack mismatch at bc_off=%zu (expected %d, actual " + "%d)\n", + current_bc_off, depth, tm->stack_depth); + return false; + } + } + return true; +} + +static bool validate_closure_captures(meta_info *meta, int32_t target_off, + int32_t n_captured, + size_t current_bc_off) { + int32_t *expected = &meta[target_off].n_captured; + if (*expected == -1) { + *expected = n_captured; + return true; + } + if (*expected != n_captured) { + fprintf(stderr, + "Error: mismatched closure arity at bc_off=%zu, target=%d " + "(expected %d, got %d)\n", + current_bc_off, target_off, *expected, n_captured); + return false; + } + return true; +} + +#define DEPTH_INC(n) \ + do { \ + VM_DEBUG(" DEPTH: %d -> %d (+%d)\n", ctx->sv.depth, ctx->sv.depth + (n), \ + (n)); \ + ctx->sv.depth += (n); \ + } while (0) +#define DEPTH_DEC(n) \ + do { \ + VM_DEBUG(" DEPTH: %d -> %d (-%d)\n", ctx->sv.depth, ctx->sv.depth - (n), \ + (n)); \ + ctx->sv.depth -= (n); \ + assert(ctx->sv.depth >= 0 && "stack underflow"); \ + } while (0) +#define DEPTH_PUSH() DEPTH_INC(1) +#define DEPTH_POP() DEPTH_DEC(1) + +#define CHECK_IDX(idx, limit, name) \ + do { \ + if ((idx) < 0 || (idx) >= (limit)) { \ + fprintf(stderr, "%s: index %d out of range [0, %d) at bc_off=%zu\n", \ + name, (int)(idx), (int)(limit), current_bc_off); \ + goto cleanup; \ + } \ + } while (0) + +static bool decode_internal(decode_ctx *ctx) { + + const bytecode *bc = ctx->bc; + size_t global_base = ctx->global_offset; + + meta_info *meta = ALLOC_ARRAY(meta_info, bc->code_size); + + // Initialize meta table + for (size_t i = 0; i < bc->code_size; i++) { + meta[i].resolved_idx = -1; + meta[i].stack_depth = -1; + meta[i].n_captured = -1; + meta[i].func_idx = -1; + meta[i].fixups = NULL; + } + + bool ok = false; + + while (!reader_eof(&ctx->reader)) { + size_t current_bc_off = reader_pos(&ctx->reader); + uint8_t opcode = reader_u8(&ctx->reader); + + VM_DEBUG("DECODE: bc_off=%zu %s (0x%02X) depth=%d%s\n", current_bc_off, + opcode_to_string(opcode), opcode, ctx->sv.depth, + ctx->sv.state == BARRIER ? " [barrier]" : ""); + + // Validate no nested function + if (opcode_is_func_begin(opcode)) { + if (ctx->func_idx != -1) { + fprintf(stderr, "Error: nested function at bc_off=%zu\n", + current_bc_off); + goto cleanup; + } + ctx->func_idx = (int32_t)current_bc_off; + } + + meta_info *m = &meta[current_bc_off]; + m->resolved_idx = (int32_t)ctx->code.len; + m->func_idx = ctx->func_idx; + + // Validate stack depth at intersections + if (ctx->sv.state == BARRIER) { + if (m->stack_depth != -1) { + // Forward jump visited + VM_DEBUG(" DEPTH: %d -> %d", ctx->sv.depth, m->stack_depth); + ctx->sv.depth = m->stack_depth; + } else { + // No forward jump has targeted this instruction yet. We are starting a + // new "region" after JMP/END, so there is no previous instruction to + // validate against. So set the current decode depth which will be + // checked by some backward jump. Example (while loop): + // JMP cond + // body: + // ... + // cond: + // ... + // CJMP_NZ body + VM_DEBUG(" DEPTH: barrier, keeping stale depth=%d at bc_off=%zu\n", + ctx->sv.depth, current_bc_off); + m->stack_depth = ctx->sv.depth; + } + ctx->sv.state = LIVE; + } else { + if (m->stack_depth != -1 && m->stack_depth != ctx->sv.depth) { + fprintf(stderr, + "Error: Stack mismatch at offset %zu (expected %d, got %d)\n", + current_bc_off, m->stack_depth, ctx->sv.depth); + goto cleanup; + } + m->stack_depth = ctx->sv.depth; + } + + // Resolve forward jumps (backpatching) — store as index, record + // relocation + fixup_node *f = m->fixups; + while (f) { + // Validate jumps + if (meta[f->origin_bc_off].func_idx != m->func_idx) { + uint8_t origin_opcode = bc->code[f->origin_bc_off]; + bool is_jump = origin_opcode == OP_JMP || origin_opcode == OP_CJMP_Z || + origin_opcode == OP_CJMP_NZ; + if (is_jump) { + fprintf(stderr, + "Error: forward jump escapes function at bc_off=%zu -> " + "target=%zu\n", + f->origin_bc_off, current_bc_off); + goto cleanup; + } + } + + VM_DEBUG("DECODE: Resolving fixup at bc_off=%zu: insn_idx=%zu -> " + "code_idx=%zu\n", + current_bc_off, f->insn_idx, ctx->code.len); + ctx->code.data[f->insn_idx].num = (int32_t)ctx->code.len; + add_reloc(ctx, f->insn_idx, NULL, INTERNAL); + + fixup_node *next = f->next; + free(f); + f = next; + } + m->fixups = NULL; + + // Validate no instructions outside function bodies (except EOF) + if (ctx->func_idx == -1 && opcode != OP_EOF) { + fprintf(stderr, + "Error: instruction %s outside function body at bc_off=%zu\n", + opcode_to_string(opcode), current_bc_off); + goto cleanup; + } + + switch (opcode) { + case OP_CONST: + DEPTH_PUSH(); + EMIT_FUNC(op_const); + EMIT_NUM(reader_i32(&ctx->reader)); + break; + + case OP_BINOP_ADD: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_add); + break; + + case OP_BINOP_SUB: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_sub); + break; + + case OP_BINOP_MUL: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_mul); + break; + + case OP_BINOP_DIV: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_div); + break; + + case OP_BINOP_MOD: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_mod); + break; + + case OP_BINOP_LT: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_lt); + break; + + case OP_BINOP_LE: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_le); + break; + + case OP_BINOP_GT: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_gt); + break; + + case OP_BINOP_GE: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_ge); + break; + + case OP_BINOP_EQ: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_eq); + break; + + case OP_BINOP_NE: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_ne); + break; + + case OP_BINOP_AND: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_and); + break; + + case OP_BINOP_OR: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_or); + break; + + case OP_JMP: + EMIT_FUNC(op_jmp); + if (!handle_jump(ctx, meta, current_bc_off)) { + goto cleanup; + } + ctx->sv.state = BARRIER; + break; + + case OP_CJMP_Z: + DEPTH_POP(); + EMIT_FUNC(op_cjmp_z); + if (!handle_jump(ctx, meta, current_bc_off)) { + goto cleanup; + } + break; + + case OP_CJMP_NZ: + DEPTH_POP(); + EMIT_FUNC(op_cjmp_nz); + if (!handle_jump(ctx, meta, current_bc_off)) { + goto cleanup; + } + break; + + case OP_DROP: + DEPTH_POP(); + EMIT_FUNC(op_drop); + break; + + case OP_DUP: + DEPTH_PUSH(); + EMIT_FUNC(op_dup); + break; + + case OP_SWAP: + DEPTH_DEC(2); + DEPTH_INC(2); + EMIT_FUNC(op_swap); + break; + + case OP_ELEM: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_elem); + break; + + case OP_STA: + DEPTH_DEC(3); + DEPTH_PUSH(); + EMIT_FUNC(op_sta); + break; + + case OP_LD_GLO: { + DEPTH_PUSH(); + int32_t idx = reader_i32(&ctx->reader); + VM_DEBUG("DECODE: OP_LD_GLO idx=%d\n", idx); + emit_glo(ctx, idx, global_base, op_ld_glo); + break; + } + + case OP_ST_GLO: { + int32_t idx = reader_i32(&ctx->reader); + VM_DEBUG("DECODE: OP_ST_GLO idx=%d\n", idx); + emit_glo(ctx, idx, global_base, op_st_glo); + break; + } + + case OP_LD_LOC: { + DEPTH_PUSH(); + int32_t idx = reader_i32(&ctx->reader); + CHECK_IDX(idx, ctx->func.n_locals, "LD_LOC"); + EMIT_FUNC(op_ld_loc); + EMIT_NUM(idx); + break; + } + + case OP_ST_LOC: { + int32_t idx = reader_i32(&ctx->reader); + CHECK_IDX(idx, ctx->func.n_locals, "ST_LOC"); + EMIT_FUNC(op_st_loc); + EMIT_NUM(idx); + break; + } + + case OP_LD_ARG: { + DEPTH_PUSH(); + int32_t idx = reader_i32(&ctx->reader); + CHECK_IDX(idx, ctx->func.n_args, "LD_ARG"); + EMIT_FUNC(op_ld_arg); + EMIT_NUM(idx); + break; + } + + case OP_ST_ARG: { + int32_t idx = reader_i32(&ctx->reader); + CHECK_IDX(idx, ctx->func.n_args, "ST_ARG"); + EMIT_FUNC(op_st_arg); + EMIT_NUM(idx); + break; + } + + case OP_LD_CLO: { + DEPTH_PUSH(); + int32_t idx = reader_i32(&ctx->reader); + CHECK_IDX(idx, ctx->func.n_captured, "LD_CLO"); + EMIT_FUNC(op_ld_clo); + EMIT_NUM(idx); + break; + } + + case OP_ST_CLO: { + int32_t idx = reader_i32(&ctx->reader); + CHECK_IDX(idx, ctx->func.n_captured, "ST_CLO"); + EMIT_FUNC(op_st_clo); + EMIT_NUM(idx); + break; + } + + case OP_STRING: { + DEPTH_PUSH(); + int32_t str_idx = reader_i32(&ctx->reader); + EMIT_FUNC(op_string); + EMIT_STR(bytecode_get_string(bc, str_idx)); + break; + } + + case OP_BARRAY: { + int32_t n = reader_i32(&ctx->reader); + DEPTH_DEC(n); + DEPTH_PUSH(); + EMIT_FUNC(op_barray); + EMIT_NUM(n); + break; + } + + case OP_SEXP: { + int32_t tag_idx = reader_i32(&ctx->reader); + int32_t n_fields = reader_i32(&ctx->reader); + DEPTH_DEC(n_fields); + DEPTH_PUSH(); + EMIT_FUNC(op_sexp); + EMIT_ANUM(LtagHash(bytecode_get_string(bc, tag_idx))); + EMIT_NUM(n_fields); + break; + } + + case OP_TAG: { + DEPTH_POP(); + DEPTH_PUSH(); + int32_t tag_idx = reader_i32(&ctx->reader); + int32_t n_fields = reader_i32(&ctx->reader); + EMIT_FUNC(op_tag); + EMIT_ANUM(LtagHash(bytecode_get_string(bc, tag_idx))); + EMIT_NUM(n_fields); + break; + } + + case OP_ARRAY: { + DEPTH_POP(); + DEPTH_PUSH(); + int32_t n = reader_i32(&ctx->reader); + EMIT_FUNC(op_array); + EMIT_NUM(n); + break; + } + + case OP_FAIL: + case OP_FAIL_KEEP: { + int32_t line = reader_i32(&ctx->reader); + int32_t col = reader_i32(&ctx->reader); + bool drop_value = opcode == OP_FAIL; + if (drop_value) { + DEPTH_POP(); + } + EMIT_FUNC(op_fail); + EMIT_NUM(line); + EMIT_NUM(col); + EMIT_NUM(drop_value); + EMIT_STR(ctx->bc->name); + break; + } + + case OP_PATT_STR_CMP: + DEPTH_DEC(2); + DEPTH_PUSH(); + EMIT_FUNC(op_patt_str_cmp); + break; + + case OP_PATT_STRING: + DEPTH_POP(); + DEPTH_PUSH(); + EMIT_FUNC(op_patt_string); + break; + + case OP_PATT_ARRAY: + DEPTH_POP(); + DEPTH_PUSH(); + EMIT_FUNC(op_patt_array); + break; + + case OP_PATT_SEXP: + DEPTH_POP(); + DEPTH_PUSH(); + EMIT_FUNC(op_patt_sexp); + break; + + case OP_PATT_BOXED: + DEPTH_POP(); + DEPTH_PUSH(); + EMIT_FUNC(op_patt_boxed); + break; + + case OP_PATT_UNBOXED: + DEPTH_POP(); + DEPTH_PUSH(); + EMIT_FUNC(op_patt_unboxed); + break; + + case OP_PATT_CLOSURE: + DEPTH_POP(); + DEPTH_PUSH(); + EMIT_FUNC(op_patt_closure); + break; + + case OP_BEGIN: { + int32_t n_args = reader_i32(&ctx->reader); + int32_t n_locals = reader_i32(&ctx->reader); + ctx->sv.depth = 0; + + ctx->func = + (func_ctx){.n_args = n_args, .n_locals = n_locals, .n_captured = 0}; + + EMIT_FUNC(op_begin); + EMIT_NUM(n_args); + EMIT_NUM(n_locals); + + break; + } + + case OP_BEGIN_CLOSURE: { + int32_t n_args = reader_i32(&ctx->reader); + int32_t n_locals = reader_i32(&ctx->reader); + int32_t n_captured = reader_i32(&ctx->reader); + if (!validate_closure_captures(meta, (int32_t)current_bc_off, n_captured, + current_bc_off)) { + goto cleanup; + } + ctx->sv.depth = 0; + + ctx->func = (func_ctx){ + .n_args = n_args, .n_locals = n_locals, .n_captured = n_captured}; + + EMIT_FUNC(op_begin_closure); + EMIT_NUM(n_args); + EMIT_NUM(n_locals); + + break; + } + + case OP_CLOSURE: { + int32_t target_off = reader_i32(&ctx->reader); + int32_t n_captured = reader_i32(&ctx->reader); + + VM_DEBUG("DECODE: OP_CLOSURE target_raw=0x%x n_captured=%d bc_off=%zu\n", + target_off, n_captured, current_bc_off); + + // Emit load instructions for each captured variable + for (int32_t i = 0; i < n_captured; i++) { + uint8_t type_byte = reader_u8(&ctx->reader); + int32_t idx = reader_i32(&ctx->reader); + + int designation_type = type_byte & 0xF; + switch (designation_type) { + case 0: // Global + DEPTH_PUSH(); + emit_glo(ctx, idx, global_base, op_ld_glo); + break; + case 1: // Local + CHECK_IDX(idx, ctx->func.n_locals, "CLOSURE desig local"); + DEPTH_PUSH(); + EMIT_FUNC(op_ld_loc); + EMIT_NUM(idx); + break; + case 2: // Arg + CHECK_IDX(idx, ctx->func.n_args, "CLOSURE desig arg"); + DEPTH_PUSH(); + EMIT_FUNC(op_ld_arg); + EMIT_NUM(idx); + break; + case 3: // Closure var + CHECK_IDX(idx, ctx->func.n_captured, "CLOSURE desig closure"); + DEPTH_PUSH(); + EMIT_FUNC(op_ld_clo); + EMIT_NUM(idx); + break; + default: + fprintf(stderr, "Unknown designation type: %d\n", designation_type); + goto cleanup; + } + } + + DEPTH_DEC(n_captured); + DEPTH_PUSH(); + + EMIT_FUNC(op_closure); + if (!emit_target(ctx, meta, target_off, current_bc_off, TARGET_CLOSURE)) + goto cleanup; + EMIT_NUM(n_captured); + + // Validate CLOSURE target's n_captured consistency + if (!IS_EXT_REF(target_off)) { + if (!validate_closure_captures(meta, target_off, n_captured, + current_bc_off)) { + goto cleanup; + } + } + break; + } + + case OP_CALL: { + int32_t target_off = reader_i32(&ctx->reader); + int32_t n_args = reader_i32(&ctx->reader); + DEPTH_DEC(n_args); + DEPTH_PUSH(); + + VM_DEBUG("DECODE: OP_CALL target_off=0x%x n_args=%d " + "current_bc_off=%zu code_idx=%zu\n", + target_off, n_args, current_bc_off, ctx->code.len); + + EMIT_FUNC(op_call); + if (!emit_target(ctx, meta, target_off, current_bc_off, TARGET_CALL)) + goto cleanup; + EMIT_NUM(n_args); + break; + } + + case OP_CALLC: { + int32_t n_args = reader_i32(&ctx->reader); + DEPTH_DEC(n_args + 1); + DEPTH_PUSH(); + EMIT_FUNC(op_callc); + EMIT_NUM(n_args); + break; + } + + case OP_END: + // depth == 1 <=> return value (?) + if (ctx->sv.depth != 1) { + fprintf(stderr, "Error: END with depth = %d at bc_off=%zu\n", + ctx->sv.depth, current_bc_off); + goto cleanup; + } + EMIT_FUNC(op_end); + ctx->sv.state = BARRIER; + ctx->func = (func_ctx){.n_captured = -1}; + ctx->func_idx = -1; + break; + + case OP_LINE: { +#ifdef DEBUG_PRINT + int32_t line = reader_i32(&ctx->reader); + EMIT_FUNC(op_line); + EMIT_NUM(line); +#else + reader_skip(&ctx->reader, 4); +#endif + break; + } + + case OP_EOF: + if (ctx->func_idx != -1) { + fprintf(stderr, "Error: EOF inside function body at bc_off=%zu\n", + current_bc_off); + goto cleanup; + } + if (current_bc_off + 1 != bc->code_size) { + fprintf(stderr, + "Error: EOF opcode before end of bytecode at bc_off=%zu\n", + current_bc_off); + goto cleanup; + } + break; + + default: + fprintf(stderr, "Not yet supported opcode 0x%02X at ip=0x%08zx\n", opcode, + reader_pos(&ctx->reader) - 1); + goto cleanup; + } + } + + // Extract mapping + for (size_t i = 0; i < bc->code_size; i++) { + ctx->bc_to_insn_map[i] = meta[i].resolved_idx; + } + + ok = true; + +cleanup: + // Free temporary metadata and fixup nodes + for (size_t i = 0; i < bc->code_size; i++) { + fixup_node *node = meta[i].fixups; + while (node) { + fixup_node *next = node->next; + free(node); + node = next; + } + } + free(meta); + + return ok; +} + +#undef CHECK_IDX +#undef DEPTH_INC +#undef DEPTH_DEC +#undef DEPTH_PUSH +#undef DEPTH_POP +#undef EMIT_FUNC +#undef EMIT_NUM +#undef EMIT_ANUM +#undef EMIT_STR +#undef EMIT_TARGET +#undef EMIT_GLOBAL_PTR +#undef EMIT_PTR + +static bool register_public_symbols(symbol_table *st, const bytecode *bc, + size_t code_offset, size_t global_base, + const int32_t *bc_to_insn_map) { + public_symbol pub; + bytecode_iterator iter; + bytecode_pubs_init(&iter, bc); + + while (bytecode_pubs_next(&iter, &pub)) { + if (pub.code_offset < 0 || (size_t)pub.code_offset >= bc->code_size) { + fprintf(stderr, + "Error: public symbol '%s' has out-of-range code_offset %d\n", + pub.name, pub.code_offset); + return false; + } + if (pub.flag == PUB_FLAG_FUNCTION) { + // pub.code_offset is the offset in the bytecode, so we use the mapping + int32_t insn_idx = bc_to_insn_map[pub.code_offset]; + if (insn_idx == -1) { + fprintf(stderr, + "Error: public symbol '%s' at bytecode offset %d not decoded\n", + pub.name, pub.code_offset); + return false; + } + int32_t code_idx = insn_idx + code_offset; + if (!symbol_table_add_function(st, pub.name, code_idx)) { + return false; + } + } else { + int32_t global_idx = pub.code_offset + global_base; + if (!symbol_table_add_global(st, pub.name, global_idx)) { + return false; + } + } + } + + return true; +} + +/* + * Resolve relocs / placeholders in the final code array after all units are + * decoded and merged. + */ +static bool resolve_relocs(insn *all_code, decoded *dec, size_t code_offset, + size_t ffi_call_offset) { + for (size_t j = 0; j < dec->relocs_len; j++) { + reloc rel = dec->relocs[j]; + size_t slot = code_offset + rel.patch_idx; + int32_t target_idx = all_code[slot].num; + switch (rel.kind) { + case INTERNAL: { + all_code[slot].target = &all_code[code_offset + target_idx]; + break; + } + case UNIT: { + // Validate inter-unit CALL/CLOSURE targets + insn *target = &all_code[target_idx]; + fn caller = all_code[slot - 1].func; + assert(caller == op_call || caller == op_closure); + const char *caller_name = caller == op_call ? "CALL" : "CLOSURE"; + const char *target_name = + caller == op_call ? "BEGIN" : "BEGIN/BEGIN_CLOSURE"; + bool ok = caller == op_call ? target->func == op_begin + : target->func == op_begin || + target->func == op_begin_closure; + if (!ok) { + fprintf(stderr, "Error: inter-unit %s to non-%s function '%s'\n", + caller_name, target_name, rel.name); + return false; + } + all_code[slot].target = target; + break; + } + case FFI: { + all_code[slot].target = + &all_code[ffi_call_offset + target_idx * FFI_STUB_SIZE]; + break; + } + } + } + return true; +} + +static program *link_program(decoded *dec_arr, size_t n, size_t total_code_len, + ffi_call_table *ffi) { + static insn eof_ip = {.func = op_eof}; + size_t ffi_call_len = ffi_call_table_len(ffi); + size_t ffi_call_offset = total_code_len; + size_t all_code_len = ffi_call_offset + ffi_call_len * FFI_STUB_SIZE; + + insn *all_code = ALLOC_ARRAY(insn, all_code_len); + insn *entry_points = ALLOC_ARRAY(insn, ENTRY_STEP_SLOTS * n + 1); + // Copy code and resolve relocations + size_t code_offset = 0; + for (size_t i = 0; i < n; i++) { + decoded *dec = &dec_arr[i]; + + // Move instructions into final code array + memcpy(all_code + code_offset, dec->code, dec->code_len * sizeof(insn)); + emit_entry_step(&entry_points[ENTRY_STEP_SLOTS * i], + &all_code[code_offset]); + if (!resolve_relocs(all_code, dec, code_offset, ffi_call_offset)) { + free(all_code); + free(entry_points); + return NULL; + } + + code_offset += dec->code_len; + } + entry_points[ENTRY_STEP_SLOTS * n] = eof_ip; + + ffi_call_iterator ffi_iter; + ffi_call_table_emit_init(&ffi_iter, ffi); + ffi_resolved *res; + size_t ffi_idx = 0; + while (ffi_call_table_emit_next(&ffi_iter, &res)) { + all_code[ffi_call_offset + ffi_idx * FFI_STUB_SIZE].func = op_ffi_call; + all_code[ffi_call_offset + ffi_idx * FFI_STUB_SIZE + 1].ptr = res; + ffi_idx++; + } + + program *prog = ALLOC(program); + prog->code = all_code; + prog->code_len = all_code_len; + prog->entry_points = entry_points; + prog->ffi_data = ffi_call_table_release(ffi); + prog->ffi_len = ffi_call_len; + + return prog; +} + +program *decode(bytecode **bc_arr, size_t n, aint *globals) { + symbol_table *st = symbol_table_create(); + ffi_call_table *ffi = ffi_call_table_create(); + ext_global_cache ext_globals = {0}; + da_init(ext_globals.entries); + + decoded *dec_arr = ALLOC_ARRAY(decoded, n); + program *prog = NULL; + size_t n_decoded = 0; + + size_t total_code_len = 0; + size_t total_globals = 0; + + for (size_t i = 0; i < n; i++) { + decode_ctx ctx; + decode_ctx_init(&ctx, bc_arr[i], st, ffi, &ext_globals, globals, + total_globals); + if (!decode_internal(&ctx)) { + fprintf(stderr, "Failed to decode %s\n", bc_arr[i]->name); + free(ctx.bc_to_insn_map); + goto cleanup; + } + + dec_arr[i] = (decoded){ + .code = ctx.code.data, + .code_len = ctx.code.len, + .bc_to_insn_map = ctx.bc_to_insn_map, + .relocs = ctx.relocs.data, + .relocs_len = ctx.relocs.len, + }; + n_decoded++; + + if (!register_public_symbols(st, bc_arr[i], total_code_len, total_globals, + ctx.bc_to_insn_map)) { + fprintf(stderr, "Failed to register public symbols for %s\n", + bc_arr[i]->name); + goto cleanup; + } + + total_code_len += ctx.code.len; + total_globals += bc_arr[i]->globals_count; + } + + prog = link_program(dec_arr, n, total_code_len, ffi); + +cleanup: + symbol_table_destroy(st); + ffi_call_table_destroy(ffi); + da_free(ext_globals.entries); + free_decoded_arr(dec_arr, n_decoded); + free(dec_arr); + + return prog; +} + +void program_free(program *prog) { + if (!prog) { + return; + } + free(prog->ffi_data); + free(prog->code); + free(prog->entry_points); + free(prog); +} diff --git a/virtual_machine/converter.h b/virtual_machine/converter.h new file mode 100644 index 000000000..4615fcd8d --- /dev/null +++ b/virtual_machine/converter.h @@ -0,0 +1,21 @@ +#ifndef CONVERTER_H +#define CONVERTER_H + +#include "../runtime/runtime_common.h" +#include "bytecode.h" +#include "insn.h" +#include +#include + +typedef struct { + insn *code; + size_t code_len; + insn *entry_points; + void *ffi_data; + size_t ffi_len; +} program; + +program *decode(bytecode **bc_arr, size_t n, aint *globals); +void program_free(program *prog); + +#endif // CONVERTER_H diff --git a/virtual_machine/da.h b/virtual_machine/da.h new file mode 100644 index 000000000..395189273 --- /dev/null +++ b/virtual_machine/da.h @@ -0,0 +1,37 @@ +#ifndef DA_H +#define DA_H + +#include "memory.h" + +/* + * Dynamic array macros + */ +#define da_append(xs, x) \ + do { \ + if ((xs).len >= (xs).cap) { \ + (xs).cap = (xs).cap == 0 ? 256 : (xs).cap * 2; \ + (xs).data = EREALLOC((xs).data, (xs).cap * sizeof(*(xs).data)); \ + if (!(xs).data) { \ + perror("realloc"); \ + exit(EXIT_FAILURE); \ + } \ + } \ + (xs).data[(xs).len++] = x; \ + } while (0) + +#define da_init(xs) \ + do { \ + (xs).data = NULL; \ + (xs).len = 0; \ + (xs).cap = 0; \ + } while (0) + +#define da_free(xs) \ + do { \ + free((xs).data); \ + (xs).data = NULL; \ + (xs).len = 0; \ + (xs).cap = 0; \ + } while (0) + +#endif // DA_H diff --git a/virtual_machine/debug.h b/virtual_machine/debug.h new file mode 100644 index 000000000..82a92b27c --- /dev/null +++ b/virtual_machine/debug.h @@ -0,0 +1,12 @@ +#ifndef DEBUG_H +#define DEBUG_H + +#include + +#ifdef DEBUG_PRINT +#define VM_DEBUG(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__) +#else +#define VM_DEBUG(fmt, ...) +#endif + +#endif // DEBUG_H diff --git a/virtual_machine/disasm.c b/virtual_machine/disasm.c new file mode 100644 index 000000000..e6ef984c8 --- /dev/null +++ b/virtual_machine/disasm.c @@ -0,0 +1,227 @@ +#include "disasm.h" +#include "bytecode.h" +#include "opcodes.h" + +#include +#include +#include +#include + +static void print_escaped_string(FILE *f, const char *s) { + for (const uint8_t *p = (const uint8_t *)s; *p; p++) { + switch (*p) { + case '"': + fprintf(f, "\\\""); + break; + case '\\': + switch (p[1]) { + case '\n': + fprintf(f, "\\n"); + p++; + break; + case '\r': + fprintf(f, "\\r"); + p++; + break; + case '\t': + fprintf(f, "\\t"); + p++; + break; + default: + fprintf(f, "\\\\"); + break; + } + break; + default: + if (*p >= 0x20 && *p <= 0x7e) { + fputc(*p, f); + } else { + fprintf(f, "\\x%02x", *p); + } + break; + } + } +} + +static void disassemble(FILE *f, const bytecode *bc) { + byte_reader reader; + reader_init(&reader, bc->code, bc->code_size); + + while (true) { + size_t offset = reader_pos(&reader); + uint8_t x = reader_u8(&reader); + + fprintf(f, "0x%.8x:\t", (unsigned int)offset); + + switch (x) { + case OP_BINOP_ADD: + case OP_BINOP_SUB: + case OP_BINOP_MUL: + case OP_BINOP_DIV: + case OP_BINOP_MOD: + case OP_BINOP_LT: + case OP_BINOP_LE: + case OP_BINOP_GT: + case OP_BINOP_GE: + case OP_BINOP_EQ: + case OP_BINOP_NE: + case OP_BINOP_AND: + case OP_BINOP_OR: + fprintf(f, "%s", opcode_to_string(x)); + break; + + case OP_CONST: + fprintf(f, "%s\t%d", opcode_to_string(x), reader_i32(&reader)); + break; + + case OP_STRING: + fprintf(f, "%s\t", opcode_to_string(x)); + print_escaped_string(f, bytecode_get_string(bc, reader_i32(&reader))); + break; + + case OP_SEXP: + fprintf(f, "%s\t", opcode_to_string(x)); + print_escaped_string(f, bytecode_get_string(bc, reader_i32(&reader))); + fprintf(f, " %d", reader_i32(&reader)); + break; + + case OP_END: + case OP_STA: + case OP_DROP: + case OP_DUP: + case OP_SWAP: + case OP_ELEM: + fprintf(f, "%s", opcode_to_string(x)); + break; + + case OP_JMP: + case OP_CJMP_Z: + case OP_CJMP_NZ: + fprintf(f, "%s\t0x%.8x", opcode_to_string(x), + (unsigned int)reader_i32(&reader)); + break; + + case OP_LD_GLO: + case OP_LD_LOC: + case OP_LD_ARG: + case OP_LD_CLO: + case OP_ST_GLO: + case OP_ST_LOC: + case OP_ST_ARG: + case OP_ST_CLO: + fprintf(f, "%s\t%d", opcode_to_string(x), reader_i32(&reader)); + break; + + case OP_BEGIN: + fprintf(f, "%s\t%d ", opcode_to_string(x), reader_i32(&reader)); + fprintf(f, "%d", reader_i32(&reader)); + break; + + case OP_BEGIN_CLOSURE: + fprintf(f, "%s\t%d ", opcode_to_string(x), reader_i32(&reader)); + fprintf(f, "%d ", reader_i32(&reader)); + fprintf(f, "%d", reader_i32(&reader)); + break; + + case OP_CLOSURE: { + int32_t n; + fprintf(f, "%s\t0x%.8x", opcode_to_string(x), + (unsigned int)reader_i32(&reader)); + n = reader_i32(&reader); + fprintf(f, " %d", n); + for (int32_t i = 0; i < n; i++) { + uint8_t ref = reader_u8(&reader); + fprintf(f, " %u %d", (unsigned int)ref, reader_i32(&reader)); + } + break; + } + + case OP_CALLC: + case OP_ARRAY: + case OP_LINE: + fprintf(f, "%s\t%d", opcode_to_string(x), reader_i32(&reader)); + break; + + case OP_CALL: + fprintf(f, "%s\t0x%.8x ", opcode_to_string(x), + (unsigned int)reader_i32(&reader)); + fprintf(f, "%d", reader_i32(&reader)); + break; + + case OP_TAG: + fprintf(f, "%s\t", opcode_to_string(x)); + print_escaped_string(f, bytecode_get_string(bc, reader_i32(&reader))); + fprintf(f, " %d", reader_i32(&reader)); + break; + + case OP_FAIL: + case OP_FAIL_KEEP: + fprintf(f, "%s\t%d", opcode_to_string(x), reader_i32(&reader)); + fprintf(f, "%d", reader_i32(&reader)); + break; + + case OP_PATT_STR_CMP: + case OP_PATT_STRING: + case OP_PATT_ARRAY: + case OP_PATT_SEXP: + case OP_PATT_BOXED: + case OP_PATT_UNBOXED: + case OP_PATT_CLOSURE: + fprintf(f, "%s", opcode_to_string(x)); + break; + + case OP_BARRAY: + fprintf(f, "%s\t%d", opcode_to_string(x), reader_i32(&reader)); + break; + + case OP_EOF: + fprintf(f, "%s\n", opcode_to_string(x)); + return; + + default: + fprintf(f, "%s\n", opcode_to_string(x)); + } + fprintf(f, "\n"); + } +} + +void dump_bytecode(FILE *f, const bytecode *bc) { + bytecode_iterator pubs, imports; + public_symbol pub; + const char *import_name; + + fprintf(f, "Version: %d\n", bc->version); + fprintf(f, "Size of the string table (in bytes): %zu\n", + bc->string_table_size); + fprintf(f, "Number of global variables: %zu\n", bc->globals_count); + fprintf(f, "Number of imports: %zu\n", bc->imports_len); + fprintf(f, "Number of public symbols: %zu\n", bc->pubs_len); + fprintf(f, "Code size: %zu\n", bc->code_size); + fprintf(f, "Imports: \n"); + + bytecode_imports_init(&imports, bc); + while (bytecode_imports_next(&imports, &import_name)) { + fprintf(f, " %s\n", import_name); + } + + fprintf(f, "Public functions: \n"); + + bytecode_pubs_init(&pubs, bc); + while (bytecode_pubs_next(&pubs, &pub)) { + if (pub.flag == PUB_FLAG_FUNCTION) { + fprintf(f, " 0x%.8x: %s\n", (unsigned int)pub.code_offset, pub.name); + } + } + + fprintf(f, "Public globals: \n"); + + bytecode_pubs_init(&pubs, bc); + while (bytecode_pubs_next(&pubs, &pub)) { + if (pub.flag == PUB_FLAG_GLOBAL) { + fprintf(f, " 0x%.8x: %s\n", (unsigned int)pub.code_offset, pub.name); + } + } + + fprintf(f, "Code:\n"); + disassemble(f, bc); +} diff --git a/virtual_machine/dune b/virtual_machine/dune new file mode 100644 index 000000000..25a0d7a72 --- /dev/null +++ b/virtual_machine/dune @@ -0,0 +1,32 @@ +(rule + (target lama.exe) + (mode + (promote (until-clean))) + (deps + Makefile + lama.c + converter.c + vm.c + bytecode.c + ffi.c + loader.c + symbols.c + ops.c + memory.c + opcodes.c + (glob_files *.h) + ../runtime/Makefile + ../runtime/gc.c + ../runtime/gc.h + ../runtime/runtime.c + ../runtime/runtime.h + ../runtime/runtime_common.h + ../runtime/printf.S) + (action + (run + make + TARGET=%{target}))) + +(alias + (name default) + (deps lama.exe)) diff --git a/virtual_machine/ffi.c b/virtual_machine/ffi.c new file mode 100644 index 000000000..7b7bf8ca0 --- /dev/null +++ b/virtual_machine/ffi.c @@ -0,0 +1,252 @@ +/* + * External function calling for Lama VM. + * Uses libffi to dynamically call C functions. + */ + +#include "ffi.h" +#include "../runtime/runtime_common.h" +#include "da.h" +#include "memory.h" +#include +#include +#include +#include +#include +#include + +struct ffi_call_table { + ffi_resolved *data; + size_t len; + size_t cap; + + // Used for dedup + struct { + const char **data; + size_t len; + size_t cap; + } names; +}; + +ffi_call_table *ffi_call_table_create(void) { + ffi_call_table *table = ALLOC(ffi_call_table); + da_init(*table); + da_init(table->names); + return table; +} + +void ffi_call_table_destroy(ffi_call_table *table) { + if (!table) { + return; + } + for (size_t i = 0; i < table->names.len; i++) { + free((char *)table->names.data[i]); + } + da_free(table->names); + free(table->data); + free(table); +} + +#define FUNC_PREFIX "L" + +typedef struct { + const char *name; // raw name as it appears in bytecode + const char *target_name; // explicit dlsym name, or NULL for default + bool is_args_array; + int fixed_args; +} func_metadata; + +static const func_metadata func_table[] = { + // Args array functions + {"substring", NULL, true, 0}, + {"stringcat", NULL, true, 0}, + {"string", NULL, true, 0}, + {"i__Infix_4343", NULL, true, 0}, // strcat + {"s__Infix_58", NULL, true, 0}, // : (cons) + {"clone", NULL, true, 0}, + + // Variadic functions with mapping + {"printf", "Bprintf", false, 1}, + {"fprintf", "Bfprintf", false, 2}, + {"sprintf", "Bsprintf", false, 1}, + + // Sentinel + {NULL, NULL, false, 0}}; + +static void *lookup_function(const char *name) { + void *fn = dlsym(RTLD_DEFAULT, name); + char *error = dlerror(); + if (error) { + fprintf(stderr, "Error looking up function '%s': %s\n", name, error); + return NULL; + } + return fn; +} + +static const func_metadata *lookup_metadata(const char *name) { + for (int i = 0; func_table[i].name != NULL; i++) { + if (strcmp(name, func_table[i].name) == 0) { + return &func_table[i]; + } + } + return NULL; +} + +size_t ffi_call_table_intern(ffi_call_table *table, const char *name) { + for (size_t i = 0; i < table->names.len; i++) { + if (strcmp(table->names.data[i], name) == 0) { + return i; + } + } + + const func_metadata *meta = lookup_metadata(name); + + ffi_kind kind = FFI_REGULAR; + int fixed_args = 0; + if (meta) { + if (meta->is_args_array) { + kind = FFI_ARGS_ARRAY; + } else { + kind = FFI_VARIADIC; + fixed_args = meta->fixed_args; + } + } + + void *fn; + if (meta && meta->target_name) { + fn = lookup_function(meta->target_name); + } else { + size_t nlen = strlen(name); + char prefixed[sizeof(FUNC_PREFIX) + nlen]; + memcpy(prefixed, FUNC_PREFIX, sizeof(FUNC_PREFIX) - 1); + memcpy(prefixed + sizeof(FUNC_PREFIX) - 1, name, nlen + 1); + fn = lookup_function(prefixed); + } + + if (!fn) { + fprintf(stderr, "Undefined external function: %s\n", name); + exit(EXIT_FAILURE); + } + + ffi_resolved entry = { + .fn_ptr = fn, + .kind = kind, + .fixed_args = fixed_args, + }; + + da_append(*table, entry); + da_append(table->names, ESTRDUP(name)); + return table->len - 1; +} + +size_t ffi_call_table_len(ffi_call_table *table) { return table->len; } + +ffi_resolved *ffi_call_table_release(ffi_call_table *table) { + ffi_resolved *data = table->data; + table->data = NULL; + table->len = 0; + table->cap = 0; + return data; +} + +void ffi_call_table_emit_init(ffi_call_iterator *iter, ffi_call_table *table) { + iter->table = table; + iter->curr = 0; +} + +bool ffi_call_table_emit_next(ffi_call_iterator *iter, ffi_resolved **out) { + if (iter->curr >= iter->table->len) { + return false; + } + *out = &iter->table->data[iter->curr]; + iter->curr++; + return true; +} + +static aint call_args_array(void *fn_ptr, aint *args) { + ffi_cif cif; + ffi_type *arg_types[1] = {&ffi_type_pointer}; + void *arg_values[1] = {&args}; + void *result = NULL; + + ffi_status status = + ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 1, &ffi_type_pointer, arg_types); + if (status != FFI_OK) { + fprintf(stderr, "FFI prep failed: status=%d\n", status); + exit(EXIT_FAILURE); + } + + ffi_call(&cif, FFI_FN(fn_ptr), &result, arg_values); + return (aint)result; +} + +static aint call_variadic(void *fn_ptr, int fixed_args, aint *args, + int n_args) { + if (n_args < fixed_args) { + fprintf(stderr, "FFI variadic call: expected at least %d args, got %d\n", + fixed_args, n_args); + exit(EXIT_FAILURE); + } + + ffi_cif cif; + ffi_type *arg_types[n_args]; + void *arg_values[n_args]; + aint int_values[n_args]; + void *result = NULL; + + for (int i = 0; i < n_args; i++) { + if (UNBOXED(args[i])) { + int_values[i] = UNBOX(args[i]); + arg_types[i] = &ffi_type_pointer; + arg_values[i] = &int_values[i]; + } else { + arg_types[i] = &ffi_type_pointer; + arg_values[i] = &args[i]; + } + } + + ffi_status status = ffi_prep_cif_var(&cif, FFI_DEFAULT_ABI, fixed_args, + n_args, &ffi_type_pointer, arg_types); + if (status != FFI_OK) { + fprintf(stderr, "FFI prep failed: status=%d\n", status); + exit(EXIT_FAILURE); + } + + ffi_call(&cif, FFI_FN(fn_ptr), &result, arg_values); + return (aint)result; +} + +static aint call_regular(void *fn_ptr, aint *args, int n_args) { + ffi_cif cif; + ffi_type *arg_types[n_args]; + void *arg_values[n_args]; + aint result; + + for (int i = 0; i < n_args; i++) { + arg_types[i] = &ffi_type_pointer; + arg_values[i] = &args[i]; + } + + ffi_status status = + ffi_prep_cif(&cif, FFI_DEFAULT_ABI, n_args, &ffi_type_pointer, arg_types); + if (status != FFI_OK) { + fprintf(stderr, "FFI prep failed: status=%d\n", status); + exit(EXIT_FAILURE); + } + + ffi_call(&cif, FFI_FN(fn_ptr), &result, arg_values); + return result; +} + +aint ffi_call_c(const ffi_resolved *res, aint *args, int n_args) { + switch (res->kind) { + case FFI_ARGS_ARRAY: + return call_args_array(res->fn_ptr, args); + case FFI_VARIADIC: + return call_variadic(res->fn_ptr, res->fixed_args, args, n_args); + case FFI_REGULAR: + return call_regular(res->fn_ptr, args, n_args); + default: + fprintf(stderr, "Unknown FFI kind: %d\n", res->kind); + exit(EXIT_FAILURE); + } +} diff --git a/virtual_machine/ffi.h b/virtual_machine/ffi.h new file mode 100644 index 000000000..20eb465b8 --- /dev/null +++ b/virtual_machine/ffi.h @@ -0,0 +1,41 @@ +#ifndef FFI_CALL_H +#define FFI_CALL_H + +#include "../runtime/runtime_common.h" +#include "insn.h" +#include +#include +#include + +typedef enum { FFI_REGULAR, FFI_ARGS_ARRAY, FFI_VARIADIC } ffi_kind; + +typedef struct { + void *fn_ptr; + ffi_kind kind; + int fixed_args; +} ffi_resolved; + +typedef struct ffi_call_table ffi_call_table; + +ffi_call_table *ffi_call_table_create(void); +void ffi_call_table_destroy(ffi_call_table *table); + +/* + * Find existing or resolve and add. + */ +size_t ffi_call_table_intern(ffi_call_table *table, const char *name); +size_t ffi_call_table_len(ffi_call_table *table); + +ffi_resolved *ffi_call_table_release(ffi_call_table *table); + +typedef struct { + ffi_call_table *table; + size_t curr; +} ffi_call_iterator; + +void ffi_call_table_emit_init(ffi_call_iterator *iter, ffi_call_table *table); +bool ffi_call_table_emit_next(ffi_call_iterator *iter, ffi_resolved **out); + +aint ffi_call_c(const ffi_resolved *res, aint *args, int n_args); + +#endif // FFI_CALL_H diff --git a/virtual_machine/insn.h b/virtual_machine/insn.h new file mode 100644 index 000000000..c665c3082 --- /dev/null +++ b/virtual_machine/insn.h @@ -0,0 +1,30 @@ +/* + * Internal instruction representation and VM state definitions. + */ + +#ifndef INSN_H +#define INSN_H + +#include "../runtime/runtime_common.h" +#include + +union insn; +// State: ip = instruction pointer, sp = stack pointer, bp = base pointer +#define DECL_STATE union insn *ip, aint *sp, aint *bp +#define STATE ip, sp, bp + +// Function pointer type for opcode handlers (returns void for tail calls) +typedef void (*fn)(DECL_STATE); + +// Union representing a single threaded code instruction/operand +typedef union insn { + fn func; // Pointer to function + int32_t num; // Integer operand (signed) + aint anum; // Runtime value operand + const char *str; // String operand (direct pointer) + union insn *target; // Direct jump target (pointer to insn) + aint *global_ptr; // Pointer to a C global variable + void *ptr; // Generic pointer +} insn; + +#endif // INSN_H diff --git a/virtual_machine/lama.c b/virtual_machine/lama.c new file mode 100644 index 000000000..5332d88b0 --- /dev/null +++ b/virtual_machine/lama.c @@ -0,0 +1,154 @@ +#define _POSIX_C_SOURCE 200809L + +#include "disasm.h" +#include "loader.h" +#include "memory.h" +#include "vm.h" +#include +#include +#include +#include +#include +#include + +/* + * Extract directory and unit name from a path. Returns false if the suffix does + * not match. + */ +static bool parse_bytecode_path(const char *path, char **unit_name_out, + char **dir_out) { + size_t len = strlen(path); + size_t suffix_len = sizeof(BYTECODE_SUFFIX) - 1; + if (len <= suffix_len || + strcmp(path + len - suffix_len, BYTECODE_SUFFIX) != 0) { + return false; + } + + char *path_copy = ESTRDUP(path); + char *base = basename(path_copy); + base[strlen(base) - suffix_len] = '\0'; + + char *dir_copy = ESTRDUP(path); + *unit_name_out = ESTRDUP(base); + *dir_out = ESTRDUP(dirname(dir_copy)); + free(dir_copy); + free(path_copy); + return true; +} + +#define MAX_INCLUDE_PATHS 64 + +static void print_usage(FILE *dest, const char *prog_name) { + fprintf(dest, "Usage: %s [options] [args]\n", + prog_name); + fprintf(dest, + "\nWhen no options are specified, the VM will run the bytecode file " + "and look for units in the same directory.\n"); + fprintf(dest, + "You can also specify unit name instead of a bytecode file, but " + "you need to manually include relevant search paths.\n"); + fprintf(dest, "Options:\n"); + fprintf(dest, " -h, --help Show this help message\n"); + fprintf(dest, " -d, --disassemble Disassemble the bytecode unit\n"); + fprintf(dest, + " -I, --include PATH Add PATH to unit search paths (can be " + "used multiple times)\n"); +} + +int main(int argc, char *argv[]) { + char *include_paths[MAX_INCLUDE_PATHS]; + int include_path_count = 1; + int exit_code = 0; + char *bytecode_dir = NULL; + char *main_unit_name = NULL; + virtual_machine *vm = NULL; + bool disassemble = false; + bool is_path = false; + + static struct option long_options[] = {{"help", no_argument, 0, 'h'}, + {"disassemble", no_argument, 0, 'd'}, + {"include", required_argument, 0, 'I'}, + {0, 0, 0, 0}}; + + int opt; + int option_index = 0; + + while ((opt = getopt_long(argc, argv, "hdI:", long_options, &option_index)) != + -1) { + switch (opt) { + case 'h': + print_usage(stdout, argv[0]); + return 0; + case 'd': + disassemble = true; + break; + case 'I': + if (include_path_count < MAX_INCLUDE_PATHS) { + include_paths[include_path_count++] = optarg; + } else { + fprintf(stderr, "Maximum number of include paths (%d) exceeded\n", + MAX_INCLUDE_PATHS); + return EXIT_FAILURE; + } + break; + default: + print_usage(stderr, argv[0]); + return EXIT_FAILURE; + } + } + + if (optind >= argc) { + fprintf(stderr, "No bytecode file specified\n\n"); + print_usage(stderr, argv[0]); + return EXIT_FAILURE; + } + + char *entry_arg = argv[optind]; + is_path = parse_bytecode_path(entry_arg, &main_unit_name, &bytecode_dir); + if (is_path) { + include_paths[0] = bytecode_dir; + } else { + main_unit_name = entry_arg; + } + + if (disassemble) { + bytecode *bc = NULL; + + if (!is_path) { + fprintf(stderr, "Disassembly requires a .bc file path\n"); + exit_code = EXIT_FAILURE; + goto cleanup; + } + + bc = bytecode_load(entry_arg); + if (!bc) { + exit_code = EXIT_FAILURE; + goto cleanup; + } + + dump_bytecode(stdout, bc); + bytecode_free(bc); + goto cleanup; + } + + vm = vm_create(main_unit_name, + (const char **)(is_path ? include_paths : include_paths + 1), + is_path ? include_path_count : include_path_count - 1); + if (!vm) { + exit_code = EXIT_FAILURE; + goto cleanup; + } + + // Skip options, pass only program args + vm_set_args(vm, argc - optind, argv + optind); + + vm_run(vm); + +cleanup: + vm_destroy(vm); + if (is_path) { + free(main_unit_name); + } + free(bytecode_dir); + return exit_code; +} diff --git a/virtual_machine/loader.c b/virtual_machine/loader.c new file mode 100644 index 000000000..baa284d9b --- /dev/null +++ b/virtual_machine/loader.c @@ -0,0 +1,152 @@ +/* + * Unit loader implementation for Lama VM. + * Recursively loads bytecode files following import declarations. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "loader.h" +#include "bytecode.h" +#include "da.h" +#include "memory.h" +#include +#include +#include +#include +#include + +typedef struct { + bytecode **data; + size_t len; + size_t cap; +} bytecode_array; + +typedef struct { + const char **data; + size_t len; + size_t cap; +} name_array; + +static bool is_loading(const name_array *stack, const char *name) { + for (size_t i = stack->len - 1; ~i; --i) { + if (strcmp(stack->data[i], name) == 0) + return true; + } + return false; +} + +static void free_loaded_units(bytecode_array *units) { + for (size_t i = 0; i < units->len; i++) { + bytecode_free(units->data[i]); + } + da_free(*units); +} + +/* + * Resolve a unit name against the search paths and load the first + * bytecode file. + */ +static bytecode *load_unit_from_paths(const char *unit_name, + const search_paths *paths) { + static char path[MAX_PATH_LEN]; + for (size_t i = 0; i < paths->len; i++) { + snprintf(path, MAX_PATH_LEN, "%s/%s%s", paths->paths[i], unit_name, + BYTECODE_SUFFIX); + int fd = open(path, O_RDONLY); + if (fd >= 0) { + return bytecode_load_fd(fd); + } + } + + return NULL; +} + +static bool find_loaded(bytecode_array *units, const char *name) { + for (size_t i = 0; i < units->len; i++) { + if (strcmp(units->data[i]->name, name) == 0) { + return true; + } + } + return false; +} + +/* + * Load a single unit and its dependencies recursively. + */ +static bool load_unit_recursive(bytecode_array *units, name_array *loading, + const char *unit_name, bytecode *bc, + const search_paths *paths) { + bc->name = ESTRDUP(unit_name); + + da_append(*loading, unit_name); + + // Recursively load dependencies first (topological order) + const char *import_name; + bytecode_iterator iter; + bytecode_imports_init(&iter, bc); + while (bytecode_imports_next(&iter, &import_name)) { + + // Skip Std since we have it as runtime.a + if (strcmp(import_name, "Std") == 0) { + continue; + } + + if (is_loading(loading, import_name)) { + fprintf(stderr, "Circular dependency: '%s' -> '%s'\n", unit_name, + import_name); + goto fail; + } + + if (find_loaded(units, import_name)) { + continue; + } + + bytecode *dep_bc = load_unit_from_paths(import_name, paths); + if (!dep_bc) { + fprintf(stderr, "Failed to load dependency '%s'\n", import_name); + goto fail; + } + + if (!load_unit_recursive(units, loading, import_name, dep_bc, paths)) { + goto fail; + } + } + + loading->len--; + da_append(*units, bc); + return true; + +fail: + loading->len--; + bytecode_free(bc); + return false; +} + +load_result load(const char *main_unit_name, const search_paths *paths) { + bytecode_array m; + da_init(m); + + name_array loading; + da_init(loading); + + bytecode *bc = load_unit_from_paths(main_unit_name, paths); + if (!bc) { + fprintf(stderr, "Failed to load unit '%s'\n", main_unit_name); + goto cleanup; + } + + if (!load_unit_recursive(&m, &loading, main_unit_name, bc, paths)) { + goto cleanup; + } + da_free(loading); + + return (load_result){ + .units = m.data, + .units_len = m.len, + }; + +cleanup: + da_free(loading); + free_loaded_units(&m); + return (load_result){0}; +} diff --git a/virtual_machine/loader.h b/virtual_machine/loader.h new file mode 100644 index 000000000..f5a8f0b7b --- /dev/null +++ b/virtual_machine/loader.h @@ -0,0 +1,23 @@ +#ifndef LOADER_H +#define LOADER_H + +#include "bytecode.h" +#include +#include + +#define BYTECODE_SUFFIX ".bc" +#define MAX_PATH_LEN 1024 + +typedef struct { + const char **paths; + size_t len; +} search_paths; + +typedef struct { + bytecode **units; // Array of unique loaded bytecode units + size_t units_len; // Number of unique units +} load_result; + +load_result load(const char *main_unit_name, const search_paths *paths); + +#endif // LOADER_H diff --git a/virtual_machine/memory.c b/virtual_machine/memory.c new file mode 100644 index 000000000..a64b52323 --- /dev/null +++ b/virtual_machine/memory.c @@ -0,0 +1,31 @@ +#define _POSIX_C_SOURCE 200809L + +#include "memory.h" +#include +#include +#include + +static void check_ptr(void *ptr, const char *file, int line) { + if (ptr == NULL) { + fprintf(stderr, "Out of memory at %s:%d\n", file, line); + exit(EXIT_FAILURE); + } +} + +void *emalloc(size_t size, const char *file, int line) { + void *ptr = malloc(size); + check_ptr(ptr, file, line); + return ptr; +} + +void *erealloc(void *ptr, size_t size, const char *file, int line) { + void *new_ptr = realloc(ptr, size); + check_ptr(new_ptr, file, line); + return new_ptr; +} + +char *estrdup(const char *s, const char *file, int line) { + char *ptr = strdup(s); + check_ptr(ptr, file, line); + return ptr; +} diff --git a/virtual_machine/memory.h b/virtual_machine/memory.h new file mode 100644 index 000000000..c800cd67f --- /dev/null +++ b/virtual_machine/memory.h @@ -0,0 +1,19 @@ +#ifndef MEMORY_H +#define MEMORY_H + +#include + +void *emalloc(size_t size, const char *file, int line); +void *erealloc(void *ptr, size_t size, const char *file, int line); +char *estrdup(const char *s, const char *file, int line); + +#define EMALLOC(size) emalloc((size), __FILE__, __LINE__) +#define EREALLOC(ptr, size) erealloc((ptr), (size), __FILE__, __LINE__) +#define ESTRDUP(s) estrdup((s), __FILE__, __LINE__) + +#define ALLOC(type) ((type *)emalloc(sizeof(type), __FILE__, __LINE__)) + +#define ALLOC_ARRAY(type, count) \ + ((type *)emalloc(sizeof(type) * (count), __FILE__, __LINE__)) + +#endif diff --git a/virtual_machine/opcodes.c b/virtual_machine/opcodes.c new file mode 100644 index 000000000..0624bb1bc --- /dev/null +++ b/virtual_machine/opcodes.c @@ -0,0 +1,120 @@ +#include "opcodes.h" +#include +#include + +static void unknown_opcode(uint8_t opcode) { + fprintf(stderr, "Unknown opcode: %d\n", opcode); + exit(EXIT_FAILURE); +} + +const char *opcode_to_string(uint8_t opcode) { + switch ((opcode_t)opcode) { + case OP_BINOP_ADD: + return "ADD"; + case OP_BINOP_SUB: + return "SUB"; + case OP_BINOP_MUL: + return "MUL"; + case OP_BINOP_DIV: + return "DIV"; + case OP_BINOP_MOD: + return "MOD"; + case OP_BINOP_LT: + return "LT"; + case OP_BINOP_LE: + return "LE"; + case OP_BINOP_GT: + return "GT"; + case OP_BINOP_GE: + return "GE"; + case OP_BINOP_EQ: + return "EQ"; + case OP_BINOP_NE: + return "NE"; + case OP_BINOP_AND: + return "AND"; + case OP_BINOP_OR: + return "OR"; + case OP_CONST: + return "CONST"; + case OP_STRING: + return "STRING"; + case OP_SEXP: + return "SEXP"; + case OP_STA: + return "STA"; + case OP_JMP: + return "JMP"; + case OP_END: + return "END"; + case OP_DROP: + return "DROP"; + case OP_DUP: + return "DUP"; + case OP_SWAP: + return "SWAP"; + case OP_ELEM: + return "ELEM"; + case OP_LD_GLO: + return "LD_GLO"; + case OP_LD_LOC: + return "LD_LOC"; + case OP_LD_ARG: + return "LD_ARG"; + case OP_LD_CLO: + return "LD_CLO"; + case OP_ST_GLO: + return "ST_GLO"; + case OP_ST_LOC: + return "ST_LOC"; + case OP_ST_ARG: + return "ST_ARG"; + case OP_ST_CLO: + return "ST_CLO"; + case OP_CJMP_Z: + return "CJMP_Z"; + case OP_CJMP_NZ: + return "CJMP_NZ"; + case OP_BEGIN: + return "BEGIN"; + case OP_BEGIN_CLOSURE: + return "BEGIN_CLOSURE"; + case OP_CLOSURE: + return "CLOSURE"; + case OP_CALLC: + return "CALLC"; + case OP_CALL: + return "CALL"; + case OP_TAG: + return "TAG"; + case OP_ARRAY: + return "ARRAY"; + case OP_FAIL: + return "FAIL"; + case OP_FAIL_KEEP: + return "FAIL_KEEP"; + case OP_LINE: + return "LINE"; + case OP_PATT_STR_CMP: + return "PATT_STR_CMP"; + case OP_PATT_STRING: + return "PATT_STRING"; + case OP_PATT_ARRAY: + return "PATT_ARRAY"; + case OP_PATT_SEXP: + return "PATT_SEXP"; + case OP_PATT_BOXED: + return "PATT_BOXED"; + case OP_PATT_UNBOXED: + return "PATT_UNBOXED"; + case OP_PATT_CLOSURE: + return "PATT_CLOSURE"; + case OP_BARRAY: + return "BARRAY"; + case OP_EOF: + return "EOF"; + default: + unknown_opcode(opcode); + return NULL; + } +} diff --git a/virtual_machine/opcodes.h b/virtual_machine/opcodes.h new file mode 100755 index 000000000..d51ca43a5 --- /dev/null +++ b/virtual_machine/opcodes.h @@ -0,0 +1,68 @@ +#ifndef OPCODES_H +#define OPCODES_H + +#include +#include + +typedef enum { + OP_BINOP_ADD = 0x01, + OP_BINOP_SUB = 0x02, + OP_BINOP_MUL = 0x03, + OP_BINOP_DIV = 0x04, + OP_BINOP_MOD = 0x05, + OP_BINOP_LT = 0x06, + OP_BINOP_LE = 0x07, + OP_BINOP_GT = 0x08, + OP_BINOP_GE = 0x09, + OP_BINOP_EQ = 0x0A, + OP_BINOP_NE = 0x0B, + OP_BINOP_AND = 0x0C, + OP_BINOP_OR = 0x0D, + OP_CONST = 0x10, + OP_STRING = 0x11, + OP_SEXP = 0x12, + OP_STA = 0x14, + OP_JMP = 0x15, + OP_END = 0x16, + OP_DROP = 0x18, + OP_DUP = 0x19, + OP_SWAP = 0x1A, + OP_ELEM = 0x1B, + OP_LD_GLO = 0x20, + OP_LD_LOC = 0x21, + OP_LD_ARG = 0x22, + OP_LD_CLO = 0x23, + OP_ST_GLO = 0x40, + OP_ST_LOC = 0x41, + OP_ST_ARG = 0x42, + OP_ST_CLO = 0x43, + OP_CJMP_Z = 0x50, + OP_CJMP_NZ = 0x51, + OP_BEGIN = 0x52, + OP_BEGIN_CLOSURE = 0x53, + OP_CLOSURE = 0x54, + OP_CALLC = 0x55, + OP_CALL = 0x56, + OP_TAG = 0x57, + OP_ARRAY = 0x58, + OP_FAIL = 0x59, + OP_FAIL_KEEP = 0x5A, + OP_LINE = 0x5B, + OP_PATT_STR_CMP = 0x60, + OP_PATT_STRING = 0x61, + OP_PATT_ARRAY = 0x62, + OP_PATT_SEXP = 0x63, + OP_PATT_BOXED = 0x64, + OP_PATT_UNBOXED = 0x65, + OP_PATT_CLOSURE = 0x66, + OP_BARRAY = 0x74, + OP_EOF = 0xFF, +} opcode_t; + +const char *opcode_to_string(uint8_t opcode); + +static inline bool opcode_is_func_begin(uint8_t opcode) { + return opcode == OP_BEGIN || opcode == OP_BEGIN_CLOSURE; +} + +#endif diff --git a/virtual_machine/ops.c b/virtual_machine/ops.c new file mode 100644 index 000000000..1e129b140 --- /dev/null +++ b/virtual_machine/ops.c @@ -0,0 +1,531 @@ +#include "ops.h" +#include "../runtime/runtime_common.h" +#include "debug.h" +#include "ffi.h" +#include "insn.h" +#include +#include +#include +#include + +extern size_t __gc_stack_top; + +/* + * External runtime functions (runtime.c) + */ +extern aint Ls__Infix_43(aint p, aint q); // + +extern aint Ls__Infix_45(aint p, aint q); // - +extern aint Ls__Infix_42(aint p, aint q); // * +extern aint Ls__Infix_47(aint p, aint q); // / +extern aint Ls__Infix_37(aint p, aint q); // % +extern aint Ls__Infix_60(aint p, aint q); // < +extern aint Ls__Infix_6061(aint p, aint q); // <= +extern aint Ls__Infix_62(aint p, aint q); // > +extern aint Ls__Infix_6261(aint p, aint q); // >= +extern aint Ls__Infix_6161(aint p, aint q); // == +extern aint Ls__Infix_3361(aint p, aint q); // != +extern aint Ls__Infix_3838(aint p, aint q); // && +extern aint Ls__Infix_3333(aint p, aint q); // || + +extern void *Barray(aint *args, aint bn); +extern void *Bsexp(aint *args, aint bn); +extern void *Bclosure(aint *args, aint bn); +extern void *Bstring(aint *args); +extern void *Belem(void *p, aint i); +extern void *Bsta(void *x, aint i, void *v); + +extern aint Btag(void *d, aint t, aint n); +extern aint Barray_patt(aint d, aint n); +extern aint Bstring_patt(aint x, aint y); +extern aint Bclosure_tag_patt(aint x); +extern aint Bboxed_patt(aint x); +extern aint Bunboxed_patt(aint x); +extern aint Barray_tag_patt(aint x); +extern aint Bstring_tag_patt(aint x); +extern aint Bsexp_tag_patt(aint x); +extern void Bmatch_failure(aint v, const char *fname, aint line, aint col); + +#define DISPATCH() \ + do { \ + ip++; \ + __attribute__((musttail)) return ip->func(STATE); \ + } while (0) + +#define DISPATCH_JUMP() \ + do { \ + __attribute__((musttail)) return ip->func(STATE); \ + } while (0) + +/* + * Stack manipulation macros (stack grows downwards) + */ +#define STACK_PUSH(sp, val) (*--(sp) = (val)) +#define STACK_POP(sp) (*sp++) +#define STACK_PEEK(sp) (*sp) +#define SYNC_GC_STACK(sp) (__gc_stack_top = (size_t)((sp) - 1)) +#define STACK_REVERSE(base, n) \ + do { \ + for (int32_t _i = 0; _i < (n) / 2; _i++) { \ + aint _tmp = (base)[_i]; \ + (base)[_i] = (base)[(n) - 1 - _i]; \ + (base)[(n) - 1 - _i] = _tmp; \ + } \ + } while (0) + +#define FRAME_SAVED_BP (-1) +#define FRAME_SAVED_IP (-2) +#define FRAME_SAVED_SP (-3) +#define FRAME_LOCALS (-4) + +#define PUSH_FRAME(n_args_val, saved_bp, saved_ip, caller_sp_val) \ + do { \ + STACK_PUSH(sp, n_args_val); \ + aint *new_bp = sp; \ + STACK_PUSH(sp, (aint)(saved_bp)); \ + STACK_PUSH(sp, (aint)(saved_ip)); \ + STACK_PUSH(sp, (aint)(caller_sp_val)); \ + bp = new_bp; \ + } while (0) + +#define DEFINE_BINARY_OP(name, fn, opname) \ + void name(DECL_STATE) { \ + aint y = STACK_POP(sp); \ + aint x = STACK_POP(sp); \ + VM_DEBUG(opname ": x=0x%lx, y=0x%lx\n", (unsigned long)x, \ + (unsigned long)y); \ + aint res = fn(x, y); \ + VM_DEBUG(opname " result=%ld\n", (long)UNBOX(res)); \ + STACK_PUSH(sp, res); \ + DISPATCH(); \ + } + +#define DEFINE_UNARY_OP(name, fn, opname) \ + void name(DECL_STATE) { \ + aint val = STACK_POP(sp); \ + VM_DEBUG(opname ": val=0x%lx\n", (unsigned long)val); \ + aint result = fn(val); \ + VM_DEBUG(opname " result=%ld\n", (long)UNBOX(result)); \ + STACK_PUSH(sp, result); \ + DISPATCH(); \ + } + +DEFINE_BINARY_OP(op_add, Ls__Infix_43, "ADD") +DEFINE_BINARY_OP(op_sub, Ls__Infix_45, "SUB") +DEFINE_BINARY_OP(op_mul, Ls__Infix_42, "MUL") +DEFINE_BINARY_OP(op_div, Ls__Infix_47, "DIV") +DEFINE_BINARY_OP(op_mod, Ls__Infix_37, "MOD") +DEFINE_BINARY_OP(op_lt, Ls__Infix_60, "LT") +DEFINE_BINARY_OP(op_le, Ls__Infix_6061, "LE") +DEFINE_BINARY_OP(op_gt, Ls__Infix_62, "GT") +DEFINE_BINARY_OP(op_ge, Ls__Infix_6261, "GE") +DEFINE_BINARY_OP(op_eq, Ls__Infix_6161, "EQ") +DEFINE_BINARY_OP(op_ne, Ls__Infix_3361, "NE") +DEFINE_BINARY_OP(op_and, Ls__Infix_3838, "AND") +DEFINE_BINARY_OP(op_or, Ls__Infix_3333, "OR") + +/* + * Pattern matching operations + */ +DEFINE_BINARY_OP(op_patt_str_cmp, Bstring_patt, "PATT_STR_CMP") +DEFINE_UNARY_OP(op_patt_string, Bstring_tag_patt, "PATT_STRING") +DEFINE_UNARY_OP(op_patt_array, Barray_tag_patt, "PATT_ARRAY") +DEFINE_UNARY_OP(op_patt_sexp, Bsexp_tag_patt, "PATT_SEXP") +DEFINE_UNARY_OP(op_patt_boxed, Bboxed_patt, "PATT_BOXED") +DEFINE_UNARY_OP(op_patt_unboxed, Bunboxed_patt, "PATT_UNBOXED") +DEFINE_UNARY_OP(op_patt_closure, Bclosure_tag_patt, "PATT_CLOSURE") + +#undef DEFINE_BINARY_OP +#undef DEFINE_UNARY_OP + +void op_const(DECL_STATE) { + ip++; + aint val = ip->num; + VM_DEBUG("CONST: %ld\n", (long)val); + STACK_PUSH(sp, BOX(val)); + DISPATCH(); +} + +void op_drop(DECL_STATE) { + VM_DEBUG("DROP\n"); + (void)STACK_POP(sp); + DISPATCH(); +} + +void op_dup(DECL_STATE) { + aint val = STACK_PEEK(sp); + VM_DEBUG("DUP: %ld\n", (long)UNBOX(val)); + STACK_PUSH(sp, val); + DISPATCH(); +} + +void op_swap(DECL_STATE) { + aint a = STACK_POP(sp); + aint b = STACK_POP(sp); + VM_DEBUG("SWAP: a=%ld, b=%ld\n", (long)UNBOX(a), (long)UNBOX(b)); + STACK_PUSH(sp, a); + STACK_PUSH(sp, b); + DISPATCH(); +} + +void op_elem(DECL_STATE) { + aint idx = STACK_POP(sp); + aint arr = STACK_POP(sp); + VM_DEBUG("ELEM: arr=%p, idx=%ld\n", (void *)arr, (long)UNBOX(idx)); + void *elem = Belem((void *)arr, idx); + STACK_PUSH(sp, (aint)elem); + DISPATCH(); +} + +void op_sta(DECL_STATE) { + aint val = STACK_POP(sp); + aint idx = STACK_POP(sp); + aint arr = STACK_POP(sp); + VM_DEBUG("STA: arr=%p, idx=%ld, val=%ld\n", (void *)arr, (long)UNBOX(idx), + (long)UNBOX(val)); + Bsta((void *)arr, idx, (void *)val); + STACK_PUSH(sp, val); + DISPATCH(); +} + +/* + * Jumps + */ +void op_jmp(DECL_STATE) { + ip++; + VM_DEBUG("JMP: target=%p\n", (void *)ip->target); + ip = ip->target; + DISPATCH_JUMP(); +} + +void op_cjmp_z(DECL_STATE) { + ip++; + insn *target = ip->target; + ip++; + aint val = STACK_POP(sp); + VM_DEBUG("CJMP_Z: val=%ld, target=%p, will_jump=%d\n", (long)UNBOX(val), + (void *)target, UNBOX(val) == 0); + if (UNBOX(val) == 0) { + ip = target; + } + DISPATCH_JUMP(); +} + +void op_cjmp_nz(DECL_STATE) { + ip++; + insn *target = ip->target; + ip++; + aint val = STACK_POP(sp); + VM_DEBUG("CJMP_NZ: val=%ld, target=%p, will_jump=%d\n", (long)UNBOX(val), + (void *)target, UNBOX(val) != 0); + if (UNBOX(val) != 0) { + ip = target; + } + DISPATCH_JUMP(); +} + +/* + * String, data etc. + */ +void op_string(DECL_STATE) { + ip++; + const char *str = ip->str; + SYNC_GC_STACK(sp); + void *result = Bstring((void *)&str); + VM_DEBUG("STRING literal: \"%s\" -> %p\n", str, result); + STACK_PUSH(sp, (aint)result); + DISPATCH(); +} + +void op_barray(DECL_STATE) { + ip++; + int32_t n = ip->num; + VM_DEBUG("BARRAY: n=%d\n", n); + aint *args = sp; + STACK_REVERSE(args, n); + SYNC_GC_STACK(sp); + void *arr = Barray(args, BOX(n)); + sp = args + n; + STACK_PUSH(sp, (aint)arr); + DISPATCH(); +} + +void op_sexp(DECL_STATE) { + ip++; + aint tag_hash = ip->anum; + ip++; + int32_t n_fields = ip->num; + + VM_DEBUG("SEXP: tag_hash=0x%lx, n_fields=%d\n", tag_hash, n_fields); + // Use the free slot below the current top for tag_hash. + aint *args = sp - 1; + args[0] = tag_hash; + STACK_REVERSE(args, n_fields + 1); + + SYNC_GC_STACK(args); + void *s = Bsexp(args, BOX(n_fields + 1)); + sp += n_fields; + STACK_PUSH(sp, (aint)s); + DISPATCH(); +} + +void op_tag(DECL_STATE) { + ip++; + aint tag_hash = ip->anum; + ip++; + int32_t n_fields = ip->num; + + aint val = STACK_POP(sp); + VM_DEBUG("TAG: tag_hash=0x%lx n_fields=%d val=0x%lx\n", + (unsigned long)tag_hash, n_fields, (long)val); + aint result = Btag((void *)val, tag_hash, BOX(n_fields)); + VM_DEBUG("TAG: result=%ld\n", (long)UNBOX(result)); + STACK_PUSH(sp, result); + DISPATCH(); +} + +void op_array(DECL_STATE) { + ip++; + int32_t n = ip->num; + aint val = STACK_POP(sp); + VM_DEBUG("ARRAY: n=%d, val=%p\n", n, (void *)val); + aint result = Barray_patt(val, BOX(n)); + STACK_PUSH(sp, result); + DISPATCH(); +} + +void op_fail(DECL_STATE) { + (void)bp; + ip++; + int32_t line = ip->num; + ip++; + int32_t col = ip->num; + ip++; + bool drop_value = ip->num; + ip++; + const char *module_name = ip->str; + + aint val = drop_value ? STACK_POP(sp) : STACK_PEEK(sp); + Bmatch_failure(val, module_name, BOX(line), BOX(col)); +} + +/* + * Load / store global variables (by pointer) + */ +void op_ld_glo(DECL_STATE) { + ip++; + aint *ptr = ip->global_ptr; + VM_DEBUG("LD_GLO ptr=%p val=%ld\n", (void *)ptr, (long)*ptr); + STACK_PUSH(sp, *ptr); + DISPATCH(); +} + +void op_st_glo(DECL_STATE) { + ip++; + aint *ptr = ip->global_ptr; + aint val = STACK_PEEK(sp); + VM_DEBUG("ST_GLO ptr=%p val=%ld\n", (void *)ptr, (long)val); + *ptr = val; + DISPATCH(); +} + +void op_ld_loc(DECL_STATE) { + ip++; + int32_t idx = ip->num; + VM_DEBUG("LD_LOC[%d] bp=%p val=%ld\n", idx, (void *)bp, + (long)bp[FRAME_LOCALS - idx]); + STACK_PUSH(sp, bp[FRAME_LOCALS - idx]); + DISPATCH(); +} + +void op_st_loc(DECL_STATE) { + ip++; + int32_t idx = ip->num; + aint val = STACK_PEEK(sp); + VM_DEBUG("ST_LOC[%d] = %ld bp=%p\n", idx, (long)val, (void *)bp); + bp[FRAME_LOCALS - idx] = val; + DISPATCH(); +} + +void op_ld_arg(DECL_STATE) { + ip++; + int32_t idx = ip->num; + int32_t n_args = (int32_t)bp[0]; + aint val = bp[n_args - idx]; + VM_DEBUG("LD_ARG[%d] n_args=%d bp=%p val=%ld\n", idx, n_args, (void *)bp, + (long)val); + STACK_PUSH(sp, val); + DISPATCH(); +} + +void op_st_arg(DECL_STATE) { + ip++; + int32_t idx = ip->num; + int32_t n_args = (int32_t)bp[0]; + aint val = STACK_PEEK(sp); + VM_DEBUG("ST_ARG[%d] = %ld bp=%p\n", idx, (long)val, (void *)bp); + bp[n_args - idx] = val; + DISPATCH(); +} + +void op_ld_clo(DECL_STATE) { + ip++; + int32_t idx = ip->num; + int32_t n_args = (int32_t)bp[0]; + aint *closure = (aint *)bp[n_args + 1]; + VM_DEBUG("LD_CLO[%d] closure=%p val=%ld\n", idx, (void *)closure, + (long)closure[idx + 1]); + STACK_PUSH(sp, closure[idx + 1]); + DISPATCH(); +} + +void op_st_clo(DECL_STATE) { + ip++; + int32_t idx = ip->num; + int32_t n_args = (int32_t)bp[0]; + aint val = STACK_PEEK(sp); + aint *closure = (aint *)bp[n_args + 1]; + VM_DEBUG("ST_CLO[%d] = %ld closure=%p\n", idx, (long)val, (void *)closure); + closure[idx + 1] = val; + DISPATCH(); +} + +/* + * Function call operations + */ +#define DEFINE_BEGIN(name) \ + void name(DECL_STATE) { \ + ip++; \ + int32_t n_args = ip->num; \ + (void)n_args; \ + ip++; \ + int32_t n_locals = ip->num; \ + \ + VM_DEBUG("BEGIN n_args=%d n_locals=%d bp=%p sp=%p\n", n_args, n_locals, \ + (void *)bp, (void *)sp); \ + \ + for (int32_t i = 0; i < n_locals; i++) { \ + STACK_PUSH(sp, BOX(0)); \ + } \ + \ + DISPATCH(); \ + } + +/* + * The distinction is made at the opcode level to allow for easier validation + * during decoding (and maybe for future things). + */ +DEFINE_BEGIN(op_begin) +DEFINE_BEGIN(op_begin_closure) +#undef DEFINE_BEGIN + +void op_call(DECL_STATE) { + ip++; + insn *target = ip->target; + ip++; + int32_t n_args = ip->num; + ip++; // sort of a return address + + VM_DEBUG("CALL target=%p n_args=%d sp=%p bp=%p\n", (void *)target, n_args, + (void *)sp, (void *)bp); + + aint *caller_sp = sp + n_args; + PUSH_FRAME(n_args, bp, ip, caller_sp); + ip = target; + DISPATCH_JUMP(); +} + +void op_callc(DECL_STATE) { + ip++; + int32_t n_args = ip->num; + ip++; // sort of a return address + + aint closure_val = *(sp + n_args); + aint *closure = (aint *)closure_val; + aint entry = closure[0]; + insn *target = (insn *)entry; + + VM_DEBUG("CALLC closure=%p target=%p n_args=%d sp=%p bp=%p\n", + (void *)closure, (void *)target, n_args, (void *)sp, (void *)bp); + + aint *caller_sp = sp + n_args + 1; + PUSH_FRAME(n_args, bp, ip, caller_sp); + ip = target; + DISPATCH_JUMP(); +} + +void op_end(DECL_STATE) { + aint ret_val = STACK_POP(sp); + + VM_DEBUG("END ret_val=%ld bp=%p sp=%p\n", (long)ret_val, (void *)bp, + (void *)sp); + + // Restore caller's state from frame + sp = (aint *)bp[FRAME_SAVED_SP]; + ip = (insn *)bp[FRAME_SAVED_IP]; + bp = (aint *)bp[FRAME_SAVED_BP]; + + STACK_PUSH(sp, ret_val); + DISPATCH_JUMP(); +} + +/* + * FFI call — dispatches via pre-resolved ffi_resolved struct + */ +void op_ffi_call(DECL_STATE) { + ip++; + const ffi_resolved *res = (const ffi_resolved *)ip->ptr; + + int32_t n_args = (int32_t)bp[0]; + + VM_DEBUG("FFI_CALL: kind=%d n_args=%d bp=%p\n", res->kind, n_args, + (void *)bp); + + // args at bp[1..n_args] + STACK_REVERSE(bp + 1, n_args); + SYNC_GC_STACK(sp); + aint result = ffi_call_c(res, bp + 1, n_args); + VM_DEBUG("FFI_CALL: result=%ld\n", (long)result); + + // Same as op_end + sp = (aint *)bp[FRAME_SAVED_SP]; + ip = (insn *)bp[FRAME_SAVED_IP]; + bp = (aint *)bp[FRAME_SAVED_BP]; + + STACK_PUSH(sp, result); + DISPATCH_JUMP(); +} + +void op_closure(DECL_STATE) { + ip++; + insn *target = ip->target; + ip++; + int32_t n_captured = ip->num; + + VM_DEBUG("CLOSURE: target=%p n_captured=%d\n", (void *)target, n_captured); + + aint *args = sp - 1; + args[0] = (aint)target; + STACK_REVERSE(args + 1, n_captured); + + SYNC_GC_STACK(args); + void *closure = Bclosure(args, BOX(n_captured)); + sp += n_captured; + VM_DEBUG("CLOSURE: created=%p\n", (void *)closure); + STACK_PUSH(sp, (aint)closure); + DISPATCH(); +} + +void op_eof(DECL_STATE) { + (void)ip; + (void)bp; + (void)sp; + return; +} + +void op_line(DECL_STATE) { + ip++; + int32_t line = ip->num; + fprintf(stderr, "LINE %d\n", line); + (void)line; + DISPATCH(); +} diff --git a/virtual_machine/ops.h b/virtual_machine/ops.h new file mode 100644 index 000000000..de04113e3 --- /dev/null +++ b/virtual_machine/ops.h @@ -0,0 +1,68 @@ +#ifndef OPS_H +#define OPS_H + +#include "insn.h" + +void op_add(DECL_STATE); +void op_sub(DECL_STATE); +void op_mul(DECL_STATE); +void op_div(DECL_STATE); +void op_mod(DECL_STATE); + +void op_lt(DECL_STATE); +void op_le(DECL_STATE); +void op_gt(DECL_STATE); +void op_ge(DECL_STATE); +void op_eq(DECL_STATE); +void op_ne(DECL_STATE); + +void op_and(DECL_STATE); +void op_or(DECL_STATE); + +void op_const(DECL_STATE); +void op_drop(DECL_STATE); +void op_dup(DECL_STATE); +void op_swap(DECL_STATE); + +void op_elem(DECL_STATE); +void op_sta(DECL_STATE); +void op_string(DECL_STATE); +void op_barray(DECL_STATE); +void op_sexp(DECL_STATE); +void op_tag(DECL_STATE); +void op_array(DECL_STATE); + +void op_jmp(DECL_STATE); +void op_cjmp_z(DECL_STATE); +void op_cjmp_nz(DECL_STATE); +void op_fail(DECL_STATE); + +void op_patt_str_cmp(DECL_STATE); +void op_patt_string(DECL_STATE); +void op_patt_array(DECL_STATE); +void op_patt_sexp(DECL_STATE); +void op_patt_boxed(DECL_STATE); +void op_patt_unboxed(DECL_STATE); +void op_patt_closure(DECL_STATE); + +void op_ld_glo(DECL_STATE); +void op_st_glo(DECL_STATE); +void op_ld_loc(DECL_STATE); +void op_st_loc(DECL_STATE); +void op_ld_arg(DECL_STATE); +void op_st_arg(DECL_STATE); +void op_ld_clo(DECL_STATE); +void op_st_clo(DECL_STATE); + +void op_begin(DECL_STATE); +void op_begin_closure(DECL_STATE); +void op_call(DECL_STATE); +void op_callc(DECL_STATE); +void op_end(DECL_STATE); +void op_closure(DECL_STATE); +void op_ffi_call(DECL_STATE); + +void op_eof(DECL_STATE); +void op_line(DECL_STATE); + +#endif // OPS_H diff --git a/virtual_machine/reader.h b/virtual_machine/reader.h new file mode 100644 index 000000000..2c8547830 --- /dev/null +++ b/virtual_machine/reader.h @@ -0,0 +1,58 @@ +#ifndef READER_H +#define READER_H + +#include +#include +#include +#include + +typedef struct { + const uint8_t *data; + size_t size; + size_t pos; +} byte_reader; + +static inline void reader_init(byte_reader *r, const uint8_t *data, + size_t size) { + r->data = data; + r->size = size; + r->pos = 0; +} + +/* + * Read 32-bit little-endian integer and advance position + */ +static inline int32_t reader_i32(byte_reader *r) { + assert(r->pos + 4 <= r->size); + const uint8_t *p = r->data + r->pos; + r->pos += 4; + return (int32_t)((uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24)); +} + +static inline uint8_t reader_u8(byte_reader *r) { + assert(r->pos < r->size); + return r->data[r->pos++]; +} + +static inline void reader_skip(byte_reader *r, size_t n) { + r->pos += n; + if (r->pos > r->size) { + r->pos = r->size; + } +} + +static inline void reader_seek(byte_reader *r, size_t pos) { + r->pos = pos; + if (r->pos > r->size) { + r->pos = r->size; + } +} + +static inline size_t reader_pos(const byte_reader *r) { return r->pos; } + +static inline bool reader_eof(const byte_reader *r) { + return r->pos >= r->size; +} + +#endif // READER_H diff --git a/virtual_machine/symbols.c b/virtual_machine/symbols.c new file mode 100644 index 000000000..a2185c880 --- /dev/null +++ b/virtual_machine/symbols.c @@ -0,0 +1,77 @@ +#include "symbols.h" +#include "da.h" +#include "memory.h" +#include +#include +#include + +struct symbol_table { + resolved_symbol *data; + size_t len; + size_t cap; +}; + +symbol_table *symbol_table_create(void) { + symbol_table *table = ALLOC(symbol_table); + da_init(*table); + return table; +} + +void symbol_table_destroy(symbol_table *table) { + if (!table) { + return; + } + da_free(*table); + free(table); +} + +static resolved_symbol *symbol_table_find(symbol_table *table, const char *name, + bool is_function) { + for (size_t i = 0; i < table->len; i++) { + if (strcmp(table->data[i].name, name) == 0 && + table->data[i].is_function == is_function) { + return &table->data[i]; + } + } + return NULL; +} + +static bool symbol_table_add(symbol_table *table, const char *name, int32_t idx, + bool is_function) { + resolved_symbol *existing = symbol_table_find(table, name, is_function); + if (existing) { + fprintf(stderr, "Error: Duplicate symbol '%s' found in symbol table\n", + name); + return false; + } + + resolved_symbol entry = { + .name = name, + .is_function = is_function, + .idx = idx, + }; + + da_append(*table, entry); + + return true; +} + +resolved_symbol *symbol_table_find_function(symbol_table *table, + const char *name) { + return symbol_table_find(table, name, true); +} + +resolved_symbol *symbol_table_find_global(symbol_table *table, + const char *name) { + return symbol_table_find(table, name, false); +} + +bool symbol_table_add_function(symbol_table *table, const char *name, + int32_t code_idx) { + return symbol_table_add(table, name, code_idx, true); +} + +bool symbol_table_add_global(symbol_table *table, const char *name, + int32_t global_idx) { + return symbol_table_add(table, name, global_idx, false); +} diff --git a/virtual_machine/symbols.h b/virtual_machine/symbols.h new file mode 100644 index 000000000..dd37b63f2 --- /dev/null +++ b/virtual_machine/symbols.h @@ -0,0 +1,37 @@ +#ifndef SYMBOLS_H +#define SYMBOLS_H + +#include +#include +#include + +/* + * Resolved symbol — represents a public function or global variable + * that has been registered by the linker after decoding a unit. + */ +typedef struct { + const char *name; // Symbol name (points into bytecode's string table) + bool is_function; // true = function, false = global variable + // For functions: index into the final code array + // For globals: rebased global index (stack position) + int32_t idx; +} resolved_symbol; + +/* + * Maps symbol names to resolved symbols (functions or globals). + * Used by the linker to resolve stubs after all units are decoded. + */ +typedef struct symbol_table symbol_table; + +symbol_table *symbol_table_create(void); +void symbol_table_destroy(symbol_table *table); +resolved_symbol *symbol_table_find_function(symbol_table *table, + const char *name); +resolved_symbol *symbol_table_find_global(symbol_table *table, + const char *name); +bool symbol_table_add_function(symbol_table *table, const char *name, + int32_t code_index); +bool symbol_table_add_global(symbol_table *table, const char *name, + int32_t global_idx); + +#endif // SYMBOLS_H diff --git a/virtual_machine/vm.c b/virtual_machine/vm.c new file mode 100644 index 000000000..b2075cbd2 --- /dev/null +++ b/virtual_machine/vm.c @@ -0,0 +1,117 @@ +#include "vm.h" +#include "../runtime/gc.h" +#include "../runtime/runtime_common.h" +#include "converter.h" +#include "loader.h" +#include "memory.h" +#include +#include +#include +#include +#include + +extern size_t __gc_stack_top, __gc_stack_bottom; +extern void set_args(aint argc, char *argv[]); + +struct virtual_machine { + bytecode **bc_arr; // Array of unique loaded bytecode units + size_t bc_len; + insn *code; // Contiguous code array + insn *entry_points; // Entry point for each unique unit + size_t entry_points_len; + size_t total_globals; + aint *globals; // Globals array (at the top of the stack) + void *ffi_data; // ffi_resolved array + size_t ffi_count; + void *stack_base; + size_t stack_size; + int argc; + char **argv; +}; + +virtual_machine *vm_create(const char *main_unit_name, const char **paths, + size_t total_paths_len) { + search_paths search_paths = {.paths = paths, .len = total_paths_len}; + + virtual_machine *vm = ALLOC(virtual_machine); + memset(vm, 0, sizeof(virtual_machine)); + vm->stack_base = MAP_FAILED; + + load_result lr = load(main_unit_name, &search_paths); + if (!lr.units) { + vm_destroy(vm); + return NULL; + } + vm->bc_arr = lr.units; + vm->bc_len = lr.units_len; + + vm->stack_size = 8 * 1024 * 1024; + void *mmap_base = mmap(NULL, vm->stack_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0); + if (mmap_base == MAP_FAILED) { + perror("mmap stack"); + vm_destroy(vm); + return NULL; + } + vm->stack_base = (char *)mmap_base + vm->stack_size; + + // Compute total globals and place at the top of the stack + vm->total_globals = bytecode_count_globals(lr.units, lr.units_len); + vm->globals = (aint *)vm->stack_base - vm->total_globals; + for (size_t i = 0; i < vm->total_globals; i++) { + vm->globals[i] = BOX(0); + } + + program *prog = decode(lr.units, lr.units_len, vm->globals); + if (!prog) { + vm_destroy(vm); + return NULL; + } + + vm->code = prog->code; + vm->entry_points = prog->entry_points; + vm->ffi_data = prog->ffi_data; + vm->ffi_count = prog->ffi_len; + + free(prog); + + return vm; +} + +void vm_destroy(virtual_machine *vm) { + if (!vm) { + return; + } + if (vm->bc_arr) { + for (size_t i = 0; i < vm->bc_len; i++) { + bytecode_free(vm->bc_arr[i]); + } + free(vm->bc_arr); + } + free(vm->ffi_data); + free(vm->code); + free(vm->entry_points); + if (vm->stack_base && vm->stack_base != MAP_FAILED) { + munmap((char *)vm->stack_base - vm->stack_size, vm->stack_size); + } + free(vm); +} + +void vm_set_args(virtual_machine *vm, int argc, char *argv[]) { + vm->argc = argc; + vm->argv = argv; +} + +void vm_run(virtual_machine *vm) { + __init(); + __gc_stack_bottom = (size_t)vm->stack_base; + set_args(vm->argc, vm->argv); + + insn *ip = vm->entry_points; + aint *sp = vm->globals; + aint *bp = NULL; + + ip->func(ip, sp, bp); + + __shutdown(); +} diff --git a/virtual_machine/vm.h b/virtual_machine/vm.h new file mode 100644 index 000000000..8b3f1ff50 --- /dev/null +++ b/virtual_machine/vm.h @@ -0,0 +1,17 @@ +#ifndef VM_H +#define VM_H + +#include + +typedef struct virtual_machine virtual_machine; + +virtual_machine *vm_create(const char *main_unit_name, const char **paths, + size_t total_paths_len); + +void vm_destroy(virtual_machine *vm); + +void vm_set_args(virtual_machine *vm, int argc, char *argv[]); + +void vm_run(virtual_machine *vm); + +#endif // VM_H