Compare commits
14 Commits
9fc093e3fb
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bc6ad1c92 | |||
| f9733f3b20 | |||
| a1aed4988a | |||
| b835dbb36e | |||
|
|
71390b0f84 | ||
|
|
b471726e0d | ||
|
|
feb272e545 | ||
|
|
03b61f4339 | ||
|
|
30bfb38e9a | ||
|
|
635596c31d | ||
| 148c98e642 | |||
| 54729c1e14 | |||
| d8130272a0 | |||
| 3f097b0ba8 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -32,3 +32,5 @@
|
||||
*.out
|
||||
*.app
|
||||
|
||||
cmake-build-*/
|
||||
build/
|
||||
|
||||
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
37
.idea/editor.xml
generated
Normal file
37
.idea/editor.xml
generated
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="BackendCodeEditorSettings">
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefinitionsOrder/@EntryIndexedValue" value="HINT" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedOverridenMethod/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppModulePartitionWithSeveralPartitionUnits/@EntryIndexedValue" value="WARNING" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantFwdClassOrEnumSpecifier/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifierADL/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppCodeStyle/CVQualifiersPlacement/@EntryValue" value="AfterType" type="string" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALIGN_TERNARY/@EntryValue" value="ALIGN_ALL" type="string" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALLOW_COMMENT_AFTER_LBRACE/@EntryValue" value="true" type="bool" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CASE_BLOCK_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/EMPTY_BLOCK_STYLE/@EntryValue" value="TOGETHER_SAME_LINE" type="string" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ENABLE_SLATE_FORMAT/@EntryValue" value="false" type="bool" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CASE_FROM_SWITCH/@EntryValue" value="true" type="bool" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_GOTO_LABELS/@EntryValue" value="false" type="bool" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/MAX_ENUM_MEMBERS_ON_LINE/@EntryValue" value="6" type="long" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_INDENTATION/@EntryValue" value="None" type="string" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/OTHER_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_CATCH_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_ELSE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_WHILE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/REQUIRES_EXPRESSION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue" value="LINE_BREAK" type="string" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/SPACE_AFTER_CAST_EXPRESSION_PARENTHESES/@EntryValue" value="true" type="bool" />
|
||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/WRAP_LINES/@EntryValue" value="false" type="bool" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Classes_0020and_0020structs/@EntryIndexedValue" value="<NamingElement Priority="1"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="__interface" /><type Name="class" /><type Name="struct" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Concepts/@EntryIndexedValue" value="<NamingElement Priority="2"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="concept" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Enum_0020members/@EntryIndexedValue" value="<NamingElement Priority="14"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="scoped enumerator" /><type Name="unscoped enumerator" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Enums/@EntryIndexedValue" value="<NamingElement Priority="3"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="enum" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Other_0020constants/@EntryIndexedValue" value="<NamingElement Priority="15"><Descriptor Static="True" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="local variable" /><type Name="struct field" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></NamingElement>" type="string" />
|
||||
<option name="/Default/CodeStyle/Naming/CppNaming/Rules/=Unions/@EntryIndexedValue" value="<NamingElement Priority="4"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="union" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></NamingElement>" type="string" />
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CMakePythonSetting">
|
||||
<option name="pythonIntegrationState" value="YES" />
|
||||
</component>
|
||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/tyche.iml" filepath="$PROJECT_DIR$/.idea/tyche.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/tyche.iml
generated
Normal file
2
.idea/tyche.iml
generated
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module classpath="CIDR" type="CPP_MODULE" version="4" />
|
||||
7
.idea/vcs.xml
generated
Normal file
7
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
127
CMakeLists.txt
Normal file
127
CMakeLists.txt
Normal file
@@ -0,0 +1,127 @@
|
||||
cmake_minimum_required (VERSION 3.24)
|
||||
|
||||
project(tyche
|
||||
VERSION 0.0.1
|
||||
DESCRIPTION "An embeddable/standalone programming language"
|
||||
LANGUAGES C CXX ASM)
|
||||
|
||||
#
|
||||
# project options / configuration
|
||||
#
|
||||
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 23 CACHE STRING "C++ Standard")
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set_property(GLOBAL PROPERTY CXX_EXTENSIONS OFF)
|
||||
set_property(GLOBAL PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
set_property(GLOBAL PROPERTY LINK_WHAT_YOU_USE TRUE)
|
||||
|
||||
# warnings / flags
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(warnings -Wall -Wextra -Wformat-nonliteral -Wundef -Wshadow -Wwrite-strings -Wfloat-equal -Wswitch-default -Wmissing-format-attribute -Wswitch-enum -Wmissing-noreturn -Wno-unused-parameter -Wno-unused)
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set(warnings ${warnings} -Wsuggest-attribute=pure -Wsuggest-attribute=const -Wsuggest-attribute=noreturn -Wsuggest-attribute=malloc -Wsuggest-attribute=format -Wsuggest-attribute=cold)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# try to use ccache, if available
|
||||
find_program(CCACHE_PROGRAM ccache)
|
||||
if(CCACHE_PROGRAM)
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
endif()
|
||||
|
||||
# ignore warnings in imported files
|
||||
set_source_files_properties(${IMGUI_SRC} PROPERTIES COMPILE_FLAGS "-w")
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
add_compile_options(-ggdb -O0)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
set(DEF B_PRODUCTION_MODE=ON)
|
||||
add_compile_options(-Ofast -flto)
|
||||
endif()
|
||||
|
||||
#
|
||||
# libraries
|
||||
#
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
# Specify the commit you depend on and update it regularly.
|
||||
URL https://github.com/google/googletest/releases/download/v1.17.0/googletest-1.17.0.tar.gz
|
||||
)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
|
||||
#
|
||||
# library
|
||||
#
|
||||
|
||||
add_library(lib${PROJECT_NAME} SHARED
|
||||
src/common/overloaded.hh
|
||||
src/common/bytearray.hh
|
||||
src/common/bytearray.cc
|
||||
src/bytecode/bytecode.cc
|
||||
src/bytecode/bytecode.hh
|
||||
src/bytecode/bytecodeprototype.hh
|
||||
src/bytecode/constant.hh
|
||||
src/vm/code.cc
|
||||
src/vm/code.hh
|
||||
src/vm/instruction.hh
|
||||
src/vm/instruction.cc
|
||||
src/vm/value.cc
|
||||
src/vm/value.hh
|
||||
src/vm/stack.cc
|
||||
src/vm/stack.hh
|
||||
src/vm/vm_exceptions.hh
|
||||
src/vm/vm.cc
|
||||
src/vm/vm.hh
|
||||
src/vm/expr.cc
|
||||
src/vm/expr.hh
|
||||
src/vm/location.hh
|
||||
src/assembler/lexer.cc
|
||||
src/assembler/lexer.hh
|
||||
src/assembler/assembler.cc
|
||||
src/assembler/assembler.hh
|
||||
src/assembler/as_exceptions.hh
|
||||
src/bytecode/bc_exceptions.hh
|
||||
)
|
||||
|
||||
target_compile_options(lib${PROJECT_NAME} PRIVATE ${warnings})
|
||||
|
||||
#
|
||||
# tests
|
||||
#
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_executable(${PROJECT_NAME}-bytecode-test src/bytecode/tests.cc)
|
||||
target_link_libraries(${PROJECT_NAME}-bytecode-test lib${PROJECT_NAME} gtest_main)
|
||||
add_test(NAME tyche_bytecode_test COMMAND ${PROJECT_NAME}-bytecode-test)
|
||||
|
||||
add_executable(${PROJECT_NAME}-vm-test src/vm/tests.cc)
|
||||
target_link_libraries(${PROJECT_NAME}-vm-test lib${PROJECT_NAME} gtest_main)
|
||||
add_test(NAME tyche_vm_test COMMAND ${PROJECT_NAME}-vm-test)
|
||||
|
||||
add_executable(${PROJECT_NAME}-as-test src/assembler/tests.cc)
|
||||
target_link_libraries(${PROJECT_NAME}-as-test lib${PROJECT_NAME} gtest_main)
|
||||
add_test(NAME tyche_as_test COMMAND ${PROJECT_NAME}-as-test)
|
||||
|
||||
#
|
||||
# check for leaks
|
||||
#
|
||||
|
||||
add_custom_target(leaks-vm-test)
|
||||
add_custom_command(TARGET leaks-vm-test
|
||||
POST_BUILD
|
||||
COMMENT "Check for leaks using valgrind."
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
COMMAND valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --suppressions=${CMAKE_SOURCE_DIR}/valgrind.supp ./${PROJECT_NAME}-vm-test
|
||||
)
|
||||
|
||||
#
|
||||
# installation
|
||||
#
|
||||
|
||||
install(TARGETS lib${CMAKE_PROJECT_NAME} RUNTIME DESTINATION lib)
|
||||
65
TODO.md
Normal file
65
TODO.md
Normal file
@@ -0,0 +1,65 @@
|
||||
## Bytecode
|
||||
|
||||
- [x] Byte array
|
||||
- Auto-expand
|
||||
- Add/retrive byte/int/float/string
|
||||
- Should not be larger than the byte array itself
|
||||
- [x] Bytecode
|
||||
- Add/retrive all types of data
|
||||
- Keeps no memory except for caching
|
||||
- [x] Refactor bytecode code
|
||||
|
||||
Improvements:
|
||||
- [x] Fixed int type (based on opcode)
|
||||
- [x] Constant type (only floats and strings for now)
|
||||
|
||||
After some additional development:
|
||||
- [ ] Bytecode debugging info
|
||||
|
||||
|
||||
## VM
|
||||
|
||||
- [x] VM
|
||||
- [x] Code
|
||||
- [x] Simple bytecode loader
|
||||
- [x] Output bytecode format
|
||||
- [x] Value object
|
||||
- [x] Stack object
|
||||
- [x] External interface
|
||||
- [x] Code execution (except functions)
|
||||
- [x] Functions
|
||||
- [x] Print stack
|
||||
- [x] Assembler
|
||||
- [ ] VM execution
|
||||
- [x] Stack operations (nil, integer, float, string, function)
|
||||
- [x] Integer
|
||||
- [x] Float
|
||||
- [x] String
|
||||
- [x] Expressions
|
||||
- [x] Integer
|
||||
- [x] Float
|
||||
- [x] String
|
||||
- [ ] Local/global variables
|
||||
- [ ] Functions
|
||||
- [ ] Constants
|
||||
- [ ] Other operations
|
||||
- [ ] Arrays
|
||||
- [ ] Iteration
|
||||
- [ ] Expressions
|
||||
- [ ] Tables
|
||||
- [ ] Iteration
|
||||
- [ ] Metatables
|
||||
- [ ] Expressions
|
||||
- [ ] Control flow
|
||||
- [ ] Compilation
|
||||
- [ ] Error handling
|
||||
- [ ] C++ API
|
||||
- [ ] Run native code on VM
|
||||
- [ ] Run tyche code from C++
|
||||
- [ ] C API
|
||||
|
||||
After some additional development:
|
||||
- [ ] Bytecode loader
|
||||
- Combine multiple chunks
|
||||
- Resolve function ids, constant ids, etc
|
||||
- [ ] Upvalues
|
||||
35
doc/BYTECODE
Normal file
35
doc/BYTECODE
Normal file
@@ -0,0 +1,35 @@
|
||||
Bytecode format
|
||||
---------------
|
||||
|
||||
The bytecode file is composed of the following sections:
|
||||
|
||||
* HEADER: 16-byte header
|
||||
[0:3]: Magic
|
||||
[4]: VM format
|
||||
[rest]: Reserved for future use
|
||||
* TABLE_OF_CONTENTS: list of 8 records pointing to each one of the sections
|
||||
Each record (6 bytes):
|
||||
- Pointer to section: 4 bytes
|
||||
- Number of records in section: 2 bytes
|
||||
* [0x0] Constants indexes: pointers to each of the constant locations
|
||||
* Table of 4-byte constant indexes with pointer to constant
|
||||
(counter start at beginning of raw constants)
|
||||
* [0x1] Functions indexes: Pointer to functions within the code
|
||||
[0:3]: function pointer (counter start at the beginning of executable code)
|
||||
[4:5]: number of parameters
|
||||
[6:7]: number of local variables
|
||||
[8:b]: function size
|
||||
* [0x2] Constants raw data
|
||||
* [0x3] Code: executable code
|
||||
* [0x4] Debugging info
|
||||
???
|
||||
|
||||
The max file size is 2 Gb.
|
||||
|
||||
## Values can be encoded in the following ways:
|
||||
* The type is defined by the operator.
|
||||
* Encoding varies according to the type:
|
||||
int: use protobuf format
|
||||
float: 4-bit floating point
|
||||
string: int-defined length, followed by the string proper - no null terminator
|
||||
* Constant indexes and function ids are encoded as ints
|
||||
93
doc/OPCODES
Normal file
93
doc/OPCODES
Normal file
@@ -0,0 +1,93 @@
|
||||
Operations
|
||||
----------
|
||||
|
||||
Operations take either 0 or 1 parameter. The ones that take a parameter, it can be either a int8, int16 or int32.
|
||||
|
||||
Instructions follow this logic:
|
||||
|
||||
00 ~ 9F : no parameter
|
||||
A0 ~ BF : int8 (1 byte)
|
||||
C0 ~ DF : int16 (2 bytes)
|
||||
E0 ~ FF : int32 (4 bytes)
|
||||
|
||||
The operations of 1, 2 and 4 bytes are always interchangeable by adding/subtracting 0x20.
|
||||
|
||||
,----------- no parameter
|
||||
| ,-------- int8
|
||||
| | ,----- int16
|
||||
| | | ,-- int32
|
||||
NP I8 I16 I32 Opc Instruction Description
|
||||
|
||||
Stack operations:
|
||||
a0 c0 e0 pushi [int] Push int
|
||||
a1 c1 e1 pushc [index] Push constant
|
||||
a2 c2 e2 pushf [function] Push function id
|
||||
00 pushz Push zero (or false)
|
||||
01 pusht Push true
|
||||
02 newa Push (create) empty array
|
||||
03 newt Push (create) empty table
|
||||
04 pop
|
||||
05 dup
|
||||
|
||||
Local variables:
|
||||
a3 c3 e3 pushv [int] Push n nil values into the stack (used to init local vars)
|
||||
ab cb eb set [index] Set value in stack position (set local variable)
|
||||
a4 c4 e4 dupv [index] Duplicate stack value (load local variable)
|
||||
a5 c5 e5 setg [int] Set global variable
|
||||
a6 c6 e6 getg [int] Get global variable
|
||||
|
||||
Function operations:
|
||||
a7 c7 e7 call [n_pars] Enter function on stack toplevel (passing n next stack values as parameters)
|
||||
10 ret Leave a function (return value in stack)
|
||||
11 retn Leave a function (return nil)
|
||||
|
||||
Table and array operations:
|
||||
16 getkv Get table's value based on key (pull 1 value, push 1 value)
|
||||
17 setkv Set table's key and value (pull 2 values from stack)
|
||||
18 geta Get array's position value
|
||||
19 seta Set array's position value (pull 2 values from stack)
|
||||
1a appnd Add value to the end of array
|
||||
1b next Push the next pair into the stack (for loops)
|
||||
1c smt Set value metatable
|
||||
1d mt Get value metatable
|
||||
|
||||
Logical/arithmetic:
|
||||
20 sum Sum top 2 values in stack
|
||||
21 sub Subtract top 2 values in stack
|
||||
22 mul Multiply top 2 values in stack
|
||||
23 div Float division
|
||||
24 idiv Integer division
|
||||
25 eq Equality
|
||||
26 neq Inequality
|
||||
27 lt Less than
|
||||
28 lte Less than or equals
|
||||
29 gt Greater than
|
||||
2a gte Greater than or equals
|
||||
2b and Bitwise AND
|
||||
2c or Bitwise OR
|
||||
2d xor Bitwise XOR
|
||||
2e pow Power
|
||||
2f shl Shift left
|
||||
30 shr Shift right
|
||||
31 mod Modulo
|
||||
|
||||
Other value operations:
|
||||
40 len Get table, array or string size
|
||||
41 type Get type from value at the top of the stack
|
||||
b0 cast [type] Cast type to another type
|
||||
42 ver Return VM version
|
||||
|
||||
External code:
|
||||
48 cmpl Compile code to assembly
|
||||
49 asmbl Assemble code to bytecode format
|
||||
4a load Load bytecode as function (will place function on stack)
|
||||
|
||||
Control flow:
|
||||
a8 c8 e8 bz [pc] Branch if zero
|
||||
a9 c9 e9 bnz [pc] Branch if not zero
|
||||
aa ca ea jmp [pc] Unconditional jump
|
||||
* Jumps can only happen within the same function.
|
||||
|
||||
|
||||
Error handling: (0xa0~0xaf)
|
||||
???
|
||||
15
doc/VM
Normal file
15
doc/VM
Normal file
@@ -0,0 +1,15 @@
|
||||
Internal handling of values
|
||||
---------------------------
|
||||
|
||||
## Supported types
|
||||
Nil 0
|
||||
Integer 1
|
||||
Float 2
|
||||
String 3
|
||||
Array 4
|
||||
Table 5
|
||||
Function 6
|
||||
NativePointer 7
|
||||
|
||||
## Internal format
|
||||
???
|
||||
18
src/assembler/as_exceptions.hh
Normal file
18
src/assembler/as_exceptions.hh
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef TYCHE_VM_EXCEPTIONS_HH
|
||||
#define TYCHE_VM_EXCEPTIONS_HH
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace tyche::as {
|
||||
|
||||
class AssemblyError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
explicit AssemblyError(std::string const& str, size_t line, size_t column)
|
||||
: std::runtime_error((str + " at: line " + std::to_string(line) + ", column: " + std::to_string(column)).c_str()) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_VM_EXCEPTIONS_HH
|
||||
98
src/assembler/assembler.cc
Normal file
98
src/assembler/assembler.cc
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "assembler.hh"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "as_exceptions.hh"
|
||||
#include "../bytecode/bytecode.hh"
|
||||
#include "../vm/instruction.hh"
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
namespace tyche::as {
|
||||
|
||||
ByteArray Assembler::assemble()
|
||||
{
|
||||
bc::BytecodePrototype bp;
|
||||
|
||||
lexer_.reset();
|
||||
|
||||
enum class Section { Const, Function } section;
|
||||
uint32_t function_id = 0;
|
||||
|
||||
for (;;) {
|
||||
Token t = lexer_.ingest();
|
||||
if (t.type == TokenType::Enter)
|
||||
continue;
|
||||
|
||||
if (t.type == TokenType::Directive) {
|
||||
if (std::get<std::string>(t.token) == ".const") {
|
||||
section = Section::Const;
|
||||
expect_token(TokenType::Enter);
|
||||
} else if (std::get<std::string>(t.token) == ".func") {
|
||||
section = Section::Function;
|
||||
function_id = std::get<int>(expect_token(TokenType::Integer));
|
||||
if (function_id >= bp.functions.size())
|
||||
bp.functions.resize(function_id + 1, { 0, 0 });
|
||||
expect_token(TokenType::Enter);
|
||||
} else {
|
||||
throw AssemblyError("Invalid directive " + std::get<std::string>(t.token), t.line, t.column);
|
||||
}
|
||||
|
||||
} else if (section == Section::Const && t.type == TokenType::Integer) {
|
||||
int index = std::get<int>(t.token);
|
||||
if ((size_t) index >= bp.constants.size())
|
||||
bp.constants.resize(index + 1);
|
||||
expect_token(TokenType::Colon);
|
||||
Token tt = lexer_.ingest();
|
||||
if (tt.type == TokenType::Float)
|
||||
bp.constants[index] = std::get<float>(tt.token);
|
||||
else if (tt.type == TokenType::String)
|
||||
bp.constants[index] = std::get<std::string>(tt.token);
|
||||
else
|
||||
throw AssemblyError("Expected float or string as constant", tt.line, tt.column);
|
||||
expect_token(TokenType::Enter);
|
||||
|
||||
} else if (section == Section::Function && t.type == TokenType::Instruction) {
|
||||
std::string instruction = std::get<std::string>(t.token);
|
||||
std::optional<int> oper = {};
|
||||
Token tt = lexer_.ingest();
|
||||
if (tt.type == TokenType::Integer) {
|
||||
oper = std::get<int>(tt.token);
|
||||
tt = lexer_.ingest();
|
||||
}
|
||||
|
||||
auto oinst = vm::translate_instruction(instruction, oper);
|
||||
if (!oinst)
|
||||
throw AssemblyError("Invalid or misused instruction '" + instruction + "'", tt.line, tt.column);
|
||||
|
||||
bp.functions.at(function_id).code.append_byte((uint8_t) *oinst);
|
||||
switch (vm::instruction_operand_type(*oinst)) {
|
||||
case vm::OperandType::Int8: bp.functions.at(function_id).code.append_int8((int8_t) *oper); break;
|
||||
case vm::OperandType::Int16: bp.functions.at(function_id).code.append_int16((int16_t) *oper); break;
|
||||
case vm::OperandType::Int32: bp.functions.at(function_id).code.append_int32(*oper); break;
|
||||
case vm::OperandType::NoOperand: default: break;
|
||||
}
|
||||
|
||||
if (tt.type != TokenType::Enter)
|
||||
throw AssemblyError("Expected enter", tt.line, tt.column);
|
||||
|
||||
} else if (t.type == TokenType::EOF_) {
|
||||
break;
|
||||
|
||||
} else if (t.type != TokenType::Enter) {
|
||||
throw AssemblyError("Unexpected token of type " + token_type_name(t.type) + ")", t.line, t.column);
|
||||
}
|
||||
}
|
||||
|
||||
return bc::Bytecode::generate(bp);
|
||||
}
|
||||
|
||||
TokenValue Assembler::expect_token(TokenType type)
|
||||
{
|
||||
Token t = lexer_.ingest();
|
||||
if (t.type != type)
|
||||
throw AssemblyError("Expected " + token_type_name(t.type), t.line, t.column);
|
||||
return t.token;
|
||||
}
|
||||
|
||||
} // tyche
|
||||
27
src/assembler/assembler.hh
Normal file
27
src/assembler/assembler.hh
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef TYCHE_ASSEMBLER_HH
|
||||
#define TYCHE_ASSEMBLER_HH
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "lexer.hh"
|
||||
#include "../common/bytearray.hh"
|
||||
#include "../bytecode/bytecodeprototype.hh"
|
||||
|
||||
namespace tyche::as {
|
||||
|
||||
class Assembler {
|
||||
public:
|
||||
explicit Assembler(std::string source) : lexer_(std::move(source) + "\n") {}
|
||||
|
||||
[[nodiscard]] ByteArray assemble();
|
||||
|
||||
private:
|
||||
Lexer lexer_;
|
||||
|
||||
TokenValue expect_token(TokenType type);
|
||||
};
|
||||
|
||||
} // tyche
|
||||
|
||||
#endif //TYCHE_ASSEMBLER_HH
|
||||
122
src/assembler/lexer.cc
Normal file
122
src/assembler/lexer.cc
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "lexer.hh"
|
||||
|
||||
#include "as_exceptions.hh"
|
||||
|
||||
namespace tyche::as {
|
||||
|
||||
std::string token_type_name(TokenType type)
|
||||
{
|
||||
switch (type) {
|
||||
case TokenType::BOF: return "BOF";
|
||||
case TokenType::Directive: return "directive";
|
||||
case TokenType::Instruction: return "instruction";
|
||||
case TokenType::Integer: return "integer";
|
||||
case TokenType::Float: return "float";
|
||||
case TokenType::String: return "string";
|
||||
case TokenType::Enter: return "enter";
|
||||
case TokenType::Colon: return "colon";
|
||||
case TokenType::EOF_: return "EOF";
|
||||
default: return "???";
|
||||
}
|
||||
}
|
||||
|
||||
void Lexer::reset()
|
||||
{
|
||||
pos_ = 0;
|
||||
ingest_next_token();
|
||||
}
|
||||
|
||||
Token Lexer::peek() const
|
||||
{
|
||||
return current_token_;
|
||||
}
|
||||
|
||||
Token Lexer::ingest()
|
||||
{
|
||||
Token t = current_token_;
|
||||
ingest_next_token();
|
||||
return t;
|
||||
}
|
||||
|
||||
void Lexer::ingest_next_token()
|
||||
{
|
||||
size_t current_line_pos = 1;
|
||||
size_t current_line = 1;
|
||||
|
||||
if (pos_ >= source_.size()) {
|
||||
current_token_ = { TokenType::EOF_ };
|
||||
return;
|
||||
}
|
||||
|
||||
char c = source_.at(pos_);
|
||||
|
||||
TokenType type {};
|
||||
std::string stoken;
|
||||
TokenValue value = std::monostate();
|
||||
|
||||
if (c == '.') {
|
||||
type = TokenType::Directive;
|
||||
stoken += '.';
|
||||
while (c = source_.at(++pos_), isalpha(c) || c == '_')
|
||||
stoken += c;
|
||||
value = stoken;
|
||||
} else if (c == '"') {
|
||||
type = TokenType::String;
|
||||
++pos_;
|
||||
while (true) {
|
||||
if (source_.at(pos_) == '\\') { // TODO - improve this for special characters
|
||||
++pos_;
|
||||
} else if (source_.at(pos_) == '"') {
|
||||
++pos_;
|
||||
break;
|
||||
} else if (pos_ >= source_.size()) {
|
||||
throw AssemblyError("Unterminated string", current_line, pos_ - current_line_pos);
|
||||
}
|
||||
stoken += source_.at(pos_++);
|
||||
}
|
||||
value = stoken;
|
||||
} else if (isdigit(c) || c == '-') {
|
||||
type = TokenType::Integer;
|
||||
stoken += c;
|
||||
while (c = source_.at(++pos_), isdigit(c) || c == '.') {
|
||||
stoken += c;
|
||||
if (c == '.') {
|
||||
if (type == TokenType::Integer)
|
||||
type = TokenType::Float;
|
||||
else
|
||||
throw AssemblyError("Double point in floating point number", current_line, pos_ - current_line_pos);
|
||||
}
|
||||
}
|
||||
if (type == TokenType::Integer)
|
||||
value = std::stoi(stoken);
|
||||
else
|
||||
value = std::stof(stoken);
|
||||
} else if (isalpha(c)) {
|
||||
type = TokenType::Instruction;
|
||||
stoken += c;
|
||||
while (c = source_.at(++pos_), isalpha(c))
|
||||
stoken += c;
|
||||
value = stoken;
|
||||
} else if (c == ':') {
|
||||
type = TokenType::Colon;
|
||||
++pos_;
|
||||
} else if (c == '\n' || c == ';') {
|
||||
while (pos_ < source_.size() && source_.at(pos_) != '\n')
|
||||
++pos_;
|
||||
type = TokenType::Enter;
|
||||
value = "\n";
|
||||
++pos_;
|
||||
++current_line;
|
||||
current_line_pos = pos_;
|
||||
} else {
|
||||
throw AssemblyError(std::string("Unexpected character '") + c + "' (ascii: " + std::to_string((int) c) + ")", current_line, pos_ - current_line_pos);
|
||||
}
|
||||
|
||||
// skip ignored tokens
|
||||
while (pos_ < source_.size() && (source_.at(pos_) == ' ' || source_.at(pos_) == '\t' || source_.at(pos_) == '\r'))
|
||||
++pos_;
|
||||
|
||||
current_token_ = { .type = type, .token = value, .line = current_line, .column = pos_ - current_line_pos };
|
||||
}
|
||||
|
||||
} // tyche
|
||||
45
src/assembler/lexer.hh
Normal file
45
src/assembler/lexer.hh
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef TYCHE_LEXER_HH
|
||||
#define TYCHE_LEXER_HH
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
namespace tyche::as {
|
||||
|
||||
enum class TokenType {
|
||||
BOF, Directive, Instruction, Integer, Float, String, Enter, Colon, EOF_
|
||||
};
|
||||
|
||||
using TokenValue = std::variant<std::monostate, int, float, std::string>;
|
||||
|
||||
struct Token {
|
||||
TokenType type;
|
||||
TokenValue token = std::monostate();
|
||||
size_t line = 0;
|
||||
size_t column = 0;
|
||||
|
||||
friend bool operator==(Token const& lhs, Token const& rhs) { return std::tie(lhs.type, lhs.token) == std::tie(rhs.type, rhs.token); }
|
||||
};
|
||||
|
||||
std::string token_type_name(TokenType type);
|
||||
|
||||
class Lexer {
|
||||
public:
|
||||
explicit Lexer(std::string source) : source_(std::move(source)) { reset(); }
|
||||
|
||||
void reset();
|
||||
[[nodiscard]] Token peek() const;
|
||||
[[nodiscard]] Token ingest();
|
||||
|
||||
private:
|
||||
const std::string source_;
|
||||
size_t pos_ = 0;
|
||||
Token current_token_ { TokenType::BOF };
|
||||
|
||||
void ingest_next_token();
|
||||
};
|
||||
|
||||
} // tyche
|
||||
|
||||
#endif //TYCHE_LEXER_HH
|
||||
76
src/assembler/tests.cc
Normal file
76
src/assembler/tests.cc
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "assembler.hh"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "../bytecode/bytecodeprototype.hh"
|
||||
#include "../bytecode/bytecode.hh"
|
||||
#include "../vm/instruction.hh"
|
||||
|
||||
using namespace tyche;
|
||||
using namespace tyche::as;
|
||||
using namespace tyche::bc;
|
||||
using namespace tyche::vm;
|
||||
|
||||
TEST(Lexer, Lexer)
|
||||
{
|
||||
Token t;
|
||||
Lexer lexer(".dir push 382 -12 3.14 -12.8 \"Hello\" \"Hel\\\"lo\"\n");
|
||||
|
||||
ASSERT_EQ(lexer.ingest(), (Token { TokenType::Directive, ".dir" }));
|
||||
ASSERT_EQ(lexer.ingest(), (Token { TokenType::Instruction, "push" }));
|
||||
t = lexer.ingest(); ASSERT_EQ(t.type, TokenType::Integer); ASSERT_EQ(std::get<int>(t.token), 382);
|
||||
t = lexer.ingest(); ASSERT_EQ(t.type, TokenType::Integer); ASSERT_EQ(std::get<int>(t.token), -12);
|
||||
t = lexer.ingest(); ASSERT_EQ(t.type, TokenType::Float); ASSERT_FLOAT_EQ(std::get<float>(t.token), 3.14f);
|
||||
t = lexer.ingest(); ASSERT_EQ(t.type, TokenType::Float); ASSERT_FLOAT_EQ(std::get<float>(t.token), -12.8f);
|
||||
ASSERT_EQ(lexer.ingest(), (Token { TokenType::String, "Hello" }));
|
||||
ASSERT_EQ(lexer.ingest(), (Token { TokenType::String, "Hel\"lo" }));
|
||||
ASSERT_EQ(lexer.ingest(), (Token { TokenType::Enter, "\n" }));
|
||||
ASSERT_EQ(lexer.ingest(), (Token { TokenType::EOF_ }));
|
||||
ASSERT_EQ(lexer.ingest(), (Token { TokenType::EOF_ }));
|
||||
ASSERT_EQ(lexer.ingest(), (Token { TokenType::EOF_ }));
|
||||
|
||||
lexer.reset();
|
||||
ASSERT_EQ(lexer.ingest(), (Token { TokenType::Directive, ".dir" }));
|
||||
}
|
||||
|
||||
TEST(Assember, Assembler)
|
||||
{
|
||||
BytecodePrototype bp;
|
||||
bp.constants.emplace_back(3.14f);
|
||||
bp.constants.emplace_back("Hello world");
|
||||
bp.functions.emplace_back(0, 0);
|
||||
bp.functions.at(0).code.append_byte((uint8_t) Instruction::PushInt8);
|
||||
bp.functions.at(0).code.append_int8(2);
|
||||
bp.functions.at(0).code.append_byte((uint8_t) Instruction::PushInt8);
|
||||
bp.functions.at(0).code.append_int8(3);
|
||||
bp.functions.at(0).code.append_byte((uint8_t) Instruction::Sum);
|
||||
bp.functions.at(0).code.append_byte((uint8_t) Instruction::Return);
|
||||
bp.functions.emplace_back(0, 0);
|
||||
bp.functions.at(1).code.append_byte((uint8_t) Instruction::PushInt16);
|
||||
bp.functions.at(1).code.append_int16(5000);
|
||||
bp.functions.at(1).code.append_byte((uint8_t) Instruction::Return);
|
||||
ByteArray expected = Bytecode::generate(bp);
|
||||
|
||||
std::string src = R"(
|
||||
.const
|
||||
0: 3.14
|
||||
1: "Hello world"
|
||||
|
||||
.func 0
|
||||
pushi 2 ; this is a comment
|
||||
pushi 3
|
||||
sum
|
||||
ret
|
||||
.func 1
|
||||
pushi 5000
|
||||
ret
|
||||
)";
|
||||
|
||||
ByteArray actual = Assembler(src).assemble();
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
15
src/bytecode/bc_exceptions.hh
Normal file
15
src/bytecode/bc_exceptions.hh
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef TYCHE_BC_EXCEPTIONS_HH
|
||||
#define TYCHE_BC_EXCEPTIONS_HH
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace tyche::bc {
|
||||
|
||||
class BytecodeParsingError : public std::runtime_error {
|
||||
public:
|
||||
explicit BytecodeParsingError(std::string const& str) : std::runtime_error(str.c_str()) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_BC_EXCEPTIONS_HH
|
||||
166
src/bytecode/bytecode.cc
Normal file
166
src/bytecode/bytecode.cc
Normal file
@@ -0,0 +1,166 @@
|
||||
#include "bytecode.hh"
|
||||
|
||||
#include "bc_exceptions.hh"
|
||||
#include "../common/overloaded.hh"
|
||||
|
||||
namespace tyche::bc {
|
||||
|
||||
Bytecode::Bytecode(ByteArray ba)
|
||||
: byte_array_(std::move(ba))
|
||||
{
|
||||
// check file size
|
||||
if (byte_array_.size() < (TOC_START + TOC_SZ))
|
||||
throw BytecodeParsingError("Invalid bytecode format (file too short)");
|
||||
|
||||
// check magic number and version
|
||||
if (byte_array_.get_uint32(0) != MAGIC_NUMBER)
|
||||
throw BytecodeParsingError("Invalid bytecode format (magic number not matching)");
|
||||
if (byte_array_.get_uint32(4) != BYTECODE_VERSION)
|
||||
throw BytecodeParsingError("Unexpected bytecode format version");
|
||||
|
||||
// load cache
|
||||
cache_.constants_idx_addr = byte_array_.get_uint32(TOC_START);
|
||||
cache_.n_constants = byte_array_.get_uint16(TOC_START + 4);
|
||||
cache_.functions_idx_addr = byte_array_.get_uint32(TOC_START + (1 * TOC_RECORD_SZ));
|
||||
cache_.n_functions = byte_array_.get_uint16(TOC_START + (1 * TOC_RECORD_SZ) + 4);
|
||||
cache_.constants_start_addr = byte_array_.get_uint32(TOC_START + (2 * TOC_RECORD_SZ));
|
||||
uint32_t code_start = byte_array_.get_uint32(TOC_START + (3 * TOC_RECORD_SZ));
|
||||
for (uint32_t i = 0; i < cache_.n_functions; ++i) {
|
||||
cache_.function_addr.emplace_back(code_start + byte_array_.get_uint32(cache_.functions_idx_addr + (i * FUNCTION_RECORD_SZ)));
|
||||
cache_.function_sz.emplace_back(byte_array_.get_uint32(cache_.functions_idx_addr + (i * FUNCTION_RECORD_SZ) + 8));
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Bytecode::n_constants() const
|
||||
{
|
||||
return cache_.n_constants;
|
||||
}
|
||||
|
||||
uint32_t Bytecode::n_functions() const
|
||||
{
|
||||
return cache_.n_functions;
|
||||
}
|
||||
|
||||
ConstantValue Bytecode::get_constant(uint32_t idx) const
|
||||
{
|
||||
uint32_t constant_idx = byte_array_.get_uint32(cache_.constants_idx_addr + (idx * CONST_RECORD_SZ));
|
||||
switch ((ConstantType) byte_array_.get_byte(cache_.constants_start_addr + constant_idx)) {
|
||||
case CONST_TYPE_FLOAT:
|
||||
return byte_array_.get_float(cache_.constants_start_addr + constant_idx + 1);
|
||||
case CONST_TYPE_STRING:
|
||||
return byte_array_.get_string(cache_.constants_start_addr + constant_idx + 1).first;
|
||||
default:
|
||||
throw BytecodeParsingError("Invalid bytecode format (invalid constant type)");
|
||||
}
|
||||
}
|
||||
|
||||
Bytecode::FunctionDef Bytecode::get_function_def(uint32_t function_id) const
|
||||
{
|
||||
uint32_t idx = cache_.functions_idx_addr + (function_id * FUNCTION_RECORD_SZ);
|
||||
return {
|
||||
.n_params = byte_array_.get_uint16(idx + 4),
|
||||
.locals = byte_array_.get_uint16(idx + 6),
|
||||
};
|
||||
}
|
||||
|
||||
uint32_t Bytecode::get_function_sz(uint32_t function_id) const
|
||||
{
|
||||
return cache_.function_sz.at(function_id);
|
||||
}
|
||||
|
||||
uint8_t Bytecode::get_code_byte(uint32_t function_id, uint32_t idx) const
|
||||
{
|
||||
return byte_array_.get_byte(cache_.function_addr.at(function_id) + idx);
|
||||
}
|
||||
|
||||
int8_t Bytecode::get_code_int8(uint32_t function_id, uint32_t idx) const
|
||||
{
|
||||
return byte_array_.get_int8(cache_.function_addr.at(function_id) + idx);
|
||||
}
|
||||
|
||||
int16_t Bytecode::get_code_int16(uint32_t function_id, uint32_t idx) const
|
||||
{
|
||||
return byte_array_.get_int16(cache_.function_addr.at(function_id) + idx);
|
||||
}
|
||||
|
||||
int32_t Bytecode::get_code_int32(uint32_t function_id, uint32_t idx) const
|
||||
{
|
||||
return byte_array_.get_int32(cache_.function_addr.at(function_id) + idx);
|
||||
}
|
||||
|
||||
ByteArray Bytecode::generate(BytecodePrototype const& bp)
|
||||
{
|
||||
// header section
|
||||
ByteArray header;
|
||||
header.set_uint32(0, MAGIC_NUMBER);
|
||||
header.set_byte(4, BYTECODE_VERSION);
|
||||
|
||||
// constants
|
||||
ByteArray constant_indexes;
|
||||
ByteArray raw_constants;
|
||||
|
||||
uint32_t idx = 0;
|
||||
for (auto const& constant: bp.constants) {
|
||||
constant_indexes.append_uint32(idx);
|
||||
std::visit(overloaded {
|
||||
[&](float f) {
|
||||
raw_constants.append_byte(CONST_TYPE_FLOAT);
|
||||
raw_constants.append_float(f);
|
||||
},
|
||||
[&](std::string const& s) {
|
||||
raw_constants.append_byte(CONST_TYPE_STRING);
|
||||
raw_constants.append_string(s);
|
||||
},
|
||||
}, constant);
|
||||
idx = raw_constants.size();
|
||||
}
|
||||
|
||||
// functions
|
||||
ByteArray functions_indexes;
|
||||
ByteArray raw_code;
|
||||
|
||||
uint32_t idx_idx = 0, code_idx = 0;
|
||||
for (auto const& f: bp.functions) {
|
||||
functions_indexes.set_uint32(idx_idx, code_idx);
|
||||
functions_indexes.set_uint16(idx_idx + 4, f.n_pars);
|
||||
functions_indexes.set_uint16(idx_idx + 6, f.n_locals);
|
||||
functions_indexes.set_uint32(idx_idx + 8, f.code.size());
|
||||
raw_code.append_bytearray(f.code);
|
||||
code_idx = raw_code.size();
|
||||
idx_idx += FUNCTION_RECORD_SZ;
|
||||
}
|
||||
|
||||
// table of contents
|
||||
uint32_t function_idx_start = CONST_IDX_START + constant_indexes.size();
|
||||
uint32_t raw_constant_start = function_idx_start + functions_indexes.size();
|
||||
uint32_t raw_code_start = raw_constant_start + raw_constants.size();
|
||||
|
||||
ByteArray toc;
|
||||
if (!bp.constants.empty()) {
|
||||
toc.set_uint32(SEC_CONST_IDX * TOC_RECORD_SZ, CONST_IDX_START);
|
||||
toc.set_uint32(SEC_CONST_IDX * TOC_RECORD_SZ + 4, constant_indexes.size() / CONST_RECORD_SZ);
|
||||
toc.set_uint32(SEC_CONST_DATA * TOC_RECORD_SZ, raw_constant_start);
|
||||
toc.set_uint32(SEC_CONST_DATA * TOC_RECORD_SZ + 4, raw_constants.size());
|
||||
}
|
||||
if (!bp.functions.empty()) {
|
||||
toc.set_uint32(SEC_FUNC_IDX * TOC_RECORD_SZ, function_idx_start);
|
||||
toc.set_uint32(SEC_FUNC_IDX * TOC_RECORD_SZ + 4, functions_indexes.size() / FUNCTION_RECORD_SZ);
|
||||
toc.set_uint32(SEC_CODE * TOC_RECORD_SZ, raw_code_start);
|
||||
toc.set_uint32(SEC_CODE * TOC_RECORD_SZ + 4, raw_code.size());
|
||||
}
|
||||
|
||||
//
|
||||
// assemble bytecode
|
||||
//
|
||||
|
||||
ByteArray ba;
|
||||
ba.set_bytearray(0, header);
|
||||
ba.set_bytearray(TOC_START, toc);
|
||||
ba.set_bytearray(CONST_IDX_START, constant_indexes);
|
||||
ba.set_bytearray(function_idx_start, functions_indexes);
|
||||
ba.set_bytearray(raw_constant_start, raw_constants);
|
||||
ba.set_bytearray(raw_code_start, raw_code);
|
||||
return ba;
|
||||
}
|
||||
|
||||
}
|
||||
62
src/bytecode/bytecode.hh
Normal file
62
src/bytecode/bytecode.hh
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef TYCHE_BYTECODE_HH
|
||||
#define TYCHE_BYTECODE_HH
|
||||
|
||||
#include "../common/bytearray.hh"
|
||||
#include "bytecodeprototype.hh"
|
||||
|
||||
namespace tyche::bc {
|
||||
|
||||
class Bytecode {
|
||||
public:
|
||||
Bytecode() = default;
|
||||
explicit Bytecode(ByteArray ba);
|
||||
|
||||
[[nodiscard]] uint32_t n_constants() const;
|
||||
[[nodiscard]] uint32_t n_functions() const;
|
||||
|
||||
[[nodiscard]] ConstantValue get_constant(uint32_t idx) const;
|
||||
|
||||
struct FunctionDef { uint16_t n_params, locals; };
|
||||
[[nodiscard]] FunctionDef get_function_def(uint32_t function_id) const;
|
||||
[[nodiscard]] uint32_t get_function_sz(uint32_t function_id) const;
|
||||
|
||||
[[nodiscard]] uint8_t get_code_byte(uint32_t function_id, uint32_t idx) const;
|
||||
[[nodiscard]] int8_t get_code_int8(uint32_t function_id, uint32_t idx) const;
|
||||
[[nodiscard]] int16_t get_code_int16(uint32_t function_id, uint32_t idx) const;
|
||||
[[nodiscard]] int32_t get_code_int32(uint32_t function_id, uint32_t idx) const;
|
||||
|
||||
// TODO - debugging info
|
||||
|
||||
[[nodiscard]] static ByteArray generate(BytecodePrototype const& bp);
|
||||
|
||||
private:
|
||||
ByteArray byte_array_; // the actual data
|
||||
|
||||
static constexpr uint8_t BYTECODE_VERSION = 1;
|
||||
static constexpr uint32_t MAGIC_NUMBER = 0x74b3c138;
|
||||
static constexpr uint32_t TOC_START = 16,
|
||||
TOC_N_RECORDS = 8,
|
||||
TOC_RECORD_SZ = 8,
|
||||
TOC_SZ = TOC_N_RECORDS * TOC_RECORD_SZ;
|
||||
static constexpr uint32_t CONST_IDX_START = TOC_START + TOC_SZ,
|
||||
CONST_RECORD_SZ = 4;
|
||||
static constexpr uint32_t FUNCTION_RECORD_SZ = 12;
|
||||
|
||||
enum Sections { SEC_CONST_IDX = 0, SEC_FUNC_IDX = 1, SEC_CONST_DATA = 2, SEC_CODE = 3 };
|
||||
|
||||
// caching for faster reading of data
|
||||
struct Cache {
|
||||
uint32_t constants_idx_addr;
|
||||
uint16_t n_constants;
|
||||
uint32_t constants_start_addr;
|
||||
uint32_t functions_idx_addr;
|
||||
uint32_t n_functions;
|
||||
std::vector<uint32_t> function_addr;
|
||||
std::vector<uint32_t> function_sz;
|
||||
};
|
||||
Cache cache_ {};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_BYTECODE_HH
|
||||
30
src/bytecode/bytecodeprototype.hh
Normal file
30
src/bytecode/bytecodeprototype.hh
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef TYCHE_BYTECODEPROTOTYPE_HH
|
||||
#define TYCHE_BYTECODEPROTOTYPE_HH
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include "constant.hh"
|
||||
#include "../common/bytearray.hh"
|
||||
|
||||
namespace tyche::bc {
|
||||
|
||||
struct BytecodePrototype {
|
||||
struct Function {
|
||||
uint16_t n_pars;
|
||||
uint16_t n_locals;
|
||||
ByteArray code {};
|
||||
|
||||
Function(uint16_t n_pars_, uint16_t n_locals_) : n_pars(n_pars_), n_locals(n_locals_), code(ByteArray {}) {}
|
||||
};
|
||||
|
||||
std::vector<ConstantValue> constants {};
|
||||
std::vector<Function> functions {};
|
||||
|
||||
// TODO - debugging info
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_BYTECODEPROTOTYPE_HH
|
||||
15
src/bytecode/constant.hh
Normal file
15
src/bytecode/constant.hh
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef TYCHE_CONSTANT_HH
|
||||
#define TYCHE_CONSTANT_HH
|
||||
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
namespace tyche::bc {
|
||||
|
||||
using ConstantValue = std::variant<float, std::string>;
|
||||
|
||||
enum ConstantType : uint8_t { CONST_TYPE_FLOAT = 1, CONST_TYPE_STRING = 2 };
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_CONSTANT_HH
|
||||
167
src/bytecode/tests.cc
Normal file
167
src/bytecode/tests.cc
Normal file
@@ -0,0 +1,167 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
|
||||
#include "../common/bytearray.hh"
|
||||
#include "bytecodeprototype.hh"
|
||||
#include "bytecode.hh"
|
||||
|
||||
using namespace tyche;
|
||||
using namespace tyche::bc;
|
||||
|
||||
TEST(ByteArray, ByteArray)
|
||||
{
|
||||
auto test = [](std::function<void(ByteArray&)> const& f, std::vector<uint8_t> const& expected) {
|
||||
ByteArray ba;
|
||||
f(ba);
|
||||
ASSERT_EQ(ba.data().size(), expected.size());
|
||||
ASSERT_EQ(std::memcmp(ba.data().data(), expected.data(), ba.data().size()), 0);
|
||||
};
|
||||
|
||||
#define TESTX(a, ...) test([](ByteArray& ba) { a; }, std::vector<uint8_t>({ __VA_ARGS__ }));
|
||||
|
||||
TESTX(ba.set_byte(1, 0xab), 0x00, 0xab)
|
||||
|
||||
ByteArray ba;
|
||||
ba.set_byte(1, 0xab); ASSERT_EQ(ba.get_byte(1), 0xab);
|
||||
|
||||
ba.set_int8(1, 12); ASSERT_EQ(ba.get_int8(1), 12);
|
||||
ba.set_int8(1, -12); ASSERT_EQ(ba.get_int8(1), -12);
|
||||
ba.set_int16(1, 5000); ASSERT_EQ(ba.get_int16(1), 5000);
|
||||
ba.set_int32(1, 5000300); ASSERT_EQ(ba.get_int32(1), 5000300);
|
||||
ba.set_int32(1, -5000300); ASSERT_EQ(ba.get_int32(1), -5000300);
|
||||
|
||||
ba.set_float(1, 3.14); ASSERT_FLOAT_EQ(ba.get_float(1), 3.14);
|
||||
ba.set_float(1, -3.14); ASSERT_FLOAT_EQ(ba.get_float(1), -3.14);
|
||||
ba.set_float(1, -5000300.1324); ASSERT_FLOAT_EQ(ba.get_float(1), -5000300.1324);
|
||||
|
||||
ba.set_string(1, "Hello world!"); ASSERT_EQ(ba.get_string(1), std::make_pair("Hello world!", 13));
|
||||
|
||||
#undef TESTX
|
||||
}
|
||||
|
||||
TEST(Bytecode, Constants)
|
||||
{
|
||||
BytecodePrototype bp;
|
||||
bp.constants.emplace_back(42.3f);
|
||||
bp.constants.emplace_back("HELLO");
|
||||
|
||||
std::vector<uint8_t> expected = {
|
||||
// header
|
||||
0x38, 0xc1, 0xb3, 0x74, // magic
|
||||
0x01, 0x00, 0x00, 0x00, // version
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
// index
|
||||
0x50, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // constant index
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // function undex
|
||||
0x58, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, // raw constants
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // raw code
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
// constant indexes
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x05, 0x00, 0x00, 0x00,
|
||||
|
||||
// constant values
|
||||
CONST_TYPE_FLOAT, 0x33, 0x33, 0x29, 0x42, // float: 42.3f
|
||||
CONST_TYPE_STRING, 'H', 'E', 'L', 'L', 'O', 0x00
|
||||
};
|
||||
|
||||
ByteArray ba = Bytecode::generate(bp);
|
||||
ASSERT_EQ(ba.data(), expected);
|
||||
}
|
||||
|
||||
TEST(Bytecode, Code)
|
||||
{
|
||||
BytecodePrototype bp;
|
||||
auto& f = bp.functions.emplace_back(0, 0);
|
||||
f.code.append_byte(0x68);
|
||||
f.code.append_int8(42);
|
||||
|
||||
auto& f2 = bp.functions.emplace_back(2, 1);
|
||||
f2.code.append_byte(0x42);
|
||||
|
||||
std::vector<uint8_t> expected = {
|
||||
// header
|
||||
0x38, 0xc1, 0xb3, 0x74, // magic
|
||||
0x01, 0x00, 0x00, 0x00, // version
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
// index
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // constant index
|
||||
0x50, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // variable index
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // raw constants
|
||||
0x68, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, // raw code
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
// function definitions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
|
||||
// code
|
||||
0x68, 42, 0x42,
|
||||
};
|
||||
|
||||
ByteArray ba = Bytecode::generate(bp);
|
||||
ASSERT_EQ(ba.data(), expected);
|
||||
}
|
||||
|
||||
TEST(Bytecode, Parsing)
|
||||
{
|
||||
// write bytecode
|
||||
|
||||
BytecodePrototype bp;
|
||||
|
||||
bp.constants.emplace_back(3.14f);
|
||||
bp.constants.emplace_back("HELLO");
|
||||
|
||||
auto& f = bp.functions.emplace_back(0, 0);
|
||||
f.code.append_byte(0x68);
|
||||
f.code.append_int8(42);
|
||||
|
||||
auto& ff = bp.functions.emplace_back(2, 1);
|
||||
ff.code.append_byte(0x42);
|
||||
|
||||
ByteArray ba = Bytecode::generate(bp);
|
||||
// print(ba.data());
|
||||
|
||||
// read bytecode
|
||||
|
||||
Bytecode bc(std::move(ba));
|
||||
|
||||
ASSERT_EQ(bc.n_constants(), 2);
|
||||
ASSERT_EQ(bc.n_functions(), 2);
|
||||
ASSERT_EQ(bc.get_function_sz(0), 2);
|
||||
ASSERT_EQ(bc.get_function_sz(1), 1);
|
||||
|
||||
ASSERT_FLOAT_EQ(std::get<float>(bc.get_constant(0)), 3.14f);
|
||||
ASSERT_EQ(std::get<std::string>(bc.get_constant(1)), "HELLO");
|
||||
|
||||
Bytecode::FunctionDef f1 = bc.get_function_def(0);
|
||||
ASSERT_EQ(f1.n_params, 0);
|
||||
ASSERT_EQ(f1.locals, 0);
|
||||
|
||||
Bytecode::FunctionDef f2 = bc.get_function_def(1);
|
||||
ASSERT_EQ(f2.n_params, 2);
|
||||
ASSERT_EQ(f2.locals, 1);
|
||||
|
||||
ASSERT_EQ(bc.get_code_byte(0, 0), 0x68);
|
||||
ASSERT_EQ(bc.get_code_int8(0, 1), 42);
|
||||
ASSERT_EQ(bc.get_code_byte(1, 0), 0x42);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
152
src/common/bytearray.cc
Normal file
152
src/common/bytearray.cc
Normal file
@@ -0,0 +1,152 @@
|
||||
#include "bytearray.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
namespace tyche {
|
||||
|
||||
void ByteArray::set_byte(uint32_t addr, uint8_t byte)
|
||||
{
|
||||
if (data_.size() < (addr + 1))
|
||||
data_.resize(addr + 1, 0);
|
||||
data_.at(addr) = byte;
|
||||
}
|
||||
|
||||
void ByteArray::set_int8(uint32_t addr, int8_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) value);
|
||||
}
|
||||
|
||||
void ByteArray::set_int16(uint32_t addr, int16_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) (value));
|
||||
set_byte(addr+1, (uint8_t) (value >> 8));
|
||||
}
|
||||
|
||||
void ByteArray::set_int32(uint32_t addr, int32_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) (value));
|
||||
set_byte(addr+1, (uint8_t) (value >> 8));
|
||||
set_byte(addr+2, (uint8_t) (value >> 16));
|
||||
set_byte(addr+3, (uint8_t) (value >> 24));
|
||||
}
|
||||
|
||||
void ByteArray::set_uint16(uint32_t addr, uint16_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) (value));
|
||||
set_byte(addr+1, (uint8_t) (value >> 8));
|
||||
}
|
||||
|
||||
void ByteArray::set_uint32(uint32_t addr, uint32_t value)
|
||||
{
|
||||
set_byte(addr, (uint8_t) (value));
|
||||
set_byte(addr+1, (uint8_t) (value >> 8));
|
||||
set_byte(addr+2, (uint8_t) (value >> 16));
|
||||
set_byte(addr+3, (uint8_t) (value >> 24));
|
||||
}
|
||||
|
||||
void ByteArray::set_float(uint32_t addr, float value)
|
||||
{
|
||||
uint32_t bits;
|
||||
std::memcpy(&bits, &value, 4);
|
||||
set_byte(addr, (uint8_t) (bits));
|
||||
set_byte(addr+1, (uint8_t) (bits >> 8));
|
||||
set_byte(addr+2, (uint8_t) (bits >> 16));
|
||||
set_byte(addr+3, (uint8_t) (bits >> 24));
|
||||
}
|
||||
|
||||
void ByteArray::set_string(uint32_t addr, std::string const& str)
|
||||
{
|
||||
for (uint8_t c: str)
|
||||
set_byte(addr++, c);
|
||||
set_byte(addr, 0);
|
||||
}
|
||||
|
||||
void ByteArray::set_bytearray(uint32_t addr, ByteArray const& bytearray)
|
||||
{
|
||||
for (uint8_t byte: bytearray.data())
|
||||
set_byte(addr++, byte);
|
||||
}
|
||||
|
||||
uint8_t ByteArray::get_byte(uint32_t addr) const
|
||||
{
|
||||
return data_.at(addr);
|
||||
}
|
||||
|
||||
uint16_t ByteArray::get_uint16(uint32_t addr) const
|
||||
{
|
||||
return (uint32_t) get_byte(addr)
|
||||
| (uint32_t) get_byte(addr+1) << 8;
|
||||
}
|
||||
|
||||
uint32_t ByteArray::get_uint32(uint32_t addr) const
|
||||
{
|
||||
return (uint32_t) get_byte(addr)
|
||||
| (uint32_t) get_byte(addr+1) << 8
|
||||
| (uint32_t) get_byte(addr+2) << 16
|
||||
| (uint32_t) get_byte(addr+3) << 24;
|
||||
}
|
||||
|
||||
int8_t ByteArray::get_int8(uint32_t addr) const
|
||||
{
|
||||
return std::bit_cast<int8_t>(get_byte(addr));
|
||||
}
|
||||
|
||||
int16_t ByteArray::get_int16(uint32_t addr) const
|
||||
{
|
||||
return (uint16_t) get_byte(addr)
|
||||
| (uint16_t) get_byte(addr+1) << 8;
|
||||
}
|
||||
|
||||
int32_t ByteArray::get_int32(uint32_t addr) const
|
||||
{
|
||||
return std::bit_cast<int32_t>((uint32_t) get_byte(addr)
|
||||
| (uint32_t) get_byte(addr+1) << 8
|
||||
| (uint32_t) get_byte(addr+2) << 16
|
||||
| (uint32_t) get_byte(addr+3) << 24);
|
||||
}
|
||||
|
||||
float ByteArray::get_float(uint32_t addr) const
|
||||
{
|
||||
uint32_t bits = (uint32_t) get_byte(addr)
|
||||
| (uint32_t) get_byte(addr+1) << 8
|
||||
| (uint32_t) get_byte(addr+2) << 16
|
||||
| (uint32_t) get_byte(addr+3) << 24;
|
||||
float value;
|
||||
std::memcpy(&value, &bits, 4);
|
||||
return value;
|
||||
}
|
||||
|
||||
std::pair<std::string, size_t> ByteArray::get_string(uint32_t addr) const
|
||||
{
|
||||
std::string data;
|
||||
while (char c = (char) get_byte(addr++))
|
||||
data += c;
|
||||
return { data, data.size() + 1 };
|
||||
}
|
||||
|
||||
void ByteArray::append_bytearray(ByteArray const& bytearray)
|
||||
{
|
||||
data_.insert(data_.end(), bytearray.data().begin(), bytearray.data().end());
|
||||
}
|
||||
|
||||
std::string ByteArray::hexdump() const
|
||||
{
|
||||
auto to_hex = [](uint32_t value, size_t n_chars) -> std::string {
|
||||
char buf[15];
|
||||
snprintf(buf, sizeof buf, "%0*X", (int) n_chars, value);
|
||||
return { buf };
|
||||
};
|
||||
|
||||
std::string out;
|
||||
for (size_t i = 0; i < data_.size(); ++i) {
|
||||
if (i % 16 == 0)
|
||||
out += to_hex(i, 4) + " | ";
|
||||
out += to_hex(data_.at(i), 2) + " ";
|
||||
if (i % 16 == 15)
|
||||
out += "\n";
|
||||
}
|
||||
return out + "\n";
|
||||
}
|
||||
|
||||
}
|
||||
58
src/common/bytearray.hh
Normal file
58
src/common/bytearray.hh
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef TYCHE_BYTEARRAY_HH
|
||||
#define TYCHE_BYTEARRAY_HH
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace tyche {
|
||||
|
||||
class ByteArray {
|
||||
public:
|
||||
ByteArray() = default;
|
||||
explicit ByteArray(std::vector<uint8_t> data) : data_(std::move(data)) {}
|
||||
|
||||
void set_byte(uint32_t addr, uint8_t byte);
|
||||
void set_uint16(uint32_t addr, uint16_t value);
|
||||
void set_uint32(uint32_t addr, uint32_t value);
|
||||
void set_int8(uint32_t addr, int8_t value);
|
||||
void set_int16(uint32_t addr, int16_t value);
|
||||
void set_int32(uint32_t addr, int32_t value);
|
||||
void set_float(uint32_t addr, float value);
|
||||
void set_string(uint32_t addr, std::string const& str);
|
||||
void set_bytearray(uint32_t addr, ByteArray const& bytearray);
|
||||
|
||||
void append_byte(uint8_t byte) { set_byte(data_.size(), byte); }
|
||||
void append_uint16(uint16_t value) { set_uint16(data_.size(), value); }
|
||||
void append_uint32(uint32_t value) { set_uint32(data_.size(), value); }
|
||||
void append_int8(int8_t value) { set_int8(data_.size(), value); }
|
||||
void append_int16(int16_t value) { set_int16(data_.size(), value); }
|
||||
void append_int32(int32_t value) { set_int32(data_.size(), value); }
|
||||
void append_float(float value) { set_float(data_.size(), value); }
|
||||
void append_string(std::string const& str) { set_string(data_.size(), str); }
|
||||
void append_bytearray(ByteArray const& bytearray);
|
||||
|
||||
[[nodiscard]] uint8_t get_byte(uint32_t addr) const;
|
||||
[[nodiscard]] uint16_t get_uint16(uint32_t addr) const;
|
||||
[[nodiscard]] uint32_t get_uint32(uint32_t addr) const;
|
||||
[[nodiscard]] int8_t get_int8(uint32_t addr) const;
|
||||
[[nodiscard]] int16_t get_int16(uint32_t addr) const;
|
||||
[[nodiscard]] int32_t get_int32(uint32_t addr) const;
|
||||
[[nodiscard]] float get_float(uint32_t addr) const;
|
||||
[[nodiscard]] std::pair<std::string, size_t> get_string(uint32_t addr) const;
|
||||
|
||||
[[nodiscard]] std::vector<uint8_t> const& data() const { return data_; }
|
||||
[[nodiscard]] size_t size() const { return data_.size(); }
|
||||
|
||||
[[nodiscard]] std::string hexdump() const;
|
||||
|
||||
friend bool operator==(ByteArray const& lhs, ByteArray const& rhs) { return lhs.data_ == rhs.data_; }
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> data_ {};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_BYTEARRAY_HH
|
||||
8
src/common/overloaded.hh
Normal file
8
src/common/overloaded.hh
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef TYCHE_OVERLOADED_HH
|
||||
#define TYCHE_OVERLOADED_HH
|
||||
|
||||
// used by std::visitor
|
||||
template<class... Ts>
|
||||
struct overloaded : Ts... { using Ts::operator()...; };
|
||||
|
||||
#endif //TYCHE_OVERLOADED_HH
|
||||
82
src/vm/code.cc
Normal file
82
src/vm/code.cc
Normal file
@@ -0,0 +1,82 @@
|
||||
#include "code.hh"
|
||||
#include "../common/overloaded.hh"
|
||||
#include "instruction.hh"
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
FunctionId Code::import_bytecode(ByteArray incoming)
|
||||
{
|
||||
bc::Bytecode bc(std::move(incoming));
|
||||
// TODO - adjust function calls, constants
|
||||
|
||||
bytecode_ = std::move(bc);
|
||||
|
||||
return 0; // TODO
|
||||
}
|
||||
|
||||
Operation Code::operation(Location const& location) const
|
||||
{
|
||||
Instruction inst = (Instruction) bytecode_.get_code_byte(location.function_id, location.pc);
|
||||
OperandType opet = instruction_operand_type(inst);
|
||||
|
||||
switch (opet) {
|
||||
case OperandType::NoOperand:
|
||||
return {
|
||||
.instruction = inst,
|
||||
.operator_ = 0,
|
||||
.next_location = { .function_id = location.function_id, .pc = location.pc + 1 },
|
||||
};
|
||||
case OperandType::Int8:
|
||||
return {
|
||||
.instruction = inst,
|
||||
.operator_ = bytecode_.get_code_int8(location.function_id, location.pc + 1),
|
||||
.next_location = { .function_id = location.function_id, .pc = location.pc + 2 },
|
||||
};
|
||||
case OperandType::Int16:
|
||||
return {
|
||||
.instruction = inst,
|
||||
.operator_ = bytecode_.get_code_int16(location.function_id, location.pc + 1),
|
||||
.next_location = { .function_id = location.function_id, .pc = location.pc + 3 },
|
||||
};
|
||||
case OperandType::Int32:
|
||||
return {
|
||||
.instruction = inst,
|
||||
.operator_ = bytecode_.get_code_int32(location.function_id, location.pc + 1),
|
||||
.next_location = { .function_id = location.function_id, .pc = location.pc + 5 },
|
||||
};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
throw std::logic_error("Should not get here");
|
||||
}
|
||||
|
||||
std::string Code::disassemble() const
|
||||
{
|
||||
std::string out;
|
||||
|
||||
out += ".const\n";
|
||||
for (size_t i = 0; i < bytecode_.n_constants(); ++i) {
|
||||
out += "\t" + std::to_string(i) + ": ";
|
||||
std::visit(overloaded {
|
||||
[&out](float f) { out += std::to_string(f); },
|
||||
[&out](std::string const& str) { out += "\"" + str + "\""; },
|
||||
}, bytecode_.get_constant(i));
|
||||
out += "\n";
|
||||
}
|
||||
out += "\n";
|
||||
|
||||
for (size_t i = 0; i < bytecode_.n_functions(); ++i) {
|
||||
out += ".func " + std::to_string(i) + "\n";
|
||||
uint32_t addr = 0;
|
||||
while (addr < bytecode_.get_function_sz(i)) {
|
||||
auto [op, sz] = debug_instruction(bytecode_, i, addr);
|
||||
out += "\t" + op + "\n";
|
||||
addr += sz;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // tyche
|
||||
34
src/vm/code.hh
Normal file
34
src/vm/code.hh
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef TYCHE_CODE_HH
|
||||
#define TYCHE_CODE_HH
|
||||
|
||||
#include "instruction.hh"
|
||||
#include "location.hh"
|
||||
#include "value.hh"
|
||||
#include "../bytecode/bytecode.hh"
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
struct Operation
|
||||
{
|
||||
Instruction instruction;
|
||||
int32_t operator_;
|
||||
Location next_location;
|
||||
};
|
||||
|
||||
class Code {
|
||||
public:
|
||||
FunctionId import_bytecode(ByteArray incoming);
|
||||
|
||||
[[nodiscard]] std::string disassemble() const;
|
||||
|
||||
[[nodiscard]] Operation operation(Location const& location) const;
|
||||
|
||||
[[nodiscard]] bc::Bytecode const& bytecode() const { return bytecode_; }
|
||||
|
||||
private:
|
||||
bc::Bytecode bytecode_;
|
||||
};
|
||||
|
||||
} // tyche
|
||||
|
||||
#endif //TYCHE_CODE_HH
|
||||
114
src/vm/expr.cc
Normal file
114
src/vm/expr.cc
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "expr.hh"
|
||||
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
|
||||
#include "vm_exceptions.hh"
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
std::function<Value(Value const&, Value const&)> binary_ops[(size_t) BinaryOperationType::COUNT][(size_t) Type::COUNT][(size_t) Type::COUNT];
|
||||
|
||||
static int init_ = []() {
|
||||
// every combination, except when explicit, return type error
|
||||
for (size_t i = 0; i < (size_t) BinaryOperationType::COUNT; ++i) {
|
||||
for (size_t j = 0; j < (size_t) Type::COUNT; ++j) {
|
||||
for (size_t k = 0; k < (size_t) Type::COUNT; ++k) {
|
||||
binary_ops[i][j][k] = [&i](Value const& a, Value const& b) -> Value {
|
||||
throw VMInvalidOperation((BinaryOperationType) i, a.type(), b.type());
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// every equality/inequality, by default, return inequal
|
||||
for (size_t j = 0; j < (size_t) Type::COUNT; ++j) {
|
||||
for (size_t k = 0; k < (size_t) Type::COUNT; ++k) {
|
||||
binary_ops[(size_t) BinaryOperationType::Equality][j][k] = [](Value const&, Value const&) { return Value::createFalse(); };
|
||||
binary_ops[(size_t) BinaryOperationType::Inequality][j][k] = [](Value const&, Value const&) { return Value::createTrue(); };
|
||||
}
|
||||
}
|
||||
|
||||
#define BIN_OP(op, t1, t2) binary_ops[(size_t) BinaryOperationType::op][(size_t) Type::t1][(size_t) Type::t2] = [](Value const& b, Value const& a)
|
||||
|
||||
BIN_OP(Sum, Integer, Integer) { return Value::createInteger(a.as_integer() + b.as_integer()); };
|
||||
BIN_OP(Sum, Integer, Float) { return Value::createFloat((float) a.as_integer() + b.as_float()); };
|
||||
BIN_OP(Sum, Float, Integer) { return Value::createFloat(a.as_float() + (float) b.as_integer()); };
|
||||
BIN_OP(Sum, Float, Float) { return Value::createFloat(a.as_float() + b.as_float()); };
|
||||
BIN_OP(Sum, String, String) { return Value::createString(a.as_string() + b.as_string()); };
|
||||
|
||||
BIN_OP(Subtraction, Integer, Integer) { return Value::createInteger(a.as_integer() - b.as_integer()); };
|
||||
BIN_OP(Subtraction, Integer, Float) { return Value::createFloat((float) a.as_integer() - b.as_float()); };
|
||||
BIN_OP(Subtraction, Float, Integer) { return Value::createFloat(a.as_float() - (float) b.as_integer()); };
|
||||
BIN_OP(Subtraction, Float, Float) { return Value::createFloat(a.as_float() - b.as_float()); };
|
||||
|
||||
BIN_OP(Multiplication, Integer, Integer) { return Value::createInteger(a.as_integer() * b.as_integer()); };
|
||||
BIN_OP(Multiplication, Integer, Float) { return Value::createFloat((float) a.as_integer() * b.as_float()); };
|
||||
BIN_OP(Multiplication, Float, Integer) { return Value::createFloat(a.as_float() * (float) b.as_integer()); };
|
||||
BIN_OP(Multiplication, Float, Float) { return Value::createFloat(a.as_float() * b.as_float()); };
|
||||
|
||||
BIN_OP(Division, Integer, Integer) { return Value::createFloat((float) a.as_integer() / (float) b.as_integer()); };
|
||||
BIN_OP(Division, Integer, Float) { return Value::createFloat((float) a.as_integer() / b.as_float()); };
|
||||
BIN_OP(Division, Float, Integer) { return Value::createFloat(a.as_float() / (float) b.as_integer()); };
|
||||
BIN_OP(Division, Float, Float) { return Value::createFloat(a.as_float() / b.as_float()); };
|
||||
|
||||
BIN_OP(IntegerDivision, Integer, Integer) { return Value::createInteger(a.as_integer() / b.as_integer()); };
|
||||
BIN_OP(IntegerDivision, Integer, Float) { return Value::createInteger(a.as_integer() / (int32_t) b.as_float()); };
|
||||
BIN_OP(IntegerDivision, Float, Integer) { return Value::createInteger((int32_t) a.as_float() / b.as_integer()); };
|
||||
BIN_OP(IntegerDivision, Float, Float) { return Value::createInteger((int32_t) a.as_float() / (int32_t) b.as_float()); };
|
||||
|
||||
BIN_OP(Equality, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() == b.as_integer()); };
|
||||
BIN_OP(Equality, Integer, Float) { return Value::createIntegerFromBool(std::abs((float) a.as_integer() - b.as_float()) < FLOAT_EPSILON); };
|
||||
BIN_OP(Equality, Float, Integer) { return Value::createIntegerFromBool(std::abs(a.as_float() - (float) b.as_integer()) < FLOAT_EPSILON); };
|
||||
BIN_OP(Equality, Float, Float) { return Value::createIntegerFromBool(std::abs(a.as_float() - b.as_float()) < FLOAT_EPSILON); };
|
||||
BIN_OP(Equality, String, String) { return Value::createIntegerFromBool(a.as_string() == b.as_string()); };
|
||||
|
||||
BIN_OP(Inequality, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() != b.as_integer()); };
|
||||
BIN_OP(Inequality, Integer, Float) { return Value::createIntegerFromBool(std::abs((float) a.as_integer() - b.as_float()) >= FLOAT_EPSILON); };
|
||||
BIN_OP(Inequality, Float, Integer) { return Value::createIntegerFromBool(std::abs(a.as_float() - (float) b.as_integer()) >= FLOAT_EPSILON); };
|
||||
BIN_OP(Inequality, Float, Float) { return Value::createIntegerFromBool(std::abs(a.as_float() - b.as_float()) >= FLOAT_EPSILON); };
|
||||
BIN_OP(Inequality, String, String) { return Value::createIntegerFromBool(a.as_string() != b.as_string()); };
|
||||
|
||||
BIN_OP(LessThan, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() < b.as_integer()); };
|
||||
BIN_OP(LessThan, Integer, Float) { return Value::createIntegerFromBool((float) a.as_integer() < b.as_float()); };
|
||||
BIN_OP(LessThan, Float, Integer) { return Value::createIntegerFromBool(a.as_float() < (float) b.as_integer()); };
|
||||
BIN_OP(LessThan, Float, Float) { return Value::createIntegerFromBool(a.as_float() < b.as_float()); };
|
||||
|
||||
BIN_OP(LessThanOrEquals, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() <= b.as_integer()); };
|
||||
BIN_OP(LessThanOrEquals, Integer, Float) { return Value::createIntegerFromBool((float) a.as_integer() <= b.as_float()); };
|
||||
BIN_OP(LessThanOrEquals, Float, Integer) { return Value::createIntegerFromBool(a.as_float() <= (float) b.as_integer()); };
|
||||
BIN_OP(LessThanOrEquals, Float, Float) { return Value::createIntegerFromBool(a.as_float() <= b.as_float()); };
|
||||
|
||||
BIN_OP(GreaterThan, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() > b.as_integer()); };
|
||||
BIN_OP(GreaterThan, Integer, Float) { return Value::createIntegerFromBool((float) a.as_integer() > b.as_float()); };
|
||||
BIN_OP(GreaterThan, Float, Integer) { return Value::createIntegerFromBool(a.as_float() > (float) b.as_integer()); };
|
||||
BIN_OP(GreaterThan, Float, Float) { return Value::createIntegerFromBool(a.as_float() > b.as_float()); };
|
||||
|
||||
BIN_OP(GreaterThanOrEquals, Integer, Integer) { return Value::createIntegerFromBool(a.as_integer() >= b.as_integer()); };
|
||||
BIN_OP(GreaterThanOrEquals, Integer, Float) { return Value::createIntegerFromBool((float) a.as_integer() >= b.as_float()); };
|
||||
BIN_OP(GreaterThanOrEquals, Float, Integer) { return Value::createIntegerFromBool(a.as_float() >= (float) b.as_integer()); };
|
||||
BIN_OP(GreaterThanOrEquals, Float, Float) { return Value::createIntegerFromBool(a.as_float() >= b.as_float()); };
|
||||
|
||||
BIN_OP(Power, Integer, Integer) { return Value::createInteger((int32_t) powl(a.as_integer(), b.as_integer())); };
|
||||
BIN_OP(Power, Integer, Float) { return Value::createFloat(powf((float) a.as_integer(), b.as_float())); };
|
||||
BIN_OP(Power, Float, Integer) { return Value::createFloat(powf(a.as_float(), (float) b.as_integer())); };
|
||||
BIN_OP(Power, Float, Float) { return Value::createFloat(powf(a.as_float(), b.as_float())); };
|
||||
|
||||
BIN_OP(Modulo, Integer, Integer) { return Value::createInteger(a.as_integer() % b.as_integer()); };
|
||||
BIN_OP(ShiftLeft, Integer, Integer) { return Value::createInteger(a.as_integer() << b.as_integer()); };
|
||||
BIN_OP(ShiftRight, Integer, Integer) { return Value::createInteger(a.as_integer() >> b.as_integer()); };
|
||||
BIN_OP(BitwiseAnd, Integer, Integer) { return Value::createInteger(a.as_integer() & b.as_integer()); };
|
||||
BIN_OP(BitwiseOr, Integer, Integer) { return Value::createInteger(a.as_integer() | b.as_integer()); };
|
||||
BIN_OP(BitwiseXor, Integer, Integer) { return Value::createInteger(a.as_integer() ^ b.as_integer()); };
|
||||
|
||||
#undef BIN_OP
|
||||
|
||||
return 0;
|
||||
}();
|
||||
|
||||
Value binary_operation(Value const& a, Value const& b, BinaryOperationType op)
|
||||
{
|
||||
return binary_ops[(size_t) op][(size_t) b.type()][(size_t) a.type()](a, b);
|
||||
}
|
||||
|
||||
}
|
||||
21
src/vm/expr.hh
Normal file
21
src/vm/expr.hh
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef TYCHE_EXPR_HH
|
||||
#define TYCHE_EXPR_HH
|
||||
#include "value.hh"
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
enum class BinaryOperationType
|
||||
{
|
||||
Sum, Subtraction, Multiplication, Division, IntegerDivision,
|
||||
Equality, Inequality, LessThan, LessThanOrEquals,
|
||||
GreaterThan, GreaterThanOrEquals, Power, Modulo,
|
||||
BitwiseAnd, BitwiseOr, BitwiseXor, ShiftLeft, ShiftRight,
|
||||
COUNT
|
||||
};
|
||||
|
||||
constexpr float FLOAT_EPSILON = 0.000001f;
|
||||
|
||||
Value binary_operation(Value const& a, Value const& b, BinaryOperationType op);
|
||||
|
||||
}
|
||||
#endif //TYCHE_EXPR_HH
|
||||
244
src/vm/instruction.cc
Normal file
244
src/vm/instruction.cc
Normal file
@@ -0,0 +1,244 @@
|
||||
#include "instruction.hh"
|
||||
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
const std::unordered_map<std::string, Instruction> instruction_names = {
|
||||
{ "pushi", Instruction::PushInt8 },
|
||||
{ "pushc", Instruction::PushConstant8 },
|
||||
{ "pushz", Instruction::PushZero },
|
||||
{ "pusht", Instruction::PushTrue },
|
||||
{ "pushf", Instruction::PushFunction8 },
|
||||
{ "newa", Instruction::NewArray },
|
||||
{ "newt", Instruction::NewTable },
|
||||
{ "pop", Instruction::Pop },
|
||||
{ "dup", Instruction::Duplicate },
|
||||
{ "pushv", Instruction::PushValues8 },
|
||||
{ "set", Instruction::SetValue8 },
|
||||
{ "dupv", Instruction::DuplicateValue8 },
|
||||
{ "setg", Instruction::SetGlobal8 },
|
||||
{ "getl", Instruction::GetGlobal8 },
|
||||
{ "call8", Instruction::Call8 },
|
||||
{ "ret", Instruction::Return },
|
||||
{ "retn", Instruction::ReturnNil },
|
||||
{ "getkv", Instruction::GetKeyValue },
|
||||
{ "setkv", Instruction::SetKeyValue },
|
||||
{ "geta", Instruction::GetArrayItem },
|
||||
{ "seta", Instruction::SetArrayItem },
|
||||
{ "appnd", Instruction::Append },
|
||||
{ "next", Instruction::Next },
|
||||
{ "smt", Instruction::SetMetatable },
|
||||
{ "mt", Instruction::GetMetatable },
|
||||
{ "sum", Instruction::Sum },
|
||||
{ "sub", Instruction::Subtract },
|
||||
{ "mul", Instruction::Multiply },
|
||||
{ "div", Instruction::Divide },
|
||||
{ "idiv", Instruction::DivideInt },
|
||||
{ "eq", Instruction::Equals },
|
||||
{ "neq", Instruction::NotEquals },
|
||||
{ "lt", Instruction::LessThan },
|
||||
{ "lte", Instruction::LessThanEq },
|
||||
{ "gt", Instruction::GreaterThan },
|
||||
{ "gte", Instruction::GreaterThanEq },
|
||||
{ "and", Instruction::And },
|
||||
{ "or", Instruction::Or },
|
||||
{ "xor", Instruction::Xor },
|
||||
{ "pow", Instruction::Power },
|
||||
{ "shl", Instruction::ShiftLeft },
|
||||
{ "shr", Instruction::ShiftRight },
|
||||
{ "mod", Instruction::Modulo },
|
||||
{ "len", Instruction::Len },
|
||||
{ "type", Instruction::Type },
|
||||
{ "cast", Instruction::Cast },
|
||||
{ "ver", Instruction::Version },
|
||||
{ "bz", Instruction::BranchIfZero8 },
|
||||
{ "bnz", Instruction::BranchIfNotZero8 },
|
||||
{ "jmp", Instruction::Jump8 },
|
||||
{ "cmpl", Instruction::Compile },
|
||||
{ "asmbl", Instruction::Assemble },
|
||||
{ "load", Instruction::Load },
|
||||
};
|
||||
|
||||
|
||||
|
||||
std::pair<std::string, size_t> debug_instruction(Instruction inst, int oper)
|
||||
{
|
||||
std::string out;
|
||||
switch (inst) {
|
||||
|
||||
case Instruction::PushInt8:
|
||||
case Instruction::PushInt16:
|
||||
case Instruction::PushInt32:
|
||||
out = "pushi";
|
||||
break;
|
||||
case Instruction::PushConstant8:
|
||||
case Instruction::PushConstant16:
|
||||
case Instruction::PushConstant32:
|
||||
out = "pushc";
|
||||
break;
|
||||
case Instruction::PushFunction8:
|
||||
case Instruction::PushFunction16:
|
||||
case Instruction::PushFunction32:
|
||||
out = "pushf";
|
||||
break;
|
||||
case Instruction::PushZero: out = "pushz"; break;
|
||||
case Instruction::PushTrue: out = "pusht"; break;
|
||||
case Instruction::NewArray: out = "newa"; break;
|
||||
case Instruction::NewTable: out = "newt"; break;
|
||||
case Instruction::Pop: out = "pop"; break;
|
||||
case Instruction::Duplicate: out = "dup"; break;
|
||||
case Instruction::PushValues8:
|
||||
case Instruction::PushValues16:
|
||||
case Instruction::PushValues32:
|
||||
out = "pushv";
|
||||
break;
|
||||
case Instruction::SetValue8:
|
||||
case Instruction::SetValue16:
|
||||
case Instruction::SetValue32:
|
||||
out = "set";
|
||||
break;
|
||||
case Instruction::DuplicateValue8:
|
||||
case Instruction::DuplicateValue16:
|
||||
case Instruction::DuplicateValue32:
|
||||
out = "dupv";
|
||||
break;
|
||||
case Instruction::SetGlobal8:
|
||||
case Instruction::SetGlobal16:
|
||||
case Instruction::SetGlobal32:
|
||||
out = "setg";
|
||||
break;
|
||||
case Instruction::GetGlobal8:
|
||||
case Instruction::GetGlobal16:
|
||||
case Instruction::GetGlobal32:
|
||||
out = "getg";
|
||||
break;
|
||||
case Instruction::Call8:
|
||||
case Instruction::Call16:
|
||||
case Instruction::Call32:
|
||||
out = "call";
|
||||
break;
|
||||
case Instruction::Return: out = "ret"; break;
|
||||
case Instruction::ReturnNil: out = "retn"; break;
|
||||
case Instruction::GetKeyValue: out = "getkv"; break;
|
||||
case Instruction::SetKeyValue: out = "setkv"; break;
|
||||
case Instruction::GetArrayItem: out = "geta"; break;
|
||||
case Instruction::SetArrayItem: out = "seta"; break;
|
||||
case Instruction::Append: out = "appnd"; break;
|
||||
case Instruction::Next: out = "next"; break;
|
||||
case Instruction::SetMetatable: out = "smt"; break;
|
||||
case Instruction::GetMetatable: out = "mt"; break;
|
||||
case Instruction::Sum: out = "sum"; break;
|
||||
case Instruction::Subtract: out = "sub"; break;
|
||||
case Instruction::Multiply: out = "mul"; break;
|
||||
case Instruction::Divide: out = "div"; break;
|
||||
case Instruction::DivideInt: out = "idiv"; break;
|
||||
case Instruction::Equals: out = "eq"; break;
|
||||
case Instruction::NotEquals: out = "neq"; break;
|
||||
case Instruction::LessThan: out = "lt"; break;
|
||||
case Instruction::LessThanEq: out = "lte"; break;
|
||||
case Instruction::GreaterThan: out = "gt"; break;
|
||||
case Instruction::GreaterThanEq: out = "gte"; break;
|
||||
case Instruction::And: out = "and"; break;
|
||||
case Instruction::Or: out = "or"; break;
|
||||
case Instruction::Xor: out = "xor"; break;
|
||||
case Instruction::Power: out = "pow"; break;
|
||||
case Instruction::ShiftLeft: out = "shl"; break;
|
||||
case Instruction::ShiftRight: out = "shr"; break;
|
||||
case Instruction::Modulo: out = "mod"; break;
|
||||
case Instruction::Len: out = "len"; break;
|
||||
case Instruction::Type: out = "type"; break;
|
||||
case Instruction::Cast: out = "cast"; break;
|
||||
case Instruction::Version: out = "ver"; break;
|
||||
case Instruction::BranchIfZero8:
|
||||
case Instruction::BranchIfZero16:
|
||||
case Instruction::BranchIfZero32:
|
||||
out = "bz";
|
||||
break;
|
||||
case Instruction::BranchIfNotZero8:
|
||||
case Instruction::BranchIfNotZero16:
|
||||
case Instruction::BranchIfNotZero32:
|
||||
out = "bnz";
|
||||
break;
|
||||
case Instruction::Jump8:
|
||||
case Instruction::Jump16:
|
||||
case Instruction::Jump32:
|
||||
out = "jmp";
|
||||
break;
|
||||
case Instruction::Compile: out = "cmpl"; break;
|
||||
case Instruction::Assemble: out = "asmbl"; break;
|
||||
case Instruction::Load: out = "load"; break;
|
||||
default:
|
||||
out = "???";
|
||||
}
|
||||
|
||||
OperandType operands = instruction_operand_type(inst);
|
||||
|
||||
if (operands == OperandType::NoOperand)
|
||||
return { out, 1 };
|
||||
|
||||
out += " " + std::to_string(oper);
|
||||
if (operands == OperandType::Int32)
|
||||
return { out, 5 };
|
||||
if (operands == OperandType::Int16)
|
||||
return { out, 3 };
|
||||
|
||||
return { out, 2 };
|
||||
}
|
||||
|
||||
std::pair<std::string, size_t> debug_instruction(bc::Bytecode const& bt, uint32_t function_id, uint32_t addr)
|
||||
{
|
||||
auto inst = (Instruction) bt.get_code_byte(function_id, addr);
|
||||
|
||||
switch (instruction_operand_type(inst)) {
|
||||
case OperandType::NoOperand:
|
||||
return debug_instruction(inst);
|
||||
case OperandType::Int8:
|
||||
return debug_instruction(inst, bt.get_code_int8(function_id, addr + 1));
|
||||
case OperandType::Int16:
|
||||
return debug_instruction(inst, bt.get_code_int16(function_id, addr + 1));
|
||||
case OperandType::Int32:
|
||||
return debug_instruction(inst, bt.get_code_int32(function_id, addr + 1));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return { "???", 1 };
|
||||
}
|
||||
|
||||
OperandType instruction_operand_type(Instruction inst)
|
||||
{
|
||||
if ((uint8_t) inst >= 0xe0)
|
||||
return OperandType::Int32;
|
||||
if ((uint8_t) inst >= 0xc0)
|
||||
return OperandType::Int16;
|
||||
if ((uint8_t) inst >= 0xa0)
|
||||
return OperandType::Int8;
|
||||
return OperandType::NoOperand;
|
||||
}
|
||||
|
||||
std::optional<Instruction> translate_instruction(std::string const& txt, std::optional<int> op)
|
||||
{
|
||||
auto it = instruction_names.find(txt);
|
||||
if (it == instruction_names.end())
|
||||
return {};
|
||||
Instruction inst = it->second;
|
||||
OperandType optype = instruction_operand_type(inst);
|
||||
|
||||
if (optype == OperandType::NoOperand && op)
|
||||
return {};
|
||||
if (optype != OperandType::NoOperand && !op)
|
||||
return {};
|
||||
|
||||
if (optype == OperandType::NoOperand)
|
||||
return inst;
|
||||
|
||||
if (op >= std::numeric_limits<int8_t>::min() && op <= std::numeric_limits<int8_t>::max())
|
||||
return inst;
|
||||
if (op >= std::numeric_limits<int16_t>::min() && op <= std::numeric_limits<int16_t>::max())
|
||||
return (Instruction) ((uint8_t) inst + OPCODE_NEXT_SIZE);
|
||||
return (Instruction) ((uint8_t) inst + (OPCODE_NEXT_SIZE * 2));
|
||||
}
|
||||
|
||||
}
|
||||
121
src/vm/instruction.hh
Normal file
121
src/vm/instruction.hh
Normal file
@@ -0,0 +1,121 @@
|
||||
#ifndef TYCHE_INSTRUCTION_HH
|
||||
#define TYCHE_INSTRUCTION_HH
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "../bytecode/bytecode.hh"
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
constexpr uint8_t OPCODE_NEXT_SIZE = 0x20;
|
||||
|
||||
enum class Instruction : uint8_t {
|
||||
|
||||
// stack operations
|
||||
PushInt8 = 0xa0,
|
||||
PushInt16 = 0xc0,
|
||||
PushInt32 = 0xe0,
|
||||
PushConstant8 = 0xa1,
|
||||
PushConstant16 = 0xc1,
|
||||
PushConstant32 = 0xe1,
|
||||
PushFunction8 = 0xa2,
|
||||
PushFunction16 = 0xc2,
|
||||
PushFunction32 = 0xe2,
|
||||
PushZero = 0x00,
|
||||
PushTrue = 0x01,
|
||||
NewArray = 0x02,
|
||||
NewTable = 0x03,
|
||||
Pop = 0x04,
|
||||
Duplicate = 0x05,
|
||||
|
||||
// local variables
|
||||
PushValues8 = 0xa3,
|
||||
PushValues16 = 0xc3,
|
||||
PushValues32 = 0xe3,
|
||||
SetValue8 = 0xab,
|
||||
SetValue16 = 0xcb,
|
||||
SetValue32 = 0xeb,
|
||||
DuplicateValue8 = 0xa4,
|
||||
DuplicateValue16 = 0xc4,
|
||||
DuplicateValue32 = 0xe4,
|
||||
SetGlobal8 = 0xa5,
|
||||
SetGlobal16 = 0xc5,
|
||||
SetGlobal32 = 0xe5,
|
||||
GetGlobal8 = 0xa6,
|
||||
GetGlobal16 = 0xc6,
|
||||
GetGlobal32 = 0xe6,
|
||||
|
||||
// function operations
|
||||
Call8 = 0xa7,
|
||||
Call16 = 0xc7,
|
||||
Call32 = 0xe7,
|
||||
Return = 0x10,
|
||||
ReturnNil = 0x11,
|
||||
|
||||
// table and array operations
|
||||
GetKeyValue = 0x16,
|
||||
SetKeyValue = 0x17,
|
||||
GetArrayItem = 0x18,
|
||||
SetArrayItem = 0x19,
|
||||
Append = 0x1a,
|
||||
Next = 0x1b,
|
||||
SetMetatable = 0x1c,
|
||||
GetMetatable = 0x1d,
|
||||
|
||||
// logical/arithmetic
|
||||
Sum = 0x20,
|
||||
Subtract = 0x21,
|
||||
Multiply = 0x22,
|
||||
Divide = 0x23,
|
||||
DivideInt = 0x24,
|
||||
Equals = 0x25,
|
||||
NotEquals = 0x26,
|
||||
LessThan = 0x27,
|
||||
LessThanEq = 0x28,
|
||||
GreaterThan = 0x29,
|
||||
GreaterThanEq = 0x2a,
|
||||
And = 0x2b,
|
||||
Or = 0x2c,
|
||||
Xor = 0x2d,
|
||||
Power = 0x2e,
|
||||
ShiftLeft = 0x2f,
|
||||
ShiftRight = 0x30,
|
||||
Modulo = 0x31,
|
||||
|
||||
// other value operations
|
||||
Len = 0x40,
|
||||
Type = 0x41,
|
||||
Cast = 0x42,
|
||||
Version = 0x43,
|
||||
|
||||
// control flow
|
||||
BranchIfZero8 = 0xa8,
|
||||
BranchIfZero16 = 0xc8,
|
||||
BranchIfZero32 = 0xe8,
|
||||
BranchIfNotZero8 = 0xa9,
|
||||
BranchIfNotZero16 = 0xc9,
|
||||
BranchIfNotZero32 = 0xe9,
|
||||
Jump8 = 0xaa,
|
||||
Jump16 = 0xca,
|
||||
Jump32 = 0xea,
|
||||
|
||||
// external code
|
||||
Compile = 0x48,
|
||||
Assemble = 0x49,
|
||||
Load = 0x4a,
|
||||
};
|
||||
|
||||
std::pair<std::string, size_t> debug_instruction(Instruction inst, int oper=0);
|
||||
std::pair<std::string, size_t> debug_instruction(bc::Bytecode const& bt, uint32_t function_id, uint32_t addr);
|
||||
|
||||
enum class OperandType { NoOperand, Int8, Int16, Int32 };
|
||||
OperandType instruction_operand_type(Instruction instruction);
|
||||
|
||||
std::optional<Instruction> translate_instruction(std::string const& txt, std::optional<int> op);
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_INSTRUCTION_HH
|
||||
16
src/vm/location.hh
Normal file
16
src/vm/location.hh
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef TYCHE_LOCATION_HH
|
||||
#define TYCHE_LOCATION_HH
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
struct Location
|
||||
{
|
||||
uint32_t function_id;
|
||||
uint32_t pc;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_LOCATION_HH
|
||||
95
src/vm/stack.cc
Normal file
95
src/vm/stack.cc
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "stack.hh"
|
||||
|
||||
#include "vm_exceptions.hh"
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
Stack::Stack()
|
||||
{
|
||||
fps_.push(0);
|
||||
}
|
||||
|
||||
void Stack::push(Value const& value)
|
||||
{
|
||||
stack_.push_back(value);
|
||||
}
|
||||
|
||||
Value Stack::pop()
|
||||
{
|
||||
if (stack_.size() <= fps_.top())
|
||||
throw VMStackUnderflow();
|
||||
|
||||
Value v = stack_.back();
|
||||
stack_.pop_back();
|
||||
return v;
|
||||
}
|
||||
|
||||
Value Stack::peek() const
|
||||
{
|
||||
if (stack_.size() <= fps_.top())
|
||||
throw VMStackUnderflow();
|
||||
|
||||
return stack_.back();
|
||||
}
|
||||
|
||||
Value Stack::at(int pos) const
|
||||
{
|
||||
try {
|
||||
if (pos >= 0) {
|
||||
return stack_.at(fps_.top() + pos);
|
||||
} else {
|
||||
if ((int) fps_.top() + (int) stack_.size() + pos < 0)
|
||||
throw VMStackOutOfRange();
|
||||
return stack_.at(stack_.size() + pos);
|
||||
}
|
||||
} catch (std::out_of_range&) {
|
||||
throw VMStackOutOfRange();
|
||||
}
|
||||
}
|
||||
|
||||
void Stack::set(int pos, Value const& val)
|
||||
{
|
||||
try {
|
||||
if (pos >= 0) {
|
||||
stack_.at(fps_.top() + pos) = val;
|
||||
} else {
|
||||
if ((int) fps_.top() + (int) stack_.size() + pos < 0)
|
||||
throw VMStackOutOfRange();
|
||||
stack_.at(stack_.size() + pos) = val;
|
||||
}
|
||||
} catch (std::out_of_range&) {
|
||||
throw VMStackOutOfRange();
|
||||
}
|
||||
}
|
||||
|
||||
size_t Stack::size() const
|
||||
{
|
||||
return stack_.size() - fps_.top();
|
||||
}
|
||||
|
||||
void Stack::push_fp()
|
||||
{
|
||||
fps_.push(stack_.size());
|
||||
}
|
||||
|
||||
void Stack::pop_fp()
|
||||
{
|
||||
if (fps_.size() == 1)
|
||||
throw VMStackUnderflow();
|
||||
|
||||
stack_.resize(fps_.top());
|
||||
fps_.pop();
|
||||
}
|
||||
|
||||
std::string Stack::debug() const
|
||||
{
|
||||
if (stack_.empty())
|
||||
return "empty";
|
||||
|
||||
std::string out;
|
||||
for (size_t i = 0; i < stack_.size(); ++i)
|
||||
out += "[" + stack_.at(i).to_string() + "] ";
|
||||
return out;
|
||||
}
|
||||
|
||||
} // tyche
|
||||
38
src/vm/stack.hh
Normal file
38
src/vm/stack.hh
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef TYCHE_STACK_HH
|
||||
#define TYCHE_STACK_HH
|
||||
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
|
||||
#include "value.hh"
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
class Stack {
|
||||
public:
|
||||
Stack();
|
||||
|
||||
void push(Value const& value);
|
||||
Value pop();
|
||||
[[nodiscard]] Value peek() const;
|
||||
|
||||
[[nodiscard]] Value at(int pos) const;
|
||||
[[nodiscard]] size_t size() const;
|
||||
|
||||
void set(int pos, Value const& val);
|
||||
|
||||
void push_fp();
|
||||
void pop_fp();
|
||||
|
||||
[[nodiscard]] size_t fp_level() const { return fps_.size(); }
|
||||
|
||||
[[nodiscard]] std::string debug() const;
|
||||
|
||||
private:
|
||||
std::vector<Value> stack_;
|
||||
std::stack<size_t> fps_;
|
||||
};
|
||||
|
||||
} // tyche
|
||||
|
||||
#endif //TYCHE_STACK_HH
|
||||
292
src/vm/tests.cc
Normal file
292
src/vm/tests.cc
Normal file
@@ -0,0 +1,292 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "../bytecode/bytecodeprototype.hh"
|
||||
#include "../bytecode/bytecode.hh"
|
||||
#include "../assembler/assembler.hh"
|
||||
#include "code.hh"
|
||||
#include "stack.hh"
|
||||
#include "vm.hh"
|
||||
|
||||
using namespace tyche;
|
||||
using namespace tyche::bc;
|
||||
using namespace tyche::vm;
|
||||
|
||||
static VM run(std::string oper) {
|
||||
return VM().load_bytecode(as::Assembler(std::format(R"(
|
||||
.const
|
||||
0: 3.14
|
||||
1: "Hello world"
|
||||
.func 0
|
||||
{}
|
||||
ret
|
||||
)", oper)).assemble()).call(0);
|
||||
}
|
||||
|
||||
TEST(Code, ImportSingleAndDebug)
|
||||
{
|
||||
BytecodePrototype bp;
|
||||
|
||||
bp.constants.emplace_back(3.14f);
|
||||
bp.constants.emplace_back("HELLO");
|
||||
|
||||
bp.functions.emplace_back(0, 0);
|
||||
bp.functions.at(0).code.append_byte(0xa0); // pushi
|
||||
bp.functions.at(0).code.append_int8(42);
|
||||
|
||||
bp.functions.emplace_back(2, 1);
|
||||
bp.functions.at(1).code.append_byte(0x1a); // appnd
|
||||
|
||||
ByteArray ba = Bytecode::generate(bp);
|
||||
|
||||
Code code;
|
||||
code.import_bytecode(std::move(ba));
|
||||
printf("%s\n", code.disassemble().c_str());
|
||||
}
|
||||
|
||||
TEST(Stack, PushPullGet)
|
||||
{
|
||||
Stack stack;
|
||||
stack.push(Value::createInteger(10));
|
||||
stack.push(Value::createInteger(20));
|
||||
stack.push(Value::createInteger(30));
|
||||
|
||||
ASSERT_EQ(stack.size(), 3);
|
||||
ASSERT_EQ(stack.at(0).as_integer(), 10);
|
||||
ASSERT_EQ(stack.at(1).as_integer(), 20);
|
||||
ASSERT_EQ(stack.at(-1).as_integer(), 30);
|
||||
ASSERT_EQ(stack.at(-2).as_integer(), 20);
|
||||
}
|
||||
|
||||
TEST(Stack, FramePointer)
|
||||
{
|
||||
Stack stack;
|
||||
stack.push(Value::createInteger(10));
|
||||
stack.push(Value::createInteger(20));
|
||||
stack.push_fp();
|
||||
stack.push(Value::createInteger(30));
|
||||
stack.push(Value::createInteger(40));
|
||||
stack.push(Value::createInteger(50));
|
||||
|
||||
ASSERT_EQ(stack.size(), 3);
|
||||
ASSERT_EQ(stack.at(0).as_integer(), 30);
|
||||
ASSERT_EQ(stack.at(1).as_integer(), 40);
|
||||
ASSERT_EQ(stack.at(-1).as_integer(), 50);
|
||||
ASSERT_EQ(stack.at(-2).as_integer(), 40);
|
||||
|
||||
stack.pop_fp();
|
||||
|
||||
ASSERT_EQ(stack.size(), 2);
|
||||
ASSERT_EQ(stack.at(0).as_integer(), 10);
|
||||
ASSERT_EQ(stack.at(1).as_integer(), 20);
|
||||
ASSERT_EQ(stack.at(-1).as_integer(), 20);
|
||||
ASSERT_EQ(stack.at(-2).as_integer(), 10);
|
||||
}
|
||||
|
||||
TEST(VM, BasicCode)
|
||||
{
|
||||
// code (2+3)
|
||||
BytecodePrototype bp;
|
||||
bp.functions.emplace_back(0, 0);
|
||||
bp.functions.at(0).code.append_byte((uint8_t) Instruction::PushInt8);
|
||||
bp.functions.at(0).code.append_int8(2);
|
||||
bp.functions.at(0).code.append_byte((uint8_t) Instruction::PushInt8);
|
||||
bp.functions.at(0).code.append_int8(3);
|
||||
bp.functions.at(0).code.append_byte((uint8_t) Instruction::Sum);
|
||||
bp.functions.at(0).code.append_byte((uint8_t) Instruction::Return);
|
||||
ByteArray ba = Bytecode::generate(bp);
|
||||
|
||||
VM vm;
|
||||
vm.load_bytecode(std::move(ba));
|
||||
vm.call(0);
|
||||
|
||||
int32_t result = vm.to_integer(-1);
|
||||
ASSERT_EQ(result, 5);
|
||||
}
|
||||
|
||||
TEST(VM, StackOperations)
|
||||
{
|
||||
ASSERT_EQ(run("pushi 5000").to_integer(-1), 5000);
|
||||
ASSERT_EQ(run("pushi -5000").to_integer(-1), -5000);
|
||||
ASSERT_FLOAT_EQ(run("pushi 5000").to_float(-1), 5000.f);
|
||||
ASSERT_FLOAT_EQ(run("pushc 0").to_float(-1), 3.14f);
|
||||
ASSERT_EQ(run("pushc 0").to_integer(-1), 3);
|
||||
ASSERT_EQ(run("pushc 1").to_string(-1), "Hello world");
|
||||
ASSERT_TRUE(run("pushf 0").is_function(-1));
|
||||
ASSERT_EQ(run("pushi 2\n pushi 3\n pop").to_integer(-1), 2);
|
||||
}
|
||||
|
||||
TEST(VM, IntegerIntegerOperations)
|
||||
{
|
||||
auto test_op = [](int32_t op1, int32_t op2, std::string oper) {
|
||||
return VM().load_bytecode(as::Assembler(std::format(R"(
|
||||
.func 0
|
||||
pushi {}
|
||||
pushi {}
|
||||
{}
|
||||
ret
|
||||
)", op1, op2, oper)).assemble()).call(0).to_integer(-1);
|
||||
};
|
||||
|
||||
ASSERT_EQ(test_op(2, 3, "sum"), 5);
|
||||
ASSERT_EQ(test_op(2, 3, "sub"), -1);
|
||||
ASSERT_EQ(test_op(2, 3, "mul"), 6);
|
||||
ASSERT_EQ(test_op(20, 3, "idiv"), 6);
|
||||
ASSERT_EQ(test_op(2, 3, "eq"), 0);
|
||||
ASSERT_EQ(test_op(2, 3, "neq"), 1);
|
||||
ASSERT_EQ(test_op(2, 3, "lt"), 1);
|
||||
ASSERT_EQ(test_op(2, 3, "lte"), 1);
|
||||
ASSERT_EQ(test_op(3, 3, "lte"), 1);
|
||||
ASSERT_EQ(test_op(4, 3, "lte"), 0);
|
||||
ASSERT_EQ(test_op(2, 3, "gt"), 0);
|
||||
ASSERT_EQ(test_op(2, 3, "gte"), 0);
|
||||
ASSERT_EQ(test_op(3, 3, "gte"), 1);
|
||||
ASSERT_EQ(test_op(4, 3, "gte"), 1);
|
||||
ASSERT_EQ(test_op(2, 3, "and"), 2);
|
||||
ASSERT_EQ(test_op(2, 3, "or"), 3);
|
||||
ASSERT_EQ(test_op(2, 3, "xor"), 1);
|
||||
ASSERT_EQ(test_op(2, 3, "pow"), 8);
|
||||
ASSERT_EQ(test_op(2, 3, "shl"), 16);
|
||||
ASSERT_EQ(test_op(30, 2, "shr"), 7);
|
||||
ASSERT_EQ(test_op(8, 3, "mod"), 2);
|
||||
|
||||
ASSERT_FLOAT_EQ(run("pushi 3\n pushi 2\n div").to_float(-1), 1.5f);
|
||||
}
|
||||
|
||||
TEST(VM, IntegerFloatOperations)
|
||||
{
|
||||
auto test_op = [](int op1, std::string const& op2, std::string oper) -> VM {
|
||||
return VM().load_bytecode(as::Assembler(std::format(R"(
|
||||
.const
|
||||
0: {}
|
||||
.func 0
|
||||
pushi {}
|
||||
pushc 0
|
||||
{}
|
||||
ret
|
||||
)", op2, op1, oper)).assemble()).call(0);
|
||||
};
|
||||
|
||||
ASSERT_FLOAT_EQ(test_op(2, "3.5", "sum").to_float(-1), 5.5f);
|
||||
ASSERT_FLOAT_EQ(test_op(2, "3.5", "sub").to_float(-1), -1.5f);
|
||||
ASSERT_FLOAT_EQ(test_op(2, "3.5", "mul").to_float(-1), 7.f);
|
||||
ASSERT_FLOAT_EQ(test_op(20, "3.5", "idiv").to_integer(-1), 6);
|
||||
ASSERT_FLOAT_EQ(test_op(20, "3.5", "div").to_float(-1), 5.7142859);
|
||||
ASSERT_FLOAT_EQ(test_op(3, "3.5", "eq").to_integer(-1), 0);
|
||||
ASSERT_FLOAT_EQ(test_op(3, "3.0", "eq").to_integer(-1), 1);
|
||||
}
|
||||
|
||||
TEST(VM, FloatIntegerOperations)
|
||||
{
|
||||
auto test_op = [](std::string const& op1, int op2, std::string oper) -> VM {
|
||||
return VM().load_bytecode(as::Assembler(std::format(R"(
|
||||
.const
|
||||
0: {}
|
||||
.func 0
|
||||
pushc 0
|
||||
pushi {}
|
||||
{}
|
||||
ret
|
||||
)", op1, op2, oper)).assemble()).call(0);
|
||||
};
|
||||
|
||||
ASSERT_FLOAT_EQ(test_op("3.5", 2, "sum").to_float(-1), 5.5f);
|
||||
ASSERT_FLOAT_EQ(test_op("3.5", 2, "sub").to_float(-1), 1.5f);
|
||||
ASSERT_FLOAT_EQ(test_op("3.5", 2, "mul").to_float(-1), 7.f);
|
||||
ASSERT_FLOAT_EQ(test_op("3.5", 2, "idiv").to_integer(-1), 1);
|
||||
ASSERT_FLOAT_EQ(test_op("3.5", 2, "div").to_float(-1), 1.75f);
|
||||
ASSERT_FLOAT_EQ(test_op("3.5", 3, "eq").to_integer(-1), 0);
|
||||
ASSERT_FLOAT_EQ(test_op("3.0", 3, "eq").to_integer(-1), 1);
|
||||
}
|
||||
|
||||
TEST(VM, FloatFloatOperations)
|
||||
{
|
||||
auto test_op = [](std::string const& op1, std::string const& op2, std::string oper) -> VM {
|
||||
return VM().load_bytecode(as::Assembler(std::format(R"(
|
||||
.const
|
||||
0: {}
|
||||
1: {}
|
||||
.func 0
|
||||
pushc 0
|
||||
pushc 1
|
||||
{}
|
||||
ret
|
||||
)", op1, op2, oper)).assemble()).call(0);
|
||||
};
|
||||
|
||||
ASSERT_FLOAT_EQ(test_op("3.5", "2.2", "sum").to_float(-1), 5.7f);
|
||||
ASSERT_FLOAT_EQ(test_op("3.5", "2.2", "sub").to_float(-1), 1.3f);
|
||||
ASSERT_FLOAT_EQ(test_op("3.5", "2.2", "mul").to_float(-1), 7.7f);
|
||||
ASSERT_FLOAT_EQ(test_op("3.5", "2.2", "idiv").to_integer(-1), 1);
|
||||
ASSERT_FLOAT_EQ(test_op("4.5", "2.5", "div").to_float(-1), 1.8f);
|
||||
ASSERT_FLOAT_EQ(test_op("3.2005", "3.2", "eq").to_integer(-1), 0);
|
||||
ASSERT_FLOAT_EQ(test_op("3.2", "3.2", "eq").to_integer(-1), 1);
|
||||
}
|
||||
|
||||
TEST(VM, StringString)
|
||||
{
|
||||
ASSERT_EQ(run(R"(
|
||||
.const
|
||||
0: "Hello"
|
||||
1: "World"
|
||||
.func 0
|
||||
pushc 0
|
||||
pushc 1
|
||||
sum
|
||||
ret
|
||||
)").to_string(-1), "HelloWorld");
|
||||
|
||||
ASSERT_EQ(run(R"(
|
||||
.const
|
||||
0: "Hello"
|
||||
1: "World"
|
||||
.func 0
|
||||
pushc 0
|
||||
pushc 1
|
||||
eq
|
||||
ret
|
||||
)").to_integer(-1), 0);
|
||||
|
||||
ASSERT_EQ(run(R"(
|
||||
.const
|
||||
0: "Hello"
|
||||
1: "Hello"
|
||||
.func 0
|
||||
pushc 0
|
||||
pushc 1
|
||||
eq
|
||||
ret
|
||||
)").to_integer(-1), 1);
|
||||
|
||||
ASSERT_EQ(run(R"(
|
||||
.const
|
||||
0: "Hello"
|
||||
.func 0
|
||||
pushc 0
|
||||
pushi 1
|
||||
eq
|
||||
ret
|
||||
)").to_integer(-1), 0);
|
||||
}
|
||||
|
||||
TEST(VM, LocalVariables)
|
||||
{
|
||||
VM vm = run(R"(
|
||||
.func 0
|
||||
pushv 2 ; local a, b
|
||||
pushi 3 ; a = 3
|
||||
set 0
|
||||
pushi 4 ; b = 4
|
||||
set 1
|
||||
dupv 0 ; return a
|
||||
ret
|
||||
)");
|
||||
|
||||
ASSERT_EQ(vm.stack_sz(), 1);
|
||||
ASSERT_EQ(vm.to_integer(-1), 3);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
44
src/vm/value.cc
Normal file
44
src/vm/value.cc
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "value.hh"
|
||||
|
||||
#include "../common/overloaded.hh"
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
std::string type_name(Type type)
|
||||
{
|
||||
switch (type) {
|
||||
case Type::Nil: return "nil";
|
||||
case Type::Integer: return "integer";
|
||||
case Type::Float: return "float";
|
||||
case Type::String: return "string";
|
||||
case Type::Array: return "array";
|
||||
case Type::Table: return "table";
|
||||
case Type::Function: return "function";
|
||||
case Type::NativePointer: return "native pointer";
|
||||
case Type::COUNT: default: return "???";
|
||||
}
|
||||
}
|
||||
|
||||
Type Value::type() const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[](std::monostate) { return Type::Nil; },
|
||||
[](int32_t) { return Type::Integer; },
|
||||
[](float) { return Type::Float; },
|
||||
[](std::string const&) { return Type::String; },
|
||||
[](Function const&) { return Type::Function; },
|
||||
}, value_);
|
||||
}
|
||||
|
||||
std::string Value::to_string() const
|
||||
{
|
||||
return std::visit(overloaded {
|
||||
[](std::monostate) { return std::string("nil"); },
|
||||
[](int32_t i) { return std::to_string(i); },
|
||||
[](float f) { return std::to_string(f); },
|
||||
[](std::string const& s) { return s; },
|
||||
[](Function const& f) { return "@" + std::to_string(f.f_id); }
|
||||
}, value_);
|
||||
}
|
||||
|
||||
}
|
||||
53
src/vm/value.hh
Normal file
53
src/vm/value.hh
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef TYCHE_VALUE_HH
|
||||
#define TYCHE_VALUE_HH
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
using FunctionId = uint32_t;
|
||||
|
||||
enum class Type : uint8_t
|
||||
{
|
||||
Nil = 0, Integer, Float, String, Array, Table, Function, NativePointer, COUNT
|
||||
};
|
||||
|
||||
std::string type_name(Type type);
|
||||
|
||||
class Value {
|
||||
struct Function { FunctionId f_id; };
|
||||
|
||||
public:
|
||||
Value() : value_(std::monostate()) {}
|
||||
|
||||
static Value createNil() { return Value(std::monostate()); }
|
||||
static Value createInteger(int32_t v) { return Value(v); }
|
||||
static Value createFloat(float f) { return Value(f); }
|
||||
static Value createString(std::string const& str) { return Value(str); }
|
||||
static Value createFunctionId(FunctionId f_id) { return Value(Function { f_id }); }
|
||||
|
||||
static Value createFalse() { return createInteger(0); }
|
||||
static Value createTrue() { return createInteger(1); }
|
||||
static Value createIntegerFromBool(bool b) { return createInteger(b ? 1 : 0); }
|
||||
|
||||
[[nodiscard]] Type type() const;
|
||||
|
||||
[[nodiscard]] int32_t as_integer() const { return std::get<int32_t>(value_); }
|
||||
[[nodiscard]] float as_float() const { return std::get<float>(value_); }
|
||||
[[nodiscard]] std::string as_string() const { return std::get<std::string>(value_); }
|
||||
[[nodiscard]] FunctionId as_function_id() const { return std::get<Function>(value_).f_id; }
|
||||
|
||||
[[nodiscard]] std::string to_string() const;
|
||||
|
||||
private:
|
||||
using Internal = std::variant<std::monostate, int32_t, float, std::string, Function>;
|
||||
Internal value_;
|
||||
|
||||
explicit Value(Internal const& internal) : value_(internal) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_VALUE_HH
|
||||
208
src/vm/vm.cc
Normal file
208
src/vm/vm.cc
Normal file
@@ -0,0 +1,208 @@
|
||||
#include "vm.hh"
|
||||
|
||||
#include "vm_exceptions.hh"
|
||||
#include "expr.hh"
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
VM& VM::load_bytecode(ByteArray const& ba)
|
||||
{
|
||||
FunctionId f_id = code_.import_bytecode(ba);
|
||||
stack_.push(Value::createFunctionId(f_id));
|
||||
return *this;
|
||||
}
|
||||
|
||||
VM& VM::call(size_t n_params)
|
||||
{
|
||||
// TODO - parameters
|
||||
|
||||
Value f = stack_.pop();
|
||||
if (f.type() != Type::Function)
|
||||
throw VMTypeError(Type::Function, f.type());
|
||||
|
||||
loc_.emplace(f.as_function_id(), 0);
|
||||
stack_.push_fp();
|
||||
run_until_return();
|
||||
// stack_.pop_fp();
|
||||
loc_.pop();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
int32_t VM::to_integer(int index) const
|
||||
{
|
||||
Value i = stack_.at(index);
|
||||
if (i.type() == Type::Integer)
|
||||
return i.as_integer();
|
||||
if (i.type() == Type::Float)
|
||||
return (int32_t) i.as_float();
|
||||
throw VMTypeError(Type::Integer, i.type());
|
||||
}
|
||||
|
||||
float VM::to_float(int index) const
|
||||
{
|
||||
Value f = stack_.at(index);
|
||||
if (f.type() == Type::Float)
|
||||
return f.as_float();
|
||||
if (f.type() == Type::Integer)
|
||||
return (float) f.as_integer();
|
||||
throw VMTypeError(Type::Float, f.type());
|
||||
}
|
||||
|
||||
std::string VM::to_string(int index) const
|
||||
{
|
||||
Value i = stack_.at(index);
|
||||
assert_type(i, Type::String);
|
||||
return i.as_string();
|
||||
}
|
||||
|
||||
VM& VM::push_nil()
|
||||
{
|
||||
stack_.push(Value::createNil());
|
||||
return *this;
|
||||
}
|
||||
|
||||
VM& VM::push_integer(int32_t value)
|
||||
{
|
||||
stack_.push(Value::createInteger(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
VM& VM::push_float(float value)
|
||||
{
|
||||
stack_.push(Value::createFloat(value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
VM& VM::push_string(std::string const& str)
|
||||
{
|
||||
stack_.push(Value::createString(str));
|
||||
return *this;
|
||||
}
|
||||
|
||||
void VM::run_until_return()
|
||||
{
|
||||
size_t level = stack_.fp_level();
|
||||
|
||||
while (stack_.fp_level() >= level)
|
||||
step();
|
||||
}
|
||||
|
||||
void VM::step()
|
||||
{
|
||||
|
||||
Operation op = code_.operation(loc_.top());
|
||||
switch (op.instruction) {
|
||||
|
||||
//
|
||||
// stack management
|
||||
//
|
||||
|
||||
case Instruction::PushInt8:
|
||||
case Instruction::PushInt16:
|
||||
case Instruction::PushInt32:
|
||||
push_integer(op.operator_);
|
||||
break;
|
||||
|
||||
case Instruction::PushConstant8:
|
||||
case Instruction::PushConstant16:
|
||||
case Instruction::PushConstant32: {
|
||||
auto cnst = code_.bytecode().get_constant(op.operator_);
|
||||
if (auto f = std::get_if<float>(&cnst))
|
||||
push_float(*f);
|
||||
else if (auto s = std::get_if<std::string>(&cnst))
|
||||
push_string(*s);
|
||||
else
|
||||
throw std::logic_error("Shouldn't get here");
|
||||
break;
|
||||
}
|
||||
|
||||
case Instruction::PushFunction8:
|
||||
case Instruction::PushFunction16:
|
||||
case Instruction::PushFunction32:
|
||||
stack_.push(Value::createFunctionId(op.operator_));
|
||||
break;
|
||||
|
||||
case Instruction::Pop:
|
||||
stack_.pop();
|
||||
break;
|
||||
|
||||
case Instruction::Duplicate:
|
||||
stack_.push(stack_.peek());
|
||||
break;
|
||||
|
||||
//
|
||||
// variables
|
||||
//
|
||||
|
||||
case Instruction::PushValues8:
|
||||
case Instruction::PushValues16:
|
||||
case Instruction::PushValues32:
|
||||
for (int i = 0; i < op.operator_; ++i)
|
||||
push_nil();
|
||||
break;
|
||||
|
||||
case Instruction::SetValue8:
|
||||
case Instruction::SetValue16:
|
||||
case Instruction::SetValue32: {
|
||||
Value a = stack_.pop();
|
||||
stack_.set(op.operator_, a);
|
||||
break;
|
||||
}
|
||||
|
||||
case Instruction::DuplicateValue8:
|
||||
case Instruction::DuplicateValue16:
|
||||
case Instruction::DuplicateValue32: {
|
||||
Value a = stack_.at(op.operator_);
|
||||
stack_.push(a);
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// logical/arithmetic
|
||||
//
|
||||
|
||||
#define BIN_OP(op) { Value a = stack_.pop(); Value b = stack_.pop(); stack_.push(binary_operation(a, b, BinaryOperationType::op)); }
|
||||
case Instruction::Sum: BIN_OP(Sum) break;
|
||||
case Instruction::Subtract: BIN_OP(Subtraction) break;
|
||||
case Instruction::Multiply: BIN_OP(Multiplication) break;
|
||||
case Instruction::Divide: BIN_OP(Division) break;
|
||||
case Instruction::DivideInt: BIN_OP(IntegerDivision) break;
|
||||
case Instruction::Equals: BIN_OP(Equality) break;
|
||||
case Instruction::NotEquals: BIN_OP(Inequality) break;
|
||||
case Instruction::LessThan: BIN_OP(LessThan) break;
|
||||
case Instruction::LessThanEq: BIN_OP(LessThanOrEquals) break;
|
||||
case Instruction::GreaterThan: BIN_OP(GreaterThan) break;
|
||||
case Instruction::GreaterThanEq: BIN_OP(GreaterThanOrEquals) break;
|
||||
case Instruction::And: BIN_OP(BitwiseAnd) break;
|
||||
case Instruction::Or: BIN_OP(BitwiseOr) break;
|
||||
case Instruction::Xor: BIN_OP(BitwiseXor) break;
|
||||
case Instruction::Power: BIN_OP(Power) break;
|
||||
case Instruction::ShiftLeft: BIN_OP(ShiftLeft) break;
|
||||
case Instruction::ShiftRight: BIN_OP(ShiftRight) break;
|
||||
case Instruction::Modulo: BIN_OP(Modulo) break;
|
||||
#undef BIN_OP
|
||||
|
||||
//
|
||||
// function operations
|
||||
//
|
||||
|
||||
case Instruction::Return: {
|
||||
Value v = stack_.pop();
|
||||
stack_.pop_fp();
|
||||
stack_.push(v);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw VMInvalidOpcode((uint8_t) op.instruction);
|
||||
}
|
||||
loc_.top() = op.next_location;
|
||||
}
|
||||
|
||||
void VM::assert_type(Value const& val, Type type)
|
||||
{
|
||||
if (val.type() != type)
|
||||
throw VMTypeError(type, val.type());
|
||||
}
|
||||
|
||||
} // tyche
|
||||
51
src/vm/vm.hh
Normal file
51
src/vm/vm.hh
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef TYCHE_VM_HH
|
||||
#define TYCHE_VM_HH
|
||||
|
||||
#include "code.hh"
|
||||
#include "location.hh"
|
||||
#include "stack.hh"
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
class VM {
|
||||
public:
|
||||
VM& load_bytecode(ByteArray const& ba);
|
||||
|
||||
VM& call(size_t n_params);
|
||||
|
||||
[[nodiscard]] bool is_nil(int index) const { return stack_.at(index).type() == Type::Nil; }
|
||||
[[nodiscard]] bool is_integer(int index) const { return stack_.at(index).type() == Type::Integer; }
|
||||
[[nodiscard]] bool is_float(int index) const { return stack_.at(index).type() == Type::Float; }
|
||||
[[nodiscard]] bool is_string(int index) const { return stack_.at(index).type() == Type::String; }
|
||||
[[nodiscard]] bool is_array(int index) const { return stack_.at(index).type() == Type::Array; }
|
||||
[[nodiscard]] bool is_table(int index) const { return stack_.at(index).type() == Type::Table; }
|
||||
[[nodiscard]] bool is_function(int index) const { return stack_.at(index).type() == Type::Function; }
|
||||
[[nodiscard]] bool is_native_pointer(int index) const { return stack_.at(index).type() == Type::NativePointer; }
|
||||
|
||||
[[nodiscard]] size_t stack_sz() const { return stack_.size(); }
|
||||
|
||||
VM& push_nil();
|
||||
VM& push_integer(int32_t value);
|
||||
VM& push_float(float value);
|
||||
VM& push_string(std::string const& string);
|
||||
|
||||
[[nodiscard]] int32_t to_integer(int index) const;
|
||||
[[nodiscard]] float to_float(int index) const;
|
||||
[[nodiscard]] std::string to_string(int index) const;
|
||||
|
||||
[[nodiscard]] std::string debug_stack() const { return stack_.debug(); }
|
||||
|
||||
private:
|
||||
void run_until_return();
|
||||
void step();
|
||||
|
||||
static void assert_type(Value const& val, Type type);
|
||||
|
||||
Stack stack_;
|
||||
Code code_;
|
||||
std::stack<Location> loc_;
|
||||
};
|
||||
|
||||
} // tyche
|
||||
|
||||
#endif //TYCHE_VM_HH
|
||||
49
src/vm/vm_exceptions.hh
Normal file
49
src/vm/vm_exceptions.hh
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef TYCHE_VM_EXCEPTIONS_HH
|
||||
#define TYCHE_VM_EXCEPTIONS_HH
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "expr.hh"
|
||||
|
||||
namespace tyche::vm {
|
||||
|
||||
class VMRuntimeError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
explicit VMRuntimeError(std::string const& str) : std::runtime_error(str.c_str()) {}
|
||||
};
|
||||
|
||||
class VMStackUnderflow : public VMRuntimeError
|
||||
{
|
||||
public:
|
||||
explicit VMStackUnderflow() : VMRuntimeError("Stack underflow") {}
|
||||
};
|
||||
|
||||
class VMStackOutOfRange : public VMRuntimeError
|
||||
{
|
||||
public:
|
||||
explicit VMStackOutOfRange() : VMRuntimeError("Item does not exist in stack") {}
|
||||
};
|
||||
|
||||
class VMTypeError : public VMRuntimeError
|
||||
{
|
||||
public:
|
||||
explicit VMTypeError(Type expected, Type found) : VMRuntimeError("Type error (expected " + type_name(expected) + ", found " + type_name(found) + ")") {}
|
||||
};
|
||||
|
||||
class VMInvalidOpcode : public VMRuntimeError
|
||||
{
|
||||
public:
|
||||
explicit VMInvalidOpcode(uint8_t opcode) : VMRuntimeError("Invalid opcode " + std::to_string(opcode)) {}
|
||||
};
|
||||
|
||||
class VMInvalidOperation : public VMRuntimeError
|
||||
{
|
||||
public:
|
||||
explicit VMInvalidOperation(BinaryOperationType op, Type type1, Type type2) : VMRuntimeError("Invalid binary operation for types " + type_name(type1) + " and " + type_name(type2)) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //TYCHE_VM_EXCEPTIONS_HH
|
||||
Reference in New Issue
Block a user