From: Brendan Hansen Date: Mon, 5 Sep 2022 00:28:09 +0000 (-0500) Subject: restructured folders as this project has grown X-Git-Url: https://git.brendanfh.com/?a=commitdiff_plain;h=f18f6bb1d5dead6b3f20c757cc969a5334fb6d0f;p=onyx.git restructured folders as this project has grown --- diff --git a/build.sh b/build.sh index 5f529668..7b685b80 100755 --- a/build.sh +++ b/build.sh @@ -1,132 +1,38 @@ #!/bin/sh -# The base path for the installation. -# This is typcially /usr or /usr/local. -INSTALL_DIR="/usr" - -# Where the core libraries for Onyx will go. -CORE_DIR="$INSTALL_DIR/share/onyx" - -# Where the onyx executable will be placed. -BIN_DIR="$INSTALL_DIR/bin" - -# The compiler to use. Only GCC and TCC have been tested. -CC='gcc' - -# The architecture of your system. If your not sure, leave this alone. -ARCH="$(uname -m)" - -# RUNTIME_LIBRARY="ovmwasm" -RUNTIME_LIBRARY="wasmer" - -# Comment this line if you do not want the above library installed, -# and do not with to have the Onyx runtime. -ENABLE_BUNDLING_WASM_RUNTIME=1 - -# Where the Wasmer library files can be found. -# They are bundled with the project, but if a different version is available, these can be changed. -WASMER_INCLUDE_DIR="$(pwd)/lib/common/include" -WASMER_LIBRARY_DIR="$(pwd)/lib/linux_$ARCH/lib" - -# Where the intermediate build files go. -BUILD_DIR='./build' - -# Temporary flag -ENABLE_DEBUG_INFO=0 - - - +. ./settings.sh echo "Installing core libs" sudo mkdir -p "$CORE_DIR" sudo cp -r ./core/ "$CORE_DIR" -# This is a development feature to allow for quickly reinstalling core libraries -# without have to recompile the entire compiler -[ "$1" = "core" ] && exit 0 +if [ ! -f "$CORE_DIR/lib/lib$RUNTIME_LIBRARY.so" ] || true; then + echo "Copying lib$RUNTIME_LIBRARY to $CORE_DIR/lib (first install)" + sudo mkdir -p "$CORE_DIR/lib" + sudo mkdir -p "$CORE_DIR/include" -C_FILES="onyx astnodes builtins checker clone doc entities errors lex parser symres types utils wasm_emit" -LIBS= -INCLUDES="-I./include" + sudo cp "$WASMER_LIBRARY_DIR/lib$RUNTIME_LIBRARY.so" "$CORE_DIR/lib/lib$RUNTIME_LIBRARY.so" -WARNINGS='-Wimplicit -Wmisleading-indentation -Wparentheses -Wsequence-point -Wreturn-type -Wshift-negative-value -Wunused-but-set-parameter -Wunused-but-set-variable -Wunused-function -Wunused-label -Wmaybe-uninitialized -Wsign-compare -Wstrict-overflow -Wduplicated-branches -Wduplicated-cond -Wtrigraphs -Waddress -Wlogical-op' - -TARGET="./bin/onyx" -if [ "$1" = "debug" ]; then - FLAGS="$WARNINGS -g3" -else - FLAGS="$WARNINGS -O3" -fi - -if [ "$RUNTIME_LIBRARY" = "ovmwasm" ]; then - FLAGS="$FLAGS -DUSE_OVM_DEBUGGER" + sudo cp "shared/include/onyx_library.h" "$CORE_DIR/include/onyx_library.h" + sudo cp "$WASMER_INCLUDE_DIR/wasm.h" "$CORE_DIR/include/wasm.h" fi -if [ "$ENABLE_DEBUG_INFO" = "1" ]; then - FLAGS="$FLAGS -DENABLE_DEBUG_INFO" -fi - - -if [ ! -z "$ENABLE_BUNDLING_WASM_RUNTIME" ]; then - C_FILES="$C_FILES wasm_runtime" - FLAGS="$FLAGS -DENABLE_RUN_WITH_WASMER" - LIBS="$LIBS -L$CORE_DIR/lib -l$RUNTIME_LIBRARY -Wl,-rpath=$CORE_DIR/lib:./ -lpthread -ldl -lm" - INCLUDES="$INCLUDES -I$WASMER_INCLUDE_DIR" - - if [ ! -f "$CORE_DIR/lib/lib$RUNTIME_LIBRARY.so" ] || true; then - echo "Copying lib$RUNTIME_LIBRARY to $CORE_DIR/lib (first install)" - - sudo mkdir -p "$CORE_DIR/lib" - sudo mkdir -p "$CORE_DIR/include" - - sudo cp "$WASMER_LIBRARY_DIR/lib$RUNTIME_LIBRARY.so" "$CORE_DIR/lib/lib$RUNTIME_LIBRARY.so" - - sudo cp "include/onyx_library.h" "$CORE_DIR/include/onyx_library.h" - sudo cp "lib/common/include/wasm.h" "$CORE_DIR/include/wasm.h" - fi -fi - -mkdir -p "$BUILD_DIR" - -compile() { - for file in $C_FILES ; do - echo "Compiling $file.c" - $CC -o $BUILD_DIR/$file.o \ - $FLAGS \ - "-DCORE_INSTALLATION=\"$CORE_DIR\"" \ - -c src/$file.c \ - $INCLUDES $LIBS - done - - echo "Linking $TARGET" - $CC -o $TARGET $FLAGS $(for file in $C_FILES ; do printf "$BUILD_DIR/%s.o " $file ; done) $LIBS - - echo "Removing object files" - for file in $C_FILES ; do rm -f "$BUILD_DIR/$file".o 2>/dev/null ; done -} - -compile -echo "Installing onyx executable" -sudo mkdir -p "$BIN_DIR" -sudo cp ./bin/onyx "$BIN_DIR/onyx" - -if [ ! -z "$ENABLE_BUNDLING_WASM_RUNTIME" ]; then - C_FILES="onyxrun wasm_runtime" - TARGET="./bin/onyx-run" - - compile - echo "Installing onyxrun executable" - sudo cp ./bin/onyx-run "$BIN_DIR/onyx-run" +# This is a development feature to allow for quickly reinstalling core libraries +# without have to recompile the entire compiler +[ "$1" = "core" ] && exit 0 - $CC -shared -fpic -I include -I lib/common/include src/onyx_runtime.c -o onyx_runtime.so -lpthread - sudo mv "./onyx_runtime.so" "$CORE_DIR/lib/onyx_runtime.so" +sudo cp ./bin/onyx-pkg "$BIN_DIR/onyx-pkg" +sudo mkdir -p "$CORE_DIR/tools" +sudo cp ./scripts/onyx-pkg.onyx "$CORE_DIR/tools" +cd compiler +./build.sh +cd .. - sudo cp ./bin/onyx-pkg "$BIN_DIR/onyx-pkg" - sudo mkdir -p "$CORE_DIR/tools" - sudo cp ./scripts/onyx-pkg.onyx "$CORE_DIR/tools" -fi +cd runtime +./build.sh +cd .. # Otherwise the prompt ends on the same line printf "\n" diff --git a/compiler/build.sh b/compiler/build.sh new file mode 100755 index 00000000..2bf9a64f --- /dev/null +++ b/compiler/build.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +. ../settings.sh + +# Temporary flag +ENABLE_DEBUG_INFO=1 + +C_FILES="onyx astnodes builtins checker clone doc entities errors lex parser symres types utils wasm_emit wasm_runtime " +LIBS="-L$CORE_DIR/lib -l$RUNTIME_LIBRARY -Wl,-rpath=$CORE_DIR/lib:./ -lpthread -ldl -lm" +INCLUDES="-I./include -I../shared/include -I../lib/common/include" + +WARNINGS='-Wimplicit -Wmisleading-indentation -Wparentheses -Wsequence-point -Wreturn-type -Wshift-negative-value -Wunused-but-set-parameter -Wunused-but-set-variable -Wunused-function -Wunused-label -Wmaybe-uninitialized -Wsign-compare -Wstrict-overflow -Wduplicated-branches -Wduplicated-cond -Wtrigraphs -Waddress -Wlogical-op' + +if [ "$1" = "debug" ]; then + FLAGS="$WARNINGS -g3" +else + FLAGS="$WARNINGS -O3" +fi + +if [ "$RUNTIME_LIBRARY" = "ovmwasm" ]; then + FLAGS="$FLAGS -DUSE_OVM_DEBUGGER" +fi + +if [ "$ENABLE_DEBUG_INFO" = "1" ]; then + FLAGS="$FLAGS -DENABLE_DEBUG_INFO" +fi + +FLAGS="$FLAGS -DENABLE_RUN_WITH_WASMER" + +sudo mkdir -p "$BIN_DIR" + +echo "Compiling onyx..." +$CC -o "../bin/onyx" \ + $FLAGS \ + "-DCORE_INSTALLATION=\"$CORE_DIR\"" \ + $INCLUDES \ + $(echo "$C_FILES" | sed 's/ /\n/g;s/\([a-zA-Z_0-9]*\)\n/src\/\1.c\n/g;s/\n/ /g') \ + $LIBS + +echo "Installing onyx executable" +sudo cp "../bin/onyx" "$BIN_DIR/onyx" + +C_FILES="onyxrun wasm_runtime " + +echo "Compiling onyx-run..." +$CC -o "../bin/onyx-run" \ + $FLAGS \ + "-DCORE_INSTALLATION=\"CORE_DIR\"" \ + $INCLUDES \ + $(echo "$C_FILES" | sed 's/ /\n/g;s/\([a-zA-Z_0-9]*\)\n/src\/\1.c\n/g;s/\n/ /g') \ + $LIBS + +echo "Installing onyx-run executable" +sudo cp "../bin/onyx-run" "$BIN_DIR/onyx-run" diff --git a/compiler/include/astnodes.h b/compiler/include/astnodes.h new file mode 100644 index 00000000..0052d233 --- /dev/null +++ b/compiler/include/astnodes.h @@ -0,0 +1,1841 @@ +#ifndef ONYXASTNODES_H +#define ONYXASTNODES_H + +#include "stb_ds.h" +#include "lex.h" +#include "types.h" +#include "errors.h" + +#define AST_NODES \ + NODE(Node) \ + NODE(Typed) \ + \ + NODE(NamedValue) \ + NODE(BinaryOp) \ + NODE(UnaryOp) \ + NODE(NumLit) \ + NODE(StrLit) \ + NODE(Local) \ + NODE(Call) \ + NODE(Argument) \ + NODE(AddressOf) \ + NODE(Dereference) \ + NODE(Subscript) \ + NODE(FieldAccess) \ + NODE(UnaryFieldAccess) \ + NODE(SizeOf) \ + NODE(AlignOf) \ + NODE(FileContents) \ + NODE(StructLiteral) \ + NODE(ArrayLiteral) \ + NODE(RangeLiteral) \ + NODE(Compound) \ + NODE(IfExpression) \ + NODE(DoBlock) \ + \ + NODE(DirectiveSolidify) \ + NODE(DirectiveError) \ + NODE(DirectiveAddOverload) \ + NODE(DirectiveOperator) \ + NODE(DirectiveExport) \ + NODE(DirectiveDefined) \ + NODE(DirectiveInit) \ + NODE(DirectiveLibrary) \ + NODE(DirectiveRemove) \ + \ + NODE(Return) \ + NODE(Jump) \ + NODE(Use) \ + \ + NODE(Block) \ + NODE(IfWhile) \ + NODE(For) \ + NODE(Defer) \ + NODE(SwitchCase) \ + NODE(Switch) \ + \ + NODE(Type) \ + NODE(BasicType) \ + NODE(PointerType) \ + NODE(FunctionType) \ + NODE(ArrayType) \ + NODE(SliceType) \ + NODE(DynArrType) \ + NODE(VarArgType) \ + NODE(StructType) \ + NODE(StructMember) \ + NODE(PolyStructType) \ + NODE(PolyStructParam) \ + NODE(PolyCallType) \ + NODE(EnumType) \ + NODE(EnumValue) \ + NODE(TypeAlias) \ + NODE(TypeRawAlias) \ + NODE(CompoundType) \ + NODE(TypeOf) \ + NODE(DistinctType) \ + \ + NODE(Binding) \ + NODE(Alias) \ + NODE(Injection) \ + NODE(MemRes) \ + NODE(Include) \ + NODE(UsePackage) \ + NODE(Global) \ + NODE(Param) \ + NODE(Function) \ + NODE(OverloadedFunction) \ + NODE(Interface) \ + NODE(Constraint) \ + \ + NODE(PolyParam) \ + NODE(PolySolution) \ + NODE(SolidifiedFunction) \ + NODE(PolyProc) \ + NODE(PolyQuery) \ + \ + NODE(Note) \ + NODE(CallSite) \ + \ + NODE(CodeBlock) \ + NODE(DirectiveInsert) \ + NODE(Macro) \ + \ + NODE(ForeignBlock) \ + \ + NODE(Package) \ + \ + NODE(ZeroValue) + +#define NODE(name) typedef struct Ast ## name Ast ## name; +AST_NODES +#undef NODE + +typedef struct Package Package; + +typedef struct Scope { + u64 id; + struct Scope *parent; + OnyxFilePos created_at; + char* name; + Table(AstNode *) symbols; +} Scope; + + +typedef enum AstKind { + Ast_Kind_Error, + Ast_Kind_Package, + Ast_Kind_Load_File, + Ast_Kind_Load_Path, + Ast_Kind_Load_All, + Ast_Kind_Library_Path, + Ast_Kind_Memres, + + Ast_Kind_Binding, + Ast_Kind_Alias, + Ast_Kind_Injection, + Ast_Kind_Function, + Ast_Kind_Overloaded_Function, + Ast_Kind_Polymorphic_Proc, + Ast_Kind_Polymorph_Query, + Ast_Kind_Interface, + Ast_Kind_Constraint, + Ast_Kind_Constraint_Sentinel, + Ast_Kind_Block, + Ast_Kind_Local, + Ast_Kind_Global, + Ast_Kind_Symbol, + + Ast_Kind_Unary_Op, + Ast_Kind_Binary_Op, + + Ast_Kind_Compound, + Ast_Kind_Named_Value, + + Ast_Kind_Type_Start, + Ast_Kind_Type, + Ast_Kind_Basic_Type, + Ast_Kind_Pointer_Type, + Ast_Kind_Function_Type, + Ast_Kind_Array_Type, + Ast_Kind_Slice_Type, + Ast_Kind_DynArr_Type, + Ast_Kind_VarArg_Type, + Ast_Kind_Struct_Type, + Ast_Kind_Poly_Struct_Type, + Ast_Kind_Poly_Call_Type, + Ast_Kind_Enum_Type, + Ast_Kind_Type_Alias, + Ast_Kind_Type_Raw_Alias, + Ast_Kind_Type_Compound, + Ast_Kind_Typeof, + Ast_Kind_Distinct_Type, + Ast_Kind_Type_End, + + Ast_Kind_Struct_Member, + Ast_Kind_Enum_Value, + + Ast_Kind_NumLit, + Ast_Kind_StrLit, + Ast_Kind_Param, + Ast_Kind_Argument, + Ast_Kind_Call, + Ast_Kind_Intrinsic_Call, + Ast_Kind_Return, + Ast_Kind_Address_Of, + Ast_Kind_Dereference, + Ast_Kind_Subscript, + Ast_Kind_Slice, + Ast_Kind_Field_Access, + Ast_Kind_Unary_Field_Access, + Ast_Kind_Pipe, + Ast_Kind_Method_Call, + Ast_Kind_Range_Literal, + Ast_Kind_Size_Of, + Ast_Kind_Align_Of, + Ast_Kind_File_Contents, + Ast_Kind_Struct_Literal, + Ast_Kind_Array_Literal, + Ast_Kind_If_Expression, + + Ast_Kind_If, + Ast_Kind_For, + Ast_Kind_While, + Ast_Kind_Jump, + Ast_Kind_Use, + Ast_Kind_Defer, + Ast_Kind_Switch, + Ast_Kind_Switch_Case, + + Ast_Kind_Directive_Solidify, + Ast_Kind_Static_If, + Ast_Kind_Directive_Error, + Ast_Kind_Directive_Add_Overload, + Ast_Kind_Directive_Operator, + Ast_Kind_Directive_Export, + Ast_Kind_Directive_Defined, + Ast_Kind_Directive_Init, + Ast_Kind_Directive_Library, + Ast_Kind_Directive_Remove, + Ast_Kind_Call_Site, + + Ast_Kind_Code_Block, + Ast_Kind_Directive_Insert, + Ast_Kind_Macro, + Ast_Kind_Do_Block, + + Ast_Kind_Foreign_Block, + + Ast_Kind_Zero_Value, + + Ast_Kind_Note, + + Ast_Kind_Count +} AstKind; + +// NOTE: Some of these flags will overlap since there are +// only 32-bits of flags to play with +typedef enum AstFlags { + // Top-level flags + Ast_Flag_Const = BH_BIT(1), + Ast_Flag_Comptime = BH_BIT(2), + Ast_Flag_Private_Package = BH_BIT(3), + Ast_Flag_Private_File = BH_BIT(4), + + // Function flags + Ast_Flag_Function_Used = BH_BIT(5), + + // Expression flags + Ast_Flag_Expr_Ignored = BH_BIT(6), + Ast_Flag_Address_Taken = BH_BIT(7), + + // Type flags + Ast_Flag_Type_Is_Resolved = BH_BIT(8), + + Ast_Flag_No_Clone = BH_BIT(9), + + Ast_Flag_Cannot_Take_Addr = BH_BIT(10), + + // HACK: NullProcHack + Ast_Flag_Proc_Is_Null = BH_BIT(11), + + Ast_Flag_From_Polymorphism = BH_BIT(12), + + Ast_Flag_Incomplete_Body = BH_BIT(13), + + Ast_Flag_Array_Literal_Typed = BH_BIT(14), + + Ast_Flag_Has_Been_Symres = BH_BIT(15), + + Ast_Flag_Has_Been_Checked = BH_BIT(16), + + Ast_Flag_Static_If_Resolved = BH_BIT(17), + + Ast_Flag_Symbol_Invisible = BH_BIT(18), + + Ast_Flag_Header_Check_No_Error = BH_BIT(19), + + Ast_Flag_Decl_Followed_By_Init = BH_BIT(20), + + Ast_Flag_Param_Symbol_Dirty = BH_BIT(21), + + Ast_Flag_Dead = BH_BIT(22), +} AstFlags; + +typedef enum UnaryOp { + Unary_Op_Negate, + Unary_Op_Not, + Unary_Op_Bitwise_Not, + Unary_Op_Cast, + Unary_Op_Auto_Cast, +} UnaryOp; + +typedef enum BinaryOp { + Binary_Op_Add = 0, + Binary_Op_Minus = 1, + Binary_Op_Multiply = 2, + Binary_Op_Divide = 3, + Binary_Op_Modulus = 4, + + Binary_Op_Equal = 5, + Binary_Op_Not_Equal = 6, + Binary_Op_Less = 7, + Binary_Op_Less_Equal = 8, + Binary_Op_Greater = 9, + Binary_Op_Greater_Equal = 10, + + Binary_Op_And = 11, + Binary_Op_Or = 12, + Binary_Op_Xor = 13, + Binary_Op_Shl = 14, + Binary_Op_Shr = 15, + Binary_Op_Sar = 16, + + Binary_Op_Bool_And = 17, + Binary_Op_Bool_Or = 18, + + Binary_Op_Assign_Start = 19, + Binary_Op_Assign = 20, + Binary_Op_Assign_Add = 21, + Binary_Op_Assign_Minus = 22, + Binary_Op_Assign_Multiply = 23, + Binary_Op_Assign_Divide = 24, + Binary_Op_Assign_Modulus = 25, + Binary_Op_Assign_And = 26, + Binary_Op_Assign_Or = 27, + Binary_Op_Assign_Xor = 28, + Binary_Op_Assign_Shl = 29, + Binary_Op_Assign_Shr = 30, + Binary_Op_Assign_Sar = 31, + Binary_Op_Assign_End = 32, + + Binary_Op_Pipe = 33, + Binary_Op_Range = 34, + Binary_Op_Method_Call = 35, + + Binary_Op_Subscript = 36, + Binary_Op_Subscript_Equals = 37, + Binary_Op_Ptr_Subscript = 38, + + Binary_Op_Count +} BinaryOp; + +extern const char *binaryop_string[Binary_Op_Count]; + +typedef enum OnyxIntrinsic { + ONYX_INTRINSIC_UNDEFINED, + + ONYX_INTRINSIC_MEMORY_SIZE, ONYX_INTRINSIC_MEMORY_GROW, + ONYX_INTRINSIC_MEMORY_COPY, ONYX_INTRINSIC_MEMORY_FILL, + + ONYX_INTRINSIC_INITIALIZE, + + ONYX_INTRINSIC_I32_CLZ, ONYX_INTRINSIC_I32_CTZ, ONYX_INTRINSIC_I32_POPCNT, + ONYX_INTRINSIC_I32_AND, ONYX_INTRINSIC_I32_OR, ONYX_INTRINSIC_I32_XOR, + ONYX_INTRINSIC_I32_SHL, ONYX_INTRINSIC_I32_SLR, ONYX_INTRINSIC_I32_SAR, + ONYX_INTRINSIC_I32_ROTL, ONYX_INTRINSIC_I32_ROTR, + + ONYX_INTRINSIC_I64_CLZ, ONYX_INTRINSIC_I64_CTZ, ONYX_INTRINSIC_I64_POPCNT, + ONYX_INTRINSIC_I64_AND, ONYX_INTRINSIC_I64_OR, ONYX_INTRINSIC_I64_XOR, + ONYX_INTRINSIC_I64_SHL, ONYX_INTRINSIC_I64_SLR, ONYX_INTRINSIC_I64_SAR, + ONYX_INTRINSIC_I64_ROTL, ONYX_INTRINSIC_I64_ROTR, + + ONYX_INTRINSIC_F32_ABS, ONYX_INTRINSIC_F32_SQRT, + ONYX_INTRINSIC_F32_CEIL, ONYX_INTRINSIC_F32_FLOOR, + ONYX_INTRINSIC_F32_TRUNC, ONYX_INTRINSIC_F32_NEAREST, + ONYX_INTRINSIC_F32_MIN, ONYX_INTRINSIC_F32_MAX, + ONYX_INTRINSIC_F32_COPYSIGN, + + ONYX_INTRINSIC_F64_ABS, ONYX_INTRINSIC_F64_SQRT, + ONYX_INTRINSIC_F64_CEIL, ONYX_INTRINSIC_F64_FLOOR, + ONYX_INTRINSIC_F64_TRUNC, ONYX_INTRINSIC_F64_NEAREST, + ONYX_INTRINSIC_F64_MIN, ONYX_INTRINSIC_F64_MAX, + ONYX_INTRINSIC_F64_COPYSIGN, + + + + ONYX_INTRINSIC_V128_CONST, + ONYX_INTRINSIC_I8X16_CONST, ONYX_INTRINSIC_I16X8_CONST, + ONYX_INTRINSIC_I32X4_CONST, ONYX_INTRINSIC_I64X2_CONST, + ONYX_INTRINSIC_F32X4_CONST, ONYX_INTRINSIC_F64X2_CONST, + ONYX_INTRINSIC_I8X16_SHUFFLE, + + ONYX_INTRINSIC_I8X16_EXTRACT_LANE_S, ONYX_INTRINSIC_I8X16_EXTRACT_LANE_U, ONYX_INTRINSIC_I8X16_REPLACE_LANE, + ONYX_INTRINSIC_I16X8_EXTRACT_LANE_S, ONYX_INTRINSIC_I16X8_EXTRACT_LANE_U, ONYX_INTRINSIC_I16X8_REPLACE_LANE, + ONYX_INTRINSIC_I32X4_EXTRACT_LANE, ONYX_INTRINSIC_I32X4_REPLACE_LANE, + ONYX_INTRINSIC_I64X2_EXTRACT_LANE, ONYX_INTRINSIC_I64X2_REPLACE_LANE, + ONYX_INTRINSIC_F32X4_EXTRACT_LANE, ONYX_INTRINSIC_F32X4_REPLACE_LANE, + ONYX_INTRINSIC_F64X2_EXTRACT_LANE, ONYX_INTRINSIC_F64X2_REPLACE_LANE, + + ONYX_INTRINSIC_I8X16_SWIZZLE, + ONYX_INTRINSIC_I8X16_SPLAT, ONYX_INTRINSIC_I16X8_SPLAT, + ONYX_INTRINSIC_I32X4_SPLAT, ONYX_INTRINSIC_I64X2_SPLAT, + ONYX_INTRINSIC_F32X4_SPLAT, ONYX_INTRINSIC_F64X2_SPLAT, + + ONYX_INTRINSIC_I8X16_EQ, ONYX_INTRINSIC_I8X16_NEQ, + ONYX_INTRINSIC_I8X16_LT_S, ONYX_INTRINSIC_I8X16_LT_U, + ONYX_INTRINSIC_I8X16_GT_S, ONYX_INTRINSIC_I8X16_GT_U, + ONYX_INTRINSIC_I8X16_LE_S, ONYX_INTRINSIC_I8X16_LE_U, + ONYX_INTRINSIC_I8X16_GE_S, ONYX_INTRINSIC_I8X16_GE_U, + + ONYX_INTRINSIC_I16X8_EQ, ONYX_INTRINSIC_I16X8_NEQ, + ONYX_INTRINSIC_I16X8_LT_S, ONYX_INTRINSIC_I16X8_LT_U, + ONYX_INTRINSIC_I16X8_GT_S, ONYX_INTRINSIC_I16X8_GT_U, + ONYX_INTRINSIC_I16X8_LE_S, ONYX_INTRINSIC_I16X8_LE_U, + ONYX_INTRINSIC_I16X8_GE_S, ONYX_INTRINSIC_I16X8_GE_U, + + ONYX_INTRINSIC_I32X4_EQ, ONYX_INTRINSIC_I32X4_NEQ, + ONYX_INTRINSIC_I32X4_LT_S, ONYX_INTRINSIC_I32X4_LT_U, + ONYX_INTRINSIC_I32X4_GT_S, ONYX_INTRINSIC_I32X4_GT_U, + ONYX_INTRINSIC_I32X4_LE_S, ONYX_INTRINSIC_I32X4_LE_U, + ONYX_INTRINSIC_I32X4_GE_S, ONYX_INTRINSIC_I32X4_GE_U, + + ONYX_INTRINSIC_F32X4_EQ, ONYX_INTRINSIC_F32X4_NEQ, + ONYX_INTRINSIC_F32X4_LT, ONYX_INTRINSIC_F32X4_GT, + ONYX_INTRINSIC_F32X4_LE, ONYX_INTRINSIC_F32X4_GE, + + ONYX_INTRINSIC_F64X2_EQ, ONYX_INTRINSIC_F64X2_NEQ, + ONYX_INTRINSIC_F64X2_LT, ONYX_INTRINSIC_F64X2_GT, + ONYX_INTRINSIC_F64X2_LE, ONYX_INTRINSIC_F64X2_GE, + + ONYX_INTRINSIC_V128_NOT, ONYX_INTRINSIC_V128_AND, ONYX_INTRINSIC_V128_ANDNOT, + ONYX_INTRINSIC_V128_OR, ONYX_INTRINSIC_V128_XOR, ONYX_INTRINSIC_V128_BITSELECT, + + ONYX_INTRINSIC_I8X16_ABS, ONYX_INTRINSIC_I8X16_NEG, + ONYX_INTRINSIC_I8X16_ANY_TRUE, ONYX_INTRINSIC_I8X16_ALL_TRUE, + ONYX_INTRINSIC_I8X16_BITMASK, + ONYX_INTRINSIC_I8X16_NARROW_I16X8_S, ONYX_INTRINSIC_I8X16_NARROW_I16X8_U, + ONYX_INTRINSIC_I8X16_SHL, ONYX_INTRINSIC_I8X16_SHR_S, ONYX_INTRINSIC_I8X16_SHR_U, + ONYX_INTRINSIC_I8X16_ADD, ONYX_INTRINSIC_I8X16_ADD_SAT_S, ONYX_INTRINSIC_I8X16_ADD_SAT_U, + ONYX_INTRINSIC_I8X16_SUB, ONYX_INTRINSIC_I8X16_SUB_SAT_S, ONYX_INTRINSIC_I8X16_SUB_SAT_U, + ONYX_INTRINSIC_I8X16_MIN_S, ONYX_INTRINSIC_I8X16_MIN_U, + ONYX_INTRINSIC_I8X16_MAX_S, ONYX_INTRINSIC_I8X16_MAX_U, + ONYX_INTRINSIC_I8X16_AVGR_U, + + ONYX_INTRINSIC_I16X8_ABS, ONYX_INTRINSIC_I16X8_NEG, + ONYX_INTRINSIC_I16X8_ANY_TRUE, ONYX_INTRINSIC_I16X8_ALL_TRUE, + ONYX_INTRINSIC_I16X8_BITMASK, + ONYX_INTRINSIC_I16X8_NARROW_I32X4_S, ONYX_INTRINSIC_I16X8_NARROW_I32X4_U, + ONYX_INTRINSIC_I16X8_WIDEN_LOW_I8X16_S, ONYX_INTRINSIC_I16X8_WIDEN_HIGH_I8X16_S, + ONYX_INTRINSIC_I16X8_WIDEN_LOW_I8X16_U, ONYX_INTRINSIC_I16X8_WIDEN_HIGH_I8X16_U, + ONYX_INTRINSIC_I16X8_SHL, ONYX_INTRINSIC_I16X8_SHR_S, ONYX_INTRINSIC_I16X8_SHR_U, + ONYX_INTRINSIC_I16X8_ADD, ONYX_INTRINSIC_I16X8_ADD_SAT_S, ONYX_INTRINSIC_I16X8_ADD_SAT_U, + ONYX_INTRINSIC_I16X8_SUB, ONYX_INTRINSIC_I16X8_SUB_SAT_S, ONYX_INTRINSIC_I16X8_SUB_SAT_U, + ONYX_INTRINSIC_I16X8_MUL, + ONYX_INTRINSIC_I16X8_MIN_S, ONYX_INTRINSIC_I16X8_MIN_U, + ONYX_INTRINSIC_I16X8_MAX_S, ONYX_INTRINSIC_I16X8_MAX_U, + ONYX_INTRINSIC_I16X8_AVGR_U, + + ONYX_INTRINSIC_I32X4_ABS, ONYX_INTRINSIC_I32X4_NEG, + ONYX_INTRINSIC_I32X4_ANY_TRUE, ONYX_INTRINSIC_I32X4_ALL_TRUE, + ONYX_INTRINSIC_I32X4_BITMASK, + ONYX_INTRINSIC_I32X4_WIDEN_LOW_I16X8_S, ONYX_INTRINSIC_I32X4_WIDEN_HIGH_I16X8_S, + ONYX_INTRINSIC_I32X4_WIDEN_LOW_I16X8_U, ONYX_INTRINSIC_I32X4_WIDEN_HIGH_I16X8_U, + ONYX_INTRINSIC_I32X4_SHL, ONYX_INTRINSIC_I32X4_SHR_S, ONYX_INTRINSIC_I32X4_SHR_U, + ONYX_INTRINSIC_I32X4_ADD, ONYX_INTRINSIC_I32X4_SUB, ONYX_INTRINSIC_I32X4_MUL, + ONYX_INTRINSIC_I32X4_MIN_S, ONYX_INTRINSIC_I32X4_MIN_U, + ONYX_INTRINSIC_I32X4_MAX_S, ONYX_INTRINSIC_I32X4_MAX_U, + + ONYX_INTRINSIC_I64X2_NEG, ONYX_INTRINSIC_I64X2_SHL, + ONYX_INTRINSIC_I64X2_SHR_S, ONYX_INTRINSIC_I64X2_SHR_U, + ONYX_INTRINSIC_I64X2_ADD, ONYX_INTRINSIC_I64X2_SUB, ONYX_INTRINSIC_I64X2_MUL, + + ONYX_INTRINSIC_F32X4_ABS, ONYX_INTRINSIC_F32X4_NEG, ONYX_INTRINSIC_F32X4_SQRT, + ONYX_INTRINSIC_F32X4_ADD, ONYX_INTRINSIC_F32X4_SUB, + ONYX_INTRINSIC_F32X4_MUL, ONYX_INTRINSIC_F32X4_DIV, + ONYX_INTRINSIC_F32X4_MIN, ONYX_INTRINSIC_F32X4_MAX, + + ONYX_INTRINSIC_F64X2_ABS, ONYX_INTRINSIC_F64X2_NEG, ONYX_INTRINSIC_F64X2_SQRT, + ONYX_INTRINSIC_F64X2_ADD, ONYX_INTRINSIC_F64X2_SUB, + ONYX_INTRINSIC_F64X2_MUL, ONYX_INTRINSIC_F64X2_DIV, + ONYX_INTRINSIC_F64X2_MIN, ONYX_INTRINSIC_F64X2_MAX, + + ONYX_INTRINSIC_I32X4_TRUNC_SAT_F32X4_S, + ONYX_INTRINSIC_I32X4_TRUNC_SAT_F32X4_U, + ONYX_INTRINSIC_F32X4_CONVERT_I32X4_S, + ONYX_INTRINSIC_F32X4_CONVERT_I32X4_U, + + ONYX_INTRINSIC_ATOMIC_WAIT, + ONYX_INTRINSIC_ATOMIC_NOTIFY, + + ONYX_INTRINSIC_ATOMIC_FENCE, + + ONYX_INTRINSIC_ATOMIC_LOAD, + ONYX_INTRINSIC_ATOMIC_STORE, + ONYX_INTRINSIC_ATOMIC_ADD, + ONYX_INTRINSIC_ATOMIC_SUB, + ONYX_INTRINSIC_ATOMIC_AND, + ONYX_INTRINSIC_ATOMIC_OR, + ONYX_INTRINSIC_ATOMIC_XOR, + ONYX_INTRINSIC_ATOMIC_XCHG, + ONYX_INTRINSIC_ATOMIC_CMPXCHG, +} OnyxIntrinsic; + +typedef enum CallingConvention { + CC_Undefined, + CC_Return_Wasm, + CC_Return_Stack +} CallingConvention; + +typedef enum JumpType { + Jump_Type_Break, + Jump_Type_Continue, + Jump_Type_Fallthrough, + Jump_Type_Return, + + Jump_Type_Count, +} JumpType; + +typedef enum ForLoopType { + For_Loop_Invalid, + For_Loop_Range, + For_Loop_Array, + For_Loop_Slice, + For_Loop_DynArr, + For_Loop_Iterator, +} ForLoopType; + +typedef enum ParamPassType { + Param_Pass_Invalid, + Param_Pass_By_Value, + Param_Pass_By_VarArg, + Param_Pass_By_Implicit_Pointer, +} ParamPassType; + +typedef enum VarArgKind { + VA_Kind_Not_VA, + VA_Kind_Typed, + VA_Kind_Any, + VA_Kind_Untyped, +} VarArgKind; + +typedef enum BlockRule { + Block_Rule_New_Scope = BH_BIT(1), + Block_Rule_Emit_Instructions = BH_BIT(2), + Block_Rule_Clear_Defer = BH_BIT(3), + Block_Rule_Override_Return = BH_BIT(4), + + Block_Rule_Normal = Block_Rule_New_Scope | Block_Rule_Clear_Defer | Block_Rule_Emit_Instructions, + Block_Rule_Macro = Block_Rule_New_Scope, + Block_Rule_Code_Block = Block_Rule_New_Scope, + Block_Rule_Do_Block = Block_Rule_New_Scope | Block_Rule_Clear_Defer | Block_Rule_Override_Return | Block_Rule_Emit_Instructions, +} BlockRule; + +typedef enum ConstraintCheckStatus { + Constraint_Check_Status_Queued, + Constraint_Check_Status_Failed, + Constraint_Check_Status_Success, +} ConstraintCheckStatus; + +typedef struct ConstraintContext { + bh_arr(AstConstraint *) constraints; + ConstraintCheckStatus *constraint_checks; + b32 constraints_met : 1; + b32 produce_errors : 1; +} ConstraintContext; + + +typedef struct Arguments Arguments; +struct Arguments { + bh_arr(AstTyped *) values; + bh_arr(AstNamedValue *) named_values; + + // How many arguments were not baked. + i32 used_argument_count; +}; + + +// Base Nodes +#define AstNode_base \ + AstKind kind; \ + u32 flags; \ + OnyxToken *token; \ + struct Entity* entity; \ + AstNode *next +struct AstNode { AstNode_base; }; + +// NOTE: 'type_node' is filled out by the parser. +// For a type such as '^^i32', the tree would look something like +// +// Typed Thing -> AstPointerType -> AstPointerType -> AstNode (symbol node) +// +// The symbol node will be filled out during symbol resolution. +// It will end up pointing to an AstBasicType that corresponds to +// the underlying type. +// +// 'type' is filled out afterwards. If it is NULL, the Type* is built +// using the type_node. This can then be used to typecheck this node. +#define AstTyped_base \ + AstNode_base; \ + AstType *type_node; \ + Type *type +struct AstTyped { AstTyped_base; }; + +// Expression Nodes +struct AstNamedValue { AstTyped_base; AstTyped* value; }; +struct AstUnaryOp { AstTyped_base; UnaryOp operation; AstTyped *expr; }; +struct AstStrLit { AstTyped_base; u64 data_id; u64 length; b32 is_cstr: 1; }; +struct AstLocal { AstTyped_base; }; +struct AstDereference { AstTyped_base; AstTyped *expr; }; +struct AstSizeOf { AstTyped_base; AstType *so_ast_type; Type *so_type; u64 size; }; +struct AstAlignOf { AstTyped_base; AstType *ao_ast_type; Type *ao_type; u64 alignment; }; +struct AstNumLit { + AstTyped_base; + union { + i32 i; + i64 l; + f32 f; + f64 d; + } value; + + b32 was_hex_literal : 1; +}; +struct AstBinaryOp { + AstTyped_base; + BinaryOp operation; + AstTyped *left, *right; + + Arguments *overload_args; // This is set of the binary operator is attempted to be overloaded + // but isnt successful yet. + AstBinaryOp *potential_substitute; +}; +struct AstAddressOf { + AstTyped_base; + AstTyped *expr; + + AstBinaryOp *potential_substitute; + + // This is set by check_method_call. + // If set, the address of node can be removed if the + // type checking does not pass for it. This makes it + // possible to have something that will optionally + // have its address taken, if necessary. + b32 can_be_removed : 1; +}; +struct AstArgument { + AstTyped_base; + + AstTyped *value; + + VarArgKind va_kind; + b32 is_baked : 1; + b32 pass_as_any : 1; +}; +struct AstSubscript { + AstTyped_base; + BinaryOp __unused_operation; // This will be set to Binary_Op_Subscript + AstTyped *addr; + AstTyped *expr; + + Arguments *overload_args; // This is set of the binary operator is attempted to be overloaded + // but isnt successful yet. + AstBinaryOp *potential_substitute; + + u64 elem_size; +}; +struct AstFieldAccess { + AstTyped_base; + AstTyped *expr; + u32 offset; + u32 idx; + char* field; // If token is null, defer to field +}; +struct AstFileContents { + AstTyped_base; + + AstTyped *filename_expr; + char *filename; // The parsed file name, with '\' sequences removed and resolved to a particular file if possible. + + u32 data_id, size; +}; +struct AstUnaryFieldAccess { + AstTyped_base; + + // "token" represents the field. This does not need an "offset" or an "index" + // because this node is meant to be replaced. +}; +struct AstStructLiteral { + AstTyped_base; + + AstTyped *stnode; + + Arguments args; +}; +struct AstArrayLiteral { + AstTyped_base; + + AstTyped *atnode; + + bh_arr(AstTyped *) values; +}; +struct AstRangeLiteral { + AstTyped_base; + + // HACK: Currently, range literals are parsed as binary operators, which means + // the first sizeof(AstBinaryOp) bytes of this structure must match that of + // AstBinaryOp, which means I need this dummy field here. + // - brendanfh 2020/12/23 + BinaryOp __unused_operation; + AstTyped *low, *high; + + // Currently, there is no way to specify this in the grammar, but it is set + // to be the initial value of the `step` member of the range structure in + // core/builtin.onyx. + // - brendanfh 2020/12/23 + AstTyped *step; +}; +struct AstCall { + AstTyped_base; + + Arguments args; + + union { + AstTyped *callee; + OnyxIntrinsic intrinsic; + }; + + VarArgKind va_kind; +}; +struct AstCompound { + AstTyped_base; + + bh_arr(AstTyped *) exprs; +}; +struct AstIfExpression { + AstTyped_base; + + AstTyped* cond; + AstTyped* true_expr; + AstTyped* false_expr; +}; +struct AstDoBlock { + AstTyped_base; + + AstBlock* block; +}; +struct AstZeroValue { + AstTyped_base; +}; + +struct AstDirectiveSolidify { + AstTyped_base; + + AstFunction* poly_proc; + bh_arr(AstPolySolution) known_polyvars; + + AstNode* resolved_proc; +}; + +// Intruction Node +struct AstReturn { AstNode_base; AstTyped* expr; }; +struct AstJump { AstNode_base; JumpType jump; u32 count; }; + +typedef struct QualifiedUse { + OnyxToken* symbol_name; + OnyxToken* as_name; +} QualifiedUse; +struct AstUse { + AstNode_base; + + AstTyped* expr; + bh_arr(QualifiedUse) only; +}; + +// Structure Nodes +struct AstBlock { + AstNode_base; + + AstNode *body; + + Scope *scope; + Scope *binding_scope; + BlockRule rules; + + u32 statement_idx; +}; +struct AstDefer { AstNode_base; AstNode *stmt; }; +struct AstFor { + AstNode_base; + + // NOTE: Stores the iteration variable + Scope *scope; + + // NOTE: Local defining the iteration variable + AstLocal* var; + + // NOTE: This can be any expression, but it is checked that + // it is of a type that we know how to iterate over. + AstTyped* iter; + ForLoopType loop_type; + + AstBlock *stmt; + + // ROBUSTNESS: This should be able to be set by a compile time variable at some point. + // But for now, this will do. + b32 by_pointer : 1; + b32 no_close : 1; +}; +struct AstIfWhile { + AstNode_base; + + Scope *scope; + AstNode* initialization; + + AstTyped *cond; + + AstBlock *true_stmt; + AstBlock *false_stmt; + + union { + // Used by Static_If + struct { + Scope *defined_in_scope; + bh_arr(struct Entity *) true_entities; + bh_arr(struct Entity *) false_entities; + }; + + // Used by While + b32 bottom_test; + }; +}; +typedef struct AstIfWhile AstIf; +typedef struct AstIfWhile AstWhile; + +typedef enum SwitchKind { + Switch_Kind_Integer, + Switch_Kind_Use_Equals, +} SwitchKind; + +typedef struct CaseToBlock { + AstTyped *original_value; + AstBinaryOp *comparison; + AstBlock *block; +} CaseToBlock; + +struct AstSwitchCase { + AstNode_base; + + // NOTE: All expressions that end up in this block + bh_arr(AstTyped *) values; + + AstBlock *block; + + b32 is_default: 1; // Could this be inferred by the values array being null? +}; + +struct AstSwitch { + AstNode_base; + + Scope *scope; + AstNode* initialization; + + AstTyped *expr; + + AstBlock *case_block; + + bh_arr(AstSwitchCase *) cases; + AstBlock *default_case; + + i32 yield_return_index; + SwitchKind switch_kind; + + union { + struct { + // NOTE: This is a mapping from the compile time known case value + // to a pointer to the block that it is associated with. + bh_imap case_map; + u64 min_case, max_case; + }; + + struct { + // NOTE: This is a mapping from the '==' binary op node to + // a pointer to the block that it is associated with. + bh_arr(CaseToBlock) case_exprs; + }; + }; +}; + +// Type Nodes +// NOTE: This node is very similar to an AstNode, just +// without the 'next' member. This is because types +// can't be in expressions so a 'next' thing +// doesn't make sense. +#define AstType_base \ + AstKind kind; \ + u32 flags; \ + OnyxToken* token; \ + struct Entity* entity; \ + void* __unused; \ + u64 type_id; \ + Type* type +struct AstType { AstType_base; }; + +struct AstBasicType { AstType_base; Type* basic_type; }; +struct AstPointerType { AstType_base; AstType* elem; }; +struct AstArrayType { AstType_base; AstType* elem; AstTyped *count_expr; }; +struct AstSliceType { AstType_base; AstType* elem; }; +struct AstDynArrType { AstType_base; AstType* elem; }; +struct AstVarArgType { AstType_base; AstType* elem; }; +struct AstFunctionType { + AstType_base; + + // Used in a rare case in solve_for_polymorphic_param_type. + Type *partial_function_type; + + AstType* return_type; + + u64 param_count; + AstType* params[]; +}; +struct AstStructType { + AstType_base; + char *name; + + bh_arr(AstStructMember *) members; + bh_arr(AstTyped *) meta_tags; + + // u32 min_alignment, min_size; + AstTyped *min_alignment_, *min_size_; + + // NOTE: Used to cache the actual type, since building + // a struct type is kind of complicated and should + // only happen once. + Type *stcache; + + // NOTE: This type is used when the structure has not been + // completely generated, but is a valid pointer to where the + // type will be generated to. + Type *pending_type; + + // NOTE: Used to store statically bound expressions in the struct. + Scope* scope; + + struct Entity* entity_type; + struct Entity* entity_defaults; + + OnyxFilePos polymorphic_error_loc; + ConstraintContext constraints; + + bh_arr(AstType *) polymorphic_argument_types; + bh_arr(AstPolySolution) polymorphic_arguments; + + b32 pending_type_is_valid : 1; + b32 is_union : 1; + b32 is_packed : 1; + b32 ready_to_build_type : 1; +}; +struct AstStructMember { + AstTyped_base; + AstTyped* initial_value; + + bh_arr(AstTyped *) meta_tags; + + b32 is_used : 1; +}; +struct AstPolyStructParam { + AstTyped_base; +}; +struct AstPolyStructType { + AstType_base; + char *name; + + Scope *scope; + bh_arr(AstPolyStructParam) poly_params; + Table(AstStructType *) concrete_structs; + + AstStructType* base_struct; +}; +struct AstPolyCallType { + AstType_base; + + AstType* callee; + + // NOTE: These nodes can be either AstTypes, or AstTyped expressions. + bh_arr(AstNode *) params; +}; +struct AstEnumType { + AstType_base; + char *name; + Scope *scope; + + AstType *backing; + Type *backing_type; + + bh_arr(AstEnumValue *) values; + + // NOTE: Used to cache the actual type for the same reason as above. + Type *etcache; + + b32 is_flags : 1; +}; +struct AstEnumValue { AstTyped_base; AstTyped* value; }; +struct AstTypeAlias { AstType_base; AstType* to; }; +struct AstTypeRawAlias { AstType_base; Type* to; }; +struct AstCompoundType { + AstType_base; + + bh_arr(AstType *) types; +}; +struct AstTypeOf { + AstType_base; + + AstTyped* expr; + Type* resolved_type; +}; +struct AstDistinctType { + AstType_base; + char *name; + AstType *base_type; + Type *dtcache; +}; + +// Top level nodes +struct AstBinding { AstTyped_base; AstNode* node; }; +struct AstAlias { AstTyped_base; AstTyped* alias; }; +struct AstInclude { AstNode_base; AstTyped* name_node; char* name; }; +struct AstInjection { + AstTyped_base; + AstTyped* full_loc; + AstTyped* to_inject; + AstTyped* dest; + OnyxToken *symbol; +}; +struct AstMemRes { + AstTyped_base; + AstTyped *initial_value; + + struct Entity *type_entity; + + b32 threadlocal : 1; + + // Set and used in the wasm emission. + u32 data_id; + u32 tls_offset; +}; +struct AstGlobal { + AstTyped_base; + + char* name; + + OnyxToken* foreign_module; + OnyxToken* foreign_name; +}; +struct AstParam { + // HACK CLEANUP: This does not need to have a local buried inside of it. + // Convert this to be AstTyped_base and pull the ParamPassType from AstLocal + // to here. - brendanfh 2020/09/18 + AstLocal *local; + AstTyped *default_value; + + VarArgKind vararg_kind; + b32 is_used : 1; + b32 use_processed : 1; + b32 is_baked : 1; +}; +typedef struct OverloadOption OverloadOption; +struct OverloadOption { + // This is u64 because padding will make it that anyway. + // Consider: would there be any practical benefit to having the precedence setting + // be a compile-time known value? as opposed to a hardcoded value? + u64 precedence; + + AstTyped* option; +}; + +struct AstOverloadedFunction { + AstTyped_base; + + bh_arr(OverloadOption) overloads; + + // CLEANUP: This is unused right now, but should be used to cache + // the complete set of overloads that can be used by an overloaded + // function. + bh_imap all_overloads; + + b32 locked : 1; + b32 only_local_functions : 1; +}; + +// @CLEANUP: Is this really necessary? +typedef struct InterfaceParam { + OnyxToken *value_token; + OnyxToken *type_token; +} InterfaceParam; + +typedef struct InterfaceConstraint { + AstTyped *expr; + AstType *expected_type_expr; + Type *expected_type; + + b32 invert_condition: 1; +} InterfaceConstraint; + +struct AstInterface { + AstTyped_base; + char *name; + + bh_arr(InterfaceParam) params; + bh_arr(InterfaceConstraint) exprs; +}; + +typedef enum ConstraintPhase { + Constraint_Phase_Waiting_To_Be_Queued = 0, + Constraint_Phase_Cloning_Expressions = 1, + Constraint_Phase_Checking_Expressions = 2, + Constraint_Phase_Finished = 3, +} ConstraintPhase; + +struct AstConstraint { + AstNode_base; + + ConstraintPhase phase; + + AstInterface *interface; + bh_arr(AstType *) type_args; + + ConstraintCheckStatus *report_status; + + Scope* scope; + bh_arr(InterfaceConstraint) exprs; + u32 expr_idx; +}; + + +struct AstPackage { + AstTyped_base; + + // Allocated in the ast arena + char * package_name; + + // NOTE: Symbol nodes + bh_arr(OnyxToken *) path; + + Package* package; +}; + +// +// Polymorphic procedures +// + +typedef enum PolyParamKind { + // Dont love these names + PPK_Undefined, + PPK_Poly_Type, + PPK_Baked_Value, +} PolyParamKind; + +typedef enum PolySolutionKind { + PSK_Undefined, + PSK_Type, + PSK_Value, +} PolySolutionKind; + +typedef enum PolyProcLookupMethod { + PPLM_By_Arguments, + PPLM_By_Function_Type, +} PolyProcLookupMethod; + +struct AstPolyParam { + PolyParamKind kind; + + // The parameter index where the polymorphic variable occurs. + u32 idx; + + // The symbol node that represents the polymorphic variable. + AstNode* poly_sym; + + // The type expression that contains `poly_sym` in it somewhere. + // Matching polymorphic variables does a parallel tree traversal + // to find the pairing of the actual type and polymorphic variable + // symbol. + AstType* type_expr; + + // Used for baked values. The expected type of the parameter. + Type* type; +}; + +struct AstPolySolution { + PolySolutionKind kind; + AstNode* poly_sym; + + union { + struct { + // If `type` is null, it is filled in with this type. + AstType* ast_type; + Type* type; + }; + + AstTyped* value; + }; +}; + +struct AstSolidifiedFunction { + AstFunction *func; + struct Entity *func_header_entity; +}; + +struct AstFunction { + AstTyped_base; + + Scope *scope; + + bh_arr(AstParam) params; + AstType* return_type; + + AstBlock *body; + + char* name; + + // NOTE: This is NULL, unless this function was generated from a polymorphic + // procedure call. Then it is set to the token of the call node. + OnyxToken* generated_from; + Scope* poly_scope; + + // NOTE: This is NULL, unless this function is used in a "#export" directive. + // It is undefined which name it will have if there are multiple export directives + // for this particular function. + OnyxToken* exported_name; + + OnyxToken* closing_brace; + + union { + OnyxToken* intrinsic_name; + + // NOTE: Used when the function is declared as foreign + struct { + OnyxToken* foreign_module; + OnyxToken* foreign_name; + }; + }; + + struct Entity* entity_header; + struct Entity* entity_body; + + ConstraintContext constraints; + + bh_arr(AstTyped *) tags; + + // Polymorphic procedures use the following fields + Scope *parent_scope_of_poly_proc; + bh_arr(AstPolyParam) poly_params; + + bh_arr(AstPolySolution) known_slns; + + Table(AstSolidifiedFunction) concrete_funcs; + bh_imap active_queries; + + bh_arr(AstNode *) nodes_that_need_entities_after_clone; + + b32 is_exported : 1; + b32 is_foreign : 1; + b32 is_intrinsic : 1; +}; + +struct AstPolyQuery { + AstNode_base; + + AstFunction *proc; + PolyProcLookupMethod pp_lookup; + ptr given; + OnyxToken *error_loc; + + bh_arr(AstPolySolution) slns; + + AstFunction *function_header; + + b32 error_on_fail : 1; // Whether or not to report errors on failing to match. + b32 successful_symres : 1; // If something successful happened in symbol resolution +}; + + +struct AstDirectiveError { + AstNode_base; + + OnyxToken* error_msg; +}; + +struct AstDirectiveAddOverload { + AstNode_base; + + // NOTE: used by the #add_overload directive. Initially set to a symbol, + // then resolved to an overloaded function. + AstNode *overloaded_function; + + // See note in OverloadOption. This could be refactored into an OverloadOption? + u64 precedence; + AstTyped *overload; +}; + +struct AstDirectiveOperator { + AstNode_base; + + BinaryOp operator; + AstTyped *overload; +}; + +struct AstDirectiveExport { + AstNode_base; + + OnyxToken* export_name; + AstTyped* export_name_expr; + AstTyped* export; +}; + +struct AstDirectiveDefined { + AstTyped_base; + AstTyped *expr; + + b32 is_defined: 1; +}; + +struct AstDirectiveRemove { + AstNode_base; +}; + +struct AstNote { + AstNode_base; +}; + +struct AstCallSite { + AstTyped_base; + + OnyxToken* callsite_token; + + AstStrLit* filename; + AstNumLit* line; + AstNumLit* column; +}; + +// Represents a "pastable" block of code. +struct AstCodeBlock { + AstTyped_base; + + AstNode *code; +}; + +struct AstDirectiveInsert { + AstTyped_base; + + AstTyped *code_expr; +}; + +struct AstDirectiveInit { + AstNode_base; + + AstTyped *init_proc; + bh_arr(AstDirectiveInit *) dependencies; +}; + +struct AstMacro { + AstTyped_base; + + AstTyped* body; +}; + +struct AstDirectiveLibrary { + AstNode_base; + + AstTyped *library_symbol; // This should resolve to a string literal + char *library_name; +}; + +struct AstForeignBlock { + AstTyped_base; + + Scope* scope; + OnyxToken *module_name; + bh_arr(struct Entity *) captured_entities; + + u32 foreign_block_number; +}; + +typedef enum EntityState { + Entity_State_Error, + + Entity_State_Parse_Builtin, + Entity_State_Introduce_Symbols, + Entity_State_Parse, + Entity_State_Resolve_Symbols, + Entity_State_Check_Types, + Entity_State_Code_Gen, + Entity_State_Finalized, + Entity_State_Failed, + + Entity_State_Count, +} EntityState; + +extern const char* entity_state_strings[Entity_State_Count]; + +// NOTE: An Entity represents something will need to be +// processed later down the pipeline. +typedef enum EntityType { + Entity_Type_Unknown, + + Entity_Type_Error, + Entity_Type_Note, + Entity_Type_Load_Path, + Entity_Type_Load_File, + Entity_Type_Binding, + Entity_Type_Use_Package, + Entity_Type_Static_If, + Entity_Type_String_Literal, + Entity_Type_File_Contents, + Entity_Type_Enum, + Entity_Type_Enum_Value, + Entity_Type_Type_Alias, + Entity_Type_Memory_Reservation_Type, + Entity_Type_Use, + Entity_Type_Interface, + Entity_Type_Constraint_Check, + Entity_Type_Polymorphic_Proc, + Entity_Type_Polymorph_Query, + Entity_Type_Macro, + Entity_Type_Foreign_Block, + Entity_Type_Foreign_Function_Header, + Entity_Type_Temp_Function_Header, // Same as a Function_Header, except it disappears after it checks completely. + Entity_Type_Function_Header, + Entity_Type_Global_Header, + Entity_Type_Process_Directive, + Entity_Type_Struct_Member_Default, + Entity_Type_Memory_Reservation, + Entity_Type_Expression, + Entity_Type_Global, + Entity_Type_Overloaded_Function, + Entity_Type_Function, + + Entity_Type_Count, +} EntityType; + +extern const char* entity_type_strings[Entity_Type_Count]; + +typedef struct Entity { + u32 id; + + EntityType type; + EntityState state; + + // TODO: Document this! + u32 macro_attempts; + u32 micro_attempts; + + b32 entered_in_queue : 1; + + Package *package; + Scope *scope; + + // TODO: This is incomplete. Add proper cycle detection and halting. + // struct Entity *waiting_on; + + union { + AstDirectiveError *error; + AstInclude *include; + AstBinding *binding; + AstIf *static_if; + AstFunction *function; + AstOverloadedFunction *overloaded_function; + AstGlobal *global; + AstTyped *expr; + AstStrLit *strlit; + AstFileContents *file_contents; + AstType *type_alias; + AstEnumType *enum_type; + AstEnumValue *enum_value; + AstMemRes *mem_res; + AstFunction *poly_proc; + AstPolyQuery *poly_query; + AstForeignBlock *foreign_block; + AstMacro *macro; + AstUse *use; + AstInterface *interface; + AstConstraint *constraint; + AstDirectiveLibrary *library; + }; +} Entity; + +typedef struct EntityHeap { + bh_arena entity_arena; + bh_arr(Entity *) entities; + i32 next_id; + + i32 state_count[Entity_State_Count]; + i32 type_count[Entity_Type_Count]; + + i32 all_count[Entity_State_Count][Entity_Type_Count]; +} EntityHeap; + +void entity_heap_init(EntityHeap* entities); +void entity_heap_insert_existing(EntityHeap* entities, Entity* e); +Entity* entity_heap_insert(EntityHeap* entities, Entity e); +Entity* entity_heap_top(EntityHeap* entities); +void entity_heap_change_top(EntityHeap* entities, Entity* new_top); +void entity_heap_remove_top(EntityHeap* entities); +void entity_change_type(EntityHeap* entities, Entity *ent, EntityType new_type); + +// If target_arr is null, the entities will be placed directly in the heap. +void add_entities_for_node(bh_arr(Entity *)* target_arr, AstNode* node, Scope* scope, Package* package); + +void symres_entity(Entity* ent); +void check_entity(Entity* ent); +void emit_entity(Entity* ent); + +struct Package { + char *name; + + Scope *scope; + Scope *private_scope; + + // NOTE: This tracks all of the 'use package' statements of this package throughout + // the code base. This is used when a static if clears and new symbols are introduced. + // 'use package' statements have to be reevaluated to pull in the new symbols. + bh_arr(Entity *) use_package_entities; +}; + +typedef enum CompileAction CompileAction; +enum CompileAction { + ONYX_COMPILE_ACTION_COMPILE, + ONYX_COMPILE_ACTION_RUN, + ONYX_COMPILE_ACTION_DOCUMENT, + ONYX_COMPILE_ACTION_PRINT_HELP, +}; + + +// ROBUSTNESS: The Runtime definitions here must match those in build_opts.onyx!! +typedef enum Runtime Runtime; +enum Runtime { + Runtime_Unknown = 0, + Runtime_Onyx = 1, + Runtime_Wasi = 2, + Runtime_Js = 3, + Runtime_Custom = 4, +}; + + +typedef struct CompileOptions CompileOptions; +struct CompileOptions { + bh_allocator allocator; + CompileAction action; + + u32 verbose_output : 2; + b32 fun_output : 1; + b32 print_function_mappings : 1; + b32 print_static_if_results : 1; + b32 print_notes : 1; + b32 no_colors : 1; + b32 no_file_contents : 1; + + b32 use_post_mvp_features : 1; + b32 use_multi_threading : 1; + b32 generate_foreign_info : 1; + + Runtime runtime; + + bh_arr(const char *) included_folders; + bh_arr(const char *) files; + const char* target_file; + const char* documentation_file; + + b32 debug_enabled; + + i32 passthrough_argument_count; + char** passthrough_argument_data; +}; + +typedef struct Context Context; +struct Context { + Table(Package *) packages; + EntityHeap entities; + + Scope *global_scope; + + CompileOptions* options; + + bh_arena ast_arena; + bh_allocator token_alloc, ast_alloc; + + bh_arr(bh_file_contents) loaded_files; + + // NOTE: This is defined in onyxwasm.h + struct OnyxWasmModule* wasm_module; + + b32 cycle_almost_detected : 1; + b32 cycle_detected : 1; +}; + +extern Context context; + +// NOTE: Basic internal types constructed in the parser +extern AstBasicType basic_type_void; +extern AstBasicType basic_type_bool; +extern AstBasicType basic_type_i8; +extern AstBasicType basic_type_u8; +extern AstBasicType basic_type_i16; +extern AstBasicType basic_type_u16; +extern AstBasicType basic_type_i32; +extern AstBasicType basic_type_u32; +extern AstBasicType basic_type_i64; +extern AstBasicType basic_type_u64; +extern AstBasicType basic_type_f32; +extern AstBasicType basic_type_f64; +extern AstBasicType basic_type_rawptr; +extern AstBasicType basic_type_type_expr; // :TypeExprHack + +extern AstBasicType basic_type_int_unsized; +extern AstBasicType basic_type_float_unsized; + +extern AstBasicType basic_type_i8x16; +extern AstBasicType basic_type_i16x8; +extern AstBasicType basic_type_i32x4; +extern AstBasicType basic_type_i64x2; +extern AstBasicType basic_type_f32x4; +extern AstBasicType basic_type_f64x2; +extern AstBasicType basic_type_v128; + +// HACK +// :AutoReturnType +extern Type type_auto_return; +extern AstBasicType basic_type_auto_return; + +extern OnyxToken builtin_package_token; +extern AstGlobal builtin_heap_start; +extern AstGlobal builtin_stack_top; +extern AstGlobal builtin_tls_base; +extern AstGlobal builtin_tls_size; +extern AstType *builtin_string_type; +extern AstType *builtin_cstring_type; +extern AstType *builtin_range_type; +extern Type *builtin_range_type_type; +extern AstType *builtin_vararg_type; +extern Type *builtin_vararg_type_type; +extern AstTyped *builtin_context_variable; +extern AstType *builtin_allocator_type; +extern AstType *builtin_iterator_type; +extern AstType *builtin_callsite_type; +extern AstType *builtin_any_type; +extern AstType *builtin_code_type; +extern AstType *builtin_link_options_type; +extern AstTyped *type_table_node; +extern AstTyped *foreign_blocks_node; +extern AstType *foreign_block_type; +extern AstTyped *tagged_procedures_node; +extern AstFunction *builtin_initialize_data_segments; +extern AstFunction *builtin_run_init_procedures; +extern bh_arr(AstFunction *) init_procedures; + +typedef struct BuiltinSymbol { + char* package; + char* sym; + AstNode* node; +} BuiltinSymbol; + +extern const BuiltinSymbol builtin_symbols[]; + +typedef struct IntrinsicMap { + char* name; + OnyxIntrinsic intrinsic; +} IntrinsicMap; + +typedef Table(OnyxIntrinsic) IntrinsicTable; +extern IntrinsicTable intrinsic_table; + +extern bh_arr(OverloadOption) operator_overloads[Binary_Op_Count]; + +void initialize_builtins(bh_allocator a); +void initalize_special_globals(); +void introduce_build_options(bh_allocator a); + + +// NOTE: Useful not inlined functions +AstTyped* ast_reduce(bh_allocator a, AstTyped* node); +AstNode* ast_clone(bh_allocator a, void* n); +AstFunction* clone_function_header(bh_allocator a, AstFunction* func); +void clone_function_body(bh_allocator a, AstFunction* dest, AstFunction* source); + +void promote_numlit_to_larger(AstNumLit* num); +b32 convert_numlit_to_type(AstNumLit* num, Type* type); + +typedef enum TypeMatch { + TYPE_MATCH_SUCCESS, + TYPE_MATCH_FAILED, + TYPE_MATCH_YIELD, + TYPE_MATCH_SPECIAL, // Only used for nest polymorph function lookups +} TypeMatch; +#define unify_node_and_type(node, type) (unify_node_and_type_((node), (type), 1)) +TypeMatch unify_node_and_type_(AstTyped** pnode, Type* type, b32 permanent); +Type* resolve_expression_type(AstTyped* node); + +i64 get_expression_integer_value(AstTyped* node, b32 *out_is_valid); +char *get_expression_string_value(AstTyped* node, b32 *out_is_valid); + +b32 cast_is_legal(Type* from_, Type* to_, char** err_msg); +char* get_function_name(AstFunction* func); + +AstNode* strip_aliases(AstNode* node); + +AstNumLit* make_bool_literal(bh_allocator, b32 b); +AstNumLit* make_int_literal(bh_allocator a, i64 value); +AstNumLit* make_float_literal(bh_allocator a, f64 value); +AstRangeLiteral* make_range_literal(bh_allocator a, AstTyped* low, AstTyped* high); +AstBinaryOp* make_binary_op(bh_allocator a, BinaryOp operation, AstTyped* left, AstTyped* right); +AstArgument* make_argument(bh_allocator a, AstTyped* value); +AstFieldAccess* make_field_access(bh_allocator a, AstTyped* node, char* field); +AstAddressOf* make_address_of(bh_allocator a, AstTyped* node); +AstLocal* make_local(bh_allocator a, OnyxToken* token, AstType* type_node); +AstNode* make_symbol(bh_allocator a, OnyxToken* sym); +AstUnaryOp* make_cast(bh_allocator a, AstTyped* expr, Type* to); +AstZeroValue* make_zero_value(bh_allocator a, OnyxToken *token, Type* type); + +void arguments_initialize(Arguments* args); +b32 fill_in_arguments(Arguments* args, AstNode* provider, char** err_msg, b32 insert_zero_values); +void arguments_ensure_length(Arguments* args, u32 count); +void arguments_copy(Arguments* dest, Arguments* src); +void arguments_clone(Arguments* dest, Arguments* src); +void arguments_deep_clone(bh_allocator a, Arguments* dest, Arguments* src); +void arguments_remove_baked(Arguments* args); +void arguments_clear_baked_flags(Arguments* args); +TypeMatch check_arguments_against_type(Arguments* args, TypeFunction* func_type, VarArgKind* va_kind, + OnyxToken* location, char* func_name, struct OnyxError* error); +i32 get_argument_buffer_size(TypeFunction* type, Arguments* args); + +// GROSS: Using void* to avoid having to cast everything. +const char* node_get_type_name(void* node); + +b32 static_if_resolution(AstIf* static_if); + +void insert_poly_sln_into_scope(Scope* scope, AstPolySolution *sln); +TypeMatch find_polymorphic_sln(AstPolySolution *out, AstPolyParam *param, AstFunction *func, PolyProcLookupMethod pp_lookup, ptr actual, char** err_msg); +AstFunction* polymorphic_proc_lookup(AstFunction* pp, PolyProcLookupMethod pp_lookup, ptr actual, OnyxToken* tkn); +AstFunction* polymorphic_proc_solidify(AstFunction* pp, bh_arr(AstPolySolution) slns, OnyxToken* tkn); +AstNode* polymorphic_proc_try_solidify(AstFunction* pp, bh_arr(AstPolySolution) slns, OnyxToken* tkn); +AstFunction* polymorphic_proc_build_only_header(AstFunction* pp, PolyProcLookupMethod pp_lookup, ptr actual); +AstFunction* polymorphic_proc_build_only_header_with_slns(AstFunction* pp, bh_arr(AstPolySolution) slns, b32 error_if_failed); +b32 potentially_convert_function_to_polyproc(AstFunction *func); +AstPolyCallType* convert_call_to_polycall(AstCall* call); + +void add_overload_option(bh_arr(OverloadOption)* poverloads, u64 precedence, AstTyped* overload); +AstTyped* find_matching_overload_by_arguments(bh_arr(OverloadOption) overloads, Arguments* args); +AstTyped* find_matching_overload_by_type(bh_arr(OverloadOption) overloads, Type* type); +void report_unable_to_match_overload(AstCall* call, bh_arr(OverloadOption) overloads); + +void expand_macro(AstCall** pcall, AstFunction* template); +AstFunction* macro_resolve_header(AstMacro* macro, Arguments* args, OnyxToken* callsite, b32 error_if_failed); + +Type* polymorphic_struct_lookup(AstPolyStructType* ps_type, bh_arr(AstPolySolution) slns, OnyxFilePos pos, b32 error_if_failed); + +// NOTE: Useful inlined functions +static inline b32 is_lval(AstNode* node) { + node = strip_aliases(node); + + if ((node->kind == Ast_Kind_Local) + || (node->kind == Ast_Kind_Param) + || (node->kind == Ast_Kind_Global) + || (node->kind == Ast_Kind_Dereference) + || (node->kind == Ast_Kind_Subscript) + || (node->kind == Ast_Kind_Field_Access) + || (node->kind == Ast_Kind_Memres) + || (node->kind == Ast_Kind_Constraint_Sentinel)) // Bit of a hack, but this makes constraints like 'T->foo()' work. + return 1; + + if (node->kind == Ast_Kind_Compound) { + b32 all_lval = 1; + bh_arr_each(AstTyped *, expr, ((AstCompound *) node)->exprs) all_lval = all_lval && is_lval((AstNode *) *expr); + return all_lval; + } + + return 0; +} + +static inline b32 binop_is_assignment(BinaryOp binop) { + return (binop >= Binary_Op_Assign_Start && binop <= Binary_Op_Assign_End); +} + +static inline b32 binop_is_compare(BinaryOp binop) { + return (binop >= Binary_Op_Equal && binop <= Binary_Op_Greater_Equal); +} + +static inline b32 node_is_type(AstNode* node) { + node = strip_aliases(node); + + return (node->kind > Ast_Kind_Type_Start) && (node->kind < Ast_Kind_Type_End); +} + +static inline b32 node_is_auto_cast(AstNode* node) { + return (node->kind == Ast_Kind_Unary_Op) && (((AstUnaryOp *) node)->operation == Unary_Op_Auto_Cast); +} + +static inline b32 node_is_addressable_literal(AstNode* node) { + return (node->kind == Ast_Kind_Struct_Literal) + || (node->kind == Ast_Kind_Array_Literal); +} + +static inline Type* get_expression_type(AstTyped* expr) { + switch (expr->kind) { + case Ast_Kind_Block: case Ast_Kind_If: case Ast_Kind_While: return NULL; + default: return expr->type; + } +} + +static inline CallingConvention type_function_get_cc(Type* type) { + if (type == NULL) return CC_Undefined; + if (type->kind != Type_Kind_Function) return CC_Undefined; + if (type_is_compound(type->Function.return_type)) return CC_Return_Stack; + return CC_Return_Wasm; +} + +static inline ParamPassType type_get_param_pass(Type* type) { + if (type_is_structlike_strict(type) && !type_structlike_is_simple(type)) return Param_Pass_By_Implicit_Pointer; + return Param_Pass_By_Value; +} + +static inline AstFunction* get_function_from_node(AstNode* node) { + if (node->kind == Ast_Kind_Function) return (AstFunction *) node; + if (node->kind == Ast_Kind_Polymorphic_Proc) return (AstFunction *) node; + if (node->kind == Ast_Kind_Macro) return get_function_from_node((AstNode*) ((AstMacro *) node)->body); + return NULL; +} + +static inline void convert_polyproc_to_function(AstFunction *func) { + if (func->kind != Ast_Kind_Polymorphic_Proc) return; + + func->kind = Ast_Kind_Function; + func->parent_scope_of_poly_proc = NULL; + func->poly_params = NULL; + func->known_slns = NULL; + func->concrete_funcs = NULL; + func->active_queries.hashes = NULL; + func->active_queries.entries = NULL; + func->poly_scope = NULL; + func->entity = NULL; + func->type = NULL; + func->tags = NULL; +} + +static inline void convert_function_to_polyproc(AstFunction *func) { + if (func->kind != Ast_Kind_Function) return; + + func->kind = Ast_Kind_Polymorphic_Proc; + func->parent_scope_of_poly_proc = func->scope->parent; + func->scope = NULL; + if (func->entity) entity_change_type(&context.entities, func->entity, Entity_Type_Polymorphic_Proc); +} + +#endif // #ifndef ONYXASTNODES_H diff --git a/compiler/include/doc.h b/compiler/include/doc.h new file mode 100644 index 00000000..75d4bfc5 --- /dev/null +++ b/compiler/include/doc.h @@ -0,0 +1,38 @@ +#ifndef ONYXDOC_H +#define ONYXDOC_H + +#include "bh.h" +#include "astnodes.h" + +typedef enum DocFormat { + Doc_Format_Human, + Doc_Format_Tags, + Doc_Format_Html, +} DocFormat; + +typedef struct DocEntry { + OnyxFilePos pos; + char* sym; // Unused by doc generator + char* def; + char* additional; +} DocEntry; + +typedef struct DocPackage { + const char* name; + + bh_arr(DocEntry) public_entries; + bh_arr(DocEntry) private_entries; +} DocPackage; + +typedef struct OnyxDocumentation { + bh_arena doc_arena; + + DocFormat format; + + bh_arr(DocPackage) package_docs; +} OnyxDocumentation; + +OnyxDocumentation onyx_docs_generate(); +void onyx_docs_emit(OnyxDocumentation* doc, const char* filename); + +#endif diff --git a/compiler/include/errors.h b/compiler/include/errors.h new file mode 100644 index 00000000..9df95def --- /dev/null +++ b/compiler/include/errors.h @@ -0,0 +1,44 @@ +#ifndef ONYXERRORS_H +#define ONYXERRORS_H + +#include "bh.h" +#include "lex.h" + +#include + +typedef enum OnyxErrorRank { + Error_Undefined = 0, + Error_Other = 1, + Error_Warning = 2, + Error_Waiting_On = 3, + Error_Critical = 4, +} OnyxErrorRank; + +typedef struct OnyxError { + OnyxFilePos pos; + OnyxErrorRank rank; + char *text; +} OnyxError; + +typedef struct OnyxErrors { + bh_arena msg_arena; + bh_allocator msg_alloc; + + // NOTE: Pointer to a array of all the loaded files. + bh_arr(bh_file_contents)* file_contents; + + bh_arr(OnyxError) errors; +} OnyxErrors; + +extern OnyxErrors msgs; + +void onyx_errors_init(bh_arr(bh_file_contents)* files); +void onyx_submit_error(OnyxError error); +void onyx_report_error(OnyxFilePos pos, OnyxErrorRank rank, char * format, ...); +void onyx_submit_warning(OnyxError error); +void onyx_report_warning(OnyxFilePos pos, char* format, ...); +void onyx_errors_print(); +b32 onyx_has_errors(); +void onyx_clear_errors(); + +#endif diff --git a/compiler/include/lex.h b/compiler/include/lex.h new file mode 100644 index 00000000..df684476 --- /dev/null +++ b/compiler/include/lex.h @@ -0,0 +1,122 @@ +#ifndef ONYXLEX_H +#define ONYXLEX_H + +#include "bh.h" + +// NOTE: Used for global statistics +extern u64 lexer_lines_processed; +extern u64 lexer_tokens_processed; + +typedef enum TokenType { + Token_Type_Ascii_End = 256, + Token_Type_Unknown = 256, + Token_Type_End_Stream = 257, + + Token_Type_Comment = 258, + + Token_Type_Keyword_Package, + Token_Type_Keyword_Struct, + Token_Type_Keyword_Enum, + Token_Type_Keyword_Use, + Token_Type_Keyword_If, + Token_Type_Keyword_Else, + Token_Type_Keyword_Elseif, + Token_Type_Keyword_Return, + Token_Type_Keyword_Global, + Token_Type_Keyword_Cast, + Token_Type_Keyword_While, + Token_Type_Keyword_For, + Token_Type_Keyword_Break, + Token_Type_Keyword_Continue, + Token_Type_Keyword_Sizeof, + Token_Type_Keyword_Alignof, + Token_Type_Keyword_Typeof, + Token_Type_Keyword_Defer, + Token_Type_Keyword_Do, + Token_Type_Keyword_Case, + Token_Type_Keyword_Switch, + Token_Type_Keyword_Fallthrough, + Token_Type_Keyword_Macro, + Token_Type_Keyword_Interface, + Token_Type_Keyword_Where, + + Token_Type_Right_Arrow, + Token_Type_Left_Arrow, + Token_Type_Empty_Block, + Token_Type_Pipe, + + Token_Type_Greater_Equal, + Token_Type_Less_Equal, + Token_Type_Equal_Equal, + Token_Type_Not_Equal, + Token_Type_Plus_Equal, + Token_Type_Minus_Equal, + Token_Type_Star_Equal, + Token_Type_Fslash_Equal, + Token_Type_Percent_Equal, + Token_Type_And_Equal, + Token_Type_Or_Equal, + Token_Type_Xor_Equal, + Token_Type_And_And, + Token_Type_Or_Or, + Token_Type_Shift_Left, + Token_Type_Shift_Right, + Token_Type_Shift_Arith_Right, + Token_Type_Shl_Equal, + Token_Type_Shr_Equal, + Token_Type_Sar_Equal, + + Token_Type_Dot_Dot, + Token_Type_Tilde_Tilde, + + Token_Type_Symbol, + Token_Type_Literal_String, + Token_Type_Literal_Integer, + Token_Type_Literal_Float, + Token_Type_Literal_True, + Token_Type_Literal_False, + + Token_Type_Note, + + Token_Type_Count, +} TokenType; + +typedef struct OnyxFilePos { + const char* filename; + char* line_start; + u32 line; + + // NOTE: This assumes that no line is no longer than 2^16 chars + u16 column, length; +} OnyxFilePos; + +typedef struct OnyxToken { + TokenType type; + i32 length; + char* text; + OnyxFilePos pos; +} OnyxToken; + +typedef struct OnyxTokenizer { + char *start, *curr, *end; + + const char* filename; + + char* line_start; + u64 line_number; + + bh_arr(OnyxToken) tokens; +} OnyxTokenizer; + +const char* token_name(TokenType tkn_type); +void token_toggle_end(OnyxToken* tkn); +OnyxToken* onyx_get_token(OnyxTokenizer* tokenizer); +OnyxTokenizer onyx_tokenizer_create(bh_allocator allocator, bh_file_contents *fc); +void onyx_tokenizer_free(OnyxTokenizer* tokenizer); +void onyx_lex_tokens(OnyxTokenizer* tokenizer); + +b32 token_equals(OnyxToken* tkn1, OnyxToken* tkn2); +b32 token_text_equals(OnyxToken* tkn, char* text); +b32 token_same_file(OnyxToken *tkn1, OnyxToken *tkn2); + +#endif diff --git a/compiler/include/parser.h b/compiler/include/parser.h new file mode 100644 index 00000000..48889a86 --- /dev/null +++ b/compiler/include/parser.h @@ -0,0 +1,49 @@ +#ifndef ONYXPARSER_H +#define ONYXPARSER_H + +#include "bh.h" + +#include "lex.h" +#include "errors.h" +#include "astnodes.h" + +typedef struct PolymorphicContext { + AstType* root_node; + bh_arr(AstPolyParam)* poly_params; +} PolymorphicContext; + +typedef struct OnyxParser { + bh_allocator allocator; + + Package *package; + Scope *file_scope; + + // NOTE: not used since all tokens are lexed before parsing starts + OnyxTokenizer *tokenizer; + OnyxToken *prev; + OnyxToken *curr; + + PolymorphicContext polymorph_context; + + bh_arr(AstFunction *) current_function_stack; + Scope *current_scope; + bh_arr(bh_arr(Entity *) *) alternate_entity_placement_stack; + bh_arr(OnyxToken *) current_symbol_stack; + + bh_arr(AstFlags) scope_flags; + + bh_arr(AstTyped *) stored_tags; + + u16 tag_depth : 16; + + b32 hit_unexpected_token : 1; + b32 parse_calls : 1; +} OnyxParser; + +const char* onyx_ast_node_kind_string(AstKind kind); +void* onyx_ast_node_new(bh_allocator alloc, i32 size, AstKind kind); +OnyxParser onyx_parser_create(bh_allocator alloc, OnyxTokenizer *tokenizer); +void onyx_parser_free(OnyxParser* parser); +void onyx_parse(OnyxParser *parser); + +#endif // #ifndef ONYXPARSER_H diff --git a/compiler/include/types.h b/compiler/include/types.h new file mode 100644 index 00000000..5c2b5e69 --- /dev/null +++ b/compiler/include/types.h @@ -0,0 +1,247 @@ +#ifndef ONYXTYPES_H +#define ONYXTYPES_H + +#include "bh.h" + +#define POINTER_SIZE 4 + +enum BasicKind { + Basic_Kind_Void, + + Basic_Kind_Bool, + + Basic_Kind_Int_Unsized, + Basic_Kind_I8, + Basic_Kind_U8, + Basic_Kind_I16, + Basic_Kind_U16, + Basic_Kind_I32, + Basic_Kind_U32, + Basic_Kind_I64, + Basic_Kind_U64, + + Basic_Kind_Float_Unsized, + Basic_Kind_F32, + Basic_Kind_F64, + + Basic_Kind_Rawptr, + + Basic_Kind_I8X16, + Basic_Kind_I16X8, + Basic_Kind_I32X4, + Basic_Kind_I64X2, + Basic_Kind_F32X4, + Basic_Kind_F64X2, + Basic_Kind_V128, + + Basic_Kind_Type_Index, + + Basic_Kind_Count, +}; + +enum BasicFlag { + Basic_Flag_Boolean = BH_BIT(0), + Basic_Flag_Integer = BH_BIT(1), + Basic_Flag_Unsigned = BH_BIT(2), + Basic_Flag_Float = BH_BIT(3), + Basic_Flag_Pointer = BH_BIT(4), + + Basic_Flag_SIMD = BH_BIT(5), + + Basic_Flag_Type_Index = BH_BIT(6), + + Basic_Flag_Numeric = Basic_Flag_Integer | Basic_Flag_Float, + Basic_Flag_Ordered = Basic_Flag_Integer | Basic_Flag_Float | Basic_Flag_Pointer, + Basic_Flag_Equality = Basic_Flag_Ordered | Basic_Flag_Type_Index | Basic_Flag_Boolean, + Basic_Flag_Constant_Type = Basic_Flag_Boolean | Basic_Flag_Numeric | Basic_Flag_Pointer, + Basic_Flag_Numeric_Ordered = Basic_Flag_Numeric | Basic_Flag_Ordered, +}; + +typedef struct TypeBasic { + enum BasicKind kind; + u32 flags; + u32 size, alignment; // NOTE: In bytes + const char* name; +} TypeBasic; + +// NOTE: Forward declaration for some of the types below +typedef struct Type Type; + +typedef struct StructMember { + u32 offset, idx; + Type *type; + + // NOTE: Since the name is stored here, it may not be necessary to, + // store a hash table of struct members. There realistically will not + // be many struct members, and iterating through an array would be + // easier and less costly. - brendanfh 2020/09/17 + char *name; + struct OnyxToken* token; + + struct AstTyped** initial_value; + i32 use_through_pointer_index; + b32 included_through_use : 1; + b32 used : 1; + + bh_arr(struct AstTyped *) meta_tags; +} StructMember; + +typedef struct TypeWithOffset TypeWithOffset; +struct TypeWithOffset { + Type* type; + u32 offset; +}; + +typedef enum StructProcessingStatus { + SPS_Start, + SPS_Members_Done, + SPS_Uses_Done, +} StructProcessingStatus; + +#define TYPE_KINDS \ + TYPE_KIND(Basic, TypeBasic) \ + TYPE_KIND(Pointer, struct { TypeBasic base; Type *elem; }) \ + TYPE_KIND(Function, struct { \ + Type *return_type; \ + u16 param_count; \ + u16 needed_param_count; \ + i16 vararg_arg_pos; \ + Type* params[]; \ + }) \ + TYPE_KIND(Struct, struct { \ + char* name; \ + u32 size; \ + u16 alignment, mem_count; \ + Table(StructMember *) members; \ + bh_arr(StructMember *) memarr; \ + bh_arr(struct AstPolySolution) poly_sln; \ + bh_arr(TypeWithOffset) linear_members; \ + struct AstType *constructed_from; \ + bh_arr(struct AstTyped *) meta_tags; \ + StructProcessingStatus status; \ + }) \ + TYPE_KIND(PolyStruct, struct { \ + char* name; \ + bh_arr(struct AstTyped *) meta_tags; \ + }) \ + TYPE_KIND(Compound, struct { \ + u32 count; \ + u32 size; \ + bh_arr(TypeWithOffset) linear_members; \ + Type* types[]; \ + }) \ + TYPE_KIND(Array, struct { Type* elem; u32 size; u32 count; }) \ + TYPE_KIND(Slice, struct { Type *elem; }) \ + TYPE_KIND(DynArray, struct { Type *elem; }) \ + TYPE_KIND(VarArgs, struct { Type *elem; }) \ + TYPE_KIND(Enum, struct { \ + char* name; \ + Type* backing; \ + b32 is_flags; \ + }) \ + TYPE_KIND(Distinct, struct { \ + char* name; \ + Type* base_type; \ + }) + + +typedef enum TypeKind { + Type_Kind_Invalid, + +#define TYPE_KIND(k, ...) Type_Kind_##k, + TYPE_KINDS +#undef TYPE_KIND + + Type_Kind_Count, +} TypeKind; + +#define TYPE_KIND(k, ...) typedef __VA_ARGS__ Type ## k; + TYPE_KINDS +#undef TYPE_KIND + +enum TypeFlag { + Type_Flag_Default +}; + +struct Type { + TypeKind kind; + + u32 id; + u32 flags; + + // NOTE(Brendan Hansen): The abstract syntax tree node used to create + // the type. Primarily used to look up symbols in scopes that are embedded + // in the type. + struct AstType* ast_type; + + union { +#define TYPE_KIND(k, ...) Type##k k; + TYPE_KINDS +#undef TYPE_KIND + }; +}; + +extern bh_imap type_map; + +extern Type basic_types[]; + +struct AstType; +struct AstFunction; +struct AstCompound; +struct AstStructLiteral; + +void types_init(); +void types_dump_type_info(); + +b32 types_are_compatible(Type* t1, Type* t2); +u32 type_size_of(Type* type); +u32 type_alignment_of(Type* type); +Type* type_build_from_ast(bh_allocator alloc, struct AstType* type_node); +Type* type_build_implicit_type_of_struct_literal(bh_allocator alloc, struct AstStructLiteral* lit); + +Type* type_build_function_type(bh_allocator alloc, struct AstFunction* func); +Type* type_build_compound_type(bh_allocator alloc, struct AstCompound* compound); + +Type* type_make_pointer(bh_allocator alloc, Type* to); +Type* type_make_array(bh_allocator alloc, Type* to, u32 count); +Type* type_make_slice(bh_allocator alloc, Type* of); +Type* type_make_dynarray(bh_allocator alloc, Type* of); +Type* type_make_varargs(bh_allocator alloc, Type* of); + +void build_linear_types_with_offset(Type* type, bh_arr(TypeWithOffset)* pdest, u32 offset); +b32 type_struct_member_apply_use(bh_allocator alloc, Type *s_type, StructMember *smem); + +const char* type_get_unique_name(Type* type); +const char* type_get_name(Type* type); +u32 type_get_alignment_log2(Type* type); +Type* type_get_contained_type(Type* type); + +b32 type_lookup_member(Type* type, char* member, StructMember* smem); +b32 type_lookup_member_by_idx(Type* type, i32 idx, StructMember* smem); + +i32 type_linear_member_count(Type* type); +b32 type_linear_member_lookup(Type* type, i32 idx, TypeWithOffset* two); +i32 type_get_idx_of_linear_member_with_offset(Type* type, u32 offset); + +b32 type_struct_is_simple(Type* type); + +b32 type_is_pointer(Type* type); +b32 type_is_rawptr(Type* type); +b32 type_is_array(Type* tyoe); +b32 type_is_struct(Type* type); +b32 type_is_bool(Type* type); +b32 type_is_small_integer(Type* type); +b32 type_is_integer(Type* type); +b32 type_is_numeric(Type* type); +b32 type_is_compound(Type* type); +b32 type_is_simd(Type* type); +b32 type_results_in_void(Type* type); +b32 type_is_array_accessible(Type* type); +b32 type_is_structlike(Type* type); +b32 type_is_structlike_strict(Type* type); +u32 type_structlike_mem_count(Type* type); +u32 type_structlike_is_simple(Type* type); +b32 type_is_sl_constructable(Type* type); +b32 type_struct_constructed_from_poly_struct(Type* struct_type, struct AstType* from); + +#endif // #ifndef ONYX_TYPES diff --git a/compiler/include/utils.h b/compiler/include/utils.h new file mode 100644 index 00000000..2b0083c2 --- /dev/null +++ b/compiler/include/utils.h @@ -0,0 +1,44 @@ +#include "bh.h" + +#include "astnodes.h" + +extern bh_scratch global_scratch; +extern bh_allocator global_scratch_allocator; + +extern bh_managed_heap global_heap; +extern bh_allocator global_heap_allocator; + +const char* onyx_ast_node_kind_string(AstKind kind); + +Package* package_lookup(char* package_name); +Package* package_lookup_or_create(char* package_name, Scope* parent_scope, bh_allocator alloc, OnyxFilePos pos); +void package_track_use_package(Package* package, Entity* entity); +void package_reinsert_use_packages(Package* package); + +Scope* scope_create(bh_allocator a, Scope* parent, OnyxFilePos created_at); +void scope_include(Scope* target, Scope* source, OnyxFilePos pos); +b32 symbol_introduce(Scope* scope, OnyxToken* tkn, AstNode* symbol); +b32 symbol_raw_introduce(Scope* scope, char* tkn, OnyxFilePos pos, AstNode* symbol); +void symbol_builtin_introduce(Scope* scope, char* sym, AstNode *node); +void symbol_subpackage_introduce(Scope* scope, char* sym, AstPackage *node); +AstNode* symbol_raw_resolve(Scope* start_scope, char* sym); +AstNode* symbol_resolve(Scope* start_scope, OnyxToken* tkn); +AstNode* try_symbol_raw_resolve_from_node(AstNode* node, char* symbol); +AstNode* try_symbol_resolve_from_node(AstNode* node, OnyxToken* token); +AstNode* try_symbol_raw_resolve_from_type(Type *type, char* symbol); +Scope *get_scope_from_node(AstNode *node); +Scope *get_scope_from_node_or_create(AstNode *node); + +void build_all_overload_options(bh_arr(OverloadOption) overloads, bh_imap* all_overloads); + +u32 char_to_base16_value(char x); + +// Returns the length after processing the string. +i32 string_process_escape_seqs(char* dest, char* src, i32 len); + +u32 levenshtein_distance(const char *str1, const char *str2); +char *find_closest_symbol_in_scope_and_parents(Scope *scope, char *sym); +char *find_closest_symbol_in_node(AstNode *node, char *sym); + +extern AstTyped node_that_signals_a_yield; +extern AstTyped node_that_signals_failure; diff --git a/compiler/include/wasm_emit.h b/compiler/include/wasm_emit.h new file mode 100644 index 00000000..3a79e549 --- /dev/null +++ b/compiler/include/wasm_emit.h @@ -0,0 +1,864 @@ +#ifndef ONYXWASM_H +#define ONYXWASM_H + +#include "bh.h" + +#include "astnodes.h" +#include "errors.h" + +typedef u8 WasmType; + +typedef struct WasmFuncType { + // NOTE: For now, WASM only allows for 1 return value. + // This may be lifted in the future. + i32 param_count; + WasmType return_type; + WasmType param_types[]; +} WasmFuncType; + +#define SIMD_INSTR_MASK 0x10000 +#define EXT_INSTR_MASK 0x20000 +#define ATOMIC_INSTR_MASK 0x40000 + +typedef enum WasmInstructionType { + WI_UNREACHABLE = 0x00, + WI_NOP = 0x01, + + // NOTE: Control flow + WI_BLOCK_START = 0x02, + WI_BLOCK_END = 0x0B, // NOTE: These ends are not unique + WI_LOOP_START = 0x03, + WI_LOOP_END = 0x0B, + WI_IF_START = 0x04, + WI_ELSE = 0x05, + WI_IF_END = 0x0B, + WI_JUMP = 0x0C, + WI_COND_JUMP = 0x0D, + WI_JUMP_TABLE = 0x0E, + WI_RETURN = 0x0F, + WI_CALL = 0x10, + WI_CALL_INDIRECT = 0x11, + + // NOTE: Parametric instructions + WI_DROP = 0x1A, + WI_SELECT = 0x1B, + + // NOTE: Variable instructions + WI_LOCAL_GET = 0x20, + WI_LOCAL_SET = 0x21, + WI_LOCAL_TEE = 0x22, + WI_GLOBAL_GET = 0x23, + WI_GLOBAL_SET = 0x24, + + // NOTE: Memory instructions + WI_I32_LOAD = 0x28, + WI_I64_LOAD = 0x29, + WI_F32_LOAD = 0x2A, + WI_F64_LOAD = 0x2B, + WI_I32_LOAD_8_S = 0x2C, + WI_I32_LOAD_8_U = 0x2D, + WI_I32_LOAD_16_S = 0x2E, + WI_I32_LOAD_16_U = 0x2F, + WI_I64_LOAD_8_S = 0x30, + WI_I64_LOAD_8_U = 0x31, + WI_I64_LOAD_16_S = 0x32, + WI_I64_LOAD_16_U = 0x33, + WI_I64_LOAD_32_S = 0x34, + WI_I64_LOAD_32_U = 0x35, + WI_I32_STORE = 0x36, + WI_I64_STORE = 0x37, + WI_F32_STORE = 0x38, + WI_F64_STORE = 0x39, + WI_I32_STORE_8 = 0x3A, + WI_I32_STORE_16 = 0x3B, + WI_I64_STORE_8 = 0x3C, + WI_I64_STORE_16 = 0x3D, + WI_I64_STORE_32 = 0x3E, + WI_MEMORY_SIZE = 0x3F, + WI_MEMORY_GROW = 0x40, + + // NOTE: Numeric Instructions + WI_I32_CONST = 0x41, + WI_I64_CONST = 0x42, + WI_F32_CONST = 0x43, + WI_F64_CONST = 0x44, + + WI_I32_EQZ = 0x45, + WI_I32_EQ = 0x46, + WI_I32_NE = 0x47, + WI_I32_LT_S = 0x48, + WI_I32_LT_U = 0x49, + WI_I32_GT_S = 0x4a, + WI_I32_GT_U = 0x4b, + WI_I32_LE_S = 0x4c, + WI_I32_LE_U = 0x4d, + WI_I32_GE_S = 0x4e, + WI_I32_GE_U = 0x4f, + + WI_I64_EQZ = 0x50, + WI_I64_EQ = 0x51, + WI_I64_NE = 0x52, + WI_I64_LT_S = 0x53, + WI_I64_LT_U = 0x54, + WI_I64_GT_S = 0x55, + WI_I64_GT_U = 0x56, + WI_I64_LE_S = 0x57, + WI_I64_LE_U = 0x58, + WI_I64_GE_S = 0x59, + WI_I64_GE_U = 0x5a, + + WI_F32_EQ = 0x5b, + WI_F32_NE = 0x5c, + WI_F32_LT = 0x5d, + WI_F32_GT = 0x5e, + WI_F32_LE = 0x5f, + WI_F32_GE = 0x60, + + WI_F64_EQ = 0x61, + WI_F64_NE = 0x62, + WI_F64_LT = 0x63, + WI_F64_GT = 0x64, + WI_F64_LE = 0x65, + WI_F64_GE = 0x66, + + WI_I32_CLZ = 0x67, + WI_I32_CTZ = 0x68, + WI_I32_POPCNT = 0x69, + WI_I32_ADD = 0x6a, + WI_I32_SUB = 0x6b, + WI_I32_MUL = 0x6c, + WI_I32_DIV_S = 0x6d, + WI_I32_DIV_U = 0x6e, + WI_I32_REM_S = 0x6f, + WI_I32_REM_U = 0x70, + WI_I32_AND = 0x71, + WI_I32_OR = 0x72, + WI_I32_XOR = 0x73, + WI_I32_SHL = 0x74, + WI_I32_SHR_S = 0x75, + WI_I32_SHR_U = 0x76, + WI_I32_ROTL = 0x77, + WI_I32_ROTR = 0x78, + + WI_I64_CLZ = 0x79, + WI_I64_CTZ = 0x7a, + WI_I64_POPCNT = 0x7b, + WI_I64_ADD = 0x7c, + WI_I64_SUB = 0x7d, + WI_I64_MUL = 0x7e, + WI_I64_DIV_S = 0x7f, + WI_I64_DIV_U = 0x80, + WI_I64_REM_S = 0x81, + WI_I64_REM_U = 0x82, + WI_I64_AND = 0x83, + WI_I64_OR = 0x84, + WI_I64_XOR = 0x85, + WI_I64_SHL = 0x86, + WI_I64_SHR_S = 0x87, + WI_I64_SHR_U = 0x88, + WI_I64_ROTL = 0x89, + WI_I64_ROTR = 0x8a, + + WI_F32_ABS = 0x8b, + WI_F32_NEG = 0x8c, + WI_F32_CEIL = 0x8d, + WI_F32_FLOOR = 0x8e, + WI_F32_TRUNC = 0x8f, + WI_F32_NEAREST = 0x90, + WI_F32_SQRT = 0x91, + WI_F32_ADD = 0x92, + WI_F32_SUB = 0x93, + WI_F32_MUL = 0x94, + WI_F32_DIV = 0x95, + WI_F32_MIN = 0x96, + WI_F32_MAX = 0x97, + WI_F32_COPYSIGN = 0x98, + + WI_F64_ABS = 0x99, + WI_F64_NEG = 0x9a, + WI_F64_CEIL = 0x9b, + WI_F64_FLOOR = 0x9c, + WI_F64_TRUNC = 0x9d, + WI_F64_NEAREST = 0x9e, + WI_F64_SQRT = 0x9f, + WI_F64_ADD = 0xA0, + WI_F64_SUB = 0xA1, + WI_F64_MUL = 0xA2, + WI_F64_DIV = 0xA3, + WI_F64_MIN = 0xA4, + WI_F64_MAX = 0xA5, + WI_F64_COPYSIGN = 0xA6, + + WI_I32_FROM_I64 = 0xA7, + WI_I32_FROM_F32_S = 0xA8, + WI_I32_FROM_F32_U = 0xA9, + WI_I32_FROM_F64_S = 0xAA, + WI_I32_FROM_F64_U = 0xAB, + + WI_I64_FROM_I32_S = 0xAC, + WI_I64_FROM_I32_U = 0xAD, + WI_I64_FROM_F32_S = 0xAE, + WI_I64_FROM_F32_U = 0xAF, + WI_I64_FROM_F64_S = 0xB0, + WI_I64_FROM_F64_U = 0xB1, + + WI_F32_FROM_I32_S = 0xB2, + WI_F32_FROM_I32_U = 0xB3, + WI_F32_FROM_I64_S = 0xB4, + WI_F32_FROM_I64_U = 0xB5, + WI_F32_FROM_F64 = 0xB6, + + WI_F64_FROM_I32_S = 0xB7, + WI_F64_FROM_I32_U = 0xB8, + WI_F64_FROM_I64_S = 0xB9, + WI_F64_FROM_I64_U = 0xBA, + WI_F64_FROM_F32 = 0xBB, + + WI_I32_REINTERPRET_F32 = 0xBC, + WI_I64_REINTERPRET_F64 = 0xBD, + WI_F32_REINTERPRET_I32 = 0xBE, + WI_F64_REINTERPRET_I64 = 0xBF, + + WI_I32_EXTEND_8_S = 0xC0, + WI_I32_EXTEND_16_S = 0xC1, + WI_I64_EXTEND_8_S = 0xC2, + WI_I64_EXTEND_16_S = 0xC3, + WI_I64_EXTEND_32_S = 0xC4, + + // Pointer stuff; this will make it easier to switch to 64-bit + // pointers, whenever WebAssembly standarizes that. + WI_PTR_CONST = WI_I32_CONST, + WI_PTR_LOAD = WI_I32_LOAD, + WI_PTR_STORE = WI_I32_STORE, + WI_PTR_ADD = WI_I32_ADD, + WI_PTR_SUB = WI_I32_SUB, + WI_PTR_MUL = WI_I32_MUL, + WI_PTR_GE = WI_I32_GE_U, + WI_PTR_GT = WI_I32_GT_U, + WI_PTR_EQ = WI_I32_EQ, + + WI_V128_LOAD = SIMD_INSTR_MASK | 0, + WI_V128_STORE = SIMD_INSTR_MASK | 11, + + WI_V128_CONST = SIMD_INSTR_MASK | 12, + + WI_I8X16_SHUFFLE = SIMD_INSTR_MASK | 13, + + WI_I8X16_EXTRACT_LANE_S = SIMD_INSTR_MASK | 21, + WI_I8X16_EXTRACT_LANE_U = SIMD_INSTR_MASK | 22, + WI_I8X16_REPLACE_LANE = SIMD_INSTR_MASK | 23, + WI_I16X8_EXTRACT_LANE_S = SIMD_INSTR_MASK | 24, + WI_I16X8_EXTRACT_LANE_U = SIMD_INSTR_MASK | 25, + WI_I16X8_REPLACE_LANE = SIMD_INSTR_MASK | 26, + WI_I32X4_EXTRACT_LANE = SIMD_INSTR_MASK | 27, + WI_I32X4_REPLACE_LANE = SIMD_INSTR_MASK | 28, + WI_I64X2_EXTRACT_LANE = SIMD_INSTR_MASK | 29, + WI_I64X2_REPLACE_LANE = SIMD_INSTR_MASK | 30, + WI_F32X4_EXTRACT_LANE = SIMD_INSTR_MASK | 31, + WI_F32X4_REPLACE_LANE = SIMD_INSTR_MASK | 32, + WI_F64X2_EXTRACT_LANE = SIMD_INSTR_MASK | 33, + WI_F64X2_REPLACE_LANE = SIMD_INSTR_MASK | 34, + + WI_I8X16_SWIZZLE = SIMD_INSTR_MASK | 14, + WI_I8X16_SPLAT = SIMD_INSTR_MASK | 15, + WI_I16X8_SPLAT = SIMD_INSTR_MASK | 16, + WI_I32X4_SPLAT = SIMD_INSTR_MASK | 17, + WI_I64X2_SPLAT = SIMD_INSTR_MASK | 18, + WI_F32X4_SPLAT = SIMD_INSTR_MASK | 19, + WI_F64X2_SPLAT = SIMD_INSTR_MASK | 20, + + WI_I8X16_EQ = SIMD_INSTR_MASK | 35, + WI_I8X16_NEQ = SIMD_INSTR_MASK | 36, + WI_I8X16_LT_S = SIMD_INSTR_MASK | 37, + WI_I8X16_LT_U = SIMD_INSTR_MASK | 38, + WI_I8X16_GT_S = SIMD_INSTR_MASK | 39, + WI_I8X16_GT_U = SIMD_INSTR_MASK | 40, + WI_I8X16_LE_S = SIMD_INSTR_MASK | 41, + WI_I8X16_LE_U = SIMD_INSTR_MASK | 42, + WI_I8X16_GE_S = SIMD_INSTR_MASK | 43, + WI_I8X16_GE_U = SIMD_INSTR_MASK | 44, + + WI_I16X8_EQ = SIMD_INSTR_MASK | 45, + WI_I16X8_NEQ = SIMD_INSTR_MASK | 46, + WI_I16X8_LT_S = SIMD_INSTR_MASK | 47, + WI_I16X8_LT_U = SIMD_INSTR_MASK | 48, + WI_I16X8_GT_S = SIMD_INSTR_MASK | 49, + WI_I16X8_GT_U = SIMD_INSTR_MASK | 50, + WI_I16X8_LE_S = SIMD_INSTR_MASK | 51, + WI_I16X8_LE_U = SIMD_INSTR_MASK | 52, + WI_I16X8_GE_S = SIMD_INSTR_MASK | 53, + WI_I16X8_GE_U = SIMD_INSTR_MASK | 54, + + WI_I32X4_EQ = SIMD_INSTR_MASK | 55, + WI_I32X4_NEQ = SIMD_INSTR_MASK | 56, + WI_I32X4_LT_S = SIMD_INSTR_MASK | 57, + WI_I32X4_LT_U = SIMD_INSTR_MASK | 58, + WI_I32X4_GT_S = SIMD_INSTR_MASK | 59, + WI_I32X4_GT_U = SIMD_INSTR_MASK | 60, + WI_I32X4_LE_S = SIMD_INSTR_MASK | 61, + WI_I32X4_LE_U = SIMD_INSTR_MASK | 62, + WI_I32X4_GE_S = SIMD_INSTR_MASK | 63, + WI_I32X4_GE_U = SIMD_INSTR_MASK | 64, + + WI_F32X4_EQ = SIMD_INSTR_MASK | 65, + WI_F32X4_NEQ = SIMD_INSTR_MASK | 66, + WI_F32X4_LT = SIMD_INSTR_MASK | 67, + WI_F32X4_GT = SIMD_INSTR_MASK | 68, + WI_F32X4_LE = SIMD_INSTR_MASK | 69, + WI_F32X4_GE = SIMD_INSTR_MASK | 70, + + WI_F64X2_EQ = SIMD_INSTR_MASK | 71, + WI_F64X2_NEQ = SIMD_INSTR_MASK | 72, + WI_F64X2_LT = SIMD_INSTR_MASK | 73, + WI_F64X2_GT = SIMD_INSTR_MASK | 74, + WI_F64X2_LE = SIMD_INSTR_MASK | 75, + WI_F64X2_GE = SIMD_INSTR_MASK | 76, + + WI_V128_NOT = SIMD_INSTR_MASK | 77, + WI_V128_AND = SIMD_INSTR_MASK | 78, + WI_V128_ANDNOT = SIMD_INSTR_MASK | 79, + WI_V128_OR = SIMD_INSTR_MASK | 80, + WI_V128_XOR = SIMD_INSTR_MASK | 81, + WI_V128_BITSELECT = SIMD_INSTR_MASK | 82, + + WI_I8X16_ABS = SIMD_INSTR_MASK | 96, + WI_I8X16_NEG = SIMD_INSTR_MASK | 97, + WI_I8X16_ANY_TRUE = SIMD_INSTR_MASK | 98, + WI_I8X16_ALL_TRUE = SIMD_INSTR_MASK | 99, + WI_I8X16_BITMASK = SIMD_INSTR_MASK | 100, + WI_I8X16_NARROW_I16X8_S = SIMD_INSTR_MASK | 101, + WI_I8X16_NARROW_I16X8_U = SIMD_INSTR_MASK | 102, + WI_I8X16_SHL = SIMD_INSTR_MASK | 107, + WI_I8X16_SHR_S = SIMD_INSTR_MASK | 108, + WI_I8X16_SHR_U = SIMD_INSTR_MASK | 109, + WI_I8X16_ADD = SIMD_INSTR_MASK | 110, + WI_I8X16_ADD_SAT_S = SIMD_INSTR_MASK | 111, + WI_I8X16_ADD_SAT_U = SIMD_INSTR_MASK | 112, + WI_I8X16_SUB = SIMD_INSTR_MASK | 113, + WI_I8X16_SUB_SAT_S = SIMD_INSTR_MASK | 114, + WI_I8X16_SUB_SAT_U = SIMD_INSTR_MASK | 115, + WI_I8X16_MIN_S = SIMD_INSTR_MASK | 118, + WI_I8X16_MIN_U = SIMD_INSTR_MASK | 119, + WI_I8X16_MAX_S = SIMD_INSTR_MASK | 120, + WI_I8X16_MAX_U = SIMD_INSTR_MASK | 121, + WI_I8X16_AVGR_U = SIMD_INSTR_MASK | 123, + + WI_I16X8_ABS = SIMD_INSTR_MASK | 128, + WI_I16X8_NEG = SIMD_INSTR_MASK | 129, + WI_I16X8_ANY_TRUE = SIMD_INSTR_MASK | 130, + WI_I16X8_ALL_TRUE = SIMD_INSTR_MASK | 131, + WI_I16X8_BITMASK = SIMD_INSTR_MASK | 132, + WI_I16X8_NARROW_I32X4_S = SIMD_INSTR_MASK | 133, + WI_I16X8_NARROW_I32X4_U = SIMD_INSTR_MASK | 134, + WI_I16X8_WIDEN_LOW_I8X16_S = SIMD_INSTR_MASK | 135, + WI_I16X8_WIDEN_HIGH_I8X16_S = SIMD_INSTR_MASK | 136, + WI_I16X8_WIDEN_LOW_I8X16_U = SIMD_INSTR_MASK | 137, + WI_I16X8_WIDEN_HIGH_I8X16_U = SIMD_INSTR_MASK | 138, + WI_I16X8_SHL = SIMD_INSTR_MASK | 139, + WI_I16X8_SHR_S = SIMD_INSTR_MASK | 140, + WI_I16X8_SHR_U = SIMD_INSTR_MASK | 141, + WI_I16X8_ADD = SIMD_INSTR_MASK | 142, + WI_I16X8_ADD_SAT_S = SIMD_INSTR_MASK | 143, + WI_I16X8_ADD_SAT_U = SIMD_INSTR_MASK | 144, + WI_I16X8_SUB = SIMD_INSTR_MASK | 145, + WI_I16X8_SUB_SAT_S = SIMD_INSTR_MASK | 146, + WI_I16X8_SUB_SAT_U = SIMD_INSTR_MASK | 147, + WI_I16X8_MUL = SIMD_INSTR_MASK | 149, + WI_I16X8_MIN_S = SIMD_INSTR_MASK | 150, + WI_I16X8_MIN_U = SIMD_INSTR_MASK | 151, + WI_I16X8_MAX_S = SIMD_INSTR_MASK | 152, + WI_I16X8_MAX_U = SIMD_INSTR_MASK | 153, + WI_I16X8_AVGR_U = SIMD_INSTR_MASK | 155, + + WI_I32X4_ABS = SIMD_INSTR_MASK | 160, + WI_I32X4_NEG = SIMD_INSTR_MASK | 161, + WI_I32X4_ANY_TRUE = SIMD_INSTR_MASK | 162, + WI_I32X4_ALL_TRUE = SIMD_INSTR_MASK | 163, + WI_I32X4_BITMASK = SIMD_INSTR_MASK | 164, + WI_I32X4_WIDEN_LOW_I16X8_S = SIMD_INSTR_MASK | 167, + WI_I32X4_WIDEN_HIGH_I16X8_S = SIMD_INSTR_MASK | 168, + WI_I32X4_WIDEN_LOW_I16X8_U = SIMD_INSTR_MASK | 169, + WI_I32X4_WIDEN_HIGH_I16X8_U = SIMD_INSTR_MASK | 170, + WI_I32X4_SHL = SIMD_INSTR_MASK | 171, + WI_I32X4_SHR_S = SIMD_INSTR_MASK | 172, + WI_I32X4_SHR_U = SIMD_INSTR_MASK | 173, + WI_I32X4_ADD = SIMD_INSTR_MASK | 174, + WI_I32X4_SUB = SIMD_INSTR_MASK | 177, + WI_I32X4_MUL = SIMD_INSTR_MASK | 181, + WI_I32X4_MIN_S = SIMD_INSTR_MASK | 182, + WI_I32X4_MIN_U = SIMD_INSTR_MASK | 183, + WI_I32X4_MAX_S = SIMD_INSTR_MASK | 184, + WI_I32X4_MAX_U = SIMD_INSTR_MASK | 185, + + WI_I64X2_NEG = SIMD_INSTR_MASK | 193, + WI_I64X2_SHL = SIMD_INSTR_MASK | 203, + WI_I64X2_SHR_S = SIMD_INSTR_MASK | 204, + WI_I64X2_SHR_U = SIMD_INSTR_MASK | 205, + WI_I64X2_ADD = SIMD_INSTR_MASK | 206, + WI_I64X2_SUB = SIMD_INSTR_MASK | 209, + WI_I64X2_MUL = SIMD_INSTR_MASK | 213, + + WI_F32X4_ABS = SIMD_INSTR_MASK | 224, + WI_F32X4_NEG = SIMD_INSTR_MASK | 225, + WI_F32X4_SQRT = SIMD_INSTR_MASK | 227, + WI_F32X4_ADD = SIMD_INSTR_MASK | 228, + WI_F32X4_SUB = SIMD_INSTR_MASK | 229, + WI_F32X4_MUL = SIMD_INSTR_MASK | 230, + WI_F32X4_DIV = SIMD_INSTR_MASK | 231, + WI_F32X4_MIN = SIMD_INSTR_MASK | 232, + WI_F32X4_MAX = SIMD_INSTR_MASK | 233, + + WI_F64X2_ABS = SIMD_INSTR_MASK | 236, + WI_F64X2_NEG = SIMD_INSTR_MASK | 237, + WI_F64X2_SQRT = SIMD_INSTR_MASK | 239, + WI_F64X2_ADD = SIMD_INSTR_MASK | 240, + WI_F64X2_SUB = SIMD_INSTR_MASK | 241, + WI_F64X2_MUL = SIMD_INSTR_MASK | 242, + WI_F64X2_DIV = SIMD_INSTR_MASK | 243, + WI_F64X2_MIN = SIMD_INSTR_MASK | 244, + WI_F64X2_MAX = SIMD_INSTR_MASK | 245, + + WI_I32X4_TRUNC_SAT_F32X4_S = SIMD_INSTR_MASK | 248, + WI_I32X4_TRUNC_SAT_F32X4_U = SIMD_INSTR_MASK | 249, + WI_F32X4_CONVERT_I32X4_S = SIMD_INSTR_MASK | 250, + WI_F32X4_CONVERT_I32X4_U = SIMD_INSTR_MASK | 251, + + + WI_MEMORY_INIT = EXT_INSTR_MASK | 0x08, + WI_MEMORY_COPY = EXT_INSTR_MASK | 0x0a, + WI_MEMORY_FILL = EXT_INSTR_MASK | 0x0b, + + WI_ATOMIC_NOTIFY = ATOMIC_INSTR_MASK | 0x00, + WI_ATOMIC_WAIT32 = ATOMIC_INSTR_MASK | 0x01, + WI_ATOMIC_WAIT64 = ATOMIC_INSTR_MASK | 0x02, + + WI_ATOMIC_FENCE = ATOMIC_INSTR_MASK | 0x03, + + WI_ATOMIC_I32_LOAD = ATOMIC_INSTR_MASK | 0x10, + WI_ATOMIC_I64_LOAD = ATOMIC_INSTR_MASK | 0x11, + WI_ATOMIC_I32_LOAD8_U = ATOMIC_INSTR_MASK | 0x12, + WI_ATOMIC_I32_LOAD16_U = ATOMIC_INSTR_MASK | 0x13, + WI_ATOMIC_I64_LOAD8_U = ATOMIC_INSTR_MASK | 0x14, + WI_ATOMIC_I64_LOAD16_U = ATOMIC_INSTR_MASK | 0x15, + WI_ATOMIC_I64_LOAD32_U = ATOMIC_INSTR_MASK | 0x16, + + WI_ATOMIC_I32_STORE = ATOMIC_INSTR_MASK | 0x17, + WI_ATOMIC_I64_STORE = ATOMIC_INSTR_MASK | 0x18, + WI_ATOMIC_I32_STORE8 = ATOMIC_INSTR_MASK | 0x19, + WI_ATOMIC_I32_STORE16 = ATOMIC_INSTR_MASK | 0x1a, + WI_ATOMIC_I64_STORE8 = ATOMIC_INSTR_MASK | 0x1b, + WI_ATOMIC_I64_STORE16 = ATOMIC_INSTR_MASK | 0x1c, + WI_ATOMIC_I64_STORE32 = ATOMIC_INSTR_MASK | 0x1d, + + WI_ATOMIC_I32_ADD = ATOMIC_INSTR_MASK | 0x1e, + WI_ATOMIC_I64_ADD = ATOMIC_INSTR_MASK | 0x1f, + WI_ATOMIC_I32_ADD8_U = ATOMIC_INSTR_MASK | 0x20, + WI_ATOMIC_I32_ADD16_U = ATOMIC_INSTR_MASK | 0x21, + WI_ATOMIC_I64_ADD8_U = ATOMIC_INSTR_MASK | 0x22, + WI_ATOMIC_I64_ADD16_U = ATOMIC_INSTR_MASK | 0x23, + WI_ATOMIC_I64_ADD32_U = ATOMIC_INSTR_MASK | 0x24, + + WI_ATOMIC_I32_SUB = ATOMIC_INSTR_MASK | 0x25, + WI_ATOMIC_I64_SUB = ATOMIC_INSTR_MASK | 0x26, + WI_ATOMIC_I32_SUB8_U = ATOMIC_INSTR_MASK | 0x27, + WI_ATOMIC_I32_SUB16_U = ATOMIC_INSTR_MASK | 0x28, + WI_ATOMIC_I64_SUB8_U = ATOMIC_INSTR_MASK | 0x29, + WI_ATOMIC_I64_SUB16_U = ATOMIC_INSTR_MASK | 0x2a, + WI_ATOMIC_I64_SUB32_U = ATOMIC_INSTR_MASK | 0x2b, + + WI_ATOMIC_I32_AND = ATOMIC_INSTR_MASK | 0x2c, + WI_ATOMIC_I64_AND = ATOMIC_INSTR_MASK | 0x2d, + WI_ATOMIC_I32_AND8_U = ATOMIC_INSTR_MASK | 0x2e, + WI_ATOMIC_I32_AND16_U = ATOMIC_INSTR_MASK | 0x2f, + WI_ATOMIC_I64_AND8_U = ATOMIC_INSTR_MASK | 0x30, + WI_ATOMIC_I64_AND16_U = ATOMIC_INSTR_MASK | 0x31, + WI_ATOMIC_I64_AND32_U = ATOMIC_INSTR_MASK | 0x32, + + WI_ATOMIC_I32_OR = ATOMIC_INSTR_MASK | 0x33, + WI_ATOMIC_I64_OR = ATOMIC_INSTR_MASK | 0x34, + WI_ATOMIC_I32_OR8_U = ATOMIC_INSTR_MASK | 0x35, + WI_ATOMIC_I32_OR16_U = ATOMIC_INSTR_MASK | 0x36, + WI_ATOMIC_I64_OR8_U = ATOMIC_INSTR_MASK | 0x37, + WI_ATOMIC_I64_OR16_U = ATOMIC_INSTR_MASK | 0x38, + WI_ATOMIC_I64_OR32_U = ATOMIC_INSTR_MASK | 0x39, + + WI_ATOMIC_I32_XOR = ATOMIC_INSTR_MASK | 0x3a, + WI_ATOMIC_I64_XOR = ATOMIC_INSTR_MASK | 0x3b, + WI_ATOMIC_I32_XOR8_U = ATOMIC_INSTR_MASK | 0x3c, + WI_ATOMIC_I32_XOR16_U = ATOMIC_INSTR_MASK | 0x3d, + WI_ATOMIC_I64_XOR8_U = ATOMIC_INSTR_MASK | 0x3e, + WI_ATOMIC_I64_XOR16_U = ATOMIC_INSTR_MASK | 0x3f, + WI_ATOMIC_I64_XOR32_U = ATOMIC_INSTR_MASK | 0x40, + + WI_ATOMIC_I32_XCHG = ATOMIC_INSTR_MASK | 0x41, + WI_ATOMIC_I64_XCHG = ATOMIC_INSTR_MASK | 0x42, + WI_ATOMIC_I32_XCHG8_U = ATOMIC_INSTR_MASK | 0x43, + WI_ATOMIC_I32_XCHG16_U = ATOMIC_INSTR_MASK | 0x44, + WI_ATOMIC_I64_XCHG8_U = ATOMIC_INSTR_MASK | 0x45, + WI_ATOMIC_I64_XCHG16_U = ATOMIC_INSTR_MASK | 0x46, + WI_ATOMIC_I64_XCHG32_U = ATOMIC_INSTR_MASK | 0x47, + + WI_ATOMIC_I32_CMPXCHG = ATOMIC_INSTR_MASK | 0x48, + WI_ATOMIC_I64_CMPXCHG = ATOMIC_INSTR_MASK | 0x49, + WI_ATOMIC_I32_CMPXCHG8_U = ATOMIC_INSTR_MASK | 0x4a, + WI_ATOMIC_I32_CMPXCHG16_U = ATOMIC_INSTR_MASK | 0x4b, + WI_ATOMIC_I64_CMPXCHG8_U = ATOMIC_INSTR_MASK | 0x4c, + WI_ATOMIC_I64_CMPXCHG16_U = ATOMIC_INSTR_MASK | 0x4d, + WI_ATOMIC_I64_CMPXCHG32_U = ATOMIC_INSTR_MASK | 0x4e, +} WasmInstructionType; + +typedef union { + struct { + i32 i1, i2; + }; + i64 l; + float f; + double d; + ptr p; +} WasmInstructionData; + +typedef struct WasmInstruction { + WasmInstructionType type; + WasmInstructionData data; +} WasmInstruction; + +typedef struct BranchTable { + u32 count; + u32 default_case; + u32 cases[]; +} BranchTable; + +#define LOCAL_IS_WASM 0x8000000000000 +typedef struct LocalAllocator { + u32 param_count; + + u32 allocated[5]; + u32 freed[5]; + + i32 max_stack; + i32 curr_stack; +} LocalAllocator; + +typedef struct WasmFunc { + i32 type_idx; + LocalAllocator locals; + bh_arr(WasmInstruction) code; + OnyxToken *location; +} WasmFunc; + +typedef struct WasmGlobal { + WasmType type; + u32 mutable : 1; + bh_arr(WasmInstruction) initial_value; +} WasmGlobal; + +typedef enum WasmForeignKind { + WASM_FOREIGN_FUNCTION = 0x00, + WASM_FOREIGN_TABLE = 0x01, + WASM_FOREIGN_MEMORY = 0x02, + WASM_FOREIGN_GLOBAL = 0x03, +} WasmForeignKind; + +typedef struct WasmExport { + WasmForeignKind kind; + i32 idx; +} WasmExport; + +typedef struct WasmImport { + WasmForeignKind kind; + union { + i32 idx; + struct { + i32 min, max; + b32 shared; + }; + }; + char *mod, *name; +} WasmImport; + +typedef struct WasmDatum { + u32 id; + u32 offset_, alignment; + u32 length; + ptr data; +} WasmDatum; + +typedef enum DatumPatchInfoKind { + Datum_Patch_Instruction, + Datum_Patch_Data, + Datum_Patch_Relative, +} DatumPatchInfoKind; + +// +// This represents a pointer that should be filled in +// later when the corresponding data element is placed. +// +// There are three kinds of patches: +// - Instruction +// - Data +// - Relative +// +// In all cases, the `data_id` member is set to the id +// of the WasmDatum entry that will be the base address, +// and then the `offset` member will be added to that. +// +// In instruction patches, the `index` member is set +// to the index of the function where the instruction should +// be patched. The `location` member is set to the instruction +// that needs to have its data changed. +// +// In data patches, the `index` member is set to the id +// of the WasmDatum entry that needs to have a part of it +// updated. The `location` member is the offset into the +// data to update. It is assumed that 4 bytes will be reserved +// to be replaced with the pointer value. +// +// In relative patches, `index` member is set to the id +// of the WasmDatum entry that needs to have a part of it +// updated. The `location` member is the offset into the +// data to update. The difference between `Data` and `Relative` +// is that `Relative` *adds* the base address to the current +// value in the 4 bytes, as opposed to replacing it. As a +// convenience, if the value is 0 (null), it will remain as +// 0. +// +typedef struct DatumPatchInfo { + DatumPatchInfoKind kind; + u32 data_id; + u32 offset; + u32 location; + u32 index; +} DatumPatchInfo; + +// Context used when building a constexpr buffer +typedef struct ConstExprContext { + struct OnyxWasmModule *module; + ptr data; + u32 data_id; +} ConstExprContext; + +typedef enum DeferredStmtType { + Deferred_Stmt_Node, + Deferred_Stmt_Code, +} DeferredStmtType; + +typedef struct DeferredStmt { + DeferredStmtType type; + u32 depth; + AstDefer *defer_node; + + union { + AstNode *stmt; + struct { + WasmInstruction* instructions; + u32 instruction_count; + }; + }; +} DeferredStmt; + +typedef struct AllocatedSpace { + u64 depth; + AstTyped *expr; +} AllocatedSpace; + +typedef struct StrLitInfo { + u32 data_id; + u32 len; +} StrLitInfo; + +typedef struct PatchInfo { + u32 instruction_index; +} PatchInfo; + +typedef struct ForRemoveInfo { + // These are WASM locals + u64 iterator_remove_func; + u64 iterator_data_ptr; + + i32 remove_func_type_idx; +} ForRemoveInfo; + +typedef struct OnyxWasmModule { + bh_allocator allocator; + + bh_arena *extended_instr_data; + bh_allocator extended_instr_alloc; + + // NOTE: Mapping ptrs to function / global indicies + bh_imap index_map; + + // NOTE: Mapping from local ast node ptrs to indicies or offsets, depending on the mode + bh_imap local_map; + + i32 current_func_idx; + LocalAllocator* local_alloc; + + // NOTE: Mapping ptrs to elements + bh_imap elem_map; + + bh_arr(DeferredStmt) deferred_stmts; + bh_arr(AllocatedSpace) local_allocations; + + bh_arr(PatchInfo) stack_leave_patches; + bh_arr(DatumPatchInfo) data_patches; + + bh_arr(ForRemoveInfo) for_remove_info; + + bh_arr(AstForeignBlock *) foreign_blocks; + u32 next_foreign_block_idx; + + bh_arr(AstFunction *) procedures_with_tags; + + // NOTE: Used internally as a map from strings that represent function types, + // 0x7f 0x7f : 0x7f ( (i32, i32) -> i32 ) + // to the function type index if it has been created. + Table(i32) type_map; + + Table(StrLitInfo) loaded_file_info; + Table(StrLitInfo) string_literals; + + bh_arr(u8) structured_jump_target; + bh_arr(AstLocal*) return_location_stack; // NOTE: Used for do-block return expressions. + + bh_arr(WasmFuncType*) types; // NOTE: This have to be pointers because the type is variadic in size + bh_arr(WasmImport) imports; + Table(WasmExport) exports; + bh_arr(WasmGlobal) globals; + bh_arr(WasmFunc) funcs; + bh_arr(WasmDatum) data; + bh_arr(i32) elems; + bh_arr(char *) libraries; + bh_arr(char *) library_paths; + b32 needs_memory_section; + u32 memory_min_size; + u32 memory_max_size; + + // NOTE: Set of things used when compiling; not part of the actual module + u32 export_count; + u32 next_type_idx; + u32 next_func_idx; + u32 next_global_idx; + u32 next_tls_offset; + u32 next_elem_idx; + u32 foreign_function_count; + + i32 *stack_top_ptr; + i32 *tls_size_ptr; + i32 *heap_start_ptr; + u64 stack_base_idx; + CallingConvention curr_cc; + i32 null_proc_func_idx; + + b32 has_stack_locals : 1; + +#ifdef ENABLE_DEBUG_INFO + struct DebugContext *debug_context; +#endif + +} OnyxWasmModule; + +typedef struct OnyxWasmLinkOptions { + b32 stack_first; + u32 stack_size; + u32 stack_alignment; + + u32 null_reserve_size; + + b32 import_memory; + char *import_memory_module_name; + char *import_memory_import_name; + + b32 export_memory; + char *export_memory_name; + + b32 export_func_table; + char *export_func_table_name; + + u32 memory_min_size; + u32 memory_max_size; +} OnyxWasmLinkOptions; + +b32 onyx_wasm_build_link_options_from_node(OnyxWasmLinkOptions *opts, struct AstTyped *node); + +OnyxWasmModule onyx_wasm_module_create(bh_allocator alloc); +void onyx_wasm_module_link(OnyxWasmModule *module, OnyxWasmLinkOptions *options); +void onyx_wasm_module_free(OnyxWasmModule* module); +void onyx_wasm_module_write_to_buffer(OnyxWasmModule* module, bh_buffer* buffer); +void onyx_wasm_module_write_to_file(OnyxWasmModule* module, bh_file file); + +#ifdef ENABLE_RUN_WITH_WASMER +void onyx_run_initialize(b32 debug_enabled); +b32 onyx_run_wasm(bh_buffer code_buffer, int argc, char *argv[]); +#endif + +#ifdef ENABLE_DEBUG_INFO + +typedef enum DebugOpType { + DOT_END = 0b00000000, + DOT_SET = 0b00000001, + DOT_PUSHF = 0b00000010, + DOT_POPF = 0b00000011, + DOT_SYM = 0b00000100, + + DOT_INC = 0b01000000, + DOT_DEC = 0b10000000, + DOT_REP = 0b11000000, +} DebugOpType; + +typedef struct DebugFuncContext { + u32 func_index; + u32 file_id; + u32 line; + u32 op_offset; + u32 stack_ptr_idx; + + u32 name_length; + char *name; +} DebugFuncContext; + +typedef struct DebugFileInfo { + u32 file_id; + u32 line_count; +} DebugFileInfo; + +typedef enum DebugSymLoc { + DSL_REGISTER = 1, + DSL_STACK = 2, + DSL_GLOBAL = 3, +} DebugSymLoc; + +typedef struct DebugSymInfo { + u32 sym_id; + u32 location_type; + u64 location_num; + char *name; + u32 type; +} DebugSymInfo; + +typedef struct DebugSymPatch { + u32 func_idx; + u32 sym_id; + u64 local_idx; +} DebugSymPatch; + +typedef struct DebugContext { + bh_allocator allocator; + + Table(DebugFileInfo) file_info; + u32 next_file_id; + + bh_arr(DebugSymInfo) sym_info; + bh_arr(DebugSymPatch) sym_patches; + u32 next_sym_id; + + bh_arr(DebugFuncContext) funcs; + bh_buffer op_buffer; + + // Used during building the debug info + OnyxToken *last_token; + b32 last_op_was_rep : 1; +} DebugContext; + +#endif + +#endif diff --git a/compiler/src/astnodes.c b/compiler/src/astnodes.c new file mode 100644 index 00000000..f2c6faf2 --- /dev/null +++ b/compiler/src/astnodes.c @@ -0,0 +1,1406 @@ +#include "astnodes.h" +#include "parser.h" +#include "utils.h" + +static const char* ast_node_names[] = { + "ERROR", + "PACKAGE", + "INCLUDE FILE", + "INCLUDE FOLDER", + "INCLUDE ALL IN FOLDER", + "INCLUDE LIBRARY PATH", + "MEMORY RESERVATION", + + "BINDING", + "ALIAS", + "INJECTION", + "FUNCTION", + "OVERLOADED_FUNCTION", + "POLYMORPHIC PROC", + "POLYMORPH QUERY", + "INTERFACE", + "CONSTRAINT", + "CONSTRAINT SENTITEL", + "BLOCK", + "LOCAL", + "GLOBAL", + "SYMBOL", + + "UN_OP", + "BIN_OP", + + "COMPOUND", + "NAMED_VALUE", + + "TYPE_START (BAD)", + "TYPE", + "BASIC_TYPE", + "POINTER_TYPE", + "FUNCTION_TYPE", + "ARRAY TYPE", + "SLICE TYPE", + "DYNARR TYPE", + "VARARG TYPE", + "STRUCT TYPE", + "POLYMORPHIC STRUCT TYPE", + "POLYMORPHIC STRUCT CALL TYPE", + "ENUM TYPE", + "TYPE_ALIAS", + "TYPE RAW ALIAS", + "COMPOUND TYPE", + "TYPE OF", + "DISTINCT TYPE", + "TYPE_END (BAD)", + + "STRUCT MEMBER", + "ENUM VALUE", + + "NUMERIC LITERAL", + "STRING LITERAL", + "PARAM", + "ARGUMENT", + "CALL", + "INTRINSIC CALL", + "RETURN", + "ADDRESS OF", + "DEREFERENCE", + "ARRAY ACCESS", + "SLICE", + "FIELD ACCESS", + "UNARY FIELD ACCESS", + "PIPE", + "METHOD_CALL", + "RANGE", + "SIZE OF", + "ALIGN OF", + "FILE CONTENTS", + "STRUCT LITERAL", + "ARRAY LITERAL", + "IF EXPRESSION", + + "IF", + "FOR", + "WHILE", + "JUMP", + "USE", + "DEFER", + "SWITCH", + "CASE", + + "SOLIDIFY", + "STATIC IF", + "STATIC ERROR", + "ADD OVERLOAD", + "OPERATOR OVERLOAD", + "EXPORT", + "DEFINED", + "TAG", + "INIT", + "LIBRARY", + "REMOVE", + "CALL SITE", + + "CODE BLOCK", + "DIRECTIVE INSERT", + "MACRO", + "DO BLOCK", + + "FOREIGN BLOCK", + "ZERO VALUE", + + "NOTE", + + "AST_NODE_KIND_COUNT", +}; + +const char* onyx_ast_node_kind_string(AstKind kind) { + return ast_node_names[kind]; +} + +const char *binaryop_string[Binary_Op_Count] = { + "+", "-", "*", "/", "%", + "==", "!=", "<", "<=", ">", ">=", + "&", "|", "^", "<<", ">>", ">>>", + "&&", "||", + + "NONE", + "=", "+=", "-=", "*=", "/=", "%=", + "&=", "|=", "^=", "<<=", ">>=", ">>>=", + "NONE", + + "|>", "..", "->", + + "[]", "[]=", "^[]", +}; + +const char* entity_state_strings[Entity_State_Count] = { + "Error", + "Parse Builtin", + "Introduce Symbols", + "Parse", + "Resolve Symbols", + "Check Types", + "Code Gen", + "Finalized", + "Failed", +}; + +const char* entity_type_strings[Entity_Type_Count] = { + "Unknown", + "Error", + "Note", + "Add to Load Path", + "Load File", + "Binding (Declaration)", + "Use Package", + "Static If", + "String Literal", + "File Contents", + "Enum", + "Enum Value", + "Type Alias", + "Memory Reservation Type", + "Use", + "Interface", + "Constraint Check", + "Polymorphic Proc", + "Polymorph Query", + "Foreign Block", + "Macro", + "Foreign_Function Header", + "Temporary Function Header", + "Function Header", + "Global Header", + "Process Directive", + "Struct Member Default", + "Memory Reservation", + "Expression", + "Global", + "Overloaded_Function", + "Function", +}; + +AstNumLit* ast_reduce_type_compare(bh_allocator a, AstBinaryOp* node) { + AstType* left = (AstType *) ast_reduce(a, node->left); + AstType* right = (AstType *) ast_reduce(a, node->right); + + Type* left_type = type_build_from_ast(context.ast_alloc, left); + Type* right_type = type_build_from_ast(context.ast_alloc, right); + + AstNumLit* res = onyx_ast_node_new(a, sizeof(AstNumLit), Ast_Kind_NumLit); + res->token = node->token; + res->flags |= node->flags; + res->flags |= Ast_Flag_Comptime; + res->type_node = (AstType *) &basic_type_bool; + res->type = &basic_types[Basic_Kind_Bool]; + res->next = node->next; + + switch (node->operation) { + case Binary_Op_Equal: res->value.l = left_type->id == right_type->id; break; + case Binary_Op_Not_Equal: res->value.l = left_type->id != right_type->id; break; + default: assert(("Bad case in ast_reduce_type_compare", 0)); + } + + return res; +} + +#define REDUCE_BINOP_ALL(op) \ + if (type_is_small_integer(res->type) || type_is_bool(res->type)) { \ + res->value.i = left->value.i op right->value.i; \ + } else if (type_is_integer(res->type) \ + || res->type->Basic.kind == Basic_Kind_Int_Unsized \ + || res->type->kind == Type_Kind_Enum) { \ + res->value.l = left->value.l op right->value.l; \ + } else if (res->type->Basic.kind == Basic_Kind_F32) { \ + res->value.f = left->value.f op right->value.f; \ + } else if (res->type->Basic.kind == Basic_Kind_F64 || res->type->Basic.kind == Basic_Kind_Float_Unsized) { \ + res->value.d = left->value.d op right->value.d; \ + } \ + break; + +#define REDUCE_BINOP_INT(op) \ + if (type_is_small_integer(res->type) || type_is_bool(res->type)) { \ + res->value.i = left->value.i op right->value.i; \ + } else if (type_is_integer(res->type) \ + || res->type->Basic.kind == Basic_Kind_Int_Unsized \ + || res->type->kind == Type_Kind_Enum) { \ + res->value.l = left->value.l op right->value.l; \ + } \ + break; + +#define REDUCE_BINOP_BOOL(op) \ + if (type_is_bool(res->type)) { \ + res->value.i = left->value.i op right->value.i; \ + } \ + break; + +AstNumLit* ast_reduce_binop(bh_allocator a, AstBinaryOp* node) { + AstNumLit* left = (AstNumLit *) ast_reduce(a, node->left); + AstNumLit* right = (AstNumLit *) ast_reduce(a, node->right); + + if (node_is_type((AstNode *) left) && node_is_type((AstNode *) right)) { + if (node->operation == Binary_Op_Equal || node->operation == Binary_Op_Not_Equal) { + return (AstNumLit *) ast_reduce_type_compare(a, node); + } + } + + if (left->kind != Ast_Kind_NumLit || right->kind != Ast_Kind_NumLit) { + node->left = (AstTyped *) left; + node->right = (AstTyped *) right; + return (AstNumLit *) node; + } + + AstNumLit* res = onyx_ast_node_new(a, sizeof(AstNumLit), Ast_Kind_NumLit); + res->token = node->token; + res->flags |= node->flags; + res->flags |= Ast_Flag_Comptime; + res->type_node = node->type_node; + res->type = node->type; + res->next = node->next; + + switch (node->operation) { + case Binary_Op_Add: REDUCE_BINOP_ALL(+); + case Binary_Op_Minus: REDUCE_BINOP_ALL(-); + case Binary_Op_Multiply: REDUCE_BINOP_ALL(*); + case Binary_Op_Divide: REDUCE_BINOP_ALL(/); + case Binary_Op_Modulus: REDUCE_BINOP_INT(%); + + case Binary_Op_Equal: REDUCE_BINOP_ALL(==); + case Binary_Op_Not_Equal: REDUCE_BINOP_ALL(!=); + case Binary_Op_Less: REDUCE_BINOP_ALL(<); + case Binary_Op_Less_Equal: REDUCE_BINOP_ALL(<=); + case Binary_Op_Greater: REDUCE_BINOP_ALL(>); + case Binary_Op_Greater_Equal: REDUCE_BINOP_ALL(>=); + + case Binary_Op_And: REDUCE_BINOP_INT(&); + case Binary_Op_Or: REDUCE_BINOP_INT(|); + case Binary_Op_Xor: REDUCE_BINOP_INT(^); + case Binary_Op_Shl: REDUCE_BINOP_INT(<<); + case Binary_Op_Shr: REDUCE_BINOP_INT(>>); + case Binary_Op_Sar: REDUCE_BINOP_INT(>>); + + case Binary_Op_Bool_And: REDUCE_BINOP_BOOL(&&); + case Binary_Op_Bool_Or: REDUCE_BINOP_BOOL(||); + + default: break; + } + + return res; +} + +#define REDUCE_UNOP(op) \ + if (type_is_small_integer(unop->type) || type_is_bool(unop->type)) { \ + res->value.i = op (operand)->value.i; \ + } else if (type_is_integer(unop->type) || unop->type->Basic.kind == Basic_Kind_Int_Unsized) { \ + res->value.l = op (operand)->value.l; \ + } else if (unop->type->Basic.kind == Basic_Kind_F32) { \ + res->value.f = op (operand)->value.f; \ + } else if (unop->type->Basic.kind == Basic_Kind_F64 || unop->type->Basic.kind == Basic_Kind_Float_Unsized) { \ + res->value.d = op (operand)->value.d; \ + } \ + break; + +#define REDUCE_UNOP_INT(op) \ + if (type_is_small_integer(unop->type) || type_is_bool(unop->type)) { \ + res->value.i = op (operand)->value.i; \ + } else if (type_is_integer(unop->type) || res->type->Basic.kind == Basic_Kind_Int_Unsized) { \ + res->value.l = op (operand)->value.l; \ + } + +AstTyped* ast_reduce_unaryop(bh_allocator a, AstUnaryOp* unop) { + // GROSS + AstNumLit* operand = (AstNumLit *) ast_reduce(a, unop->expr); + unop->expr = (AstTyped *) operand; + + if (operand->kind != Ast_Kind_NumLit) { + return (AstTyped *) unop; + } + + AstNumLit* res = onyx_ast_node_new(a, sizeof(AstNumLit), Ast_Kind_NumLit); + res->token = unop->token; + res->flags |= Ast_Flag_Comptime; + res->type_node = unop->type_node; + res->type = unop->type; + res->next = unop->next; + + switch (unop->operation) { + case Unary_Op_Negate: REDUCE_UNOP(-); + case Unary_Op_Not: { + if (type_is_bool(res->type)) res->value.i = ! (operand)->value.i; + break; + } + case Unary_Op_Bitwise_Not: REDUCE_UNOP_INT(~); + + case Unary_Op_Cast: { + #if 0 + Type* from = operand->type; + Type* to = unop->type; + + if (from->kind == Type_Kind_Enum) from = from->Enum.backing; + if (to->kind == Type_Kind_Enum) to = to->Enum.backing; + + if (from->kind != Type_Kind_Pointer && from->kind != Type_Kind_Basic) return (AstTyped *) unop; + if (to->kind != Type_Kind_Pointer && to->kind != Type_Kind_Basic) return (AstTyped *) unop; + + b32 from_is_int=0, to_is_int=0; + i32 from_size, to_size; + + from_size = type_size_of(from); + to_size = type_size_of(to); + + if (from->kind == Type_Kind_Basic) from_is_int = (from->flags & (Basic_Flag_Integer | Basic_Flag_Pointer)) != 0; + if (to->kind == Type_Kind_Basic) to_is_int = (to->flags & (Basic_Flag_Integer | Basic_Flag_Pointer)) != 0; + + if (from->kind == Type_Kind_Pointer) from_is_int = 1; + if (to->kind == Type_Kind_Pointer) to_is_int = 1; + + if (from_is_int == to_is_int && from_size == to_size) { + // If they are already equal, nothing to do + return (AstTyped *) res; + } + + if (from_is_int && !to_is_int) { + if (from_size == to_size) { + if (from_size == 4) { + res->value.f = (f32) operand->value.i; + } + else if (from_size == 8) { + res->value.d = (f64) operand->value.l; + } + } else { + if (from_size == 4) { + res->value.d = (f64) operand->value.i; + } + else if (from_size == 8) { + res->value.f = (f32) operand->value.l; + } + } + } else { + if (from_size == to_size) { + if (from_size == 4) { + res->value.i = (i32) operand->value.f; + } + else if (from_size == 8) { + res->value.l = (i64) operand->value.d; + } + } else { + if (from_size == 4) { + res->value.l = (i64) operand->value.f; + } + else if (from_size == 8) { + res->value.i = (i32) operand->value.d; + } + } + } + break; + + #endif + } + + default: return (AstTyped *) unop; + } + + return (AstTyped *) res; +} + +AstTyped* ast_reduce(bh_allocator a, AstTyped* node) { + assert(node->flags & Ast_Flag_Comptime); + + switch (node->kind) { + case Ast_Kind_Binary_Op: return (AstTyped *) ast_reduce_binop(a, (AstBinaryOp *) node); + case Ast_Kind_Unary_Op: return (AstTyped *) ast_reduce_unaryop(a, (AstUnaryOp *) node); + case Ast_Kind_Enum_Value: return (AstTyped *) ((AstEnumValue *) node)->value; + case Ast_Kind_Alias: return (AstTyped *) ast_reduce(a, ((AstAlias *) node)->alias); + default: return node; + } +} + +void promote_numlit_to_larger(AstNumLit* num) { + assert(num->type != NULL); + + if (type_is_integer(num->type) && num->type->Basic.size <= 4) { + // NOTE: Int32, Int16, Int8 + i64 val = (i64) num->value.i; + num->value.l = val; + num->type = &basic_types[Basic_Kind_I64]; + } else if (num->type->Basic.size <= 4) { + // NOTE: Float32 + f64 val = (f64) num->value.f; + num->value.d = val; + num->type = &basic_types[Basic_Kind_F64]; + } +} + +// NOTE: Returns 1 if the conversion was successful. +b32 convert_numlit_to_type(AstNumLit* num, Type* to_type) { + if (num->type == NULL) + num->type = type_build_from_ast(context.ast_alloc, num->type_node); + assert(num->type); + + if (types_are_compatible(num->type, to_type)) return 1; + if (!type_is_numeric(to_type)) return 0; + + Type *type = to_type; + if (type->kind == Type_Kind_Enum) type = type->Enum.backing; + if (type->kind == Type_Kind_Distinct) type = type->Distinct.base_type; + + if (num->type->Basic.kind == Basic_Kind_Int_Unsized) { + + // + // Integer literal auto cast rules: + // - Up in size always works + // - Down in size only works if value is in range of smaller type. + // - Cast to float only works if value is less than the maximum precise value for float size. + // + + if (type->Basic.flags & Basic_Flag_Integer) { + if (type->Basic.flags & Basic_Flag_Unsigned) { + u64 value = (u64) num->value.l; + if (type->Basic.size == 8) { + num->type = to_type; + return 1; + } + switch (type->Basic.size) { + case 1: if (value <= 255) { + num->type = to_type; + return 1; + } + case 2: if (value <= 65535) { + num->type = to_type; + return 1; + } + case 4: if (value <= 4294967295) { + num->type = to_type; + return 1; + } + } + + onyx_report_error(num->token->pos, Error_Critical, "Unsigned integer constant with value '%l' does not fit into %d-bits.", + num->value.l, + type->Basic.size * 8); + + } else { + i64 value = (i64) num->value.l; + switch (type->Basic.size) { + case 1: if (-128ll <= value && value <= 127ll) { + num->value.i = (i32) value; + num->type = to_type; + return 1; + } break; + case 2: if (-32768ll <= value && value <= 32767ll) { + num->value.i = (i32) value; + num->type = to_type; + return 1; + } break; + case 4: if (-2147483648ll <= value && value <= 2147483647ll) { + num->value.i = (i32) value; + num->type = to_type; + return 1; + } break; + case 8: { num->type = to_type; + return 1; + } break; + } + + onyx_report_error(num->token->pos, Error_Critical, "Integer constant with value '%l' does not fit into %d-bits.", + num->value.l, + type->Basic.size * 8); + } + } + + else if (type->Basic.flags & Basic_Flag_Float) { + if (type->Basic.size == 4) { + // TODO(Brendan): Check these boundary conditions + if (bh_abs(num->value.l) >= (1 << 23)) { + onyx_report_error(num->token->pos, Error_Critical, "Integer '%l' does not fit in 32-bit float exactly.", num->value.l); + return 0; + } + + num->type = to_type; + num->value.f = (f32) num->value.l; + return 1; + } + if (type->Basic.size == 8) { + // TODO(Brendan): Check these boundary conditions + if (bh_abs(num->value.l) >= (1ull << 52)) { + onyx_report_error(num->token->pos, Error_Critical, "Integer '%l' does not fit in 64-bit float exactly.", num->value.l); + return 0; + } + + num->type = to_type; + num->value.d = (f64) num->value.l; + return 1; + } + } + } + else if (num->type->Basic.kind == Basic_Kind_Float_Unsized) { + // NOTE: Floats don't cast to integers implicitly. + if ((type->Basic.flags & Basic_Flag_Float) == 0) return 0; + + if (type->Basic.kind == Basic_Kind_F32) { + num->value.f = (f32) num->value.d; + } + + num->type = to_type; + return 1; + } + else if (num->type->Basic.kind == Basic_Kind_F32) { + // NOTE: Floats don't cast to integers implicitly. + if ((type->Basic.flags & Basic_Flag_Float) == 0) return 0; + + if (type->Basic.kind == Basic_Kind_F64) { + num->value.d = (f64) num->value.f; + num->type = to_type; + return 1; + } + } + + return 0; +} + +// TODO: This function should be able return a "yield" condition. There +// are a couple cases that need to yield in order to be correct, like +// polymorphic functions with a typeof for the return type. +TypeMatch unify_node_and_type_(AstTyped** pnode, Type* type, b32 permanent) { + AstTyped* node = *pnode; + if (type == NULL) return TYPE_MATCH_FAILED; + if (node == NULL) return TYPE_MATCH_FAILED; + + if (node->kind == Ast_Kind_Struct_Literal && (node->type_node == NULL && node->type == NULL)) { + if (node->entity != NULL) return TYPE_MATCH_SUCCESS; + if (type->kind == Type_Kind_VarArgs) type = type->VarArgs.elem; + + // + // If the structure literal has arguments, and the type is not constructable + // using a struct literal, then they cannot be unified. However, if no arguments + // are given, e.g. .{}, then any type should be matched, as that is the universal + // zero-value. + if (!type_is_sl_constructable(type)) { + AstStructLiteral *sl = (AstStructLiteral *) node; + if (bh_arr_length(sl->args.values) != 0 || bh_arr_length(sl->args.named_values) != 0) { + return TYPE_MATCH_FAILED; + } + } + + // If this shouldn't make permanent changes and submit entities, + // just assume that it works and don't submit the entities. + if (!permanent) return TYPE_MATCH_SUCCESS; + + node->type = type; + + add_entities_for_node(NULL, (AstNode *) node, NULL, NULL); + return TYPE_MATCH_SUCCESS; + } + + if (node->kind == Ast_Kind_Array_Literal && node->type == NULL) { + if (node->entity != NULL) return TYPE_MATCH_SUCCESS; + + // If this shouldn't make permanent changes and submit entities, + // just assume that it works and don't submit the entities. + if (!permanent) return TYPE_MATCH_SUCCESS; + + Type* array_type=NULL; + switch (type->kind) { + case Type_Kind_Array: array_type = type; break; + case Type_Kind_Slice: { + Type* elem_type = type->Slice.elem; + AstArrayLiteral* al = (AstArrayLiteral *) node; + array_type = type_make_array(context.ast_alloc, elem_type, bh_arr_length(al->values)); + + *pnode = (AstTyped *) make_cast(context.ast_alloc, node, type); + + break; + } + + default: assert(0); + } + + node->type = array_type; + node->flags |= Ast_Flag_Array_Literal_Typed; + + add_entities_for_node(NULL, (AstNode *) node, NULL, NULL); + return TYPE_MATCH_SUCCESS; + } + + if (node->kind == Ast_Kind_Unary_Field_Access) { + AstType* ast_type = type->ast_type; + AstNode* resolved = try_symbol_resolve_from_node((AstNode *) ast_type, node->token); + if (resolved == NULL) { + if (context.cycle_detected) { + token_toggle_end(node->token); + char *closest = find_closest_symbol_in_node((AstNode *) ast_type, node->token->text); + token_toggle_end(node->token); + + if (closest) { + onyx_report_error(node->token->pos, Error_Critical, "'%b' does not exist in '%s'. Did you mean '%s'?", + node->token->text, node->token->length, + type_get_name(type), + closest); + return TYPE_MATCH_FAILED; + } + } + + return TYPE_MATCH_YIELD; + } + + if (permanent) *pnode = (AstTyped *) resolved; + return TYPE_MATCH_SUCCESS; + } + + if (node->kind == Ast_Kind_Overloaded_Function) { + AstTyped* func = find_matching_overload_by_type(((AstOverloadedFunction *) node)->overloads, type); + if (func == NULL) return TYPE_MATCH_FAILED; + if (func == (AstTyped *) &node_that_signals_a_yield) return TYPE_MATCH_YIELD; + + // HACK: It feels like there should be a better place to flag that a procedure was definitely used. + if (func->kind == Ast_Kind_Function) + func->flags |= Ast_Flag_Function_Used; + + *pnode = func; + node = *pnode; + } + + if (node->kind == Ast_Kind_Polymorphic_Proc) { + AstFunction* func = polymorphic_proc_lookup((AstFunction *) node, PPLM_By_Function_Type, type, node->token); + if (func == NULL) return TYPE_MATCH_FAILED; + if (func == (AstFunction *) &node_that_signals_a_yield) return TYPE_MATCH_YIELD; + + *pnode = (AstTyped *) func; + node = *pnode; + } + + // HACK: NullProcHack + // The null_proc matches any procedure, and because of that, will cause a runtime error if you + // try to call it. + if (type->kind == Type_Kind_Function && (node->flags & Ast_Flag_Proc_Is_Null) != 0) return TYPE_MATCH_SUCCESS; + + // The normal case where everything works perfectly. + Type* node_type = get_expression_type(node); + if (types_are_compatible(node_type, type)) return TYPE_MATCH_SUCCESS; + + Type* any_type = type_build_from_ast(context.ast_alloc, builtin_any_type); + if (any_type == NULL) return TYPE_MATCH_YIELD; + i64 any_id = any_type->id; + + if (node_type && node_type->id != any_id && type->id == any_id) return TYPE_MATCH_SUCCESS; + + // Here are some of the ways you can unify a node with a type if the type of the + // node does not match the given type: + // + // If the nodes type is a function type and that function has an automatic return + // value placeholder, fill in that placeholder with the actual type. + // :AutoReturnType + if (node_type && node_type->kind == Type_Kind_Function + && node_type->Function.return_type == &type_auto_return + && type->kind == Type_Kind_Function) { + + node_type->Function.return_type = type->Function.return_type; + return TYPE_MATCH_SUCCESS; + } + + // If the node is an auto cast (~~) node, then check to see if the cast is legal + // to the destination type, and if it is change the type to cast to. + if (node_is_auto_cast((AstNode *) node)) { + char* dummy; + Type* from_type = get_expression_type(((AstUnaryOp *) node)->expr); + if (!from_type || !cast_is_legal(from_type, type, &dummy)) { + return TYPE_MATCH_FAILED; + + } else { + if (permanent) ((AstUnaryOp *) node)->type = type; + return TYPE_MATCH_SUCCESS; + } + } + + // If the destination type is a slice, then automatically convert arrays, dynamic + // arrays, and var args, if they are the same type. This is big convenience feature + // that makes working with arrays much easier. + // [N] T -> [] T + // [..] T -> [] T + // ..T -> [] T + else if (node_type && type->kind == Type_Kind_Slice) { + if (node_type->kind == Type_Kind_Array || node_type->kind == Type_Kind_DynArray || node_type->kind == Type_Kind_VarArgs) { + char* dummy; + b32 legal = cast_is_legal(node_type, type, &dummy); + if (permanent && legal) { + *pnode = (AstTyped *) make_cast(context.ast_alloc, node, type); + } + + return legal ? TYPE_MATCH_SUCCESS : TYPE_MATCH_FAILED; + } + } + + // If the node is a numeric literal, try to convert it to the destination type. + else if (node->kind == Ast_Kind_NumLit) { + if (convert_numlit_to_type((AstNumLit *) node, type)) return TYPE_MATCH_SUCCESS; + } + + // If the node is a compound expression, and it doesn't have a type created, + // recursive call this function with the individual components of the compound + // expression. + else if (node->kind == Ast_Kind_Compound) { + if (type->kind != Type_Kind_Compound) return TYPE_MATCH_FAILED; + + AstCompound* compound = (AstCompound *) node; + + u32 expr_count = bh_arr_length(compound->exprs); + if (expr_count != type->Compound.count) return TYPE_MATCH_FAILED; + + fori (i, 0, (i64) expr_count) { + TypeMatch tm = unify_node_and_type_(&compound->exprs[i], type->Compound.types[i], permanent); + if (tm != TYPE_MATCH_SUCCESS) { + return tm; + } + } + + compound->type = type_build_compound_type(context.ast_alloc, compound); + + return TYPE_MATCH_SUCCESS; + } + + else if (node->kind == Ast_Kind_If_Expression) { + AstIfExpression* if_expr = (AstIfExpression *) node; + + TypeMatch true_success = unify_node_and_type_(&if_expr->true_expr, type, permanent); + TypeMatch false_success = unify_node_and_type_(&if_expr->false_expr, type, permanent); + + if (true_success == TYPE_MATCH_SUCCESS && false_success == TYPE_MATCH_SUCCESS) { + if (permanent) if_expr->type = type; + return TYPE_MATCH_SUCCESS; + + } else if (true_success == TYPE_MATCH_FAILED || false_success == TYPE_MATCH_FAILED) { + return TYPE_MATCH_FAILED; + + } else { + return TYPE_MATCH_YIELD; + } + } + + else if (node->kind == Ast_Kind_Alias) { + AstAlias* alias = (AstAlias *) node; + return unify_node_and_type_(&alias->alias, type, permanent); + } + + else if (node->kind == Ast_Kind_Address_Of) { + AstAddressOf *address_of = (AstAddressOf *) node; + if (address_of->can_be_removed) { + if (!permanent) { + return unify_node_and_type_(&address_of->expr, type, permanent); + + } else { + *pnode = (AstTyped *) address_of->expr; + return unify_node_and_type_(pnode, type, permanent); + } + } + } + + else if (node->kind == Ast_Kind_Zero_Value) { + if (node_type == NULL) { + node->type = type; + return TYPE_MATCH_SUCCESS; // Shouldn't this be on the next line? And have node_type == node->type checked? + } + } + + return TYPE_MATCH_FAILED; +} + +Type* resolve_expression_type(AstTyped* node) { + if (node == NULL) return NULL; + + if (node->kind == Ast_Kind_Compound) { + bh_arr_each(AstTyped *, expr, ((AstCompound *) node)->exprs) { + resolve_expression_type(*expr); + } + + node->type = type_build_compound_type(context.ast_alloc, (AstCompound *) node); + return node->type; + } + + if (node->kind == Ast_Kind_Argument) { + node->type = resolve_expression_type(((AstArgument *) node)->value); + } + + if (node->kind == Ast_Kind_If_Expression) { + AstIfExpression* if_expr = (AstIfExpression *) node; + + Type* ltype = resolve_expression_type(if_expr->true_expr); + unify_node_and_type(&if_expr->false_expr, ltype); + + if_expr->type = ltype; + } + + if (node->kind == Ast_Kind_Alias) { + AstAlias* alias = (AstAlias *) node; + alias->type = resolve_expression_type(alias->alias); + } + + if (node_is_type((AstNode *) node)) { + return &basic_types[Basic_Kind_Type_Index]; + } + + if (node->kind == Ast_Kind_Array_Literal && node->type == NULL) { + AstArrayLiteral* al = (AstArrayLiteral *) node; + Type* elem_type = &basic_types[Basic_Kind_Void]; + if (bh_arr_length(al->values) > 0) { + elem_type = resolve_expression_type(al->values[0]); + } + + if (elem_type) { + node->type = type_make_array(context.ast_alloc, elem_type, bh_arr_length(al->values)); + node->flags |= Ast_Flag_Array_Literal_Typed; + + if (node->entity == NULL) { + add_entities_for_node(NULL, (AstNode *) node, NULL, NULL); + } + } + } + + if (node->kind == Ast_Kind_Struct_Literal && node->type == NULL) { + AstStructLiteral* sl = (AstStructLiteral *) node; + if (sl->stnode || sl->type_node) return NULL; + + // If values without names are given to a struct literal without + // a type, then we cannot implicitly build the type of the struct + // literal, as the name of every member cannot be known. Maybe we + // could implicitly do something like _1, _2, ... for the members + // that we not given names? + if (bh_arr_length(sl->args.values) > 0) { + return NULL; + } + + sl->type = type_build_implicit_type_of_struct_literal(context.ast_alloc, sl); + if (sl->type) { + add_entities_for_node(NULL, (AstNode *) sl, NULL, NULL); + } + } + + // If polymorphic procedures HAVE to have a type, most likely + // because they are part of a `typeof` expression, they are + // assigned a void type. This is cleared before the procedure + // is solidified. + if (node->kind == Ast_Kind_Polymorphic_Proc) { + node->type = &basic_types[Basic_Kind_Void]; + } + + if (node->kind == Ast_Kind_Macro) { + return resolve_expression_type((AstTyped *) ((AstMacro *) node)->body); + } + + if (node->type == NULL) + node->type = type_build_from_ast(context.ast_alloc, node->type_node); + + if (node->kind == Ast_Kind_NumLit && node->type->kind == Type_Kind_Basic) { + if (node->type->Basic.kind == Basic_Kind_Int_Unsized) { + b32 big = bh_abs(((AstNumLit *) node)->value.l) >= (1ull << 32); + b32 unsign = ((AstNumLit *) node)->was_hex_literal; + + if ( big && !unsign) convert_numlit_to_type((AstNumLit *) node, &basic_types[Basic_Kind_I64]); + else if ( big && unsign) convert_numlit_to_type((AstNumLit *) node, &basic_types[Basic_Kind_U64]); + else if (!big && !unsign) convert_numlit_to_type((AstNumLit *) node, &basic_types[Basic_Kind_I32]); + else if (!big && unsign) convert_numlit_to_type((AstNumLit *) node, &basic_types[Basic_Kind_U32]); + } + else if (node->type->Basic.kind == Basic_Kind_Float_Unsized) { + convert_numlit_to_type((AstNumLit *) node, &basic_types[Basic_Kind_F64]); + } + } + + return node->type; +} + +i64 get_expression_integer_value(AstTyped* node, b32 *is_valid) { + if (!node) return 0; + + resolve_expression_type(node); + + if (is_valid) *is_valid = 1; + + if (node->kind == Ast_Kind_NumLit && type_is_integer(node->type)) { + return ((AstNumLit *) node)->value.l; + } + + if (node->kind == Ast_Kind_NumLit && type_is_bool(node->type)) { + return ((AstNumLit *) node)->value.i; + } + + if (node->kind == Ast_Kind_Argument) { + return get_expression_integer_value(((AstArgument *) node)->value, is_valid); + } + + if (node->kind == Ast_Kind_Size_Of) { + return ((AstSizeOf *) node)->size; + } + + if (node->kind == Ast_Kind_Align_Of) { + return ((AstAlignOf *) node)->alignment; + } + + if (node->kind == Ast_Kind_Alias) { + return get_expression_integer_value(((AstAlias *) node)->alias, is_valid); + } + + if (node->kind == Ast_Kind_Enum_Value) { + return get_expression_integer_value(((AstEnumValue *) node)->value, is_valid); + } + + if (node_is_type((AstNode*) node)) { + Type* type = type_build_from_ast(context.ast_alloc, (AstType *) node); + if (type) return type->id; + } + + if (is_valid) *is_valid = 0; + return 0; +} + +char *get_expression_string_value(AstTyped* node, b32 *out_is_valid) { + resolve_expression_type(node); + + if (out_is_valid) *out_is_valid = 1; + + if (node->kind == Ast_Kind_StrLit) { + AstStrLit *str = (AstStrLit *) node; + + // CLEANUP: Maybe this should allocate on the heap? + // I guess if in all cases the memory is allocated on the heap, + // then the caller can free the memory. + i8* strdata = bh_alloc_array(global_heap_allocator, i8, str->token->length + 1); + i32 length = string_process_escape_seqs(strdata, str->token->text, str->token->length); + strdata[length] = '\0'; + + return strdata; + } + + if (node->kind == Ast_Kind_Alias) { + return get_expression_string_value(((AstAlias *) node)->alias, out_is_valid); + } + + if (out_is_valid) *out_is_valid = 0; + return NULL; +} + +static const b32 cast_legality[][12] = { + /* I8 */ { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + /* U8 */ { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + /* I16 */ { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + /* U16 */ { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, + /* I32 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + /* U32 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + /* I64 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + /* U64 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, + /* F32 */ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0 }, + /* F64 */ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0 }, + /* PTR */ { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0 }, + /* TYP */ { 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1,} +}; + +b32 cast_is_legal(Type* from_, Type* to_, char** err_msg) { + Type* from = from_; + Type* to = to_; + + if (from == NULL) { + if (err_msg) *err_msg = "'from' is null. (Compiler Error)"; + return 0; + } + if (to == NULL) { + if (err_msg) *err_msg = "'to' is null. (Compiler Error)"; + return 0; + } + + if (from_->id == to_->id) return 1; + + if (from->kind == Type_Kind_Enum) from = from->Enum.backing; + if (to->kind == Type_Kind_Enum) to = to->Enum.backing; + + if (from->kind == Type_Kind_Struct || to->kind == Type_Kind_Struct) { + *err_msg = "Cannot cast to or from a struct."; + return 0; + } + + // CLEANUP: These error messages should be a lot better and actually + // provide the types of the things in question. + if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_Array) { + if (!types_are_compatible(to->Slice.elem, from->Array.elem)) { + *err_msg = "Array to slice cast is not valid here because the types are different."; + return 0; + } else { + return 1; + } + } + + if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_DynArray) { + //if (!types_are_compatible(to->Slice.elem, from->DynArray.elem)) { + if (type_size_of(to->Slice.elem) != type_size_of(from->DynArray.elem)) { + *err_msg = "Dynmaic array to slice cast is not valid here because the types are different sizes."; + return 0; + } else { + return 1; + } + } + + if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_VarArgs) { + if (!types_are_compatible(to->Slice.elem, from->VarArgs.elem)) { + *err_msg = "Variadic argument to slice cast is not valid here because the types are different."; + return 0; + } else { + return 1; + } + } + + if (to->kind == Type_Kind_Distinct) { + if (!types_are_compatible(to->Distinct.base_type, from)) { + // :BadErrorMessage + *err_msg = "Cannot convert to a distinct type using the wrong base type."; + return 0; + } else { + return 1; + } + } + + if (from->kind == Type_Kind_Distinct) { + if (!types_are_compatible(from->Distinct.base_type, to)) { + // :BadErrorMessage + *err_msg = "Cannot convert from a distinct type to the wrong destination type."; + return 0; + } else { + return 1; + } + } + + if (from->kind == Type_Kind_Slice || to->kind == Type_Kind_Slice) { + if ((from->kind != Type_Kind_Slice || to->kind != Type_Kind_Slice) + || to->Slice.elem->kind != Type_Kind_Pointer || from->Slice.elem->kind != Type_Kind_Pointer + || !types_are_compatible(from->Slice.elem, to->Slice.elem)) { + *err_msg = "Cannot only cast between slice types when both are a slice of compatible pointers."; + return 0; + } else { + return 1; + } + } + + if (from->kind == Type_Kind_DynArray || to->kind == Type_Kind_DynArray) { + *err_msg = "Cannot cast to or from a dynamic array."; + return 0; + } + + if (to->kind == Type_Kind_Function) { + *err_msg = "Cannot cast to a function."; + return 0; + } + + if ( (type_is_simd(to) && !type_is_simd(from)) + || (!type_is_simd(to) && type_is_simd(from))) { + *err_msg = "Can only perform a SIMD cast between SIMD types."; + return 0; + } + + if (from->kind == Type_Kind_Basic && from->Basic.kind == Basic_Kind_Void) { + *err_msg = "Cannot cast from void."; + return 0; + } + i32 fromidx = -1, toidx = -1; + if (from->Basic.flags & Basic_Flag_Pointer || from->kind == Type_Kind_Array) { + fromidx = 10; + } + else if (from->Basic.flags & Basic_Flag_Integer) { + b32 unsign = (from->Basic.flags & Basic_Flag_Unsigned) != 0; + + fromidx = log2_dumb(from->Basic.size) * 2 + unsign; + } + else if (from->Basic.flags & Basic_Flag_Float) { + if (from->Basic.size == 4) fromidx = 8; + else if (from->Basic.size == 8) fromidx = 9; + } + else if (from->Basic.flags & Basic_Flag_Boolean) { + fromidx = 0; + } + else if (from->Basic.flags & Basic_Flag_Type_Index) { + fromidx = 11; + } + + if (to->Basic.flags & Basic_Flag_Pointer || to->kind == Type_Kind_Array) { + toidx = 10; + } + else if (to->Basic.flags & Basic_Flag_Integer) { + b32 unsign = (to->Basic.flags & Basic_Flag_Unsigned) != 0; + + toidx = log2_dumb(to->Basic.size) * 2 + unsign; + } + else if (to->Basic.flags & Basic_Flag_Float) { + if (to->Basic.size == 4) toidx = 8; + else if (to->Basic.size == 8) toidx = 9; + } + else if (to->Basic.flags & Basic_Flag_Boolean) { + toidx = 0; + } + else if (to->Basic.flags & Basic_Flag_Type_Index) { + toidx = 11; + } + + if (fromidx != -1 && toidx != -1) { + if (!cast_legality[fromidx][toidx]) { + *err_msg = bh_aprintf(global_heap_allocator, "Cast from '%s' to '%s' is not allowed.", type_get_name(from_), type_get_name(to_)); + return 0; + } + } else { + *err_msg = bh_aprintf(global_heap_allocator, "Cast from '%s' to '%s' is not allowed.", type_get_name(from_), type_get_name(to_)); + return 0; + } + + *err_msg = NULL; + return 1; +} + +char* get_function_name(AstFunction* func) { + if (func->kind != Ast_Kind_Function) return ""; + + if (func->name != NULL) return func->name; + + if (func->exported_name != NULL) { + return bh_aprintf(global_scratch_allocator, + "EXPORTED:%b", + func->exported_name->text, + func->exported_name->length); + } + + return ""; +} + +AstNode* strip_aliases(AstNode* n) { + if (n == NULL) return n; + + while (n->kind == Ast_Kind_Alias) n = (AstNode *) ((AstAlias *) n)->alias; + + return n; +} + +AstNumLit* make_bool_literal(bh_allocator a, b32 b) { + AstNumLit* bl = onyx_ast_node_new(a, sizeof(AstNumLit), Ast_Kind_NumLit); + bl->flags |= Ast_Flag_Comptime; + bl->type_node = (AstType *) &basic_type_bool; + + bl->value.i = b ? 1 : 0; + return bl; +} + +AstNumLit* make_int_literal(bh_allocator a, i64 i) { + AstNumLit* num = onyx_ast_node_new(a, sizeof(AstNumLit), Ast_Kind_NumLit); + num->flags |= Ast_Flag_Comptime; + + if (bh_abs(i) >= ((u64) 1 << 32)) + num->type_node = (AstType *) &basic_type_i64; + else + num->type_node = (AstType *) &basic_type_i32; + + num->value.l = i; + return num; +} + +AstNumLit* make_float_literal(bh_allocator a, f64 d) { + // NOTE: Use convert_numlit_to_type to make this a concrete float + AstNumLit* num = onyx_ast_node_new(a, sizeof(AstNumLit), Ast_Kind_NumLit); + num->flags |= Ast_Flag_Comptime; + num->type_node = (AstType *) &basic_type_float_unsized; + num->value.d = d; + return num; +} + +AstRangeLiteral* make_range_literal(bh_allocator a, AstTyped* low, AstTyped* high) { + AstRangeLiteral* rl = onyx_ast_node_new(a, sizeof(AstRangeLiteral), Ast_Kind_Range_Literal); + rl->type = builtin_range_type_type; + rl->low = low; + rl->high = high; + return rl; +} + +AstBinaryOp* make_binary_op(bh_allocator a, BinaryOp operation, AstTyped* left, AstTyped* right) { + AstBinaryOp* binop_node = onyx_ast_node_new(a, sizeof(AstBinaryOp), Ast_Kind_Binary_Op); + binop_node->left = left; + binop_node->right = right; + binop_node->operation = operation; + return binop_node; +} + +AstArgument* make_argument(bh_allocator a, AstTyped* value) { + AstArgument* arg = onyx_ast_node_new(a, sizeof(AstArgument), Ast_Kind_Argument); + if (value->token) arg->token = value->token; + arg->value = value; + arg->type = value->type; + arg->next = NULL; + arg->va_kind = VA_Kind_Not_VA; + return arg; +} + +AstFieldAccess* make_field_access(bh_allocator a, AstTyped* node, char* field) { + AstFieldAccess* fa = onyx_ast_node_new(a, sizeof(AstFieldAccess), Ast_Kind_Field_Access); + if (node->token) fa->token = node->token; + fa->field = field; + fa->expr = node; + + return fa; +} + +AstAddressOf* make_address_of(bh_allocator a, AstTyped* node) { + AstAddressOf* ao = onyx_ast_node_new(a, sizeof(AstAddressOf), Ast_Kind_Address_Of); + if (node->token) ao->token = node->token; + ao->expr = node; + + return ao; +} + +AstLocal* make_local(bh_allocator a, OnyxToken* token, AstType* type_node) { + AstLocal* local = onyx_ast_node_new(a, sizeof(AstLocal), Ast_Kind_Local); + local->token = token; + local->type_node = type_node; + + return local; +} + +AstNode* make_symbol(bh_allocator a, OnyxToken* sym) { + AstNode* symbol = onyx_ast_node_new(a, sizeof(AstNode), Ast_Kind_Symbol); + symbol->token = sym; + return symbol; +} + +AstUnaryOp* make_cast(bh_allocator a, AstTyped* expr, Type* to) { + AstUnaryOp* cast = onyx_ast_node_new(a, sizeof(AstUnaryOp), Ast_Kind_Unary_Op); + cast->token = expr->token; + cast->operation = Unary_Op_Cast; + cast->expr = expr; + cast->type = to; + return cast; +} + +AstZeroValue* make_zero_value(bh_allocator a, OnyxToken* token, Type* type) { + AstZeroValue* zero_value = onyx_ast_node_new(a, sizeof(AstZeroValue), Ast_Kind_Zero_Value); + zero_value->token = token; + zero_value->flags |= Ast_Flag_Comptime; + zero_value->type = type; + return zero_value; +} + +void arguments_initialize(Arguments* args) { + if (args->values == NULL) bh_arr_new(global_heap_allocator, args->values, 2); + if (args->named_values == NULL) bh_arr_new(global_heap_allocator, args->named_values, 2); + + // CLEANUP: I'm not sure if I need to initialize these to NULL values, but it doesn't hurt. + fori (i, 0, 2) { + args->values[i] = NULL; + args->named_values[i] = NULL; + } + + args->used_argument_count = -1; +} + +void arguments_ensure_length(Arguments* args, u32 count) { + // Make the array big enough + bh_arr_grow(args->values, count); + + // NULL initialize the new elements + fori (i, bh_arr_length(args->values), count) args->values[i] = NULL; + + // Set the actual length to the count, but never let it decrease in size + bh_arr_set_length(args->values, bh_max(count, (u32) bh_arr_length(args->values))); +} + +void arguments_copy(Arguments* dest, Arguments* src) { + dest->used_argument_count = -1; + dest->named_values = src->named_values; + + bh_arr_grow(dest->values, (u32) bh_arr_length(src->values)); + bh_arr_set_length(dest->values, (u32) bh_arr_length(src->values)); + bh_arr_each(AstTyped*, arg, dest->values) *arg = NULL; + fori (i, 0, bh_arr_length(src->values)) dest->values[i] = src->values[i]; +} + +// In clone, the named_values are not copied. This is used in find_matching_overload_by_arguments since it doesn't need them to be copied. +void arguments_clone(Arguments* dest, Arguments* src) { + dest->used_argument_count = -1; + dest->named_values = src->named_values; + dest->values = bh_arr_copy(global_heap_allocator, src->values); +} + +void arguments_deep_clone(bh_allocator a, Arguments* dest, Arguments* src) { + dest->used_argument_count = -1; + dest->values = NULL; + dest->named_values = NULL; + + bh_arr_new(global_heap_allocator, dest->named_values, bh_arr_length(src->named_values)); + bh_arr_new(global_heap_allocator, dest->values, bh_arr_length(src->values)); + + bh_arr_each(AstNamedValue *, nv, src->named_values) + bh_arr_push(dest->named_values, (AstNamedValue *) ast_clone(a, *nv)); + + bh_arr_each(AstTyped *, val, src->values) + bh_arr_push(dest->values, (AstTyped *) ast_clone(a, (AstNode *) *val)); +} + +void arguments_remove_baked(Arguments* args) { + fori (i, 0, bh_arr_length(args->values)) { + if (args->values[i]->kind != Ast_Kind_Argument) continue; + if (!((AstArgument *) args->values[i])->is_baked) continue; + + bh_arr_deleten(args->values, i, 1); + i--; + } + + fori (i, 0, bh_arr_length(args->named_values)) { + if (args->named_values[i]->value->kind != Ast_Kind_Argument) continue; + if (!((AstArgument *) args->named_values[i]->value)->is_baked) continue; + + bh_arr_deleten(args->named_values, i, 1); + i--; + } +} + +void arguments_clear_baked_flags(Arguments* args) { + bh_arr_each(AstTyped *, arg, args->values) { + if ((*arg)->kind == Ast_Kind_Argument) { + ((AstArgument *) *arg)->is_baked = 0; + } + } + + bh_arr_each(AstNamedValue *, arg, args->named_values) { + if ((*arg)->value->kind == Ast_Kind_Argument) { + ((AstArgument *) (*arg)->value)->is_baked = 0; + } + } +} + +// GROSS: Using void* to avoid having to cast everything. +const char* node_get_type_name(void* node) { + if (node_is_type((AstNode *) node)) return "type_expr"; + + if (((AstNode *) node)->kind == Ast_Kind_Argument) { + return node_get_type_name(((AstArgument *) node)->value); + } + + if (((AstNode *) node)->kind == Ast_Kind_Polymorphic_Proc) { + return "polymorphic procedure"; + } + + return type_get_name(((AstTyped *) node)->type); +} + +b32 static_if_resolution(AstIf* static_if) { + if (static_if->kind != Ast_Kind_Static_If) return 0; + + // assert(condition_value->kind == Ast_Kind_NumLit); // This should be right, right? + i64 value = get_expression_integer_value(static_if->cond, NULL); + + return value != 0; +} + +AstPolyCallType* convert_call_to_polycall(AstCall* call) { + // HACK HACK HACK + AstPolyCallType *pct = onyx_ast_node_new(context.ast_alloc, sizeof(AstPolyCallType), Ast_Kind_Poly_Call_Type); + pct->token = call->token; + pct->__unused = call->next; + pct->callee = (AstType *) call->callee; + pct->params = (AstNode **) call->args.values; + bh_arr_each(AstNode *, pp, pct->params) { + if ((*pp)->kind == Ast_Kind_Argument) { + *pp = (AstNode *) (*(AstArgument **) pp)->value; + } + } + + return pct; +} diff --git a/compiler/src/builtins.c b/compiler/src/builtins.c new file mode 100644 index 00000000..b6eacef0 --- /dev/null +++ b/compiler/src/builtins.c @@ -0,0 +1,555 @@ +#include "astnodes.h" +#include "types.h" +#include "errors.h" +#include "utils.h" + +AstBasicType basic_type_void = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_Void] }; +AstBasicType basic_type_bool = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_Bool] }; +AstBasicType basic_type_i8 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I8] }; +AstBasicType basic_type_u8 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_U8] }; +AstBasicType basic_type_i16 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I16] }; +AstBasicType basic_type_u16 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_U16] }; +AstBasicType basic_type_i32 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I32] }; +AstBasicType basic_type_u32 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_U32] }; +AstBasicType basic_type_i64 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I64] }; +AstBasicType basic_type_u64 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_U64] }; +AstBasicType basic_type_f32 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_F32] }; +AstBasicType basic_type_f64 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_F64] }; +AstBasicType basic_type_rawptr = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_Rawptr] }; +AstBasicType basic_type_type_expr = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_Type_Index] }; + +// NOTE: Types used for numeric literals +AstBasicType basic_type_int_unsized = { Ast_Kind_Basic_Type, 0, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_Int_Unsized] }; +AstBasicType basic_type_float_unsized = { Ast_Kind_Basic_Type, 0, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_Float_Unsized] }; + +static OnyxToken simd_token = { Token_Type_Symbol, 0, "", { 0 } }; +AstBasicType basic_type_i8x16 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I8X16] }; +AstBasicType basic_type_i16x8 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I16X8] }; +AstBasicType basic_type_i32x4 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I32X4] }; +AstBasicType basic_type_i64x2 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I64X2] }; +AstBasicType basic_type_f32x4 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_F32X4] }; +AstBasicType basic_type_f64x2 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_F64X2] }; +AstBasicType basic_type_v128 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_V128] }; + +// HACK +// :AutoReturnType +Type type_auto_return = { 0 }; +AstBasicType basic_type_auto_return = { Ast_Kind_Basic_Type, 0, &simd_token, NULL, NULL, 0, NULL, &type_auto_return }; + +OnyxToken builtin_package_token = { Token_Type_Symbol, 7, "builtin ", { 0 } }; + +static OnyxToken builtin_heap_start_token = { Token_Type_Symbol, 12, "__heap_start ", { 0 } }; +static OnyxToken builtin_stack_top_token = { Token_Type_Symbol, 11, "__stack_top ", { 0 } }; +static OnyxToken builtin_tls_base_token = { Token_Type_Symbol, 10, "__tls_base ", { 0 } }; +static OnyxToken builtin_tls_size_token = { Token_Type_Symbol, 10, "__tls_size ", { 0 } }; +AstGlobal builtin_heap_start = { Ast_Kind_Global, Ast_Flag_Const, &builtin_heap_start_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; +AstGlobal builtin_stack_top = { Ast_Kind_Global, 0, &builtin_stack_top_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; +AstGlobal builtin_tls_base = { Ast_Kind_Global, 0, &builtin_tls_base_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; +AstGlobal builtin_tls_size = { Ast_Kind_Global, 0, &builtin_tls_size_token, NULL, NULL, (AstType *) &basic_type_u32, NULL }; + +AstType *builtin_string_type; +AstType *builtin_cstring_type; +AstType *builtin_range_type; +Type *builtin_range_type_type; +AstType *builtin_vararg_type; +Type *builtin_vararg_type_type; +AstTyped *builtin_context_variable; +AstType *builtin_allocator_type; +AstType *builtin_iterator_type; +AstType *builtin_callsite_type; +AstType *builtin_any_type; +AstType *builtin_code_type; +AstType *builtin_link_options_type; + +AstTyped *type_table_node = NULL; +AstTyped *foreign_blocks_node = NULL; +AstType *foreign_block_type = NULL; +AstTyped *tagged_procedures_node = NULL; +AstFunction *builtin_initialize_data_segments = NULL; +AstFunction *builtin_run_init_procedures = NULL; +bh_arr(AstFunction *) init_procedures = NULL; + +const BuiltinSymbol builtin_symbols[] = { + { NULL, "void", (AstNode *) &basic_type_void }, + { NULL, "bool", (AstNode *) &basic_type_bool }, + { NULL, "i8", (AstNode *) &basic_type_i8 }, + { NULL, "u8", (AstNode *) &basic_type_u8 }, + { NULL, "i16", (AstNode *) &basic_type_i16 }, + { NULL, "u16", (AstNode *) &basic_type_u16 }, + { NULL, "i32", (AstNode *) &basic_type_i32 }, + { NULL, "u32", (AstNode *) &basic_type_u32 }, + { NULL, "i64", (AstNode *) &basic_type_i64 }, + { NULL, "u64", (AstNode *) &basic_type_u64 }, + { NULL, "f32", (AstNode *) &basic_type_f32 }, + { NULL, "f64", (AstNode *) &basic_type_f64 }, + { NULL, "rawptr", (AstNode *) &basic_type_rawptr }, + { NULL, "type_expr", (AstNode *) &basic_type_type_expr }, + + { "simd", "i8x16", (AstNode *) &basic_type_i8x16 }, + { "simd", "i16x8", (AstNode *) &basic_type_i16x8 }, + { "simd", "i32x4", (AstNode *) &basic_type_i32x4 }, + { "simd", "i64x2", (AstNode *) &basic_type_i64x2 }, + { "simd", "f32x4", (AstNode *) &basic_type_f32x4 }, + { "simd", "f64x2", (AstNode *) &basic_type_f64x2 }, + { "simd", "v128", (AstNode *) &basic_type_v128 }, + + { "builtin", "__heap_start", (AstNode *) &builtin_heap_start }, + { "builtin", "__stack_top", (AstNode *) &builtin_stack_top }, + { "builtin", "__tls_base", (AstNode *) &builtin_tls_base }, + { "builtin", "__tls_size", (AstNode *) &builtin_tls_size }, + + { NULL, NULL, NULL }, +}; + +IntrinsicTable intrinsic_table; + +static IntrinsicMap builtin_intrinsics[] = { + { "memory_size", ONYX_INTRINSIC_MEMORY_SIZE }, + { "memory_grow", ONYX_INTRINSIC_MEMORY_GROW }, + { "memory_copy", ONYX_INTRINSIC_MEMORY_COPY }, + { "memory_fill", ONYX_INTRINSIC_MEMORY_FILL }, + + { "__initialize", ONYX_INTRINSIC_INITIALIZE }, + + { "clz_i32", ONYX_INTRINSIC_I32_CLZ }, + { "ctz_i32", ONYX_INTRINSIC_I32_CTZ }, + { "popcnt_i32", ONYX_INTRINSIC_I32_POPCNT }, + { "and_i32", ONYX_INTRINSIC_I32_AND }, + { "or_i32", ONYX_INTRINSIC_I32_OR }, + { "xor_i32", ONYX_INTRINSIC_I32_XOR }, + { "shl_i32", ONYX_INTRINSIC_I32_SHL }, + { "slr_i32", ONYX_INTRINSIC_I32_SLR }, + { "sar_i32", ONYX_INTRINSIC_I32_SAR }, + { "rotl_i32", ONYX_INTRINSIC_I32_ROTL }, + { "rotr_i32", ONYX_INTRINSIC_I32_ROTR }, + + { "clz_i64", ONYX_INTRINSIC_I64_CLZ }, + { "ctz_i64", ONYX_INTRINSIC_I64_CTZ }, + { "popcnt_i64", ONYX_INTRINSIC_I64_POPCNT }, + { "and_i64", ONYX_INTRINSIC_I64_AND }, + { "or_i64", ONYX_INTRINSIC_I64_OR }, + { "xor_i64", ONYX_INTRINSIC_I64_XOR }, + { "shl_i64", ONYX_INTRINSIC_I64_SHL }, + { "slr_i64", ONYX_INTRINSIC_I64_SLR }, + { "sar_i64", ONYX_INTRINSIC_I64_SAR }, + { "rotl_i64", ONYX_INTRINSIC_I64_ROTL }, + { "rotr_i64", ONYX_INTRINSIC_I64_ROTR }, + + { "abs_f32", ONYX_INTRINSIC_F32_ABS }, + { "ceil_f32", ONYX_INTRINSIC_F32_CEIL }, + { "floor_f32", ONYX_INTRINSIC_F32_FLOOR }, + { "trunc_f32", ONYX_INTRINSIC_F32_TRUNC }, + { "nearest_f32", ONYX_INTRINSIC_F32_NEAREST }, + { "sqrt_f32", ONYX_INTRINSIC_F32_SQRT }, + { "min_f32", ONYX_INTRINSIC_F32_MIN }, + { "max_f32", ONYX_INTRINSIC_F32_MAX }, + { "copysign_f32", ONYX_INTRINSIC_F32_COPYSIGN }, + + { "abs_f64", ONYX_INTRINSIC_F64_ABS }, + { "ceil_f64", ONYX_INTRINSIC_F64_CEIL }, + { "floor_f64", ONYX_INTRINSIC_F64_FLOOR }, + { "trunc_f64", ONYX_INTRINSIC_F64_TRUNC }, + { "nearest_f64", ONYX_INTRINSIC_F64_NEAREST }, + { "sqrt_f64", ONYX_INTRINSIC_F64_SQRT }, + { "min_f64", ONYX_INTRINSIC_F64_MIN }, + { "max_f64", ONYX_INTRINSIC_F64_MAX }, + { "copysign_f64", ONYX_INTRINSIC_F64_COPYSIGN }, + + + // SIMD Intrinsics + { "v128_const", ONYX_INTRINSIC_V128_CONST }, + { "i8x16_const", ONYX_INTRINSIC_I8X16_CONST }, + { "i16x8_const", ONYX_INTRINSIC_I16X8_CONST }, + { "i32x4_const", ONYX_INTRINSIC_I32X4_CONST }, + { "i64x2_const", ONYX_INTRINSIC_I64X2_CONST }, + { "f32x4_const", ONYX_INTRINSIC_F32X4_CONST }, + { "f64x2_const", ONYX_INTRINSIC_F64X2_CONST }, + { "i8x16_shuffle", ONYX_INTRINSIC_I8X16_SHUFFLE }, + + { "i8x16_extract_lane_s", ONYX_INTRINSIC_I8X16_EXTRACT_LANE_S }, + { "i8x16_extract_lane_u", ONYX_INTRINSIC_I8X16_EXTRACT_LANE_U }, + { "i8x16_replace_lane", ONYX_INTRINSIC_I8X16_REPLACE_LANE }, + { "i16x8_extract_lane_s", ONYX_INTRINSIC_I16X8_EXTRACT_LANE_S }, + { "i16x8_extract_lane_u", ONYX_INTRINSIC_I16X8_EXTRACT_LANE_U }, + { "i16x8_replace_lane", ONYX_INTRINSIC_I16X8_REPLACE_LANE }, + { "i32x4_extract_lane", ONYX_INTRINSIC_I32X4_EXTRACT_LANE }, + { "i32x4_replace_lane", ONYX_INTRINSIC_I32X4_REPLACE_LANE }, + { "i64x2_extract_lane", ONYX_INTRINSIC_I64X2_EXTRACT_LANE }, + { "i64x2_replace_lane", ONYX_INTRINSIC_I64X2_REPLACE_LANE }, + { "f32x4_extract_lane", ONYX_INTRINSIC_F32X4_EXTRACT_LANE }, + { "f32x4_replace_lane", ONYX_INTRINSIC_F32X4_REPLACE_LANE }, + { "f64x2_extract_lane", ONYX_INTRINSIC_F64X2_EXTRACT_LANE }, + { "f64x2_replace_lane", ONYX_INTRINSIC_F64X2_REPLACE_LANE }, + + { "i8x16_swizzle", ONYX_INTRINSIC_I8X16_SWIZZLE }, + { "i8x16_splat", ONYX_INTRINSIC_I8X16_SPLAT }, + { "i16x8_splat", ONYX_INTRINSIC_I16X8_SPLAT }, + { "i32x4_splat", ONYX_INTRINSIC_I32X4_SPLAT }, + { "i64x2_splat", ONYX_INTRINSIC_I64X2_SPLAT }, + { "f32x4_splat", ONYX_INTRINSIC_F32X4_SPLAT }, + { "f64x2_splat", ONYX_INTRINSIC_F64X2_SPLAT }, + + { "i8x16_eq", ONYX_INTRINSIC_I8X16_EQ }, + { "i8x16_neq", ONYX_INTRINSIC_I8X16_NEQ }, + { "i8x16_lt_s", ONYX_INTRINSIC_I8X16_LT_S }, + { "i8x16_lt_u", ONYX_INTRINSIC_I8X16_LT_U }, + { "i8x16_gt_s", ONYX_INTRINSIC_I8X16_GT_S }, + { "i8x16_gt_u", ONYX_INTRINSIC_I8X16_GT_U }, + { "i8x16_le_s", ONYX_INTRINSIC_I8X16_LE_S }, + { "i8x16_le_u", ONYX_INTRINSIC_I8X16_LE_U }, + { "i8x16_ge_s", ONYX_INTRINSIC_I8X16_GE_S }, + { "i8x16_ge_u", ONYX_INTRINSIC_I8X16_GE_U }, + + { "i16x8_eq", ONYX_INTRINSIC_I16X8_EQ }, + { "i16x8_neq", ONYX_INTRINSIC_I16X8_NEQ }, + { "i16x8_lt_s", ONYX_INTRINSIC_I16X8_LT_S }, + { "i16x8_lt_u", ONYX_INTRINSIC_I16X8_LT_U }, + { "i16x8_gt_s", ONYX_INTRINSIC_I16X8_GT_S }, + { "i16x8_gt_u", ONYX_INTRINSIC_I16X8_GT_U }, + { "i16x8_le_s", ONYX_INTRINSIC_I16X8_LE_S }, + { "i16x8_le_u", ONYX_INTRINSIC_I16X8_LE_U }, + { "i16x8_ge_s", ONYX_INTRINSIC_I16X8_GE_S }, + { "i16x8_ge_u", ONYX_INTRINSIC_I16X8_GE_U }, + + { "i32x4_eq", ONYX_INTRINSIC_I32X4_EQ }, + { "i32x4_neq", ONYX_INTRINSIC_I32X4_NEQ }, + { "i32x4_lt_s", ONYX_INTRINSIC_I32X4_LT_S }, + { "i32x4_lt_u", ONYX_INTRINSIC_I32X4_LT_U }, + { "i32x4_gt_s", ONYX_INTRINSIC_I32X4_GT_S }, + { "i32x4_gt_u", ONYX_INTRINSIC_I32X4_GT_U }, + { "i32x4_le_s", ONYX_INTRINSIC_I32X4_LE_S }, + { "i32x4_le_u", ONYX_INTRINSIC_I32X4_LE_U }, + { "i32x4_ge_s", ONYX_INTRINSIC_I32X4_GE_S }, + { "i32x4_ge_u", ONYX_INTRINSIC_I32X4_GE_U }, + + { "f32x4_eq", ONYX_INTRINSIC_F32X4_EQ }, + { "f32x4_neq", ONYX_INTRINSIC_F32X4_NEQ }, + { "f32x4_lt", ONYX_INTRINSIC_F32X4_LT }, + { "f32x4_gt", ONYX_INTRINSIC_F32X4_GT }, + { "f32x4_le", ONYX_INTRINSIC_F32X4_LE }, + { "f32x4_ge", ONYX_INTRINSIC_F32X4_GE }, + + { "f64x2_eq", ONYX_INTRINSIC_F64X2_EQ }, + { "f64x2_neq", ONYX_INTRINSIC_F64X2_NEQ }, + { "f64x2_lt", ONYX_INTRINSIC_F64X2_LT }, + { "f64x2_gt", ONYX_INTRINSIC_F64X2_GT }, + { "f64x2_le", ONYX_INTRINSIC_F64X2_LE }, + { "f64x2_ge", ONYX_INTRINSIC_F64X2_GE }, + + { "v128_not", ONYX_INTRINSIC_V128_NOT }, + { "v128_and", ONYX_INTRINSIC_V128_AND }, + { "v128_andnot", ONYX_INTRINSIC_V128_ANDNOT }, + { "v128_or", ONYX_INTRINSIC_V128_OR }, + { "v128_xor", ONYX_INTRINSIC_V128_XOR }, + { "v128_bitselect", ONYX_INTRINSIC_V128_BITSELECT }, + + { "i8x16_abs", ONYX_INTRINSIC_I8X16_ABS }, + { "i8x16_neg", ONYX_INTRINSIC_I8X16_NEG }, + { "i8x16_any_true", ONYX_INTRINSIC_I8X16_ANY_TRUE }, + { "i8x16_all_true", ONYX_INTRINSIC_I8X16_ALL_TRUE }, + { "i8x16_bitmask", ONYX_INTRINSIC_I8X16_BITMASK }, + { "i8x16_narrow_i16x8_s", ONYX_INTRINSIC_I8X16_NARROW_I16X8_S }, + { "i8x16_narrow_i16x8_u", ONYX_INTRINSIC_I8X16_NARROW_I16X8_U }, + { "i8x16_shl", ONYX_INTRINSIC_I8X16_SHL }, + { "i8x16_shr_s", ONYX_INTRINSIC_I8X16_SHR_S }, + { "i8x16_shr_u", ONYX_INTRINSIC_I8X16_SHR_U }, + { "i8x16_add", ONYX_INTRINSIC_I8X16_ADD }, + { "i8x16_add_sat_s", ONYX_INTRINSIC_I8X16_ADD_SAT_S }, + { "i8x16_add_sat_u", ONYX_INTRINSIC_I8X16_ADD_SAT_U }, + { "i8x16_sub", ONYX_INTRINSIC_I8X16_SUB }, + { "i8x16_sub_sat_s", ONYX_INTRINSIC_I8X16_SUB_SAT_S }, + { "i8x16_sub_sat_u", ONYX_INTRINSIC_I8X16_SUB_SAT_U }, + { "i8x16_min_s", ONYX_INTRINSIC_I8X16_MIN_S }, + { "i8x16_min_u", ONYX_INTRINSIC_I8X16_MIN_U }, + { "i8x16_max_s", ONYX_INTRINSIC_I8X16_MAX_S }, + { "i8x16_max_u", ONYX_INTRINSIC_I8X16_MAX_U }, + { "i8x16_avgr_u", ONYX_INTRINSIC_I8X16_AVGR_U }, + + { "i16x8_abs", ONYX_INTRINSIC_I16X8_ABS }, + { "i16x8_neg", ONYX_INTRINSIC_I16X8_NEG }, + { "i16x8_any_true", ONYX_INTRINSIC_I16X8_ANY_TRUE }, + { "i16x8_all_true", ONYX_INTRINSIC_I16X8_ALL_TRUE }, + { "i16x8_bitmask", ONYX_INTRINSIC_I16X8_BITMASK }, + { "i16x8_narrow_i32x4_s", ONYX_INTRINSIC_I16X8_NARROW_I32X4_S }, + { "i16x8_narrow_i32x4_u", ONYX_INTRINSIC_I16X8_NARROW_I32X4_U }, + { "i16x8_widen_low_i8x16_s", ONYX_INTRINSIC_I16X8_WIDEN_LOW_I8X16_S }, + { "i16x8_widen_high_i8x16_s", ONYX_INTRINSIC_I16X8_WIDEN_HIGH_I8X16_S }, + { "i16x8_widen_low_i8x16_u", ONYX_INTRINSIC_I16X8_WIDEN_LOW_I8X16_U }, + { "i16x8_widen_high_i8x16_u", ONYX_INTRINSIC_I16X8_WIDEN_HIGH_I8X16_U }, + { "i16x8_shl", ONYX_INTRINSIC_I16X8_SHL }, + { "i16x8_shr_s", ONYX_INTRINSIC_I16X8_SHR_S }, + { "i16x8_shr_u", ONYX_INTRINSIC_I16X8_SHR_U }, + { "i16x8_add", ONYX_INTRINSIC_I16X8_ADD }, + { "i16x8_add_sat_s", ONYX_INTRINSIC_I16X8_ADD_SAT_S }, + { "i16x8_add_sat_u", ONYX_INTRINSIC_I16X8_ADD_SAT_U }, + { "i16x8_sub", ONYX_INTRINSIC_I16X8_SUB }, + { "i16x8_sub_sat_s", ONYX_INTRINSIC_I16X8_SUB_SAT_S }, + { "i16x8_sub_sat_u", ONYX_INTRINSIC_I16X8_SUB_SAT_U }, + { "i16x8_mul", ONYX_INTRINSIC_I16X8_MUL }, + { "i16x8_min_s", ONYX_INTRINSIC_I16X8_MIN_S }, + { "i16x8_min_u", ONYX_INTRINSIC_I16X8_MIN_U }, + { "i16x8_max_s", ONYX_INTRINSIC_I16X8_MAX_S }, + { "i16x8_max_u", ONYX_INTRINSIC_I16X8_MAX_U }, + { "i16x8_avgr_u", ONYX_INTRINSIC_I16X8_AVGR_U }, + + { "i32x4_abs", ONYX_INTRINSIC_I32X4_ABS }, + { "i32x4_neg", ONYX_INTRINSIC_I32X4_NEG }, + { "i32x4_any_true", ONYX_INTRINSIC_I32X4_ANY_TRUE }, + { "i32x4_all_true", ONYX_INTRINSIC_I32X4_ALL_TRUE }, + { "i32x4_bitmask", ONYX_INTRINSIC_I32X4_BITMASK }, + { "i32x4_widen_low_i16x8_s", ONYX_INTRINSIC_I32X4_WIDEN_LOW_I16X8_S }, + { "i32x4_widen_high_i16x8_s", ONYX_INTRINSIC_I32X4_WIDEN_HIGH_I16X8_S }, + { "i32x4_widen_low_i16x8_u", ONYX_INTRINSIC_I32X4_WIDEN_LOW_I16X8_U }, + { "i32x4_widen_high_i16x8_u", ONYX_INTRINSIC_I32X4_WIDEN_HIGH_I16X8_U }, + { "i32x4_shl", ONYX_INTRINSIC_I32X4_SHL }, + { "i32x4_shr_s", ONYX_INTRINSIC_I32X4_SHR_S }, + { "i32x4_shl_u", ONYX_INTRINSIC_I32X4_SHR_U }, + { "i32x4_add", ONYX_INTRINSIC_I32X4_ADD }, + { "i32x4_sub", ONYX_INTRINSIC_I32X4_SUB }, + { "i32x4_mul", ONYX_INTRINSIC_I32X4_MUL }, + { "i32x4_min_s", ONYX_INTRINSIC_I32X4_MIN_S }, + { "i32x4_min_u", ONYX_INTRINSIC_I32X4_MIN_U }, + { "i32x4_max_s", ONYX_INTRINSIC_I32X4_MAX_S }, + { "i32x4_max_u", ONYX_INTRINSIC_I32X4_MAX_U }, + + { "i64x2_neg", ONYX_INTRINSIC_I64X2_NEG }, + { "i64x2_shl", ONYX_INTRINSIC_I64X2_SHL }, + { "i64x2_shr_s", ONYX_INTRINSIC_I64X2_SHR_S }, + { "i64x2_shr_u", ONYX_INTRINSIC_I64X2_SHR_U }, + { "i64x2_add", ONYX_INTRINSIC_I64X2_ADD }, + { "i64x2_sub", ONYX_INTRINSIC_I64X2_SUB }, + { "i64x2_mul", ONYX_INTRINSIC_I64X2_MUL }, + + { "f32x4_abs", ONYX_INTRINSIC_F32X4_ABS }, + { "f32x4_neg", ONYX_INTRINSIC_F32X4_NEG }, + { "f32x4_sqrt", ONYX_INTRINSIC_F32X4_SQRT }, + { "f32x4_add", ONYX_INTRINSIC_F32X4_ADD }, + { "f32x4_sub", ONYX_INTRINSIC_F32X4_SUB }, + { "f32x4_mul", ONYX_INTRINSIC_F32X4_MUL }, + { "f32x4_div", ONYX_INTRINSIC_F32X4_DIV }, + { "f32x4_min", ONYX_INTRINSIC_F32X4_MIN }, + { "f32x4_max", ONYX_INTRINSIC_F32X4_MAX }, + + { "f64x2_abs", ONYX_INTRINSIC_F64X2_ABS }, + { "f64x2_neg", ONYX_INTRINSIC_F64X2_NEG }, + { "f64x2_sqrt", ONYX_INTRINSIC_F64X2_SQRT }, + { "f64x2_add", ONYX_INTRINSIC_F64X2_ADD }, + { "f64x2_sub", ONYX_INTRINSIC_F64X2_SUB }, + { "f64x2_mul", ONYX_INTRINSIC_F64X2_MUL }, + { "f64x2_div", ONYX_INTRINSIC_F64X2_DIV }, + { "f64x2_min", ONYX_INTRINSIC_F64X2_MIN }, + { "f64x2_max", ONYX_INTRINSIC_F64X2_MAX }, + + { "i32x4_trunc_sat_f32x4_s", ONYX_INTRINSIC_I32X4_TRUNC_SAT_F32X4_S }, + { "i32x4_trunc_sat_f32x4_u", ONYX_INTRINSIC_I32X4_TRUNC_SAT_F32X4_U }, + { "f32x4_convert_i32x4_s", ONYX_INTRINSIC_F32X4_CONVERT_I32X4_S }, + { "f32x4_convert_i32x4_u", ONYX_INTRINSIC_F32X4_CONVERT_I32X4_U }, + + { "__atomic_wait", ONYX_INTRINSIC_ATOMIC_WAIT }, + { "__atomic_notify", ONYX_INTRINSIC_ATOMIC_NOTIFY }, + + { "__atomic_fence", ONYX_INTRINSIC_ATOMIC_FENCE }, + + { "__atomic_load", ONYX_INTRINSIC_ATOMIC_LOAD }, + { "__atomic_store", ONYX_INTRINSIC_ATOMIC_STORE }, + { "__atomic_add", ONYX_INTRINSIC_ATOMIC_ADD }, + { "__atomic_sub", ONYX_INTRINSIC_ATOMIC_SUB }, + { "__atomic_and", ONYX_INTRINSIC_ATOMIC_AND }, + { "__atomic_or", ONYX_INTRINSIC_ATOMIC_OR }, + { "__atomic_xor", ONYX_INTRINSIC_ATOMIC_XOR }, + { "__atomic_xchg", ONYX_INTRINSIC_ATOMIC_XCHG }, + { "__atomic_cmpxchg", ONYX_INTRINSIC_ATOMIC_CMPXCHG }, + + { NULL, ONYX_INTRINSIC_UNDEFINED }, +}; + +bh_arr(OverloadOption) operator_overloads[Binary_Op_Count] = { 0 }; + +void initialize_builtins(bh_allocator a) { + // HACK + builtin_package_token.text = bh_strdup(global_heap_allocator, builtin_package_token.text); + + BuiltinSymbol* bsym = (BuiltinSymbol *) &builtin_symbols[0]; + while (bsym->sym != NULL) { + if (bsym->package == NULL) + symbol_builtin_introduce(context.global_scope, bsym->sym, bsym->node); + else { + Package* p = package_lookup_or_create(bsym->package, context.global_scope, a, context.global_scope->created_at); + assert(p); + + symbol_builtin_introduce(p->scope, bsym->sym, bsym->node); + } + bsym++; + } + + Package* p = package_lookup_or_create("builtin", context.global_scope, a, context.global_scope->created_at); + + builtin_string_type = (AstType *) symbol_raw_resolve(p->scope, "str"); + if (builtin_string_type == NULL) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'str' struct not found in builtin package."); + return; + } + + builtin_cstring_type = (AstType *) symbol_raw_resolve(p->scope, "cstr"); + if (builtin_cstring_type == NULL) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'cstr' type not found in builtin package."); + return; + } + + builtin_range_type = (AstType *) symbol_raw_resolve(p->scope, "range"); + if (builtin_range_type == NULL) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'range' struct not found in builtin package."); + return; + } + + builtin_vararg_type = (AstType *) symbol_raw_resolve(p->scope, "vararg"); + if (builtin_range_type == NULL) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'vararg' struct not found in builtin package."); + return; + } + + builtin_context_variable = (AstTyped *) symbol_raw_resolve(p->scope, "context"); + if (builtin_context_variable == NULL) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'context' variable not found in builtin package."); + return; + } + + builtin_allocator_type = (AstType *) symbol_raw_resolve(p->scope, "Allocator"); + if (builtin_allocator_type == NULL) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'Allocator' struct not found in builtin package."); + return; + } + + builtin_iterator_type = (AstType *) symbol_raw_resolve(p->scope, "Iterator"); + if (builtin_iterator_type == NULL) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'Iterator' struct not found in builtin package."); + return; + } + + builtin_callsite_type = (AstType *) symbol_raw_resolve(p->scope, "CallSite"); + if (builtin_callsite_type == NULL) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'CallSite' struct not found in builtin package."); + return; + } + + builtin_any_type = (AstType *) symbol_raw_resolve(p->scope, "any"); + if (builtin_any_type == NULL) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'any' struct not found in builtin package."); + return; + } + + builtin_code_type = (AstType *) symbol_raw_resolve(p->scope, "Code"); + if (builtin_code_type == NULL) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'Code' struct not found in builtin package."); + return; + } + + builtin_initialize_data_segments = (AstFunction *) symbol_raw_resolve(p->scope, "__initialize_data_segments"); + if (builtin_initialize_data_segments == NULL || builtin_initialize_data_segments->kind != Ast_Kind_Function) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'__initialize_data_segments' procedure not found in builtin package."); + return; + } + + builtin_run_init_procedures = (AstFunction *) symbol_raw_resolve(p->scope, "__run_init_procedures"); + if (builtin_run_init_procedures == NULL || builtin_run_init_procedures->kind != Ast_Kind_Function) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'__run_init_procedures' procedure not found."); + return; + } + + builtin_link_options_type = (AstType *) symbol_raw_resolve(p->scope, "Link_Options"); + if (builtin_link_options_type == NULL) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'Link_Options' type not found."); + return; + } + + bh_arr_new(global_heap_allocator, init_procedures, 4); + + fori (i, 0, Binary_Op_Count) { + bh_arr_new(global_heap_allocator, operator_overloads[i], 4); + } + + IntrinsicMap* intrinsic = &builtin_intrinsics[0]; + while (intrinsic->name != NULL) { + shput(intrinsic_table, intrinsic->name, intrinsic->intrinsic); + intrinsic++; + } +} + +void initalize_special_globals() { + Package *p = package_lookup("runtime.info"); + if (p != NULL) { + type_table_node = (AstTyped *) symbol_raw_resolve(p->scope, "type_table"); + foreign_blocks_node = (AstTyped *) symbol_raw_resolve(p->scope, "foreign_blocks"); + foreign_block_type = (AstType *) symbol_raw_resolve(p->scope, "foreign_block"); + tagged_procedures_node = (AstTyped *) symbol_raw_resolve(p->scope, "tagged_procedures"); + } +} + +void introduce_build_options(bh_allocator a) { + Package* p = package_lookup_or_create("runtime", context.global_scope, a, context.global_scope->created_at); + + AstType* Runtime_Type = (AstType *) symbol_raw_resolve(p->scope, "Runtime"); + if (Runtime_Type == NULL) { + onyx_report_error((OnyxFilePos) {0}, Error_Critical, "'Runtime' type not found in package runtime."); + return; + } + + AstNumLit* runtime_type = make_int_literal(a, context.options->runtime); + runtime_type->type_node = Runtime_Type; + add_entities_for_node(NULL, (AstNode *) runtime_type, NULL, NULL); + symbol_builtin_introduce(p->scope, "runtime", (AstNode *) runtime_type); + + AstNumLit* multi_threaded = make_int_literal(a, context.options->use_multi_threading); + multi_threaded->type_node = (AstType *) &basic_type_bool; + symbol_builtin_introduce(p->scope, "Multi_Threading_Enabled", (AstNode *) multi_threaded); + + AstNumLit* wait_notify_available = make_int_literal(a, context.options->use_multi_threading && context.options->runtime == Runtime_Js); + wait_notify_available->type_node = (AstType *) &basic_type_bool; + symbol_builtin_introduce(p->scope, "Wait_Notify_Available", (AstNode *) wait_notify_available); + + i32 os; + #ifdef _BH_LINUX + os = 1; + #endif + #ifdef _BH_WINDOWS + os = 2; + #endif + + AstType* OS_Type = (AstType *) symbol_raw_resolve(p->scope, "OS"); + if (OS_Type == NULL) { + onyx_report_error((OnyxFilePos) {0}, Error_Critical, "'OS' type not found in package runtime."); + return; + } + + AstNumLit* os_type = make_int_literal(a, os); + os_type->type_node = OS_Type; + add_entities_for_node(NULL, (AstNode *) os_type, NULL, NULL); + symbol_builtin_introduce(p->scope, "compiler_os", (AstNode *) os_type); + + i32 arch = 0; + #if defined(__x86_64__) || defined(_M_X64) + arch = 1; // X86_64; + #elif defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86) + arch = 2; // X86_32; + #elif defined(__aarch64__) || defined(_M_ARM64) + arch = 3; // AARCH64; + #endif + + AstType* Arch_Type = (AstType *) symbol_raw_resolve(p->scope, "Arch"); + if (Arch_Type == NULL) { + onyx_report_error((OnyxFilePos) {0}, Error_Critical, "'Arch' type not found in package runtime."); + return; + } + + AstNumLit* arch_type = make_int_literal(a, arch); + arch_type->type_node = Arch_Type; + add_entities_for_node(NULL, (AstNode *) arch_type, NULL, NULL); + symbol_builtin_introduce(p->scope, "arch", (AstNode *) arch_type); + + if (context.options->generate_foreign_info) { + AstNumLit* foreign_info = make_int_literal(a, 1); + foreign_info->type_node = (AstType *) &basic_type_bool; + symbol_builtin_introduce(p->scope, "Generated_Foreign_Info", (AstNode *) foreign_info); + } +} + diff --git a/compiler/src/checker.c b/compiler/src/checker.c new file mode 100644 index 00000000..333e331c --- /dev/null +++ b/compiler/src/checker.c @@ -0,0 +1,3023 @@ +#define BH_DEBUG +#include "parser.h" +#include "utils.h" + +// All of the `check` functions return a boolean that signals if an issue +// was reached while processing the node. These error booleans propagate +// up the call stack until they reach `check_entity`. + +#define CHECK(kind, ...) do { \ + CheckStatus cs = check_ ## kind (__VA_ARGS__); \ + if (cs > Check_Errors_Start) return cs; \ + } while (0) + +#define YIELD(loc, msg) do { \ + if (context.cycle_detected) { \ + onyx_report_error(loc, Error_Waiting_On, msg); \ + return Check_Error; \ + } else { \ + return Check_Yield_Macro; \ + } \ + } while (0) + +#define YIELD_(loc, msg, ...) do { \ + if (context.cycle_detected) { \ + onyx_report_error(loc, Error_Waiting_On, msg, __VA_ARGS__); \ + return Check_Error; \ + } else { \ + return Check_Yield_Macro; \ + } \ + } while (0) + +#define YIELD_ERROR(loc, msg) do { \ + if (context.cycle_detected) { \ + onyx_report_error(loc, Error_Critical, msg); \ + return Check_Error; \ + } else { \ + return Check_Yield_Macro; \ + } \ + } while (0) + +#define ERROR(loc, msg) do { \ + onyx_report_error(loc, Error_Critical, msg); \ + return Check_Error; \ + } while (0) + +#define ERROR_(loc, msg, ...) do { \ + onyx_report_error(loc, Error_Critical, msg, __VA_ARGS__); \ + return Check_Error; \ + } while (0) + +#define TYPE_CHECK_(expr, type, type_name) \ + TypeMatch type_name; \ + type_name = unify_node_and_type(expr, type); \ + if (type_name == TYPE_MATCH_YIELD) YIELD((*expr)->token->pos, "Waiting on type checking."); \ + if (type_name == TYPE_MATCH_FAILED) + +#define CONCAT(a, b) a##_##b +#define DEFER_LINE(a, line) CONCAT(a, line) +#define TYPE_CHECK(expr, type) TYPE_CHECK_(expr, type, DEFER_LINE(tc, __LINE__)) + +typedef enum CheckStatus { + Check_Success, // The node was successfully checked with out errors + Check_Complete, // The node is done processing + + Check_Errors_Start, + Check_Return_To_Symres, // Return this node for further symres processing + Check_Yield_Macro, + Check_Failed, // The node is done processing and should be put in the state of Failed. + Check_Error, // There was an error when checking the node +} CheckStatus; + +CheckStatus check_block(AstBlock* block); +CheckStatus check_statement_chain(AstNode** start); +CheckStatus check_statement(AstNode** pstmt); +CheckStatus check_return(AstReturn* retnode); +CheckStatus check_if(AstIfWhile* ifnode); +CheckStatus check_while(AstIfWhile* whilenode); +CheckStatus check_for(AstFor* fornode); +CheckStatus check_switch(AstSwitch* switchnode); +CheckStatus check_call(AstCall** pcall); +CheckStatus check_binaryop(AstBinaryOp** pbinop); +CheckStatus check_unaryop(AstUnaryOp** punop); +CheckStatus check_struct_literal(AstStructLiteral* sl); +CheckStatus check_array_literal(AstArrayLiteral* al); +CheckStatus check_range_literal(AstRangeLiteral** range); +CheckStatus check_compound(AstCompound* compound); +CheckStatus check_if_expression(AstIfExpression* if_expr); +CheckStatus check_expression(AstTyped** expr); +CheckStatus check_address_of(AstAddressOf** paof); +CheckStatus check_dereference(AstDereference* deref); +CheckStatus check_subscript(AstSubscript** paa); +CheckStatus check_field_access(AstFieldAccess** pfield); +CheckStatus check_method_call(AstBinaryOp** mcall); +CheckStatus check_size_of(AstSizeOf* so); +CheckStatus check_align_of(AstAlignOf* ao); +CheckStatus check_global(AstGlobal* global); +CheckStatus check_function(AstFunction* func); +CheckStatus check_overloaded_function(AstOverloadedFunction* func); +CheckStatus check_struct(AstStructType* s_node); +CheckStatus check_temp_function_header(AstFunction* func); +CheckStatus check_function_header(AstFunction* func); +CheckStatus check_memres_type(AstMemRes* memres); +CheckStatus check_memres(AstMemRes* memres); +CheckStatus check_type(AstType** ptype); +CheckStatus check_insert_directive(AstDirectiveInsert** pinsert); +CheckStatus check_directive_solidify(AstDirectiveSolidify** psolid); +CheckStatus check_do_block(AstDoBlock** pdoblock); +CheckStatus check_constraint(AstConstraint *constraint); +CheckStatus check_constraint_context(ConstraintContext *cc, Scope *scope, OnyxFilePos pos); +CheckStatus check_polyquery(AstPolyQuery *query); + +// HACK HACK HACK +b32 expression_types_must_be_known = 0; +b32 all_checks_are_final = 1; +b32 inside_for_iterator = 0; + +#define STATEMENT_LEVEL 1 +#define EXPRESSION_LEVEL 2 +u32 current_checking_level=0; + +static inline void fill_in_type(AstTyped* node) { + if (node->type == NULL) { + if (check_type(&node->type_node) > Check_Errors_Start) return; + + node->type = type_build_from_ast(context.ast_alloc, node->type_node); + } +} + +// HACK: This should be baked into a structure, not a global variable. +static Type** expected_return_type = NULL; + +CheckStatus check_return(AstReturn* retnode) { + if (retnode->expr) { + CHECK(expression, &retnode->expr); + + if (*expected_return_type == &type_auto_return) { + resolve_expression_type(retnode->expr); + if (retnode->expr->type == NULL) + YIELD_ERROR(retnode->token->pos, "Unable to determine the automatic return type here."); + + *expected_return_type = retnode->expr->type; + return Check_Success; + } + + TYPE_CHECK(&retnode->expr, *expected_return_type) { + ERROR_(retnode->token->pos, + "Expected to return a value of type '%s', returning value of type '%s'.", + type_get_name(*expected_return_type), + node_get_type_name(retnode->expr)); + } + + // + // Catch the obvious case of return '^.{ ... }', as that will never + // be legal. + if (retnode->expr->kind == Ast_Kind_Address_Of) { + AstAddressOf *aof = (AstAddressOf *) retnode->expr; + if (node_is_addressable_literal((AstNode *) aof->expr)) { + ERROR(retnode->token->pos, "Cannot return a pointer to a literal, as the space reserved for the literal will be freed upon returning."); + } + } + + } else { + if (*expected_return_type == &type_auto_return) { + *expected_return_type = &basic_types[Basic_Kind_Void]; + return Check_Success; + } + + if ((*expected_return_type)->Basic.size > 0) { + ERROR_(retnode->token->pos, + "Returning from non-void function without a value. Expected a value of type '%s'.", + type_get_name(*expected_return_type)); + } + } + + return Check_Success; +} + +CheckStatus check_if(AstIfWhile* ifnode) { + if (ifnode->initialization != NULL) CHECK(statement_chain, &ifnode->initialization); + + if (ifnode->kind == Ast_Kind_Static_If) { + if ((ifnode->flags & Ast_Flag_Static_If_Resolved) == 0) { + YIELD(ifnode->token->pos, "Waiting for static if to be resolved."); + } + + if (static_if_resolution(ifnode)) { + if (ifnode->true_stmt != NULL) { + CHECK(statement, (AstNode **) &ifnode->true_stmt); + ifnode->true_stmt->rules = Block_Rule_Macro; + } + + } else { + if (ifnode->false_stmt != NULL) { + CHECK(statement, (AstNode **) &ifnode->false_stmt); + ifnode->false_stmt->rules = Block_Rule_Macro; + } + } + + } else { + CHECK(expression, &ifnode->cond); + + if (!type_is_bool(ifnode->cond->type)) { + ERROR_(ifnode->cond->token->pos, "Expected expression of type 'bool' for condition, got '%s'", type_get_name(ifnode->cond->type)); + } + + if (ifnode->true_stmt) CHECK(statement, (AstNode **) &ifnode->true_stmt); + if (ifnode->false_stmt) CHECK(statement, (AstNode **) &ifnode->false_stmt); + } + + return Check_Success; +} + +CheckStatus check_while(AstIfWhile* whilenode) { + if (whilenode->initialization != NULL) CHECK(statement_chain, &whilenode->initialization); + + CHECK(expression, &whilenode->cond); + + if (!type_is_bool(whilenode->cond->type)) { + ERROR_(whilenode->cond->token->pos, "Expected expression of type 'bool' for condition, got '%s'", type_get_name(whilenode->cond->type)); + } + + if (whilenode->true_stmt) CHECK(statement, (AstNode **) &whilenode->true_stmt); + if (whilenode->false_stmt) { + if (whilenode->bottom_test) { + ERROR(whilenode->token->pos, "while-loops with an 'else' clause cannot be bottom tested."); + } + + CHECK(statement, (AstNode **) &whilenode->false_stmt); + } + + return Check_Success; +} + +CheckStatus check_for(AstFor* fornode) { + b32 old_inside_for_iterator; + if (fornode->flags & Ast_Flag_Has_Been_Checked) goto fornode_expr_checked; + + CHECK(expression, &fornode->iter); + resolve_expression_type(fornode->iter); + + Type* iter_type = fornode->iter->type; + if (iter_type == NULL) YIELD(fornode->token->pos, "Waiting for iteration expression type to be known."); + + OnyxFilePos error_loc = fornode->var->token->pos; + if (error_loc.filename == NULL) { + error_loc = fornode->token->pos; + } + + fornode->loop_type = For_Loop_Invalid; + if (types_are_compatible(iter_type, &basic_types[Basic_Kind_I32])) { + if (fornode->by_pointer) { + ERROR(error_loc, "Cannot iterate by pointer over a range."); + } + + AstNumLit* low_0 = make_int_literal(context.ast_alloc, 0); + AstRangeLiteral* rl = make_range_literal(context.ast_alloc, (AstTyped *) low_0, fornode->iter); + CHECK(range_literal, &rl); + fornode->iter = (AstTyped *) rl; + + fornode->var->type = builtin_range_type_type->Struct.memarr[0]->type; + fornode->var->flags |= Ast_Flag_Cannot_Take_Addr; + fornode->loop_type = For_Loop_Range; + } + else if (types_are_compatible(iter_type, builtin_range_type_type)) { + if (fornode->by_pointer) { + ERROR(error_loc, "Cannot iterate by pointer over a range."); + } + + // NOTE: Blindly copy the first range member's type which will + // be the low value. - brendanfh 2020/09/04 + fornode->var->type = builtin_range_type_type->Struct.memarr[0]->type; + fornode->var->flags |= Ast_Flag_Cannot_Take_Addr; + fornode->loop_type = For_Loop_Range; + + } + else if (iter_type->kind == Type_Kind_Array) { + if (fornode->by_pointer) fornode->var->type = type_make_pointer(context.ast_alloc, iter_type->Array.elem); + else fornode->var->type = iter_type->Array.elem; + + fornode->loop_type = For_Loop_Array; + } + else if (iter_type->kind == Type_Kind_Slice) { + if (fornode->by_pointer) fornode->var->type = type_make_pointer(context.ast_alloc, iter_type->Slice.elem); + else fornode->var->type = iter_type->Slice.elem; + + fornode->loop_type = For_Loop_Slice; + + } + else if (iter_type->kind == Type_Kind_VarArgs) { + if (fornode->by_pointer) { + ERROR_(error_loc, "Cannot iterate by pointer over '%s'.", type_get_name(iter_type)); + } + + fornode->var->type = iter_type->VarArgs.elem; + + // NOTE: Slices are VarArgs are being treated the same here. + fornode->loop_type = For_Loop_Slice; + } + else if (iter_type->kind == Type_Kind_DynArray) { + if (fornode->by_pointer) fornode->var->type = type_make_pointer(context.ast_alloc, iter_type->DynArray.elem); + else fornode->var->type = iter_type->DynArray.elem; + + fornode->loop_type = For_Loop_DynArr; + } + else if (type_struct_constructed_from_poly_struct(iter_type, builtin_iterator_type)) { + if (fornode->by_pointer) { + ERROR(error_loc, "Cannot iterate by pointer over an iterator."); + } + + // HACK: This assumes the Iterator type only has a single type argument. + fornode->var->type = iter_type->Struct.poly_sln[0].type; + fornode->loop_type = For_Loop_Iterator; + } + + if (fornode->by_pointer) + fornode->var->flags |= Ast_Flag_Cannot_Take_Addr; + + if (fornode->loop_type == For_Loop_Invalid) + ERROR_(error_loc, "Cannot iterate over a '%s'.", type_get_name(iter_type)); + + if (fornode->no_close && fornode->loop_type != For_Loop_Iterator) { + onyx_report_warning(error_loc, "Warning: #no_close here is meaningless as the iterable is not an iterator."); + } + + fornode->flags |= Ast_Flag_Has_Been_Checked; + +fornode_expr_checked: + old_inside_for_iterator = inside_for_iterator; + inside_for_iterator = 0; + iter_type = fornode->iter->type; + if (type_struct_constructed_from_poly_struct(iter_type, builtin_iterator_type)) { + inside_for_iterator = 1; + } + + do { + CheckStatus cs = check_block(fornode->stmt); + inside_for_iterator = old_inside_for_iterator; + if (cs > Check_Errors_Start) return cs; + } while(0); + + return Check_Success; +} + +static b32 add_case_to_switch_statement(AstSwitch* switchnode, u64 case_value, AstBlock* block, OnyxFilePos pos) { + assert(switchnode->switch_kind == Switch_Kind_Integer); + + switchnode->min_case = bh_min(switchnode->min_case, case_value); + switchnode->max_case = bh_max(switchnode->max_case, case_value); + + if (bh_imap_has(&switchnode->case_map, case_value)) { + onyx_report_error(pos, Error_Critical, "Multiple cases for values '%d'.", case_value); + return 1; + } + + bh_imap_put(&switchnode->case_map, case_value, (u64) block); + return 0; +} + +static CheckStatus collect_switch_case_blocks(AstSwitch* switchnode, AstBlock* root) { + AstNode *walker = root->body; + while (walker != NULL) { + switch (walker->kind) { + case Ast_Kind_Block: + collect_switch_case_blocks(switchnode, (AstBlock *) walker); + break; + + case Ast_Kind_Switch_Case: { + AstSwitchCase *case_node = (AstSwitchCase *) walker; + if (case_node->is_default) { + if (switchnode->default_case != NULL && switchnode->default_case != case_node->block) { + ERROR(case_node->token->pos, "Multiple #default cases given"); + ERROR(switchnode->default_case->token->pos, "Multiple #default cases given"); + return Check_Error; + } + + switchnode->default_case = case_node->block; + } else { + bh_arr_push(switchnode->cases, case_node); + } + break; + } + + default: + ERROR(walker->token->pos, "This statement is not allowed here."); + } + + walker = walker->next; + } + + return Check_Success; +} + +CheckStatus check_switch(AstSwitch* switchnode) { + if (switchnode->initialization != NULL) CHECK(statement_chain, &switchnode->initialization); + + CHECK(expression, &switchnode->expr); + Type* resolved_expr_type = resolve_expression_type(switchnode->expr); + + if (!(switchnode->flags & Ast_Flag_Has_Been_Checked)) { + if (resolved_expr_type == NULL) YIELD(switchnode->token->pos, "Waiting for expression type to be known."); + + switchnode->switch_kind = Switch_Kind_Integer; + if (!type_is_integer(switchnode->expr->type) && switchnode->expr->type->kind != Type_Kind_Enum) { + switchnode->switch_kind = Switch_Kind_Use_Equals; + } + + switch (switchnode->switch_kind) { + case Switch_Kind_Integer: + switchnode->min_case = 0xffffffffffffffff; + bh_imap_init(&switchnode->case_map, global_heap_allocator, 4); + break; + + case Switch_Kind_Use_Equals: + bh_arr_new(global_heap_allocator, switchnode->case_exprs, 4); + break; + + default: assert(0); + } + } + switchnode->flags |= Ast_Flag_Has_Been_Checked; + + // Should the case block code be checked here? + // Or should this just exist to resolve macros and expand #unquotes + // then the cases are consumed into the array or cases, THEN the blocks + // are actually checked? + if (switchnode->cases == NULL) { + CHECK(block, switchnode->case_block); + + bh_arr_new(global_heap_allocator, switchnode->cases, 4); + if (collect_switch_case_blocks(switchnode, switchnode->case_block) != Check_Success) { + return Check_Error; + } + + // This is important, otherwise if this block has to return to symbol resolution. + switchnode->case_block->statement_idx = 0; + } + + fori (i, switchnode->yield_return_index, bh_arr_length(switchnode->cases)) { + AstSwitchCase *sc = switchnode->cases[i]; + CHECK(block, sc->block); + + bh_arr_each(AstTyped *, value, sc->values) { + CHECK(expression, value); + + if (switchnode->switch_kind == Switch_Kind_Integer && (*value)->kind == Ast_Kind_Range_Literal) { + AstRangeLiteral* rl = (AstRangeLiteral *) (*value); + resolve_expression_type(rl->low); + resolve_expression_type(rl->high); + + if (rl->low->kind != Ast_Kind_NumLit || rl->high->kind != Ast_Kind_NumLit) { + ERROR(rl->token->pos, "case statement expected compile time known range."); + } + + promote_numlit_to_larger((AstNumLit *) rl->low); + promote_numlit_to_larger((AstNumLit *) rl->high); + + i64 lower = ((AstNumLit *) rl->low)->value.l; + i64 upper = ((AstNumLit *) rl->high)->value.l; + + // NOTE: This is inclusive!!!! + fori (case_value, lower, upper + 1) { + if (add_case_to_switch_statement(switchnode, case_value, sc->block, rl->token->pos)) + return Check_Error; + } + + continue; + } + + TYPE_CHECK(value, resolved_expr_type) { + OnyxToken* tkn = sc->block->token; + if ((*value)->token) tkn = (*value)->token; + + ERROR_(tkn->pos, "Mismatched types in switch-case. Expected '%s', got '%s'.", + type_get_name(resolved_expr_type), type_get_name((*value)->type)); + } + + switch (switchnode->switch_kind) { + case Switch_Kind_Integer: { + b32 is_valid; + i64 integer_value = get_expression_integer_value(*value, &is_valid); + if (!is_valid) + ERROR_((*value)->token->pos, "Case statement expected compile time known integer. Got '%s'.", onyx_ast_node_kind_string((*value)->kind)); + + if (add_case_to_switch_statement(switchnode, integer_value, sc->block, sc->block->token->pos)) + return Check_Error; + + break; + } + + case Switch_Kind_Use_Equals: { + // Gross + b32 found = 0; + bh_arr_each(CaseToBlock, ctb, switchnode->case_exprs) { + if (ctb->original_value == *value) { + CHECK(expression, (AstTyped **) &ctb->comparison); + found = 1; + break; + } + } + if (found) break; + + CaseToBlock ctb; + ctb.block = sc->block; + ctb.original_value = *value; + ctb.comparison = make_binary_op(context.ast_alloc, Binary_Op_Equal, switchnode->expr, *value); + ctb.comparison->token = (*value)->token; + bh_arr_push(switchnode->case_exprs, ctb); + + CHECK(binaryop, &bh_arr_last(switchnode->case_exprs).comparison); + break; + } + } + } + + switchnode->yield_return_index += 1; + } + + if (switchnode->default_case) + CHECK(block, switchnode->default_case); + + return 0; +} + +CheckStatus check_arguments(Arguments* args) { + bh_arr_each(AstTyped *, actual, args->values) + CHECK(expression, actual); + + bh_arr_each(AstNamedValue *, named_value, args->named_values) + CHECK(expression, &(*named_value)->value); + + return Check_Success; +} + +CheckStatus check_argument(AstArgument** parg) { + CHECK(expression, &(*parg)->value); + (*parg)->type = (*parg)->value->type; + + return Check_Success; +} + +static CheckStatus check_resolve_callee(AstCall* call, AstTyped** effective_callee) { + if (call->kind == Ast_Kind_Intrinsic_Call) return Check_Success; + + AstTyped* callee = (AstTyped *) strip_aliases((AstNode *) call->callee); + b32 calling_a_macro = 0; + + if (callee->kind == Ast_Kind_Overloaded_Function) { + AstTyped* new_callee = find_matching_overload_by_arguments( + ((AstOverloadedFunction *) callee)->overloads, + &call->args); + + if (new_callee == NULL) { + report_unable_to_match_overload(call, ((AstOverloadedFunction *) callee)->overloads); + return Check_Error; + } + + if (new_callee == (AstTyped *) &node_that_signals_a_yield) { + YIELD(call->token->pos, "Waiting for overloaded function option to pass type-checking."); + } + + callee = new_callee; + } + + if (callee->kind == Ast_Kind_Macro) { + calling_a_macro = 1; + call->callee = callee; + + AstTyped* new_callee = (AstTyped *) macro_resolve_header((AstMacro *) callee, &call->args, call->token, 1); + if (new_callee == NULL) return Check_Error; + if (new_callee == (AstTyped *) &node_that_signals_a_yield) { + YIELD(call->token->pos, "Waiting for macro header to pass type-checking."); + } + + arguments_remove_baked(&call->args); + callee = new_callee; + + } else while (callee->kind == Ast_Kind_Polymorphic_Proc) { + AstTyped* new_callee = (AstTyped *) polymorphic_proc_lookup((AstFunction *) callee, PPLM_By_Arguments, &call->args, call->token); + if (new_callee == NULL) return Check_Error; + if (new_callee == (AstTyped *) &node_that_signals_a_yield) { + YIELD(call->token->pos, "Waiting for polymorphic procedure header to pass type-checking."); + } + + arguments_remove_baked(&call->args); + callee = new_callee; + } + + // NOTE: Build callee's type + fill_in_type((AstTyped *) callee); + if (callee->type == NULL) { + YIELD(call->token->pos, "Trying to resolve function type for callee."); + } + + if (!calling_a_macro) call->callee = callee; + + if (callee->type->kind != Type_Kind_Function) { + ERROR_(call->token->pos, + "Attempting to call something that is not a function, '%b'.", + callee->token->text, callee->token->length); + } + + *effective_callee = callee; + return Check_Success; +} + +CheckStatus check_call(AstCall** pcall) { + // All the things that need to be done when checking a call node. + // 1. Ensure the callee is not a symbol + // 2. Check the callee expression (since it could be a variable or a field access, etc) + // 3. Check all arguments + // * Cannot pass overloaded functions (ROBUSTNESS) + // 4. If callee is an overloaded function, use the argument types to determine which overload is used. + // 5. If callee is polymorphic, use the arguments type to generate a polymorphic function. + // 7. Fill in arguments + // 8. If callee is an intrinsic, turn call into an Intrinsic_Call node + // 9. Check types of formal and actual params against each other, handling varargs + AstCall* call = *pcall; + + if (call->kind == Ast_Kind_Call) { + AstNode* callee = strip_aliases((AstNode *) call->callee); + if (callee->kind == Ast_Kind_Poly_Struct_Type) { + *pcall = (AstCall *) convert_call_to_polycall(call); + CHECK(expression, (AstTyped **) pcall); + return Check_Success; + } + } + + if (call->flags & Ast_Flag_Has_Been_Checked) return Check_Success; + + u32 current_checking_level_store = current_checking_level; + CHECK(expression, &call->callee); + CHECK(arguments, &call->args); + current_checking_level = current_checking_level_store; + + AstFunction* callee=NULL; + CHECK(resolve_callee, call, (AstTyped **) &callee); + + if (callee->kind == Ast_Kind_Function) { + if (callee->constraints.constraints != NULL && callee->constraints.constraints_met == 0) { + YIELD(call->token->pos, "Waiting for constraints to be checked on callee."); + } + } + + i32 arg_count = get_argument_buffer_size(&callee->type->Function, &call->args); + arguments_ensure_length(&call->args, arg_count); + + char* err_msg = NULL; + fill_in_arguments(&call->args, (AstNode *) callee, &err_msg, 0); + if (err_msg != NULL) ERROR(call->token->pos, err_msg); + + bh_arr(AstArgument *) arg_arr = (bh_arr(AstArgument *)) call->args.values; + bh_arr_each(AstArgument *, arg, arg_arr) { + if (*arg != NULL) continue; + + ERROR(call->token->pos, "Not all arguments were given a value."); + } + + // HACK HACK HACK + // :CallSiteIsGross + bh_arr_each(AstArgument *, arg, arg_arr) { + AstTyped** arg_value = &(*arg)->value; + + if ((*arg_value)->kind == Ast_Kind_Call_Site) { + AstCallSite* callsite = (AstCallSite *) ast_clone(context.ast_alloc, *arg_value); + callsite->callsite_token = call->token; + + // HACK CLEANUP + OnyxToken* str_token = bh_alloc(context.ast_alloc, sizeof(OnyxToken)); + str_token->text = bh_strdup(global_heap_allocator, (char *) call->token->pos.filename); + str_token->length = strlen(call->token->pos.filename); + str_token->pos = call->token->pos; + str_token->type = Token_Type_Literal_String; + + AstStrLit* filename = bh_alloc_item(context.ast_alloc, AstStrLit); + memset(filename, 0, sizeof(AstStrLit)); + filename->kind = Ast_Kind_StrLit; + filename->token = str_token; + filename->data_id = 0; + + add_entities_for_node(NULL, (AstNode *) filename, NULL, NULL); + callsite->filename = filename; + + callsite->line = make_int_literal(context.ast_alloc, call->token->pos.line); + callsite->column = make_int_literal(context.ast_alloc, call->token->pos.column); + + convert_numlit_to_type(callsite->line, &basic_types[Basic_Kind_U32]); + convert_numlit_to_type(callsite->column, &basic_types[Basic_Kind_U32]); + + *arg_value = (AstTyped *) callsite; + } + } + + // NOTE: If we are calling an intrinsic function, translate the + // call into an intrinsic call node. + if (callee->kind == Ast_Kind_Function && callee->is_intrinsic) { + call->kind = Ast_Kind_Intrinsic_Call; + call->callee = NULL; + + token_toggle_end(callee->intrinsic_name); + char* intr_name = callee->intrinsic_name->text; + + i32 index; + if ((index = shgeti(intrinsic_table, intr_name)) == -1) { + onyx_report_error(callee->token->pos, Error_Critical, "Intrinsic not supported, '%s'.", intr_name); + token_toggle_end(callee->intrinsic_name); + return Check_Error; + } + + call->intrinsic = intrinsic_table[index].value; + + token_toggle_end(callee->intrinsic_name); + } + + call->va_kind = VA_Kind_Not_VA; + call->type = callee->type->Function.return_type; + if (call->type == &type_auto_return && call->callee->kind != Ast_Kind_Macro) { + YIELD(call->token->pos, "Waiting for auto-return type to be solved."); + } + + OnyxError error; + TypeMatch tm = check_arguments_against_type(&call->args, &callee->type->Function, &call->va_kind, + call->token, get_function_name(callee), &error); + if (tm == TYPE_MATCH_FAILED) { + onyx_submit_error(error); + return Check_Error; + } + + if (tm == TYPE_MATCH_YIELD) YIELD(call->token->pos, "Waiting on argument type checking."); + + call->flags |= Ast_Flag_Has_Been_Checked; + callee->flags |= Ast_Flag_Function_Used; + + if (call->kind == Ast_Kind_Call && call->callee->kind == Ast_Kind_Macro) { + expand_macro(pcall, callee); + return Check_Return_To_Symres; + } + + return Check_Success; +} + +static void report_bad_binaryop(AstBinaryOp* binop) { + onyx_report_error(binop->token->pos, Error_Critical, "Binary operator '%s' not understood for arguments of type '%s' and '%s'.", + binaryop_string[binop->operation], + node_get_type_name(binop->left), + node_get_type_name(binop->right)); +} + +static AstCall* binaryop_try_operator_overload(AstBinaryOp* binop, AstTyped* third_argument) { + if (bh_arr_length(operator_overloads[binop->operation]) == 0) return NULL; + + if (binop->overload_args == NULL) { + binop->overload_args = bh_alloc_item(context.ast_alloc, Arguments); + bh_arr_new(context.ast_alloc, binop->overload_args->values, 3); + bh_arr_set_length(binop->overload_args->values, third_argument ? 3 : 2); + + if (binop_is_assignment(binop->operation)) { + binop->overload_args->values[0] = (AstTyped *) make_address_of(context.ast_alloc, binop->left); + + u32 current_all_checks_are_final = all_checks_are_final; + all_checks_are_final = 0; + u32 current_checking_level_store = current_checking_level; + CheckStatus cs = check_address_of((AstAddressOf **) &binop->overload_args->values[0]); + current_checking_level = current_checking_level_store; + all_checks_are_final = current_all_checks_are_final; + + if (cs == Check_Yield_Macro) return (AstCall *) &node_that_signals_a_yield; + if (cs == Check_Error) return NULL; + + binop->overload_args->values[0] = (AstTyped *) make_argument(context.ast_alloc, binop->overload_args->values[0]); + + } else { + binop->overload_args->values[0] = (AstTyped *) make_argument(context.ast_alloc, binop->left); + } + + + binop->overload_args->values[1] = (AstTyped *) make_argument(context.ast_alloc, binop->right); + if (third_argument != NULL) binop->overload_args->values[2] = (AstTyped *) make_argument(context.ast_alloc, third_argument); + } + + AstTyped* overload = find_matching_overload_by_arguments(operator_overloads[binop->operation], binop->overload_args); + if (overload == NULL || overload == (AstTyped *) &node_that_signals_a_yield) return (AstCall *) overload; + + AstCall* implicit_call = onyx_ast_node_new(context.ast_alloc, sizeof(AstCall), Ast_Kind_Call); + implicit_call->token = binop->token; + implicit_call->callee = overload; + implicit_call->va_kind = VA_Kind_Not_VA; + + arguments_clone(&implicit_call->args, binop->overload_args); + return implicit_call; +} + + +CheckStatus check_binaryop_assignment(AstBinaryOp** pbinop) { + AstBinaryOp* binop = *pbinop; + if (current_checking_level == EXPRESSION_LEVEL) + ERROR(binop->token->pos, "Assignment not valid in expression."); + + if (!is_lval((AstNode *) binop->left)) + ERROR_(binop->left->token->pos, + "Cannot assign to '%b'.", + binop->left->token->text, binop->left->token->length); + + if ((binop->left->flags & Ast_Flag_Const) != 0 && binop->left->type != NULL) + ERROR_(binop->token->pos, + "Cannot assign to constant '%b'.", + binop->left->token->text, binop->left->token->length); + + if (binop->operation == Binary_Op_Assign) { + // NOTE: Raw assignment + + // NOTE: This is the 'type inference' system. Very stupid, but very easy. + // If a left operand has an unknown type, fill it in with the type of + // the right hand side. + if (binop->left->type == NULL) { + if (binop->left->type_node != NULL && binop->left->entity && binop->left->entity->state <= Entity_State_Check_Types) { + YIELD(binop->token->pos, "Waiting for type to be constructed on left hand side."); + } + + // NOTE: There is a subtlety here. You cannot use the result of `resolve_expression_type` directly, + // as in some cases (especially with macros and polyprocs), the result is not "correct". The result + // makes them appears as though they are runtime-known values, which they are not. Using the following + // pattern does prevent this issue. + resolve_expression_type(binop->right); + + Type* right_type = get_expression_type(binop->right); + if (right_type == NULL) { + if (binop->right->entity == NULL || binop->right->entity->state > Entity_State_Check_Types) { + ERROR(binop->token->pos, "Could not resolve type of right hand side to infer."); + + } else { + YIELD(binop->token->pos, "Trying to resolve try of right hand side."); + } + } + + if (right_type->kind == Type_Kind_Compound) { + AstCompound* lhs = (AstCompound *) binop->left; + i32 expr_count = right_type->Compound.count; + if (lhs->kind != Ast_Kind_Compound || bh_arr_length(lhs->exprs) != expr_count) { + ERROR_(binop->token->pos, "Expected left hand side to have %d expressions.", expr_count); + } + + fori (i, 0, expr_count) lhs->exprs[i]->type = right_type->Compound.types[i]; + + lhs->type = type_build_compound_type(context.ast_alloc, lhs); + + } else { + binop->left->type = right_type; + } + } + + } else { + // NOTE: +=, -=, ... + // NOTE: At this point, it is assumed that operator overloads for +=, -=, etc have been tested. + + BinaryOp operation = -1; + if (binop->operation == Binary_Op_Assign_Add) operation = Binary_Op_Add; + else if (binop->operation == Binary_Op_Assign_Minus) operation = Binary_Op_Minus; + else if (binop->operation == Binary_Op_Assign_Multiply) operation = Binary_Op_Multiply; + else if (binop->operation == Binary_Op_Assign_Divide) operation = Binary_Op_Divide; + else if (binop->operation == Binary_Op_Assign_Modulus) operation = Binary_Op_Modulus; + else if (binop->operation == Binary_Op_Assign_And) operation = Binary_Op_And; + else if (binop->operation == Binary_Op_Assign_Or) operation = Binary_Op_Or; + else if (binop->operation == Binary_Op_Assign_Xor) operation = Binary_Op_Xor; + else if (binop->operation == Binary_Op_Assign_Shl) operation = Binary_Op_Shl; + else if (binop->operation == Binary_Op_Assign_Shr) operation = Binary_Op_Shr; + else if (binop->operation == Binary_Op_Assign_Sar) operation = Binary_Op_Sar; + + AstBinaryOp* new_right = make_binary_op(context.ast_alloc, operation, binop->left, binop->right); + binop->right = (AstTyped *) new_right; + new_right->token = binop->token; + binop->operation = Binary_Op_Assign; + + CHECK(binaryop, (AstBinaryOp **) &binop->right); + } + + if (binop->right->type == NULL) { + if (binop->right->entity != NULL && binop->right->entity->state <= Entity_State_Check_Types) { + YIELD(binop->token->pos, "Trying to resolve type of right hand side."); + } + } + + TYPE_CHECK(&binop->right, binop->left->type) { + ERROR_(binop->token->pos, + "Cannot assign value of type '%s' to a '%s'.", + node_get_type_name(binop->right), + node_get_type_name(binop->left)); + } + + binop->type = &basic_types[Basic_Kind_Void]; + + return Check_Success; +} + +static b32 binary_op_is_allowed(BinaryOp operation, Type* type) { + static const u8 binop_allowed[Binary_Op_Count] = { + /* Add */ Basic_Flag_Numeric | Basic_Flag_Pointer, + /* Minus */ Basic_Flag_Numeric | Basic_Flag_Pointer, + /* Multiply */ Basic_Flag_Numeric, + /* Divide */ Basic_Flag_Numeric, + /* Modulus */ Basic_Flag_Integer, + + /* Equal */ Basic_Flag_Equality, + /* Not_Equal */ Basic_Flag_Equality, + /* Less */ Basic_Flag_Ordered, + /* Less_Equal */ Basic_Flag_Ordered, + /* Greater */ Basic_Flag_Ordered, + /* Greater_Equal */ Basic_Flag_Ordered, + + /* And */ Basic_Flag_Integer, + /* Or */ Basic_Flag_Integer, + /* Xor */ Basic_Flag_Integer, + /* Shl */ Basic_Flag_Integer, + /* Shr */ Basic_Flag_Integer, + /* Sar */ Basic_Flag_Integer, + + /* Bool_And */ Basic_Flag_Boolean, + /* Bool_Or */ Basic_Flag_Boolean, + + /* Assign_Start */ 0, + /* Assign */ 0, + /* Assign_Add */ 0, + /* Assign_Minus */ 0, + /* Assign_Multiply */ 0, + /* Assign_Divide */ 0, + /* Assign_Modulus */ 0, + /* Assign_And */ 0, + /* Assign_Or */ 0, + /* Assign_Xor */ 0, + /* Assign_Shl */ 0, + /* Assign_Shr */ 0, + /* Assign_Sar */ 0, + /* Assign_End */ 0, + + /* Pipe */ 0, + /* Range */ 0, + }; + + enum BasicFlag effective_flags = 0; + switch (type->kind) { + case Type_Kind_Basic: effective_flags = type->Basic.flags; break; + case Type_Kind_Pointer: effective_flags = Basic_Flag_Pointer; break; + case Type_Kind_Enum: effective_flags = Basic_Flag_Integer; break; + case Type_Kind_Function: effective_flags = Basic_Flag_Equality; break; + } + + return (binop_allowed[operation] & effective_flags) != 0; +} + +CheckStatus check_binaryop_compare(AstBinaryOp** pbinop) { + AstBinaryOp* binop = *pbinop; + + // HACK: Since ^... to rawptr is a one way conversion, strip any pointers + // away so they can be compared as expected + Type* ltype = binop->left->type; + Type* rtype = binop->right->type; + + if (ltype == NULL) YIELD(binop->token->pos, "Waiting for left-type to be known."); + if (rtype == NULL) YIELD(binop->token->pos, "Waiting for right-type to be known."); + + if (ltype->kind == Type_Kind_Pointer) ltype = &basic_types[Basic_Kind_Rawptr]; + if (rtype->kind == Type_Kind_Pointer) rtype = &basic_types[Basic_Kind_Rawptr]; + + if (!types_are_compatible(ltype, rtype)) { + b32 left_ac = node_is_auto_cast((AstNode *) binop->left); + b32 right_ac = node_is_auto_cast((AstNode *) binop->right); + if (left_ac && right_ac) ERROR(binop->token->pos, "Cannot have auto cast on both sides of binary operator."); + + TYPE_CHECK(&binop->left, rtype) { + TYPE_CHECK(&binop->right, ltype) { + ERROR_(binop->token->pos, + "Cannot compare '%s' to '%s'.", + type_get_name(binop->left->type), + type_get_name(binop->right->type)); + } + } + } + + if (!binary_op_is_allowed(binop->operation, binop->left->type)) { + report_bad_binaryop(binop); + return Check_Error; + } + + binop->type = &basic_types[Basic_Kind_Bool]; + if (binop->flags & Ast_Flag_Comptime) { + // NOTE: Not a binary op + *pbinop = (AstBinaryOp *) ast_reduce(context.ast_alloc, (AstTyped *) binop); + } + + return Check_Success; +} + +CheckStatus check_binaryop_bool(AstBinaryOp** pbinop) { + AstBinaryOp* binop = *pbinop; + + if (!type_is_bool(binop->left->type) || !type_is_bool(binop->right->type)) { + report_bad_binaryop(binop); + return Check_Error; + } + + binop->type = &basic_types[Basic_Kind_Bool]; + + if (binop->flags & Ast_Flag_Comptime) { + // NOTE: Not a binary op + *pbinop = (AstBinaryOp *) ast_reduce(context.ast_alloc, (AstTyped *) binop); + } + return Check_Success; +} + +CheckStatus check_binaryop(AstBinaryOp** pbinop) { + AstBinaryOp* binop = *pbinop; + + if (binop->flags & Ast_Flag_Has_Been_Checked) return Check_Success; + + if (binop->operation == Binary_Op_Assign && binop->left->kind == Ast_Kind_Subscript && bh_arr_length(operator_overloads[Binary_Op_Subscript_Equals]) > 0) { + AstSubscript* sub = (AstSubscript *) binop->left; + + if (binop->potential_substitute == NULL) { + u32 current_checking_level_store = current_checking_level; + CHECK(expression, &sub->addr); + CHECK(expression, &sub->expr); + CHECK(expression, &binop->right); + current_checking_level = current_checking_level_store; + + AstBinaryOp *op = onyx_ast_node_new(context.ast_alloc, sizeof(AstBinaryOp), Ast_Kind_Binary_Op); + op->token = binop->token; + op->operation = Binary_Op_Subscript_Equals; + op->left = ((AstSubscript *) binop->left)->addr; + op->right = ((AstSubscript *) binop->left)->expr; + + binop->potential_substitute = op; + } + + AstCall* call = binaryop_try_operator_overload(binop->potential_substitute, binop->right); + if (call == (AstCall *) &node_that_signals_a_yield) YIELD(binop->token->pos, "Waiting on potential operator overload."); + if (call != NULL) { + call->next = binop->next; + *(AstCall **) pbinop = call; + + CHECK(call, (AstCall **) pbinop); + return Check_Success; + } + + } + + u32 current_checking_level_store = current_checking_level; + CHECK(expression, &binop->left); + CHECK(expression, &binop->right); + current_checking_level = current_checking_level_store; + + // :UnaryFieldAccessIsGross + if (binop->left->kind == Ast_Kind_Unary_Field_Access || binop->right->kind == Ast_Kind_Unary_Field_Access) { + TYPE_CHECK(&binop->left, binop->right->type) { + TYPE_CHECK(&binop->right, binop->left->type) { + // TODO: This should report a better error about the Unary_Field_Access not be able to be resolved given whatever type. + // - brendanfh 2021/12/31 + report_bad_binaryop(binop); + return Check_Error; + } + } + } + + if ((binop->left->flags & Ast_Flag_Comptime) && (binop->right->flags & Ast_Flag_Comptime)) { + binop->flags |= Ast_Flag_Comptime; + } + + if (expression_types_must_be_known) { + if (binop->left->type == NULL || binop->right->type == NULL) { + ERROR(binop->token->pos, "Internal compiler error: one of the operands types is unknown here."); + } + } + + // NOTE: Try operator overloading before checking everything else. + if ((binop->left->type != NULL && (binop->left->type->kind != Type_Kind_Basic || (binop->left->type->Basic.flags & Basic_Flag_SIMD) != 0)) + || (binop->right->type != NULL && (binop->right->type->kind != Type_Kind_Basic || (binop->right->type->Basic.flags & Basic_Flag_SIMD) != 0))) { + AstCall *implicit_call = binaryop_try_operator_overload(binop, NULL); + + if (implicit_call == (AstCall *) &node_that_signals_a_yield) + YIELD(binop->token->pos, "Trying to resolve operator overload."); + + if (implicit_call != NULL) { + // NOTE: Not a binary op + implicit_call->next = binop->next; + *pbinop = (AstBinaryOp *) implicit_call; + + CHECK(call, (AstCall **) pbinop); + return Check_Success; + } + } + + if (binop_is_assignment(binop->operation)) return check_binaryop_assignment(pbinop); + + if (binop->left->type == NULL && binop->left->entity && binop->left->entity->state <= Entity_State_Check_Types) { + YIELD(binop->left->token->pos, "Waiting for this type to be known"); + } + if (binop->right->type == NULL && binop->right->entity && binop->right->entity->state <= Entity_State_Check_Types) { + YIELD(binop->right->token->pos, "Waiting for this type to be known"); + } + + // NOTE: Comparision operators and boolean operators are handled separately. + if (binop_is_compare(binop->operation)) + return check_binaryop_compare(pbinop); + if (binop->operation == Binary_Op_Bool_And || binop->operation == Binary_Op_Bool_Or) + return check_binaryop_bool(pbinop); + + // NOTE: The left side cannot be compound. + // The right side always is numeric. + // The left side cannot be rawptr. + if (type_is_compound(binop->left->type)) goto bad_binaryop; + if (!type_is_numeric(binop->right->type)) goto bad_binaryop; + if (type_is_rawptr(binop->left->type)) { + ERROR(binop->token->pos, "Cannot operate on a 'rawptr'. Cast it to a another pointer type first."); + } + + // NOTE: Handle basic pointer math. + if (type_is_pointer(binop->left->type)) { + if (binop->operation != Binary_Op_Add && binop->operation != Binary_Op_Minus) goto bad_binaryop; + + resolve_expression_type(binop->right); + if (!type_is_integer(binop->right->type)) goto bad_binaryop; + + AstNumLit* numlit = make_int_literal(context.ast_alloc, type_size_of(binop->left->type->Pointer.elem)); + numlit->token = binop->right->token; + numlit->type = binop->right->type; + + AstBinaryOp* binop_node = make_binary_op(context.ast_alloc, Binary_Op_Multiply, binop->right, (AstTyped *) numlit); + binop_node->token = binop->token; + CHECK(binaryop, &binop_node); + + binop->right = (AstTyped *) binop_node; + binop->type = binop->left->type; + binop->right->type = binop->left->type; + } + + if (!types_are_compatible(binop->left->type, binop->right->type)) { + b32 left_ac = node_is_auto_cast((AstNode *) binop->left); + b32 right_ac = node_is_auto_cast((AstNode *) binop->right); + if (left_ac && right_ac) { + ERROR(binop->token->pos, "Cannot have auto cast on both sides of binary operator."); + } + + TYPE_CHECK(&binop->left, binop->right->type) { + TYPE_CHECK(&binop->right, binop->left->type) { + ERROR_(binop->token->pos, + "Mismatched types for binary operation '%s'. left: '%s', right: '%s'.", + binaryop_string[binop->operation], + node_get_type_name(binop->left), + node_get_type_name(binop->right)); + } + } + } + + binop->type = binop->left->type; + if (!binary_op_is_allowed(binop->operation, binop->type)) goto bad_binaryop; + + // NOTE: Enum flags with '&' result in a boolean value + if (binop->type->kind == Type_Kind_Enum && binop->type->Enum.is_flags && binop->operation == Binary_Op_And) { + binop->type = &basic_types[Basic_Kind_Bool]; + } + + if (all_checks_are_final) { + binop->flags |= Ast_Flag_Has_Been_Checked; + + if (binop->flags & Ast_Flag_Comptime) { + // NOTE: Not a binary op + *pbinop = (AstBinaryOp *) ast_reduce(context.ast_alloc, (AstTyped *) binop); + } + } + + return Check_Success; + +bad_binaryop: + report_bad_binaryop(binop); + + return Check_Error; +} + +CheckStatus check_unaryop(AstUnaryOp** punop) { + AstUnaryOp* unaryop = *punop; + + CHECK(expression, &unaryop->expr); + + if (unaryop->operation != Unary_Op_Negate) { + resolve_expression_type(unaryop->expr); + } + + if (unaryop->operation == Unary_Op_Cast) { + char* err; + if (unaryop->type == NULL) + YIELD(unaryop->token->pos, "Trying to resolve destination type for cast."); + + if (!cast_is_legal(unaryop->expr->type, unaryop->type, &err)) { + ERROR_(unaryop->token->pos, "Cast Error: %s", err); + } + + } else { + unaryop->type = unaryop->expr->type; + } + + if (unaryop->operation == Unary_Op_Not) { + if (!type_is_bool(unaryop->expr->type)) { + ERROR_(unaryop->token->pos, + "Bool negation operator expected bool type, got '%s'.", + node_get_type_name(unaryop->expr)); + } + } + + if (unaryop->operation == Unary_Op_Bitwise_Not) { + if (!type_is_integer(unaryop->expr->type)) { + ERROR_(unaryop->token->pos, + "Bitwise operator expected integer type, got '%s'.", + node_get_type_name(unaryop->expr)); + } + } + + if (unaryop->expr->flags & Ast_Flag_Comptime) { + unaryop->flags |= Ast_Flag_Comptime; + // NOTE: Not a unary op + *punop = (AstUnaryOp *) ast_reduce(context.ast_alloc, (AstTyped *) unaryop); + } + + return Check_Success; +} + +CheckStatus check_struct_literal(AstStructLiteral* sl) { + + if (sl->type == NULL) { + // NOTE: This is used for automatically typed struct literals. If there is no provided + // type for the struct literal, assume that it is passes successfully. When it is used + // elsewhere, it will be added as an expression entity that will be processed once the + // stnode is filled out. + if (sl->stnode == NULL) { + CHECK(arguments, &sl->args); + + return Check_Success; + } + + CHECK(expression, &sl->stnode); + if (!node_is_type((AstNode *) sl->stnode)) { + ERROR(sl->token->pos, "Type used for struct literal is not a type."); + } + + sl->type = type_build_from_ast(context.ast_alloc, (AstType *) sl->stnode); + if (sl->type == NULL) + YIELD(sl->token->pos, "Trying to resolve type of struct literal."); + } + + if (!type_is_structlike_strict(sl->type)) { + // + // If there are no given arguments to a structure literal, it is treated as a 'zero-value', + // and can be used to create a completely zeroed value of any type. + if (bh_arr_length(sl->args.values) == 0 && bh_arr_length(sl->args.named_values) == 0) { + AstZeroValue *zv = make_zero_value(context.ast_alloc, sl->token, sl->type); + bh_arr_push(sl->args.values, (AstTyped *) zv); + + sl->flags |= Ast_Flag_Has_Been_Checked; + return Check_Success; + } + + if ((sl->flags & Ast_Flag_Has_Been_Checked) != 0) { + assert(sl->args.values); + assert(sl->args.values[0]); + assert(sl->args.values[0]->kind == Ast_Kind_Zero_Value); + return Check_Success; + } + + // + // Otherwise, it is not possible to construct the type if it is not a structure. + ERROR_(sl->token->pos, + "'%s' is not constructable using a struct literal.", + type_get_name(sl->type)); + } + + i32 mem_count = type_structlike_mem_count(sl->type); + arguments_ensure_length(&sl->args, mem_count); + + // :Idempotency + if ((sl->flags & Ast_Flag_Has_Been_Checked) == 0) { + char* err_msg = NULL; + if (!fill_in_arguments(&sl->args, (AstNode *) sl, &err_msg, 1)) { + onyx_report_error(sl->token->pos, Error_Critical, err_msg); + + bh_arr_each(AstTyped *, value, sl->args.values) { + if (*value == NULL) { + i32 member_idx = value - sl->args.values; // Pointer subtraction hack + StructMember smem; + type_lookup_member_by_idx(sl->type, member_idx, &smem); + + onyx_report_error(sl->token->pos, Error_Critical, + "Value not given for %d%s member, '%s', for type '%s'.", + member_idx + 1, bh_num_suffix(member_idx + 1), + smem.name, type_get_name(sl->type)); + } + } + + return Check_Error; + } + } + sl->flags |= Ast_Flag_Has_Been_Checked; + + AstTyped** actual = sl->args.values; + StructMember smem; + + // BUG: There are problems setting the comptime flag this late in the checking because + // if the struct literal was type inferred, then the literal won't be correctly determined + // to be comptime on the first pass, which is needed for top level expressions. + sl->flags |= Ast_Flag_Comptime; + + fori (i, 0, mem_count) { + // NOTE: Not checking the return on this function because + // this for loop is bounded by the number of members in the + // type. + type_lookup_member_by_idx(sl->type, i, &smem); + Type* formal = smem.type; + + CHECK(expression, actual); + if ((*actual)->type == NULL && (*actual)->entity != NULL && (*actual)->entity->state <= Entity_State_Check_Types) { + YIELD((*actual)->token->pos, "Trying to resolve type of expression for member."); + } + + TYPE_CHECK(actual, formal) { + ERROR_(sl->token->pos, + "Mismatched types for %d%s member named '%s', expected '%s', got '%s'.", + i + 1, bh_num_suffix(i + 1), + smem.name, + type_get_name(formal), + node_get_type_name(*actual)); + } + + sl->flags &= ((*actual)->flags & Ast_Flag_Comptime) | (sl->flags &~ Ast_Flag_Comptime); + actual++; + } + + return Check_Success; +} + +CheckStatus check_array_literal(AstArrayLiteral* al) { + // :Idempotency + if ((al->flags & Ast_Flag_Array_Literal_Typed) == 0) { + if (al->atnode == NULL) return Check_Success; + // YIELD(al->token->pos, "Waiting for array literal type to be known."); + + CHECK(expression, &al->atnode); + if (!node_is_type((AstNode *) al->atnode)) + ERROR(al->token->pos, "Array type is not a type."); + + al->type = type_build_from_ast(context.ast_alloc, (AstType *) al->atnode); + if (al->type == NULL) + YIELD(al->token->pos, "Trying to resolve type of array literal."); + + al->type = type_make_array(context.ast_alloc, al->type, bh_arr_length(al->values)); + if (al->type == NULL || al->type->kind != Type_Kind_Array) + ERROR(al->token->pos, "Expected array type for array literal. This is a compiler bug."); + + al->flags |= Ast_Flag_Array_Literal_Typed; + } + + if (al->type->Array.count != (u32) bh_arr_length(al->values)) { + ERROR_(al->token->pos, "Wrong array size (%d) for number of values (%d).", + al->type->Array.count, bh_arr_length(al->values)); + } + + al->flags |= Ast_Flag_Comptime; + assert(al->type->kind == Type_Kind_Array); + + Type* elem_type = al->type->Array.elem; + bh_arr_each(AstTyped *, expr, al->values) { + CHECK(expression, expr); + + // HACK HACK HACK + if ((*expr)->type == NULL && + (*expr)->entity != NULL && + (*expr)->entity->state <= Entity_State_Check_Types) { + YIELD_(al->token->pos, "Trying to resolve type of %d%s element of array literal.", expr - al->values, bh_num_suffix(expr - al->values)); + } + + al->flags &= ((*expr)->flags & Ast_Flag_Comptime) | (al->flags &~ Ast_Flag_Comptime); + + TYPE_CHECK(expr, elem_type) { + ERROR_((*expr)->token->pos, "Mismatched types for value of in array, expected '%s', got '%s'.", + type_get_name(elem_type), + node_get_type_name(*expr)); + } + } + + return Check_Success; +} + +CheckStatus check_range_literal(AstRangeLiteral** prange) { + AstRangeLiteral* range = *prange; + if (range->flags & Ast_Flag_Has_Been_Checked) return Check_Success; + + CHECK(expression, &range->low); + CHECK(expression, &range->high); + + builtin_range_type_type = type_build_from_ast(context.ast_alloc, builtin_range_type); + if (builtin_range_type_type == NULL) YIELD(range->token->pos, "Waiting for 'range' structure to be built."); + + Type* expected_range_type = builtin_range_type_type; + StructMember smem; + + type_lookup_member(expected_range_type, "low", &smem); + TYPE_CHECK(&range->low, smem.type) { + ERROR_(range->token->pos, + "Expected left side of range to be a 32-bit integer, got '%s'.", + node_get_type_name(range->low)); + } + + type_lookup_member(expected_range_type, "high", &smem); + TYPE_CHECK(&range->high, smem.type) { + ERROR_(range->token->pos, + "Expected right side of range to be a 32-bit integer, got '%s'.", + node_get_type_name(range->high)); + } + + if (range->step == NULL) { + type_lookup_member(expected_range_type, "step", &smem); + assert(smem.initial_value != NULL); + CHECK(expression, smem.initial_value); + + range->step = *smem.initial_value; + } + + range->flags |= Ast_Flag_Has_Been_Checked; + return Check_Success; +} + +CheckStatus check_compound(AstCompound* compound) { + bh_arr_each(AstTyped *, expr, compound->exprs) { + CHECK(expression, expr); + } + + compound->type = type_build_compound_type(context.ast_alloc, compound); + return Check_Success; +} + +CheckStatus check_if_expression(AstIfExpression* if_expr) { + CHECK(expression, &if_expr->cond); + CHECK(expression, &if_expr->true_expr); + CHECK(expression, &if_expr->false_expr); + + TYPE_CHECK(&if_expr->cond, &basic_types[Basic_Kind_Bool]) { + ERROR_(if_expr->token->pos, "If-expression expected boolean for condition, got '%s'.", + type_get_name(if_expr->cond->type)); + } + + resolve_expression_type((AstTyped *) if_expr); + + if (!types_are_compatible(if_expr->true_expr->type, if_expr->false_expr->type)) { + ERROR_(if_expr->token->pos, "Mismatched types for if-expression, left side is '%s', and right side is '%s'.", + type_get_name(if_expr->true_expr->type), type_get_name(if_expr->false_expr->type)); + } + + return Check_Success; +} + +CheckStatus check_do_block(AstDoBlock** pdoblock) { + AstDoBlock* doblock = *pdoblock; + if (doblock->flags & Ast_Flag_Has_Been_Checked) return Check_Success; + + fill_in_type((AstTyped *) doblock); + + Type** old_expected_return_type = expected_return_type; + expected_return_type = &doblock->type; + + doblock->block->rules = Block_Rule_Do_Block; + CHECK(block, doblock->block); + + if (doblock->type == &type_auto_return) doblock->type = &basic_types[Basic_Kind_Void]; + + expected_return_type = old_expected_return_type; + doblock->flags |= Ast_Flag_Has_Been_Checked; + return Check_Success; +} + +CheckStatus check_address_of(AstAddressOf** paof) { + AstAddressOf* aof = *paof; + + AstTyped* expr = (AstTyped *) strip_aliases((AstNode *) aof->expr); + if (expr->kind == Ast_Kind_Subscript && bh_arr_length(operator_overloads[Binary_Op_Ptr_Subscript]) > 0) { + if (aof->potential_substitute == NULL) { + CHECK(expression, &((AstSubscript *) expr)->addr); + CHECK(expression, &((AstSubscript *) expr)->expr); + + AstBinaryOp *op = onyx_ast_node_new(context.ast_alloc, sizeof(AstBinaryOp), Ast_Kind_Binary_Op); + op->operation = Binary_Op_Ptr_Subscript; + op->left = ((AstSubscript *) expr)->addr; + op->right = ((AstSubscript *) expr)->expr; + op->token = aof->token; + + aof->potential_substitute = op; + } + + AstCall* call = binaryop_try_operator_overload(aof->potential_substitute, NULL); + if (call == (AstCall *) &node_that_signals_a_yield) YIELD(aof->token->pos, "Waiting for operator overload to possibly resolve."); + if (call != NULL) { + call->next = aof->next; + *(AstCall **) paof = call; + + CHECK(call, (AstCall **) paof); + return Check_Success; + } + } + + CHECK(expression, &aof->expr); + if (node_is_addressable_literal((AstNode *) aof->expr)) { + resolve_expression_type(aof->expr); + } + + if (aof->expr->type == NULL) { + YIELD(aof->token->pos, "Trying to resolve type of expression to take a reference."); + } + + expr = (AstTyped *) strip_aliases((AstNode *) aof->expr); + if (node_is_type((AstNode *) expr)) { + AstPointerType *pt = onyx_ast_node_new(context.ast_alloc, sizeof(AstPointerType), Ast_Kind_Pointer_Type); + pt->token = aof->token; + pt->elem = (AstType *) expr; + pt->__unused = aof->next; + *paof = (AstAddressOf *) pt; + CHECK(type, (AstType **) &pt); + return Check_Success; + } + + if ((expr->kind != Ast_Kind_Subscript + && expr->kind != Ast_Kind_Dereference + && expr->kind != Ast_Kind_Field_Access + && expr->kind != Ast_Kind_Memres + && expr->kind != Ast_Kind_Local + && expr->kind != Ast_Kind_Constraint_Sentinel + && !node_is_addressable_literal((AstNode *) expr)) + || (expr->flags & Ast_Flag_Cannot_Take_Addr) != 0) { + + if (aof->can_be_removed) { + *(AstTyped **) paof = aof->expr; + return Check_Yield_Macro; + } + + ERROR_(aof->token->pos, "Cannot take the address of something that is not an l-value. %s", onyx_ast_node_kind_string(expr->kind)); + } + + expr->flags |= Ast_Flag_Address_Taken; + + aof->type = type_make_pointer(context.ast_alloc, expr->type); + + return Check_Success; +} + +CheckStatus check_dereference(AstDereference* deref) { + CHECK(expression, &deref->expr); + + if (!type_is_pointer(deref->expr->type)) + ERROR(deref->token->pos, "Cannot dereference non-pointer value."); + + if (deref->expr->type == basic_type_rawptr.basic_type) + ERROR(deref->token->pos, "Cannot dereference 'rawptr'. Cast to another pointer type first."); + + deref->type = deref->expr->type->Pointer.elem; + + return Check_Success; +} + +CheckStatus check_subscript(AstSubscript** psub) { + AstSubscript* sub = *psub; + CHECK(expression, &sub->addr); + CHECK(expression, &sub->expr); + + if (sub->addr->type == NULL) YIELD(sub->token->pos, "Waiting to know type of left-hand side of subscript."); + + // NOTE: Try operator overloading before checking everything else. + if (sub->expr->type != NULL && + (sub->addr->type->kind != Type_Kind_Basic || sub->expr->type->kind != Type_Kind_Basic)) { + // AstSubscript is the same as AstBinaryOp for the first sizeof(AstBinaryOp) bytes + AstBinaryOp* binop = (AstBinaryOp *) sub; + AstCall *implicit_call = binaryop_try_operator_overload(binop, NULL); + + if (implicit_call == (AstCall *) &node_that_signals_a_yield) + YIELD(sub->token->pos, "Trying to resolve operator overload."); + + if (implicit_call != NULL) { + // NOTE: Not an array access + implicit_call->next = sub->next; + *psub = (AstSubscript *) implicit_call; + + CHECK(call, (AstCall **) psub); + return Check_Success; + } + } + + if (!type_is_array_accessible(sub->addr->type)) { + report_bad_binaryop((AstBinaryOp *) sub); + return Check_Error; + } + + if (sub->addr->type->kind == Type_Kind_Slice || sub->addr->type->kind == Type_Kind_DynArray || sub->addr->type->kind == Type_Kind_VarArgs) { + // If we are accessing on a slice or a dynamic array, implicitly add a field access for the data member + StructMember smem; + type_lookup_member(sub->addr->type, "data", &smem); + + AstFieldAccess* fa = make_field_access(context.ast_alloc, sub->addr, "data"); + fa->type = smem.type; + fa->offset = smem.offset; + fa->idx = smem.idx; + + sub->addr = (AstTyped *) fa; + } + + if (types_are_compatible(sub->expr->type, builtin_range_type_type)) { + Type *of = type_get_contained_type(sub->addr->type); + if (of == NULL) { + // FIXME: Slice creation should be allowed for slice types and dynamic array types, like it + // is below, but this code doesn't look at that. + report_bad_binaryop((AstBinaryOp *) sub); + ERROR(sub->token->pos, "Invalid type for left of slice creation."); + } + + sub->kind = Ast_Kind_Slice; + sub->type = type_make_slice(context.ast_alloc, of); + sub->elem_size = type_size_of(of); + + return Check_Success; + } + + resolve_expression_type(sub->expr); + if (!type_is_small_integer(sub->expr->type)) { + report_bad_binaryop((AstBinaryOp *) sub); + ERROR_(sub->token->pos, "Expected small integer type for index, got '%s'.", node_get_type_name(sub->expr)); + } + + sub->type = type_get_contained_type(sub->addr->type); + if (sub->type == NULL) { + report_bad_binaryop((AstBinaryOp *) sub); + ERROR(sub->token->pos, "Invalid type for left of array access."); + } + + sub->elem_size = type_size_of(sub->type); + return Check_Success; +} + +CheckStatus check_field_access(AstFieldAccess** pfield) { + AstFieldAccess* field = *pfield; + if (field->flags & Ast_Flag_Has_Been_Checked) return Check_Success; + + CHECK(expression, &field->expr); + if (field->expr->type == NULL) { + YIELD(field->token->pos, "Trying to resolve type of source expression."); + } + + if (!type_is_structlike(field->expr->type)) { + ERROR_(field->token->pos, + "Cannot access field '%b' on '%s'. Type is not a struct.", + field->token->text, + field->token->length, + node_get_type_name(field->expr)); + } + + // Optimization for (*foo).member. + if (field->expr->kind == Ast_Kind_Dereference) { + field->expr = ((AstDereference *) field->expr)->expr; + } + + if (field->token != NULL && field->field == NULL) { + token_toggle_end(field->token); + field->field = bh_strdup(context.ast_alloc, field->token->text); + token_toggle_end(field->token); + } + + if (field->expr->type->kind == Type_Kind_Struct) { + if (field->expr->type->Struct.status != SPS_Uses_Done) { + YIELD(field->token->pos, "Waiting for struct type to be completed before looking up members."); + } + } + + StructMember smem; + if (!type_lookup_member(field->expr->type, field->field, &smem)) { + if (field->expr->type->kind == Type_Kind_Array) { + if (!strcmp(field->field, "count")) { + *pfield = (AstFieldAccess *) make_int_literal(context.ast_alloc, field->expr->type->Array.count); + return Check_Success; + } + } + + AstNode* n = try_symbol_raw_resolve_from_type(field->expr->type, field->field); + + AstType* type_node = field->expr->type->ast_type; + if (!n) n = try_symbol_raw_resolve_from_node((AstNode *) type_node, field->field); + + if (n) { + *pfield = (AstFieldAccess *) n; + return Check_Success; + } + + char* closest = find_closest_symbol_in_node((AstNode *) type_node, field->field); + if (closest) { + ERROR_(field->token->pos, "Field '%s' does not exists on '%s'. Did you mean '%s'?", field->field, node_get_type_name(field->expr), closest); + } else { + ERROR_(field->token->pos, "Field '%s' does not exists on '%s'.", field->field, node_get_type_name(field->expr)); + } + } + + // NOTE: If this member was included into the structure through a "use x: ^T" kind of statement, + // then we have to insert a intermediate field access in order to access the correct member. + if (smem.use_through_pointer_index >= 0) { + StructMember containing_member; + assert(type_lookup_member_by_idx(field->expr->type, smem.use_through_pointer_index, &containing_member)); + + AstFieldAccess *new_access = onyx_ast_node_new(context.ast_alloc, sizeof(AstFieldAccess), Ast_Kind_Field_Access); + new_access->token = field->token; + new_access->offset = containing_member.offset; + new_access->idx = containing_member.idx; + new_access->type = containing_member.type; + new_access->expr = field->expr; + new_access->flags |= Ast_Flag_Has_Been_Checked; + + field->expr = (AstTyped *) new_access; + } + + field->offset = smem.offset; + field->idx = smem.idx; + field->type = smem.type; + field->flags |= Ast_Flag_Has_Been_Checked; + return Check_Success; +} + +CheckStatus check_method_call(AstBinaryOp** pmcall) { + AstBinaryOp* mcall = *pmcall; + CHECK(expression, &mcall->left); + if (mcall->left->type == NULL) YIELD(mcall->token->pos, "Trying to resolve type of left hand side."); + + mcall->type = mcall->left->type; + AstTyped* implicit_argument = mcall->left; + + // Symbol resolution should have ensured that this is call node. + AstCall* call_node = (AstCall *) mcall->right; + assert(call_node->kind == Ast_Kind_Call); + + // :Idempotency + if ((mcall->flags & Ast_Flag_Has_Been_Checked) == 0) { + // Implicitly take the address of the value if it is not already a pointer type. + // This could be weird to think about semantically so some testing with real code + // would be good. - brendanfh 2020/02/05 + if (implicit_argument->type->kind != Type_Kind_Pointer) { + AstAddressOf *address_of = make_address_of(context.ast_alloc, implicit_argument); + address_of->can_be_removed = 1; + implicit_argument = (AstTyped *) address_of; + } + + implicit_argument = (AstTyped *) make_argument(context.ast_alloc, implicit_argument); + + bh_arr_insertn(call_node->args.values, 0, 1); + call_node->args.values[0] = implicit_argument; + + *pmcall = (AstBinaryOp *) mcall->right; + mcall->right->next = mcall->next; + } + mcall->flags |= Ast_Flag_Has_Been_Checked; + + CHECK(call, (AstCall **) pmcall); + return Check_Success; +} + +CheckStatus check_size_of(AstSizeOf* so) { + CHECK(type, &so->so_ast_type); + + so->so_type = type_build_from_ast(context.ast_alloc, so->so_ast_type); + if (so->so_type == NULL) + YIELD(so->token->pos, "Trying to resolve type to take the size of."); + + so->size = type_size_of(so->so_type); + so->flags |= Ast_Flag_Comptime; + + return Check_Success; +} + +CheckStatus check_align_of(AstAlignOf* ao) { + CHECK(type, &ao->ao_ast_type); + + ao->ao_type = type_build_from_ast(context.ast_alloc, ao->ao_ast_type); + if (ao->ao_type == NULL) + YIELD(ao->token->pos, "Trying to resolve type to take the alignment of."); + + ao->alignment = type_alignment_of(ao->ao_type); + ao->flags |= Ast_Flag_Comptime; + + return Check_Success; +} + +CheckStatus check_expression(AstTyped** pexpr) { + AstTyped* expr = *pexpr; + if (expr->kind > Ast_Kind_Type_Start && expr->kind < Ast_Kind_Type_End) { + // This is to ensure that the type will exist when compiling. For example, a poly-call type + // would have to wait for the entity to pass through, which the code generation does not know + // about. + CHECK(type, (AstType **) pexpr); + expr = *pexpr; + + // Don't try to construct a polystruct ahead of time because you can't. + if (expr->kind != Ast_Kind_Poly_Struct_Type) { + if (type_build_from_ast(context.ast_alloc, (AstType*) expr) == NULL) { + YIELD(expr->token->pos, "Trying to construct type."); + } + } else { + type_build_from_ast(context.ast_alloc, (AstType*) expr); + } + + expr->type = &basic_types[Basic_Kind_Type_Index]; + return Check_Success; + } + + if (expr->kind == Ast_Kind_Polymorphic_Proc) { + // polymorphic procedures do not need to be checked. Their concrete instantiations + // will be checked when they are created. + return Check_Success; + } + + if (expr->kind == Ast_Kind_Macro) { + return Check_Success; + } + + if (expr->kind == Ast_Kind_Directive_Init) { + ERROR(expr->token->pos, "#init declarations are not in normal expressions, only in #after clauses."); + } + + fill_in_type(expr); + current_checking_level = EXPRESSION_LEVEL; + + CheckStatus retval = Check_Success; + switch (expr->kind) { + case Ast_Kind_Binary_Op: retval = check_binaryop((AstBinaryOp **) pexpr); break; + case Ast_Kind_Unary_Op: retval = check_unaryop((AstUnaryOp **) pexpr); break; + + case Ast_Kind_Intrinsic_Call: + case Ast_Kind_Call: retval = check_call((AstCall **) pexpr); break; + case Ast_Kind_Argument: retval = check_argument((AstArgument **) pexpr); break; + case Ast_Kind_Block: retval = check_block((AstBlock *) expr); break; + + case Ast_Kind_Symbol: + YIELD_(expr->token->pos, "Waiting to resolve symbol, '%b'.", expr->token->text, expr->token->length); + break; + + case Ast_Kind_Param: + if (expr->type == NULL) { + YIELD(expr->token->pos, "Waiting on parameter type."); + } + break; + + case Ast_Kind_Local: break; + + case Ast_Kind_Address_Of: retval = check_address_of((AstAddressOf **) pexpr); break; + case Ast_Kind_Dereference: retval = check_dereference((AstDereference *) expr); break; + case Ast_Kind_Slice: + case Ast_Kind_Subscript: retval = check_subscript((AstSubscript **) pexpr); break; + case Ast_Kind_Field_Access: retval = check_field_access((AstFieldAccess **) pexpr); break; + case Ast_Kind_Method_Call: retval = check_method_call((AstBinaryOp **) pexpr); break; + case Ast_Kind_Size_Of: retval = check_size_of((AstSizeOf *) expr); break; + case Ast_Kind_Align_Of: retval = check_align_of((AstAlignOf *) expr); break; + case Ast_Kind_Range_Literal: retval = check_range_literal((AstRangeLiteral **) pexpr); break; + + case Ast_Kind_Global: + if (expr->type == NULL) { + onyx_report_error(expr->token->pos, Error_Critical, "Global with unknown type."); + retval = Check_Error; + } + break; + + case Ast_Kind_NumLit: + assert(expr->type != NULL); + break; + + case Ast_Kind_Struct_Literal: + retval = check_struct_literal((AstStructLiteral *) expr); + break; + + case Ast_Kind_Array_Literal: + retval = check_array_literal((AstArrayLiteral *) expr); + break; + + case Ast_Kind_Function: + // NOTE: Will need something like this at some point + // AstFunction* func = (AstFunction *) expr; + // bh_arr_each(AstParam, param, func->params) { + // if (param->default_value != NULL) { + // onyx_message_add(Msg_Type_Literal, + // func->token->pos, + // "cannot use functions with default parameters in this way"); + // retval = 1; + // break; + // } + // } + if (expr->type == NULL) + YIELD(expr->token->pos, "Waiting for function type to be resolved."); + + expr->flags |= Ast_Flag_Function_Used; + break; + + case Ast_Kind_Directive_Solidify: + CHECK(directive_solidify, (AstDirectiveSolidify **) pexpr); + break; + + case Ast_Kind_Directive_Defined: + *pexpr = (AstTyped *) make_bool_literal(context.ast_alloc, ((AstDirectiveDefined *) expr)->is_defined); + fill_in_type(*pexpr); + break; + + case Ast_Kind_Compound: + CHECK(compound, (AstCompound *) expr); + break; + + case Ast_Kind_Call_Site: + // NOTE: This has to be set here because if it were to be set in the parser, + // builtin_callsite_type wouldn't be known when parsing the builtin.onyx file. + expr->type_node = builtin_callsite_type; + break; + + case Ast_Kind_If_Expression: + CHECK(if_expression, (AstIfExpression *) expr); + break; + + case Ast_Kind_Alias: + CHECK(expression, &((AstAlias *) expr)->alias); + expr->flags |= (((AstAlias *) expr)->alias->flags & Ast_Flag_Comptime); + expr->type = ((AstAlias *) expr)->alias->type; + break; + + case Ast_Kind_Directive_Insert: + retval = check_insert_directive((AstDirectiveInsert **) pexpr); + break; + + case Ast_Kind_Code_Block: + expr->flags |= Ast_Flag_Comptime; + fill_in_type(expr); + break; + + case Ast_Kind_Do_Block: + retval = check_do_block((AstDoBlock **) pexpr); + break; + + case Ast_Kind_Memres: + if (expr->type == NULL) YIELD(expr->token->pos, "Waiting to know globals type."); + break; + + case Ast_Kind_StrLit: break; + case Ast_Kind_File_Contents: break; + case Ast_Kind_Overloaded_Function: break; + case Ast_Kind_Enum_Value: break; + case Ast_Kind_Polymorphic_Proc: break; + case Ast_Kind_Package: break; + case Ast_Kind_Error: break; + case Ast_Kind_Unary_Field_Access: break; + case Ast_Kind_Constraint_Sentinel: break; + case Ast_Kind_Switch_Case: break; + case Ast_Kind_Foreign_Block: break; + case Ast_Kind_Zero_Value: break; + + default: + retval = Check_Error; + onyx_report_error(expr->token->pos, Error_Critical, "UNEXPECTED INTERNAL COMPILER ERROR"); + DEBUG_HERE; + break; + } + + return retval; +} + +CheckStatus check_global(AstGlobal* global) { + fill_in_type((AstTyped *) global); + + if (global->type == NULL) { + YIELD(global->token->pos, "Trying to resolve type for global."); + } + + return Check_Success; +} + +CheckStatus check_insert_directive(AstDirectiveInsert** pinsert) { + AstDirectiveInsert* insert = *pinsert; + if (insert->flags & Ast_Flag_Has_Been_Checked) return Check_Success; + + CHECK(expression, &insert->code_expr); + if (insert->code_expr->type == NULL) { + if (insert->code_expr->entity && insert->code_expr->entity->state >= Entity_State_Code_Gen) { + ERROR(insert->token->pos, "Expected expression of type 'Code'."); + } + + // Bad wording for the message. + YIELD(insert->token->pos, "Waiting for resolution to code expression type."); + } + + Type* code_type = type_build_from_ast(context.ast_alloc, builtin_code_type); + + TYPE_CHECK(&insert->code_expr, code_type) { + ERROR_(insert->token->pos, "#unquote expected a value of type 'Code', got '%s'.", + type_get_name(insert->code_expr->type)); + } + + AstCodeBlock* code_block = (AstCodeBlock *) insert->code_expr; + code_block = (AstCodeBlock *) strip_aliases((AstNode *) code_block); + + assert(code_block->kind == Ast_Kind_Code_Block); + + AstNode* cloned_block = ast_clone(context.ast_alloc, code_block->code); + cloned_block->next = insert->next; + *(AstNode **) pinsert = cloned_block; + + insert->flags |= Ast_Flag_Has_Been_Checked; + + return Check_Return_To_Symres; +} + +CheckStatus check_directive_solidify(AstDirectiveSolidify** psolid) { + AstDirectiveSolidify* solid = *psolid; + + bh_arr_each(AstPolySolution, sln, solid->known_polyvars) { + CHECK(expression, &sln->value); + + if (node_is_type((AstNode *) sln->value)) { + sln->type = type_build_from_ast(context.ast_alloc, sln->ast_type); + sln->kind = PSK_Type; + } else { + sln->kind = PSK_Value; + } + } + + solid->resolved_proc = polymorphic_proc_try_solidify(solid->poly_proc, solid->known_polyvars, solid->token); + if (solid->resolved_proc == (AstNode *) &node_that_signals_a_yield) { + solid->resolved_proc = NULL; + YIELD(solid->token->pos, "Waiting for partially solidified procedure."); + } + + // NOTE: Not a DirectiveSolidify. + *psolid = (AstDirectiveSolidify *) solid->resolved_proc; + + return Check_Success; +} + +CheckStatus check_remove_directive(AstDirectiveRemove *remove) { + if (!inside_for_iterator) { + ERROR(remove->token->pos, "#remove is only allowed in the body of a for-loop over an iterator."); + } + + return Check_Success; +} + +CheckStatus check_statement(AstNode** pstmt) { + AstNode* stmt = *pstmt; + + current_checking_level = STATEMENT_LEVEL; + + switch (stmt->kind) { + case Ast_Kind_Jump: return Check_Success; + + case Ast_Kind_Return: return check_return((AstReturn *) stmt); + case Ast_Kind_If: return check_if((AstIfWhile *) stmt); + case Ast_Kind_Static_If: return check_if((AstIfWhile *) stmt); + case Ast_Kind_While: return check_while((AstIfWhile *) stmt); + case Ast_Kind_For: return check_for((AstFor *) stmt); + case Ast_Kind_Switch: return check_switch((AstSwitch *) stmt); + case Ast_Kind_Block: return check_block((AstBlock *) stmt); + case Ast_Kind_Defer: return check_statement(&((AstDefer *) stmt)->stmt); + case Ast_Kind_Directive_Remove: return check_remove_directive((AstDirectiveRemove *) stmt); + case Ast_Kind_Call: { + CHECK(call, (AstCall **) pstmt); + (*pstmt)->flags |= Ast_Flag_Expr_Ignored; + return Check_Success; + } + + case Ast_Kind_Binary_Op: + CHECK(binaryop, (AstBinaryOp **) pstmt); + (*pstmt)->flags |= Ast_Flag_Expr_Ignored; + return Check_Success; + + // NOTE: Local variable declarations used to be removed after the symbol + // resolution phase because long long ago, all locals needed to be known + // in a block in order to efficiently allocate enough space and registers + // for them all. Now with LocalAllocator, this is no longer necessary. + // Therefore, locals stay in the tree and need to be passed along. + case Ast_Kind_Local: { + AstTyped* typed_stmt = (AstTyped *) stmt; + fill_in_type(typed_stmt); + if (typed_stmt->type_node != NULL && typed_stmt->type == NULL) { + CHECK(type, &typed_stmt->type_node); + + if (!node_is_type((AstNode *) typed_stmt->type_node)) { + ERROR(stmt->token->pos, "Local's type is not a type."); + } + + YIELD(typed_stmt->token->pos, "Waiting for local variable's type."); + } + + if (typed_stmt->next != NULL && typed_stmt->next->kind == Ast_Kind_Binary_Op) { + AstBinaryOp *next = (AstBinaryOp *) typed_stmt->next; + if (next->operation == Binary_Op_Assign && next->left == typed_stmt) { + typed_stmt->flags |= Ast_Flag_Decl_Followed_By_Init; + } + } + return Check_Success; + } + + default: + CHECK(expression, (AstTyped **) pstmt); + (*pstmt)->flags |= Ast_Flag_Expr_Ignored; + return Check_Success; + } +} + +CheckStatus check_statement_chain(AstNode** start) { + while (*start) { + CHECK(statement, start); + start = &(*start)->next; + } + + return Check_Success; +} + +CheckStatus check_block(AstBlock* block) { + // This used to use statement_chain, but since block optimize which statements need to be rechecked, + // it has to be its own thing. + + AstNode** start = &block->body; + fori (i, 0, block->statement_idx) { + start = &(*start)->next; + } + + while (*start) { + CheckStatus cs = check_statement(start); + switch (cs) { + case Check_Success: + start = &(*start)->next; + block->statement_idx++; + break; + + case Check_Return_To_Symres: + block->statement_idx = 0; + + default: + return cs; + } + + } + + return Check_Success; +} + +CheckStatus check_function(AstFunction* func) { + if (func->flags & Ast_Flag_Has_Been_Checked) return Check_Success; + if (func->entity_header && func->entity_header->state < Entity_State_Code_Gen) + YIELD(func->token->pos, "Waiting for procedure header to pass type-checking"); + + bh_arr_each(AstTyped *, pexpr, func->tags) { + CHECK(expression, pexpr); + + if (((*pexpr)->flags & Ast_Flag_Comptime) == 0) { + ERROR((*pexpr)->token->pos, "#tag expressions should be compile time known."); + } + } + + inside_for_iterator = 0; + expected_return_type = &func->type->Function.return_type; + if (func->body) { + CheckStatus status = check_block(func->body); + if (status == Check_Error && func->generated_from && context.cycle_detected == 0) + ERROR(func->generated_from->pos, "Error in polymorphic procedure generated from this location."); + + if (status != Check_Success) { + expected_return_type = NULL; + return status; + } + } + + if (*expected_return_type == &type_auto_return) { + *expected_return_type = &basic_types[Basic_Kind_Void]; + } + + func->flags |= Ast_Flag_Has_Been_Checked; + return Check_Success; +} + +CheckStatus check_overloaded_function(AstOverloadedFunction* func) { + b32 done = 1; + + bh_imap all_overloads; + bh_imap_init(&all_overloads, global_heap_allocator, 4); + build_all_overload_options(func->overloads, &all_overloads); + + bh_arr_each(bh__imap_entry, entry, all_overloads.entries) { + AstTyped* node = (AstTyped *) entry->key; + if (node->kind == Ast_Kind_Overloaded_Function) continue; + + if ( node->kind != Ast_Kind_Function + && node->kind != Ast_Kind_Polymorphic_Proc + && node->kind != Ast_Kind_Macro) { + onyx_report_error(node->token->pos, Error_Critical, "Overload option not procedure or macro. Got '%s'", + onyx_ast_node_kind_string(node->kind)); + + bh_imap_free(&all_overloads); + return Check_Error; + } + + if (node->kind == Ast_Kind_Function) { + AstFunction* func = (AstFunction *) node; + + if (func->entity_header && func->entity_header->state <= Entity_State_Check_Types) { + done = 0; + } + } + } + + bh_imap_free(&all_overloads); + + if (done) return Check_Success; + else YIELD(func->token->pos, "Waiting for all options to pass type-checking."); +} + +CheckStatus check_struct(AstStructType* s_node) { + if (s_node->entity_defaults && s_node->entity_defaults->state < Entity_State_Check_Types) + YIELD(s_node->token->pos, "Waiting for struct member defaults to pass symbol resolution."); + + if (s_node->min_size_) CHECK(expression, &s_node->min_size_); + if (s_node->min_alignment_) CHECK(expression, &s_node->min_alignment_); + + if (s_node->polymorphic_argument_types) { + assert(s_node->polymorphic_arguments); + + fori (i, 0, (i64) bh_arr_length(s_node->polymorphic_argument_types)) { + Type *arg_type = type_build_from_ast(context.ast_alloc, s_node->polymorphic_argument_types[i]); + if (arg_type == NULL) YIELD(s_node->polymorphic_argument_types[i]->token->pos, "Waiting to build type for polymorph argument."); + + // CLEANUP: This might be wrong... + if (s_node->polymorphic_arguments[i].value) { + TYPE_CHECK(&s_node->polymorphic_arguments[i].value, arg_type) { + ERROR_(s_node->polymorphic_arguments[i].value->token->pos, "Expected value of type %s, got %s.", + type_get_name(arg_type), + type_get_name(s_node->polymorphic_arguments[i].value->type)); + } + } + } + } + + if (s_node->constraints.constraints) { + s_node->constraints.produce_errors = (s_node->flags & Ast_Flag_Header_Check_No_Error) == 0; + + OnyxFilePos pos = s_node->token->pos; + if (s_node->polymorphic_error_loc.filename) { + pos = s_node->polymorphic_error_loc; + } + CHECK(constraint_context, &s_node->constraints, s_node->scope, pos); + } + + // + // This ensures that all procedures defined inside of a structure are + // not pruned and omitted from the binary. This is because a large + // use-case of procedures in structures is dynamically linking them + // using the type info data. + if (s_node->scope) { + fori (i, 0, shlen(s_node->scope->symbols)) { + AstNode* node = s_node->scope->symbols[i].value; + if (node->kind == Ast_Kind_Function) { + node->flags |= Ast_Flag_Function_Used; + } + } + } + + bh_arr_each(AstStructMember *, smem, s_node->members) { + if ((*smem)->type_node != NULL) { + CHECK(type, &(*smem)->type_node); + } + + if ((*smem)->type_node == NULL && (*smem)->initial_value != NULL) { + CHECK(expression, &(*smem)->initial_value); + + fill_in_type((*smem)->initial_value); + if ((*smem)->initial_value->type == NULL) + YIELD((*smem)->initial_value->token->pos, "Trying to resolve type for initial value for member."); + + resolve_expression_type((*smem)->initial_value); + if ((*smem)->type == NULL) (*smem)->type = (*smem)->initial_value->type; + + if ((*smem)->type == NULL) { + ERROR((*smem)->initial_value->token->pos, "Unable to deduce type of initial value. This is probably a compiler bug."); + } + } + } + + // NOTE: fills in the pending_type. + s_node->ready_to_build_type = 1; + type_build_from_ast(context.ast_alloc, (AstType *) s_node); + if (s_node->pending_type == NULL || !s_node->pending_type_is_valid) + YIELD(s_node->token->pos, "Waiting for type to be constructed."); + + bh_arr_each(StructMember *, smem, s_node->pending_type->Struct.memarr) { + if ((*smem)->type->kind == Type_Kind_Compound) { + ERROR(s_node->token->pos, "Compound types are not allowed as struct member types."); + } + + if ((*smem)->used) { + if (!type_struct_member_apply_use(context.ast_alloc, s_node->pending_type, *smem)) { + YIELD((*smem)->token->pos, "Waiting for use to be applied."); + } + } + } + + s_node->stcache = s_node->pending_type; + s_node->stcache->Struct.status = SPS_Uses_Done; + + return Check_Success; +} + +CheckStatus check_struct_defaults(AstStructType* s_node) { + if (s_node->entity_type && s_node->entity_type->state < Entity_State_Code_Gen) + YIELD(s_node->token->pos, "Waiting for struct type to be constructed before checking defaulted members."); + if (s_node->entity_type && s_node->entity_type->state == Entity_State_Failed) + return Check_Failed; + + if (s_node->meta_tags) { + bh_arr_each(AstTyped *, meta, s_node->meta_tags) { + CHECK(expression, meta); + resolve_expression_type(*meta); + + if (((*meta)->flags & Ast_Flag_Comptime) == 0) { + onyx_report_error((*meta)->token->pos, Error_Critical, "#tag expressions are expected to be compile-time known."); + return Check_Error; + } + } + } + + bh_arr_each(StructMember *, smem, s_node->stcache->Struct.memarr) { + if ((*smem)->initial_value && *(*smem)->initial_value) { + CHECK(expression, (*smem)->initial_value); + + TYPE_CHECK((*smem)->initial_value, (*smem)->type) { + ERROR_((*(*smem)->initial_value)->token->pos, + "Mismatched type for initial value, expected '%s', got '%s'.", + type_get_name((*smem)->type), + type_get_name((*(*smem)->initial_value)->type)); + } + + resolve_expression_type(*(*smem)->initial_value); + } + + if ((*smem)->meta_tags) { + bh_arr_each(AstTyped *, meta, (*smem)->meta_tags) { + CHECK(expression, meta); + resolve_expression_type(*meta); + + if (((*meta)->flags & Ast_Flag_Comptime) == 0) { + onyx_report_error((*meta)->token->pos, Error_Critical, "#tag expressions are expected to be compile-time known."); + return Check_Error; + } + } + } + } + + return Check_Success; +} + +CheckStatus check_temp_function_header(AstFunction* func) { + CheckStatus cs = check_function_header(func); + if (cs == Check_Error) { + if (func->flags & Ast_Flag_Header_Check_No_Error) { + onyx_clear_errors(); + } + + return Check_Failed; + } + + if (cs != Check_Success) return cs; + + return Check_Complete; +} + +CheckStatus check_function_header(AstFunction* func) { + //if (func->entity_body && func->entity_body->state < Entity_State_Check_Types) + // YIELD(func->token->pos, "Waiting for function body to complete symbol resolution to check header."); + + b32 expect_default_param = 0; + b32 has_had_varargs = 0; + + if (func->constraints.constraints != NULL && func->constraints.constraints_met == 0) { + func->constraints.produce_errors = (func->flags & Ast_Flag_Header_Check_No_Error) == 0; + CHECK(constraint_context, &func->constraints, func->scope, func->token->pos); + + // All constraints have been met. Return to symbol resolution to finish + // looking up all symbols in the function. + return Check_Return_To_Symres; + } + + bh_arr_each(AstParam, param, func->params) { + AstLocal* local = param->local; + + if (expect_default_param && param->default_value == NULL) { + ERROR(local->token->pos, + "All parameters must have default values after the first default valued parameter."); + } + + if (has_had_varargs && param->vararg_kind != VA_Kind_Not_VA) { + ERROR(local->token->pos, + "Can only have one param that is of variable argument type."); + } + + if (has_had_varargs && param->vararg_kind != VA_Kind_Not_VA) { + ERROR(local->token->pos, + "Variable arguments must be last in parameter list"); + } + + if (param->vararg_kind == VA_Kind_Untyped) { + // HACK + if (builtin_vararg_type_type == NULL) + builtin_vararg_type_type = type_build_from_ast(context.ast_alloc, builtin_vararg_type); + + local->type = builtin_vararg_type_type; + } + + if (param->default_value != NULL) { + if (param->vararg_kind != VA_Kind_Not_VA) { + ERROR(local->token->pos, "Variadic arguments cannot have default values."); + } + + CHECK(expression, ¶m->default_value); + + if (local->type_node == NULL && local->type == NULL) { + local->type = resolve_expression_type(param->default_value); + } + + expect_default_param = 1; + } + + if (local->type_node != NULL) { + // If the function has the no_error flag, then the type node should have it set too. + // This allows for polymorphic structures with constraints to fail gracefully. + local->type_node->flags |= (func->flags & Ast_Flag_Header_Check_No_Error); + CHECK(type, &local->type_node); + } + + fill_in_type((AstTyped *) local); + if (local->type == NULL) { + YIELD(local->token->pos, "Waiting for parameter type to be known."); + } + + if (local->type == (Type *) &node_that_signals_failure) { + return Check_Failed; + } + + if (local->type->kind == Type_Kind_Compound) { + ERROR(param->local->token->pos, "Compound types are not allowed as parameter types. Try splitting this into multiple parameters."); + } + + // NOTE: I decided to make parameter default values not type checked against + // the actual parameter type. The actual type checking will happen in check_call + // when the default value is used as an argument and then has to be checked against + // the parameter type - brendanfh 2021/01/06 + // if (param->default_value != NULL) { + // if (!unify_node_and_type(¶m->default_value, param->local->type)) { + // onyx_report_error(param->local->token->pos, + // "Expected default value of type '%s', was of type '%s'.", + // type_get_name(param->local->type), + // type_get_name(param->default_value->type)); + // return Check_Error; + // } + // } + + if (param->vararg_kind != VA_Kind_Not_VA) has_had_varargs = 1; + + if (local->type->kind != Type_Kind_Array && type_size_of(local->type) == 0) { + ERROR(local->token->pos, "Function parameters cannot have zero-width types."); + } + } + + if (func->return_type != NULL) CHECK(type, &func->return_type); + + func->type = type_build_function_type(context.ast_alloc, func); + if (func->type == NULL) YIELD(func->token->pos, "Waiting for function type to be constructed"); + + return Check_Success; +} + +CheckStatus check_memres_type(AstMemRes* memres) { + CHECK(type, &memres->type_node); + fill_in_type((AstTyped *) memres); + if (memres->type_node && !memres->type) YIELD(memres->token->pos, "Waiting for global type to be constructed."); + return Check_Success; +} + +CheckStatus check_memres(AstMemRes* memres) { + assert(memres->type_entity); + if (memres->type_entity->state < Entity_State_Code_Gen) YIELD(memres->token->pos, "Waiting for global to pass type construction."); + + if (memres->initial_value != NULL) { + if (memres->threadlocal) { + onyx_report_error(memres->token->pos, Error_Critical, "'#thread_local' variables cannot have an initializer at the moment."); + return Check_Error; + } + + CHECK(expression, &memres->initial_value); + + if (memres->type != NULL) { + Type* memres_type = memres->type; + TYPE_CHECK(&memres->initial_value, memres_type) { + ERROR_(memres->token->pos, + "Cannot assign value of type '%s' to a '%s'.", + node_get_type_name(memres->initial_value), + type_get_name(memres_type)); + } + + } else { + resolve_expression_type(memres->initial_value); + if (memres->initial_value->type == NULL && memres->initial_value->entity != NULL && memres->initial_value->entity->state <= Entity_State_Check_Types) { + YIELD(memres->token->pos, "Waiting for global type to be constructed."); + } + memres->type = memres->initial_value->type; + } + + if ((memres->initial_value->flags & Ast_Flag_Comptime) == 0) { + if (memres->initial_value->entity != NULL && memres->initial_value->entity->state <= Entity_State_Check_Types) { + YIELD(memres->token->pos, "Waiting for initial value to be checked."); + } + + ERROR(memres->initial_value->token->pos, "Top level expressions must be compile time known."); + } + } + + return Check_Success; +} + +CheckStatus check_type(AstType** ptype) { + if (ptype == NULL || *ptype == NULL) return Check_Success; + + AstType* type = *ptype; + AstType* original_type = type; + while (type->kind == Ast_Kind_Type_Alias) + type = ((AstTypeAlias *) type)->to; + + if (type->flags & Ast_Flag_Has_Been_Checked) return Check_Success; + + switch (type->kind) { + case Ast_Kind_Poly_Call_Type: { + AstPolyCallType* pc_node = (AstPolyCallType *) type; + + bh_arr_each(AstNode *, param, pc_node->params) { + if (!node_is_type(*param)) { + CHECK(expression, (AstTyped **) param); + resolve_expression_type((AstTyped *) *param); + fill_in_type((AstTyped *) *param); + } + } + + break; + } + + case Ast_Kind_Typeof: { + AstTypeOf *type_of = (AstTypeOf *) type; + CHECK(expression, (AstTyped **) &type_of->expr); + resolve_expression_type(type_of->expr); + + if (type_of->expr->type == NULL) { + YIELD(type_of->token->pos, "Trying to check type for type-of expression."); + } + + type_of->resolved_type = type_of->expr->type; + break; + } + + case Ast_Kind_Pointer_Type: ((AstPointerType *) type)->elem->flags |= type->flags & Ast_Flag_Header_Check_No_Error; CHECK(type, &((AstPointerType *) type)->elem); break; + case Ast_Kind_Slice_Type: ((AstSliceType *) type)->elem->flags |= type->flags & Ast_Flag_Header_Check_No_Error; CHECK(type, &((AstSliceType *) type)->elem); break; + case Ast_Kind_DynArr_Type: ((AstDynArrType *) type)->elem->flags |= type->flags & Ast_Flag_Header_Check_No_Error; CHECK(type, &((AstDynArrType *) type)->elem); break; + case Ast_Kind_VarArg_Type: ((AstVarArgType *) type)->elem->flags |= type->flags & Ast_Flag_Header_Check_No_Error; CHECK(type, &((AstVarArgType *) type)->elem); break; + + case Ast_Kind_Function_Type: { + AstFunctionType* ftype = (AstFunctionType *) type; + + CHECK(type, &ftype->return_type); + + if (ftype->param_count > 0) { + fori (i, 0, (i64) ftype->param_count) { + CHECK(type, &ftype->params[i]); + } + } + break; + } + + case Ast_Kind_Type_Compound: { + AstCompoundType* ctype = (AstCompoundType *) type; + + bh_arr_each(AstType *, type, ctype->types) CHECK(type, type); + break; + } + + case Ast_Kind_Array_Type: { + AstArrayType* atype = (AstArrayType *) type; + if (atype->count_expr) { + CHECK(expression, &atype->count_expr); + resolve_expression_type(atype->count_expr); + } + + break; + } + + case Ast_Kind_Field_Access: { + CHECK(field_access, (AstFieldAccess **) ptype); + type = *ptype; + original_type = type; + + if (!node_is_type((AstNode *) type)) { + ERROR_(original_type->token->pos, "This field access did not resolve to be a type. It resolved to be a '%s'.", onyx_ast_node_kind_string(type->kind)); + } + break; + } + } + + type = original_type; + type->flags |= Ast_Flag_Comptime; + while (type->kind == Ast_Kind_Type_Alias) { + type->flags |= Ast_Flag_Comptime; + type = ((AstTypeAlias *) type)->to; + } + + type->flags |= Ast_Flag_Has_Been_Checked; + return Check_Success; +} + +CheckStatus check_static_if(AstIf* static_if) { + expression_types_must_be_known = 1; + CheckStatus result = check_expression(&static_if->cond); + expression_types_must_be_known = 0; + if (result == Check_Yield_Macro) return Check_Yield_Macro; + + if (result > Check_Errors_Start || !(static_if->cond->flags & Ast_Flag_Comptime)) { + ERROR(static_if->token->pos, "Expected this condition to be compile time known."); + } + + if (!type_is_bool(static_if->cond->type)) { + ERROR(static_if->token->pos, "Expected this condition to be a boolean value."); + } + + static_if->flags |= Ast_Flag_Static_If_Resolved; + + b32 resolution = static_if_resolution(static_if); + + if (context.options->print_static_if_results) + bh_printf("Static if statement at %s:%d:%d resulted in %s\n", + static_if->token->pos.filename, + static_if->token->pos.line, + static_if->token->pos.column, + resolution ? "true" : "false"); + + if (resolution) { + bh_arr_each(Entity *, ent, static_if->true_entities) { + entity_heap_insert_existing(&context.entities, *ent); + } + + } else { + bh_arr_each(Entity *, ent, static_if->false_entities) { + entity_heap_insert_existing(&context.entities, *ent); + } + } + + return Check_Complete; +} + +CheckStatus check_process_directive(AstNode* directive) { + if (directive->kind == Ast_Kind_Directive_Export) { + AstDirectiveExport *export = (AstDirectiveExport *) directive; + AstTyped *exported = export->export; + if (exported->entity && exported->entity->state <= Entity_State_Check_Types) + YIELD(directive->token->pos, "Waiting for exported type to be known."); + + CHECK(expression, &export->export_name_expr); + + if (export->export_name_expr->kind != Ast_Kind_StrLit) { + ERROR_(export->token->pos, "Expected export name to be a string literal, got '%s'.", onyx_ast_node_kind_string(export->export_name_expr->kind)); + } + + export->export_name = export->export_name_expr->token; + } + + if (directive->kind == Ast_Kind_Directive_Init) { + AstDirectiveInit *init = (AstDirectiveInit *) directive; + if ((init->flags & Ast_Flag_Has_Been_Checked) == 0) { + CHECK(expression, &init->init_proc); + + if (init->init_proc->kind != Ast_Kind_Function) { + ERROR_(init->token->pos, "#init only works for functions, got '%s'", onyx_ast_node_kind_string(init->init_proc->kind)); + } + + assert(init->init_proc->type); + if (init->init_proc->type->Function.param_count != 0) { + ERROR(init->token->pos, "#init expects a function that takes 0 arguments."); + } + } + + init->flags |= Ast_Flag_Has_Been_Checked; + + if (init->dependencies) { + i32 i = 0; + bh_arr_each(AstDirectiveInit *, dependency, init->dependencies) { + AstTyped *d = (AstTyped *) strip_aliases((AstNode *) *dependency); + if (d->kind != Ast_Kind_Directive_Init) { + ERROR_(init->token->pos, "All dependencies of an #init must be another #init. The %d%s dependency was not.", i + 1, bh_num_suffix(i + 1)); + } + + assert(d->entity); + if (d->entity->state != Entity_State_Finalized) { + YIELD(init->token->pos, "Circular dependency in #init nodes. Here are the nodes involved."); + } + + i++; + } + } + + bh_arr_push(init_procedures, (AstFunction *) init->init_proc); + return Check_Complete; + } + + if (directive->kind == Ast_Kind_Directive_Library) { + AstDirectiveLibrary *library = (AstDirectiveLibrary *) directive; + + if (library->library_symbol->kind != Ast_Kind_StrLit) { + ERROR_(library->token->pos, "#library directive expected compile-time known string for library name. Got '%s'.", + onyx_ast_node_kind_string(library->library_symbol->kind)); + } + + AstStrLit *symbol = (AstStrLit *) library->library_symbol; + char* temp_name = bh_alloc_array(global_scratch_allocator, char, symbol->token->length); + i32 temp_name_len = string_process_escape_seqs(temp_name, symbol->token->text, symbol->token->length); + library->library_name = bh_strdup(global_heap_allocator, temp_name); + return Check_Success; + } + + return Check_Success; +} + +CheckStatus check_macro(AstMacro* macro) { + if (macro->body->kind == Ast_Kind_Function) { + CHECK(function_header, (AstFunction *) macro->body); + } + + return Check_Success; +} + +CheckStatus check_constraint(AstConstraint *constraint) { + switch (constraint->phase) { + case Constraint_Phase_Cloning_Expressions: { + if (constraint->interface->kind == Ast_Kind_Symbol) { + return Check_Return_To_Symres; + } + + if (constraint->interface->kind != Ast_Kind_Interface) { + // CLEANUP: This error message might not look totally right in some cases. + ERROR_(constraint->token->pos, "'%b' is not an interface. It is a '%s'.", + constraint->token->text, constraint->token->length, + onyx_ast_node_kind_string(constraint->interface->kind)); + } + + bh_arr_new(global_heap_allocator, constraint->exprs, bh_arr_length(constraint->interface->exprs)); + bh_arr_each(InterfaceConstraint, ic, constraint->interface->exprs) { + InterfaceConstraint new_ic = {0}; + new_ic.expr = (AstTyped *) ast_clone(context.ast_alloc, (AstNode *) ic->expr); + new_ic.expected_type_expr = (AstType *) ast_clone(context.ast_alloc, (AstNode *) ic->expected_type_expr); + new_ic.invert_condition = ic->invert_condition; + bh_arr_push(constraint->exprs, new_ic); + } + + assert(constraint->interface->entity && constraint->interface->entity->scope); + + constraint->scope = scope_create(context.ast_alloc, constraint->interface->entity->scope, constraint->token->pos); + + fori (i, 0, bh_arr_length(constraint->interface->params)) { + InterfaceParam *ip = &constraint->interface->params[i]; + + AstTyped *sentinel = onyx_ast_node_new(context.ast_alloc, sizeof(AstTyped), Ast_Kind_Constraint_Sentinel); + sentinel->token = ip->value_token; + sentinel->type_node = constraint->type_args[i]; + + AstAlias *type_alias = onyx_ast_node_new(context.ast_alloc, sizeof(AstAlias), Ast_Kind_Alias); + type_alias->token = ip->type_token; + type_alias->alias = (AstTyped *) constraint->type_args[i]; + + symbol_introduce(constraint->scope, ip->value_token, (AstNode *) sentinel); + symbol_introduce(constraint->scope, ip->type_token, (AstNode *) type_alias); + } + + assert(constraint->entity); + constraint->entity->scope = constraint->scope; + + constraint->phase = Constraint_Phase_Checking_Expressions; + return Check_Return_To_Symres; + } + + case Constraint_Phase_Checking_Expressions: { + fori (i, constraint->expr_idx, bh_arr_length(constraint->exprs)) { + InterfaceConstraint* ic = &constraint->exprs[i]; + + CheckStatus cs = check_expression(&ic->expr); + if (cs == Check_Return_To_Symres || cs == Check_Yield_Macro) { + return cs; + } + + if (cs == Check_Error && !ic->invert_condition) { + goto constraint_error; + } + + if (cs == Check_Success && ic->invert_condition) { + goto constraint_error; + } + + if (ic->expected_type_expr) { + cs = check_type(&ic->expected_type_expr); + if (cs == Check_Return_To_Symres || cs == Check_Yield_Macro) { + return cs; + } + + ic->expected_type = type_build_from_ast(context.ast_alloc, ic->expected_type_expr); + if (ic->expected_type == NULL) { + YIELD(ic->expected_type_expr->token->pos, "Waiting on expected type expression to be resolved."); + } + + TYPE_CHECK(&ic->expr, ic->expected_type) { + if (!ic->invert_condition) + goto constraint_error; + } + } + + constraint->expr_idx++; + continue; + + constraint_error: + // HACK HACK HACK + onyx_clear_errors(); + *constraint->report_status = Constraint_Check_Status_Failed; + return Check_Failed; + } + + // HACK HACK HACK + onyx_clear_errors(); + *constraint->report_status = Constraint_Check_Status_Success; + return Check_Complete; + } + } + + return Check_Success; +} + +CheckStatus check_constraint_context(ConstraintContext *cc, Scope *scope, OnyxFilePos pos) { + if (cc->constraint_checks) { + if (cc->constraints_met == 1) return Check_Success; + + fori (i, 0, bh_arr_length(cc->constraints)) { + if (cc->constraint_checks[i] == Constraint_Check_Status_Failed) { + if (cc->produce_errors) { + AstConstraint *constraint = cc->constraints[i]; + char constraint_map[512] = {0}; + fori (i, 0, bh_arr_length(constraint->type_args)) { + if (i != 0) strncat(constraint_map, ", ", 511); + + OnyxToken* symbol = constraint->interface->params[i].value_token; + token_toggle_end(symbol); + strncat(constraint_map, symbol->text, 511); + token_toggle_end(symbol); + + strncat(constraint_map, " is of type '", 511); + strncat(constraint_map, type_get_name(type_build_from_ast(context.ast_alloc, constraint->type_args[i])), 511); + strncat(constraint_map, "'", 511); + } + + onyx_report_error(constraint->exprs[constraint->expr_idx].expr->token->pos, Error_Critical, "Failed to satisfy constraint where %s.", constraint_map); + onyx_report_error(constraint->token->pos, Error_Critical, "Here is where the interface was used."); + onyx_report_error(pos, Error_Critical, "Here is the code that caused this constraint to be checked."); + + return Check_Error; + + } else { + // If no error are suppose to be produced, we still need to signal that + // the node reached a completed state. + return Check_Failed; + } + } + + if (cc->constraint_checks[i] == Constraint_Check_Status_Queued) { + YIELD(pos, "Waiting for constraints to be checked."); + } + } + + cc->constraints_met = 1; + return Check_Success; + + } else { + u32 count = bh_arr_length(cc->constraints); + ConstraintCheckStatus *ccs = bh_alloc_array(context.ast_alloc, ConstraintCheckStatus, count); + + cc->constraint_checks = ccs; + + fori (i, 0, count) { + ccs[i] = Constraint_Check_Status_Queued; + cc->constraints[i]->report_status = &ccs[i]; + cc->constraints[i]->phase = Constraint_Phase_Cloning_Expressions; + + add_entities_for_node(NULL, (AstNode *) cc->constraints[i], scope, NULL); + } + + return Check_Yield_Macro; + } +} + +CheckStatus check_polyquery(AstPolyQuery *query) { + if (query->function_header->scope == NULL) + query->function_header->scope = scope_create(context.ast_alloc, query->proc->parent_scope_of_poly_proc, query->token->pos); + + CheckStatus header_check = check_temp_function_header(query->function_header); + if (header_check == Check_Return_To_Symres) return Check_Return_To_Symres; + + b32 solved_something = 0; + i32 solved_count = 0; + char *err_msg = NULL; + bh_arr_each(AstPolyParam, param, query->proc->poly_params) { + AstPolySolution sln; + bh_arr_each(AstPolySolution, solved_sln, query->slns) { + if (token_equals(param->poly_sym->token, solved_sln->poly_sym->token)) { + goto poly_query_done; + } + } + + // CLEANUP: I think this can go away because it is already done in polymorph.c + bh_arr_each(AstPolySolution, known_sln, query->proc->known_slns) { + if (token_equals(param->poly_sym->token, known_sln->poly_sym->token)) { + sln = *known_sln; + goto poly_var_solved; + } + } + + TypeMatch result = find_polymorphic_sln(&sln, param, query->function_header, query->pp_lookup, query->given, &err_msg); + + switch (result) { + case TYPE_MATCH_SUCCESS: + goto poly_var_solved; + + case TYPE_MATCH_SPECIAL: + return Check_Yield_Macro; + + case TYPE_MATCH_YIELD: + case TYPE_MATCH_FAILED: { + if (query->successful_symres || solved_something) continue; + + if (query->error_on_fail || context.cycle_detected) { + onyx_report_error(query->token->pos, Error_Critical, "Error solving for polymorphic variable '%b'.", param->poly_sym->token->text, param->poly_sym->token->length); + if (err_msg != NULL) onyx_report_error(query->token->pos, Error_Critical, "%s", err_msg); + if (query->error_loc) onyx_report_error(query->error_loc->pos, Error_Critical, "Here is where the call is located."); // :ErrorMessage + } + + return Check_Failed; + } + } + +poly_var_solved: + solved_something = 1; + bh_arr_push(query->slns, sln); + insert_poly_sln_into_scope(query->function_header->scope, &sln); + +poly_query_done: + solved_count += 1; + } + + if (solved_count != bh_arr_length(query->proc->poly_params)) { + if (solved_something || query->successful_symres) { + return Check_Return_To_Symres; + } else { + return Check_Failed; + } + } + + return Check_Complete; +} + +void check_entity(Entity* ent) { + CheckStatus cs = Check_Success; + + switch (ent->type) { + case Entity_Type_Foreign_Function_Header: + case Entity_Type_Function_Header: cs = check_function_header(ent->function); break; + case Entity_Type_Temp_Function_Header: cs = check_temp_function_header(ent->function); break; + case Entity_Type_Function: cs = check_function(ent->function); break; + case Entity_Type_Overloaded_Function: cs = check_overloaded_function(ent->overloaded_function); break; + case Entity_Type_Global: cs = check_global(ent->global); break; + case Entity_Type_Struct_Member_Default: cs = check_struct_defaults((AstStructType *) ent->type_alias); break; + case Entity_Type_Memory_Reservation_Type: cs = check_memres_type(ent->mem_res); break; + case Entity_Type_Memory_Reservation: cs = check_memres(ent->mem_res); break; + case Entity_Type_Static_If: cs = check_static_if(ent->static_if); break; + case Entity_Type_Macro: cs = check_macro(ent->macro); break; + case Entity_Type_Constraint_Check: cs = check_constraint(ent->constraint); break; + case Entity_Type_Polymorph_Query: cs = check_polyquery(ent->poly_query); break; + case Entity_Type_Enum_Value: cs = check_expression(&ent->enum_value->value); break; + case Entity_Type_Process_Directive: cs = check_process_directive((AstNode *) ent->expr); break; + + case Entity_Type_Expression: + cs = check_expression(&ent->expr); + resolve_expression_type(ent->expr); + break; + + case Entity_Type_Type_Alias: + if (ent->type_alias->kind == Ast_Kind_Struct_Type) + cs = check_struct((AstStructType *) ent->type_alias); + else + cs = check_type(&ent->type_alias); + break; + + case Entity_Type_File_Contents: + if (context.options->no_file_contents) { + onyx_report_error(ent->expr->token->pos, Error_Critical, "#file_contents is disabled for this compilation."); + } + break; + + default: break; + } + + switch (cs) { + case Check_Yield_Macro: ent->macro_attempts++; break; + case Check_Success: ent->state = Entity_State_Code_Gen; goto clear_attempts; + case Check_Complete: ent->state = Entity_State_Finalized; goto clear_attempts; + case Check_Return_To_Symres: ent->state = Entity_State_Resolve_Symbols; goto clear_attempts; + case Check_Failed: ent->state = Entity_State_Failed; goto clear_attempts; + + clear_attempts: + ent->macro_attempts = 0; + ent->micro_attempts = 0; + } +} diff --git a/compiler/src/clone.c b/compiler/src/clone.c new file mode 100644 index 00000000..828f6613 --- /dev/null +++ b/compiler/src/clone.c @@ -0,0 +1,643 @@ +#include "astnodes.h" +#include "parser.h" +#include "utils.h" + +// Weird flags that shouldn't be used too often because they complicate things +static b32 dont_copy_structs = 0; + +static inline b32 should_clone(AstNode* node) { + if (node->flags & Ast_Flag_No_Clone) return 0; + + if (dont_copy_structs) { + if (node->kind == Ast_Kind_Struct_Type) return 0; + if (node->kind == Ast_Kind_Function) return 0; + if (node->kind == Ast_Kind_Polymorphic_Proc) return 0; + } + + switch (node->kind) { + // List of nodes that should not be copied + case Ast_Kind_Global: + case Ast_Kind_Memres: + case Ast_Kind_StrLit: + case Ast_Kind_Package: + case Ast_Kind_Overloaded_Function: + case Ast_Kind_Alias: + case Ast_Kind_Code_Block: + case Ast_Kind_Macro: + case Ast_Kind_Symbol: + case Ast_Kind_Poly_Struct_Type: + case Ast_Kind_Basic_Type: + case Ast_Kind_Enum_Type: + case Ast_Kind_Enum_Value: + return 0; + + default: return 1; + } +} + +static inline i32 ast_kind_to_size(AstNode* node) { + switch (node->kind) { + case Ast_Kind_Error: return sizeof(AstNode); + case Ast_Kind_Package: return sizeof(AstPackage); + case Ast_Kind_Load_File: return sizeof(AstInclude); + case Ast_Kind_Load_Path: return sizeof(AstInclude); + case Ast_Kind_Load_All: return sizeof(AstInclude); + case Ast_Kind_Memres: return sizeof(AstMemRes); + case Ast_Kind_Binding: return sizeof(AstBinding); + case Ast_Kind_Function: return sizeof(AstFunction); + case Ast_Kind_Overloaded_Function: return sizeof(AstOverloadedFunction); + case Ast_Kind_Polymorphic_Proc: return sizeof(AstFunction); + case Ast_Kind_Block: return sizeof(AstBlock); + case Ast_Kind_Local: return sizeof(AstLocal); + case Ast_Kind_Global: return sizeof(AstGlobal); + case Ast_Kind_Symbol: return sizeof(AstNode); + case Ast_Kind_Unary_Op: return sizeof(AstUnaryOp); + case Ast_Kind_Binary_Op: return sizeof(AstBinaryOp); + case Ast_Kind_Type_Start: return 0; + case Ast_Kind_Type: return sizeof(AstType); + case Ast_Kind_Basic_Type: return sizeof(AstBasicType); + case Ast_Kind_Pointer_Type: return sizeof(AstPointerType); + case Ast_Kind_Function_Type: return sizeof(AstFunctionType) + ((AstFunctionType *) node)->param_count * sizeof(AstType *); + case Ast_Kind_Array_Type: return sizeof(AstArrayType); + case Ast_Kind_Slice_Type: return sizeof(AstSliceType); + case Ast_Kind_DynArr_Type: return sizeof(AstDynArrType); + case Ast_Kind_VarArg_Type: return sizeof(AstVarArgType); + case Ast_Kind_Struct_Type: return sizeof(AstStructType); + case Ast_Kind_Poly_Struct_Type: return sizeof(AstPolyStructType); + case Ast_Kind_Poly_Call_Type: return sizeof(AstPolyCallType); + case Ast_Kind_Enum_Type: return sizeof(AstEnumType); + case Ast_Kind_Type_Alias: return sizeof(AstTypeAlias); + case Ast_Kind_Type_Raw_Alias: return sizeof(AstTypeRawAlias); + case Ast_Kind_Type_Compound: return sizeof(AstCompoundType); + case Ast_Kind_Typeof: return sizeof(AstTypeOf); + case Ast_Kind_Type_End: return 0; + case Ast_Kind_Struct_Member: return sizeof(AstStructMember); + case Ast_Kind_Enum_Value: return sizeof(AstEnumValue); + case Ast_Kind_NumLit: return sizeof(AstNumLit); + case Ast_Kind_StrLit: return sizeof(AstStrLit); + case Ast_Kind_Param: return sizeof(AstLocal); + case Ast_Kind_Argument: return sizeof(AstArgument); + case Ast_Kind_Call: return sizeof(AstCall); + case Ast_Kind_Intrinsic_Call: return sizeof(AstCall); + case Ast_Kind_Return: return sizeof(AstReturn); + case Ast_Kind_Address_Of: return sizeof(AstAddressOf); + case Ast_Kind_Dereference: return sizeof(AstDereference); + case Ast_Kind_Subscript: return sizeof(AstSubscript); + case Ast_Kind_Slice: return sizeof(AstSubscript); + case Ast_Kind_Field_Access: return sizeof(AstFieldAccess); + case Ast_Kind_Unary_Field_Access: return sizeof(AstUnaryFieldAccess); + case Ast_Kind_Pipe: return sizeof(AstBinaryOp); + case Ast_Kind_Range_Literal: return sizeof(AstRangeLiteral); + case Ast_Kind_Method_Call: return sizeof(AstBinaryOp); + case Ast_Kind_Size_Of: return sizeof(AstSizeOf); + case Ast_Kind_Align_Of: return sizeof(AstAlignOf); + case Ast_Kind_File_Contents: return sizeof(AstFileContents); + case Ast_Kind_Struct_Literal: return sizeof(AstStructLiteral); + case Ast_Kind_Array_Literal: return sizeof(AstArrayLiteral); + case Ast_Kind_If: return sizeof(AstIfWhile); + case Ast_Kind_For: return sizeof(AstFor); + case Ast_Kind_While: return sizeof(AstIfWhile); + case Ast_Kind_Jump: return sizeof(AstJump); + case Ast_Kind_Use: return sizeof(AstUse); + case Ast_Kind_Defer: return sizeof(AstDefer); + case Ast_Kind_Switch: return sizeof(AstSwitch); + case Ast_Kind_Switch_Case: return sizeof(AstSwitchCase); + case Ast_Kind_Directive_Solidify: return sizeof(AstDirectiveSolidify); + case Ast_Kind_Compound: return sizeof(AstCompound); + case Ast_Kind_Named_Value: return sizeof(AstNamedValue); + case Ast_Kind_Call_Site: return sizeof(AstCallSite); + case Ast_Kind_Static_If: return sizeof(AstIfWhile); + case Ast_Kind_If_Expression: return sizeof(AstIfExpression); + case Ast_Kind_Directive_Insert: return sizeof(AstDirectiveInsert); + case Ast_Kind_Directive_Defined: return sizeof(AstDirectiveDefined); + case Ast_Kind_Do_Block: return sizeof(AstDoBlock); + case Ast_Kind_Constraint: return sizeof(AstConstraint); + case Ast_Kind_Directive_Remove: return sizeof(AstDirectiveRemove); + case Ast_Kind_Count: return 0; + } + + return 0; +} + +AstNode* ast_clone_list(bh_allocator a, void* n) { + AstNode* node = (AstNode *) n; + if (node == NULL) return NULL; + + AstNode* root = ast_clone(a, node); + AstNode* curr = root->next; + AstNode** insertion = &root->next; + + while (curr != NULL) { + curr = ast_clone(a, curr); + *insertion = curr; + insertion = &curr->next; + curr = curr->next; + } + + return root; +} + +static bh_arr(AstNode *) captured_entities=NULL; +#define E(ent) do { \ + assert(captured_entities); \ + ent->entity = NULL; \ + bh_arr_push(captured_entities, (AstNode *) ent); \ + } while (0); + + +#define C(nt, mname) ((nt *) nn)->mname = (void *) ast_clone(a, ((nt *) node)->mname); + +// NOTE: Using void* to avoid a lot of unnecessary casting +AstNode* ast_clone(bh_allocator a, void* n) { + AstNode* node = (AstNode *) n; + + if (node == NULL) return NULL; + if (!should_clone(node)) return node; + + static int clone_depth = 0; + clone_depth++; + + i32 node_size = ast_kind_to_size(node); + // bh_printf("Cloning %s with size %d\n", onyx_ast_node_kind_string(node->kind), node_size); + + AstNode* nn = onyx_ast_node_new(a, node_size, node->kind); + memmove(nn, node, node_size); + + switch ((u16) node->kind) { + case Ast_Kind_Binary_Op: + case Ast_Kind_Pipe: + case Ast_Kind_Method_Call: + C(AstBinaryOp, left); + C(AstBinaryOp, right); + break; + + case Ast_Kind_Unary_Op: + C(AstUnaryOp, expr); + C(AstUnaryOp, type_node); + break; + + case Ast_Kind_Param: + case Ast_Kind_Local: + C(AstLocal, type_node); + break; + + case Ast_Kind_Call: + C(AstCall, callee); + arguments_deep_clone(a, &((AstCall *) nn)->args, &((AstCall *) node)->args); + break; + + case Ast_Kind_Argument: + C(AstArgument, value); + break; + + case Ast_Kind_Address_Of: + C(AstAddressOf, expr); + break; + + case Ast_Kind_Dereference: + C(AstDereference, expr); + break; + + case Ast_Kind_Slice: + case Ast_Kind_Subscript: + C(AstSubscript, addr); + C(AstSubscript, expr); + break; + + case Ast_Kind_Field_Access: + C(AstFieldAccess, expr); + break; + + case Ast_Kind_Size_Of: + C(AstSizeOf, so_ast_type); + break; + + case Ast_Kind_Align_Of: + C(AstAlignOf, ao_ast_type); + break; + + case Ast_Kind_Struct_Literal: { + AstStructLiteral* st = (AstStructLiteral *) node; + AstStructLiteral* dt = (AstStructLiteral *) nn; + + dt->stnode = (AstTyped *) ast_clone(a, st->stnode); + + arguments_deep_clone(a, &dt->args, &st->args); + break; + } + + case Ast_Kind_Array_Literal: { + AstArrayLiteral* st = (AstArrayLiteral *) node; + AstArrayLiteral* dt = (AstArrayLiteral *) nn; + + dt->atnode = (AstTyped *) ast_clone(a, st->atnode); + + dt->values = NULL; + bh_arr_new(global_heap_allocator, dt->values, bh_arr_length(st->values)); + bh_arr_each(AstTyped *, val, st->values) + bh_arr_push(dt->values, (AstTyped *) ast_clone(a, *val)); + + break; + } + + case Ast_Kind_Range_Literal: + C(AstRangeLiteral, low); + C(AstRangeLiteral, high); + C(AstRangeLiteral, step); + break; + + case Ast_Kind_Return: + C(AstReturn, expr); + break; + + case Ast_Kind_Block: + ((AstBlock *) nn)->body = ast_clone_list(a, ((AstBlock *) node)->body); + break; + + case Ast_Kind_Defer: + C(AstDefer, stmt); + break; + + case Ast_Kind_For: + C(AstFor, var); + C(AstFor, iter); + C(AstFor, stmt); + break; + + case Ast_Kind_If: + case Ast_Kind_While: + ((AstIfWhile *) nn)->initialization = ast_clone_list(a, ((AstIfWhile *) node)->initialization); + //fallthrough + + case Ast_Kind_Static_If: + C(AstIfWhile, cond); + + C(AstIfWhile, true_stmt); + C(AstIfWhile, false_stmt); + + if (nn->kind == Ast_Kind_Static_If) { + ((AstIfWhile *) node)->flags |= Ast_Flag_Dead; + ((AstIfWhile *) node)->flags |= Ast_Flag_Static_If_Resolved; + + ((AstIfWhile *) nn)->flags &= ~Ast_Flag_Dead; + ((AstIfWhile *) nn)->flags &= ~Ast_Flag_Static_If_Resolved; + E(nn); + } + + break; + + case Ast_Kind_Switch_Case: { + C(AstSwitchCase, block); + + AstSwitchCase *dw = (AstSwitchCase *) nn; + AstSwitchCase *sw = (AstSwitchCase *) node; + + dw->values = NULL; + bh_arr_new(global_heap_allocator, dw->values, bh_arr_length(sw->values)); + bh_arr_each(AstTyped *, value, sw->values) + bh_arr_push(dw->values, (AstTyped *) ast_clone(a, *value)); + + break; + } + + case Ast_Kind_Switch: { + AstSwitch* dw = (AstSwitch *) nn; + AstSwitch* sw = (AstSwitch *) node; + + dw->initialization = ast_clone_list(a, sw->initialization); + C(AstSwitch, expr); + + + dw->cases = NULL; + C(AstSwitch, case_block); + break; + } + + case Ast_Kind_Pointer_Type: + C(AstPointerType, elem); + break; + + case Ast_Kind_Array_Type: + C(AstArrayType, count_expr); + C(AstArrayType, elem); + break; + + case Ast_Kind_Slice_Type: + C(AstSliceType, elem); + break; + + case Ast_Kind_DynArr_Type: + C(AstDynArrType, elem); + break; + + case Ast_Kind_VarArg_Type: + C(AstVarArgType, elem); + break; + + case Ast_Kind_Type_Alias: + C(AstTypeAlias, to); + break; + + case Ast_Kind_Struct_Type: { + AstStructType* ds = (AstStructType *) nn; + AstStructType* ss = (AstStructType *) node; + + ds->members = NULL; + bh_arr_new(global_heap_allocator, ds->members, bh_arr_length(ss->members)); + + bh_arr_each(AstStructMember *, smem, ss->members) { + bh_arr_push(ds->members, (AstStructMember *) ast_clone(a, *smem)); + } + + ds->meta_tags = NULL; + bh_arr_new(global_heap_allocator, ds->meta_tags, bh_arr_length(ss->meta_tags)); + bh_arr_each(AstTyped *, tag, ss->meta_tags) { + bh_arr_push(ds->meta_tags, (AstTyped *) ast_clone(a, *tag)); + } + + if (ss->constraints.constraints) { + memset(&ds->constraints, 0, sizeof(ConstraintContext)); + bh_arr_new(global_heap_allocator, ds->constraints.constraints, bh_arr_length(ss->constraints.constraints)); + + bh_arr_each(AstConstraint *, constraint, ss->constraints.constraints) { + bh_arr_push(ds->constraints.constraints, (AstConstraint *) ast_clone(a, (AstNode *) *constraint)); + } + } + + ds->stcache = NULL; + break; + } + + case Ast_Kind_Struct_Member: { + C(AstStructMember, type_node); + C(AstStructMember, initial_value); + + AstStructMember *ds = (AstStructMember *) nn; + AstStructMember *ss = (AstStructMember *) node; + + ds->meta_tags = NULL; + bh_arr_new(global_heap_allocator, ds->meta_tags, bh_arr_length(ss->meta_tags)); + bh_arr_each(AstTyped *, tag, ss->meta_tags) { + bh_arr_push(ds->meta_tags, (AstTyped *) ast_clone(a, *tag)); + } + + break; + } + + case Ast_Kind_Poly_Call_Type: { + AstPolyCallType* pcd = (AstPolyCallType *) nn; + AstPolyCallType* pcs = (AstPolyCallType *) node; + + pcd->callee = (AstType *) ast_clone(a, pcs->callee); + pcd->params = NULL; + bh_arr_new(global_heap_allocator, pcd->params, bh_arr_length(pcs->params)); + + bh_arr_each(AstNode *, param, pcs->params) { + bh_arr_push(pcd->params, ast_clone(a, *param)); + } + + break; + } + + case Ast_Kind_Type_Compound: { + AstCompoundType* cd = (AstCompoundType *) nn; + AstCompoundType* cs = (AstCompoundType *) node; + + cd->types = NULL; + bh_arr_new(global_heap_allocator, cd->types, bh_arr_length(cs->types)); + + bh_arr_each(AstType *, type, cs->types) { + bh_arr_push(cd->types, (AstType *) ast_clone(a, (AstNode *) *type)); + } + break; + } + + case Ast_Kind_Function_Type: + C(AstFunctionType, return_type); + ((AstFunctionType *) nn)->param_count = ((AstFunctionType *) node)->param_count; + fori (i, 0, (i64) ((AstFunctionType *) nn)->param_count) { + ((AstFunctionType *) nn)->params[i] = (AstType *) ast_clone(a, ((AstFunctionType *) node)->params[i]); + } + break; + + case Ast_Kind_Binding: + bh_printf("Cloning binding: %b\n", node->token->text, node->token->length); + C(AstTyped, type_node); + C(AstBinding, node); + break; + + case Ast_Kind_Function: + case Ast_Kind_Polymorphic_Proc: { + if (clone_depth > 1) { + clone_depth--; + return node; + } + + AstFunction* df = (AstFunction *) nn; + AstFunction* sf = (AstFunction *) node; + + convert_polyproc_to_function(df); + + if (sf->is_foreign) return node; + assert(df->scope == NULL); + + df->nodes_that_need_entities_after_clone = NULL; + bh_arr_new(global_heap_allocator, df->nodes_that_need_entities_after_clone, 1); + captured_entities = df->nodes_that_need_entities_after_clone; + + df->return_type = (AstType *) ast_clone(a, sf->return_type); + df->body = (AstBlock *) ast_clone(a, sf->body); + + df->nodes_that_need_entities_after_clone = captured_entities; + captured_entities = NULL; + + df->params = NULL; + bh_arr_new(context.ast_alloc, df->params, bh_arr_length(sf->params)); + + bh_arr_each(AstParam, param, sf->params) { + AstParam new_param = { 0 }; + + dont_copy_structs = 1; + new_param.local = (AstLocal *) ast_clone(a, param->local); + new_param.local->flags &= ~Ast_Flag_Param_Symbol_Dirty; + new_param.default_value = (AstTyped *) ast_clone(a, param->default_value); + new_param.use_processed = 0; + dont_copy_structs = 0; + + new_param.vararg_kind = param->vararg_kind; + new_param.is_used = param->is_used; + bh_arr_push(df->params, new_param); + } + + if (sf->constraints.constraints) { + memset(&df->constraints, 0, sizeof(ConstraintContext)); + bh_arr_new(context.ast_alloc, df->constraints.constraints, bh_arr_length(sf->constraints.constraints)); + + bh_arr_each(AstConstraint *, constraint, sf->constraints.constraints) { + bh_arr_push(df->constraints.constraints, (AstConstraint *) ast_clone(a, (AstNode *) *constraint)); + } + } + + if (sf->tags) { + bh_arr_new(context.ast_alloc, df->tags, bh_arr_length(sf->tags)); + bh_arr_each(AstTyped *, pexpr, sf->tags) { + bh_arr_push(df->tags, (AstTyped *) ast_clone(a, (AstNode *) *pexpr)); + } + } + + break; + } + + case Ast_Kind_Constraint: { + C(AstConstraint, interface); + + AstConstraint* dc = (AstConstraint *) nn; + AstConstraint* sc = (AstConstraint *) node; + + dc->type_args = NULL; + bh_arr_new(global_heap_allocator, dc->type_args, bh_arr_length(sc->type_args)); + + bh_arr_each(AstType *, type_arg, sc->type_args) { + bh_arr_push(dc->type_args, (AstType *) ast_clone(a, (AstNode *) *type_arg)); + } + + dc->phase = Constraint_Phase_Waiting_To_Be_Queued; + break; + } + + case Ast_Kind_Use: + C(AstUse, expr); + break; + + case Ast_Kind_Directive_Solidify: { + AstDirectiveSolidify* dd = (AstDirectiveSolidify *) nn; + AstDirectiveSolidify* sd = (AstDirectiveSolidify *) node; + + dd->poly_proc = (AstFunction *) ast_clone(a, (AstNode *) sd->poly_proc); + dd->resolved_proc = NULL; + + dd->known_polyvars = NULL; + bh_arr_new(global_heap_allocator, dd->known_polyvars, bh_arr_length(sd->known_polyvars)); + + bh_arr_each(AstPolySolution, sln, sd->known_polyvars) { + AstPolySolution new_sln; + new_sln.kind = sln->kind; + new_sln.poly_sym = (AstNode *) ast_clone(a, (AstNode *) sln->poly_sym); + new_sln.ast_type = (AstType *) ast_clone(a, (AstNode *) sln->ast_type); + bh_arr_push(dd->known_polyvars, new_sln); + } + + break; + } + + case Ast_Kind_Compound: { + AstCompound* cd = (AstCompound *) nn; + AstCompound* cs = (AstCompound *) node; + + cd->exprs = NULL; + bh_arr_new(global_heap_allocator, cd->exprs, bh_arr_length(cs->exprs)); + + bh_arr_each(AstTyped *, expr, cs->exprs) { + bh_arr_push(cd->exprs, (AstTyped *) ast_clone(a, (AstNode *) *expr)); + } + break; + } + + case Ast_Kind_Named_Value: + C(AstNamedValue, value); + break; + + case Ast_Kind_If_Expression: + C(AstIfExpression, cond); + C(AstIfExpression, true_expr); + C(AstIfExpression, false_expr); + break; + + case Ast_Kind_Directive_Insert: + C(AstDirectiveInsert, code_expr); + break; + + case Ast_Kind_Directive_Defined: + C(AstDirectiveDefined, expr); + ((AstDirectiveDefined *) nn)->is_defined = 0; + break; + + case Ast_Kind_Typeof: + C(AstTypeOf, expr); + ((AstTypeOf *) nn)->resolved_type = NULL; + break; + + case Ast_Kind_Do_Block: + C(AstDoBlock, block); + ((AstDoBlock *) nn)->type_node = (AstType *) &basic_type_auto_return; + break; + + case Ast_Kind_File_Contents: + C(AstFileContents, filename_expr); + E(nn); + break; + } + + clone_depth--; + return nn; +} + +#undef C + +AstFunction* clone_function_header(bh_allocator a, AstFunction* func) { + if (func->kind != Ast_Kind_Function && func->kind != Ast_Kind_Polymorphic_Proc) return NULL; + + if (func->is_foreign) return func; + + AstFunction* new_func = onyx_ast_node_new(a, sizeof(AstFunction), func->kind); + memmove(new_func, func, sizeof(AstFunction)); + assert(new_func->scope == NULL); + + convert_polyproc_to_function(new_func); + + new_func->return_type = (AstType *) ast_clone(a, func->return_type); + + new_func->params = NULL; + bh_arr_new(global_heap_allocator, new_func->params, bh_arr_length(func->params)); + bh_arr_each(AstParam, param, func->params) { + AstParam new_param; + + dont_copy_structs = 1; + new_param.local = (AstLocal *) ast_clone(a, param->local); + new_param.local->flags &= ~Ast_Flag_Param_Symbol_Dirty; + new_param.default_value = (AstTyped *) ast_clone(a, param->default_value); + new_param.use_processed = 0; + dont_copy_structs = 0; + + new_param.vararg_kind = param->vararg_kind; + new_param.is_used = param->is_used; + bh_arr_push(new_func->params, new_param); + } + + if (func->constraints.constraints) { + memset(&new_func->constraints, 0, sizeof(ConstraintContext)); + bh_arr_new(global_heap_allocator, new_func->constraints.constraints, bh_arr_length(func->constraints.constraints)); + + bh_arr_each(AstConstraint *, constraint, func->constraints.constraints) { + bh_arr_push(new_func->constraints.constraints, (AstConstraint *) ast_clone(a, (AstNode *) *constraint)); + } + } + + return new_func; +} + +// Clones a function body from a given function. It is assumed that `dest` is +// a function from `clone_function_header`. +void clone_function_body(bh_allocator a, AstFunction* dest, AstFunction* source) { + if (dest->kind != Ast_Kind_Function) return; + if (source->kind != Ast_Kind_Polymorphic_Proc && source->kind != Ast_Kind_Function) return; + + dest->nodes_that_need_entities_after_clone = NULL; + bh_arr_new(global_heap_allocator, dest->nodes_that_need_entities_after_clone, 1); + captured_entities = dest->nodes_that_need_entities_after_clone; + + dest->body = (AstBlock *) ast_clone(a, source->body); + + dest->nodes_that_need_entities_after_clone = captured_entities; + captured_entities = NULL; +} diff --git a/compiler/src/doc.c b/compiler/src/doc.c new file mode 100644 index 00000000..2a8bbd44 --- /dev/null +++ b/compiler/src/doc.c @@ -0,0 +1,292 @@ +#include "doc.h" +#include "utils.h" +#include "types.h" + +static i32 cmp_doc_entry(const void * a, const void * b) { + DocEntry* d1 = (DocEntry *) a; + DocEntry* d2 = (DocEntry *) b; + + return strncmp(d1->def, d2->def, 1024); +} + +static i32 cmp_doc_package(const void * a, const void * b) { + DocPackage* d1 = (DocPackage *) a; + DocPackage* d2 = (DocPackage *) b; + + return strncmp(d1->name, d2->name, 1024); +} + +static char* node_to_doc_def(const char* sym, AstNode *node, bh_allocator a) { + static char buf[1024]; + memset(buf, 0, 1023); + + strncat(buf, sym, 1023); + strncat(buf, " :: ", 1023); + + switch (node->kind) { + case Ast_Kind_Function: { + strncat(buf, "(", 1023); + + AstFunction *func = (AstFunction *) node; + bh_arr_each(AstParam, param, func->params) { + if (param != func->params) + strncat(buf, ", ", 1023); + + token_toggle_end(param->local->token); + strncat(buf, param->local->token->text, 1023); + token_toggle_end(param->local->token); + + strncat(buf, ": ", 1023); + + strncat(buf, type_get_name(param->local->type), 1023); + + if (param->default_value != NULL) { + strncat(buf, " = ", 1023); + } + } + + strncat(buf, ") -> ", 1023); + strncat(buf, type_get_name(func->type->Function.return_type), 1023); + + break; + } + + case Ast_Kind_Struct_Type: { + strncat(buf, "struct { ", 1023); + + AstStructType* st = (AstStructType *) node; + bh_arr_each(AstStructMember *, smem, st->members) { + + token_toggle_end((*smem)->token); + strncat(buf, (*smem)->token->text, 1023); + token_toggle_end((*smem)->token); + + strncat(buf, ": ", 1023); + + strncat(buf, type_get_name((*smem)->type), 1023); + + strncat(buf, "; ", 1023); + } + + strncat(buf, "}", 1023); + break; + } + + case Ast_Kind_Basic_Type: + case Ast_Kind_Array_Type: + case Ast_Kind_Type_Alias: + case Ast_Kind_Slice_Type: + case Ast_Kind_DynArr_Type: { + strncat(buf, type_get_name(type_build_from_ast(global_heap_allocator, (AstType *) node)), 1023); + break; + } + + default: { + strncat(buf, "", 1023); + } + } + + return bh_strdup(a, buf); +} + +static DocPackage doc_package_create(Package* p, bh_allocator a) { + DocPackage dp; + dp.name = p->name; + dp.public_entries = NULL; + dp.private_entries = NULL; + + bh_arr_new(global_heap_allocator, dp.public_entries, 16); + bh_arr_new(global_heap_allocator, dp.private_entries, 16); + + fori (i, 0, shlen(p->scope->symbols)) { + char *key = p->scope->symbols[i].key; + AstNode *value = p->scope->symbols[i].value; + + DocEntry de; + if (value->token) de.pos = value->token->pos; + de.def = node_to_doc_def(key, value, a); + de.sym = (char *) key; + de.additional = NULL; + + bh_arr_push(dp.public_entries, de); + } + + fori (i, 0, shlen(p->private_scope->symbols)) { + char *key = p->scope->symbols[i].key; + AstNode *value = p->scope->symbols[i].value; + + DocEntry de; + if (value->token) de.pos = value->token->pos; + de.def = node_to_doc_def(key, value, a); + de.sym = (char *) key; + de.additional = NULL; + + bh_arr_push(dp.private_entries, de); + } + + qsort(dp.public_entries, bh_arr_length(dp.public_entries), sizeof(DocEntry), cmp_doc_entry); + qsort(dp.private_entries, bh_arr_length(dp.private_entries), sizeof(DocEntry), cmp_doc_entry); + + return dp; +} + +OnyxDocumentation onyx_docs_generate() { + OnyxDocumentation doc; + + bh_arena_init(&doc.doc_arena, global_heap_allocator, 16 * 1024); + bh_allocator a = bh_arena_allocator(&doc.doc_arena); + + doc.package_docs = NULL; + bh_arr_new(global_heap_allocator, doc.package_docs, 16); + + fori (i, 0, shlen(context.packages)) { + DocPackage dp = doc_package_create(context.packages[i].value, a); + bh_arr_push(doc.package_docs, dp); + } + + qsort(doc.package_docs, bh_arr_length(doc.package_docs), sizeof(DocPackage), cmp_doc_package); + + return doc; +} + +static void onyx_docs_emit_human(OnyxDocumentation* doc, bh_file* file) { + // NOTE: Disabling fancy line printing until I can make it better + #if 0 + bh_arr_each(DocPackage, dp, doc->package_docs) { + bh_printf("Package '%s'\n", dp->name); + + if (bh_arr_length(dp->public_entries) > 0) { + bh_printf("\xe2\x94\x9c\xe2\x94\x80 Public symbols\n"); + bh_arr_each(DocEntry, de, dp->public_entries) { + bh_printf("\xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80 %s\n", de->def); + + if (de->pos.filename != NULL) + bh_printf("\xe2\x94\x82 \xe2\x94\x82 at %s:%d,%d\n", de->pos.filename, de->pos.line, de->pos.column); + else + bh_printf("\xe2\x94\x82 \xe2\x94\x82 compiler built-in\n"); + + bh_printf("\xe2\x94\x82 \xe2\x94\x82\n"); + } + } + + if (bh_arr_length(dp->private_entries) > 0) { + bh_printf("\xe2\x94\x9c\xe2\x94\x80 Private symbols\n"); + bh_arr_each(DocEntry, de, dp->private_entries) { + bh_printf("\xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80 %s\n", de->def); + if (de->pos.filename != NULL) + bh_printf("\xe2\x94\x82 \xe2\x94\x82 at %s:%d,%d\n", de->pos.filename, de->pos.line, de->pos.column); + else + bh_printf("\xe2\x94\x82 \xe2\x94\x82 compiler built-in\n"); + + bh_printf("\xe2\x94\x82 \xe2\x94\x82\n"); + } + + bh_printf("\n"); + } + } + #else + bh_arr_each(DocPackage, dp, doc->package_docs) { + bh_fprintf(file, "Package '%s'\n", dp->name); + + if (bh_arr_length(dp->public_entries) > 0) { + bh_fprintf(file, " Public symbols\n"); + bh_arr_each(DocEntry, de, dp->public_entries) { + bh_fprintf(file, " %s\n", de->def); + + if (de->pos.filename != NULL) + bh_fprintf(file, " at %s:%d,%d\n", de->pos.filename, de->pos.line, de->pos.column); + else + bh_fprintf(file, " compiler built-in\n"); + + bh_fprintf(file, " \n"); + } + } + + if (bh_arr_length(dp->private_entries) > 0) { + bh_fprintf(file, " Private symbols\n"); + bh_arr_each(DocEntry, de, dp->private_entries) { + bh_fprintf(file, " %s\n", de->def); + if (de->pos.filename != NULL) + bh_fprintf(file, " at %s:%d,%d\n", de->pos.filename, de->pos.line, de->pos.column); + else + bh_fprintf(file, " compiler built-in\n"); + + bh_fprintf(file, " \n"); + } + + bh_fprintf(file, "\n"); + } + } + #endif +} + + +static i32 sort_tags(const void* a, const void* b) { + DocEntry *da = *((DocEntry **) a); + DocEntry *db = *((DocEntry **) b); + + return strcmp(da->sym, db->sym); +} + +static void onyx_docs_emit_tags(OnyxDocumentation* doc) { + bh_file tags_file; + if (bh_file_create(&tags_file, "./tags") != BH_FILE_ERROR_NONE) { + bh_printf("Cannot create 'tags'.\n"); + return; + } + + bh_fprintf(&tags_file, "!_TAG_FILE_FORMAT\t2\n"); + bh_fprintf(&tags_file, "!_TAG_FILE_SORTED\t1\n"); + bh_fprintf(&tags_file, "!_TAG_OUTPUT_FILESEP\tslash\n"); + bh_fprintf(&tags_file, "!_TAG_OUTPUT_MODE\tu-ctags\n"); + bh_fprintf(&tags_file, "!_TAG_PROGRAM_AUTHOR\tOnyx Compiler\n"); + bh_fprintf(&tags_file, "!_TAG_PROGRAM_NAME\tOnyx Compiler\n"); + bh_fprintf(&tags_file, "!_TAG_PROGRAM_URL\thttps://github.com/brendanfh/onyx\n"); + bh_fprintf(&tags_file, "!_TAG_PROGRAM_VERSION\t0.0.1\n"); + + bh_arr(DocEntry *) tags = NULL; + bh_arr_new(global_heap_allocator, tags, 256); + + bh_arr_each(DocPackage, dp, doc->package_docs) { + bh_arr_each(DocEntry, de, dp->public_entries) bh_arr_push(tags, de); + bh_arr_each(DocEntry, de, dp->private_entries) bh_arr_push(tags, de); + } + + qsort(tags, bh_arr_length(tags), sizeof(DocEntry *), sort_tags); + + bh_arr_each(DocEntry *, tag, tags) { + if ((*tag)->pos.filename == NULL) continue; + + i32 line_len = 0; + char *c = (*tag)->pos.line_start; + while (*c++ != '\n') line_len++; + + bh_fprintf(&tags_file, "%s\t%s\t/^%b$/\n", + (*tag)->sym, (*tag)->pos.filename, + (*tag)->pos.line_start, line_len); + } + + bh_file_close(&tags_file); +} + +static void onyx_docs_emit_html(OnyxDocumentation* doc, bh_file* file) { + bh_fprintf(file, "HTML documentation output not supported yet.\n"); + return; +} + +void onyx_docs_emit(OnyxDocumentation* doc, const char* filename) { + bh_file file; + if (bh_file_create(&file, filename) != BH_FILE_ERROR_NONE) { + bh_printf("ERROR: Failed to open file '%s' for writing documentation.\n", filename); + return; + } + + switch (doc->format) { + case Doc_Format_Human: onyx_docs_emit_human(doc, &file); break; + case Doc_Format_Html: onyx_docs_emit_html(doc, &file); break; + case Doc_Format_Tags: onyx_docs_emit_tags(doc); break; + } + + bh_file_close(&file); +} + diff --git a/compiler/src/entities.c b/compiler/src/entities.c new file mode 100644 index 00000000..0ceb2f92 --- /dev/null +++ b/compiler/src/entities.c @@ -0,0 +1,406 @@ +#include "bh.h" +#include "astnodes.h" +#include "utils.h" + +static inline i32 entity_phase(Entity* e1) { + if (e1->state <= Entity_State_Parse && e1->macro_attempts == 0) return 1; + if (e1->state < Entity_State_Code_Gen) return 2; + return 3; +} + +// NOTE: Returns >0 if e1 should be processed after e2. +static i32 entity_compare(Entity* e1, Entity* e2) { + i32 phase1 = entity_phase(e1); + i32 phase2 = entity_phase(e2); + + if (phase1 != phase2) + return phase1 - phase2; + else if (e1->macro_attempts != e2->macro_attempts) + return (i32) e1->macro_attempts - (i32) e2->macro_attempts; + else if (e1->state != e2->state) + return (i32) e1->state - (i32) e2->state; + else if (e1->type != e2->type) + return (i32) e1->type - (i32) e2->type; + else if (e1->micro_attempts != e2->micro_attempts) + return (i32) (e1->micro_attempts - e2->micro_attempts); + else + return (i32) (e1->id - e2->id); +} + +#define eh_parent(index) (((index) - 1) / 2) +#define eh_lchild(index) (((index) * 2) + 1) +#define eh_rchild(index) (((index) * 2) + 2) + +static void eh_shift_up(EntityHeap* entities, i32 index) { + while (index > 0 && entity_compare(entities->entities[eh_parent(index)], entities->entities[index]) > 0) { + Entity* tmp = entities->entities[eh_parent(index)]; + entities->entities[eh_parent(index)] = entities->entities[index]; + entities->entities[index] = tmp; + + index = eh_parent(index); + } +} + +static void eh_shift_down(EntityHeap* entities, i32 index) { + while (1) { + i32 min_index = index; + + i32 l = eh_lchild(index); + if (l < bh_arr_length(entities->entities) + && entity_compare(entities->entities[l], entities->entities[min_index]) < 0) { + min_index = l; + } + + i32 r = eh_rchild(index); + if (r < bh_arr_length(entities->entities) + && entity_compare(entities->entities[r], entities->entities[min_index]) < 0) { + min_index = r; + } + + if (index != min_index) { + Entity* tmp = entities->entities[min_index]; + entities->entities[min_index] = entities->entities[index]; + entities->entities[index] = tmp; + + index = min_index; + continue; + } + + break; + } +} + +void entity_heap_init(EntityHeap* entities) { + bh_arena_init(&entities->entity_arena, global_heap_allocator, 32 * 1024); +} + +static u32 next_entity_id = 0; + +// Allocates the entity in the entity heap. Don't quite feel this is necessary... +Entity* entity_heap_register(EntityHeap* entities, Entity e) { + bh_allocator alloc = bh_arena_allocator(&entities->entity_arena); + Entity* entity = bh_alloc_item(alloc, Entity); + *entity = e; + entity->id = next_entity_id++; + entity->macro_attempts = 0; + entity->micro_attempts = 0; + entity->entered_in_queue = 0; + + return entity; +} + +void entity_heap_insert_existing(EntityHeap* entities, Entity* e) { + if (e->entered_in_queue) return; + + if (entities->entities == NULL) { + bh_arr_new(global_heap_allocator, entities->entities, 128); + } + + bh_arr_push(entities->entities, e); + eh_shift_up(entities, bh_arr_length(entities->entities) - 1); + e->entered_in_queue = 1; + + entities->state_count[e->state]++; + entities->type_count[e->type]++; + entities->all_count[e->state][e->type]++; +} + +Entity* entity_heap_insert(EntityHeap* entities, Entity e) { + Entity* entity = entity_heap_register(entities, e); + entity_heap_insert_existing(entities, entity); + return entity; +} + +Entity* entity_heap_top(EntityHeap* entities) { + return entities->entities[0]; +} + +void entity_heap_change_top(EntityHeap* entities, Entity* new_top) { + entities->state_count[entities->entities[0]->state]--; + entities->state_count[new_top->state]++; + + entities->type_count[entities->entities[0]->type]--; + entities->type_count[new_top->type]++; + + entities->all_count[entities->entities[0]->state][entities->entities[0]->type]--; + entities->all_count[new_top->state][new_top->type]++; + + entities->entities[0] = new_top; + eh_shift_down(entities, 0); +} + +void entity_heap_remove_top(EntityHeap* entities) { + entities->state_count[entities->entities[0]->state]--; + entities->type_count[entities->entities[0]->type]--; + entities->all_count[entities->entities[0]->state][entities->entities[0]->type]--; + entities->entities[0]->entered_in_queue = 0; + + entities->entities[0] = entities->entities[bh_arr_length(entities->entities) - 1]; + bh_arr_pop(entities->entities); + eh_shift_down(entities, 0); +} + +void entity_change_type(EntityHeap* entities, Entity *ent, EntityType new_type) { + entities->type_count[ent->type]--; + entities->type_count[new_type]++; + ent->type = new_type; +} + +// NOTE(Brendan Hansen): Uses the entity heap in the context structure +void add_entities_for_node(bh_arr(Entity *) *target_arr, AstNode* node, Scope* scope, Package* package) { +#define ENTITY_INSERT(_ent) \ + entity = entity_heap_register(entities, _ent); \ + if (target_arr) { \ + bh_arr(Entity *) __tmp_arr = *target_arr; \ + bh_arr_push(__tmp_arr, entity); \ + *target_arr = __tmp_arr; \ + } else { \ + entity_heap_insert_existing(entities, entity); \ + } \ + + if (node->entity != NULL) return; + + EntityHeap* entities = &context.entities; + Entity* entity; + + Entity ent; + ent.id = entities->next_id++; + ent.state = Entity_State_Resolve_Symbols; + ent.package = package; + ent.scope = scope; + + switch (node->kind) { + case Ast_Kind_Load_All: + case Ast_Kind_Load_File: { + ent.type = Entity_Type_Load_File; + ent.include = (AstInclude *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Library_Path: + case Ast_Kind_Load_Path: { + ent.type = Entity_Type_Load_Path; + ent.include = (AstInclude *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Binding: { + ent.state = Entity_State_Introduce_Symbols; + ent.type = Entity_Type_Binding; + ent.binding = (AstBinding *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Function: { + if (((AstFunction *) node)->is_foreign != 0) { + ent.type = Entity_Type_Foreign_Function_Header; + ent.function = (AstFunction *) node; + ENTITY_INSERT(ent); + + } else { + ent.type = Entity_Type_Function_Header; + ent.function = (AstFunction *) node; + ENTITY_INSERT(ent); + ((AstFunction *) node)->entity_header = entity; + + ent.id = entities->next_id++; + ent.type = Entity_Type_Function; + ent.function = (AstFunction *) node; + ENTITY_INSERT(ent); + ((AstFunction *) node)->entity_body = entity; + } + break; + } + + case Ast_Kind_Overloaded_Function: { + ent.type = Entity_Type_Overloaded_Function; + ent.overloaded_function = (AstOverloadedFunction *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Global: { + ent.type = Entity_Type_Global_Header; + ent.global = (AstGlobal *) node; + ENTITY_INSERT(ent); + + ent.id = entities->next_id++; + ent.type = Entity_Type_Global; + ent.global = (AstGlobal *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_StrLit: { + ent.type = Entity_Type_String_Literal; + ent.strlit = (AstStrLit *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_File_Contents: { + ent.type = Entity_Type_File_Contents; + ent.file_contents = (AstFileContents *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Struct_Type: { + ent.type = Entity_Type_Struct_Member_Default; + ent.type_alias = (AstType *) node; + ENTITY_INSERT(ent); + ((AstStructType *) node)->entity_defaults = entity; + + ent.id = entities->next_id++; + // fallthrough + } + + case Ast_Kind_Poly_Struct_Type: + case Ast_Kind_Type_Alias: { + ent.type = Entity_Type_Type_Alias; + ent.type_alias = (AstType *) node; + ENTITY_INSERT(ent); + + if (node->kind == Ast_Kind_Struct_Type) { + ((AstStructType *) node)->entity_type = entity; + } + + break; + } + + case Ast_Kind_Enum_Type: { + ent.type = Entity_Type_Enum; + ent.enum_type = (AstEnumType *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Enum_Value: { + ent.type = Entity_Type_Enum_Value; + ent.state = Entity_State_Check_Types; + ent.enum_value = (AstEnumValue *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Use: { + if (((AstUse *) node)->expr->kind == Ast_Kind_Package) { + ent.state = Entity_State_Resolve_Symbols; + ent.type = Entity_Type_Use_Package; + } else { + ent.type = Entity_Type_Use; + } + + ent.use = (AstUse *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Memres: { + ent.type = Entity_Type_Memory_Reservation_Type; + ent.mem_res = (AstMemRes *) node; + ENTITY_INSERT(ent); + ((AstMemRes *) node)->type_entity = entity; + + ent.id = entities->next_id++; + ent.type = Entity_Type_Memory_Reservation; + ent.mem_res = (AstMemRes *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Macro: { + ent.type = Entity_Type_Macro; + ent.macro = (AstMacro *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Polymorphic_Proc: { + ent.type = Entity_Type_Polymorphic_Proc; + ent.poly_proc = (AstFunction *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Polymorph_Query: { + ent.type = Entity_Type_Polymorph_Query; + ent.state = Entity_State_Check_Types; + ent.poly_query = (AstPolyQuery *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Static_If: { + ent.state = Entity_State_Resolve_Symbols; + ent.type = Entity_Type_Static_If; + ent.static_if = (AstIf *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Directive_Error: { + ent.state = Entity_State_Error; + ent.type = Entity_Type_Error; + ent.error = (AstDirectiveError *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Directive_Export: + case Ast_Kind_Directive_Add_Overload: + case Ast_Kind_Directive_Operator: + case Ast_Kind_Directive_Init: + case Ast_Kind_Directive_Library: + case Ast_Kind_Injection: { + ent.type = Entity_Type_Process_Directive; + ent.expr = (AstTyped *) node; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Note: { + ent.type = Entity_Type_Note; + ent.expr = (AstTyped *) node; + ent.state = Entity_State_Code_Gen; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Interface: { + ent.type = Entity_Type_Interface; + ent.interface = (AstInterface *) node; + ent.state = Entity_State_Resolve_Symbols; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Constraint: { + ent.type = Entity_Type_Constraint_Check; + ent.constraint = (AstConstraint *) node; + ent.state = Entity_State_Resolve_Symbols; + ENTITY_INSERT(ent); + break; + } + + case Ast_Kind_Foreign_Block: { + ent.type = Entity_Type_Foreign_Block; + ent.foreign_block = (AstForeignBlock *) node; + ent.state = Entity_State_Resolve_Symbols; + ENTITY_INSERT(ent); + break; + } + + default: { + ent.type = Entity_Type_Expression; + ent.expr = (AstTyped *) node; + ENTITY_INSERT(ent); + break; + } + } + + node->entity = entity; +} diff --git a/compiler/src/errors.c b/compiler/src/errors.c new file mode 100644 index 00000000..f6811ca6 --- /dev/null +++ b/compiler/src/errors.c @@ -0,0 +1,156 @@ +#include "errors.h" +#include "utils.h" + +OnyxErrors errors; + +void onyx_errors_init(bh_arr(bh_file_contents)* files) { + errors.file_contents = files; + + bh_arena_init(&errors.msg_arena, global_heap_allocator, 16 * 1024); + errors.msg_alloc = bh_arena_allocator(&errors.msg_arena); + + bh_arr_new(global_heap_allocator, errors.errors, 4); +} + +static void print_detailed_message(OnyxError* err, bh_file_contents* fc) { + bh_printf("(%s:%l,%l) %s\n", err->pos.filename, err->pos.line, err->pos.column, err->text); + + b32 colored_printing = 0; + #ifdef _BH_LINUX + colored_printing = !context.options->no_colors; + #endif + + i32 linelength = 0; + i32 first_char = 0; + char* walker = err->pos.line_start; + while (*walker == ' ' || *walker == '\t') first_char++, linelength++, walker++; + while (*walker != '\n') linelength++, walker++; + + if (colored_printing) bh_printf("\033[90m"); + i32 numlen = bh_printf(" %d | ", err->pos.line); + if (colored_printing) bh_printf("\033[94m"); + bh_printf("%b\n", err->pos.line_start, linelength); + + char* pointer_str = bh_alloc_array(global_scratch_allocator, char, linelength + numlen); + memset(pointer_str, ' ', linelength + numlen); + memcpy(pointer_str + numlen - 1, err->pos.line_start, first_char); + memset(pointer_str + first_char + numlen - 1, ' ', err->pos.column - first_char); + memset(pointer_str + err->pos.column + numlen - 1, '~', err->pos.length - 1); + pointer_str[err->pos.column + numlen - 2] = '^'; + pointer_str[err->pos.column + numlen + err->pos.length - 1] = 0; + + if (colored_printing) bh_printf("\033[91m"); + bh_printf("%s\n", pointer_str); + + if (colored_printing) bh_printf("\033[0m"); +} + +static i32 errors_sort(const void* v1, const void* v2) { + OnyxError* e1 = (OnyxError *) v1; + OnyxError* e2 = (OnyxError *) v2; + return e2->rank - e1->rank; +} + +void onyx_errors_print() { + // NOTE: If the format of the error messages is ever changed, + // update onyx_compile.vim and onyx.sublime-build to match + // the new format. This was editor error highlighting is still + // supported. + // + // - brendanfh 2020/09/03 + + qsort(errors.errors, bh_arr_length(errors.errors), sizeof(OnyxError), errors_sort); + + OnyxErrorRank last_rank = errors.errors[0].rank; + bh_arr_each(OnyxError, err, errors.errors) { + if (last_rank != err->rank) break; + + if (err->pos.filename) { + bh_file_contents file_contents = { 0 }; + bh_arr_each(bh_file_contents, fc, *errors.file_contents) { + if (!strcmp(fc->filename, err->pos.filename)) { + file_contents = *fc; + break; + } + } + + print_detailed_message(err, &file_contents); + + } else { + bh_printf("(%l,%l) %s\n", err->pos.line, err->pos.column, err->text); + } + + last_rank = err->rank; + } +} + +b32 onyx_has_errors() { + return bh_arr_length(errors.errors) > 0; +} + +void onyx_clear_errors() { + if (context.cycle_detected) return; + + bh_arr_set_length(errors.errors, 0); +} + +void onyx_submit_error(OnyxError error) { + bh_arr_push(errors.errors, error); +} + +void onyx_report_error(OnyxFilePos pos, OnyxErrorRank rank, char * format, ...) { + + va_list vargs; + va_start(vargs, format); + char* msg = bh_bprintf_va(format, vargs); + va_end(vargs); + + OnyxError err = { + .pos = pos, + .rank = rank, + .text = bh_strdup(errors.msg_alloc, msg), + }; + + bh_arr_push(errors.errors, err); +} + +void onyx_submit_warning(OnyxError error) { + bh_file_contents file_contents = { 0 }; + bh_arr_each(bh_file_contents, fc, *errors.file_contents) { + if (!strcmp(fc->filename, error.pos.filename)) { + file_contents = *fc; + break; + } + } + + print_detailed_message(&error, &file_contents); +} + +// This definitely doesn't do what I thought it did? +void onyx_report_warning(OnyxFilePos pos, char* format, ...) { + va_list vargs; + va_start(vargs, format); + char* msg = bh_bprintf_va(format, vargs); + va_end(vargs); + + OnyxError err = { + .pos = pos, + .rank = Error_Warning, + .text = msg, + }; + + bh_arr_push(errors.errors, err); + + /* + + bh_file_contents file_contents = { 0 }; + bh_arr_each(bh_file_contents, fc, *errors.file_contents) { + if (!strcmp(fc->filename, pos.filename)) { + file_contents = *fc; + break; + } + } + + print_detailed_message(&err, &file_contents); + */ +} diff --git a/compiler/src/lex.c b/compiler/src/lex.c new file mode 100644 index 00000000..eba315ed --- /dev/null +++ b/compiler/src/lex.c @@ -0,0 +1,548 @@ +#include "bh.h" +#include "lex.h" +#include "utils.h" +#include "errors.h" + +u64 lexer_lines_processed = 0; +u64 lexer_tokens_processed = 0; + +static const char* token_type_names[] = { + "TOKEN_TYPE_UNKNOWN", + "TOKEN_TYPE_END_STREAM", + + "TOKEN_TYPE_COMMENT", + + "package", + "struct", + "enum", + "use", + "if", + "else", + "elseif", + "return", + "global", + "cast", + "while", + "for", + "break", + "continue", + "sizeof", + "alignof", + "typeof", + "defer", + "do", + "case", + "switch", + "fallthrough", + "macro", + "interface", + "where", + + "->", + "<-", + "---", + "|>", + + ">=", + "<=", + "==", + "!=", + "+=", + "-=", + "*=", + "/=", + "%=", + "&=", + "|=", + "^=", + "&&", + "||", + "<<", + ">>", + ">>>", + "<<=", + ">>=", + ">>>=", + "..", + "~~", + + "TOKEN_TYPE_SYMBOL", + "TOKEN_TYPE_LITERAL_STRING", + "TOKEN_TYPE_LITERAL_INTEGER", + "TOKEN_TYPE_LITERAL_FLOAT", + "true", + "false", + + "NOTE", + + "TOKEN_TYPE_COUNT" +}; + +#ifndef LITERAL_TOKEN +#define LITERAL_TOKEN(token, word, token_type) \ + if (token_lit(tokenizer, &tk, token, word, token_type)) goto token_parsed; +#endif + +#ifndef INCREMENT_CURR_TOKEN +#define INCREMENT_CURR_TOKEN(tkn) { \ + if (*(tkn)->curr == '\n') { \ + (tkn)->line_number++; \ + (tkn)->line_start = (tkn)->curr + 1; \ + } \ + (tkn)->curr++; \ +} +#endif + +#define char_is_alphanum(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') || ((c) >= '0' && (c) <= '9')) +#define char_is_num(c) ((c) >= '0' && (c) <= '9') +#define char_is_alpha(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) + +static inline b32 token_lit(OnyxTokenizer* tokenizer, OnyxToken* tk, char* lit, b32 is_word, TokenType type) { + i64 len = 0; + char* ptr1 = tokenizer->curr; + char* ptr2 = lit; + while (*ptr2 != '\0' && *ptr1 == *ptr2) ptr1++, ptr2++, len++; + if (*ptr2 != '\0') return 0; + + if (is_word && (char_is_alphanum(*ptr1) || *ptr1 == '_')) + return 0; + + tk->type = type; + tk->text = tokenizer->curr; + tk->length = len; + tk->pos.line = tokenizer->line_number; + tk->pos.column = (i32)(tokenizer->curr - tokenizer->line_start) + 1; + + tokenizer->curr += len; + + return 1; +} + +const char* token_name(TokenType tkn_type) { + if (tkn_type < Token_Type_Ascii_End) { + return bh_aprintf(global_scratch_allocator, "%c", (char) tkn_type); + } else { + return token_type_names[tkn_type - Token_Type_Ascii_End]; + } +} + +void token_toggle_end(OnyxToken* tkn) { + static char backup = 0; + char tmp = tkn->text[tkn->length]; + tkn->text[tkn->length] = backup; + backup = tmp; +} + +OnyxToken* onyx_get_token(OnyxTokenizer* tokenizer) { + OnyxToken tk; + + // Skip whitespace + while (1) { + if (tokenizer->curr == tokenizer->end) break; + + switch (*tokenizer->curr) { + case ' ': + case '\n': + case '\t': + case '\r': + INCREMENT_CURR_TOKEN(tokenizer); + break; + default: + goto whitespace_skipped; + } + } + +whitespace_skipped: + + tk.type = Token_Type_Unknown; + tk.text = tokenizer->curr; + tk.length = 1; + tk.pos.line_start = tokenizer->line_start; + tk.pos.filename = tokenizer->filename; + tk.pos.line = tokenizer->line_number; + tk.pos.column = (u16)(tokenizer->curr - tokenizer->line_start) + 1; + + if (tokenizer->curr == tokenizer->end) { + tk.type = Token_Type_End_Stream; + goto token_parsed; + } + + // She-bang + if (tokenizer->curr == tokenizer->start) { + if (*tokenizer->curr == '#' && *(tokenizer->curr + 1) == '!') { + tk.type = Token_Type_Comment; + + tokenizer->curr += 2; + while (*tokenizer->curr++ != '\n' && tokenizer->curr != tokenizer->end); + + tk.length = tokenizer->curr - tk.text; + goto token_parsed; + } + } + + // Comments + if (*tokenizer->curr == '/' && *(tokenizer->curr + 1) == '/') { + tokenizer->curr += 2; + tk.type = Token_Type_Comment; + tk.text = tokenizer->curr; + + while (*tokenizer->curr != '\n' && tokenizer->curr != tokenizer->end) { + INCREMENT_CURR_TOKEN(tokenizer); + } + + tk.length = tokenizer->curr - tk.text - 2; + goto token_parsed; + } + + if (*tokenizer->curr == '/' && *(tokenizer->curr + 1) == '*') { + tokenizer->curr += 2; + tk.type = Token_Type_Comment; + tk.text = tokenizer->curr; + + i32 comment_depth = 1; + + while (comment_depth > 0 && tokenizer->curr != tokenizer->end) { + if (*tokenizer->curr == '/' && *(tokenizer->curr + 1) == '*') { + tokenizer->curr += 2; + comment_depth += 1; + } + + else if (*tokenizer->curr == '*' && *(tokenizer->curr + 1) == '/') { + tokenizer->curr += 2; + comment_depth -= 1; + } + + else { + INCREMENT_CURR_TOKEN(tokenizer); + } + } + + goto token_parsed; + } + + if (*tk.text == '"' && *(tk.text + 1) == '"' && *(tk.text + 2) == '"') { + u64 len = 0; + + INCREMENT_CURR_TOKEN(tokenizer); + INCREMENT_CURR_TOKEN(tokenizer); + INCREMENT_CURR_TOKEN(tokenizer); + + while (!(*tokenizer->curr == '"' && *(tokenizer->curr + 1) == '"' && *(tokenizer->curr + 2) == '"')) { + len++; + INCREMENT_CURR_TOKEN(tokenizer); + } + + INCREMENT_CURR_TOKEN(tokenizer); + INCREMENT_CURR_TOKEN(tokenizer); + INCREMENT_CURR_TOKEN(tokenizer); + + tk.text += 3; + tk.length = len; + tk.type = Token_Type_Literal_String; + goto token_parsed; + } + + // String literal + if (*tk.text == '"') { + u64 len = 0; + u64 slash_count = 0; + + INCREMENT_CURR_TOKEN(tokenizer); + + while (!(*tokenizer->curr == '"' && slash_count == 0)) { + len++; + + // if (*tokenizer->curr == '\n') { + // onyx_report_error(tk.pos, "String literal not terminated by end of line."); + // } + + if (*tokenizer->curr == '\\') { + slash_count += 1; + slash_count %= 2; + } else { + slash_count = 0; + } + + INCREMENT_CURR_TOKEN(tokenizer); + } + + INCREMENT_CURR_TOKEN(tokenizer); + + tk.text++; + tk.type = Token_Type_Literal_String; + tk.length = len; + goto token_parsed; + } + + // Hex literal + if (*tokenizer->curr == '0' && *(tokenizer->curr + 1) == 'x' && charset_contains("0123456789abcdefABCDEF", *(tokenizer->curr + 2))) { + INCREMENT_CURR_TOKEN(tokenizer); + INCREMENT_CURR_TOKEN(tokenizer); + u32 len = 3; + while (char_is_num(*(tokenizer->curr + 1)) || charset_contains("abcdefABCDEF", *(tokenizer->curr + 1))) { + len++; + INCREMENT_CURR_TOKEN(tokenizer); + } + + tk.type = Token_Type_Literal_Integer; + tk.length = len; + + INCREMENT_CURR_TOKEN(tokenizer); + goto token_parsed; + } + + if (*tokenizer->curr == '@') { + INCREMENT_CURR_TOKEN(tokenizer); + u32 len = 2; + while (char_is_alphanum(*(tokenizer->curr + 1)) || *(tokenizer->curr + 1) == '_') { + len++; + INCREMENT_CURR_TOKEN(tokenizer); + } + + tk.type = Token_Type_Note; + tk.length = len; + INCREMENT_CURR_TOKEN(tokenizer); + goto token_parsed; + } + + // Number literal + if (char_is_num(*tokenizer->curr) + || (*(tokenizer->curr) == '.' && char_is_num(*(tokenizer->curr + 1)))) { + tk.type = Token_Type_Literal_Integer; + + b32 hit_decimal = 0; + if (*tokenizer->curr == '.') hit_decimal = 1; + + u32 len = 1; + while (char_is_num(*(tokenizer->curr + 1)) + || (!hit_decimal && *(tokenizer->curr + 1) == '.' && *(tokenizer->curr + 2) != '.')) { + len++; + INCREMENT_CURR_TOKEN(tokenizer); + + if (*tokenizer->curr == '.') hit_decimal = 1; + } + + if (*(tokenizer->curr + 1) == 'f') { + tk.type = Token_Type_Literal_Float; + + len++; + INCREMENT_CURR_TOKEN(tokenizer); + } + + if (hit_decimal) tk.type = Token_Type_Literal_Float; + + tk.length = len; + + INCREMENT_CURR_TOKEN(tokenizer); + goto token_parsed; + } + + char curr = *tokenizer->curr; + switch (curr) { + case 'a': + LITERAL_TOKEN("alignof", 1, Token_Type_Keyword_Alignof); + break; + case 'b': + LITERAL_TOKEN("break", 1, Token_Type_Keyword_Break); + break; + case 'c': + LITERAL_TOKEN("case", 1, Token_Type_Keyword_Case); + LITERAL_TOKEN("cast", 1, Token_Type_Keyword_Cast); + LITERAL_TOKEN("continue", 1, Token_Type_Keyword_Continue); + break; + case 'd': + LITERAL_TOKEN("defer", 1, Token_Type_Keyword_Defer); + LITERAL_TOKEN("do", 1, Token_Type_Keyword_Do); + break; + case 'e': + LITERAL_TOKEN("enum", 1, Token_Type_Keyword_Enum); + LITERAL_TOKEN("elseif", 1, Token_Type_Keyword_Elseif); + LITERAL_TOKEN("else", 1, Token_Type_Keyword_Else); + break; + case 'f': + LITERAL_TOKEN("for", 1, Token_Type_Keyword_For); + LITERAL_TOKEN("false", 1, Token_Type_Literal_False); + LITERAL_TOKEN("fallthrough", 1, Token_Type_Keyword_Fallthrough); + break; + case 'g': + LITERAL_TOKEN("global", 1, Token_Type_Keyword_Global); + break; + case 'i': + LITERAL_TOKEN("if", 1, Token_Type_Keyword_If); + LITERAL_TOKEN("interface", 1, Token_Type_Keyword_Interface); + break; + case 'm': + LITERAL_TOKEN("macro", 1, Token_Type_Keyword_Macro); + break; + case 'p': + LITERAL_TOKEN("package", 1, Token_Type_Keyword_Package); + break; + case 'r': + LITERAL_TOKEN("return", 1, Token_Type_Keyword_Return); + break; + case 's': + LITERAL_TOKEN("struct", 1, Token_Type_Keyword_Struct); + LITERAL_TOKEN("sizeof", 1, Token_Type_Keyword_Sizeof); + LITERAL_TOKEN("switch", 1, Token_Type_Keyword_Switch); + break; + case 't': + LITERAL_TOKEN("true", 1, Token_Type_Literal_True); + LITERAL_TOKEN("typeof", 1, Token_Type_Keyword_Typeof); + break; + case 'u': + LITERAL_TOKEN("use", 1, Token_Type_Keyword_Use); + break; + case 'w': + LITERAL_TOKEN("while", 1, Token_Type_Keyword_While); + LITERAL_TOKEN("where", 1, Token_Type_Keyword_Where); + break; + + case '-': + LITERAL_TOKEN("->", 0, Token_Type_Right_Arrow); + LITERAL_TOKEN("---", 0, Token_Type_Empty_Block); + LITERAL_TOKEN("-=", 0, Token_Type_Minus_Equal); + break; + + case '<': + LITERAL_TOKEN("<=", 0, Token_Type_Less_Equal); + LITERAL_TOKEN("<-", 0, Token_Type_Right_Arrow); + LITERAL_TOKEN("<<=", 0, Token_Type_Shl_Equal); + LITERAL_TOKEN("<<", 0, Token_Type_Shift_Left); + break; + + case '>': + LITERAL_TOKEN(">=", 0, Token_Type_Greater_Equal); + LITERAL_TOKEN(">>>=", 0, Token_Type_Sar_Equal); + LITERAL_TOKEN(">>=", 0, Token_Type_Shr_Equal); + LITERAL_TOKEN(">>>", 0, Token_Type_Shift_Arith_Right); + LITERAL_TOKEN(">>", 0, Token_Type_Shift_Right); + break; + + case '&': + LITERAL_TOKEN("&&", 0, Token_Type_And_And); + LITERAL_TOKEN("&=", 0, Token_Type_And_Equal); + break; + + case '|': + LITERAL_TOKEN("|>", 0, Token_Type_Pipe); + LITERAL_TOKEN("||", 0, Token_Type_Or_Or); + LITERAL_TOKEN("|=", 0, Token_Type_Or_Equal); + break; + + case '=': + LITERAL_TOKEN("==", 0, Token_Type_Equal_Equal); + break; + + case '!': + LITERAL_TOKEN("!=", 0, Token_Type_Not_Equal); + break; + + case '+': + LITERAL_TOKEN("+=", 0, Token_Type_Plus_Equal); + break; + + case '*': + LITERAL_TOKEN("*=", 0, Token_Type_Star_Equal); + break; + + case '^': + LITERAL_TOKEN("^=", 0, Token_Type_Xor_Equal); + break; + + case '/': + LITERAL_TOKEN("/=", 0, Token_Type_Fslash_Equal); + break; + + case '%': + LITERAL_TOKEN("%=", 0, Token_Type_Percent_Equal); + break; + + case '.': + LITERAL_TOKEN("..", 0, Token_Type_Dot_Dot); + break; + + case '~': + LITERAL_TOKEN("~~", 0, Token_Type_Tilde_Tilde); + break; + } + + // Symbols + if (char_is_alpha(*tk.text) || *tokenizer->curr == '_') { + u64 len = 0; + while (char_is_alphanum(*tokenizer->curr) || *tokenizer->curr == '_') { + len++; + INCREMENT_CURR_TOKEN(tokenizer); + } + + tk.length = len; + tk.type = Token_Type_Symbol; + goto token_parsed; + } + + + tk.type = (TokenType) *tokenizer->curr; + INCREMENT_CURR_TOKEN(tokenizer); + +token_parsed: + tk.pos.length = (u16) tk.length; + bh_arr_push(tokenizer->tokens, tk); + + return &tokenizer->tokens[bh_arr_length(tokenizer->tokens) - 1]; +} + +OnyxTokenizer onyx_tokenizer_create(bh_allocator allocator, bh_file_contents *fc) { + OnyxTokenizer tknizer = { + .start = fc->data, + .curr = fc->data, + .end = bh_pointer_add(fc->data, fc->length), + + .filename = fc->filename, + + .line_number = 1, + .line_start = fc->data, + .tokens = NULL, + }; + + bh_arr_new(allocator, tknizer.tokens, 1 << 12); + return tknizer; +} + +void onyx_tokenizer_free(OnyxTokenizer* tokenizer) { + bh_arr_free(tokenizer->tokens); +} + +void onyx_lex_tokens(OnyxTokenizer* tokenizer) { + OnyxToken* tk; + do { + tk = onyx_get_token(tokenizer); + } while (tk->type != Token_Type_End_Stream); + + lexer_lines_processed += tokenizer->line_number - 1; + lexer_tokens_processed += bh_arr_length(tokenizer->tokens); +} + +b32 token_equals(OnyxToken* tkn1, OnyxToken* tkn2) { + if (tkn1->length != tkn2->length) return 0; + fori (i, 0, tkn1->length) + if (tkn1->text[i] != tkn2->text[i]) return 0; + return 1; +} + +b32 token_text_equals(OnyxToken* tkn, char* text) { + i32 text_len = strlen(text); + if (tkn->length != text_len) return 0; + + fori (i, 0, tkn->length) + if (tkn->text[i] != text[i]) return 0; + + return 1; +} + +b32 token_same_file(OnyxToken *tkn1, OnyxToken *tkn2) { + if (!tkn1 || !tkn2) return 0; + + if (tkn1->pos.filename == tkn2->pos.filename) return 1; + + // :Security? + return strcmp(tkn1->pos.filename, tkn2->pos.filename) == 0; +} diff --git a/compiler/src/onyx.c b/compiler/src/onyx.c new file mode 100644 index 00000000..9433df38 --- /dev/null +++ b/compiler/src/onyx.c @@ -0,0 +1,800 @@ +// #define BH_DEBUG +#define BH_DEFINE +#define BH_NO_TABLE +#define STB_DS_IMPLEMENTATION +#include "bh.h" + +#include "lex.h" +#include "errors.h" +#include "parser.h" +#include "utils.h" +#include "wasm_emit.h" +#include "doc.h" + +#define VERSION "v0.1.0" + + +// #ifndef CORE_INSTALLATION +// #ifdef _BH_LINUX +// #define CORE_INSTALLATION "/usr/share/onyx" +// #elif defined(_WIN32) || defined(_WIN64) +// #define CORE_INSTALLATION "\\dev\\onyx\\" +// #endif +// #endif + + + + +Context context; + + +static const char* docstring = "Onyx compiler version " VERSION "\n" + "\n" + "The compiler for the Onyx programming language, created by Brendan Hansen.\n" + "\n" + "Usage:\n" + "\tonyx compile [-o ] [--verbose] \n" +#ifdef ENABLE_RUN_WITH_WASMER + "\tonyx run -- \n" +#endif + // "\tonyx doc \n" + "\tonyx help\n" + "\n" + "Flags:\n" + "\t List of initial files\n" + "\t-o Specify the target file (default: out.wasm).\n" + "\t--runtime, -r Specifies the runtime. Can be: onyx, wasi, js, custom.\n" + "\t--verbose, -V Verbose output.\n" + "\t -VV Very verbose output.\n" + "\t -VVV Very very verbose output (to be used by compiler developers).\n" + "\t--wasm-mvp Use only WebAssembly MVP features.\n" + "\t--multi-threaded Enables multi-threading for this compilation.\n" + "\t--doc \n" + "\t--generate-foreign-info\n" + "\n" + "Developer flags:\n" + "\t--print-function-mappings Prints a mapping from WASM function index to source location.\n" + "\t--print-static-if-results Prints the conditional result of each #if statement. Useful for debugging.\n" + "\t--print-notes Prints the location of notes throughout the loaded code.\n" + "\t--no-colors Disables colors in the error message.\n" + "\t--no-file-contents Disables '#file_contents' for security.\n" + "\n"; + + +static CompileOptions compile_opts_parse(bh_allocator alloc, int argc, char *argv[]) { + CompileOptions options = { + .allocator = alloc, + .action = ONYX_COMPILE_ACTION_PRINT_HELP, + + .verbose_output = 0, + .fun_output = 0, + .print_function_mappings = 0, + .no_file_contents = 0, + + .use_post_mvp_features = 1, + .use_multi_threading = 0, + + .runtime = Runtime_Onyx, + + .files = NULL, + .target_file = "out.wasm", + + .documentation_file = NULL, + + .debug_enabled = 0, + + .passthrough_argument_count = 0, + .passthrough_argument_data = NULL, + }; + + bh_arr_new(alloc, options.files, 2); + bh_arr_new(alloc, options.included_folders, 2); + + char* core_installation; + #ifdef _BH_LINUX + core_installation = CORE_INSTALLATION; + #endif + #ifdef _BH_WINDOWS + core_installation = bh_alloc_array(global_heap_allocator, u8, 512); + GetEnvironmentVariableA("ONYX_PATH", core_installation, 512); + #endif + + // NOTE: Add the current folder + bh_arr_push(options.included_folders, core_installation); + bh_arr_push(options.included_folders, "."); + + if (argc == 1) return options; + i32 arg_parse_start = 1; + + if (!strcmp(argv[1], "help")) options.action = ONYX_COMPILE_ACTION_PRINT_HELP; + if (!strcmp(argv[1], "compile")) { + options.action = ONYX_COMPILE_ACTION_COMPILE; + arg_parse_start = 2; + } + #ifdef ENABLE_RUN_WITH_WASMER + else if (!strcmp(argv[1], "run")) { + options.action = ONYX_COMPILE_ACTION_RUN; + arg_parse_start = 2; + } + #endif + else options.action = ONYX_COMPILE_ACTION_COMPILE; + + if (options.action != ONYX_COMPILE_ACTION_PRINT_HELP) { + fori(i, arg_parse_start, argc) { + if (!strcmp(argv[i], "-o")) { + options.target_file = argv[++i]; + } + else if (!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-V")) { + options.verbose_output = 1; + } + else if (!strcmp(argv[i], "-VV")) { + options.verbose_output = 2; + } + else if (!strcmp(argv[i], "-VVV")) { + options.verbose_output = 3; + } + else if (!strcmp(argv[i], "--print-function-mappings")) { + options.print_function_mappings = 1; + } + else if (!strcmp(argv[i], "--print-static-if-results")) { + options.print_static_if_results = 1; + } + else if (!strcmp(argv[i], "--print-notes")) { + options.print_notes = 1; + } + else if (!strcmp(argv[i], "--no-colors")) { + options.no_colors = 1; + } + else if (!strcmp(argv[i], "--no-file-contents")) { + options.no_file_contents = 1; + } + else if (!strcmp(argv[i], "--wasm-mvp")) { + options.use_post_mvp_features = 0; + } + else if (!strcmp(argv[i], "--multi-threaded")) { + options.use_multi_threading = 1; + } + else if (!strcmp(argv[i], "--generate-foreign-info")) { + options.generate_foreign_info = 1; + } + else if (!strcmp(argv[i], "-I")) { + bh_arr_push(options.included_folders, argv[++i]); + } + else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--runtime")) { + i += 1; + if (!strcmp(argv[i], "onyx")) options.runtime = Runtime_Onyx; + else if (!strcmp(argv[i], "wasi")) options.runtime = Runtime_Wasi; + else if (!strcmp(argv[i], "js")) options.runtime = Runtime_Js; + else if (!strcmp(argv[i], "custom")) options.runtime = Runtime_Custom; + else { + bh_printf("WARNING: '%s' is not a valid runtime. Defaulting to 'onyx'.\n", argv[i]); + options.runtime = Runtime_Wasi; + } + } + else if (!strcmp(argv[i], "--doc")) { + options.documentation_file = argv[++i]; + } + else if (!strcmp(argv[i], "--debug")) { + options.debug_enabled = 1; + } + else if (!strcmp(argv[i], "--")) { + options.passthrough_argument_count = argc - i - 1; + options.passthrough_argument_data = &argv[i + 1]; + break; + } +#if defined(_BH_LINUX) + // NOTE: Fun output is only enabled for Linux because Windows command line + // is not ANSI compatible and for a silly feature, I don't want to learn + // how to properly do arbitrary graphics in it. + else if (!strcmp(argv[i], "--fun") || !strcmp(argv[i], "-F")) { + options.fun_output = 1; + } +#endif + else { + bh_arr_push(options.files, argv[i]); + } + } + } + + // NOTE: Always enable multi-threading for the Onyx runtime. + if (options.runtime == Runtime_Onyx) { + options.use_multi_threading = 1; + } + + return options; +} + +static void compile_opts_free(CompileOptions* opts) { + bh_arr_free(opts->files); + bh_arr_free(opts->included_folders); +} + + + + + +typedef enum CompilerProgress { + ONYX_COMPILER_PROGRESS_ERROR, + ONYX_COMPILER_PROGRESS_FAILED_OUTPUT, + ONYX_COMPILER_PROGRESS_SUCCESS +} CompilerProgress; + +static OnyxToken implicit_load_token = { '#', 1, 0, { 0, 0, 0, 0, 0 } }; +static AstInclude* create_load(bh_allocator alloc, char* filename) { + AstInclude* include_node = onyx_ast_node_new(alloc, sizeof(AstInclude), Ast_Kind_Load_File); + include_node->name = filename; + include_node->token = &implicit_load_token; + + return include_node; +} + +// HACK +static u32 special_global_entities_remaining = 3; +static Entity *runtime_info_types_entity; +static Entity *runtime_info_foreign_entity; +static Entity *runtime_info_proc_tags_entity; + +static void context_init(CompileOptions* opts) { + types_init(); + + context.options = opts; + context.cycle_detected = 0; + context.cycle_almost_detected = 0; + + OnyxFilePos internal_location = { 0 }; + internal_location.filename = ""; + internal_location.line = 1; + internal_location.column = 1; + context.global_scope = scope_create(global_heap_allocator, NULL, internal_location); + sh_new_arena(context.packages); + + // NOTE: This will be initialized upon the first call to entity_heap_insert. + context.entities.next_id = 0; + context.entities.entities = NULL; + + onyx_errors_init(&context.loaded_files); + + context.token_alloc = global_heap_allocator; + + // NOTE: Create the arena where tokens and AST nodes will exist + // Prevents nodes from being scattered across memory due to fragmentation + bh_arena_init(&context.ast_arena, global_heap_allocator, 16 * 1024 * 1024); // 16MB + context.ast_alloc = bh_arena_allocator(&context.ast_arena); + + context.wasm_module = bh_alloc_item(global_heap_allocator, OnyxWasmModule); + *context.wasm_module = onyx_wasm_module_create(global_heap_allocator); + + entity_heap_init(&context.entities); + + // NOTE: Add builtin entities to pipeline. + entity_heap_insert(&context.entities, ((Entity) { + .state = Entity_State_Parse_Builtin, + .type = Entity_Type_Load_File, + .package = NULL, + .include = create_load(context.ast_alloc, "core/builtin"), + })); + + entity_heap_insert(&context.entities, ((Entity) { + .state = Entity_State_Parse_Builtin, + .type = Entity_Type_Load_File, + .package = NULL, + .include = create_load(context.ast_alloc, "core/runtime/build_opts"), + })); + + if (context.options->runtime != Runtime_Custom) { + runtime_info_types_entity = entity_heap_insert(&context.entities, ((Entity) { + .state = Entity_State_Parse, + .type = Entity_Type_Load_File, + .package = NULL, + .include = create_load(context.ast_alloc, "core/runtime/info/types"), + })); + runtime_info_foreign_entity = entity_heap_insert(&context.entities, ((Entity) { + .state = Entity_State_Parse, + .type = Entity_Type_Load_File, + .package = NULL, + .include = create_load(context.ast_alloc, "core/runtime/info/foreign_blocks"), + })); + runtime_info_proc_tags_entity = entity_heap_insert(&context.entities, ((Entity) { + .state = Entity_State_Parse, + .type = Entity_Type_Load_File, + .package = NULL, + .include = create_load(context.ast_alloc, "core/runtime/info/proc_tags"), + })); + } + + add_entities_for_node(NULL, (AstNode *) &builtin_stack_top, context.global_scope, NULL); + add_entities_for_node(NULL, (AstNode *) &builtin_heap_start, context.global_scope, NULL); + add_entities_for_node(NULL, (AstNode *) &builtin_tls_base, context.global_scope, NULL); + add_entities_for_node(NULL, (AstNode *) &builtin_tls_size, context.global_scope, NULL); + + // NOTE: Add all files passed by command line to the queue + bh_arr_each(const char *, filename, opts->files) { + AstInclude* load_node = create_load(context.ast_alloc, (char *) *filename); + add_entities_for_node(NULL, (AstNode *) load_node, context.global_scope, NULL); + } +} + +static void context_free() { + bh_arena_free(&context.ast_arena); + bh_arr_free(context.loaded_files); + + compile_opts_free(context.options); +} + +static void parse_source_file(bh_file_contents* file_contents) { + // :Remove passing the allocators as parameters + OnyxTokenizer tokenizer = onyx_tokenizer_create(context.token_alloc, file_contents); + onyx_lex_tokens(&tokenizer); + + file_contents->line_count = tokenizer.line_number; + + OnyxParser parser = onyx_parser_create(context.ast_alloc, &tokenizer); + onyx_parse(&parser); + onyx_parser_free(&parser); +} + +static b32 process_source_file(char* filename, OnyxFilePos error_pos) { + bh_arr_each(bh_file_contents, fc, context.loaded_files) { + // Duplicates are detected here and since these filenames will be the full path, + // string comparing them should be all that is necessary. + if (!strcmp(fc->filename, filename)) return 1; + } + + bh_file file; + bh_file_error err = bh_file_open(&file, filename); + if (err != BH_FILE_ERROR_NONE) { + if (context.cycle_detected) { + onyx_report_error(error_pos, Error_Critical, "Failed to open file %s", filename); + } + return 0; + } + + bh_file_contents fc = bh_file_read_contents(context.token_alloc, &file); + bh_file_close(&file); + + bh_arr_push(context.loaded_files, fc); + + if (context.options->verbose_output == 2) + bh_printf("Processing source file: %s (%d bytes)\n", file.filename, fc.length); + + parse_source_file(&bh_arr_last(context.loaded_files)); + return 1; +} + +static b32 process_load_entity(Entity* ent) { + assert(ent->type == Entity_Type_Load_File || ent->type == Entity_Type_Load_Path); + AstInclude* include = ent->include; + + if (include->kind == Ast_Kind_Load_File) { + // :RelativeFiles + const char* parent_file = include->token->pos.filename; + if (parent_file == NULL) parent_file = "."; + + char* parent_folder = bh_path_get_parent(parent_file, global_scratch_allocator); + + char* filename = bh_lookup_file(include->name, parent_folder, ".onyx", 1, context.options->included_folders, 1); + char* formatted_name = bh_strdup(global_heap_allocator, filename); + + return process_source_file(formatted_name, include->token->pos); + + } else if (include->kind == Ast_Kind_Load_All) { + const char* parent_file = include->token->pos.filename; + if (parent_file == NULL) parent_file = "."; + + char* parent_folder = bh_path_get_parent(parent_file, global_scratch_allocator); + char folder[512]; + if (bh_str_starts_with(include->name, "./")) { + bh_snprintf(folder, 511, "%s/%s", parent_folder, include->name + 2); + } else { + bh_snprintf(folder, 511, "%s", include->name); + } + + // This does not take into account #load_path'd folders... + + bh_path_convert_separators(folder); + bh_dir dir = bh_dir_open(folder); + if (dir == NULL) { + onyx_report_error(include->token->pos, Error_Critical, "Could not find folder '%s'.", folder); + return 0; + } + + bh_dirent entry; + b32 success = 1; + char fullpath[512]; + while (bh_dir_read(dir, &entry)) { + if (entry.type == BH_DIRENT_FILE && bh_str_ends_with(entry.name, ".onyx")) { + bh_snprintf(fullpath, 511, "%s/%s", folder, entry.name); + bh_path_convert_separators(fullpath); + u8* formatted_name = bh_path_get_full_name(fullpath, global_heap_allocator); + success = process_source_file(formatted_name, include->token->pos); + if (!success) break; + } + } + + bh_dir_close(dir); + return success; + + } else if (include->kind == Ast_Kind_Load_Path) { + bh_arr_push(context.options->included_folders, include->name); + + } else if (include->kind == Ast_Kind_Library_Path) { + bh_arr_push(context.wasm_module->library_paths, include->name); + } + + return 1; +} + +static b32 process_entity(Entity* ent) { + static char verbose_output_buffer[512]; + if (context.options->verbose_output == 3) { + if (ent->expr && ent->expr->token) + snprintf(verbose_output_buffer, 511, + "%20s | %24s (%d, %d) | %s:%i:%i \n", + entity_state_strings[ent->state], + entity_type_strings[ent->type], + (u32) ent->macro_attempts, + (u32) ent->micro_attempts, + ent->expr->token->pos.filename, + ent->expr->token->pos.line, + ent->expr->token->pos.column); + + else if (ent->expr) + snprintf(verbose_output_buffer, 511, + "%20s | %24s (%d, %d) \n", + entity_state_strings[ent->state], + entity_type_strings[ent->type], + (u32) ent->macro_attempts, + (u32) ent->micro_attempts); + } + + // CLEANUP: There should be a nicer way to track if the builtins have + // already been initialized. + static b32 builtins_initialized = 0; + + EntityState before_state = ent->state; + switch (before_state) { + case Entity_State_Error: + if (ent->type != Entity_Type_Error) { + onyx_report_error(ent->expr->token->pos, Error_Critical, "Error entity unexpected. This is definitely a compiler bug"); + } else { + onyx_report_error(ent->error->token->pos, Error_Critical, "Static error occured: '%b'", ent->error->error_msg->text, ent->error->error_msg->length); + } + break; + + case Entity_State_Parse_Builtin: + process_load_entity(ent); + ent->state = Entity_State_Finalized; + break; + + case Entity_State_Introduce_Symbols: + // Currently, introducing symbols is handled in the symbol resolution + // function. Maybe there should be a different place where that happens? + symres_entity(ent); + break; + + case Entity_State_Parse: + if (!builtins_initialized) { + builtins_initialized = 1; + initialize_builtins(context.ast_alloc); + introduce_build_options(context.ast_alloc); + } + + // GROSS + if (special_global_entities_remaining == 0) { + special_global_entities_remaining--; + initalize_special_globals(); + } + + if (process_load_entity(ent)) { + // GROSS + if (ent == runtime_info_types_entity + || ent == runtime_info_proc_tags_entity + || ent == runtime_info_foreign_entity) { + special_global_entities_remaining--; + } + + ent->state = Entity_State_Finalized; + } else { + ent->macro_attempts++; + } + break; + + case Entity_State_Resolve_Symbols: symres_entity(ent); break; + case Entity_State_Check_Types: check_entity(ent); break; + case Entity_State_Code_Gen: emit_entity(ent); break; + } + + b32 changed = ent->state != before_state; + if (context.options->verbose_output == 3) { + if (changed) printf("SUCCESS to %20s | %s", entity_state_strings[ent->state], verbose_output_buffer); + else printf("YIELD to %20s | %s", entity_state_strings[ent->state], verbose_output_buffer); + } + + return changed; +} + +// Just having fun with some visual output - brendanfh 2020/12/14 +#if defined(_BH_LINUX) +static void output_dummy_progress_bar() { + EntityHeap* eh = &context.entities; + if (bh_arr_length(eh->entities) == 0) return; + + static const char* state_colors[] = { + "\e[91m", "\e[93m", "\e[94m", "\e[93m", + "\e[97m", "\e[95m", "\e[96m", "\e[92m", "\e[91m", + }; + + printf("\e[2;1H"); + + for (i32 i = 0; i < Entity_State_Count - 1; i++) { + if (i % 2 == 0) printf("\n"); + printf("%s %25s \xe2\x96\x88 ", state_colors[i], entity_state_strings[i]); + } + + printf("\n\n"); + + for (i32 i = 0; i < Entity_Type_Count; i++) { + if (eh->type_count[i] == 0) printf("\e[90m"); + else if ((i32) eh->entities[0]->type == i) printf("\e[92m"); + else printf("\e[97m"); + + printf("%25s (%4d) | ", entity_type_strings[i], eh->type_count[i]); + + printf("\e[0K"); + for (i32 j = 0; j < Entity_State_Count; j++) { + if (eh->all_count[j][i] == 0) continue; + + printf("%s", state_colors[j]); + + i32 count = (eh->all_count[j][i] >> 5) + 1; + for (i32 c = 0; c < count * 2; c++) printf("\xe2\x96\x88"); + + printf("\e[0m"); + } + printf("\n"); + } +} +#endif + +static void dump_cycles() { + context.cycle_detected = 1; + Entity* ent; + + while (1) { + ent = entity_heap_top(&context.entities); + entity_heap_remove_top(&context.entities); + if (ent->state < Entity_State_Code_Gen) process_entity(ent); + else break; + + if (bh_arr_length(context.entities.entities) == 0) { + break; + } + } +} + +static i32 onyx_compile() { + u64 start_time = bh_time_curr(); + + if (context.options->fun_output) + printf("\e[2J"); + + while (!bh_arr_is_empty(context.entities.entities)) { + Entity* ent = entity_heap_top(&context.entities); + +#if defined(_BH_LINUX) + if (context.options->fun_output) { + output_dummy_progress_bar(); + + if (ent->expr->token) { + OnyxFilePos pos = ent->expr->token->pos; + printf("\e[0K%s on %s in %s:%d:%d\n", entity_state_strings[ent->state], entity_type_strings[ent->type], pos.filename, pos.line, pos.column); + } + + // Slowing things down for the effect + usleep(1000); + } +#endif + + entity_heap_remove_top(&context.entities); + b32 changed = process_entity(ent); + + // NOTE: VERY VERY dumb cycle breaking. Basically, remember the first entity that did + // not change (i.e. did not make any progress). Then everytime an entity doesn't change, + // check if it is the same entity. If it is, it means all other entities that were processed + // between the two occurences didn't make any progress either, and there must be a cycle. + // - brendanfh 2021/02/06 + // + // Because of the recent changes to the compiler architecture (again), this condition + // does not always hold anymore. There can be nodes that get scheduled multiple times + // before the "key" node that will unblock the progress. This means a more sophisticated + // cycle detection algorithm must be used. + // + static Entity* watermarked_node = NULL; + static u32 highest_watermark = 0; + if (!changed) { + if (!watermarked_node) { + watermarked_node = ent; + highest_watermark = bh_max(highest_watermark, ent->macro_attempts); + } + else if (watermarked_node == ent) { + if (ent->macro_attempts > highest_watermark) { + entity_heap_insert_existing(&context.entities, ent); + + if (context.cycle_almost_detected) { + dump_cycles(); + } else { + context.cycle_almost_detected = 1; + } + } + } + else if (watermarked_node->macro_attempts < ent->macro_attempts) { + watermarked_node = ent; + highest_watermark = bh_max(highest_watermark, ent->macro_attempts); + } + } else { + watermarked_node = NULL; + context.cycle_almost_detected = 0; + } + + if (onyx_has_errors()) return ONYX_COMPILER_PROGRESS_ERROR; + + if (ent->state != Entity_State_Finalized && ent->state != Entity_State_Failed) + entity_heap_insert_existing(&context.entities, ent); + } + + u64 duration = bh_time_duration(start_time); + + if (context.options->verbose_output > 0) { + // TODO: Replace these with bh_printf when padded formatting is added. + printf("\nStatistics:\n"); + printf(" Time taken: %lf seconds\n", (double) duration / 1000); + printf(" Processed %ld lines (%f lines/second).\n", lexer_lines_processed, ((f32) 1000 * lexer_lines_processed) / (duration)); + printf(" Processed %ld tokens (%f tokens/second).\n", lexer_tokens_processed, ((f32) 1000 * lexer_tokens_processed) / (duration)); + printf("\n"); + } + + return ONYX_COMPILER_PROGRESS_SUCCESS; +} + +static void link_wasm_module() { + Package *runtime_var_package = package_lookup("runtime.vars"); + assert(runtime_var_package); + + AstTyped *link_options_node = (AstTyped *) symbol_raw_resolve(runtime_var_package->scope, "link_options"); + Type *link_options_type = type_build_from_ast(context.ast_alloc, builtin_link_options_type); + + assert(unify_node_and_type(&link_options_node, link_options_type) == TYPE_MATCH_SUCCESS); + + OnyxWasmLinkOptions link_opts; + // CLEANUP: Properly handle this case. + assert(onyx_wasm_build_link_options_from_node(&link_opts, link_options_node)); + + onyx_wasm_module_link(context.wasm_module, &link_opts); +} + +static CompilerProgress onyx_flush_module() { + link_wasm_module(); + + // NOTE: Output to file + bh_file output_file; + if (bh_file_create(&output_file, context.options->target_file) != BH_FILE_ERROR_NONE) + return ONYX_COMPILER_PROGRESS_FAILED_OUTPUT; + + if (context.options->verbose_output) + bh_printf("Outputting to WASM file: %s\n", output_file.filename); + + // APPARENTLY... the WebAssembly Threading proposal says that the data segment initializations + // in a WASM module are copied into the linear memory EVERY time the module is instantiated, not + // just the first time. This means that if we are happily chugging along and modifying global state + // and then we spawn a thread, that thread will completely wipe all changes to the global and return + // it to its original state. This is horrible obviously, but the only thing that is more horrible is + // that the best way around this is to create a second WASM module that simply initializes the given + // data section. Then have a section module that is actually your code. For right now, this is going + // to be fine since the browser is really the only place that multi-threading can be used to any + // degree of competency. But still... This is god awful and I hope that there is some other way to + // around this down the line. + if (context.options->use_multi_threading && !context.options->use_post_mvp_features) { + bh_file data_file; + if (bh_file_create(&data_file, bh_aprintf(global_scratch_allocator, "%s.data", context.options->target_file)) != BH_FILE_ERROR_NONE) + return ONYX_COMPILER_PROGRESS_FAILED_OUTPUT; + + OnyxWasmModule* data_module = bh_alloc_item(global_heap_allocator, OnyxWasmModule); + *data_module = onyx_wasm_module_create(global_heap_allocator); + + data_module->data = context.wasm_module->data; + context.wasm_module->data = NULL; + + onyx_wasm_module_write_to_file(data_module, data_file); + onyx_wasm_module_write_to_file(context.wasm_module, output_file); + + bh_file_close(&data_file); + } else { + onyx_wasm_module_write_to_file(context.wasm_module, output_file); + } + + bh_file_close(&output_file); + + if (context.options->documentation_file != NULL) { + OnyxDocumentation docs = onyx_docs_generate(); + docs.format = Doc_Format_Human; + onyx_docs_emit(&docs, context.options->documentation_file); + } + + return ONYX_COMPILER_PROGRESS_SUCCESS; +} + +#ifdef ENABLE_RUN_WITH_WASMER +static b32 onyx_run() { + link_wasm_module(); + + bh_buffer code_buffer; + onyx_wasm_module_write_to_buffer(context.wasm_module, &code_buffer); + + onyx_run_initialize(context.options->debug_enabled); + + if (context.options->verbose_output > 0) + bh_printf("Running program:\n"); + + return onyx_run_wasm(code_buffer, context.options->passthrough_argument_count, context.options->passthrough_argument_data); +} +#endif + +int main(int argc, char *argv[]) { + + bh_scratch_init(&global_scratch, bh_heap_allocator(), 256 * 1024); // NOTE: 256 KiB + global_scratch_allocator = bh_scratch_allocator(&global_scratch); + + // SPEED: This used to be a managed heap allocator where all allocations + // were tracked and would be automatically freed at the end of execution. + // I don't know why I ever did that because that is the job of the operating + // system when a process exits. + global_heap_allocator = bh_heap_allocator(); + + CompileOptions compile_opts = compile_opts_parse(global_heap_allocator, argc, argv); + context_init(&compile_opts); + + CompilerProgress compiler_progress = ONYX_COMPILER_PROGRESS_ERROR; + switch (compile_opts.action) { + case ONYX_COMPILE_ACTION_PRINT_HELP: bh_printf(docstring); return 1; + + case ONYX_COMPILE_ACTION_COMPILE: + compiler_progress = onyx_compile(); + if (compiler_progress == ONYX_COMPILER_PROGRESS_SUCCESS) { + onyx_flush_module(); + } + break; + + #ifdef ENABLE_RUN_WITH_WASMER + case ONYX_COMPILE_ACTION_RUN: + compiler_progress = onyx_compile(); + if (compiler_progress == ONYX_COMPILER_PROGRESS_SUCCESS) { + if (!onyx_run()) { + compiler_progress = ONYX_COMPILER_PROGRESS_ERROR; + } + } + break; + #endif + + default: break; + } + + switch (compiler_progress) { + case ONYX_COMPILER_PROGRESS_ERROR: + onyx_errors_print(); + break; + + case ONYX_COMPILER_PROGRESS_FAILED_OUTPUT: + bh_printf_err("Failed to open file for writing: '%s'\n", compile_opts.target_file); + break; + + case ONYX_COMPILER_PROGRESS_SUCCESS: + break; + } + + context_free(); + + bh_scratch_free(&global_scratch); + // bh_managed_heap_free(&global_heap); + + return compiler_progress != ONYX_COMPILER_PROGRESS_SUCCESS; +} diff --git a/compiler/src/onyxrun.c b/compiler/src/onyxrun.c new file mode 100644 index 00000000..5200090b --- /dev/null +++ b/compiler/src/onyxrun.c @@ -0,0 +1,42 @@ +#define BH_DEFINE +#define BH_NO_TABLE +#define STB_DS_IMPLEMENTATION +#include "bh.h" + +#include "wasm_emit.h" + +int main(int argc, char *argv[]) { + i32 wasm_file_idx = 1; + if (argc < 2) { + fprintf(stderr, "Expected a WASM file to run.\n"); + return 1; + } + + b32 debug = 0; + if (!strcmp(argv[1], "--debug")) { + debug = 1; + wasm_file_idx = 2; + } + + if (debug && argc < 3) { + fprintf(stderr, "Expected a WASM file to run.\n"); + return 1; + } + + onyx_run_initialize(debug); + + bh_file wasm_file; + bh_file_error err = bh_file_open(&wasm_file, argv[wasm_file_idx]); + if (err != BH_FILE_ERROR_NONE) { + fprintf(stderr, "Failed to open file %s", argv[wasm_file_idx]); + return 1; + } + + bh_file_contents wasm_data = bh_file_read_contents(bh_heap_allocator(), &wasm_file); + bh_file_close(&wasm_file); + + bh_buffer data; + data.data = wasm_data.data; + data.length = wasm_data.length; + return onyx_run_wasm(data, argc - 1 - debug, argv + 1 + debug); +} diff --git a/compiler/src/parser.c b/compiler/src/parser.c new file mode 100644 index 00000000..1ccf7521 --- /dev/null +++ b/compiler/src/parser.c @@ -0,0 +1,3475 @@ +// The sole job of the parser for Onyx is to submit nodes to the +// entity heap for further processing. These nodes include things +// such as procedure definitions, string literals, struct definitions +// and declarations to be introduced into scopes. + +#include "lex.h" +#include "errors.h" +#include "parser.h" +#include "utils.h" + +#define make_node(nclass, kind) onyx_ast_node_new(parser->allocator, sizeof(nclass), kind) +// :LinearTokenDependent +#define peek_token(ahead) (parser->curr + ahead) + +static AstNode error_node = { Ast_Kind_Error, 0, NULL, NULL }; + +#define ENTITY_SUBMIT(node) (submit_entity_in_scope(parser, (AstNode *) (node), parser->current_scope, parser->package)) +#define ENTITY_SUBMIT_IN_SCOPE(node, scope) (submit_entity_in_scope(parser, (AstNode *) (node), scope, parser->package)) + +void submit_entity_in_scope(OnyxParser* parser, AstNode* node, Scope* scope, Package* package) { + if (bh_arr_length(parser->alternate_entity_placement_stack) == 0) { + add_entities_for_node(NULL, node, scope, package); + + } else { + bh_arr(Entity *) *entity_array = bh_arr_last(parser->alternate_entity_placement_stack); + add_entities_for_node(entity_array, node, scope, package); + } +} + +// Parsing Utilities +static void consume_token(OnyxParser* parser); +static OnyxToken* expect_token(OnyxParser* parser, TokenType token_type); +static b32 consume_token_if_next(OnyxParser* parser, TokenType token_type); +static b32 next_tokens_are(OnyxParser* parser, i32 n, ...); +static OnyxToken* find_matching_paren(OnyxToken* paren); + +static AstNumLit* parse_int_literal(OnyxParser* parser); +static AstNumLit* parse_float_literal(OnyxParser* parser); +static b32 parse_possible_struct_literal(OnyxParser* parser, AstTyped* left, AstTyped** ret); +static b32 parse_possible_array_literal(OnyxParser* parser, AstTyped* left, AstTyped** ret); +static b32 parse_possible_unary_field_access(OnyxParser* parser, AstTyped** ret); +static void parse_arguments(OnyxParser* parser, TokenType end_token, Arguments* args); +static AstTyped* parse_factor(OnyxParser* parser); +static AstTyped* parse_compound_assignment(OnyxParser* parser, AstTyped* lhs); +static AstTyped* parse_compound_expression(OnyxParser* parser, b32 assignment_allowed); +static AstTyped* parse_expression(OnyxParser* parser, b32 assignment_allowed); +static AstIfWhile* parse_if_stmt(OnyxParser* parser); +static AstIfWhile* parse_while_stmt(OnyxParser* parser); +static AstFor* parse_for_stmt(OnyxParser* parser); +static AstSwitchCase* parse_case_stmt(OnyxParser* parser); +static AstSwitch* parse_switch_stmt(OnyxParser* parser); +static i32 parse_possible_symbol_declaration(OnyxParser* parser, AstNode** ret); +static AstReturn* parse_return_stmt(OnyxParser* parser); +static AstNode* parse_use_stmt(OnyxParser* parser); +static AstBlock* parse_block(OnyxParser* parser, b32 make_a_new_scope, char* block_name); +static AstNode* parse_statement(OnyxParser* parser); +static void parse_polymorphic_variable(OnyxParser* parser, AstType*** next_insertion); +static AstType* parse_type(OnyxParser* parser); +static AstTypeOf* parse_typeof(OnyxParser* parser); +static AstStructType* parse_struct(OnyxParser* parser); +static AstInterface* parse_interface(OnyxParser* parser); +static AstConstraint* parse_constraint(OnyxParser* parser); +static void parse_constraints(OnyxParser* parser, ConstraintContext *constraints); +static void parse_function_params(OnyxParser* parser, AstFunction* func); +static b32 parse_possible_directive(OnyxParser* parser, const char* dir); +static b32 parse_possible_function_definition_no_consume(OnyxParser* parser); +static b32 parse_possible_function_definition(OnyxParser* parser, AstTyped** ret); +static b32 parse_possible_quick_function_definition_no_consume(OnyxParser* parser); +static b32 parse_possible_quick_function_definition(OnyxParser* parser, AstTyped** ret); +static AstFunction* parse_function_definition(OnyxParser* parser, OnyxToken* token); +static AstTyped* parse_global_declaration(OnyxParser* parser); +static AstEnumType* parse_enum_declaration(OnyxParser* parser); +static AstMacro* parse_macro(OnyxParser* parser); +static AstIf* parse_static_if_stmt(OnyxParser* parser, b32 parse_block_as_statements); +static AstMemRes* parse_memory_reservation(OnyxParser* parser, OnyxToken* symbol, b32 thread_local); +static AstTyped* parse_top_level_expression(OnyxParser* parser); +static AstBinding* parse_top_level_binding(OnyxParser* parser, OnyxToken* symbol); +static void parse_top_level_statement(OnyxParser* parser); +static AstPackage* parse_package_expression(OnyxParser* parser); +static void parse_top_level_statements_until(OnyxParser* parser, TokenType tt); + +static void consume_token(OnyxParser* parser) { + if (parser->hit_unexpected_token) return; + + parser->prev = parser->curr; + // :LinearTokenDependent + parser->curr++; + while (parser->curr->type == Token_Type_Comment || parser->curr->type == Token_Type_Note) { + if (parser->curr->type == Token_Type_Note) { + AstNote* note = make_node(AstNode, Ast_Kind_Note); + note->token = parser->curr; + ENTITY_SUBMIT(note); + } + + parser->curr++; + } +} + +static OnyxToken* find_matching_paren(OnyxToken* paren) { + TokenType match = 0; + switch ((u16) paren->type) { + case '(': match = ')'; break; + case '[': match = ']'; break; + case '{': match = '}'; break; + case '<': match = '>'; break; + default: return NULL; + } + + i32 paren_count = 1; + i32 i = 1; + while (paren_count > 0) { + // :LinearTokenDependent + TokenType type = (paren + i)->type; + if (type == Token_Type_End_Stream) return NULL; + + if (type == paren->type) paren_count++; + else if (type == match) paren_count--; + + i++; + } + + // :LinearTokenDependent + return paren + (i - 1); +} + +// NOTE: This advances to next token no matter what +static OnyxToken* expect_token(OnyxParser* parser, TokenType token_type) { + if (parser->hit_unexpected_token) return NULL; + + OnyxToken* token = parser->curr; + consume_token(parser); + + if (token->type != token_type) { + onyx_report_error(token->pos, Error_Critical, "expected token '%s', got '%s'.", token_name(token_type), token_name(token->type)); + parser->hit_unexpected_token = 1; + // :LinearTokenDependent + parser->curr = &parser->tokenizer->tokens[bh_arr_length(parser->tokenizer->tokens) - 1]; + return NULL; + } + + return token; +} + +static b32 consume_token_if_next(OnyxParser* parser, TokenType token_type) { + if (parser->hit_unexpected_token) return 0; + + if (parser->curr->type == token_type) { + consume_token(parser); + return 1; + } else { + return 0; + } +} + +static void consume_tokens(OnyxParser* parser, i32 n) { + fori (i, 0, n) consume_token(parser); +} + +static b32 next_tokens_are(OnyxParser* parser, i32 n, ...) { + va_list va; + va_start(va, n); + + i32 matched = 1; + + // BUG: This does not take into consideration comments and notes that can occur between any tokens. + fori (i, 0, n) { + TokenType expected_type = va_arg(va, TokenType); + if (peek_token(i)->type != expected_type) { + matched = 0; + break; + } + } + + va_end(va); + return matched; +} + + +static void expect_no_stored_tags_pos(OnyxParser *parser, OnyxFilePos pos) { + if (bh_arr_length(parser->stored_tags) > 0) { + onyx_report_error(pos, Error_Critical, "#tag is not allowed on this element."); + parser->hit_unexpected_token = 1; + } +} + +static void expect_no_stored_tags(OnyxParser *parser) { + expect_no_stored_tags_pos(parser, parser->curr->pos); +} + +static void flush_stored_tags(OnyxParser *parser, bh_arr(AstTyped *) *out_arr) { + // + // When tag_depth > 0, no tags will be added to the element. + // This happens if you have a something like so, + // + // #tag "asdf" + // #tag handler.{ (x: i32) => x * 2 } + // foo :: () { ... } + // + // In this situation, the inner procedure defined in the second + // tag should NOT consume the "asdf" tag. + // + if (bh_arr_length(parser->stored_tags) == 0 || parser->tag_depth > 0) return; + + bh_arr(AstTyped *) arr = *out_arr; + + if (arr == NULL) { + bh_arr_new(parser->allocator, arr, bh_arr_length(parser->stored_tags)); + } + + bh_arr_each(AstTyped *, pexpr, parser->stored_tags) { + bh_arr_push(arr, *pexpr); + } + + bh_arr_clear(parser->stored_tags); + + *out_arr = arr; +} + + +// TODO: Make parsing numeric literals not rely on the C standard libary. +static AstNumLit* parse_int_literal(OnyxParser* parser) { + AstNumLit* int_node = make_node(AstNumLit, Ast_Kind_NumLit); + int_node->token = expect_token(parser, Token_Type_Literal_Integer); + int_node->flags |= Ast_Flag_Comptime; + int_node->value.l = 0ll; + + token_toggle_end(int_node->token); + + char* first_invalid = NULL; + i64 value = strtoll(int_node->token->text, &first_invalid, 0); + + int_node->value.l = value; + + int_node->type_node = (AstType *) &basic_type_int_unsized; + + // NOTE: Hex literals are unsigned. + if (int_node->token->length >= 2 && int_node->token->text[1] == 'x') { + int_node->was_hex_literal = 1; + } + + token_toggle_end(int_node->token); + return int_node; +} + +static AstNumLit* parse_float_literal(OnyxParser* parser) { + AstNumLit* float_node = make_node(AstNumLit, Ast_Kind_NumLit); + float_node->token = expect_token(parser, Token_Type_Literal_Float); + float_node->flags |= Ast_Flag_Comptime; + float_node->value.d = 0.0; + + AstType* type = (AstType *) &basic_type_float_unsized; + token_toggle_end(float_node->token); + + if (float_node->token->text[float_node->token->length - 1] == 'f') { + type = (AstType *) &basic_type_f32; + float_node->value.f = strtof(float_node->token->text, NULL); + } else { + float_node->value.d = strtod(float_node->token->text, NULL); + } + + float_node->type_node = type; + + token_toggle_end(float_node->token); + return float_node; +} + +static b32 parse_possible_directive(OnyxParser* parser, const char* dir) { + if (!next_tokens_are(parser, 2, '#', Token_Type_Symbol)) return 0; + + OnyxToken* sym = peek_token(1); + + b32 match = (strlen(dir) == (u64) sym->length) && (strncmp(dir, sym->text, sym->length) == 0); + if (match) consume_tokens(parser, 2); + + return match; +} + +static b32 parse_possible_struct_literal(OnyxParser* parser, AstTyped* left, AstTyped** ret) { + if (!next_tokens_are(parser, 2, '.', '{')) return 0; + + AstStructLiteral* sl = make_node(AstStructLiteral, Ast_Kind_Struct_Literal); + sl->token = parser->curr; + sl->stnode = left; + + arguments_initialize(&sl->args); + + expect_token(parser, '.'); + expect_token(parser, '{'); + + parse_arguments(parser, '}', &sl->args); + + *ret = (AstTyped *) sl; + return 1; +} + +static b32 parse_possible_array_literal(OnyxParser* parser, AstTyped* left, AstTyped** ret) { + if (!next_tokens_are(parser, 2, '.', '[')) return 0; + + AstArrayLiteral* al = make_node(AstArrayLiteral, Ast_Kind_Array_Literal); + al->token = parser->curr; + al->atnode = left; + + bh_arr_new(global_heap_allocator, al->values, 4); + fori (i, 0, 4) al->values[i] = NULL; + + expect_token(parser, '.'); + expect_token(parser, '['); + while (!consume_token_if_next(parser, ']')) { + if (parser->hit_unexpected_token) return 1; + + AstTyped* value = parse_expression(parser, 0); + bh_arr_push(al->values, value); + + if (parser->curr->type != ']') + expect_token(parser, ','); + } + + *ret = (AstTyped *) al; + return 1; +} + +static b32 parse_possible_unary_field_access(OnyxParser* parser, AstTyped** ret) { + if (!next_tokens_are(parser, 2, '.', Token_Type_Symbol)) return 0; + + AstUnaryFieldAccess* ufl = make_node(AstUnaryFieldAccess, Ast_Kind_Unary_Field_Access); + expect_token(parser, '.'); + ufl->token = expect_token(parser, Token_Type_Symbol); + + *ret = (AstTyped *) ufl; + return 1; +} + +static void parse_arguments(OnyxParser* parser, TokenType end_token, Arguments* args) { + while (!consume_token_if_next(parser, end_token)) { + if (parser->hit_unexpected_token) return; + + // + // This has a weird condition to avoid the problem of using a quick function as an argument: + // f(x => x + 1) + // This shouldn't be a named argument, but this should: + // f(g = x => x + 1) + // + if (next_tokens_are(parser, 2, Token_Type_Symbol, '=') && peek_token(2)->type != '>') { + OnyxToken* name = expect_token(parser, Token_Type_Symbol); + expect_token(parser, '='); + + AstNamedValue* named_value = make_node(AstNamedValue, Ast_Kind_Named_Value); + named_value->token = name; + named_value->value = parse_expression(parser, 0); + + bh_arr_push(args->named_values, named_value); + + } else { + AstTyped* value = parse_expression(parser, 0); + bh_arr_push(args->values, value); + } + + if (parser->curr->type != end_token) + expect_token(parser, ','); + } +} + +static AstTyped* parse_factor(OnyxParser* parser) { + AstTyped* retval = NULL; + + switch ((u16) parser->curr->type) { + case '(': { + if (parse_possible_function_definition(parser, &retval)) { + ENTITY_SUBMIT(retval); + break; + } + if (parse_possible_quick_function_definition(parser, &retval)) { + ENTITY_SUBMIT(retval); + break; + } + + consume_token(parser); + retval = parse_compound_expression(parser, 0); + expect_token(parser, ')'); + break; + } + + case '-': { + AstUnaryOp* negate_node = make_node(AstUnaryOp, Ast_Kind_Unary_Op); + negate_node->operation = Unary_Op_Negate; + negate_node->token = expect_token(parser, '-'); + negate_node->expr = parse_factor(parser); + + retval = (AstTyped *) negate_node; + break; + } + + case '!': { + AstUnaryOp* not_node = make_node(AstUnaryOp, Ast_Kind_Unary_Op); + not_node->operation = Unary_Op_Not; + not_node->token = expect_token(parser, '!'); + not_node->expr = parse_factor(parser); + + retval = (AstTyped *) not_node; + break; + } + + case '~': { + AstUnaryOp* not_node = make_node(AstUnaryOp, Ast_Kind_Unary_Op); + not_node->operation = Unary_Op_Bitwise_Not; + not_node->token = expect_token(parser, '~'); + not_node->expr = parse_factor(parser); + + retval = (AstTyped *) not_node; + break; + } + + case '*': { + AstDereference* deref_node = make_node(AstDereference, Ast_Kind_Dereference); + deref_node->token = expect_token(parser, '*'); + deref_node->expr = parse_factor(parser); + + retval = (AstTyped *) deref_node; + break; + } + + case '^': { + AstAddressOf* aof_node = make_node(AstAddressOf, Ast_Kind_Address_Of); + aof_node->token = expect_token(parser, '^'); + aof_node->expr = parse_factor(parser); + + retval = (AstTyped *) aof_node; + break; + } + + case '.': { + if (parse_possible_struct_literal(parser, NULL, &retval)) return retval; + if (parse_possible_array_literal(parser, NULL, &retval)) return retval; + if (parse_possible_unary_field_access(parser, &retval)) return retval; + goto no_match; + } + + case Token_Type_Tilde_Tilde: { + AstUnaryOp* ac_node = make_node(AstUnaryOp, Ast_Kind_Unary_Op); + ac_node->operation = Unary_Op_Auto_Cast; + ac_node->token = expect_token(parser, Token_Type_Tilde_Tilde); + ac_node->expr = parse_factor(parser); + + retval = (AstTyped *) ac_node; + break; + } + + case Token_Type_Keyword_Cast: { + AstUnaryOp* cast_node = make_node(AstUnaryOp, Ast_Kind_Unary_Op); + cast_node->operation = Unary_Op_Cast; + cast_node->token = expect_token(parser, Token_Type_Keyword_Cast); + + expect_token(parser, '('); + cast_node->type_node = parse_type(parser); + expect_token(parser, ')'); + + cast_node->expr = parse_factor(parser); + + retval = (AstTyped *) cast_node; + break; + } + + case Token_Type_Keyword_Sizeof: { + AstSizeOf* so_node = make_node(AstSizeOf, Ast_Kind_Size_Of); + so_node->token = expect_token(parser, Token_Type_Keyword_Sizeof); + so_node->so_ast_type = (AstType *) parse_type(parser); + so_node->type_node = (AstType *) &basic_type_i32; + + retval = (AstTyped *) so_node; + break; + } + + case Token_Type_Keyword_Alignof: { + AstAlignOf* ao_node = make_node(AstAlignOf, Ast_Kind_Align_Of); + ao_node->token = expect_token(parser, Token_Type_Keyword_Alignof); + ao_node->ao_ast_type = (AstType *) parse_type(parser); + ao_node->type_node = (AstType *) &basic_type_i32; + + retval = (AstTyped *) ao_node; + break; + } + + case Token_Type_Keyword_Typeof: { + retval = (AstTyped *) parse_typeof(parser); + break; + } + + case Token_Type_Symbol: { + if (parse_possible_quick_function_definition(parser, &retval)) { + ENTITY_SUBMIT(retval); + break; + } + + OnyxToken* sym_token = expect_token(parser, Token_Type_Symbol); + AstTyped* sym_node = make_node(AstTyped, Ast_Kind_Symbol); + sym_node->token = sym_token; + + retval = sym_node; + break; + } + + case Token_Type_Literal_Integer: + retval = (AstTyped *) parse_int_literal(parser); + break; + + case Token_Type_Literal_Float: + retval = (AstTyped *) parse_float_literal(parser); + break; + + case Token_Type_Literal_String: { + AstStrLit* str_node = make_node(AstStrLit, Ast_Kind_StrLit); + str_node->token = expect_token(parser, Token_Type_Literal_String); + str_node->data_id = 0; + str_node->flags |= Ast_Flag_Comptime; + + ENTITY_SUBMIT(str_node); + + retval = (AstTyped *) str_node; + break; + } + + case Token_Type_Literal_True: { + AstNumLit* bool_node = make_node(AstNumLit, Ast_Kind_NumLit); + bool_node->type_node = (AstType *) &basic_type_bool; + bool_node->token = expect_token(parser, Token_Type_Literal_True); + bool_node->value.i = 1; + bool_node->flags |= Ast_Flag_Comptime; + retval = (AstTyped *) bool_node; + break; + } + + case Token_Type_Literal_False: { + AstNumLit* bool_node = make_node(AstNumLit, Ast_Kind_NumLit); + bool_node->type_node = (AstType *) &basic_type_bool; + bool_node->token = expect_token(parser, Token_Type_Literal_False); + bool_node->value.i = 0; + bool_node->flags |= Ast_Flag_Comptime; + retval = (AstTyped *) bool_node; + break; + } + + case Token_Type_Keyword_Package: { + retval = (AstTyped *) parse_package_expression(parser); + break; + } + + case Token_Type_Keyword_Macro: { + retval = (AstTyped *) parse_macro(parser); + break; + } + + case Token_Type_Keyword_Do: { + OnyxToken* do_token = expect_token(parser, Token_Type_Keyword_Do); + AstDoBlock* do_block = make_node(AstDoBlock, Ast_Kind_Do_Block); + do_block->token = do_token; + do_block->type_node = (AstType *) &basic_type_auto_return; + + if (parser->curr->type != '{') { + onyx_report_error(parser->curr->pos, Error_Critical, "Expected '{' after 'do', got '%s'.", token_name(parser->curr->type)); + retval = NULL; + break; + } + + do_block->block = parse_block(parser, 1, NULL); + + retval = (AstTyped *) do_block; + break; + } + + case '[': { + AstType *type = parse_type(parser); + retval = (AstTyped *) type; + break; + } + + case '$': { + AstType **tmp = (AstType **) &retval; + parse_polymorphic_variable(parser, &tmp); + break; + } + + case '#': { + if (parse_possible_directive(parser, "file_contents")) { + AstFileContents* fc = make_node(AstFileContents, Ast_Kind_File_Contents); + fc->token = parser->prev - 1; + fc->filename_expr = parse_expression(parser, 0); + fc->type = type_make_slice(parser->allocator, &basic_types[Basic_Kind_U8]); + + if (parser->current_function_stack && bh_arr_length(parser->current_function_stack) > 0) { + bh_arr_push(bh_arr_last(parser->current_function_stack)->nodes_that_need_entities_after_clone, (AstNode *) fc); + + } else { + ENTITY_SUBMIT(fc); + } + + retval = (AstTyped *) fc; + break; + } + else if (parse_possible_directive(parser, "file")) { + OnyxToken* dir_token = parser->curr - 2; + + OnyxToken* str_token = bh_alloc(parser->allocator, sizeof(OnyxToken)); + str_token->text = bh_strdup(global_heap_allocator, (char *) dir_token->pos.filename); + str_token->length = strlen(dir_token->pos.filename); + str_token->pos = dir_token->pos; + str_token->type = Token_Type_Literal_String; + + AstStrLit* filename = make_node(AstStrLit, Ast_Kind_StrLit); + filename->token = str_token; + filename->data_id = 0; + + ENTITY_SUBMIT(filename); + retval = (AstTyped *) filename; + break; + } + else if (parse_possible_directive(parser, "line")) { + OnyxToken* dir_token = parser->curr - 2; + + AstNumLit* line_num = make_int_literal(parser->allocator, dir_token->pos.line); + retval = (AstTyped *) line_num; + break; + } + else if (parse_possible_directive(parser, "column")) { + OnyxToken* dir_token = parser->curr - 2; + + AstNumLit* col_num = make_int_literal(parser->allocator, dir_token->pos.column); + retval = (AstTyped *) col_num; + break; + } + else if (parse_possible_directive(parser, "char")) { + AstNumLit* char_lit = make_node(AstNumLit, Ast_Kind_NumLit); + char_lit->flags |= Ast_Flag_Comptime; + char_lit->type_node = (AstType *) &basic_type_int_unsized; + char_lit->token = expect_token(parser, Token_Type_Literal_String); + + i8 dest = '\0'; + i32 length = string_process_escape_seqs((char *) &dest, char_lit->token->text, 1); + char_lit->value.i = (u32) dest; + + if (length != 1) { + onyx_report_error(char_lit->token->pos, Error_Critical, "Expected only a single character in character literal."); + } + + retval = (AstTyped *) char_lit; + break; + } + else if (parse_possible_directive(parser, "type")) { + AstTypeAlias* alias = make_node(AstTypeAlias, Ast_Kind_Type_Alias); + alias->token = parser->curr - 2; + alias->to = parse_type(parser); + retval = (AstTyped *) alias; + break; + } + else if (parse_possible_directive(parser, "solidify")) { + AstDirectiveSolidify* solid = make_node(AstDirectiveSolidify, Ast_Kind_Directive_Solidify); + // :LinearTokenDependent + solid->token = parser->curr - 1; + + solid->poly_proc = (AstFunction *) parse_factor(parser); + + solid->known_polyvars = NULL; + bh_arr_new(global_heap_allocator, solid->known_polyvars, 2); + + expect_token(parser, '{'); + while (!consume_token_if_next(parser, '}')) { + if (parser->hit_unexpected_token) break; + + AstNode* poly_var = make_node(AstNode, Ast_Kind_Symbol); + poly_var->token = expect_token(parser, Token_Type_Symbol); + + expect_token(parser, '='); + AstType* poly_type = (AstType *) parse_expression(parser, 0); + + bh_arr_push(solid->known_polyvars, ((AstPolySolution) { + .kind = PSK_Undefined, + .poly_sym = poly_var, + .ast_type = poly_type, + .type = NULL + })); + + if (parser->curr->type != '}') + expect_token(parser, ','); + } + + retval = (AstTyped *) solid; + break; + } + else if (parse_possible_directive(parser, "defined")) { + AstDirectiveDefined* defined = make_node(AstDirectiveDefined, Ast_Kind_Directive_Defined); + // :LinearTokenDependent + defined->token = parser->curr - 1; + defined->type_node = (AstType *) &basic_type_bool; + + expect_token(parser, '('); + defined->expr = parse_expression(parser, 0); + expect_token(parser, ')'); + + retval = (AstTyped *) defined; + break; + } + else if (parse_possible_directive(parser, "quote")) { + OnyxToken* code_token = parser->curr - 1; + + AstCodeBlock* code_block = make_node(AstCodeBlock, Ast_Kind_Code_Block); + code_block->token = code_token; + + assert(builtin_code_type != NULL); + code_block->type_node = builtin_code_type; + + if (parser->curr->type == '{') { + code_block->code = (AstNode *) parse_block(parser, 1, NULL); + ((AstBlock *) code_block->code)->rules = Block_Rule_Code_Block; + + } else { + code_block->code = (AstNode *) parse_expression(parser, 0); + } + + retval = (AstTyped *) code_block; + break; + } + else if (next_tokens_are(parser, 2, '#', '(')) { + OnyxToken* code_token = expect_token(parser, '#'); + expect_token(parser, '('); + + AstCodeBlock* code_block = make_node(AstCodeBlock, Ast_Kind_Code_Block); + code_block->token = code_token; + + assert(builtin_code_type != NULL); + code_block->type_node = builtin_code_type; + + code_block->code = (AstNode *) parse_expression(parser, 0); + + expect_token(parser, ')'); + + retval = (AstTyped *) code_block; + break; + } + else if (parse_possible_directive(parser, "unquote")) { + AstDirectiveInsert* insert = make_node(AstDirectiveInsert, Ast_Kind_Directive_Insert); + insert->token = parser->curr - 1; + insert->code_expr = parse_expression(parser, 0); + + retval = (AstTyped *) insert; + break; + } + else if (parse_possible_directive(parser, "cstr")) { + // Copy pasted from above. + AstStrLit* str_node = make_node(AstStrLit, Ast_Kind_StrLit); + str_node->token = expect_token(parser, Token_Type_Literal_String); + str_node->data_id = 0; + str_node->flags |= Ast_Flag_Comptime; + str_node->is_cstr = 1; + + ENTITY_SUBMIT(str_node); + + retval = (AstTyped *) str_node; + break; + } + + onyx_report_error(parser->curr->pos, Error_Critical, "Invalid directive in expression."); + return NULL; + } + + default: + no_match: + onyx_report_error(parser->curr->pos, Error_Critical, "Unexpected token '%s'.", token_name(parser->curr->type)); + return NULL; + } + + while (1) { + if (parser->hit_unexpected_token) return retval; + + switch ((u16) parser->curr->type) { + case '[': { + OnyxToken *open_bracket = expect_token(parser, '['); + AstTyped *expr = parse_compound_expression(parser, 0); + + AstSubscript *sub_node = make_node(AstSubscript, Ast_Kind_Subscript); + sub_node->token = open_bracket; + sub_node->addr = retval; + sub_node->expr = expr; + sub_node->__unused_operation = Binary_Op_Subscript; + + retval = (AstTyped *) sub_node; + expect_token(parser, ']'); + break; + } + + case '.': { + if (parse_possible_struct_literal(parser, retval, &retval)) return retval; + if (parse_possible_array_literal(parser, retval, &retval)) return retval; + + consume_token(parser); + AstFieldAccess* field = make_node(AstFieldAccess, Ast_Kind_Field_Access); + field->token = expect_token(parser, Token_Type_Symbol); + field->expr = retval; + + retval = (AstTyped *) field; + break; + } + + case '(': { + if (!parser->parse_calls) goto factor_parsed; + + AstCall* call_node = make_node(AstCall, Ast_Kind_Call); + call_node->token = expect_token(parser, '('); + call_node->callee = retval; + + arguments_initialize(&call_node->args); + + parse_arguments(parser, ')', &call_node->args); + + // Wrap expressions in AstArgument + bh_arr_each(AstTyped *, arg, call_node->args.values) { + if ((*arg) == NULL) continue; + *arg = (AstTyped *) make_argument(parser->allocator, *arg); + } + + bh_arr_each(AstNamedValue *, named_value, call_node->args.named_values) { + if ((*named_value)->value == NULL) continue; + (*named_value)->value = (AstTyped *) make_argument(parser->allocator, (AstTyped *) (*named_value)->value); + } + + retval = (AstTyped *) call_node; + break; + } + + case Token_Type_Keyword_If: { + AstIfExpression* if_expression = make_node(AstIfExpression, Ast_Kind_If_Expression); + if_expression->token = expect_token(parser, Token_Type_Keyword_If); + + if_expression->true_expr = retval; + if_expression->cond = parse_expression(parser, 0); + expect_token(parser, Token_Type_Keyword_Else); + if_expression->false_expr = parse_expression(parser, 0); + + retval = (AstTyped *) if_expression; + break; + } + + default: goto factor_parsed; + } + } + +factor_parsed: + + return retval; +} + +static inline i32 get_precedence(BinaryOp kind) { + switch (kind) { + case Binary_Op_Assign: return 1; + case Binary_Op_Assign_Add: return 1; + case Binary_Op_Assign_Minus: return 1; + case Binary_Op_Assign_Multiply: return 1; + case Binary_Op_Assign_Divide: return 1; + case Binary_Op_Assign_Modulus: return 1; + case Binary_Op_Assign_And: return 1; + case Binary_Op_Assign_Or: return 1; + case Binary_Op_Assign_Xor: return 1; + case Binary_Op_Assign_Shl: return 1; + case Binary_Op_Assign_Shr: return 1; + case Binary_Op_Assign_Sar: return 1; + + case Binary_Op_Pipe: return 2; + case Binary_Op_Range: return 2; + + case Binary_Op_Bool_And: return 3; + case Binary_Op_Bool_Or: return 3; + + case Binary_Op_Equal: return 4; + case Binary_Op_Not_Equal: return 4; + + case Binary_Op_Less_Equal: return 5; + case Binary_Op_Less: return 5; + case Binary_Op_Greater_Equal: return 5; + case Binary_Op_Greater: return 5; + + case Binary_Op_And: return 6; + case Binary_Op_Or: return 6; + case Binary_Op_Xor: return 6; + case Binary_Op_Shl: return 6; + case Binary_Op_Shr: return 6; + case Binary_Op_Sar: return 6; + + case Binary_Op_Add: return 7; + case Binary_Op_Minus: return 7; + + case Binary_Op_Multiply: return 8; + case Binary_Op_Divide: return 8; + + case Binary_Op_Modulus: return 9; + + case Binary_Op_Method_Call: return 10; + + default: return -1; + } +} + +static BinaryOp binary_op_from_token_type(TokenType t) { + switch ((u16) t) { + case Token_Type_Equal_Equal: return Binary_Op_Equal; + case Token_Type_Not_Equal: return Binary_Op_Not_Equal; + case Token_Type_Less_Equal: return Binary_Op_Less_Equal; + case Token_Type_Greater_Equal: return Binary_Op_Greater_Equal; + case '<': return Binary_Op_Less; + case '>': return Binary_Op_Greater; + + case '+': return Binary_Op_Add; + case '-': return Binary_Op_Minus; + case '*': return Binary_Op_Multiply; + case '/': return Binary_Op_Divide; + case '%': return Binary_Op_Modulus; + + case '&': return Binary_Op_And; + case '|': return Binary_Op_Or; + case '^': return Binary_Op_Xor; + case Token_Type_Shift_Left: return Binary_Op_Shl; + case Token_Type_Shift_Right: return Binary_Op_Shr; + case Token_Type_Shift_Arith_Right: return Binary_Op_Sar; + + case Token_Type_And_And: return Binary_Op_Bool_And; + case Token_Type_Or_Or: return Binary_Op_Bool_Or; + + case '=': return Binary_Op_Assign; + case Token_Type_Plus_Equal: return Binary_Op_Assign_Add; + case Token_Type_Minus_Equal: return Binary_Op_Assign_Minus; + case Token_Type_Star_Equal: return Binary_Op_Assign_Multiply; + case Token_Type_Fslash_Equal: return Binary_Op_Assign_Divide; + case Token_Type_Percent_Equal: return Binary_Op_Assign_Modulus; + case Token_Type_And_Equal: return Binary_Op_Assign_And; + case Token_Type_Or_Equal: return Binary_Op_Assign_Or; + case Token_Type_Xor_Equal: return Binary_Op_Assign_Xor; + case Token_Type_Shl_Equal: return Binary_Op_Assign_Shl; + case Token_Type_Shr_Equal: return Binary_Op_Assign_Shr; + case Token_Type_Sar_Equal: return Binary_Op_Assign_Sar; + + case Token_Type_Pipe: return Binary_Op_Pipe; + case Token_Type_Dot_Dot: return Binary_Op_Range; + case '[': return Binary_Op_Subscript; + case Token_Type_Right_Arrow: return Binary_Op_Method_Call; + default: return Binary_Op_Count; + } +} + +static AstTyped* parse_compound_assignment(OnyxParser* parser, AstTyped* lhs) { + if (parser->curr->type != '=') return lhs; + + AstBinaryOp* assignment = make_node(AstBinaryOp, Ast_Kind_Binary_Op); + assignment->token = expect_token(parser, '='); + assignment->operation = Binary_Op_Assign; + assignment->left = lhs; + assignment->right = parse_compound_expression(parser, 0); + + return (AstTyped *) assignment; +} + +static AstTyped* parse_compound_expression(OnyxParser* parser, b32 assignment_allowed) { + AstTyped* first = parse_expression(parser, assignment_allowed); + + if (parser->curr->type == ',') { + AstCompound* compound = make_node(AstCompound, Ast_Kind_Compound); + compound->token = parser->curr; + + bh_arr_new(global_heap_allocator, compound->exprs, 2); + bh_arr_push(compound->exprs, first); + + while (consume_token_if_next(parser, ',')) { + if (parser->hit_unexpected_token) return (AstTyped *) compound; + + AstTyped* expr = parse_expression(parser, 0); + bh_arr_push(compound->exprs, expr); + + if (assignment_allowed && parser->curr->type == '=') { + return parse_compound_assignment(parser, (AstTyped *) compound); + } + } + + return (AstTyped *) compound; + + } else { + return first; + } +} + +static AstTyped* parse_expression(OnyxParser* parser, b32 assignment_allowed) { + bh_arr(AstBinaryOp*) tree_stack = NULL; + bh_arr_new(global_heap_allocator, tree_stack, 4); + bh_arr_set_length(tree_stack, 0); + + AstTyped* left = parse_factor(parser); + AstTyped* right; + AstTyped* root = left; + + BinaryOp bin_op_kind; + OnyxToken* bin_op_tok; + + while (1) { + if (parser->hit_unexpected_token) return root; + + bin_op_kind = binary_op_from_token_type(parser->curr->type); + if (bin_op_kind == Binary_Op_Count) goto expression_done; + if (binop_is_assignment(bin_op_kind) && !assignment_allowed) goto expression_done; + if (bin_op_kind == Binary_Op_Subscript) goto expression_done; + + bin_op_tok = parser->curr; + consume_token(parser); + + AstBinaryOp* bin_op; + if (bin_op_kind == Binary_Op_Pipe) bin_op = make_node(AstBinaryOp, Ast_Kind_Pipe); + else if (bin_op_kind == Binary_Op_Method_Call) bin_op = make_node(AstBinaryOp, Ast_Kind_Method_Call); + else if (bin_op_kind == Binary_Op_Range) bin_op = (AstBinaryOp *) make_node(AstRangeLiteral, Ast_Kind_Range_Literal); + else bin_op = make_node(AstBinaryOp, Ast_Kind_Binary_Op); + + bin_op->token = bin_op_tok; + bin_op->operation = bin_op_kind; + + while ( !bh_arr_is_empty(tree_stack) && + get_precedence(bh_arr_last(tree_stack)->operation) >= get_precedence(bin_op_kind)) + bh_arr_pop(tree_stack); + + if (bh_arr_is_empty(tree_stack)) { + // NOTE: new is now the root node + bin_op->left = root; + root = (AstTyped *) bin_op; + + } else { + bin_op->left = bh_arr_last(tree_stack)->right; + bh_arr_last(tree_stack)->right = (AstTyped *) bin_op; + } + + bh_arr_push(tree_stack, bin_op); + + right = parse_factor(parser); + bin_op->right = right; + } + +expression_done: + bh_arr_free(tree_stack); + return root; +} + +static AstIfWhile* parse_if_stmt(OnyxParser* parser) { + AstIfWhile* if_node = make_node(AstIfWhile, Ast_Kind_If); + if_node->token = expect_token(parser, Token_Type_Keyword_If); + + AstIfWhile* root_if = if_node; + AstTyped* cond; + AstNode* initialization_or_cond=NULL; + b32 had_initialization = 0; + if (parse_possible_symbol_declaration(parser, &initialization_or_cond)) { + had_initialization = 1; + + } else { + // NOTE: Assignment is allowed here because instead of not parsing correctly, + // an error is reported in the typechecking, saying that assignment isn't allowed + // here, which is better than an unexpected token error. + initialization_or_cond = (AstNode *) parse_compound_expression(parser, 1); + } + + if (had_initialization || parser->curr->type == ';') { + expect_token(parser, ';'); + cond = parse_expression(parser, 1); + } else { + cond = (AstTyped *) initialization_or_cond; + initialization_or_cond = NULL; + } + + AstBlock* true_stmt = parse_block(parser, 1, NULL); + + if_node->initialization = initialization_or_cond; + if_node->cond = cond; + if (true_stmt != NULL) + if_node->true_stmt = true_stmt; + + while (consume_token_if_next(parser, Token_Type_Keyword_Elseif)) { + if (parser->hit_unexpected_token) return root_if; + + AstIfWhile* elseif_node = make_node(AstIfWhile, Ast_Kind_If); + elseif_node->token = parser->curr - 1; + + cond = parse_expression(parser, 1); + true_stmt = parse_block(parser, 1, NULL); + + elseif_node->cond = cond; + if (true_stmt != NULL) + elseif_node->true_stmt = true_stmt; + + if_node->false_stmt = (AstBlock *) elseif_node; + if_node = elseif_node; + } + + if (consume_token_if_next(parser, Token_Type_Keyword_Else)) { + AstBlock* false_stmt = parse_block(parser, 1, NULL); + if (false_stmt != NULL) + if_node->false_stmt = false_stmt; + } + + return root_if; +} + +static AstIfWhile* parse_while_stmt(OnyxParser* parser) { + OnyxToken* while_token = expect_token(parser, Token_Type_Keyword_While); + AstIfWhile* while_node = make_node(AstIfWhile, Ast_Kind_While); + while_node->token = while_token; + + if (parse_possible_directive(parser, "bottom_test")) { + while_node->bottom_test = 1; + } + + AstTyped* cond; + AstNode* initialization_or_cond=NULL; + b32 had_initialization = 0; + if (parse_possible_symbol_declaration(parser, &initialization_or_cond)) { + had_initialization = 1; + + } else { + // NOTE: Assignment is allowed here because instead of not parsing correctly, + // an error is reported in the typechecking, saying that assignment isn't allowed + // here, which is better than an unexpected token error. + initialization_or_cond = (AstNode *) parse_compound_expression(parser, 1); + } + + if (had_initialization || parser->curr->type == ';') { + expect_token(parser, ';'); + cond = parse_expression(parser, 1); + } else { + cond = (AstTyped *) initialization_or_cond; + initialization_or_cond = NULL; + } + + while_node->initialization = initialization_or_cond; + while_node->cond = cond; + while_node->true_stmt = parse_block(parser, 1, NULL); + + if (consume_token_if_next(parser, Token_Type_Keyword_Else)) { + while_node->false_stmt = parse_block(parser, 1, NULL); + } + + return while_node; +} + +static AstFor* parse_for_stmt(OnyxParser* parser) { + AstFor* for_node = make_node(AstFor, Ast_Kind_For); + for_node->token = expect_token(parser, Token_Type_Keyword_For); + + if (parse_possible_directive(parser, "no_close")) { + for_node->no_close = 1; + } + + if (consume_token_if_next(parser, '^')) { + for_node->by_pointer = 1; + } + + if (next_tokens_are(parser, 2, Token_Type_Symbol, ':')) { + OnyxToken* local_sym = expect_token(parser, Token_Type_Symbol); + AstLocal* var_node = make_local(parser->allocator, local_sym, NULL); + + for_node->var = var_node; + + expect_token(parser, ':'); + } else { + // HACK + static char it_name[] = "it "; + static OnyxToken it_token = { Token_Type_Symbol, 2, it_name, { 0 } }; + + AstLocal* var_node = make_local(parser->allocator, &it_token, NULL); + for_node->var = var_node; + } + + for_node->iter = parse_expression(parser, 1); + for_node->stmt = parse_block(parser, 1, NULL); + + return for_node; +} + +static AstSwitchCase* parse_case_stmt(OnyxParser* parser) { + AstSwitchCase *sc_node = make_node(AstSwitchCase, Ast_Kind_Switch_Case); + sc_node->token = expect_token(parser, Token_Type_Keyword_Case); + + if (parse_possible_directive(parser, "default")) { + sc_node->is_default = 1; + + } else { + bh_arr_new(global_heap_allocator, sc_node->values, 1); + + AstTyped* value = parse_expression(parser, 1); + bh_arr_push(sc_node->values, value); + while (consume_token_if_next(parser, ',')) { + if (parser->hit_unexpected_token) return sc_node; + + value = parse_expression(parser, 1); + bh_arr_push(sc_node->values, value); + } + } + + sc_node->block = parse_block(parser, 1, NULL); + + return sc_node; +} + +static AstSwitch* parse_switch_stmt(OnyxParser* parser) { + AstSwitch* switch_node = make_node(AstSwitch, Ast_Kind_Switch); + switch_node->token = expect_token(parser, Token_Type_Keyword_Switch); + + AstTyped* expr; + AstNode* initialization_or_expr=NULL; + b32 had_initialization = 0; + if (parse_possible_symbol_declaration(parser, &initialization_or_expr)) { + had_initialization = 1; + + } else { + // NOTE: Assignment is allowed here because instead of not parsing correctly, + // an error is reported in the typechecking, saying that assignment isn't allowed + // here, which is better than an unexpected token error. + initialization_or_expr = (AstNode *) parse_compound_expression(parser, 1); + } + + if (had_initialization || parser->curr->type == ';') { + expect_token(parser, ';'); + expr = parse_expression(parser, 1); + + } else { + expr = (AstTyped *) initialization_or_expr; + initialization_or_expr = NULL; + } + + switch_node->initialization = initialization_or_expr; + switch_node->expr = expr; + + switch_node->case_block = parse_block(parser, 1, NULL); + return switch_node; +} + +static i32 parse_possible_compound_symbol_declaration(OnyxParser* parser, AstNode** ret) { + u32 token_offset = 0; + while (peek_token(token_offset)->type == Token_Type_Symbol) { + token_offset += 1; + + if (peek_token(token_offset)->type == '\'') token_offset += 1; + + if (peek_token(token_offset)->type != ',') break; + token_offset += 1; + } + + if (peek_token(token_offset)->type != ':') return 0; + + // At this point, we are sure it is a compound declaration. + AstCompound* local_compound = make_node(AstCompound, Ast_Kind_Compound); + bh_arr_new(global_heap_allocator, local_compound->exprs, token_offset / 2); + + AstLocal* first_local = NULL; + AstLocal* prev_local = NULL; + + while (parser->curr->type == Token_Type_Symbol) { + if (parser->hit_unexpected_token) return 1; + + OnyxToken* local_sym = expect_token(parser, Token_Type_Symbol); + AstNode* sym_node = make_symbol(parser->allocator, local_sym); + bh_arr_push(local_compound->exprs, (AstTyped *) sym_node); + + if (!consume_token_if_next(parser, '\'')) { + AstLocal* new_local = make_local(parser->allocator, local_sym, NULL); + if (prev_local == NULL) { + first_local = new_local; + } else { + prev_local->next = (AstNode *) new_local; + } + prev_local = new_local; + } + + consume_token_if_next(parser, ','); + } + + expect_token(parser, ':'); + + if (parser->curr->type == '=') { + AstBinaryOp* assignment = make_binary_op(parser->allocator, Binary_Op_Assign, (AstTyped *) local_compound, NULL); + assignment->token = expect_token(parser, '='); + assignment->right = parse_compound_expression(parser, 0); + + prev_local->next = (AstNode *) assignment; + + } else { + AstType* type_for_all = parse_type(parser); + forll (AstLocal, local, first_local, next) { + local->type_node = type_for_all; + } + } + + *ret = (AstNode *) first_local; + return 1; +} + +// Returns: +// 0 - if this was not a symbol declaration. +// 1 - if this was a local declaration. +// 2 - if this was binding declaration. +// ret is set to the statement to insert +static i32 parse_possible_symbol_declaration(OnyxParser* parser, AstNode** ret) { + // Has to start with a symbol to be a declaration + if (parser->curr->type != Token_Type_Symbol) return 0; + + // If the token after the symbol is a comma, assume this is a compound declaration. + if (peek_token(1)->type == ',' || + (peek_token(1)->type == '\'' && peek_token(2)->type == ',')) { + return parse_possible_compound_symbol_declaration(parser, ret); + } + + if (peek_token(1)->type != ':') return 0; + + OnyxToken* symbol = expect_token(parser, Token_Type_Symbol); + expect_token(parser, ':'); + + if (parser->curr->type == ':') { + bh_arr_push(parser->current_symbol_stack, symbol); + AstBinding* binding = parse_top_level_binding(parser, symbol); + bh_arr_pop(parser->current_symbol_stack); + if (parser->hit_unexpected_token) return 2; + + ENTITY_SUBMIT(binding); + return 2; + } + + AstType* type_node = NULL; + if (parser->curr->type != '=') { + type_node = parse_type(parser); + } + + AstLocal* local = make_local(parser->allocator, symbol, type_node); + *ret = (AstNode *) local; + + if (parser->curr->type == '=') { + AstBinaryOp* assignment = make_node(AstBinaryOp, Ast_Kind_Binary_Op); + assignment->operation = Binary_Op_Assign; + assignment->token = expect_token(parser, '='); + local->next = (AstNode *) assignment; + + AstTyped* expr = parse_expression(parser, 1); + if (expr == NULL) return 1; + assignment->right = expr; + + // INVESTIGATE: I don't know why, but appearantly, this has to be a + // symbol node, not a direct link to the local. There is an error about + // being unable to resolve the type of the local if it is immediately set. + AstNode* left_symbol = make_node(AstNode, Ast_Kind_Symbol); + left_symbol->token = symbol; + assignment->left = (AstTyped *) left_symbol; + } + + return 1; +} + +static AstReturn* parse_return_stmt(OnyxParser* parser) { + AstReturn* return_node = make_node(AstReturn, Ast_Kind_Return); + return_node->token = expect_token(parser, Token_Type_Keyword_Return); + + AstTyped* expr = NULL; + + if (parser->curr->type != ';') { + expr = parse_compound_expression(parser, 0); + + if (expr == NULL || expr == (AstTyped *) &error_node) { + return (AstReturn *) &error_node; + } else { + return_node->expr = expr; + } + } + + return return_node; +} + +static AstNode* parse_use_stmt(OnyxParser* parser) { + OnyxToken* use_token = expect_token(parser, Token_Type_Keyword_Use); + AstUse* use_node = make_node(AstUse, Ast_Kind_Use); + use_node->token = use_token; + use_node->expr = parse_expression(parser, 1); + + if (consume_token_if_next(parser, '{')) { + bh_arr_new(global_heap_allocator, use_node->only, 4); + + while (!consume_token_if_next(parser, '}')) { + if (parser->hit_unexpected_token) return NULL; + + QualifiedUse qu; + qu.as_name = expect_token(parser, Token_Type_Symbol); + qu.symbol_name = qu.as_name; + + if (consume_token_if_next(parser, ':')) { + expect_token(parser, ':'); + qu.symbol_name = expect_token(parser, Token_Type_Symbol); + } + + bh_arr_push(use_node->only, qu); + + if (parser->curr->type != '}') + expect_token(parser, ','); + } + } + + if (use_node->expr->kind == Ast_Kind_Package) { + ENTITY_SUBMIT(use_node); + return NULL; + + } else { + return (AstNode *) use_node; + } +} + +static AstNode* parse_jump_stmt(OnyxParser* parser, TokenType token_type, JumpType jump_type) { + AstJump* jnode = make_node(AstJump, Ast_Kind_Jump); + jnode->token = expect_token(parser, token_type); + jnode->jump = jump_type; + + u64 count = 1; + while (consume_token_if_next(parser, token_type)) count++; + jnode->count = count; + + return (AstNode *) jnode; +} + +static AstNode* parse_statement(OnyxParser* parser) { + b32 needs_semicolon = 1; + AstNode* retval = NULL; + + switch ((u16) parser->curr->type) { + case Token_Type_Keyword_Return: + retval = (AstNode *) parse_return_stmt(parser); + break; + + case '{': + case Token_Type_Empty_Block: + case Token_Type_Keyword_Do: + needs_semicolon = 0; + retval = (AstNode *) parse_block(parser, 1, NULL); + break; + + case Token_Type_Symbol: { + i32 symbol_res = parse_possible_symbol_declaration(parser, &retval); + if (symbol_res == 2) needs_semicolon = 0; + if (symbol_res != 0) break; + + // fallthrough + } + + case '(': case '+': case '-': case '!': case '*': case '^': + case Token_Type_Literal_Integer: + case Token_Type_Literal_Float: + case Token_Type_Literal_String: + retval = (AstNode *) parse_compound_expression(parser, 1); + if (retval->kind == Ast_Kind_Call || retval->kind == Ast_Kind_Method_Call) { + if (parser->curr->type == '{') { + AstCodeBlock* code_block = make_node(AstCodeBlock, Ast_Kind_Code_Block); + code_block->token = parser->curr; + code_block->type_node = builtin_code_type; + + code_block->code = (AstNode *) parse_block(parser, 1, NULL); + ((AstBlock *) code_block->code)->rules = Block_Rule_Code_Block; + + AstCall *dest = (AstCall *) retval; + if (dest->kind == Ast_Kind_Method_Call) { + dest = (AstCall *) ((AstBinaryOp *) dest)->right; + if (dest->kind != Ast_Kind_Call) { + onyx_report_error(retval->token->pos, Error_Critical, "Expected function call on right side of '->'."); + needs_semicolon = 0; + break; + } + } + + bh_arr_push(dest->args.values, (AstTyped *) make_argument(context.ast_alloc, (AstTyped *) code_block)); + needs_semicolon = 0; + } + } + break; + + case Token_Type_Keyword_If: + needs_semicolon = 0; + retval = (AstNode *) parse_if_stmt(parser); + break; + + case Token_Type_Keyword_While: + needs_semicolon = 0; + retval = (AstNode *) parse_while_stmt(parser); + break; + + case Token_Type_Keyword_For: + needs_semicolon = 0; + retval = (AstNode *) parse_for_stmt(parser); + break; + + case Token_Type_Keyword_Switch: + needs_semicolon = 0; + retval = (AstNode *) parse_switch_stmt(parser); + break; + + case Token_Type_Keyword_Case: + needs_semicolon = 0; + retval = (AstNode *) parse_case_stmt(parser); + break; + + case Token_Type_Keyword_Break: + retval = parse_jump_stmt(parser, Token_Type_Keyword_Break, Jump_Type_Break); + break; + + case Token_Type_Keyword_Continue: + retval = parse_jump_stmt(parser, Token_Type_Keyword_Continue, Jump_Type_Continue); + break; + + case Token_Type_Keyword_Fallthrough: + retval = parse_jump_stmt(parser, Token_Type_Keyword_Fallthrough, Jump_Type_Fallthrough); + break; + + case Token_Type_Keyword_Defer: { + needs_semicolon = 0; + + AstDefer* defer = make_node(AstDefer, Ast_Kind_Defer); + defer->token = expect_token(parser, Token_Type_Keyword_Defer); + defer->stmt = parse_statement(parser); + + retval = (AstNode *) defer; + break; + } + + case Token_Type_Keyword_Use: { + needs_semicolon = 0; + + retval = (AstNode *) parse_use_stmt(parser); + break; + } + + case '#': { + if (parse_possible_directive(parser, "context_scope")) { + // :LinearTokenDependent + OnyxToken* directive_token = parser->curr - 2; + + OnyxToken* sym_token = bh_alloc_item(parser->allocator, OnyxToken); + sym_token->type = Token_Type_Symbol; + sym_token->length = 15; + sym_token->text = bh_strdup(parser->allocator, "__saved_context "); + sym_token->pos = ((OnyxFilePos) {0}); + + AstNode *sym_node = make_symbol(parser->allocator, sym_token); + + AstLocal* context_tmp = make_local(parser->allocator, sym_token, NULL); + retval = (AstNode *) context_tmp; + + AstBinaryOp* assignment = make_node(AstBinaryOp, Ast_Kind_Binary_Op); + assignment->token = directive_token; + assignment->operation = Binary_Op_Assign; + assignment->left = (AstTyped *) sym_node; + assignment->right = builtin_context_variable; + context_tmp->next = (AstNode *) assignment; + + AstBinaryOp* assignment2 = make_node(AstBinaryOp, Ast_Kind_Binary_Op); + assignment2->token = directive_token + 1; + assignment2->operation = Binary_Op_Assign; + assignment2->left = builtin_context_variable; + assignment2->right = (AstTyped *) sym_node; + + AstDefer* defer_node = make_node(AstDefer, Ast_Kind_Defer); + defer_node->token = directive_token; + defer_node->stmt = (AstNode *) assignment2; + assignment->next = (AstNode *) defer_node; + + AstBlock* context_block = parse_block(parser, 1, NULL); + defer_node->next = context_block->body; + context_block->body = (AstNode *) context_tmp; + + needs_semicolon = 0; + break; + } + + if (next_tokens_are(parser, 2, '#', Token_Type_Keyword_If)) { + AstIf* static_if = parse_static_if_stmt(parser, 1); + + assert(parser->current_function_stack && bh_arr_length(parser->current_function_stack) > 0); + bh_arr_push(bh_arr_last(parser->current_function_stack)->nodes_that_need_entities_after_clone, (AstNode *) static_if); + + needs_semicolon = 0; + retval = (AstNode *) static_if; + break; + } + + if (parse_possible_directive(parser, "persist")) { + b32 thread_local = parse_possible_directive(parser, "thread_local"); + + OnyxToken* symbol = expect_token(parser, Token_Type_Symbol); + AstMemRes* memres = parse_memory_reservation(parser, symbol, thread_local); + + AstBinding* binding = make_node(AstBinding, Ast_Kind_Binding); + binding->token = memres->token; + binding->node = (AstNode *) memres; + ENTITY_SUBMIT(binding); + break; + } + + if (parse_possible_directive(parser, "remove")) { + // :LinearTokenDependent + AstDirectiveRemove *remove = make_node(AstDirectiveRemove, Ast_Kind_Directive_Remove); + remove->token = parser->curr - 2; + retval = (AstNode *) remove; + break; + } + + if (next_tokens_are(parser, 2, '#', Token_Type_Symbol)) { + retval = (AstNode *) parse_factor(parser); + break; + } + } + + default: + break; + } + + if (needs_semicolon) expect_token(parser, ';'); + + return retval; +} + +static AstBlock* parse_block(OnyxParser* parser, b32 make_a_new_scope, char* block_name) { + AstBlock* block = make_node(AstBlock, Ast_Kind_Block); + block->rules = Block_Rule_Normal; + + // NOTE: --- is for an empty block + if (parser->curr->type == Token_Type_Empty_Block) { + block->token = expect_token(parser, Token_Type_Empty_Block); + return block; + } + + if (make_a_new_scope) { + block->binding_scope = scope_create(parser->allocator, parser->current_scope, parser->curr->pos); + block->binding_scope->name = block_name; + parser->current_scope = block->binding_scope; + } + + if (parser->curr->type == Token_Type_Keyword_Do) { + block->token = expect_token(parser, Token_Type_Keyword_Do); + block->body = parse_statement(parser); + if (make_a_new_scope) parser->current_scope = parser->current_scope->parent; + return block; + } + + block->token = expect_token(parser, '{'); + + AstNode** next = &block->body; + AstNode* stmt = NULL; + while (!consume_token_if_next(parser, '}')) { + if (parser->hit_unexpected_token) { + if (make_a_new_scope) parser->current_scope = parser->current_scope->parent; + return block; + } + + stmt = parse_statement(parser); + + if (stmt != NULL && stmt->kind != Ast_Kind_Error) { + *next = stmt; + + while (stmt->next != NULL) stmt = stmt->next; + next = &stmt->next; + } + } + + if (make_a_new_scope) parser->current_scope = parser->current_scope->parent; + return block; +} + +static void parse_polymorphic_variable(OnyxParser* parser, AstType*** next_insertion) { + bh_arr(AstPolyParam) pv = NULL; + + if (parser->polymorph_context.poly_params == NULL) + onyx_report_error(parser->curr->pos, Error_Critical, "Polymorphic variable not valid here."); + else + pv = *parser->polymorph_context.poly_params; + + consume_token(parser); + + AstNode* symbol_node = make_node(AstNode, Ast_Kind_Symbol); + symbol_node->token = expect_token(parser, Token_Type_Symbol); + + **next_insertion = (AstType *) symbol_node; + *next_insertion = NULL; + + if (pv != NULL) { + bh_arr_push(pv, ((AstPolyParam) { + .kind = PPK_Poly_Type, + .poly_sym = symbol_node, + + // These will be filled out by function_params() + .type_expr = NULL, + .idx = -1, + })); + + *parser->polymorph_context.poly_params = pv; + } +} + +static AstType* parse_compound_type(OnyxParser* parser) { + // CLEANUP this is little weird having this here because it means that this parses: + // + // foo :: (x: (something_here: i32)) -> void --- + // + if (next_tokens_are(parser, 2, Token_Type_Symbol, ':')) { + consume_tokens(parser, 2); + } + + AstType* first = parse_type(parser); + + if (parser->curr->type == ',') { + AstCompoundType* ctype = make_node(AstCompoundType, Ast_Kind_Type_Compound); + ctype->token = parser->curr; + + bh_arr_new(global_heap_allocator, ctype->types, 2); + bh_arr_push(ctype->types, first); + + while (consume_token_if_next(parser, ',')) { + if (parser->hit_unexpected_token) return (AstType *) ctype; + + if (next_tokens_are(parser, 2, Token_Type_Symbol, ':')) { + consume_tokens(parser, 2); + } + + bh_arr_push(ctype->types, parse_type(parser)); + } + + return (AstType *) ctype; + + } else { + return first; + } +} + +static AstType* parse_function_type(OnyxParser* parser, OnyxToken* proc_token) { + bh_arr(AstType *) params = NULL; + bh_arr_new(global_scratch_allocator, params, 4); + bh_arr_set_length(params, 0); + + expect_token(parser, '('); + while (!consume_token_if_next(parser, ')')) { + if (parser->hit_unexpected_token) return NULL; + + // NOTE: Allows for names to be put in the function types, just for readability. + if (next_tokens_are(parser, 2, Token_Type_Symbol, ':')) consume_tokens(parser, 2); + + AstType* param_type = parse_type(parser); + bh_arr_push(params, param_type); + + if (parser->curr->type != ')') + expect_token(parser, ','); + } + + AstType* return_type = (AstType *) &basic_type_void; + if (consume_token_if_next(parser, Token_Type_Right_Arrow)) + return_type = parse_type(parser); + + i64 param_count = bh_arr_length(params); + AstFunctionType* new = onyx_ast_node_new(parser->allocator, + sizeof(AstFunctionType) + sizeof(AstType*) * param_count, + Ast_Kind_Function_Type); + new->token = proc_token; + new->param_count = param_count; + new->return_type = return_type; + + if (param_count > 0) + fori (i, 0, param_count) new->params[i] = params[i]; + + return (AstType *) new; +} + +static AstType* parse_type(OnyxParser* parser) { + AstType* root = NULL; + AstType** next_insertion = &root; + + while (1) { + if (parser->hit_unexpected_token) return root; + + switch ((u16) parser->curr->type) { + case '^': { + AstPointerType* new = make_node(AstPointerType, Ast_Kind_Pointer_Type); + new->flags |= Basic_Flag_Pointer; + new->token = expect_token(parser, '^'); + + *next_insertion = (AstType *) new; + next_insertion = &new->elem; + break; + } + + case '[': { + AstType *new; + OnyxToken *open_bracket = expect_token(parser, '['); + + if (parser->curr->type == ']') { + new = make_node(AstSliceType, Ast_Kind_Slice_Type); + new->token = open_bracket; + + } else if (parser->curr->type == Token_Type_Dot_Dot) { + new = make_node(AstDynArrType, Ast_Kind_DynArr_Type); + new->token = open_bracket; + consume_token(parser); + + } else { + new = make_node(AstArrayType, Ast_Kind_Array_Type); + new->token = open_bracket; + + if (parser->curr->type == '$') { + AstType** insertion = (AstType **) &((AstArrayType *) new)->count_expr; + parse_polymorphic_variable(parser, &insertion); + } else { + ((AstArrayType *) new)->count_expr = parse_expression(parser, 0); + } + } + + expect_token(parser, ']'); + *next_insertion = (AstType *) new; + next_insertion = &((AstSliceType *) new)->elem; + break; + } + + case '$': { + parse_polymorphic_variable(parser, &next_insertion); + break; + } + + case Token_Type_Symbol: { + AstNode* symbol_node = make_node(AstNode, Ast_Kind_Symbol); + symbol_node->token = expect_token(parser, Token_Type_Symbol); + + *next_insertion = (AstType *) symbol_node; + + while (consume_token_if_next(parser, '.')) { + AstFieldAccess* field = make_node(AstFieldAccess, Ast_Kind_Field_Access); + field->token = expect_token(parser, Token_Type_Symbol); + field->expr = (AstTyped *) *next_insertion; + + *next_insertion = (AstType *) field; + } + + if (parser->curr->type == '(' && parser->parse_calls) { + OnyxToken* paren_token = expect_token(parser, '('); + + bh_arr(AstNode *) params = NULL; + bh_arr_new(global_heap_allocator, params, 2); + + while (!consume_token_if_next(parser, ')')) { + if (parser->hit_unexpected_token) break; + + AstNode* t = (AstNode *) parse_expression(parser, 0); + bh_arr_push(params, t); + + if (parser->curr->type != ')') + expect_token(parser, ','); + } + + AstPolyCallType* pc_type = make_node(AstPolyCallType, Ast_Kind_Poly_Call_Type); + pc_type->token = paren_token; + pc_type->callee = *next_insertion; + pc_type->params = params; + + *next_insertion = (AstType *) pc_type; + } + + next_insertion = NULL; + break; + } + + case Token_Type_Keyword_Struct: { + AstStructType* s_node = parse_struct(parser); + *next_insertion = (AstType *) s_node; + next_insertion = NULL; + break; + } + + // + // I don't think any of these cases are necesary any more? + case Token_Type_Literal_Integer: + case Token_Type_Literal_String: + case Token_Type_Literal_Float: + case Token_Type_Literal_True: + case Token_Type_Literal_False: + case '-': { + *next_insertion = (AstType *) parse_expression(parser, 0); + next_insertion = NULL; + break; + } + + case '(': { + OnyxToken* matching = find_matching_paren(parser->curr); + + // :LinearTokenDependent + if ((matching + 1)->type == Token_Type_Right_Arrow) { + *next_insertion = parse_function_type(parser, parser->curr); + + } else { + expect_token(parser, '('); + *next_insertion = parse_compound_type(parser); + expect_token(parser, ')'); + } + + next_insertion = NULL; + break; + } + + case Token_Type_Keyword_Typeof: { + *next_insertion = (AstType *) parse_typeof(parser); + next_insertion = NULL; + break; + } + + default: + onyx_report_error(parser->curr->pos, Error_Critical, "unexpected token '%b'.", parser->curr->text, parser->curr->length); + consume_token(parser); + break; + } + + if (next_insertion == NULL) break; + } + + return root; +} + +static AstTypeOf* parse_typeof(OnyxParser* parser) { + OnyxToken* token = expect_token(parser, Token_Type_Keyword_Typeof); + + AstTypeOf* type_of = make_node(AstTypeOf, Ast_Kind_Typeof); + type_of->token = token; + type_of->expr = parse_expression(parser, 0); + type_of->resolved_type = NULL; + + return type_of; +} + +static void struct_type_create_scope(OnyxParser *parser, AstStructType *s_node) { + if (!s_node->scope) { + s_node->scope = scope_create(context.ast_alloc, parser->current_scope, s_node->token->pos); + parser->current_scope = s_node->scope; + + OnyxToken* current_symbol = bh_arr_last(parser->current_symbol_stack); + s_node->scope->name = bh_aprintf(global_heap_allocator, "%b", current_symbol->text, current_symbol->length); + } +} + +static AstStructType* parse_struct(OnyxParser* parser) { + OnyxToken *s_token = expect_token(parser, Token_Type_Keyword_Struct); + + AstStructType* s_node; + AstPolyStructType* poly_struct = NULL; + + s_node = make_node(AstStructType, Ast_Kind_Struct_Type); + s_node->token = s_token; + + flush_stored_tags(parser, &s_node->meta_tags); + + // Parse polymorphic parameters + if (consume_token_if_next(parser, '(')) { + bh_arr(AstPolyStructParam) poly_params = NULL; + bh_arr_new(global_heap_allocator, poly_params, 1); + + while (!consume_token_if_next(parser, ')')) { + if (parser->hit_unexpected_token) return NULL; + + OnyxToken* sym_token = expect_token(parser, Token_Type_Symbol); + expect_token(parser, ':'); + + AstType* param_type = parse_type(parser); + + bh_arr_push(poly_params, ((AstPolyStructParam) { + .token = sym_token, + .type_node = param_type, + .type = NULL, + })); + + if (parser->curr->type != ')') + expect_token(parser, ','); + } + + poly_struct = make_node(AstPolyStructType, Ast_Kind_Poly_Struct_Type); + poly_struct->token = s_token; + poly_struct->poly_params = poly_params; + poly_struct->base_struct = s_node; + } + + // Parse constraints clause + if (parser->curr->type == Token_Type_Keyword_Where) { + parse_constraints(parser, &s_node->constraints); + } + + bh_arr_new(global_heap_allocator, s_node->members, 4); + + // Parse directives + while (parser->curr->type == '#') { + if (parser->hit_unexpected_token) return NULL; + + if (parse_possible_directive(parser, "union")) s_node->is_union = 1; + + else if (parse_possible_directive(parser, "pack")) s_node->is_packed = 1; + + else if (parse_possible_directive(parser, "align")) { + s_node->min_alignment_ = parse_expression(parser, 0); + } + + else if (parse_possible_directive(parser, "size")) { + s_node->min_size_ = parse_expression(parser, 0); + } + + else { + OnyxToken* directive_token = expect_token(parser, '#'); + OnyxToken* symbol_token = expect_token(parser, Token_Type_Symbol); + + onyx_report_error(directive_token->pos, Error_Critical, "unknown directive '#%b'.", symbol_token->text, symbol_token->length); + } + } + + expect_token(parser, '{'); + + b32 member_is_used = 0; + bh_arr(OnyxToken *) member_list_temp = NULL; + bh_arr_new(global_heap_allocator, member_list_temp, 4); + + while (!consume_token_if_next(parser, '}')) { + if (parser->hit_unexpected_token) return s_node; + + if (parse_possible_directive(parser, "persist")) { + struct_type_create_scope(parser, s_node); + + b32 thread_local = parse_possible_directive(parser, "thread_local"); + + OnyxToken* symbol = expect_token(parser, Token_Type_Symbol); + AstMemRes* memres = parse_memory_reservation(parser, symbol, thread_local); + consume_token_if_next(parser, ';'); + + AstBinding* binding = make_node(AstBinding, Ast_Kind_Binding); + binding->token = memres->token; + binding->node = (AstNode *) memres; + ENTITY_SUBMIT(binding); + continue; + } + + if (next_tokens_are(parser, 3, Token_Type_Symbol, ':', ':')) { + struct_type_create_scope(parser, s_node); + + OnyxToken* binding_name = expect_token(parser, Token_Type_Symbol); + consume_token(parser); + + AstBinding* binding = parse_top_level_binding(parser, binding_name); + if (binding) ENTITY_SUBMIT(binding); + + consume_token_if_next(parser, ';'); + continue; + } + + bh_arr(AstTyped *) meta_tags=NULL; + while (parse_possible_directive(parser, "tag")) { + if (meta_tags == NULL) bh_arr_new(global_heap_allocator, meta_tags, 1); + + parser->tag_depth += 1; + + do { + AstTyped* expr = parse_expression(parser, 0); + bh_arr_push(meta_tags, expr); + } while (consume_token_if_next(parser, ',')); + + parser->tag_depth -= 1; + } + + member_is_used = consume_token_if_next(parser, Token_Type_Keyword_Use); + + bh_arr_clear(member_list_temp); + while (!consume_token_if_next(parser, ':')) { + if (parser->hit_unexpected_token) return NULL; + bh_arr_push(member_list_temp, expect_token(parser, Token_Type_Symbol)); + + if (parser->curr->type != ':') + expect_token(parser, ','); + } + + AstType* member_type = NULL; + if (parser->curr->type != '=') + member_type = parse_type(parser); + + AstTyped* initial_value = NULL; + if (consume_token_if_next(parser, '=')) + initial_value = parse_expression(parser, 0); + + // RECONSIDER: There are seamingly arbitrary limitations put in place here which do two things: + // 1. Prevent multiple struct members being used in the same declaration. + // This makes sense because the members will be of the same type, which means + // they have the same members. Using both of the members would immediately result + // in name collisions. + // + // 2. Prevent multiple struct members having an initializer set for them. + // I think the semantics could be confusing either way, so I'm deciding to leave + // them out of discussion for now. Initialized members should be treated special and + // deserve their own line. + if (bh_arr_length(member_list_temp) > 1) { + if (member_is_used) onyx_report_error((member_list_temp[0] - 1)->pos, Error_Critical, "'use' is only allowed for a single struct member declaration. Try splitting this compound declaration into multiple lines."); + if (initial_value) onyx_report_error(initial_value->token->pos, Error_Critical, "Intialized values are only allowed on single struct member declarations. Try splitting this compound initializer into multiple lines."); + } + + bh_arr_each(OnyxToken *, member_name, member_list_temp) { + AstStructMember* mem = make_node(AstStructMember, Ast_Kind_Struct_Member); + mem->token = *member_name; + mem->type_node = member_type; + mem->initial_value = initial_value; + mem->meta_tags = meta_tags; + + if (member_is_used) mem->is_used = 1; + + bh_arr_push(s_node->members, mem); + } + + if (peek_token(0)->type != '}') { + expect_token(parser, ';'); + } + } + + if (s_node->scope) parser->current_scope = parser->current_scope->parent; + + bh_arr_free(member_list_temp); + + if (poly_struct != NULL) { + // NOTE: Not a StructType + return (AstStructType *) poly_struct; + + } else { + ENTITY_SUBMIT(s_node); + return s_node; + } +} + +static AstInterface* parse_interface(OnyxParser* parser) { + AstInterface *interface = make_node(AstInterface, Ast_Kind_Interface); + interface->token = expect_token(parser, Token_Type_Keyword_Interface); + + bh_arr_new(global_heap_allocator, interface->params, 2); + + expect_token(parser, '('); + while (!consume_token_if_next(parser, ')')) { + if (parser->hit_unexpected_token) return interface; + + InterfaceParam ip; + ip.value_token = expect_token(parser, Token_Type_Symbol); + expect_token(parser, ':'); + expect_token(parser, '$'); + ip.type_token = expect_token(parser, Token_Type_Symbol); + + bh_arr_push(interface->params, ip); + + if (parser->curr->type != ')') + expect_token(parser, ','); + } + + bh_arr_new(global_heap_allocator, interface->exprs, 2); + + expect_token(parser, '{'); + while (!consume_token_if_next(parser, '}')) { + if (parser->hit_unexpected_token) return interface; + + InterfaceConstraint ic = {0}; + if (parse_possible_directive(parser, "not")) { + ic.invert_condition = 1; + } + + if (consume_token_if_next(parser, '{')) { + ic.expr = parse_expression(parser, 0); + + expect_token(parser, '}'); + expect_token(parser, Token_Type_Right_Arrow); + + ic.expected_type_expr = parse_type(parser); + + } else { + ic.expr = parse_expression(parser, 0); + } + + bh_arr_push(interface->exprs, ic); + + expect_token(parser, ';'); + } + + return interface; +} + +static AstConstraint* parse_constraint(OnyxParser* parser) { + AstConstraint* constraint = make_node(AstConstraint, Ast_Kind_Constraint); + + parser->parse_calls = 0; + constraint->interface = (AstInterface *) parse_factor(parser); + parser->parse_calls = 1; + + constraint->token = constraint->interface->token; + + bh_arr_new(global_heap_allocator, constraint->type_args, 2); + + expect_token(parser, '('); + while (!consume_token_if_next(parser, ')')) { + if (parser->hit_unexpected_token) return constraint; + + AstType* type_node = parse_type(parser); + bh_arr_push(constraint->type_args, type_node); + + if (parser->curr->type != ')') + expect_token(parser, ','); + } + + return constraint; +} + +static void parse_constraints(OnyxParser* parser, ConstraintContext *out_constraints) { + bh_arr_new(global_heap_allocator, out_constraints->constraints, 2); + + expect_token(parser, Token_Type_Keyword_Where); + + do { + AstConstraint *constraint = parse_constraint(parser); + if (parser->hit_unexpected_token) return; + + bh_arr_push(out_constraints->constraints, constraint); + } while (consume_token_if_next(parser, ',')); +} + +static void parse_function_params(OnyxParser* parser, AstFunction* func) { + expect_token(parser, '('); + + if (consume_token_if_next(parser, ')')) return; + + u32 param_idx = 0; + assert(parser->polymorph_context.poly_params != NULL); + + bh_arr(AstParam) param_buffer=NULL; + bh_arr_new(global_heap_allocator, param_buffer, 2); + + OnyxToken* symbol; + while (!consume_token_if_next(parser, ')')) { + do { + if (parser->hit_unexpected_token) return; + + b32 param_use = 0; + b32 param_is_baked = 0; + AstParam curr_param = { 0 }; + + if (consume_token_if_next(parser, Token_Type_Keyword_Use)) param_use = 1; + if (consume_token_if_next(parser, '$')) param_is_baked = 1; + + symbol = expect_token(parser, Token_Type_Symbol); + + curr_param.vararg_kind = VA_Kind_Not_VA; + curr_param.local = make_local(parser->allocator, symbol, NULL); + curr_param.local->kind = Ast_Kind_Param; + + if (param_use) { + curr_param.is_used = 1; + param_use = 0; + } + + if (param_is_baked) { + curr_param.is_baked = 1; + param_is_baked = 0; + } + + bh_arr_push(param_buffer, curr_param); + } while (consume_token_if_next(parser, ',')); + + expect_token(parser, ':'); + + VarArgKind vararg_kind=VA_Kind_Not_VA; + AstType* type_node=NULL; + AstTyped* default_value=NULL; + + if (parser->curr->type != '=') { + if (consume_token_if_next(parser, Token_Type_Dot_Dot)) { + if (consume_token_if_next(parser, '.')) vararg_kind = VA_Kind_Untyped; + else vararg_kind = VA_Kind_Typed; + } + + if (vararg_kind != VA_Kind_Untyped) { + // CLEANUP: This is mess and it is hard to follow what is going on here. + // I think with recent rewrites, this should be easier to do. + i32 old_len = bh_arr_length(*parser->polymorph_context.poly_params); + type_node = parse_type(parser); + i32 new_len = bh_arr_length(*parser->polymorph_context.poly_params); + + if (vararg_kind == VA_Kind_Typed) { + AstVarArgType* va_type = make_node(AstVarArgType, Ast_Kind_VarArg_Type); + va_type->elem = type_node; + va_type->token = type_node->token; + type_node = (AstType *) va_type; + } + + fori (i, 0, new_len - old_len) { + (*parser->polymorph_context.poly_params)[old_len + i].type_expr = type_node; + (*parser->polymorph_context.poly_params)[old_len + i].idx = param_idx; + } + } + } + + if (vararg_kind == VA_Kind_Not_VA && consume_token_if_next(parser, '=')) { + OnyxToken* directive_token = parser->curr; + + // :Callsite currently #callsite is only valid as a default value for a funciton parameter. + if (parse_possible_directive(parser, "callsite")) { + AstCallSite* cs = make_node(AstCallSite, Ast_Kind_Call_Site); + cs->token = directive_token; + default_value = (AstTyped *) cs; + + } else { + default_value = parse_expression(parser, 0); + } + } + + bh_arr_each(AstParam, param, param_buffer) { + param->vararg_kind = vararg_kind; + param->local->type_node = type_node; + param->default_value = default_value; + + if (param->is_baked) { + bh_arr(AstPolyParam) pv = *parser->polymorph_context.poly_params; + bh_arr_push(pv, ((AstPolyParam) { + .kind = PPK_Baked_Value, + .idx = param_idx, + + .poly_sym = (AstNode *) param->local, + .type_expr = type_node, + })); + + *parser->polymorph_context.poly_params = pv; + } + + bh_arr_push(func->params, *param); + param_idx++; + } + + bh_arr_clear(param_buffer); + + if (parser->curr->type != ')') + expect_token(parser, ','); + } + + bh_arr_free(param_buffer); + return; +} + +static AstOverloadedFunction* parse_overloaded_function(OnyxParser* parser, OnyxToken* token) { + b32 locked = 0; + if (parse_possible_directive(parser, "locked")) { + locked = 1; + } + + b32 local = 0; + if (parse_possible_directive(parser, "local")) { + local = 1; + } + + // This could be checked elsewhere? + if (locked && local) { + onyx_report_error(token->pos, Error_Critical, "Only one of '#locked' and '#local' can because use at a time."); + } + + expect_token(parser, '{'); + + AstOverloadedFunction* ofunc = make_node(AstOverloadedFunction, Ast_Kind_Overloaded_Function); + ofunc->token = token; + ofunc->flags |= Ast_Flag_Comptime; + ofunc->locked = locked; + ofunc->only_local_functions = local; + + bh_arr_new(global_heap_allocator, ofunc->overloads, 4); + + u64 precedence = 0; + while (!consume_token_if_next(parser, '}')) { + if (parser->hit_unexpected_token) return ofunc; + + if (parse_possible_directive(parser, "precedence")) { + AstNumLit* pre = parse_int_literal(parser); + if (parser->hit_unexpected_token) return ofunc; + + precedence = bh_max(pre->value.l, 0); + } + + AstTyped* option = parse_expression(parser, 0); + add_overload_option(&ofunc->overloads, precedence++, option); + + if (parser->curr->type != '}') + expect_token(parser, ','); + } + + ENTITY_SUBMIT(ofunc); + return ofunc; +} + +static AstFunction* parse_function_definition(OnyxParser* parser, OnyxToken* token) { + AstFunction* func_def = make_node(AstFunction, Ast_Kind_Function); + func_def->token = token; + bh_arr_push(parser->current_function_stack, func_def); + + flush_stored_tags(parser, &func_def->tags); + + bh_arr_new(global_heap_allocator, func_def->params, 4); + + bh_arr(AstPolyParam) polymorphic_vars = NULL; + bh_arr_new(global_heap_allocator, polymorphic_vars, 4); + // defer bh_arr_free(polymorphic_vars); + + parser->polymorph_context.poly_params = &polymorphic_vars; + parse_function_params(parser, func_def); + parser->polymorph_context.poly_params = NULL; + + func_def->return_type = (AstType *) &basic_type_void; + + char* name = NULL; + if (bh_arr_length(parser->current_symbol_stack) > 0) { + OnyxToken *current_symbol = bh_arr_last(parser->current_symbol_stack); + name = bh_aprintf(global_heap_allocator, "%b", current_symbol->text, current_symbol->length); + } + + if (consume_token_if_next(parser, '=')) { + expect_token(parser, '>'); + func_def->return_type = (AstType *) &basic_type_auto_return; + + if (parser->curr->type == '{') { + func_def->body = parse_block(parser, 1, name); + + } else { + AstTyped* returned_value = parse_compound_expression(parser, 0); + if (returned_value == NULL) goto function_defined; + + AstReturn* return_node = make_node(AstReturn, Ast_Kind_Return); + return_node->token = returned_value->token; + return_node->expr = returned_value; + + AstBlock* body_block = make_node(AstBlock, Ast_Kind_Block); + body_block->token = returned_value->token; + body_block->body = (AstNode *) return_node; + + func_def->body = body_block; + } + + goto function_defined; + } + + if (consume_token_if_next(parser, Token_Type_Right_Arrow)) { + if (parse_possible_directive(parser, "auto")) { + func_def->return_type = (AstType *) &basic_type_auto_return; + } else { + func_def->return_type = parse_type(parser); + } + } + + if (parser->curr->type == Token_Type_Keyword_Where) { + parse_constraints(parser, &func_def->constraints); + } + + while (parser->curr->type == '#') { + if (parse_possible_directive(parser, "intrinsic")) { + func_def->is_intrinsic = 1; + + if (parser->curr->type == Token_Type_Literal_String) { + func_def->intrinsic_name = expect_token(parser, Token_Type_Literal_String); + } + } + + else if (parse_possible_directive(parser, "foreign")) { + func_def->foreign_module = expect_token(parser, Token_Type_Literal_String); + func_def->foreign_name = expect_token(parser, Token_Type_Literal_String); + + func_def->is_foreign = 1; + } + + // HACK: NullProcHack + else if (parse_possible_directive(parser, "null")) { + func_def->flags |= Ast_Flag_Proc_Is_Null; + } + + else { + OnyxToken* directive_token = expect_token(parser, '#'); + OnyxToken* symbol_token = expect_token(parser, Token_Type_Symbol); + + onyx_report_error(directive_token->pos, Error_Critical, "unknown directive '#%b'.", symbol_token->text, symbol_token->length); + } + } + + func_def->body = parse_block(parser, 1, name); + + // :LinearTokenDependent + func_def->closing_brace = parser->curr - 1; + +function_defined: + if (bh_arr_length(polymorphic_vars) > 0) { + func_def->kind = Ast_Kind_Polymorphic_Proc; + func_def->poly_params = polymorphic_vars; + + } else { + bh_arr_free(polymorphic_vars); + } + + bh_arr_pop(parser->current_function_stack); + return func_def; +} + +static b32 parse_possible_function_definition_no_consume(OnyxParser* parser) { + if (parser->curr->type == '(') { + OnyxToken* matching_paren = find_matching_paren(parser->curr); + if (matching_paren == NULL) return 0; + + if (next_tokens_are(parser, 4, '(', ')', '=', '>')) return 0; + + // :LinearTokenDependent + OnyxToken* token_after_paren = matching_paren + 1; + if (token_after_paren->type != Token_Type_Right_Arrow + && token_after_paren->type != '{' + && token_after_paren->type != Token_Type_Keyword_Do + && token_after_paren->type != Token_Type_Empty_Block + && token_after_paren->type != Token_Type_Keyword_Where + && (token_after_paren->type != '=' || (token_after_paren + 1)->type != '>')) + return 0; + + // :LinearTokenDependent + b32 is_params = (parser->curr + 1) == matching_paren; + OnyxToken* tmp_token = parser->curr; + while (!is_params && tmp_token < matching_paren) { + if (tmp_token->type == ':') is_params = 1; + + tmp_token++; + } + + if (!is_params) return 0; + + return 1; + } + + return 0; +} + +static b32 parse_possible_function_definition(OnyxParser* parser, AstTyped** ret) { + if (parse_possible_function_definition_no_consume(parser)) { + OnyxToken* proc_token = parser->curr; + AstFunction* func_node = parse_function_definition(parser, proc_token); + *ret = (AstTyped *) func_node; + + return 1; + } + + return 0; +} + +typedef struct QuickParam { + OnyxToken* token; + b32 is_baked; +} QuickParam; + +static b32 parse_possible_quick_function_definition_no_consume(OnyxParser* parser) { + // + // x => x + 1 case. + if (next_tokens_are(parser, 3, Token_Type_Symbol, '=', '>')) { + return 1; + } + + if (parser->curr->type != '(') return 0; + + OnyxToken* matching_paren = find_matching_paren(parser->curr); + if (matching_paren == NULL) return 0; + + // :LinearTokenDependent + OnyxToken* token_after_paren = matching_paren + 1; + if (token_after_paren->type != '=' || (token_after_paren + 1)->type != '>') + return 0; + + return 1; +} + +static b32 parse_possible_quick_function_definition(OnyxParser* parser, AstTyped** ret) { + if (!parse_possible_quick_function_definition_no_consume(parser)) return 0; + + bh_arr(QuickParam) params=NULL; + bh_arr_new(global_heap_allocator, params, 4); + OnyxToken* proc_token; + + if (parser->curr->type == Token_Type_Symbol) { + QuickParam param = { 0 }; + param.token = expect_token(parser, Token_Type_Symbol); + proc_token = param.token; + bh_arr_push(params, param); + + } else { + proc_token = expect_token(parser, '('); + while (parser->curr->type != ')') { + if (parser->hit_unexpected_token) return 0; + + QuickParam param = { 0 }; + if (consume_token_if_next(parser, '$')) param.is_baked = 1; + param.token = expect_token(parser, Token_Type_Symbol); + + bh_arr_push(params, param); + + if (parser->curr->type != ')') { + expect_token(parser, ','); + } + } + + expect_token(parser, ')'); + } + + expect_token(parser, '='); + expect_token(parser, '>'); + + bh_arr(AstNode *) poly_params=NULL; + bh_arr_new(global_heap_allocator, poly_params, bh_arr_length(params)); + bh_arr_each(QuickParam, param, params) { + char text[512]; + memset(text, 0, 512); + strncat(text, "__type_", 511); + token_toggle_end(param->token); + strncat(text, param->token->text, 511); + token_toggle_end(param->token); + + OnyxToken* new_token = bh_alloc(parser->allocator, sizeof(OnyxToken)); + new_token->type = Token_Type_Symbol; + new_token->length = 7 + param->token->length; + new_token->text = bh_strdup(parser->allocator, text); + new_token->pos = param->token->pos; + + AstNode* type_node = make_symbol(parser->allocator, new_token); + bh_arr_push(poly_params, type_node); + } + + AstFunction* poly_proc = make_node(AstFunction, Ast_Kind_Polymorphic_Proc); + + bh_arr_new(global_heap_allocator, poly_proc->params, bh_arr_length(params)); + fori (i, 0, bh_arr_length(params)) { + AstLocal* param_local = make_local(parser->allocator, params[i].token, (AstType *) poly_params[i]); + param_local->kind = Ast_Kind_Param; + + bh_arr_push(poly_proc->params, ((AstParam) { + .local = param_local, + .default_value = NULL, + + .vararg_kind = 0, + .use_processed = 0, + })); + } + + AstBlock* body_block; + AstType* return_type; + bh_arr_push(parser->current_function_stack, poly_proc); + + if (parser->curr->type == '{') { + char* name = NULL; + if (bh_arr_length(parser->current_symbol_stack) > 0) { + OnyxToken *current_symbol = bh_arr_last(parser->current_symbol_stack); + name = bh_aprintf(global_heap_allocator, "%b", current_symbol->text, current_symbol->length); + } + + body_block = parse_block(parser, 1, name); + return_type = (AstType *) &basic_type_auto_return; + + } else { + AstTyped* body = parse_compound_expression(parser, 0); + + AstReturn* return_node = make_node(AstReturn, Ast_Kind_Return); + return_node->token = body->token; + return_node->expr = body; + + body_block = make_node(AstBlock, Ast_Kind_Block); + body_block->token = body->token; + body_block->body = (AstNode *) return_node; + + AstTypeOf* return_type_of = make_node(AstTypeOf, Ast_Kind_Typeof); + return_type_of->token = body->token; + return_type_of->expr = body; + return_type = (AstType *) return_type_of; + } + + poly_proc->token = proc_token; + poly_proc->body = body_block; + poly_proc->return_type = (AstType *) return_type; + + poly_proc->token = proc_token; + bh_arr_new(global_heap_allocator, poly_proc->poly_params, bh_arr_length(params)); + fori (i, 0, bh_arr_length(params)) { + bh_arr_push(poly_proc->poly_params, ((AstPolyParam) { + .kind = PSK_Type, + .idx = i, + .poly_sym = poly_params[i], + .type_expr = (AstType *) poly_params[i], + .type = NULL, + })); + + if (params[i].is_baked) { + // This is not handled currently, as you cannot say f :: ($x: $T) yet, which is what this would have to do. + } + } + + *ret = (AstTyped *) poly_proc; + + bh_arr_pop(parser->current_function_stack); + bh_arr_free(params); + bh_arr_free(poly_params); + return 1; +} + +static AstTyped* parse_global_declaration(OnyxParser* parser) { + expect_no_stored_tags(parser); + + AstGlobal* global_node = make_node(AstGlobal, Ast_Kind_Global); + global_node->token = expect_token(parser, Token_Type_Keyword_Global); + + global_node->type_node = parse_type(parser); + + ENTITY_SUBMIT(global_node); + + return (AstTyped *) global_node; +} + +static AstEnumType* parse_enum_declaration(OnyxParser* parser) { + expect_no_stored_tags(parser); + + AstEnumType* enum_node = make_node(AstEnumType, Ast_Kind_Enum_Type); + enum_node->token = expect_token(parser, Token_Type_Keyword_Enum); + + bh_arr_new(global_heap_allocator, enum_node->values, 4); + + while (parser->curr->type == '#') { + if (parser->hit_unexpected_token) return enum_node; + + if (parse_possible_directive(parser, "flags")) { + enum_node->is_flags = 1; + } else { + OnyxToken* directive_token = expect_token(parser, '#'); + OnyxToken* symbol_token = expect_token(parser, Token_Type_Symbol); + + onyx_report_error(directive_token->pos, Error_Critical, "unknown directive '#%b'.", symbol_token->text, symbol_token->length); + } + } + + AstType* backing = (AstType *) &basic_type_u32; + if (consume_token_if_next(parser, '(')) { + AstNode* backing_sym = make_node(AstNode, Ast_Kind_Symbol); + backing_sym->token = expect_token(parser, Token_Type_Symbol); + backing = (AstType *) backing_sym; + + expect_token(parser, ')'); + } + enum_node->backing = backing; + + expect_token(parser, '{'); + + while (!consume_token_if_next(parser, '}')) { + if (parser->hit_unexpected_token) return enum_node; + + AstEnumValue* evalue = make_node(AstEnumValue, Ast_Kind_Enum_Value); + evalue->token = expect_token(parser, Token_Type_Symbol); + evalue->type_node = (AstType *) enum_node; + + if (consume_token_if_next(parser, ':')) { + expect_token(parser, ':'); + evalue->value = parse_expression(parser, 0); + } + + expect_token(parser, ';'); + + bh_arr_push(enum_node->values, evalue); + } + + return enum_node; +} + +static AstIf* parse_static_if_stmt(OnyxParser* parser, b32 parse_block_as_statements) { + expect_no_stored_tags(parser); + + AstIf* static_if_node = make_node(AstIf, Ast_Kind_Static_If); + static_if_node->token = expect_token(parser, '#'); + static_if_node->defined_in_scope = parser->current_scope; + expect_token(parser, Token_Type_Keyword_If); + + static_if_node->cond = parse_expression(parser, 0); + + bh_arr_new(global_heap_allocator, static_if_node->true_entities, 2); + bh_arr_push(parser->alternate_entity_placement_stack, &static_if_node->true_entities); + + if (parse_block_as_statements) { + static_if_node->true_stmt = parse_block(parser, 0, NULL); + + } else { + expect_token(parser, '{'); + while (!consume_token_if_next(parser, '}')) { + if (parser->hit_unexpected_token) return static_if_node; + + parse_top_level_statement(parser); + } + } + + bh_arr_pop(parser->alternate_entity_placement_stack); + + if (consume_token_if_next(parser, Token_Type_Keyword_Else)) { + bh_arr_new(global_heap_allocator, static_if_node->false_entities, 2); + bh_arr_push(parser->alternate_entity_placement_stack, &static_if_node->false_entities); + + if (parse_block_as_statements) { + static_if_node->false_stmt = parse_block(parser, 0, NULL); + + } else { + expect_token(parser, '{'); + while (!consume_token_if_next(parser, '}')) { + if (parser->hit_unexpected_token) return static_if_node; + + parse_top_level_statement(parser); + } + } + + bh_arr_pop(parser->alternate_entity_placement_stack); + } + + return static_if_node; +} + +static AstMemRes* parse_memory_reservation(OnyxParser* parser, OnyxToken* symbol, b32 threadlocal) { + expect_token(parser, ':'); + + expect_no_stored_tags(parser); + + AstMemRes* memres = make_node(AstMemRes, Ast_Kind_Memres); + memres->threadlocal = threadlocal; + memres->token = symbol; + + if (parser->curr->type != '=') + memres->type_node = parse_type(parser); + + if (consume_token_if_next(parser, '=')) + memres->initial_value = parse_expression(parser, 1); + + ENTITY_SUBMIT(memres); + return memres; +} + +static AstMacro* parse_macro(OnyxParser* parser) { + expect_no_stored_tags(parser); + + AstMacro* macro = make_node(AstMacro, Ast_Kind_Macro); + macro->token = expect_token(parser, Token_Type_Keyword_Macro); + + if (parse_possible_function_definition(parser, ¯o->body)) { + ENTITY_SUBMIT(macro); + return macro; + } + + if (parse_possible_quick_function_definition(parser, ¯o->body)) { + ENTITY_SUBMIT(macro); + return macro; + } + + onyx_report_error(parser->curr->pos, Error_Critical, "'macro' expects to be followed by a producure definition."); + return NULL; +} + +static AstDirectiveInit* parse_init_directive(OnyxParser *parser, OnyxToken *token) { + AstDirectiveInit *init = make_node(AstDirectiveInit, Ast_Kind_Directive_Init); + init->token = token; + + parser->parse_calls = 0; + while (parse_possible_directive(parser, "after")) { + if (parser->hit_unexpected_token) return init; + if (init->dependencies == NULL) bh_arr_new(global_heap_allocator, init->dependencies, 2); + + AstTyped *dependency = parse_expression(parser, 0); + bh_arr_push(init->dependencies, (AstDirectiveInit *) dependency); + } + parser->parse_calls = 1; + + init->init_proc = parse_expression(parser, 0); + ENTITY_SUBMIT(init); + return init; +} + +static AstForeignBlock* parse_foreign_block(OnyxParser* parser, OnyxToken *token) { + // :LinearTokenDependent + AstForeignBlock *fb = make_node(AstForeignBlock, Ast_Kind_Foreign_Block); + fb->token = token; + fb->module_name = expect_token(parser, Token_Type_Literal_String); + + // + // This has a fun implication that there cannot be foreign blocks in the builtin + // or type_info packages, as those are loaded before foreign_block_type has a value. + fb->type_node = foreign_block_type; + + bh_arr_new(global_heap_allocator, fb->captured_entities, 4); + bh_arr_push(parser->alternate_entity_placement_stack, &fb->captured_entities); + + expect_token(parser, '{'); + parse_top_level_statements_until(parser, '}'); + expect_token(parser, '}'); + + bh_arr_pop(parser->alternate_entity_placement_stack); + ENTITY_SUBMIT(fb); + + return fb; +} + +static AstTyped* parse_top_level_expression(OnyxParser* parser) { + if (parser->curr->type == Token_Type_Keyword_Global) return parse_global_declaration(parser); + if (parser->curr->type == Token_Type_Keyword_Struct) return (AstTyped *) parse_struct(parser); + if (parser->curr->type == Token_Type_Keyword_Interface) return (AstTyped *) parse_interface(parser); + if (parser->curr->type == Token_Type_Keyword_Enum) return (AstTyped *) parse_enum_declaration(parser); + if (parser->curr->type == Token_Type_Keyword_Macro) return (AstTyped *) parse_macro(parser); + + if (parser->curr->type == '#') { + if (parse_possible_directive(parser, "type")) { + AstTypeAlias* alias = make_node(AstTypeAlias, Ast_Kind_Type_Alias); + alias->to = parse_type(parser); + return (AstTyped *) alias; + } + + if (parse_possible_directive(parser, "match")) { + // :LinearTokenDependent + OnyxToken* directive_token = parser->curr - 2; + AstOverloadedFunction* ofunc = parse_overloaded_function(parser, directive_token); + return (AstTyped *) ofunc; + } + + if (parse_possible_directive(parser, "init")) { + // :LinearTokenDependent + AstDirectiveInit *init = parse_init_directive(parser, parser->curr - 2); + return (AstTyped *) init; + } + + if (parse_possible_directive(parser, "distinct")) { + // :LinearTokenDependent + AstDistinctType *distinct = make_node(AstDistinctType, Ast_Kind_Distinct_Type); + distinct->token = parser->curr - 2; + distinct->base_type = parse_type(parser); + return (AstTyped *) distinct; + } + + if (parse_possible_directive(parser, "foreign")) { + AstForeignBlock *foreign = parse_foreign_block(parser, parser->curr - 2); + return (AstTyped *) foreign; + } + } + + return parse_expression(parser, 1); +} + +static char* generate_name_within_scope(OnyxParser* parser, OnyxToken* symbol) { + char name[512]; + memset(name, 0, 512); + + bh_arr(char *) names=NULL; + bh_arr_new(global_heap_allocator, names, 4); + + Scope* scope = parser->current_scope; + while (scope != NULL) { + bh_arr_push(names, scope->name); + scope = scope->parent; + } + + bh_arr_each(char *, n, names) { + if (*n == NULL) continue; + + strncat(name, *n, 511); + strncat(name, ".", 511); + } + bh_arr_free(names); + + return bh_aprintf(global_heap_allocator, "%s%b", name, symbol->text, symbol->length); +} + +static AstBinding* parse_top_level_binding(OnyxParser* parser, OnyxToken* symbol) { + OnyxToken *after_second_colon = expect_token(parser, ':'); + if (after_second_colon) after_second_colon += 1; + + AstTyped* node = parse_top_level_expression(parser); + if (parser->hit_unexpected_token || node == NULL) + return NULL; + + switch (node->kind) { + case Ast_Kind_Function: + case Ast_Kind_Polymorphic_Proc: { + AstFunction* func = (AstFunction *) node; + + if (func->intrinsic_name == NULL) func->intrinsic_name = symbol; + + func->name = generate_name_within_scope(parser, symbol); + break; + } + + case Ast_Kind_Macro: { + AstMacro* macro = (AstMacro *) node; + + AstFunction* func = (AstFunction *) macro->body; + func->name = generate_name_within_scope(parser, symbol); + break; + } + + case Ast_Kind_Directive_Init: break; + + case Ast_Kind_Global: ((AstGlobal *) node)->name = generate_name_within_scope(parser, symbol); + + case Ast_Kind_Overloaded_Function: + case Ast_Kind_StrLit: + break; + + case Ast_Kind_Interface: + case Ast_Kind_Struct_Type: + case Ast_Kind_Poly_Struct_Type: + case Ast_Kind_Enum_Type: + case Ast_Kind_Distinct_Type: + ((AstStructType *) node)->name = generate_name_within_scope(parser, symbol); + goto default_case; + + case Ast_Kind_Type_Alias: + node->token = symbol; + goto default_case; + + case Ast_Kind_Package: goto default_case; + case Ast_Kind_NumLit: goto default_case; + + default: { + if (!node_is_type((AstNode *) node)) { + AstAlias* alias = make_node(AstAlias, Ast_Kind_Alias); + alias->token = node->token; + alias->alias = node; + node = (AstTyped *) alias; + } + +default_case: + ENTITY_SUBMIT(node); + } + } + + AstBinding* binding = make_node(AstBinding, Ast_Kind_Binding); + binding->token = symbol; + binding->node = (AstNode *) node; + + if (after_second_colon) expect_no_stored_tags_pos(parser, after_second_colon->pos); + return binding; +} + +static void parse_top_level_statement(OnyxParser* parser) { + AstFlags private_kind = 0; + if (bh_arr_length(parser->scope_flags) > 0) + private_kind = bh_arr_last(parser->scope_flags); + + // :CLEANUP this very repetetive code... + if (next_tokens_are(parser, 2, '#', Token_Type_Keyword_Package)) { + consume_tokens(parser, 2); + private_kind = Ast_Flag_Private_Package; + if (parser->curr->type == '{') { + bh_arr_push(parser->scope_flags, private_kind); + + expect_token(parser, '{'); + parse_top_level_statements_until(parser, '}'); + expect_token(parser, '}'); + + bh_arr_pop(parser->scope_flags); + return; + } + } + else if (parse_possible_directive(parser, "local")) { + private_kind = Ast_Flag_Private_File; + if (parser->curr->type == '{') { + bh_arr_push(parser->scope_flags, private_kind); + + expect_token(parser, '{'); + parse_top_level_statements_until(parser, '}'); + expect_token(parser, '}'); + + bh_arr_pop(parser->scope_flags); + return; + } + } + + AstBinding* binding = NULL; + + switch ((u16) parser->curr->type) { + case Token_Type_Keyword_Use: { + AstNode* use_node = parse_use_stmt(parser); + if (use_node) ENTITY_SUBMIT(use_node); + return; + } + + case Token_Type_Symbol: { + OnyxToken* symbol = expect_token(parser, Token_Type_Symbol); + + if (next_tokens_are(parser, 2, ':', ':')) { + expect_token(parser, ':'); + + bh_arr_push(parser->current_symbol_stack, symbol); + binding = parse_top_level_binding(parser, symbol); + bh_arr_pop(parser->current_symbol_stack); + + if (binding != NULL) binding->flags |= private_kind; + + goto submit_binding_to_entities; + } + + AstMemRes* memres = parse_memory_reservation(parser, symbol, 0); + + binding = make_node(AstBinding, Ast_Kind_Binding); + binding->token = symbol; + binding->flags |= private_kind; + binding->node = (AstNode *) memres; + + goto submit_binding_to_entities; + } + + case '(': { + AstTyped *retval = NULL; + if (parse_possible_function_definition(parser, &retval)) { + ENTITY_SUBMIT(retval); + return; + } + if (parse_possible_quick_function_definition(parser, &retval)) { + ENTITY_SUBMIT(retval); + return; + } + break; + } + + case '#': { + if (next_tokens_are(parser, 2, '#', Token_Type_Keyword_If)) { + AstIf* static_if = parse_static_if_stmt(parser, 0); + ENTITY_SUBMIT(static_if); + return; + } + + OnyxToken* dir_token = parser->curr; + + if (parse_possible_directive(parser, "load")) { + AstInclude* include = make_node(AstInclude, Ast_Kind_Load_File); + include->token = dir_token; + include->name_node = parse_expression(parser, 0); + + ENTITY_SUBMIT(include); + return; + } + else if (parse_possible_directive(parser, "load_all")) { + AstInclude* include = make_node(AstInclude, Ast_Kind_Load_All); + include->token = dir_token; + include->name_node = parse_expression(parser, 0); + + ENTITY_SUBMIT(include); + return; + } + else if (parse_possible_directive(parser, "load_path")) { + AstInclude* include = make_node(AstInclude, Ast_Kind_Load_Path); + include->token = dir_token; + include->name_node = parse_expression(parser, 0); + + ENTITY_SUBMIT(include); + return; + } + else if (parse_possible_directive(parser, "library_path")) { + AstInclude* include = make_node(AstInclude, Ast_Kind_Library_Path); + include->token = dir_token; + include->name_node = parse_expression(parser, 0); + + ENTITY_SUBMIT(include); + return; + } + else if (parse_possible_directive(parser, "error")) { + AstDirectiveError *error = make_node(AstDirectiveError, Ast_Kind_Directive_Error); + error->token = dir_token; + error->error_msg = expect_token(parser, Token_Type_Literal_String); + + ENTITY_SUBMIT(error); + return; + } + else if (parse_possible_directive(parser, "foreign")) { + parse_foreign_block(parser, parser->curr - 2); + return; + } + else if (parse_possible_directive(parser, "operator")) { + AstDirectiveOperator *operator = make_node(AstDirectiveOperator, Ast_Kind_Directive_Operator); + operator->token = dir_token; + + // These cases have to happen first because these are not necessarily "binary operators", + // they are just things that I want to be able to overload. []= is technically a ternary + // operator so all these things are horribly named anyway. + if (next_tokens_are(parser, 3, '^', '[', ']')) { + consume_tokens(parser, 3); + operator->operator = Binary_Op_Ptr_Subscript; + goto operator_determined; + } + + if (next_tokens_are(parser, 3, '[', ']', '=')) { + consume_tokens(parser, 3); + operator->operator = Binary_Op_Subscript_Equals; + goto operator_determined; + } + + // The default case + BinaryOp op = binary_op_from_token_type(parser->curr->type); + consume_token(parser); + if (op == Binary_Op_Subscript) expect_token(parser, ']'); // #operator [] ... needs to consume the other ']' + + if (op == Binary_Op_Count) { + onyx_report_error(parser->curr->pos, Error_Critical, "Invalid binary operator."); + } else { + operator->operator = op; + } + + operator_determined: + operator->overload = parse_expression(parser, 0); + + ENTITY_SUBMIT(operator); + return; + } + else if (parse_possible_directive(parser, "match") || parse_possible_directive(parser, "overload")) { + AstDirectiveAddOverload *add_overload = make_node(AstDirectiveAddOverload, Ast_Kind_Directive_Add_Overload); + add_overload->token = dir_token; + + if (parse_possible_directive(parser, "precedence")) { + AstNumLit* pre = parse_int_literal(parser); + if (parser->hit_unexpected_token) return; + + add_overload->precedence = bh_max(pre->value.l, 0); + } else { + add_overload->precedence = 0; + } + + parser->parse_calls = 0; + add_overload->overloaded_function = (AstNode *) parse_expression(parser, 0); + parser->parse_calls = 1; + + // Allow for + // #match + // something :: (....) { + // } + // + // This will make converting something to a overloaded + // function easier and require less copying by the programmer. + if (next_tokens_are(parser, 2, ':', ':')) { + consume_tokens(parser, 2); + } + + add_overload->overload = parse_expression(parser, 0); + + ENTITY_SUBMIT(add_overload); + return; + } + else if (parse_possible_directive(parser, "inject")) { + AstInjection *inject = make_node(AstInjection, Ast_Kind_Injection); + inject->token = dir_token; + + parser->parse_calls = 0; + inject->full_loc = parse_expression(parser, 0); + parser->parse_calls = 1; + + // See comment above + if (next_tokens_are(parser, 2, ':', ':')) { + consume_tokens(parser, 2); + } + + inject->to_inject = parse_expression(parser, 0); + + ENTITY_SUBMIT(inject); + return; + } + else if (parse_possible_directive(parser, "export")) { + AstDirectiveExport *export = make_node(AstDirectiveExport, Ast_Kind_Directive_Export); + export->token = dir_token; + parser->parse_calls = 0; + export->export_name_expr = parse_expression(parser, 0); // expect_token(parser, Token_Type_Literal_String); + parser->parse_calls = 1; + + export->export = parse_expression(parser, 0); + + ENTITY_SUBMIT(export); + return; + } + else if (parse_possible_directive(parser, "thread_local")) { + OnyxToken* symbol = expect_token(parser, Token_Type_Symbol); + AstMemRes* memres = parse_memory_reservation(parser, symbol, 1); + + binding = make_node(AstBinding, Ast_Kind_Binding); + binding->token = symbol; + binding->flags |= private_kind; + binding->node = (AstNode *) memres; + + goto submit_binding_to_entities; + } + else if (parse_possible_directive(parser, "init")) { + // :LinearTokenDependent + parse_init_directive(parser, parser->curr - 2); + return; + } + else if (parse_possible_directive(parser, "library")) { + // :LinearTokenDependent + AstDirectiveLibrary *library = make_node(AstDirectiveLibrary, Ast_Kind_Directive_Library); + library->token = parser->curr - 2; + library->library_symbol = parse_expression(parser, 0); + + ENTITY_SUBMIT(library); + return; + } + else if (parse_possible_directive(parser, "tag")) { + parser->tag_depth += 1; + + AstTyped *expr = parse_expression(parser, 0); + bh_arr_push(parser->stored_tags, expr); + + parser->tag_depth -= 1; + return; + } + else { + OnyxToken* directive_token = expect_token(parser, '#'); + OnyxToken* symbol_token = expect_token(parser, Token_Type_Symbol); + + onyx_report_error(directive_token->pos, Error_Critical, "unknown directive '#%b'.", symbol_token->text, symbol_token->length); + return; + } + } + + default: break; + } + + expect_token(parser, ';'); + return; + +submit_binding_to_entities: + { + if (!binding) return; + + Scope* target_scope = parser->package->scope; + + if (binding->flags & Ast_Flag_Private_Package) + target_scope = parser->package->private_scope; + if (binding->flags & Ast_Flag_Private_File) + target_scope = parser->file_scope; + + ENTITY_SUBMIT_IN_SCOPE(binding, target_scope); + } +} + +static AstPackage* parse_package_expression(OnyxParser* parser) { + AstPackage* package_node = make_node(AstPackage, Ast_Kind_Package); + package_node->token = expect_token(parser, Token_Type_Keyword_Package); + + bh_arr_new(global_heap_allocator, package_node->path, 2); + + while (parser->curr->type == Token_Type_Symbol) { + if (parser->hit_unexpected_token) return package_node; + + OnyxToken* symbol = expect_token(parser, Token_Type_Symbol); + + bh_arr_push(package_node->path, symbol); + + if (consume_token_if_next(parser, '.')); + else break; + } + + i32 total_package_name_length = 0; + bh_arr_each(OnyxToken *, token, package_node->path) { + total_package_name_length += (*token)->length + 1; + } + + char* package_name = bh_alloc_array(context.ast_alloc, char, total_package_name_length); + *package_name = '\0'; + + bh_arr_each(OnyxToken *, token, package_node->path) { + token_toggle_end(*token); + strncat(package_name, (*token)->text, total_package_name_length - 1); + token_toggle_end(*token); + + if (token != &bh_arr_last(package_node->path)) { + strncat(package_name, ".", total_package_name_length - 1); + } + } + + package_node->package_name = package_name; + package_node->package = package_lookup(package_name); + + return package_node; +} + +static Package* parse_file_package(OnyxParser* parser) { + if (parser->curr->type != Token_Type_Keyword_Package) { + return package_lookup_or_create("main", context.global_scope, parser->allocator, parser->curr->pos); + } + + AstPackage* package_node = parse_package_expression(parser); + + char aggregate_name[2048]; + aggregate_name[0] = '\0'; + + Package* prevpackage = NULL; + + bh_arr_each(OnyxToken *, symbol, package_node->path) { + token_toggle_end(*symbol); + + strncat(aggregate_name, (*symbol)->text, 2047); + Package* newpackage = package_lookup_or_create(aggregate_name, context.global_scope, parser->allocator, package_node->token->pos); + + AstPackage* pnode = make_node(AstPackage, Ast_Kind_Package); + pnode->token = *symbol; + pnode->package = newpackage; + pnode->package_name = newpackage->name; + + if (prevpackage != NULL) { + symbol_subpackage_introduce(prevpackage->scope, (*symbol)->text, pnode); + package_reinsert_use_packages(prevpackage); + } + + token_toggle_end(*symbol); + strncat(aggregate_name, ".", 2047); + + prevpackage = newpackage; + } + + package_node->package = prevpackage; + + return package_node->package; +} + +static void parse_top_level_statements_until(OnyxParser* parser, TokenType tt) { + while (parser->curr->type != tt) { + if (parser->hit_unexpected_token) break; + if (onyx_has_errors()) break; + parse_top_level_statement(parser); + } +} + + +// NOTE: This returns a void* so I don't need to cast it everytime I use it +void* onyx_ast_node_new(bh_allocator alloc, i32 size, AstKind kind) { + void* node = bh_alloc(alloc, size); + + memset(node, 0, size); + *(AstKind *) node = kind; + + return node; +} + +OnyxParser onyx_parser_create(bh_allocator alloc, OnyxTokenizer *tokenizer) { + OnyxParser parser; + + parser.allocator = alloc; + parser.tokenizer = tokenizer; + parser.curr = tokenizer->tokens; + parser.prev = NULL; + parser.hit_unexpected_token = 0; + parser.current_scope = NULL; + parser.alternate_entity_placement_stack = NULL; + parser.current_symbol_stack = NULL; + parser.current_function_stack = NULL; + parser.scope_flags = NULL; + parser.stored_tags = NULL; + parser.parse_calls = 1; + parser.tag_depth = 0; + + parser.polymorph_context = (PolymorphicContext) { + .root_node = NULL, + .poly_params = NULL, + }; + + bh_arr_new(global_heap_allocator, parser.alternate_entity_placement_stack, 4); + bh_arr_new(global_heap_allocator, parser.current_symbol_stack, 4); + bh_arr_new(global_heap_allocator, parser.scope_flags, 4); + bh_arr_new(global_heap_allocator, parser.stored_tags, 4); + + return parser; +} + +void onyx_parser_free(OnyxParser* parser) { +} + +void onyx_parse(OnyxParser *parser) { + // NOTE: Skip comments at the beginning of the file + while (consume_token_if_next(parser, Token_Type_Comment) || consume_token_if_next(parser, Token_Type_Note)); + + parser->package = parse_file_package(parser); + parser->file_scope = scope_create(parser->allocator, parser->package->private_scope, parser->tokenizer->tokens[0].pos); + parser->current_scope = parser->file_scope; + + parse_top_level_statements_until(parser, Token_Type_End_Stream); + + parser->current_scope = parser->current_scope->parent; +} diff --git a/compiler/src/polymorph.h b/compiler/src/polymorph.h new file mode 100644 index 00000000..0185b8f3 --- /dev/null +++ b/compiler/src/polymorph.h @@ -0,0 +1,1091 @@ + +// +// Polymorphic Procedures +// + +// This flag is used by some of the procedures that try working with polymorphic things, +// but need to wait until more information is known. Instead of passing a out parameter +// into each of these procedures, a single global variable is used instead. If the type +// checker ever gets multi-threaded, this would have to become a threadlocal variable. +static b32 flag_to_yield = 0; + +// This flag is used in the very special case that you are passing a polymorphic procedure +// to a polymorphic procedure, and you have enough information to instantiate said procedure +// in order to resolve the type of one of the return values. +static b32 doing_nested_polymorph_lookup = 0; + +// The name is pretty self-descriptive, but this is a node that is returned from things +// like polymorphic_proc_lookup when it is determined that everything works so far, but +// the caller must yield in order to finish checking this polymorphic procedure. +AstTyped node_that_signals_a_yield = { Ast_Kind_Function, 0 }; +AstTyped node_that_signals_failure = { Ast_Kind_Error, 0 }; + +static void ensure_polyproc_cache_is_created(AstFunction* pp) { + if (pp->concrete_funcs == NULL) sh_new_arena(pp->concrete_funcs); + if (pp->active_queries.hashes == NULL) bh_imap_init(&pp->active_queries, global_heap_allocator, 31); +} + +void insert_poly_sln_into_scope(Scope* scope, AstPolySolution *sln) { + AstNode *node = NULL; + + switch (sln->kind) { + case PSK_Type: + node = onyx_ast_node_new(context.ast_alloc, sizeof(AstTypeRawAlias), Ast_Kind_Type_Raw_Alias); + ((AstTypeRawAlias *) node)->token = sln->poly_sym->token; + ((AstTypeRawAlias *) node)->to = sln->type; + ((AstTypeRawAlias *) node)->type = &basic_types[Basic_Kind_Type_Index]; + ((AstTypeRawAlias *) node)->type_id = sln->type->id; + break; + + case PSK_Value: + // CLEANUP: Maybe clone this? + assert(sln->value->flags & Ast_Flag_Comptime); + node = (AstNode *) sln->value; + break; + } + + symbol_introduce(scope, sln->poly_sym->token, node); +} + +static void insert_poly_slns_into_scope(Scope* scope, bh_arr(AstPolySolution) slns) { + bh_arr_each(AstPolySolution, sln, slns) { + insert_poly_sln_into_scope(scope, sln); + } +} + +// NOTE: This might return a volatile string. Do not store it without copying it. +static char* build_poly_solution_key(AstPolySolution* sln) { + if (sln->kind == PSK_Type) { + return (char *) type_get_unique_name(sln->type); + + } else if (sln->kind == PSK_Value) { + static char buffer[256]; + + fori (i, 0, 256) buffer[i] = 0; + + if (sln->value->kind == Ast_Kind_NumLit) { + strncat(buffer, "NUMLIT:", 127); + strncat(buffer, bh_bprintf("%l", ((AstNumLit *) sln->value)->value.l), 127); + + } else { + // HACK: For now, the value pointer is just used. This means that + // sometimes, even through the solution is the same, it won't be + // stored the same. + bh_snprintf(buffer, 128, "%p", sln->value); + } + + return buffer; + } + + return NULL; +} + +// NOTE: This returns a volatile string. Do not store it without copying it. +static char* build_poly_slns_unique_key(bh_arr(AstPolySolution) slns) { + static char key_buf[1024]; + fori (i, 0, 1024) key_buf[i] = 0; + + bh_arr_each(AstPolySolution, sln, slns) { + token_toggle_end(sln->poly_sym->token); + + strncat(key_buf, sln->poly_sym->token->text, 1023); + strncat(key_buf, "=", 1023); + strncat(key_buf, build_poly_solution_key(sln), 1023); + strncat(key_buf, ";", 1023); + + token_toggle_end(sln->poly_sym->token); + } + + return key_buf; +} + +// NOTE: This function adds a solidified function to the entity heap for it to be processed +// later. It optionally can start the function header entity at the code generation state if +// the header has already been processed. +static b32 add_solidified_function_entities(AstSolidifiedFunction *solidified_func) { + solidified_func->func->flags |= Ast_Flag_Function_Used; + solidified_func->func->flags |= Ast_Flag_From_Polymorphism; + + Entity func_header_entity = { + .state = Entity_State_Resolve_Symbols, + .type = Entity_Type_Function_Header, + .function = solidified_func->func, + .package = NULL, + .scope = solidified_func->func->poly_scope, + }; + + Entity func_entity = { + .state = Entity_State_Resolve_Symbols, + .type = Entity_Type_Function, + .function = solidified_func->func, + .package = NULL, + .scope = solidified_func->func->poly_scope, + }; + + Entity* entity_header = entity_heap_insert(&context.entities, func_header_entity); + Entity* entity_body = entity_heap_insert(&context.entities, func_entity); + + solidified_func->func_header_entity = entity_header; + solidified_func->func->entity_header = entity_header; + solidified_func->func->entity_body = entity_body; + solidified_func->func->entity = entity_body; + return 1; +} + +// NOTE: This function is responsible for taking all of the information about generating +// a new polymorphic variant, and producing a solidified function. It optionally can only +// generate the header of the function, which is useful for cases such as checking if a +// set of arguments works for a polymorphic overload option. +static AstSolidifiedFunction generate_solidified_function( + AstFunction* pp, + bh_arr(AstPolySolution) slns, + OnyxToken* tkn, + b32 header_only) { + + AstSolidifiedFunction solidified_func; + solidified_func.func_header_entity = NULL; + + // NOTE: Use the position of token if one was provided, otherwise just use NULL. + OnyxFilePos poly_scope_pos = { 0 }; + if (tkn) poly_scope_pos = tkn->pos; + + if (header_only) { + solidified_func.func = (AstFunction *) clone_function_header(context.ast_alloc, pp); + solidified_func.func->flags |= Ast_Flag_Incomplete_Body; + + } else { + solidified_func.func = (AstFunction *) ast_clone(context.ast_alloc, pp); + } + + solidified_func.func->poly_scope = scope_create(context.ast_alloc, pp->parent_scope_of_poly_proc, poly_scope_pos); + insert_poly_slns_into_scope(solidified_func.func->poly_scope, slns); + + solidified_func.func->flags |= Ast_Flag_From_Polymorphism; + solidified_func.func->generated_from = tkn; + + // HACK: Remove the baked parameters from the function defintion so they can be + // resolved in the poly scope above the function. This does feel kinda of gross + // and I would love an alternative to tell it to just "skip" the parameter, but + // that is liable to breaking because it is one more thing to remember. + // - brendanfh 2021/01/18 + u32 removed_params = 0; + bh_arr_each(AstPolyParam, param, pp->poly_params) { + if (param->kind != PPK_Baked_Value) continue; + + bh_arr_deleten(solidified_func.func->params, param->idx - removed_params, 1); + removed_params++; + } + + return solidified_func; +} + +static void ensure_solidified_function_has_body(AstFunction* pp, AstSolidifiedFunction *solidified_func) { + if (solidified_func->func->flags & Ast_Flag_Incomplete_Body) { + clone_function_body(context.ast_alloc, solidified_func->func, pp); + + // HACK: I'm asserting that this function should return without an error, because + // the only case where it can return an error is if there was a problem with the + // header. This should never be the case in this situation, since the header would + // have to have successfully passed type checking before it would become a solidified + // procedure. + assert(add_solidified_function_entities(solidified_func)); + + solidified_func->func->flags &= ~Ast_Flag_Incomplete_Body; + } +} + +// NOTE: These are temporary data structures used to represent the pattern matching system +// of polymorphic type resolution. +typedef struct PolySolveResult { + PolySolutionKind kind; + union { + AstTyped* value; + Type* actual; + }; +} PolySolveResult; + +typedef struct PolySolveElem { + AstType* type_expr; + + PolySolutionKind kind; + union { + AstTyped* value; + Type* actual; + }; +} PolySolveElem; + +// NOTE: The job of this function is to solve for the type/value that belongs in a +// polymorphic variable. This function takes in three arguments: +// * The symbol node of the polymorphic parameter being searched for +// * The type expression that should contain the symbol node it is some where +// * The actual type to pattern match against +// +// This function utilizes a basic breadth-first search of the type_expr and actual type +// trees, always moving along them in parallel, so when the target is reached (if it is +// ever reached), the "actual" is the matched type/value. +static PolySolveResult solve_poly_type(AstNode* target, AstType* type_expr, Type* actual) { + bh_arr(PolySolveElem) elem_queue = NULL; + bh_arr_new(global_heap_allocator, elem_queue, 4); + + PolySolveResult result = { PSK_Undefined, { NULL } }; + + bh_arr_push(elem_queue, ((PolySolveElem) { + .type_expr = type_expr, + .kind = PSK_Type, + .actual = actual + })); + + while (!bh_arr_is_empty(elem_queue)) { + PolySolveElem elem = elem_queue[0]; + bh_arr_deleten(elem_queue, 0, 1); + + if (elem.type_expr == (AstType *) target) { + result.kind = elem.kind; + + assert(elem.kind != PSK_Undefined); + if (result.kind == PSK_Type) result.actual = elem.actual; + if (result.kind == PSK_Value) result.value = elem.value; + break; + } + + if (elem.kind != PSK_Type) continue; + + switch (elem.type_expr->kind) { + case Ast_Kind_Pointer_Type: { + if (elem.actual->kind != Type_Kind_Pointer) break; + + bh_arr_push(elem_queue, ((PolySolveElem) { + .type_expr = ((AstPointerType *) elem.type_expr)->elem, + .kind = PSK_Type, + .actual = elem.actual->Pointer.elem, + })); + break; + } + + case Ast_Kind_Address_Of: { + if (elem.actual->kind != Type_Kind_Pointer) break; + + bh_arr_push(elem_queue, ((PolySolveElem) { + .type_expr = (AstType *) ((AstAddressOf *) elem.type_expr)->expr, + .kind = PSK_Type, + .actual = elem.actual->Pointer.elem, + })); + break; + } + + case Ast_Kind_Array_Type: { + if (elem.actual->kind != Type_Kind_Array) break; + + bh_arr_push(elem_queue, ((PolySolveElem) { + .type_expr = (AstType*) ((AstArrayType *) elem.type_expr)->count_expr, + .kind = PSK_Value, + + // CLEANUP: Making an integer literal every time is very very very gross. This should + // at least be cached or something. + .value = (AstTyped *) make_int_literal(context.ast_alloc, elem.actual->Array.count) + })); + + bh_arr_push(elem_queue, ((PolySolveElem) { + .type_expr = ((AstArrayType *) elem.type_expr)->elem, + .kind = PSK_Type, + .actual = elem.actual->Array.elem, + })); + break; + } + + case Ast_Kind_Slice_Type: { + if (elem.actual->kind != Type_Kind_Slice && elem.actual->kind != Type_Kind_DynArray + && elem.actual->kind != Type_Kind_VarArgs && elem.actual->kind != Type_Kind_Array) break; + + bh_arr_push(elem_queue, ((PolySolveElem) { + .type_expr = ((AstSliceType *) elem.type_expr)->elem, + .kind = PSK_Type, + + // HACK: This makes the assumption that arrays, slices, dynamic arrays and varargs have the same element type at the same location. + .actual = elem.actual->Slice.elem, + })); + break; + } + + case Ast_Kind_DynArr_Type: { + if (elem.actual->kind != Type_Kind_DynArray) break; + + bh_arr_push(elem_queue, ((PolySolveElem) { + .type_expr = ((AstDynArrType *) elem.type_expr)->elem, + .kind = PSK_Type, + .actual = elem.actual->DynArray.elem, + })); + break; + } + + case Ast_Kind_VarArg_Type: + bh_arr_push(elem_queue, ((PolySolveElem) { + .type_expr = ((AstVarArgType *) elem.type_expr)->elem, + .kind = PSK_Type, + .actual = actual, + })); + break; + + case Ast_Kind_Function_Type: { + if (elem.actual->kind != Type_Kind_Function) break; + + AstFunctionType* ft = (AstFunctionType *) elem.type_expr; + + fori (i, 0, (i64) ft->param_count) { + bh_arr_push(elem_queue, ((PolySolveElem) { + .type_expr = ft->params[i], + .kind = PSK_Type, + .actual = elem.actual->Function.params[i], + })); + } + + bh_arr_push(elem_queue, ((PolySolveElem) { + .type_expr = ft->return_type, + .kind = PSK_Type, + .actual = elem.actual->Function.return_type, + })); + + break; + } + + case Ast_Kind_Call: { + AstPolyCallType *pct = convert_call_to_polycall((AstCall *) elem.type_expr); + elem.type_expr = (AstType *) pct; + + // fallthrough + } + + case Ast_Kind_Poly_Call_Type: { + if (elem.actual->kind != Type_Kind_Struct) break; + if (bh_arr_length(elem.actual->Struct.poly_sln) != bh_arr_length(((AstPolyCallType *) elem.type_expr)->params)) break; + + AstPolyCallType* pt = (AstPolyCallType *) elem.type_expr; + + fori (i, 0, bh_arr_length(pt->params)) { + PolySolutionKind kind = elem.actual->Struct.poly_sln[i].kind; + if (kind == PSK_Type) { + bh_arr_push(elem_queue, ((PolySolveElem) { + .kind = kind, + .type_expr = (AstType *) pt->params[i], + .actual = elem.actual->Struct.poly_sln[i].type, + })); + } else { + bh_arr_push(elem_queue, ((PolySolveElem) { + .kind = kind, + .type_expr = (AstType *) pt->params[i], + .value = elem.actual->Struct.poly_sln[i].value, + })); + } + } + + break; + } + + case Ast_Kind_Type_Compound: { + if (elem.actual->kind != Type_Kind_Compound) break; + if (elem.actual->Compound.count != (u32) bh_arr_length(((AstCompoundType *) elem.type_expr)->types)) break; + + AstCompoundType* ct = (AstCompoundType *) elem.type_expr; + + fori (i, 0, bh_arr_length(ct->types)) { + bh_arr_push(elem_queue, ((PolySolveElem) { + .kind = PSK_Type, + .type_expr = ct->types[i], + .actual = elem.actual->Compound.types[i], + })); + } + + break; + } + + default: break; + } + } + + bh_arr_free(elem_queue); + + return result; +} + +// NOTE: The job of this function is to take a polymorphic parameter and a set of arguments +// and solve for the argument that matches the parameter. This is needed because polymorphic +// procedure resolution has to happen before the named arguments are placed in their correct +// positions. +static AstTyped* lookup_param_in_arguments(AstFunction* func, AstPolyParam* param, Arguments* args, char** err_msg) { + bh_arr(AstTyped *) arg_arr = args->values; + bh_arr(AstNamedValue *) named_values = args->named_values; + + if ((i32) param->idx < 0) + return NULL; + + // NOTE: This check is safe because currently the arguments given without a name + // always map to the beginning indidies of the argument array. + if (param->idx >= (u64) bh_arr_length(arg_arr)) { + OnyxToken* param_name = func->params[param->idx].local->token; + + bh_arr_each(AstNamedValue *, named_value, named_values) { + if (token_equals(param_name, (*named_value)->token)) { + return (AstTyped *) (*named_value)->value; + } + } + + if (param->idx <= (u64) bh_arr_length(func->params)) { + if (func->params[param->idx].default_value) { + return (AstTyped *) func->params[param->idx].default_value; + } + } + + // CLEANUP + if (err_msg) *err_msg = "Not enough arguments to polymorphic procedure. This error message may not be entirely right."; + + } else { + return (AstTyped *) arg_arr[param->idx]; + } + + return NULL; +} + +static AstTyped* try_lookup_based_on_partial_function_type(AstFunction *pp, AstFunctionType *ft) { + if (ft->partial_function_type == NULL) { + AstType *old_return_type = ft->return_type; + ft->return_type = (AstType *) &basic_type_void; + ft->partial_function_type = type_build_from_ast(context.ast_alloc, (AstType *) ft); + ft->return_type = old_return_type; + if (!ft->partial_function_type) { + doing_nested_polymorph_lookup = 1; + return NULL; + } + + assert(ft->partial_function_type); + } + + AstTyped *result = (AstTyped *) polymorphic_proc_lookup(pp, PPLM_By_Function_Type, ft->partial_function_type, pp->token); + if (result && result->type == NULL) { + doing_nested_polymorph_lookup = 1; + result = NULL; + } + if (result == &node_that_signals_a_yield) { + doing_nested_polymorph_lookup = 1; + result = NULL; + } + + return result; +} + +// NOTE: The job of this function is to solve for type of AstPolySolution using the provided +// information. It is asssumed that the "param" is of kind PPK_Poly_Type. This function uses +// either the arguments provided, or a function type to compare against to pattern match for +// the type that the parameter but be. +static void solve_for_polymorphic_param_type(PolySolveResult* resolved, AstFunction* func, AstPolyParam* param, PolyProcLookupMethod pp_lookup, ptr actual, char** err_msg) { + Type* actual_type = NULL; + + switch (pp_lookup) { + case PPLM_By_Arguments: { + Arguments* args = (Arguments *) actual; + + AstTyped* typed_param = lookup_param_in_arguments(func, param, args, err_msg); + if (typed_param == NULL) return; + + // CLEANUP FIXME HACK TODO GROSS + if (typed_param->kind == Ast_Kind_Argument) { + AstTyped* potential = ((AstArgument *) typed_param)->value; + if (potential->kind == Ast_Kind_Polymorphic_Proc) { + if (param->idx < (u32) bh_arr_length(func->params)) { + AstType *param_type = func->params[param->idx].local->type_node; + if (param_type->kind == Ast_Kind_Function_Type) { + AstFunctionType *ft = (AstFunctionType *) param_type; + b32 all_types = 1; + fori (i, 0, (i32) ft->param_count) { + if (!node_is_type((AstNode *) ft->params[i])) { + all_types = 0; + break; + } + } + + if (all_types) { + typed_param = try_lookup_based_on_partial_function_type((AstFunction *) potential, ft); + } + } + } + } + } + + actual_type = resolve_expression_type(typed_param); + if (actual_type == NULL) return; + + break; + } + + case PPLM_By_Function_Type: { + Type* ft = (Type *) actual; + if (param->idx >= ft->Function.param_count) { + if (err_msg) *err_msg = "Incompatible polymorphic argument to function parameter."; + return; + } + + actual_type = ft->Function.params[param->idx]; + break; + } + + default: return; + } + + PolySolveResult res = solve_poly_type(param->poly_sym, param->type_expr, actual_type); + if (res.kind == PSK_Undefined) { + *err_msg = bh_aprintf(global_scratch_allocator, + "Unable to solve for polymorphic variable '%b', given the type '%s'.", + param->poly_sym->token->text, + param->poly_sym->token->length, + type_get_name(actual_type)); + } + + *resolved = res; +} + + +// NOTE: The job of this function is to look through the arguments provided and find a matching +// value that is to be baked into the polymorphic procedures poly-scope. It expected that param +// will be of kind PPK_Baked_Value. In other words, this handles the ($Baked: type) case. +// CLEANUP: This function is kind of gross at the moment, because it handles different cases for +// the argument kind. When type expressions (type_expr) become first-class types in the type +// system, this code should be able to be a lot cleaner. +static void solve_for_polymorphic_param_value(PolySolveResult* resolved, AstFunction* func, AstPolyParam* param, PolyProcLookupMethod pp_lookup, ptr actual, char** err_msg) { + if (pp_lookup != PPLM_By_Arguments) { + *err_msg = "Function type cannot be used to solved for baked parameter value."; + return; + } + + Arguments* args = (Arguments *) actual; + AstTyped* value = lookup_param_in_arguments(func, param, args, err_msg); + if (value == NULL) return; + + // HACK: Storing the original value because if this was an AstArgument, we need to flag + // it as baked if it is determined that the argument is of the correct kind and type. + AstTyped* orig_value = value; + if (value->kind == Ast_Kind_Argument) { + ((AstArgument *) orig_value)->is_baked = 0; + value = ((AstArgument *) value)->value; + } + + Type* param_type = NULL; + AstType *param_type_expr = func->params[param->idx].local->type_node; + if (param_type_expr == (AstType *) &basic_type_type_expr) { + if (!node_is_type((AstNode *) value)) { + if (err_msg) *err_msg = "Expected type expression."; + return; + } + + Type* resolved_type = type_build_from_ast(context.ast_alloc, (AstType *) value); + if (resolved_type == NULL) flag_to_yield = 1; + + *resolved = ((PolySolveResult) { PSK_Type, .actual = resolved_type }); + + } else { + resolve_expression_type(value); + + if ((value->flags & Ast_Flag_Comptime) == 0) { + if (err_msg) *err_msg = "Expected compile-time known argument."; + return; + } + + param_type = type_build_from_ast(context.ast_alloc, param_type_expr); + if (param_type == NULL) { + flag_to_yield = 1; + *err_msg = "Waiting to know type for polymorphic value."; + return; + } + + AstTyped* value_to_use = value; + if (value->kind == Ast_Kind_Macro) { + value_to_use = (AstTyped *) get_function_from_node((AstNode *) value); + } + + TypeMatch tm = unify_node_and_type(&value_to_use, param_type); + if (tm == TYPE_MATCH_FAILED) { + if (err_msg) *err_msg = bh_aprintf(global_scratch_allocator, + "The procedure '%s' expects a value of type '%s' for baked %d%s parameter, got '%s'.", + get_function_name(func), + type_get_name(param_type), + param->idx + 1, + bh_num_suffix(param->idx + 1), + node_get_type_name(value_to_use)); + return; + } + + if (tm == TYPE_MATCH_YIELD) flag_to_yield = 1; + + *resolved = ((PolySolveResult) { PSK_Value, value }); + } + + if (orig_value->kind == Ast_Kind_Argument) { + ((AstArgument *) orig_value)->is_baked = 1; + } +} + +TypeMatch find_polymorphic_sln(AstPolySolution *out, AstPolyParam *param, AstFunction *func, PolyProcLookupMethod pp_lookup, ptr actual, char** err_msg) { + // NOTE: Solve for the polymorphic parameter's value + PolySolveResult resolved = { PSK_Undefined }; + switch (param->kind) { + case PPK_Poly_Type: solve_for_polymorphic_param_type (&resolved, func, param, pp_lookup, actual, err_msg); break; + case PPK_Baked_Value: solve_for_polymorphic_param_value(&resolved, func, param, pp_lookup, actual, err_msg); break; + + default: if (err_msg) *err_msg = "Invalid polymorphic parameter kind. This is a compiler bug."; + } + + if (doing_nested_polymorph_lookup) { + doing_nested_polymorph_lookup = 0; + return TYPE_MATCH_SPECIAL; + } + + if (flag_to_yield) { + flag_to_yield = 0; + return TYPE_MATCH_YIELD; + } + + switch (resolved.kind) { + case PSK_Type: + out->kind = PSK_Type; + out->poly_sym = param->poly_sym; + out->type = resolved.actual; + return TYPE_MATCH_SUCCESS; + + case PSK_Value: + out->kind = PSK_Value; + out->poly_sym = param->poly_sym; + out->value = resolved.value; + return TYPE_MATCH_SUCCESS; + + case PSK_Undefined: + default: + // NOTE: If no error message has been assigned to why this polymorphic parameter + // resolution was unsuccessful, provide a basic dummy one. + if (err_msg && *err_msg == NULL) + *err_msg = bh_aprintf(global_scratch_allocator, + "Unable to solve for polymorphic variable '%b'.", + param->poly_sym->token->text, + param->poly_sym->token->length); + + out->kind = PSK_Undefined; + return TYPE_MATCH_FAILED; + } +} + +// NOTE: The job of this function is to take a polymorphic procedure, as well as a method of +// solving for the polymorphic variables, in order to return an array of the solutions for all +// of the polymorphic variables. +static bh_arr(AstPolySolution) find_polymorphic_slns(AstFunction* pp, PolyProcLookupMethod pp_lookup, ptr actual, OnyxToken *tkn, b32 necessary) { + ensure_polyproc_cache_is_created(pp); + if (bh_imap_has(&pp->active_queries, (u64) actual)) { + AstPolyQuery *query = (AstPolyQuery *) bh_imap_get(&pp->active_queries, (u64) actual); + assert(query->kind == Ast_Kind_Polymorph_Query); + assert(query->entity); + + if (query->entity->state == Entity_State_Finalized) return query->slns; + if (query->entity->state == Entity_State_Failed) return NULL; + + flag_to_yield = 1; + return NULL; + } + + bh_arr(AstPolySolution) slns = NULL; + bh_arr_new(global_heap_allocator, slns, bh_arr_length(pp->poly_params)); + + // NOTE: "known solutions" are given through a '#solidify' directive. If this polymorphic + // procedure is the result of a partially applied solidification, this array will be non- + // empty and these solutions will be used. + bh_arr_each(AstPolySolution, known_sln, pp->known_slns) bh_arr_push(slns, *known_sln); + + AstPolyQuery *query = onyx_ast_node_new(context.ast_alloc, sizeof(AstPolyQuery), Ast_Kind_Polymorph_Query); + query->token = pp->token; + query->proc = pp; + query->pp_lookup = pp_lookup; + query->given = actual; + query->error_loc = tkn; + query->slns = slns; + query->function_header = clone_function_header(context.ast_alloc, pp); + query->function_header->flags |= Ast_Flag_Header_Check_No_Error; + query->function_header->scope = NULL; + query->error_on_fail = necessary; + query->successful_symres = 1; + + bh_imap_put(&pp->active_queries, (u64) actual, (u64) query); + add_entities_for_node(NULL, (AstNode *) query, NULL, NULL); + + flag_to_yield = 1; + return NULL; +} + +// NOTE: The job of this function is to be a wrapper to other functions, providing an error +// message if a solution could not be found. This can't be merged with polymorphic_proc_solidify +// because polymorphic_proc_try_solidify uses the aforementioned function. +AstFunction* polymorphic_proc_lookup(AstFunction* pp, PolyProcLookupMethod pp_lookup, ptr actual, OnyxToken* tkn) { + ensure_polyproc_cache_is_created(pp); + + bh_arr(AstPolySolution) slns = find_polymorphic_slns(pp, pp_lookup, actual, tkn, 1); + if (slns == NULL) { + if (flag_to_yield) { + flag_to_yield = 0; + return (AstFunction *) &node_that_signals_a_yield; + } + + return NULL; + } + + AstFunction* result = polymorphic_proc_solidify(pp, slns, tkn); + return result; +} + +AstFunction* polymorphic_proc_solidify(AstFunction* pp, bh_arr(AstPolySolution) slns, OnyxToken* tkn) { + ensure_polyproc_cache_is_created(pp); + + // NOTE: Check if a version of this polyproc has already been created. + char* unique_key = build_poly_slns_unique_key(slns); + i32 index = shgeti(pp->concrete_funcs, unique_key); + if (index != -1) { + AstSolidifiedFunction solidified_func = pp->concrete_funcs[index].value; + + // NOTE: If this solution was originally created from a "build_only_header" call, then the body + // will not have been or type checked, or anything. This ensures that the body is copied, the + // entities are created and entered into the pipeline. + ensure_solidified_function_has_body(pp, &solidified_func); + + // NOTE: Again, if this came from a "build_only_header" call, then there was no known token and + // the "generated_from" member will be null. It is best to set it here so errors reported in that + // function can report where the polymorphic instantiation occurred. + if (solidified_func.func->generated_from == NULL) + solidified_func.func->generated_from = tkn; + + return solidified_func.func; + } + + AstSolidifiedFunction solidified_func = generate_solidified_function(pp, slns, tkn, 0); + add_solidified_function_entities(&solidified_func); + + // NOTE: Cache the function for later use, reducing duplicate functions. + shput(pp->concrete_funcs, unique_key, solidified_func); + + return (AstFunction *) &node_that_signals_a_yield; +} + +// NOTE: This can return either a AstFunction or an AstFunction, depending if enough parameters were +// supplied to remove all the polymorphic variables from the function. +AstNode* polymorphic_proc_try_solidify(AstFunction* pp, bh_arr(AstPolySolution) slns, OnyxToken* tkn) { + i32 valid_argument_count = 0; + + bh_arr_each(AstPolySolution, sln, slns) { + b32 found_match = 0; + + bh_arr_each(AstPolyParam, param, pp->poly_params) { + if (token_equals(sln->poly_sym->token, param->poly_sym->token)) { + found_match = 1; + break; + } + } + + if (found_match) { + valid_argument_count++; + } else { + if (pp->name) { + onyx_report_error(tkn->pos, Error_Critical, "'%b' is not a type variable of '%s'.", + sln->poly_sym->token->text, sln->poly_sym->token->length, pp->name); + } else { + onyx_report_error(tkn->pos, Error_Critical, "'%b' is not a type variable of '%b'.", + sln->poly_sym->token->text, sln->poly_sym->token->length, + pp->token->text, pp->token->length); + } + return (AstNode *) pp; + } + } + + if (valid_argument_count == bh_arr_length(pp->poly_params)) { + return (AstNode *) polymorphic_proc_solidify(pp, slns, tkn); + + } else { + // HACK: Some of these initializations assume that the entity for this polyproc has + // made it through the symbol resolution phase. + // - brendanfh 2020/12/25 + AstFunction* new_pp = onyx_ast_node_new(context.ast_alloc, sizeof(AstFunction), Ast_Kind_Polymorphic_Proc); + memcpy(new_pp, pp, sizeof(AstFunction)); + new_pp->token = tkn; + new_pp->poly_params = bh_arr_copy(context.ast_alloc, pp->poly_params); + + ensure_polyproc_cache_is_created(pp); + new_pp->concrete_funcs = pp->concrete_funcs; + + new_pp->known_slns = NULL; + bh_arr_new(global_heap_allocator, new_pp->known_slns, bh_arr_length(pp->known_slns) + bh_arr_length(slns)); + + bh_arr_each(AstPolySolution, sln, pp->known_slns) bh_arr_push(new_pp->known_slns, *sln); + bh_arr_each(AstPolySolution, sln, slns) bh_arr_push(new_pp->known_slns, *sln); + + return (AstNode *) new_pp; + } +} + +AstFunction* polymorphic_proc_build_only_header(AstFunction* pp, PolyProcLookupMethod pp_lookup, ptr actual) { + ensure_polyproc_cache_is_created(pp); + bh_arr(AstPolySolution) slns = find_polymorphic_slns(pp, pp_lookup, actual, NULL, 0); + if (flag_to_yield) { + flag_to_yield = 0; + return (AstFunction *) &node_that_signals_a_yield; + } + if (slns == NULL) return NULL; + + ensure_polyproc_cache_is_created(pp); + + return polymorphic_proc_build_only_header_with_slns(pp, slns, 0); +} + +AstFunction* polymorphic_proc_build_only_header_with_slns(AstFunction* pp, bh_arr(AstPolySolution) slns, b32 error_if_failed) { + AstSolidifiedFunction solidified_func; + + char* unique_key = build_poly_slns_unique_key(slns); + i32 index = shgeti(pp->concrete_funcs, unique_key); + if (index != -1) { + solidified_func = pp->concrete_funcs[index].value; + + } else { + // NOTE: This function is only going to have the header of it correctly created. + // Nothing should happen to this function's body or else the original will be corrupted. + // - brendanfh 2021/01/10 + solidified_func = generate_solidified_function(pp, slns, NULL, 1); + } + + if (solidified_func.func_header_entity) { + if (solidified_func.func_header_entity->state == Entity_State_Finalized) return solidified_func.func; + if (solidified_func.func_header_entity->state == Entity_State_Failed) return NULL; + + return (AstFunction *) &node_that_signals_a_yield; + } + + BH_MASK_SET(solidified_func.func->flags, !error_if_failed, Ast_Flag_Header_Check_No_Error); + + Entity func_header_entity = { + .state = Entity_State_Resolve_Symbols, + .type = Entity_Type_Temp_Function_Header, + .function = solidified_func.func, + .package = NULL, + .scope = solidified_func.func->poly_scope, + }; + + Entity* func_header_entity_ptr = entity_heap_insert(&context.entities, func_header_entity); + solidified_func.func_header_entity = func_header_entity_ptr; + + // NOTE: Cache the function for later use. + shput(pp->concrete_funcs, unique_key, solidified_func); + + return (AstFunction *) &node_that_signals_a_yield; +} + +typedef struct AutoPolymorphVariable { + u32 idx; + u32 variable_count; + AstType *base_type; + AstType **replace; +} AutoPolymorphVariable; + +// This should be called after all the parameter types have been symresed, but before anything +// happens to the body. +b32 potentially_convert_function_to_polyproc(AstFunction *func) { + bh_arr(AutoPolymorphVariable) auto_vars = NULL; + bh_arr_new(global_heap_allocator, auto_vars, 2); + + u32 param_idx = 0; + bh_arr_each(AstParam, param, func->params) { + AstType **to_replace = ¶m->local->type_node; + AstType *param_type = param->local->type_node; + + b32 done = 0; + while (!done && param_type) { + switch (param_type->kind) { + case Ast_Kind_Pointer_Type: to_replace = &((AstPointerType *) *to_replace)->elem; param_type = ((AstPointerType *) param_type)->elem; break; + case Ast_Kind_Array_Type: to_replace = &((AstArrayType *) *to_replace)->elem; param_type = ((AstArrayType *) param_type)->elem; break; + case Ast_Kind_Slice_Type: to_replace = &((AstSliceType *) *to_replace)->elem; param_type = ((AstSliceType *) param_type)->elem; break; + case Ast_Kind_DynArr_Type: to_replace = &((AstDynArrType *) *to_replace)->elem; param_type = ((AstDynArrType *) param_type)->elem; break; + case Ast_Kind_Alias: param_type = (AstType *) ((AstAlias *) param_type)->alias; break; + case Ast_Kind_Type_Alias: param_type = ((AstTypeAlias *) param_type)->to; break; + case Ast_Kind_Poly_Struct_Type: { + AutoPolymorphVariable apv; + apv.idx = param_idx; + apv.base_type = param->local->type_node; + apv.variable_count = bh_arr_length(((AstPolyStructType *) param_type)->poly_params); + apv.replace = to_replace; + + bh_arr_push(auto_vars, apv); + done = 1; + break; + } + + default: done = 1; break; + } + } + + param_idx++; + } + + if (bh_arr_length(auto_vars) == 0) return 0; + + param_idx = 0; + bh_arr_each(AutoPolymorphVariable, apv, auto_vars) { + AstPolyParam pp; + pp.idx = apv->idx; + pp.kind = PPK_Poly_Type; + + AstPolyCallType* pcall = onyx_ast_node_new(context.ast_alloc, sizeof(AstPolyCallType), Ast_Kind_Poly_Call_Type); + pcall->callee = *apv->replace; + pcall->token = pcall->callee->token; + bh_arr_new(global_heap_allocator, pcall->params, apv->variable_count); + + if (apv->base_type->kind == Ast_Kind_Poly_Struct_Type) { + pp.type_expr = (AstType *) pcall; + } else { + pp.type_expr = apv->base_type; + } + *apv->replace = (AstType *) pcall; + + fori (i, 0, apv->variable_count) { + OnyxToken* name_token = bh_alloc_item(context.ast_alloc, OnyxToken); + name_token->text = bh_aprintf(context.ast_alloc, "__autopoly_var_%d\0", param_idx); + name_token->length = strlen(name_token->text); + name_token->type = Token_Type_Symbol; + name_token->pos = pcall->token->pos; + + pp.poly_sym = make_symbol(context.ast_alloc, name_token); + bh_arr_push(pcall->params, pp.poly_sym); + bh_arr_push(func->poly_params, pp); + param_idx ++; + } + } + + convert_function_to_polyproc(func); + + bh_arr_each(AstParam, param, func->params) { + param->local->flags |= Ast_Flag_Param_Symbol_Dirty; + } + + return 1; +} + +// +// Polymorphic Structures +// +// +// Currently, I am not very happy about how polymorphic structure generation works. My biggest problem +// with it is that it is very different from the polymorhic procedure generation. Also, it needs to +// completely generate and check the structure right away, which means there is a lot of up-front work +// done here that could probably be done elsewhere. This really relates to a large problem in the compiler +// that types need to be known completely by the time symbol resolution is done, even though that +// information shouldn't need to be known until right before the types are checked. +// +// The above documentation is very incorrect but I don't want to fix it right now. Basically, polymorphic +// structures now have a delay instantiation phase and are not forced to be completed immediately. + +char* build_poly_struct_name(AstPolyStructType* ps_type, Type* cs_type) { + char name_buf[256]; + fori (i, 0, 256) name_buf[i] = 0; + + strncat(name_buf, ps_type->name, 255); + strncat(name_buf, "(", 255); + bh_arr_each(AstPolySolution, ptype, cs_type->Struct.poly_sln) { + if (ptype != cs_type->Struct.poly_sln) + strncat(name_buf, ", ", 255); + + // This logic will have to be other places as well. + + switch (ptype->kind) { + case PSK_Undefined: assert(0); break; + case PSK_Type: strncat(name_buf, type_get_name(ptype->type), 255); break; + case PSK_Value: { + // FIX + AstNode* value = strip_aliases((AstNode *) ptype->value); + + if (value->kind == Ast_Kind_NumLit) { + AstNumLit* nl = (AstNumLit *) value; + if (type_is_integer(nl->type)) { + strncat(name_buf, bh_bprintf("%l", nl->value.l), 127); + } else { + strncat(name_buf, "numlit (FIX ME)", 127); + } + } else if (value->kind == Ast_Kind_Code_Block) { + AstCodeBlock* code = (AstCodeBlock *) value; + OnyxFilePos code_loc = code->token->pos; + strncat(name_buf, bh_bprintf("code at %s:%d,%d", code_loc.filename, code_loc.line, code_loc.column), 127); + } else { + strncat(name_buf, "", 127); + } + + break; + } + } + } + strncat(name_buf, ")", 255); + + return bh_aprintf(global_heap_allocator, "%s", name_buf); +} + +Type* polymorphic_struct_lookup(AstPolyStructType* ps_type, bh_arr(AstPolySolution) slns, OnyxFilePos pos, b32 error_if_failed) { + // @Cleanup + assert(ps_type->scope != NULL); + + if (ps_type->concrete_structs == NULL) { + sh_new_arena(ps_type->concrete_structs); + } + + if (bh_arr_length(slns) != bh_arr_length(ps_type->poly_params)) { + onyx_report_error(pos, Error_Critical, "Wrong number of arguments for '%s'. Expected %d, got %d.", + ps_type->name, + bh_arr_length(ps_type->poly_params), + bh_arr_length(slns)); + + return NULL; + } + + i32 i = 0; + bh_arr_each(AstPolySolution, sln, slns) { + sln->poly_sym = (AstNode *) &ps_type->poly_params[i]; + i++; + } + + char* unique_key = build_poly_slns_unique_key(slns); + i32 index = shgeti(ps_type->concrete_structs, unique_key); + if (index != -1) { + AstStructType* concrete_struct = ps_type->concrete_structs[index].value; + + if (concrete_struct->entity_type->state < Entity_State_Check_Types) { + return NULL; + } + + if (concrete_struct->entity_type->state == Entity_State_Failed) { + return (Type *) &node_that_signals_failure; + } + + Type* cs_type = type_build_from_ast(context.ast_alloc, (AstType *) concrete_struct); + if (!cs_type) return NULL; + + if (cs_type->Struct.poly_sln == NULL) cs_type->Struct.poly_sln = bh_arr_copy(global_heap_allocator, slns); + if (cs_type->Struct.name == NULL) cs_type->Struct.name = build_poly_struct_name(ps_type, cs_type); + + return cs_type; + } + + Scope* sln_scope = scope_create(context.ast_alloc, ps_type->scope, ps_type->token->pos); + insert_poly_slns_into_scope(sln_scope, slns); + + AstStructType* concrete_struct = (AstStructType *) ast_clone(context.ast_alloc, ps_type->base_struct); + concrete_struct->polymorphic_error_loc = pos; + BH_MASK_SET(concrete_struct->flags, !error_if_failed, Ast_Flag_Header_Check_No_Error); + + + i64 arg_count = bh_arr_length(ps_type->poly_params); + bh_arr_new(global_heap_allocator, concrete_struct->polymorphic_argument_types, arg_count); + bh_arr_set_length(concrete_struct->polymorphic_argument_types, arg_count); + concrete_struct->polymorphic_arguments = bh_arr_copy(global_heap_allocator, slns); + + fori (i, 0, (i64) bh_arr_length(ps_type->poly_params)) { + concrete_struct->polymorphic_argument_types[i] = (AstType *) ast_clone(context.ast_alloc, ps_type->poly_params[i].type_node); + } + + shput(ps_type->concrete_structs, unique_key, concrete_struct); + add_entities_for_node(NULL, (AstNode *) concrete_struct, sln_scope, NULL); + return NULL; +} diff --git a/compiler/src/symres.c b/compiler/src/symres.c new file mode 100644 index 00000000..4410bb62 --- /dev/null +++ b/compiler/src/symres.c @@ -0,0 +1,1727 @@ +#define BH_DEBUG +#include "parser.h" +#include "utils.h" +#include "astnodes.h" +#include "errors.h" + +// :EliminatingSymres - notes the places where too much work is being done in symbol resolution + +// Variables used during the symbol resolution phase. +static Scope* curr_scope = NULL; +static b32 report_unresolved_symbols = 1; +static b32 resolved_a_symbol = 0; + +// Everything related to waiting on is imcomplete at the moment. +static Entity* waiting_on = NULL; + +#define SYMRES(kind, ...) do { \ + SymresStatus ss = symres_ ## kind (__VA_ARGS__); \ + if (ss > Symres_Errors_Start) return ss; \ + } while (0) + +#define SYMRES_INVISIBLE(kind, node, ...) do { \ + (node)->flags |= Ast_Flag_Symbol_Invisible; \ + SymresStatus ss = symres_ ## kind (__VA_ARGS__); \ + (node)->flags &= ~Ast_Flag_Symbol_Invisible; \ + if (ss > Symres_Errors_Start) return ss; \ + } while (0) + +typedef enum SymresStatus { + Symres_Success, + Symres_Complete, + Symres_Goto_Parse, + + Symres_Errors_Start, + Symres_Yield_Macro, + Symres_Yield_Micro, + Symres_Error, +} SymresStatus; + +static SymresStatus symres_type(AstType** type); +static SymresStatus symres_local(AstLocal** local); +static SymresStatus symres_call(AstCall** pcall); +static SymresStatus symres_size_of(AstSizeOf* so); +static SymresStatus symres_align_of(AstAlignOf* so); +static SymresStatus symres_field_access(AstFieldAccess** fa); +static SymresStatus symres_compound(AstCompound* compound); +static SymresStatus symres_expression(AstTyped** expr); +static SymresStatus symres_return(AstReturn* ret); +static SymresStatus symres_if(AstIfWhile* ifnode); +static SymresStatus symres_while(AstIfWhile* whilenode); +static SymresStatus symres_for(AstFor* fornode); +static SymresStatus symres_case(AstSwitchCase *casenode); +static SymresStatus symres_switch(AstSwitch* switchnode); +static SymresStatus symres_use(AstUse* use); +static SymresStatus symres_directive_solidify(AstDirectiveSolidify** psolid); +static SymresStatus symres_directive_defined(AstDirectiveDefined** pdefined); +static SymresStatus symres_directive_insert(AstDirectiveInsert* insert); +static SymresStatus symres_statement_chain(AstNode** walker); +static SymresStatus symres_statement(AstNode** stmt, b32 *remove); +static SymresStatus symres_block(AstBlock* block); +static SymresStatus symres_function_header(AstFunction* func); +static SymresStatus symres_function(AstFunction* func); +static SymresStatus symres_global(AstGlobal* global); +static SymresStatus symres_overloaded_function(AstOverloadedFunction* ofunc); +static SymresStatus symres_package(AstPackage* package); +static SymresStatus symres_enum(AstEnumType* enum_node); +static SymresStatus symres_memres_type(AstMemRes** memres); +static SymresStatus symres_memres(AstMemRes** memres); +static SymresStatus symres_struct_defaults(AstType* st); +static SymresStatus symres_static_if(AstIf* static_if); +static SymresStatus symres_macro(AstMacro* macro); +static SymresStatus symres_constraint(AstConstraint* constraint); +static SymresStatus symres_polyquery(AstPolyQuery *query); + +static void scope_enter(Scope* new_scope) { + curr_scope = new_scope; +} + +static void scope_leave() { + curr_scope = curr_scope->parent; +} + +static SymresStatus symres_symbol(AstNode** symbol_node) { + OnyxToken* token = (*symbol_node)->token; + AstNode* res = symbol_resolve(curr_scope, token); + + if (!res) { // :SymresStall + if (report_unresolved_symbols) { + token_toggle_end(token); + char *closest = find_closest_symbol_in_scope_and_parents(curr_scope, token->text); + token_toggle_end(token); + + if (closest) onyx_report_error(token->pos, Error_Critical, "Unable to resolve symbol '%b'. Did you mean '%s'?", token->text, token->length, closest); + else onyx_report_error(token->pos, Error_Critical, "Unable to resolve symbol '%b'.", token->text, token->length); + + return Symres_Error; + } else { + return Symres_Yield_Macro; + } + + } else { + *symbol_node = res; + resolved_a_symbol = 1; + } + + return Symres_Success; +} + +static SymresStatus symres_struct_type(AstStructType* s_node) { + if (s_node->flags & Ast_Flag_Type_Is_Resolved) return Symres_Success; + + s_node->flags |= Ast_Flag_Type_Is_Resolved; + s_node->flags |= Ast_Flag_Comptime; + + if (s_node->scope) { + // FIX: This is probably wrong for the long term. + s_node->scope->parent = curr_scope; + + scope_enter(s_node->scope); + } + + if (s_node->min_size_) SYMRES(expression, &s_node->min_size_); + if (s_node->min_alignment_) SYMRES(expression, &s_node->min_alignment_); + + if (s_node->polymorphic_argument_types) { + assert(s_node->polymorphic_arguments); + + SymresStatus ss = Symres_Success, result; + fori (i, 0, (i64) bh_arr_length(s_node->polymorphic_argument_types)) { + result = symres_type(&s_node->polymorphic_argument_types[i]); + if (result > ss) ss = result; + + if (s_node->polymorphic_arguments[i].value) { + result = symres_expression(&s_node->polymorphic_arguments[i].value); + if (result > ss) ss = result; + } + } + } + + if (s_node->constraints.constraints) { + bh_arr_each(AstConstraint *, constraint, s_node->constraints.constraints) { + SYMRES(constraint, *constraint); + } + } + + fori (i, 0, bh_arr_length(s_node->members)) { + AstStructMember *member = s_node->members[i]; + + if (member->type_node) { + SymresStatus ss = symres_type(&member->type_node); + if (ss != Symres_Success) { + s_node->flags &= ~Ast_Flag_Type_Is_Resolved; + if (s_node->scope) scope_leave(); + return ss; + } + } + } + + if (s_node->scope) scope_leave(); + return Symres_Success; +} + +static SymresStatus symres_type(AstType** type) { + // Don't make this kill all symbol resolution if the type is null. + if (!type || !*type) return Symres_Success; + + switch ((*type)->kind) { + case Ast_Kind_Symbol: SYMRES(symbol, (AstNode **) type); break; + case Ast_Kind_Basic_Type: break; + case Ast_Kind_Type_Alias: SYMRES(type, &((AstTypeAlias *) *type)->to); break; + case Ast_Kind_Field_Access: { + SYMRES(field_access, (AstFieldAccess **) type); + break; + } + + case Ast_Kind_Pointer_Type: SYMRES(type, &((AstPointerType *) *type)->elem); break; + case Ast_Kind_Slice_Type: SYMRES(type, &((AstSliceType *) *type)->elem); break; + case Ast_Kind_DynArr_Type: SYMRES(type, &((AstDynArrType *) *type)->elem); break; + case Ast_Kind_VarArg_Type: SYMRES(type, &((AstVarArgType *) *type)->elem); break; + + case Ast_Kind_Function_Type: { + AstFunctionType* ftype = (AstFunctionType *) *type; + + if (ftype->param_count > 0) { + fori (i, 0, (i64) ftype->param_count) { + SYMRES(type, &ftype->params[i]); + } + } + + SYMRES(type, &ftype->return_type); + break; + } + + case Ast_Kind_Struct_Type: SYMRES(struct_type, (AstStructType *) *type); break; + case Ast_Kind_Array_Type: { + AstArrayType* a_node = (AstArrayType *) *type; + + if (a_node->count_expr) SYMRES(expression, &a_node->count_expr); + SYMRES(type, &a_node->elem); + break; + } + + case Ast_Kind_Enum_Type: break; + + case Ast_Kind_Poly_Struct_Type: { + AstPolyStructType* pst_node = (AstPolyStructType *) *type; + + if (pst_node->scope == NULL) { + pst_node->scope = scope_create(context.ast_alloc, pst_node->entity->scope, pst_node->token->pos); + } + break; + } + + case Ast_Kind_Poly_Call_Type: { + AstPolyCallType* pc_node = (AstPolyCallType *) *type; + + SYMRES(type, &pc_node->callee); + + bh_arr_each(AstNode *, param, pc_node->params) { + if (node_is_type(*param)) { + SYMRES(type, (AstType **) param); + } else { + SYMRES(expression, (AstTyped **) param); + } + } + break; + } + + case Ast_Kind_Type_Compound: { + AstCompoundType* ctype = (AstCompoundType *) *type; + + bh_arr_each(AstType *, type, ctype->types) SYMRES(type, type); + break; + } + + case Ast_Kind_Alias: { + AstAlias* alias = (AstAlias *) *type; + SYMRES_INVISIBLE(type, alias, (AstType **) &alias->alias); + + break; + } + + case Ast_Kind_Typeof: { + AstTypeOf* type_of = (AstTypeOf *) *type; + SYMRES(expression, &type_of->expr); + break; + } + + case Ast_Kind_Distinct_Type: { + AstDistinctType *distinct = (AstDistinctType *) *type; + SYMRES(type, &distinct->base_type); + break; + } + } + + return Symres_Success; +} + +static SymresStatus symres_local(AstLocal** local) { + SYMRES(type, &(*local)->type_node); + + if ((*local)->token != NULL) + symbol_introduce(curr_scope, (*local)->token, (AstNode *) *local); + + return Symres_Success; +} + +static SymresStatus symres_arguments(Arguments* args) { + bh_arr_each(AstTyped *, arg, args->values) + SYMRES(expression, arg); + + bh_arr_each(AstNamedValue *, named_arg, args->named_values) + SYMRES(expression, &(*named_arg)->value); + + return Symres_Success; +} + +static SymresStatus symres_call(AstCall** pcall) { + AstCall *call = *pcall; + SYMRES(expression, (AstTyped **) &call->callee); + SYMRES(arguments, &call->args); + + AstNode* callee = strip_aliases((AstNode *) call->callee); + if (callee->kind == Ast_Kind_Poly_Struct_Type) { + *pcall = (AstCall *) convert_call_to_polycall(call); + SYMRES(type, (AstType **) pcall); + return Symres_Success; + } + + return Symres_Success; +} + +static SymresStatus symres_size_of(AstSizeOf* so) { + SYMRES(type, &so->type_node); + SYMRES(type, &so->so_ast_type); + + return Symres_Success; +} + +static SymresStatus symres_align_of(AstAlignOf* ao) { + SYMRES(type, &ao->type_node); + SYMRES(type, &ao->ao_ast_type); + + return Symres_Success; +} + +static SymresStatus symres_field_access(AstFieldAccess** fa) { + if ((*fa)->expr == NULL) return Symres_Error; + SYMRES(expression, &(*fa)->expr); + if ((*fa)->expr == NULL) return Symres_Error; + + AstTyped* expr = (AstTyped *) strip_aliases((AstNode *) (*fa)->expr); + + b32 force_a_lookup = 0; + + if (expr->kind == Ast_Kind_Enum_Type || expr->kind == Ast_Kind_Type_Raw_Alias) { + force_a_lookup = 1; + } + + // + // If we are trying to access a field on an alias, we have to make sure + // the alias is "ready" to have a symbol looked up inside of it. This means + // the alias should have passed symbol resolution. If not, force a lookup + // and yield if the alias was not ready. + if ((*fa)->expr->kind == Ast_Kind_Alias) { + assert((*fa)->expr->entity); + if ((*fa)->expr->entity->state < Entity_State_Check_Types) { + force_a_lookup = 1; + } + } + + AstNode* resolution = try_symbol_resolve_from_node((AstNode *) expr, (*fa)->token); + if (resolution) *((AstNode **) fa) = resolution; + else if (expr->kind == Ast_Kind_Package) { + if (report_unresolved_symbols) { + token_toggle_end((*fa)->token); + char *closest = find_closest_symbol_in_node((AstNode *) expr, (*fa)->token->text); + token_toggle_end((*fa)->token); + + AstPackage *package = (AstPackage *) strip_aliases((AstNode *) (*fa)->expr); + + if (closest) { + onyx_report_error((*fa)->token->pos, Error_Critical, "'%b' was not found in package '%s'. Did you mean '%s'?", + (*fa)->token->text, + (*fa)->token->length, + package->package->name, + closest); + } else { + onyx_report_error((*fa)->token->pos, Error_Critical, "'%b' was not found in package '%s'. Perhaps it is defined in a file that wasn't loaded?", + (*fa)->token->text, + (*fa)->token->length, + package->package->name); + } + return Symres_Error; + + } else { + return Symres_Yield_Macro; + } + } + else if (force_a_lookup) { + if (context.cycle_detected) { + onyx_report_error((*fa)->token->pos, Error_Critical, "'%b' does not exist here. This is a bad error message.", + (*fa)->token->text, + (*fa)->token->length); + return Symres_Error; + } + + return Symres_Yield_Macro; + } + + return Symres_Success; +} + +static SymresStatus symres_compound(AstCompound* compound) { + bh_arr_each(AstTyped *, expr, compound->exprs) { + SYMRES(expression, expr); + } + + return Symres_Success; +} + +static SymresStatus symres_if_expression(AstIfExpression* if_expr) { + SYMRES(expression, &if_expr->cond); + SYMRES(expression, &if_expr->true_expr); + SYMRES(expression, &if_expr->false_expr); + return Symres_Success; +} + +static SymresStatus symres_pipe(AstBinaryOp** pipe) { + AstCall* call_node = (AstCall *) (*pipe)->right; + SYMRES(expression, (AstTyped **) &call_node); + SYMRES(expression, &(*pipe)->left); + + if (call_node->kind != Ast_Kind_Call) { + onyx_report_error((*pipe)->token->pos, Error_Critical, "Pipe operator expected call on right side."); + return Symres_Error; + } + + if ((*pipe)->left == NULL) return Symres_Error; + + // :EliminatingSymres + bh_arr_insertn(call_node->args.values, 0, 1); + call_node->args.values[0] = (AstTyped *) make_argument(context.ast_alloc, (*pipe)->left); + call_node->next = (*pipe)->next; + + // NOTE: Not a BinaryOp node + *pipe = (AstBinaryOp *) call_node; + + return Symres_Success; +} + +// CLEANUP: This is an experimental feature and might be removed in the future. +// I noticed a common pattern when writing in Onyx is something that looks like this: +// +// foo.member_function(^foo, ...) +// +// I decided it would be worth adding a bit of syntactic sugar for such as call. I +// decided to use the '->' operator for this purpose. The snippet below is the exact +// same as the snippet above (after the nodes have been processed by the function below) +// +// foo->member_function(...) +static SymresStatus symres_method_call(AstBinaryOp** mcall) { + SYMRES(expression, &(*mcall)->left); + if ((*mcall)->left == NULL) return Symres_Error; + + // :EliminatingSymres + if (((*mcall)->flags & Ast_Flag_Has_Been_Symres) == 0) { + if ((*mcall)->right->kind != Ast_Kind_Call) { + onyx_report_error((*mcall)->token->pos, Error_Critical, "'->' expected procedure call on right side."); + return Symres_Error; + } + + AstFieldAccess* implicit_field_access = make_field_access(context.ast_alloc, (*mcall)->left, NULL); + implicit_field_access->token = ((AstCall *) (*mcall)->right)->callee->token; + ((AstCall *) (*mcall)->right)->callee = (AstTyped *) implicit_field_access; + (*mcall)->flags |= Ast_Flag_Has_Been_Symres; + } + + assert((*mcall)->right->kind == Ast_Kind_Call); + SYMRES(expression, (AstTyped **) &(*mcall)->right); + + return Symres_Success; +} + +static SymresStatus symres_unaryop(AstUnaryOp** unaryop) { + if ((*unaryop)->operation == Unary_Op_Cast) { + SYMRES(type, &(*unaryop)->type_node); + } + + SYMRES(expression, &(*unaryop)->expr); + + return Symres_Success; +} + +static SymresStatus symres_struct_literal(AstStructLiteral* sl) { + if (sl->stnode != NULL) SYMRES(expression, &sl->stnode); + + // :EliminatingSymres + sl->type_node = (AstType *) sl->stnode; + while (sl->type_node && sl->type_node->kind == Ast_Kind_Type_Alias) + sl->type_node = ((AstTypeAlias *) sl->type_node)->to; + + SYMRES(arguments, &sl->args); + + return Symres_Success; +} + +static SymresStatus symres_array_literal(AstArrayLiteral* al) { + if (al->atnode != NULL) SYMRES(expression, &al->atnode); + + // :EliminatingSymres + al->type_node = (AstType *) al->atnode; + while (al->type_node && al->type_node->kind == Ast_Kind_Type_Alias) + al->type_node = ((AstTypeAlias *) al->type_node)->to; + + bh_arr_each(AstTyped *, expr, al->values) + SYMRES(expression, expr); + + return Symres_Success; +} + +static SymresStatus symres_address_of(AstAddressOf** paof) { + AstAddressOf *aof = (AstAddressOf *) *paof; + SYMRES(expression, &aof->expr); + + AstTyped *expr = (AstTyped *) strip_aliases((AstNode *) aof->expr); + if (node_is_type((AstNode *) expr)) { + AstPointerType *pt = onyx_ast_node_new(context.ast_alloc, sizeof(AstPointerType), Ast_Kind_Pointer_Type); + pt->token = aof->token; + pt->elem = (AstType *) expr; + pt->__unused = aof->next; + *paof = (AstAddressOf *) pt; + SYMRES(type, (AstType **) &pt); + return Symres_Success; + } + + return Symres_Success; +} + +static SymresStatus symres_expression(AstTyped** expr) { + if (node_is_type((AstNode *) *expr)) { + SYMRES(type, (AstType **) expr); + return Symres_Success; + } + + switch ((*expr)->kind) { + case Ast_Kind_Symbol: SYMRES(symbol, (AstNode **) expr); break; + + case Ast_Kind_Binary_Op: + SYMRES(expression, &((AstBinaryOp *)(*expr))->left); + SYMRES(expression, &((AstBinaryOp *)(*expr))->right); + break; + + case Ast_Kind_Unary_Op: SYMRES(unaryop, (AstUnaryOp **) expr); break; + case Ast_Kind_Call: SYMRES(call, (AstCall **) expr); break; + case Ast_Kind_Argument: SYMRES(expression, &((AstArgument *) *expr)->value); break; + case Ast_Kind_Block: SYMRES(block, (AstBlock *) *expr); break; + case Ast_Kind_Dereference: SYMRES(expression, &((AstDereference *)(*expr))->expr); break; + case Ast_Kind_Field_Access: SYMRES(field_access, (AstFieldAccess **) expr); break; + case Ast_Kind_Pipe: SYMRES(pipe, (AstBinaryOp **) expr); break; + case Ast_Kind_Method_Call: SYMRES(method_call, (AstBinaryOp **) expr); break; + case Ast_Kind_Size_Of: SYMRES(size_of, (AstSizeOf *)*expr); break; + case Ast_Kind_Align_Of: SYMRES(align_of, (AstAlignOf *)*expr); break; + case Ast_Kind_Address_Of: SYMRES(address_of, (AstAddressOf **) expr); break; + case Ast_Kind_Alias: { + AstAlias *alias = (AstAlias *) *expr; + SYMRES_INVISIBLE(expression, alias, &alias->alias); + break; + } + + case Ast_Kind_Range_Literal: + SYMRES(expression, &((AstRangeLiteral *)(*expr))->low); + SYMRES(expression, &((AstRangeLiteral *)(*expr))->high); + + // :EliminatingSymres + SYMRES(type, &builtin_range_type); + (*expr)->type_node = builtin_range_type; + break; + + case Ast_Kind_Function: + case Ast_Kind_NumLit: + SYMRES(type, &(*expr)->type_node); + break; + + case Ast_Kind_StrLit: { + AstStrLit* str = (AstStrLit *) *expr; + if (str->is_cstr) { + SYMRES(type, &builtin_cstring_type); + str->type_node = builtin_cstring_type; + + } else { + SYMRES(type, &builtin_string_type); + str->type_node = builtin_string_type; + } + break; + } + + case Ast_Kind_Slice: + case Ast_Kind_Subscript: + SYMRES(expression, &((AstSubscript *)(*expr))->addr); + SYMRES(expression, &((AstSubscript *)(*expr))->expr); + break; + + case Ast_Kind_Struct_Literal: + SYMRES(struct_literal, (AstStructLiteral *)(*expr)); + break; + + case Ast_Kind_Array_Literal: + SYMRES(array_literal, (AstArrayLiteral *)(*expr)); + break; + + case Ast_Kind_Directive_Solidify: + SYMRES(directive_solidify, (AstDirectiveSolidify **) expr); + break; + + case Ast_Kind_Directive_Defined: + SYMRES(directive_defined, (AstDirectiveDefined **) expr); + break; + + case Ast_Kind_Compound: + SYMRES(compound, (AstCompound *) *expr); + break; + + case Ast_Kind_Package: + SYMRES(package, (AstPackage *) *expr); + break; + + case Ast_Kind_If_Expression: + SYMRES(if_expression, (AstIfExpression *) *expr); + break; + + case Ast_Kind_Directive_Insert: + SYMRES(directive_insert, (AstDirectiveInsert *) *expr); + break; + + case Ast_Kind_Do_Block: + SYMRES(block, ((AstDoBlock *) *expr)->block); + break; + + case Ast_Kind_Param: + if ((*expr)->flags & Ast_Flag_Param_Symbol_Dirty) { + assert((*expr)->token->type == Token_Type_Symbol); + *expr = (AstTyped *) make_symbol(context.ast_alloc, (*expr)->token); + SYMRES(expression, expr); + } + break; + + default: break; + } + + return Symres_Success; +} + +static SymresStatus symres_return(AstReturn* ret) { + if (ret->expr) + SYMRES(expression, &ret->expr); + + return Symres_Success; +} + +static SymresStatus symres_if(AstIfWhile* ifnode) { + if (ifnode->kind == Ast_Kind_Static_If) { + if ((ifnode->flags & Ast_Flag_Static_If_Resolved) == 0) { + if (context.cycle_detected) { + onyx_report_error(ifnode->token->pos, Error_Waiting_On, "Waiting on static if resolution."); + return Symres_Error; + } else { + return Symres_Yield_Macro; + } + } + + if (static_if_resolution(ifnode)) { + if (ifnode->true_stmt != NULL) SYMRES(statement, (AstNode **) &ifnode->true_stmt, NULL); + + } else { + if (ifnode->false_stmt != NULL) SYMRES(statement, (AstNode **) &ifnode->false_stmt, NULL); + } + + } else { + if (ifnode->initialization != NULL) { + ifnode->scope = scope_create(context.ast_alloc, curr_scope, ifnode->token->pos); + scope_enter(ifnode->scope); + + SYMRES(statement_chain, &ifnode->initialization); + } + + SYMRES(expression, &ifnode->cond); + + // NOTE: These are statements because "elseif" means the `false_stmt` has an if node. + if (ifnode->true_stmt != NULL) SYMRES(statement, (AstNode **) &ifnode->true_stmt, NULL); + if (ifnode->false_stmt != NULL) SYMRES(statement, (AstNode **) &ifnode->false_stmt, NULL); + + if (ifnode->initialization != NULL) scope_leave(); + } + + return Symres_Success; +} + +static SymresStatus symres_while(AstIfWhile* whilenode) { + if (whilenode->initialization != NULL) { + whilenode->scope = scope_create(context.ast_alloc, curr_scope, whilenode->token->pos); + scope_enter(whilenode->scope); + + SYMRES(statement_chain, &whilenode->initialization); + } + + SYMRES(expression, &whilenode->cond); + + if (whilenode->true_stmt) SYMRES(block, whilenode->true_stmt); + if (whilenode->false_stmt) SYMRES(block, whilenode->false_stmt); + + if (whilenode->initialization != NULL) scope_leave(); + + return Symres_Success; +} + +static SymresStatus symres_for(AstFor* fornode) { + fornode->scope = scope_create(context.ast_alloc, curr_scope, fornode->token->pos); + scope_enter(fornode->scope); + SYMRES(expression, &fornode->iter); + SYMRES(local, &fornode->var); + SYMRES(block, fornode->stmt); + scope_leave(); + + return Symres_Success; +} + +static SymresStatus symres_case(AstSwitchCase *casenode) { + if (!casenode->is_default) { + bh_arr_each(AstTyped *, expr, casenode->values) { + SYMRES(expression, expr); + } + } + + SYMRES(block, casenode->block); + return Symres_Success; +} + +static SymresStatus symres_switch(AstSwitch* switchnode) { + if (switchnode->initialization != NULL) { + switchnode->scope = scope_create(context.ast_alloc, curr_scope, switchnode->token->pos); + scope_enter(switchnode->scope); + + SYMRES(statement_chain, &switchnode->initialization); + } + + SYMRES(expression, &switchnode->expr); + + if (switchnode->cases == NULL) { + SYMRES(block, switchnode->case_block); + } else { + bh_arr_each(AstSwitchCase *, pcase, switchnode->cases) { + SYMRES(case, *pcase); + } + + if (switchnode->default_case) SYMRES(block, switchnode->default_case); + } + + if (switchnode->switch_kind == Switch_Kind_Use_Equals && switchnode->case_exprs) { + bh_arr_each(CaseToBlock, ctb, switchnode->case_exprs) { + SYMRES(expression, (AstTyped **) &ctb->comparison); + } + } + + if (switchnode->initialization != NULL) scope_leave(); + + return Symres_Success; +} + +// CLEANUP: A lot of duplication going on in this function. A proper +// "namespace" concept would be useful to remove a lot of the fluff +// code here. There already is try_resolve_symbol_from_node which +// may be able to do what is needed here? +static SymresStatus symres_use(AstUse* use) { + SYMRES(expression, &use->expr); + + AstTyped *use_expr = (AstTyped *) strip_aliases((AstNode *) use->expr); + + // :EliminatingSymres + if (use_expr->kind == Ast_Kind_Package) { + AstPackage* package = (AstPackage *) use_expr; + SYMRES(package, package); + + if (package->package->scope == curr_scope) return Symres_Success; + + if (use->only == NULL) { + OnyxFilePos pos = { 0 }; + if (use->token != NULL) + pos = use->token->pos; + + scope_include(curr_scope, package->package->scope, pos); + + } else { + bh_arr_each(QualifiedUse, qu, use->only) { + AstNode* thing = symbol_resolve(package->package->scope, qu->symbol_name); + if (thing == NULL) { // :SymresStall + if (report_unresolved_symbols) { + onyx_report_error(qu->symbol_name->pos, Error_Critical, + "The symbol '%b' was not found in this package.", + qu->symbol_name->text, qu->symbol_name->length); + return Symres_Error; + } else { + return Symres_Yield_Macro; + } + } + + symbol_introduce(curr_scope, qu->as_name, thing); + } + } + + if (!use->entity) { + add_entities_for_node(NULL, (AstNode *) use, curr_scope, NULL); + } + + package_track_use_package(package->package, use->entity); + return Symres_Success; + } + + if (use_expr->kind == Ast_Kind_Foreign_Block) { + AstForeignBlock* fb = (AstForeignBlock *) use_expr; + if (fb->entity->state <= Entity_State_Resolve_Symbols) return Symres_Yield_Macro; + + if (fb->scope == curr_scope) return Symres_Success; + + if (use->only == NULL) { + OnyxFilePos pos = { 0 }; + if (use->token != NULL) + pos = use->token->pos; + + scope_include(curr_scope, fb->scope, pos); + + } else { + bh_arr_each(QualifiedUse, qu, use->only) { + AstNode* thing = symbol_resolve(fb->scope, qu->symbol_name); + if (thing == NULL) { // :SymresStall + if (report_unresolved_symbols) { + onyx_report_error(qu->symbol_name->pos, Error_Critical, + "The symbol '%b' was not found in this package.", + qu->symbol_name->text, qu->symbol_name->length); + return Symres_Error; + } else { + return Symres_Yield_Macro; + } + } + + symbol_introduce(curr_scope, qu->as_name, thing); + } + } + + return Symres_Success; + } + + if (use_expr->kind == Ast_Kind_Enum_Type) { + AstEnumType* et = (AstEnumType *) use_expr; + + bh_arr_each(AstEnumValue *, ev, et->values) + symbol_introduce(curr_scope, (*ev)->token, (AstNode *) *ev); + + return Symres_Success; + } + + if (use_expr->kind == Ast_Kind_Struct_Type) { + AstStructType* st = (AstStructType *) use_expr; + if (!st->scope) return Symres_Success; + + if (use->only == NULL) { + scope_include(curr_scope, st->scope, use->token->pos); + + } else { + bh_arr_each(QualifiedUse, qu, use->only) { + AstNode* thing = symbol_resolve(st->scope, qu->symbol_name); + if (thing == NULL) { + onyx_report_error(qu->symbol_name->pos, Error_Critical, + "The symbol '%b' was not found in this scope.", + qu->symbol_name->text, qu->symbol_name->length); + return Symres_Error; + } + + symbol_introduce(curr_scope, qu->as_name, thing); + } + } + + return Symres_Success; + } + + if (use_expr->type_node == NULL && use_expr->type == NULL) goto cannot_use; + + // :EliminatingSymres + AstType* effective_type = use_expr->type_node; + if (effective_type->kind == Ast_Kind_Pointer_Type) + effective_type = ((AstPointerType *) effective_type)->elem; + + if (effective_type->kind == Ast_Kind_Struct_Type || + effective_type->kind == Ast_Kind_Poly_Call_Type) { + + if (use_expr->type == NULL) + use_expr->type = type_build_from_ast(context.ast_alloc, use_expr->type_node); + if (use_expr->type == NULL) goto cannot_use; + + Type* st = use_expr->type; + if (st->kind == Type_Kind_Pointer) + st = st->Pointer.elem; + + fori (i, 0, shlen(st->Struct.members)) { + StructMember* value = st->Struct.members[i].value; + AstFieldAccess* fa = make_field_access(context.ast_alloc, use_expr, value->name); + symbol_raw_introduce(curr_scope, value->name, use->token->pos, (AstNode *) fa); + } + + return Symres_Success; + } + +cannot_use: + onyx_report_error(use->token->pos, Error_Critical, "Cannot use this because its type is unknown."); + return Symres_Error; +} + +static SymresStatus symres_directive_solidify(AstDirectiveSolidify** psolid) { + AstDirectiveSolidify* solid = *psolid; + + SYMRES(expression, (AstTyped **) &solid->poly_proc); + if (solid->poly_proc && solid->poly_proc->kind == Ast_Kind_Directive_Solidify) { + AstFunction* potentially_resolved_proc = (AstFunction *) ((AstDirectiveSolidify *) solid->poly_proc)->resolved_proc; + if (!potentially_resolved_proc) return Symres_Yield_Micro; + + solid->poly_proc = potentially_resolved_proc; + } + + if (!solid->poly_proc || solid->poly_proc->kind != Ast_Kind_Polymorphic_Proc) { + onyx_report_error(solid->token->pos, Error_Critical, "Expected polymorphic procedure in #solidify directive."); + return Symres_Error; + } + + bh_arr_each(AstPolySolution, sln, solid->known_polyvars) { + // HACK: This assumes that 'ast_type' and 'value' are at the same offset. + SYMRES(expression, &sln->value); + } + + return Symres_Success; +} + +static SymresStatus symres_directive_defined(AstDirectiveDefined** pdefined) { + AstDirectiveDefined* defined = *pdefined; + + b32 has_to_be_resolved = context.cycle_almost_detected; + + resolved_a_symbol = 0; + SymresStatus ss = symres_expression(&defined->expr); + if (has_to_be_resolved && ss != Symres_Success && !resolved_a_symbol) { + // The symbol definitely was not found and there is no chance that it could be found. + defined->is_defined = 0; + return Symres_Success; + } + + if (ss == Symres_Success) { + defined->is_defined = 1; + return Symres_Success; + } + + return Symres_Yield_Macro; +} + +static SymresStatus symres_directive_insert(AstDirectiveInsert* insert) { + SYMRES(expression, &insert->code_expr); + return Symres_Success; +} + +static SymresStatus symres_statement(AstNode** stmt, b32 *remove) { + if (remove) *remove = 0; + + switch ((*stmt)->kind) { + case Ast_Kind_Return: SYMRES(return, (AstReturn *) *stmt); break; + case Ast_Kind_If: SYMRES(if, (AstIfWhile *) *stmt); break; + case Ast_Kind_Static_If: SYMRES(if, (AstIfWhile *) *stmt); break; + case Ast_Kind_While: SYMRES(while, (AstIfWhile *) *stmt); break; + case Ast_Kind_For: SYMRES(for, (AstFor *) *stmt); break; + case Ast_Kind_Switch: SYMRES(switch, (AstSwitch *) *stmt); break; + case Ast_Kind_Call: SYMRES(call, (AstCall **) stmt); break; + case Ast_Kind_Argument: SYMRES(expression, (AstTyped **) &((AstArgument *) *stmt)->value); break; + case Ast_Kind_Block: SYMRES(block, (AstBlock *) *stmt); break; + case Ast_Kind_Defer: SYMRES(statement, &((AstDefer *) *stmt)->stmt, NULL); break; + case Ast_Kind_Switch_Case: SYMRES(case, (AstSwitchCase *) *stmt); break; + case Ast_Kind_Jump: break; + case Ast_Kind_Directive_Remove: break; + + case Ast_Kind_Local: + // if (remove) *remove = 1; + SYMRES(local, (AstLocal **) stmt); + break; + + case Ast_Kind_Use: + if (remove) *remove = 1; + SYMRES(use, (AstUse *) *stmt); + break; + + default: SYMRES(expression, (AstTyped **) stmt); break; + } + + return Symres_Success; +} + +static SymresStatus symres_statement_chain(AstNode** walker) { + b32 remove = 0; + + while (*walker) { + SYMRES(statement, walker, &remove); + if (remove) { + remove = 0; + AstNode* tmp = (*walker)->next; + (*walker)->next = NULL; + (*walker) = tmp; + + } else { + walker = &(*walker)->next; + } + } + return Symres_Success; +} + +static SymresStatus symres_block(AstBlock* block) { + if (block->rules & Block_Rule_New_Scope) { + if (block->scope == NULL) + block->scope = scope_create(context.ast_alloc, curr_scope, block->token->pos); + + scope_enter(block->scope); + } + + if (block->binding_scope != NULL) + scope_include(curr_scope, block->binding_scope, block->token->pos); + + if (block->body) { + AstNode** start = &block->body; + fori (i, 0, block->statement_idx) { + start = &(*start)->next; + } + + b32 remove = 0; + + while (*start) { + SymresStatus cs = symres_statement(start, &remove); + + if (remove) { + remove = 0; + AstNode* tmp = (*start)->next; + (*start)->next = NULL; + (*start) = tmp; + + } else { + switch (cs) { + case Symres_Success: + start = &(*start)->next; + block->statement_idx++; + break; + + default: + return cs; + } + } + } + + block->statement_idx = 0; + } + + if (block->rules & Block_Rule_New_Scope) + scope_leave(); + + return Symres_Success; +} + +SymresStatus symres_function_header(AstFunction* func) { + func->flags |= Ast_Flag_Comptime; + + if (func->scope == NULL) + func->scope = scope_create(context.ast_alloc, curr_scope, func->token->pos); + + if (func->constraints.constraints != NULL && func->constraints.constraints_met == 0) { + bh_arr_each(AstConstraint *, constraint, func->constraints.constraints) { + SYMRES(constraint, *constraint); + } + + // Return early here to finish checking constraints in the checker. + // Will resume here after constraints have been met. + return Symres_Success; + } + + scope_enter(func->scope); + + bh_arr_each(AstParam, param, func->params) { + if (param->default_value != NULL) { + SYMRES(expression, ¶m->default_value); + if (onyx_has_errors()) return Symres_Error; + } + } + + bh_arr_each(AstParam, param, func->params) { + symbol_introduce(curr_scope, param->local->token, (AstNode *) param->local); + } + + bh_arr_each(AstParam, param, func->params) { + if (param->local->type_node != NULL) { + SYMRES_INVISIBLE(type, param->local, ¶m->local->type_node); + } + } + + if (potentially_convert_function_to_polyproc(func)) { + return Symres_Complete; + } + + if (func->nodes_that_need_entities_after_clone && bh_arr_length(func->nodes_that_need_entities_after_clone) > 0 && func->entity) { + bh_arr_each(AstNode *, node, func->nodes_that_need_entities_after_clone) { + // This makes a lot of assumptions about how these nodes are being processed, + // and I don't want to start using this with other nodes without considering + // what the ramifications of that is. + assert((*node)->kind == Ast_Kind_Static_If || (*node)->kind == Ast_Kind_File_Contents); + + // Need to curr_scope->parent because curr_scope is the function body scope. + Scope *scope = curr_scope->parent; + + if ((*node)->kind == Ast_Kind_Static_If) { + AstIf *static_if = (AstIf *) *node; + assert(static_if->defined_in_scope); + scope = static_if->defined_in_scope; + + if (func->poly_scope) { + scope = scope_create(context.ast_alloc, scope, static_if->token->pos); + scope_include(scope, func->poly_scope, static_if->token->pos); + } + } + + add_entities_for_node(NULL, *node, scope, func->entity->package); + } + + bh_arr_set_length(func->nodes_that_need_entities_after_clone, 0); + } + + SYMRES(type, &func->return_type); + + scope_leave(); + + return Symres_Success; +} + +SymresStatus symres_function(AstFunction* func) { + if (func->entity_header && func->entity_header->state < Entity_State_Check_Types) return Symres_Yield_Macro; + if (func->kind == Ast_Kind_Polymorphic_Proc) return Symres_Complete; + assert(func->scope); + + scope_enter(func->scope); + + if ((func->flags & Ast_Flag_Has_Been_Symres) == 0) { + // :EliminatingSymres + bh_arr_each(AstParam, param, func->params) { + // CLEANUP: Currently, in order to 'use' parameters, the type must be completely + // resolved and built. This is excessive because all that should need to be known + // is the names of the members, since all that happens is implicit field accesses + // are placed in the scope. So instead, there should be a way to just query all the + // member names in the structure, without needing to know their type. This would be + // easy if it were not for 'use' statements in structs. It is made even more complicated + // by this situtation: + // + // Foo :: struct (T: type_expr) { + // use t : T; + // + // something_else := 5 + 6 * 8; + // } + // + // The 'use t : T' member requires completely knowing the type of T, to know which + // members should be brought in. At the moment, that requires completely building the + // type of Foo($T). + if (param->is_used && !param->use_processed) { + if (param->local->type_node != NULL && param->local->type == NULL) { + param->local->type = type_build_from_ast(context.ast_alloc, param->local->type_node); + + if (param->local->type == NULL) return Symres_Yield_Macro; + } + + if (type_is_struct(param->local->type)) { + Type* st; + if (param->local->type->kind == Type_Kind_Struct) { + st = param->local->type; + } else { + st = param->local->type->Pointer.elem; + } + + if (st->Struct.status != SPS_Uses_Done) return Symres_Yield_Macro; + + fori (i, 0, shlen(st->Struct.members)) { + StructMember* value = st->Struct.members[i].value; + AstFieldAccess* fa = make_field_access(context.ast_alloc, (AstTyped *) param->local, value->name); + symbol_raw_introduce(curr_scope, value->name, param->local->token->pos, (AstNode *) fa); + } + + param->use_processed = 1; + + } else if (param->local->type != NULL) { + onyx_report_error(param->local->token->pos, Error_Critical, "Can only 'use' structures or pointers to structures."); + + } else { + // :ExplicitTyping + onyx_report_error(param->local->token->pos, Error_Critical, "Cannot deduce type of parameter '%b'; Try adding it explicitly.", + param->local->token->text, + param->local->token->length); + } + } + } + + bh_arr_each(AstTyped *, pexpr, func->tags) { + SYMRES(expression, pexpr); + } + + func->flags |= Ast_Flag_Has_Been_Symres; + } + + SYMRES(block, func->body); + + scope_leave(); + return Symres_Success; +} + +static SymresStatus symres_global(AstGlobal* global) { + SYMRES(type, &global->type_node); + return Symres_Success; +} + +static SymresStatus symres_overloaded_function(AstOverloadedFunction* ofunc) { + bh_arr_each(OverloadOption, overload, ofunc->overloads) { + SYMRES(expression, &overload->option); + } + return Symres_Success; +} + +static SymresStatus symres_package(AstPackage* package) { + if (package->package == NULL) { + if (!package->package_name) return Symres_Error; + + package->package = package_lookup(package->package_name); + } + + if (package->package) { + return Symres_Success; + } else { + if (report_unresolved_symbols) { + onyx_report_error(package->token->pos, Error_Critical, + "Package '%s' not found in included source files.", + package->package_name); + return Symres_Error; + } else { + return Symres_Yield_Macro; + } + } +} + +static SymresStatus symres_enum(AstEnumType* enum_node) { + if (enum_node->backing->kind == Ast_Kind_Symbol) SYMRES(symbol, (AstNode **) &enum_node->backing); + if (enum_node->backing == NULL) return Symres_Error; + + if (enum_node->scope == NULL) { + enum_node->backing_type = type_build_from_ast(context.ast_alloc, enum_node->backing); + enum_node->scope = scope_create(context.ast_alloc, curr_scope, enum_node->token->pos); + + type_build_from_ast(context.ast_alloc, (AstType *) enum_node); + } + + scope_enter(enum_node->scope); + + // :EliminatingSymres + u64 next_assign_value = enum_node->is_flags ? 1 : 0; + bh_arr_each(AstEnumValue *, value, enum_node->values) { + if ((*value)->flags & Ast_Flag_Has_Been_Checked) continue; + + (*value)->type = enum_node->etcache; + (*value)->flags |= Ast_Flag_Comptime; + + if ((*value)->value != NULL) { + SYMRES(expression, &(*value)->value); + + if ((*value)->value->kind == Ast_Kind_Enum_Value) { + (*value)->value = ((AstEnumValue *) (*value)->value)->value; + (*value)->value->type = enum_node->etcache; + } + + if ((*value)->value->kind == Ast_Kind_NumLit) { + AstNumLit *n_value = (AstNumLit *) (*value)->value; + resolve_expression_type((AstTyped *) n_value); + + if (type_is_small_integer(n_value->type)) { + next_assign_value = n_value->value.i; + } else if (type_is_integer(n_value->type)) { + next_assign_value = n_value->value.l; + } else { + onyx_report_error((*value)->token->pos, Error_Critical, "expected numeric integer literal for enum initialization, got '%s'", type_get_name(n_value->type)); + return Symres_Error; + } + + n_value->type = enum_node->etcache; + + } else { + if ((*value)->entity == NULL) { + add_entities_for_node(NULL, (AstNode *) (*value), enum_node->scope, NULL); + } + + if (context.cycle_detected) { + onyx_report_error((*value)->token->pos, Error_Critical, "Expected compile time known value for enum initialization."); + return Symres_Error; + } + + return Symres_Yield_Macro; + } + + } else { + AstNumLit* num = make_int_literal(context.ast_alloc, next_assign_value); + num->type = enum_node->etcache; + + (*value)->value = (AstTyped *) num; + } + + symbol_introduce(enum_node->scope, (*value)->token, (AstNode *) (*value)); + + (*value)->flags |= Ast_Flag_Comptime | Ast_Flag_Has_Been_Checked; + + if (enum_node->is_flags) { + next_assign_value <<= 1; + } else { + next_assign_value++; + } + } + + scope_leave(); + + // HACK this ensure that you can only lookup symbols in an Enum that are actually defined in the enum. + // However, during the symbol resolution of the values in an enum, they need to be able to see the + // enclosing scope. + enum_node->scope->parent = NULL; + + return Symres_Success; +} + +static SymresStatus symres_memres_type(AstMemRes** memres) { + SYMRES(type, &(*memres)->type_node); + return Symres_Success; +} + +static SymresStatus symres_memres(AstMemRes** memres) { + if ((*memres)->initial_value != NULL) { + SYMRES(expression, &(*memres)->initial_value); + } + return Symres_Success; +} + +static SymresStatus symres_struct_defaults(AstType* t) { + if (t->kind != Ast_Kind_Struct_Type) return Symres_Error; + + AstStructType* st = (AstStructType *) t; + if (st->scope) scope_enter(st->scope); + + if (st->meta_tags) { + bh_arr_each(AstTyped *, meta, st->meta_tags) { + SYMRES(expression, meta); + } + } + + bh_arr_each(AstStructMember *, smem, st->members) { + if ((*smem)->initial_value != NULL) { + SYMRES(expression, &(*smem)->initial_value); + } + + if ((*smem)->meta_tags != NULL) { + bh_arr_each(AstTyped *, meta, (*smem)->meta_tags) { + SYMRES(expression, meta); + } + } + } + + if (st->scope) scope_leave(); + return Symres_Success; +} + +static SymresStatus symres_polyproc(AstFunction* pp) { + pp->flags |= Ast_Flag_Comptime; + pp->parent_scope_of_poly_proc = curr_scope; + return Symres_Success; +} + +static SymresStatus symres_static_if(AstIf* static_if) { + if (static_if->flags & Ast_Flag_Dead) return Symres_Complete; + + SYMRES(expression, &static_if->cond); + return Symres_Success; +} + +static SymresStatus symres_process_directive(AstNode* directive) { + // :EliminatingSymres + switch (directive->kind) { + case Ast_Kind_Directive_Add_Overload: { + AstDirectiveAddOverload *add_overload = (AstDirectiveAddOverload *) directive; + + SYMRES(expression, (AstTyped **) &add_overload->overloaded_function); + if (add_overload->overloaded_function == NULL) return Symres_Error; // NOTE: Error message will already be generated + + if (add_overload->overloaded_function->kind != Ast_Kind_Overloaded_Function) { + onyx_report_error(add_overload->token->pos, Error_Critical, "#match directive expects a matched procedure."); + return Symres_Error; + } + + AstOverloadedFunction* ofunc = (AstOverloadedFunction *) add_overload->overloaded_function; + if (ofunc->locked) { + onyx_report_error(add_overload->token->pos, Error_Critical, "Cannot add match option here as the original #match was declared as #locked."); + onyx_report_error(ofunc->token->pos, Error_Critical, "Here is the original #match."); + return Symres_Error; + } + + if (ofunc->only_local_functions) { + if (!token_same_file(add_overload->token, ofunc->token)) { + onyx_report_error(add_overload->token->pos, Error_Critical, "Cannot add match option here as this option is not within the same file as the original #match declared with #local."); + onyx_report_error(ofunc->token->pos, Error_Critical, "Here is the original #match."); + } + } + + SYMRES(expression, (AstTyped **) &add_overload->overload); + add_overload_option(&ofunc->overloads, add_overload->precedence, add_overload->overload); + break; + } + + case Ast_Kind_Directive_Operator: { + AstDirectiveOperator *operator = (AstDirectiveOperator *) directive; + SYMRES(expression, &operator->overload); + if (!operator->overload) return Symres_Error; + + AstFunction* overload = get_function_from_node((AstNode *) operator->overload); + if (overload == NULL) { + onyx_report_error(operator->token->pos, Error_Critical, "This cannot be used as an operator overload."); + return Symres_Error; + } + + if (operator->operator != Binary_Op_Subscript_Equals && bh_arr_length(overload->params) != 2) { + onyx_report_error(operator->token->pos, Error_Critical, "Expected exactly 2 arguments for binary operator overload."); + return Symres_Error; + } + + add_overload_option(&operator_overloads[operator->operator], 0, operator->overload); + break; + } + + case Ast_Kind_Directive_Export: { + AstDirectiveExport *export = (AstDirectiveExport *) directive; + SYMRES(expression, &export->export); + SYMRES(expression, &export->export_name_expr); + + if (export->export->kind == Ast_Kind_Polymorphic_Proc) { + onyx_report_error(export->token->pos, Error_Critical, "Cannot export a polymorphic function."); + return Symres_Error; + } + + if (export->export->kind == Ast_Kind_Function) { + AstFunction *func = (AstFunction *) export->export; + func->exported_name = export->export_name; + func->is_exported = 1; + + if (func->is_exported) { + if (func->is_foreign) { + onyx_report_error(export->token->pos, Error_Critical, "Cannot export a foreign function."); + return Symres_Error; + } + + if (func->is_intrinsic) { + onyx_report_error(export->token->pos, Error_Critical, "Cannot export an intrinsic function."); + return Symres_Error; + } + } + } + + break; + } + + case Ast_Kind_Directive_Init: { + AstDirectiveInit *init = (AstDirectiveInit *) directive; + SYMRES(expression, &init->init_proc); + + if (init->dependencies) { + bh_arr_each(AstDirectiveInit *, dependency, init->dependencies) { + SYMRES(expression, (AstTyped **) dependency); + } + } + + break; + } + + case Ast_Kind_Directive_Library: { + AstDirectiveLibrary *library = (AstDirectiveLibrary *) directive; + SYMRES(expression, &library->library_symbol); + break; + } + + case Ast_Kind_Injection: { + AstInjection *inject = (AstInjection *) directive; + + if (inject->dest == NULL) { + if (inject->full_loc == NULL) return Symres_Error; + + if (inject->full_loc->kind != Ast_Kind_Field_Access) { + onyx_report_error(inject->token->pos, Error_Critical, "#inject expects a dot (a.b) expression for the injection point."); + return Symres_Error; + } + + AstFieldAccess *acc = (AstFieldAccess *) inject->full_loc; + inject->dest = acc->expr; + inject->symbol = acc->token; + } + + SYMRES(expression, &inject->dest); + SYMRES(expression, &inject->to_inject); + + Scope *scope = get_scope_from_node_or_create((AstNode *) inject->dest); + if (scope == NULL) { + onyx_report_error(inject->token->pos, Error_Critical, "Cannot #inject here."); + return Symres_Error; + } + + AstBinding *binding = onyx_ast_node_new(context.ast_alloc, sizeof(AstBinding), Ast_Kind_Binding); + binding->token = inject->symbol; + binding->node = (AstNode *) inject->to_inject; + + Package *pac = NULL; + if (inject->dest->kind == Ast_Kind_Package) { + pac = ((AstPackage *) inject->dest)->package; + } + + add_entities_for_node(NULL, (AstNode *) binding, scope, pac); + return Symres_Complete; + } + } + + return Symres_Success; +} + +static SymresStatus symres_macro(AstMacro* macro) { + macro->flags |= Ast_Flag_Comptime; + + if (macro->body->kind == Ast_Kind_Function) { + SYMRES(function_header, (AstFunction *) macro->body); + } + else if (macro->body->kind == Ast_Kind_Polymorphic_Proc) { + SYMRES(polyproc, (AstFunction *) macro->body); + } + + return Symres_Success; +} + +static SymresStatus symres_constraint(AstConstraint* constraint) { + switch (constraint->phase) { + case Constraint_Phase_Cloning_Expressions: + case Constraint_Phase_Waiting_To_Be_Queued: { + SYMRES(expression, (AstTyped **) &constraint->interface); + + bh_arr_each(AstType *, type_arg, constraint->type_args) { + SYMRES(type, type_arg); + } + + return Symres_Success; + } + + case Constraint_Phase_Checking_Expressions: { + fori (i, constraint->expr_idx, bh_arr_length(constraint->exprs)) { + SYMRES(expression, &constraint->exprs[i].expr); + + if (constraint->exprs[i].expected_type_expr) { + SYMRES(type, &constraint->exprs[i].expected_type_expr); + } + } + + return Symres_Success; + } + } + + return Symres_Success; +} + +static SymresStatus symres_polyquery(AstPolyQuery *query) { + // :EliminatingSymres + query->successful_symres = 0; + + if (query->function_header->scope == NULL) + query->function_header->scope = scope_create(context.ast_alloc, query->proc->parent_scope_of_poly_proc, query->token->pos); + + scope_enter(query->function_header->scope); + + u32 idx = 0; + bh_arr_each(AstParam, param, query->function_header->params) { + bh_arr_each(AstPolyParam, pp, query->proc->poly_params) { + if (pp->kind == PPK_Baked_Value && pp->idx == idx) goto skip_introducing_symbol; + } + + symbol_introduce(curr_scope, param->local->token, (AstNode *) param->local); + + skip_introducing_symbol: + idx++; + } + + bh_arr_each(AstParam, param, query->function_header->params) { + if (param->local->type_node != NULL) { + resolved_a_symbol = 0; + + param->local->flags |= Ast_Flag_Symbol_Invisible; + symres_type(¶m->local->type_node); + param->local->flags &= ~Ast_Flag_Symbol_Invisible; + + onyx_clear_errors(); + + if (resolved_a_symbol) query->successful_symres = 1; + } + } + + scope_leave(); + return Symres_Success; +} + +static SymresStatus symres_foreign_block(AstForeignBlock *fb) { + if (fb->scope == NULL) + fb->scope = scope_create(context.ast_alloc, curr_scope, fb->token->pos); + + bh_arr_each(Entity *, pent, fb->captured_entities) { + Entity *ent = *pent; + if (ent->type == Entity_Type_Function_Header) { + if (ent->function->body->next != NULL) { + onyx_report_error(ent->function->token->pos, Error_Critical, "Procedures declared in a #foreign block should not have bodies."); + return Symres_Error; + } + + ent->function->foreign_name = ent->function->intrinsic_name; // Hmm... This might not be right? + ent->function->foreign_module = fb->module_name; + ent->function->is_foreign = 1; + ent->function->entity = NULL; + ent->function->entity_header = NULL; + ent->function->entity_body = NULL; + + add_entities_for_node(NULL, (AstNode *) ent->function, ent->scope, ent->package); + continue; + } + + if (ent->type == Entity_Type_Binding) { + AstBinding* new_binding = onyx_ast_node_new(context.ast_alloc, sizeof(AstBinding), Ast_Kind_Binding); + new_binding->token = ent->binding->token; + new_binding->node = ent->binding->node; + + Entity e; + memset(&e, 0, sizeof(e)); + e.type = Entity_Type_Binding; + e.state = Entity_State_Introduce_Symbols; + e.binding = new_binding; + e.scope = fb->scope; + e.package = ent->package; + + entity_heap_insert(&context.entities, e); + } + + if (ent->type != Entity_Type_Function) { + entity_heap_insert_existing(&context.entities, ent); + } + } + + return Symres_Complete; +} + +static SymresStatus symres_include(AstInclude* include) { + if (include->name != NULL) return Symres_Goto_Parse; + + SYMRES(expression, &include->name_node); + + if (include->name_node->kind != Ast_Kind_StrLit) { + onyx_report_error(include->token->pos, Error_Critical, "Expected compile-time known string literal here. Got '%s'.", onyx_ast_node_kind_string(include->name_node->kind)); + return Symres_Error; + } + + OnyxToken* str_token = include->name_node->token; + if (str_token != NULL) { + token_toggle_end(str_token); + include->name = bh_strdup(context.ast_alloc, str_token->text); + string_process_escape_seqs(include->name, include->name, strlen(include->name)); + token_toggle_end(str_token); + } + + return Symres_Goto_Parse; +} + +static SymresStatus symres_file_contents(AstFileContents* fc) { + SYMRES(expression, &fc->filename_expr); + + if (fc->filename_expr->kind != Ast_Kind_StrLit) { + onyx_report_error(fc->token->pos, Error_Critical, "Expected given expression to be a compile-time stirng literal."); + return Symres_Error; + } + + return Symres_Success; +} + +void symres_entity(Entity* ent) { + if (ent->scope) scope_enter(ent->scope); + + report_unresolved_symbols = context.cycle_detected; + + SymresStatus ss = Symres_Success; + EntityState next_state = Entity_State_Check_Types; + + switch (ent->type) { + case Entity_Type_Binding: { + symbol_introduce(curr_scope, ent->binding->token, ent->binding->node); + package_reinsert_use_packages(ent->package); + next_state = Entity_State_Finalized; + break; + } + + case Entity_Type_Static_If: ss = symres_static_if(ent->static_if); break; + + case Entity_Type_Load_Path: + case Entity_Type_Load_File: ss = symres_include(ent->include); break; + case Entity_Type_File_Contents: ss = symres_file_contents(ent->file_contents); break; + + case Entity_Type_Foreign_Function_Header: + case Entity_Type_Temp_Function_Header: + case Entity_Type_Function_Header: ss = symres_function_header(ent->function); break; + case Entity_Type_Function: ss = symres_function(ent->function); break; + + case Entity_Type_Global_Header: ss = symres_global(ent->global); break; + + case Entity_Type_Use_Package: + case Entity_Type_Use: ss = symres_use(ent->use); + next_state = Entity_State_Finalized; + break; + + case Entity_Type_Polymorphic_Proc: ss = symres_polyproc(ent->poly_proc); + next_state = Entity_State_Finalized; + break; + + case Entity_Type_Overloaded_Function: ss = symres_overloaded_function(ent->overloaded_function); break; + case Entity_Type_Expression: ss = symres_expression(&ent->expr); break; + case Entity_Type_Type_Alias: ss = symres_type(&ent->type_alias); break; + case Entity_Type_Enum: ss = symres_enum(ent->enum_type); break; + case Entity_Type_Memory_Reservation_Type: ss = symres_memres_type(&ent->mem_res); break; + case Entity_Type_Memory_Reservation: ss = symres_memres(&ent->mem_res); break; + case Entity_Type_String_Literal: ss = symres_expression(&ent->expr); break; + case Entity_Type_Struct_Member_Default: ss = symres_struct_defaults((AstType *) ent->type_alias); break; + case Entity_Type_Process_Directive: ss = symres_process_directive((AstNode *) ent->expr); break; + case Entity_Type_Macro: ss = symres_macro(ent->macro); break; + case Entity_Type_Constraint_Check: ss = symres_constraint(ent->constraint); break; + case Entity_Type_Polymorph_Query: ss = symres_polyquery(ent->poly_query); break; + case Entity_Type_Foreign_Block: ss = symres_foreign_block(ent->foreign_block); + if (context.options->generate_foreign_info) { + next_state = Entity_State_Check_Types; + ss = Symres_Success; + } + break; + + default: break; + } + + if (ss == Symres_Yield_Macro) ent->macro_attempts++; + if (ss == Symres_Yield_Micro) ent->micro_attempts++; + if (ss == Symres_Complete) ent->state = Entity_State_Finalized; + if (ss == Symres_Goto_Parse) ent->state = Entity_State_Parse; + if (ss == Symres_Success) { + ent->macro_attempts = 0; + ent->micro_attempts = 0; + ent->state = next_state; + } + + curr_scope = NULL; +} diff --git a/compiler/src/types.c b/compiler/src/types.c new file mode 100644 index 00000000..0d9568a0 --- /dev/null +++ b/compiler/src/types.c @@ -0,0 +1,1490 @@ +#define BH_DEBUG +#include "stb_ds.h" +#include "types.h" +#include "astnodes.h" +#include "utils.h" +#include "errors.h" + +// NOTE: These have to be in the same order as Basic +Type basic_types[] = { + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_void, { Basic_Kind_Void, 0, 0, 1, "void" } }, + + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_bool, { Basic_Kind_Bool, Basic_Flag_Boolean, 1, 1, "bool" } }, + + { Type_Kind_Basic, 0, 0, NULL, { Basic_Kind_Int_Unsized, Basic_Flag_Integer, 0, 0, "unsized int" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i8, { Basic_Kind_I8, Basic_Flag_Integer, 1, 1, "i8" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_u8, { Basic_Kind_U8, Basic_Flag_Integer | Basic_Flag_Unsigned, 1, 1, "u8" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i16, { Basic_Kind_I16, Basic_Flag_Integer, 2, 2, "i16" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_u16, { Basic_Kind_U16, Basic_Flag_Integer | Basic_Flag_Unsigned, 2, 2, "u16" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i32, { Basic_Kind_I32, Basic_Flag_Integer, 4, 4, "i32" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_u32, { Basic_Kind_U32, Basic_Flag_Integer | Basic_Flag_Unsigned, 4, 4, "u32" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i64, { Basic_Kind_I64, Basic_Flag_Integer, 8, 8, "i64" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_u64, { Basic_Kind_U64, Basic_Flag_Integer | Basic_Flag_Unsigned, 8, 8, "u64" } }, + + { Type_Kind_Basic, 0, 0, NULL, { Basic_Kind_Float_Unsized, Basic_Flag_Float, 0, 0, "unsized float" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_f32, { Basic_Kind_F32, Basic_Flag_Float, 4, 4, "f32" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_f64, { Basic_Kind_F64, Basic_Flag_Float, 8, 4, "f64" } }, + + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_rawptr, { Basic_Kind_Rawptr, Basic_Flag_Pointer, POINTER_SIZE, POINTER_SIZE, "rawptr" } }, + + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i8x16, { Basic_Kind_I8X16, Basic_Flag_SIMD, 16, 16, "i8x16" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i16x8, { Basic_Kind_I16X8, Basic_Flag_SIMD, 16, 16, "i16x8" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i32x4, { Basic_Kind_I32X4, Basic_Flag_SIMD, 16, 16, "i32x4" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i64x2, { Basic_Kind_I64X2, Basic_Flag_SIMD, 16, 16, "i64x2" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_f32x4, { Basic_Kind_F32X4, Basic_Flag_SIMD, 16, 16, "f32x4" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_f64x2, { Basic_Kind_F64X2, Basic_Flag_SIMD, 16, 16, "f64x2" } }, + { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_v128, { Basic_Kind_V128, Basic_Flag_SIMD, 16, 16, "v128" } }, + + { Type_Kind_Basic, 0, 0, NULL, { Basic_Kind_Type_Index, Basic_Flag_Type_Index, 4, 4, "type_expr" } }, +}; + +// TODO: Document this!! + bh_imap type_map; +static bh_imap type_pointer_map; +static bh_imap type_array_map; +static bh_imap type_slice_map; +static bh_imap type_dynarr_map; +static bh_imap type_vararg_map; +static Table(u64) type_func_map; + +static Type* type_create(TypeKind kind, bh_allocator a, u32 extra_type_pointer_count) { + Type* type = bh_alloc(a, sizeof(Type) + sizeof(Type *) * extra_type_pointer_count); + type->kind = kind; + type->ast_type = NULL; + return type; +} + +static void type_register(Type* type) { + static u32 next_unique_id = 1; + type->id = next_unique_id++; + if (type->ast_type) type->ast_type->type_id = type->id; + + bh_imap_put(&type_map, type->id, (u64) type); +} + +void types_init() { + bh_imap_init(&type_map, global_heap_allocator, 255); + bh_imap_init(&type_pointer_map, global_heap_allocator, 255); + bh_imap_init(&type_array_map, global_heap_allocator, 255); + bh_imap_init(&type_slice_map, global_heap_allocator, 255); + bh_imap_init(&type_dynarr_map, global_heap_allocator, 255); + bh_imap_init(&type_vararg_map, global_heap_allocator, 255); + sh_new_arena(type_func_map); + + fori (i, 0, Basic_Kind_Count) type_register(&basic_types[i]); +} + +void types_dump_type_info() { + bh_arr_each(bh__imap_entry, entry, type_map.entries) { + bh_printf("%d -> %s\n", entry->key, type_get_name((Type *) entry->value)); + } +} + +b32 types_are_compatible_(Type* t1, Type* t2, b32 recurse_pointers) { + // NOTE: If they are pointing to the same thing, + // it is safe to assume they are the same type + if (t1 == t2) return 1; + if (t1 == NULL || t2 == NULL) return 0; + if (t1->id == t2->id) return 1; + + if (t1 == &type_auto_return || t2 == &type_auto_return) { + return 0; + } + + switch (t1->kind) { + case Type_Kind_Basic: + if (t2->kind == Type_Kind_Basic) { + // Signedness of an integer doesn't matter. + if ((t1->Basic.flags & Basic_Flag_Integer) && (t2->Basic.flags & Basic_Flag_Integer)) { + return t1->Basic.size == t2->Basic.size; + } + + if (t1->Basic.kind == Basic_Kind_V128 || t2->Basic.kind == Basic_Kind_V128) return 1; + } + + if (t1->Basic.kind == Basic_Kind_Rawptr && type_is_pointer(t2)) { + return 1; + } + break; + + case Type_Kind_Pointer: { + if (t2->kind == Type_Kind_Pointer) { + if (!recurse_pointers) return 1; + + if (types_are_compatible(t1->Pointer.elem, t2->Pointer.elem)) return 1; + + if (t1->Pointer.elem->kind == Type_Kind_Struct && t2->Pointer.elem->kind == Type_Kind_Struct) { + Type* t1_struct = t1->Pointer.elem; + Type* t2_struct = t2->Pointer.elem; + + bh_arr(StructMember *) members = t1_struct->Struct.memarr; + if (bh_arr_length(members) > 0 && members[0]->used) + return types_are_compatible(t2_struct,members[0]->type); + } + } + + if (t2->kind == Type_Kind_Basic && t2->Basic.kind == Basic_Kind_Rawptr) return 1; + + break; + } + + case Type_Kind_Array: { + if (t2->kind != Type_Kind_Array) return 0; + + if (t1->Array.count != 0) + if (t1->Array.count != t2->Array.count) return 0; + + return types_are_compatible(t1->Array.elem, t2->Array.elem); + } + + case Type_Kind_Struct: { + // NOTE: The check above for t1 == t2 would already catch this. + + // if (t2->kind != Type_Kind_Struct) return 0; + // if (t1->Struct.unique_id != t2->Struct.unique_id) return 0; + // if (t1->Struct.mem_count != t2->Struct.mem_count) return 0; + return 0; + } + + case Type_Kind_Enum: { + // NOTE: The check above for t1 == t2 would already catch this. + return 0; + } + + case Type_Kind_Function: { + if (t2->kind != Type_Kind_Function) return 0; + if (t1->Function.param_count != t2->Function.param_count) return 0; + + if (!types_are_compatible(t1->Function.return_type, t2->Function.return_type)) return 0; + + if (t1->Function.param_count > 0) { + fori (i, 0, t1->Function.param_count) { + if (!types_are_compatible(t1->Function.params[i], t2->Function.params[i])) return 0; + } + } + + return 1; + } + + case Type_Kind_Slice: { + if (t2->kind != Type_Kind_Slice) return 0; + return types_are_compatible(t1->Slice.elem, t2->Slice.elem); + } + + case Type_Kind_VarArgs: { + if (t2->kind != Type_Kind_VarArgs) return 0; + return types_are_compatible(t1->VarArgs.elem, t2->VarArgs.elem); + } + + case Type_Kind_DynArray: { + if (t2->kind != Type_Kind_DynArray) return 0; + return types_are_compatible(t1->DynArray.elem, t2->DynArray.elem); + } + + case Type_Kind_Compound: { + if (t2->kind != Type_Kind_Compound) return 0; + if (t1->Compound.count != t2->Compound.count) return 0; + + fori (i, 0, (i64) t1->Compound.count) { + if (!types_are_compatible(t1->Compound.types[i], t2->Compound.types[i])) return 0; + } + + return 1; + } + + case Type_Kind_Distinct: + // If the above cases didn't catch it, then these distinct types are not compatible. + return 0; + + default: + assert(("Invalid type", 0)); + break; + } + + return 0; +} + +b32 types_are_compatible(Type* t1, Type* t2) { + return types_are_compatible_(t1, t2, 1); +} + +u32 type_size_of(Type* type) { + if (type == NULL) return 0; + + switch (type->kind) { + case Type_Kind_Basic: return type->Basic.size; + case Type_Kind_Pointer: return POINTER_SIZE; + case Type_Kind_Function: return 4; + case Type_Kind_Array: return type->Array.size; + case Type_Kind_Struct: return type->Struct.size; + case Type_Kind_Enum: return type_size_of(type->Enum.backing); + case Type_Kind_Slice: return POINTER_SIZE * 2; // HACK: These should not have to be 16 bytes in size, they should only have to be 12, + case Type_Kind_VarArgs: return POINTER_SIZE * 2; // but there are alignment issues right now with that so I decided to not fight it and just make them 16 bytes in size. + case Type_Kind_DynArray: return POINTER_SIZE + 8 + 2 * POINTER_SIZE; // data (8), count (4), capacity (4), allocator { func (4), ---(4), data (8) } + case Type_Kind_Compound: return type->Compound.size; + case Type_Kind_Distinct: return type_size_of(type->Distinct.base_type); + default: return 0; + } +} + +u32 type_alignment_of(Type* type) { + if (type == NULL) return 1; + + switch (type->kind) { + case Type_Kind_Basic: return type->Basic.alignment; + case Type_Kind_Pointer: return POINTER_SIZE; + case Type_Kind_Function: return 4; + case Type_Kind_Array: return type_alignment_of(type->Array.elem); + case Type_Kind_Struct: return type->Struct.alignment; + case Type_Kind_Enum: return type_alignment_of(type->Enum.backing); + case Type_Kind_Slice: return POINTER_SIZE; + case Type_Kind_VarArgs: return POINTER_SIZE; + case Type_Kind_DynArray: return POINTER_SIZE; + case Type_Kind_Compound: return 4; // HACK + case Type_Kind_Distinct: return type_alignment_of(type->Distinct.base_type); + default: return 1; + } +} + +// If this function returns NULL, then the caller MUST yield because the type may still be constructed in the future. +// If there was an error constructing the type, then this function will report that directly. +Type* type_build_from_ast(bh_allocator alloc, AstType* type_node) { + if (type_node == NULL) return NULL; + + switch (type_node->kind) { + case Ast_Kind_Pointer_Type: { + // ((AstPointerType *) type_node)->elem->flags |= type_node->flags & Ast_Flag_Header_Check_No_Error; + Type* ptr_type = type_make_pointer(alloc, type_build_from_ast(alloc, ((AstPointerType *) type_node)->elem)); + if (ptr_type) ptr_type->ast_type = type_node; + return ptr_type; + } + + case Ast_Kind_Function_Type: { + AstFunctionType* ftype_node = (AstFunctionType *) type_node; + u64 param_count = ftype_node->param_count; + + Type* return_type = type_build_from_ast(alloc, ftype_node->return_type); + if (return_type == NULL) return NULL; + + Type* func_type = type_create(Type_Kind_Function, alloc, param_count); + func_type->ast_type = type_node; + func_type->Function.param_count = param_count; + func_type->Function.needed_param_count = param_count; + func_type->Function.vararg_arg_pos = -1; + func_type->Function.return_type = return_type; + + if (param_count > 0) { + fori (i, 0, (i64) param_count) { + func_type->Function.params[i] = type_build_from_ast(alloc, ftype_node->params[i]); + + // LEAK LEAK LEAK + if (func_type->Function.params[i] == NULL) return NULL; + } + } + + char* name = (char *) type_get_unique_name(func_type); + if (func_type->Function.return_type != &type_auto_return) { + i32 index = shgeti(type_func_map, name); + if (index != -1) { + u64 id = type_func_map[index].value; + Type* existing_type = (Type *) bh_imap_get(&type_map, id); + + // LEAK LEAK LEAK the func_type that is created + return existing_type; + } + } + + type_register(func_type); + shput(type_func_map, name, func_type->id); + + return func_type; + } + + case Ast_Kind_Array_Type: { + AstArrayType* a_node = (AstArrayType *) type_node; + + Type *elem_type = type_build_from_ast(alloc, a_node->elem); + if (elem_type == NULL) return NULL; + + u32 count = 0; + if (a_node->count_expr) { + if (a_node->count_expr->type == NULL) + a_node->count_expr->type = type_build_from_ast(alloc, a_node->count_expr->type_node); + + if (node_is_auto_cast((AstNode *) a_node->count_expr)) { + a_node->count_expr = ((AstUnaryOp *) a_node)->expr; + } + + resolve_expression_type(a_node->count_expr); + + // NOTE: Currently, the count_expr has to be an I32 literal + if (a_node->count_expr->type->kind != Type_Kind_Basic + || a_node->count_expr->type->Basic.kind != Basic_Kind_I32) { + onyx_report_error(type_node->token->pos, Error_Critical, "Array type expects type 'i32' for size, got '%s'.", + type_get_name(a_node->count_expr->type)); + return NULL; + } + + count = get_expression_integer_value(a_node->count_expr, NULL); + } + + Type* array_type = type_make_array(alloc, elem_type, count); + if (array_type) array_type->ast_type = type_node; + return array_type; + } + + case Ast_Kind_Struct_Type: { + AstStructType* s_node = (AstStructType *) type_node; + if (s_node->stcache != NULL) return s_node->stcache; + if (s_node->pending_type != NULL && s_node->pending_type_is_valid) return s_node->pending_type; + if (!s_node->ready_to_build_type) return NULL; + + Type* s_type; + if (s_node->pending_type == NULL) { + s_type = type_create(Type_Kind_Struct, alloc, 0); + s_node->pending_type = s_type; + + s_type->ast_type = type_node; + s_type->Struct.name = s_node->name; + s_type->Struct.mem_count = bh_arr_length(s_node->members); + s_type->Struct.meta_tags = s_node->meta_tags; + s_type->Struct.constructed_from = NULL; + s_type->Struct.status = SPS_Start; + type_register(s_type); + + s_type->Struct.memarr = NULL; + sh_new_arena(s_type->Struct.members); + bh_arr_new(global_heap_allocator, s_type->Struct.memarr, s_type->Struct.mem_count); + + } else { + s_type = s_node->pending_type; + } + + s_type->Struct.poly_sln = NULL; + + bh_arr_clear(s_type->Struct.memarr); + shfree(s_type->Struct.members); + sh_new_arena(s_type->Struct.members); + + s_node->pending_type_is_valid = 1; + + b32 is_union = s_node->is_union; + u32 size = 0; + u32 offset = 0; + u32 alignment = 1, mem_alignment; + u32 idx = 0; + bh_arr_each(AstStructMember *, member, s_node->members) { + if ((*member)->type == NULL) + (*member)->type = type_build_from_ast(alloc, (*member)->type_node); + + if ((*member)->type == NULL) { + s_node->pending_type_is_valid = 0; + return NULL; + } + + mem_alignment = type_alignment_of((*member)->type); + if (mem_alignment <= 0) { + onyx_report_error((*member)->token->pos, Error_Critical, "Invalid member type: %s. Has alignment %d", type_get_name((*member)->type), mem_alignment); + return NULL; + } + + if (mem_alignment > alignment) alignment = mem_alignment; + + if (!s_node->is_packed) { + bh_align(offset, mem_alignment); + } + + token_toggle_end((*member)->token); + if (shgeti(s_type->Struct.members, (*member)->token->text) != -1) { + onyx_report_error((*member)->token->pos, Error_Critical, "Duplicate struct member, '%s'.", (*member)->token->text); + return NULL; + } + + StructMember* smem = bh_alloc_item(alloc, StructMember); + smem->offset = offset; + smem->type = (*member)->type; + smem->idx = idx; + smem->name = bh_strdup(alloc, (*member)->token->text); + smem->token = (*member)->token; + smem->initial_value = &(*member)->initial_value; + smem->meta_tags = (*member)->meta_tags; + + smem->included_through_use = 0; + smem->used = (*member)->is_used; + smem->use_through_pointer_index = -1; + shput(s_type->Struct.members, (*member)->token->text, smem); + bh_arr_push(s_type->Struct.memarr, smem); + token_toggle_end((*member)->token); + + u32 type_size = type_size_of((*member)->type); + + if (!is_union) { + offset += type_size; + size = offset; + } else { + size = bh_max(size, type_size); + } + + idx++; + } + + u32 min_alignment = get_expression_integer_value(s_node->min_alignment_, NULL); + alignment = bh_max(min_alignment, alignment); + if (!s_node->is_packed) { + bh_align(size, alignment); + } + + u32 min_size = get_expression_integer_value(s_node->min_size_, NULL); + size = bh_max(min_size, size); + + s_type->Struct.alignment = alignment; + s_type->Struct.size = size; + + s_type->Struct.linear_members = NULL; + bh_arr_new(global_heap_allocator, s_type->Struct.linear_members, s_type->Struct.mem_count); + build_linear_types_with_offset(s_type, &s_type->Struct.linear_members, 0); + + s_type->Struct.status = SPS_Members_Done; + return s_type; + } + + case Ast_Kind_Enum_Type: { + AstEnumType* enum_node = (AstEnumType *) type_node; + if (enum_node->etcache) return enum_node->etcache; + if (enum_node->backing_type == NULL) return NULL; + + Type* enum_type = type_create(Type_Kind_Enum, alloc, 0); + enum_node->etcache = enum_type; + + enum_type->ast_type = type_node; + enum_type->Enum.backing = enum_node->backing_type; + enum_type->Enum.name = enum_node->name; + enum_type->Enum.is_flags = enum_node->is_flags; + + type_register(enum_type); + return enum_type; + } + + case Ast_Kind_Slice_Type: { + Type* slice_type = type_make_slice(alloc, type_build_from_ast(alloc, ((AstSliceType *) type_node)->elem)); + if (slice_type) slice_type->ast_type = type_node; + return slice_type; + } + + case Ast_Kind_DynArr_Type: { + Type* dynarr_type = type_make_dynarray(alloc, type_build_from_ast(alloc, ((AstDynArrType *) type_node)->elem)); + if (dynarr_type) dynarr_type->ast_type = type_node; + return dynarr_type; + } + + case Ast_Kind_VarArg_Type: { + Type* va_type = type_make_varargs(alloc, type_build_from_ast(alloc, ((AstVarArgType *) type_node)->elem)); + if (va_type) va_type->ast_type = type_node; + return va_type; + } + + case Ast_Kind_Basic_Type: { + return ((AstBasicType *) type_node)->basic_type; + } + + case Ast_Kind_Type_Alias: { + Type* type = type_build_from_ast(alloc, ((AstTypeAlias *) type_node)->to); + if (type && type->ast_type) type_node->type_id = type->id; + return type; + } + + case Ast_Kind_Type_Raw_Alias: + return ((AstTypeRawAlias *) type_node)->to; + + case Ast_Kind_Poly_Struct_Type: { + if (type_node->type_id != 0) return NULL; + + Type* p_type = type_create(Type_Kind_PolyStruct, alloc, 0); + p_type->ast_type = type_node; + p_type->PolyStruct.name = ((AstPolyStructType *) type_node)->name; + p_type->PolyStruct.meta_tags = ((AstPolyStructType *) type_node)->base_struct->meta_tags; + + type_register(p_type); + return NULL; + } + + case Ast_Kind_Poly_Call_Type: { + AstPolyCallType* pc_type = (AstPolyCallType *) type_node; + pc_type->callee = (AstType *) strip_aliases((AstNode *) pc_type->callee); + + if (!(pc_type->callee && pc_type->callee->kind == Ast_Kind_Poly_Struct_Type)) { + // If it is an unresolved field access or symbol, just return because an error will be printed elsewhere. + if (pc_type->callee->kind == Ast_Kind_Field_Access || pc_type->callee->kind == Ast_Kind_Symbol) return NULL; + + onyx_report_error(pc_type->token->pos, Error_Critical, "Cannot instantiate a concrete type off of a non-polymorphic type."); + onyx_report_error(pc_type->callee->token->pos, Error_Critical, "Here is the type trying to be instantiated. (%s)", onyx_ast_node_kind_string(pc_type->callee->kind)); + return NULL; + } + + AstPolyStructType* ps_type = (AstPolyStructType *) pc_type->callee; + + bh_arr(AstPolySolution) slns = NULL; + bh_arr_new(global_heap_allocator, slns, bh_arr_length(pc_type->params)); + bh_arr_each(AstNode *, given, pc_type->params) { + if (node_is_type(*given)) { + Type* param_type = type_build_from_ast(alloc, (AstType *) *given); + + // LEAK LEAK LEAK + if (param_type == NULL) return NULL; + + bh_arr_push(slns, ((AstPolySolution) { + .kind = PSK_Type, + .type = param_type, + })); + } else { + bh_arr_push(slns, ((AstPolySolution) { + .kind = PSK_Value, + .value = (AstTyped *) *given, + })); + } + } + + Type* concrete = polymorphic_struct_lookup(ps_type, slns, pc_type->token->pos, (pc_type->flags & Ast_Flag_Header_Check_No_Error) == 0); + + // This should be copied in the previous function. + // CLEANUP: Maybe don't copy it and just use this one since it is allocated on the heap? + bh_arr_free(slns); + + if (!concrete) return NULL; + if (concrete == (Type *) &node_that_signals_failure) return concrete; + concrete->Struct.constructed_from = (AstType *) ps_type; + return concrete; + } + + case Ast_Kind_Type_Compound: { + AstCompoundType* ctype = (AstCompoundType *) type_node; + + i64 type_count = bh_arr_length(ctype->types); + + Type* comp_type = type_create(Type_Kind_Compound, alloc, type_count); + comp_type->Compound.size = 0; + comp_type->Compound.count = type_count; + + fori (i, 0, type_count) { + assert(ctype->types[i] != NULL); + comp_type->Compound.types[i] = type_build_from_ast(alloc, ctype->types[i]); + + // LEAK LEAK LEAK + if (comp_type->Compound.types[i] == NULL) return NULL; + + comp_type->Compound.size += bh_max(type_size_of(comp_type->Compound.types[i]), 4); + } + + bh_align(comp_type->Compound.size, 4); + + comp_type->Compound.linear_members = NULL; + bh_arr_new(global_heap_allocator, comp_type->Compound.linear_members, comp_type->Compound.count); + build_linear_types_with_offset(comp_type, &comp_type->Compound.linear_members, 0); + + type_register(comp_type); + return comp_type; + } + + case Ast_Kind_Alias: { + AstAlias* alias = (AstAlias *) type_node; + return type_build_from_ast(alloc, (AstType *) alias->alias); + } + + case Ast_Kind_Typeof: { + AstTypeOf* type_of = (AstTypeOf *) type_node; + if (type_of->resolved_type != NULL) { + return type_of->resolved_type; + } + + return NULL; + } + + case Ast_Kind_Distinct_Type: { + AstDistinctType* distinct = (AstDistinctType *) type_node; + if (distinct->dtcache) return distinct->dtcache; + + Type *base_type = type_build_from_ast(alloc, distinct->base_type); + if (base_type == NULL) return NULL; + if (base_type->kind != Type_Kind_Basic) { + onyx_report_error(distinct->token->pos, Error_Critical, "Distinct types can only be made out of primitive types. '%s' is not a primitive type.", type_get_name(base_type)); + return NULL; + } + + Type *distinct_type = type_create(Type_Kind_Distinct, alloc, 0); + distinct_type->Distinct.base_type = base_type; + distinct_type->Distinct.name = distinct->name; + distinct->dtcache = distinct_type; + + type_register(distinct_type); + return distinct_type; + } + } + + return NULL; +} + +// CLEANUP: This needs to be merged with the very similar code from up above. +Type* type_build_function_type(bh_allocator alloc, AstFunction* func) { + u64 param_count = bh_arr_length(func->params); + + Type* return_type = type_build_from_ast(alloc, func->return_type); + if (return_type == NULL) return NULL; + + Type* func_type = type_create(Type_Kind_Function, alloc, param_count); + func_type->Function.param_count = param_count; + func_type->Function.needed_param_count = 0; + func_type->Function.vararg_arg_pos = -1; + func_type->Function.return_type = return_type; + + if (param_count > 0) { + i32 i = 0; + bh_arr_each(AstParam, param, func->params) { + if (param->default_value == NULL && param->vararg_kind == VA_Kind_Not_VA) + func_type->Function.needed_param_count++; + + if (param->vararg_kind == VA_Kind_Untyped) + func_type->Function.vararg_arg_pos = i; + + func_type->Function.params[i++] = param->local->type; + } + } + + // CopyPaste from above in type_build_from_ast + char* name = (char *) type_get_unique_name(func_type); + if (func_type->Function.return_type != &type_auto_return) { + i32 index = shgeti(type_func_map, name); + if (index != -1) { + u64 id = type_func_map[index].value; + Type* existing_type = (Type *) bh_imap_get(&type_map, id); + + // LEAK LEAK LEAK the func_type that is created + return existing_type; + } + } + + type_register(func_type); + shput(type_func_map, name, func_type->id); + + return func_type; +} + +Type* type_build_compound_type(bh_allocator alloc, AstCompound* compound) { + i64 expr_count = bh_arr_length(compound->exprs); + fori (i, 0, expr_count) { + if (compound->exprs[i]->type == NULL) return NULL; + if (compound->exprs[i]->type->kind == Type_Kind_Basic) { + if (compound->exprs[i]->type->Basic.kind == Basic_Kind_Int_Unsized || compound->exprs[i]->type->Basic.kind == Basic_Kind_Float_Unsized) { + return NULL; + } + } + } + + Type* comp_type = type_create(Type_Kind_Compound, alloc, expr_count); + comp_type->Compound.size = 0; + comp_type->Compound.count = expr_count; + + fori (i, 0, expr_count) { + assert(compound->exprs[i]->type != NULL); + comp_type->Compound.types[i] = compound->exprs[i]->type; + comp_type->Compound.size += bh_max(type_size_of(comp_type->Compound.types[i]), 4); + } + + bh_align(comp_type->Compound.size, 4); + + comp_type->Compound.linear_members = NULL; + bh_arr_new(global_heap_allocator, comp_type->Compound.linear_members, comp_type->Compound.count); + build_linear_types_with_offset(comp_type, &comp_type->Compound.linear_members, 0); + + type_register(comp_type); + return comp_type; +} + +Type* type_build_implicit_type_of_struct_literal(bh_allocator alloc, AstStructLiteral* lit) { + Type* type = type_create(Type_Kind_Struct, alloc, 0); + type->ast_type = NULL; + type->Struct.name = NULL; + type->Struct.mem_count = bh_arr_length(lit->args.named_values); + type->Struct.meta_tags = NULL; + type->Struct.constructed_from = NULL; + type->Struct.status = SPS_Start; + type->Struct.poly_sln = NULL; + type_register(type); + + type->Struct.memarr = NULL; + sh_new_arena(type->Struct.members); + bh_arr_new(global_heap_allocator, type->Struct.memarr, type->Struct.mem_count); + + u32 size = 0; + u32 offset = 0; + u32 alignment = 1; + u32 idx = 0; + bh_arr_each(AstNamedValue *, pnv, lit->args.named_values) { + AstNamedValue *nv = *pnv; + + Type* member_type = resolve_expression_type(nv->value); + if (member_type == NULL) { + return NULL; + } + + u32 mem_alignment = type_alignment_of(member_type); + if (mem_alignment <= 0) { + return NULL; + } + + alignment = bh_max(alignment, mem_alignment); + + // Should these structs be packed or not? + bh_align(offset, mem_alignment); + + token_toggle_end(nv->token); + if (shgeti(type->Struct.members, nv->token->text) != -1) { + token_toggle_end(nv->token); + return NULL; + } + + StructMember *smem = bh_alloc_item(alloc, StructMember); + smem->offset = offset; + smem->type = member_type; + smem->idx = idx; + smem->name = bh_strdup(alloc, nv->token->text); + smem->token = nv->token; + smem->initial_value = &nv->value; + smem->meta_tags = NULL; + smem->included_through_use = 0; + smem->used = 0; + smem->use_through_pointer_index = -1; + shput(type->Struct.members, nv->token->text, smem); + bh_arr_push(type->Struct.memarr, smem); + token_toggle_end(nv->token); + + u32 type_size = type_size_of(member_type); + offset += type_size; + size = offset; + idx++; + } + + type->Struct.alignment = alignment; + type->Struct.size = size; + type->Struct.linear_members = NULL; + bh_arr_new(global_heap_allocator, type->Struct.linear_members, type->Struct.mem_count); + build_linear_types_with_offset(type, &type->Struct.linear_members, 0); + + type->Struct.status = SPS_Uses_Done; + return type; +} + +Type* type_make_pointer(bh_allocator alloc, Type* to) { + if (to == NULL) return NULL; + if (to == (Type *) &node_that_signals_failure) return to; + + assert(to->id > 0); + u64 ptr_id = bh_imap_get(&type_pointer_map, to->id); + if (ptr_id > 0) { + Type* ptr_type = (Type *) bh_imap_get(&type_map, ptr_id); + return ptr_type; + + } else { + Type* ptr_type = type_create(Type_Kind_Pointer, alloc, 0); + ptr_type->Pointer.base.flags |= Basic_Flag_Pointer; + ptr_type->Pointer.base.size = POINTER_SIZE; + ptr_type->Pointer.elem = to; + + type_register(ptr_type); + bh_imap_put(&type_pointer_map, to->id, ptr_type->id); + + return ptr_type; + } +} + +Type* type_make_array(bh_allocator alloc, Type* to, u32 count) { + if (to == NULL) return NULL; + if (to == (Type *) &node_that_signals_failure) return to; + + assert(to->id > 0); + u64 key = ((((u64) to->id) << 32) | (u64) count); + u64 array_id = bh_imap_get(&type_array_map, key); + if (array_id > 0) { + Type* array_type = (Type *) bh_imap_get(&type_map, array_id); + return array_type; + + } else { + Type* arr_type = type_create(Type_Kind_Array, alloc, 0); + arr_type->Array.count = count; + arr_type->Array.elem = to; + arr_type->Array.size = count * type_size_of(to); + + type_register(arr_type); + bh_imap_put(&type_array_map, key, arr_type->id); + + return arr_type; + } +} + +Type* type_make_slice(bh_allocator alloc, Type* of) { + if (of == NULL) return NULL; + if (of == (Type *) &node_that_signals_failure) return of; + + assert(of->id > 0); + u64 slice_id = bh_imap_get(&type_slice_map, of->id); + if (slice_id > 0) { + Type* slice_type = (Type *) bh_imap_get(&type_map, slice_id); + return slice_type; + + } else { + Type* slice_type = type_create(Type_Kind_Slice, alloc, 0); + type_register(slice_type); + bh_imap_put(&type_slice_map, of->id, slice_type->id); + + type_make_pointer(alloc, of); + slice_type->Slice.elem = of; + + return slice_type; + } +} + +Type* type_make_dynarray(bh_allocator alloc, Type* of) { + if (of == NULL) return NULL; + if (of == (Type *) &node_that_signals_failure) return of; + + assert(of->id > 0); + u64 dynarr_id = bh_imap_get(&type_dynarr_map, of->id); + if (dynarr_id > 0) { + Type* dynarr = (Type *) bh_imap_get(&type_map, dynarr_id); + return dynarr; + + } else { + Type* dynarr = type_create(Type_Kind_DynArray, alloc, 0); + type_register(dynarr); + bh_imap_put(&type_dynarr_map, of->id, dynarr->id); + + type_make_pointer(alloc, of); + dynarr->DynArray.elem = of; + + return dynarr; + } +} + +Type* type_make_varargs(bh_allocator alloc, Type* of) { + if (of == NULL) return NULL; + if (of == (Type *) &node_that_signals_failure) return of; + + assert(of->id > 0); + u64 vararg_id = bh_imap_get(&type_vararg_map, of->id); + if (vararg_id > 0) { + Type* va_type = (Type *) bh_imap_get(&type_map, vararg_id); + return va_type; + + } else { + Type* va_type = type_create(Type_Kind_VarArgs, alloc, 0); + type_register(va_type); + bh_imap_put(&type_vararg_map, of->id, va_type->id); + + type_make_pointer(alloc, of); + va_type->VarArgs.elem = of; + + return va_type; + } +} + +void build_linear_types_with_offset(Type* type, bh_arr(TypeWithOffset)* pdest, u32 offset) { + if (type_is_structlike_strict(type)) { + u32 mem_count = type_structlike_mem_count(type); + StructMember smem = { 0 }; + fori (i, 0, mem_count) { + type_lookup_member_by_idx(type, i, &smem); + build_linear_types_with_offset(smem.type, pdest, offset + smem.offset); + } + + } else if (type->kind == Type_Kind_Compound) { + u32 elem_offset = 0; + fori (i, 0, type->Compound.count) { + build_linear_types_with_offset(type->Compound.types[i], pdest, offset + elem_offset); + elem_offset += bh_max(type_size_of(type->Compound.types[i]), 4); + } + + } else { + bh_arr(TypeWithOffset) dest = *pdest; + + TypeWithOffset two; + two.type = type; + two.offset = offset; + bh_arr_push(dest, two); + + *pdest = dest; + } +} + +b32 type_struct_member_apply_use(bh_allocator alloc, Type *s_type, StructMember *smem) { + Type* used_type = smem->type; + + b32 type_is_pointer = 0; + if (used_type->kind == Type_Kind_Pointer) { + type_is_pointer = 1; + used_type = type_get_contained_type(used_type); + } + + if (used_type->kind != Type_Kind_Struct) { + onyx_report_error(smem->token->pos, Error_Critical, "Can only use things of structure, or pointer to structure type."); + return 0; + } + + if (used_type->Struct.status == SPS_Start) return 0; + + bh_arr_each(StructMember*, psmem, used_type->Struct.memarr) { + if (shgeti(s_type->Struct.members, (*psmem)->name) != -1) { + onyx_report_error(smem->token->pos, Error_Critical, "Used name '%s' conflicts with existing struct member.", (*psmem)->name); + return 0; + } + + StructMember* new_smem = bh_alloc_item(alloc, StructMember); + new_smem->type = (*psmem)->type; + new_smem->name = (*psmem)->name; + new_smem->meta_tags = (*psmem)->meta_tags; + new_smem->used = 0; + new_smem->included_through_use = 1; + + if (type_is_pointer) { + new_smem->offset = (*psmem)->offset; + new_smem->idx = (*psmem)->idx; + new_smem->initial_value = NULL; + new_smem->use_through_pointer_index = smem->idx; + } else { + new_smem->offset = smem->offset + (*psmem)->offset; + new_smem->idx = -1; // Dummy value because I don't think this is needed. + new_smem->initial_value = (*psmem)->initial_value; + new_smem->use_through_pointer_index = -1; + } + + shput(s_type->Struct.members, (*psmem)->name, new_smem); + } + + return 1; +} + +const char* type_get_unique_name(Type* type) { + if (type == NULL) return "unknown"; + + switch (type->kind) { + case Type_Kind_Basic: return type->Basic.name; + case Type_Kind_Pointer: return bh_aprintf(global_scratch_allocator, "^%s", type_get_unique_name(type->Pointer.elem)); + case Type_Kind_Array: return bh_aprintf(global_scratch_allocator, "[%d] %s", type->Array.count, type_get_unique_name(type->Array.elem)); + case Type_Kind_Struct: + if (type->Struct.name) + return bh_aprintf(global_scratch_allocator, "%s@%l", type->Struct.name, type->id); + else + return bh_aprintf(global_scratch_allocator, "%s@%l", "", type->id); + case Type_Kind_Enum: + if (type->Enum.name) + return bh_aprintf(global_scratch_allocator, "%s@%l", type->Enum.name, type->id); + else + return bh_aprintf(global_scratch_allocator, "%s@%l", "", type->id); + + case Type_Kind_Slice: return bh_aprintf(global_scratch_allocator, "[] %s", type_get_unique_name(type->Slice.elem)); + case Type_Kind_VarArgs: return bh_aprintf(global_scratch_allocator, "..%s", type_get_unique_name(type->VarArgs.elem)); + case Type_Kind_DynArray: return bh_aprintf(global_scratch_allocator, "[..] %s", type_get_unique_name(type->DynArray.elem)); + + case Type_Kind_Function: { + char buf[1024]; + memset(buf, 0, 1024); + + strncat(buf, "(", 1023); + fori (i, 0, type->Function.param_count) { + strncat(buf, type_get_unique_name(type->Function.params[i]), 1023); + + if (i >= type->Function.needed_param_count) + strncat(buf, "?", 1023); + + if (i != type->Function.param_count - 1) + strncat(buf, ", ", 1023); + } + + strncat(buf, ") -> ", 1023); + strncat(buf, type_get_unique_name(type->Function.return_type), 1023); + + return bh_aprintf(global_scratch_allocator, "%s", buf); + } + + case Type_Kind_Compound: { + char buf[1024]; + memset(buf, 0, 1024); + + strncat(buf, "(", 1023); + fori (i, 0, type->Compound.count) { + strncat(buf, type_get_unique_name(type->Compound.types[i]), 1023); + if (i != type->Compound.count - 1) + strncat(buf, ", ", 1023); + } + strncat(buf, ")", 1023); + + return bh_aprintf(global_scratch_allocator, "%s", buf); + } + + case Type_Kind_Distinct: { + return bh_aprintf(global_scratch_allocator, "%s@%l", type->Distinct.name, type->id); + } + + default: return "unknown (not null)"; + } +} + +const char* type_get_name(Type* type) { + if (type == NULL) return "unknown"; + + switch (type->kind) { + case Type_Kind_Basic: return type->Basic.name; + case Type_Kind_Pointer: return bh_aprintf(global_scratch_allocator, "^%s", type_get_name(type->Pointer.elem)); + case Type_Kind_Array: return bh_aprintf(global_scratch_allocator, "[%d] %s", type->Array.count, type_get_name(type->Array.elem)); + case Type_Kind_Struct: + if (type->Struct.name) + return type->Struct.name; + else + return ""; + case Type_Kind_Enum: + if (type->Enum.name) + return type->Enum.name; + else + return ""; + + case Type_Kind_Slice: return bh_aprintf(global_scratch_allocator, "[] %s", type_get_name(type->Slice.elem)); + case Type_Kind_VarArgs: return bh_aprintf(global_scratch_allocator, "..%s", type_get_name(type->VarArgs.elem)); + case Type_Kind_DynArray: return bh_aprintf(global_scratch_allocator, "[..] %s", type_get_name(type->DynArray.elem)); + + case Type_Kind_Function: { + char buf[512]; + fori (i, 0, 512) buf[i] = 0; + + strncat(buf, "(", 511); + fori (i, 0, type->Function.param_count) { + strncat(buf, type_get_name(type->Function.params[i]), 511); + if (i != type->Function.param_count - 1) + strncat(buf, ", ", 511); + } + + strncat(buf, ") -> ", 511); + strncat(buf, type_get_name(type->Function.return_type), 511); + + return bh_aprintf(global_scratch_allocator, "%s", buf); + } + + case Type_Kind_Compound: { + char buf[512]; + fori (i, 0, 512) buf[i] = 0; + + strncat(buf, "(", 511); + fori (i, 0, type->Compound.count) { + strncat(buf, type_get_name(type->Compound.types[i]), 511); + if (i != type->Compound.count - 1) + strncat(buf, ", ", 511); + } + strncat(buf, ")", 511); + + return bh_aprintf(global_scratch_allocator, "%s", buf); + } + + case Type_Kind_Distinct: { + return bh_aprintf(global_scratch_allocator, "%s", type->Distinct.name); + } + + default: return "unknown"; + } +} + +u32 type_get_alignment_log2(Type* type) { + i32 store_size = type_alignment_of(type); + if (store_size == 1) return 0; + else if (store_size == 2) return 1; + else if (store_size == 4) return 2; + else if (store_size == 8) return 3; + else if (store_size == 16) return 4; + return 2; +} + +Type* type_get_contained_type(Type* type) { + if (type == NULL) return NULL; + switch (type->kind) { + case Type_Kind_Pointer: return type->Pointer.elem; + case Type_Kind_Array: return type->Array.elem; + case Type_Kind_Slice: return type->Slice.elem; + case Type_Kind_DynArray: return type->DynArray.elem; + case Type_Kind_VarArgs: return type->VarArgs.elem; + default: return NULL; + } +} + +static const StructMember slice_members[] = { + { 0, 0, NULL, "data", NULL, NULL, -1, 0, 0 }, + { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "count", NULL, NULL, -1, 0, 0 }, + { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "size", NULL, NULL, -1, 0, 0 }, + { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "length", NULL, NULL, -1, 0, 0 }, +}; + +static const StructMember array_members[] = { + { 0, 0, NULL, "data", NULL, NULL, -1, 0, 0 }, + { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "count", NULL, NULL, -1, 0, 0 }, + { POINTER_SIZE + 4, 2, &basic_types[Basic_Kind_U32], "capacity", NULL, NULL, -1, 0, 0 }, + { POINTER_SIZE + 8, 3, NULL, "allocator", NULL, NULL, -1, 0, 0 }, + { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "size", NULL, NULL, -1, 0, 0 }, + { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "length", NULL, NULL, -1, 0, 0 }, +}; + +b32 type_lookup_member(Type* type, char* member, StructMember* smem) { + if (type->kind == Type_Kind_Pointer) type = type->Pointer.elem; + + switch (type->kind) { + case Type_Kind_Struct: { + TypeStruct* stype = &type->Struct; + + i32 index = shgeti(stype->members, member); + if (index == -1) return 0; + *smem = *stype->members[index].value; + return 1; + } + + case Type_Kind_VarArgs: + case Type_Kind_Slice: { + fori (i, 0, (i64) (sizeof(slice_members) / sizeof(StructMember))) { + if (strcmp(slice_members[i].name, member) == 0) { + *smem = slice_members[i]; + if (smem->idx == 0) smem->type = type_make_pointer(context.ast_alloc, type->Slice.elem); + + return 1; + } + } + return 0; + } + + case Type_Kind_DynArray: { + fori (i, 0, (i64) (sizeof(array_members) / sizeof(StructMember))) { + if (strcmp(array_members[i].name, member) == 0) { + *smem = array_members[i]; + if (smem->idx == 0) smem->type = type_make_pointer(context.ast_alloc, type->DynArray.elem); + if (smem->idx == 3) smem->type = type_build_from_ast(context.ast_alloc, builtin_allocator_type); + + return 1; + } + } + return 0; + } + + default: return 0; + } +} + +b32 type_lookup_member_by_idx(Type* type, i32 idx, StructMember* smem) { + if (type->kind == Type_Kind_Pointer) type = type->Pointer.elem; + + switch (type->kind) { + case Type_Kind_Struct: { + TypeStruct* stype = &type->Struct; + + if (idx > stype->mem_count) return 0; + *smem = *stype->memarr[idx]; + return 1; + } + + // HACK: This relies on the fact that the structures for Slice and VarArgs + // are identical. - brendanfh 2020/09/07 + case Type_Kind_VarArgs: + case Type_Kind_Slice: { + if (idx > 2) return 0; + + *smem = slice_members[idx]; + if (smem->idx == 0) smem->type = type_make_pointer(context.ast_alloc, type->Slice.elem); + + return 1; + } + + case Type_Kind_DynArray: { + if (idx > 4) return 0; + + *smem = array_members[idx]; + if (idx == 0) smem->type = type_make_pointer(context.ast_alloc, type->DynArray.elem); + if (idx == 3) smem->type = type_build_from_ast(context.ast_alloc, builtin_allocator_type); + + return 1; + } + + default: return 0; + } +} + +i32 type_linear_member_count(Type* type) { + switch (type->kind) { + case Type_Kind_Slice: + case Type_Kind_VarArgs: return 2; + case Type_Kind_DynArray: return 5; + case Type_Kind_Compound: return bh_arr_length(type->Compound.linear_members); + case Type_Kind_Struct: return bh_arr_length(type->Struct.linear_members); + default: return 1; + } +} + +b32 type_linear_member_lookup(Type* type, i32 idx, TypeWithOffset* two) { + switch (type->kind) { + case Type_Kind_Slice: + case Type_Kind_VarArgs: { + if (idx == 0) { + two->type = type_make_pointer(context.ast_alloc, type->Slice.elem); + two->offset = 0; + } + if (idx == 1) { + two->type = &basic_types[Basic_Kind_U32]; + two->offset = POINTER_SIZE; + } + + return 1; + } + case Type_Kind_DynArray: { + if (idx == 0) { + two->type = type_make_pointer(context.ast_alloc, type->DynArray.elem); + two->offset = 0; + } + if (idx == 1) { + two->type = &basic_types[Basic_Kind_U32]; + two->offset = POINTER_SIZE; + } + if (idx == 2) { + two->type = &basic_types[Basic_Kind_U32]; + two->offset = POINTER_SIZE + 4; + } + if (idx == 3 || idx == 4) { + Type* allocator_type = type_build_from_ast(context.ast_alloc, builtin_allocator_type); + type_linear_member_lookup(allocator_type, idx - 3, two); + two->offset += POINTER_SIZE + 8; + } + + return 1; + } + case Type_Kind_Compound: *two = type->Compound.linear_members[idx]; return 1; + case Type_Kind_Struct: *two = type->Struct.linear_members[idx]; return 1; + + case Type_Kind_Distinct: + two->type = type->Distinct.base_type; + two->offset = 0; + return 1; + + default: { + if (idx > 0) return 0; + two->offset = 0; + two->type = type; + return 1; + } + } +} + +i32 type_get_idx_of_linear_member_with_offset(Type* type, u32 offset) { + switch (type->kind) { + case Type_Kind_Slice: + case Type_Kind_VarArgs: { + if (offset == 0) return 0; + if (offset == POINTER_SIZE) return 1; + return -1; + } + case Type_Kind_DynArray: { + if (offset == 0) return 0; + if (offset == POINTER_SIZE) return 1; + if (offset == POINTER_SIZE + 4) return 2; + if (offset == POINTER_SIZE + 8) return 3; + if (offset == POINTER_SIZE * 2 + 8) return 4; + return -1; + } + case Type_Kind_Compound: { + i32 idx = 0; + bh_arr_each(TypeWithOffset, two, type->Compound.linear_members) { + if (two->offset == offset) return idx; + idx++; + } + + return -1; + } + case Type_Kind_Struct: { + i32 idx = 0; + bh_arr_each(TypeWithOffset, two, type->Struct.linear_members) { + if (two->offset == offset) return idx; + idx++; + } + + return -1; + } + default: return -1; + } +} + +b32 type_struct_is_simple(Type* type) { + if (type->kind != Type_Kind_Struct) return 0; + + b32 is_simple = 1; + bh_arr_each(StructMember *, mem, type->Struct.memarr) { + if (type_is_compound((*mem)->type) || (*mem)->type->kind == Type_Kind_Array) { + is_simple = 0; + break; + } + } + + return is_simple; +} + +b32 type_is_pointer(Type* type) { + if (type == NULL) return 0; + return type->kind == Type_Kind_Pointer; +} + +b32 type_is_rawptr(Type* type) { + if (type == NULL) return 0; + return type->kind == Type_Kind_Basic && type->Basic.kind == Basic_Kind_Rawptr; +} + +b32 type_is_array(Type* type) { + if (type == NULL) return 0; + return type->kind == Type_Kind_Array; +} + +b32 type_is_struct(Type* type) { + if (type == NULL) return 0; + if (type->kind == Type_Kind_Struct) return 1; + if (type->kind == Type_Kind_Pointer) + if (type->Pointer.elem != NULL && type->Pointer.elem->kind == Type_Kind_Struct) return 1; + return 0; +} + +b32 type_is_bool(Type* type) { + if (type == NULL) return 0; + return type != NULL && type->kind == Type_Kind_Basic && type->Basic.kind == Basic_Kind_Bool; +} + +b32 type_is_small_integer(Type* type) { + if (type == NULL) return 0; + if (type->kind == Type_Kind_Enum) return type_is_small_integer(type->Enum.backing); + if (type->kind == Type_Kind_Distinct) return type_is_small_integer(type->Distinct.base_type); + if (type->kind != Type_Kind_Basic) return 0; + + return type->Basic.kind >= Basic_Kind_I8 && type->Basic.kind <= Basic_Kind_U32; +} + +b32 type_is_integer(Type* type) { + if (type == NULL) return 0; + if (type->kind == Type_Kind_Enum) return type_is_integer(type->Enum.backing); + if (type->kind == Type_Kind_Distinct) return type_is_integer(type->Distinct.base_type); + if (type->kind != Type_Kind_Basic) return 0; + + return (type->Basic.kind >= Basic_Kind_I8 && type->Basic.kind <= Basic_Kind_U64) + || type->Basic.kind == Basic_Kind_Type_Index; +} + +b32 type_is_numeric(Type* type) { + if (type == NULL) return 0; + if (type->kind == Type_Kind_Enum) return 1; + if (type->kind == Type_Kind_Distinct) return type_is_numeric(type->Distinct.base_type); + if (type->kind != Type_Kind_Basic) return 0; + + return type->Basic.kind >= Basic_Kind_Int_Unsized && type->Basic.kind <= Basic_Kind_F64; +} + +b32 type_is_compound(Type* type) { + if (type == NULL) return 0; + + if (type->kind == Type_Kind_Struct) { + // + // This is for the kind of common case where a structure simply wraps a + // single non-compound value; in this situation, the structure can be + // "dissolved" at compile-time and turn into the underlying type. + // + + if (bh_arr_length(type->Struct.linear_members) != 1) return 1; + return type_is_compound(type->Struct.linear_members[0].type); + } + + return type->kind != Type_Kind_Basic + && type->kind != Type_Kind_Pointer + && type->kind != Type_Kind_Enum + && type->kind != Type_Kind_Function + && type->kind != Type_Kind_Array + && type->kind != Type_Kind_Distinct; +} + +b32 type_is_simd(Type* type) { + if (type == NULL) return 0; + if (type->kind != Type_Kind_Basic) return 0; + return type->Basic.flags & Basic_Flag_SIMD; +} + +b32 type_results_in_void(Type* type) { + return (type == NULL) + || (type->kind == Type_Kind_Basic && type->Basic.kind == Basic_Kind_Void) + || ( (type->kind == Type_Kind_Function) + && (type->Function.return_type->kind == Type_Kind_Basic) + && (type->Function.return_type->Basic.kind == Basic_Kind_Void)); +} + +b32 type_is_array_accessible(Type* type) { + if (type == NULL) return 0; + if (type_is_pointer(type)) return 1; + if (type->kind == Type_Kind_Array) return 1; + if (type->kind == Type_Kind_Slice) return 1; + if (type->kind == Type_Kind_DynArray) return 1; + if (type->kind == Type_Kind_VarArgs) return 1; + return 0; +} + +b32 type_is_structlike(Type* type) { + if (type == NULL) return 0; + if (type->kind == Type_Kind_Array) return 1; + if (type->kind == Type_Kind_Struct) return 1; + if (type->kind == Type_Kind_Slice) return 1; + if (type->kind == Type_Kind_DynArray) return 1; + if (type->kind == Type_Kind_VarArgs) return 1; + if (type->kind == Type_Kind_Pointer) { + if (type->Pointer.elem->kind == Type_Kind_Struct) return 1; + if (type->Pointer.elem->kind == Type_Kind_Slice) return 1; + if (type->Pointer.elem->kind == Type_Kind_DynArray) return 1; + } + return 0; +} + +b32 type_is_structlike_strict(Type* type) { + if (type == NULL) return 0; + if (type->kind == Type_Kind_Struct) return 1; + if (type->kind == Type_Kind_Slice) return 1; + if (type->kind == Type_Kind_DynArray) return 1; + if (type->kind == Type_Kind_VarArgs) return 1; + return 0; +} + +u32 type_structlike_mem_count(Type* type) { + if (type == NULL) return 0; + switch (type->kind) { + case Type_Kind_Struct: return type->Struct.mem_count; + case Type_Kind_Slice: return 2; + case Type_Kind_VarArgs: return 2; + case Type_Kind_DynArray: return 4; + default: return 0; + } +} + +u32 type_structlike_is_simple(Type* type) { + if (type == NULL) return 0; + switch (type->kind) { + case Type_Kind_Struct: return type_struct_is_simple(type); + case Type_Kind_Slice: return 1; + case Type_Kind_VarArgs: return 1; + case Type_Kind_DynArray: return 0; + default: return 0; + } +} + +b32 type_is_sl_constructable(Type* type) { + if (type == NULL) return 0; + switch (type->kind) { + case Type_Kind_Struct: return 1; + case Type_Kind_Slice: return 1; + case Type_Kind_DynArray: return 1; + default: return 0; + } +} + +b32 type_struct_constructed_from_poly_struct(Type* struct_type, struct AstType* from) { + if (struct_type == NULL) return 0; + if (struct_type->kind != Type_Kind_Struct) return 0; + + return struct_type->Struct.constructed_from == from; +} diff --git a/compiler/src/utils.c b/compiler/src/utils.c new file mode 100644 index 00000000..7f4ea49b --- /dev/null +++ b/compiler/src/utils.c @@ -0,0 +1,1206 @@ +#define BH_DEBUG + +#include "utils.h" +#include "lex.h" +#include "astnodes.h" +#include "errors.h" +#include "parser.h" +#include "astnodes.h" +#include "errors.h" + +bh_scratch global_scratch; +bh_allocator global_scratch_allocator; + +bh_managed_heap global_heap; +bh_allocator global_heap_allocator; + +// +// Program info and packages +// +Package* package_lookup(char* package_name) { + i32 index = shgeti(context.packages, package_name); + if (index != -1) { + return context.packages[index].value; + } else { + return NULL; + } +} + +Package* package_lookup_or_create(char* package_name, Scope* parent_scope, bh_allocator alloc, OnyxFilePos pos) { + i32 index = shgeti(context.packages, package_name); + if (index != -1) { + return context.packages[index].value; + + } else { + Package* package = bh_alloc_item(alloc, Package); + + char* pac_name = bh_alloc_array(alloc, char, strlen(package_name) + 1); + memcpy(pac_name, package_name, strlen(package_name) + 1); + pac_name[strlen(package_name)] = '\0'; + + package->name = pac_name; + package->use_package_entities = NULL; + + if (!strcmp(pac_name, "builtin")) { + package->private_scope = scope_create(alloc, context.global_scope, pos); + package->scope = context.global_scope; + } else { + package->scope = scope_create(alloc, parent_scope, pos); + package->private_scope = scope_create(alloc, package->scope, pos); + } + + shput(context.packages, pac_name, package); + + if (!charset_contains(pac_name, '.')) { + AstPackage* package_node = onyx_ast_node_new(alloc, sizeof(AstPackage), Ast_Kind_Package); + package_node->package_name = package->name; + package_node->package = package; + + symbol_raw_introduce(context.global_scope, pac_name, pos, (AstNode *) package_node); + } + + return package; + } +} + +void package_track_use_package(Package* package, Entity* entity) { + assert(entity); + + if (package->use_package_entities == NULL) { + bh_arr_new(global_heap_allocator, package->use_package_entities, 4); + } + + bh_arr_push(package->use_package_entities, entity); +} + +void package_reinsert_use_packages(Package* package) { + if (!package) return; + if (!package->use_package_entities) return; + + bh_arr_each(Entity *, use_package, package->use_package_entities) { + (*use_package)->state = Entity_State_Resolve_Symbols; + (*use_package)->macro_attempts = 0; + entity_heap_insert_existing(&context.entities, *use_package); + } + + bh_arr_set_length(package->use_package_entities, 0); +} + + +// +// Scoping +// +static u64 next_scope_id = 1; + +Scope* scope_create(bh_allocator a, Scope* parent, OnyxFilePos created_at) { + Scope* scope = bh_alloc_item(a, Scope); + scope->id = next_scope_id++; + scope->parent = parent; + scope->created_at = created_at; + scope->name = NULL; + + scope->symbols = NULL; + sh_new_arena(scope->symbols); + + return scope; +} + +void scope_include(Scope* target, Scope* source, OnyxFilePos pos) { + fori (i, 0, shlen(source->symbols)) { + symbol_raw_introduce(target, source->symbols[i].key, pos, source->symbols[i].value); + } +} + +b32 symbol_introduce(Scope* scope, OnyxToken* tkn, AstNode* symbol) { + token_toggle_end(tkn); + + b32 ret = symbol_raw_introduce(scope, tkn->text, tkn->pos, symbol); + + token_toggle_end(tkn); + return ret; +} + +b32 symbol_raw_introduce(Scope* scope, char* name, OnyxFilePos pos, AstNode* symbol) { + if (strcmp(name, "_")) { + i32 index = shgeti(scope->symbols, name); + if (index != -1) { + AstNode *node = scope->symbols[index].value; + if (node != symbol) { + onyx_report_error(pos, Error_Critical, "Redeclaration of symbol '%s'.", name); + + if (node->token) { + onyx_report_error(node->token->pos, Error_Critical, "Previous declaration was here."); + } + + return 0; + } + return 1; + } + } + + shput(scope->symbols, name, symbol); + return 1; +} + +void symbol_builtin_introduce(Scope* scope, char* sym, AstNode *node) { + shput(scope->symbols, sym, node); +} + +void symbol_subpackage_introduce(Scope* scope, char* sym, AstPackage* package) { + i32 index = shgeti(scope->symbols, sym); + if (index != -1) { + AstNode* maybe_package = scope->symbols[index].value; + + // CLEANUP: Make this assertion an actual error message. + assert(maybe_package->kind == Ast_Kind_Package); + } else { + shput(scope->symbols, sym, (AstNode *) package); + } +} + +AstNode* symbol_raw_resolve(Scope* start_scope, char* sym) { + Scope* scope = start_scope; + + while (scope != NULL) { + i32 index = shgeti(scope->symbols, sym); + if (index != -1) { + AstNode* res = scope->symbols[index].value; + + if ((res->flags & Ast_Flag_Symbol_Invisible) == 0) { + return res; + } + } + + scope = scope->parent; + } + + return NULL; +} + +AstNode* symbol_resolve(Scope* start_scope, OnyxToken* tkn) { + token_toggle_end(tkn); + AstNode* res = symbol_raw_resolve(start_scope, tkn->text); + token_toggle_end(tkn); + + return res; +} + +AstNode* try_symbol_raw_resolve_from_node(AstNode* node, char* symbol) { + b32 used_pointer = 0; + + while (1) { + if (!node) return NULL; + + switch (node->kind) { + case Ast_Kind_Type_Raw_Alias: node = (AstNode *) ((AstTypeRawAlias *) node)->to->ast_type; break; + case Ast_Kind_Type_Alias: node = (AstNode *) ((AstTypeAlias *) node)->to; break; + case Ast_Kind_Alias: node = (AstNode *) ((AstAlias *) node)->alias; break; + case Ast_Kind_Pointer_Type: { + if (used_pointer) goto all_types_peeled_off; + used_pointer = 1; + + node = (AstNode *) ((AstPointerType *) node)->elem; + break; + } + + default: goto all_types_peeled_off; + } + } + +all_types_peeled_off: + if (!node) return NULL; + + switch (node->kind) { + case Ast_Kind_Package: { + AstPackage* package = (AstPackage *) node; + + // CLEANUP + if (package->package == NULL) { + package->package = package_lookup(package->package_name); + } + + if (package->package == NULL) { + return NULL; + } + + return symbol_raw_resolve(package->package->scope, symbol); + } + + case Ast_Kind_Foreign_Block: { + AstForeignBlock* fb = (AstForeignBlock *) node; + + if (fb->scope == NULL) + return NULL; + + return symbol_raw_resolve(fb->scope, symbol); + } + + case Ast_Kind_Enum_Type: { + AstEnumType* etype = (AstEnumType *) node; + return symbol_raw_resolve(etype->scope, symbol); + } + + case Ast_Kind_Struct_Type: { + AstStructType* stype = (AstStructType *) node; + + // HACK HACK + // Temporarily disable the parent scope so that you can't access things + // "above" the structures scope. This leads to unintended behavior, as when + // you are accessing a static element on a structure, you don't expect to + // bleed to the top level scope. + AstNode *result = NULL; + if (stype->scope) { + Scope *tmp_parent = stype->scope->parent; + stype->scope->parent = NULL; + result = symbol_raw_resolve(stype->scope, symbol); + stype->scope->parent = tmp_parent; + } + + if (result == NULL && stype->stcache != NULL) { + Type* struct_type = stype->stcache; + assert(struct_type->kind == Type_Kind_Struct); + + bh_arr_each(AstPolySolution, sln, struct_type->Struct.poly_sln) { + if (token_text_equals(sln->poly_sym->token, symbol)) { + if (sln->kind == PSK_Type) { + result = (AstNode *) sln->type->ast_type; + } else { + result = (AstNode *) sln->value; + } + } + } + } + + return result; + } + + case Ast_Kind_Poly_Struct_Type: { + AstStructType* stype = ((AstPolyStructType *) node)->base_struct; + return symbol_raw_resolve(stype->scope, symbol); + } + + case Ast_Kind_Poly_Call_Type: { + AstNode* callee = (AstNode *) ((AstPolyCallType *) node)->callee; + return try_symbol_raw_resolve_from_node(callee, symbol); + } + } + + return NULL; +} + +AstNode* try_symbol_resolve_from_node(AstNode* node, OnyxToken* token) { + token_toggle_end(token); + AstNode* result = try_symbol_raw_resolve_from_node(node, token->text); + token_toggle_end(token); + + return result; +} + +AstNode* try_symbol_raw_resolve_from_type(Type *type, char* symbol) { + while (type->kind == Type_Kind_Pointer) { + type = type->Pointer.elem; + } + + if (type->kind == Type_Kind_Struct) { + if (type->Struct.poly_sln == NULL) return NULL; + + bh_arr_each(AstPolySolution, sln, type->Struct.poly_sln) { + if (token_text_equals(sln->poly_sym->token, symbol)) { + if (sln->kind == PSK_Type) { + AstTypeRawAlias* alias = onyx_ast_node_new(context.ast_alloc, sizeof(AstTypeRawAlias), Ast_Kind_Type_Raw_Alias); + alias->type = &basic_types[Basic_Kind_Type_Index]; + alias->to = sln->type; + return (AstNode *) alias; + + } else { + return (AstNode *) sln->value; + } + } + } + } + + return NULL; +} + +void scope_clear(Scope* scope) { + sh_new_arena(scope->symbols); +} + +// Polymorphic procedures are in their own file to clean up this file. +#include "polymorph.h" + +// +// Overloaded Procedures +// + +// +// @Cleanup: Everything having to do with overload resolving! +// Things that need to be available: +// * A copy of the arguments list that can be mutated +// - The named_values do not need to be copied, because they are not modified when fill_in_arguments happens. +// - Only values needs to have a copy available +// - This copy needs to be reset after checking every option +// +// Steps needed to check if an overload option is "the one": +// 1. Figure out what the overload is +// a. If polymorphic, generate the header for the procedure only +// 2. Place the arguments in the copy, according to the overload option +// 3. Ensure the option has a type filled out +// 4. For each argument +// a. Ensure it has a place to go (not too many arguments) +// b. Ensure it has a type +// c. Ensure the types match (currently there could be a problem if an option is attempted and doesn't work all the way that polymorphic procedures as arguments could still be solidified) +// +// Additional features that this needs to account for: +// * Resolving an overload from a list of parameter types +// * Resolving an overload from a TypeFunction (so an overloaded procedure can be passed as a parameter) +// + +void add_overload_option(bh_arr(OverloadOption)* poverloads, u64 precedence, AstTyped* overload) { + bh_arr(OverloadOption) overloads = *poverloads; + + i32 index = -1; + fori (i, 0, bh_arr_length(overloads)) { + if (overloads[i].precedence > precedence) { + index = i; + break; + } + } + + if (index < 0) { + bh_arr_push(overloads, ((OverloadOption) { + .precedence = precedence, + .option = overload, + })); + + } else { + bh_arr_insertn(overloads, index, 1); + overloads[index].precedence = precedence; + overloads[index].option = overload; + } + + *poverloads = overloads; +} + +// NOTE: The job of this function is to take a set of overloads, and traverse it to add all possible +// overloads that are reachable. This is slightly more difficult than it may seem. In this language, +// overloaded procedures have a strict ordering to their overloads, which determines how the correct +// match will be found. This was not very complicated until overloaded procedures could be used as +// overload options. This means that you could create an "infinite loop" of overloads like so: +// +// o1 :: { o2 :: { +// (...) { ... }, (...) { ... }, +// o2 o1 +// } } +// +// Obviously, this is not really an infinite loop. It just means that all options are available if +// o1 or o2 are called. The difference between o1 and o2 is the order that the overloads will be +// searched. To build the the list of overloads, a hashmap is used to prevent the problem from being +// O(n^2), even though n would (probably) be small. bh_imap has the useful property that it maintains +// an "entries" array that, so long as nothing is ever removed from it, will maintain the order in +// which entries were put into the map. This is useful because a simple recursive algorithm can +// collect all the overloads into the map, and also use the map to provide a base case. +void build_all_overload_options(bh_arr(OverloadOption) overloads, bh_imap* all_overloads) { + bh_arr_each(OverloadOption, overload, overloads) { + if (bh_imap_has(all_overloads, (u64) overload->option)) continue; + + bh_imap_put(all_overloads, (u64) overload->option, 1); + + if (overload->option->kind == Ast_Kind_Overloaded_Function) { + AstOverloadedFunction* sub_overload = (AstOverloadedFunction *) overload->option; + build_all_overload_options(sub_overload->overloads, all_overloads); + } + } +} + +AstTyped* find_matching_overload_by_arguments(bh_arr(OverloadOption) overloads, Arguments* param_args) { + Arguments args; + arguments_clone(&args, param_args); + arguments_ensure_length(&args, bh_arr_length(args.values) + bh_arr_length(args.named_values)); + + // CLEANUP SPEED: This currently rebuilds the complete set of overloads every time one is looked up. + // This should be cached in the AstOverloadedFunction or somewhere like that. + bh_imap all_overloads; + bh_imap_init(&all_overloads, global_heap_allocator, bh_arr_length(overloads) * 2); + build_all_overload_options(overloads, &all_overloads); + + AstTyped *matched_overload = NULL; + + bh_arr_each(bh__imap_entry, entry, all_overloads.entries) { + AstTyped* node = (AstTyped *) strip_aliases((AstNode *) entry->key); + arguments_copy(&args, param_args); + + AstFunction* overload = NULL; + switch (node->kind) { + case Ast_Kind_Macro: overload = macro_resolve_header((AstMacro *) node, param_args, NULL, 0); break; + case Ast_Kind_Polymorphic_Proc: overload = polymorphic_proc_build_only_header((AstFunction *) node, PPLM_By_Arguments, param_args); break; + case Ast_Kind_Function: + overload = (AstFunction *) node; + arguments_clear_baked_flags(&args); + break; + } + + // NOTE: Overload is not something that is known to be overloadable. + if (overload == NULL) continue; + if (overload == (AstFunction *) &node_that_signals_a_yield) return (AstTyped *) overload; + if (overload->kind != Ast_Kind_Function) continue; + if (overload->type == NULL) { + // If it was not possible to create the type for this procedure, tell the + // caller that this should yield and try again later. + + // return and not continue because if the overload that didn't have a type will + // work in the future, then it has to take precedence over the other options available. + return (AstTyped *) &node_that_signals_a_yield; + } + assert(overload->type->kind == Type_Kind_Function); + + arguments_remove_baked(&args); + arguments_ensure_length(&args, get_argument_buffer_size(&overload->type->Function, &args)); + + // NOTE: If the arguments cannot be placed successfully in the parameters list + if (!fill_in_arguments(&args, (AstNode *) overload, NULL, 0)) continue; + + VarArgKind va_kind; + TypeMatch tm = check_arguments_against_type(&args, &overload->type->Function, &va_kind, NULL, NULL, NULL); + if (tm == TYPE_MATCH_SUCCESS) { + matched_overload = node; + break; + } + + if (tm == TYPE_MATCH_YIELD) { + return (AstTyped *) &node_that_signals_a_yield; + } + } + + bh_imap_free(&all_overloads); + bh_arr_free(args.values); + return matched_overload; +} + +AstTyped* find_matching_overload_by_type(bh_arr(OverloadOption) overloads, Type* type) { + if (type->kind != Type_Kind_Function) return NULL; + + bh_imap all_overloads; + bh_imap_init(&all_overloads, global_heap_allocator, bh_arr_length(overloads) * 2); + build_all_overload_options(overloads, &all_overloads); + + AstTyped *matched_overload = NULL; + + bh_arr_each(bh__imap_entry, entry, all_overloads.entries) { + AstTyped* node = (AstTyped *) entry->key; + if (node->kind == Ast_Kind_Overloaded_Function) continue; + + TypeMatch tm = unify_node_and_type(&node, type); + if (tm == TYPE_MATCH_SUCCESS) { + matched_overload = node; + break; + } + + if (tm == TYPE_MATCH_YIELD) { + return (AstTyped *) &node_that_signals_a_yield; + } + } + + bh_imap_free(&all_overloads); + return matched_overload; +} + +void report_unable_to_match_overload(AstCall* call, bh_arr(OverloadOption) overloads) { + char* arg_str = bh_alloc(global_scratch_allocator, 1024); + arg_str[0] = '\0'; + + bh_arr_each(AstTyped *, arg, call->args.values) { + strncat(arg_str, node_get_type_name(*arg), 1023); + + if (arg != &bh_arr_last(call->args.values)) + strncat(arg_str, ", ", 1023); + } + + if (bh_arr_length(call->args.named_values) > 0) { + if (bh_arr_length(call->args.values) > 0) { + strncat(arg_str, ", ", 1023); + } + + bh_arr_each(AstNamedValue *, named_value, call->args.named_values) { + token_toggle_end((*named_value)->token); + strncat(arg_str, (*named_value)->token->text, 1023); + token_toggle_end((*named_value)->token); + + strncat(arg_str, "=", 1023); + strncat(arg_str, node_get_type_name((*named_value)->value), 1023); // CHECK: this might say 'unknown'. + + if (named_value != &bh_arr_last(call->args.named_values)) + strncat(arg_str, ", ", 1023); + } + } + + onyx_report_error(call->token->pos, Error_Critical, "Unable to match overloaded function with provided argument types: (%s)", arg_str); + + bh_free(global_scratch_allocator, arg_str); + + // CLEANUP SPEED: This currently rebuilds the complete set of overloads every time one is looked up. + // This should be cached in the AstOverloadedFunction or somewhere like that. + bh_imap all_overloads; + bh_imap_init(&all_overloads, global_heap_allocator, bh_arr_length(overloads) * 2); + build_all_overload_options(overloads, &all_overloads); + + i32 i = 1; + bh_arr_each(bh__imap_entry, entry, all_overloads.entries) { + AstTyped* node = (AstTyped *) strip_aliases((AstNode *) entry->key); + onyx_report_error(node->token->pos, Error_Critical, "Here is one of the overloads. %d/%d", i++, bh_arr_length(all_overloads.entries)); + } + + bh_imap_free(&all_overloads); +} + + +// +// Macros +// +// +// TODO: Write this documentation +void expand_macro(AstCall** pcall, AstFunction* template) { + AstCall* call = *pcall; + AstMacro* macro = (AstMacro *) call->callee; + assert(macro->kind == Ast_Kind_Macro); + + assert(template->kind == Ast_Kind_Function); + assert(template->type != NULL); + assert(template->type->kind == Type_Kind_Function); + + AstBlock* expansion = (AstBlock *) ast_clone(context.ast_alloc, template->body); + expansion->rules = Block_Rule_Macro; + expansion->scope = NULL; + expansion->next = call->next; + + AstNode* subst = (AstNode *) expansion; + + if (template->type->Function.return_type != &basic_types[Basic_Kind_Void]) { + AstDoBlock* doblock = (AstDoBlock *) onyx_ast_node_new(context.ast_alloc, sizeof(AstDoBlock), Ast_Kind_Do_Block); + doblock->token = expansion->token; + doblock->block = expansion; + doblock->type = template->type->Function.return_type; + doblock->next = expansion->next; + expansion->next = NULL; + + subst = (AstNode *) doblock; + } + + Scope* argument_scope = scope_create(context.ast_alloc, NULL, call->token->pos); + if (expansion->binding_scope != NULL) + scope_include(argument_scope, expansion->binding_scope, call->token->pos); + expansion->binding_scope = argument_scope; + + // HACK HACK HACK This is probably very wrong. I don't know what guarentees that + // the paramters and arguments are going to be in the same order exactly. + Type *any_type = type_build_from_ast(context.ast_alloc, builtin_any_type); + fori (i, 0, bh_arr_length(call->args.values)) { + AstNode *value = (AstNode *) ((AstArgument *) call->args.values[i])->value; + assert(template->params[i].local->type); + + Type *param_type = template->params[i].local->type; + if (param_type == any_type + || (param_type->kind == Type_Kind_VarArgs && param_type->VarArgs.elem == any_type)) { + onyx_report_error(macro->token->pos, Error_Critical, "Currently, macros do not support arguments of type 'any' or '..any'."); + } + + symbol_introduce(argument_scope, template->params[i].local->token, value); + } + + if (template->poly_scope != NULL) + scope_include(argument_scope, template->poly_scope, call->token->pos); + + *(AstNode **) pcall = subst; + return; +} + +AstFunction* macro_resolve_header(AstMacro* macro, Arguments* args, OnyxToken* callsite, b32 error_if_failed) { + switch (macro->body->kind) { + case Ast_Kind_Function: return (AstFunction *) macro->body; + + case Ast_Kind_Polymorphic_Proc: { + AstFunction* pp = (AstFunction *) macro->body; + ensure_polyproc_cache_is_created(pp); + + bh_arr(AstPolySolution) slns = find_polymorphic_slns(pp, PPLM_By_Arguments, args, callsite, error_if_failed); + + if (slns == NULL) { + if (flag_to_yield) { + flag_to_yield = 0; + return (AstFunction *) &node_that_signals_a_yield; + } + + return NULL; + } + + return polymorphic_proc_build_only_header_with_slns(pp, slns, error_if_failed); + } + + default: assert(("Bad macro body type.", 0)); + } + + return NULL; +} + + +// +// Arguments resolving +// +static i32 lookup_idx_by_name(AstNode* provider, char* name) { + switch (provider->kind) { + case Ast_Kind_Struct_Literal: { + AstStructLiteral* sl = (AstStructLiteral *) provider; + assert(sl->type); + + StructMember s; + if (!type_lookup_member(sl->type, name, &s)) return -1; + if (s.included_through_use) return -1; + + return s.idx; + } + + case Ast_Kind_Function: { + AstFunction* func = (AstFunction *) provider; + + i32 param_idx = -1; + i32 idx = 0; + bh_arr_each(AstParam, param, func->params) { + if (token_text_equals(param->local->token, name)) { + param_idx = idx; + break; + } + + idx++; + } + + return param_idx; + } + + default: return -1; + } +} + +static AstNode* lookup_default_value_by_idx(AstNode* provider, i32 idx) { + switch (provider->kind) { + case Ast_Kind_Struct_Literal: { + AstStructLiteral* sl = (AstStructLiteral *) provider; + assert(sl->type); + + if (sl->type->kind == Type_Kind_Struct) { + bh_arr(StructMember *) memarr = sl->type->Struct.memarr; + if (idx >= bh_arr_length(memarr)) return NULL; + + return (AstNode *) *memarr[idx]->initial_value; + } + + return NULL; + } + + case Ast_Kind_Function: { + AstFunction* func = (AstFunction *) provider; + + AstTyped* default_value = func->params[idx].default_value; + if (default_value == NULL) return NULL; + + AstArgument* arg = make_argument(context.ast_alloc, default_value); + return (AstNode *) arg; + } + + default: return NULL; + } +} + +static i32 maximum_argument_count(AstNode* provider) { + switch (provider->kind) { + case Ast_Kind_Struct_Literal: { + AstStructLiteral* sl = (AstStructLiteral *) provider; + assert(sl->type); + + return type_structlike_mem_count(sl->type); + } + } + + // NOTE: This returns int_max for anything other than struct literals because the + // bounds checking on the arguments will be done elsewhere. + return 0x7fffffff; +} + +static i32 non_baked_argument_count(Arguments* args) { + if (args->used_argument_count >= 0) return args->used_argument_count; + + i32 count = 0; + + bh_arr_each(AstTyped *, actual, args->values) { + if ((*actual)->kind != Ast_Kind_Argument) count++; + else if (!((AstArgument *) (*actual))->is_baked) count++; + } + + bh_arr_each(AstNamedValue *, named_value, args->named_values) { + if ((*named_value)->value->kind != Ast_Kind_Argument) count++; + else if (!((AstArgument *) (*named_value)->value)->is_baked) count++; + } + + args->used_argument_count = count; + return count; +} + +i32 get_argument_buffer_size(TypeFunction* type, Arguments* args) { + i32 non_vararg_param_count = (i32) type->param_count; + if (non_vararg_param_count > 0) { + if (type->params[type->param_count - 1] == builtin_vararg_type_type) non_vararg_param_count--; + if (type->params[type->param_count - 1]->kind == Type_Kind_VarArgs) non_vararg_param_count--; + } + + return bh_max(non_vararg_param_count, non_baked_argument_count(args)); +} + +// NOTE: The values array can be partially filled out, and is the resulting array. +// Returns if all the values were filled in. +b32 fill_in_arguments(Arguments* args, AstNode* provider, char** err_msg, b32 insert_zero_values) { + + { // Delete baked arguments + // :ArgumentResolvingIsComplicated + i32 max = bh_arr_length(args->values); + fori (i, 0, max) { + AstTyped* value = args->values[i]; + if (value == NULL) continue; + + if (value->kind == Ast_Kind_Argument) { + if (((AstArgument *) value)->is_baked) { + i--; + max--; + bh_arr_deleten(args->values, i, 1); + } + } + } + } + + if (args->named_values != NULL) { + bh_arr_each(AstNamedValue *, p_named_value, args->named_values) { + AstNamedValue* named_value = *p_named_value; + + if (named_value->value->kind == Ast_Kind_Argument) { + if (((AstArgument *) named_value->value)->is_baked) { + // :ArgumentResolvingIsComplicated + bh_arr_set_length(args->values, bh_arr_length(args->values) - 1); + continue; + } + } + + token_toggle_end(named_value->token); + i32 idx = lookup_idx_by_name(provider, named_value->token->text); + if (idx == -1) { + if (err_msg) *err_msg = bh_aprintf(global_scratch_allocator, "'%s' is not a valid named parameter here.", named_value->token->text); + token_toggle_end(named_value->token); + return 0; + } + + // assert(idx < bh_arr_length(args->values)); + if (idx >= bh_arr_length(args->values)) { + if (err_msg) *err_msg = bh_aprintf(global_scratch_allocator, "Error placing value with name '%s' at index '%d'.", named_value->token->text, idx); + token_toggle_end(named_value->token); + return 0; + } + + if (args->values[idx] != NULL && args->values[idx] != named_value->value) { + if (err_msg) *err_msg = bh_aprintf(global_scratch_allocator, "Multiple values given for parameter named '%s'.", named_value->token->text); + token_toggle_end(named_value->token); + return 0; + } + + args->values[idx] = named_value->value; + token_toggle_end(named_value->token); + } + } + + b32 success = 1; + fori (idx, 0, bh_arr_length(args->values)) { + if (args->values[idx] == NULL) args->values[idx] = (AstTyped *) lookup_default_value_by_idx(provider, idx); + if (args->values[idx] == NULL) { + if (insert_zero_values) { + assert(provider->token); + args->values[idx] = (AstTyped *) make_zero_value(context.ast_alloc, provider->token, NULL); + } else { + if (err_msg) *err_msg = bh_aprintf(global_scratch_allocator, "No value given for %d%s argument.", idx + 1, bh_num_suffix(idx + 1)); + success = 0; + } + } + } + + i32 maximum_arguments = maximum_argument_count(provider); + if (bh_arr_length(args->values) > maximum_arguments) { + if (err_msg) *err_msg = bh_aprintf(global_scratch_allocator, "Too many values provided. Expected at most %d.", maximum_arguments); + success = 0; + } + + return success; +} + + +// +// Argument checking +// + +typedef enum ArgState { + AS_Expecting_Exact, + AS_Expecting_Typed_VA, + AS_Expecting_Untyped_VA, +} ArgState; + +TypeMatch check_arguments_against_type(Arguments* args, TypeFunction* func_type, VarArgKind* va_kind, + OnyxToken* location, char* func_name, OnyxError* error) { + b32 permanent = location != NULL; + if (func_name == NULL) func_name = "UNKNOWN FUNCTION"; + + if (error) error->rank = Error_Critical; + + bh_arr(AstArgument *) arg_arr = (bh_arr(AstArgument *)) args->values; + i32 arg_count = get_argument_buffer_size(func_type, args); + + Type **formal_params = func_type->params; + Type* variadic_type = NULL; + i64 any_type_id = type_build_from_ast(context.ast_alloc, builtin_any_type)->id; + + ArgState arg_state = AS_Expecting_Exact; + u32 arg_pos = 0; + while (1) { + switch (arg_state) { + case AS_Expecting_Exact: { + if (arg_pos >= func_type->param_count) goto type_checking_done; + + if (formal_params[arg_pos]->kind == Type_Kind_VarArgs) { + variadic_type = formal_params[arg_pos]->VarArgs.elem; + arg_state = AS_Expecting_Typed_VA; + continue; + } + + if ((i16) arg_pos == func_type->vararg_arg_pos) { + arg_state = AS_Expecting_Untyped_VA; + continue; + } + + if (arg_pos >= (u32) bh_arr_length(arg_arr)) goto type_checking_done; + + assert(arg_arr[arg_pos]->kind == Ast_Kind_Argument); + TypeMatch tm = unify_node_and_type_(&arg_arr[arg_pos]->value, formal_params[arg_pos], permanent); + if (tm == TYPE_MATCH_YIELD) return tm; + if (tm == TYPE_MATCH_FAILED) { + if (error != NULL) { + error->pos = arg_arr[arg_pos]->token->pos; + error->text = bh_aprintf(global_heap_allocator, + "The procedure '%s' expects a value of type '%s' for %d%s parameter, got '%s'.", + func_name, + type_get_name(formal_params[arg_pos]), + arg_pos + 1, + bh_num_suffix(arg_pos + 1), + node_get_type_name(arg_arr[arg_pos]->value)); + } + return tm; + } + + if (arg_arr[arg_pos]->value->type && arg_arr[arg_pos]->value->type->id != any_type_id && formal_params[arg_pos]->id == any_type_id) { + resolve_expression_type(arg_arr[arg_pos]->value); + if (error != NULL) { + arg_arr[arg_pos]->pass_as_any = 1; + } + } + + arg_arr[arg_pos]->va_kind = VA_Kind_Not_VA; + break; + } + + case AS_Expecting_Typed_VA: { + if (variadic_type->id == any_type_id) *va_kind = VA_Kind_Any; + + if (arg_pos >= (u32) bh_arr_length(arg_arr)) goto type_checking_done; + + if (variadic_type->id == any_type_id) { + resolve_expression_type(arg_arr[arg_pos]->value); + if (arg_arr[arg_pos]->value->type == NULL) { + if (error != NULL) { + error->pos = arg_arr[arg_pos]->token->pos; + error->text = "Unable to resolve type of argument."; + } + return TYPE_MATCH_FAILED; + } + + arg_arr[arg_pos]->va_kind = VA_Kind_Any; + break; + } + + *va_kind = VA_Kind_Typed; + + assert(arg_arr[arg_pos]->kind == Ast_Kind_Argument); + TypeMatch tm = unify_node_and_type_(&arg_arr[arg_pos]->value, variadic_type, permanent); + if (tm == TYPE_MATCH_YIELD) return tm; + if (tm == TYPE_MATCH_FAILED) { + if (error != NULL) { + error->pos = arg_arr[arg_pos]->token->pos, + error->text = bh_aprintf(global_heap_allocator, + "The procedure '%s' expects a value of type '%s' for the variadic parameter, got '%s'.", + func_name, + type_get_name(variadic_type), + node_get_type_name(arg_arr[arg_pos]->value)); + } + return tm; + } + + arg_arr[arg_pos]->va_kind = VA_Kind_Typed; + break; + } + + case AS_Expecting_Untyped_VA: { + *va_kind = VA_Kind_Untyped; + + if (arg_pos >= (u32) bh_arr_length(arg_arr)) goto type_checking_done; + + assert(arg_arr[arg_pos]->kind == Ast_Kind_Argument); + resolve_expression_type(arg_arr[arg_pos]->value); + if (arg_arr[arg_pos]->value->type == NULL) { + if (error != NULL) { + error->pos = arg_arr[arg_pos]->token->pos; + error->text = "Unable to resolve type for argument."; + } + return TYPE_MATCH_FAILED; + } + + arg_arr[arg_pos]->va_kind = VA_Kind_Untyped; + break; + } + } + + arg_pos++; + } + +type_checking_done: + if (arg_pos < func_type->needed_param_count) { + if (error != NULL) { + error->pos = location->pos; + error->text = "Too few arguments to function call."; + } + return TYPE_MATCH_FAILED; + } + + if (arg_pos < (u32) arg_count) { + if (error != NULL) { + error->pos = location->pos; + error->text = bh_aprintf(global_heap_allocator, "Too many arguments to function call. %d %d", arg_pos, arg_count); + } + return TYPE_MATCH_FAILED; + } + + return TYPE_MATCH_SUCCESS; +} + + + + + +// +// String parsing helpers +// +u32 char_to_base16_value(char x) { + if (x >= '0' && x <= '9') return (u32) (x - '0'); + if (x >= 'A' && x <= 'F') return (u32) (x - 'A' + 10); + if (x >= 'a' && x <= 'f') return (u32) (x - 'a' + 10); + return 0xffffffff; +} + +i32 string_process_escape_seqs(char* dest, char* src, i32 len) { + i32 total_len = 0; + for (i32 i = 0; i < len; i++) { + if (src[i] == '\\') { + i++; + switch (src[i]) { + case '0': *dest++ = '\0'; total_len++; break; + case 'a': *dest++ = '\a'; total_len++; break; + case 'b': *dest++ = '\b'; total_len++; break; + case 'f': *dest++ = '\f'; total_len++; break; + case 'n': *dest++ = '\n'; total_len++; break; + case 't': *dest++ = '\t'; total_len++; break; + case 'r': *dest++ = '\r'; total_len++; break; + case 'v': *dest++ = '\v'; total_len++; break; + case 'e': *dest++ = '\e'; total_len++; break; + case '"': *dest++ = '"'; total_len++; break; + case '\\': *dest++ = '\\'; total_len++; break; + case 'x': { + u8 ch1 = src[i + 1]; + u8 ch2 = src[i + 2]; + *dest++ = (i8) (char_to_base16_value(ch1) << 4 | char_to_base16_value(ch2)); + total_len++; + i += 2; + break; + } + default: *dest++ = '\\'; + *dest++ = src[i]; + total_len += 2; + } + } else { + *dest++ = src[i]; + total_len += 1; + } + } + + // NOTE: Gotta null terminate + *dest = 0; + + return total_len; +} + +static Scope **get_scope_from_node_helper(AstNode *node) { + b32 used_pointer = 0; + + while (1) { + if (!node) return NULL; + + switch (node->kind) { + case Ast_Kind_Type_Raw_Alias: node = (AstNode *) ((AstTypeRawAlias *) node)->to->ast_type; break; + case Ast_Kind_Type_Alias: node = (AstNode *) ((AstTypeAlias *) node)->to; break; + case Ast_Kind_Alias: node = (AstNode *) ((AstAlias *) node)->alias; break; + case Ast_Kind_Pointer_Type: { + if (used_pointer) goto all_types_peeled_off; + used_pointer = 1; + + node = (AstNode *) ((AstPointerType *) node)->elem; + break; + } + + default: goto all_types_peeled_off; + } + } + +all_types_peeled_off: + if (!node) return NULL; + + switch (node->kind) { + case Ast_Kind_Package: { + AstPackage* package = (AstPackage *) node; + if (package->package == NULL) return NULL; + + return &package->package->scope; + } + + case Ast_Kind_Enum_Type: { + AstEnumType* etype = (AstEnumType *) node; + return &etype->scope; + } + + case Ast_Kind_Struct_Type: { + AstStructType* stype = (AstStructType *) node; + return &stype->scope; + } + + case Ast_Kind_Poly_Struct_Type: { + AstPolyStructType* pstype = (AstPolyStructType *) node; + AstStructType* stype = pstype->base_struct; + return &stype->scope; + } + } + + return NULL; +} + +Scope *get_scope_from_node(AstNode *node) { + if (!node) return NULL; + + Scope **pscope = get_scope_from_node_helper(node); + if (!pscope) return NULL; + return *pscope; +} + +Scope *get_scope_from_node_or_create(AstNode *node) { + if (!node) return NULL; + + Scope **pscope = get_scope_from_node_helper(node); + if (!pscope) return NULL; + + // Create the scope if it does not exist. + // This uses a NULL parent, which I think is what + // is used in other parts of the compiler for struct/enum + // scopes? + if (!*pscope) { + assert(node->token); + *pscope = scope_create(context.ast_alloc, NULL, node->token->pos); + } + + return *pscope; +} + +u32 levenshtein_distance(const char *str1, const char *str2) { + i32 m = strlen(str1) + 1; + i32 n = strlen(str2) + 1; + + i32 *d = bh_alloc_array(global_scratch_allocator, i32, m * n); + fori (i, 0, m * n) d[i] = 0; + + fori (i, 0, m) d[i * n + 0] = i; + fori (j, 0, n) d[0 * n + j] = j; + + fori (j, 1, n) { + fori (i, 1, m) { + i32 subst_cost = str1[i - 1] == str2[j - 1] ? 0 : 1; + + i32 a = d[(i - 1) * n + j] + 1; + i32 b = d[i * n + (j - 1)] + 1; + i32 c = d[(i - 1) * n + (j - 1)] + subst_cost; + + d[i * n + j] = bh_min(bh_min(a, b), c); + } + } + + return d[m * n - 1]; +} + +char *find_closest_symbol_in_scope(Scope *scope, char *sym, u32 *out_distance) { + *out_distance = 0x7fffffff; + + if (scope == NULL) return NULL; + + char* closest = NULL; + fori (i, 0, shlen(scope->symbols)) { + if (scope->symbols[i].value->flags & Ast_Flag_Symbol_Invisible) continue; + + char *key = scope->symbols[i].key; + u32 d = levenshtein_distance(key, sym); + if (d < *out_distance) { + *out_distance = d; + closest = (char *) key; + } + } + + return closest; +} + +char *find_closest_symbol_in_scope_and_parents(Scope *scope, char *sym) { + u32 min_dist = 0x7fffffff; + u32 tmp_dist; + + char *closest = NULL; + while (scope != NULL) { + char *tmp_closest = find_closest_symbol_in_scope(scope, sym, &tmp_dist); + if (tmp_dist < min_dist) { + min_dist = tmp_dist; + closest = tmp_closest; + } + + scope = scope->parent; + } + + return closest; +} + +char *find_closest_symbol_in_node(AstNode* node, char *sym) { + Scope *scope = get_scope_from_node(node); + if (!scope) { + if (node->kind == Ast_Kind_Poly_Call_Type) { + AstPolyCallType* pcall = (AstPolyCallType *) node; + return find_closest_symbol_in_node((AstNode *) pcall->callee, sym); + } + + return NULL; + } + + u32 dist; + return find_closest_symbol_in_scope(scope, sym, &dist); +} + diff --git a/compiler/src/wasm_emit.c b/compiler/src/wasm_emit.c new file mode 100644 index 00000000..0dc1c4a6 --- /dev/null +++ b/compiler/src/wasm_emit.c @@ -0,0 +1,4588 @@ +// +// There are several things I'm seeing in this file that I want to clean up. +// They are: +// [x] remove the need to know if the stack is needed before generating the function. +// Just leave 5 nops at the beginning because they will be automatically removed +// by the WASM outputter. +// [x] remove the need to have "allocate_exprs" on blocks and in functions. This will +// be easy once the above is done. +// [x] there should be a better way to emit pending deferred statements because there +// is some code duplication between emit_return and emit_structured_jump. +// [ ] Change the calling convention so it is easier to use from both JS and in the compiler. + + + + +#define BH_DEBUG +#include "wasm_emit.h" +#include "utils.h" + +#define WASM_TYPE_INT32 0x7F +#define WASM_TYPE_INT64 0x7E +#define WASM_TYPE_FLOAT32 0x7D +#define WASM_TYPE_FLOAT64 0x7C +#define WASM_TYPE_VAR128 0x7B +#define WASM_TYPE_PTR WASM_TYPE_INT32 +#define WASM_TYPE_FUNC WASM_TYPE_INT32 +#define WASM_TYPE_VOID 0x00 + +static WasmType onyx_type_to_wasm_type(Type* type) { + if (type->kind == Type_Kind_Struct) { + if (type_linear_member_count(type) == 1) { + return onyx_type_to_wasm_type(type->Struct.linear_members[0].type); + } + + return WASM_TYPE_VOID; + } + + if (type->kind == Type_Kind_Slice) { + return WASM_TYPE_VOID; + } + + if (type->kind == Type_Kind_Compound) { + return WASM_TYPE_VOID; + } + + if (type->kind == Type_Kind_Enum) { + return onyx_type_to_wasm_type(type->Enum.backing); + } + + if (type->kind == Type_Kind_Distinct) { + return onyx_type_to_wasm_type(type->Distinct.base_type); + } + + if (type->kind == Type_Kind_Pointer) { + return WASM_TYPE_PTR; + } + + if (type->kind == Type_Kind_Array) { + return WASM_TYPE_PTR; + } + + if (type->kind == Type_Kind_Function) { + return WASM_TYPE_FUNC; + } + + if (type->kind == Type_Kind_Basic) { + TypeBasic* basic = &type->Basic; + if (basic->flags & Basic_Flag_Boolean) return WASM_TYPE_INT32; + if (basic->flags & Basic_Flag_Integer) { + if (basic->size <= 4) return WASM_TYPE_INT32; + if (basic->size == 8) return WASM_TYPE_INT64; + } + if (basic->flags & Basic_Flag_Pointer) return WASM_TYPE_INT32; + if (basic->flags & Basic_Flag_Float) { + if (basic->size <= 4) return WASM_TYPE_FLOAT32; + if (basic->size == 8) return WASM_TYPE_FLOAT64; + } + if (basic->flags & Basic_Flag_SIMD) return WASM_TYPE_VAR128; + if (basic->flags & Basic_Flag_Type_Index) return WASM_TYPE_INT32; + if (basic->size == 0) return WASM_TYPE_VOID; + } + + return WASM_TYPE_VOID; +} + +static i32 generate_type_idx(OnyxWasmModule* mod, Type* ft); +static i32 get_element_idx(OnyxWasmModule* mod, AstFunction* func); + +#define LOCAL_I32 0x000000000 +#define LOCAL_I64 0x100000000 +#define LOCAL_F32 0x300000000 +#define LOCAL_F64 0x700000000 +#define LOCAL_V128 0xf00000000 + +static b32 local_is_wasm_local(AstTyped* local) { + if (local->kind == Ast_Kind_Local && local->flags & Ast_Flag_Address_Taken) return 0; + if (local->type->kind == Type_Kind_Basic) return 1; + if (local->type->kind == Type_Kind_Enum && local->type->Enum.backing->kind == Type_Kind_Basic) return 1; + if (local->type->kind == Type_Kind_Distinct && local->type->Distinct.base_type->kind == Type_Kind_Basic) return 1; + if (local->type->kind == Type_Kind_Pointer) return 1; + return 0; +} + +static u64 local_raw_allocate(LocalAllocator* la, WasmType wt) { + i32 idx = 0; + if (wt == WASM_TYPE_INT32) idx = 0; + if (wt == WASM_TYPE_INT64) idx = 1; + if (wt == WASM_TYPE_FLOAT32) idx = 2; + if (wt == WASM_TYPE_FLOAT64) idx = 3; + if (wt == WASM_TYPE_VAR128) idx = 4; + + u64 flag_bits = LOCAL_IS_WASM; + if (wt == WASM_TYPE_INT32) flag_bits |= LOCAL_I32; + if (wt == WASM_TYPE_INT64) flag_bits |= LOCAL_I64; + if (wt == WASM_TYPE_FLOAT32) flag_bits |= LOCAL_F32; + if (wt == WASM_TYPE_FLOAT64) flag_bits |= LOCAL_F64; + if (wt == WASM_TYPE_VAR128) flag_bits |= LOCAL_V128; + + if (la->freed[idx] > 0) { + la->freed[idx]--; + return flag_bits | ((u64) (la->allocated[idx] - la->freed[idx] - 1 + la->param_count)); + + } else { + la->allocated[idx]++; + return flag_bits | ((u64) (la->allocated[idx] - 1 + la->param_count)); + } +} + +static void local_raw_free(LocalAllocator* la, WasmType wt) { + i32 idx = 0; + + if (wt == WASM_TYPE_INT32) idx = 0; + if (wt == WASM_TYPE_INT64) idx = 1; + if (wt == WASM_TYPE_FLOAT32) idx = 2; + if (wt == WASM_TYPE_FLOAT64) idx = 3; + if (wt == WASM_TYPE_VAR128) idx = 4; + + assert(la->allocated[idx] > 0 && la->freed[idx] < la->allocated[idx]); + + la->freed[idx]++; +} + +static u64 local_allocate(LocalAllocator* la, AstTyped* local) { + if (local_is_wasm_local(local)) { + WasmType wt = onyx_type_to_wasm_type(local->type); + return local_raw_allocate(la, wt); + + } else { + u32 size = type_size_of(local->type); + u32 alignment = type_alignment_of(local->type); + + bh_align(la->curr_stack, alignment); + + if (la->max_stack < la->curr_stack) + la->max_stack = la->curr_stack; + + bh_align(size, alignment); + + if (la->max_stack - la->curr_stack >= (i32) size) { + la->curr_stack += size; + + } else { + la->max_stack += size - (la->max_stack - la->curr_stack); + la->curr_stack = la->max_stack; + } + + return la->curr_stack - size; + } +} + +static void local_free(LocalAllocator* la, AstTyped* local) { + if (local_is_wasm_local(local)) { + WasmType wt = onyx_type_to_wasm_type(local->type); + local_raw_free(la, wt); + + } else { + u32 size = type_size_of(local->type); + u32 alignment = type_alignment_of(local->type); + bh_align(size, alignment); + + la->curr_stack -= size; + } +} + +static u64 local_lookup_idx(LocalAllocator* la, u64 value) { + assert(value & LOCAL_IS_WASM); + + u32 idx = value & 0xFFFFFFFF; + if (value & 0x100000000) idx += la->allocated[0]; + if (value & 0x200000000) idx += la->allocated[1]; + if (value & 0x400000000) idx += la->allocated[2]; + if (value & 0x800000000) idx += la->allocated[3]; + + return (u64) idx; +} + + +static inline b32 should_emit_function(AstFunction* fd) { + // NOTE: Don't output intrinsic functions + if (fd->is_intrinsic) return 0; + + // NOTE: Don't output functions that are not used, only if + // they are also not exported. + if ((fd->flags & Ast_Flag_Function_Used) == 0) { + if (fd->is_exported || bh_arr_length(fd->tags) > 0) { + return 1; + } else { + return 0; + } + } + + return 1; +} + + +// +// Debug Info Generation +// + +#ifdef ENABLE_DEBUG_INFO + +static u32 debug_introduce_symbol(OnyxWasmModule *mod, OnyxToken *token, DebugSymLoc loc, u64 num, Type* type) { + + u32 id = mod->debug_context->next_sym_id++; + + DebugSymInfo sym_info; + sym_info.sym_id = id; + sym_info.location_type = loc; + sym_info.location_num = num; + sym_info.type = type->id; + + if (token) { + token_toggle_end(token); + sym_info.name = bh_strdup(context.ast_alloc, token->text); + token_toggle_end(token); + } else { + sym_info.name = NULL; + } + + bh_arr_push(mod->debug_context->sym_info, sym_info); + + if (loc == DSL_REGISTER) { + assert(mod->local_alloc); + DebugSymPatch patch; + patch.func_idx = mod->current_func_idx; + patch.sym_id = id; + patch.local_idx = num; + bh_arr_push(mod->debug_context->sym_patches, patch); + } + + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_SYM); + u32 leb_len=0; + u8 *bytes = uint_to_uleb128(id, &leb_len); + bh_buffer_append(&mod->debug_context->op_buffer, bytes, leb_len); + + mod->debug_context->last_op_was_rep = 0; + return id; +} + +static u32 debug_get_file_id(OnyxWasmModule *mod, const char *name) { + assert(mod && mod->debug_context); + + i32 index = shgeti(mod->debug_context->file_info, name); + if (index == -1) { + u32 id = mod->debug_context->next_file_id++; + DebugFileInfo file_info; + file_info.file_id = id; + + bh_arr_each(bh_file_contents, fc, context.loaded_files) { + if (!strcmp(fc->filename, name)) { + file_info.line_count = fc->line_count; + } + } + shput(mod->debug_context->file_info, name, file_info); + + return id; + } + + return mod->debug_context->file_info[index].value.file_id; +} + +static void debug_set_position(OnyxWasmModule *mod, OnyxToken *token) { + i32 file_id = debug_get_file_id(mod, token->pos.filename); + + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_SET); + mod->debug_context->last_op_was_rep = 0; + + u32 leb_len=0; + u8 *bytes = uint_to_uleb128(file_id, &leb_len); + bh_buffer_append(&mod->debug_context->op_buffer, bytes, leb_len); + + bytes = uint_to_uleb128(token->pos.line, &leb_len); + bh_buffer_append(&mod->debug_context->op_buffer, bytes, leb_len); + + mod->debug_context->last_token = token; +} + +// Called for every instruction being emitted +// This has to emit either: +// - INC +// - DEC +// - REP +// - SET, REP 0 +static void debug_emit_instruction(OnyxWasmModule *mod, OnyxToken *token) { + DebugContext *ctx = mod->debug_context; + assert(ctx && ctx->last_token); + + // Sanity check + if (ctx->last_op_was_rep) { + assert((ctx->op_buffer.data[ctx->op_buffer.length - 1] & DOT_REP) == DOT_REP); + } + i32 file_id, old_file_id; + + b32 repeat_previous = 0; + if (!token || !token->pos.filename) { + repeat_previous = 1; + + } else { + file_id = debug_get_file_id(mod, token->pos.filename); + old_file_id = debug_get_file_id(mod, ctx->last_token->pos.filename); + if (old_file_id == file_id && token->pos.line == ctx->last_token->pos.line) { + repeat_previous = 1; + } + } + + if (repeat_previous) { + // Output / increment REP instruction + if (ctx->last_op_was_rep) { + u8 rep_count = ctx->op_buffer.data[ctx->op_buffer.length - 1] & 0b00111111; + if (rep_count != 63) { + rep_count += 1; + ctx->op_buffer.data[ctx->op_buffer.length - 1] = DOT_REP | rep_count; + return; + } + } + + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_REP); + ctx->last_op_was_rep = 1; + return; + } + + // At this point, token is definitely non-null. + ctx->last_op_was_rep = 0; + + if (old_file_id == file_id) { + // We see if we can INC/DEC to get to the line number + if (ctx->last_token->pos.line < token->pos.line) { + u32 diff = token->pos.line - ctx->last_token->pos.line; + if (diff <= 64) { + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_INC | (diff - 1)); + goto done; + } + } + + if (ctx->last_token->pos.line > token->pos.line) { + u32 diff = ctx->last_token->pos.line - token->pos.line; + if (diff <= 64) { + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_DEC | (diff - 1)); + goto done; + } + } + } + + // Otherwise, we need to output a SET, followed by a REP 0, + // which is what set_position does. + debug_set_position(mod, token); + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_REP); + ctx->last_op_was_rep = 1; + + done: + ctx->last_token = token; +} + +static void debug_begin_function(OnyxWasmModule *mod, u32 func_idx, OnyxToken *token, char *name) { + u32 file_id = debug_get_file_id(mod, token->pos.filename); + u32 line = token->pos.line; + + assert(mod->debug_context); + + DebugFuncContext func; + func.func_index = func_idx; + func.file_id = file_id; + func.line = line; + func.op_offset = mod->debug_context->op_buffer.length; + func.stack_ptr_idx = 0; + func.name_length = strlen(name); + func.name = name; + bh_arr_push(mod->debug_context->funcs, func); + + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_PUSHF); + debug_set_position(mod, token); +} + +static void debug_end_function(OnyxWasmModule *mod) { + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_POPF); + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_END); + mod->debug_context->last_op_was_rep = 0; + mod->debug_context->last_token = NULL; +} + +static void debug_enter_symbol_frame(OnyxWasmModule *mod) { + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_PUSHF); + mod->debug_context->last_op_was_rep = 0; +} + +static void debug_leave_symbol_frame(OnyxWasmModule *mod) { + bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_POPF); + mod->debug_context->last_op_was_rep = 0; +} + +#else + +#define debug_introduce_symbol(mod, name, loc, num, type) (void)0 +#define debug_get_file_id(mod, name) (void)0 +#define debug_set_position(mod, token) (void)0 +#define debug_emit_instruction(mod, token) (void)0 +#define debug_begin_function(mod, idx, token, name) (void)0 +#define debug_end_function(mod) (void)0 +#define debug_enter_symbol_frame(mod) (void)0 +#define debug_leave_symbol_frame(mod) (void)0 + +#endif + + +typedef enum StructuredBlockType StructuredBlockType; +enum StructuredBlockType { + SBT_Basic_Block, // Cannot be targeted using jump + SBT_Breakable_Block, // Targeted using break + SBT_Continue_Block, // Targeted using continue + SBT_Fallthrough_Block, // Targeted using fallthrough + SBT_Return_Block, // Targeted using return, (used for expression macros) + + SBT_Basic_If, // Cannot be targeted using jump + SBT_Breakable_If, // Targeted using break + + SBT_Basic_Loop, // Cannot be targeted using jump + SBT_Continue_Loop, // Targeted using continue + + SBT_Count, +}; + +#ifdef ENABLE_DEBUG_INFO + #define WI(token, instr) (debug_emit_instruction(mod, token), bh_arr_push(code, ((WasmInstruction){ instr, 0x00 }))) + #define WID(token, instr, data) (debug_emit_instruction(mod, token), bh_arr_push(code, ((WasmInstruction){ instr, data }))) + #define WIL(token, instr, data) (debug_emit_instruction(mod, token), bh_arr_push(code, ((WasmInstruction){ instr, { .l = data } }))) + #define WIP(token, instr, data) (debug_emit_instruction(mod, token), bh_arr_push(code, ((WasmInstruction){ instr, { .p = data } }))) + #define WIR(token, full_instr) (debug_emit_instruction(mod, token), bh_arr_push(code, full_instr)) +#else + #define WI(token, instr) (bh_arr_push(code, ((WasmInstruction){ instr, 0x00 }))) + #define WID(token, instr, data) (bh_arr_push(code, ((WasmInstruction){ instr, data }))) + #define WIL(token, instr, data) (bh_arr_push(code, ((WasmInstruction){ instr, { .l = data } }))) + #define WIP(token, instr, data) (bh_arr_push(code, ((WasmInstruction){ instr, { .p = data } }))) + #define WIR(token, full_instr) (bh_arr_push(code, full_instr)) +#endif + +#define EMIT_FUNC(kind, ...) static void emit_ ## kind (OnyxWasmModule* mod, bh_arr(WasmInstruction)* pcode, __VA_ARGS__) +#define EMIT_FUNC_NO_ARGS(kind) static void emit_ ## kind (OnyxWasmModule* mod, bh_arr(WasmInstruction)* pcode) +#define STACK_SWAP(token, type1, type2) { \ + u64 t0 = local_raw_allocate(mod->local_alloc, type1); \ + u64 t1 = local_raw_allocate(mod->local_alloc, type2); \ + \ + WIL(token, WI_LOCAL_SET, t0); \ + WIL(token, WI_LOCAL_SET, t1); \ + WIL(token, WI_LOCAL_GET, t0); \ + WIL(token, WI_LOCAL_GET, t1); \ + \ + local_raw_free(mod->local_alloc, type1); \ + local_raw_free(mod->local_alloc, type2); \ + } +#define SUBMIT_PATCH(patch_arr, offset) bh_arr_push((patch_arr), ((PatchInfo) { bh_arr_length(code) - offset })) +#define NEXT_DATA_ID(mod) ((u32) bh_arr_length((mod)->data) + 1) + +EMIT_FUNC(function_body, AstFunction* fd); +EMIT_FUNC(block, AstBlock* block, b32 generate_block_headers); +EMIT_FUNC(statement, AstNode* stmt); +EMIT_FUNC(local_allocation, AstTyped* stmt); +EMIT_FUNC_NO_ARGS(free_local_allocations); +EMIT_FUNC(data_relocation, u32 data_id); +EMIT_FUNC(assignment, AstBinaryOp* assign); +EMIT_FUNC(assignment_of_array, AstTyped* left, AstTyped* right); +EMIT_FUNC(compound_assignment, AstBinaryOp* assign); +EMIT_FUNC(store_instruction, Type* type, u32 offset); +EMIT_FUNC(load_instruction, Type* type, u32 offset); +EMIT_FUNC(if, AstIfWhile* if_node); +EMIT_FUNC(while, AstIfWhile* while_node); +EMIT_FUNC(for, AstFor* for_node); +EMIT_FUNC(switch, AstSwitch* switch_node); +EMIT_FUNC(defer, AstDefer* defer); +EMIT_FUNC(defer_code, WasmInstruction* deferred_code, u32 code_count); +EMIT_FUNC(deferred_stmt, DeferredStmt deferred_stmt); +EMIT_FUNC_NO_ARGS(deferred_stmts); +EMIT_FUNC(remove_directive, AstDirectiveRemove* remove); +EMIT_FUNC(binop, AstBinaryOp* binop); +EMIT_FUNC(unaryop, AstUnaryOp* unop); +EMIT_FUNC(call, AstCall* call); +EMIT_FUNC(intrinsic_call, AstCall* call); +EMIT_FUNC(subscript_location, AstSubscript* sub, u64* offset_return); +EMIT_FUNC(field_access_location, AstFieldAccess* field, u64* offset_return); +EMIT_FUNC(local_location, AstLocal* local, u64* offset_return); +EMIT_FUNC(memory_reservation_location, AstMemRes* memres); +EMIT_FUNC(location_return_offset, AstTyped* expr, u64* offset_return); +EMIT_FUNC(location, AstTyped* expr); +EMIT_FUNC(compound_load, Type* type, u64 offset); +EMIT_FUNC(struct_lval, AstTyped* lval); +EMIT_FUNC(struct_literal, AstStructLiteral* sl); +EMIT_FUNC(compound_store, Type* type, u64 offset, b32 location_first); +EMIT_FUNC(array_store, Type* type, u32 offset); +EMIT_FUNC(array_literal, AstArrayLiteral* al); +EMIT_FUNC(range_literal, AstRangeLiteral* range); +EMIT_FUNC(if_expression, AstIfExpression* if_expr); +EMIT_FUNC(do_block, AstDoBlock* doblock); +EMIT_FUNC(expression, AstTyped* expr); +EMIT_FUNC(cast, AstUnaryOp* cast); +EMIT_FUNC(return, AstReturn* ret); +EMIT_FUNC(stack_enter, u64 stacksize); +EMIT_FUNC(zero_value, WasmType wt); +EMIT_FUNC(zero_value_for_type, Type* type, OnyxToken* where); + +EMIT_FUNC(enter_structured_block, StructuredBlockType sbt, OnyxToken* block_token); +EMIT_FUNC_NO_ARGS(leave_structured_block); + +static u32 emit_data_entry(OnyxWasmModule *mod, WasmDatum *datum); + +static void emit_constexpr(ConstExprContext *ctx, AstTyped *node, u32 offset); +static b32 emit_constexpr_(ConstExprContext *ctx, AstTyped *node, u32 offset); + +#include "wasm_intrinsics.h" +#include "wasm_type_table.h" + +EMIT_FUNC(function_body, AstFunction* fd) { + if (fd->body == NULL) return; + + emit_block(mod, pcode, fd->body, 0); +} + +EMIT_FUNC(block, AstBlock* block, b32 generate_block_headers) { + bh_arr(WasmInstruction) code = *pcode; + + generate_block_headers = generate_block_headers && (block->rules & Block_Rule_Emit_Instructions); + + if (generate_block_headers) { + emit_enter_structured_block(mod, &code, (block->rules & Block_Rule_Override_Return) + ? SBT_Return_Block + : SBT_Breakable_Block, + block->token); + debug_enter_symbol_frame(mod); + } + + forll (AstNode, stmt, block->body, next) { + emit_statement(mod, &code, stmt); + } + + // HACK: I'm not convinced this is the best way to handle this case. Essentially, if + // a deferred statement uses a local that is freed from the a macro block, then the + // local won't be valid anymore and could have garbage data. This ensures that the + // freeing of the local variables and flushing of the deferred statements happen together, + // so a deferred statement can never try to use a local that has been freed. + // - brendanfh 2021/08/29 + if (block->rules & Block_Rule_Clear_Defer) { + emit_deferred_stmts(mod, &code); + + // if (block->rules & Block_Rule_New_Scope) + emit_free_local_allocations(mod, &code); + } + + if (generate_block_headers) { + emit_leave_structured_block(mod, &code); + debug_leave_symbol_frame(mod); + } + + *pcode = code; +} + +EMIT_FUNC(enter_structured_block, StructuredBlockType sbt, OnyxToken* token) { + bh_arr(WasmInstruction) code = *pcode; + + static const StructuredBlockType jump_numbers[SBT_Count] = { + /* SBT_Basic_Block */ 0, + /* SBT_Breakable_Block */ 1, + /* SBT_Continue_Block */ 2, + /* SBT_Fallthrough_Block */ 3, + /* SBT_Return_Block */ 4, + + /* SBT_Basic_If */ 0, + /* SBT_Breakable_If */ 1, + + /* SBT_Basic_Loop */ 0, + /* SBT_Continue_Loop */ 2, + }; + + static const WasmInstructionType block_instrs[SBT_Count] = { + /* SBT_Basic_Block */ WI_BLOCK_START, + /* SBT_Breakable_Block */ WI_BLOCK_START, + /* SBT_Continue_Block */ WI_BLOCK_START, + /* SBT_Fallthrough_Block */ WI_BLOCK_START, + /* SBT_Return_Block */ WI_BLOCK_START, + + /* SBT_Basic_If */ WI_IF_START, + /* SBT_Breakable_If */ WI_IF_START, + + /* SBT_Basic_Loop */ WI_LOOP_START, + /* SBT_Continue_Loop */ WI_LOOP_START, + }; + + + WID(token, block_instrs[sbt], 0x40); + bh_arr_push(mod->structured_jump_target, jump_numbers[sbt]); + + *pcode = code; +} + +EMIT_FUNC_NO_ARGS(leave_structured_block) { + bh_arr(WasmInstruction) code = *pcode; + + WI(NULL, WI_BLOCK_END); + bh_arr_pop(mod->structured_jump_target); + + *pcode = code; +} + +i64 get_structured_jump_label(OnyxWasmModule* mod, JumpType jump_type, u32 jump_count) { + // :CLEANUP These numbers should become constants because they are shared with + // enter_structured_block's definitions. + static const u8 wants[Jump_Type_Count] = { 1, 2, 3, 4 }; + + i64 labelidx = 0; + u8 wanted = wants[jump_type]; + b32 success = 0; + + i32 len = bh_arr_length(mod->structured_jump_target) - 1; + for (u8* t = &bh_arr_last(mod->structured_jump_target); len >= 0; len--, t--) { + if (*t == wanted) jump_count--; + if (jump_count == 0) { + success = 1; + break; + } + + labelidx++; + } + + return (success == 0) ? -1 : labelidx; +} + +EMIT_FUNC(structured_jump, AstJump* jump) { + bh_arr(WasmInstruction) code = *pcode; + + i64 labelidx = get_structured_jump_label(mod, jump->jump, jump->count); + + if (bh_arr_length(mod->deferred_stmts) > 0) { + i32 i = bh_arr_length(mod->deferred_stmts) - 1; + i32 d = bh_arr_length(mod->structured_jump_target) - (labelidx + 1); + + while (i >= 0 && (i32) mod->deferred_stmts[i].depth > d) { + emit_deferred_stmt(mod, &code, mod->deferred_stmts[i]); + i--; + } + } + + if (labelidx >= 0) { + // NOTE: If the previous instruction was a non conditional jump, + // don't emit another jump since it will never be reached. + if (bh_arr_last(code).type != WI_JUMP) + WID(jump->token, WI_JUMP, labelidx); + } else { + onyx_report_error(jump->token->pos, Error_Critical, "Invalid structured jump."); + } + + *pcode = code; +} + +EMIT_FUNC(statement, AstNode* stmt) { + bh_arr(WasmInstruction) code = *pcode; + +#ifdef ENABLE_DEBUG_INFO + debug_set_position(mod, stmt->token); +#endif + + switch (stmt->kind) { + case Ast_Kind_Return: emit_return(mod, &code, (AstReturn *) stmt); break; + case Ast_Kind_If: emit_if(mod, &code, (AstIfWhile *) stmt); break; + case Ast_Kind_Static_If: emit_if(mod, &code, (AstIfWhile *) stmt); break; + case Ast_Kind_While: emit_while(mod, &code, (AstIfWhile *) stmt); break; + case Ast_Kind_For: emit_for(mod, &code, (AstFor *) stmt); break; + case Ast_Kind_Switch: emit_switch(mod, &code, (AstSwitch *) stmt); break; + case Ast_Kind_Jump: emit_structured_jump(mod, &code, (AstJump *) stmt); break; + case Ast_Kind_Block: emit_block(mod, &code, (AstBlock *) stmt, 1); break; + case Ast_Kind_Defer: emit_defer(mod, &code, (AstDefer *) stmt); break; + case Ast_Kind_Local: emit_local_allocation(mod, &code, (AstTyped *) stmt); break; + + case Ast_Kind_Directive_Remove: emit_remove_directive(mod, &code, (AstDirectiveRemove *) stmt); break; + case Ast_Kind_Directive_Insert: break; + + default: emit_expression(mod, &code, (AstTyped *) stmt); break; + } + + *pcode = code; +} + +EMIT_FUNC(local_allocation, AstTyped* stmt) { + u64 local_idx = local_allocate(mod->local_alloc, stmt); + bh_imap_put(&mod->local_map, (u64) stmt, local_idx); + + if (local_is_wasm_local(stmt)) { + debug_introduce_symbol(mod, stmt->token, DSL_REGISTER, local_idx, stmt->type); + } else { + debug_introduce_symbol(mod, stmt->token, DSL_STACK, local_idx, stmt->type); + } + + if (stmt->kind == Ast_Kind_Local && !(stmt->flags & Ast_Flag_Decl_Followed_By_Init)) { + bh_arr(WasmInstruction) code = *pcode; + if (local_is_wasm_local(stmt)) { + emit_zero_value(mod, &code, onyx_type_to_wasm_type(stmt->type)); + WIL(stmt->token, WI_LOCAL_SET, local_idx); + + } else { + emit_location(mod, &code, stmt); + WID(stmt->token, WI_I32_CONST, 0); + WID(stmt->token, WI_I32_CONST, type_size_of(stmt->type)); + if (context.options->use_post_mvp_features) { + WID(stmt->token, WI_MEMORY_FILL, 0x00); + } else { + emit_intrinsic_memory_fill(mod, &code); + } + } + + *pcode = code; + } + + bh_arr_push(mod->local_allocations, ((AllocatedSpace) { + .depth = bh_arr_length(mod->structured_jump_target), + .expr = stmt, + })); +} + +EMIT_FUNC_NO_ARGS(free_local_allocations) { + if (bh_arr_length(mod->local_allocations) == 0) return; + + u64 depth = bh_arr_length(mod->structured_jump_target); + while (bh_arr_length(mod->local_allocations) > 0 && bh_arr_last(mod->local_allocations).depth >= depth) { + // CHECK: Not sure this next line is okay to be here... + bh_imap_delete(&mod->local_map, (u64) bh_arr_last(mod->local_allocations).expr); + + local_free(mod->local_alloc, bh_arr_last(mod->local_allocations).expr); + bh_arr_pop(mod->local_allocations); + } +} + +EMIT_FUNC(data_relocation, u32 data_id) { + bh_arr(WasmInstruction) code = *pcode; + + u32 instr_idx = bh_arr_length(code); + WID(NULL, WI_PTR_CONST, 0); + assert(mod->current_func_idx >= 0); + + DatumPatchInfo patch; + patch.kind = Datum_Patch_Instruction; + patch.index = mod->current_func_idx; + patch.location = instr_idx; + patch.data_id = data_id; + patch.offset = 0; + bh_arr_push(mod->data_patches, patch); + + *pcode = code; +} + +EMIT_FUNC(assignment, AstBinaryOp* assign) { + bh_arr(WasmInstruction) code = *pcode; + + if (assign->right->type->kind == Type_Kind_Array) { + emit_assignment_of_array(mod, &code, assign->left, assign->right); + *pcode = code; + return; + } + + if (assign->right->type->kind == Type_Kind_Compound) { + emit_compound_assignment(mod, &code, assign); + *pcode = code; + return; + } + + AstTyped* lval = assign->left; + + if (lval->kind == Ast_Kind_Local || lval->kind == Ast_Kind_Param) { + if (bh_imap_get(&mod->local_map, (u64) lval) & LOCAL_IS_WASM) { + emit_expression(mod, &code, assign->right); + + u64 localidx = bh_imap_get(&mod->local_map, (u64) lval); + + if (lval->kind == Ast_Kind_Param && type_is_structlike_strict(lval->type)) { + u32 mem_count = type_structlike_mem_count(lval->type); + fori (i, 0, mem_count) WIL(assign->token, WI_LOCAL_SET, localidx + i); + + } else { + WIL(assign->token, WI_LOCAL_SET, localidx); + } + + *pcode = code; + return; + } + } + + if (lval->kind == Ast_Kind_Field_Access) { + AstFieldAccess* fa = (AstFieldAccess *) lval; + if (fa->expr->kind == Ast_Kind_Param && type_is_structlike_strict(fa->expr->type)) { + emit_expression(mod, &code, assign->right); + + u64 localidx = bh_imap_get(&mod->local_map, (u64) fa->expr); + WIL(assign->token, WI_LOCAL_SET, localidx + fa->idx); + + *pcode = code; + return; + } + } + + if (lval->kind == Ast_Kind_Global) { + emit_expression(mod, &code, assign->right); + + i32 globalidx = (i32) bh_imap_get(&mod->index_map, (u64) lval); + WID(assign->token, WI_GLOBAL_SET, globalidx); + + *pcode = code; + return; + } + + u64 offset = 0; + emit_location_return_offset(mod, &code, lval, &offset); + emit_expression(mod, &code, assign->right); + emit_store_instruction(mod, &code, lval->type, offset); + + *pcode = code; +} + +EMIT_FUNC(assignment_of_array, AstTyped* left, AstTyped* right) { + bh_arr(WasmInstruction) code = *pcode; + + Type* rtype = right->type; + assert(rtype->kind == Type_Kind_Array); + + if (right->kind == Ast_Kind_Array_Literal) { + Type* elem_type = rtype; + u32 elem_count = 1; + while (elem_type->kind == Type_Kind_Array) { + elem_count *= elem_type->Array.count; + elem_type = elem_type->Array.elem; + } + u32 elem_size = type_size_of(elem_type); + + u64 lptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + + emit_location(mod, &code, left); + WIL(left->token, WI_LOCAL_SET, lptr_local); + + AstArrayLiteral* al = (AstArrayLiteral *) right; + fori (i, 0, elem_count) { + WIL(left->token, WI_LOCAL_GET, lptr_local); + emit_expression(mod, &code, al->values[i]); + emit_store_instruction(mod, &code, elem_type, i * elem_size); + } + + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + + } else { + u64 offset = 0; + emit_location_return_offset(mod, &code, left, &offset); + emit_expression(mod, &code, right); + emit_array_store(mod, &code, rtype, offset); + } + + *pcode = code; + return; +} + +EMIT_FUNC(compound_assignment, AstBinaryOp* assign) { + bh_arr(WasmInstruction) code = *pcode; + + emit_expression(mod, &code, assign->right); + + AstCompound* compound_lval = (AstCompound *) assign->left; + bh_arr_rev_each(AstTyped *, plval, compound_lval->exprs) { + AstTyped *lval = *plval; + + if (type_is_structlike_strict(lval->type)) { + emit_struct_lval(mod, &code, lval); + continue; + } + + if (lval->kind == Ast_Kind_Local || lval->kind == Ast_Kind_Param) { + if (bh_imap_get(&mod->local_map, (u64) lval) & LOCAL_IS_WASM) { + u64 localidx = bh_imap_get(&mod->local_map, (u64) lval); + WIL(assign->token, WI_LOCAL_SET, localidx); + continue; + } + } + + WasmType wt = onyx_type_to_wasm_type(lval->type); + u64 expr_tmp = local_raw_allocate(mod->local_alloc, wt); + WIL(assign->token, WI_LOCAL_SET, expr_tmp); + u64 offset = 0; + emit_location_return_offset(mod, &code, lval, &offset); + WIL(assign->token, WI_LOCAL_GET, expr_tmp); + + local_raw_free(mod->local_alloc, wt); + emit_store_instruction(mod, &code, lval->type, offset); + } + + *pcode = code; + return; +} + +EMIT_FUNC(store_instruction, Type* type, u32 offset) { + bh_arr(WasmInstruction) code = *pcode; + + if (type_is_compound(type)) { + emit_compound_store(mod, pcode, type, offset, 0); + return; + } + + if (type->kind == Type_Kind_Struct) { + assert(bh_arr_length(type->Struct.linear_members) == 1); + type = type->Struct.linear_members[0].type; + } + + if (type->kind == Type_Kind_Array) { + emit_array_store(mod, pcode, type, offset); + return; + } + + if (type->kind == Type_Kind_Enum) type = type->Enum.backing; + if (type->kind == Type_Kind_Distinct) type = type->Distinct.base_type; + if (type->kind == Type_Kind_Function) type = &basic_types[Basic_Kind_U32]; + + u32 alignment = type_get_alignment_log2(type); + + i32 store_size = type_size_of(type); + i32 is_basic = type->kind == Type_Kind_Basic || type->kind == Type_Kind_Pointer; + + if (is_basic && (type->Basic.flags & Basic_Flag_Pointer)) { + WID(NULL, WI_I32_STORE, ((WasmInstructionData) { 2, offset })); + } else if (is_basic && ((type->Basic.flags & Basic_Flag_Integer) + || (type->Basic.flags & Basic_Flag_Boolean) + || (type->Basic.flags & Basic_Flag_Type_Index))) { + if (store_size == 1) WID(NULL, WI_I32_STORE_8, ((WasmInstructionData) { alignment, offset })); + else if (store_size == 2) WID(NULL, WI_I32_STORE_16, ((WasmInstructionData) { alignment, offset })); + else if (store_size == 4) WID(NULL, WI_I32_STORE, ((WasmInstructionData) { alignment, offset })); + else if (store_size == 8) WID(NULL, WI_I64_STORE, ((WasmInstructionData) { alignment, offset })); + } else if (is_basic && (type->Basic.flags & Basic_Flag_Float)) { + if (store_size == 4) WID(NULL, WI_F32_STORE, ((WasmInstructionData) { alignment, offset })); + else if (store_size == 8) WID(NULL, WI_F64_STORE, ((WasmInstructionData) { alignment, offset })); + } else if (is_basic && (type->Basic.flags & Basic_Flag_SIMD)) { + WID(NULL, WI_V128_STORE, ((WasmInstructionData) { alignment, offset })); + } else { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, + "Failed to generate store instruction for type '%s'.", + type_get_name(type)); + } + + *pcode = code; +} + +EMIT_FUNC(load_instruction, Type* type, u32 offset) { + bh_arr(WasmInstruction) code = *pcode; + + if (type_is_compound(type)) { + emit_compound_load(mod, pcode, type, offset); + return; + } + + if (type->kind == Type_Kind_Struct) { + assert(bh_arr_length(type->Struct.linear_members) == 1); + type = type->Struct.linear_members[0].type; + } + + if (type->kind == Type_Kind_Array) { + if (offset != 0) { + WID(NULL, WI_PTR_CONST, offset); + WI(NULL, WI_PTR_ADD); + } + + *pcode = code; + return; + } + + if (type->kind == Type_Kind_Enum) type = type->Enum.backing; + if (type->kind == Type_Kind_Distinct) type = type->Distinct.base_type; + if (type->kind == Type_Kind_Function) type = &basic_types[Basic_Kind_U32]; + + i32 load_size = type_size_of(type); + i32 is_basic = type->kind == Type_Kind_Basic || type->kind == Type_Kind_Pointer; + + WasmInstructionType instr = WI_NOP; + i32 alignment = type_get_alignment_log2(type); + + if (is_basic && (type->Basic.flags & Basic_Flag_Pointer)) { + instr = WI_I32_LOAD; + alignment = 2; + } + else if (is_basic && ((type->Basic.flags & Basic_Flag_Integer) + || (type->Basic.flags & Basic_Flag_Boolean) + || (type->Basic.flags & Basic_Flag_Type_Index))) { + if (load_size == 1) instr = WI_I32_LOAD_8_S; + else if (load_size == 2) instr = WI_I32_LOAD_16_S; + else if (load_size == 4) instr = WI_I32_LOAD; + else if (load_size == 8) instr = WI_I64_LOAD; + + if (load_size < 4 && (type->Basic.flags & Basic_Flag_Unsigned)) instr += 1; + } + else if (is_basic && (type->Basic.flags & Basic_Flag_Float)) { + if (load_size == 4) instr = WI_F32_LOAD; + else if (load_size == 8) instr = WI_F64_LOAD; + } + else if (is_basic && (type->Basic.flags & Basic_Flag_SIMD)) { + instr = WI_V128_LOAD; + } + + WID(NULL, instr, ((WasmInstructionData) { alignment, offset })); + + if (instr == WI_NOP) { + onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, + "Failed to generate load instruction for type '%s'.", + type_get_name(type)); + } + + *pcode = code; +} + +EMIT_FUNC(if, AstIfWhile* if_node) { + bh_arr(WasmInstruction) code = *pcode; + + if (if_node->initialization != NULL) { + // bh_imap_put(&mod->local_map, (u64) if_node->local, local_allocate(mod->local_alloc, (AstTyped *) if_node->local)); + + forll (AstNode, stmt, if_node->initialization, next) { + emit_statement(mod, &code, stmt); + } + } + + if (if_node->kind == Ast_Kind_Static_If) { + if (static_if_resolution(if_node)) { + if (if_node->true_stmt) emit_block(mod, &code, if_node->true_stmt, 1); + } else { + if (if_node->false_stmt) emit_block(mod, &code, if_node->false_stmt, 1); + } + + *pcode = code; + return; + } + + emit_expression(mod, &code, if_node->cond); + + emit_enter_structured_block(mod, &code, SBT_Basic_If, if_node->token); + if (if_node->true_stmt) emit_block(mod, &code, if_node->true_stmt, 0); + + if (if_node->false_stmt) { + WI(if_node->false_stmt->token, WI_ELSE); + + if (if_node->false_stmt->kind == Ast_Kind_If) { + emit_if(mod, &code, (AstIfWhile *) if_node->false_stmt); + } else { + emit_block(mod, &code, if_node->false_stmt, 0); + } + } + + emit_leave_structured_block(mod, &code); + + *pcode = code; +} + +EMIT_FUNC(while, AstIfWhile* while_node) { + bh_arr(WasmInstruction) code = *pcode; + + if (while_node->initialization != NULL) { + forll (AstNode, stmt, while_node->initialization, next) { + emit_statement(mod, &code, stmt); + } + } + + if (while_node->false_stmt == NULL) { + emit_enter_structured_block(mod, &code, SBT_Breakable_Block, while_node->token); + emit_enter_structured_block(mod, &code, SBT_Continue_Loop, while_node->token); + + if (!while_node->bottom_test) { + emit_expression(mod, &code, while_node->cond); + WI(NULL, WI_I32_EQZ); + WID(NULL, WI_COND_JUMP, 0x01); + } + + emit_block(mod, &code, while_node->true_stmt, 0); + + if (while_node->bottom_test) { + emit_expression(mod, &code, while_node->cond); + WID(while_node->cond->token, WI_COND_JUMP, 0x00); + + } else { + if (bh_arr_last(code).type != WI_JUMP) + WID(while_node->cond->token, WI_JUMP, 0x00); + } + + emit_leave_structured_block(mod, &code); + emit_leave_structured_block(mod, &code); + + } else { + emit_expression(mod, &code, while_node->cond); + + emit_enter_structured_block(mod, &code, SBT_Breakable_If, while_node->token); + emit_enter_structured_block(mod, &code, SBT_Continue_Loop, while_node->token); + + emit_block(mod, &code, while_node->true_stmt, 0); + + emit_expression(mod, &code, while_node->cond); + WID(while_node->cond->token, WI_COND_JUMP, 0x00); + + emit_leave_structured_block(mod, &code); + WI(while_node->false_stmt->token, WI_ELSE); + + emit_block(mod, &code, while_node->false_stmt, 0); + + emit_leave_structured_block(mod, &code); + } + + *pcode = code; +} + +EMIT_FUNC(for_range, AstFor* for_node, u64 iter_local) { + bh_arr(WasmInstruction) code = *pcode; + + // NOTE: There are some aspects of the code below that rely on the + // low, high, and step members to be i32's. This restriction can be lifted, + // but it is important to change the code here. + // -brendanfh 2020/09/04 + + // NOTE: This might not be a range literal + AstStructLiteral *range = (AstStructLiteral *) for_node->iter; + u64 offset = 0; + + StructMember low_mem, high_mem, step_mem; + type_lookup_member(builtin_range_type_type, "low", &low_mem); + type_lookup_member(builtin_range_type_type, "high", &high_mem); + type_lookup_member(builtin_range_type_type, "step", &step_mem); + u64 low_local = local_raw_allocate(mod->local_alloc, onyx_type_to_wasm_type(low_mem.type)); + u64 high_local = local_raw_allocate(mod->local_alloc, onyx_type_to_wasm_type(high_mem.type)); + u64 step_local = local_raw_allocate(mod->local_alloc, onyx_type_to_wasm_type(step_mem.type)); + + WIL(for_node->token, WI_LOCAL_SET, step_local); + WIL(for_node->token, WI_LOCAL_SET, high_local); + WIL(for_node->token, WI_LOCAL_TEE, low_local); + WIL(for_node->token, WI_LOCAL_SET, iter_local); + + emit_enter_structured_block(mod, &code, SBT_Breakable_Block, for_node->token); + emit_enter_structured_block(mod, &code, SBT_Basic_Loop, for_node->token); + emit_enter_structured_block(mod, &code, SBT_Continue_Block, for_node->token); + + if (range->kind == Ast_Kind_Struct_Literal && (range->args.values[2]->flags & Ast_Flag_Comptime) != 0) { + AstNumLit *step_value = (AstNumLit *) range->args.values[2]; + assert(step_value->kind == Ast_Kind_NumLit); + + if (step_value->value.l >= 0) { + WIL(for_node->token, WI_LOCAL_GET, iter_local); + WIL(for_node->token, WI_LOCAL_GET, high_local); + WI(for_node->token, WI_I32_GE_S); + WID(for_node->token, WI_COND_JUMP, 0x02); + } else { + WIL(for_node->token, WI_LOCAL_GET, iter_local); + WIL(for_node->token, WI_LOCAL_GET, high_local); + WI(for_node->token, WI_I32_LT_S); + WID(for_node->token, WI_COND_JUMP, 0x02); + } + + } else { + WIL(for_node->token, WI_LOCAL_GET, step_local); + WID(for_node->token, WI_I32_CONST, 0); + WI(for_node->token, WI_I32_GE_S); + WID(for_node->token, WI_IF_START, 0x40); + WIL(for_node->token, WI_LOCAL_GET, iter_local); + WIL(for_node->token, WI_LOCAL_GET, high_local); + WI(for_node->token, WI_I32_GE_S); + WID(for_node->token, WI_COND_JUMP, 0x03); + WI(for_node->token, WI_ELSE); + WIL(for_node->token, WI_LOCAL_GET, iter_local); + WIL(for_node->token, WI_LOCAL_GET, high_local); + WI(for_node->token, WI_I32_LT_S); + WID(for_node->token, WI_COND_JUMP, 0x03); + WI(for_node->token, WI_IF_END); + } + + + emit_block(mod, &code, for_node->stmt, 0); + + emit_leave_structured_block(mod, &code); + + WIL(for_node->token, WI_LOCAL_GET, iter_local); + WIL(for_node->token, WI_LOCAL_GET, step_local); + WI(for_node->token, WI_I32_ADD); + WIL(for_node->token, WI_LOCAL_SET, iter_local); + + if (bh_arr_last(code).type != WI_JUMP) + WID(for_node->token, WI_JUMP, 0x00); + + emit_leave_structured_block(mod, &code); + emit_leave_structured_block(mod, &code); + + local_raw_free(mod->local_alloc, onyx_type_to_wasm_type(low_mem.type)); + local_raw_free(mod->local_alloc, onyx_type_to_wasm_type(high_mem.type)); + local_raw_free(mod->local_alloc, onyx_type_to_wasm_type(step_mem.type)); + + *pcode = code; +} + +EMIT_FUNC(for_array, AstFor* for_node, u64 iter_local) { + bh_arr(WasmInstruction) code = *pcode; + + u64 end_ptr_local, ptr_local; + end_ptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + + if (for_node->by_pointer) { + ptr_local = iter_local; + } else { + ptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + } + + AstLocal* var = for_node->var; + b32 it_is_local = (b32) ((iter_local & LOCAL_IS_WASM) != 0); + u64 offset = 0; + + u64 elem_size; + if (for_node->by_pointer) elem_size = type_size_of(var->type->Pointer.elem); + else elem_size = type_size_of(var->type); + + WIL(for_node->token, WI_LOCAL_TEE, ptr_local); + WIL(for_node->token, WI_PTR_CONST, for_node->iter->type->Array.count * elem_size); + WI(for_node->token, WI_PTR_ADD); + WIL(for_node->token, WI_LOCAL_SET, end_ptr_local); + + emit_enter_structured_block(mod, &code, SBT_Breakable_Block, for_node->token); + emit_enter_structured_block(mod, &code, SBT_Basic_Loop, for_node->token); + emit_enter_structured_block(mod, &code, SBT_Continue_Block, for_node->token); + + WIL(for_node->token, WI_LOCAL_GET, ptr_local); + WIL(for_node->token, WI_LOCAL_GET, end_ptr_local); + WI(for_node->token, WI_PTR_GE); + WID(for_node->token, WI_COND_JUMP, 0x02); + + if (!for_node->by_pointer) { + if (!it_is_local) emit_local_location(mod, &code, var, &offset); + + WIL(for_node->token, WI_LOCAL_GET, ptr_local); + emit_load_instruction(mod, &code, var->type, 0); + + if (!it_is_local) emit_store_instruction(mod, &code, var->type, offset); + else WIL(for_node->token, WI_LOCAL_SET, iter_local); + } + + emit_block(mod, &code, for_node->stmt, 0); + + emit_leave_structured_block(mod, &code); + + WIL(for_node->token, WI_LOCAL_GET, ptr_local); + WIL(for_node->token, WI_PTR_CONST, elem_size); + WI(for_node->token, WI_PTR_ADD); + WIL(for_node->token, WI_LOCAL_SET, ptr_local); + + if (bh_arr_last(code).type != WI_JUMP) + WID(for_node->token, WI_JUMP, 0x00); + + emit_leave_structured_block(mod, &code); + emit_leave_structured_block(mod, &code); + + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + if (!for_node->by_pointer) local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + + *pcode = code; +} + +EMIT_FUNC(for_slice, AstFor* for_node, u64 iter_local) { + bh_arr(WasmInstruction) code = *pcode; + + u64 end_ptr_local, ptr_local; + end_ptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + + if (for_node->by_pointer) { + ptr_local = iter_local; + } else { + ptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + } + + AstLocal* var = for_node->var; + b32 it_is_local = (b32) ((iter_local & LOCAL_IS_WASM) != 0); + u64 offset = 0; + + u64 elem_size; + if (for_node->by_pointer) elem_size = type_size_of(var->type->Pointer.elem); + else elem_size = type_size_of(var->type); + + WIL(for_node->token, WI_LOCAL_SET, end_ptr_local); + WIL(for_node->token, WI_LOCAL_TEE, ptr_local); + WIL(for_node->token, WI_LOCAL_GET, end_ptr_local); + if (elem_size != 1) { + WID(for_node->token, WI_PTR_CONST, elem_size); + WI(for_node->token, WI_PTR_MUL); + } + WI(for_node->token, WI_PTR_ADD); + WIL(for_node->token, WI_LOCAL_SET, end_ptr_local); + + emit_enter_structured_block(mod, &code, SBT_Breakable_Block, for_node->token); + emit_enter_structured_block(mod, &code, SBT_Basic_Loop, for_node->token); + emit_enter_structured_block(mod, &code, SBT_Continue_Block, for_node->token); + + WIL(for_node->token, WI_LOCAL_GET, ptr_local); + WIL(for_node->token, WI_LOCAL_GET, end_ptr_local); + WI(for_node->token, WI_PTR_GE); + WID(for_node->token, WI_COND_JUMP, 0x02); + + if (!for_node->by_pointer) { + if (!it_is_local) emit_local_location(mod, &code, var, &offset); + + WIL(for_node->token, WI_LOCAL_GET, ptr_local); + emit_load_instruction(mod, &code, var->type, 0); + + if (!it_is_local) emit_store_instruction(mod, &code, var->type, offset); + else WIL(for_node->token, WI_LOCAL_SET, iter_local); + } + + emit_block(mod, &code, for_node->stmt, 0); + + emit_leave_structured_block(mod, &code); + + WIL(for_node->token, WI_LOCAL_GET, ptr_local); + WIL(for_node->token, WI_PTR_CONST, elem_size); + WI(for_node->token, WI_PTR_ADD); + WIL(for_node->token, WI_LOCAL_SET, ptr_local); + + if (bh_arr_last(code).type != WI_JUMP) + WID(for_node->token, WI_JUMP, 0x00); + + emit_leave_structured_block(mod, &code); + emit_leave_structured_block(mod, &code); + + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + if (!for_node->by_pointer) local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + + *pcode = code; +} + +EMIT_FUNC(for_iterator, AstFor* for_node, u64 iter_local) { + bh_arr(WasmInstruction) code = *pcode; + + // Allocate temporaries for iterator contents + u64 iterator_data_ptr = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + u64 iterator_next_func = local_raw_allocate(mod->local_alloc, WASM_TYPE_FUNC); + u64 iterator_close_func = local_raw_allocate(mod->local_alloc, WASM_TYPE_FUNC); + u64 iterator_remove_func = local_raw_allocate(mod->local_alloc, WASM_TYPE_FUNC); + u64 iterator_done_bool = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32); + WIL(for_node->token, WI_LOCAL_SET, iterator_remove_func); + WIL(for_node->token, WI_LOCAL_SET, iterator_close_func); + WIL(for_node->token, WI_LOCAL_SET, iterator_next_func); + WIL(for_node->token, WI_LOCAL_SET, iterator_data_ptr); + + + { + // + // This pushes an entry onto the stack of for loops that have + // are iterator that can have a '#remove' directive in them. + ForRemoveInfo remove_info; + remove_info.iterator_data_ptr = iterator_data_ptr; + remove_info.iterator_remove_func = iterator_remove_func; + + TypeWithOffset remove_func_type; + type_linear_member_lookup(for_node->iter->type, 3, &remove_func_type); + remove_info.remove_func_type_idx = generate_type_idx(mod, remove_func_type.type); + + bh_arr_push(mod->for_remove_info, remove_info); + } + + AstLocal* var = for_node->var; + b32 it_is_local = (b32) ((iter_local & LOCAL_IS_WASM) != 0); + u64 offset = 0; + + // Enter a deferred statement for the auto-close + emit_enter_structured_block(mod, &code, SBT_Basic_Block, for_node->token); + + if (!for_node->no_close) { + TypeWithOffset close_func_type; + type_linear_member_lookup(for_node->iter->type, 2, &close_func_type); + i32 close_type_idx = generate_type_idx(mod, close_func_type.type); + + WasmInstruction* close_instructions = bh_alloc_array(global_heap_allocator, WasmInstruction, 8); + close_instructions[0] = (WasmInstruction) { WI_LOCAL_GET, { .l = iterator_close_func } }; + close_instructions[1] = (WasmInstruction) { WI_I32_CONST, { .l = mod->null_proc_func_idx } }; + close_instructions[2] = (WasmInstruction) { WI_I32_NE, { .l = 0x00 } }; + close_instructions[3] = (WasmInstruction) { WI_IF_START, { .l = 0x40 } }; + close_instructions[4] = (WasmInstruction) { WI_LOCAL_GET, { .l = iterator_data_ptr } }; + close_instructions[5] = (WasmInstruction) { WI_LOCAL_GET, { .l = iterator_close_func } }; + close_instructions[6] = (WasmInstruction) { WI_CALL_INDIRECT, { .l = close_type_idx } }; + close_instructions[7] = (WasmInstruction) { WI_IF_END, { .l = 0x00 } }; + + emit_defer_code(mod, &code, close_instructions, 8); + } + + emit_enter_structured_block(mod, &code, SBT_Breakable_Block, for_node->token); + emit_enter_structured_block(mod, &code, SBT_Continue_Loop, for_node->token); + + if (!it_is_local) emit_local_location(mod, &code, var, &offset); + + { + WIL(for_node->token, WI_LOCAL_GET, iterator_data_ptr); + WIL(for_node->token, WI_LOCAL_GET, iterator_next_func); + + // CLEANUP: Calling a function is way too f-ing complicated. FACTOR IT!! + u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top); + + TypeWithOffset next_func_type; + type_linear_member_lookup(for_node->iter->type, 1, &next_func_type); + Type* return_type = next_func_type.type->Function.return_type; + + u32 return_size = type_size_of(return_type); + u32 return_align = type_alignment_of(return_type); + bh_align(return_size, return_align); + + u64 reserve_size = return_size; + bh_align(reserve_size, 16); + + WID(for_node->token, WI_GLOBAL_GET, stack_top_idx); + WID(for_node->token, WI_PTR_CONST, reserve_size); + WI(for_node->token, WI_PTR_ADD); + WID(for_node->token, WI_GLOBAL_SET, stack_top_idx); + + i32 type_idx = generate_type_idx(mod, next_func_type.type); + WID(for_node->token, WI_CALL_INDIRECT, ((WasmInstructionData) { type_idx, 0x00 })); + + WID(for_node->token, WI_GLOBAL_GET, stack_top_idx); + WID(for_node->token, WI_PTR_CONST, reserve_size); + WI(for_node->token, WI_PTR_SUB); + WID(for_node->token, WI_GLOBAL_SET, stack_top_idx); + + WID(for_node->token, WI_GLOBAL_GET, stack_top_idx); + emit_load_instruction(mod, &code, return_type, reserve_size - return_size); + } + + WIL(for_node->token, WI_LOCAL_SET, iterator_done_bool); + + if (!it_is_local) emit_store_instruction(mod, &code, var->type, offset); + else WIL(for_node->token, WI_LOCAL_SET, iter_local); + + WIL(for_node->token, WI_LOCAL_GET, iterator_done_bool); + WI(for_node->token, WI_I32_EQZ); + WID(for_node->token, WI_COND_JUMP, 0x01); + + emit_block(mod, &code, for_node->stmt, 0); + WID(for_node->token, WI_JUMP, 0x00); + + emit_leave_structured_block(mod, &code); + emit_leave_structured_block(mod, &code); + + emit_deferred_stmts(mod, &code); + emit_leave_structured_block(mod, &code); + + bh_arr_pop(mod->for_remove_info); + + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + local_raw_free(mod->local_alloc, WASM_TYPE_FUNC); + local_raw_free(mod->local_alloc, WASM_TYPE_FUNC); + local_raw_free(mod->local_alloc, WASM_TYPE_INT32); + *pcode = code; +} + +EMIT_FUNC(for, AstFor* for_node) { + bh_arr(WasmInstruction) code = *pcode; + + AstLocal* var = for_node->var; + u64 iter_local = local_allocate(mod->local_alloc, (AstTyped *) var); + bh_imap_put(&mod->local_map, (u64) var, iter_local); + + debug_enter_symbol_frame(mod); + debug_introduce_symbol(mod, var->token, + local_is_wasm_local((AstTyped *) var) ? DSL_REGISTER : DSL_STACK, + iter_local, var->type); + + emit_expression(mod, &code, for_node->iter); + + switch (for_node->loop_type) { + case For_Loop_Range: emit_for_range(mod, &code, for_node, iter_local); break; + case For_Loop_Array: emit_for_array(mod, &code, for_node, iter_local); break; + // NOTE: A dynamic array is just a slice with a capacity and allocator on the end. + // Just dropping the extra fields will mean we can just use the slice implementation. + // - brendanfh 2020/09/04 + // - brendanfh 2021/04/13 + case For_Loop_DynArr: WI(for_node->token, WI_DROP); WI(for_node->token, WI_DROP); WI(for_node->token, WI_DROP); + case For_Loop_Slice: emit_for_slice(mod, &code, for_node, iter_local); break; + case For_Loop_Iterator: emit_for_iterator(mod, &code, for_node, iter_local); break; + default: onyx_report_error(for_node->token->pos, Error_Critical, "Invalid for loop type. You should probably not be seeing this..."); + } + + local_free(mod->local_alloc, (AstTyped *) var); + debug_leave_symbol_frame(mod); + + *pcode = code; +} + +EMIT_FUNC(switch, AstSwitch* switch_node) { + bh_arr(WasmInstruction) code = *pcode; + + bh_imap block_map; + bh_imap_init(&block_map, global_heap_allocator, bh_arr_length(switch_node->cases)); + + if (switch_node->initialization != NULL) { + forll (AstNode, stmt, switch_node->initialization, next) { + emit_statement(mod, &code, stmt); + } + } + + emit_enter_structured_block(mod, &code, SBT_Breakable_Block, switch_node->token); + + u64 block_num = 0; + bh_arr_each(AstSwitchCase *, sc, switch_node->cases) { + if (bh_imap_has(&block_map, (u64) (*sc)->block)) continue; + + emit_enter_structured_block(mod, &code, SBT_Fallthrough_Block, (*sc)->block->token); + + bh_imap_put(&block_map, (u64) (*sc)->block, block_num); + block_num++; + } + + switch (switch_node->switch_kind) { + case Switch_Kind_Integer: { + u64 count = switch_node->max_case + 1 - switch_node->min_case; + BranchTable* bt = bh_alloc(mod->extended_instr_alloc, sizeof(BranchTable) + sizeof(u32) * count); + bt->count = count; + bt->default_case = block_num; + fori (i, 0, bt->count) bt->cases[i] = bt->default_case; + + bh_arr_each(bh__imap_entry, sc, switch_node->case_map.entries) { + bt->cases[sc->key - switch_node->min_case] = bh_imap_get(&block_map, (u64) sc->value); + } + + // NOTE: We enter a new block here in order to setup the correct + // indicies for the jump targets in the branch table. For example, + // + // + // jump_table + // label0: + // ... + // label1: + // ... + // + // If we didn't enter a new block, then jumping to label 0, would jump + // to the second block, and so on. + WID(switch_node->expr->token, WI_BLOCK_START, 0x40); + emit_expression(mod, &code, switch_node->expr); + if (switch_node->min_case != 0) { + WID(switch_node->expr->token, WI_I32_CONST, switch_node->min_case); + WI(switch_node->expr->token, WI_I32_SUB); + } + WIP(switch_node->expr->token, WI_JUMP_TABLE, bt); + WI(switch_node->expr->token, WI_BLOCK_END); + break; + } + + case Switch_Kind_Use_Equals: { + WID(switch_node->expr->token, WI_BLOCK_START, 0x40); + + bh_arr_each(CaseToBlock, ctb, switch_node->case_exprs) { + emit_expression(mod, &code, (AstTyped *) ctb->comparison); + + u64 bn = bh_imap_get(&block_map, (u64) ctb->block); + WID(switch_node->expr->token, WI_IF_START, 0x40); + WID(switch_node->expr->token, WI_JUMP, bn + 1); + WI(switch_node->expr->token, WI_IF_END); + } + + WID(switch_node->expr->token, WI_JUMP, block_num); + WI(switch_node->expr->token, WI_BLOCK_END); + break; + } + } + + bh_arr_each(AstSwitchCase *, psc, switch_node->cases) { + AstSwitchCase *sc = *psc; + if (bh_imap_get(&block_map, (u64) sc->block) == 0xdeadbeef) continue; + + u64 bn = bh_imap_get(&block_map, (u64) sc->block); + + // Maybe the Symbol Frame idea should be controlled as a block_flag? + debug_enter_symbol_frame(mod); + emit_block(mod, &code, sc->block, 0); + debug_leave_symbol_frame(mod); + + if (bh_arr_last(code).type != WI_JUMP) + WID(NULL, WI_JUMP, block_num - bn); + + emit_leave_structured_block(mod, &code); + + bh_imap_put(&block_map, (u64) sc->block, 0xdeadbeef); + } + + if (switch_node->default_case != NULL) { + emit_block(mod, &code, switch_node->default_case, 0); + } + + emit_leave_structured_block(mod, &code); + + bh_imap_free(&block_map); + *pcode = code; +} + +EMIT_FUNC(defer, AstDefer* defer) { + bh_arr_push(mod->deferred_stmts, ((DeferredStmt) { + .type = Deferred_Stmt_Node, + .depth = bh_arr_length(mod->structured_jump_target), + .defer_node= defer, + .stmt = defer->stmt, + })); +} + +EMIT_FUNC(defer_code, WasmInstruction* deferred_code, u32 code_count) { + bh_arr_push(mod->deferred_stmts, ((DeferredStmt) { + .type = Deferred_Stmt_Code, + .depth = bh_arr_length(mod->structured_jump_target), + .defer_node= NULL, + .instructions = deferred_code, + .instruction_count = code_count, + })); +} + +EMIT_FUNC(deferred_stmt, DeferredStmt deferred_stmt) { + bh_arr(WasmInstruction) code = *pcode; + + if (deferred_stmt.defer_node) { + WI(deferred_stmt.defer_node->token, WI_NOP); + } + + switch (deferred_stmt.type) { + case Deferred_Stmt_Node: emit_statement(mod, &code, deferred_stmt.stmt); break; + case Deferred_Stmt_Code: { + fori (i, 0, deferred_stmt.instruction_count) { + WIR(NULL, deferred_stmt.instructions[i]); + } + break; + } + } + + *pcode = code; +} + +EMIT_FUNC_NO_ARGS(deferred_stmts) { + if (bh_arr_length(mod->deferred_stmts) <= 0) return; + + u64 depth = bh_arr_length(mod->structured_jump_target); + + while (bh_arr_length(mod->deferred_stmts) > 0 && bh_arr_last(mod->deferred_stmts).depth >= depth) { + DeferredStmt stmt = bh_arr_pop(mod->deferred_stmts); + emit_deferred_stmt(mod, pcode, stmt); + } +} + +EMIT_FUNC(remove_directive, AstDirectiveRemove* remove) { + assert(bh_arr_length(mod->for_remove_info) > 0); + + bh_arr(WasmInstruction) code = *pcode; + + ForRemoveInfo remove_info = bh_arr_last(mod->for_remove_info); + + WIL(remove->token, WI_LOCAL_GET, remove_info.iterator_remove_func); + WIL(remove->token, WI_I32_CONST, mod->null_proc_func_idx); + WI(remove->token, WI_I32_NE); + WID(remove->token, WI_IF_START, 0x40); + WIL(remove->token, WI_LOCAL_GET, remove_info.iterator_data_ptr); + WIL(remove->token, WI_LOCAL_GET, remove_info.iterator_remove_func); + WIL(remove->token, WI_CALL_INDIRECT, remove_info.remove_func_type_idx); + WI(remove->token, WI_IF_END); + + *pcode = code; +} + +// NOTE: These need to be in the same order as +// the OnyxBinaryOp enum +static const WasmInstructionType binop_map[][4] = { + // I32 I64 F32 F64 + /* ADD */ { WI_I32_ADD, WI_I64_ADD, WI_F32_ADD, WI_F64_ADD }, + /* SUB */ { WI_I32_SUB, WI_I64_SUB, WI_F32_SUB, WI_F64_SUB }, + /* MUL */ { WI_I32_MUL, WI_I64_MUL, WI_F32_MUL, WI_F64_MUL }, + /* DIV */ { WI_I32_DIV_S, WI_I64_DIV_S, WI_F32_DIV, WI_F64_DIV }, + /* REM */ { WI_I32_REM_S, WI_I64_REM_S, WI_NOP, WI_NOP }, + + /* EQ */ { WI_I32_EQ, WI_I64_EQ, WI_F32_EQ, WI_F64_EQ }, + /* NEQ */ { WI_I32_NE, WI_I64_NE, WI_F32_NE , WI_F64_NE }, + /* LT */ { WI_I32_LT_S, WI_I64_LT_S, WI_F32_LT, WI_F64_LT }, + /* LTE */ { WI_I32_LE_S, WI_I64_LE_S, WI_F32_LE, WI_F64_LE }, + /* GT */ { WI_I32_GT_S, WI_I64_GT_S, WI_F32_GT, WI_F64_GT }, + /* GTE */ { WI_I32_GE_S, WI_I64_GE_S, WI_F32_GE, WI_F64_GE }, + + /* AND */ { WI_I32_AND, WI_I64_AND, WI_NOP, WI_NOP }, + /* OR */ { WI_I32_OR, WI_I64_OR, WI_NOP, WI_NOP }, + /* XOR */ { WI_I32_XOR, WI_I64_XOR, WI_NOP, WI_NOP }, + /* SHL */ { WI_I32_SHL, WI_I64_SHL, WI_NOP, WI_NOP }, + /* SHR */ { WI_I32_SHR_U, WI_I64_SHR_U, WI_NOP, WI_NOP }, + /* SAR */ { WI_I32_SHR_S, WI_I64_SHR_S, WI_NOP, WI_NOP }, + + /* BAND */ { WI_I32_AND, WI_I64_AND, WI_NOP, WI_NOP }, + /* BOR */ { WI_I32_OR, WI_I64_OR, WI_NOP, WI_NOP }, +}; + +EMIT_FUNC(binop, AstBinaryOp* binop) { + bh_arr(WasmInstruction) code = *pcode; + + if (binop_is_assignment(binop->operation)) { + emit_assignment(mod, &code, binop); + *pcode = code; + return; + } + + b32 is_sign_significant = 0; + switch (binop->operation) { + case Binary_Op_Divide: case Binary_Op_Modulus: + case Binary_Op_Less: case Binary_Op_Less_Equal: + case Binary_Op_Greater: case Binary_Op_Greater_Equal: + is_sign_significant = 1; + } + + WasmType operator_type = onyx_type_to_wasm_type(binop->left->type); + i32 optype = 0; + if (operator_type == WASM_TYPE_INT32) optype = 0; + else if (operator_type == WASM_TYPE_INT64) optype = 1; + else if (operator_type == WASM_TYPE_FLOAT32) optype = 2; + else if (operator_type == WASM_TYPE_FLOAT64) optype = 3; + + WasmInstructionType binop_instr = binop_map[(i32) binop->operation][optype]; + + assert(binop_instr != WI_NOP); + + // NOTE: Use unsigned variant if needed + // Unsigned instructions are always right after + // the signed equivalent + if (is_sign_significant) { + if (binop->left->type->Basic.flags & Basic_Flag_Unsigned) { + binop_instr = (WasmInstructionType) ((i32) binop_instr + 1); + } + } + + emit_expression(mod, &code, binop->left); + emit_expression(mod, &code, binop->right); + + WI(binop->token, binop_instr); + + *pcode = code; +} + +EMIT_FUNC(unaryop, AstUnaryOp* unop) { + bh_arr(WasmInstruction) code = *pcode; + + switch (unop->operation) { + case Unary_Op_Negate: { + TypeBasic* type = &unop->type->Basic; + + if (type->kind == Basic_Kind_I32 + || type->kind == Basic_Kind_I16 + || type->kind == Basic_Kind_I8) { + WID(unop->token, WI_I32_CONST, 0x00); + emit_expression(mod, &code, unop->expr); + WI(unop->token, WI_I32_SUB); + + } + else if (type->kind == Basic_Kind_I64) { + WID(unop->token, WI_I64_CONST, 0x00); + emit_expression(mod, &code, unop->expr); + WI(unop->token, WI_I64_SUB); + + } + else { + emit_expression(mod, &code, unop->expr); + + if (type->kind == Basic_Kind_F32) WI(unop->token, WI_F32_NEG); + if (type->kind == Basic_Kind_F64) WI(unop->token, WI_F64_NEG); + } + + break; + } + + case Unary_Op_Not: + emit_expression(mod, &code, unop->expr); + + WI(unop->token, WI_I32_EQZ); + break; + + case Unary_Op_Bitwise_Not: { + emit_expression(mod, &code, unop->expr); + + TypeBasic* type = &unop->type->Basic; + + if (type->kind == Basic_Kind_I8 || type->kind == Basic_Kind_U8) { + WID(unop->token, WI_I32_CONST, 0xff); + WI(unop->token, WI_I32_XOR); + } + else if (type->kind == Basic_Kind_I16 || type->kind == Basic_Kind_U16) { + WID(unop->token, WI_I32_CONST, 0xffff); + WI(unop->token, WI_I32_XOR); + } + else if (type->kind == Basic_Kind_I32 || type->kind == Basic_Kind_U32) { + WID(unop->token, WI_I32_CONST, 0xffffffff); + WI(unop->token, WI_I32_XOR); + } + else if (type->kind == Basic_Kind_I64 || type->kind == Basic_Kind_U64) { + WIL(unop->token, WI_I64_CONST, 0xffffffffffffffff); + WI(unop->token, WI_I64_XOR); + } + + break; + } + + case Unary_Op_Auto_Cast: + case Unary_Op_Cast: emit_cast(mod, &code, unop); break; + } + + *pcode = code; +} + +// Calling a procedure in Onyx. +// +// This documentation should be placed elsewhere, but for right now I'm going to write it in the relevant +// piece of code. Calling a procedure is relatively simple, at least compared to other calling conventions +// out there, mostly due the fact that this is WebAssembly, where registers are "infinite" and there's no +// really need to use stack canaries for security. +// +// The biggest piece to understand is how the stack gets laid out for the called procedure. To be confusing, +// there are two stacks at play: the WASM expression stack, and the linear memory stack. Here is the general +// lay out for calling a procedure with the following signature. +// +// foo :: (x: i32, y: str, z: [..] i32, va: ..i32) -> (i32, i32) +// +// WASM stack: top is last pushed +// +// vararg count +// vararg pointer to variadic arguments in the linear memory +// pointer to struct-like arguments (z) +// simple structs/primitives (y, x) +// +// Linear memory stack: +// +// ... | struct-like arguments (z) | variadic arguments (va) | return space | ... +// +// The interesting part from above is the fact that 'y' gets passed on the WASM stack, not the linear memory +// stack, even though a 'str' in Onyx is 2-component structure. This is because so-called "simple" structures, +// i.e. structures that are completely flat, with no sub-structures, are passed as multiple primitives. I do +// this because many times for interoperability, it is nicer to get two primitive values for the pointer and +// count of a slice, instead of a pointer. +EMIT_FUNC(call, AstCall* call) { + bh_arr(WasmInstruction) code = *pcode; + + u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top); + u64 stack_top_store_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + + OnyxToken* call_token = call->token; + + // Because it would be inefficient to increment and decrement the global stack pointer for every argument, + // a simple set of instructions increments it once to the size it will need to be. However, because it is + // impossible to know what size the reserved memory will be, a location patch is taken in order to fill it + // in later. + u32 reserve_space_patch = bh_arr_length(code); + WID(call_token, WI_GLOBAL_GET, stack_top_idx); + WIL(call_token, WI_LOCAL_TEE, stack_top_store_local); + WID(call_token, WI_PTR_CONST, 0); // This will be filled in later. + WI(call_token, WI_PTR_ADD); + WID(call_token, WI_GLOBAL_SET, stack_top_idx); + + u32 reserve_size = 0; + u32 vararg_count = 0; + i32 vararg_offset = -1; + + u32* vararg_any_offsets=NULL; + u32* vararg_any_types=NULL; + if (call->va_kind == VA_Kind_Any) { + vararg_any_offsets = bh_alloc_array(global_scratch_allocator, u32, bh_arr_length(call->args.values)); + vararg_any_types = bh_alloc_array(global_scratch_allocator, u32, bh_arr_length(call->args.values)); + } + + bh_arr_each(AstTyped *, parg, call->args.values) { + AstArgument* arg = (AstArgument *) *parg; + if (arg->is_baked) continue; + + b32 place_on_stack = 0; + + if (type_get_param_pass(arg->value->type) == Param_Pass_By_Implicit_Pointer) { + // This arguments needs to be written to the stack because it is not a simple structure. + place_on_stack = 1; + } + + if (arg->va_kind != VA_Kind_Not_VA) { + // This is a variadic argument and needs to be written to the stack. If the starting + // location of the vararg array hasn't been noted, note it. + if (vararg_offset < 0) vararg_offset = reserve_size; + + place_on_stack = 1; + vararg_count += 1; + } + + if (arg->pass_as_any) { + place_on_stack = 1; + } + + if (place_on_stack) WIL(call_token, WI_LOCAL_GET, stack_top_store_local); + + emit_expression(mod, &code, arg->value); + + if (place_on_stack) { + emit_store_instruction(mod, &code, arg->value->type, reserve_size); + + if (arg->va_kind == VA_Kind_Not_VA) { + // Non-variadic arguments on the stack need a pointer to them placed on the WASM stack. + WIL(call_token, WI_LOCAL_GET, stack_top_store_local); + WID(call_token, WI_PTR_CONST, reserve_size); + WI(call_token, WI_PTR_ADD); + } + + if (arg->va_kind == VA_Kind_Any) { + vararg_any_offsets[vararg_count - 1] = reserve_size; + vararg_any_types[vararg_count - 1] = arg->value->type->id; + } + + if (arg->pass_as_any) { + WIL(call_token, WI_I32_CONST, arg->value->type->id); + } + + reserve_size += type_size_of(arg->value->type); + } + } + + switch (call->va_kind) { + case VA_Kind_Any: { + vararg_offset = reserve_size; + + i32 any_size = type_size_of(type_build_from_ast(context.ast_alloc, builtin_any_type)); + + fori (i, 0, vararg_count) { + WIL(call_token, WI_LOCAL_GET, stack_top_store_local); + WIL(call_token, WI_LOCAL_GET, stack_top_store_local); + WID(call_token, WI_PTR_CONST, vararg_any_offsets[i]); + WI(call_token, WI_PTR_ADD); + emit_store_instruction(mod, &code, &basic_types[Basic_Kind_Rawptr], vararg_offset + i * any_size); + + WIL(call_token, WI_LOCAL_GET, stack_top_store_local); + WID(call_token, WI_I32_CONST, vararg_any_types[i]); + emit_store_instruction(mod, &code, &basic_types[Basic_Kind_Type_Index], vararg_offset + i * any_size + POINTER_SIZE); + + reserve_size += any_size; + } + + // fallthrough + } + + case VA_Kind_Typed: { + WIL(call_token, WI_LOCAL_GET, stack_top_store_local); + if (vararg_offset > 0) { + WID(call_token, WI_PTR_CONST, vararg_offset); + WI(call_token, WI_PTR_ADD); + } + WID(call_token, WI_I32_CONST, vararg_count); + break; + } + + case VA_Kind_Untyped: { + WIL(call_token, WI_LOCAL_GET, stack_top_store_local); + WIL(call_token, WI_LOCAL_GET, stack_top_store_local); + if (vararg_offset > 0) { + WID(call_token, WI_PTR_CONST, vararg_offset); + WI(call_token, WI_PTR_ADD); + } + emit_store_instruction(mod, &code, &basic_types[Basic_Kind_Rawptr], reserve_size); + + // NOTE: There may be 4 uninitialized bytes here, because pointers are only 4 bytes in WASM. + + WIL(call_token, WI_LOCAL_GET, stack_top_store_local); + WID(call_token, WI_I32_CONST, vararg_count); + emit_store_instruction(mod, &code, &basic_types[Basic_Kind_I32], reserve_size + POINTER_SIZE); + + WIL(call_token, WI_LOCAL_GET, stack_top_store_local); + if (reserve_size > 0) { + WID(call_token, WI_PTR_CONST, reserve_size); + WI(call_token, WI_PTR_ADD); + } + + reserve_size += 4 + POINTER_SIZE; + break; + } + } + + CallingConvention cc = type_function_get_cc(call->callee->type); + assert(cc != CC_Undefined); + + Type* return_type = call->callee->type->Function.return_type; + u32 return_size = type_size_of(return_type); + assert(return_size % type_alignment_of(return_type) == 0); + + if (cc == CC_Return_Stack) reserve_size += return_size; + + if (call->callee->kind == Ast_Kind_Function) { + i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) call->callee); + WIL(NULL, WI_CALL, func_idx); + + } else { + emit_expression(mod, &code, call->callee); + + i32 type_idx = generate_type_idx(mod, call->callee->type); + WID(NULL, WI_CALL_INDIRECT, ((WasmInstructionData) { type_idx, 0x00 })); + } + + if (reserve_size > 0) { + WIL(call_token, WI_LOCAL_GET, stack_top_store_local); + WID(call_token, WI_GLOBAL_SET, stack_top_idx); + + bh_align(reserve_size, 16); + code[reserve_space_patch + 2].data.l = reserve_size; + + } else { + fori (i, 0, 5) code[reserve_space_patch + i].type = WI_NOP; + } + + if (cc == CC_Return_Stack) { + WID(call_token, WI_GLOBAL_GET, stack_top_idx); + emit_load_instruction(mod, &code, return_type, reserve_size - return_size); + } + + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + *pcode = code; +} + +// BUG: This implementation assumes that the host system C's implementation is using +// little endian integers. +#define SIMD_INT_CONST_INTRINSIC(type, count) { \ + type* byte_buffer = bh_alloc(mod->extended_instr_alloc, 16); \ + bh_arr(AstArgument *) arg_arr = (bh_arr(AstArgument *)) call->args.values; \ + fori (i, 0, count) { \ + if (arg_arr[i]->value->kind != Ast_Kind_NumLit) { \ + onyx_report_error(arg_arr[i]->token->pos, Error_Critical, \ + "SIMD constants expect compile time constants as parameters. The %d%s parameter was not.", \ + i, bh_num_suffix(i)); \ + *pcode = code; \ + return; \ + } \ + byte_buffer[i] = (type) ((AstNumLit *) arg_arr[i]->value)->value.l; \ + } \ + WIP(call->token, WI_V128_CONST, byte_buffer); \ + } + +#define SIMD_EXTRACT_LANE_INSTR(instr, arg_arr) \ + emit_expression(mod, &code, arg_arr[0]->value);\ + if (arg_arr[1]->value->kind != Ast_Kind_NumLit) { \ + onyx_report_error(arg_arr[1]->token->pos, Error_Critical, "SIMD lane instructions expect a compile time lane number."); \ + *pcode = code; \ + return; \ + } \ + WID(call->token, instr, (u8) ((AstNumLit *) arg_arr[1]->value)->value.i); + +#define SIMD_REPLACE_LANE_INSTR(instr, arg_arr) { \ + emit_expression(mod, &code, arg_arr[0]->value);\ + if (arg_arr[1]->value->kind != Ast_Kind_NumLit) { \ + onyx_report_error(arg_arr[1]->token->pos, Error_Critical, "SIMD lane instructions expect a compile time lane number."); \ + *pcode = code; \ + return; \ + } \ + u8 lane = (u8) ((AstNumLit *) arg_arr[1]->value)->value.i; \ + emit_expression(mod, &code, arg_arr[2]->value); \ + WID(call->token, instr, lane); \ +} + + +EMIT_FUNC(intrinsic_call, AstCall* call) { + bh_arr(WasmInstruction) code = *pcode; + + b32 place_arguments_normally = 1; + + switch (call->intrinsic) { + case ONYX_INTRINSIC_V128_CONST: + case ONYX_INTRINSIC_I8X16_CONST: case ONYX_INTRINSIC_I16X8_CONST: + case ONYX_INTRINSIC_I32X4_CONST: case ONYX_INTRINSIC_I64X2_CONST: + case ONYX_INTRINSIC_F32X4_CONST: case ONYX_INTRINSIC_F64X2_CONST: + case ONYX_INTRINSIC_I8X16_EXTRACT_LANE_S: case ONYX_INTRINSIC_I8X16_EXTRACT_LANE_U: + case ONYX_INTRINSIC_I16X8_EXTRACT_LANE_S: case ONYX_INTRINSIC_I16X8_EXTRACT_LANE_U: + case ONYX_INTRINSIC_I32X4_EXTRACT_LANE: case ONYX_INTRINSIC_I64X2_EXTRACT_LANE: + case ONYX_INTRINSIC_F32X4_EXTRACT_LANE: case ONYX_INTRINSIC_F64X2_EXTRACT_LANE: + case ONYX_INTRINSIC_I8X16_REPLACE_LANE: case ONYX_INTRINSIC_I16X8_REPLACE_LANE: + case ONYX_INTRINSIC_I32X4_REPLACE_LANE: case ONYX_INTRINSIC_I64X2_REPLACE_LANE: + case ONYX_INTRINSIC_F32X4_REPLACE_LANE: case ONYX_INTRINSIC_F64X2_REPLACE_LANE: + case ONYX_INTRINSIC_I8X16_SHUFFLE: + place_arguments_normally = 0; + + default: break; + } + + if (place_arguments_normally) { + bh_arr_each(AstTyped *, arg, call->args.values) { + emit_expression(mod, &code, *arg); + } + } + + switch (call->intrinsic) { + case ONYX_INTRINSIC_MEMORY_SIZE: WID(call->token, WI_MEMORY_SIZE, 0x00); break; + case ONYX_INTRINSIC_MEMORY_GROW: WID(call->token, WI_MEMORY_GROW, 0x00); break; + case ONYX_INTRINSIC_MEMORY_COPY: + if (context.options->use_post_mvp_features) { + WIL(call->token, WI_MEMORY_COPY, 0x00); + } else { + emit_intrinsic_memory_copy(mod, &code); + } + break; + case ONYX_INTRINSIC_MEMORY_FILL: + if (context.options->use_post_mvp_features) { + WIL(call->token, WI_MEMORY_FILL, 0x00); + } else { + emit_intrinsic_memory_fill(mod, &code); + } + break; + + case ONYX_INTRINSIC_INITIALIZE: { + Type* type_to_initialize = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; + emit_initialize_type(mod, &code, type_to_initialize, call->token); + break; + } + + case ONYX_INTRINSIC_I32_CLZ: WI(call->token, WI_I32_CLZ); break; + case ONYX_INTRINSIC_I32_CTZ: WI(call->token, WI_I32_CTZ); break; + case ONYX_INTRINSIC_I32_POPCNT: WI(call->token, WI_I32_POPCNT); break; + case ONYX_INTRINSIC_I32_AND: WI(call->token, WI_I32_AND); break; + case ONYX_INTRINSIC_I32_OR: WI(call->token, WI_I32_OR); break; + case ONYX_INTRINSIC_I32_XOR: WI(call->token, WI_I32_XOR); break; + case ONYX_INTRINSIC_I32_SHL: WI(call->token, WI_I32_SHL); break; + case ONYX_INTRINSIC_I32_SLR: WI(call->token, WI_I32_SHR_U); break; + case ONYX_INTRINSIC_I32_SAR: WI(call->token, WI_I32_SHR_S); break; + case ONYX_INTRINSIC_I32_ROTL: WI(call->token, WI_I32_ROTL); break; + case ONYX_INTRINSIC_I32_ROTR: WI(call->token, WI_I32_ROTR); break; + + case ONYX_INTRINSIC_I64_CLZ: WI(call->token, WI_I64_CLZ); break; + case ONYX_INTRINSIC_I64_CTZ: WI(call->token, WI_I64_CTZ); break; + case ONYX_INTRINSIC_I64_POPCNT: WI(call->token, WI_I64_POPCNT); break; + case ONYX_INTRINSIC_I64_AND: WI(call->token, WI_I64_AND); break; + case ONYX_INTRINSIC_I64_OR: WI(call->token, WI_I64_OR); break; + case ONYX_INTRINSIC_I64_XOR: WI(call->token, WI_I64_XOR); break; + case ONYX_INTRINSIC_I64_SHL: WI(call->token, WI_I64_SHL); break; + case ONYX_INTRINSIC_I64_SLR: WI(call->token, WI_I64_SHR_U); break; + case ONYX_INTRINSIC_I64_SAR: WI(call->token, WI_I64_SHR_S); break; + case ONYX_INTRINSIC_I64_ROTL: WI(call->token, WI_I64_ROTL); break; + case ONYX_INTRINSIC_I64_ROTR: WI(call->token, WI_I64_ROTR); break; + + case ONYX_INTRINSIC_F32_ABS: WI(call->token, WI_F32_ABS); break; + case ONYX_INTRINSIC_F32_CEIL: WI(call->token, WI_F32_CEIL); break; + case ONYX_INTRINSIC_F32_FLOOR: WI(call->token, WI_F32_FLOOR); break; + case ONYX_INTRINSIC_F32_TRUNC: WI(call->token, WI_F32_TRUNC); break; + case ONYX_INTRINSIC_F32_NEAREST: WI(call->token, WI_F32_NEAREST); break; + case ONYX_INTRINSIC_F32_SQRT: WI(call->token, WI_F32_SQRT); break; + case ONYX_INTRINSIC_F32_MIN: WI(call->token, WI_F32_MIN); break; + case ONYX_INTRINSIC_F32_MAX: WI(call->token, WI_F32_MAX); break; + case ONYX_INTRINSIC_F32_COPYSIGN: WI(call->token, WI_F32_COPYSIGN); break; + + case ONYX_INTRINSIC_F64_ABS: WI(call->token, WI_F64_ABS); break; + case ONYX_INTRINSIC_F64_CEIL: WI(call->token, WI_F64_CEIL); break; + case ONYX_INTRINSIC_F64_FLOOR: WI(call->token, WI_F64_FLOOR); break; + case ONYX_INTRINSIC_F64_TRUNC: WI(call->token, WI_F64_TRUNC); break; + case ONYX_INTRINSIC_F64_NEAREST: WI(call->token, WI_F64_NEAREST); break; + case ONYX_INTRINSIC_F64_SQRT: WI(call->token, WI_F64_SQRT); break; + case ONYX_INTRINSIC_F64_MIN: WI(call->token, WI_F64_MIN); break; + case ONYX_INTRINSIC_F64_MAX: WI(call->token, WI_F64_MAX); break; + case ONYX_INTRINSIC_F64_COPYSIGN: WI(call->token, WI_F64_COPYSIGN); break; + + case ONYX_INTRINSIC_I8X16_CONST: + case ONYX_INTRINSIC_V128_CONST: SIMD_INT_CONST_INTRINSIC(u8, 16); break; + case ONYX_INTRINSIC_I16X8_CONST: SIMD_INT_CONST_INTRINSIC(u16, 8); break; + case ONYX_INTRINSIC_I32X4_CONST: SIMD_INT_CONST_INTRINSIC(u32, 4); break; + case ONYX_INTRINSIC_I64X2_CONST: SIMD_INT_CONST_INTRINSIC(u64, 2); break; + case ONYX_INTRINSIC_F32X4_CONST: { + f32* byte_buffer = bh_alloc(mod->extended_instr_alloc, 16); + bh_arr(AstArgument *) arg_arr = (bh_arr(AstArgument *)) call->args.values; + fori (i, 0, 4) { + if (arg_arr[i]->value->kind != Ast_Kind_NumLit) { + onyx_report_error(arg_arr[i]->token->pos, Error_Critical, + "SIMD constants expect compile time constants as parameters. The %d%s parameter was not.", + i, bh_num_suffix(i)); + *pcode = code; + return; + } + byte_buffer[i] = (f32) ((AstNumLit *) arg_arr[i]->value)->value.f; + } + WIP(call->token, WI_V128_CONST, byte_buffer); + break; + } + + case ONYX_INTRINSIC_F64X2_CONST: { + f64* byte_buffer = bh_alloc(mod->extended_instr_alloc, 16); + bh_arr(AstArgument *) arg_arr = (bh_arr(AstArgument *)) call->args.values; + fori (i, 0, 2) { + if (arg_arr[i]->value->kind != Ast_Kind_NumLit) { + onyx_report_error(arg_arr[i]->token->pos, Error_Critical, + "SIMD constants expect compile time constants as parameters. The %d%s parameter was not.", + i, bh_num_suffix(i)); + *pcode = code; + return; + } + byte_buffer[i] = (f64) ((AstNumLit *) arg_arr[i]->value)->value.d; + } + WIP(call->token, WI_V128_CONST, byte_buffer); + break; + } + + case ONYX_INTRINSIC_I8X16_SHUFFLE: { + u8* byte_buffer = bh_alloc(mod->extended_instr_alloc, 16); + bh_arr(AstArgument *) arg_arr = (bh_arr(AstArgument *)) call->args.values; + + // NOTE: There are two parameters that have to be outputted before + // the immediate bytes + emit_expression(mod, &code, arg_arr[0]->value); + emit_expression(mod, &code, arg_arr[1]->value); + + fori (i, 0, 16) { + if (arg_arr[i + 2]->value->kind != Ast_Kind_NumLit) { + onyx_report_error(arg_arr[i + 2]->token->pos, Error_Critical, + "SIMD constants expect compile time constants as parameters. The %d%s parameter was not.", + i, bh_num_suffix(i)); + *pcode = code; + return; + } + byte_buffer[i] = (u8) ((AstNumLit *) arg_arr[i + 2]->value)->value.i; + } + WIP(call->token, WI_I8X16_SHUFFLE, byte_buffer); + break; + } + + // CLEANUP ALL OF THIS + case ONYX_INTRINSIC_I8X16_EXTRACT_LANE_S: SIMD_EXTRACT_LANE_INSTR(WI_I8X16_EXTRACT_LANE_S, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_I8X16_EXTRACT_LANE_U: SIMD_EXTRACT_LANE_INSTR(WI_I8X16_EXTRACT_LANE_U, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_I8X16_REPLACE_LANE: SIMD_REPLACE_LANE_INSTR(WI_I8X16_REPLACE_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_I16X8_EXTRACT_LANE_S: SIMD_EXTRACT_LANE_INSTR(WI_I16X8_EXTRACT_LANE_S, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_I16X8_EXTRACT_LANE_U: SIMD_EXTRACT_LANE_INSTR(WI_I16X8_EXTRACT_LANE_U, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_I16X8_REPLACE_LANE: SIMD_REPLACE_LANE_INSTR(WI_I16X8_REPLACE_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_I32X4_EXTRACT_LANE: SIMD_EXTRACT_LANE_INSTR(WI_I32X4_EXTRACT_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_I32X4_REPLACE_LANE: SIMD_REPLACE_LANE_INSTR(WI_I32X4_REPLACE_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_I64X2_EXTRACT_LANE: SIMD_EXTRACT_LANE_INSTR(WI_I64X2_EXTRACT_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_I64X2_REPLACE_LANE: SIMD_REPLACE_LANE_INSTR(WI_I64X2_REPLACE_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_F32X4_EXTRACT_LANE: SIMD_EXTRACT_LANE_INSTR(WI_F32X4_EXTRACT_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_F32X4_REPLACE_LANE: SIMD_REPLACE_LANE_INSTR(WI_F32X4_REPLACE_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_F64X2_EXTRACT_LANE: SIMD_EXTRACT_LANE_INSTR(WI_F64X2_EXTRACT_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; + case ONYX_INTRINSIC_F64X2_REPLACE_LANE: SIMD_REPLACE_LANE_INSTR(WI_F64X2_REPLACE_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; + + case ONYX_INTRINSIC_I8X16_SWIZZLE: WI(call->token, WI_I8X16_SWIZZLE); break; + case ONYX_INTRINSIC_I8X16_SPLAT: WI(call->token, WI_I8X16_SPLAT); break; + case ONYX_INTRINSIC_I16X8_SPLAT: WI(call->token, WI_I16X8_SPLAT); break; + case ONYX_INTRINSIC_I32X4_SPLAT: WI(call->token, WI_I32X4_SPLAT); break; + case ONYX_INTRINSIC_I64X2_SPLAT: WI(call->token, WI_I64X2_SPLAT); break; + case ONYX_INTRINSIC_F32X4_SPLAT: WI(call->token, WI_F32X4_SPLAT); break; + case ONYX_INTRINSIC_F64X2_SPLAT: WI(call->token, WI_F64X2_SPLAT); break; + + case ONYX_INTRINSIC_I8X16_EQ: WI(call->token, WI_I8X16_EQ); break; + case ONYX_INTRINSIC_I8X16_NEQ: WI(call->token, WI_I8X16_NEQ); break; + case ONYX_INTRINSIC_I8X16_LT_S: WI(call->token, WI_I8X16_LT_S); break; + case ONYX_INTRINSIC_I8X16_LT_U: WI(call->token, WI_I8X16_LT_U); break; + case ONYX_INTRINSIC_I8X16_GT_S: WI(call->token, WI_I8X16_GT_S); break; + case ONYX_INTRINSIC_I8X16_GT_U: WI(call->token, WI_I8X16_GT_U); break; + case ONYX_INTRINSIC_I8X16_LE_S: WI(call->token, WI_I8X16_LE_S); break; + case ONYX_INTRINSIC_I8X16_LE_U: WI(call->token, WI_I8X16_LE_U); break; + case ONYX_INTRINSIC_I8X16_GE_S: WI(call->token, WI_I8X16_GE_S); break; + case ONYX_INTRINSIC_I8X16_GE_U: WI(call->token, WI_I8X16_GE_U); break; + + case ONYX_INTRINSIC_I16X8_EQ: WI(call->token, WI_I16X8_EQ); break; + case ONYX_INTRINSIC_I16X8_NEQ: WI(call->token, WI_I16X8_NEQ); break; + case ONYX_INTRINSIC_I16X8_LT_S: WI(call->token, WI_I16X8_LT_S); break; + case ONYX_INTRINSIC_I16X8_LT_U: WI(call->token, WI_I16X8_LT_U); break; + case ONYX_INTRINSIC_I16X8_GT_S: WI(call->token, WI_I16X8_GT_S); break; + case ONYX_INTRINSIC_I16X8_GT_U: WI(call->token, WI_I16X8_GT_U); break; + case ONYX_INTRINSIC_I16X8_LE_S: WI(call->token, WI_I16X8_LE_S); break; + case ONYX_INTRINSIC_I16X8_LE_U: WI(call->token, WI_I16X8_LE_U); break; + case ONYX_INTRINSIC_I16X8_GE_S: WI(call->token, WI_I16X8_GE_S); break; + case ONYX_INTRINSIC_I16X8_GE_U: WI(call->token, WI_I16X8_GE_U); break; + + case ONYX_INTRINSIC_I32X4_EQ: WI(call->token, WI_I32X4_EQ); break; + case ONYX_INTRINSIC_I32X4_NEQ: WI(call->token, WI_I32X4_NEQ); break; + case ONYX_INTRINSIC_I32X4_LT_S: WI(call->token, WI_I32X4_LT_S); break; + case ONYX_INTRINSIC_I32X4_LT_U: WI(call->token, WI_I32X4_LT_U); break; + case ONYX_INTRINSIC_I32X4_GT_S: WI(call->token, WI_I32X4_GT_S); break; + case ONYX_INTRINSIC_I32X4_GT_U: WI(call->token, WI_I32X4_GT_U); break; + case ONYX_INTRINSIC_I32X4_LE_S: WI(call->token, WI_I32X4_LE_S); break; + case ONYX_INTRINSIC_I32X4_LE_U: WI(call->token, WI_I32X4_LE_U); break; + case ONYX_INTRINSIC_I32X4_GE_S: WI(call->token, WI_I32X4_GE_S); break; + case ONYX_INTRINSIC_I32X4_GE_U: WI(call->token, WI_I32X4_GE_U); break; + + case ONYX_INTRINSIC_F32X4_EQ: WI(call->token, WI_F32X4_EQ); break; + case ONYX_INTRINSIC_F32X4_NEQ: WI(call->token, WI_F32X4_NEQ); break; + case ONYX_INTRINSIC_F32X4_LT: WI(call->token, WI_F32X4_LT); break; + case ONYX_INTRINSIC_F32X4_GT: WI(call->token, WI_F32X4_GT); break; + case ONYX_INTRINSIC_F32X4_LE: WI(call->token, WI_F32X4_LE); break; + case ONYX_INTRINSIC_F32X4_GE: WI(call->token, WI_F32X4_GE); break; + + case ONYX_INTRINSIC_F64X2_EQ: WI(call->token, WI_F64X2_EQ); break; + case ONYX_INTRINSIC_F64X2_NEQ: WI(call->token, WI_F64X2_NEQ); break; + case ONYX_INTRINSIC_F64X2_LT: WI(call->token, WI_F64X2_LT); break; + case ONYX_INTRINSIC_F64X2_GT: WI(call->token, WI_F64X2_GT); break; + case ONYX_INTRINSIC_F64X2_LE: WI(call->token, WI_F64X2_LE); break; + case ONYX_INTRINSIC_F64X2_GE: WI(call->token, WI_F64X2_GE); break; + + case ONYX_INTRINSIC_V128_NOT: WI(call->token, WI_V128_NOT); break; + case ONYX_INTRINSIC_V128_AND: WI(call->token, WI_V128_AND); break; + case ONYX_INTRINSIC_V128_ANDNOT: WI(call->token, WI_V128_ANDNOT); break; + case ONYX_INTRINSIC_V128_OR: WI(call->token, WI_V128_OR); break; + case ONYX_INTRINSIC_V128_XOR: WI(call->token, WI_V128_XOR); break; + case ONYX_INTRINSIC_V128_BITSELECT: WI(call->token, WI_V128_BITSELECT); break; + + case ONYX_INTRINSIC_I8X16_ABS: WI(call->token, WI_I8X16_ABS); break; + case ONYX_INTRINSIC_I8X16_NEG: WI(call->token, WI_I8X16_NEG); break; + case ONYX_INTRINSIC_I8X16_ANY_TRUE: WI(call->token, WI_I8X16_ANY_TRUE); break; + case ONYX_INTRINSIC_I8X16_ALL_TRUE: WI(call->token, WI_I8X16_ALL_TRUE); break; + case ONYX_INTRINSIC_I8X16_BITMASK: WI(call->token, WI_I8X16_BITMASK); break; + case ONYX_INTRINSIC_I8X16_NARROW_I16X8_S: WI(call->token, WI_I8X16_NARROW_I16X8_S); break; + case ONYX_INTRINSIC_I8X16_NARROW_I16X8_U: WI(call->token, WI_I8X16_NARROW_I16X8_U); break; + case ONYX_INTRINSIC_I8X16_SHL: WI(call->token, WI_I8X16_SHL); break; + case ONYX_INTRINSIC_I8X16_SHR_S: WI(call->token, WI_I8X16_SHR_S); break; + case ONYX_INTRINSIC_I8X16_SHR_U: WI(call->token, WI_I8X16_SHR_U); break; + case ONYX_INTRINSIC_I8X16_ADD: WI(call->token, WI_I8X16_ADD); break; + case ONYX_INTRINSIC_I8X16_ADD_SAT_S: WI(call->token, WI_I8X16_ADD_SAT_S); break; + case ONYX_INTRINSIC_I8X16_ADD_SAT_U: WI(call->token, WI_I8X16_ADD_SAT_U); break; + case ONYX_INTRINSIC_I8X16_SUB: WI(call->token, WI_I8X16_SUB); break; + case ONYX_INTRINSIC_I8X16_SUB_SAT_S: WI(call->token, WI_I8X16_SUB_SAT_S); break; + case ONYX_INTRINSIC_I8X16_SUB_SAT_U: WI(call->token, WI_I8X16_SUB_SAT_U); break; + case ONYX_INTRINSIC_I8X16_MIN_S: WI(call->token, WI_I8X16_MIN_S); break; + case ONYX_INTRINSIC_I8X16_MIN_U: WI(call->token, WI_I8X16_MIN_U); break; + case ONYX_INTRINSIC_I8X16_MAX_S: WI(call->token, WI_I8X16_MAX_S); break; + case ONYX_INTRINSIC_I8X16_MAX_U: WI(call->token, WI_I8X16_MAX_U); break; + case ONYX_INTRINSIC_I8X16_AVGR_U: WI(call->token, WI_I8X16_AVGR_U); break; + + case ONYX_INTRINSIC_I16X8_ABS: WI(call->token, WI_I16X8_ABS); break; + case ONYX_INTRINSIC_I16X8_NEG: WI(call->token, WI_I16X8_NEG); break; + case ONYX_INTRINSIC_I16X8_ANY_TRUE: WI(call->token, WI_I16X8_ANY_TRUE); break; + case ONYX_INTRINSIC_I16X8_ALL_TRUE: WI(call->token, WI_I16X8_ALL_TRUE); break; + case ONYX_INTRINSIC_I16X8_BITMASK: WI(call->token, WI_I16X8_BITMASK); break; + case ONYX_INTRINSIC_I16X8_NARROW_I32X4_S: WI(call->token, WI_I16X8_NARROW_I32X4_S); break; + case ONYX_INTRINSIC_I16X8_NARROW_I32X4_U: WI(call->token, WI_I16X8_NARROW_I32X4_U); break; + case ONYX_INTRINSIC_I16X8_WIDEN_LOW_I8X16_S: WI(call->token, WI_I16X8_WIDEN_LOW_I8X16_S); break; + case ONYX_INTRINSIC_I16X8_WIDEN_HIGH_I8X16_S: WI(call->token, WI_I16X8_WIDEN_HIGH_I8X16_S); break; + case ONYX_INTRINSIC_I16X8_WIDEN_LOW_I8X16_U: WI(call->token, WI_I16X8_WIDEN_LOW_I8X16_U); break; + case ONYX_INTRINSIC_I16X8_WIDEN_HIGH_I8X16_U: WI(call->token, WI_I16X8_WIDEN_HIGH_I8X16_U); break; + case ONYX_INTRINSIC_I16X8_SHL: WI(call->token, WI_I16X8_SHL); break; + case ONYX_INTRINSIC_I16X8_SHR_S: WI(call->token, WI_I16X8_SHR_S); break; + case ONYX_INTRINSIC_I16X8_SHR_U: WI(call->token, WI_I16X8_SHR_U); break; + case ONYX_INTRINSIC_I16X8_ADD: WI(call->token, WI_I16X8_ADD); break; + case ONYX_INTRINSIC_I16X8_ADD_SAT_S: WI(call->token, WI_I16X8_ADD_SAT_S); break; + case ONYX_INTRINSIC_I16X8_ADD_SAT_U: WI(call->token, WI_I16X8_ADD_SAT_U); break; + case ONYX_INTRINSIC_I16X8_SUB: WI(call->token, WI_I16X8_SUB); break; + case ONYX_INTRINSIC_I16X8_SUB_SAT_S: WI(call->token, WI_I16X8_SUB_SAT_S); break; + case ONYX_INTRINSIC_I16X8_SUB_SAT_U: WI(call->token, WI_I16X8_SUB_SAT_U); break; + case ONYX_INTRINSIC_I16X8_MUL: WI(call->token, WI_I16X8_MUL); break; + case ONYX_INTRINSIC_I16X8_MIN_S: WI(call->token, WI_I16X8_MIN_S); break; + case ONYX_INTRINSIC_I16X8_MIN_U: WI(call->token, WI_I16X8_MIN_U); break; + case ONYX_INTRINSIC_I16X8_MAX_S: WI(call->token, WI_I16X8_MAX_S); break; + case ONYX_INTRINSIC_I16X8_MAX_U: WI(call->token, WI_I16X8_MAX_U); break; + case ONYX_INTRINSIC_I16X8_AVGR_U: WI(call->token, WI_I16X8_AVGR_U); break; + + case ONYX_INTRINSIC_I32X4_ABS: WI(call->token, WI_I32X4_ABS); break; + case ONYX_INTRINSIC_I32X4_NEG: WI(call->token, WI_I32X4_NEG); break; + case ONYX_INTRINSIC_I32X4_ANY_TRUE: WI(call->token, WI_I32X4_ANY_TRUE); break; + case ONYX_INTRINSIC_I32X4_ALL_TRUE: WI(call->token, WI_I32X4_ALL_TRUE); break; + case ONYX_INTRINSIC_I32X4_BITMASK: WI(call->token, WI_I32X4_BITMASK); break; + case ONYX_INTRINSIC_I32X4_WIDEN_LOW_I16X8_S: WI(call->token, WI_I32X4_WIDEN_LOW_I16X8_S); break; + case ONYX_INTRINSIC_I32X4_WIDEN_HIGH_I16X8_S: WI(call->token, WI_I32X4_WIDEN_HIGH_I16X8_S); break; + case ONYX_INTRINSIC_I32X4_WIDEN_LOW_I16X8_U: WI(call->token, WI_I32X4_WIDEN_LOW_I16X8_U); break; + case ONYX_INTRINSIC_I32X4_WIDEN_HIGH_I16X8_U: WI(call->token, WI_I32X4_WIDEN_HIGH_I16X8_U); break; + case ONYX_INTRINSIC_I32X4_SHL: WI(call->token, WI_I32X4_SHL); break; + case ONYX_INTRINSIC_I32X4_SHR_S: WI(call->token, WI_I32X4_SHR_S); break; + case ONYX_INTRINSIC_I32X4_SHR_U: WI(call->token, WI_I32X4_SHR_U); break; + case ONYX_INTRINSIC_I32X4_ADD: WI(call->token, WI_I32X4_ADD); break; + case ONYX_INTRINSIC_I32X4_SUB: WI(call->token, WI_I32X4_SUB); break; + case ONYX_INTRINSIC_I32X4_MUL: WI(call->token, WI_I32X4_MUL); break; + case ONYX_INTRINSIC_I32X4_MIN_S: WI(call->token, WI_I32X4_MIN_S); break; + case ONYX_INTRINSIC_I32X4_MIN_U: WI(call->token, WI_I32X4_MIN_U); break; + case ONYX_INTRINSIC_I32X4_MAX_S: WI(call->token, WI_I32X4_MAX_S); break; + case ONYX_INTRINSIC_I32X4_MAX_U: WI(call->token, WI_I32X4_MAX_U); break; + + case ONYX_INTRINSIC_I64X2_NEG: WI(call->token, WI_I64X2_NEG); break; + case ONYX_INTRINSIC_I64X2_SHL: WI(call->token, WI_I64X2_SHL); break; + case ONYX_INTRINSIC_I64X2_SHR_S: WI(call->token, WI_I64X2_SHR_S); break; + case ONYX_INTRINSIC_I64X2_SHR_U: WI(call->token, WI_I64X2_SHR_U); break; + case ONYX_INTRINSIC_I64X2_ADD: WI(call->token, WI_I64X2_ADD); break; + case ONYX_INTRINSIC_I64X2_SUB: WI(call->token, WI_I64X2_SUB); break; + case ONYX_INTRINSIC_I64X2_MUL: WI(call->token, WI_I64X2_MUL); break; + + case ONYX_INTRINSIC_F32X4_ABS: WI(call->token, WI_F32X4_ABS); break; + case ONYX_INTRINSIC_F32X4_NEG: WI(call->token, WI_F32X4_NEG); break; + case ONYX_INTRINSIC_F32X4_SQRT: WI(call->token, WI_F32X4_SQRT); break; + case ONYX_INTRINSIC_F32X4_ADD: WI(call->token, WI_F32X4_ADD); break; + case ONYX_INTRINSIC_F32X4_SUB: WI(call->token, WI_F32X4_SUB); break; + case ONYX_INTRINSIC_F32X4_MUL: WI(call->token, WI_F32X4_MUL); break; + case ONYX_INTRINSIC_F32X4_DIV: WI(call->token, WI_F32X4_DIV); break; + case ONYX_INTRINSIC_F32X4_MIN: WI(call->token, WI_F32X4_MIN); break; + case ONYX_INTRINSIC_F32X4_MAX: WI(call->token, WI_F32X4_MAX); break; + + case ONYX_INTRINSIC_F64X2_ABS: WI(call->token, WI_F64X2_ABS); break; + case ONYX_INTRINSIC_F64X2_NEG: WI(call->token, WI_F64X2_NEG); break; + case ONYX_INTRINSIC_F64X2_SQRT: WI(call->token, WI_F64X2_SQRT); break; + case ONYX_INTRINSIC_F64X2_ADD: WI(call->token, WI_F64X2_ADD); break; + case ONYX_INTRINSIC_F64X2_SUB: WI(call->token, WI_F64X2_SUB); break; + case ONYX_INTRINSIC_F64X2_MUL: WI(call->token, WI_F64X2_MUL); break; + case ONYX_INTRINSIC_F64X2_DIV: WI(call->token, WI_F64X2_DIV); break; + case ONYX_INTRINSIC_F64X2_MIN: WI(call->token, WI_F64X2_MIN); break; + case ONYX_INTRINSIC_F64X2_MAX: WI(call->token, WI_F64X2_MAX); break; + + case ONYX_INTRINSIC_I32X4_TRUNC_SAT_F32X4_S: WI(call->token, WI_I32X4_TRUNC_SAT_F32X4_S); break; + case ONYX_INTRINSIC_I32X4_TRUNC_SAT_F32X4_U: WI(call->token, WI_I32X4_TRUNC_SAT_F32X4_U); break; + case ONYX_INTRINSIC_F32X4_CONVERT_I32X4_S: WI(call->token, WI_F32X4_CONVERT_I32X4_S); break; + case ONYX_INTRINSIC_F32X4_CONVERT_I32X4_U: WI(call->token, WI_F32X4_CONVERT_I32X4_U); break; + + case ONYX_INTRINSIC_ATOMIC_WAIT: { + Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; + emit_intrinsic_atomic_wait(mod, &code, atomic_type, call->token); + break; + } + + case ONYX_INTRINSIC_ATOMIC_NOTIFY: { + emit_intrinsic_atomic_notify(mod, &code); + break; + } + + case ONYX_INTRINSIC_ATOMIC_FENCE: { + emit_intrinsic_atomic_fence(mod, &code); + break; + } + + case ONYX_INTRINSIC_ATOMIC_LOAD: { + Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; + emit_intrinsic_atomic_load(mod, &code, atomic_type, call->token); + break; + } + + case ONYX_INTRINSIC_ATOMIC_STORE: { + Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; + emit_intrinsic_atomic_store(mod, &code, atomic_type, call->token); + break; + } + + case ONYX_INTRINSIC_ATOMIC_ADD: { + Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; + emit_intrinsic_atomic_add(mod, &code, atomic_type, call->token); + break; + } + + case ONYX_INTRINSIC_ATOMIC_SUB: { + Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; + emit_intrinsic_atomic_sub(mod, &code, atomic_type, call->token); + break; + } + + case ONYX_INTRINSIC_ATOMIC_AND: { + Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; + emit_intrinsic_atomic_and(mod, &code, atomic_type, call->token); + break; + } + + case ONYX_INTRINSIC_ATOMIC_OR: { + Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; + emit_intrinsic_atomic_or(mod, &code, atomic_type, call->token); + break; + } + + case ONYX_INTRINSIC_ATOMIC_XOR: { + Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; + emit_intrinsic_atomic_xor(mod, &code, atomic_type, call->token); + break; + } + + case ONYX_INTRINSIC_ATOMIC_XCHG: { + Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; + emit_intrinsic_atomic_xchg(mod, &code, atomic_type, call->token); + break; + } + + case ONYX_INTRINSIC_ATOMIC_CMPXCHG: { + Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; + emit_intrinsic_atomic_cmpxchg(mod, &code, atomic_type, call->token); + break; + } + + default: assert(("Unsupported intrinsic", 0)); + } + + *pcode = code; +} + +EMIT_FUNC(subscript_location, AstSubscript* sub, u64* offset_return) { + bh_arr(WasmInstruction) code = *pcode; + + emit_expression(mod, &code, sub->expr); + if (sub->elem_size != 1) { + WID(sub->token, WI_PTR_CONST, sub->elem_size); + WI(sub->token, WI_PTR_MUL); + } + + // CLEANUP: This is one dense clusterf**k of code... + u64 offset = 0; + if (sub->addr->kind == Ast_Kind_Subscript + && sub->addr->type->kind == Type_Kind_Array) { + emit_subscript_location(mod, &code, (AstSubscript *) sub->addr, &offset); + } else if (sub->addr->kind == Ast_Kind_Field_Access + && sub->addr->type->kind == Type_Kind_Array) { + emit_field_access_location(mod, &code, (AstFieldAccess *) sub->addr, &offset); + } else if ((sub->addr->kind == Ast_Kind_Local || sub->addr->kind == Ast_Kind_Param) + && sub->addr->type->kind == Type_Kind_Array) { + emit_local_location(mod, &code, (AstLocal *) sub->addr, &offset); + } else if (sub->addr->kind == Ast_Kind_Memres + && sub->addr->type->kind != Type_Kind_Array) { + emit_memory_reservation_location(mod, &code, (AstMemRes *) sub->addr); + } else { + emit_expression(mod, &code, sub->addr); + } + + WI(sub->token, WI_PTR_ADD); + + *offset_return += offset; + + *pcode = code; +} + +EMIT_FUNC(field_access_location, AstFieldAccess* field, u64* offset_return) { + bh_arr(WasmInstruction) code = *pcode; + + u64 offset = field->offset; + AstTyped* source_expr = field->expr; + while (source_expr->kind == Ast_Kind_Field_Access + && type_is_structlike_strict(source_expr->type)) { + offset += ((AstFieldAccess *) source_expr)->offset; + source_expr = (AstTyped *) ((AstFieldAccess *) source_expr)->expr; + } + + if (source_expr->kind == Ast_Kind_Subscript + && source_expr->type->kind != Type_Kind_Pointer) { + u64 o2 = 0; + emit_subscript_location(mod, &code, (AstSubscript *) source_expr, &o2); + offset += o2; + + } else if ((source_expr->kind == Ast_Kind_Local || source_expr->kind == Ast_Kind_Param) + && source_expr->type->kind != Type_Kind_Pointer) { + u64 o2 = 0; + emit_local_location(mod, &code, (AstLocal *) source_expr, &o2); + offset += o2; + + } else if (source_expr->kind == Ast_Kind_Memres + && source_expr->type->kind != Type_Kind_Pointer) { + emit_memory_reservation_location(mod, &code, (AstMemRes *) source_expr); + + } else { + emit_expression(mod, &code, source_expr); + } + + *offset_return = offset; + + *pcode = code; +} + +EMIT_FUNC(memory_reservation_location, AstMemRes* memres) { + bh_arr(WasmInstruction) code = *pcode; + + if (memres->threadlocal) { + u64 tls_base_idx = bh_imap_get(&mod->index_map, (u64) &builtin_tls_base); + WID(NULL, WI_PTR_CONST, memres->tls_offset); + WIL(NULL, WI_GLOBAL_GET, tls_base_idx); + WI(NULL, WI_PTR_ADD); + + } else { + // :ProperLinking + assert(memres->data_id != 0); + emit_data_relocation(mod, &code, memres->data_id); + } + + *pcode = code; +} + +EMIT_FUNC(local_location, AstLocal* local, u64* offset_return) { + bh_arr(WasmInstruction) code = *pcode; + + u64 local_offset = (u64) bh_imap_get(&mod->local_map, (u64) local); + + if (local_offset & LOCAL_IS_WASM) { + // This is a weird condition but it is relied on in a couple places including + // passing non-simple structs by value. -brendanfh 2020/09/18 + WIL(NULL, WI_LOCAL_GET, local_offset); + + } else { + WIL(NULL, WI_LOCAL_GET, mod->stack_base_idx); + + *offset_return += local_offset; + } + + *pcode = code; +} + +EMIT_FUNC(struct_lval, AstTyped* lval) { + bh_arr(WasmInstruction) code = *pcode; + + assert(type_is_structlike_strict(lval->type)); + + u64 offset = 0; + emit_location_return_offset(mod, &code, lval, &offset); + emit_compound_store(mod, &code, lval->type, offset, 1); + + *pcode = code; +} + +EMIT_FUNC(compound_load, Type* type, u64 offset) { + bh_arr(WasmInstruction) code = *pcode; + i32 mem_count = type_linear_member_count(type); + TypeWithOffset two; + + if (mem_count == 1) { + type_linear_member_lookup(type, 0, &two); + emit_load_instruction(mod, &code, two.type, offset + two.offset); // two.offset should be 0 + } else { + u64 tmp_idx = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + WIL(NULL, WI_LOCAL_TEE, tmp_idx); + + fori (i, 0, mem_count) { + type_linear_member_lookup(type, i, &two); + if (i != 0) WIL(NULL, WI_LOCAL_GET, tmp_idx); + emit_load_instruction(mod, &code, two.type, offset + two.offset); + } + + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + } + + *pcode = code; + return; +} + +EMIT_FUNC(compound_store, Type* type, u64 offset, b32 location_first) { + bh_arr(WasmInstruction) code = *pcode; + + TypeWithOffset two; + + u64 loc_idx = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + if (location_first) WIL(NULL, WI_LOCAL_SET, loc_idx); + + i32 elem_count = type_linear_member_count(type); + u64 *temp_locals = bh_alloc_array(global_scratch_allocator, u64, elem_count); + + forir (i, elem_count - 1, 0) { + type_linear_member_lookup(type, i, &two); + + WasmType wt = onyx_type_to_wasm_type(two.type); + temp_locals[i] = local_raw_allocate(mod->local_alloc, wt); + WIL(NULL, WI_LOCAL_SET, temp_locals[i]); + } + + if (!location_first) WIL(NULL, WI_LOCAL_SET, loc_idx); + + fori (i, 0, elem_count) { + type_linear_member_lookup(type, i, &two); + + u64 tmp_idx = temp_locals[i]; + WIL(NULL, WI_LOCAL_GET, loc_idx); + WIL(NULL, WI_LOCAL_GET, tmp_idx); + emit_store_instruction(mod, &code, two.type, offset + two.offset); + + WasmType wt = onyx_type_to_wasm_type(two.type); + local_raw_free(mod->local_alloc, wt); + } + + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + + // This shouldn't be necessary because the scratch allocator doesn't free. + bh_free(global_scratch_allocator, temp_locals); + + *pcode = code; +} + +EMIT_FUNC(struct_literal, AstStructLiteral* sl) { + bh_arr(WasmInstruction) code = *pcode; + + bh_arr_each(AstTyped *, val, sl->args.values) { + emit_expression(mod, &code, *val); + } + + *pcode = code; +} + +EMIT_FUNC(array_store, Type* type, u32 offset) { + assert(type->kind == Type_Kind_Array); + bh_arr(WasmInstruction) code = *pcode; + + Type* elem_type = type; + u32 elem_count = 1; + while (elem_type->kind == Type_Kind_Array) { + elem_count *= elem_type->Array.count; + elem_type = elem_type->Array.elem; + } + u32 elem_size = type_size_of(elem_type); + + u64 lptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + u64 rptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + WIL(NULL, WI_LOCAL_SET, rptr_local); + WIL(NULL, WI_LOCAL_SET, lptr_local); + + WIL(NULL, WI_LOCAL_GET, rptr_local); + WID(NULL, WI_I32_CONST, 0); + WI(NULL, WI_I32_NE); + emit_enter_structured_block(mod, &code, SBT_Basic_If, NULL); + + // + // CLEANUP: Most of these cases could be much shorter if they used existing intrinsics. + // + if (elem_count <= 2) { + // Inline copying for a small number of elements. It still may be faster to do this in a tight loop. + + fori (i, 0, elem_count) { + if (bh_arr_last(code).type == WI_LOCAL_SET && (u64) bh_arr_last(code).data.l == lptr_local) + bh_arr_last(code).type = WI_LOCAL_TEE; + else + WIL(NULL, WI_LOCAL_GET, lptr_local); + + WIL(NULL, WI_LOCAL_GET, rptr_local); + emit_load_instruction(mod, &code, elem_type, i * elem_size); + + emit_store_instruction(mod, &code, elem_type, i * elem_size + offset); + } + + } else if (context.options->use_post_mvp_features) { + // Use a simple memory copy if it is available. This may be what happens in the case below too at a later time. + + if (bh_arr_last(code).type == WI_LOCAL_SET && (u64) bh_arr_last(code).data.l == lptr_local) + bh_arr_last(code).type = WI_LOCAL_TEE; + else + WIL(NULL, WI_LOCAL_GET, lptr_local); + + if (offset != 0) { + WIL(NULL, WI_PTR_CONST, offset); + WI(NULL, WI_PTR_ADD); + } + + WIL(NULL, WI_LOCAL_GET, rptr_local); + WIL(NULL, WI_I32_CONST, elem_count * elem_size); + WI(NULL, WI_MEMORY_COPY); + + } else { + // Emit a loop that copies the memory. This could be switched to a tight loop that just copies word per word. + + u64 offset_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + WIL(NULL, WI_PTR_CONST, 0); + WIL(NULL, WI_LOCAL_SET, offset_local); + + WID(NULL, WI_BLOCK_START, 0x40); + WID(NULL, WI_LOOP_START, 0x40); + WIL(NULL, WI_LOCAL_GET, offset_local); + WIL(NULL, WI_LOCAL_GET, lptr_local); + WI(NULL, WI_PTR_ADD); + + WIL(NULL, WI_LOCAL_GET, offset_local); + WIL(NULL, WI_LOCAL_GET, rptr_local); + WI(NULL, WI_PTR_ADD); + + emit_load_instruction(mod, &code, elem_type, 0); + emit_store_instruction(mod, &code, elem_type, offset); + + WIL(NULL, WI_LOCAL_GET, offset_local); + WIL(NULL, WI_PTR_CONST, elem_size); + WI(NULL, WI_PTR_ADD); + WIL(NULL, WI_LOCAL_TEE, offset_local); + + WIL(NULL, WI_PTR_CONST, elem_count * elem_size); + WI(NULL, WI_PTR_GE); + WID(NULL, WI_COND_JUMP, 0x01); + + WID(NULL, WI_JUMP, 0x00); + + WI(NULL, WI_LOOP_END); + WI(NULL, WI_BLOCK_END); + + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + } + + WI(NULL, WI_ELSE); + + { // If the source ptr is null (0), then just copy in 0 bytes. + WIL(NULL, WI_LOCAL_GET, lptr_local); + if (offset != 0) { + WIL(NULL, WI_PTR_CONST, offset); + WI(NULL, WI_PTR_ADD); + } + + WIL(NULL, WI_I32_CONST, 0); + + WIL(NULL, WI_I32_CONST, elem_count * elem_size); + + if (context.options->use_post_mvp_features) { + WI(NULL, WI_MEMORY_FILL); + } else { + emit_intrinsic_memory_fill(mod, &code); + } + } + + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + + emit_leave_structured_block(mod, &code); + *pcode = code; + return; +} + +EMIT_FUNC(array_literal, AstArrayLiteral* al) { + bh_arr(WasmInstruction) code = *pcode; + + u64 local_offset = (u64) bh_imap_get(&mod->local_map, (u64) al); + assert((local_offset & LOCAL_IS_WASM) == 0); + + assert(al->type->kind == Type_Kind_Array); + + u32 elem_size = type_size_of(al->type->Array.elem); + + fori (i, 0, al->type->Array.count) { + WIL(al->token, WI_LOCAL_GET, mod->stack_base_idx); + emit_expression(mod, &code, al->values[i]); + emit_store_instruction(mod, &code, al->type->Array.elem, local_offset + i * elem_size); + } + + WIL(al->token, WI_LOCAL_GET, mod->stack_base_idx); + WIL(al->token, WI_PTR_CONST, local_offset); + WI(al->token, WI_PTR_ADD); + + *pcode = code; +} + +EMIT_FUNC(range_literal, AstRangeLiteral* range) { + bh_arr(WasmInstruction) code = *pcode; + + emit_expression(mod, &code, range->low); + emit_expression(mod, &code, range->high); + emit_expression(mod, &code, range->step); + + *pcode = code; +} + +EMIT_FUNC(if_expression, AstIfExpression* if_expr) { + bh_arr(WasmInstruction) code = *pcode; + + u64 offset = 0; + u64 result_local = local_allocate(mod->local_alloc, (AstTyped *) if_expr); + b32 result_is_local = (b32) ((result_local & LOCAL_IS_WASM) != 0); + bh_imap_put(&mod->local_map, (u64) if_expr, result_local); + + emit_expression(mod, &code, if_expr->cond); + + emit_enter_structured_block(mod, &code, SBT_Basic_If, if_expr->token); + if (!result_is_local) emit_local_location(mod, &code, (AstLocal *) if_expr, &offset); + + emit_expression(mod, &code, if_expr->true_expr); + + if (!result_is_local) emit_store_instruction(mod, &code, if_expr->type, offset); + else WIL(if_expr->token, WI_LOCAL_SET, result_local); + + offset = 0; + WI(if_expr->token, WI_ELSE); + if (!result_is_local) emit_local_location(mod, &code, (AstLocal *) if_expr, &offset); + + emit_expression(mod, &code, if_expr->false_expr); + + if (!result_is_local) emit_store_instruction(mod, &code, if_expr->type, offset); + else WIL(if_expr->token, WI_LOCAL_SET, result_local); + + emit_leave_structured_block(mod, &code); + + offset = 0; + if (!result_is_local) { + emit_local_location(mod, &code, (AstLocal *) if_expr, &offset); + emit_load_instruction(mod, &code, if_expr->type, offset); + + } else { + WIL(if_expr->token, WI_LOCAL_GET, result_local); + } + + local_free(mod->local_alloc, (AstTyped *) if_expr); + + *pcode = code; +} + +EMIT_FUNC(do_block, AstDoBlock* doblock) { + bh_arr(WasmInstruction) code = *pcode; + + u64 result_local = local_allocate(mod->local_alloc, (AstTyped *) doblock); + b32 result_is_local = (b32) ((result_local & LOCAL_IS_WASM) != 0); + + bh_imap_put(&mod->local_map, (u64) doblock, result_local); + bh_arr_push(mod->return_location_stack, (AstLocal *) doblock); + + emit_block(mod, &code, doblock->block, 1); + + u64 offset = 0; + if (!result_is_local) { + emit_local_location(mod, &code, (AstLocal *) doblock, &offset); + emit_load_instruction(mod, &code, doblock->type, offset); + + } else { + WIL(doblock->block->token, WI_LOCAL_GET, result_local); + } + + bh_arr_pop(mod->return_location_stack); + local_free(mod->local_alloc, (AstTyped *) doblock); + + *pcode = code; +} + +EMIT_FUNC(location_return_offset, AstTyped* expr, u64* offset_return) { + bh_arr(WasmInstruction) code = *pcode; + + expr = (AstTyped *) strip_aliases((AstNode *) expr); + + switch (expr->kind) { + case Ast_Kind_Param: + case Ast_Kind_Local: + case Ast_Kind_Array_Literal: + case Ast_Kind_Struct_Literal: { + emit_local_location(mod, &code, (AstLocal *) expr, offset_return); + break; + } + + case Ast_Kind_Dereference: { + emit_expression(mod, &code, ((AstDereference *) expr)->expr); + *offset_return = 0; + break; + } + + case Ast_Kind_Subscript: { + AstSubscript* sub = (AstSubscript *) expr; + emit_subscript_location(mod, &code, sub, offset_return); + break; + } + + case Ast_Kind_Field_Access: { + AstFieldAccess* field = (AstFieldAccess *) expr; + emit_field_access_location(mod, &code, field, offset_return); + break; + } + + case Ast_Kind_Memres: { + AstMemRes* memres = (AstMemRes *) expr; + emit_memory_reservation_location(mod, &code, memres); + *offset_return = 0; + break; + } + + default: { + onyx_report_error(expr->token->pos, Error_Critical, "Unable to generate location for '%s'.", onyx_ast_node_kind_string(expr->kind)); + break; + } + } + + *pcode = code; +} + +EMIT_FUNC(location, AstTyped* expr) { + bh_arr(WasmInstruction) code = *pcode; + + u64 offset = 0; + emit_location_return_offset(mod, &code, expr, &offset); + if (offset != 0) { + WID(NULL, WI_PTR_CONST, offset); + WI(NULL, WI_PTR_ADD); + } + + *pcode = code; +} + +EMIT_FUNC(expression, AstTyped* expr) { + bh_arr(WasmInstruction) code = *pcode; + + if (node_is_type((AstNode *) expr)) { + AstType* type = (AstType *) expr; + if (type->flags & Ast_Flag_Expr_Ignored) return; + + if (type->type_id != 0) { + WID(NULL, WI_I32_CONST, ((AstType *) expr)->type_id); + } else { + Type* t = type_build_from_ast(context.ast_alloc, type); + WID(NULL, WI_I32_CONST, t->id); + } + + + *pcode = code; + return; + } + + switch (expr->kind) { + case Ast_Kind_Param: { + AstLocal* param = (AstLocal *) expr; + u64 localidx = bh_imap_get(&mod->local_map, (u64) param); + + switch (type_get_param_pass(param->type)) { + case Param_Pass_By_Value: { + if (type_is_structlike_strict(expr->type)) { + u32 mem_count = type_structlike_mem_count(expr->type); + fori (idx, 0, mem_count) WIL(NULL, WI_LOCAL_GET, localidx + idx); + + } else { + assert(localidx & LOCAL_IS_WASM); + WIL(NULL, WI_LOCAL_GET, localidx); + } + break; + } + + case Param_Pass_By_Implicit_Pointer: { + assert(localidx & LOCAL_IS_WASM); + WIL(NULL, WI_LOCAL_GET, localidx); + emit_load_instruction(mod, &code, expr->type, 0); + break; + } + + default: assert(0); + } + + break; + } + + case Ast_Kind_Local: { + u64 tmp = bh_imap_get(&mod->local_map, (u64) expr); + + if (tmp & LOCAL_IS_WASM) { + if (bh_arr_last(code).type == WI_LOCAL_SET && (u64) bh_arr_last(code).data.l == tmp) { + bh_arr_last(code).type = WI_LOCAL_TEE; + } else { + WIL(NULL, WI_LOCAL_GET, tmp); + } + + } else { + u64 offset = 0; + emit_local_location(mod, &code, (AstLocal *) expr, &offset); + + if (expr->type->kind != Type_Kind_Array) { + emit_load_instruction(mod, &code, expr->type, offset); + } else if (offset != 0) { + WID(NULL, WI_PTR_CONST, offset); + WI(NULL, WI_PTR_ADD); + } + } + + break; + } + + case Ast_Kind_Global: { + i32 globalidx = (i32) bh_imap_get(&mod->index_map, (u64) expr); + + WID(NULL, WI_GLOBAL_GET, globalidx); + break; + } + + case Ast_Kind_NumLit: { + AstNumLit* lit = (AstNumLit *) expr; + WasmType lit_type = onyx_type_to_wasm_type(lit->type); + WasmInstruction instr = { WI_NOP, 0 }; + + if (lit_type == WASM_TYPE_INT32) { + instr.type = WI_I32_CONST; + instr.data.i1 = lit->value.i; + } else if (lit_type == WASM_TYPE_INT64) { + instr.type = WI_I64_CONST; + instr.data.l = lit->value.l; + } else if (lit_type == WASM_TYPE_FLOAT32) { + instr.type = WI_F32_CONST; + instr.data.f = lit->value.f; + } else if (lit_type == WASM_TYPE_FLOAT64) { + instr.type = WI_F64_CONST; + instr.data.d = lit->value.d; + } + + WIR(NULL, instr); + break; + } + + case Ast_Kind_StrLit: { + // :ProperLinking + AstStrLit *strlit = (AstStrLit *) expr; + assert(strlit->data_id > 0); + emit_data_relocation(mod, &code, strlit->data_id); + + if (strlit->is_cstr == 0) + WID(NULL, WI_I32_CONST, strlit->length); + break; + } + + case Ast_Kind_Struct_Literal: { + emit_struct_literal(mod, &code, (AstStructLiteral *) expr); + break; + } + + case Ast_Kind_Array_Literal: { + emit_local_allocation(mod, &code, expr); + emit_array_literal(mod, &code, (AstArrayLiteral *) expr); + break; + } + + case Ast_Kind_Range_Literal: { + emit_range_literal(mod, &code, (AstRangeLiteral *) expr); + break; + } + + case Ast_Kind_Function: { + i32 elemidx = get_element_idx(mod, (AstFunction *) expr); + + WID(NULL, WI_I32_CONST, elemidx); + break; + } + + case Ast_Kind_Block: emit_block(mod, &code, (AstBlock *) expr, 1); break; + case Ast_Kind_Do_Block: emit_do_block(mod, &code, (AstDoBlock *) expr); break; + case Ast_Kind_Call: emit_call(mod, &code, (AstCall *) expr); break; + case Ast_Kind_Argument: emit_expression(mod, &code, ((AstArgument *) expr)->value); break; + case Ast_Kind_Intrinsic_Call: emit_intrinsic_call(mod, &code, (AstCall *) expr); break; + case Ast_Kind_Binary_Op: emit_binop(mod, &code, (AstBinaryOp *) expr); break; + case Ast_Kind_Unary_Op: emit_unaryop(mod, &code, (AstUnaryOp *) expr); break; + case Ast_Kind_Alias: emit_expression(mod, &code, ((AstAlias *) expr)->alias); break; + + case Ast_Kind_Address_Of: { + AstAddressOf* aof = (AstAddressOf *) expr; + + if (node_is_addressable_literal((AstNode *) aof->expr)) { + aof->expr->flags |= Ast_Flag_Decl_Followed_By_Init; + aof->expr->flags |= Ast_Flag_Address_Taken; + emit_local_allocation(mod, &code, aof->expr); + emit_location(mod, &code, aof->expr); + emit_expression(mod, &code, aof->expr); + emit_store_instruction(mod, &code, aof->expr->type, 0); + } + + emit_location(mod, &code, aof->expr); + break; + } + + case Ast_Kind_Dereference: { + AstDereference* deref = (AstDereference *) expr; + emit_expression(mod, &code, deref->expr); + emit_load_instruction(mod, &code, deref->type, 0); + break; + } + + case Ast_Kind_Subscript: { + AstSubscript* sub = (AstSubscript *) expr; + u64 offset = 0; + emit_subscript_location(mod, &code, sub, &offset); + emit_load_instruction(mod, &code, sub->type, offset); + break; + } + + case Ast_Kind_Field_Access: { + AstFieldAccess* field = (AstFieldAccess* ) expr; + + if (field->expr->kind == Ast_Kind_Param) { + if (type_get_param_pass(field->expr->type) == Param_Pass_By_Value && !type_is_pointer(field->expr->type)) { + u64 localidx = bh_imap_get(&mod->local_map, (u64) field->expr) + field->idx; + assert(localidx & LOCAL_IS_WASM); + WIL(NULL, WI_LOCAL_GET, localidx); + break; + } + } + + if (is_lval((AstNode *) field->expr) || type_is_pointer(field->expr->type)) { + u64 offset = 0; + emit_field_access_location(mod, &code, field, &offset); + emit_load_instruction(mod, &code, field->type, offset); + + } else { + emit_expression(mod, &code, field->expr); + + i32 idx = type_get_idx_of_linear_member_with_offset(field->expr->type, field->offset); + i32 field_linear_members = type_linear_member_count(field->type); + i32 total_linear_members = type_linear_member_count(field->expr->type); + + if (idx == 0) { + // Easy case: the member is the first one and all other members just have to be dropped. + fori (i, 0, total_linear_members - field_linear_members) WI(NULL, WI_DROP); + + } else { + // Tough case: Stack shuffling to make the member the only thing on the stack. + // This is very similar to the compound_load/compound_store procedures but it is different enough + // that I cannot find a good way to factor them all without just introducing a ton of complexity. + fori (i, 0, total_linear_members - idx - field_linear_members) WI(NULL, WI_DROP); + + u64 *temporaries = bh_alloc_array(global_scratch_allocator, u64, field_linear_members); + fori (i, 0, field_linear_members) temporaries[i] = 0; + + TypeWithOffset two = { 0 }; + forir (i, field_linear_members - 1, 0) { + type_linear_member_lookup(field->type, i, &two); + + WasmType wt = onyx_type_to_wasm_type(two.type); + temporaries[i] = local_raw_allocate(mod->local_alloc, wt); + WIL(NULL, WI_LOCAL_SET, temporaries[i]); + } + + fori (i, 0, idx) WI(NULL, WI_DROP); + + fori (i, 0, field_linear_members) { + type_linear_member_lookup(field->type, i, &two); + + WIL(NULL, WI_LOCAL_GET, temporaries[i]); + + WasmType wt = onyx_type_to_wasm_type(two.type); + local_raw_free(mod->local_alloc, wt); + } + + bh_free(global_scratch_allocator, temporaries); + } + } + + break; + } + + case Ast_Kind_Slice: { + AstSubscript* sl = (AstSubscript *) expr; + + emit_expression(mod, &code, sl->expr); + + u64 lo_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32); + u64 hi_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32); + + WI(NULL, WI_DROP); + WIL(NULL, WI_LOCAL_SET, hi_local); + WIL(NULL, WI_LOCAL_TEE, lo_local); + if (sl->elem_size != 1) { + WID(NULL, WI_I32_CONST, sl->elem_size); + WI(NULL, WI_I32_MUL); + } + emit_expression(mod, &code, sl->addr); + WI(NULL, WI_I32_ADD); + WIL(NULL, WI_LOCAL_GET, hi_local); + WIL(NULL, WI_LOCAL_GET, lo_local); + WI(NULL, WI_I32_SUB); + + local_raw_free(mod->local_alloc, lo_local); + local_raw_free(mod->local_alloc, hi_local); + break; + } + + case Ast_Kind_Size_Of: { + AstSizeOf* so = (AstSizeOf *) expr; + WID(NULL, WI_I32_CONST, so->size); + break; + } + + case Ast_Kind_Align_Of: { + AstAlignOf* ao = (AstAlignOf *) expr; + WID(NULL, WI_I32_CONST, ao->alignment); + break; + } + + case Ast_Kind_Enum_Value: { + AstEnumValue* ev = (AstEnumValue *) expr; + AstNumLit * num = (AstNumLit *) ev->value; + assert(num->kind == Ast_Kind_NumLit); + + WasmType backing_type = onyx_type_to_wasm_type(ev->type); + if (backing_type == WASM_TYPE_INT32) WID(NULL, WI_I32_CONST, num->value.i); + else if (backing_type == WASM_TYPE_INT64) WID(NULL, WI_I64_CONST, num->value.l); + else onyx_report_error(ev->token->pos, Error_Critical, "Invalid backing type for enum."); + break; + } + + case Ast_Kind_Memres: { + AstMemRes* memres = (AstMemRes *) expr; + emit_memory_reservation_location(mod, &code, memres); + emit_load_instruction(mod, &code, memres->type, 0); + break; + } + + case Ast_Kind_File_Contents: { + AstFileContents* fc = (AstFileContents *) expr; + + assert(fc->data_id > 0); + assert(fc->size > 0); + + // :ProperLinking + emit_data_relocation(mod, &code, fc->data_id); + WID(NULL, WI_I32_CONST, fc->size); + break; + } + + case Ast_Kind_Compound: { + AstCompound* compound = (AstCompound *) expr; + + bh_arr_each(AstTyped *, expr, compound->exprs) { + emit_expression(mod, &code, *expr); + } + break; + } + + case Ast_Kind_Call_Site: { + AstCallSite* callsite = (AstCallSite *) expr; + + emit_expression(mod, &code, (AstTyped *) callsite->filename); + emit_expression(mod, &code, (AstTyped *) callsite->line); + emit_expression(mod, &code, (AstTyped *) callsite->column); + break; + } + + case Ast_Kind_If_Expression: { + AstIfExpression* if_expr = (AstIfExpression *) expr; + emit_if_expression(mod, &code, if_expr); + break; + } + + case Ast_Kind_Switch_Case: { + // This error message should be moved to checking, but this is the + // best place to do it right now. + onyx_report_error(expr->token->pos, Error_Critical, "'case' statements are only allowed in a 'switch' statement."); + break; + } + + case Ast_Kind_Code_Block: { + // Like above, this error message should be moved to checking, but + // this is the best place to do it right now. + onyx_report_error(expr->token->pos, Error_Critical, "'#quote' blocks are only to be used at compile-time. Using them as a runtime value is not allowed."); + break; + } + + case Ast_Kind_Foreign_Block: { + AstForeignBlock *fb = (AstForeignBlock *) expr; + WID(NULL, WI_I32_CONST, fb->foreign_block_number); + break; + } + + case Ast_Kind_Zero_Value: { + AstZeroValue *zv = (AstZeroValue *) expr; + assert(zv->type); + emit_zero_value_for_type(mod, &code, zv->type, zv->token); + break; + } + + default: + bh_printf("Unhandled case: %d\n", expr->kind); + DEBUG_HERE; + assert(0); + } + + if ((expr->flags & Ast_Flag_Expr_Ignored) != 0 && !type_results_in_void(expr->type)) { + i32 mem_count = 1; + if (type_is_compound(expr->type)) mem_count = type_linear_member_count(expr->type); + fori (i, 0, mem_count) WI(NULL, WI_DROP); + } + + *pcode = code; +} + +static const WasmInstructionType cast_map[][12] = { + // I8 U8 I16 U16 I32 U32 I64 U64 F32 F64 PTR + /* I8 */ { WI_NOP, WI_NOP, WI_I32_EXTEND_8_S, WI_NOP, WI_I32_EXTEND_8_S, WI_NOP, WI_I64_FROM_I32_S, WI_I64_FROM_I32_S, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE }, + /* U8 */ { WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_I64_FROM_I32_U, WI_I64_FROM_I32_U, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE }, + /* I16 */ { WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_I32_EXTEND_16_S, WI_NOP, WI_I64_FROM_I32_S, WI_I64_FROM_I32_S, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE }, + /* U16 */ { WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_I64_FROM_I32_U, WI_I64_FROM_I32_U, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE }, + /* I32 */ { WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_I64_FROM_I32_S, WI_I64_FROM_I32_S, WI_F32_FROM_I32_S, WI_F64_FROM_I32_S, WI_NOP, WI_NOP }, + /* U32 */ { WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_I64_FROM_I32_U, WI_I64_FROM_I32_U, WI_F32_FROM_I32_U, WI_F64_FROM_I32_U, WI_NOP, WI_NOP }, + /* I64 */ { WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_NOP, WI_NOP, WI_F32_FROM_I64_S, WI_F64_FROM_I64_S, WI_I32_FROM_I64, WI_UNREACHABLE }, + /* U64 */ { WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_NOP, WI_NOP, WI_F32_FROM_I64_U, WI_F64_FROM_I64_U, WI_I32_FROM_I64, WI_UNREACHABLE }, + /* F32 */ { WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_I32_FROM_F32_S, WI_I32_FROM_F32_U, WI_I64_FROM_F32_S, WI_I64_FROM_F32_U, WI_NOP, WI_F64_FROM_F32, WI_UNREACHABLE, WI_UNREACHABLE }, + /* F64 */ { WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_I32_FROM_F64_S, WI_I32_FROM_F64_U, WI_I64_FROM_F64_S, WI_I64_FROM_F64_U, WI_F32_FROM_F64, WI_NOP, WI_UNREACHABLE, WI_UNREACHABLE }, + /* PTR */ { WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_NOP, WI_NOP, WI_I64_FROM_I32_U, WI_I64_FROM_I32_U, WI_UNREACHABLE, WI_UNREACHABLE, WI_NOP, WI_UNREACHABLE }, + /* TYP */ { WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_NOP, WI_NOP, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_NOP }, +}; + +EMIT_FUNC(cast, AstUnaryOp* cast) { + bh_arr(WasmInstruction) code = *pcode; + + emit_expression(mod, &code, cast->expr); + + Type* from = cast->expr->type; + Type* to = cast->type; + if (from->kind == Type_Kind_Enum) from = from->Enum.backing; + if (to->kind == Type_Kind_Enum) to = to->Enum.backing; + + if (type_is_simd(from) && type_is_simd(to)) { + *pcode = code; + return; + } + + if (to->kind == Type_Kind_Basic && to->Basic.kind == Basic_Kind_Void) { + WI(NULL, WI_DROP); + *pcode = code; + return; + } + + if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_Array) { + WID(NULL, WI_I32_CONST, from->Array.count); + *pcode = code; + return; + } + + if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_DynArray) { + WI(NULL, WI_DROP); + WI(NULL, WI_DROP); + WI(NULL, WI_DROP); + *pcode = code; + return; + } + + if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_Slice) { + // Nothing needs to be done because they are identical + *pcode = code; + return; + } + + if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_VarArgs) { + // Nothing needs to be done because they are identical + *pcode = code; + return; + } + + if (to->kind == Type_Kind_Distinct || from->kind == Type_Kind_Distinct) { + // Nothing needs to be done because they are identical + *pcode = code; + return; + } + + i32 fromidx = -1, toidx = -1; + if (from->Basic.flags & Basic_Flag_Pointer || from->kind == Type_Kind_Array) { + fromidx = 10; + } + else if (from->Basic.flags & Basic_Flag_Integer) { + b32 unsign = (from->Basic.flags & Basic_Flag_Unsigned) != 0; + + fromidx = log2_dumb(from->Basic.size) * 2 + unsign; + } + else if (from->Basic.flags & Basic_Flag_Float) { + if (from->Basic.size == 4) fromidx = 8; + else if (from->Basic.size == 8) fromidx = 9; + } + else if (from->Basic.flags & Basic_Flag_Boolean) { + fromidx = 0; + } + else if (from->Basic.flags & Basic_Flag_Type_Index) { + fromidx = 11; + } + + if (to->Basic.flags & Basic_Flag_Pointer || to->kind == Type_Kind_Array) { + toidx = 10; + } + else if (to->Basic.flags & Basic_Flag_Integer) { + b32 unsign = (to->Basic.flags & Basic_Flag_Unsigned) != 0; + + toidx = log2_dumb(to->Basic.size) * 2 + unsign; + } + else if (to->Basic.flags & Basic_Flag_Float) { + if (to->Basic.size == 4) toidx = 8; + else if (to->Basic.size == 8) toidx = 9; + } + else if (to->Basic.flags & Basic_Flag_Boolean) { + toidx = 0; + } + else if (to->Basic.flags & Basic_Flag_Type_Index) { + toidx = 11; + } + + if (fromidx != -1 && toidx != -1) { + WasmInstructionType cast_op = cast_map[fromidx][toidx]; + assert(cast_op != WI_UNREACHABLE); + + if (cast_op != WI_NOP) { + WI(NULL, cast_op); + } + } + + *pcode = code; +} + +EMIT_FUNC(return, AstReturn* ret) { + bh_arr(WasmInstruction) code = *pcode; + + // If we have an expression to return, we see if it should be placed on the linear memory stack, or the WASM stack. + if (ret->expr) { + if (bh_arr_length(mod->return_location_stack) > 0) { + AstLocal* dest = bh_arr_last(mod->return_location_stack); + u64 dest_loc = bh_imap_get(&mod->local_map, (u64) dest); + b32 dest_is_local = (b32) ((dest_loc & LOCAL_IS_WASM) != 0); + + u64 offset = 0; + if (!dest_is_local) emit_local_location(mod, &code, dest, &offset); + + emit_expression(mod, &code, ret->expr); + + if (!dest_is_local) emit_store_instruction(mod, &code, dest->type, offset); + else WIL(NULL, WI_LOCAL_SET, dest_loc); + + } else if (mod->curr_cc == CC_Return_Stack) { + WIL(NULL, WI_LOCAL_GET, mod->stack_base_idx); + WID(NULL, WI_I32_CONST, type_size_of(ret->expr->type)); + WI(NULL, WI_I32_SUB); + + emit_expression(mod, &code, ret->expr); + emit_store_instruction(mod, &code, ret->expr->type, 0); + + } else { + emit_expression(mod, &code, ret->expr); + } + } + + // Clear the normal deferred statements + emit_deferred_stmts(mod, &code); + + i64 jump_label = get_structured_jump_label(mod, Jump_Type_Return, 1); + if (jump_label >= 0) { + WIL(NULL, WI_JUMP, jump_label); + + } else { + // Clear the rest of the deferred statements + if (bh_arr_length(mod->deferred_stmts) > 0) { + i32 i = bh_arr_length(mod->deferred_stmts) - 1; + while (i >= 0) { + emit_deferred_stmt(mod, &code, mod->deferred_stmts[i]); + i--; + } + } + + // Make a patch for the two instructions needed to restore the stack pointer + SUBMIT_PATCH(mod->stack_leave_patches, 0); + WI(NULL, WI_NOP); + WI(NULL, WI_NOP); + + WI(NULL, WI_RETURN); + } + + *pcode = code; +} + +EMIT_FUNC(stack_enter, u64 stacksize) { + bh_arr(WasmInstruction) code = *pcode; + + bh_align(stacksize, 16); + + u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top); + + // HACK: slightly... There will be space for 5 instructions + if (stacksize == 0) { + code[0] = (WasmInstruction) { WI_GLOBAL_GET, { .l = stack_top_idx } }; + code[1] = (WasmInstruction) { WI_LOCAL_SET, { .l = mod->stack_base_idx} }; + code[2] = (WasmInstruction) { WI_NOP, 0 }; + code[3] = (WasmInstruction) { WI_NOP, 0 }; + code[4] = (WasmInstruction) { WI_NOP, 0 }; + } else { + code[0] = (WasmInstruction) { WI_GLOBAL_GET, { .l = stack_top_idx } }; + code[1] = (WasmInstruction) { WI_LOCAL_TEE, { .l = mod->stack_base_idx} }; + code[2] = (WasmInstruction) { WI_I32_CONST, { .l = stacksize } }; + code[3] = (WasmInstruction) { WI_I32_ADD, 0 }; + code[4] = (WasmInstruction) { WI_GLOBAL_SET, { .l = stack_top_idx } }; + } + + *pcode = code; +} + +EMIT_FUNC(zero_value, WasmType wt) { + bh_arr(WasmInstruction) code = *pcode; + + switch (wt) { + case WASM_TYPE_INT32: WIL(NULL, WI_I32_CONST, 0); break; + case WASM_TYPE_INT64: WIL(NULL, WI_I64_CONST, 0); break; + case WASM_TYPE_FLOAT32: WIL(NULL, WI_F32_CONST, 0); break; + case WASM_TYPE_FLOAT64: WIL(NULL, WI_F64_CONST, 0); break; + case WASM_TYPE_VAR128: { + static u8 zero_v128[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + WIP(NULL, WI_V128_CONST, &zero_v128); + break; + } + } + + *pcode = code; +} + +EMIT_FUNC(zero_value_for_type, Type* type, OnyxToken* where) { + bh_arr(WasmInstruction) code = *pcode; + + if (type_is_structlike_strict(type)) { + i32 mem_count = type_linear_member_count(type); + TypeWithOffset two; + + fori (i, 0, mem_count) { + type_linear_member_lookup(type, i, &two); + emit_zero_value_for_type(mod, &code, two.type, where); + } + } + else if (type->kind == Type_Kind_Function) { + WID(NULL, WI_I32_CONST, mod->null_proc_func_idx); + } + else { + WasmType wt = onyx_type_to_wasm_type(type); + if (wt == WASM_TYPE_VOID) { + onyx_report_error(where->pos, Error_Critical, "Cannot produce a zero-value for this type."); + } + emit_zero_value(mod, &code, wt); + } + + *pcode = code; +} + +static i32 generate_type_idx(OnyxWasmModule* mod, Type* ft) { + if (ft->kind != Type_Kind_Function) return -1; + + static char type_repr_buf[128]; + char* t = type_repr_buf; + + Type** param_type = ft->Function.params; + i32 param_count = ft->Function.param_count; + i32 params_left = param_count; + + while (params_left-- > 0) { + if (type_get_param_pass(*param_type) == Param_Pass_By_Implicit_Pointer) { + *(t++) = (char) onyx_type_to_wasm_type(&basic_types[Basic_Kind_Rawptr]); + + } + else if (type_is_structlike_strict(*param_type)) { + u32 mem_count = type_structlike_mem_count(*param_type); + StructMember smem; + + fori (i, 0, mem_count) { + type_lookup_member_by_idx(*param_type, i, &smem); + *(t++) = (char) onyx_type_to_wasm_type(smem.type); + } + + param_count += mem_count - 1; + + } else { + *(t++) = (char) onyx_type_to_wasm_type(*param_type); + } + + param_type++; + } + *(t++) = ':'; + + // HACK: Slightly: the wasm type for structs has to be 0x00 + WasmType return_type = onyx_type_to_wasm_type(ft->Function.return_type); + if (ft->Function.return_type->kind == Type_Kind_Struct && !type_is_compound(ft->Function.return_type)) { + return_type = onyx_type_to_wasm_type(ft->Function.return_type->Struct.linear_members[0].type); + } + + *(t++) = (char) return_type; + *t = '\0'; + + i32 type_idx = 0; + i32 index = shgeti(mod->type_map, type_repr_buf); + if (index != -1) { + type_idx = mod->type_map[index].value; + } else { + // NOTE: Make a new type + WasmFuncType* type = (WasmFuncType*) bh_alloc(mod->allocator, sizeof(WasmFuncType) + sizeof(WasmType) * param_count); + type->return_type = return_type; + type->param_count = param_count; + + fori (i, 0, type->param_count) { + type->param_types[i] = type_repr_buf[i]; + } + + bh_arr_push(mod->types, type); + + shput(mod->type_map, type_repr_buf, mod->next_type_idx); + type_idx = mod->next_type_idx; + mod->next_type_idx++; + } + + return type_idx; +} + +static i32 get_element_idx(OnyxWasmModule* mod, AstFunction* func) { + if (bh_imap_has(&mod->elem_map, (u64) func)) { + return bh_imap_get(&mod->elem_map, (u64) func); + } else { + i32 idx = mod->next_elem_idx; + bh_imap_put(&mod->elem_map, (u64) func, idx); + + i32 func_idx = bh_imap_get(&mod->index_map, (u64) func); + bh_arr_push(mod->elems, func_idx); + + mod->next_elem_idx++; + + return idx; + } +} + +static void emit_function(OnyxWasmModule* mod, AstFunction* fd) { + if (!should_emit_function(fd)) return; + + i32 type_idx = generate_type_idx(mod, fd->type); + + WasmFunc wasm_func = { 0 }; + wasm_func.type_idx = type_idx; + wasm_func.location = fd->token; + + bh_arr_new(mod->allocator, wasm_func.code, 16); + + i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) fd); + mod->current_func_idx = func_idx; + + debug_begin_function(mod, func_idx, fd->token, get_function_name(fd)); + + if (fd == builtin_initialize_data_segments && context.options->use_post_mvp_features) { + emit_initialize_data_segments_body(mod, &wasm_func.code); + + debug_emit_instruction(mod, NULL); + bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 })); + + bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func); + mod->current_func_idx = -1; + + debug_end_function(mod); + return; + } + + if (fd == builtin_run_init_procedures) { + emit_run_init_procedures(mod, &wasm_func.code); + + debug_emit_instruction(mod, NULL); + bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 })); + + bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func); + mod->current_func_idx = -1; + + debug_end_function(mod); + return; + } + + if (fd->body != NULL) { + mod->local_alloc = &wasm_func.locals; + + // NOTE: Generate the local map + u64 localidx = 0; + bh_arr_each(AstParam, param, fd->params) { + switch (type_get_param_pass(param->local->type)) { + case Param_Pass_By_Value: { + if (type_is_structlike_strict(param->local->type)) { + debug_introduce_symbol(mod, param->local->token, DSL_REGISTER, localidx | LOCAL_IS_WASM, param->local->type); + bh_imap_put(&mod->local_map, (u64) param->local, localidx | LOCAL_IS_WASM); + localidx += type_structlike_mem_count(param->local->type); + + break; + } + // fallthrough + } + + case Param_Pass_By_Implicit_Pointer: { + debug_introduce_symbol(mod, param->local->token, DSL_REGISTER, localidx | LOCAL_IS_WASM, param->local->type); + bh_imap_put(&mod->local_map, (u64) param->local, localidx++ | LOCAL_IS_WASM); + break; + } + + default: assert(0); + } + } + + mod->local_alloc->param_count = localidx; + + mod->curr_cc = type_function_get_cc(fd->type); + assert(mod->curr_cc != CC_Undefined); + + bh_arr_clear(mod->stack_leave_patches); + + debug_emit_instruction(mod, fd->token); + debug_emit_instruction(mod, fd->token); + debug_emit_instruction(mod, fd->token); + debug_emit_instruction(mod, fd->token); + debug_emit_instruction(mod, fd->token); + bh_arr_insert_end(wasm_func.code, 5); + fori (i, 0, 5) wasm_func.code[i] = (WasmInstruction) { WI_NOP, 0 }; + + // TODO: Emit debug info for the above instructions + + mod->stack_base_idx = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + + // Generate code + emit_function_body(mod, &wasm_func.code, fd); + + if (mod->local_alloc->max_stack > 0 || mod->curr_cc == CC_Return_Stack) { + emit_stack_enter(mod, &wasm_func.code, mod->local_alloc->max_stack); + + // Place all stack leaves in patch locations. These will (probably) all be + // right before a "return" instruction. + debug_emit_instruction(mod, NULL); + debug_emit_instruction(mod, NULL); + + u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top); + bh_arr_push(wasm_func.code, ((WasmInstruction) { WI_LOCAL_GET, { .l = mod->stack_base_idx } })); + bh_arr_push(wasm_func.code, ((WasmInstruction) { WI_GLOBAL_SET, { .l = stack_top_idx } })); + + bh_arr_each(PatchInfo, patch, mod->stack_leave_patches) { + wasm_func.code[patch->instruction_index + 0] = (WasmInstruction) { WI_LOCAL_GET, { .l = mod->stack_base_idx } }; + wasm_func.code[patch->instruction_index + 1] = (WasmInstruction) { WI_GLOBAL_SET, { .l = stack_top_idx } }; + } + } + } + + WasmFuncType* ft = mod->types[type_idx]; + emit_zero_value(mod, &wasm_func.code, ft->return_type); + + if (fd->closing_brace) { + debug_emit_instruction(mod, fd->closing_brace); + bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_NOP, 0x00 })); + } + + debug_emit_instruction(mod, NULL); + bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 })); + + bh_imap_clear(&mod->local_map); + + bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func); + mod->current_func_idx = -1; + + debug_end_function(mod); +} + +static void emit_foreign_function(OnyxWasmModule* mod, AstFunction* fd) { + if (!should_emit_function(fd)) return; + + i32 type_idx = generate_type_idx(mod, fd->type); + + WasmImport import = { + .kind = WASM_FOREIGN_FUNCTION, + .idx = type_idx, + .mod = bh_aprintf(global_heap_allocator, "%b", fd->foreign_module->text, fd->foreign_module->length), + .name = bh_aprintf(global_heap_allocator, "%b", fd->foreign_name->text, fd->foreign_name->length), + }; + + bh_arr_push(mod->imports, import); + return; +} + +static void emit_export_directive(OnyxWasmModule* mod, AstDirectiveExport* export) { + assert(export->export_name); + assert(export->export); + + token_toggle_end(export->export_name); + + i64 idx = bh_imap_get(&mod->index_map, (u64) export->export); + + WasmExport wasm_export; + wasm_export.idx = (i32) idx; + + switch (export->export->kind) { + case Ast_Kind_Function: wasm_export.kind = WASM_FOREIGN_FUNCTION; + break; + + case Ast_Kind_Global: wasm_export.kind = WASM_FOREIGN_GLOBAL; + break; + } + + shput(mod->exports, export->export_name->text, wasm_export); + mod->export_count++; + + token_toggle_end(export->export_name); + return; +} + +static void emit_global(OnyxWasmModule* module, AstGlobal* global) { + WasmType global_type = onyx_type_to_wasm_type(global->type); + + WasmGlobal glob = { + .type = global_type, + .mutable = (global->flags & Ast_Flag_Const) == 0, + .initial_value = NULL, + }; + + i32 global_idx = (i32) bh_imap_get(&module->index_map, (u64) global); + + bh_arr_new(global_heap_allocator, glob.initial_value, 1); + + switch (global_type) { + case WASM_TYPE_INT32: bh_arr_push(glob.initial_value, ((WasmInstruction) { WI_I32_CONST, 0 })); break; + case WASM_TYPE_INT64: bh_arr_push(glob.initial_value, ((WasmInstruction) { WI_I64_CONST, 0 })); break; + case WASM_TYPE_FLOAT32: bh_arr_push(glob.initial_value, ((WasmInstruction) { WI_F32_CONST, 0 })); break; + case WASM_TYPE_FLOAT64: bh_arr_push(glob.initial_value, ((WasmInstruction) { WI_F64_CONST, 0 })); break; + + default: assert(("Invalid global type", 0)); break; + } + + bh_arr_set_at(module->globals, global_idx, glob); + + if (global == &builtin_stack_top) + module->stack_top_ptr = &module->globals[global_idx].initial_value[0].data.i1; + + if (global == &builtin_heap_start) + module->heap_start_ptr = &module->globals[global_idx].initial_value[0].data.i1; + + if (global == &builtin_tls_size) + module->globals[global_idx].initial_value[0].data.i1 = module->next_tls_offset; +} + +static void emit_string_literal(OnyxWasmModule* mod, AstStrLit* strlit) { + + // NOTE: Allocating more than necessary, but there are no cases + // in a string literal that create more bytes than already + // existed. You can create less however ('\n' => 0x0a). + i8* strdata = bh_alloc_array(global_heap_allocator, i8, strlit->token->length + 1); + i32 length = string_process_escape_seqs(strdata, strlit->token->text, strlit->token->length); + + i32 index = shgeti(mod->string_literals, (char *) strdata); + if (index != -1) { + StrLitInfo sti = mod->string_literals[index].value; + strlit->data_id = sti.data_id; + strlit->length = sti.len; + + bh_free(global_heap_allocator, strdata); + return; + } + + // :ProperLinking + u32 actual_length = length + (strlit->is_cstr ? 1 : 0); + WasmDatum datum = { + .alignment = 1, + .length = actual_length, + .data = strdata, + }; + + strlit->data_id = emit_data_entry(mod, &datum); + strlit->length = length; + + // :ProperLinking + shput(mod->string_literals, (char *) strdata, ((StrLitInfo) { strlit->data_id, strlit->length })); +} + +static u32 emit_data_entry(OnyxWasmModule *mod, WasmDatum *datum) { + datum->offset_ = 0; + datum->id = NEXT_DATA_ID(mod); + bh_arr_push(mod->data, *datum); + return datum->id; +} + +static void emit_constexpr(ConstExprContext *ctx, AstTyped *node, u32 offset) { + if (!emit_constexpr_(ctx, node, offset)) { + onyx_report_error(node->token->pos, Error_Critical, + "Cannot generate constant data for '%s'.", + onyx_ast_node_kind_string(node->kind)); + } +} + +static b32 emit_constexpr_(ConstExprContext *ctx, AstTyped *node, u32 offset) { +#define CE(type, off) (*((type *) bh_pointer_add(ctx->data, offset + (off)))) + assert(ctx->data_id); + assert(ctx->data); + + b32 retval = 1; + node = (AstTyped *) strip_aliases((AstNode *) node); + + if (node_is_type((AstNode *) node)) { + Type* constructed_type = type_build_from_ast(context.ast_alloc, (AstType *) node); + CE(i32, 0) = constructed_type->id; + return 1; + } + + switch (node->kind) { + case Ast_Kind_Array_Literal: { + AstArrayLiteral* al = (AstArrayLiteral *) node; + + i32 i = 0; + i32 elem_size = type_size_of(al->type->Array.elem); + + bh_arr_each(AstTyped *, expr, al->values) { + retval &= emit_constexpr_(ctx, *expr, i * elem_size + offset); + i++; + } + + break; + } + + case Ast_Kind_Struct_Literal: { + AstStructLiteral* sl = (AstStructLiteral *) node; + + Type* sl_type = sl->type; + // ROBUSTNESS: Handle cases for slices and dynamic arrays + if (sl_type->kind != Type_Kind_Struct && sl_type->kind != Type_Kind_Slice && sl_type->kind != Type_Kind_DynArray) { + retval = 0; + break; + } + + i32 mem_count = type_structlike_mem_count(sl_type); + StructMember smem; + + fori (i, 0, mem_count) { + type_lookup_member_by_idx(sl_type, i, &smem); + retval &= emit_constexpr_(ctx, sl->args.values[i], smem.offset + offset); + } + + break; + } + + case Ast_Kind_StrLit: { + AstStrLit* sl = (AstStrLit *) node; + + // NOTE: This assumes the data_id and the length fields have been filled out + // by emit_string_literal. + if (POINTER_SIZE == 4) { + CE(u32, 0) = 0; + CE(u32, 4) = sl->length; + } else { + CE(u64, 0) = 0; + CE(u64, 8) = sl->length; + } + + assert(sl->data_id > 0); + + DatumPatchInfo patch; + patch.kind = Datum_Patch_Data; + patch.index = ctx->data_id; + patch.location = offset; + patch.data_id = sl->data_id; + patch.offset = 0; + bh_arr_push(ctx->module->data_patches, patch); + + break; + } + + case Ast_Kind_Enum_Value: { + AstEnumValue* ev = (AstEnumValue *) node; + retval &= emit_constexpr_(ctx, (AstTyped *) ev->value, offset); + break; + } + + case Ast_Kind_Function: { + AstFunction* func = (AstFunction *) node; + CE(u32, 0) = get_element_idx(ctx->module, func); + break; + } + + case Ast_Kind_Size_Of: { + AstSizeOf* so = (AstSizeOf *) node; + CE(u32, 0) = so->size; + break; + } + + case Ast_Kind_Align_Of: { + AstAlignOf* ao = (AstAlignOf *) node; + CE(u32, 0) = ao->alignment; + break; + } + + case Ast_Kind_Zero_Value: { + memset(bh_pointer_add(ctx->data, offset), 0, type_size_of(node->type)); + break; + } + + case Ast_Kind_NumLit: { + // NOTE: This makes a big assumption that we are running on a + // little endian machine, since WebAssembly is little endian + // by specification. This is probably a safe assumption, but + // should probably be fixed at some point. + // - brendanfh 2020/12/15 + + Type* effective_type = node->type; + + if (effective_type->kind == Type_Kind_Enum) + effective_type = effective_type->Enum.backing; + + switch (effective_type->Basic.kind) { + case Basic_Kind_Bool: + case Basic_Kind_I8: + case Basic_Kind_U8: + CE(i8, 0) = (i8) ((AstNumLit *) node)->value.i; + return retval; + + case Basic_Kind_I16: + case Basic_Kind_U16: + CE(i16, 0) = (i16) ((AstNumLit *) node)->value.i; + return retval; + + case Basic_Kind_I32: + case Basic_Kind_U32: + case Basic_Kind_Rawptr: + CE(i32, 0) = ((AstNumLit *) node)->value.i; + return retval; + + case Basic_Kind_I64: + case Basic_Kind_U64: + CE(i64, 0) = ((AstNumLit *) node)->value.l; + return retval; + + case Basic_Kind_F32: + CE(f32, 0) = ((AstNumLit *) node)->value.f; + return retval; + + case Basic_Kind_F64: + CE(f64, 0) = ((AstNumLit *) node)->value.d; + return retval; + + default: break; + } + + //fallthrough + } + + case Ast_Kind_Code_Block: break; + + default: retval = 0; + } + + return retval; + +#undef CE +} + +static void emit_memory_reservation(OnyxWasmModule* mod, AstMemRes* memres) { + // :ProperLinking + Type* effective_type = memres->type; + + u64 alignment = type_alignment_of(effective_type); + u64 size = type_size_of(effective_type); + + if (type_table_node != NULL && (AstMemRes *) type_table_node == memres) { + u64 table_location = build_type_table(mod); + memres->data_id = table_location; + return; + } + + if (foreign_blocks_node != NULL && (AstMemRes *) foreign_blocks_node == memres) { + u64 foreign_blocks_location = build_foreign_blocks(mod); + memres->data_id = foreign_blocks_location; + return; + } + + if (tagged_procedures_node != NULL && (AstMemRes *) tagged_procedures_node == memres) { + u64 tagged_procedures_location = build_tagged_procedures(mod); + memres->data_id = tagged_procedures_location; + return; + } + + if (memres->threadlocal) { + memres->tls_offset = mod->next_tls_offset; + bh_align(memres->tls_offset, alignment); + mod->next_tls_offset = memres->tls_offset + size; + + } else { + // :ProperLinking + u8* data = NULL; + if (memres->initial_value != NULL) { + assert(!memres->threadlocal); + data = bh_alloc(global_heap_allocator, size); + } + + WasmDatum datum = { + .alignment = alignment, + .length = size, + .data = data, + }; + memres->data_id = emit_data_entry(mod, &datum); + + if (memres->initial_value != NULL) { + ConstExprContext constexpr_ctx; + constexpr_ctx.module = mod; + constexpr_ctx.data = data; + constexpr_ctx.data_id = memres->data_id; + emit_constexpr(&constexpr_ctx, memres->initial_value, 0); + } + } +} + +static void emit_file_contents(OnyxWasmModule* mod, AstFileContents* fc) { + // INVESTIGATE: I think that filename should always be NULL when this function starts because + // a file contents entity is only processed once and therefore should only go through this step + // once. But somehow filename isn't NULL occasionally so I have to check for that... + // - brendanfh 2021/05/23 + if (fc->filename == NULL) { + const char* parent_file = fc->token->pos.filename; + if (parent_file == NULL) parent_file = "."; + + char* parent_folder = bh_path_get_parent(parent_file, global_scratch_allocator); + + OnyxToken *filename_token = fc->filename_expr->token; + + token_toggle_end(filename_token); + char* temp_fn = bh_alloc_array(global_scratch_allocator, char, filename_token->length); + i32 temp_fn_len = string_process_escape_seqs(temp_fn, filename_token->text, filename_token->length); + char* filename = bh_lookup_file(temp_fn, parent_folder, "", 0, NULL, 0); + fc->filename = bh_strdup(global_heap_allocator, filename); + token_toggle_end(filename_token); + } + + i32 index = shgeti(mod->loaded_file_info, fc->filename); + if (index != -1) { + StrLitInfo info = mod->loaded_file_info[index].value; + fc->data_id = info.data_id; + fc->size = info.len; + return; + } + + if (!bh_file_exists(fc->filename)) { + onyx_report_error(fc->token->pos, Error_Critical, + "Unable to open file for reading, '%s'.", + fc->filename); + return; + } + + // :RelativeFiles This should be relative to the current directory by default; However, + // if the filename is prefixed with a './' or '.\\' then it should be relative to the + // file in which is was inclded. The loaded file info above should probably use the full + // file path in order to avoid duplicates. + bh_file_contents contents = bh_file_read_contents(global_heap_allocator, fc->filename); + u8* actual_data = bh_alloc(global_heap_allocator, contents.length + 1); + u32 length = contents.length + 1; + memcpy(actual_data, contents.data, contents.length); + actual_data[contents.length] = 0; + bh_file_contents_free(&contents); + + WasmDatum datum = { + .alignment = 16, + .length = length, + .data = actual_data, + }; + fc->data_id = emit_data_entry(mod, &datum); + fc->size = length - 1; + + shput(mod->loaded_file_info, fc->filename, ((StrLitInfo) { + .data_id = fc->data_id, + .len = fc->size, + })); +} + +OnyxWasmModule onyx_wasm_module_create(bh_allocator alloc) { + OnyxWasmModule module = { + .allocator = alloc, + + .type_map = NULL, + .next_type_idx = 0, + .types = NULL, + + .funcs = NULL, + .next_func_idx = 0, + + .exports = NULL, + .export_count = 0, + + .imports = NULL, + + .globals = NULL, + .next_global_idx = 0, + + .data = NULL, + .data_patches = NULL, + + .next_tls_offset = 0, + .tls_size_ptr = NULL, + + .elems = NULL, + .next_elem_idx = 0, + + .needs_memory_section = 0, + .memory_min_size = 0, + .memory_max_size = 0, + + .structured_jump_target = NULL, + .return_location_stack = NULL, + .local_allocations = NULL, + .stack_leave_patches = NULL, + .deferred_stmts = NULL, + + .heap_start_ptr = NULL, + + .stack_top_ptr = NULL, + .stack_base_idx = 0, + + .foreign_function_count = 0, + + .null_proc_func_idx = -1, + + .libraries = NULL, + .library_paths = NULL, + + .foreign_blocks = NULL, + .next_foreign_block_idx = 0, + + .procedures_with_tags = NULL + }; + + bh_arena* eid = bh_alloc(global_heap_allocator, sizeof(bh_arena)); + bh_arena_init(eid, global_heap_allocator, 8196); + module.extended_instr_data = eid; + module.extended_instr_alloc = bh_arena_allocator(eid); + + bh_arr_new(alloc, module.types, 4); + bh_arr_new(alloc, module.funcs, 4); + bh_arr_new(alloc, module.imports, 4); + bh_arr_new(alloc, module.globals, 4); + bh_arr_new(alloc, module.data, 4); + bh_arr_new(alloc, module.elems, 4); + bh_arr_new(alloc, module.libraries, 4); + bh_arr_new(alloc, module.library_paths, 4); + + bh_arr_new(global_heap_allocator, module.return_location_stack, 4); + bh_arr_new(global_heap_allocator, module.structured_jump_target, 16); + bh_arr_set_length(module.structured_jump_target, 0); + + sh_new_arena(module.type_map); + sh_new_arena(module.exports); + sh_new_arena(module.loaded_file_info); + sh_new_arena(module.string_literals); + + bh_imap_init(&module.index_map, global_heap_allocator, 128); + bh_imap_init(&module.local_map, global_heap_allocator, 16); + bh_imap_init(&module.elem_map, global_heap_allocator, 16); + + bh_arr_new(global_heap_allocator, module.deferred_stmts, 4); + bh_arr_new(global_heap_allocator, module.local_allocations, 4); + bh_arr_new(global_heap_allocator, module.stack_leave_patches, 4); + bh_arr_new(global_heap_allocator, module.foreign_blocks, 4); + bh_arr_new(global_heap_allocator, module.procedures_with_tags, 4); + bh_arr_new(global_heap_allocator, module.data_patches, 4); + +#ifdef ENABLE_DEBUG_INFO + module.debug_context = bh_alloc_item(context.ast_alloc, DebugContext); + module.debug_context->allocator = global_heap_allocator; + module.debug_context->next_file_id = 0; + module.debug_context->next_sym_id = 0; + module.debug_context->last_token = NULL; + + sh_new_arena(module.debug_context->file_info); + bh_arr_new(global_heap_allocator, module.debug_context->sym_info, 32); + bh_arr_new(global_heap_allocator, module.debug_context->sym_patches, 32); + bh_arr_new(global_heap_allocator, module.debug_context->funcs, 16); + + bh_buffer_init(&module.debug_context->op_buffer, global_heap_allocator, 1024); +#endif + + return module; +} + +void emit_entity(Entity* ent) { + OnyxWasmModule* module = context.wasm_module; + module->current_func_idx = -1; + + switch (ent->type) { + case Entity_Type_Foreign_Function_Header: + if (!should_emit_function(ent->function)) break; + + module->foreign_function_count++; + emit_foreign_function(module, ent->function); + // fallthrough + + case Entity_Type_Function_Header: + if (!should_emit_function(ent->function)) break; + + if (context.options->print_function_mappings) { + bh_printf("%d -> %s:%d:%d\n", + module->next_func_idx, + ent->expr->token->pos.filename, + ent->expr->token->pos.line, + ent->expr->token->pos.column); + } + + bh_imap_put(&module->index_map, (u64) ent->function, module->next_func_idx++); + + if (ent->function->flags & Ast_Flag_Proc_Is_Null) { + if (module->null_proc_func_idx == -1) module->null_proc_func_idx = get_element_idx(module, ent->function); + } + + if (ent->function->tags != NULL) { + bh_arr_push(module->procedures_with_tags, ent->function); + } + break; + + case Entity_Type_Global_Header: + bh_imap_put(&module->index_map, (u64) ent->global, module->next_global_idx++); + break; + + case Entity_Type_String_Literal: { + emit_string_literal(module, (AstStrLit *) ent->strlit); + break; + } + + case Entity_Type_File_Contents: { + emit_file_contents(module, (AstFileContents *) ent->file_contents); + break; + } + + case Entity_Type_Memory_Reservation: { + emit_memory_reservation(module, (AstMemRes *) ent->mem_res); + break; + } + + case Entity_Type_Process_Directive: { + if (ent->expr->kind == Ast_Kind_Directive_Export) { + emit_export_directive(module, (AstDirectiveExport *) ent->expr); + } + + if (ent->expr->kind == Ast_Kind_Directive_Library) { + bh_arr_push(module->libraries, ent->library->library_name); + } + break; + } + + case Entity_Type_Foreign_Block: { + ent->foreign_block->foreign_block_number = module->next_foreign_block_idx++; + bh_arr_push(module->foreign_blocks, (AstForeignBlock *) ent->foreign_block); + break; + } + + case Entity_Type_Function: emit_function(module, ent->function); break; + case Entity_Type_Global: emit_global(module, ent->global); break; + + // Cleanup: Maybe these should be printed elsewhere? + // Also, they should be sorted? Or have that ability? + case Entity_Type_Note: { + if (!context.options->print_notes) break; + + AstNote* note = (AstNote *) ent->expr; + OnyxFilePos pos = note->token->pos; + + bh_printf("Note: %b %s:%d:%d\n", note->token->text, note->token->length, pos.filename, pos.line, pos.column); + + break; + } + + default: break; + } + + ent->state = Entity_State_Finalized; +} + +void onyx_wasm_module_link(OnyxWasmModule *module, OnyxWasmLinkOptions *options) { + // If the pointer size is going to change, + // the code will probably need to be altered. + assert(POINTER_SIZE == 4); + + module->memory_min_size = options->memory_min_size; + module->memory_max_size = options->memory_max_size; + + if (context.options->use_multi_threading || options->import_memory) { + module->needs_memory_section = 0; + + WasmImport mem_import = { + .kind = WASM_FOREIGN_MEMORY, + .min = options->memory_min_size, + .max = options->memory_max_size, // NOTE: Why not use all 4 Gigs of memory? + .shared = context.options->runtime == Runtime_Js, + + .mod = options->import_memory_module_name, + .name = options->import_memory_import_name, + }; + + bh_arr_push(module->imports, mem_import); + + } else { + module->needs_memory_section = 1; + } + + if (options->export_memory) { + WasmExport mem_export = { + .kind = WASM_FOREIGN_MEMORY, + .idx = 0, + }; + + shput(module->exports, options->export_memory_name, mem_export); + module->export_count++; + } + + if (options->export_func_table) { + WasmExport func_table_export = { + .kind = WASM_FOREIGN_TABLE, + .idx = 0, + }; + + shput(module->exports, options->export_func_table_name, func_table_export); + module->export_count++; + } + + u32 datum_offset = options->null_reserve_size; + bh_arr_each(WasmDatum, datum, module->data) { + assert(datum->id > 0); + + bh_align(datum_offset, datum->alignment); + datum->offset_ = datum_offset; + + datum_offset += datum->length; + } + + bh_arr_each(DatumPatchInfo, patch, module->data_patches) { + assert(patch->data_id > 0); + WasmDatum *datum = &module->data[patch->data_id - 1]; + assert(datum->id == patch->data_id); + + switch (patch->kind) { + case Datum_Patch_Instruction: { + WasmFunc *func = &module->funcs[patch->index - module->foreign_function_count]; + + assert(func->code[patch->location].type == WI_PTR_CONST); + func->code[patch->location].data.l = (u64) datum->offset_ + patch->offset; + break; + } + + case Datum_Patch_Data: { + WasmDatum *datum_to_alter = &module->data[patch->index - 1]; + assert(datum_to_alter->id == patch->index); + + *((u32 *) bh_pointer_add(datum_to_alter->data, patch->location)) = (u32) datum->offset_ + patch->offset; + break; + } + + case Datum_Patch_Relative: { + WasmDatum *datum_to_alter = &module->data[patch->index - 1]; + assert(datum_to_alter->id == patch->index); + + u32 *addr = (u32 *) bh_pointer_add(datum_to_alter->data, patch->location); + if (*addr != 0) { + *addr += (u32) datum->offset_ + patch->offset; + } + + break; + } + + default: assert(0); + } + } + + assert(module->stack_top_ptr && module->heap_start_ptr); + + *module->stack_top_ptr = datum_offset; + bh_align(*module->stack_top_ptr, options->stack_alignment); + + *module->heap_start_ptr = *module->stack_top_ptr + options->stack_size; + bh_align(*module->heap_start_ptr, 16); +} + +void onyx_wasm_module_free(OnyxWasmModule* module) { + if (module->extended_instr_data != NULL) + bh_arena_free(module->extended_instr_data); + + bh_arr_free(module->types); + bh_arr_free(module->funcs); + bh_imap_free(&module->local_map); + bh_imap_free(&module->index_map); + shfree(module->type_map); + shfree(module->exports); +} + + +b32 onyx_wasm_build_link_options_from_node(OnyxWasmLinkOptions *opts, AstTyped *node) { + node = (AstTyped *) strip_aliases((AstNode *) node); + + assert(node && node->kind == Ast_Kind_Struct_Literal); + assert(builtin_link_options_type); + + Type *link_options_type = type_build_from_ast(context.ast_alloc, builtin_link_options_type); + + AstStructLiteral *input = (AstStructLiteral *) node; + + StructMember smem; + b32 out_is_valid; + + // TODO: These should be properly error handled. + assert(type_lookup_member(link_options_type, "stack_first", &smem)); + opts->stack_first = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid) != 0; + if (!out_is_valid) return 0; + + assert(type_lookup_member(link_options_type, "stack_size", &smem)); + opts->stack_size = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid); + if (!out_is_valid) return 0; + + assert(type_lookup_member(link_options_type, "stack_alignment", &smem)); + opts->stack_alignment = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid); + if (!out_is_valid) return 0; + + assert(type_lookup_member(link_options_type, "null_reserve_size", &smem)); + opts->null_reserve_size = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid); + if (!out_is_valid) return 0; + + assert(type_lookup_member(link_options_type, "import_memory", &smem)); + opts->import_memory = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid) != 0; + if (!out_is_valid) return 0; + + assert(type_lookup_member(link_options_type, "import_memory_module_name", &smem)); + opts->import_memory_module_name = get_expression_string_value(input->args.values[smem.idx], &out_is_valid); + if (!out_is_valid) return 0; + + assert(type_lookup_member(link_options_type, "import_memory_import_name", &smem)); + opts->import_memory_import_name = get_expression_string_value(input->args.values[smem.idx], &out_is_valid); + if (!out_is_valid) return 0; + + assert(type_lookup_member(link_options_type, "export_memory", &smem)); + opts->export_memory = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid) != 0; + if (!out_is_valid) return 0; + + assert(type_lookup_member(link_options_type, "export_memory_name", &smem)); + opts->export_memory_name = get_expression_string_value(input->args.values[smem.idx], &out_is_valid); + if (!out_is_valid) return 0; + + assert(type_lookup_member(link_options_type, "export_func_table", &smem)); + opts->export_func_table = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid) != 0; + if (!out_is_valid) return 0; + + assert(type_lookup_member(link_options_type, "export_func_table_name", &smem)); + opts->export_func_table_name = get_expression_string_value(input->args.values[smem.idx], &out_is_valid); + if (!out_is_valid) return 0; + + assert(type_lookup_member(link_options_type, "memory_min_size", &smem)); + opts->memory_min_size = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid); + if (!out_is_valid) return 0; + + assert(type_lookup_member(link_options_type, "memory_max_size", &smem)); + opts->memory_max_size = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid); + if (!out_is_valid) return 0; + + return 1; +} + +#include "wasm_output.h" diff --git a/compiler/src/wasm_intrinsics.h b/compiler/src/wasm_intrinsics.h new file mode 100644 index 00000000..49dae8c0 --- /dev/null +++ b/compiler/src/wasm_intrinsics.h @@ -0,0 +1,452 @@ +// This file is directly included in src/onxywasm.c +// It is here purely to decrease the amount of clutter in the main file. + + +// IMPROVE: This implementation assumes that the source and destination buffers do not overlap. +// The specification for memory.copy in WASM does work even if the buffers overlap. +// Also, this implementation copies byte-by-byte, which is terrible. It should copy +// quad word by quad word, and then the additional bytes if the count was not divisible by 8. +// :32BitPointers +EMIT_FUNC_NO_ARGS(intrinsic_memory_copy) { + bh_arr(WasmInstruction) code = *pcode; + + // The stack should look like this: + // + // + // + + u64 count_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32); + u64 source_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + u64 dest_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + + WIL(NULL, WI_LOCAL_SET, count_local); + WIL(NULL, WI_LOCAL_SET, source_local); + WIL(NULL, WI_LOCAL_SET, dest_local); + + // count is greater than 0 + WIL(NULL, WI_LOCAL_GET, count_local); + WID(NULL, WI_I32_CONST, 0); + WI(NULL, WI_I32_GT_S); + + WID(NULL, WI_IF_START, 0x40); + WID(NULL, WI_LOOP_START, 0x40); + + WIL(NULL, WI_LOCAL_GET, count_local); + WID(NULL, WI_I32_CONST, 1); + WI(NULL, WI_I32_SUB); + WIL(NULL, WI_LOCAL_SET, count_local); + + WIL(NULL, WI_LOCAL_GET, dest_local); + WIL(NULL, WI_LOCAL_GET, count_local); + WI(NULL, WI_PTR_ADD); + + WIL(NULL, WI_LOCAL_GET, source_local); + WIL(NULL, WI_LOCAL_GET, count_local); + WI(NULL, WI_PTR_ADD); + + WID(NULL, WI_I32_LOAD_8_U, ((WasmInstructionData) { 0, 0 })); + WID(NULL, WI_I32_STORE_8, ((WasmInstructionData) { 0, 0 })); + + WIL(NULL, WI_LOCAL_GET, count_local); + WID(NULL, WI_I32_CONST, 0); + WI(NULL, WI_I32_GT_S); + WID(NULL, WI_COND_JUMP, 0x00); + + WI(NULL, WI_LOOP_END); + WI(NULL, WI_IF_END); + + local_raw_free(mod->local_alloc, WASM_TYPE_INT32); + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + + *pcode = code; +} + +EMIT_FUNC_NO_ARGS(intrinsic_memory_fill) { + bh_arr(WasmInstruction) code = *pcode; + + // The stack should look like this: + // + // + // + + u64 count_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32); + u64 byte_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32); + u64 dest_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + + WIL(NULL, WI_LOCAL_SET, count_local); + WIL(NULL, WI_LOCAL_SET, byte_local); + WIL(NULL, WI_LOCAL_SET, dest_local); + + // count is greater than 0 + WIL(NULL, WI_LOCAL_GET, count_local); + WID(NULL, WI_I32_CONST, 0); + WI(NULL, WI_I32_GT_S); + + WID(NULL, WI_IF_START, 0x40); + WID(NULL, WI_LOOP_START, 0x40); + + WIL(NULL, WI_LOCAL_GET, count_local); + WID(NULL, WI_I32_CONST, 1); + WI(NULL, WI_I32_SUB); + WIL(NULL, WI_LOCAL_SET, count_local); + + WIL(NULL, WI_LOCAL_GET, dest_local); + WIL(NULL, WI_LOCAL_GET, count_local); + WI(NULL, WI_PTR_ADD); + + WIL(NULL, WI_LOCAL_GET, byte_local); + WID(NULL, WI_I32_STORE_8, ((WasmInstructionData) { 0, 0 })); + + WIL(NULL, WI_LOCAL_GET, count_local); + WID(NULL, WI_I32_CONST, 0); + WI(NULL, WI_I32_GT_S); + WID(NULL, WI_COND_JUMP, 0x00); + + WI(NULL, WI_LOOP_END); + WI(NULL, WI_IF_END); + + local_raw_free(mod->local_alloc, WASM_TYPE_INT32); + local_raw_free(mod->local_alloc, WASM_TYPE_INT32); + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + + *pcode = code; +} + +EMIT_FUNC(initialize_type, Type* type, OnyxToken* where) { + bh_arr(WasmInstruction) code = *pcode; + + switch (type->kind) { + case Type_Kind_Pointer: + case Type_Kind_Basic: { + WasmType basic_type = onyx_type_to_wasm_type(type); + emit_zero_value(mod, &code, basic_type); + emit_store_instruction(mod, &code, type, 0); + break; + } + + case Type_Kind_Struct: { + u64 value_ptr = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); + WIL(NULL, WI_LOCAL_SET, value_ptr); + + bh_arr_each(StructMember *, psmem, type->Struct.memarr) { + StructMember* smem = *psmem; + if (smem->initial_value == NULL || *smem->initial_value == NULL) continue; + + WIL(NULL, WI_LOCAL_GET, value_ptr); + emit_expression(mod, &code, *smem->initial_value); + emit_store_instruction(mod, &code, smem->type, smem->offset); + } + + local_raw_free(mod->local_alloc, WASM_TYPE_PTR); + break; + } + + default: + onyx_report_error(where->pos, Error_Critical, + "Unable to initialize type, '%s'. The reason for this is largely due to the compiler not knowing what the initial value should be.", + type_get_name(type)); + break; + } + + *pcode = code; +} + +EMIT_FUNC(intrinsic_atomic_wait, Type* type, OnyxToken* where) { + if (type->kind != Type_Kind_Basic) goto bad_type; + + bh_arr(WasmInstruction) code = *pcode; + + switch (type->Basic.kind) { + case Basic_Kind_I32: + case Basic_Kind_U32: WID(NULL, WI_ATOMIC_WAIT32, ((WasmInstructionData) { 2, 0 })); break; + + case Basic_Kind_I64: + case Basic_Kind_U64: WID(NULL, WI_ATOMIC_WAIT64, ((WasmInstructionData) { 3, 0 })); break; + + default: goto bad_type; + } + + *pcode = code; + return; + +bad_type: + onyx_report_error(where->pos, Error_Critical, "Bad type for atomic wait, '%s'. Only i32 and i64 are supported.", type_get_name(type)); +} + +EMIT_FUNC_NO_ARGS(intrinsic_atomic_notify) { + bh_arr(WasmInstruction) code = *pcode; + WID(NULL, WI_ATOMIC_NOTIFY, ((WasmInstructionData) { 2, 0 })); + *pcode = code; +} + +EMIT_FUNC_NO_ARGS(intrinsic_atomic_fence) { + bh_arr(WasmInstruction) code = *pcode; + WI(NULL, WI_ATOMIC_FENCE); + *pcode = code; +} + +EMIT_FUNC(intrinsic_atomic_load, Type* type, OnyxToken* where) { + if (type->kind != Type_Kind_Basic) goto bad_type; + + bh_arr(WasmInstruction) code = *pcode; + + switch (type->Basic.kind) { + case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_LOAD8_U, ((WasmInstructionData) { 0, 0 })); break; + case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_LOAD16_U, ((WasmInstructionData) { 1, 0 })); break; + + case Basic_Kind_I32: + case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_LOAD, ((WasmInstructionData) { 2, 0 })); break; + + case Basic_Kind_I64: + case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_LOAD, ((WasmInstructionData) { 3, 0 })); break; + + default: goto bad_type; + } + + *pcode = code; + return; + +bad_type: + onyx_report_error(where->pos, Error_Critical, "Bad type for atomic load, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); +} + +EMIT_FUNC(intrinsic_atomic_store, Type* type, OnyxToken* where) { + if (type->kind != Type_Kind_Basic) goto bad_type; + + bh_arr(WasmInstruction) code = *pcode; + + switch (type->Basic.kind) { + case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_STORE8, ((WasmInstructionData) { 0, 0 })); break; + case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_STORE16, ((WasmInstructionData) { 1, 0 })); break; + + case Basic_Kind_I32: + case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_STORE, ((WasmInstructionData) { 2, 0 })); break; + + case Basic_Kind_I64: + case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_STORE, ((WasmInstructionData) { 3, 0 })); break; + + default: goto bad_type; + } + + *pcode = code; + return; + +bad_type: + onyx_report_error(where->pos, Error_Critical, "Bad type for atomic store, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); +} + +EMIT_FUNC(intrinsic_atomic_add, Type* type, OnyxToken* where) { + if (type->kind != Type_Kind_Basic) goto bad_type; + + bh_arr(WasmInstruction) code = *pcode; + + switch (type->Basic.kind) { + case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_ADD8_U, ((WasmInstructionData) { 0, 0 })); break; + case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_ADD16_U, ((WasmInstructionData) { 1, 0 })); break; + + case Basic_Kind_I32: + case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_ADD, ((WasmInstructionData) { 2, 0 })); break; + + case Basic_Kind_I64: + case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_ADD, ((WasmInstructionData) { 3, 0 })); break; + + default: goto bad_type; + } + + *pcode = code; + return; + +bad_type: + onyx_report_error(where->pos, Error_Critical, "Bad type for atomic add, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); +} + +EMIT_FUNC(intrinsic_atomic_sub, Type* type, OnyxToken* where) { + if (type->kind != Type_Kind_Basic) goto bad_type; + + bh_arr(WasmInstruction) code = *pcode; + + switch (type->Basic.kind) { + case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_SUB8_U, ((WasmInstructionData) { 0, 0 })); break; + case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_SUB16_U, ((WasmInstructionData) { 1, 0 })); break; + + case Basic_Kind_I32: + case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_SUB, ((WasmInstructionData) { 2, 0 })); break; + + case Basic_Kind_I64: + case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_SUB, ((WasmInstructionData) { 3, 0 })); break; + + default: goto bad_type; + } + + *pcode = code; + return; + +bad_type: + onyx_report_error(where->pos, Error_Critical, "Bad type for atomic sub, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); +} + +EMIT_FUNC(intrinsic_atomic_and, Type* type, OnyxToken* where) { + if (type->kind != Type_Kind_Basic) goto bad_type; + + bh_arr(WasmInstruction) code = *pcode; + + switch (type->Basic.kind) { + case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_AND8_U, ((WasmInstructionData) { 0, 0 })); break; + case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_AND16_U, ((WasmInstructionData) { 1, 0 })); break; + + case Basic_Kind_I32: + case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_AND, ((WasmInstructionData) { 2, 0 })); break; + + case Basic_Kind_I64: + case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_AND, ((WasmInstructionData) { 3, 0 })); break; + + default: goto bad_type; + } + + *pcode = code; + return; + +bad_type: + onyx_report_error(where->pos, Error_Critical, "Bad type for atomic and, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); +} + +EMIT_FUNC(intrinsic_atomic_or, Type* type, OnyxToken* where) { + if (type->kind != Type_Kind_Basic) goto bad_type; + + bh_arr(WasmInstruction) code = *pcode; + + switch (type->Basic.kind) { + case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_OR8_U, ((WasmInstructionData) { 0, 0 })); break; + case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_OR16_U, ((WasmInstructionData) { 1, 0 })); break; + + case Basic_Kind_I32: + case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_OR, ((WasmInstructionData) { 2, 0 })); break; + + case Basic_Kind_I64: + case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_OR, ((WasmInstructionData) { 3, 0 })); break; + + default: goto bad_type; + } + + *pcode = code; + return; + +bad_type: + onyx_report_error(where->pos, Error_Critical, "Bad type for atomic or, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); +} + +EMIT_FUNC(intrinsic_atomic_xor, Type* type, OnyxToken* where) { + if (type->kind != Type_Kind_Basic) goto bad_type; + + bh_arr(WasmInstruction) code = *pcode; + + switch (type->Basic.kind) { + case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_XOR8_U, ((WasmInstructionData) { 0, 0 })); break; + case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_XOR16_U, ((WasmInstructionData) { 1, 0 })); break; + + case Basic_Kind_I32: + case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_XOR, ((WasmInstructionData) { 2, 0 })); break; + + case Basic_Kind_I64: + case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_XOR, ((WasmInstructionData) { 3, 0 })); break; + + default: goto bad_type; + } + + *pcode = code; + return; + +bad_type: + onyx_report_error(where->pos, Error_Critical, "Bad type for atomic xor, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); +} + +EMIT_FUNC(intrinsic_atomic_xchg, Type* type, OnyxToken* where) { + if (type->kind != Type_Kind_Basic) goto bad_type; + + bh_arr(WasmInstruction) code = *pcode; + + switch (type->Basic.kind) { + case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_XCHG8_U, ((WasmInstructionData) { 0, 0 })); break; + case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_XCHG16_U, ((WasmInstructionData) { 1, 0 })); break; + + case Basic_Kind_I32: + case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_XCHG, ((WasmInstructionData) { 2, 0 })); break; + + case Basic_Kind_I64: + case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_XCHG, ((WasmInstructionData) { 3, 0 })); break; + + default: goto bad_type; + } + + *pcode = code; + return; + +bad_type: + onyx_report_error(where->pos, Error_Critical, "Bad type for atomic xchg, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); +} + +EMIT_FUNC(intrinsic_atomic_cmpxchg, Type* type, OnyxToken* where) { + if (type->kind != Type_Kind_Basic) goto bad_type; + + bh_arr(WasmInstruction) code = *pcode; + + switch (type->Basic.kind) { + case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_CMPXCHG8_U, ((WasmInstructionData) { 0, 0 })); break; + case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_CMPXCHG16_U, ((WasmInstructionData) { 1, 0 })); break; + + case Basic_Kind_I32: + case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_CMPXCHG, ((WasmInstructionData) { 2, 0 })); break; + + case Basic_Kind_I64: + case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_CMPXCHG, ((WasmInstructionData) { 3, 0 })); break; + + default: goto bad_type; + } + + *pcode = code; + return; + +bad_type: + onyx_report_error(where->pos, Error_Critical, "Bad type for atomic cmpxchg, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); +} + +EMIT_FUNC_NO_ARGS(initialize_data_segments_body) { + // :ProperLinking + if (!context.options->use_multi_threading || !context.options->use_post_mvp_features) return; + + bh_arr(WasmInstruction) code = *pcode; + + // + // Because this code is generated direction in the function + // it is assumed that EVERY data entry will be entered by + // this point. If data section entries can be entered after + // function body generation starts, this code will have to + // move to a link phase thing. + i32 index = 0; + bh_arr_each(WasmDatum, datum, mod->data) { + assert(datum->id > 0); + if (datum->data == NULL) { index++; continue; } + + emit_data_relocation(mod, &code, datum->id); + WID(NULL, WI_PTR_CONST, 0); + WID(NULL, WI_I32_CONST, datum->length); + WID(NULL, WI_MEMORY_INIT, ((WasmInstructionData) { index, 0 })); + + index += 1; + } + + *pcode = code; +} + +EMIT_FUNC_NO_ARGS(run_init_procedures) { + bh_arr(WasmInstruction) code = *pcode; + + bh_arr_each(AstFunction *, func, init_procedures) { + i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) *func); + debug_emit_instruction(mod, NULL); + bh_arr_push(code, ((WasmInstruction){ WI_CALL, func_idx })); + } + + *pcode = code; +} diff --git a/compiler/src/wasm_output.h b/compiler/src/wasm_output.h new file mode 100644 index 00000000..74213c57 --- /dev/null +++ b/compiler/src/wasm_output.h @@ -0,0 +1,1061 @@ +// This file is included in src/onyxwasm.c. +// It is separated because of its fundamentally different goals. + +//------------------------------------------------- +// BINARY OUPUT +//------------------------------------------------- + +#define WASM_SECTION_ID_CUSTOM 0 +#define WASM_SECTION_ID_TYPE 1 +#define WASM_SECTION_ID_IMPORT 2 +#define WASM_SECTION_ID_FUNCTION 3 +#define WASM_SECTION_ID_TABLE 4 +#define WASM_SECTION_ID_MEMORY 5 +#define WASM_SECTION_ID_GLOBAL 6 +#define WASM_SECTION_ID_EXPORT 7 +#define WASM_SECTION_ID_START 8 +#define WASM_SECTION_ID_ELEMENT 9 +#define WASM_SECTION_ID_DATACOUNT 12 +#define WASM_SECTION_ID_CODE 10 +#define WASM_SECTION_ID_DATA 11 + +typedef i32 vector_func(void*, bh_buffer*); + +static const u8 ONYX_MAGIC_STRING[] = "ONYX"; +static const u8 WASM_MAGIC_STRING[] = { 0x00, 0x61, 0x73, 0x6D }; +static const u8 WASM_VERSION[] = { 0x01, 0x00, 0x00, 0x00 }; + +static inline i32 output_unsigned_integer(u64 i, bh_buffer *buff) { + i32 leb_len; + u8* leb = uint_to_uleb128(i, &leb_len); + bh_buffer_append(buff, leb, leb_len); + return leb_len; +} + +static inline i32 output_custom_section_name(char *name, bh_buffer *buff) { + u32 len = strlen(name); + i32 len_len = output_unsigned_integer(len, buff); + bh_buffer_append(buff, name, len); + return len_len + len; +} + +static void output_instruction(WasmFunc* func, WasmInstruction* instr, bh_buffer* buff); + +static i32 output_vector(void** arr, i32 stride, i32 arrlen, vector_func elem, bh_buffer* vec_buff) { + i32 len; + u8* leb = uint_to_uleb128((u64) arrlen, &len); + bh_buffer_append(vec_buff, leb, len); + + i32 i = 0; + while (i < arrlen) { + elem(*arr, vec_buff); + arr = bh_pointer_add(arr, stride); + i++; + } + + return vec_buff->length; +} + +static i32 output_name(const char* start, i32 length, bh_buffer* buff) { + i32 leb_len, prev_len = buff->length; + u8* leb = uint_to_uleb128((u64) length, &leb_len); + bh_buffer_append(buff, leb, leb_len); + bh_buffer_append(buff, start, length); + return buff->length - prev_len; +} + +static i32 output_limits(i32 min, i32 max, b32 shared, bh_buffer* buff) { + i32 leb_len, prev_len = buff->length; + u8* leb; + + u8 mem_type = 0x00; + if (max >= 0) mem_type |= 0x01; + if (shared) mem_type |= 0x02; + + bh_buffer_write_byte(buff, mem_type); + + leb = uint_to_uleb128((u64) min, &leb_len); + bh_buffer_append(buff, leb, leb_len); + + if (max >= 0) { + leb = uint_to_uleb128((u64) max, &leb_len); + bh_buffer_append(buff, leb, leb_len); + } + + return buff->length - prev_len; +} + +static i32 output_functype(WasmFuncType* type, bh_buffer* buff) { + i32 prev_len = buff->length; + + bh_buffer_write_byte(buff, 0x60); + + i32 len; + u8* leb_buff = uint_to_uleb128(type->param_count, &len); + bh_buffer_append(buff, leb_buff, len); + bh_buffer_append(buff, type->param_types, type->param_count); + + if (type->return_type != WASM_TYPE_VOID) { + bh_buffer_write_byte(buff, 0x01); + bh_buffer_write_byte(buff, type->return_type); + } else { + bh_buffer_write_byte(buff, 0x00); + } + + return buff->length - prev_len; +} + +static i32 output_typesection(OnyxWasmModule* module, bh_buffer* buff) { + i32 prev_len = buff->length; + bh_buffer_write_byte(buff, 0x01); + + bh_buffer vec_buff; + bh_buffer_init(&vec_buff, buff->allocator, 128); + + i32 vec_len = output_vector( + (void**) module->types, + sizeof(WasmFuncType*), + bh_arr_length(module->types), + (vector_func *) output_functype, + &vec_buff); + + i32 leb_len; + u8* leb = uint_to_uleb128((u64) vec_len, &leb_len); + bh_buffer_append(buff, leb, leb_len); + + bh_buffer_concat(buff, vec_buff); + bh_buffer_free(&vec_buff); + + return buff->length - prev_len; +} + +static i32 output_funcsection(OnyxWasmModule* module, bh_buffer* buff) { + i32 prev_len = buff->length; + bh_buffer_write_byte(buff, WASM_SECTION_ID_FUNCTION); + + bh_buffer vec_buff; + bh_buffer_init(&vec_buff, buff->allocator, 128); + + i32 leb_len; + u8* leb = uint_to_uleb128((u64) (bh_arr_length(module->funcs)), &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + + bh_arr_each(WasmFunc, func, module->funcs) { + leb = uint_to_uleb128((u64) (func->type_idx), &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + } + + leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + bh_buffer_concat(buff, vec_buff); + bh_buffer_free(&vec_buff); + + return buff->length - prev_len; +} + +static i32 output_tablesection(OnyxWasmModule* module, bh_buffer* buff) { + // if (bh_arr_length(module->elems) == 0) return 0; + + i32 prev_len = buff->length; + bh_buffer_write_byte(buff, WASM_SECTION_ID_TABLE); + + bh_buffer vec_buff; + bh_buffer_init(&vec_buff, buff->allocator, 128); + + i32 leb_len; + u8* leb = uint_to_uleb128((u64) 1, &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + + // NOTE: funcrefs are the only valid table element type + bh_buffer_write_byte(&vec_buff, 0x70); + output_limits(bh_arr_length(module->elems), -1, 0, &vec_buff); + + leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + bh_buffer_concat(buff, vec_buff); + bh_buffer_free(&vec_buff); + + return buff->length - prev_len; +} + +static i32 output_memorysection(OnyxWasmModule* module, bh_buffer* buff) { + // :ProperLinking + // if (context.options->use_multi_threading) return 0; + if (!module->needs_memory_section) return 0; + + i32 prev_len = buff->length; + bh_buffer_write_byte(buff, WASM_SECTION_ID_MEMORY); + + bh_buffer vec_buff; + bh_buffer_init(&vec_buff, buff->allocator, 128); + + i32 leb_len; + u8* leb = uint_to_uleb128((u64) 1, &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + + output_limits(module->memory_min_size, -1, 0, &vec_buff); + + leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + bh_buffer_concat(buff, vec_buff); + bh_buffer_free(&vec_buff); + + return buff->length - prev_len; +} + +static i32 output_globalsection(OnyxWasmModule* module, bh_buffer* buff) { + i32 prev_len = buff->length; + bh_buffer_write_byte(buff, WASM_SECTION_ID_GLOBAL); + + bh_buffer vec_buff; + bh_buffer_init(&vec_buff, buff->allocator, 128); + + i32 leb_len; + u8* leb = uint_to_uleb128((u64) (bh_arr_length(module->globals)), &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + + bh_arr_each(WasmGlobal, global, module->globals) { + bh_buffer_write_byte(&vec_buff, global->type); + bh_buffer_write_byte(&vec_buff, 0x01); + + bh_arr_each(WasmInstruction, instr, global->initial_value) + output_instruction(NULL, instr, &vec_buff); + + // NOTE: Initial value expression terminator + bh_buffer_write_byte(&vec_buff, (u8) WI_BLOCK_END); + } + + leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + bh_buffer_concat(buff, vec_buff); + bh_buffer_free(&vec_buff); + + return buff->length - prev_len; +} + +static i32 output_importsection(OnyxWasmModule* module, bh_buffer* buff) { + i32 prev_len = buff->length; + bh_buffer_write_byte(buff, WASM_SECTION_ID_IMPORT); + + bh_buffer vec_buff; + bh_buffer_init(&vec_buff, buff->allocator, 128); + + i32 leb_len; + u8* leb = uint_to_uleb128((u64) (bh_arr_length(module->imports)), &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + + bh_arr_each(WasmImport, import, module->imports) { + output_name(import->mod, strlen(import->mod), &vec_buff); + output_name(import->name, strlen(import->name), &vec_buff); + bh_buffer_write_byte(&vec_buff, (u8) import->kind); + + switch (import->kind) { + case WASM_FOREIGN_FUNCTION: + leb = uint_to_uleb128((u64) import->idx, &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + break; + + case WASM_FOREIGN_MEMORY: + output_limits(import->min, import->max, import->shared, &vec_buff); + break; + + case WASM_FOREIGN_TABLE: assert(0); + } + } + + leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + bh_buffer_concat(buff, vec_buff); + bh_buffer_free(&vec_buff); + + return buff->length - prev_len; +} + +static i32 output_exportsection(OnyxWasmModule* module, bh_buffer* buff) { + i32 prev_len = buff->length; + bh_buffer_write_byte(buff, WASM_SECTION_ID_EXPORT); + + bh_buffer vec_buff; + bh_buffer_init(&vec_buff, buff->allocator, 128); + + i32 leb_len; + u8* leb = uint_to_uleb128((u64) (module->export_count), &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + + i32 key_len = 0; + fori (i, 0, shlen(module->exports)) { + char *key = module->exports[i].key; + WasmExport value = module->exports[i].value; + + key_len = strlen(key); + output_name(key, key_len, &vec_buff); + + bh_buffer_write_byte(&vec_buff, (u8) (value.kind)); + leb = uint_to_uleb128((u64) value.idx, &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + } + + leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + bh_buffer_concat(buff, vec_buff); + bh_buffer_free(&vec_buff); + + return buff->length - prev_len; +} + +static i32 output_startsection(OnyxWasmModule* module, bh_buffer* buff) { + i32 prev_len = buff->length; + + i32 start_idx = -1; + fori (i, 0, shlen(module->exports)) { + char *key = module->exports[i].key; + WasmExport value = module->exports[i].value; + + if (value.kind == WASM_FOREIGN_FUNCTION) { + if (strncmp("main", key, 5) == 0) { + start_idx = value.idx; + break; + } + } + } + + if (start_idx != -1) { + bh_buffer_write_byte(buff, WASM_SECTION_ID_START); + + i32 start_leb_len, section_leb_len; + uint_to_uleb128((u64) start_idx, &start_leb_len); + u8* section_leb = uint_to_uleb128((u64) start_leb_len, §ion_leb_len); + bh_buffer_append(buff, section_leb, section_leb_len); + + u8* start_leb = uint_to_uleb128((u64) start_idx, &start_leb_len); + bh_buffer_append(buff, start_leb, start_leb_len); + } + + return buff->length - prev_len; +} + +static i32 output_elemsection(OnyxWasmModule* module, bh_buffer* buff) { + if (bh_arr_length(module->elems) == 0) return 0; + + i32 prev_len = buff->length; + + bh_buffer_write_byte(buff, WASM_SECTION_ID_ELEMENT); + + bh_buffer vec_buff; + bh_buffer_init(&vec_buff, buff->allocator, 128); + + i32 leb_len; + u8* leb; + + // NOTE: 0x01 count of elems + bh_buffer_write_byte(&vec_buff, 0x01); + + // NOTE: 0x00 table index + bh_buffer_write_byte(&vec_buff, 0x00); + + bh_buffer_write_byte(&vec_buff, WI_I32_CONST); + bh_buffer_write_byte(&vec_buff, 0x00); + bh_buffer_write_byte(&vec_buff, WI_BLOCK_END); + + leb = uint_to_uleb128((u64) bh_arr_length(module->elems), &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + + bh_arr_each(i32, elem, module->elems) { + leb = uint_to_uleb128((u64) *elem, &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + } + + leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + bh_buffer_concat(buff, vec_buff); + bh_buffer_free(&vec_buff); + + return buff->length - prev_len; +} + +static i32 output_locals(WasmFunc* func, bh_buffer* buff) { + i32 prev_len = buff->length; + + // NOTE: Output vector length + i32 total_locals = + (i32) (func->locals.allocated[0] != 0) + + (i32) (func->locals.allocated[1] != 0) + + (i32) (func->locals.allocated[2] != 0) + + (i32) (func->locals.allocated[3] != 0) + + (i32) (func->locals.allocated[4] != 0); + + i32 leb_len; + u8* leb = uint_to_uleb128((u64) total_locals, &leb_len); + bh_buffer_append(buff, leb, leb_len); + + if (func->locals.allocated[0] != 0) { + leb = uint_to_uleb128((u64) func->locals.allocated[0], &leb_len); + bh_buffer_append(buff, leb, leb_len); + bh_buffer_write_byte(buff, WASM_TYPE_INT32); + } + if (func->locals.allocated[1] != 0) { + leb = uint_to_uleb128((u64) func->locals.allocated[1], &leb_len); + bh_buffer_append(buff, leb, leb_len); + bh_buffer_write_byte(buff, WASM_TYPE_INT64); + } + if (func->locals.allocated[2] != 0) { + leb = uint_to_uleb128((u64) func->locals.allocated[2], &leb_len); + bh_buffer_append(buff, leb, leb_len); + bh_buffer_write_byte(buff, WASM_TYPE_FLOAT32); + } + if (func->locals.allocated[3] != 0) { + leb = uint_to_uleb128((u64) func->locals.allocated[3], &leb_len); + bh_buffer_append(buff, leb, leb_len); + bh_buffer_write_byte(buff, WASM_TYPE_FLOAT64); + } + if (func->locals.allocated[4] != 0) { + leb = uint_to_uleb128((u64) func->locals.allocated[4], &leb_len); + bh_buffer_append(buff, leb, leb_len); + bh_buffer_write_byte(buff, WASM_TYPE_VAR128); + } + + return buff->length - prev_len; +} + +static void output_instruction(WasmFunc* func, WasmInstruction* instr, bh_buffer* buff) { + i32 leb_len; + u8* leb; + + if (instr->type == WI_UNREACHABLE) assert(("EMITTING UNREACHABLE!!", 0)); + + if (instr->type == WI_NOP && !context.options->debug_enabled) return; + + if (instr->type & SIMD_INSTR_MASK) { + bh_buffer_write_byte(buff, 0xFD); + leb = uint_to_uleb128((u64) (instr->type &~ SIMD_INSTR_MASK), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + } else if (instr->type & EXT_INSTR_MASK) { + bh_buffer_write_byte(buff, 0xFC); + leb = uint_to_uleb128((u64) (instr->type &~ EXT_INSTR_MASK), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + } else if (instr->type & ATOMIC_INSTR_MASK) { + bh_buffer_write_byte(buff, 0xFE); + leb = uint_to_uleb128((u64) (instr->type &~ ATOMIC_INSTR_MASK), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + if (instr->type == WI_ATOMIC_FENCE) { + bh_buffer_write_byte(buff, 0x00); + + } else { + leb = uint_to_uleb128((u64) instr->data.i1, &leb_len); + bh_buffer_append(buff, leb, leb_len); + + leb = uint_to_uleb128((u64) instr->data.i2, &leb_len); + bh_buffer_append(buff, leb, leb_len); + } + + } else { + bh_buffer_write_byte(buff, (u8) instr->type); + } + + switch (instr->type) { + case WI_LOCAL_GET: + case WI_LOCAL_SET: + case WI_LOCAL_TEE: { + u64 actual_idx = local_lookup_idx(&func->locals, instr->data.l); + leb = uint_to_uleb128(actual_idx, &leb_len); + bh_buffer_append(buff, leb, leb_len); + break; + } + + case WI_GLOBAL_GET: + case WI_GLOBAL_SET: + case WI_CALL: + case WI_BLOCK_START: + case WI_LOOP_START: + case WI_JUMP: + case WI_COND_JUMP: + case WI_IF_START: + case WI_MEMORY_SIZE: + case WI_MEMORY_GROW: + case WI_MEMORY_FILL: + leb = uint_to_uleb128((u64) instr->data.i1, &leb_len); + bh_buffer_append(buff, leb, leb_len); + break; + + case WI_MEMORY_INIT: + case WI_MEMORY_COPY: + leb = uint_to_uleb128((u64) instr->data.i1, &leb_len); + bh_buffer_append(buff, leb, leb_len); + + leb = uint_to_uleb128((u64) instr->data.i2, &leb_len); + bh_buffer_append(buff, leb, leb_len); + break; + + case WI_JUMP_TABLE: { + BranchTable* bt = (BranchTable *) instr->data.p; + + leb = uint_to_uleb128((u64) bt->count, &leb_len); + bh_buffer_append(buff, leb, leb_len); + + fori (i, 0, bt->count) { + leb = uint_to_uleb128((u64) bt->cases[i], &leb_len); + bh_buffer_append(buff, leb, leb_len); + } + + leb = uint_to_uleb128((u64) bt->default_case, &leb_len); + bh_buffer_append(buff, leb, leb_len); + break; + } + + + case WI_CALL_INDIRECT: + case WI_I32_STORE: case WI_I32_STORE_8: case WI_I32_STORE_16: + case WI_I64_STORE: case WI_I64_STORE_8: case WI_I64_STORE_16: case WI_I64_STORE_32: + case WI_F32_STORE: case WI_F64_STORE: + case WI_V128_STORE: + case WI_I32_LOAD: + case WI_I32_LOAD_8_S: case WI_I32_LOAD_8_U: + case WI_I32_LOAD_16_S: case WI_I32_LOAD_16_U: + case WI_I64_LOAD: + case WI_I64_LOAD_8_S: case WI_I64_LOAD_8_U: + case WI_I64_LOAD_16_S: case WI_I64_LOAD_16_U: + case WI_I64_LOAD_32_S: case WI_I64_LOAD_32_U: + case WI_F32_LOAD: case WI_F64_LOAD: + case WI_V128_LOAD: + leb = uint_to_uleb128((u64) instr->data.i1, &leb_len); + bh_buffer_append(buff, leb, leb_len); + leb = uint_to_uleb128((u64) instr->data.i2, &leb_len); + bh_buffer_append(buff, leb, leb_len); + break; + + case WI_I32_CONST: + leb = int_to_leb128((i64) instr->data.i1, &leb_len); + bh_buffer_append(buff, leb, leb_len); + break; + case WI_I64_CONST: + leb = int_to_leb128((i64) instr->data.l, &leb_len); + bh_buffer_append(buff, leb, leb_len); + break; + case WI_F32_CONST: + leb = float_to_ieee754(instr->data.f, 0); + bh_buffer_append(buff, leb, 4); + break; + case WI_F64_CONST: + leb = double_to_ieee754(instr->data.d, 0); + bh_buffer_append(buff, leb, 8); + break; + + case WI_V128_CONST: + case WI_I8X16_SHUFFLE: + fori (i, 0, 16) bh_buffer_write_byte(buff, ((u8*) instr->data.p)[i]); + break; + + case WI_I8X16_EXTRACT_LANE_S: case WI_I8X16_EXTRACT_LANE_U: case WI_I8X16_REPLACE_LANE: + case WI_I16X8_EXTRACT_LANE_S: case WI_I16X8_EXTRACT_LANE_U: case WI_I16X8_REPLACE_LANE: + case WI_I32X4_EXTRACT_LANE: case WI_I32X4_REPLACE_LANE: + case WI_I64X2_EXTRACT_LANE: case WI_I64X2_REPLACE_LANE: + case WI_F32X4_EXTRACT_LANE: case WI_F32X4_REPLACE_LANE: + case WI_F64X2_EXTRACT_LANE: case WI_F64X2_REPLACE_LANE: + bh_buffer_write_byte(buff, (u8) instr->data.i1); + break; + + default: break; + } +} + +static i32 output_code(WasmFunc* func, bh_buffer* buff) { + + bh_buffer code_buff; + bh_buffer_init(&code_buff, buff->allocator, 128); + + // Output locals + output_locals(func, &code_buff); + + assert(func->code); + + // Output code + bh_arr_each(WasmInstruction, instr, func->code) output_instruction(func, instr, &code_buff); + + i32 leb_len; + u8* leb = uint_to_uleb128((u64) code_buff.length, &leb_len); + bh_buffer_append(buff, leb, leb_len); + + bh_buffer_concat(buff, code_buff); + bh_buffer_free(&code_buff); + + return 0; +} + +static i32 output_codesection(OnyxWasmModule* module, bh_buffer* buff) { + i32 prev_len = buff->length; + + bh_buffer_write_byte(buff, WASM_SECTION_ID_CODE); + + bh_buffer vec_buff; + bh_buffer_init(&vec_buff, buff->allocator, 128); + + i32 leb_len; + u8* leb = uint_to_uleb128((u64) bh_arr_length(module->funcs), &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + + bh_arr_each(WasmFunc, func, module->funcs) output_code(func, &vec_buff); + + leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + bh_buffer_concat(buff, vec_buff); + bh_buffer_free(&vec_buff); + + return buff->length - prev_len; +} + +static i32 output_datacountsection(OnyxWasmModule* module, bh_buffer* buff) { + if (!context.options->use_post_mvp_features) return 0; + + i32 prev_len = buff->length; + + bh_buffer_write_byte(buff, WASM_SECTION_ID_DATACOUNT); + + bh_buffer vec_buff; + bh_buffer_init(&vec_buff, buff->allocator, 128); + + i32 leb_len; + u8* leb = uint_to_uleb128((u64) bh_arr_length(module->data), &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + + leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + bh_buffer_concat(buff, vec_buff); + bh_buffer_free(&vec_buff); + + return buff->length - prev_len; +} + +static i32 output_datasection(OnyxWasmModule* module, bh_buffer* buff) { + i32 prev_len = buff->length; + + bh_buffer_write_byte(buff, WASM_SECTION_ID_DATA); + + bh_buffer vec_buff; + bh_buffer_init(&vec_buff, buff->allocator, 128); + + i32 leb_len; + u8* leb = uint_to_uleb128((u64) bh_arr_length(module->data), &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + + bh_arr_each(WasmDatum, datum, module->data) { + i32 memory_flags = 0x00; + // :ProperLinking + if (context.options->use_multi_threading) memory_flags |= 0x01; + + bh_buffer_write_byte(&vec_buff, memory_flags); + + // :ProperLinking + if (!context.options->use_multi_threading) { + bh_buffer_write_byte(&vec_buff, WI_I32_CONST); + leb = int_to_leb128((i64) datum->offset_, &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + bh_buffer_write_byte(&vec_buff, WI_BLOCK_END); + } + + if (datum->data != NULL) { + leb = uint_to_uleb128((u64) datum->length, &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + fori (i, 0, datum->length) bh_buffer_write_byte(&vec_buff, ((u8 *) datum->data)[i]); + } else { + leb = uint_to_uleb128(0, &leb_len); + bh_buffer_append(&vec_buff, leb, leb_len); + } + } + + leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); + bh_buffer_append(buff, leb, leb_len); + + bh_buffer_concat(buff, vec_buff); + bh_buffer_free(&vec_buff); + + return buff->length - prev_len; +} + +static i32 output_onyx_libraries_section(OnyxWasmModule* module, bh_buffer* buff) { + if (bh_arr_length(module->libraries) == 0) return 0; + i32 prev_len = buff->length; + + bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); + + bh_buffer libs_buff; + bh_buffer_init(&libs_buff, buff->allocator, 128); + + output_custom_section_name("_onyx_libs", &libs_buff); + + output_unsigned_integer(bh_arr_length(module->library_paths), &libs_buff); + + bh_arr_each(char *, lib, module->library_paths) { + assert(*lib != NULL); + + u32 lib_len = strlen(*lib); + output_unsigned_integer(lib_len, &libs_buff); + bh_buffer_append(&libs_buff, *lib, lib_len); + } + + output_unsigned_integer(bh_arr_length(module->libraries), &libs_buff); + + bh_arr_each(char *, lib, module->libraries) { + assert(*lib != NULL); + + u32 lib_len = strlen(*lib); + output_unsigned_integer(lib_len, &libs_buff); + bh_buffer_append(&libs_buff, *lib, lib_len); + } + + output_unsigned_integer(libs_buff.length, buff); + + bh_buffer_concat(buff, libs_buff); + bh_buffer_free(&libs_buff); + + return buff->length - prev_len; +} + +static i32 output_onyx_func_offset_section(OnyxWasmModule* module, bh_buffer* buff) { + i32 prev_len = buff->length; + + bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); + + bh_buffer section_buff; + bh_buffer_init(§ion_buff, buff->allocator, 128); + + output_custom_section_name("_onyx_func_offsets", §ion_buff); + + i32 func_count = bh_arr_length(module->funcs) + module->foreign_function_count; + + bh_buffer name_buff; + bh_buffer_init(&name_buff, buff->allocator, 1024); + u32 str_cursor = func_count * 4; + fori (i, 0, func_count) { + bh_buffer_write_u32(§ion_buff, str_cursor); + + if (i < module->foreign_function_count) { + bh_buffer_append(&name_buff, "", 20); + str_cursor += 20; + } else { + WasmFunc *func = &module->funcs[i - module->foreign_function_count]; + assert(func->location); + char *str = bh_bprintf("%s:%d,%d\0", func->location->pos.filename, func->location->pos.line, func->location->pos.column); + i32 len = strlen(str); + bh_buffer_append(&name_buff, str, len + 1); + str_cursor += len + 1; + } + } + + bh_buffer_concat(§ion_buff, name_buff); + + output_unsigned_integer(section_buff.length, buff); + + bh_buffer_concat(buff, section_buff); + bh_buffer_free(§ion_buff); + + return buff->length - prev_len; +} + +#ifdef ENABLE_DEBUG_INFO +static i32 output_ovm_debug_sections(OnyxWasmModule* module, bh_buffer* buff) { + if (!module->debug_context || !context.options->debug_enabled) return 0; + + DebugContext *ctx = module->debug_context; + + bh_buffer section_buff; + bh_buffer_init(§ion_buff, buff->allocator, 128); + + { + // ovm_debug_files section + bh_buffer_clear(§ion_buff); + bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); + + output_custom_section_name("ovm_debug_files", §ion_buff); + + i32 file_count = shlenu(ctx->file_info); + output_unsigned_integer(file_count, §ion_buff); + + fori (i, 0, file_count) { + Table(DebugFileInfo) entry = (void *) &ctx->file_info[i]; + output_unsigned_integer(entry->value.file_id, §ion_buff); + output_unsigned_integer(entry->value.line_count, §ion_buff); + output_name(entry->key, strlen(entry->key), §ion_buff); + } + + output_unsigned_integer(section_buff.length, buff); + + bh_buffer_concat(buff, section_buff); + } + + { + // ovm_debug_funcs section + bh_buffer_clear(§ion_buff); + bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); + + output_custom_section_name("ovm_debug_funcs", §ion_buff); + + i32 func_count = bh_arr_length(ctx->funcs); + output_unsigned_integer(func_count, §ion_buff); + + fori (i, 0, func_count) { + DebugFuncContext *func = &ctx->funcs[i]; + output_unsigned_integer(func->func_index, §ion_buff); + output_unsigned_integer(func->file_id, §ion_buff); + output_unsigned_integer(func->line, §ion_buff); + output_name(func->name, func->name_length, §ion_buff); + output_unsigned_integer(1, §ion_buff); + output_unsigned_integer(func->op_offset, §ion_buff); + + LocalAllocator *locals = &module->funcs[i].locals; + if (func->stack_ptr_idx > 0) { + u32 local_idx = local_lookup_idx(locals, func->stack_ptr_idx); + output_unsigned_integer(local_idx, §ion_buff); + } else { + output_unsigned_integer(0, §ion_buff); + } + + output_unsigned_integer(0, §ion_buff); + } + + output_unsigned_integer(section_buff.length, buff); + + bh_buffer_concat(buff, section_buff); + } + + { + // ovm_debug_syms section + + // First, apply patches for register locations + bh_arr_each(DebugSymPatch, patch, ctx->sym_patches) { + // CLEANUP: This is (kind of) incorrect, as there is nothing guarenteeing + // that the symbol with id a will be a position a, other than the way + // that this has been implemented right now. + assert(ctx->sym_info[patch->sym_id].location_type == DSL_REGISTER); + + LocalAllocator *locals = &module->funcs[patch->func_idx - module->foreign_function_count].locals; + ctx->sym_info[patch->sym_id].location_num = local_lookup_idx(locals, patch->local_idx); + } + + bh_buffer_clear(§ion_buff); + bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); + + output_custom_section_name("ovm_debug_syms", §ion_buff); + + i32 sym_count = bh_arr_length(ctx->sym_info); + output_unsigned_integer(sym_count, §ion_buff); + + fori (i, 0, sym_count) { + DebugSymInfo *sym = &ctx->sym_info[i]; + output_unsigned_integer(sym->sym_id, §ion_buff); + if (sym->name) { + output_name(sym->name, strlen(sym->name), §ion_buff); + } else { + output_unsigned_integer(0, §ion_buff); + } + output_unsigned_integer(sym->location_type, §ion_buff); + output_unsigned_integer(sym->location_num, §ion_buff); + output_unsigned_integer(sym->type, §ion_buff); + } + + output_unsigned_integer(section_buff.length, buff); + + bh_buffer_concat(buff, section_buff); + } + + { + // ovm_debug_types section + bh_buffer_clear(§ion_buff); + bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); + + output_custom_section_name("ovm_debug_types", §ion_buff); + + i32 type_count = bh_arr_length(type_map.entries); + output_unsigned_integer(type_count, §ion_buff); + + bh_arr_each(bh__imap_entry, entry, type_map.entries) { + u32 id = entry->key; + Type *type = (Type *) entry->value; + const char *name = type_get_name(type); + + output_unsigned_integer(id, §ion_buff); + output_name(name, strlen(name), §ion_buff); + output_unsigned_integer(type_size_of(type), §ion_buff); + + if (type->kind == Type_Kind_Basic) { + // Type indicies are special because they are encoded + // as a "distinct" unsigned 32-bit integer, which is + // effectively how they are used in the code anyway. + // + // This is probably a change that will be made throughout + // the entire compiler, but for now they will remain as + // a special type. + if (type->Basic.kind == Basic_Kind_Type_Index) { + output_unsigned_integer(5, §ion_buff); + output_unsigned_integer(2, §ion_buff); + output_unsigned_integer(basic_types[Basic_Kind_U32].id, §ion_buff); + continue; + } + + if (type->Basic.kind == Basic_Kind_Rawptr) { + // rawptr -> ^void + output_unsigned_integer(2, §ion_buff); + output_unsigned_integer(1, §ion_buff); + output_unsigned_integer(basic_types[Basic_Kind_Void].id, §ion_buff); + continue; + } + + output_unsigned_integer(1, §ion_buff); + if (type->Basic.kind == Basic_Kind_Void) output_unsigned_integer(0, §ion_buff); + else if (type_is_bool(type)) output_unsigned_integer(4, §ion_buff); + else if (type_is_integer(type)) { + if (type->Basic.flags & Basic_Flag_Unsigned) output_unsigned_integer(2, §ion_buff); + else output_unsigned_integer(1, §ion_buff); + } + else if (type->Basic.flags & Basic_Flag_Float) output_unsigned_integer(3, §ion_buff); + else if (type_is_simd(type)) output_unsigned_integer(6, §ion_buff); + else { + output_unsigned_integer(0, §ion_buff); + } + + continue; + } + + if (type->kind == Type_Kind_Pointer) { + output_unsigned_integer(2, §ion_buff); + output_unsigned_integer(1, §ion_buff); + output_unsigned_integer(type->Pointer.elem->id, §ion_buff); + continue; + } + + if (type->kind == Type_Kind_Enum) { + output_unsigned_integer(5, §ion_buff); + output_unsigned_integer(2, §ion_buff); + output_unsigned_integer(type->Enum.backing->id, §ion_buff); + continue; + } + + if (type->kind == Type_Kind_Array) { + output_unsigned_integer(4, §ion_buff); + output_unsigned_integer(type->Array.count, §ion_buff); + output_unsigned_integer(type->Array.elem->id, §ion_buff); + continue; + } + + if (type_is_structlike_strict(type)) { + output_unsigned_integer(3, §ion_buff); + + i32 mem_count = type_structlike_mem_count(type); + output_unsigned_integer(mem_count, §ion_buff); + + fori (i, 0, mem_count) { + StructMember smem; + type_lookup_member_by_idx(type, i, &smem); + + output_unsigned_integer(smem.offset, §ion_buff); + output_unsigned_integer(smem.type->id, §ion_buff); + output_name(smem.name, strlen(smem.name), §ion_buff); + } + + continue; + } + + if (type->kind == Type_Kind_Function) { + output_unsigned_integer(6, §ion_buff); + output_unsigned_integer(type->Function.param_count, §ion_buff); + + fori (i, 0, (i32) type->Function.param_count) { + output_unsigned_integer(type->Function.params[i]->id, §ion_buff); + } + + output_unsigned_integer(type->Function.return_type->id, §ion_buff); + continue; + } + + if (type->kind == Type_Kind_Distinct) { + output_unsigned_integer(5, §ion_buff); + output_unsigned_integer(2, §ion_buff); + output_unsigned_integer(type->Distinct.base_type->id, §ion_buff); + continue; + } + + // No debug information will be given about the poly struct + // or compound types. + // Outside of runtime type information, they provide no useful + // debugging information (I don't think at least...). + if (type->kind == Type_Kind_PolyStruct || + type->kind == Type_Kind_Compound) { + output_unsigned_integer(1, §ion_buff); + output_unsigned_integer(0, §ion_buff); + continue; + } + + assert(("Unhandled type", 0)); + } + + output_unsigned_integer(section_buff.length, buff); + + bh_buffer_concat(buff, section_buff); + } + + { + // ovm_debug_ops section + bh_buffer_clear(§ion_buff); + bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); + + output_custom_section_name("ovm_debug_ops", §ion_buff); + bh_buffer_concat(§ion_buff, ctx->op_buffer); + + output_unsigned_integer(section_buff.length, buff); + + bh_buffer_concat(buff, section_buff); + } + + bh_buffer_free(§ion_buff); + return 0; +} +#endif + +void onyx_wasm_module_write_to_buffer(OnyxWasmModule* module, bh_buffer* buffer) { + bh_buffer_init(buffer, global_heap_allocator, 128); + if (context.options->runtime == Runtime_Onyx) { + bh_buffer_append(buffer, ONYX_MAGIC_STRING, 4); + } else { + bh_buffer_append(buffer, WASM_MAGIC_STRING, 4); + } + bh_buffer_append(buffer, WASM_VERSION, 4); + +#ifdef ENABLE_DEBUG_INFO + output_ovm_debug_sections(module, buffer); +#endif + output_typesection(module, buffer); + output_importsection(module, buffer); + output_funcsection(module, buffer); + output_tablesection(module, buffer); + output_memorysection(module, buffer); + output_globalsection(module, buffer); + output_exportsection(module, buffer); + output_startsection(module, buffer); + output_elemsection(module, buffer); + output_datacountsection(module, buffer); + output_codesection(module, buffer); + output_datasection(module, buffer); + output_onyx_libraries_section(module, buffer); + + // TODO: Consider if this should always be included? + // It can amount to a lot of extra data. + output_onyx_func_offset_section(module, buffer); +} + +void onyx_wasm_module_write_to_file(OnyxWasmModule* module, bh_file file) { + bh_buffer master_buffer; + onyx_wasm_module_write_to_buffer(module, &master_buffer); + + bh_file_write(&file, master_buffer.data, master_buffer.length); +} diff --git a/compiler/src/wasm_runtime.c b/compiler/src/wasm_runtime.c new file mode 100644 index 00000000..09797d4d --- /dev/null +++ b/compiler/src/wasm_runtime.c @@ -0,0 +1,434 @@ +#include "bh.h" +#include "utils.h" +#include "astnodes.h" +#include "wasm.h" +#include "onyx_library.h" + +#ifndef USE_OVM_DEBUGGER + #include "wasmer.h" +#endif + +#ifdef _BH_LINUX + #include + #include + #include + #include +#endif + +static wasm_config_t* wasm_config; +static wasm_engine_t* wasm_engine; +static wasm_store_t* wasm_store; +static wasm_extern_vec_t wasm_imports; +static bh_buffer wasm_raw_bytes; +wasm_instance_t* wasm_instance; +wasm_module_t* wasm_module; +wasm_memory_t* wasm_memory; + +OnyxRuntime wasm_runtime; + +b32 wasm_name_equals(const wasm_name_t* name1, const wasm_name_t* name2) { + if (name1->size != name2->size) return 0; + return !strncmp(name1->data, name2->data, name1->size); +} + +b32 wasm_name_equals_string(const wasm_name_t* name1, const char* name2) { + u32 name2_size = strlen(name2); + if (name1->size != name2_size) return 0; + return !strncmp(name1->data, name2, name1->size); +} + +wasm_extern_t* wasm_extern_lookup_by_name(wasm_module_t* module, wasm_instance_t* instance, const char* name) { + i32 name_len = strlen(name); + + i32 idx = -1; + wasm_exporttype_vec_t export_types; + wasm_module_exports(module, &export_types); + fori (i, 0, (i64) export_types.size) { + wasm_exporttype_t* export_type = export_types.data[i]; + const wasm_name_t* export_name = wasm_exporttype_name(export_type); + + if (!strncmp(export_name->data, name, name_len)) { + idx = i; + break; + } + } + + if (idx == -1) return NULL; + + wasm_extern_vec_t exports; + wasm_instance_exports(instance, &exports); + + return exports.data[idx]; +} + + + +typedef void *(*LibraryLinker)(OnyxRuntime *runtime); + +typedef struct LibraryLinkContext { + bh_buffer wasm_bytes; + bh_arr(char *) library_paths; +} LibraryLinkContext; + +static WasmFuncDefinition** onyx_load_library(LibraryLinkContext *ctx, char *name) { + #ifdef _BH_LINUX + #define DIR_SEPARATOR '/' + #endif + #ifdef _BH_WINDOWS + #define DIR_SEPARATOR '\\' + #endif + + char *library = name; + fori (i, 0, (i32) strlen(name)) { + if (name[i] == DIR_SEPARATOR) library = &name[i + 1]; + } + + char *library_load_name_tmp = bh_bprintf("onyx_library_%s", library); + char *library_load_name = alloca(strlen(library_load_name_tmp) + 1); + strcpy(library_load_name, library_load_name_tmp); + LibraryLinker library_load; + + #ifdef _BH_LINUX + char *library_name = bh_lookup_file(name, ".", ".so", 1, (const char **) ctx->library_paths, 1); + void* handle = dlopen(library_name, RTLD_LAZY); + if (handle == NULL) { + printf("ERROR LOADING LIBRARY %s: %s\n", name, dlerror()); + return NULL; + } + + library_load = (LibraryLinker) dlsym(handle, library_load_name); + if (library_load == NULL) { + printf("ERROR RESOLVING '%s': %s\n", library_load_name, dlerror()); + return NULL; + } + #endif + + #ifdef _BH_WINDOWS + char *library_name = bh_lookup_file(name, ".", ".dll", 1, (const char **) ctx->library_paths, 1); + HMODULE handle = LoadLibraryA(library_name); + if (handle == NULL) { + printf("ERROR LOADING LIBRARY %s: %d\n", name, GetLastError()); + return NULL; + } + + library_load = (LibraryLinker) GetProcAddress(handle, library_load_name); + if (library_load == NULL) { + printf("ERROR RESOLVING '%s': %d\n", library_load_name, GetLastError()); + return NULL; + } + #endif + + return library_load(runtime); +} + +static void lookup_and_load_custom_libraries(LibraryLinkContext *ctx, bh_arr(WasmFuncDefinition **)* p_out) { + bh_arr(WasmFuncDefinition **) out = *p_out; + + bh_buffer wasm_bytes = ctx->wasm_bytes; + + i32 cursor = 8; // skip the magic number and version + while (cursor < wasm_bytes.length) { + u64 section_number = uleb128_to_uint(wasm_bytes.data, &cursor); + u64 section_size = uleb128_to_uint(wasm_bytes.data, &cursor); + + i32 section_start = cursor; + if (section_number == 0) { + u64 name_len = uleb128_to_uint(wasm_bytes.data, &cursor); + if (!strncmp(wasm_bytes.data + cursor, "_onyx_libs", name_len)) { + cursor += name_len; + u64 lib_count = uleb128_to_uint(wasm_bytes.data, &cursor); + + fori (i, 0, (i64) lib_count) { + u64 lib_path_length = uleb128_to_uint(wasm_bytes.data, &cursor); + lib_path_length = bh_min(lib_path_length, 512); + + char *lib_path = malloc(lib_path_length); + strncpy(lib_path, wasm_bytes.data + cursor, lib_path_length); + lib_path[lib_path_length] = '\0'; + bh_path_convert_separators(lib_path); + cursor += lib_path_length; + + bh_arr_push(ctx->library_paths, lib_path); + } + + lib_count = uleb128_to_uint(wasm_bytes.data, &cursor); + + fori (i, 0, (i64) lib_count) { + u64 lib_name_length = uleb128_to_uint(wasm_bytes.data, &cursor); + lib_name_length = bh_min(lib_name_length, 256); + + char library_name[256]; + strncpy(library_name, wasm_bytes.data + cursor, lib_name_length); + library_name[lib_name_length] = '\0'; + cursor += lib_name_length; + + WasmFuncDefinition** lib = onyx_load_library(ctx, library_name); + if (lib) { + bh_arr_push(out, lib); + } + } + break; + } + } + + cursor = section_start + section_size; + } + + *p_out = out; +} + +static void onyx_print_trap(wasm_trap_t* trap) { + wasm_message_t msg; + wasm_trap_message(trap, &msg); + bh_printf("TRAP: %b\n", msg.data, msg.size); + + i32 func_name_section = 0; + + i32 cursor = 8; // skip the magic number and version + while (cursor < wasm_raw_bytes.length) { + u64 section_number = uleb128_to_uint(wasm_raw_bytes.data, &cursor); + u64 section_size = uleb128_to_uint(wasm_raw_bytes.data, &cursor); + + i32 section_start = cursor; + if (section_number == 0) { + u64 name_len = uleb128_to_uint(wasm_raw_bytes.data, &cursor); + if (!strncmp(wasm_raw_bytes.data + cursor, "_onyx_func_offsets", name_len)) { + cursor += name_len; + func_name_section = cursor; + break; + } + } + + cursor = section_start + section_size; + } + + if (func_name_section == 0) return; + + bh_printf("TRACE:\n"); + wasm_frame_vec_t frames; + wasm_trap_trace(trap, &frames); + fori (i, 0, (i32) frames.size) { + i32 func_idx = wasm_frame_func_index(frames.data[i]); + i32 mod_offset = wasm_frame_module_offset(frames.data[i]); + + i32 cursor = func_name_section + 4 * func_idx; + i32 func_offset = *(i32 *) (wasm_raw_bytes.data + cursor); + char* func_name = wasm_raw_bytes.data + func_name_section + func_offset; + + bh_printf(" func[%d]:%p at %s\n", func_idx, mod_offset, func_name); + } +} + +static void cleanup_wasm_objects() { + if (wasm_instance) wasm_instance_delete(wasm_instance); + if (wasm_module) wasm_module_delete(wasm_module); + if (wasm_store) wasm_store_delete(wasm_store); + if (wasm_engine) wasm_engine_delete(wasm_engine); +} + +// +// This could be cleaned up a bit, as this function directly modifies various global variables. +// Those being wasm_memory and wasm_imports. +static b32 link_wasm_imports(bh_arr(WasmFuncDefinition **) linkable_functions, wasm_module_t *wasm_module) { + wasm_importtype_vec_t module_imports; + wasm_module_imports(wasm_module, &module_imports); + + wasm_extern_vec_new_uninitialized(&wasm_imports, module_imports.size); // @Free + + fori (i, 0, (i32) module_imports.size) { + const wasm_name_t* module_name = wasm_importtype_module(module_imports.data[i]); + const wasm_name_t* import_name = wasm_importtype_name(module_imports.data[i]); + + wasm_extern_t* import = NULL; + + if (wasm_name_equals_string(module_name, "onyx")) { + if (wasm_name_equals_string(import_name, "memory")) { + if (wasm_memory == NULL) { + wasm_limits_t limits = { 1024, 65536 }; + wasm_memorytype_t* memory_type = wasm_memorytype_new(&limits); + wasm_memory = wasm_memory_new(wasm_store, memory_type); + } + + import = wasm_memory_as_extern(wasm_memory); + goto import_found; + } + } + + bh_arr_each(WasmFuncDefinition **, library_funcs, linkable_functions) { + WasmFuncDefinition **pcurrent_function = *library_funcs; + while (*pcurrent_function != NULL) { + WasmFuncDefinition *cf = *pcurrent_function; + if (wasm_name_equals_string(module_name, cf->module_name) && wasm_name_equals_string(import_name, cf->import_name)) { + wasm_valtype_vec_t wasm_params; + wasm_valtype_vec_new_uninitialized(&wasm_params, cf->params->count); + fori (k, 0, cf->params->count) wasm_params.data[k] = wasm_valtype_new(cf->params->types[k]); + + wasm_valtype_vec_t wasm_results; + wasm_valtype_vec_new_uninitialized(&wasm_results, cf->results->count); + fori (k, 0, cf->results->count) wasm_results.data[k] = wasm_valtype_new(cf->results->types[k]); + + wasm_functype_t* wasm_functype = wasm_functype_new(&wasm_params, &wasm_results); + + wasm_func_t* wasm_func = wasm_func_new(wasm_store, wasm_functype, cf->func); + import = wasm_func_as_extern(wasm_func); + goto import_found; + } + + pcurrent_function += 1; + } + } + + goto bad_import; + + import_found: + wasm_imports.data[i] = import; + continue; + + + bad_import: + bh_printf("Couldn't find import %b.%b.\n", module_name->data, module_name->size, import_name->data, import_name->size); + return 0; + } + + return 1; +} + +void onyx_run_initialize(b32 debug_enabled) { + wasm_config = wasm_config_new(); + if (!wasm_config) { + cleanup_wasm_objects(); + return; + } + +#ifdef USE_OVM_DEBUGGER + void wasm_config_enable_debug(wasm_config_t *config, int value); + wasm_config_enable_debug(wasm_config, debug_enabled); +#endif + +#ifndef USE_OVM_DEBUGGER + if (debug_enabled) { + printf("Warning: --debug does nothing if libovmwasm.so is not being used!\n"); + } + + // Prefer the LLVM compile because it is faster. This should be configurable from the command line and/or a top-level directive. + if (wasmer_is_compiler_available(LLVM)) { + wasm_config_set_compiler(wasm_config, LLVM); + } + + wasmer_features_t* features = wasmer_features_new(); + wasmer_features_simd(features, 1); + wasmer_features_threads(features, 1); + wasmer_features_bulk_memory(features, 1); + wasm_config_set_features(wasm_config, features); +#endif + + wasm_engine = wasm_engine_new_with_config(wasm_config); + if (!wasm_engine) { + cleanup_wasm_objects(); + return; + } + + wasm_store = wasm_store_new(wasm_engine); + if (!wasm_store) { + cleanup_wasm_objects(); + return; + } + + // See comment in onyx_library.h about us being the linker. + wasm_runtime.wasm_memory_data = &wasm_memory_data; + wasm_runtime.wasm_extern_lookup_by_name = &wasm_extern_lookup_by_name; + wasm_runtime.wasm_extern_as_func = &wasm_extern_as_func; + wasm_runtime.wasm_func_call = &wasm_func_call; + wasm_runtime.wasm_instance_new = &wasm_instance_new; + wasm_runtime.wasm_store_new = &wasm_store_new; + wasm_runtime.wasm_store_delete = &wasm_store_delete; + wasm_runtime.onyx_print_trap = &onyx_print_trap; +} + +b32 onyx_run_wasm(bh_buffer wasm_bytes, int argc, char *argv[]) { + runtime = &wasm_runtime; + + if (strncmp(wasm_bytes.data, "ONYX", 4)) { + printf("Bad magic bytes for Onyx binary.\n"); + return 0; + } + + wasm_bytes.data[0] = '\0'; + wasm_bytes.data[1] = 'a'; + wasm_bytes.data[2] = 's'; + wasm_bytes.data[3] = 'm'; + wasm_raw_bytes = wasm_bytes; + + bh_arr(WasmFuncDefinition **) linkable_functions = NULL; + bh_arr_new(bh_heap_allocator(), linkable_functions, 4); + + { + LibraryLinkContext lib_ctx; + lib_ctx.wasm_bytes = wasm_bytes; + lib_ctx.library_paths = NULL; + lookup_and_load_custom_libraries(&lib_ctx, &linkable_functions); + + bh_arr_free(lib_ctx.library_paths); + } + + wasm_byte_vec_t wasm_data; + wasm_data.size = wasm_bytes.length; + wasm_data.data = wasm_bytes.data; + + wasm_module = wasm_module_new(wasm_store, &wasm_data); + if (!wasm_module) { + cleanup_wasm_objects(); + return 0; + } + + wasm_imports = (wasm_extern_vec_t) WASM_EMPTY_VEC; + if (!link_wasm_imports(linkable_functions, wasm_module)) { + return 0; + } + + wasm_trap_t* traps = NULL; + + wasm_instance = wasm_instance_new(wasm_store, wasm_module, &wasm_imports, &traps); + if (!wasm_instance) { + cleanup_wasm_objects(); + return 0; + } + + wasm_runtime.wasm_engine = wasm_engine; + wasm_runtime.wasm_module = wasm_module; + wasm_runtime.wasm_imports = wasm_imports; + wasm_runtime.wasm_memory = wasm_memory; + wasm_runtime.wasm_instance = wasm_instance; + + wasm_runtime.argc = argc; + wasm_runtime.argv = argv; + + wasm_extern_t* start_extern = wasm_extern_lookup_by_name(wasm_module, wasm_instance, "_start"); + wasm_func_t* start_func = wasm_extern_as_func(start_extern); + + wasm_val_vec_t args; + wasm_val_vec_t results; + wasm_val_vec_new_uninitialized(&args, 0); + wasm_val_vec_new_uninitialized(&results, 1); + + wasm_trap_t *run_trap = wasm_func_call(start_func, &args, &results); + +#if 1 + if (run_trap != NULL) onyx_print_trap(run_trap); +#endif + + goto end; + + bh_printf("An error occured trying to run the WASM module...\n"); + +#ifndef USE_OVM_DEBUGGER + i32 len = wasmer_last_error_length(); + char *buf = alloca(len + 1); + wasmer_last_error_message(buf, len); + bh_printf("%b\n", buf, len); +#endif + +end: + cleanup_wasm_objects(); + return run_trap == NULL; +} diff --git a/compiler/src/wasm_type_table.h b/compiler/src/wasm_type_table.h new file mode 100644 index 00000000..2cb1c671 --- /dev/null +++ b/compiler/src/wasm_type_table.h @@ -0,0 +1,902 @@ +// This file is directly included in src/onxywasm.c +// It is here purely to decrease the amount of clutter in the main file. + +typedef struct StructMethodData { + u32 name_loc; + u32 name_len; + u32 type; + u32 data_loc; +} StructMethodData; + +static u64 build_type_table(OnyxWasmModule* module) { + + bh_arr(u32) base_patch_locations=NULL; + bh_arr_new(global_heap_allocator, base_patch_locations, 256); + +#define PATCH (bh_arr_push(base_patch_locations, table_buffer.length)) +#define WRITE_PTR(val) \ + bh_buffer_align(&table_buffer, POINTER_SIZE); \ + PATCH; \ + if (POINTER_SIZE == 4) bh_buffer_write_u32(&table_buffer, val); \ + if (POINTER_SIZE == 8) bh_buffer_write_u64(&table_buffer, val); +#define WRITE_SLICE(ptr, count) \ + WRITE_PTR(ptr); \ + if (POINTER_SIZE == 4) bh_buffer_write_u32(&table_buffer, count); \ + if (POINTER_SIZE == 8) bh_buffer_write_u64(&table_buffer, count); + + // This is the data behind the "type_table" slice in runtime/info/types.onyx + #if (POINTER_SIZE == 4) + #define Table_Info_Type u32 + #else + #define Table_Info_Type u64 + #endif + u32 type_count = bh_arr_length(type_map.entries) + 1; + Table_Info_Type* table_info = bh_alloc_array(global_heap_allocator, Table_Info_Type, type_count); // HACK + memset(table_info, 0, type_count * sizeof(Table_Info_Type)); + + bh_buffer table_buffer; + bh_buffer_init(&table_buffer, global_heap_allocator, 4096); + + u32 type_table_info_data_id = NEXT_DATA_ID(module); + + ConstExprContext constexpr_ctx; + constexpr_ctx.module = module; + constexpr_ctx.data_id = type_table_info_data_id; + + // Write a "NULL" at the beginning so nothing will have to point to the first byte of the buffer. + bh_buffer_write_u64(&table_buffer, 0); + + bh_arr_each(bh__imap_entry, type_entry, type_map.entries) { + u64 type_idx = type_entry->key; + Type* type = (Type *) type_entry->value; + + switch (type->kind) { + case Type_Kind_Basic: { + table_info[type_idx] = table_buffer.length; + bh_buffer_write_u32(&table_buffer, type->kind); + bh_buffer_write_u32(&table_buffer, type_size_of(type)); + bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); + bh_buffer_write_u32(&table_buffer, type->Basic.kind); + break; + } + + case Type_Kind_Pointer: { + table_info[type_idx] = table_buffer.length; + bh_buffer_write_u32(&table_buffer, type->kind); + bh_buffer_write_u32(&table_buffer, type_size_of(type)); + bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); + bh_buffer_write_u32(&table_buffer, type->Pointer.elem->id); + break; + } + + case Type_Kind_Array: { + table_info[type_idx] = table_buffer.length; + bh_buffer_write_u32(&table_buffer, type->kind); + bh_buffer_write_u32(&table_buffer, type_size_of(type)); + bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); + bh_buffer_write_u32(&table_buffer, type->Array.elem->id); + bh_buffer_write_u32(&table_buffer, type->Array.count); + break; + } + + case Type_Kind_Slice: { + table_info[type_idx] = table_buffer.length; + bh_buffer_write_u32(&table_buffer, type->kind); + bh_buffer_write_u32(&table_buffer, type_size_of(type)); + bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); + bh_buffer_write_u32(&table_buffer, type->Slice.elem->id); + break; + } + + case Type_Kind_DynArray: { + table_info[type_idx] = table_buffer.length; + bh_buffer_write_u32(&table_buffer, type->kind); + bh_buffer_write_u32(&table_buffer, type_size_of(type)); + bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); + bh_buffer_write_u32(&table_buffer, type->DynArray.elem->id); + break; + } + + case Type_Kind_VarArgs: { + table_info[type_idx] = table_buffer.length; + bh_buffer_write_u32(&table_buffer, type->kind); + bh_buffer_write_u32(&table_buffer, type_size_of(type)); + bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); + bh_buffer_write_u32(&table_buffer, type->VarArgs.elem->id); + break; + } + + case Type_Kind_Compound: { + u32 components_base = table_buffer.length; + + u32 components_count = type->Compound.count; + fori (i, 0, components_count) { + u32 type_idx = type->Compound.types[i]->id; + bh_buffer_write_u32(&table_buffer, type_idx); + } + + bh_buffer_align(&table_buffer, 8); + table_info[type_idx] = table_buffer.length; + bh_buffer_write_u32(&table_buffer, type->kind); + bh_buffer_write_u32(&table_buffer, type_size_of(type)); + bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); + WRITE_SLICE(components_base, components_count); + break; + } + + case Type_Kind_Function: { + u32 parameters_base = table_buffer.length; + + u32 parameters_count = type->Function.param_count; + fori (i, 0, parameters_count) { + u32 type_idx = type->Function.params[i]->id; + bh_buffer_write_u32(&table_buffer, type_idx); + } + + table_info[type_idx] = table_buffer.length; + bh_buffer_write_u32(&table_buffer, type->kind); + bh_buffer_write_u32(&table_buffer, type_size_of(type)); + bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); + bh_buffer_write_u32(&table_buffer, type->Function.return_type->id); + + WRITE_SLICE(parameters_base, parameters_count); + + bh_buffer_write_u32(&table_buffer, type->Function.vararg_arg_pos > 0 ? 1 : 0); + break; + } + + case Type_Kind_Enum: { + AstEnumType* ast_enum = (AstEnumType *) type->ast_type; + u32 member_count = bh_arr_length(ast_enum->values); + u32* name_locations = bh_alloc_array(global_scratch_allocator, u32, member_count); + + u32 i = 0; + bh_arr_each(AstEnumValue *, value, ast_enum->values) { + name_locations[i++] = table_buffer.length; + + bh_buffer_append(&table_buffer, (*value)->token->text, (*value)->token->length); + } + bh_buffer_align(&table_buffer, 8); + + u32 member_base = table_buffer.length; + i = 0; + bh_arr_each(AstEnumValue *, value, ast_enum->values) { + u32 name_loc = name_locations[i++]; + + bh_buffer_align(&table_buffer, 8); + WRITE_SLICE(name_loc, (*value)->token->length); + + assert((*value)->value->kind == Ast_Kind_NumLit); + AstNumLit *num = (AstNumLit *) (*value)->value; + bh_buffer_write_u64(&table_buffer, num->value.l); + } + + u32 name_base = table_buffer.length; + u32 name_length = strlen(type->Enum.name); + bh_buffer_append(&table_buffer, type->Enum.name, name_length); + bh_buffer_align(&table_buffer, 8); + + table_info[type_idx] = table_buffer.length; + bh_buffer_write_u32(&table_buffer, type->kind); + bh_buffer_write_u32(&table_buffer, type_size_of(type)); + bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); + bh_buffer_write_u32(&table_buffer, type->Enum.backing->id); + WRITE_SLICE(name_base, name_length); + WRITE_SLICE(member_base, member_count); + bh_buffer_write_u32(&table_buffer, type->Enum.is_flags ? 1 : 0); + break; + } + + case Type_Kind_Struct: { + TypeStruct* s = &type->Struct; + u32* name_locations = bh_alloc_array(global_scratch_allocator, u32, s->mem_count); + u32* param_locations = bh_alloc_array(global_scratch_allocator, u32, bh_arr_length(s->poly_sln)); + u32* value_locations = bh_alloc_array(global_scratch_allocator, u32, s->mem_count); + u32* meta_locations = bh_alloc_array(global_scratch_allocator, u32, s->mem_count); + u32* struct_tag_locations = bh_alloc_array(global_scratch_allocator, u32, bh_arr_length(s->meta_tags)); + memset(value_locations, 0, s->mem_count * sizeof(u32)); + memset(meta_locations, 0, s->mem_count * sizeof(u32)); + memset(struct_tag_locations, 0, bh_arr_length(s->meta_tags) * sizeof(u32)); + + // Member names + u32 i = 0; + bh_arr_each(StructMember*, pmem, s->memarr) { + StructMember* mem = *pmem; + + name_locations[i++] = table_buffer.length; + bh_buffer_append(&table_buffer, mem->name, strlen(mem->name)); + } + + bh_buffer_align(&table_buffer, 8); + + // Polymorphic solutions + i = 0; + bh_arr_each(AstPolySolution, sln, s->poly_sln) { + bh_buffer_align(&table_buffer, 8); + param_locations[i++] = table_buffer.length; + + switch (sln->kind) { + case PSK_Type: { + // NOTE: This assumes a little endian compiler (which is assumed in other part of the code too) + bh_buffer_append(&table_buffer, &sln->type->id, 4); + break; + } + + case PSK_Value: { + assert(sln->value->type); + u32 size = type_size_of(sln->value->type); + + bh_buffer_grow(&table_buffer, table_buffer.length + size); + constexpr_ctx.data = table_buffer.data; + if (emit_constexpr_(&constexpr_ctx, sln->value, table_buffer.length)) { + table_buffer.length += size; + break; + } + + // fallthrough + } + + default: { + // Set to null if this is not known how to encode + param_locations[i-1] = 0; + break; + } + } + } + + bh_buffer_align(&table_buffer, 8); + + // Member default values + i = 0; + bh_arr_each(StructMember*, pmem, s->memarr) { + StructMember* mem = *pmem; + + if (mem->initial_value == NULL || *mem->initial_value == NULL) { + i++; + continue; + } + + AstTyped* value = *mem->initial_value; + assert(value->type); + + if ((value->flags & Ast_Flag_Comptime) == 0) { + // onyx_report_warning(value->token->pos, "Warning: skipping generating default value for '%s' in '%s' because it is not compile-time known.\n", mem->name, s->name); + i++; + continue; + } + + u32 size = type_size_of(value->type); + bh_buffer_align(&table_buffer, type_alignment_of(value->type)); + + bh_buffer_grow(&table_buffer, table_buffer.length + size); + constexpr_ctx.data = table_buffer.data; + if (!emit_constexpr_(&constexpr_ctx, value, table_buffer.length)) { + // Failed to generate raw data + // onyx_report_warning(value->token->pos, "Warning: failed to generate default value for '%s' in '%s'.\n", mem->name, s->name); + value_locations[i++] = 0; + + } else { + // Success + value_locations[i++] = table_buffer.length; + table_buffer.length += size; + } + } + + // Member tags + i = 0; + bh_arr_each(StructMember*, pmem, s->memarr) { + StructMember* mem = *pmem; + + if (mem->meta_tags == NULL) { + i += 1; + continue; + } + + bh_arr(AstTyped *) meta_tags = mem->meta_tags; + assert(meta_tags); + + bh_arr(u64) meta_tag_locations=NULL; + bh_arr_new(global_heap_allocator, meta_tag_locations, bh_arr_length(meta_tags)); + + int j = 0; + bh_arr_each(AstTyped *, meta, meta_tags) { + AstTyped* value = *meta; + assert(value->flags & Ast_Flag_Comptime); + assert(value->type); + + u32 size = type_size_of(value->type); + bh_buffer_align(&table_buffer, type_alignment_of(value->type)); + meta_tag_locations[j] = table_buffer.length; + + bh_buffer_grow(&table_buffer, table_buffer.length + size); + constexpr_ctx.data = table_buffer.data; + assert(emit_constexpr_(&constexpr_ctx, value, table_buffer.length)); + table_buffer.length += size; + + j += 1; + } + + bh_buffer_align(&table_buffer, 8); + meta_locations[i] = table_buffer.length; + + fori (k, 0, bh_arr_length(meta_tags)) { + WRITE_SLICE(meta_tag_locations[k], meta_tags[k]->type->id); + } + + bh_arr_free(meta_tag_locations); + i += 1; + } + + bh_buffer_align(&table_buffer, 8); + u32 members_base = table_buffer.length; + + // Member array + i = 0; + bh_arr_each(StructMember*, pmem, s->memarr) { + StructMember* mem = *pmem; + + u32 name_loc = name_locations[i]; + u32 value_loc = value_locations[i]; + u32 meta_loc = meta_locations[i++]; + + WRITE_SLICE(name_loc, strlen(mem->name)); + bh_buffer_write_u32(&table_buffer, mem->offset); + bh_buffer_write_u32(&table_buffer, mem->type->id); + bh_buffer_write_byte(&table_buffer, mem->used ? 1 : 0); + + WRITE_PTR(value_loc); + + WRITE_SLICE(meta_loc, bh_arr_length(mem->meta_tags)); + } + + bh_buffer_align(&table_buffer, 8); + u32 params_base = table_buffer.length; + + // Polymorphic solution any array + i = 0; + bh_arr_each(AstPolySolution, sln, s->poly_sln) { + WRITE_PTR(param_locations[i++]); + + if (sln->kind == PSK_Type) bh_buffer_write_u32(&table_buffer, basic_types[Basic_Kind_Type_Index].id); + else bh_buffer_write_u32(&table_buffer, sln->value->type->id); + } + + // Struct tag array + i = 0; + bh_arr_each(AstTyped *, tag, s->meta_tags) { + AstTyped* value = *tag; + assert(value->flags & Ast_Flag_Comptime); + assert(value->type); + + u32 size = type_size_of(value->type); + bh_buffer_align(&table_buffer, type_alignment_of(value->type)); + struct_tag_locations[i] = table_buffer.length; + + bh_buffer_grow(&table_buffer, table_buffer.length + size); + constexpr_ctx.data = table_buffer.data; + assert(emit_constexpr_(&constexpr_ctx, value, table_buffer.length)); + table_buffer.length += size; + + i += 1; + } + + // Struct methods + bh_arr(StructMethodData) method_data=NULL; + AstType *ast_type = type->ast_type; + if (ast_type && ast_type->kind == Ast_Kind_Struct_Type) { + AstStructType *struct_type = (AstStructType *) ast_type; + Scope* struct_scope = struct_type->scope; + + if (struct_scope == NULL) goto no_methods; + + fori (i, 0, shlen(struct_scope->symbols)) { + AstFunction* node = (AstFunction *) strip_aliases(struct_scope->symbols[i].value); + if (node->kind != Ast_Kind_Function) continue; + assert(node->entity); + assert(node->entity->function == node); + + // Name + char *name = struct_scope->symbols[i].key; + u32 name_loc = table_buffer.length; + u32 name_len = strlen(name); + bh_buffer_append(&table_buffer, name, name_len); + + // any data member + bh_buffer_align(&table_buffer, 4); + u32 data_loc = table_buffer.length; + u32 func_idx = get_element_idx(module, node); + bh_buffer_write_u32(&table_buffer, func_idx); + + bh_arr_push(method_data, ((StructMethodData) { + .name_loc = name_loc, + .name_len = name_len, + .type = node->type->id, + .data_loc = data_loc, + })); + } + } + + no_methods: + + bh_buffer_align(&table_buffer, 4); + u32 method_data_base = table_buffer.length; + + i = 0; + bh_arr_each(StructMethodData, method, method_data) { + WRITE_SLICE(method->name_loc, method->name_len); + WRITE_PTR(method->data_loc); + bh_buffer_write_u32(&table_buffer, method->type); + } + + bh_buffer_align(&table_buffer, 8); + u32 struct_tag_base = table_buffer.length; + + fori (i, 0, bh_arr_length(s->meta_tags)) { + WRITE_SLICE(struct_tag_locations[i], s->meta_tags[i]->type->id); + } + + // Struct name + u32 name_base = 0; + u32 name_length = 0; + if (s->name) { + name_length = strlen(s->name); + name_base = table_buffer.length; + bh_buffer_append(&table_buffer, s->name, name_length); + } + + bh_buffer_align(&table_buffer, 8); + table_info[type_idx] = table_buffer.length; + bh_buffer_write_u32(&table_buffer, type->kind); + bh_buffer_write_u32(&table_buffer, type_size_of(type)); + bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); + + if (type->Struct.constructed_from != NULL) { + bh_buffer_write_u32(&table_buffer, type->Struct.constructed_from->type_id); + } else { + bh_buffer_write_u32(&table_buffer, 0); + } + + WRITE_SLICE(name_base, name_length); + WRITE_SLICE(members_base, s->mem_count); + WRITE_SLICE(params_base, bh_arr_length(s->poly_sln)); + WRITE_SLICE(struct_tag_base, bh_arr_length(s->meta_tags)); + WRITE_SLICE(method_data_base, bh_arr_length(method_data)); + + bh_arr_free(method_data); + break; + } + + case Type_Kind_PolyStruct: { + u32* tag_locations = bh_alloc_array(global_scratch_allocator, u32, bh_arr_length(type->PolyStruct.meta_tags)); + memset(tag_locations, 0, sizeof(u32) * bh_arr_length(type->PolyStruct.meta_tags)); + + u32 name_base = table_buffer.length; + u32 name_length = strlen(type->PolyStruct.name); + bh_buffer_append(&table_buffer, type->PolyStruct.name, name_length); + + u32 tags_count = bh_arr_length(type->PolyStruct.meta_tags); + i32 i = 0; + bh_arr_each(AstTyped *, tag, type->PolyStruct.meta_tags) { + AstTyped* value = *tag; + + // Polymorphic structs are weird in this case, because the tag might not be constructed generically for + // the polymorphic structure so it should only be constructed for actual solidified structures. + // See core/containers/map.onyx with Custom_Format for an example. + if (!(value->flags & Ast_Flag_Comptime)) { + tags_count--; + continue; + } + + assert(value->type); + + u32 size = type_size_of(value->type); + bh_buffer_align(&table_buffer, type_alignment_of(value->type)); + tag_locations[i] = table_buffer.length; + + bh_buffer_grow(&table_buffer, table_buffer.length + size); + + constexpr_ctx.data = table_buffer.data; + assert(emit_constexpr_(&constexpr_ctx, value, table_buffer.length)); + table_buffer.length += size; + + i += 1; + } + + bh_buffer_align(&table_buffer, 8); + u32 tags_base = table_buffer.length; + + fori (i, 0, tags_count) { + WRITE_SLICE(tag_locations[i], type->PolyStruct.meta_tags[i]->type->id); + } + + bh_buffer_align(&table_buffer, 8); + table_info[type_idx] = table_buffer.length; + bh_buffer_write_u32(&table_buffer, type->kind); + bh_buffer_write_u32(&table_buffer, 0); + bh_buffer_write_u32(&table_buffer, 0); + WRITE_SLICE(name_base, name_length); + WRITE_SLICE(tags_base, tags_count); + + break; + } + + case Type_Kind_Distinct: { + u32 name_base = table_buffer.length; + u32 name_length = strlen(type->Distinct.name); + bh_buffer_append(&table_buffer, type->Distinct.name, name_length); + bh_buffer_align(&table_buffer, 8); + + table_info[type_idx] = table_buffer.length; + bh_buffer_write_u32(&table_buffer, type->kind); + bh_buffer_write_u32(&table_buffer, type_size_of(type)); + bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); + bh_buffer_write_u32(&table_buffer, type->Distinct.base_type->id); + WRITE_SLICE(name_base, name_length); + break; + } + } + } + + if (context.options->verbose_output == 1) { + bh_printf("Type table size: %d bytes.\n", table_buffer.length); + } + + WasmDatum type_info_data = { + .alignment = 8, + .length = table_buffer.length, + .data = table_buffer.data, + }; + emit_data_entry(module, &type_info_data); + assert(type_info_data.id == type_table_info_data_id); + + bh_arr_each(u32, patch_loc, base_patch_locations) { + DatumPatchInfo patch; + patch.kind = Datum_Patch_Relative; + patch.data_id = type_info_data.id; + patch.offset = 0; + patch.index = type_info_data.id; + patch.location = *patch_loc; + bh_arr_push(module->data_patches, patch); + } + + WasmDatum type_table_data = { + .alignment = POINTER_SIZE, + .length = type_count * POINTER_SIZE, + .data = table_info, + }; + emit_data_entry(module, &type_table_data); + + fori (i, 0, type_count) { + DatumPatchInfo patch; + patch.kind = Datum_Patch_Data; + patch.data_id = type_info_data.id; + patch.offset = table_info[i]; + patch.index = type_table_data.id; + patch.location = i * POINTER_SIZE; + bh_arr_push(module->data_patches, patch); + } + + Table_Info_Type* tmp_data = bh_alloc(global_heap_allocator, 2 * POINTER_SIZE); + tmp_data[0] = 0; + tmp_data[1] = type_count; + WasmDatum type_table_global_data = { + .alignment = POINTER_SIZE, + .length = 2 * POINTER_SIZE, + .data = tmp_data, + }; + emit_data_entry(module, &type_table_global_data); + + { + DatumPatchInfo patch; + patch.kind = Datum_Patch_Data; + patch.data_id = type_table_data.id; + patch.offset = 0; + patch.index = type_table_global_data.id; + patch.location = 0; + bh_arr_push(module->data_patches, patch); + } + + return type_table_global_data.id; + +#undef WRITE_SLICE +#undef WRITE_PTR +#undef PATCH +} + + + +static u64 build_foreign_blocks(OnyxWasmModule* module) { + bh_arr(u32) base_patch_locations=NULL; + bh_arr_new(global_heap_allocator, base_patch_locations, 256); + +#define PATCH (bh_arr_push(base_patch_locations, foreign_buffer.length)) +#define WRITE_PTR(val) \ + bh_buffer_align(&foreign_buffer, POINTER_SIZE); \ + PATCH; \ + if (POINTER_SIZE == 4) bh_buffer_write_u32(&foreign_buffer, val); \ + if (POINTER_SIZE == 8) bh_buffer_write_u64(&foreign_buffer, val); +#define WRITE_SLICE(ptr, count) \ + WRITE_PTR(ptr); \ + if (POINTER_SIZE == 4) bh_buffer_write_u32(&foreign_buffer, count); \ + if (POINTER_SIZE == 8) bh_buffer_write_u64(&foreign_buffer, count); + + // This is the data behind the "type_table" slice in type_info.onyx + #if (POINTER_SIZE == 4) + #define Foreign_Block_Type u32 + #else + #define Foreign_Block_Type u64 + #endif + u32 block_count = bh_arr_length(module->foreign_blocks); + Foreign_Block_Type* foreign_info = bh_alloc_array(global_heap_allocator, Foreign_Block_Type, block_count); // HACK + memset(foreign_info, 0, block_count * sizeof(Foreign_Block_Type)); + + bh_buffer foreign_buffer; + bh_buffer_init(&foreign_buffer, global_heap_allocator, 4096); + + // + // This is necessary because 0 is an invalid offset to store in this + // buffer, as 0 will map to NULL. This could be a single byte insertion, + // but 64 bytes keeps better alignment. + bh_buffer_write_u64(&foreign_buffer, 0); + + u32 index = 0; + bh_arr_each(AstForeignBlock *, pfb, module->foreign_blocks) { + AstForeignBlock *fb = *pfb; + + u32 funcs_length = 0; + + u32 *name_offsets = bh_alloc_array(global_scratch_allocator, u32, shlen(fb->scope->symbols)); + u32 *name_lengths = bh_alloc_array(global_scratch_allocator, u32, shlen(fb->scope->symbols)); + u32 *func_types = bh_alloc_array(global_scratch_allocator, u32, shlen(fb->scope->symbols)); + + fori (i, 0, shlen(fb->scope->symbols)) { + AstFunction *func = (AstFunction *) fb->scope->symbols[i].value; + if (func->kind != Ast_Kind_Function) continue; + + u32 func_name_base = foreign_buffer.length; + u32 func_name_length = func->foreign_name->length; + bh_buffer_append(&foreign_buffer, func->foreign_name->text, func_name_length); + + name_offsets[funcs_length] = func_name_base; + name_lengths[funcs_length] = func_name_length; + func_types[funcs_length] = func->type->id; + funcs_length++; + } + + bh_buffer_align(&foreign_buffer, 8); + u32 funcs_base = foreign_buffer.length; + + fori (i, 0, (i64) funcs_length) { + bh_buffer_align(&foreign_buffer, POINTER_SIZE); + WRITE_SLICE(name_offsets[i], name_lengths[i]); + bh_buffer_write_u32(&foreign_buffer, func_types[i]); + } + + u32 name_base = foreign_buffer.length; + u32 name_length = fb->module_name->length; + bh_buffer_append(&foreign_buffer, fb->module_name->text, name_length); + bh_buffer_align(&foreign_buffer, 8); + + foreign_info[index] = foreign_buffer.length; + WRITE_SLICE(name_base, name_length); + WRITE_SLICE(funcs_base, funcs_length); + index++; + } + + + if (context.options->verbose_output == 1) { + bh_printf("Foreign blocks size: %d bytes.\n", foreign_buffer.length); + } + + WasmDatum foreign_info_data = { + .alignment = 8, + .length = foreign_buffer.length, + .data = foreign_buffer.data, + }; + emit_data_entry(module, &foreign_info_data); + + bh_arr_each(u32, patch_loc, base_patch_locations) { + DatumPatchInfo patch; + patch.kind = Datum_Patch_Relative; + patch.data_id = foreign_info_data.id; + patch.offset = 0; + patch.index = foreign_info_data.id; + patch.location = *patch_loc; + bh_arr_push(module->data_patches, patch); + } + + WasmDatum foreign_table_data = { + .alignment = POINTER_SIZE, + .length = block_count * POINTER_SIZE, + .data = foreign_info, + }; + emit_data_entry(module, &foreign_table_data); + + fori (i, 0, block_count) { + DatumPatchInfo patch; + patch.kind = Datum_Patch_Data; + patch.data_id = foreign_info_data.id; + patch.offset = foreign_info[i]; + patch.index = foreign_table_data.id; + patch.location = i * POINTER_SIZE; + bh_arr_push(module->data_patches, patch); + } + + Foreign_Block_Type* tmp_data = bh_alloc(global_heap_allocator, 2 * POINTER_SIZE); + tmp_data[0] = 0; + tmp_data[1] = block_count; + WasmDatum foreign_table_global_data = { + .alignment = POINTER_SIZE, + .length = 2 * POINTER_SIZE, + .data = tmp_data, + }; + emit_data_entry(module, &foreign_table_global_data); + + { + DatumPatchInfo patch; + patch.kind = Datum_Patch_Data; + patch.data_id = foreign_table_data.id; + patch.location = 0; + patch.index = foreign_table_global_data.id; + patch.offset = 0; + bh_arr_push(module->data_patches, patch); + } + + return foreign_table_global_data.id; + +#undef WRITE_SLICE +#undef WRITE_PTR +#undef PATCH +} + + + +static u64 build_tagged_procedures(OnyxWasmModule *module) { + bh_arr(u32) base_patch_locations=NULL; + bh_arr_new(global_heap_allocator, base_patch_locations, 256); + +#define PATCH (bh_arr_push(base_patch_locations, tag_proc_buffer.length)) +#define WRITE_PTR(val) \ + bh_buffer_align(&tag_proc_buffer, POINTER_SIZE); \ + PATCH; \ + if (POINTER_SIZE == 4) bh_buffer_write_u32(&tag_proc_buffer, val); \ + if (POINTER_SIZE == 8) bh_buffer_write_u64(&tag_proc_buffer, val); +#define WRITE_SLICE(ptr, count) \ + WRITE_PTR(ptr); \ + if (POINTER_SIZE == 4) bh_buffer_write_u32(&tag_proc_buffer, count); \ + if (POINTER_SIZE == 8) bh_buffer_write_u64(&tag_proc_buffer, count); + + #if (POINTER_SIZE == 4) + #define Tagged_Procedure_Type u32 + #else + #define Tagged_Procedure_Type u64 + #endif + u32 proc_count = bh_arr_length(module->procedures_with_tags); + Tagged_Procedure_Type* tag_proc_info = bh_alloc_array(global_heap_allocator, Tagged_Procedure_Type, proc_count); // HACK + memset(tag_proc_info, 0, proc_count * sizeof(Tagged_Procedure_Type)); + + bh_buffer tag_proc_buffer; + bh_buffer_init(&tag_proc_buffer, global_heap_allocator, 4096); + + u32 proc_info_data_id = NEXT_DATA_ID(module); + + ConstExprContext constexpr_ctx; + constexpr_ctx.module = module; + constexpr_ctx.data_id = proc_info_data_id; + + // + // This is necessary because 0 is an invalid offset to store in this + // buffer, as 0 will map to NULL. This could be a single byte insertion, + // but 64 bytes keeps better alignment. + bh_buffer_write_u64(&tag_proc_buffer, 0); + + u32 index = 0; + bh_arr_each(AstFunction *, pfunc, module->procedures_with_tags) { + AstFunction *func = *pfunc; + if (!should_emit_function(func)) { + proc_count--; + continue; + } + + u32 tag_count = bh_arr_length(func->tags); + u32 *tag_data_offsets = bh_alloc_array(global_scratch_allocator, u32, tag_count); + u32 *tag_data_types = bh_alloc_array(global_scratch_allocator, u32, tag_count); + + u32 tag_index = 0; + bh_arr_each(AstTyped *, ptag, func->tags) { + AstTyped *tag = *ptag; + bh_buffer_align(&tag_proc_buffer, type_alignment_of(tag->type)); + + tag_data_offsets[tag_index ] = tag_proc_buffer.length; + tag_data_types [tag_index++] = tag->type->id; + + u32 size = type_size_of(tag->type); + bh_buffer_grow(&tag_proc_buffer, tag_proc_buffer.length + size); + + constexpr_ctx.data = tag_proc_buffer.data; + emit_constexpr(&constexpr_ctx, tag, tag_proc_buffer.length); + tag_proc_buffer.length += size; + } + + bh_buffer_align(&tag_proc_buffer, 4); + u32 tag_array_base = tag_proc_buffer.length; + fori (i, 0, tag_count) { + PATCH; + bh_buffer_write_u32(&tag_proc_buffer, tag_data_offsets[i]); + bh_buffer_write_u32(&tag_proc_buffer, tag_data_types[i]); + } + + bh_buffer_align(&tag_proc_buffer, 4); + tag_proc_info[index++] = tag_proc_buffer.length; + + bh_buffer_write_u32(&tag_proc_buffer, get_element_idx(module, func)); + bh_buffer_write_u32(&tag_proc_buffer, func->type->id); + WRITE_SLICE(tag_array_base, tag_count); + } + + if (context.options->verbose_output == 1) { + bh_printf("Tagged procedure size: %d bytes.\n", tag_proc_buffer.length); + } + + WasmDatum proc_info_data = { + .alignment = 8, + .length = tag_proc_buffer.length, + .data = tag_proc_buffer.data, + }; + emit_data_entry(module, &proc_info_data); + assert(proc_info_data.id == proc_info_data_id); + + bh_arr_each(u32, patch_loc, base_patch_locations) { + DatumPatchInfo patch; + patch.kind = Datum_Patch_Relative; + patch.data_id = proc_info_data.id; + patch.location = *patch_loc; + patch.index = proc_info_data.id; + patch.offset = 0; + bh_arr_push(module->data_patches, patch); + } + + WasmDatum proc_table_data = { + .alignment = POINTER_SIZE, + .length = proc_count * POINTER_SIZE, + .data = tag_proc_info, + }; + emit_data_entry(module, &proc_table_data); + + fori (i, 0, proc_count) { + DatumPatchInfo patch; + patch.kind = Datum_Patch_Data; + patch.data_id = proc_info_data.id; + patch.offset = tag_proc_info[i]; + patch.index = proc_table_data.id; + patch.location = i * POINTER_SIZE; + bh_arr_push(module->data_patches, patch); + } + + Tagged_Procedure_Type* tmp_data = bh_alloc(global_heap_allocator, 2 * POINTER_SIZE); + tmp_data[0] = 0; + tmp_data[1] = proc_count; + WasmDatum proc_table_global_data = { + .alignment = POINTER_SIZE, + .length = 2 * POINTER_SIZE, + .data = tmp_data, + }; + emit_data_entry(module, &proc_table_global_data); + + { + DatumPatchInfo patch; + patch.kind = Datum_Patch_Data; + patch.offset = 0; + patch.data_id = proc_table_data.id; + patch.index = proc_table_global_data.id; + patch.location = 0; + bh_arr_push(module->data_patches, patch); + } + + return proc_table_global_data.id; + +#undef WRITE_SLICE +#undef WRITE_PTR +#undef PATCH +} + diff --git a/include/astnodes.h b/include/astnodes.h deleted file mode 100644 index 0052d233..00000000 --- a/include/astnodes.h +++ /dev/null @@ -1,1841 +0,0 @@ -#ifndef ONYXASTNODES_H -#define ONYXASTNODES_H - -#include "stb_ds.h" -#include "lex.h" -#include "types.h" -#include "errors.h" - -#define AST_NODES \ - NODE(Node) \ - NODE(Typed) \ - \ - NODE(NamedValue) \ - NODE(BinaryOp) \ - NODE(UnaryOp) \ - NODE(NumLit) \ - NODE(StrLit) \ - NODE(Local) \ - NODE(Call) \ - NODE(Argument) \ - NODE(AddressOf) \ - NODE(Dereference) \ - NODE(Subscript) \ - NODE(FieldAccess) \ - NODE(UnaryFieldAccess) \ - NODE(SizeOf) \ - NODE(AlignOf) \ - NODE(FileContents) \ - NODE(StructLiteral) \ - NODE(ArrayLiteral) \ - NODE(RangeLiteral) \ - NODE(Compound) \ - NODE(IfExpression) \ - NODE(DoBlock) \ - \ - NODE(DirectiveSolidify) \ - NODE(DirectiveError) \ - NODE(DirectiveAddOverload) \ - NODE(DirectiveOperator) \ - NODE(DirectiveExport) \ - NODE(DirectiveDefined) \ - NODE(DirectiveInit) \ - NODE(DirectiveLibrary) \ - NODE(DirectiveRemove) \ - \ - NODE(Return) \ - NODE(Jump) \ - NODE(Use) \ - \ - NODE(Block) \ - NODE(IfWhile) \ - NODE(For) \ - NODE(Defer) \ - NODE(SwitchCase) \ - NODE(Switch) \ - \ - NODE(Type) \ - NODE(BasicType) \ - NODE(PointerType) \ - NODE(FunctionType) \ - NODE(ArrayType) \ - NODE(SliceType) \ - NODE(DynArrType) \ - NODE(VarArgType) \ - NODE(StructType) \ - NODE(StructMember) \ - NODE(PolyStructType) \ - NODE(PolyStructParam) \ - NODE(PolyCallType) \ - NODE(EnumType) \ - NODE(EnumValue) \ - NODE(TypeAlias) \ - NODE(TypeRawAlias) \ - NODE(CompoundType) \ - NODE(TypeOf) \ - NODE(DistinctType) \ - \ - NODE(Binding) \ - NODE(Alias) \ - NODE(Injection) \ - NODE(MemRes) \ - NODE(Include) \ - NODE(UsePackage) \ - NODE(Global) \ - NODE(Param) \ - NODE(Function) \ - NODE(OverloadedFunction) \ - NODE(Interface) \ - NODE(Constraint) \ - \ - NODE(PolyParam) \ - NODE(PolySolution) \ - NODE(SolidifiedFunction) \ - NODE(PolyProc) \ - NODE(PolyQuery) \ - \ - NODE(Note) \ - NODE(CallSite) \ - \ - NODE(CodeBlock) \ - NODE(DirectiveInsert) \ - NODE(Macro) \ - \ - NODE(ForeignBlock) \ - \ - NODE(Package) \ - \ - NODE(ZeroValue) - -#define NODE(name) typedef struct Ast ## name Ast ## name; -AST_NODES -#undef NODE - -typedef struct Package Package; - -typedef struct Scope { - u64 id; - struct Scope *parent; - OnyxFilePos created_at; - char* name; - Table(AstNode *) symbols; -} Scope; - - -typedef enum AstKind { - Ast_Kind_Error, - Ast_Kind_Package, - Ast_Kind_Load_File, - Ast_Kind_Load_Path, - Ast_Kind_Load_All, - Ast_Kind_Library_Path, - Ast_Kind_Memres, - - Ast_Kind_Binding, - Ast_Kind_Alias, - Ast_Kind_Injection, - Ast_Kind_Function, - Ast_Kind_Overloaded_Function, - Ast_Kind_Polymorphic_Proc, - Ast_Kind_Polymorph_Query, - Ast_Kind_Interface, - Ast_Kind_Constraint, - Ast_Kind_Constraint_Sentinel, - Ast_Kind_Block, - Ast_Kind_Local, - Ast_Kind_Global, - Ast_Kind_Symbol, - - Ast_Kind_Unary_Op, - Ast_Kind_Binary_Op, - - Ast_Kind_Compound, - Ast_Kind_Named_Value, - - Ast_Kind_Type_Start, - Ast_Kind_Type, - Ast_Kind_Basic_Type, - Ast_Kind_Pointer_Type, - Ast_Kind_Function_Type, - Ast_Kind_Array_Type, - Ast_Kind_Slice_Type, - Ast_Kind_DynArr_Type, - Ast_Kind_VarArg_Type, - Ast_Kind_Struct_Type, - Ast_Kind_Poly_Struct_Type, - Ast_Kind_Poly_Call_Type, - Ast_Kind_Enum_Type, - Ast_Kind_Type_Alias, - Ast_Kind_Type_Raw_Alias, - Ast_Kind_Type_Compound, - Ast_Kind_Typeof, - Ast_Kind_Distinct_Type, - Ast_Kind_Type_End, - - Ast_Kind_Struct_Member, - Ast_Kind_Enum_Value, - - Ast_Kind_NumLit, - Ast_Kind_StrLit, - Ast_Kind_Param, - Ast_Kind_Argument, - Ast_Kind_Call, - Ast_Kind_Intrinsic_Call, - Ast_Kind_Return, - Ast_Kind_Address_Of, - Ast_Kind_Dereference, - Ast_Kind_Subscript, - Ast_Kind_Slice, - Ast_Kind_Field_Access, - Ast_Kind_Unary_Field_Access, - Ast_Kind_Pipe, - Ast_Kind_Method_Call, - Ast_Kind_Range_Literal, - Ast_Kind_Size_Of, - Ast_Kind_Align_Of, - Ast_Kind_File_Contents, - Ast_Kind_Struct_Literal, - Ast_Kind_Array_Literal, - Ast_Kind_If_Expression, - - Ast_Kind_If, - Ast_Kind_For, - Ast_Kind_While, - Ast_Kind_Jump, - Ast_Kind_Use, - Ast_Kind_Defer, - Ast_Kind_Switch, - Ast_Kind_Switch_Case, - - Ast_Kind_Directive_Solidify, - Ast_Kind_Static_If, - Ast_Kind_Directive_Error, - Ast_Kind_Directive_Add_Overload, - Ast_Kind_Directive_Operator, - Ast_Kind_Directive_Export, - Ast_Kind_Directive_Defined, - Ast_Kind_Directive_Init, - Ast_Kind_Directive_Library, - Ast_Kind_Directive_Remove, - Ast_Kind_Call_Site, - - Ast_Kind_Code_Block, - Ast_Kind_Directive_Insert, - Ast_Kind_Macro, - Ast_Kind_Do_Block, - - Ast_Kind_Foreign_Block, - - Ast_Kind_Zero_Value, - - Ast_Kind_Note, - - Ast_Kind_Count -} AstKind; - -// NOTE: Some of these flags will overlap since there are -// only 32-bits of flags to play with -typedef enum AstFlags { - // Top-level flags - Ast_Flag_Const = BH_BIT(1), - Ast_Flag_Comptime = BH_BIT(2), - Ast_Flag_Private_Package = BH_BIT(3), - Ast_Flag_Private_File = BH_BIT(4), - - // Function flags - Ast_Flag_Function_Used = BH_BIT(5), - - // Expression flags - Ast_Flag_Expr_Ignored = BH_BIT(6), - Ast_Flag_Address_Taken = BH_BIT(7), - - // Type flags - Ast_Flag_Type_Is_Resolved = BH_BIT(8), - - Ast_Flag_No_Clone = BH_BIT(9), - - Ast_Flag_Cannot_Take_Addr = BH_BIT(10), - - // HACK: NullProcHack - Ast_Flag_Proc_Is_Null = BH_BIT(11), - - Ast_Flag_From_Polymorphism = BH_BIT(12), - - Ast_Flag_Incomplete_Body = BH_BIT(13), - - Ast_Flag_Array_Literal_Typed = BH_BIT(14), - - Ast_Flag_Has_Been_Symres = BH_BIT(15), - - Ast_Flag_Has_Been_Checked = BH_BIT(16), - - Ast_Flag_Static_If_Resolved = BH_BIT(17), - - Ast_Flag_Symbol_Invisible = BH_BIT(18), - - Ast_Flag_Header_Check_No_Error = BH_BIT(19), - - Ast_Flag_Decl_Followed_By_Init = BH_BIT(20), - - Ast_Flag_Param_Symbol_Dirty = BH_BIT(21), - - Ast_Flag_Dead = BH_BIT(22), -} AstFlags; - -typedef enum UnaryOp { - Unary_Op_Negate, - Unary_Op_Not, - Unary_Op_Bitwise_Not, - Unary_Op_Cast, - Unary_Op_Auto_Cast, -} UnaryOp; - -typedef enum BinaryOp { - Binary_Op_Add = 0, - Binary_Op_Minus = 1, - Binary_Op_Multiply = 2, - Binary_Op_Divide = 3, - Binary_Op_Modulus = 4, - - Binary_Op_Equal = 5, - Binary_Op_Not_Equal = 6, - Binary_Op_Less = 7, - Binary_Op_Less_Equal = 8, - Binary_Op_Greater = 9, - Binary_Op_Greater_Equal = 10, - - Binary_Op_And = 11, - Binary_Op_Or = 12, - Binary_Op_Xor = 13, - Binary_Op_Shl = 14, - Binary_Op_Shr = 15, - Binary_Op_Sar = 16, - - Binary_Op_Bool_And = 17, - Binary_Op_Bool_Or = 18, - - Binary_Op_Assign_Start = 19, - Binary_Op_Assign = 20, - Binary_Op_Assign_Add = 21, - Binary_Op_Assign_Minus = 22, - Binary_Op_Assign_Multiply = 23, - Binary_Op_Assign_Divide = 24, - Binary_Op_Assign_Modulus = 25, - Binary_Op_Assign_And = 26, - Binary_Op_Assign_Or = 27, - Binary_Op_Assign_Xor = 28, - Binary_Op_Assign_Shl = 29, - Binary_Op_Assign_Shr = 30, - Binary_Op_Assign_Sar = 31, - Binary_Op_Assign_End = 32, - - Binary_Op_Pipe = 33, - Binary_Op_Range = 34, - Binary_Op_Method_Call = 35, - - Binary_Op_Subscript = 36, - Binary_Op_Subscript_Equals = 37, - Binary_Op_Ptr_Subscript = 38, - - Binary_Op_Count -} BinaryOp; - -extern const char *binaryop_string[Binary_Op_Count]; - -typedef enum OnyxIntrinsic { - ONYX_INTRINSIC_UNDEFINED, - - ONYX_INTRINSIC_MEMORY_SIZE, ONYX_INTRINSIC_MEMORY_GROW, - ONYX_INTRINSIC_MEMORY_COPY, ONYX_INTRINSIC_MEMORY_FILL, - - ONYX_INTRINSIC_INITIALIZE, - - ONYX_INTRINSIC_I32_CLZ, ONYX_INTRINSIC_I32_CTZ, ONYX_INTRINSIC_I32_POPCNT, - ONYX_INTRINSIC_I32_AND, ONYX_INTRINSIC_I32_OR, ONYX_INTRINSIC_I32_XOR, - ONYX_INTRINSIC_I32_SHL, ONYX_INTRINSIC_I32_SLR, ONYX_INTRINSIC_I32_SAR, - ONYX_INTRINSIC_I32_ROTL, ONYX_INTRINSIC_I32_ROTR, - - ONYX_INTRINSIC_I64_CLZ, ONYX_INTRINSIC_I64_CTZ, ONYX_INTRINSIC_I64_POPCNT, - ONYX_INTRINSIC_I64_AND, ONYX_INTRINSIC_I64_OR, ONYX_INTRINSIC_I64_XOR, - ONYX_INTRINSIC_I64_SHL, ONYX_INTRINSIC_I64_SLR, ONYX_INTRINSIC_I64_SAR, - ONYX_INTRINSIC_I64_ROTL, ONYX_INTRINSIC_I64_ROTR, - - ONYX_INTRINSIC_F32_ABS, ONYX_INTRINSIC_F32_SQRT, - ONYX_INTRINSIC_F32_CEIL, ONYX_INTRINSIC_F32_FLOOR, - ONYX_INTRINSIC_F32_TRUNC, ONYX_INTRINSIC_F32_NEAREST, - ONYX_INTRINSIC_F32_MIN, ONYX_INTRINSIC_F32_MAX, - ONYX_INTRINSIC_F32_COPYSIGN, - - ONYX_INTRINSIC_F64_ABS, ONYX_INTRINSIC_F64_SQRT, - ONYX_INTRINSIC_F64_CEIL, ONYX_INTRINSIC_F64_FLOOR, - ONYX_INTRINSIC_F64_TRUNC, ONYX_INTRINSIC_F64_NEAREST, - ONYX_INTRINSIC_F64_MIN, ONYX_INTRINSIC_F64_MAX, - ONYX_INTRINSIC_F64_COPYSIGN, - - - - ONYX_INTRINSIC_V128_CONST, - ONYX_INTRINSIC_I8X16_CONST, ONYX_INTRINSIC_I16X8_CONST, - ONYX_INTRINSIC_I32X4_CONST, ONYX_INTRINSIC_I64X2_CONST, - ONYX_INTRINSIC_F32X4_CONST, ONYX_INTRINSIC_F64X2_CONST, - ONYX_INTRINSIC_I8X16_SHUFFLE, - - ONYX_INTRINSIC_I8X16_EXTRACT_LANE_S, ONYX_INTRINSIC_I8X16_EXTRACT_LANE_U, ONYX_INTRINSIC_I8X16_REPLACE_LANE, - ONYX_INTRINSIC_I16X8_EXTRACT_LANE_S, ONYX_INTRINSIC_I16X8_EXTRACT_LANE_U, ONYX_INTRINSIC_I16X8_REPLACE_LANE, - ONYX_INTRINSIC_I32X4_EXTRACT_LANE, ONYX_INTRINSIC_I32X4_REPLACE_LANE, - ONYX_INTRINSIC_I64X2_EXTRACT_LANE, ONYX_INTRINSIC_I64X2_REPLACE_LANE, - ONYX_INTRINSIC_F32X4_EXTRACT_LANE, ONYX_INTRINSIC_F32X4_REPLACE_LANE, - ONYX_INTRINSIC_F64X2_EXTRACT_LANE, ONYX_INTRINSIC_F64X2_REPLACE_LANE, - - ONYX_INTRINSIC_I8X16_SWIZZLE, - ONYX_INTRINSIC_I8X16_SPLAT, ONYX_INTRINSIC_I16X8_SPLAT, - ONYX_INTRINSIC_I32X4_SPLAT, ONYX_INTRINSIC_I64X2_SPLAT, - ONYX_INTRINSIC_F32X4_SPLAT, ONYX_INTRINSIC_F64X2_SPLAT, - - ONYX_INTRINSIC_I8X16_EQ, ONYX_INTRINSIC_I8X16_NEQ, - ONYX_INTRINSIC_I8X16_LT_S, ONYX_INTRINSIC_I8X16_LT_U, - ONYX_INTRINSIC_I8X16_GT_S, ONYX_INTRINSIC_I8X16_GT_U, - ONYX_INTRINSIC_I8X16_LE_S, ONYX_INTRINSIC_I8X16_LE_U, - ONYX_INTRINSIC_I8X16_GE_S, ONYX_INTRINSIC_I8X16_GE_U, - - ONYX_INTRINSIC_I16X8_EQ, ONYX_INTRINSIC_I16X8_NEQ, - ONYX_INTRINSIC_I16X8_LT_S, ONYX_INTRINSIC_I16X8_LT_U, - ONYX_INTRINSIC_I16X8_GT_S, ONYX_INTRINSIC_I16X8_GT_U, - ONYX_INTRINSIC_I16X8_LE_S, ONYX_INTRINSIC_I16X8_LE_U, - ONYX_INTRINSIC_I16X8_GE_S, ONYX_INTRINSIC_I16X8_GE_U, - - ONYX_INTRINSIC_I32X4_EQ, ONYX_INTRINSIC_I32X4_NEQ, - ONYX_INTRINSIC_I32X4_LT_S, ONYX_INTRINSIC_I32X4_LT_U, - ONYX_INTRINSIC_I32X4_GT_S, ONYX_INTRINSIC_I32X4_GT_U, - ONYX_INTRINSIC_I32X4_LE_S, ONYX_INTRINSIC_I32X4_LE_U, - ONYX_INTRINSIC_I32X4_GE_S, ONYX_INTRINSIC_I32X4_GE_U, - - ONYX_INTRINSIC_F32X4_EQ, ONYX_INTRINSIC_F32X4_NEQ, - ONYX_INTRINSIC_F32X4_LT, ONYX_INTRINSIC_F32X4_GT, - ONYX_INTRINSIC_F32X4_LE, ONYX_INTRINSIC_F32X4_GE, - - ONYX_INTRINSIC_F64X2_EQ, ONYX_INTRINSIC_F64X2_NEQ, - ONYX_INTRINSIC_F64X2_LT, ONYX_INTRINSIC_F64X2_GT, - ONYX_INTRINSIC_F64X2_LE, ONYX_INTRINSIC_F64X2_GE, - - ONYX_INTRINSIC_V128_NOT, ONYX_INTRINSIC_V128_AND, ONYX_INTRINSIC_V128_ANDNOT, - ONYX_INTRINSIC_V128_OR, ONYX_INTRINSIC_V128_XOR, ONYX_INTRINSIC_V128_BITSELECT, - - ONYX_INTRINSIC_I8X16_ABS, ONYX_INTRINSIC_I8X16_NEG, - ONYX_INTRINSIC_I8X16_ANY_TRUE, ONYX_INTRINSIC_I8X16_ALL_TRUE, - ONYX_INTRINSIC_I8X16_BITMASK, - ONYX_INTRINSIC_I8X16_NARROW_I16X8_S, ONYX_INTRINSIC_I8X16_NARROW_I16X8_U, - ONYX_INTRINSIC_I8X16_SHL, ONYX_INTRINSIC_I8X16_SHR_S, ONYX_INTRINSIC_I8X16_SHR_U, - ONYX_INTRINSIC_I8X16_ADD, ONYX_INTRINSIC_I8X16_ADD_SAT_S, ONYX_INTRINSIC_I8X16_ADD_SAT_U, - ONYX_INTRINSIC_I8X16_SUB, ONYX_INTRINSIC_I8X16_SUB_SAT_S, ONYX_INTRINSIC_I8X16_SUB_SAT_U, - ONYX_INTRINSIC_I8X16_MIN_S, ONYX_INTRINSIC_I8X16_MIN_U, - ONYX_INTRINSIC_I8X16_MAX_S, ONYX_INTRINSIC_I8X16_MAX_U, - ONYX_INTRINSIC_I8X16_AVGR_U, - - ONYX_INTRINSIC_I16X8_ABS, ONYX_INTRINSIC_I16X8_NEG, - ONYX_INTRINSIC_I16X8_ANY_TRUE, ONYX_INTRINSIC_I16X8_ALL_TRUE, - ONYX_INTRINSIC_I16X8_BITMASK, - ONYX_INTRINSIC_I16X8_NARROW_I32X4_S, ONYX_INTRINSIC_I16X8_NARROW_I32X4_U, - ONYX_INTRINSIC_I16X8_WIDEN_LOW_I8X16_S, ONYX_INTRINSIC_I16X8_WIDEN_HIGH_I8X16_S, - ONYX_INTRINSIC_I16X8_WIDEN_LOW_I8X16_U, ONYX_INTRINSIC_I16X8_WIDEN_HIGH_I8X16_U, - ONYX_INTRINSIC_I16X8_SHL, ONYX_INTRINSIC_I16X8_SHR_S, ONYX_INTRINSIC_I16X8_SHR_U, - ONYX_INTRINSIC_I16X8_ADD, ONYX_INTRINSIC_I16X8_ADD_SAT_S, ONYX_INTRINSIC_I16X8_ADD_SAT_U, - ONYX_INTRINSIC_I16X8_SUB, ONYX_INTRINSIC_I16X8_SUB_SAT_S, ONYX_INTRINSIC_I16X8_SUB_SAT_U, - ONYX_INTRINSIC_I16X8_MUL, - ONYX_INTRINSIC_I16X8_MIN_S, ONYX_INTRINSIC_I16X8_MIN_U, - ONYX_INTRINSIC_I16X8_MAX_S, ONYX_INTRINSIC_I16X8_MAX_U, - ONYX_INTRINSIC_I16X8_AVGR_U, - - ONYX_INTRINSIC_I32X4_ABS, ONYX_INTRINSIC_I32X4_NEG, - ONYX_INTRINSIC_I32X4_ANY_TRUE, ONYX_INTRINSIC_I32X4_ALL_TRUE, - ONYX_INTRINSIC_I32X4_BITMASK, - ONYX_INTRINSIC_I32X4_WIDEN_LOW_I16X8_S, ONYX_INTRINSIC_I32X4_WIDEN_HIGH_I16X8_S, - ONYX_INTRINSIC_I32X4_WIDEN_LOW_I16X8_U, ONYX_INTRINSIC_I32X4_WIDEN_HIGH_I16X8_U, - ONYX_INTRINSIC_I32X4_SHL, ONYX_INTRINSIC_I32X4_SHR_S, ONYX_INTRINSIC_I32X4_SHR_U, - ONYX_INTRINSIC_I32X4_ADD, ONYX_INTRINSIC_I32X4_SUB, ONYX_INTRINSIC_I32X4_MUL, - ONYX_INTRINSIC_I32X4_MIN_S, ONYX_INTRINSIC_I32X4_MIN_U, - ONYX_INTRINSIC_I32X4_MAX_S, ONYX_INTRINSIC_I32X4_MAX_U, - - ONYX_INTRINSIC_I64X2_NEG, ONYX_INTRINSIC_I64X2_SHL, - ONYX_INTRINSIC_I64X2_SHR_S, ONYX_INTRINSIC_I64X2_SHR_U, - ONYX_INTRINSIC_I64X2_ADD, ONYX_INTRINSIC_I64X2_SUB, ONYX_INTRINSIC_I64X2_MUL, - - ONYX_INTRINSIC_F32X4_ABS, ONYX_INTRINSIC_F32X4_NEG, ONYX_INTRINSIC_F32X4_SQRT, - ONYX_INTRINSIC_F32X4_ADD, ONYX_INTRINSIC_F32X4_SUB, - ONYX_INTRINSIC_F32X4_MUL, ONYX_INTRINSIC_F32X4_DIV, - ONYX_INTRINSIC_F32X4_MIN, ONYX_INTRINSIC_F32X4_MAX, - - ONYX_INTRINSIC_F64X2_ABS, ONYX_INTRINSIC_F64X2_NEG, ONYX_INTRINSIC_F64X2_SQRT, - ONYX_INTRINSIC_F64X2_ADD, ONYX_INTRINSIC_F64X2_SUB, - ONYX_INTRINSIC_F64X2_MUL, ONYX_INTRINSIC_F64X2_DIV, - ONYX_INTRINSIC_F64X2_MIN, ONYX_INTRINSIC_F64X2_MAX, - - ONYX_INTRINSIC_I32X4_TRUNC_SAT_F32X4_S, - ONYX_INTRINSIC_I32X4_TRUNC_SAT_F32X4_U, - ONYX_INTRINSIC_F32X4_CONVERT_I32X4_S, - ONYX_INTRINSIC_F32X4_CONVERT_I32X4_U, - - ONYX_INTRINSIC_ATOMIC_WAIT, - ONYX_INTRINSIC_ATOMIC_NOTIFY, - - ONYX_INTRINSIC_ATOMIC_FENCE, - - ONYX_INTRINSIC_ATOMIC_LOAD, - ONYX_INTRINSIC_ATOMIC_STORE, - ONYX_INTRINSIC_ATOMIC_ADD, - ONYX_INTRINSIC_ATOMIC_SUB, - ONYX_INTRINSIC_ATOMIC_AND, - ONYX_INTRINSIC_ATOMIC_OR, - ONYX_INTRINSIC_ATOMIC_XOR, - ONYX_INTRINSIC_ATOMIC_XCHG, - ONYX_INTRINSIC_ATOMIC_CMPXCHG, -} OnyxIntrinsic; - -typedef enum CallingConvention { - CC_Undefined, - CC_Return_Wasm, - CC_Return_Stack -} CallingConvention; - -typedef enum JumpType { - Jump_Type_Break, - Jump_Type_Continue, - Jump_Type_Fallthrough, - Jump_Type_Return, - - Jump_Type_Count, -} JumpType; - -typedef enum ForLoopType { - For_Loop_Invalid, - For_Loop_Range, - For_Loop_Array, - For_Loop_Slice, - For_Loop_DynArr, - For_Loop_Iterator, -} ForLoopType; - -typedef enum ParamPassType { - Param_Pass_Invalid, - Param_Pass_By_Value, - Param_Pass_By_VarArg, - Param_Pass_By_Implicit_Pointer, -} ParamPassType; - -typedef enum VarArgKind { - VA_Kind_Not_VA, - VA_Kind_Typed, - VA_Kind_Any, - VA_Kind_Untyped, -} VarArgKind; - -typedef enum BlockRule { - Block_Rule_New_Scope = BH_BIT(1), - Block_Rule_Emit_Instructions = BH_BIT(2), - Block_Rule_Clear_Defer = BH_BIT(3), - Block_Rule_Override_Return = BH_BIT(4), - - Block_Rule_Normal = Block_Rule_New_Scope | Block_Rule_Clear_Defer | Block_Rule_Emit_Instructions, - Block_Rule_Macro = Block_Rule_New_Scope, - Block_Rule_Code_Block = Block_Rule_New_Scope, - Block_Rule_Do_Block = Block_Rule_New_Scope | Block_Rule_Clear_Defer | Block_Rule_Override_Return | Block_Rule_Emit_Instructions, -} BlockRule; - -typedef enum ConstraintCheckStatus { - Constraint_Check_Status_Queued, - Constraint_Check_Status_Failed, - Constraint_Check_Status_Success, -} ConstraintCheckStatus; - -typedef struct ConstraintContext { - bh_arr(AstConstraint *) constraints; - ConstraintCheckStatus *constraint_checks; - b32 constraints_met : 1; - b32 produce_errors : 1; -} ConstraintContext; - - -typedef struct Arguments Arguments; -struct Arguments { - bh_arr(AstTyped *) values; - bh_arr(AstNamedValue *) named_values; - - // How many arguments were not baked. - i32 used_argument_count; -}; - - -// Base Nodes -#define AstNode_base \ - AstKind kind; \ - u32 flags; \ - OnyxToken *token; \ - struct Entity* entity; \ - AstNode *next -struct AstNode { AstNode_base; }; - -// NOTE: 'type_node' is filled out by the parser. -// For a type such as '^^i32', the tree would look something like -// -// Typed Thing -> AstPointerType -> AstPointerType -> AstNode (symbol node) -// -// The symbol node will be filled out during symbol resolution. -// It will end up pointing to an AstBasicType that corresponds to -// the underlying type. -// -// 'type' is filled out afterwards. If it is NULL, the Type* is built -// using the type_node. This can then be used to typecheck this node. -#define AstTyped_base \ - AstNode_base; \ - AstType *type_node; \ - Type *type -struct AstTyped { AstTyped_base; }; - -// Expression Nodes -struct AstNamedValue { AstTyped_base; AstTyped* value; }; -struct AstUnaryOp { AstTyped_base; UnaryOp operation; AstTyped *expr; }; -struct AstStrLit { AstTyped_base; u64 data_id; u64 length; b32 is_cstr: 1; }; -struct AstLocal { AstTyped_base; }; -struct AstDereference { AstTyped_base; AstTyped *expr; }; -struct AstSizeOf { AstTyped_base; AstType *so_ast_type; Type *so_type; u64 size; }; -struct AstAlignOf { AstTyped_base; AstType *ao_ast_type; Type *ao_type; u64 alignment; }; -struct AstNumLit { - AstTyped_base; - union { - i32 i; - i64 l; - f32 f; - f64 d; - } value; - - b32 was_hex_literal : 1; -}; -struct AstBinaryOp { - AstTyped_base; - BinaryOp operation; - AstTyped *left, *right; - - Arguments *overload_args; // This is set of the binary operator is attempted to be overloaded - // but isnt successful yet. - AstBinaryOp *potential_substitute; -}; -struct AstAddressOf { - AstTyped_base; - AstTyped *expr; - - AstBinaryOp *potential_substitute; - - // This is set by check_method_call. - // If set, the address of node can be removed if the - // type checking does not pass for it. This makes it - // possible to have something that will optionally - // have its address taken, if necessary. - b32 can_be_removed : 1; -}; -struct AstArgument { - AstTyped_base; - - AstTyped *value; - - VarArgKind va_kind; - b32 is_baked : 1; - b32 pass_as_any : 1; -}; -struct AstSubscript { - AstTyped_base; - BinaryOp __unused_operation; // This will be set to Binary_Op_Subscript - AstTyped *addr; - AstTyped *expr; - - Arguments *overload_args; // This is set of the binary operator is attempted to be overloaded - // but isnt successful yet. - AstBinaryOp *potential_substitute; - - u64 elem_size; -}; -struct AstFieldAccess { - AstTyped_base; - AstTyped *expr; - u32 offset; - u32 idx; - char* field; // If token is null, defer to field -}; -struct AstFileContents { - AstTyped_base; - - AstTyped *filename_expr; - char *filename; // The parsed file name, with '\' sequences removed and resolved to a particular file if possible. - - u32 data_id, size; -}; -struct AstUnaryFieldAccess { - AstTyped_base; - - // "token" represents the field. This does not need an "offset" or an "index" - // because this node is meant to be replaced. -}; -struct AstStructLiteral { - AstTyped_base; - - AstTyped *stnode; - - Arguments args; -}; -struct AstArrayLiteral { - AstTyped_base; - - AstTyped *atnode; - - bh_arr(AstTyped *) values; -}; -struct AstRangeLiteral { - AstTyped_base; - - // HACK: Currently, range literals are parsed as binary operators, which means - // the first sizeof(AstBinaryOp) bytes of this structure must match that of - // AstBinaryOp, which means I need this dummy field here. - // - brendanfh 2020/12/23 - BinaryOp __unused_operation; - AstTyped *low, *high; - - // Currently, there is no way to specify this in the grammar, but it is set - // to be the initial value of the `step` member of the range structure in - // core/builtin.onyx. - // - brendanfh 2020/12/23 - AstTyped *step; -}; -struct AstCall { - AstTyped_base; - - Arguments args; - - union { - AstTyped *callee; - OnyxIntrinsic intrinsic; - }; - - VarArgKind va_kind; -}; -struct AstCompound { - AstTyped_base; - - bh_arr(AstTyped *) exprs; -}; -struct AstIfExpression { - AstTyped_base; - - AstTyped* cond; - AstTyped* true_expr; - AstTyped* false_expr; -}; -struct AstDoBlock { - AstTyped_base; - - AstBlock* block; -}; -struct AstZeroValue { - AstTyped_base; -}; - -struct AstDirectiveSolidify { - AstTyped_base; - - AstFunction* poly_proc; - bh_arr(AstPolySolution) known_polyvars; - - AstNode* resolved_proc; -}; - -// Intruction Node -struct AstReturn { AstNode_base; AstTyped* expr; }; -struct AstJump { AstNode_base; JumpType jump; u32 count; }; - -typedef struct QualifiedUse { - OnyxToken* symbol_name; - OnyxToken* as_name; -} QualifiedUse; -struct AstUse { - AstNode_base; - - AstTyped* expr; - bh_arr(QualifiedUse) only; -}; - -// Structure Nodes -struct AstBlock { - AstNode_base; - - AstNode *body; - - Scope *scope; - Scope *binding_scope; - BlockRule rules; - - u32 statement_idx; -}; -struct AstDefer { AstNode_base; AstNode *stmt; }; -struct AstFor { - AstNode_base; - - // NOTE: Stores the iteration variable - Scope *scope; - - // NOTE: Local defining the iteration variable - AstLocal* var; - - // NOTE: This can be any expression, but it is checked that - // it is of a type that we know how to iterate over. - AstTyped* iter; - ForLoopType loop_type; - - AstBlock *stmt; - - // ROBUSTNESS: This should be able to be set by a compile time variable at some point. - // But for now, this will do. - b32 by_pointer : 1; - b32 no_close : 1; -}; -struct AstIfWhile { - AstNode_base; - - Scope *scope; - AstNode* initialization; - - AstTyped *cond; - - AstBlock *true_stmt; - AstBlock *false_stmt; - - union { - // Used by Static_If - struct { - Scope *defined_in_scope; - bh_arr(struct Entity *) true_entities; - bh_arr(struct Entity *) false_entities; - }; - - // Used by While - b32 bottom_test; - }; -}; -typedef struct AstIfWhile AstIf; -typedef struct AstIfWhile AstWhile; - -typedef enum SwitchKind { - Switch_Kind_Integer, - Switch_Kind_Use_Equals, -} SwitchKind; - -typedef struct CaseToBlock { - AstTyped *original_value; - AstBinaryOp *comparison; - AstBlock *block; -} CaseToBlock; - -struct AstSwitchCase { - AstNode_base; - - // NOTE: All expressions that end up in this block - bh_arr(AstTyped *) values; - - AstBlock *block; - - b32 is_default: 1; // Could this be inferred by the values array being null? -}; - -struct AstSwitch { - AstNode_base; - - Scope *scope; - AstNode* initialization; - - AstTyped *expr; - - AstBlock *case_block; - - bh_arr(AstSwitchCase *) cases; - AstBlock *default_case; - - i32 yield_return_index; - SwitchKind switch_kind; - - union { - struct { - // NOTE: This is a mapping from the compile time known case value - // to a pointer to the block that it is associated with. - bh_imap case_map; - u64 min_case, max_case; - }; - - struct { - // NOTE: This is a mapping from the '==' binary op node to - // a pointer to the block that it is associated with. - bh_arr(CaseToBlock) case_exprs; - }; - }; -}; - -// Type Nodes -// NOTE: This node is very similar to an AstNode, just -// without the 'next' member. This is because types -// can't be in expressions so a 'next' thing -// doesn't make sense. -#define AstType_base \ - AstKind kind; \ - u32 flags; \ - OnyxToken* token; \ - struct Entity* entity; \ - void* __unused; \ - u64 type_id; \ - Type* type -struct AstType { AstType_base; }; - -struct AstBasicType { AstType_base; Type* basic_type; }; -struct AstPointerType { AstType_base; AstType* elem; }; -struct AstArrayType { AstType_base; AstType* elem; AstTyped *count_expr; }; -struct AstSliceType { AstType_base; AstType* elem; }; -struct AstDynArrType { AstType_base; AstType* elem; }; -struct AstVarArgType { AstType_base; AstType* elem; }; -struct AstFunctionType { - AstType_base; - - // Used in a rare case in solve_for_polymorphic_param_type. - Type *partial_function_type; - - AstType* return_type; - - u64 param_count; - AstType* params[]; -}; -struct AstStructType { - AstType_base; - char *name; - - bh_arr(AstStructMember *) members; - bh_arr(AstTyped *) meta_tags; - - // u32 min_alignment, min_size; - AstTyped *min_alignment_, *min_size_; - - // NOTE: Used to cache the actual type, since building - // a struct type is kind of complicated and should - // only happen once. - Type *stcache; - - // NOTE: This type is used when the structure has not been - // completely generated, but is a valid pointer to where the - // type will be generated to. - Type *pending_type; - - // NOTE: Used to store statically bound expressions in the struct. - Scope* scope; - - struct Entity* entity_type; - struct Entity* entity_defaults; - - OnyxFilePos polymorphic_error_loc; - ConstraintContext constraints; - - bh_arr(AstType *) polymorphic_argument_types; - bh_arr(AstPolySolution) polymorphic_arguments; - - b32 pending_type_is_valid : 1; - b32 is_union : 1; - b32 is_packed : 1; - b32 ready_to_build_type : 1; -}; -struct AstStructMember { - AstTyped_base; - AstTyped* initial_value; - - bh_arr(AstTyped *) meta_tags; - - b32 is_used : 1; -}; -struct AstPolyStructParam { - AstTyped_base; -}; -struct AstPolyStructType { - AstType_base; - char *name; - - Scope *scope; - bh_arr(AstPolyStructParam) poly_params; - Table(AstStructType *) concrete_structs; - - AstStructType* base_struct; -}; -struct AstPolyCallType { - AstType_base; - - AstType* callee; - - // NOTE: These nodes can be either AstTypes, or AstTyped expressions. - bh_arr(AstNode *) params; -}; -struct AstEnumType { - AstType_base; - char *name; - Scope *scope; - - AstType *backing; - Type *backing_type; - - bh_arr(AstEnumValue *) values; - - // NOTE: Used to cache the actual type for the same reason as above. - Type *etcache; - - b32 is_flags : 1; -}; -struct AstEnumValue { AstTyped_base; AstTyped* value; }; -struct AstTypeAlias { AstType_base; AstType* to; }; -struct AstTypeRawAlias { AstType_base; Type* to; }; -struct AstCompoundType { - AstType_base; - - bh_arr(AstType *) types; -}; -struct AstTypeOf { - AstType_base; - - AstTyped* expr; - Type* resolved_type; -}; -struct AstDistinctType { - AstType_base; - char *name; - AstType *base_type; - Type *dtcache; -}; - -// Top level nodes -struct AstBinding { AstTyped_base; AstNode* node; }; -struct AstAlias { AstTyped_base; AstTyped* alias; }; -struct AstInclude { AstNode_base; AstTyped* name_node; char* name; }; -struct AstInjection { - AstTyped_base; - AstTyped* full_loc; - AstTyped* to_inject; - AstTyped* dest; - OnyxToken *symbol; -}; -struct AstMemRes { - AstTyped_base; - AstTyped *initial_value; - - struct Entity *type_entity; - - b32 threadlocal : 1; - - // Set and used in the wasm emission. - u32 data_id; - u32 tls_offset; -}; -struct AstGlobal { - AstTyped_base; - - char* name; - - OnyxToken* foreign_module; - OnyxToken* foreign_name; -}; -struct AstParam { - // HACK CLEANUP: This does not need to have a local buried inside of it. - // Convert this to be AstTyped_base and pull the ParamPassType from AstLocal - // to here. - brendanfh 2020/09/18 - AstLocal *local; - AstTyped *default_value; - - VarArgKind vararg_kind; - b32 is_used : 1; - b32 use_processed : 1; - b32 is_baked : 1; -}; -typedef struct OverloadOption OverloadOption; -struct OverloadOption { - // This is u64 because padding will make it that anyway. - // Consider: would there be any practical benefit to having the precedence setting - // be a compile-time known value? as opposed to a hardcoded value? - u64 precedence; - - AstTyped* option; -}; - -struct AstOverloadedFunction { - AstTyped_base; - - bh_arr(OverloadOption) overloads; - - // CLEANUP: This is unused right now, but should be used to cache - // the complete set of overloads that can be used by an overloaded - // function. - bh_imap all_overloads; - - b32 locked : 1; - b32 only_local_functions : 1; -}; - -// @CLEANUP: Is this really necessary? -typedef struct InterfaceParam { - OnyxToken *value_token; - OnyxToken *type_token; -} InterfaceParam; - -typedef struct InterfaceConstraint { - AstTyped *expr; - AstType *expected_type_expr; - Type *expected_type; - - b32 invert_condition: 1; -} InterfaceConstraint; - -struct AstInterface { - AstTyped_base; - char *name; - - bh_arr(InterfaceParam) params; - bh_arr(InterfaceConstraint) exprs; -}; - -typedef enum ConstraintPhase { - Constraint_Phase_Waiting_To_Be_Queued = 0, - Constraint_Phase_Cloning_Expressions = 1, - Constraint_Phase_Checking_Expressions = 2, - Constraint_Phase_Finished = 3, -} ConstraintPhase; - -struct AstConstraint { - AstNode_base; - - ConstraintPhase phase; - - AstInterface *interface; - bh_arr(AstType *) type_args; - - ConstraintCheckStatus *report_status; - - Scope* scope; - bh_arr(InterfaceConstraint) exprs; - u32 expr_idx; -}; - - -struct AstPackage { - AstTyped_base; - - // Allocated in the ast arena - char * package_name; - - // NOTE: Symbol nodes - bh_arr(OnyxToken *) path; - - Package* package; -}; - -// -// Polymorphic procedures -// - -typedef enum PolyParamKind { - // Dont love these names - PPK_Undefined, - PPK_Poly_Type, - PPK_Baked_Value, -} PolyParamKind; - -typedef enum PolySolutionKind { - PSK_Undefined, - PSK_Type, - PSK_Value, -} PolySolutionKind; - -typedef enum PolyProcLookupMethod { - PPLM_By_Arguments, - PPLM_By_Function_Type, -} PolyProcLookupMethod; - -struct AstPolyParam { - PolyParamKind kind; - - // The parameter index where the polymorphic variable occurs. - u32 idx; - - // The symbol node that represents the polymorphic variable. - AstNode* poly_sym; - - // The type expression that contains `poly_sym` in it somewhere. - // Matching polymorphic variables does a parallel tree traversal - // to find the pairing of the actual type and polymorphic variable - // symbol. - AstType* type_expr; - - // Used for baked values. The expected type of the parameter. - Type* type; -}; - -struct AstPolySolution { - PolySolutionKind kind; - AstNode* poly_sym; - - union { - struct { - // If `type` is null, it is filled in with this type. - AstType* ast_type; - Type* type; - }; - - AstTyped* value; - }; -}; - -struct AstSolidifiedFunction { - AstFunction *func; - struct Entity *func_header_entity; -}; - -struct AstFunction { - AstTyped_base; - - Scope *scope; - - bh_arr(AstParam) params; - AstType* return_type; - - AstBlock *body; - - char* name; - - // NOTE: This is NULL, unless this function was generated from a polymorphic - // procedure call. Then it is set to the token of the call node. - OnyxToken* generated_from; - Scope* poly_scope; - - // NOTE: This is NULL, unless this function is used in a "#export" directive. - // It is undefined which name it will have if there are multiple export directives - // for this particular function. - OnyxToken* exported_name; - - OnyxToken* closing_brace; - - union { - OnyxToken* intrinsic_name; - - // NOTE: Used when the function is declared as foreign - struct { - OnyxToken* foreign_module; - OnyxToken* foreign_name; - }; - }; - - struct Entity* entity_header; - struct Entity* entity_body; - - ConstraintContext constraints; - - bh_arr(AstTyped *) tags; - - // Polymorphic procedures use the following fields - Scope *parent_scope_of_poly_proc; - bh_arr(AstPolyParam) poly_params; - - bh_arr(AstPolySolution) known_slns; - - Table(AstSolidifiedFunction) concrete_funcs; - bh_imap active_queries; - - bh_arr(AstNode *) nodes_that_need_entities_after_clone; - - b32 is_exported : 1; - b32 is_foreign : 1; - b32 is_intrinsic : 1; -}; - -struct AstPolyQuery { - AstNode_base; - - AstFunction *proc; - PolyProcLookupMethod pp_lookup; - ptr given; - OnyxToken *error_loc; - - bh_arr(AstPolySolution) slns; - - AstFunction *function_header; - - b32 error_on_fail : 1; // Whether or not to report errors on failing to match. - b32 successful_symres : 1; // If something successful happened in symbol resolution -}; - - -struct AstDirectiveError { - AstNode_base; - - OnyxToken* error_msg; -}; - -struct AstDirectiveAddOverload { - AstNode_base; - - // NOTE: used by the #add_overload directive. Initially set to a symbol, - // then resolved to an overloaded function. - AstNode *overloaded_function; - - // See note in OverloadOption. This could be refactored into an OverloadOption? - u64 precedence; - AstTyped *overload; -}; - -struct AstDirectiveOperator { - AstNode_base; - - BinaryOp operator; - AstTyped *overload; -}; - -struct AstDirectiveExport { - AstNode_base; - - OnyxToken* export_name; - AstTyped* export_name_expr; - AstTyped* export; -}; - -struct AstDirectiveDefined { - AstTyped_base; - AstTyped *expr; - - b32 is_defined: 1; -}; - -struct AstDirectiveRemove { - AstNode_base; -}; - -struct AstNote { - AstNode_base; -}; - -struct AstCallSite { - AstTyped_base; - - OnyxToken* callsite_token; - - AstStrLit* filename; - AstNumLit* line; - AstNumLit* column; -}; - -// Represents a "pastable" block of code. -struct AstCodeBlock { - AstTyped_base; - - AstNode *code; -}; - -struct AstDirectiveInsert { - AstTyped_base; - - AstTyped *code_expr; -}; - -struct AstDirectiveInit { - AstNode_base; - - AstTyped *init_proc; - bh_arr(AstDirectiveInit *) dependencies; -}; - -struct AstMacro { - AstTyped_base; - - AstTyped* body; -}; - -struct AstDirectiveLibrary { - AstNode_base; - - AstTyped *library_symbol; // This should resolve to a string literal - char *library_name; -}; - -struct AstForeignBlock { - AstTyped_base; - - Scope* scope; - OnyxToken *module_name; - bh_arr(struct Entity *) captured_entities; - - u32 foreign_block_number; -}; - -typedef enum EntityState { - Entity_State_Error, - - Entity_State_Parse_Builtin, - Entity_State_Introduce_Symbols, - Entity_State_Parse, - Entity_State_Resolve_Symbols, - Entity_State_Check_Types, - Entity_State_Code_Gen, - Entity_State_Finalized, - Entity_State_Failed, - - Entity_State_Count, -} EntityState; - -extern const char* entity_state_strings[Entity_State_Count]; - -// NOTE: An Entity represents something will need to be -// processed later down the pipeline. -typedef enum EntityType { - Entity_Type_Unknown, - - Entity_Type_Error, - Entity_Type_Note, - Entity_Type_Load_Path, - Entity_Type_Load_File, - Entity_Type_Binding, - Entity_Type_Use_Package, - Entity_Type_Static_If, - Entity_Type_String_Literal, - Entity_Type_File_Contents, - Entity_Type_Enum, - Entity_Type_Enum_Value, - Entity_Type_Type_Alias, - Entity_Type_Memory_Reservation_Type, - Entity_Type_Use, - Entity_Type_Interface, - Entity_Type_Constraint_Check, - Entity_Type_Polymorphic_Proc, - Entity_Type_Polymorph_Query, - Entity_Type_Macro, - Entity_Type_Foreign_Block, - Entity_Type_Foreign_Function_Header, - Entity_Type_Temp_Function_Header, // Same as a Function_Header, except it disappears after it checks completely. - Entity_Type_Function_Header, - Entity_Type_Global_Header, - Entity_Type_Process_Directive, - Entity_Type_Struct_Member_Default, - Entity_Type_Memory_Reservation, - Entity_Type_Expression, - Entity_Type_Global, - Entity_Type_Overloaded_Function, - Entity_Type_Function, - - Entity_Type_Count, -} EntityType; - -extern const char* entity_type_strings[Entity_Type_Count]; - -typedef struct Entity { - u32 id; - - EntityType type; - EntityState state; - - // TODO: Document this! - u32 macro_attempts; - u32 micro_attempts; - - b32 entered_in_queue : 1; - - Package *package; - Scope *scope; - - // TODO: This is incomplete. Add proper cycle detection and halting. - // struct Entity *waiting_on; - - union { - AstDirectiveError *error; - AstInclude *include; - AstBinding *binding; - AstIf *static_if; - AstFunction *function; - AstOverloadedFunction *overloaded_function; - AstGlobal *global; - AstTyped *expr; - AstStrLit *strlit; - AstFileContents *file_contents; - AstType *type_alias; - AstEnumType *enum_type; - AstEnumValue *enum_value; - AstMemRes *mem_res; - AstFunction *poly_proc; - AstPolyQuery *poly_query; - AstForeignBlock *foreign_block; - AstMacro *macro; - AstUse *use; - AstInterface *interface; - AstConstraint *constraint; - AstDirectiveLibrary *library; - }; -} Entity; - -typedef struct EntityHeap { - bh_arena entity_arena; - bh_arr(Entity *) entities; - i32 next_id; - - i32 state_count[Entity_State_Count]; - i32 type_count[Entity_Type_Count]; - - i32 all_count[Entity_State_Count][Entity_Type_Count]; -} EntityHeap; - -void entity_heap_init(EntityHeap* entities); -void entity_heap_insert_existing(EntityHeap* entities, Entity* e); -Entity* entity_heap_insert(EntityHeap* entities, Entity e); -Entity* entity_heap_top(EntityHeap* entities); -void entity_heap_change_top(EntityHeap* entities, Entity* new_top); -void entity_heap_remove_top(EntityHeap* entities); -void entity_change_type(EntityHeap* entities, Entity *ent, EntityType new_type); - -// If target_arr is null, the entities will be placed directly in the heap. -void add_entities_for_node(bh_arr(Entity *)* target_arr, AstNode* node, Scope* scope, Package* package); - -void symres_entity(Entity* ent); -void check_entity(Entity* ent); -void emit_entity(Entity* ent); - -struct Package { - char *name; - - Scope *scope; - Scope *private_scope; - - // NOTE: This tracks all of the 'use package' statements of this package throughout - // the code base. This is used when a static if clears and new symbols are introduced. - // 'use package' statements have to be reevaluated to pull in the new symbols. - bh_arr(Entity *) use_package_entities; -}; - -typedef enum CompileAction CompileAction; -enum CompileAction { - ONYX_COMPILE_ACTION_COMPILE, - ONYX_COMPILE_ACTION_RUN, - ONYX_COMPILE_ACTION_DOCUMENT, - ONYX_COMPILE_ACTION_PRINT_HELP, -}; - - -// ROBUSTNESS: The Runtime definitions here must match those in build_opts.onyx!! -typedef enum Runtime Runtime; -enum Runtime { - Runtime_Unknown = 0, - Runtime_Onyx = 1, - Runtime_Wasi = 2, - Runtime_Js = 3, - Runtime_Custom = 4, -}; - - -typedef struct CompileOptions CompileOptions; -struct CompileOptions { - bh_allocator allocator; - CompileAction action; - - u32 verbose_output : 2; - b32 fun_output : 1; - b32 print_function_mappings : 1; - b32 print_static_if_results : 1; - b32 print_notes : 1; - b32 no_colors : 1; - b32 no_file_contents : 1; - - b32 use_post_mvp_features : 1; - b32 use_multi_threading : 1; - b32 generate_foreign_info : 1; - - Runtime runtime; - - bh_arr(const char *) included_folders; - bh_arr(const char *) files; - const char* target_file; - const char* documentation_file; - - b32 debug_enabled; - - i32 passthrough_argument_count; - char** passthrough_argument_data; -}; - -typedef struct Context Context; -struct Context { - Table(Package *) packages; - EntityHeap entities; - - Scope *global_scope; - - CompileOptions* options; - - bh_arena ast_arena; - bh_allocator token_alloc, ast_alloc; - - bh_arr(bh_file_contents) loaded_files; - - // NOTE: This is defined in onyxwasm.h - struct OnyxWasmModule* wasm_module; - - b32 cycle_almost_detected : 1; - b32 cycle_detected : 1; -}; - -extern Context context; - -// NOTE: Basic internal types constructed in the parser -extern AstBasicType basic_type_void; -extern AstBasicType basic_type_bool; -extern AstBasicType basic_type_i8; -extern AstBasicType basic_type_u8; -extern AstBasicType basic_type_i16; -extern AstBasicType basic_type_u16; -extern AstBasicType basic_type_i32; -extern AstBasicType basic_type_u32; -extern AstBasicType basic_type_i64; -extern AstBasicType basic_type_u64; -extern AstBasicType basic_type_f32; -extern AstBasicType basic_type_f64; -extern AstBasicType basic_type_rawptr; -extern AstBasicType basic_type_type_expr; // :TypeExprHack - -extern AstBasicType basic_type_int_unsized; -extern AstBasicType basic_type_float_unsized; - -extern AstBasicType basic_type_i8x16; -extern AstBasicType basic_type_i16x8; -extern AstBasicType basic_type_i32x4; -extern AstBasicType basic_type_i64x2; -extern AstBasicType basic_type_f32x4; -extern AstBasicType basic_type_f64x2; -extern AstBasicType basic_type_v128; - -// HACK -// :AutoReturnType -extern Type type_auto_return; -extern AstBasicType basic_type_auto_return; - -extern OnyxToken builtin_package_token; -extern AstGlobal builtin_heap_start; -extern AstGlobal builtin_stack_top; -extern AstGlobal builtin_tls_base; -extern AstGlobal builtin_tls_size; -extern AstType *builtin_string_type; -extern AstType *builtin_cstring_type; -extern AstType *builtin_range_type; -extern Type *builtin_range_type_type; -extern AstType *builtin_vararg_type; -extern Type *builtin_vararg_type_type; -extern AstTyped *builtin_context_variable; -extern AstType *builtin_allocator_type; -extern AstType *builtin_iterator_type; -extern AstType *builtin_callsite_type; -extern AstType *builtin_any_type; -extern AstType *builtin_code_type; -extern AstType *builtin_link_options_type; -extern AstTyped *type_table_node; -extern AstTyped *foreign_blocks_node; -extern AstType *foreign_block_type; -extern AstTyped *tagged_procedures_node; -extern AstFunction *builtin_initialize_data_segments; -extern AstFunction *builtin_run_init_procedures; -extern bh_arr(AstFunction *) init_procedures; - -typedef struct BuiltinSymbol { - char* package; - char* sym; - AstNode* node; -} BuiltinSymbol; - -extern const BuiltinSymbol builtin_symbols[]; - -typedef struct IntrinsicMap { - char* name; - OnyxIntrinsic intrinsic; -} IntrinsicMap; - -typedef Table(OnyxIntrinsic) IntrinsicTable; -extern IntrinsicTable intrinsic_table; - -extern bh_arr(OverloadOption) operator_overloads[Binary_Op_Count]; - -void initialize_builtins(bh_allocator a); -void initalize_special_globals(); -void introduce_build_options(bh_allocator a); - - -// NOTE: Useful not inlined functions -AstTyped* ast_reduce(bh_allocator a, AstTyped* node); -AstNode* ast_clone(bh_allocator a, void* n); -AstFunction* clone_function_header(bh_allocator a, AstFunction* func); -void clone_function_body(bh_allocator a, AstFunction* dest, AstFunction* source); - -void promote_numlit_to_larger(AstNumLit* num); -b32 convert_numlit_to_type(AstNumLit* num, Type* type); - -typedef enum TypeMatch { - TYPE_MATCH_SUCCESS, - TYPE_MATCH_FAILED, - TYPE_MATCH_YIELD, - TYPE_MATCH_SPECIAL, // Only used for nest polymorph function lookups -} TypeMatch; -#define unify_node_and_type(node, type) (unify_node_and_type_((node), (type), 1)) -TypeMatch unify_node_and_type_(AstTyped** pnode, Type* type, b32 permanent); -Type* resolve_expression_type(AstTyped* node); - -i64 get_expression_integer_value(AstTyped* node, b32 *out_is_valid); -char *get_expression_string_value(AstTyped* node, b32 *out_is_valid); - -b32 cast_is_legal(Type* from_, Type* to_, char** err_msg); -char* get_function_name(AstFunction* func); - -AstNode* strip_aliases(AstNode* node); - -AstNumLit* make_bool_literal(bh_allocator, b32 b); -AstNumLit* make_int_literal(bh_allocator a, i64 value); -AstNumLit* make_float_literal(bh_allocator a, f64 value); -AstRangeLiteral* make_range_literal(bh_allocator a, AstTyped* low, AstTyped* high); -AstBinaryOp* make_binary_op(bh_allocator a, BinaryOp operation, AstTyped* left, AstTyped* right); -AstArgument* make_argument(bh_allocator a, AstTyped* value); -AstFieldAccess* make_field_access(bh_allocator a, AstTyped* node, char* field); -AstAddressOf* make_address_of(bh_allocator a, AstTyped* node); -AstLocal* make_local(bh_allocator a, OnyxToken* token, AstType* type_node); -AstNode* make_symbol(bh_allocator a, OnyxToken* sym); -AstUnaryOp* make_cast(bh_allocator a, AstTyped* expr, Type* to); -AstZeroValue* make_zero_value(bh_allocator a, OnyxToken *token, Type* type); - -void arguments_initialize(Arguments* args); -b32 fill_in_arguments(Arguments* args, AstNode* provider, char** err_msg, b32 insert_zero_values); -void arguments_ensure_length(Arguments* args, u32 count); -void arguments_copy(Arguments* dest, Arguments* src); -void arguments_clone(Arguments* dest, Arguments* src); -void arguments_deep_clone(bh_allocator a, Arguments* dest, Arguments* src); -void arguments_remove_baked(Arguments* args); -void arguments_clear_baked_flags(Arguments* args); -TypeMatch check_arguments_against_type(Arguments* args, TypeFunction* func_type, VarArgKind* va_kind, - OnyxToken* location, char* func_name, struct OnyxError* error); -i32 get_argument_buffer_size(TypeFunction* type, Arguments* args); - -// GROSS: Using void* to avoid having to cast everything. -const char* node_get_type_name(void* node); - -b32 static_if_resolution(AstIf* static_if); - -void insert_poly_sln_into_scope(Scope* scope, AstPolySolution *sln); -TypeMatch find_polymorphic_sln(AstPolySolution *out, AstPolyParam *param, AstFunction *func, PolyProcLookupMethod pp_lookup, ptr actual, char** err_msg); -AstFunction* polymorphic_proc_lookup(AstFunction* pp, PolyProcLookupMethod pp_lookup, ptr actual, OnyxToken* tkn); -AstFunction* polymorphic_proc_solidify(AstFunction* pp, bh_arr(AstPolySolution) slns, OnyxToken* tkn); -AstNode* polymorphic_proc_try_solidify(AstFunction* pp, bh_arr(AstPolySolution) slns, OnyxToken* tkn); -AstFunction* polymorphic_proc_build_only_header(AstFunction* pp, PolyProcLookupMethod pp_lookup, ptr actual); -AstFunction* polymorphic_proc_build_only_header_with_slns(AstFunction* pp, bh_arr(AstPolySolution) slns, b32 error_if_failed); -b32 potentially_convert_function_to_polyproc(AstFunction *func); -AstPolyCallType* convert_call_to_polycall(AstCall* call); - -void add_overload_option(bh_arr(OverloadOption)* poverloads, u64 precedence, AstTyped* overload); -AstTyped* find_matching_overload_by_arguments(bh_arr(OverloadOption) overloads, Arguments* args); -AstTyped* find_matching_overload_by_type(bh_arr(OverloadOption) overloads, Type* type); -void report_unable_to_match_overload(AstCall* call, bh_arr(OverloadOption) overloads); - -void expand_macro(AstCall** pcall, AstFunction* template); -AstFunction* macro_resolve_header(AstMacro* macro, Arguments* args, OnyxToken* callsite, b32 error_if_failed); - -Type* polymorphic_struct_lookup(AstPolyStructType* ps_type, bh_arr(AstPolySolution) slns, OnyxFilePos pos, b32 error_if_failed); - -// NOTE: Useful inlined functions -static inline b32 is_lval(AstNode* node) { - node = strip_aliases(node); - - if ((node->kind == Ast_Kind_Local) - || (node->kind == Ast_Kind_Param) - || (node->kind == Ast_Kind_Global) - || (node->kind == Ast_Kind_Dereference) - || (node->kind == Ast_Kind_Subscript) - || (node->kind == Ast_Kind_Field_Access) - || (node->kind == Ast_Kind_Memres) - || (node->kind == Ast_Kind_Constraint_Sentinel)) // Bit of a hack, but this makes constraints like 'T->foo()' work. - return 1; - - if (node->kind == Ast_Kind_Compound) { - b32 all_lval = 1; - bh_arr_each(AstTyped *, expr, ((AstCompound *) node)->exprs) all_lval = all_lval && is_lval((AstNode *) *expr); - return all_lval; - } - - return 0; -} - -static inline b32 binop_is_assignment(BinaryOp binop) { - return (binop >= Binary_Op_Assign_Start && binop <= Binary_Op_Assign_End); -} - -static inline b32 binop_is_compare(BinaryOp binop) { - return (binop >= Binary_Op_Equal && binop <= Binary_Op_Greater_Equal); -} - -static inline b32 node_is_type(AstNode* node) { - node = strip_aliases(node); - - return (node->kind > Ast_Kind_Type_Start) && (node->kind < Ast_Kind_Type_End); -} - -static inline b32 node_is_auto_cast(AstNode* node) { - return (node->kind == Ast_Kind_Unary_Op) && (((AstUnaryOp *) node)->operation == Unary_Op_Auto_Cast); -} - -static inline b32 node_is_addressable_literal(AstNode* node) { - return (node->kind == Ast_Kind_Struct_Literal) - || (node->kind == Ast_Kind_Array_Literal); -} - -static inline Type* get_expression_type(AstTyped* expr) { - switch (expr->kind) { - case Ast_Kind_Block: case Ast_Kind_If: case Ast_Kind_While: return NULL; - default: return expr->type; - } -} - -static inline CallingConvention type_function_get_cc(Type* type) { - if (type == NULL) return CC_Undefined; - if (type->kind != Type_Kind_Function) return CC_Undefined; - if (type_is_compound(type->Function.return_type)) return CC_Return_Stack; - return CC_Return_Wasm; -} - -static inline ParamPassType type_get_param_pass(Type* type) { - if (type_is_structlike_strict(type) && !type_structlike_is_simple(type)) return Param_Pass_By_Implicit_Pointer; - return Param_Pass_By_Value; -} - -static inline AstFunction* get_function_from_node(AstNode* node) { - if (node->kind == Ast_Kind_Function) return (AstFunction *) node; - if (node->kind == Ast_Kind_Polymorphic_Proc) return (AstFunction *) node; - if (node->kind == Ast_Kind_Macro) return get_function_from_node((AstNode*) ((AstMacro *) node)->body); - return NULL; -} - -static inline void convert_polyproc_to_function(AstFunction *func) { - if (func->kind != Ast_Kind_Polymorphic_Proc) return; - - func->kind = Ast_Kind_Function; - func->parent_scope_of_poly_proc = NULL; - func->poly_params = NULL; - func->known_slns = NULL; - func->concrete_funcs = NULL; - func->active_queries.hashes = NULL; - func->active_queries.entries = NULL; - func->poly_scope = NULL; - func->entity = NULL; - func->type = NULL; - func->tags = NULL; -} - -static inline void convert_function_to_polyproc(AstFunction *func) { - if (func->kind != Ast_Kind_Function) return; - - func->kind = Ast_Kind_Polymorphic_Proc; - func->parent_scope_of_poly_proc = func->scope->parent; - func->scope = NULL; - if (func->entity) entity_change_type(&context.entities, func->entity, Entity_Type_Polymorphic_Proc); -} - -#endif // #ifndef ONYXASTNODES_H diff --git a/include/bh.h b/include/bh.h deleted file mode 100644 index 0049b269..00000000 --- a/include/bh.h +++ /dev/null @@ -1,2787 +0,0 @@ -#ifndef BH_H -#define BH_H - -// NOTE: For lseek64 -#define _LARGEFILE64_SOURCE - -#if defined(_WIN32) || defined(_WIN64) - #define _BH_WINDOWS 1 -#endif - -#if defined(__unix__) - #define _BH_LINUX 1 -#endif - -#include -#include -#include - -#ifdef _BH_LINUX - #include - #include - #include - #include -#endif - -#include -#include -#include // TODO: Replace with needed functions -#include -#include -#include - -#if defined(_MSC_VER) && !defined(_WINDOWS_) - #include "small_windows.h" -#endif - -//------------------------------------------------------------------------------------- -// Better types -//------------------------------------------------------------------------------------- -typedef uint8_t u8; -typedef uint16_t u16; -typedef uint32_t u32; -typedef uint64_t u64; -typedef int8_t i8; -typedef int16_t i16; -typedef int32_t i32; -typedef int64_t i64; -typedef int64_t isize; -typedef i32 b32; -typedef void* ptr; -typedef float f32; -typedef double f64; - - - - - - - - - - - - - - - - - - - - -//------------------------------------------------------------------------------------- -// Better character functions -//------------------------------------------------------------------------------------- -b32 char_is_alpha(const char a); -b32 char_is_num(const char a); -b32 char_is_alphanum(const char a); -char charset_contains(const char* charset, char ch); -b32 char_is_whitespace(const char a); -b32 char_in_range(const char lo, const char hi, const char a); -i64 chars_match(char* ptr1, char* ptr2); - - - - - -//------------------------------------------------------------------------------------- -// Better math functions -//------------------------------------------------------------------------------------- -#define bh_max(a, b) ((a) > (b) ? (a) : (b)) -#define bh_min(a, b) ((a) < (b) ? (a) : (b)) -#define bh_clamp(v, a, b) (bh_min((b), bh_max((a), (v)))) -#define bh_abs(x) ((x) < 0 ? -(x) : (x)) -#define size_of(x) (isize) sizeof(x) - -static inline u64 log2_dumb(u64 n) { - switch (n) { - case 1 << 0: return 0; - case 1 << 1: return 1; - case 1 << 2: return 2; - case 1 << 3: return 3; - case 1 << 4: return 4; - case 1 << 5: return 5; - case 1 << 6: return 6; - case 1 << 7: return 7; - case 1 << 8: return 8; - case 1 << 9: return 9; - case 1 << 10: return 10; - case 1 << 11: return 11; - case 1 << 12: return 12; - case 1 << 13: return 13; - case 1 << 14: return 14; - case 1 << 15: return 15; - case 1 << 16: return 16; - case 1 << 17: return 17; - case 1 << 18: return 18; - case 1 << 19: return 19; - case 1 << 20: return 20; - case 1 << 21: return 21; - case 1 << 22: return 22; - case 1 << 23: return 23; - case 1 << 24: return 24; - case 1 << 25: return 25; - case 1 << 26: return 26; - case 1 << 27: return 27; - case 1 << 28: return 28; - case 1 << 29: return 29; - case 1 << 30: return 30; - case 1 << 31: return 31; - - default: return 0; - } -} - - - -static inline const char* bh_num_suffix(u64 i) { - if (i == 11 || i == 12 || i == 13) return "th"; - - switch (i % 10) { - case 0: return "th"; - case 1: return "st"; - case 2: return "nd"; - case 3: return "rd"; - case 4: return "th"; - case 5: return "th"; - case 6: return "th"; - case 7: return "th"; - case 8: return "th"; - case 9: return "th"; - - default: return ""; - } -} - - - - -//------------------------------------------------------------------------------------- -// Conversion functions -//------------------------------------------------------------------------------------- - -// Converts an unsigned integer to the unsigned LEB128 format -u8* uint_to_uleb128(u64 n, i32* output_length); -u8* int_to_leb128(i64 n, i32* output_length); -u8* float_to_ieee754(f32 f, b32 reverse); -u8* double_to_ieee754(f64 f, b32 reverse); - -u64 uleb128_to_uint(u8* bytes, i32 *byte_walker); - - - -//------------------------------------------------------------------------------------- -// Helpful macros -//------------------------------------------------------------------------------------- -#define bh_offset_of(Type, elem) ((isize)&(((Type)*) 0)->elem) -#define bh_align_of(Type) bh_offset_of(struct { char c; Type member; }, member) -#define bh_swap(Type, a, b) do { Type tmp = (a); (a) = (b); (b) = tmp; } while(0) - -#define bh_align(x, a) if ((x) % (a) != 0) (x) += (a) - ((x) % (a)); - -#define bh_pointer_add(ptr, amm) ((void *)((u8 *) (ptr) + (amm))) -#define BH_BIT(x) (1 << (x)) -#define BH_MASK_SET(var, set, mask) ((set) ? ((var) |= (mask)) : ((var) &= ~(mask))) - -#define fori(var, lo, hi) for (i64 var = (lo); var < (hi); var++) -#define forir(var, hi, lo) for (i64 var = (hi); var >= (lo); var--) -#define forll(T, var, start, step) for (T* var = (start); var != NULL; var = (T *) var->step) - -#if defined(BH_DEBUG) && !defined(_BH_WINDOWS) - #define DEBUG_HERE __asm("int $3") -#else - #define DEBUG_HERE -#endif - - - - - -//------------------------------------------------------------------------------------- -// Custom allocators -//------------------------------------------------------------------------------------- - -typedef enum bh_allocator_actions { - bh_allocator_action_alloc, - bh_allocator_action_free, - bh_allocator_action_resize, -} bh_allocator_actions; - -#define BH_ALLOCATOR_PROC(name) \ -ptr name(ptr data, bh_allocator_actions action, \ - isize size, isize alignment, \ - void* prev_memory, \ - u64 flags) - -typedef BH_ALLOCATOR_PROC(bh__allocator_proc); // NOTE: so bh__allocator_proc can be used instead of that type - -typedef struct bh_allocator { - bh__allocator_proc* proc; // Procedure that can handle bh_allocator_actions - ptr data; // Pointer to the other data for the allocator -} bh_allocator; - -typedef enum bh_allocator_flags { - bh_allocator_flag_clear = 1 // Sets all memory to be 0 -} bh_allocator_flags; - -ptr bh_alloc(bh_allocator a, isize size); -ptr bh_alloc_aligned(bh_allocator a, isize size, isize alignment); -ptr bh_resize(bh_allocator a, ptr data, isize new_size); -ptr bh_resize_aligned(bh_allocator a, ptr data, isize new_size, isize alignment); -void bh_free(bh_allocator a, ptr data); - -#define bh_alloc_item(allocator_, T) (T *) bh_alloc(allocator_, sizeof(T)) -#define bh_alloc_array(allocator_, T, n) (T *) bh_alloc(allocator_, sizeof(T) * (n)) - -// NOTE: This should get optimized out since alignment should be a power of two -#define bh__align(x, alignment) ((((x) / alignment) + 1) * alignment) - - - - -// HEAP ALLOCATOR -// Essentially a wrapper for malloc, free and realloc -bh_allocator bh_heap_allocator(void); -BH_ALLOCATOR_PROC(bh_heap_allocator_proc); - - - - - -// ARENA ALLOCATOR -typedef struct bh_arena { - bh_allocator backing; - ptr first_arena, current_arena; - isize size, arena_size; // in bytes -} bh_arena; - -typedef struct bh__arena_internal { - ptr next_arena; - void* data; // Not actually a pointer, just used for the offset -} bh__arena_internal; - -void bh_arena_init(bh_arena* alloc, bh_allocator backing, isize arena_size); -void bh_arena_free(bh_arena* alloc); -bh_allocator bh_arena_allocator(bh_arena* alloc); -BH_ALLOCATOR_PROC(bh_arena_allocator_proc); - - - - - - -// SCRATCH ALLOCATOR -typedef struct bh_scratch { - bh_allocator backing; - ptr memory, end, curr; -} bh_scratch; - -void bh_scratch_init(bh_scratch* scratch, bh_allocator backing, isize scratch_size); -void bh_scratch_free(bh_scratch* scratch); -bh_allocator bh_scratch_allocator(bh_scratch* scratch); -BH_ALLOCATOR_PROC(bh_scratch_allocator_proc); - - - - - - - -//------------------------------------------------------------------------------------- -// Allocator based string functions -//------------------------------------------------------------------------------------- - -b32 bh_str_starts_with(char* str, char* start); -b32 bh_str_ends_with(char* str, char* end); -char* bh_strdup(bh_allocator a, char* str); - - - - - - - - - - - - - -//------------------------------------------------------------------------------------- -// Better files -//------------------------------------------------------------------------------------- -#ifndef BH_NO_FILE - -typedef enum bh_file_error { - BH_FILE_ERROR_NONE, - BH_FILE_ERROR_INVALID, - BH_FILE_ERROR_BAD_FD, -} bh_file_error; - -typedef enum bh_file_mode { - BH_FILE_MODE_READ = 1 << 0, - BH_FILE_MODE_WRITE = 1 << 1, - BH_FILE_MODE_APPEND = 1 << 2, - BH_FILE_MODE_RW = 1 << 3, - - BH_FILE_MODE_MODES = BH_FILE_MODE_READ | BH_FILE_MODE_WRITE | BH_FILE_MODE_APPEND | BH_FILE_MODE_RW -} bh_file_mode; - -typedef enum bh_file_whence { - BH_FILE_WHENCE_BEGIN = SEEK_SET, - BH_FILE_WHENCE_CURRENT = SEEK_CUR, - BH_FILE_WHENCE_END = SEEK_END, -} bh_file_whence; - -#ifdef _BH_WINDOWS - typedef HANDLE bh_file_descriptor; -#else - typedef int bh_file_descriptor; -#endif - -typedef struct bh_file { - bh_file_descriptor fd; - char const* filename; -} bh_file; - -typedef enum bh_file_standard { - BH_FILE_STANDARD_INPUT, - BH_FILE_STANDARD_OUTPUT, - BH_FILE_STANDARD_ERROR -} bh_file_standard; - -typedef struct bh_file_contents { - bh_allocator allocator; - const char *filename; - isize length; - void* data; - isize line_count; -} bh_file_contents; - - -#define BH_FILE_TYPE_DIRECTORY 3 -#define BH_FILE_TYPE_FILE 4 -#define BH_FILE_TYPE_LINK 5 -typedef struct bh_file_stats { - isize size; - u32 file_type; -} bh_file_stats; - -bh_file_error bh_file_get_standard(bh_file* file, bh_file_standard stand); - -bh_file_error bh_file_create(bh_file* file, char const* filename); -bh_file_error bh_file_open(bh_file* file, char const* filename); -bh_file_error bh_file_open_mode(bh_file* file, bh_file_mode mode, const char* filename); -bh_file_error bh_file_new(bh_file* file, bh_file_descriptor fd, const char* filename); -b32 bh_file_read_at(bh_file* file, i64 offset, void* buffer, isize buff_size, isize* bytes_read); -b32 bh_file_write_at(bh_file* file, i64 offset, void const* buffer, isize buff_size, isize* bytes_wrote); -i64 bh_file_seek(bh_file* file, i64 offset, bh_file_whence whence); -i64 bh_file_seek_to(bh_file* file, i64 offset); -i64 bh_file_seek_to_end(bh_file* file); -i64 bh_file_skip(bh_file* file, i64 bytes); -i64 bh_file_tell(bh_file* file); -bh_file_error bh_file_close(bh_file* file); -i32 bh_file_read(bh_file* file, void* buffer, isize buff_size); -i32 bh_file_write(bh_file* file, void* buffer, isize buff_size); -void bh_file_flush(bh_file* file); -i64 bh_file_size(bh_file* file); -b32 bh_file_stat(char const* filename, bh_file_stats* stat); -b32 bh_file_exists(char const* filename); -b32 bh_file_remove(char const* filename); -char* bh_path_get_full_name(char const* filename, bh_allocator a); -char* bh_path_get_parent(char const* filename, bh_allocator a); -char* bh_path_convert_separators(char* path); - -// This function returns a volatile pointer. Do not store it without copying! -// `included_folders` is bh_arr(const char *). -char* bh_lookup_file(char* filename, char* relative_to, char *suffix, b32 add_suffix, const char ** included_folders, b32 search_included_folders); - -#define bh_file_read_contents(allocator_, x) _Generic((x), \ - bh_file*: bh_file_read_contents_bh_file, \ - const char*: bh_file_read_contents_direct, \ - char*: bh_file_read_contents_direct)((allocator_), x) - -bh_file_contents bh_file_read_contents_bh_file(bh_allocator alloc, bh_file* file); -bh_file_contents bh_file_read_contents_direct(bh_allocator alloc, const char* filename); -i32 bh_file_contents_free(bh_file_contents* contents); - - -#ifdef _BH_WINDOWS - typedef struct Windows_Directory_Opened { - HANDLE hndl; - WIN32_FIND_DATAA found_file; - } Windows_Directory_Opened; - - typedef Windows_Directory_Opened *bh_dir; -#else - typedef DIR *bh_dir; -#endif - -typedef enum bh_dirent_type { - BH_DIRENT_UNKNOWN, - BH_DIRENT_BLOCK, - BH_DIRENT_CHAR, - BH_DIRENT_DIRECTORY, - BH_DIRENT_FILE, - BH_DIRENT_SYMLINK, - BH_DIRENT_OTHER, -} bh_dirent_type; - -typedef struct bh_dirent { - bh_dirent_type type; - u32 id; - u32 name_length; - char name[256]; -} bh_dirent; - -bh_dir bh_dir_open(char* path); -b32 bh_dir_read(bh_dir dir, bh_dirent* out); -void bh_dir_close(bh_dir dir); - -#endif - - - - - - - - - - -//------------------------------------------------------------------------------------- -// Alternate printing -//------------------------------------------------------------------------------------- -// Barebones implementation of printf. Does not support all format options -// Currently supports: -// %c - chars -// %_(u)d - ints where _ is: -// nothing - decimal -// o - octal -// x - hexadecimal -// %_(u)l - longs where _ is: -// nothing - decimal -// o - octal -// x - hexadecimal -// %f - floating points -// %s - null terminated strings -// %p - pointers -// %% - literal % - -typedef struct bh__print_format { - u32 base; -} bh__print_format; - -isize bh_printf(char const *fmt, ...); -isize bh_printf_va(char const *fmt, va_list va); -isize bh_printf_err(char const *fmt, ...); -isize bh_printf_err_va(char const *fmt, va_list va); -isize bh_fprintf(bh_file* f, char const *fmt, ...); -isize bh_fprintf_va(bh_file* f, char const *fmt, va_list va); -char* bh_bprintf(char const *fmt, ...); -char* bh_bprintf_va(char const *fmt, va_list va); -char* bh_aprintf(bh_allocator alloc, const char* fmt, ...); -char* bh_aprintf_va(bh_allocator alloc, const char* fmt, va_list va); -isize bh_snprintf(char *str, isize n, char const *fmt, ...); -isize bh_snprintf_va(char *str, isize n, char const *fmt, va_list va); - - - - - - - - - - - - - - - - - - - -//------------------------------------------------------------------------------------- -// Flexible buffer -//------------------------------------------------------------------------------------- - -typedef struct bh_buffer { - bh_allocator allocator; - i32 length, capacity; - u8* data; -} bh_buffer; - -#ifndef BH_BUFFER_GROW_FORMULA -#define BH_BUFFER_GROW_FORMULA(x) ((x) > 0 ? ((x) << 1) : 16) -#endif - -void bh_buffer_init(bh_buffer* buffer, bh_allocator alloc, i32 length); -void bh_buffer_free(bh_buffer* buffer); -void bh_buffer_clear(bh_buffer* buffer); -void bh_buffer_grow(bh_buffer* buffer, i32 length); -void bh_buffer_append(bh_buffer* buffer, const void * data, i32 length); -void bh_buffer_concat(bh_buffer* buffer, bh_buffer other); -void bh_buffer_write_byte(bh_buffer* buffer, u8 byte); -void bh_buffer_write_u32(bh_buffer* buffer, u32 i); -void bh_buffer_write_u64(bh_buffer* buffer, u64 i); -void bh_buffer_align(bh_buffer* buffer, u32 alignment); - - - - - - - - - - - - - - -//------------------------------------------------------------------------------------- -// Better dynamically-sized arrays -//------------------------------------------------------------------------------------- -#ifndef BH_NO_ARRAY - -typedef struct bh__arr { - bh_allocator allocator; - i32 length, capacity; -} bh__arr; - -#ifndef BH_ARR_GROW_FORMULA -#define BH_ARR_GROW_FORMULA(x) ((x) > 0 ? ((x) << 1) : 4) -#endif - -#define bh_arr(T) T* -#define bh__arrhead(arr) (((bh__arr *)(arr)) - 1) - -#define bh_arr_allocator(arr) (arr ? bh__arrhead(arr)->allocator : bh_heap_allocator()) -#define bh_arr_length(arr) (arr ? bh__arrhead(arr)->length : 0) -#define bh_arr_capacity(arr) (arr ? bh__arrhead(arr)->capacity : 0) -#define bh_arr_size(arr) (arr ? bh__arrhead(arr)->capacity * sizeof(*(arr)) : 0) -#define bh_arr_valid(arr, i) (arr ? (i32)(i) < bh__arrhead(arr)->length : 0) - -#define bh_arr_pop(arr) ((arr)[--bh__arrhead(arr)->length]) -#define bh_arr_last(arr) ((arr)[bh__arrhead(arr)->length - 1]) -#define bh_arr_end(arr, i) ((i) >= &(arr)[bh_arr_length(arr)]) -#define bh_arr_start(arr, i) ((i) < (arr)) - -#define bh_arr_new(allocator_, arr, cap) (bh__arr_grow((allocator_), (void**) &(arr), sizeof(*(arr)), cap)) -#define bh_arr_free(arr) (bh__arr_free((void**) &(arr))) -#define bh_arr_copy(allocator_, arr) (bh__arr_copy((allocator_), (arr), sizeof(*(arr)))) - -#define bh_arr_grow(arr, cap) (bh__arr_grow(bh_arr_allocator(arr), (void **) &(arr), sizeof(*(arr)), cap)) -#define bh_arr_shrink(arr, cap) (bh__arr_shrink((void **) &(arr), sizeof(*(arr)), cap)) -#define bh_arr_set_length(arr, n) (bh__arrhead(arr)->length = n) - -#define bh_arr_insertn(arr, i, n) (bh__arr_insertn((void **) &(arr), sizeof(*(arr)), i, n)) - -#define bh_arr_insert_end(arr, n) ( \ - bh__arr_grow(bh_arr_allocator(arr), (void **) &(arr), sizeof(*(arr)), bh_arr_length(arr) + n), \ - bh__arrhead(arr)->length += n) - -#define bh_arr_push(arr, value) ( \ - bh_arr_length(arr) + 1 > bh_arr_capacity(arr) ? bh__arr_grow(bh_arr_allocator(arr), (void **) &(arr), sizeof(*(arr)), bh_arr_length(arr) + 1) : 0, \ - arr[bh__arrhead(arr)->length++] = value) - -#define bh_arr_set_at(arr, n, value) ( \ - bh__arr_grow(bh_arr_allocator(arr), (void **) &(arr), sizeof(*(arr)), (n) + 1), \ - bh_arr_set_length((arr), bh_max(bh_arr_length(arr), (i32) (n) + 1)), \ - arr[n] = value) - -#define bh_arr_is_empty(arr) (arr ? bh__arrhead(arr)->length == 0 : 1) -#define bh_arr_clear(arr) (arr ? (bh__arrhead(arr)->length = 0) : 0) - -#define bh_arr_deleten(arr, i, n) (bh__arr_deleten((void **) &(arr), sizeof(*(arr)), i, n)) -#define bh_arr_fastdelete(arr, i) (arr[i] = arr[--bh__arrhead(arr)->length]) - -#define bh_arr_each(T, var, arr) for (T* var = (arr); !bh_arr_end((arr), var); var++) -#define bh_arr_rev_each(T, var, arr) for (T* var = &bh_arr_last((arr)); !bh_arr_start((arr), var); var--) - -#define bh_arr_zero(arr) memset(arr, 0, bh_arr_length(arr) * sizeof(*(arr))); - -b32 bh__arr_grow(bh_allocator alloc, void** arr, i32 elemsize, i32 cap); -b32 bh__arr_shrink(void** arr, i32 elemsize, i32 cap); -b32 bh__arr_free(void **arr); -void* bh__arr_copy(bh_allocator alloc, void *arr, i32 elemsize); -void bh__arr_insertn(void **arr, i32 elemsize, i32 index, i32 numelems); -void bh__arr_deleten(void **arr, i32 elemsize, i32 index, i32 numelems); - -#endif - - - - - - - - - - - - -//------------------------------------------------------------------------------------- -// STRING HASH TABLE FUNCTIONS -//------------------------------------------------------------------------------------- -#ifndef BH_NO_TABLE - -#ifdef BH_DEFINE -u64 bh__table_hash_function(const char* str, i32 len, i32 mod) { - u64 hash = 5381; - i32 c, l = 0; - if (len == 0) len = ((u32) 1 << 31) - 1; - - while ((c = *str++) && l++ < len) { - hash = (hash << 5) + hash + c; - } - - return hash % mod; -} -#endif - -typedef struct bh_table_iterator { - ptr *tab, *endtab; - i32 elemsize, arrlen; - ptr entry; -} bh_table_iterator; - -typedef struct bh__table { - bh_allocator allocator; - u64 table_size; // NOTE: u64 since padding will make it 8-bytes no matter what - ptr arrs[]; -} bh__table; - -#define bh_table(T) T* - -#ifdef BH_TABLE_SIZE_SAFE - #define bh_table_init(allocator_, tab, hs) bh__table_init(allocator_, (bh__table **)&(tab), hs) - #define bh_table_free(tab) bh__table_free((bh__table **)&(tab)) - #define bh_table_put(T, tab, key, value) (assert(sizeof(T) == sizeof(*(tab))), (*((T *) bh__table_put((bh__table *) tab, sizeof(T), key)) = (T) value)) - #define bh_table_has(T, tab, key) (assert(sizeof(T) == sizeof(*(tab))), (bh__table_has((bh__table *) tab, sizeof(T), key))) - #define bh_table_get(T, tab, key) (assert(sizeof(T) == sizeof(*(tab))), (*((T *) bh__table_get((bh__table *) tab, sizeof(T), key)))) - #define bh_table_delete(T, tab, key) (assert(sizeof(T) == sizeof(*(tab))), bh__table_delete((bh__table *) tab, sizeof(T), key)) - #define bh_table_clear(tab) (bh__table_clear((bh__table *) tab)) - - #define bh_table_iter_setup(T, tab) (assert(sizeof(T) == sizeof(*(tab))), bh__table_iter_setup((bh__table *) tab, sizeof(T))) - #define bh_table_iter_key(it) ((char *)(bh_pointer_add(it.entry, it.elemsize + sizeof(u16)))) - #define bh_table_iter_value(T, it) (*(T *)it.entry) -#else - #define bh_table_init(allocator_, tab, hs) bh__table_init(allocator_, (bh__table **)&(tab), hs) - #define bh_table_free(tab) bh__table_free((bh__table **)&(tab)) - #define bh_table_put(T, tab, key, value) (*((T *) bh__table_put((bh__table *) tab, sizeof(T), key)) = value) - #define bh_table_has(T, tab, key) (bh__table_has((bh__table *) tab, sizeof(T), key)) - #define bh_table_get(T, tab, key) (*((T *) bh__table_get((bh__table *) tab, sizeof(T), key))) - #define bh_table_delete(T, tab, key) (bh__table_delete((bh__table *) tab, sizeof(T), key)) - #define bh_table_clear(tab) (bh__table_clear((bh__table *) tab)) - - #define bh_table_iter_setup(T, tab) (bh__table_iter_setup((bh__table *) tab, sizeof(T))) - #define bh_table_iter_key(it) ((char *)(bh_pointer_add(it.entry, it.elemsize + sizeof(u16)))) - #define bh_table_iter_value(T, it) (*(T *)it.entry) -#endif - -#define bh_table_each_start(T, table) { \ - bh_table_iterator it = bh_table_iter_setup(T, (table)); \ - while (bh_table_iter_next(&it)) { \ - const char* key = bh_table_iter_key(it); \ - T value = bh_table_iter_value(T, it); -#define bh_table_each_end } } - -b32 bh__table_init(bh_allocator allocator, bh__table **table, i32 table_size); -b32 bh__table_free(bh__table **table); -ptr bh__table_put(bh__table *table, i32 elemsize, char *key); -b32 bh__table_has(bh__table *table, i32 elemsize, char *key); -ptr bh__table_get(bh__table *table, i32 elemsize, char *key); -void bh__table_delete(bh__table *table, i32 elemsize, char *key); -void bh__table_clear(bh__table *table); -bh_table_iterator bh__table_iter_setup(bh__table *table, i32 elemsize); -b32 bh_table_iter_next(bh_table_iterator* it); - -#endif -// Using stb_ds for tables now because they are better in every single way. -#define Table(T) struct { char *key; T value; } * - - - - - - - - -//------------------------------------------------------------------------------- -// IMAP (integer to integer map) -//------------------------------------------------------------------------------- -#ifndef BH_NO_IMAP - -typedef u64 bh_imap_entry_t; - -typedef struct bh__imap_lookup_result { - i64 hash_index; - i64 entry_prev; - i64 entry_index; -} bh__imap_lookup_result; - -typedef struct bh__imap_entry { - bh_imap_entry_t key, value; - i64 next; -} bh__imap_entry; - -typedef struct bh_imap { - bh_allocator allocator; - - bh_arr(i64) hashes; - bh_arr(bh__imap_entry) entries; -} bh_imap; - - -void bh_imap_init(bh_imap* imap, bh_allocator alloc, i32 hash_count); -void bh_imap_free(bh_imap* imap); -void bh_imap_put(bh_imap* imap, bh_imap_entry_t key, bh_imap_entry_t value); -b32 bh_imap_has(bh_imap* imap, bh_imap_entry_t key); -bh_imap_entry_t bh_imap_get(bh_imap* imap, bh_imap_entry_t key); -void bh_imap_delete(bh_imap* imap, bh_imap_entry_t key); -void bh_imap_clear(bh_imap* imap); - -#ifdef BH_DEFINE -#endif // BH_DEFINE - - -#endif - - - - - - - - - - - -// MANAGED HEAP ALLOCATOR -typedef struct bh_managed_heap { - bh_imap ptrs; -} bh_managed_heap; - -void bh_managed_heap_init(bh_managed_heap* mh); -void bh_managed_heap_free(bh_managed_heap* mh); -bh_allocator bh_managed_heap_allocator(bh_managed_heap* mh); -BH_ALLOCATOR_PROC(bh_managed_heap_allocator_proc); - - - - - - - - - - - -//------------------------------------------------------------------------------- -// OTHER COMMON DATA STRUCTURES -//------------------------------------------------------------------------------- -#ifndef BH_NO_DATASTRUCTURES - - - - - - - - - - - - - - - - -#endif // BH_NO_DATASTRUCTURES - - - - - -//------------------------------------------------------------------------------ -// TIME / DURATION -//------------------------------------------------------------------------------ -u64 bh_time_curr(); -u64 bh_time_duration(u64 old); - - - - - - - - - - - -#ifdef BH_DEFINE - -#undef BH_DEFINE -//------------------------------------------------------------------------------------- -// IMPLEMENTATIONS -//------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------- -// CHAR FUNCTIONS -//------------------------------------------------------------------------------------- - -b32 char_is_alpha(const char a) { - return ('a' <= a && a <= 'z') || ('A' <= a && a <= 'Z'); -} - -char charset_contains(const char* charset, char ch) { - while (*charset) { - if (*charset == ch) return ch; - charset++; - } - - return 0; -} - -b32 char_is_num(const char a) { - return ('0' <= a && a <= '9'); -} - -b32 char_is_alphanum(const char a) { - return char_is_alpha(a) || char_is_num(a); -} - -b32 char_is_whitespace(const char a) { - return charset_contains(" \t\r\n", a); -} - -b32 char_in_range(const char lo, const char hi, const char a) { - return lo <= a && a <= hi; -} - -i64 chars_match(char* ptr1, char* ptr2) { - i64 len = 0; - while (*ptr2 != '\0' && *ptr1 == *ptr2) ptr1++, ptr2++, len++; - return *ptr2 == '\0' ? len : 0; -} - - - - - - - - -//------------------------------------------------------------------------------------- -// CUSTOM ALLOCATORS IMPLEMENTATION -//------------------------------------------------------------------------------------- -ptr bh_alloc(bh_allocator a, isize size) { - return bh_alloc_aligned(a, size, 16); -} - -ptr bh_alloc_aligned(bh_allocator a, isize size, isize alignment) { - return a.proc(a.data, bh_allocator_action_alloc, size, alignment, NULL, 0); -} - -ptr bh_resize(bh_allocator a, ptr data, isize new_size) { - return bh_resize_aligned(a, data, new_size, 16); -} - -ptr bh_resize_aligned(bh_allocator a, ptr data, isize new_size, isize alignment) { - return a.proc(a.data, bh_allocator_action_resize, new_size, alignment, data, 0); -} - -void bh_free(bh_allocator a, ptr data) { - if (data != NULL) a.proc(a.data, bh_allocator_action_free, 0, 0, data, 0); -} - - - -// HEAP ALLOCATOR IMPLEMENTATION -bh_allocator bh_heap_allocator(void) { - return (bh_allocator) { - .proc = bh_heap_allocator_proc, - .data = NULL - }; -} - -BH_ALLOCATOR_PROC(bh_heap_allocator_proc) { - ptr retval = NULL; - - switch (action) { - case bh_allocator_action_alloc: { -#if defined(_BH_WINDOWS) - retval = _aligned_malloc(size, alignment); -#elif defined(_BH_LINUX) - i32 success = posix_memalign(&retval, alignment, size); -#endif - if (flags & bh_allocator_flag_clear && retval != NULL) { - memset(retval, 0, size); - } - } break; - - case bh_allocator_action_resize: { - // TODO: Maybe replace with better custom function -#if defined(_BH_WINDOWS) - retval = _aligned_realloc(prev_memory, size, alignment); -#elif defined(_BH_LINUX) - retval = realloc(prev_memory, size); -#endif - } break; - - case bh_allocator_action_free: { -#if defined(_BH_WINDOWS) - _aligned_free(prev_memory); -#elif defined(_BH_LINUX) - free(prev_memory); -#endif - } break; - } - - return retval; -} - - - - - - -// MANAGED HEAP ALLOCATOR IMPLEMENTATION -void bh_managed_heap_init(bh_managed_heap* mh) { - bh_imap_init(&mh->ptrs, bh_heap_allocator(), 512); -} - -void bh_managed_heap_free(bh_managed_heap* mh) { - bh_arr_each(bh__imap_entry, p, mh->ptrs.entries) { -#if defined(_BH_WINDOWS) - _aligned_free((void *) p->key); -#elif defined(_BH_LINUX) - free((void *) p->key); -#endif - } - - bh_imap_free(&mh->ptrs); -} - -bh_allocator bh_managed_heap_allocator(bh_managed_heap* mh) { - return (bh_allocator) { - .proc = bh_managed_heap_allocator_proc, - .data = mh - }; -} - -BH_ALLOCATOR_PROC(bh_managed_heap_allocator_proc) { - bh_managed_heap* mh = (bh_managed_heap *) data; - ptr retval = NULL; - - switch (action) { - case bh_allocator_action_alloc: { -#if defined(_BH_WINDOWS) - retval = _aligned_malloc(size, alignment); -#elif defined(_BH_LINUX) - i32 success = posix_memalign(&retval, alignment, size); -#endif - - if (flags & bh_allocator_flag_clear && retval != NULL) { - memset(retval, 0, size); - } - - if (retval != NULL) - bh_imap_put(&mh->ptrs, (u64) retval, 1); - } break; - - case bh_allocator_action_resize: { - bh_imap_delete(&mh->ptrs, (u64) prev_memory); -#if defined(_BH_WINDOWS) - retval = _aligned_realloc(prev_memory, size, alignment); -#elif defined(_BH_LINUX) - retval = realloc(prev_memory, size); -#endif - bh_imap_put(&mh->ptrs, (u64) retval, 1); - } break; - - case bh_allocator_action_free: { - bh_imap_delete(&mh->ptrs, (u64) prev_memory); -#if defined(_BH_WINDOWS) - _aligned_free(prev_memory); -#elif defined(_BH_LINUX) - free(prev_memory); -#endif - } break; - } - - return retval; -} - - - - - - - - -// ARENA ALLOCATOR IMPLEMENTATION -void bh_arena_init(bh_arena* alloc, bh_allocator backing, isize arena_size) { - arena_size = bh_max(arena_size, size_of(ptr)); - ptr data = bh_alloc(backing, arena_size); - - alloc->backing = backing; - alloc->arena_size = arena_size; - alloc->size = sizeof(ptr); - alloc->first_arena = data; - alloc->current_arena = data; - - ((bh__arena_internal *)(alloc->first_arena))->next_arena = NULL; -} - -void bh_arena_free(bh_arena* alloc) { - bh__arena_internal *walker = (bh__arena_internal *) alloc->first_arena; - bh__arena_internal *trailer = walker; - while (walker != NULL) { - walker = walker->next_arena; - bh_free(alloc->backing, trailer); - trailer = walker; - } - - alloc->first_arena = NULL; - alloc->current_arena = NULL; - alloc->arena_size = 0; - alloc->size = 0; -} - -bh_allocator bh_arena_allocator(bh_arena* alloc) { - return (bh_allocator) { - .proc = bh_arena_allocator_proc, - .data = alloc, - }; -} - -BH_ALLOCATOR_PROC(bh_arena_allocator_proc) { - bh_arena* alloc_arena = (bh_arena*) data; - - ptr retval = NULL; - - switch (action) { - case bh_allocator_action_alloc: { - bh_align(size, alignment); - bh_align(alloc_arena->size, alignment); - - if (size > alloc_arena->arena_size - size_of(ptr)) { - // Size too large for the arena - return NULL; - } - - if (alloc_arena->size + size >= alloc_arena->arena_size) { - bh__arena_internal* new_arena = (bh__arena_internal *) bh_alloc(alloc_arena->backing, alloc_arena->arena_size); - - if (new_arena == NULL) { - bh_printf_err("Arena Allocator: couldn't allocate new arena"); - return NULL; - } - - new_arena->next_arena = NULL; - ((bh__arena_internal *)(alloc_arena->current_arena))->next_arena = new_arena; - alloc_arena->current_arena = new_arena; - alloc_arena->size = sizeof(ptr); - } - - retval = bh_pointer_add(alloc_arena->current_arena, alloc_arena->size); - alloc_arena->size += size; - } break; - - case bh_allocator_action_resize: { - // Do nothing since this is a fixed allocator - } break; - - case bh_allocator_action_free: { - // Do nothing since this allocator isn't made for freeing memory - } break; - } - - return retval; -} - - - - -// SCRATCH ALLOCATOR IMPLEMENTATION -void bh_scratch_init(bh_scratch* scratch, bh_allocator backing, isize scratch_size) { - ptr memory = bh_alloc(backing, scratch_size); - - scratch->backing = backing; - scratch->memory = memory; - scratch->curr = memory; - scratch->end = bh_pointer_add(memory, scratch_size); -} - -void bh_scratch_free(bh_scratch* scratch) { - bh_free(scratch->backing, scratch->memory); - - scratch->memory = NULL; - scratch->curr = NULL; - scratch->end = NULL; -} - -bh_allocator bh_scratch_allocator(bh_scratch* scratch) { - return (bh_allocator) { - .proc = bh_scratch_allocator_proc, - .data = scratch, - }; -} - -BH_ALLOCATOR_PROC(bh_scratch_allocator_proc) { - bh_scratch* scratch = (bh_scratch*) data; - ptr retval = NULL; - - switch (action) { - case bh_allocator_action_alloc: { - if (size > ((u8 *) scratch->end) - ((u8 *) scratch->memory)) { - return NULL; - } - - retval = scratch->curr; - scratch->curr = bh_pointer_add(scratch->curr, size); - - if (scratch->curr >= scratch->end) { - scratch->curr = scratch->memory; - retval = scratch->curr; - } - } break; - - case bh_allocator_action_free: break; - - case bh_allocator_action_resize: { - if (size > ((u8 *) scratch->end) - ((u8 *) scratch->memory)) { - return NULL; - } - - retval = scratch->curr; - scratch->curr = bh_pointer_add(scratch->curr, size); - - if (scratch->curr >= scratch->end) { - scratch->curr = scratch->memory; - retval = scratch->curr; - } - - // HACK!!!!!: Using size instead of some kind of "old size" - memcpy(retval, prev_memory, size); - } break; - } - - return retval; -} - - - - -//------------------------------------------------------------------------------------- -// CONVERSION FUNCTIONS IMPLEMENTATION -//------------------------------------------------------------------------------------- -u8* uint_to_uleb128(u64 n, i32* output_length) { - static u8 buffer[16]; - - *output_length = 0; - u8* output = buffer; - u8 byte; - do { - byte = n & 0x7f; - n >>= 7; - if (n != 0) byte |= (1 << 7); - *output++ = byte; - (*output_length)++; - } while (n != 0); - - return buffer; -} - - -// Converts a signed integer to the signed LEB128 format -u8* int_to_leb128(i64 n, i32* output_length) { - static u8 buffer[16]; - - *output_length = 0; - u8* output = buffer; - b32 more = 1; - - i32 size = 64; - - u8 byte; - do { - byte = n & 0x7f; - n >>= 7; - - more = !(((n == 0) && (byte & 0x40) == 0) || ((n == -1) && (byte & 0x40) != 0)); - if (more) - byte |= 0x80; - *output++ = byte; - (*output_length)++; - } while (more); - - return buffer; -} - -// NOTE: This assumes the underlying implementation of float on the host -// system is already IEEE-754. This is safe to assume in most cases. -u8* float_to_ieee754(f32 f, b32 reverse) { - static u8 buffer[4]; - - u8* fmem = (u8*) &f; - if (reverse) { - buffer[0] = fmem[3]; - buffer[1] = fmem[2]; - buffer[2] = fmem[1]; - buffer[3] = fmem[0]; - } else { - buffer[0] = fmem[0]; - buffer[1] = fmem[1]; - buffer[2] = fmem[2]; - buffer[3] = fmem[3]; - } - - return buffer; -} - -u8* double_to_ieee754(f64 f, b32 reverse) { - static u8 buffer[8]; - - u8* fmem = (u8*) &f; - if (reverse) { - buffer[0] = fmem[7]; - buffer[1] = fmem[6]; - buffer[2] = fmem[5]; - buffer[3] = fmem[4]; - buffer[4] = fmem[3]; - buffer[5] = fmem[2]; - buffer[6] = fmem[1]; - buffer[7] = fmem[0]; - } else { - buffer[0] = fmem[0]; - buffer[1] = fmem[1]; - buffer[2] = fmem[2]; - buffer[3] = fmem[3]; - buffer[4] = fmem[4]; - buffer[5] = fmem[5]; - buffer[6] = fmem[6]; - buffer[7] = fmem[7]; - } - - return buffer; -} - -u64 uleb128_to_uint(u8* bytes, i32 *byte_count) { - u64 res = 0; - u64 shift = 0; - - while (1) { - u8 byte = bytes[(*byte_count)++]; - res |= (byte & 0x7f) << shift; - if ((byte & 0x80) == 0) break; - shift += 7; - } - return res; -} - - - -//------------------------------------------------------------------------------------- -// STRING IMPLEMENTATION -//------------------------------------------------------------------------------------- -b32 bh_str_starts_with(char* str, char* start) { - char* s = str; - char* p = start; - - while (*s != '\0' && *p != '\0' && *s == *p) s++, p++; - - return *p == '\0'; -} - -b32 bh_str_ends_with(char* str, char* end) { - i32 slen = strlen(str); - i32 elen = strlen(end); - - char* s = str + slen - 1; - char* e = end + elen - 1; - - while (*e == *s && e != end && s != str) e--, s--; - - return *e == *s; -} - -char* bh_strdup(bh_allocator a, char* str) { - u32 len = strlen(str); - char* buf = bh_alloc(a, len + 1); - - char* t = buf; - while ((*t++ = *str++)); - return buf; -} - - - - - -//------------------------------------------------------------------------------------- -// FILE IMPLEMENTATION -//------------------------------------------------------------------------------------- -#ifndef BH_NO_FILE - -static b32 bh__file_seek_wrapper(bh_file_descriptor fd, i64 offset, bh_file_whence whence, i64* new_offset); - -bh_file_error bh_file_get_standard(bh_file* file, bh_file_standard stand) { - const char* filename = NULL; - -#if defined(_BH_WINDOWS) - bh_file_descriptor sd_fd; - - switch (stand) { - case BH_FILE_STANDARD_INPUT: - sd_fd = GetStdHandle(STD_INPUT_HANDLE); - filename = "stdin"; - break; - case BH_FILE_STANDARD_OUTPUT: - sd_fd = GetStdHandle(STD_OUTPUT_HANDLE); - filename = "stdout"; - break; - case BH_FILE_STANDARD_ERROR: - sd_fd = GetStdHandle(STD_ERROR_HANDLE); - filename = "stderr"; - break; - default: - return BH_FILE_ERROR_BAD_FD; - } - file->fd = sd_fd; - -#elif defined(_BH_LINUX) - i32 sd_fd = -1; - - switch (stand) { - case BH_FILE_STANDARD_INPUT: - sd_fd = STDIN_FILENO; - filename = "stdin"; // These are constants in the data section so everything should be okay - break; - case BH_FILE_STANDARD_OUTPUT: - sd_fd = STDOUT_FILENO; - filename = "stdout"; - break; - case BH_FILE_STANDARD_ERROR: - sd_fd = STDERR_FILENO; - filename = "stderr"; - break; - default: - return BH_FILE_ERROR_BAD_FD; - } - - file->fd = sd_fd; -#endif - - - file->filename = filename; - return BH_FILE_ERROR_NONE; -} - -bh_file_error bh_file_create(bh_file* file, const char* filename) { - // Need to do this to avoid compiler complaining about types - bh_file_mode write_rw = (bh_file_mode) (BH_FILE_MODE_WRITE | BH_FILE_MODE_RW); - return bh_file_open_mode(file, write_rw, filename); -} - -bh_file_error bh_file_open(bh_file* file, const char* filename) { - return bh_file_open_mode(file, BH_FILE_MODE_READ, filename); -} - -bh_file_error bh_file_open_mode(bh_file* file, bh_file_mode mode, const char* filename) { -#if defined(_BH_WINDOWS) - DWORD desired_access; - DWORD creation_disposition; - - switch (mode & BH_FILE_MODE_MODES) { - case BH_FILE_MODE_READ: - desired_access = GENERIC_READ; - creation_disposition = OPEN_EXISTING; - break; - case BH_FILE_MODE_WRITE: - desired_access = GENERIC_WRITE; - creation_disposition = CREATE_ALWAYS; - break; - case BH_FILE_MODE_APPEND: - desired_access = GENERIC_WRITE; - creation_disposition = OPEN_ALWAYS; - break; - case BH_FILE_MODE_READ | BH_FILE_MODE_RW: - desired_access = GENERIC_READ | GENERIC_WRITE; - creation_disposition = OPEN_EXISTING; - break; - case BH_FILE_MODE_WRITE | BH_FILE_MODE_RW: - desired_access = GENERIC_READ | GENERIC_WRITE; - creation_disposition = CREATE_ALWAYS; - break; - case BH_FILE_MODE_APPEND | BH_FILE_MODE_RW: - desired_access = GENERIC_READ | GENERIC_WRITE; - creation_disposition = OPEN_ALWAYS; - break; - default: - return BH_FILE_ERROR_INVALID; - } - - - file->fd = CreateFileA(filename, - desired_access, - FILE_SHARE_READ, - NULL, - creation_disposition, - FILE_ATTRIBUTE_NORMAL, - NULL); - - if (file->fd == INVALID_HANDLE_VALUE) { - return BH_FILE_ERROR_INVALID; - } - - file->filename = filename; - return BH_FILE_ERROR_NONE; - -#elif defined(_BH_LINUX) - i32 os_mode = 0; - - switch (mode & BH_FILE_MODE_MODES) { - case BH_FILE_MODE_READ: os_mode = O_RDONLY; break; - case BH_FILE_MODE_WRITE: os_mode = O_WRONLY | O_CREAT | O_TRUNC; break; - case BH_FILE_MODE_APPEND: os_mode = O_WRONLY | O_APPEND | O_CREAT; break; - case BH_FILE_MODE_READ | BH_FILE_MODE_RW: os_mode = O_RDWR; break; - case BH_FILE_MODE_WRITE | BH_FILE_MODE_RW: os_mode = O_RDWR | O_CREAT | O_TRUNC; break; - case BH_FILE_MODE_APPEND | BH_FILE_MODE_RW: os_mode = O_RDWR | O_APPEND | O_CREAT; break; - //default: // TODO Handle errors - } - - file->fd = open(filename, os_mode, - S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH | S_IRGRP | S_IWGRP //+rw-rw-rw- - ); - if (file->fd < 0) { - return BH_FILE_ERROR_INVALID; - } - - // TODO: Set this using some allocator - file->filename = filename; - - return BH_FILE_ERROR_NONE; -#endif -} - -bh_file_error bh_file_new(bh_file* file, bh_file_descriptor fd, const char* filename) { - file->filename = filename; // This may be unsafe - file->fd = fd; - return BH_FILE_ERROR_NONE; -} - -b32 bh_file_read_at(bh_file* file, i64 offset, void* buffer, isize buff_size, isize* bytes_read) { -#if defined(_BH_WINDOWS) - bh_file_seek_to(file, offset); - BOOL res = ReadFile(file->fd, buffer, buff_size, (i32 *) bytes_read, NULL); - if (res) return 1; - else return 0; - -#elif defined(_BH_LINUX) - if (file->fd == 0) { - isize res = read(file->fd, buffer, buff_size); - if (res < 0) return 0; - if (bytes_read) *bytes_read = res; - return 1; - } - - isize res = pread(file->fd, buffer, buff_size, offset); - if (res < 0) return 0; - if (bytes_read) *bytes_read = res; - return 1; -#endif -} - -b32 bh_file_write_at(bh_file* file, i64 offset, void const* buffer, isize buff_size, isize* bytes_wrote) { - isize res; - i64 current_offset = 0; - bh__file_seek_wrapper(file->fd, 0, BH_FILE_WHENCE_CURRENT, ¤t_offset); - -#if defined(_BH_WINDOWS) - if (current_offset != offset) bh_file_seek_to(file, offset); - res = (isize) WriteFile(file->fd, buffer, buff_size, (i32 *) bytes_wrote, NULL); - return res; - -#elif defined(_BH_LINUX) - if (current_offset == offset || file->fd == 1 || file->fd == 2) { - // Standard in and out do like pwrite() - res = write(file->fd, buffer, buff_size); - } else { - res = pwrite(file->fd, buffer, buff_size, offset); - } - if (res < 0) return 0; - if (bytes_wrote) *bytes_wrote = res; - - return 1; -#endif -} - -static b32 bh__file_seek_wrapper(bh_file_descriptor fd, i64 offset, bh_file_whence whence, i64* new_offset) { -#if defined(_BH_WINDOWS) - LARGE_INTEGER new_file_pointer; - LARGE_INTEGER dest; - dest.QuadPart = offset; - - BOOL res = SetFilePointerEx(fd, dest, &new_file_pointer, whence); - *new_offset = new_file_pointer.QuadPart; - - return res; - -#elif defined(_BH_LINUX) - i64 res = lseek64(fd, offset, whence); - if (res < 0) return 0; - if (new_offset) *new_offset = res; - return 1; -#endif -} - -// Returns new offset -i64 bh_file_seek(bh_file* file, i64 offset, bh_file_whence whence) { - i64 new_offset = -1; - bh__file_seek_wrapper(file->fd, offset, whence, &new_offset); - return new_offset; -} - -i64 bh_file_seek_to(bh_file* file, i64 offset) { - i64 new_offset = -1; - bh__file_seek_wrapper(file->fd, offset, BH_FILE_WHENCE_BEGIN, &new_offset); - return new_offset; -} - -i64 bh_file_seek_to_end(bh_file* file) { - i64 new_offset = -1; - bh__file_seek_wrapper(file->fd, 0, BH_FILE_WHENCE_END, &new_offset); - return new_offset; -} - -i64 bh_file_skip(bh_file* file, i64 bytes) { - i64 new_offset = 0; - bh__file_seek_wrapper(file->fd, bytes, BH_FILE_WHENCE_CURRENT, &new_offset); - return new_offset; -} - -i64 bh_file_tell(bh_file* file) { - i64 new_offset = 0; - bh__file_seek_wrapper(file->fd, 0, BH_FILE_WHENCE_CURRENT, &new_offset); - return new_offset; -} - -bh_file_error bh_file_close(bh_file* file) { - bh_file_error err = BH_FILE_ERROR_NONE; - -#if defined(_BH_WINDOWS) - BOOL success = CloseHandle(file->fd); - if (!success) err = BH_FILE_ERROR_INVALID; - - return err; - -#elif defined(_BH_LINUX) - i32 res = close(file->fd); - if (res < 0) - err = BH_FILE_ERROR_INVALID; - - return err; -#endif -} - -b32 bh_file_read(bh_file* file, void* buffer, isize buff_size) { - return bh_file_read_at(file, bh_file_tell(file), buffer, buff_size, NULL); -} - -b32 bh_file_write(bh_file* file, void* buffer, isize buff_size) { - return bh_file_write_at(file, bh_file_tell(file), buffer, buff_size, NULL); -} - -void bh_file_flush(bh_file* file) { - #ifdef _BH_LINUX - fdatasync(file->fd); - #endif -} - -i64 bh_file_size(bh_file* file) { - i64 size = 0; - i64 prev = bh_file_tell(file); - bh_file_seek_to_end(file); - size = bh_file_tell(file); - bh_file_seek_to(file, prev); - return size; -} - -bh_file_contents bh_file_read_contents_bh_file(bh_allocator alloc, bh_file* file) { - bh_file_contents fc = { - .allocator = alloc, - .filename = bh_strdup(alloc, (char *) file->filename), - .length = 0, .data = NULL, .line_count = 0, - }; - - isize size = bh_file_size(file); - if (size <= 0) return fc; - - fc.data = bh_alloc(alloc, size + 1); - fc.length = size; - bh_file_read_at(file, 0, fc.data, fc.length, NULL); - ((u8*) fc.data)[fc.length] = '\0'; - - return fc; -} - -bh_file_contents bh_file_read_contents_direct(bh_allocator alloc, const char* filename) { - bh_file file; - bh_file_open(&file, filename); - bh_file_contents fc = bh_file_read_contents(alloc, &file); - bh_file_close(&file); - return fc; -} - -b32 bh_file_contents_free(bh_file_contents* contents) { - bh_free(contents->allocator, contents->data); - contents->length = 0; - return 1; -} - -b32 bh_file_stat(char const* filename, bh_file_stats* out) { - struct stat s; - if (stat(filename, &s) == -1) { - return 0; - } - - out->size = s.st_size; - - if ((s.st_mode & S_IFMT) == S_IFDIR) out->file_type = BH_FILE_TYPE_DIRECTORY; - if ((s.st_mode & S_IFMT) == S_IFREG) out->file_type = BH_FILE_TYPE_FILE; - -#if defined(_BH_LINUX) - if ((s.st_mode & S_IFMT) == S_IFLNK) out->file_type = BH_FILE_TYPE_LINK; -#endif - - return 1; -} - -b32 bh_file_exists(char const* filename) { - struct stat s; - return stat(filename, &s) != -1; -} - -b32 bh_file_remove(char const* filename) { -#if defined(_BH_WINDOWS) - return DeleteFileA(filename); - -#elif defined(_BH_LINUX) - return unlink(filename) == 0; -#endif -} - -char* bh_path_get_full_name(char const* filename, bh_allocator a) { -#if defined(_BH_WINDOWS) - char buffer[4096]; - GetFullPathNameA(filename, 4096, buffer, NULL); - - i32 len = strlen(buffer); - char* result = bh_alloc_array(a, char, len + 1); - memmove(result, buffer, len); - result[len] = 0; - - return result; - -#elif defined(_BH_LINUX) - char* p = realpath(filename, NULL); - - // Check if the file did not exists. - // :Cleanup should this return NULL? - if (p == NULL) return (char *) filename; - - i32 len = strlen(p); - char* result = bh_alloc_array(a, char, len + 1); - memmove(result, p, len); - result[len] = 0; - - free(p); - - return result; -#endif -} - -// NOTE: This assumes the filename is the full path, not relative to anything else. -#if defined(_BH_LINUX) - #define DIR_SEPARATOR '/' -#elif defined(_BH_WINDOWS) - #define DIR_SEPARATOR '\\' -#endif -char* bh_path_get_parent(char const* filename, bh_allocator a) { - - char* result = bh_strdup(a, (char *) filename); - char* end = result + strlen(result); - while (*end != DIR_SEPARATOR && end != result) *end-- = '\0'; - - return result; -} - -// This function returns a volatile pointer. Do not store it without copying! -char* bh_lookup_file(char* filename, char* relative_to, char *suffix, b32 add_suffix, bh_arr(const char *) included_folders, b32 search_included_folders) { - assert(relative_to != NULL); - - static char path[512]; - fori (i, 0, 512) path[i] = 0; - - static char fn[256]; - fori (i, 0, 256) fn[i] = 0; - - if (!bh_str_ends_with(filename, suffix) && add_suffix) { - bh_snprintf(fn, 256, "%s%s", filename, suffix); - } else { - bh_snprintf(fn, 256, "%s", filename); - } - - fori (i, 0, 256) if (fn[i] == '/') fn[i] = DIR_SEPARATOR; - - if (bh_str_starts_with(filename, "./")) { - if (relative_to[strlen(relative_to) - 1] != DIR_SEPARATOR) - bh_snprintf(path, 512, "%s%c%s", relative_to, DIR_SEPARATOR, fn + 2); - else - bh_snprintf(path, 512, "%s%s", relative_to, fn + 2); - - if (bh_file_exists(path)) return bh_path_get_full_name(path, bh_heap_allocator()); - - return fn; - } - - if (search_included_folders) { - bh_arr_each(const char *, folder, included_folders) { - if ((*folder)[strlen(*folder) - 1] != DIR_SEPARATOR) - bh_snprintf(path, 512, "%s%c%s", *folder, DIR_SEPARATOR, fn); - else - bh_snprintf(path, 512, "%s%s", *folder, fn); - - if (bh_file_exists(path)) return bh_path_get_full_name(path, bh_heap_allocator()); - } - } - - return fn; -} - -// -// Modifies the path in-place. -char* bh_path_convert_separators(char* path) { -#if defined(_BH_LINUX) - #define DIR_SEPARATOR '/' - #define OTHER_SEPARATOR '\\' -#elif defined(_BH_WINDOWS) - #define DIR_SEPARATOR '\\' - #define OTHER_SEPARATOR '/' -#endif - - fori (i, 0, (i64) strlen(path)) { - if (path[i] == OTHER_SEPARATOR) { - path[i] = DIR_SEPARATOR; - } - } - - return path; -} - - -bh_dir bh_dir_open(char* path) { -#ifdef _BH_WINDOWS - char new_path[512] = { 0 }; - strncpy(new_path, path, 511); - bh_path_convert_separators(new_path); - strncat(new_path, "\\*.*", 511); - - Windows_Directory_Opened* dir = malloc(sizeof(Windows_Directory_Opened)); - dir->hndl = FindFirstFileA(new_path, &dir->found_file); - if (dir->hndl == INVALID_HANDLE_VALUE) { - return NULL; - } - - return dir; -#endif - -#ifdef _BH_LINUX - DIR* dir = opendir(path); - return dir; -#endif -} - -b32 bh_dir_read(bh_dir dir, bh_dirent* out) { - -#ifdef _BH_WINDOWS - do { - BOOL success = FindNextFileA(dir->hndl, &dir->found_file); - if (!success) return 0; - } while (!strcmp(dir->found_file.cFileName, ".") || !strcmp(dir->found_file.cFileName, "..")); - - if (out == NULL) return 1; - - out->type = (dir->found_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - ? BH_DIRENT_DIRECTORY : BH_DIRENT_FILE; - out->id = 0; - out->name_length = strlen(dir->found_file.cFileName); - strncpy(out->name, dir->found_file.cFileName, 256); - - return 1; -#endif - -#ifdef _BH_LINUX - struct dirent *ent; - while (1) { - ent = readdir(dir); - if (ent == NULL) return 0; - - // Skip the current directory and parent directory - if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) break; - } - - bh_dirent_type type = 0; - switch (ent->d_type) { - case DT_UNKNOWN: break; - case DT_BLK: type = BH_DIRENT_BLOCK; break; - case DT_CHR: type = BH_DIRENT_CHAR; break; - case DT_DIR: type = BH_DIRENT_DIRECTORY; break; - case DT_LNK: type = BH_DIRENT_SYMLINK; break; - case DT_REG: type = BH_DIRENT_FILE; break; - default: type = BH_DIRENT_OTHER; break; - } - - if (out == NULL) return 1; - - out->type = type; - out->id = (u32) ent->d_ino; - out->name_length = strlen(ent->d_name); - strncpy(out->name, ent->d_name, 256); - - return 1; -#endif -} - -void bh_dir_close(bh_dir dir) { -#ifdef _BH_WINDOWS - if (dir == NULL) return; - - FindClose(dir->hndl); - free(dir); -#endif - -#ifdef _BH_LINUX - if (dir == NULL) return; - closedir(dir); -#endif -} - -#undef DIR_SEPARATOR - -#endif // ifndef BH_NO_FILE - - - - - - - - - - - - - - - - - - -//------------------------------------------------------------------------------------- -// ALTERNATE PRINTF IMPLEMENTATION -//------------------------------------------------------------------------------------- -isize bh_printf(char const *fmt, ...) { - isize res; - va_list va; - va_start(va, fmt); - res = bh_printf_va(fmt, va); - va_end(va); - return res; -} - -isize bh_printf_va(char const *fmt, va_list va) { - bh_file file; - bh_file_get_standard(&file, BH_FILE_STANDARD_OUTPUT); - return bh_fprintf_va(&file, fmt, va); -} - -isize bh_printf_err(char const *fmt, ...) { - isize res; - va_list va; - va_start(va, fmt); - res = bh_printf_err_va(fmt, va); - va_end(va); - return res; -} - -isize bh_printf_err_va(char const *fmt, va_list va) { - bh_file file; - bh_file_get_standard(&file, BH_FILE_STANDARD_ERROR); - return bh_fprintf_va(&file, fmt, va); -} - -isize bh_fprintf(bh_file* f, char const *fmt, ...) { - isize res; - va_list va; - va_start(va, fmt); - res = bh_fprintf_va(f, fmt, va); - va_end(va); - return res; -} - -isize bh_fprintf_va(bh_file* f, char const *fmt, va_list va) { - static char buffer[4096]; - isize len = bh_snprintf_va(buffer, sizeof(buffer), fmt, va); - bh_file_write(f, buffer, len - 1); - return len; -} - -char* bh_bprintf(char const *fmt, ...) { - char* res; - va_list va; - va_start(va, fmt); - res = bh_bprintf_va(fmt, va); - va_end(va); - return res; -} - -char* bh_bprintf_va(char const *fmt, va_list va) { - static char buffer[4096]; - isize len = bh_snprintf_va(buffer, sizeof(buffer), fmt, va); - buffer[len - 1] = 0; - return buffer; -} - -char* bh_aprintf(bh_allocator alloc, const char* fmt, ...) { - char* res; - va_list va; - va_start(va, fmt); - res = bh_aprintf_va(alloc, fmt, va); - va_end(va); - return res; -} - -char* bh_aprintf_va(bh_allocator alloc, const char* fmt, va_list va) { - static char buffer[4096]; - isize len = bh_snprintf_va(buffer, sizeof(buffer), fmt, va); - char* res = bh_alloc(alloc, len); - memcpy(res, buffer, len); - res[len - 1] = 0; - return res; -} - -isize bh_snprintf(char *str, isize n, char const *fmt, ...) { - isize res; - va_list va; - va_start(va, fmt); - res = bh_snprintf_va(str, n, fmt, va); - va_end(va); - return res; -} - -isize bh__print_string(char* dest, isize n, char* src) { - isize len = 0; - while (n-- && (*dest++ = *src++)) len++; - return len; -} - -isize bh__printu64(char* str, isize n, bh__print_format format, u64 value) { - char buf[128]; - buf[127] = 0; - char* walker = buf + 127; - u32 base = format.base ? format.base : 10, tmp; - - while (value > 0) { - tmp = value % base; - if (tmp > 9) { - switch (tmp) { - case 10: tmp = 'a'; break; - case 11: tmp = 'b'; break; - case 12: tmp = 'c'; break; - case 13: tmp = 'd'; break; - case 14: tmp = 'e'; break; - case 15: tmp = 'f'; break; - } - } else { - tmp += '0'; - } - - *--walker = tmp; - value /= base; - } - - if (format.base == 16) { - *--walker = 'x'; - *--walker = '0'; - } - - return bh__print_string(str, n, walker); -} - -isize bh__printi64(char* str, isize n, bh__print_format format, i64 value) { - char buf[128]; - buf[127] = 0; - char* walker = buf + 127; - u32 base = format.base ? format.base : 10, tmp; - - b32 negative = value < 0 ? 1 : 0; - if (negative) value = -value; - - if (value == 0) { - *--walker = '0'; - } else { - while (value > 0) { - tmp = value % base; - if (tmp > 9) { - switch (tmp) { - case 10: tmp = 'a'; break; - case 11: tmp = 'b'; break; - case 12: tmp = 'c'; break; - case 13: tmp = 'd'; break; - case 14: tmp = 'e'; break; - case 15: tmp = 'f'; break; - } - } else { - tmp += '0'; - } - - *--walker = tmp; - value /= base; - } - } - - if (negative) { - *--walker = '-'; - } - - if (format.base == 16) { - *--walker = 'x'; - *--walker = '0'; - } - - return bh__print_string(str, n, walker); -} - -// TODO: This implementation is VERY VERY BAD AND WRONG. Fix it. -isize bh__printf64(char* str, isize n, f64 value) { - fori (i, 0, 6) value *= 10.0; - i64 v = (i64) value; - - isize l1 = bh__printi64(str, n, ((bh__print_format) { .base = 10 }), v / 1000000); - str += l1; - n -= l1; - - *str = '.'; - str += 1; - n -= 1; - - isize l2 = bh__printi64(str, n, ((bh__print_format) { .base = 10 }), bh_abs(v) % 1000000); - - return l1 + l2 + 1; -} - -// TODO: This is very hacked together but for now it will work. -isize bh_snprintf_va(char *str, isize n, char const *fmt, va_list va) { - char const *text_start = str; - isize res; - - while (*fmt) { - bh__print_format format = { 0 }; - isize len = 0; - - while (*fmt && *fmt != '%') { - *(str++) = *(fmt++); - } - - if (!*fmt) goto end_of_format; - - fmt++; - - switch (*fmt++) { - case 'o': format.base = 8; break; - case 'x': format.base = 16; break; - default: fmt--; - } - - switch (*fmt) { - case 'c': { - char c = (char) va_arg(va, int); - *(str++) = c; - } break; - - case 'd': { - i64 value = (i64) va_arg(va, int); - len = bh__printi64(str, n, format, value); - } break; - - case 'l': { - i64 value = (i64) va_arg(va, long); - len = bh__printi64(str, n, format, value); - } break; - - case 'p': { - u64 value = (u64) va_arg(va, ptr); - format.base = 16; - len = bh__printu64(str, n, format, value); - } break; - - case 's': { - char* s = va_arg(va, char *); - len = bh__print_string(str, n, s); - } break; - - case 'b': { // String with a length (not null terminated) - char* s = va_arg(va, char *); - i32 l = va_arg(va, int); - len = bh__print_string(str, bh_min(l, n), s); - } break; - - case 'f': { - f64 f = va_arg(va, f64); - len = bh__printf64(str, n, f); - } break; - - default: fmt--; - } - - fmt++; - -end_of_format: - str += len; - n -= len; - } - - return str - text_start + 1; -} - - - - - -//------------------------------------------------------------------------------------- -// FLEXIBLE BUFFER IMPLEMENTATION -//------------------------------------------------------------------------------------- -#ifndef BH_NO_BUFFER - -void bh_buffer_init(bh_buffer* buffer, bh_allocator alloc, i32 init_size) { - buffer->allocator = alloc; - buffer->length = 0; - buffer->capacity = init_size; - buffer->data = bh_alloc(alloc, init_size); -} - -void bh_buffer_free(bh_buffer* buffer) { - bh_free(buffer->allocator, buffer->data); - buffer->length = 0; - buffer->capacity = 0; -} - -void bh_buffer_clear(bh_buffer* buffer) { - buffer->length = 0; -} - -void bh_buffer_grow(bh_buffer* buffer, i32 length) { - if (buffer == NULL) return; - - if (buffer->capacity >= length) { - // NOTE: Already have enough room - return; - } - - i32 newcap = buffer->capacity; - while (newcap < length) newcap = BH_BUFFER_GROW_FORMULA(newcap); - - ptr new_data = bh_resize(buffer->allocator, buffer->data, newcap); - if (new_data == NULL) return; - - buffer->capacity = newcap; - buffer->data = new_data; -} - -void bh_buffer_append(bh_buffer* buffer, const void * data, i32 length) { - if (buffer == NULL) return; - - if (buffer->length + length > buffer->capacity) { - bh_buffer_grow(buffer, buffer->length + length); - } - - memcpy(buffer->data + buffer->length, data, length); - buffer->length += length; -} - -void bh_buffer_concat(bh_buffer* buffer, bh_buffer other) { - bh_buffer_append(buffer, other.data, other.length); -} - -void bh_buffer_write_byte(bh_buffer* buffer, u8 byte) { - bh_buffer_grow(buffer, buffer->length + 1); - buffer->data[buffer->length++] = byte; -} - -void bh_buffer_write_u32(bh_buffer* buffer, u32 i) { - bh_buffer_grow(buffer, buffer->length + 4); - *((u32 *) bh_pointer_add(buffer->data, buffer->length)) = i; - buffer->length += 4; -} - -void bh_buffer_write_u64(bh_buffer* buffer, u64 i) { - bh_buffer_grow(buffer, buffer->length + 8); - *((u64 *) bh_pointer_add(buffer->data, buffer->length)) = i; - buffer->length += 8; -} - -void bh_buffer_align(bh_buffer* buffer, u32 alignment) { - if (buffer->length % alignment != 0) { - u32 difference = alignment - (buffer->length % alignment); - buffer->length += difference; - - bh_buffer_grow(buffer, buffer->length); - } -} - - -#endif - - - - - - - - - - - - - - - - - - - - - -//------------------------------------------------------------------------------------- -// ARRAY IMPLEMENTATION -//------------------------------------------------------------------------------------- -#ifndef BH_NO_ARRAY - -b32 bh__arr_grow(bh_allocator alloc, void** arr, i32 elemsize, i32 cap) { - bh__arr* arrptr; - - if (*arr == NULL) { - if (cap == 0 && elemsize == 0) return 1; - - arrptr = (bh__arr *) bh_alloc(alloc, sizeof(*arrptr) + elemsize * cap); - if (arrptr == NULL) return 0; - - arrptr->allocator = alloc; - arrptr->capacity = cap; - arrptr->length = 0; - - } else { - arrptr = bh__arrhead(*arr); - - if (arrptr->capacity < cap) { - void* p; - i32 newcap = arrptr->capacity; - while (newcap < cap) newcap = BH_ARR_GROW_FORMULA(newcap); - - p = bh_resize(arrptr->allocator, arrptr, sizeof(*arrptr) + elemsize * newcap); - - if (p) { - arrptr = (bh__arr *) p; - arrptr->capacity = newcap; - } else { - return 0; - } - } - } - - *arr = arrptr + 1; - return 1; -} - -b32 bh__arr_shrink(void** arr, i32 elemsize, i32 cap) { - if (*arr == NULL) return 0; - - bh__arr* arrptr = bh__arrhead(*arr); - cap = bh_max(cap, arrptr->length); - - if (arrptr->capacity > cap) { - void* p = bh_resize(arrptr->allocator, arrptr, sizeof(*arrptr) + elemsize * cap); - - if (p) { - arrptr = (bh__arr *) p; - arrptr->capacity = cap; - } else { - return 0; - } - } - - *arr = arrptr + 1; - return 1; -} - -b32 bh__arr_free(void **arr) { - if (*arr == NULL) return 0; - - bh__arr* arrptr = bh__arrhead(*arr); - bh_free(arrptr->allocator, arrptr); - *arr = NULL; - return 1; -} - -void* bh__arr_copy(bh_allocator alloc, void *arr, i32 elemsize) { - bh__arr* arrptr = bh__arrhead(arr); - - const i32 cap = arrptr->length; - - void* newarr = NULL; - bh__arr_grow(alloc, &newarr, elemsize, cap); - bh__arrhead(newarr)->length = cap; - bh__arrhead(newarr)->capacity = cap; - memcpy(newarr, arr, elemsize * arrptr->length); - - return newarr; -} - -void bh__arr_deleten(void **arr, i32 elemsize, i32 index, i32 numelems) { - bh__arr* arrptr = bh__arrhead(*arr); - - if (index >= arrptr->length) return; // Can't delete past the end of the array - if (numelems <= 0) return; // Can't delete nothing - - memmove( - (char *)(*arr) + elemsize * index, // Target - (char *)(*arr) + elemsize * (index + numelems), // Source - elemsize * (arrptr->length - (index + numelems))); // Length - arrptr->length -= numelems; -} - -void bh__arr_insertn(void **arr, i32 elemsize, i32 index, i32 numelems) { - if (numelems) { - if (*arr == NULL) { - bh__arr_grow(bh_arr_allocator(arr), arr, elemsize, numelems); // Making a new array - return; - } - - bh__arr* arrptr = bh__arrhead(*arr); - if (!bh__arr_grow(bh_arr_allocator(arr), arr, elemsize, arrptr->length + numelems)) return; // Fail case - arrptr = bh__arrhead(*arr); - memmove( - (char *)(*arr) + elemsize * (index + numelems), - (char *)(*arr) + elemsize * index, - elemsize * (arrptr->length - index)); - arrptr->length += numelems; - } -} - -#endif // ifndef BH_NO_ARRAY - - - - - - - - - - - - - - - -//------------------------------------------------------------------------------------- -// TABLE IMPLEMENTATION -//------------------------------------------------------------------------------------- -#ifndef BH_NO_TABLE - -b32 bh__table_init(bh_allocator allocator, bh__table **table, i32 table_size) { - *table = bh_alloc(allocator, sizeof(bh__table) + sizeof(ptr) * table_size); - if (*table == NULL) return 0; - - (*table)->allocator = allocator; - (*table)->table_size = table_size; - - for (i32 i = 0; i < table_size; i++) { - (*table)->arrs[i] = NULL; - } - - return 1; -} - -b32 bh__table_free(bh__table **table) { - if (*table == NULL) return 0; - - for (u64 i = 0; i < (*table)->table_size; i++) { - if ((*table)->arrs[i] != NULL) { - bh_arr_free((*table)->arrs[i]); - } - } - - bh_free((*table)->allocator, *table); - *table = NULL; - return 1; -} - -// Assumes NULL terminated string for key -ptr bh__table_put(bh__table *table, i32 elemsize, char *key) { - elemsize += (elemsize & 1); - - u64 index = bh__table_hash_function(key, 0, table->table_size); - u8 arr_was_new = 0; - - ptr arrptr = table->arrs[index]; - if (arrptr == NULL) { - arr_was_new = 1; - goto add_new_element; - } - u64 len = *(u64 *) arrptr; - arrptr = bh_pointer_add(arrptr, sizeof(u64)); - - u16 key_length = 0; - while (len--) { - arrptr = bh_pointer_add(arrptr, elemsize); - key_length = *(u16 *) arrptr; - arrptr = bh_pointer_add(arrptr, sizeof(u16)); - if (strncmp(key, (char *) arrptr, key_length) == 0) goto found_matching; - arrptr = bh_pointer_add(arrptr, key_length); - } - -add_new_element: - arrptr = table->arrs[index]; - i32 byte_len = bh_arr_length(arrptr); - if (byte_len == 0) byte_len = sizeof(u64); - key_length = strlen(key) + 1; - - // NOTE: Align to 16 bytes - if ((key_length + 2) % 16 != 0) { - key_length = ((((key_length + 2) >> 4) + 1) << 4) - 2; - } - - bh__arr_grow(table->allocator, &arrptr, 1, byte_len + elemsize + sizeof(u16) + key_length); - bh__arrhead(arrptr)->length = byte_len + elemsize + sizeof(u16) + key_length; - table->arrs[index] = arrptr; - - if (arr_was_new) { - *(u64 *) arrptr = 1; - } else { - (*(u64 *) arrptr)++; - } - - arrptr = bh_pointer_add(arrptr, byte_len + elemsize); - *(u16 *) arrptr = key_length; - arrptr = bh_pointer_add(arrptr, sizeof(u16)); - strncpy(arrptr, key, key_length); - -found_matching: - return bh_pointer_add(arrptr, -(sizeof(u16) + elemsize)); -} - -b32 bh__table_has(bh__table *table, i32 elemsize, char *key) { - elemsize += (elemsize & 1); - - u64 index = bh__table_hash_function(key, 0, table->table_size); - - ptr arrptr = table->arrs[index]; - if (arrptr == NULL) return 0; - - u64 len = *(u64 *) arrptr; - arrptr = bh_pointer_add(arrptr, sizeof(u64)); - - u16 key_length = 0; - while (len--) { - arrptr = bh_pointer_add(arrptr, elemsize); - key_length = *(u16 *) arrptr; - arrptr = bh_pointer_add(arrptr, sizeof(u16)); - if (strncmp(key, (char *) arrptr, key_length) == 0) return 1; - arrptr = bh_pointer_add(arrptr, key_length); - } - - return 0; -} - -ptr bh__table_get(bh__table *table, i32 elemsize, char *key) { - elemsize += (elemsize & 1); - - u64 index = bh__table_hash_function(key, 0, table->table_size); - - ptr arrptr = table->arrs[index]; - if (arrptr == NULL) return 0; - - u64 len = *(u64 *) arrptr; - arrptr = bh_pointer_add(arrptr, sizeof(u64)); - - u16 key_length = 0; - while (len--) { - arrptr = bh_pointer_add(arrptr, elemsize); - key_length = *(u16 *) arrptr; - arrptr = bh_pointer_add(arrptr, sizeof(u16)); - if (strncmp(key, (char *) arrptr, key_length) == 0) { - return bh_pointer_add(arrptr, -(sizeof(u16) + elemsize)); - } - arrptr = bh_pointer_add(arrptr, key_length); - } - - return NULL; -} - -void bh__table_delete(bh__table *table, i32 elemsize, char *key) { - elemsize += (elemsize & 1); - - u64 index = bh__table_hash_function(key, 0, table->table_size); - - ptr arrptr = table->arrs[index], walker; - if (arrptr == NULL) return; // Didn't exist - walker = arrptr; - - i32 byte_offset = 8; - i32 delete_len = 0; - - u64 len = *(u64 *) walker; - walker = bh_pointer_add(walker, sizeof(u64)); - - u16 key_length = 0; - while (len--) { - walker = bh_pointer_add(walker, elemsize); - key_length = *(u16 *) walker; - walker = bh_pointer_add(walker, sizeof(u16)); - if (strncmp(key, (char *) walker, key_length) == 0) { - delete_len = elemsize + sizeof(u16) + key_length; - goto found_matching; - } - walker = bh_pointer_add(walker, key_length); - byte_offset += elemsize + sizeof(u16) + key_length; - } - - // NOTE: Already didn't exist - return; - -found_matching: - bh__arr_deleten((void **) &arrptr, 1, byte_offset, delete_len); - table->arrs[index] = arrptr; - (*(u64 *) arrptr)--; -} - -void bh__table_clear(bh__table *table) { - for (u64 i = 0; i < table->table_size; i++) { - if (table->arrs[i] != NULL) { - // NOTE: Set length property to 0 - *((u64 *) table->arrs[i]) = 0; - bh_arr_set_length(table->arrs[i], 0); - } - } -} - -bh_table_iterator bh__table_iter_setup(bh__table *table, i32 elemsize) { - elemsize += (elemsize & 1); - - bh_table_iterator it = { - .tab = table->arrs, - .endtab = table->arrs + table->table_size, - .elemsize = elemsize, - .entry = NULL - }; - return it; -} - -b32 bh_table_iter_next(bh_table_iterator* it) { - if (it->tab == NULL) return 0; - - if (it->entry != NULL) { - it->arrlen--; - if (it->arrlen <= 0) { - it->tab++; - goto step_to_next; - } - - it->entry = bh_pointer_add(it->entry, it->elemsize); - it->entry = bh_pointer_add(it->entry, sizeof(u16) + (*(u16 *) it->entry)); - return 1; - } - -step_to_next: - // Step forward to find next valid - while (it->tab != it->endtab && *it->tab == NULL) { - it->tab++; - } - - if (it->tab == it->endtab) return 0; - - it->entry = *it->tab; - it->arrlen = *(u64 *) it->entry; - it->entry = bh_pointer_add(it->entry, sizeof(u64)); - if (it->arrlen <= 0) { - it->tab++; - goto step_to_next; - } - return 1; -} - -#endif // ifndef BH_NO_HASHTABLE - - - -//------------------------------------------------------------------------------------- -// IMAP IMPLEMENTATION -//------------------------------------------------------------------------------------- -#ifndef BH_NO_IMAP -void bh_imap_init(bh_imap* imap, bh_allocator alloc, i32 hash_count) { - imap->allocator = alloc; - - imap->hashes = NULL; - imap->entries = NULL; - - bh_arr_new(alloc, imap->hashes, hash_count); - bh_arr_new(alloc, imap->entries, 4); - - fori(count, 0, hash_count) bh_arr_push(imap->hashes, -1); -} - -void bh_imap_free(bh_imap* imap) { - bh_arr_free(imap->hashes); - bh_arr_free(imap->entries); - - imap->hashes = NULL; - imap->entries = NULL; -} - -bh__imap_lookup_result bh__imap_lookup(bh_imap* imap, bh_imap_entry_t key) { - bh__imap_lookup_result lr = { -1, -1, -1 }; - - u64 hash = 0xcbf29ce484222325ull ^ key; - u64 n = bh_arr_capacity(imap->hashes); - - lr.hash_index = hash % n; - lr.entry_index = imap->hashes[lr.hash_index]; - while (lr.entry_index >= 0) { - if (imap->entries[lr.entry_index].key == key) { - return lr; - } - - lr.entry_prev = lr.entry_index; - lr.entry_index = imap->entries[lr.entry_index].next; - } - - return lr; -} - -void bh_imap_put(bh_imap* imap, bh_imap_entry_t key, bh_imap_entry_t value) { - bh__imap_lookup_result lr = bh__imap_lookup(imap, key); - - if (lr.entry_index >= 0) { - imap->entries[lr.entry_index].value = value; - return; - } - - bh__imap_entry entry; - entry.key = key; - entry.value = value; - entry.next = imap->hashes[lr.hash_index]; - bh_arr_push(imap->entries, entry); - - imap->hashes[lr.hash_index] = bh_arr_length(imap->entries) - 1; -} - -b32 bh_imap_has(bh_imap* imap, bh_imap_entry_t key) { - bh__imap_lookup_result lr = bh__imap_lookup(imap, key); - return lr.entry_index >= 0; -} - -bh_imap_entry_t bh_imap_get(bh_imap* imap, bh_imap_entry_t key) { - bh__imap_lookup_result lr = bh__imap_lookup(imap, key); - if (lr.entry_index >= 0) { - return imap->entries[lr.entry_index].value; - } else { - return 0; - } -} - -void bh_imap_delete(bh_imap* imap, bh_imap_entry_t key) { - bh__imap_lookup_result lr = bh__imap_lookup(imap, key); - if (lr.entry_index < 0) return; - - if (lr.entry_prev < 0) { - imap->hashes[lr.hash_index] = imap->entries[lr.entry_index].next; - } else { - imap->entries[lr.entry_prev].next = imap->entries[lr.entry_index].next; - } - - // If it's that last thing in the array, just pop off the end - if (lr.entry_index == bh_arr_length(imap->entries) - 1) { - bh_arr_pop(imap->entries); - return; - } - - bh_arr_fastdelete(imap->entries, lr.entry_index); - bh__imap_lookup_result last = bh__imap_lookup(imap, imap->entries[lr.entry_index].key); - if (last.entry_prev >= 0) { - imap->entries[last.entry_prev].next = lr.entry_index; - } else { - imap->hashes[last.hash_index] = lr.entry_index; - } -} - -void bh_imap_clear(bh_imap* imap) { - // NOTE: Does not clear out an of the data that was in the map - bh_arr_each(i64, hash, imap->hashes) *hash = -1; - bh_arr_set_length(imap->entries, 0); -} - -#endif // ifndef BH_NO_IMAP - - - - - -u64 bh_time_curr() { -#if defined(_BH_WINDOWS) - LARGE_INTEGER result; - QueryPerformanceCounter(&result); - return (u64) result.QuadPart; - -#elif defined(_BH_LINUX) - struct timespec spec; - clock_gettime(CLOCK_REALTIME, &spec); - - time_t sec = spec.tv_sec; - u64 ms = spec.tv_nsec / 1000000; - if (ms > 999) { - sec++; - ms = 0; - } - - return sec * 1000 + ms; -#endif -} - -u64 bh_time_duration(u64 old) { -#if defined(_BH_WINDOWS) - u64 curr = bh_time_curr(); - u64 duration = curr - old; - - LARGE_INTEGER freq; - QueryPerformanceFrequency(&freq); - duration *= 1000; - duration /= freq.QuadPart; - return duration; - -#elif defined(_BH_LINUX) - u64 curr = bh_time_curr(); - return curr - old; -#endif -} - -#endif // ifdef BH_DEFINE - -#endif // ifndef BH_H diff --git a/include/doc.h b/include/doc.h deleted file mode 100644 index 75d4bfc5..00000000 --- a/include/doc.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef ONYXDOC_H -#define ONYXDOC_H - -#include "bh.h" -#include "astnodes.h" - -typedef enum DocFormat { - Doc_Format_Human, - Doc_Format_Tags, - Doc_Format_Html, -} DocFormat; - -typedef struct DocEntry { - OnyxFilePos pos; - char* sym; // Unused by doc generator - char* def; - char* additional; -} DocEntry; - -typedef struct DocPackage { - const char* name; - - bh_arr(DocEntry) public_entries; - bh_arr(DocEntry) private_entries; -} DocPackage; - -typedef struct OnyxDocumentation { - bh_arena doc_arena; - - DocFormat format; - - bh_arr(DocPackage) package_docs; -} OnyxDocumentation; - -OnyxDocumentation onyx_docs_generate(); -void onyx_docs_emit(OnyxDocumentation* doc, const char* filename); - -#endif diff --git a/include/errors.h b/include/errors.h deleted file mode 100644 index 9df95def..00000000 --- a/include/errors.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef ONYXERRORS_H -#define ONYXERRORS_H - -#include "bh.h" -#include "lex.h" - -#include - -typedef enum OnyxErrorRank { - Error_Undefined = 0, - Error_Other = 1, - Error_Warning = 2, - Error_Waiting_On = 3, - Error_Critical = 4, -} OnyxErrorRank; - -typedef struct OnyxError { - OnyxFilePos pos; - OnyxErrorRank rank; - char *text; -} OnyxError; - -typedef struct OnyxErrors { - bh_arena msg_arena; - bh_allocator msg_alloc; - - // NOTE: Pointer to a array of all the loaded files. - bh_arr(bh_file_contents)* file_contents; - - bh_arr(OnyxError) errors; -} OnyxErrors; - -extern OnyxErrors msgs; - -void onyx_errors_init(bh_arr(bh_file_contents)* files); -void onyx_submit_error(OnyxError error); -void onyx_report_error(OnyxFilePos pos, OnyxErrorRank rank, char * format, ...); -void onyx_submit_warning(OnyxError error); -void onyx_report_warning(OnyxFilePos pos, char* format, ...); -void onyx_errors_print(); -b32 onyx_has_errors(); -void onyx_clear_errors(); - -#endif diff --git a/include/lex.h b/include/lex.h deleted file mode 100644 index df684476..00000000 --- a/include/lex.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef ONYXLEX_H -#define ONYXLEX_H - -#include "bh.h" - -// NOTE: Used for global statistics -extern u64 lexer_lines_processed; -extern u64 lexer_tokens_processed; - -typedef enum TokenType { - Token_Type_Ascii_End = 256, - Token_Type_Unknown = 256, - Token_Type_End_Stream = 257, - - Token_Type_Comment = 258, - - Token_Type_Keyword_Package, - Token_Type_Keyword_Struct, - Token_Type_Keyword_Enum, - Token_Type_Keyword_Use, - Token_Type_Keyword_If, - Token_Type_Keyword_Else, - Token_Type_Keyword_Elseif, - Token_Type_Keyword_Return, - Token_Type_Keyword_Global, - Token_Type_Keyword_Cast, - Token_Type_Keyword_While, - Token_Type_Keyword_For, - Token_Type_Keyword_Break, - Token_Type_Keyword_Continue, - Token_Type_Keyword_Sizeof, - Token_Type_Keyword_Alignof, - Token_Type_Keyword_Typeof, - Token_Type_Keyword_Defer, - Token_Type_Keyword_Do, - Token_Type_Keyword_Case, - Token_Type_Keyword_Switch, - Token_Type_Keyword_Fallthrough, - Token_Type_Keyword_Macro, - Token_Type_Keyword_Interface, - Token_Type_Keyword_Where, - - Token_Type_Right_Arrow, - Token_Type_Left_Arrow, - Token_Type_Empty_Block, - Token_Type_Pipe, - - Token_Type_Greater_Equal, - Token_Type_Less_Equal, - Token_Type_Equal_Equal, - Token_Type_Not_Equal, - Token_Type_Plus_Equal, - Token_Type_Minus_Equal, - Token_Type_Star_Equal, - Token_Type_Fslash_Equal, - Token_Type_Percent_Equal, - Token_Type_And_Equal, - Token_Type_Or_Equal, - Token_Type_Xor_Equal, - Token_Type_And_And, - Token_Type_Or_Or, - Token_Type_Shift_Left, - Token_Type_Shift_Right, - Token_Type_Shift_Arith_Right, - Token_Type_Shl_Equal, - Token_Type_Shr_Equal, - Token_Type_Sar_Equal, - - Token_Type_Dot_Dot, - Token_Type_Tilde_Tilde, - - Token_Type_Symbol, - Token_Type_Literal_String, - Token_Type_Literal_Integer, - Token_Type_Literal_Float, - Token_Type_Literal_True, - Token_Type_Literal_False, - - Token_Type_Note, - - Token_Type_Count, -} TokenType; - -typedef struct OnyxFilePos { - const char* filename; - char* line_start; - u32 line; - - // NOTE: This assumes that no line is no longer than 2^16 chars - u16 column, length; -} OnyxFilePos; - -typedef struct OnyxToken { - TokenType type; - i32 length; - char* text; - OnyxFilePos pos; -} OnyxToken; - -typedef struct OnyxTokenizer { - char *start, *curr, *end; - - const char* filename; - - char* line_start; - u64 line_number; - - bh_arr(OnyxToken) tokens; -} OnyxTokenizer; - -const char* token_name(TokenType tkn_type); -void token_toggle_end(OnyxToken* tkn); -OnyxToken* onyx_get_token(OnyxTokenizer* tokenizer); -OnyxTokenizer onyx_tokenizer_create(bh_allocator allocator, bh_file_contents *fc); -void onyx_tokenizer_free(OnyxTokenizer* tokenizer); -void onyx_lex_tokens(OnyxTokenizer* tokenizer); - -b32 token_equals(OnyxToken* tkn1, OnyxToken* tkn2); -b32 token_text_equals(OnyxToken* tkn, char* text); -b32 token_same_file(OnyxToken *tkn1, OnyxToken *tkn2); - -#endif diff --git a/include/onyx_library.h b/include/onyx_library.h deleted file mode 100644 index 573de304..00000000 --- a/include/onyx_library.h +++ /dev/null @@ -1,102 +0,0 @@ - -#include "wasm.h" - -#if defined(_WIN32) || defined(_WIN64) - #define ONYX_EXPORT extern __declspec(dllexport) - #define ONYX_IMPORT extern __declspec(dllimport) -#endif - -#if defined(__unix__) - #define ONYX_EXPORT - #define ONYX_IMPORT -#endif - -typedef struct OnyxRuntime { - wasm_instance_t* wasm_instance; - wasm_module_t* wasm_module; - wasm_memory_t* wasm_memory; - wasm_engine_t *wasm_engine; - wasm_extern_vec_t wasm_imports; - - int argc; - char **argv; - - // HACK HACK HACK - // There should need to be this much stuff in here, but because Wasmer doesn't ship a "wasmerdll.lib" - // file for windows, it is impossible for it to link successfully against the function provided in onyx.exe. - // Therefore, we must serve as the linker and do this manually. Hopefully that library file will be - // shipped soon so this can go away... - char* (*wasm_memory_data)(wasm_memory_t *wasm_memory); - wasm_extern_t* (*wasm_extern_lookup_by_name)(wasm_module_t* module, wasm_instance_t* instance, const char* name); - wasm_func_t* (*wasm_extern_as_func)(wasm_extern_t* ext); - wasm_trap_t* (*wasm_func_call)(const wasm_func_t* wasm_func, const wasm_val_vec_t* args, wasm_val_vec_t* results); - wasm_instance_t* (*wasm_instance_new)(wasm_store_t* store, const wasm_module_t* module, const wasm_extern_vec_t* imports, wasm_trap_t** traps); - void (*onyx_print_trap)(wasm_trap_t* trap); - wasm_store_t *(*wasm_store_new)(wasm_engine_t *engine); - void (*wasm_store_delete)(wasm_store_t *store); -} OnyxRuntime; - -OnyxRuntime* runtime; - -typedef struct WasmValkindBuffer { - unsigned int count; - wasm_valkind_t types[20]; -} WasmValkindBuffer; - -typedef struct WasmFuncDefinition { - char* module_name; - char* import_name; - wasm_func_callback_t func; - - WasmValkindBuffer *params; - WasmValkindBuffer *results; -} WasmFuncDefinition; - -#define STRINGIFY1(a) #a -#define STRINGIFY2(a) STRINGIFY1(a) -#define CONCAT2(a, b) a ## _ ## b -#define CONCAT3(a, b, c) a ## _ ## b ## _ ## c -#define ONYX_MODULE_NAME_GEN(m) CONCAT2(__onyx_library, m) -#define ONYX_LINK_NAME_GEN(m) CONCAT2(onyx_library, m) -#define ONYX_FUNC_NAME(m, n) CONCAT3(__onyx_internal, m, n) -#define ONYX_DEF_NAME(m, n) CONCAT3(__onyx_internal_def, m, n) -#define ONYX_PARAM_NAME(m, n) CONCAT3(__onyx_internal_param_buffer, m, n) -#define ONYX_RESULT_NAME(m, n) CONCAT3(__onyx_internal_result_buffer, m, n) -#define ONYX_IMPORT_NAME(m, n) STRINGIFY1(m) "_" #n - -#define NUM_VALS(...) (sizeof((wasm_valkind_t []){ 0, __VA_ARGS__ }) / sizeof(wasm_valkind_t)) -#define _VALS(...) { NUM_VALS(__VA_ARGS__) - 1, __VA_ARGS__ } - -#define ONYX_DEF(name, params_types, result_types) \ - static wasm_trap_t* ONYX_FUNC_NAME(ONYX_LIBRARY_NAME, name)(const wasm_val_vec_t* params, wasm_val_vec_t* results); \ - static struct WasmValkindBuffer ONYX_PARAM_NAME(ONYX_LIBRARY_NAME, name) = _VALS params_types; \ - static struct WasmValkindBuffer ONYX_RESULT_NAME(ONYX_LIBRARY_NAME, name) = _VALS result_types; \ - static struct WasmFuncDefinition ONYX_DEF_NAME(ONYX_LIBRARY_NAME, name) = { STRINGIFY2(ONYX_LIBRARY_NAME), #name, ONYX_FUNC_NAME(ONYX_LIBRARY_NAME, name), & ONYX_PARAM_NAME(ONYX_LIBRARY_NAME, name), & ONYX_RESULT_NAME(ONYX_LIBRARY_NAME, name) }; \ - \ - static wasm_trap_t* ONYX_FUNC_NAME(ONYX_LIBRARY_NAME, name)(const wasm_val_vec_t* params, wasm_val_vec_t* results) - -#define ONYX_FUNC(name) & ONYX_DEF_NAME(ONYX_LIBRARY_NAME, name), -#define ONYX_LIBRARY \ - extern struct WasmFuncDefinition *ONYX_MODULE_NAME_GEN(ONYX_LIBRARY_NAME)[]; \ - ONYX_EXPORT WasmFuncDefinition** ONYX_LINK_NAME_GEN(ONYX_LIBRARY_NAME)(OnyxRuntime* in_runtime) { \ - runtime = in_runtime; \ - return ONYX_MODULE_NAME_GEN(ONYX_LIBRARY_NAME); \ - } \ - struct WasmFuncDefinition *ONYX_MODULE_NAME_GEN(ONYX_LIBRARY_NAME)[] = - -// Shorter names -#ifndef ONYX_NO_SHORT_NAMES -#undef BOOL -#undef INT -#undef LONG -#undef FLOAT -#undef DOUBLE -#define BOOL WASM_I32 -#define INT WASM_I32 -#define LONG WASM_I64 -#define FLOAT WASM_F32 -#define DOUBLE WASM_F64 -#define PTR WASM_I32 -#endif - -#define ONYX_PTR(p) ((void*) (p != 0 ? (runtime->wasm_memory_data(runtime->wasm_memory) + p) : NULL)) diff --git a/include/parser.h b/include/parser.h deleted file mode 100644 index 48889a86..00000000 --- a/include/parser.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef ONYXPARSER_H -#define ONYXPARSER_H - -#include "bh.h" - -#include "lex.h" -#include "errors.h" -#include "astnodes.h" - -typedef struct PolymorphicContext { - AstType* root_node; - bh_arr(AstPolyParam)* poly_params; -} PolymorphicContext; - -typedef struct OnyxParser { - bh_allocator allocator; - - Package *package; - Scope *file_scope; - - // NOTE: not used since all tokens are lexed before parsing starts - OnyxTokenizer *tokenizer; - OnyxToken *prev; - OnyxToken *curr; - - PolymorphicContext polymorph_context; - - bh_arr(AstFunction *) current_function_stack; - Scope *current_scope; - bh_arr(bh_arr(Entity *) *) alternate_entity_placement_stack; - bh_arr(OnyxToken *) current_symbol_stack; - - bh_arr(AstFlags) scope_flags; - - bh_arr(AstTyped *) stored_tags; - - u16 tag_depth : 16; - - b32 hit_unexpected_token : 1; - b32 parse_calls : 1; -} OnyxParser; - -const char* onyx_ast_node_kind_string(AstKind kind); -void* onyx_ast_node_new(bh_allocator alloc, i32 size, AstKind kind); -OnyxParser onyx_parser_create(bh_allocator alloc, OnyxTokenizer *tokenizer); -void onyx_parser_free(OnyxParser* parser); -void onyx_parse(OnyxParser *parser); - -#endif // #ifndef ONYXPARSER_H diff --git a/include/small_windows.h b/include/small_windows.h deleted file mode 100644 index 28cea0bd..00000000 --- a/include/small_windows.h +++ /dev/null @@ -1,444 +0,0 @@ -// Bill's Mini Windows.h from https://github.com/odin-lang/Odin/blob/master/src/gb/gb.h - -//////////////////////////////////////////////////////////////// -// -// Bill's Mini Windows.h -// -// - -#define GB_EXTERN extern -#define GB_DLL_EXPORT GB_EXTERN __declspec(dllexport) -#define GB_DLL_IMPORT GB_EXTERN __declspec(dllimport) - -#define WINAPI __stdcall -#define WINAPIV __cdecl -#define CALLBACK __stdcall -#define MAX_PATH 260 -#define CCHDEVICENAME 32 -#define CCHFORMNAME 32 - -typedef unsigned long DWORD; -typedef int WINBOOL; -#ifndef XFree86Server - #ifndef __OBJC__ - typedef WINBOOL BOOL; - #else - #define BOOL WINBOOL - #endif -typedef unsigned char BYTE; -#endif -typedef unsigned short WORD; -typedef float FLOAT; -typedef int INT; -typedef unsigned int UINT; -typedef short SHORT; -typedef long LONG; -typedef long long LONGLONG; -typedef unsigned short USHORT; -typedef unsigned long ULONG; -typedef unsigned long long ULONGLONG; - -typedef UINT WPARAM; -typedef LONG LPARAM; -typedef LONG LRESULT; -#ifndef _HRESULT_DEFINED -typedef LONG HRESULT; -#define _HRESULT_DEFINED -#endif -#ifndef XFree86Server -typedef WORD ATOM; -#endif /* XFree86Server */ -typedef void *HANDLE; -typedef HANDLE HGLOBAL; -typedef HANDLE HLOCAL; -typedef HANDLE GLOBALHANDLE; -typedef HANDLE LOCALHANDLE; -typedef void *HGDIOBJ; - -#define DECLARE_HANDLE(name) typedef HANDLE name -DECLARE_HANDLE(HACCEL); -DECLARE_HANDLE(HBITMAP); -DECLARE_HANDLE(HBRUSH); -DECLARE_HANDLE(HCOLORSPACE); -DECLARE_HANDLE(HDC); -DECLARE_HANDLE(HGLRC); -DECLARE_HANDLE(HDESK); -DECLARE_HANDLE(HENHMETAFILE); -DECLARE_HANDLE(HFONT); -DECLARE_HANDLE(HICON); -DECLARE_HANDLE(HKEY); -typedef HKEY *PHKEY; -DECLARE_HANDLE(HMENU); -DECLARE_HANDLE(HMETAFILE); -DECLARE_HANDLE(HINSTANCE); -typedef HINSTANCE HMODULE; -DECLARE_HANDLE(HPALETTE); -DECLARE_HANDLE(HPEN); -DECLARE_HANDLE(HRGN); -DECLARE_HANDLE(HRSRC); -DECLARE_HANDLE(HSTR); -DECLARE_HANDLE(HTASK); -DECLARE_HANDLE(HWND); -DECLARE_HANDLE(HWINSTA); -DECLARE_HANDLE(HKL); -DECLARE_HANDLE(HRAWINPUT); -DECLARE_HANDLE(HMONITOR); -#undef DECLARE_HANDLE - -typedef int HFILE; -typedef HICON HCURSOR; -typedef DWORD COLORREF; -typedef int (WINAPI *FARPROC)(); -typedef int (WINAPI *NEARPROC)(); -typedef int (WINAPI *PROC)(); -typedef LRESULT (CALLBACK *WNDPROC)(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); - -#if defined(_WIN64) -typedef unsigned __int64 ULONG_PTR; -typedef signed __int64 LONG_PTR; -#else -typedef unsigned long ULONG_PTR; -typedef signed long LONG_PTR; -#endif -typedef ULONG_PTR DWORD_PTR; - -typedef struct tagRECT { - LONG left; - LONG top; - LONG right; - LONG bottom; -} RECT; -typedef struct tagRECTL { - LONG left; - LONG top; - LONG right; - LONG bottom; -} RECTL; -typedef struct tagPOINT { - LONG x; - LONG y; -} POINT; -typedef struct tagSIZE { - LONG cx; - LONG cy; -} SIZE; -typedef struct tagPOINTS { - SHORT x; - SHORT y; -} POINTS; -typedef struct _SECURITY_ATTRIBUTES { - DWORD nLength; - HANDLE lpSecurityDescriptor; - BOOL bInheritHandle; -} SECURITY_ATTRIBUTES; -typedef enum _LOGICAL_PROCESSOR_RELATIONSHIP { - RelationProcessorCore, - RelationNumaNode, - RelationCache, - RelationProcessorPackage, - RelationGroup, - RelationAll = 0xffff -} LOGICAL_PROCESSOR_RELATIONSHIP; -typedef enum _PROCESSOR_CACHE_TYPE { - CacheUnified, - CacheInstruction, - CacheData, - CacheTrace -} PROCESSOR_CACHE_TYPE; -typedef struct _CACHE_DESCRIPTOR { - BYTE Level; - BYTE Associativity; - WORD LineSize; - DWORD Size; - PROCESSOR_CACHE_TYPE Type; -} CACHE_DESCRIPTOR; -typedef struct _SYSTEM_LOGICAL_PROCESSOR_INFORMATION { - ULONG_PTR ProcessorMask; - LOGICAL_PROCESSOR_RELATIONSHIP Relationship; - union { - struct { - BYTE Flags; - } ProcessorCore; - struct { - DWORD NodeNumber; - } NumaNode; - CACHE_DESCRIPTOR Cache; - ULONGLONG Reserved[2]; - }; -} SYSTEM_LOGICAL_PROCESSOR_INFORMATION; -typedef struct _MEMORY_BASIC_INFORMATION { - void *BaseAddress; - void *AllocationBase; - DWORD AllocationProtect; - size_t RegionSize; - DWORD State; - DWORD Protect; - DWORD Type; -} MEMORY_BASIC_INFORMATION; -typedef struct _SYSTEM_INFO { - union { - DWORD dwOemId; - struct { - WORD wProcessorArchitecture; - WORD wReserved; - }; - }; - DWORD dwPageSize; - void * lpMinimumApplicationAddress; - void * lpMaximumApplicationAddress; - DWORD_PTR dwActiveProcessorMask; - DWORD dwNumberOfProcessors; - DWORD dwProcessorType; - DWORD dwAllocationGranularity; - WORD wProcessorLevel; - WORD wProcessorRevision; -} SYSTEM_INFO; -typedef union _LARGE_INTEGER { - struct { - DWORD LowPart; - LONG HighPart; - }; - struct { - DWORD LowPart; - LONG HighPart; - } u; - LONGLONG QuadPart; -} LARGE_INTEGER; -typedef union _ULARGE_INTEGER { - struct { - DWORD LowPart; - DWORD HighPart; - }; - struct { - DWORD LowPart; - DWORD HighPart; - } u; - ULONGLONG QuadPart; -} ULARGE_INTEGER; - -typedef struct _OVERLAPPED { - ULONG_PTR Internal; - ULONG_PTR InternalHigh; - union { - struct { - DWORD Offset; - DWORD OffsetHigh; - }; - void *Pointer; - }; - HANDLE hEvent; -} OVERLAPPED; -typedef struct _FILETIME { - DWORD dwLowDateTime; - DWORD dwHighDateTime; -} FILETIME; -typedef struct _WIN32_FIND_DATAW { - DWORD dwFileAttributes; - FILETIME ftCreationTime; - FILETIME ftLastAccessTime; - FILETIME ftLastWriteTime; - DWORD nFileSizeHigh; - DWORD nFileSizeLow; - DWORD dwReserved0; - DWORD dwReserved1; - wchar_t cFileName[MAX_PATH]; - wchar_t cAlternateFileName[14]; -} WIN32_FIND_DATAW; -typedef struct _WIN32_FIND_DATAA { - DWORD dwFileAttributes; - FILETIME ftCreationTime; - FILETIME ftLastAccessTime; - FILETIME ftLastWriteTime; - DWORD nFileSizeHigh; - DWORD nFileSizeLow; - DWORD dwReserved0; - DWORD dwReserved1; - char cFileName[MAX_PATH]; - char cAlternateFileName[14]; -} WIN32_FIND_DATAA; -typedef struct _WIN32_FILE_ATTRIBUTE_DATA { - DWORD dwFileAttributes; - FILETIME ftCreationTime; - FILETIME ftLastAccessTime; - FILETIME ftLastWriteTime; - DWORD nFileSizeHigh; - DWORD nFileSizeLow; -} WIN32_FILE_ATTRIBUTE_DATA; -typedef enum _GET_FILEEX_INFO_LEVELS { - GetFileExInfoStandard, - GetFileExMaxInfoLevel -} GET_FILEEX_INFO_LEVELS; -typedef struct _STARTUPINFOA { - DWORD cb; - char * lpReserved; - char * lpDesktop; - char * lpTitle; - DWORD dwX; - DWORD dwY; - DWORD dwXSize; - DWORD dwYSize; - DWORD dwXCountChars; - DWORD dwYCountChars; - DWORD dwFillAttribute; - DWORD dwFlags; - WORD wShowWindow; - WORD cbReserved2; - char * lpReserved2; - HANDLE hStdInput; - HANDLE hStdOutput; - HANDLE hStdError; -} STARTUPINFOA, *LPSTARTUPINFOA; -typedef struct _PROCESS_INFORMATION { - HANDLE hProcess; - HANDLE hThread; - DWORD dwProcessId; - DWORD dwThreadId; -} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION; - -#define INFINITE 0xffffffffl -#define INVALID_HANDLE_VALUE ((void *)(intptr_t)(-1)) -#define STARTF_USESTDHANDLES 0x00000100 - -typedef DWORD WINAPI THREAD_START_ROUTINE(void *parameter); - -GB_DLL_IMPORT DWORD WINAPI GetLastError (void); -GB_DLL_IMPORT BOOL WINAPI CloseHandle (HANDLE object); -GB_DLL_IMPORT HANDLE WINAPI CreateSemaphoreA (SECURITY_ATTRIBUTES *semaphore_attributes, LONG initial_count, - LONG maximum_count, char const *name); -GB_DLL_IMPORT BOOL WINAPI ReleaseSemaphore (HANDLE semaphore, LONG release_count, LONG *previous_count); -GB_DLL_IMPORT DWORD WINAPI WaitForSingleObject(HANDLE handle, DWORD milliseconds); -GB_DLL_IMPORT HANDLE WINAPI CreateThread (SECURITY_ATTRIBUTES *semaphore_attributes, size_t stack_size, - THREAD_START_ROUTINE *start_address, void *parameter, - DWORD creation_flags, DWORD *thread_id); -GB_DLL_IMPORT DWORD WINAPI GetThreadId (HANDLE handle); -GB_DLL_IMPORT void WINAPI RaiseException (DWORD, DWORD, DWORD, ULONG_PTR const *); -GB_DLL_IMPORT BOOL WINAPI TerminateThread (HANDLE hThread, DWORD dwExitCode); -GB_DLL_IMPORT BOOL WINAPI CreateProcessA (char const * lpApplicationName, char * lpCommandLine, - SECURITY_ATTRIBUTES* lpProcessAttrs, SECURITY_ATTRIBUTES* lpThreadAttributes, - BOOL bInheritHandles, DWORD dwCreationFlags, void* lpEnvironment, - char const * lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, - LPPROCESS_INFORMATION lpProcessInformation); -GB_DLL_IMPORT BOOL WINAPI GetExitCodeProcess (HANDLE hProcess, DWORD *lpExitCode); -GB_DLL_IMPORT BOOL WINAPI CreatePipe (HANDLE *hReadPipe, HANDLE *hWritePipe, SECURITY_ATTRIBUTES* lpPipeAttributes, - DWORD nSize); -GB_DLL_IMPORT BOOL WINAPI TerminateProcess (HANDLE hProcess, UINT uExitCode); -GB_DLL_IMPORT BOOL WINAPI SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags); - -uintptr_t _beginthreadex( // NATIVE CODE - void *security, - unsigned stack_size, - unsigned ( __stdcall *start_address )( void * ), - void *arglist, - unsigned initflag, - unsigned *thrdaddr -); - - -GB_DLL_IMPORT BOOL WINAPI GetLogicalProcessorInformation(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buffer, DWORD *return_length); -GB_DLL_IMPORT DWORD_PTR WINAPI SetThreadAffinityMask(HANDLE thread, DWORD_PTR check_mask); -GB_DLL_IMPORT HANDLE WINAPI GetCurrentThread(void); - -#define PAGE_NOACCESS 0x01 -#define PAGE_READONLY 0x02 -#define PAGE_READWRITE 0x04 -#define PAGE_WRITECOPY 0x08 -#define PAGE_EXECUTE 0x10 -#define PAGE_EXECUTE_READ 0x20 -#define PAGE_EXECUTE_READWRITE 0x40 -#define PAGE_EXECUTE_WRITECOPY 0x80 -#define PAGE_GUARD 0x100 -#define PAGE_NOCACHE 0x200 -#define PAGE_WRITECOMBINE 0x400 - -#define MEM_COMMIT 0x1000 -#define MEM_RESERVE 0x2000 -#define MEM_DECOMMIT 0x4000 -#define MEM_RELEASE 0x8000 -#define MEM_FREE 0x10000 -#define MEM_PRIVATE 0x20000 -#define MEM_MAPPED 0x40000 -#define MEM_RESET 0x80000 -#define MEM_TOP_DOWN 0x100000 -#define MEM_LARGE_PAGES 0x20000000 -#define MEM_4MB_PAGES 0x80000000 - - - - -GB_DLL_IMPORT void * WINAPI VirtualAlloc (void *addr, size_t size, DWORD allocation_type, DWORD protect); -GB_DLL_IMPORT size_t WINAPI VirtualQuery (void const *address, MEMORY_BASIC_INFORMATION *buffer, size_t length); -GB_DLL_IMPORT BOOL WINAPI VirtualFree (void *address, size_t size, DWORD free_type); -GB_DLL_IMPORT void WINAPI GetSystemInfo(SYSTEM_INFO *system_info); - - -#define GENERIC_READ 0x80000000 -#define GENERIC_WRITE 0x40000000 -#define GENERIC_EXECUTE 0x20000000 -#define GENERIC_ALL 0x10000000 -#define FILE_SHARE_READ 0x00000001 -#define FILE_SHARE_WRITE 0x00000002 -#define FILE_SHARE_DELETE 0x00000004 -#define CREATE_NEW 1 -#define CREATE_ALWAYS 2 -#define OPEN_EXISTING 3 -#define OPEN_ALWAYS 4 -#define TRUNCATE_EXISTING 5 -#define FILE_ATTRIBUTE_READONLY 0x00000001 -#define FILE_ATTRIBUTE_NORMAL 0x00000080 -#define FILE_ATTRIBUTE_TEMPORARY 0x00000100 -#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 -#define ERROR_FILE_NOT_FOUND 2l -#define ERROR_ACCESS_DENIED 5L -#define ERROR_NO_MORE_FILES 18l -#define ERROR_FILE_EXISTS 80l -#define ERROR_ALREADY_EXISTS 183l -#define STD_INPUT_HANDLE ((DWORD)-10) -#define STD_OUTPUT_HANDLE ((DWORD)-11) -#define STD_ERROR_HANDLE ((DWORD)-12) - -GB_DLL_IMPORT int MultiByteToWideChar(UINT code_page, DWORD flags, char const * multi_byte_str, int multi_byte_len, wchar_t const *wide_char_str, int wide_char_len); -GB_DLL_IMPORT int WideCharToMultiByte(UINT code_page, DWORD flags, wchar_t const *wide_char_str, int wide_char_len, char const * multi_byte_str, int multi_byte_len); -GB_DLL_IMPORT BOOL WINAPI SetFilePointerEx(HANDLE file, LARGE_INTEGER distance_to_move, - LARGE_INTEGER *new_file_pointer, DWORD move_method); -GB_DLL_IMPORT BOOL WINAPI ReadFile (HANDLE file, void *buffer, DWORD bytes_to_read, DWORD *bytes_read, OVERLAPPED *overlapped); -GB_DLL_IMPORT BOOL WINAPI WriteFile (HANDLE file, void const *buffer, DWORD bytes_to_write, DWORD *bytes_written, OVERLAPPED *overlapped); -GB_DLL_IMPORT HANDLE WINAPI CreateFileW (wchar_t const *path, DWORD desired_access, DWORD share_mode, - SECURITY_ATTRIBUTES *, DWORD creation_disposition, - DWORD flags_and_attributes, HANDLE template_file); -GB_DLL_IMPORT HANDLE WINAPI CreateFileA (char const *path, DWORD desired_access, DWORD share_mode, - SECURITY_ATTRIBUTES *, DWORD creation_disposition, - DWORD flags_and_attributes, HANDLE template_file); -GB_DLL_IMPORT HANDLE WINAPI GetStdHandle (DWORD std_handle); -GB_DLL_IMPORT BOOL WINAPI GetFileSizeEx (HANDLE file, LARGE_INTEGER *size); -GB_DLL_IMPORT BOOL WINAPI SetEndOfFile (HANDLE file); -GB_DLL_IMPORT HANDLE WINAPI FindFirstFileW (wchar_t const *path, WIN32_FIND_DATAW *data); -GB_DLL_IMPORT HANDLE WINAPI FindFirstFileA (char const *path, WIN32_FIND_DATAA *data); -GB_DLL_IMPORT BOOL WINAPI FindNextFileA (HANDLE find_find, WIN32_FIND_DATAA *data); -GB_DLL_IMPORT BOOL WINAPI FindClose (HANDLE find_file); -GB_DLL_IMPORT BOOL WINAPI GetFileAttributesExW(wchar_t const *path, GET_FILEEX_INFO_LEVELS info_level_id, WIN32_FILE_ATTRIBUTE_DATA *data); -GB_DLL_IMPORT BOOL WINAPI CopyFileW(wchar_t const *old_f, wchar_t const *new_f, BOOL fail_if_exists); -GB_DLL_IMPORT BOOL WINAPI MoveFileW(wchar_t const *old_f, wchar_t const *new_f); -GB_DLL_IMPORT BOOL WINAPI DeleteFileA (char const *path); -GB_DLL_IMPORT BOOL WINAPI CreateDirectoryA(char const *path); -GB_DLL_IMPORT BOOL WINAPI RemoveDirectoryA(char const *path); -GB_DLL_IMPORT BOOL WINAPI MoveFileA (char const *old_path, char const *new_path); - -GB_DLL_IMPORT DWORD WINAPI GetFullPathNameA(char const *lpFileName, DWORD nBufferLength, char *lpBuffer, char **lpFilePart); - -GB_DLL_IMPORT HMODULE WINAPI LoadLibraryA (char const *filename); -GB_DLL_IMPORT BOOL WINAPI FreeLibrary (HMODULE module); -GB_DLL_IMPORT FARPROC WINAPI GetProcAddress(HMODULE module, char const *name); - -GB_DLL_IMPORT BOOL WINAPI QueryPerformanceFrequency(LARGE_INTEGER *frequency); -GB_DLL_IMPORT BOOL WINAPI QueryPerformanceCounter (LARGE_INTEGER *counter); -GB_DLL_IMPORT void WINAPI GetSystemTimeAsFileTime (FILETIME *system_time_as_file_time); -GB_DLL_IMPORT void WINAPI Sleep(DWORD milliseconds); -GB_DLL_IMPORT void WINAPI ExitProcess(UINT exit_code); - -GB_DLL_IMPORT BOOL WINAPI SetEnvironmentVariableA(char const *name, char const *value); -GB_DLL_IMPORT DWORD WINAPI GetEnvironmentVariableA(char const * lpName, char * lpBuffer, DWORD nSize); - -GB_DLL_IMPORT short WINAPI htons(short hostshort); -GB_DLL_IMPORT int WINAPI htonl(int hostint); -GB_DLL_IMPORT short WINAPI ntohs(short netshort); -GB_DLL_IMPORT int WINAPI ntohl(int netint); diff --git a/include/stb_ds.h b/include/stb_ds.h deleted file mode 100644 index e84c82d1..00000000 --- a/include/stb_ds.h +++ /dev/null @@ -1,1895 +0,0 @@ -/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 - - This is a single-header-file library that provides easy-to-use - dynamic arrays and hash tables for C (also works in C++). - - For a gentle introduction: - http://nothings.org/stb_ds - - To use this library, do this in *one* C or C++ file: - #define STB_DS_IMPLEMENTATION - #include "stb_ds.h" - -TABLE OF CONTENTS - - Table of Contents - Compile-time options - License - Documentation - Notes - Notes - Dynamic arrays - Notes - Hash maps - Credits - -COMPILE-TIME OPTIONS - - #define STBDS_NO_SHORT_NAMES - - This flag needs to be set globally. - - By default stb_ds exposes shorter function names that are not qualified - with the "stbds_" prefix. If these names conflict with the names in your - code, define this flag. - - #define STBDS_SIPHASH_2_4 - - This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. - - By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for - 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force - stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes - hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on - 64-byte keys, and 10% slower on 256-byte keys on my test computer. - - #define STBDS_REALLOC(context,ptr,size) better_realloc - #define STBDS_FREE(context,ptr) better_free - - These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. - - By default stb_ds uses stdlib realloc() and free() for memory management. You can - substitute your own functions instead by defining these symbols. You must either - define both, or neither. Note that at the moment, 'context' will always be NULL. - @TODO add an array/hash initialization function that takes a memory context pointer. - - #define STBDS_UNIT_TESTS - - Defines a function stbds_unit_tests() that checks the functioning of the data structures. - - Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' - (or equivalentally '-std=c++11') when using anonymous structures as seen on the web - page or in STBDS_UNIT_TESTS. - -LICENSE - - Placed in the public domain and also MIT licensed. - See end of file for detailed license information. - -DOCUMENTATION - - Dynamic Arrays - - Non-function interface: - - Declare an empty dynamic array of type T - T* foo = NULL; - - Access the i'th item of a dynamic array 'foo' of type T, T* foo: - foo[i] - - Functions (actually macros) - - arrfree: - void arrfree(T*); - Frees the array. - - arrlen: - ptrdiff_t arrlen(T*); - Returns the number of elements in the array. - - arrlenu: - size_t arrlenu(T*); - Returns the number of elements in the array as an unsigned type. - - arrpop: - T arrpop(T* a) - Removes the final element of the array and returns it. - - arrput: - T arrput(T* a, T b); - Appends the item b to the end of array a. Returns b. - - arrins: - T arrins(T* a, int p, T b); - Inserts the item b into the middle of array a, into a[p], - moving the rest of the array over. Returns b. - - arrinsn: - void arrinsn(T* a, int p, int n); - Inserts n uninitialized items into array a starting at a[p], - moving the rest of the array over. - - arraddnptr: - T* arraddnptr(T* a, int n) - Appends n uninitialized items onto array at the end. - Returns a pointer to the first uninitialized item added. - - arraddnindex: - size_t arraddnindex(T* a, int n) - Appends n uninitialized items onto array at the end. - Returns the index of the first uninitialized item added. - - arrdel: - void arrdel(T* a, int p); - Deletes the element at a[p], moving the rest of the array over. - - arrdeln: - void arrdeln(T* a, int p, int n); - Deletes n elements starting at a[p], moving the rest of the array over. - - arrdelswap: - void arrdelswap(T* a, int p); - Deletes the element at a[p], replacing it with the element from - the end of the array. O(1) performance. - - arrsetlen: - void arrsetlen(T* a, int n); - Changes the length of the array to n. Allocates uninitialized - slots at the end if necessary. - - arrsetcap: - size_t arrsetcap(T* a, int n); - Sets the length of allocated storage to at least n. It will not - change the length of the array. - - arrcap: - size_t arrcap(T* a); - Returns the number of total elements the array can contain without - needing to be reallocated. - - Hash maps & String hash maps - - Given T is a structure type: struct { TK key; TV value; }. Note that some - functions do not require TV value and can have other fields. For string - hash maps, TK must be 'char *'. - - Special interface: - - stbds_rand_seed: - void stbds_rand_seed(size_t seed); - For security against adversarially chosen data, you should seed the - library with a strong random number. Or at least seed it with time(). - - stbds_hash_string: - size_t stbds_hash_string(char *str, size_t seed); - Returns a hash value for a string. - - stbds_hash_bytes: - size_t stbds_hash_bytes(void *p, size_t len, size_t seed); - These functions hash an arbitrary number of bytes. The function - uses a custom hash for 4- and 8-byte data, and a weakened version - of SipHash for everything else. On 64-bit platforms you can get - specification-compliant SipHash-2-4 on all data by defining - STBDS_SIPHASH_2_4, at a significant cost in speed. - - Non-function interface: - - Declare an empty hash map of type T - T* foo = NULL; - - Access the i'th entry in a hash table T* foo: - foo[i] - - Function interface (actually macros): - - hmfree - shfree - void hmfree(T*); - void shfree(T*); - Frees the hashmap and sets the pointer to NULL. - - hmlen - shlen - ptrdiff_t hmlen(T*) - ptrdiff_t shlen(T*) - Returns the number of elements in the hashmap. - - hmlenu - shlenu - size_t hmlenu(T*) - size_t shlenu(T*) - Returns the number of elements in the hashmap. - - hmgeti - shgeti - hmgeti_ts - ptrdiff_t hmgeti(T*, TK key) - ptrdiff_t shgeti(T*, char* key) - ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) - Returns the index in the hashmap which has the key 'key', or -1 - if the key is not present. - - hmget - hmget_ts - shget - TV hmget(T*, TK key) - TV shget(T*, char* key) - TV hmget_ts(T*, TK key, ptrdiff_t tempvar) - Returns the value corresponding to 'key' in the hashmap. - The structure must have a 'value' field - - hmgets - shgets - T hmgets(T*, TK key) - T shgets(T*, char* key) - Returns the structure corresponding to 'key' in the hashmap. - - hmgetp - shgetp - hmgetp_ts - hmgetp_null - shgetp_null - T* hmgetp(T*, TK key) - T* shgetp(T*, char* key) - T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) - T* hmgetp_null(T*, TK key) - T* shgetp_null(T*, char *key) - Returns a pointer to the structure corresponding to 'key' in - the hashmap. Functions ending in "_null" return NULL if the key - is not present in the hashmap; the others return a pointer to a - structure holding the default value (but not the searched-for key). - - hmdefault - shdefault - TV hmdefault(T*, TV value) - TV shdefault(T*, TV value) - Sets the default value for the hashmap, the value which will be - returned by hmget/shget if the key is not present. - - hmdefaults - shdefaults - TV hmdefaults(T*, T item) - TV shdefaults(T*, T item) - Sets the default struct for the hashmap, the contents which will be - returned by hmgets/shgets if the key is not present. - - hmput - shput - TV hmput(T*, TK key, TV value) - TV shput(T*, char* key, TV value) - Inserts a pair into the hashmap. If the key is already - present in the hashmap, updates its value. - - hmputs - shputs - T hmputs(T*, T item) - T shputs(T*, T item) - Inserts a struct with T.key into the hashmap. If the struct is already - present in the hashmap, updates it. - - hmdel - shdel - int hmdel(T*, TK key) - int shdel(T*, char* key) - If 'key' is in the hashmap, deletes its entry and returns 1. - Otherwise returns 0. - - Function interface (actually macros) for strings only: - - sh_new_strdup - void sh_new_strdup(T*); - Overwrites the existing pointer with a newly allocated - string hashmap which will automatically allocate and free - each string key using realloc/free - - sh_new_arena - void sh_new_arena(T*); - Overwrites the existing pointer with a newly allocated - string hashmap which will automatically allocate each string - key to a string arena. Every string key ever used by this - hash table remains in the arena until the arena is freed. - Additionally, any key which is deleted and reinserted will - be allocated multiple times in the string arena. - -NOTES - - * These data structures are realloc'd when they grow, and the macro - "functions" write to the provided pointer. This means: (a) the pointer - must be an lvalue, and (b) the pointer to the data structure is not - stable, and you must maintain it the same as you would a realloc'd - pointer. For example, if you pass a pointer to a dynamic array to a - function which updates it, the function must return back the new - pointer to the caller. This is the price of trying to do this in C. - - * The following are the only functions that are thread-safe on a single data - structure, i.e. can be run in multiple threads simultaneously on the same - data structure - hmlen shlen - hmlenu shlenu - hmget_ts shget_ts - hmgeti_ts shgeti_ts - hmgets_ts shgets_ts - - * You iterate over the contents of a dynamic array and a hashmap in exactly - the same way, using arrlen/hmlen/shlen: - - for (i=0; i < arrlen(foo); ++i) - ... foo[i] ... - - * All operations except arrins/arrdel are O(1) amortized, but individual - operations can be slow, so these data structures may not be suitable - for real time use. Dynamic arrays double in capacity as needed, so - elements are copied an average of once. Hash tables double/halve - their size as needed, with appropriate hysteresis to maintain O(1) - performance. - -NOTES - DYNAMIC ARRAY - - * If you know how long a dynamic array is going to be in advance, you can avoid - extra memory allocations by using arrsetlen to allocate it to that length in - advance and use foo[n] while filling it out, or arrsetcap to allocate the memory - for that length and use arrput/arrpush as normal. - - * Unlike some other versions of the dynamic array, this version should - be safe to use with strict-aliasing optimizations. - -NOTES - HASH MAP - - * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel - and variants, the key must be an lvalue (so the macro can take the address of it). - Extensions are used that eliminate this requirement if you're using C99 and later - in GCC or clang, or if you're using C++ in GCC. But note that this can make your - code less portable. - - * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. - - * The iteration order of your data in the hashmap is determined solely by the - order of insertions and deletions. In particular, if you never delete, new - keys are always added at the end of the array. This will be consistent - across all platforms and versions of the library. However, you should not - attempt to serialize the internal hash table, as the hash is not consistent - between different platforms, and may change with future versions of the library. - - * Use sh_new_arena() for string hashmaps that you never delete from. Initialize - with NULL if you're managing the memory for your strings, or your strings are - never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). - @TODO: make an arena variant that garbage collects the strings with a trivial - copy collector into a new arena whenever the table shrinks / rebuilds. Since - current arena recommendation is to only use arena if it never deletes, then - this can just replace current arena implementation. - - * If adversarial input is a serious concern and you're on a 64-bit platform, - enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass - a strong random number to stbds_rand_seed. - - * The default value for the hash table is stored in foo[-1], so if you - use code like 'hmget(T,k)->value = 5' you can accidentally overwrite - the value stored by hmdefault if 'k' is not present. - -CREDITS - - Sean Barrett -- library, idea for dynamic array API/implementation - Per Vognsen -- idea for hash table API/implementation - Rafael Sachetto -- arrpop() - github:HeroicKatora -- arraddn() reworking - - Bugfixes: - Andy Durdin - Shane Liesegang - Vinh Truong - Andreas Molzer - github:hashitaku - github:srdjanstipic - Macoy Madson - Andreas Vennstrom - Tobias Mansfield-Williams -*/ - -#ifdef STBDS_UNIT_TESTS -#define _CRT_SECURE_NO_WARNINGS -#endif - -#ifndef INCLUDE_STB_DS_H -#define INCLUDE_STB_DS_H - -#include -#include - -#ifndef STBDS_NO_SHORT_NAMES -#define arrlen stbds_arrlen -#define arrlenu stbds_arrlenu -#define arrput stbds_arrput -#define arrpush stbds_arrput -#define arrpop stbds_arrpop -#define arrfree stbds_arrfree -#define arraddn stbds_arraddn // deprecated, use one of the following instead: -#define arraddnptr stbds_arraddnptr -#define arraddnindex stbds_arraddnindex -#define arrsetlen stbds_arrsetlen -#define arrlast stbds_arrlast -#define arrins stbds_arrins -#define arrinsn stbds_arrinsn -#define arrdel stbds_arrdel -#define arrdeln stbds_arrdeln -#define arrdelswap stbds_arrdelswap -#define arrcap stbds_arrcap -#define arrsetcap stbds_arrsetcap - -#define hmput stbds_hmput -#define hmputs stbds_hmputs -#define hmget stbds_hmget -#define hmget_ts stbds_hmget_ts -#define hmgets stbds_hmgets -#define hmgetp stbds_hmgetp -#define hmgetp_ts stbds_hmgetp_ts -#define hmgetp_null stbds_hmgetp_null -#define hmgeti stbds_hmgeti -#define hmgeti_ts stbds_hmgeti_ts -#define hmdel stbds_hmdel -#define hmlen stbds_hmlen -#define hmlenu stbds_hmlenu -#define hmfree stbds_hmfree -#define hmdefault stbds_hmdefault -#define hmdefaults stbds_hmdefaults - -#define shput stbds_shput -#define shputi stbds_shputi -#define shputs stbds_shputs -#define shget stbds_shget -#define shgeti stbds_shgeti -#define shgets stbds_shgets -#define shgetp stbds_shgetp -#define shgetp_null stbds_shgetp_null -#define shdel stbds_shdel -#define shlen stbds_shlen -#define shlenu stbds_shlenu -#define shfree stbds_shfree -#define shdefault stbds_shdefault -#define shdefaults stbds_shdefaults -#define sh_new_arena stbds_sh_new_arena -#define sh_new_strdup stbds_sh_new_strdup - -#define stralloc stbds_stralloc -#define strreset stbds_strreset -#endif - -#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) -#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." -#endif -#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) -#include -#define STBDS_REALLOC(c,p,s) realloc(p,s) -#define STBDS_FREE(c,p) free(p) -#endif - -#ifdef _MSC_VER -#define STBDS_NOTUSED(v) (void)(v) -#else -#define STBDS_NOTUSED(v) (void)sizeof(v) -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -// for security against attackers, seed the library with a random number, at least time() but stronger is better -extern void stbds_rand_seed(size_t seed); - -// these are the hash functions used internally if you want to test them or use them for other purposes -extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); -extern size_t stbds_hash_string(char *str, size_t seed); - -// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. -typedef struct stbds_string_arena stbds_string_arena; -extern char * stbds_stralloc(stbds_string_arena *a, char *str); -extern void stbds_strreset(stbds_string_arena *a); - -// have to #define STBDS_UNIT_TESTS to call this -extern void stbds_unit_tests(void); - -/////////////// -// -// Everything below here is implementation details -// - -extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); -extern void stbds_arrfreef(void *a); -extern void stbds_hmfree_func(void *p, size_t elemsize); -extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); -extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); -extern void * stbds_hmput_default(void *a, size_t elemsize); -extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); -extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); -extern void * stbds_shmode_func(size_t elemsize, int mode); - -#ifdef __cplusplus -} -#endif - -#if defined(__GNUC__) || defined(__clang__) -#define STBDS_HAS_TYPEOF -#ifdef __cplusplus -//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang -#endif -#endif - -#if !defined(__cplusplus) -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L -#define STBDS_HAS_LITERAL_ARRAY -#endif -#endif - -// this macro takes the address of the argument, but on gcc/clang can accept rvalues -#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) - #if __clang__ - #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value - #else - #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value - #endif -#else -#define STBDS_ADDRESSOF(typevar, value) &(value) -#endif - -#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) - -#define stbds_header(t) ((stbds_array_header *) (t) - 1) -#define stbds_temp(t) stbds_header(t)->temp -#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) - -#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) -#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) -#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) -#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) -#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) -#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) -#define stbds_arrpush stbds_arrput // synonym -#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) -#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: -#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) -#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) -#define stbds_arraddnoff stbds_arraddnindex -#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) -#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) -#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) -#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) -#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) -#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) -#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) - -#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ - ? (stbds_arrgrow(a,n,0),0) : 0) - -#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) - -#define stbds_hmput(t, k, v) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ - (t)[stbds_temp((t)-1)].key = (k), \ - (t)[stbds_temp((t)-1)].value = (v)) - -#define stbds_hmputs(t, s) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ - (t)[stbds_temp((t)-1)] = (s)) - -#define stbds_hmgeti(t,k) \ - ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ - stbds_temp((t)-1)) - -#define stbds_hmgeti_ts(t,k,temp) \ - ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ - (temp)) - -#define stbds_hmgetp(t, k) \ - ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) - -#define stbds_hmgetp_ts(t, k, temp) \ - ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) - -#define stbds_hmdel(t,k) \ - (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) - -#define stbds_hmdefault(t, v) \ - ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) - -#define stbds_hmdefaults(t, s) \ - ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) - -#define stbds_hmfree(p) \ - ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) - -#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) -#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) -#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) -#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) -#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) -#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) - -#define stbds_shput(t, k, v) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ - (t)[stbds_temp((t)-1)].value = (v)) - -#define stbds_shputi(t, k, v) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ - (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) - -#define stbds_shputs(t, s) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ - (t)[stbds_temp((t)-1)] = (s), \ - (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally - -#define stbds_pshput(t, p) \ - ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ - (t)[stbds_temp((t)-1)] = (p)) - -#define stbds_shgeti(t,k) \ - ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ - stbds_temp((t)-1)) - -#define stbds_pshgeti(t,k) \ - ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ - stbds_temp((t)-1)) - -#define stbds_shgetp(t, k) \ - ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) - -#define stbds_pshget(t, k) \ - ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) - -#define stbds_shdel(t,k) \ - (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) -#define stbds_pshdel(t,k) \ - (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) - -#define stbds_sh_new_arena(t) \ - ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) -#define stbds_sh_new_strdup(t) \ - ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) - -#define stbds_shdefault(t, v) stbds_hmdefault(t,v) -#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) - -#define stbds_shfree stbds_hmfree -#define stbds_shlenu stbds_hmlenu - -#define stbds_shgets(t, k) (*stbds_shgetp(t,k)) -#define stbds_shget(t, k) (stbds_shgetp(t,k)->value) -#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) -#define stbds_shlen stbds_hmlen - -typedef struct -{ - size_t length; - size_t capacity; - void * hash_table; - ptrdiff_t temp; -} stbds_array_header; - -typedef struct stbds_string_block -{ - struct stbds_string_block *next; - char storage[8]; -} stbds_string_block; - -struct stbds_string_arena -{ - stbds_string_block *storage; - size_t remaining; - unsigned char block; - unsigned char mode; // this isn't used by the string arena itself -}; - -#define STBDS_HM_BINARY 0 -#define STBDS_HM_STRING 1 - -enum -{ - STBDS_SH_NONE, - STBDS_SH_DEFAULT, - STBDS_SH_STRDUP, - STBDS_SH_ARENA -}; - -#ifdef __cplusplus -// in C we use implicit assignment from these void*-returning functions to T*. -// in C++ these templates make the same code work -template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { - return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); -} -template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { - return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); -} -template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { - return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); -} -template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { - return (T*)stbds_hmput_default((void *)a, elemsize); -} -template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { - return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); -} -template static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ - return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); -} -template static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { - return (T*)stbds_shmode_func(elemsize, mode); -} -#else -#define stbds_arrgrowf_wrapper stbds_arrgrowf -#define stbds_hmget_key_wrapper stbds_hmget_key -#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts -#define stbds_hmput_default_wrapper stbds_hmput_default -#define stbds_hmput_key_wrapper stbds_hmput_key -#define stbds_hmdel_key_wrapper stbds_hmdel_key -#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) -#endif - -#endif // INCLUDE_STB_DS_H - - -////////////////////////////////////////////////////////////////////////////// -// -// IMPLEMENTATION -// - -#ifdef STB_DS_IMPLEMENTATION -#include -#include - -#ifndef STBDS_ASSERT -#define STBDS_ASSERT_WAS_UNDEFINED -#define STBDS_ASSERT(x) ((void) 0) -#endif - -#ifdef STBDS_STATISTICS -#define STBDS_STATS(x) x -size_t stbds_array_grow; -size_t stbds_hash_grow; -size_t stbds_hash_shrink; -size_t stbds_hash_rebuild; -size_t stbds_hash_probes; -size_t stbds_hash_alloc; -size_t stbds_rehash_probes; -size_t stbds_rehash_items; -#else -#define STBDS_STATS(x) -#endif - -// -// stbds_arr implementation -// - -//int *prev_allocs[65536]; -//int num_prev; - -void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) -{ - stbds_array_header temp={0}; // force debugging - void *b; - size_t min_len = stbds_arrlen(a) + addlen; - (void) sizeof(temp); - - // compute the minimum capacity needed - if (min_len > min_cap) - min_cap = min_len; - - if (min_cap <= stbds_arrcap(a)) - return a; - - // increase needed capacity to guarantee O(1) amortized - if (min_cap < 2 * stbds_arrcap(a)) - min_cap = 2 * stbds_arrcap(a); - else if (min_cap < 4) - min_cap = 4; - - //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); - //if (num_prev == 2201) - // num_prev = num_prev; - b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); - //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; - b = (char *) b + sizeof(stbds_array_header); - if (a == NULL) { - stbds_header(b)->length = 0; - stbds_header(b)->hash_table = 0; - stbds_header(b)->temp = 0; - } else { - STBDS_STATS(++stbds_array_grow); - } - stbds_header(b)->capacity = min_cap; - - return b; -} - -void stbds_arrfreef(void *a) -{ - STBDS_FREE(NULL, stbds_header(a)); -} - -// -// stbds_hm hash table implementation -// - -#ifdef STBDS_INTERNAL_SMALL_BUCKET -#define STBDS_BUCKET_LENGTH 4 -#else -#define STBDS_BUCKET_LENGTH 8 -#endif - -#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) -#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) -#define STBDS_CACHE_LINE_SIZE 64 - -#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) - -typedef struct -{ - size_t hash [STBDS_BUCKET_LENGTH]; - ptrdiff_t index[STBDS_BUCKET_LENGTH]; -} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line - -typedef struct -{ - char * temp_key; // this MUST be the first field of the hash table - size_t slot_count; - size_t used_count; - size_t used_count_threshold; - size_t used_count_shrink_threshold; - size_t tombstone_count; - size_t tombstone_count_threshold; - size_t seed; - size_t slot_count_log2; - stbds_string_arena string; - stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct -} stbds_hash_index; - -#define STBDS_INDEX_EMPTY -1 -#define STBDS_INDEX_DELETED -2 -#define STBDS_INDEX_IN_USE(x) ((x) >= 0) - -#define STBDS_HASH_EMPTY 0 -#define STBDS_HASH_DELETED 1 - -static size_t stbds_hash_seed=0x31415926; - -void stbds_rand_seed(size_t seed) -{ - stbds_hash_seed = seed; -} - -#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ - temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ - var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ - var ^= temp ^ v32 - -#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) - -static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) -{ - size_t pos; - STBDS_NOTUSED(slot_log2); - pos = hash & (slot_count-1); - #ifdef STBDS_INTERNAL_BUCKET_START - pos &= ~STBDS_BUCKET_MASK; - #endif - return pos; -} - -static size_t stbds_log2(size_t slot_count) -{ - size_t n=0; - while (slot_count > 1) { - slot_count >>= 1; - ++n; - } - return n; -} - -static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) -{ - stbds_hash_index *t; - t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); - t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); - t->slot_count = slot_count; - t->slot_count_log2 = stbds_log2(slot_count); - t->tombstone_count = 0; - t->used_count = 0; - - #if 0 // A1 - t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow - t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild - t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink - #elif 1 // A2 - //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow - //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild - //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink - - // compute without overflowing - t->used_count_threshold = slot_count - (slot_count>>2); - t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); - t->used_count_shrink_threshold = slot_count >> 2; - - #elif 0 // B1 - t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow - t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild - t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink - #else // C1 - t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow - t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild - t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink - #endif - // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 - // Note that the larger tables have high variance as they were run fewer times - // A1 A2 B1 C1 - // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table - // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table - // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table - // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table - // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table - // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table - // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table - // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table - // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table - // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table - // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table - - if (slot_count <= STBDS_BUCKET_LENGTH) - t->used_count_shrink_threshold = 0; - // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes - STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); - STBDS_STATS(++stbds_hash_alloc); - if (ot) { - t->string = ot->string; - // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing - t->seed = ot->seed; - } else { - size_t a,b,temp; - memset(&t->string, 0, sizeof(t->string)); - t->seed = stbds_hash_seed; - // LCG - // in 32-bit, a = 2147001325 b = 715136305 - // in 64-bit, a = 2862933555777941757 b = 3037000493 - stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); - stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); - stbds_hash_seed = stbds_hash_seed * a + b; - } - - { - size_t i,j; - for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { - stbds_hash_bucket *b = &t->storage[i]; - for (j=0; j < STBDS_BUCKET_LENGTH; ++j) - b->hash[j] = STBDS_HASH_EMPTY; - for (j=0; j < STBDS_BUCKET_LENGTH; ++j) - b->index[j] = STBDS_INDEX_EMPTY; - } - } - - // copy out the old data, if any - if (ot) { - size_t i,j; - t->used_count = ot->used_count; - for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { - stbds_hash_bucket *ob = &ot->storage[i]; - for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { - if (STBDS_INDEX_IN_USE(ob->index[j])) { - size_t hash = ob->hash[j]; - size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); - size_t step = STBDS_BUCKET_LENGTH; - STBDS_STATS(++stbds_rehash_items); - for (;;) { - size_t limit,z; - stbds_hash_bucket *bucket; - bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; - STBDS_STATS(++stbds_rehash_probes); - - for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { - if (bucket->hash[z] == 0) { - bucket->hash[z] = hash; - bucket->index[z] = ob->index[j]; - goto done; - } - } - - limit = pos & STBDS_BUCKET_MASK; - for (z = 0; z < limit; ++z) { - if (bucket->hash[z] == 0) { - bucket->hash[z] = hash; - bucket->index[z] = ob->index[j]; - goto done; - } - } - - pos += step; // quadratic probing - step += STBDS_BUCKET_LENGTH; - pos &= (t->slot_count-1); - } - } - done: - ; - } - } - } - - return t; -} - -#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) -#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) - -size_t stbds_hash_string(char *str, size_t seed) -{ - size_t hash = seed; - while (*str) - hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; - - // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits - hash ^= seed; - hash = (~hash) + (hash << 18); - hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); - hash = hash * 21; - hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); - hash += (hash << 6); - hash ^= STBDS_ROTATE_RIGHT(hash,22); - return hash+seed; -} - -#ifdef STBDS_SIPHASH_2_4 -#define STBDS_SIPHASH_C_ROUNDS 2 -#define STBDS_SIPHASH_D_ROUNDS 4 -typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; -#endif - -#ifndef STBDS_SIPHASH_C_ROUNDS -#define STBDS_SIPHASH_C_ROUNDS 1 -#endif -#ifndef STBDS_SIPHASH_D_ROUNDS -#define STBDS_SIPHASH_D_ROUNDS 1 -#endif - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== -#endif - -static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) -{ - unsigned char *d = (unsigned char *) p; - size_t i,j; - size_t v0,v1,v2,v3, data; - - // hash that works on 32- or 64-bit registers without knowing which we have - // (computes different results on 32-bit and 64-bit platform) - // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit - v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; - v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; - v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; - v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; - - #ifdef STBDS_TEST_SIPHASH_2_4 - // hardcoded with key material in the siphash test vectors - v0 ^= 0x0706050403020100ull ^ seed; - v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; - v2 ^= 0x0706050403020100ull ^ seed; - v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; - #endif - - #define STBDS_SIPROUND() \ - do { \ - v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ - v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ - v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ - v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ - } while (0) - - for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { - data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); - data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 - - v3 ^= data; - for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) - STBDS_SIPROUND(); - v0 ^= data; - } - data = len << (STBDS_SIZE_T_BITS-8); - switch (len - i) { - case 7: data |= ((size_t) d[6] << 24) << 24; // fall through - case 6: data |= ((size_t) d[5] << 20) << 20; // fall through - case 5: data |= ((size_t) d[4] << 16) << 16; // fall through - case 4: data |= (d[3] << 24); // fall through - case 3: data |= (d[2] << 16); // fall through - case 2: data |= (d[1] << 8); // fall through - case 1: data |= d[0]; // fall through - case 0: break; - } - v3 ^= data; - for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) - STBDS_SIPROUND(); - v0 ^= data; - v2 ^= 0xff; - for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) - STBDS_SIPROUND(); - -#ifdef STBDS_SIPHASH_2_4 - return v0^v1^v2^v3; -#else - return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply -#endif -} - -size_t stbds_hash_bytes(void *p, size_t len, size_t seed) -{ -#ifdef STBDS_SIPHASH_2_4 - return stbds_siphash_bytes(p,len,seed); -#else - unsigned char *d = (unsigned char *) p; - - if (len == 4) { - unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); - #if 0 - // HASH32-A Bob Jenkin's hash function w/o large constants - hash ^= seed; - hash -= (hash<<6); - hash ^= (hash>>17); - hash -= (hash<<9); - hash ^= seed; - hash ^= (hash<<4); - hash -= (hash<<3); - hash ^= (hash<<10); - hash ^= (hash>>15); - #elif 1 - // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. - // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm - // not really sure what's going on. - hash ^= seed; - hash = (hash ^ 61) ^ (hash >> 16); - hash = hash + (hash << 3); - hash = hash ^ (hash >> 4); - hash = hash * 0x27d4eb2d; - hash ^= seed; - hash = hash ^ (hash >> 15); - #else // HASH32-C - Murmur3 - hash ^= seed; - hash *= 0xcc9e2d51; - hash = (hash << 17) | (hash >> 15); - hash *= 0x1b873593; - hash ^= seed; - hash = (hash << 19) | (hash >> 13); - hash = hash*5 + 0xe6546b64; - hash ^= hash >> 16; - hash *= 0x85ebca6b; - hash ^= seed; - hash ^= hash >> 13; - hash *= 0xc2b2ae35; - hash ^= hash >> 16; - #endif - // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 - // Note that the larger tables have high variance as they were run fewer times - // HASH32-A // HASH32-BB // HASH32-C - // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table - // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table - // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table - // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table - // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table - // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table - // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table - // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table - // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table - // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table - // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table - // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing - - return (((size_t) hash << 16 << 16) | hash) ^ seed; - } else if (len == 8 && sizeof(size_t) == 8) { - size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); - hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 - hash ^= seed; - hash = (~hash) + (hash << 21); - hash ^= STBDS_ROTATE_RIGHT(hash,24); - hash *= 265; - hash ^= STBDS_ROTATE_RIGHT(hash,14); - hash ^= seed; - hash *= 21; - hash ^= STBDS_ROTATE_RIGHT(hash,28); - hash += (hash << 31); - hash = (~hash) + (hash << 18); - return hash; - } else { - return stbds_siphash_bytes(p,len,seed); - } -#endif -} -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - -static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) -{ - if (mode >= STBDS_HM_STRING) - return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); - else - return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); -} - -#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) -#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) - -#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) - -void stbds_hmfree_func(void *a, size_t elemsize) -{ - if (a == NULL) return; - if (stbds_hash_table(a) != NULL) { - if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { - size_t i; - // skip 0th element, which is default - for (i=1; i < stbds_header(a)->length; ++i) - STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); - } - stbds_strreset(&stbds_hash_table(a)->string); - } - STBDS_FREE(NULL, stbds_header(a)->hash_table); - STBDS_FREE(NULL, stbds_header(a)); -} - -static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) -{ - void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); - stbds_hash_index *table = stbds_hash_table(raw_a); - size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); - size_t step = STBDS_BUCKET_LENGTH; - size_t limit,i; - size_t pos; - stbds_hash_bucket *bucket; - - if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots - - pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); - - for (;;) { - STBDS_STATS(++stbds_hash_probes); - bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; - - // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache - for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { - if (bucket->hash[i] == hash) { - if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { - return (pos & ~STBDS_BUCKET_MASK)+i; - } - } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { - return -1; - } - } - - // search from beginning of bucket to pos - limit = pos & STBDS_BUCKET_MASK; - for (i = 0; i < limit; ++i) { - if (bucket->hash[i] == hash) { - if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { - return (pos & ~STBDS_BUCKET_MASK)+i; - } - } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { - return -1; - } - } - - // quadratic probing - pos += step; - step += STBDS_BUCKET_LENGTH; - pos &= (table->slot_count-1); - } - /* NOTREACHED */ -} - -void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) -{ - size_t keyoffset = 0; - if (a == NULL) { - // make it non-empty so we can return a temp - a = stbds_arrgrowf(0, elemsize, 0, 1); - stbds_header(a)->length += 1; - memset(a, 0, elemsize); - *temp = STBDS_INDEX_EMPTY; - // adjust a to point after the default element - return STBDS_ARR_TO_HASH(a,elemsize); - } else { - stbds_hash_index *table; - void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); - // adjust a to point to the default element - table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; - if (table == 0) { - *temp = -1; - } else { - ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); - if (slot < 0) { - *temp = STBDS_INDEX_EMPTY; - } else { - stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; - *temp = b->index[slot & STBDS_BUCKET_MASK]; - } - } - return a; - } -} - -void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) -{ - ptrdiff_t temp; - void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); - stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; - return p; -} - -void * stbds_hmput_default(void *a, size_t elemsize) -{ - // three cases: - // a is NULL <- allocate - // a has a hash table but no entries, because of shmode <- grow - // a has entries <- do nothing - if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { - a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); - stbds_header(a)->length += 1; - memset(a, 0, elemsize); - a=STBDS_ARR_TO_HASH(a,elemsize); - } - return a; -} - -static char *stbds_strdup(char *str); - -void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) -{ - size_t keyoffset=0; - void *raw_a; - stbds_hash_index *table; - - if (a == NULL) { - a = stbds_arrgrowf(0, elemsize, 0, 1); - memset(a, 0, elemsize); - stbds_header(a)->length += 1; - // adjust a to point AFTER the default element - a = STBDS_ARR_TO_HASH(a,elemsize); - } - - // adjust a to point to the default element - raw_a = a; - a = STBDS_HASH_TO_ARR(a,elemsize); - - table = (stbds_hash_index *) stbds_header(a)->hash_table; - - if (table == NULL || table->used_count >= table->used_count_threshold) { - stbds_hash_index *nt; - size_t slot_count; - - slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; - nt = stbds_make_hash_index(slot_count, table); - if (table) - STBDS_FREE(NULL, table); - else - nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; - stbds_header(a)->hash_table = table = nt; - STBDS_STATS(++stbds_hash_grow); - } - - // we iterate hash table explicitly because we want to track if we saw a tombstone - { - size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); - size_t step = STBDS_BUCKET_LENGTH; - size_t pos; - ptrdiff_t tombstone = -1; - stbds_hash_bucket *bucket; - - // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly - if (hash < 2) hash += 2; - - pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); - - for (;;) { - size_t limit, i; - STBDS_STATS(++stbds_hash_probes); - bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; - - // start searching from pos to end of bucket - for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { - if (bucket->hash[i] == hash) { - if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { - stbds_temp(a) = bucket->index[i]; - if (mode >= STBDS_HM_STRING) - stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); - return STBDS_ARR_TO_HASH(a,elemsize); - } - } else if (bucket->hash[i] == 0) { - pos = (pos & ~STBDS_BUCKET_MASK) + i; - goto found_empty_slot; - } else if (tombstone < 0) { - if (bucket->index[i] == STBDS_INDEX_DELETED) - tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); - } - } - - // search from beginning of bucket to pos - limit = pos & STBDS_BUCKET_MASK; - for (i = 0; i < limit; ++i) { - if (bucket->hash[i] == hash) { - if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { - stbds_temp(a) = bucket->index[i]; - return STBDS_ARR_TO_HASH(a,elemsize); - } - } else if (bucket->hash[i] == 0) { - pos = (pos & ~STBDS_BUCKET_MASK) + i; - goto found_empty_slot; - } else if (tombstone < 0) { - if (bucket->index[i] == STBDS_INDEX_DELETED) - tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); - } - } - - // quadratic probing - pos += step; - step += STBDS_BUCKET_LENGTH; - pos &= (table->slot_count-1); - } - found_empty_slot: - if (tombstone >= 0) { - pos = tombstone; - --table->tombstone_count; - } - ++table->used_count; - - { - ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); - // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type - if ((size_t) i+1 > stbds_arrcap(a)) - *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); - raw_a = STBDS_ARR_TO_HASH(a,elemsize); - - STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); - stbds_header(a)->length = i+1; - bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; - bucket->hash[pos & STBDS_BUCKET_MASK] = hash; - bucket->index[pos & STBDS_BUCKET_MASK] = i-1; - stbds_temp(a) = i-1; - - switch (table->string.mode) { - case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; - case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; - case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; - default: memcpy((char *) a + elemsize*i, key, keysize); break; - } - } - return STBDS_ARR_TO_HASH(a,elemsize); - } -} - -void * stbds_shmode_func(size_t elemsize, int mode) -{ - void *a = stbds_arrgrowf(0, elemsize, 0, 1); - stbds_hash_index *h; - memset(a, 0, elemsize); - stbds_header(a)->length = 1; - stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); - h->string.mode = (unsigned char) mode; - return STBDS_ARR_TO_HASH(a,elemsize); -} - -void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) -{ - if (a == NULL) { - return 0; - } else { - stbds_hash_index *table; - void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); - table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; - stbds_temp(raw_a) = 0; - if (table == 0) { - return a; - } else { - ptrdiff_t slot; - slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); - if (slot < 0) - return a; - else { - stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; - int i = slot & STBDS_BUCKET_MASK; - ptrdiff_t old_index = b->index[i]; - ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' - STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); - --table->used_count; - ++table->tombstone_count; - stbds_temp(raw_a) = 1; - STBDS_ASSERT(table->used_count >= 0); - //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); - b->hash[i] = STBDS_HASH_DELETED; - b->index[i] = STBDS_INDEX_DELETED; - - if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) - STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); - - // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip - if (old_index != final_index) { - // swap delete - memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); - - // now find the slot for the last element - if (mode == STBDS_HM_STRING) - slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); - else - slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); - STBDS_ASSERT(slot >= 0); - b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; - i = slot & STBDS_BUCKET_MASK; - STBDS_ASSERT(b->index[i] == final_index); - b->index[i] = old_index; - } - stbds_header(raw_a)->length -= 1; - - if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { - stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); - STBDS_FREE(NULL, table); - STBDS_STATS(++stbds_hash_shrink); - } else if (table->tombstone_count > table->tombstone_count_threshold) { - stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); - STBDS_FREE(NULL, table); - STBDS_STATS(++stbds_hash_rebuild); - } - - return a; - } - } - } - /* NOTREACHED */ -} - -static char *stbds_strdup(char *str) -{ - // to keep replaceable allocator simple, we don't want to use strdup. - // rolling our own also avoids problem of strdup vs _strdup - size_t len = strlen(str)+1; - char *p = (char*) STBDS_REALLOC(NULL, 0, len); - memmove(p, str, len); - return p; -} - -#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN -#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u -#endif -#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX -#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) -#endif - -char *stbds_stralloc(stbds_string_arena *a, char *str) -{ - char *p; - size_t len = strlen(str)+1; - if (len > a->remaining) { - // compute the next blocksize - size_t blocksize = a->block; - - // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that - // there are log(SIZE) allocations to free when we destroy the table - blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); - - // if size is under 1M, advance to next blocktype - if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) - ++a->block; - - if (len > blocksize) { - // if string is larger than blocksize, then just allocate the full size. - // note that we still advance string_block so block size will continue - // increasing, so e.g. if somebody only calls this with 1000-long strings, - // eventually the arena will start doubling and handling those as well - stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); - memmove(sb->storage, str, len); - if (a->storage) { - // insert it after the first element, so that we don't waste the space there - sb->next = a->storage->next; - a->storage->next = sb; - } else { - sb->next = 0; - a->storage = sb; - a->remaining = 0; // this is redundant, but good for clarity - } - return sb->storage; - } else { - stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); - sb->next = a->storage; - a->storage = sb; - a->remaining = blocksize; - } - } - - STBDS_ASSERT(len <= a->remaining); - p = a->storage->storage + a->remaining - len; - a->remaining -= len; - memmove(p, str, len); - return p; -} - -void stbds_strreset(stbds_string_arena *a) -{ - stbds_string_block *x,*y; - x = a->storage; - while (x) { - y = x->next; - STBDS_FREE(NULL, x); - x = y; - } - memset(a, 0, sizeof(*a)); -} - -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// UNIT TESTS -// - -#ifdef STBDS_UNIT_TESTS -#include -#ifdef STBDS_ASSERT_WAS_UNDEFINED -#undef STBDS_ASSERT -#endif -#ifndef STBDS_ASSERT -#define STBDS_ASSERT assert -#include -#endif - -typedef struct { int key,b,c,d; } stbds_struct; -typedef struct { int key[2],b,c,d; } stbds_struct2; - -static char buffer[256]; -char *strkey(int n) -{ -#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) - sprintf_s(buffer, sizeof(buffer), "test_%d", n); -#else - sprintf(buffer, "test_%d", n); -#endif - return buffer; -} - -void stbds_unit_tests(void) -{ -#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) - // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! - STBDS_ASSERT(0); -#else - const int testsize = 100000; - const int testsize2 = testsize/20; - int *arr=NULL; - struct { int key; int value; } *intmap = NULL; - struct { char *key; int value; } *strmap = NULL, s; - struct { stbds_struct key; int value; } *map = NULL; - stbds_struct *map2 = NULL; - stbds_struct2 *map3 = NULL; - stbds_string_arena sa = { 0 }; - int key3[2] = { 1,2 }; - ptrdiff_t temp; - - int i,j; - - STBDS_ASSERT(arrlen(arr)==0); - for (i=0; i < 20000; i += 50) { - for (j=0; j < i; ++j) - arrpush(arr,j); - arrfree(arr); - } - - for (i=0; i < 4; ++i) { - arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); - arrdel(arr,i); - arrfree(arr); - arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); - arrdelswap(arr,i); - arrfree(arr); - } - - for (i=0; i < 5; ++i) { - arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); - stbds_arrins(arr,i,5); - STBDS_ASSERT(arr[i] == 5); - if (i < 4) - STBDS_ASSERT(arr[4] == 4); - arrfree(arr); - } - - i = 1; - STBDS_ASSERT(hmgeti(intmap,i) == -1); - hmdefault(intmap, -2); - STBDS_ASSERT(hmgeti(intmap, i) == -1); - STBDS_ASSERT(hmget (intmap, i) == -2); - for (i=0; i < testsize; i+=2) - hmput(intmap, i, i*5); - for (i=0; i < testsize; i+=1) { - if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); - else STBDS_ASSERT(hmget(intmap, i) == i*5); - if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); - else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); - } - for (i=0; i < testsize; i+=2) - hmput(intmap, i, i*3); - for (i=0; i < testsize; i+=1) - if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); - else STBDS_ASSERT(hmget(intmap, i) == i*3); - for (i=2; i < testsize; i+=4) - hmdel(intmap, i); // delete half the entries - for (i=0; i < testsize; i+=1) - if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); - else STBDS_ASSERT(hmget(intmap, i) == i*3); - for (i=0; i < testsize; i+=1) - hmdel(intmap, i); // delete the rest of the entries - for (i=0; i < testsize; i+=1) - STBDS_ASSERT(hmget(intmap, i) == -2 ); - hmfree(intmap); - for (i=0; i < testsize; i+=2) - hmput(intmap, i, i*3); - hmfree(intmap); - - #if defined(__clang__) || defined(__GNUC__) - #ifndef __cplusplus - intmap = NULL; - hmput(intmap, 15, 7); - hmput(intmap, 11, 3); - hmput(intmap, 9, 5); - STBDS_ASSERT(hmget(intmap, 9) == 5); - STBDS_ASSERT(hmget(intmap, 11) == 3); - STBDS_ASSERT(hmget(intmap, 15) == 7); - #endif - #endif - - for (i=0; i < testsize; ++i) - stralloc(&sa, strkey(i)); - strreset(&sa); - - { - s.key = "a", s.value = 1; - shputs(strmap, s); - STBDS_ASSERT(*strmap[0].key == 'a'); - STBDS_ASSERT(strmap[0].key == s.key); - STBDS_ASSERT(strmap[0].value == s.value); - shfree(strmap); - } - - { - s.key = "a", s.value = 1; - sh_new_strdup(strmap); - shputs(strmap, s); - STBDS_ASSERT(*strmap[0].key == 'a'); - STBDS_ASSERT(strmap[0].key != s.key); - STBDS_ASSERT(strmap[0].value == s.value); - shfree(strmap); - } - - { - s.key = "a", s.value = 1; - sh_new_arena(strmap); - shputs(strmap, s); - STBDS_ASSERT(*strmap[0].key == 'a'); - STBDS_ASSERT(strmap[0].key != s.key); - STBDS_ASSERT(strmap[0].value == s.value); - shfree(strmap); - } - - for (j=0; j < 2; ++j) { - STBDS_ASSERT(shgeti(strmap,"foo") == -1); - if (j == 0) - sh_new_strdup(strmap); - else - sh_new_arena(strmap); - STBDS_ASSERT(shgeti(strmap,"foo") == -1); - shdefault(strmap, -2); - STBDS_ASSERT(shgeti(strmap,"foo") == -1); - for (i=0; i < testsize; i+=2) - shput(strmap, strkey(i), i*3); - for (i=0; i < testsize; i+=1) - if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); - else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); - for (i=2; i < testsize; i+=4) - shdel(strmap, strkey(i)); // delete half the entries - for (i=0; i < testsize; i+=1) - if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); - else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); - for (i=0; i < testsize; i+=1) - shdel(strmap, strkey(i)); // delete the rest of the entries - for (i=0; i < testsize; i+=1) - STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); - shfree(strmap); - } - - { - struct { char *key; char value; } *hash = NULL; - char name[4] = "jen"; - shput(hash, "bob" , 'h'); - shput(hash, "sally" , 'e'); - shput(hash, "fred" , 'l'); - shput(hash, "jen" , 'x'); - shput(hash, "doug" , 'o'); - - shput(hash, name , 'l'); - shfree(hash); - } - - for (i=0; i < testsize; i += 2) { - stbds_struct s = { i,i*2,i*3,i*4 }; - hmput(map, s, i*5); - } - - for (i=0; i < testsize; i += 1) { - stbds_struct s = { i,i*2,i*3 ,i*4 }; - stbds_struct t = { i,i*2,i*3+1,i*4 }; - if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); - else STBDS_ASSERT(hmget(map, s) == i*5); - if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); - else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); - //STBDS_ASSERT(hmget(map, t.key) == 0); - } - - for (i=0; i < testsize; i += 2) { - stbds_struct s = { i,i*2,i*3,i*4 }; - hmputs(map2, s); - } - hmfree(map); - - for (i=0; i < testsize; i += 1) { - stbds_struct s = { i,i*2,i*3,i*4 }; - stbds_struct t = { i,i*2,i*3+1,i*4 }; - if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); - else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); - //STBDS_ASSERT(hmgetp(map2, t.key) == 0); - } - hmfree(map2); - - for (i=0; i < testsize; i += 2) { - stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; - hmputs(map3, s); - } - for (i=0; i < testsize; i += 1) { - stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; - stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; - if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); - else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); - //STBDS_ASSERT(hmgetp(map3, t.key) == 0); - } -#endif -} -#endif - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2019 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/include/types.h b/include/types.h deleted file mode 100644 index 5c2b5e69..00000000 --- a/include/types.h +++ /dev/null @@ -1,247 +0,0 @@ -#ifndef ONYXTYPES_H -#define ONYXTYPES_H - -#include "bh.h" - -#define POINTER_SIZE 4 - -enum BasicKind { - Basic_Kind_Void, - - Basic_Kind_Bool, - - Basic_Kind_Int_Unsized, - Basic_Kind_I8, - Basic_Kind_U8, - Basic_Kind_I16, - Basic_Kind_U16, - Basic_Kind_I32, - Basic_Kind_U32, - Basic_Kind_I64, - Basic_Kind_U64, - - Basic_Kind_Float_Unsized, - Basic_Kind_F32, - Basic_Kind_F64, - - Basic_Kind_Rawptr, - - Basic_Kind_I8X16, - Basic_Kind_I16X8, - Basic_Kind_I32X4, - Basic_Kind_I64X2, - Basic_Kind_F32X4, - Basic_Kind_F64X2, - Basic_Kind_V128, - - Basic_Kind_Type_Index, - - Basic_Kind_Count, -}; - -enum BasicFlag { - Basic_Flag_Boolean = BH_BIT(0), - Basic_Flag_Integer = BH_BIT(1), - Basic_Flag_Unsigned = BH_BIT(2), - Basic_Flag_Float = BH_BIT(3), - Basic_Flag_Pointer = BH_BIT(4), - - Basic_Flag_SIMD = BH_BIT(5), - - Basic_Flag_Type_Index = BH_BIT(6), - - Basic_Flag_Numeric = Basic_Flag_Integer | Basic_Flag_Float, - Basic_Flag_Ordered = Basic_Flag_Integer | Basic_Flag_Float | Basic_Flag_Pointer, - Basic_Flag_Equality = Basic_Flag_Ordered | Basic_Flag_Type_Index | Basic_Flag_Boolean, - Basic_Flag_Constant_Type = Basic_Flag_Boolean | Basic_Flag_Numeric | Basic_Flag_Pointer, - Basic_Flag_Numeric_Ordered = Basic_Flag_Numeric | Basic_Flag_Ordered, -}; - -typedef struct TypeBasic { - enum BasicKind kind; - u32 flags; - u32 size, alignment; // NOTE: In bytes - const char* name; -} TypeBasic; - -// NOTE: Forward declaration for some of the types below -typedef struct Type Type; - -typedef struct StructMember { - u32 offset, idx; - Type *type; - - // NOTE: Since the name is stored here, it may not be necessary to, - // store a hash table of struct members. There realistically will not - // be many struct members, and iterating through an array would be - // easier and less costly. - brendanfh 2020/09/17 - char *name; - struct OnyxToken* token; - - struct AstTyped** initial_value; - i32 use_through_pointer_index; - b32 included_through_use : 1; - b32 used : 1; - - bh_arr(struct AstTyped *) meta_tags; -} StructMember; - -typedef struct TypeWithOffset TypeWithOffset; -struct TypeWithOffset { - Type* type; - u32 offset; -}; - -typedef enum StructProcessingStatus { - SPS_Start, - SPS_Members_Done, - SPS_Uses_Done, -} StructProcessingStatus; - -#define TYPE_KINDS \ - TYPE_KIND(Basic, TypeBasic) \ - TYPE_KIND(Pointer, struct { TypeBasic base; Type *elem; }) \ - TYPE_KIND(Function, struct { \ - Type *return_type; \ - u16 param_count; \ - u16 needed_param_count; \ - i16 vararg_arg_pos; \ - Type* params[]; \ - }) \ - TYPE_KIND(Struct, struct { \ - char* name; \ - u32 size; \ - u16 alignment, mem_count; \ - Table(StructMember *) members; \ - bh_arr(StructMember *) memarr; \ - bh_arr(struct AstPolySolution) poly_sln; \ - bh_arr(TypeWithOffset) linear_members; \ - struct AstType *constructed_from; \ - bh_arr(struct AstTyped *) meta_tags; \ - StructProcessingStatus status; \ - }) \ - TYPE_KIND(PolyStruct, struct { \ - char* name; \ - bh_arr(struct AstTyped *) meta_tags; \ - }) \ - TYPE_KIND(Compound, struct { \ - u32 count; \ - u32 size; \ - bh_arr(TypeWithOffset) linear_members; \ - Type* types[]; \ - }) \ - TYPE_KIND(Array, struct { Type* elem; u32 size; u32 count; }) \ - TYPE_KIND(Slice, struct { Type *elem; }) \ - TYPE_KIND(DynArray, struct { Type *elem; }) \ - TYPE_KIND(VarArgs, struct { Type *elem; }) \ - TYPE_KIND(Enum, struct { \ - char* name; \ - Type* backing; \ - b32 is_flags; \ - }) \ - TYPE_KIND(Distinct, struct { \ - char* name; \ - Type* base_type; \ - }) - - -typedef enum TypeKind { - Type_Kind_Invalid, - -#define TYPE_KIND(k, ...) Type_Kind_##k, - TYPE_KINDS -#undef TYPE_KIND - - Type_Kind_Count, -} TypeKind; - -#define TYPE_KIND(k, ...) typedef __VA_ARGS__ Type ## k; - TYPE_KINDS -#undef TYPE_KIND - -enum TypeFlag { - Type_Flag_Default -}; - -struct Type { - TypeKind kind; - - u32 id; - u32 flags; - - // NOTE(Brendan Hansen): The abstract syntax tree node used to create - // the type. Primarily used to look up symbols in scopes that are embedded - // in the type. - struct AstType* ast_type; - - union { -#define TYPE_KIND(k, ...) Type##k k; - TYPE_KINDS -#undef TYPE_KIND - }; -}; - -extern bh_imap type_map; - -extern Type basic_types[]; - -struct AstType; -struct AstFunction; -struct AstCompound; -struct AstStructLiteral; - -void types_init(); -void types_dump_type_info(); - -b32 types_are_compatible(Type* t1, Type* t2); -u32 type_size_of(Type* type); -u32 type_alignment_of(Type* type); -Type* type_build_from_ast(bh_allocator alloc, struct AstType* type_node); -Type* type_build_implicit_type_of_struct_literal(bh_allocator alloc, struct AstStructLiteral* lit); - -Type* type_build_function_type(bh_allocator alloc, struct AstFunction* func); -Type* type_build_compound_type(bh_allocator alloc, struct AstCompound* compound); - -Type* type_make_pointer(bh_allocator alloc, Type* to); -Type* type_make_array(bh_allocator alloc, Type* to, u32 count); -Type* type_make_slice(bh_allocator alloc, Type* of); -Type* type_make_dynarray(bh_allocator alloc, Type* of); -Type* type_make_varargs(bh_allocator alloc, Type* of); - -void build_linear_types_with_offset(Type* type, bh_arr(TypeWithOffset)* pdest, u32 offset); -b32 type_struct_member_apply_use(bh_allocator alloc, Type *s_type, StructMember *smem); - -const char* type_get_unique_name(Type* type); -const char* type_get_name(Type* type); -u32 type_get_alignment_log2(Type* type); -Type* type_get_contained_type(Type* type); - -b32 type_lookup_member(Type* type, char* member, StructMember* smem); -b32 type_lookup_member_by_idx(Type* type, i32 idx, StructMember* smem); - -i32 type_linear_member_count(Type* type); -b32 type_linear_member_lookup(Type* type, i32 idx, TypeWithOffset* two); -i32 type_get_idx_of_linear_member_with_offset(Type* type, u32 offset); - -b32 type_struct_is_simple(Type* type); - -b32 type_is_pointer(Type* type); -b32 type_is_rawptr(Type* type); -b32 type_is_array(Type* tyoe); -b32 type_is_struct(Type* type); -b32 type_is_bool(Type* type); -b32 type_is_small_integer(Type* type); -b32 type_is_integer(Type* type); -b32 type_is_numeric(Type* type); -b32 type_is_compound(Type* type); -b32 type_is_simd(Type* type); -b32 type_results_in_void(Type* type); -b32 type_is_array_accessible(Type* type); -b32 type_is_structlike(Type* type); -b32 type_is_structlike_strict(Type* type); -u32 type_structlike_mem_count(Type* type); -u32 type_structlike_is_simple(Type* type); -b32 type_is_sl_constructable(Type* type); -b32 type_struct_constructed_from_poly_struct(Type* struct_type, struct AstType* from); - -#endif // #ifndef ONYX_TYPES diff --git a/include/utils.h b/include/utils.h deleted file mode 100644 index 2b0083c2..00000000 --- a/include/utils.h +++ /dev/null @@ -1,44 +0,0 @@ -#include "bh.h" - -#include "astnodes.h" - -extern bh_scratch global_scratch; -extern bh_allocator global_scratch_allocator; - -extern bh_managed_heap global_heap; -extern bh_allocator global_heap_allocator; - -const char* onyx_ast_node_kind_string(AstKind kind); - -Package* package_lookup(char* package_name); -Package* package_lookup_or_create(char* package_name, Scope* parent_scope, bh_allocator alloc, OnyxFilePos pos); -void package_track_use_package(Package* package, Entity* entity); -void package_reinsert_use_packages(Package* package); - -Scope* scope_create(bh_allocator a, Scope* parent, OnyxFilePos created_at); -void scope_include(Scope* target, Scope* source, OnyxFilePos pos); -b32 symbol_introduce(Scope* scope, OnyxToken* tkn, AstNode* symbol); -b32 symbol_raw_introduce(Scope* scope, char* tkn, OnyxFilePos pos, AstNode* symbol); -void symbol_builtin_introduce(Scope* scope, char* sym, AstNode *node); -void symbol_subpackage_introduce(Scope* scope, char* sym, AstPackage *node); -AstNode* symbol_raw_resolve(Scope* start_scope, char* sym); -AstNode* symbol_resolve(Scope* start_scope, OnyxToken* tkn); -AstNode* try_symbol_raw_resolve_from_node(AstNode* node, char* symbol); -AstNode* try_symbol_resolve_from_node(AstNode* node, OnyxToken* token); -AstNode* try_symbol_raw_resolve_from_type(Type *type, char* symbol); -Scope *get_scope_from_node(AstNode *node); -Scope *get_scope_from_node_or_create(AstNode *node); - -void build_all_overload_options(bh_arr(OverloadOption) overloads, bh_imap* all_overloads); - -u32 char_to_base16_value(char x); - -// Returns the length after processing the string. -i32 string_process_escape_seqs(char* dest, char* src, i32 len); - -u32 levenshtein_distance(const char *str1, const char *str2); -char *find_closest_symbol_in_scope_and_parents(Scope *scope, char *sym); -char *find_closest_symbol_in_node(AstNode *node, char *sym); - -extern AstTyped node_that_signals_a_yield; -extern AstTyped node_that_signals_failure; diff --git a/include/wasm_emit.h b/include/wasm_emit.h deleted file mode 100644 index 3a79e549..00000000 --- a/include/wasm_emit.h +++ /dev/null @@ -1,864 +0,0 @@ -#ifndef ONYXWASM_H -#define ONYXWASM_H - -#include "bh.h" - -#include "astnodes.h" -#include "errors.h" - -typedef u8 WasmType; - -typedef struct WasmFuncType { - // NOTE: For now, WASM only allows for 1 return value. - // This may be lifted in the future. - i32 param_count; - WasmType return_type; - WasmType param_types[]; -} WasmFuncType; - -#define SIMD_INSTR_MASK 0x10000 -#define EXT_INSTR_MASK 0x20000 -#define ATOMIC_INSTR_MASK 0x40000 - -typedef enum WasmInstructionType { - WI_UNREACHABLE = 0x00, - WI_NOP = 0x01, - - // NOTE: Control flow - WI_BLOCK_START = 0x02, - WI_BLOCK_END = 0x0B, // NOTE: These ends are not unique - WI_LOOP_START = 0x03, - WI_LOOP_END = 0x0B, - WI_IF_START = 0x04, - WI_ELSE = 0x05, - WI_IF_END = 0x0B, - WI_JUMP = 0x0C, - WI_COND_JUMP = 0x0D, - WI_JUMP_TABLE = 0x0E, - WI_RETURN = 0x0F, - WI_CALL = 0x10, - WI_CALL_INDIRECT = 0x11, - - // NOTE: Parametric instructions - WI_DROP = 0x1A, - WI_SELECT = 0x1B, - - // NOTE: Variable instructions - WI_LOCAL_GET = 0x20, - WI_LOCAL_SET = 0x21, - WI_LOCAL_TEE = 0x22, - WI_GLOBAL_GET = 0x23, - WI_GLOBAL_SET = 0x24, - - // NOTE: Memory instructions - WI_I32_LOAD = 0x28, - WI_I64_LOAD = 0x29, - WI_F32_LOAD = 0x2A, - WI_F64_LOAD = 0x2B, - WI_I32_LOAD_8_S = 0x2C, - WI_I32_LOAD_8_U = 0x2D, - WI_I32_LOAD_16_S = 0x2E, - WI_I32_LOAD_16_U = 0x2F, - WI_I64_LOAD_8_S = 0x30, - WI_I64_LOAD_8_U = 0x31, - WI_I64_LOAD_16_S = 0x32, - WI_I64_LOAD_16_U = 0x33, - WI_I64_LOAD_32_S = 0x34, - WI_I64_LOAD_32_U = 0x35, - WI_I32_STORE = 0x36, - WI_I64_STORE = 0x37, - WI_F32_STORE = 0x38, - WI_F64_STORE = 0x39, - WI_I32_STORE_8 = 0x3A, - WI_I32_STORE_16 = 0x3B, - WI_I64_STORE_8 = 0x3C, - WI_I64_STORE_16 = 0x3D, - WI_I64_STORE_32 = 0x3E, - WI_MEMORY_SIZE = 0x3F, - WI_MEMORY_GROW = 0x40, - - // NOTE: Numeric Instructions - WI_I32_CONST = 0x41, - WI_I64_CONST = 0x42, - WI_F32_CONST = 0x43, - WI_F64_CONST = 0x44, - - WI_I32_EQZ = 0x45, - WI_I32_EQ = 0x46, - WI_I32_NE = 0x47, - WI_I32_LT_S = 0x48, - WI_I32_LT_U = 0x49, - WI_I32_GT_S = 0x4a, - WI_I32_GT_U = 0x4b, - WI_I32_LE_S = 0x4c, - WI_I32_LE_U = 0x4d, - WI_I32_GE_S = 0x4e, - WI_I32_GE_U = 0x4f, - - WI_I64_EQZ = 0x50, - WI_I64_EQ = 0x51, - WI_I64_NE = 0x52, - WI_I64_LT_S = 0x53, - WI_I64_LT_U = 0x54, - WI_I64_GT_S = 0x55, - WI_I64_GT_U = 0x56, - WI_I64_LE_S = 0x57, - WI_I64_LE_U = 0x58, - WI_I64_GE_S = 0x59, - WI_I64_GE_U = 0x5a, - - WI_F32_EQ = 0x5b, - WI_F32_NE = 0x5c, - WI_F32_LT = 0x5d, - WI_F32_GT = 0x5e, - WI_F32_LE = 0x5f, - WI_F32_GE = 0x60, - - WI_F64_EQ = 0x61, - WI_F64_NE = 0x62, - WI_F64_LT = 0x63, - WI_F64_GT = 0x64, - WI_F64_LE = 0x65, - WI_F64_GE = 0x66, - - WI_I32_CLZ = 0x67, - WI_I32_CTZ = 0x68, - WI_I32_POPCNT = 0x69, - WI_I32_ADD = 0x6a, - WI_I32_SUB = 0x6b, - WI_I32_MUL = 0x6c, - WI_I32_DIV_S = 0x6d, - WI_I32_DIV_U = 0x6e, - WI_I32_REM_S = 0x6f, - WI_I32_REM_U = 0x70, - WI_I32_AND = 0x71, - WI_I32_OR = 0x72, - WI_I32_XOR = 0x73, - WI_I32_SHL = 0x74, - WI_I32_SHR_S = 0x75, - WI_I32_SHR_U = 0x76, - WI_I32_ROTL = 0x77, - WI_I32_ROTR = 0x78, - - WI_I64_CLZ = 0x79, - WI_I64_CTZ = 0x7a, - WI_I64_POPCNT = 0x7b, - WI_I64_ADD = 0x7c, - WI_I64_SUB = 0x7d, - WI_I64_MUL = 0x7e, - WI_I64_DIV_S = 0x7f, - WI_I64_DIV_U = 0x80, - WI_I64_REM_S = 0x81, - WI_I64_REM_U = 0x82, - WI_I64_AND = 0x83, - WI_I64_OR = 0x84, - WI_I64_XOR = 0x85, - WI_I64_SHL = 0x86, - WI_I64_SHR_S = 0x87, - WI_I64_SHR_U = 0x88, - WI_I64_ROTL = 0x89, - WI_I64_ROTR = 0x8a, - - WI_F32_ABS = 0x8b, - WI_F32_NEG = 0x8c, - WI_F32_CEIL = 0x8d, - WI_F32_FLOOR = 0x8e, - WI_F32_TRUNC = 0x8f, - WI_F32_NEAREST = 0x90, - WI_F32_SQRT = 0x91, - WI_F32_ADD = 0x92, - WI_F32_SUB = 0x93, - WI_F32_MUL = 0x94, - WI_F32_DIV = 0x95, - WI_F32_MIN = 0x96, - WI_F32_MAX = 0x97, - WI_F32_COPYSIGN = 0x98, - - WI_F64_ABS = 0x99, - WI_F64_NEG = 0x9a, - WI_F64_CEIL = 0x9b, - WI_F64_FLOOR = 0x9c, - WI_F64_TRUNC = 0x9d, - WI_F64_NEAREST = 0x9e, - WI_F64_SQRT = 0x9f, - WI_F64_ADD = 0xA0, - WI_F64_SUB = 0xA1, - WI_F64_MUL = 0xA2, - WI_F64_DIV = 0xA3, - WI_F64_MIN = 0xA4, - WI_F64_MAX = 0xA5, - WI_F64_COPYSIGN = 0xA6, - - WI_I32_FROM_I64 = 0xA7, - WI_I32_FROM_F32_S = 0xA8, - WI_I32_FROM_F32_U = 0xA9, - WI_I32_FROM_F64_S = 0xAA, - WI_I32_FROM_F64_U = 0xAB, - - WI_I64_FROM_I32_S = 0xAC, - WI_I64_FROM_I32_U = 0xAD, - WI_I64_FROM_F32_S = 0xAE, - WI_I64_FROM_F32_U = 0xAF, - WI_I64_FROM_F64_S = 0xB0, - WI_I64_FROM_F64_U = 0xB1, - - WI_F32_FROM_I32_S = 0xB2, - WI_F32_FROM_I32_U = 0xB3, - WI_F32_FROM_I64_S = 0xB4, - WI_F32_FROM_I64_U = 0xB5, - WI_F32_FROM_F64 = 0xB6, - - WI_F64_FROM_I32_S = 0xB7, - WI_F64_FROM_I32_U = 0xB8, - WI_F64_FROM_I64_S = 0xB9, - WI_F64_FROM_I64_U = 0xBA, - WI_F64_FROM_F32 = 0xBB, - - WI_I32_REINTERPRET_F32 = 0xBC, - WI_I64_REINTERPRET_F64 = 0xBD, - WI_F32_REINTERPRET_I32 = 0xBE, - WI_F64_REINTERPRET_I64 = 0xBF, - - WI_I32_EXTEND_8_S = 0xC0, - WI_I32_EXTEND_16_S = 0xC1, - WI_I64_EXTEND_8_S = 0xC2, - WI_I64_EXTEND_16_S = 0xC3, - WI_I64_EXTEND_32_S = 0xC4, - - // Pointer stuff; this will make it easier to switch to 64-bit - // pointers, whenever WebAssembly standarizes that. - WI_PTR_CONST = WI_I32_CONST, - WI_PTR_LOAD = WI_I32_LOAD, - WI_PTR_STORE = WI_I32_STORE, - WI_PTR_ADD = WI_I32_ADD, - WI_PTR_SUB = WI_I32_SUB, - WI_PTR_MUL = WI_I32_MUL, - WI_PTR_GE = WI_I32_GE_U, - WI_PTR_GT = WI_I32_GT_U, - WI_PTR_EQ = WI_I32_EQ, - - WI_V128_LOAD = SIMD_INSTR_MASK | 0, - WI_V128_STORE = SIMD_INSTR_MASK | 11, - - WI_V128_CONST = SIMD_INSTR_MASK | 12, - - WI_I8X16_SHUFFLE = SIMD_INSTR_MASK | 13, - - WI_I8X16_EXTRACT_LANE_S = SIMD_INSTR_MASK | 21, - WI_I8X16_EXTRACT_LANE_U = SIMD_INSTR_MASK | 22, - WI_I8X16_REPLACE_LANE = SIMD_INSTR_MASK | 23, - WI_I16X8_EXTRACT_LANE_S = SIMD_INSTR_MASK | 24, - WI_I16X8_EXTRACT_LANE_U = SIMD_INSTR_MASK | 25, - WI_I16X8_REPLACE_LANE = SIMD_INSTR_MASK | 26, - WI_I32X4_EXTRACT_LANE = SIMD_INSTR_MASK | 27, - WI_I32X4_REPLACE_LANE = SIMD_INSTR_MASK | 28, - WI_I64X2_EXTRACT_LANE = SIMD_INSTR_MASK | 29, - WI_I64X2_REPLACE_LANE = SIMD_INSTR_MASK | 30, - WI_F32X4_EXTRACT_LANE = SIMD_INSTR_MASK | 31, - WI_F32X4_REPLACE_LANE = SIMD_INSTR_MASK | 32, - WI_F64X2_EXTRACT_LANE = SIMD_INSTR_MASK | 33, - WI_F64X2_REPLACE_LANE = SIMD_INSTR_MASK | 34, - - WI_I8X16_SWIZZLE = SIMD_INSTR_MASK | 14, - WI_I8X16_SPLAT = SIMD_INSTR_MASK | 15, - WI_I16X8_SPLAT = SIMD_INSTR_MASK | 16, - WI_I32X4_SPLAT = SIMD_INSTR_MASK | 17, - WI_I64X2_SPLAT = SIMD_INSTR_MASK | 18, - WI_F32X4_SPLAT = SIMD_INSTR_MASK | 19, - WI_F64X2_SPLAT = SIMD_INSTR_MASK | 20, - - WI_I8X16_EQ = SIMD_INSTR_MASK | 35, - WI_I8X16_NEQ = SIMD_INSTR_MASK | 36, - WI_I8X16_LT_S = SIMD_INSTR_MASK | 37, - WI_I8X16_LT_U = SIMD_INSTR_MASK | 38, - WI_I8X16_GT_S = SIMD_INSTR_MASK | 39, - WI_I8X16_GT_U = SIMD_INSTR_MASK | 40, - WI_I8X16_LE_S = SIMD_INSTR_MASK | 41, - WI_I8X16_LE_U = SIMD_INSTR_MASK | 42, - WI_I8X16_GE_S = SIMD_INSTR_MASK | 43, - WI_I8X16_GE_U = SIMD_INSTR_MASK | 44, - - WI_I16X8_EQ = SIMD_INSTR_MASK | 45, - WI_I16X8_NEQ = SIMD_INSTR_MASK | 46, - WI_I16X8_LT_S = SIMD_INSTR_MASK | 47, - WI_I16X8_LT_U = SIMD_INSTR_MASK | 48, - WI_I16X8_GT_S = SIMD_INSTR_MASK | 49, - WI_I16X8_GT_U = SIMD_INSTR_MASK | 50, - WI_I16X8_LE_S = SIMD_INSTR_MASK | 51, - WI_I16X8_LE_U = SIMD_INSTR_MASK | 52, - WI_I16X8_GE_S = SIMD_INSTR_MASK | 53, - WI_I16X8_GE_U = SIMD_INSTR_MASK | 54, - - WI_I32X4_EQ = SIMD_INSTR_MASK | 55, - WI_I32X4_NEQ = SIMD_INSTR_MASK | 56, - WI_I32X4_LT_S = SIMD_INSTR_MASK | 57, - WI_I32X4_LT_U = SIMD_INSTR_MASK | 58, - WI_I32X4_GT_S = SIMD_INSTR_MASK | 59, - WI_I32X4_GT_U = SIMD_INSTR_MASK | 60, - WI_I32X4_LE_S = SIMD_INSTR_MASK | 61, - WI_I32X4_LE_U = SIMD_INSTR_MASK | 62, - WI_I32X4_GE_S = SIMD_INSTR_MASK | 63, - WI_I32X4_GE_U = SIMD_INSTR_MASK | 64, - - WI_F32X4_EQ = SIMD_INSTR_MASK | 65, - WI_F32X4_NEQ = SIMD_INSTR_MASK | 66, - WI_F32X4_LT = SIMD_INSTR_MASK | 67, - WI_F32X4_GT = SIMD_INSTR_MASK | 68, - WI_F32X4_LE = SIMD_INSTR_MASK | 69, - WI_F32X4_GE = SIMD_INSTR_MASK | 70, - - WI_F64X2_EQ = SIMD_INSTR_MASK | 71, - WI_F64X2_NEQ = SIMD_INSTR_MASK | 72, - WI_F64X2_LT = SIMD_INSTR_MASK | 73, - WI_F64X2_GT = SIMD_INSTR_MASK | 74, - WI_F64X2_LE = SIMD_INSTR_MASK | 75, - WI_F64X2_GE = SIMD_INSTR_MASK | 76, - - WI_V128_NOT = SIMD_INSTR_MASK | 77, - WI_V128_AND = SIMD_INSTR_MASK | 78, - WI_V128_ANDNOT = SIMD_INSTR_MASK | 79, - WI_V128_OR = SIMD_INSTR_MASK | 80, - WI_V128_XOR = SIMD_INSTR_MASK | 81, - WI_V128_BITSELECT = SIMD_INSTR_MASK | 82, - - WI_I8X16_ABS = SIMD_INSTR_MASK | 96, - WI_I8X16_NEG = SIMD_INSTR_MASK | 97, - WI_I8X16_ANY_TRUE = SIMD_INSTR_MASK | 98, - WI_I8X16_ALL_TRUE = SIMD_INSTR_MASK | 99, - WI_I8X16_BITMASK = SIMD_INSTR_MASK | 100, - WI_I8X16_NARROW_I16X8_S = SIMD_INSTR_MASK | 101, - WI_I8X16_NARROW_I16X8_U = SIMD_INSTR_MASK | 102, - WI_I8X16_SHL = SIMD_INSTR_MASK | 107, - WI_I8X16_SHR_S = SIMD_INSTR_MASK | 108, - WI_I8X16_SHR_U = SIMD_INSTR_MASK | 109, - WI_I8X16_ADD = SIMD_INSTR_MASK | 110, - WI_I8X16_ADD_SAT_S = SIMD_INSTR_MASK | 111, - WI_I8X16_ADD_SAT_U = SIMD_INSTR_MASK | 112, - WI_I8X16_SUB = SIMD_INSTR_MASK | 113, - WI_I8X16_SUB_SAT_S = SIMD_INSTR_MASK | 114, - WI_I8X16_SUB_SAT_U = SIMD_INSTR_MASK | 115, - WI_I8X16_MIN_S = SIMD_INSTR_MASK | 118, - WI_I8X16_MIN_U = SIMD_INSTR_MASK | 119, - WI_I8X16_MAX_S = SIMD_INSTR_MASK | 120, - WI_I8X16_MAX_U = SIMD_INSTR_MASK | 121, - WI_I8X16_AVGR_U = SIMD_INSTR_MASK | 123, - - WI_I16X8_ABS = SIMD_INSTR_MASK | 128, - WI_I16X8_NEG = SIMD_INSTR_MASK | 129, - WI_I16X8_ANY_TRUE = SIMD_INSTR_MASK | 130, - WI_I16X8_ALL_TRUE = SIMD_INSTR_MASK | 131, - WI_I16X8_BITMASK = SIMD_INSTR_MASK | 132, - WI_I16X8_NARROW_I32X4_S = SIMD_INSTR_MASK | 133, - WI_I16X8_NARROW_I32X4_U = SIMD_INSTR_MASK | 134, - WI_I16X8_WIDEN_LOW_I8X16_S = SIMD_INSTR_MASK | 135, - WI_I16X8_WIDEN_HIGH_I8X16_S = SIMD_INSTR_MASK | 136, - WI_I16X8_WIDEN_LOW_I8X16_U = SIMD_INSTR_MASK | 137, - WI_I16X8_WIDEN_HIGH_I8X16_U = SIMD_INSTR_MASK | 138, - WI_I16X8_SHL = SIMD_INSTR_MASK | 139, - WI_I16X8_SHR_S = SIMD_INSTR_MASK | 140, - WI_I16X8_SHR_U = SIMD_INSTR_MASK | 141, - WI_I16X8_ADD = SIMD_INSTR_MASK | 142, - WI_I16X8_ADD_SAT_S = SIMD_INSTR_MASK | 143, - WI_I16X8_ADD_SAT_U = SIMD_INSTR_MASK | 144, - WI_I16X8_SUB = SIMD_INSTR_MASK | 145, - WI_I16X8_SUB_SAT_S = SIMD_INSTR_MASK | 146, - WI_I16X8_SUB_SAT_U = SIMD_INSTR_MASK | 147, - WI_I16X8_MUL = SIMD_INSTR_MASK | 149, - WI_I16X8_MIN_S = SIMD_INSTR_MASK | 150, - WI_I16X8_MIN_U = SIMD_INSTR_MASK | 151, - WI_I16X8_MAX_S = SIMD_INSTR_MASK | 152, - WI_I16X8_MAX_U = SIMD_INSTR_MASK | 153, - WI_I16X8_AVGR_U = SIMD_INSTR_MASK | 155, - - WI_I32X4_ABS = SIMD_INSTR_MASK | 160, - WI_I32X4_NEG = SIMD_INSTR_MASK | 161, - WI_I32X4_ANY_TRUE = SIMD_INSTR_MASK | 162, - WI_I32X4_ALL_TRUE = SIMD_INSTR_MASK | 163, - WI_I32X4_BITMASK = SIMD_INSTR_MASK | 164, - WI_I32X4_WIDEN_LOW_I16X8_S = SIMD_INSTR_MASK | 167, - WI_I32X4_WIDEN_HIGH_I16X8_S = SIMD_INSTR_MASK | 168, - WI_I32X4_WIDEN_LOW_I16X8_U = SIMD_INSTR_MASK | 169, - WI_I32X4_WIDEN_HIGH_I16X8_U = SIMD_INSTR_MASK | 170, - WI_I32X4_SHL = SIMD_INSTR_MASK | 171, - WI_I32X4_SHR_S = SIMD_INSTR_MASK | 172, - WI_I32X4_SHR_U = SIMD_INSTR_MASK | 173, - WI_I32X4_ADD = SIMD_INSTR_MASK | 174, - WI_I32X4_SUB = SIMD_INSTR_MASK | 177, - WI_I32X4_MUL = SIMD_INSTR_MASK | 181, - WI_I32X4_MIN_S = SIMD_INSTR_MASK | 182, - WI_I32X4_MIN_U = SIMD_INSTR_MASK | 183, - WI_I32X4_MAX_S = SIMD_INSTR_MASK | 184, - WI_I32X4_MAX_U = SIMD_INSTR_MASK | 185, - - WI_I64X2_NEG = SIMD_INSTR_MASK | 193, - WI_I64X2_SHL = SIMD_INSTR_MASK | 203, - WI_I64X2_SHR_S = SIMD_INSTR_MASK | 204, - WI_I64X2_SHR_U = SIMD_INSTR_MASK | 205, - WI_I64X2_ADD = SIMD_INSTR_MASK | 206, - WI_I64X2_SUB = SIMD_INSTR_MASK | 209, - WI_I64X2_MUL = SIMD_INSTR_MASK | 213, - - WI_F32X4_ABS = SIMD_INSTR_MASK | 224, - WI_F32X4_NEG = SIMD_INSTR_MASK | 225, - WI_F32X4_SQRT = SIMD_INSTR_MASK | 227, - WI_F32X4_ADD = SIMD_INSTR_MASK | 228, - WI_F32X4_SUB = SIMD_INSTR_MASK | 229, - WI_F32X4_MUL = SIMD_INSTR_MASK | 230, - WI_F32X4_DIV = SIMD_INSTR_MASK | 231, - WI_F32X4_MIN = SIMD_INSTR_MASK | 232, - WI_F32X4_MAX = SIMD_INSTR_MASK | 233, - - WI_F64X2_ABS = SIMD_INSTR_MASK | 236, - WI_F64X2_NEG = SIMD_INSTR_MASK | 237, - WI_F64X2_SQRT = SIMD_INSTR_MASK | 239, - WI_F64X2_ADD = SIMD_INSTR_MASK | 240, - WI_F64X2_SUB = SIMD_INSTR_MASK | 241, - WI_F64X2_MUL = SIMD_INSTR_MASK | 242, - WI_F64X2_DIV = SIMD_INSTR_MASK | 243, - WI_F64X2_MIN = SIMD_INSTR_MASK | 244, - WI_F64X2_MAX = SIMD_INSTR_MASK | 245, - - WI_I32X4_TRUNC_SAT_F32X4_S = SIMD_INSTR_MASK | 248, - WI_I32X4_TRUNC_SAT_F32X4_U = SIMD_INSTR_MASK | 249, - WI_F32X4_CONVERT_I32X4_S = SIMD_INSTR_MASK | 250, - WI_F32X4_CONVERT_I32X4_U = SIMD_INSTR_MASK | 251, - - - WI_MEMORY_INIT = EXT_INSTR_MASK | 0x08, - WI_MEMORY_COPY = EXT_INSTR_MASK | 0x0a, - WI_MEMORY_FILL = EXT_INSTR_MASK | 0x0b, - - WI_ATOMIC_NOTIFY = ATOMIC_INSTR_MASK | 0x00, - WI_ATOMIC_WAIT32 = ATOMIC_INSTR_MASK | 0x01, - WI_ATOMIC_WAIT64 = ATOMIC_INSTR_MASK | 0x02, - - WI_ATOMIC_FENCE = ATOMIC_INSTR_MASK | 0x03, - - WI_ATOMIC_I32_LOAD = ATOMIC_INSTR_MASK | 0x10, - WI_ATOMIC_I64_LOAD = ATOMIC_INSTR_MASK | 0x11, - WI_ATOMIC_I32_LOAD8_U = ATOMIC_INSTR_MASK | 0x12, - WI_ATOMIC_I32_LOAD16_U = ATOMIC_INSTR_MASK | 0x13, - WI_ATOMIC_I64_LOAD8_U = ATOMIC_INSTR_MASK | 0x14, - WI_ATOMIC_I64_LOAD16_U = ATOMIC_INSTR_MASK | 0x15, - WI_ATOMIC_I64_LOAD32_U = ATOMIC_INSTR_MASK | 0x16, - - WI_ATOMIC_I32_STORE = ATOMIC_INSTR_MASK | 0x17, - WI_ATOMIC_I64_STORE = ATOMIC_INSTR_MASK | 0x18, - WI_ATOMIC_I32_STORE8 = ATOMIC_INSTR_MASK | 0x19, - WI_ATOMIC_I32_STORE16 = ATOMIC_INSTR_MASK | 0x1a, - WI_ATOMIC_I64_STORE8 = ATOMIC_INSTR_MASK | 0x1b, - WI_ATOMIC_I64_STORE16 = ATOMIC_INSTR_MASK | 0x1c, - WI_ATOMIC_I64_STORE32 = ATOMIC_INSTR_MASK | 0x1d, - - WI_ATOMIC_I32_ADD = ATOMIC_INSTR_MASK | 0x1e, - WI_ATOMIC_I64_ADD = ATOMIC_INSTR_MASK | 0x1f, - WI_ATOMIC_I32_ADD8_U = ATOMIC_INSTR_MASK | 0x20, - WI_ATOMIC_I32_ADD16_U = ATOMIC_INSTR_MASK | 0x21, - WI_ATOMIC_I64_ADD8_U = ATOMIC_INSTR_MASK | 0x22, - WI_ATOMIC_I64_ADD16_U = ATOMIC_INSTR_MASK | 0x23, - WI_ATOMIC_I64_ADD32_U = ATOMIC_INSTR_MASK | 0x24, - - WI_ATOMIC_I32_SUB = ATOMIC_INSTR_MASK | 0x25, - WI_ATOMIC_I64_SUB = ATOMIC_INSTR_MASK | 0x26, - WI_ATOMIC_I32_SUB8_U = ATOMIC_INSTR_MASK | 0x27, - WI_ATOMIC_I32_SUB16_U = ATOMIC_INSTR_MASK | 0x28, - WI_ATOMIC_I64_SUB8_U = ATOMIC_INSTR_MASK | 0x29, - WI_ATOMIC_I64_SUB16_U = ATOMIC_INSTR_MASK | 0x2a, - WI_ATOMIC_I64_SUB32_U = ATOMIC_INSTR_MASK | 0x2b, - - WI_ATOMIC_I32_AND = ATOMIC_INSTR_MASK | 0x2c, - WI_ATOMIC_I64_AND = ATOMIC_INSTR_MASK | 0x2d, - WI_ATOMIC_I32_AND8_U = ATOMIC_INSTR_MASK | 0x2e, - WI_ATOMIC_I32_AND16_U = ATOMIC_INSTR_MASK | 0x2f, - WI_ATOMIC_I64_AND8_U = ATOMIC_INSTR_MASK | 0x30, - WI_ATOMIC_I64_AND16_U = ATOMIC_INSTR_MASK | 0x31, - WI_ATOMIC_I64_AND32_U = ATOMIC_INSTR_MASK | 0x32, - - WI_ATOMIC_I32_OR = ATOMIC_INSTR_MASK | 0x33, - WI_ATOMIC_I64_OR = ATOMIC_INSTR_MASK | 0x34, - WI_ATOMIC_I32_OR8_U = ATOMIC_INSTR_MASK | 0x35, - WI_ATOMIC_I32_OR16_U = ATOMIC_INSTR_MASK | 0x36, - WI_ATOMIC_I64_OR8_U = ATOMIC_INSTR_MASK | 0x37, - WI_ATOMIC_I64_OR16_U = ATOMIC_INSTR_MASK | 0x38, - WI_ATOMIC_I64_OR32_U = ATOMIC_INSTR_MASK | 0x39, - - WI_ATOMIC_I32_XOR = ATOMIC_INSTR_MASK | 0x3a, - WI_ATOMIC_I64_XOR = ATOMIC_INSTR_MASK | 0x3b, - WI_ATOMIC_I32_XOR8_U = ATOMIC_INSTR_MASK | 0x3c, - WI_ATOMIC_I32_XOR16_U = ATOMIC_INSTR_MASK | 0x3d, - WI_ATOMIC_I64_XOR8_U = ATOMIC_INSTR_MASK | 0x3e, - WI_ATOMIC_I64_XOR16_U = ATOMIC_INSTR_MASK | 0x3f, - WI_ATOMIC_I64_XOR32_U = ATOMIC_INSTR_MASK | 0x40, - - WI_ATOMIC_I32_XCHG = ATOMIC_INSTR_MASK | 0x41, - WI_ATOMIC_I64_XCHG = ATOMIC_INSTR_MASK | 0x42, - WI_ATOMIC_I32_XCHG8_U = ATOMIC_INSTR_MASK | 0x43, - WI_ATOMIC_I32_XCHG16_U = ATOMIC_INSTR_MASK | 0x44, - WI_ATOMIC_I64_XCHG8_U = ATOMIC_INSTR_MASK | 0x45, - WI_ATOMIC_I64_XCHG16_U = ATOMIC_INSTR_MASK | 0x46, - WI_ATOMIC_I64_XCHG32_U = ATOMIC_INSTR_MASK | 0x47, - - WI_ATOMIC_I32_CMPXCHG = ATOMIC_INSTR_MASK | 0x48, - WI_ATOMIC_I64_CMPXCHG = ATOMIC_INSTR_MASK | 0x49, - WI_ATOMIC_I32_CMPXCHG8_U = ATOMIC_INSTR_MASK | 0x4a, - WI_ATOMIC_I32_CMPXCHG16_U = ATOMIC_INSTR_MASK | 0x4b, - WI_ATOMIC_I64_CMPXCHG8_U = ATOMIC_INSTR_MASK | 0x4c, - WI_ATOMIC_I64_CMPXCHG16_U = ATOMIC_INSTR_MASK | 0x4d, - WI_ATOMIC_I64_CMPXCHG32_U = ATOMIC_INSTR_MASK | 0x4e, -} WasmInstructionType; - -typedef union { - struct { - i32 i1, i2; - }; - i64 l; - float f; - double d; - ptr p; -} WasmInstructionData; - -typedef struct WasmInstruction { - WasmInstructionType type; - WasmInstructionData data; -} WasmInstruction; - -typedef struct BranchTable { - u32 count; - u32 default_case; - u32 cases[]; -} BranchTable; - -#define LOCAL_IS_WASM 0x8000000000000 -typedef struct LocalAllocator { - u32 param_count; - - u32 allocated[5]; - u32 freed[5]; - - i32 max_stack; - i32 curr_stack; -} LocalAllocator; - -typedef struct WasmFunc { - i32 type_idx; - LocalAllocator locals; - bh_arr(WasmInstruction) code; - OnyxToken *location; -} WasmFunc; - -typedef struct WasmGlobal { - WasmType type; - u32 mutable : 1; - bh_arr(WasmInstruction) initial_value; -} WasmGlobal; - -typedef enum WasmForeignKind { - WASM_FOREIGN_FUNCTION = 0x00, - WASM_FOREIGN_TABLE = 0x01, - WASM_FOREIGN_MEMORY = 0x02, - WASM_FOREIGN_GLOBAL = 0x03, -} WasmForeignKind; - -typedef struct WasmExport { - WasmForeignKind kind; - i32 idx; -} WasmExport; - -typedef struct WasmImport { - WasmForeignKind kind; - union { - i32 idx; - struct { - i32 min, max; - b32 shared; - }; - }; - char *mod, *name; -} WasmImport; - -typedef struct WasmDatum { - u32 id; - u32 offset_, alignment; - u32 length; - ptr data; -} WasmDatum; - -typedef enum DatumPatchInfoKind { - Datum_Patch_Instruction, - Datum_Patch_Data, - Datum_Patch_Relative, -} DatumPatchInfoKind; - -// -// This represents a pointer that should be filled in -// later when the corresponding data element is placed. -// -// There are three kinds of patches: -// - Instruction -// - Data -// - Relative -// -// In all cases, the `data_id` member is set to the id -// of the WasmDatum entry that will be the base address, -// and then the `offset` member will be added to that. -// -// In instruction patches, the `index` member is set -// to the index of the function where the instruction should -// be patched. The `location` member is set to the instruction -// that needs to have its data changed. -// -// In data patches, the `index` member is set to the id -// of the WasmDatum entry that needs to have a part of it -// updated. The `location` member is the offset into the -// data to update. It is assumed that 4 bytes will be reserved -// to be replaced with the pointer value. -// -// In relative patches, `index` member is set to the id -// of the WasmDatum entry that needs to have a part of it -// updated. The `location` member is the offset into the -// data to update. The difference between `Data` and `Relative` -// is that `Relative` *adds* the base address to the current -// value in the 4 bytes, as opposed to replacing it. As a -// convenience, if the value is 0 (null), it will remain as -// 0. -// -typedef struct DatumPatchInfo { - DatumPatchInfoKind kind; - u32 data_id; - u32 offset; - u32 location; - u32 index; -} DatumPatchInfo; - -// Context used when building a constexpr buffer -typedef struct ConstExprContext { - struct OnyxWasmModule *module; - ptr data; - u32 data_id; -} ConstExprContext; - -typedef enum DeferredStmtType { - Deferred_Stmt_Node, - Deferred_Stmt_Code, -} DeferredStmtType; - -typedef struct DeferredStmt { - DeferredStmtType type; - u32 depth; - AstDefer *defer_node; - - union { - AstNode *stmt; - struct { - WasmInstruction* instructions; - u32 instruction_count; - }; - }; -} DeferredStmt; - -typedef struct AllocatedSpace { - u64 depth; - AstTyped *expr; -} AllocatedSpace; - -typedef struct StrLitInfo { - u32 data_id; - u32 len; -} StrLitInfo; - -typedef struct PatchInfo { - u32 instruction_index; -} PatchInfo; - -typedef struct ForRemoveInfo { - // These are WASM locals - u64 iterator_remove_func; - u64 iterator_data_ptr; - - i32 remove_func_type_idx; -} ForRemoveInfo; - -typedef struct OnyxWasmModule { - bh_allocator allocator; - - bh_arena *extended_instr_data; - bh_allocator extended_instr_alloc; - - // NOTE: Mapping ptrs to function / global indicies - bh_imap index_map; - - // NOTE: Mapping from local ast node ptrs to indicies or offsets, depending on the mode - bh_imap local_map; - - i32 current_func_idx; - LocalAllocator* local_alloc; - - // NOTE: Mapping ptrs to elements - bh_imap elem_map; - - bh_arr(DeferredStmt) deferred_stmts; - bh_arr(AllocatedSpace) local_allocations; - - bh_arr(PatchInfo) stack_leave_patches; - bh_arr(DatumPatchInfo) data_patches; - - bh_arr(ForRemoveInfo) for_remove_info; - - bh_arr(AstForeignBlock *) foreign_blocks; - u32 next_foreign_block_idx; - - bh_arr(AstFunction *) procedures_with_tags; - - // NOTE: Used internally as a map from strings that represent function types, - // 0x7f 0x7f : 0x7f ( (i32, i32) -> i32 ) - // to the function type index if it has been created. - Table(i32) type_map; - - Table(StrLitInfo) loaded_file_info; - Table(StrLitInfo) string_literals; - - bh_arr(u8) structured_jump_target; - bh_arr(AstLocal*) return_location_stack; // NOTE: Used for do-block return expressions. - - bh_arr(WasmFuncType*) types; // NOTE: This have to be pointers because the type is variadic in size - bh_arr(WasmImport) imports; - Table(WasmExport) exports; - bh_arr(WasmGlobal) globals; - bh_arr(WasmFunc) funcs; - bh_arr(WasmDatum) data; - bh_arr(i32) elems; - bh_arr(char *) libraries; - bh_arr(char *) library_paths; - b32 needs_memory_section; - u32 memory_min_size; - u32 memory_max_size; - - // NOTE: Set of things used when compiling; not part of the actual module - u32 export_count; - u32 next_type_idx; - u32 next_func_idx; - u32 next_global_idx; - u32 next_tls_offset; - u32 next_elem_idx; - u32 foreign_function_count; - - i32 *stack_top_ptr; - i32 *tls_size_ptr; - i32 *heap_start_ptr; - u64 stack_base_idx; - CallingConvention curr_cc; - i32 null_proc_func_idx; - - b32 has_stack_locals : 1; - -#ifdef ENABLE_DEBUG_INFO - struct DebugContext *debug_context; -#endif - -} OnyxWasmModule; - -typedef struct OnyxWasmLinkOptions { - b32 stack_first; - u32 stack_size; - u32 stack_alignment; - - u32 null_reserve_size; - - b32 import_memory; - char *import_memory_module_name; - char *import_memory_import_name; - - b32 export_memory; - char *export_memory_name; - - b32 export_func_table; - char *export_func_table_name; - - u32 memory_min_size; - u32 memory_max_size; -} OnyxWasmLinkOptions; - -b32 onyx_wasm_build_link_options_from_node(OnyxWasmLinkOptions *opts, struct AstTyped *node); - -OnyxWasmModule onyx_wasm_module_create(bh_allocator alloc); -void onyx_wasm_module_link(OnyxWasmModule *module, OnyxWasmLinkOptions *options); -void onyx_wasm_module_free(OnyxWasmModule* module); -void onyx_wasm_module_write_to_buffer(OnyxWasmModule* module, bh_buffer* buffer); -void onyx_wasm_module_write_to_file(OnyxWasmModule* module, bh_file file); - -#ifdef ENABLE_RUN_WITH_WASMER -void onyx_run_initialize(b32 debug_enabled); -b32 onyx_run_wasm(bh_buffer code_buffer, int argc, char *argv[]); -#endif - -#ifdef ENABLE_DEBUG_INFO - -typedef enum DebugOpType { - DOT_END = 0b00000000, - DOT_SET = 0b00000001, - DOT_PUSHF = 0b00000010, - DOT_POPF = 0b00000011, - DOT_SYM = 0b00000100, - - DOT_INC = 0b01000000, - DOT_DEC = 0b10000000, - DOT_REP = 0b11000000, -} DebugOpType; - -typedef struct DebugFuncContext { - u32 func_index; - u32 file_id; - u32 line; - u32 op_offset; - u32 stack_ptr_idx; - - u32 name_length; - char *name; -} DebugFuncContext; - -typedef struct DebugFileInfo { - u32 file_id; - u32 line_count; -} DebugFileInfo; - -typedef enum DebugSymLoc { - DSL_REGISTER = 1, - DSL_STACK = 2, - DSL_GLOBAL = 3, -} DebugSymLoc; - -typedef struct DebugSymInfo { - u32 sym_id; - u32 location_type; - u64 location_num; - char *name; - u32 type; -} DebugSymInfo; - -typedef struct DebugSymPatch { - u32 func_idx; - u32 sym_id; - u64 local_idx; -} DebugSymPatch; - -typedef struct DebugContext { - bh_allocator allocator; - - Table(DebugFileInfo) file_info; - u32 next_file_id; - - bh_arr(DebugSymInfo) sym_info; - bh_arr(DebugSymPatch) sym_patches; - u32 next_sym_id; - - bh_arr(DebugFuncContext) funcs; - bh_buffer op_buffer; - - // Used during building the debug info - OnyxToken *last_token; - b32 last_op_was_rep : 1; -} DebugContext; - -#endif - -#endif diff --git a/runtime/build.sh b/runtime/build.sh new file mode 100755 index 00000000..25cfb9ac --- /dev/null +++ b/runtime/build.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +. ../settings.sh + +$CC -shared -fpic -w \ + -o ../bin/onyx_runtime.so \ + -I ../shared/include -I ../compiler/include -I ../lib/common/include \ + ./onyx_runtime.c \ + -lpthread + +echo "Installing onyx_runtime.so" +sudo mv "../bin/onyx_runtime.so" "$CORE_DIR/lib/onyx_runtime.so" diff --git a/runtime/onyx_runtime.c b/runtime/onyx_runtime.c new file mode 100644 index 00000000..88a13553 --- /dev/null +++ b/runtime/onyx_runtime.c @@ -0,0 +1,1444 @@ + +#define BH_DEFINE +#include "bh.h" + +#define ONYX_LIBRARY_NAME onyx_runtime +#define ONYX_NO_SHORT_NAMES +#include "onyx_library.h" + +#ifdef _BH_LINUX + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#endif + +#include "types.h" // For POINTER_SIZE + +#define ONYX_FILE_ERROR_NONE 0 +#define ONYX_FILE_ERROR_NOT_FOUND 1 +#define ONYX_FILE_ERROR_EXISTS 2 +#define ONYX_FILE_ERROR_PERMISSION 3 +#define ONYX_FILE_ERROR_BAD_FILE 4 +#define ONYX_FILE_ERROR_BAD_MODE 5 + +ONYX_DEF(__file_open_impl, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + char *path_ptr = ONYX_PTR(params->data[0].of.i32); + int path_len = params->data[1].of.i32; + + char path[512] = {0}; + path_len = bh_min(path_len, 511); + strncpy(path, path_ptr, path_len); + path[path_len] = 0; + + int mode = params->data[2].of.i32; + + bh_file_mode bh_mode; + switch (mode) { + case 1: bh_mode = BH_FILE_MODE_READ; break; + case 2: bh_mode = BH_FILE_MODE_WRITE; break; + case 3: bh_mode = BH_FILE_MODE_APPEND; break; + } + + bh_file file; + bh_file_error error = bh_file_open_mode(&file, bh_mode, path); + if (error == BH_FILE_ERROR_INVALID) { + results->data[0] = WASM_I32_VAL(ONYX_FILE_ERROR_NOT_FOUND); + return NULL; + } + + *(u64 *) ONYX_PTR(params->data[3].of.i32) = (u64) file.fd; + results->data[0] = WASM_I32_VAL(ONYX_FILE_ERROR_NONE); + return NULL; +} + +ONYX_DEF(__file_close, (WASM_I64), (WASM_I32)) { + i64 fd = params->data[0].of.i64; + + bh_file file = { (bh_file_descriptor) fd }; + bh_file_error error = bh_file_close(&file); + if (error == BH_FILE_ERROR_INVALID) { + results->data[0] = WASM_I32_VAL(ONYX_FILE_ERROR_NOT_FOUND); + return NULL; + } + + results->data[0] = WASM_I32_VAL(ONYX_FILE_ERROR_NONE); + return NULL; +} + +ONYX_DEF(__file_exists, (WASM_I32, WASM_I32), (WASM_I32)) { + char *path_ptr = ONYX_PTR(params->data[0].of.i32); + int path_len = params->data[1].of.i32; + + char path[512] = {0}; + path_len = bh_min(path_len, 511); + strncpy(path, path_ptr, path_len); + path[path_len] = 0; + + results->data[0] = WASM_I32_VAL(bh_file_exists(path)); + return NULL; +} + +ONYX_DEF(__file_stat, (WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + char *path_ptr = ONYX_PTR(params->data[0].of.i32); + int path_len = params->data[1].of.i32; + + char path[512] = {0}; + path_len = bh_min(path_len, 511); + strncpy(path, path_ptr, path_len); + path[path_len] = 0; + + results->data[0] = WASM_I32_VAL(bh_file_stat(path, ONYX_PTR(params->data[2].of.i32))); + return NULL; +} + +ONYX_DEF(__file_remove, (WASM_I32, WASM_I32), (WASM_I32)) { + char *path_ptr = ONYX_PTR(params->data[0].of.i32); + int path_len = params->data[1].of.i32; + + char path[512] = {0}; + path_len = bh_min(path_len, 511); + strncpy(path, path_ptr, path_len); + path[path_len] = 0; + + results->data[0] = WASM_I32_VAL(bh_file_remove(path)); + return NULL; +} + +ONYX_DEF(__file_seek, (WASM_I64, WASM_I32, WASM_I32), (WASM_I32)) { + i64 fd = params->data[0].of.i64; + i32 offset = params->data[1].of.i32; + i32 whence = params->data[2].of.i32; + + bh_file file = { (bh_file_descriptor) fd }; + bh_file_whence bh_whence; + switch (whence) { + case 0: bh_whence = BH_FILE_WHENCE_BEGIN; break; + case 1: bh_whence = BH_FILE_WHENCE_CURRENT; break; + case 2: bh_whence = BH_FILE_WHENCE_END; break; + } + + i64 new_offset = bh_file_seek(&file, offset, whence); + results->data[0] = WASM_I32_VAL((i32) new_offset); + return NULL; +} + +ONYX_DEF(__file_tell, (WASM_I64), (WASM_I32)) { + i64 fd = params->data[0].of.i64; + bh_file file = { (bh_file_descriptor) fd }; + results->data[0] = WASM_I32_VAL(bh_file_tell(&file)); + return NULL; +} + +ONYX_DEF(__file_read, (WASM_I64, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + i64 fd = params->data[0].of.i64; + bh_file file = { (bh_file_descriptor) fd }; + + i32 curr_pos = bh_file_tell(&file); + b32 success = bh_file_read_at(&file, + bh_file_tell(&file), + ONYX_PTR(params->data[1].of.i32), + params->data[2].of.i32, + (i64 *) ONYX_PTR(params->data[3].of.i32)); + + bh_file_seek_to(&file, curr_pos + *(i32 *) ONYX_PTR(params->data[3].of.i32)); + + results->data[0] = WASM_I32_VAL(0); + if (!success) results->data[0] = WASM_I32_VAL(2); + return NULL; +} + +ONYX_DEF(__file_write, (WASM_I64, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + i64 fd = params->data[0].of.i64; + bh_file file = { (bh_file_descriptor) fd }; + + i32 curr_pos = bh_file_tell(&file); + b32 success = bh_file_write_at(&file, + bh_file_tell(&file), + ONYX_PTR(params->data[1].of.i32), + params->data[2].of.i32, + (i64 *) ONYX_PTR(params->data[3].of.i32)); + + bh_file_seek_to(&file, curr_pos + *(i32 *) ONYX_PTR(params->data[3].of.i32)); + + results->data[0] = WASM_I32_VAL(0); + if (!success) results->data[0] = WASM_I32_VAL(2); + return NULL; +} + +ONYX_DEF(__file_flush, (WASM_I64), (WASM_I32)) { + i64 fd = params->data[0].of.i64; + bh_file file = { (bh_file_descriptor) fd }; + bh_file_flush(&file); + results->data[0] = WASM_I32_VAL(0); + return NULL; +} + +ONYX_DEF(__file_size, (WASM_I64), (WASM_I32)) { + i64 fd = params->data[0].of.i64; + bh_file file = { (bh_file_descriptor) fd }; + results->data[0] = WASM_I32_VAL(bh_file_size(&file)); + return NULL; +} + +ONYX_DEF(__file_get_standard, (WASM_I32, WASM_I32), (WASM_I32)) { + bh_file_standard standard = (bh_file_standard) params->data[0].of.i32; + + bh_file file; + bh_file_error error = bh_file_get_standard(&file, standard); + if (error == BH_FILE_ERROR_NONE) { + *(u64 *) ONYX_PTR(params->data[1].of.i32) = (u64) file.fd; + } + + results->data[0] = WASM_I32_VAL(error == BH_FILE_ERROR_NONE); + return NULL; +} + +ONYX_DEF(__file_rename, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + char *old_path_ptr = ONYX_PTR(params->data[0].of.i32); + int old_path_len = params->data[1].of.i32; + + char old_path[512] = {0}; + old_path_len = bh_min(old_path_len, 511); + strncpy(old_path, old_path_ptr, old_path_len); + old_path[old_path_len] = 0; + + char *new_path_ptr = ONYX_PTR(params->data[2].of.i32); + int new_path_len = params->data[3].of.i32; + + char new_path[512] = {0}; + new_path_len = bh_min(new_path_len, 511); + strncpy(new_path, new_path_ptr, new_path_len); + new_path[new_path_len] = 0; + +#ifdef _BH_WINDOWS + results->data[0] = WASM_I32_VAL(MoveFileA(old_path, new_path)); + return NULL; +#endif + +#ifdef _BH_LINUX + results->data[0] = WASM_I32_VAL(rename(old_path, new_path) == 0); + return NULL; +#endif +} + +// +// Directories +// + +ONYX_DEF(__dir_open, (WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + char *path_ptr = ONYX_PTR(params->data[0].of.i32); + int path_len = params->data[1].of.i32; + + char path[512] = {0}; + path_len = bh_min(path_len, 511); + strncpy(path, path_ptr, path_len); + path[path_len] = 0; + +#ifdef _BH_WINDOWS + for (int i=0; ihndl = FindFirstFileA(path, &dir->found_file); + if (dir->hndl == INVALID_HANDLE_VALUE) { + results->data[0] = WASM_I32_VAL(0); + return NULL; + } + + *(u64 *) ONYX_PTR(params->data[2].of.i32) = (u64) dir; + + results->data[0] = WASM_I32_VAL(1); + return NULL; +#endif + +#ifdef _BH_LINUX + DIR* dir = opendir(path); + *(u64 *) ONYX_PTR(params->data[2].of.i32) = (u64) dir; + results->data[0] = WASM_I32_VAL(dir != NULL); + return NULL; +#endif +} + +// (DIR*, PTR) -> BOOL +ONYX_DEF(__dir_read, (WASM_I64, WASM_I32), (WASM_I32)) { +#ifdef _BH_WINDOWS + Windows_Directory_Opened* dir = (Windows_Directory_Opened *) params->data[0].of.i64; + if (dir == NULL) { + results->data[0] = WASM_I32_VAL(0); + return NULL; + } + + do { + BOOL success = FindNextFileA(dir->hndl, &dir->found_file); + if (!success) { + results->data[0] = WASM_I32_VAL(0); + return NULL; + } + } while (!strcmp(dir->found_file.cFileName, ".") || !strcmp(dir->found_file.cFileName, "..")); + + u32 out = params->data[1].of.i32; + assert(out != 0); + + *(u32 *) ONYX_PTR(out + 0) = (dir->found_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 3 : 4; + *(u32 *) ONYX_PTR(out + 4) = 0; + *(u32 *) ONYX_PTR(out + 8) = strlen(dir->found_file.cFileName); + strncpy(ONYX_PTR(out + 12), dir->found_file.cFileName, 256); + + results->data[0] = WASM_I32_VAL(1); + return NULL; +#endif + +#ifdef _BH_LINUX + DIR* dir = (DIR *) params->data[0].of.i64; + if (dir == NULL) { + results->data[0] = WASM_I32_VAL(0); + return NULL; + } + + struct dirent *ent; + while (1) { + ent = readdir(dir); + if (ent == NULL) { + results->data[0] = WASM_I32_VAL(0); + return NULL; + } + + // Skip the current directory and parent directory + if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) break; + } + + u32 type = 0; + switch (ent->d_type) { + case DT_UNKNOWN: break; + case DT_BLK: type = 1; break; + case DT_CHR: type = 2; break; + case DT_DIR: type = 3; break; + case DT_LNK: type = 5; break; + case DT_REG: type = 4; break; + default: type = 6; break; + } + + u32 out = params->data[1].of.i32; + assert(out != 0); + + *(u32 *) ONYX_PTR(out + 0) = type; + *(u32 *) ONYX_PTR(out + 4) = (u32) ent->d_ino; + *(u32 *) ONYX_PTR(out + 8) = strlen(ent->d_name); + strncpy(ONYX_PTR(out + 12), ent->d_name, 256); + + results->data[0] = WASM_I32_VAL(1); + return NULL; +#endif +} + +ONYX_DEF(__dir_close, (WASM_I64), ()) { +#ifdef _BH_WINDOWS + Windows_Directory_Opened* dir = (Windows_Directory_Opened *) params->data[0].of.i64; + + FindClose(dir->hndl); + free(dir); + + return NULL; +#endif + +#ifdef _BH_LINUX + DIR* dir = (DIR *) params->data[0].of.i64; + if (dir == NULL) return NULL; + + closedir(dir); +#endif + return NULL; +} + +ONYX_DEF(__dir_create, (WASM_I32, WASM_I32), (WASM_I32)) { + char *path_ptr = ONYX_PTR(params->data[0].of.i32); + int path_len = params->data[1].of.i32; + + char path[512] = {0}; + path_len = bh_min(path_len, 511); + strncpy(path, path_ptr, path_len); + path[path_len] = 0; + +#ifdef _BH_WINDOWS + results->data[0] = WASM_I32_VAL(CreateDirectoryA(path)); + return NULL; +#endif + +#ifdef _BH_LINUX + results->data[0] = WASM_I32_VAL(mkdir(path, 0777) == 0); + return NULL; +#endif +} + +ONYX_DEF(__dir_remove, (WASM_I32, WASM_I32), (WASM_I32)) { + char *path_ptr = ONYX_PTR(params->data[0].of.i32); + int path_len = params->data[1].of.i32; + + char path[512] = {0}; + path_len = bh_min(path_len, 511); + strncpy(path, path_ptr, path_len); + path[path_len] = 0; + +#ifdef _BH_WINDOWS + results->data[0] = WASM_I32_VAL(RemoveDirectoryA(path)); + return NULL; +#endif + +#ifdef _BH_LINUX + results->data[0] = WASM_I32_VAL(rmdir(path) == 0); + return NULL; +#endif +} + +// +// THREADS +// +typedef struct OnyxThread { + i32 id; + i32 tls_base; + i32 funcidx; + i32 dataptr; + wasm_instance_t* instance; + + #ifdef _BH_LINUX + pthread_t thread; + #endif + + #ifdef _BH_WINDOWS + HANDLE thread_handle; + i32 thread_id; + #endif +} OnyxThread; + +static bh_arr(OnyxThread) threads = NULL; + +#ifdef _BH_LINUX +static void *onyx_run_thread(void *data) { +#endif +#ifdef _BH_WINDOWS +static i32 onyx_run_thread(void *data) { +#endif + OnyxThread *thread = (OnyxThread *) data; + + wasm_store_t *wasm_store = runtime->wasm_store_new(runtime->wasm_engine); + + wasm_trap_t* traps = NULL; + thread->instance = runtime->wasm_instance_new(wasm_store, runtime->wasm_module, &runtime->wasm_imports, &traps); + + wasm_extern_t* start_extern = runtime->wasm_extern_lookup_by_name(runtime->wasm_module, thread->instance, "_thread_start"); + wasm_func_t* start_func = runtime->wasm_extern_as_func(start_extern); + + wasm_extern_t* exit_extern = runtime->wasm_extern_lookup_by_name(runtime->wasm_module, thread->instance, "_thread_exit"); + wasm_func_t* exit_func = runtime->wasm_extern_as_func(exit_extern); + + wasm_trap_t* trap=NULL; + + // NOTE: This is cached in a local variable because there is a tiny chance that if a lot of threads are created + // then the backing array for the thread handles will move and thread* we have well not be valid. I'm betting on this + // not happening before now in the function; however, it *could* happen before we call thread_exit. This would be bad + // because of the normal reasons why accessing memory that you don't own any more is bad. + i32 thread_id = thread->id; + + { // Call the _thread_start procedure + wasm_val_t args[] = { WASM_I32_VAL(thread_id), WASM_I32_VAL(thread->tls_base), WASM_I32_VAL(thread->funcidx), WASM_I32_VAL(thread->dataptr) }; + wasm_val_vec_t results = { 0, 0 }; + wasm_val_vec_t args_array = WASM_ARRAY_VEC(args); + + trap = runtime->wasm_func_call(start_func, &args_array, &results); + if (trap != NULL) { + bh_printf("THREAD: %d\n", thread_id); + runtime->onyx_print_trap(trap); + } + } + + { // Call the _thread_exit procedure + wasm_val_t args[] = { WASM_I32_VAL(thread_id) }; + wasm_val_vec_t results = { 0, 0 }; + wasm_val_vec_t args_array = WASM_ARRAY_VEC(args); + + trap = runtime->wasm_func_call(exit_func, &args_array, &results); + } + + runtime->wasm_store_delete(wasm_store); + + return 0; +} + +ONYX_DEF(__spawn_thread, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + if (threads == NULL) bh_arr_new(bh_heap_allocator(), threads, 128); + bh_arr_insert_end(threads, 1); + OnyxThread *thread = &bh_arr_last(threads); + + thread->id = params->data[0].of.i32; + thread->tls_base = params->data[1].of.i32; + thread->funcidx = params->data[2].of.i32; + thread->dataptr = params->data[3].of.i32; + + #ifdef _BH_LINUX + pthread_create(&thread->thread, NULL, onyx_run_thread, thread); + #endif + + #ifdef _BH_WINDOWS + // thread->thread_handle = CreateThread(NULL, 0, onyx_run_thread, thread, 0, &thread->thread_id); + thread->thread_handle = (HANDLE) _beginthreadex(NULL, 0, onyx_run_thread, thread, 0, &thread->thread_id); + #endif + + results->data[0] = WASM_I32_VAL(1); + return NULL; +} + +ONYX_DEF(__kill_thread, (WASM_I32), (WASM_I32)) { + i32 thread_id = params->data[0].of.i32; + + i32 i = 0; + bh_arr_each(OnyxThread, thread, threads) { + if (thread->id == thread_id) { + #ifdef _BH_LINUX + // This leads to some weirdness and bugs... + // + pthread_kill(thread->thread, SIGKILL); + #endif + + #ifdef _BH_WINDOWS + TerminateThread(thread->thread_handle, 0); + CloseHandle(thread->thread_handle); + #endif + + bh_arr_deleten(threads, i, 1); + results->data[0] = WASM_I32_VAL(1); + return NULL; + } + + i++; + } + + results->data[0] = WASM_I32_VAL(0); + return NULL; +} + + + + +// +// PROCESS +// +#define ONYX_PROCESS_MAGIC_NUMBER 0xdeadfadebabecafe +typedef struct OnyxProcess { + u64 magic_number; + +#ifdef _BH_LINUX + // Pipes + i32 proc_to_host[2]; + i32 host_to_proc[2]; + + pid_t pid; +#endif + +#ifdef _BH_WINDOWS + HANDLE proc_to_host_read; + HANDLE proc_to_host_write; + HANDLE host_to_proc_read; + HANDLE host_to_proc_write; + + PROCESS_INFORMATION proc_info; +#endif +} OnyxProcess; + +ONYX_DEF(__process_spawn, (WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I64)) { + char* process_str = ONYX_PTR(params->data[0].of.i32); + i32 process_len = params->data[1].of.i32; + i32 args_ptr = params->data[2].of.i32; + i32 args_len = params->data[3].of.i32; + b32 blocking_io = !params->data[4].of.i32; + char *cwd_str = ONYX_PTR(params->data[5].of.i32); + i32 cwd_len = params->data[6].of.i32; + + char process_path[1024]; + process_len = bh_min(1023, process_len); + memcpy(process_path, process_str, process_len); + process_path[process_len] = '\0'; + + char starting_dir[1024]; + if (cwd_len > 0) { + cwd_len = bh_min(1023, cwd_len); + memcpy(starting_dir, cwd_str, cwd_len); + starting_dir[cwd_len] = '\0'; + } + + OnyxProcess *process = malloc(sizeof(OnyxProcess)); + memset(process, 0, sizeof(*process)); + process->magic_number = ONYX_PROCESS_MAGIC_NUMBER; + + #ifdef _BH_LINUX + // :Security - alloca a user controlled string. + char **process_args = alloca(sizeof(char *) * (args_len + 2)); + byte_t* array_loc = ONYX_PTR(args_ptr); + fori (i, 0, args_len) { + char *arg_str = ONYX_PTR(*(i32 *) (array_loc + i * 2 * POINTER_SIZE)); + i32 arg_len = *(i32 *) (array_loc + i * 2 * POINTER_SIZE + POINTER_SIZE); + + char *arg = alloca(sizeof(char) * (arg_len + 1)); + memcpy(arg, arg_str, arg_len); + arg[arg_len] = '\0'; + process_args[i + 1] = arg; + } + process_args[0] = process_path; + process_args[args_len + 1] = NULL; + + if (pipe(process->proc_to_host) || pipe(process->host_to_proc)) { + wasm_val_init_ptr(&results->data[0], NULL); // Failed to run + return NULL; + } + + pid_t pid; + switch (pid = fork()) { + case -1: // Bad fork + wasm_val_init_ptr(&results->data[0], NULL); // Failed to run + + close(process->proc_to_host[0]); + close(process->proc_to_host[1]); + close(process->host_to_proc[0]); + close(process->host_to_proc[1]); + break; + + case 0: // Child process + close(process->proc_to_host[0]); + close(process->host_to_proc[1]); + dup2(process->host_to_proc[0], 0); // Map the output to the pipe + dup2(process->proc_to_host[1], 1); // Map the output to the pipe + dup2(process->proc_to_host[1], 2); // Stderr to stdout + + if (!blocking_io) { + fcntl(0, F_SETFL, O_NONBLOCK); + fcntl(1, F_SETFL, O_NONBLOCK); + } + + if (cwd_len > 0) { + chdir(starting_dir); // Switch current working directory. + } + + execvp(process_path, process_args); + exit(-1); + break; + + default: { + process->pid = pid; + close(process->host_to_proc[0]); + close(process->proc_to_host[1]); + + wasm_val_init_ptr(&results->data[0], process); + break; + } + } + + #endif + + #ifdef _BH_WINDOWS + // CLEANUP CLEANUP CLEANUP: This is so freaking bad... + char cmdLine[2048]; + memset(cmdLine, 0, 2048); + strncat(cmdLine, "\"", 2047); + strncat(cmdLine, process_path, 2047); + strncat(cmdLine, "\"", 2047); + + byte_t* array_loc = ONYX_PTR(args_ptr); + fori (i, 0, args_len) { + char *arg_str = ONYX_PTR(*(i32 *) (array_loc + i * 2 * POINTER_SIZE)); + i32 arg_len = *(i32 *) (array_loc + i * 2 * POINTER_SIZE + 4); + + strncat(cmdLine, " \"", 2047); + strncat(cmdLine, arg_str, arg_len); + strncat(cmdLine, "\"", 2047); + } + + STARTUPINFOA startup; + memset(&startup, 0, sizeof startup); + startup.cb = sizeof(startup); + + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.lpSecurityDescriptor = NULL; + saAttr.bInheritHandle = 1; + + i32 success = 1; + success = success && CreatePipe(&process->host_to_proc_read, &process->host_to_proc_write, &saAttr, 4096); + success = success && CreatePipe(&process->proc_to_host_read, &process->proc_to_host_write, &saAttr, 4096); + if (!success) { + // printf("FAILED TO CREATE PIPES: %d\n", GetLastError()); + wasm_val_init_ptr(&results->data[0], NULL); // Failed to run @LEAK + return NULL; + } + + success = SetHandleInformation(process->proc_to_host_read, 1 /* HANDLE_FLAG_INHERIT */, 0); + success = success && SetHandleInformation(process->host_to_proc_write, 1 /* HANDLE_FLAG_INHERIT */, 0); + if (!success) { + // printf("FAILED TO CONFIGURE PIPES: %d\n", GetLastError()); + wasm_val_init_ptr(&results->data[0], NULL); // Failed to run @LEAK + return NULL; + } + + startup.hStdInput = process->host_to_proc_read; + startup.hStdOutput = process->proc_to_host_write; + startup.hStdError = process->proc_to_host_write; + + startup.dwFlags |= STARTF_USESTDHANDLES; + + memset(&process->proc_info, 0, sizeof process->proc_info); + + char *working_dir = NULL; + if (cwd_len > 0) { + working_dir = starting_dir; + } + + success = CreateProcessA(process_path, cmdLine, &saAttr, &saAttr, 1, 0, NULL, working_dir, &startup, &process->proc_info); + if (!success) { + printf("FAILED TO CREATE PROCESS: %d\n", GetLastError()); + wasm_val_init_ptr(&results->data[0], NULL); // Failed to run @LEAK + return NULL; + } + + CloseHandle(process->proc_to_host_write); + CloseHandle(process->host_to_proc_read); + wasm_val_init_ptr(&results->data[0], process); + #endif + + return NULL; +} + +ONYX_DEF(__process_read, (WASM_I64, WASM_I32, WASM_I32), (WASM_I32)) { + OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; + if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { + results->data[0] = WASM_I32_VAL(0); + return NULL; + } + + i32 output_ptr = params->data[1].of.i32; + i32 output_len = params->data[2].of.i32; + u8 *buffer = ONYX_PTR(output_ptr); + + i32 bytes_read; + #ifdef _BH_LINUX + bytes_read = read(process->proc_to_host[0], buffer, output_len); + if (bytes_read < 0) { + switch (errno) { + case EAGAIN: bytes_read = 0; break; + case EBADF: bytes_read = -1; break; + default: bytes_read = -2; break; + } + } + #endif + + #ifdef _BH_WINDOWS + i32 success = ReadFile(process->proc_to_host_read, buffer, output_len, &bytes_read, NULL); + if (!success) { + bytes_read = -1; + } + #endif + + results->data[0] = WASM_I32_VAL(bytes_read); + return NULL; +} + +ONYX_DEF(__process_write, (WASM_I64, WASM_I32, WASM_I32), (WASM_I32)) { + OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; + if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { + results->data[0] = WASM_I32_VAL(0); + return NULL; + } + + i32 input_ptr = params->data[1].of.i32; + i32 input_len = params->data[2].of.i32; + u8 *buffer = ONYX_PTR(input_ptr); + + i32 bytes_written; + #ifdef _BH_LINUX + bytes_written = write(process->host_to_proc[1], buffer, input_len); + bytes_written = bh_max(bytes_written, 0); // Silently consume errors + #endif + + #ifdef _BH_WINDOWS + i32 success = WriteFile(process->host_to_proc_write, buffer, input_len, &bytes_written, NULL); + if (!success) bytes_written = 0; + #endif + + results->data[0] = WASM_I32_VAL(bytes_written); + return NULL; +} + +ONYX_DEF(__process_kill, (WASM_I64), (WASM_I32)) { + OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; + if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { + results->data[0] = WASM_I32_VAL(0); + return NULL; + } + + #ifdef _BH_LINUX + i32 failed = kill(process->pid, SIGKILL); + results->data[0] = WASM_I32_VAL(!failed); + #endif + + #ifdef _BH_WINDOWS + i32 success = TerminateProcess(process->proc_info.hProcess, 1); + results->data[0] = WASM_I32_VAL(success ? 1 : 0); + #endif + + return NULL; +} + +ONYX_DEF(__process_wait, (WASM_I64), (WASM_I32)) { + OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; + if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { + results->data[0] = WASM_I32_VAL(1); + return NULL; + } + + #ifdef _BH_LINUX + i32 status; + waitpid(process->pid, &status, 0); + + i32 exit_code = WEXITSTATUS(status); + results->data[0] = WASM_I32_VAL(exit_code != 0 ? 2 : 0); + #endif + + #ifdef _BH_WINDOWS + DWORD exitCode; + while (1) { + if (!WaitForSingleObject(process->proc_info.hProcess, INFINITE)) { + // HACK HACK HACK + DWORD error = GetLastError(); + if (error != 109 && error != 6) { + // printf("ERROR IN WAIT FOR SINGLE: %d\n", error); + results->data[0] = WASM_I32_VAL(1); + return NULL; + } + } + + if (!GetExitCodeProcess(process->proc_info.hProcess, &exitCode)) { + // HACK HACK HACK + // Apparently, I'm doing something wrong (maybe?) where the process handle becomes + // invalid and causes error 6 "invalid handle". So I think I can safely assume that + // if that is the case, then the process exited? probably successfuly? hopefully? + // Honestly I don't know and I can't find any documentation describing when a process + // handle goes invalid, other than after you close it explicitly. But in the run_tests + // script, I'm not calling either process_kill or process_destroy, which are the only + // other functions that close the process handle. So I'm left in the dark as to why this + // is happening, but oh well. This works for now. + // - brendanfh 2021/12/03 + if (GetLastError() == 6) { + exitCode = 0; + break; + } + + results->data[0] = WASM_I32_VAL(3); + return NULL; + } + + // 259 is STILL_ACTIVE (aka STATUS_PENDING), which means that the process has not yet exited + if (exitCode != 259) break; + } + + results->data[0] = WASM_I32_VAL(exitCode != 0 ? 2 : 0); + #endif + + return NULL; +} + +ONYX_DEF(__process_destroy, (WASM_I64), ()) { + OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; + if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { + return NULL; + } + + #ifdef _BH_LINUX + close(process->proc_to_host[0]); + close(process->host_to_proc[1]); + #endif + + #ifdef _BH_WINDOWS + if (!CloseHandle(process->proc_info.hThread) + || !CloseHandle(process->proc_info.hProcess)) { + // printf("ERROR CLOSING HANDLES: %d\n", GetLastError()); + } + #endif + + free(process); + + return NULL; +} + +ONYX_DEF(__args_get, (WASM_I32, WASM_I32), ()) { + if (runtime->argc == 0 || runtime->argv == NULL) { + return NULL; + } + + i32 buffer_base = params->data[1].of.i32; + + for (int i=0; iargc; i++) { + // Should this be strncpy? What would the length be? + strcpy(ONYX_PTR(buffer_base), runtime->argv[i]); + *(i32 *) ONYX_PTR(params->data[0].of.i32 + i * 4) = buffer_base; + buffer_base += strlen(runtime->argv[i]) + 1; + } + + return NULL; +} + +ONYX_DEF(__args_sizes_get, (WASM_I32, WASM_I32), ()) { + *(i32 *) ONYX_PTR(params->data[0].of.i32) = runtime->argc; + + i32 argv_size = 0; + for (int i=0; iargc; i++) { + argv_size += strlen(runtime->argv[i]) + 1; + } + + *(i32 *) ONYX_PTR(params->data[1].of.i32) = argv_size; + return NULL; +} + +ONYX_DEF(__exit, (WASM_I32), ()) { + exit(params->data[0].of.i32); + return NULL; +} + +ONYX_DEF(__sleep, (WASM_I32), ()) { + #ifdef _BH_LINUX + usleep(params->data[0].of.i32 * 1000); + #endif + + #ifdef _BH_WINDOWS + Sleep(params->data[0].of.i32); + #endif + return NULL; +} + +ONYX_DEF(__time, (), (WASM_I64)) { + results->data[0] = WASM_I64_VAL(bh_time_curr()); + return NULL; +} + + + + +// +// Dates and Times +// +ONYX_DEF(__time_localtime, (WASM_I64, WASM_I32), ()) { + u64 t = params->data[0].of.i64; + *(struct tm *) ONYX_PTR(params->data[1].of.i32) = *localtime(&t); + return NULL; +} + +ONYX_DEF(__time_gmtime, (WASM_I64, WASM_I32), ()) { + u64 t = params->data[0].of.i64; + *(struct tm *) ONYX_PTR(params->data[1].of.i32) = *gmtime(&t); + return NULL; +} + +ONYX_DEF(__time_strftime, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + u32 len = strftime(ONYX_PTR(params->data[0].of.i32), params->data[1].of.i32, ONYX_PTR(params->data[2].of.i32), ONYX_PTR(params->data[3].of.i32)); + results->data[0] = WASM_I32_VAL(len); + return NULL; +} + +ONYX_DEF(__time_strptime, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + #if defined(_BH_LINUX) + char *rem = strptime(ONYX_PTR(params->data[0].of.i32), params->data[1].of.i32, ONYX_PTR(params->data[2].of.i32), ONYX_PTR(params->data[3].of.i32)); + results->data[0] = WASM_I32_VAL(rem != NULL); + #else + results->data[0] = WASM_I32_VAL(0); + #endif + return NULL; +} + + + +// +// Networking +// +struct onyx_socket_addr { + unsigned short family; + unsigned short port; + unsigned int addr; +}; + +static inline int onyx_socket_domain(int i) { + #if defined(_BH_LINUX) + switch (i) { // :EnumDependent + case 0: return AF_UNIX; + case 1: return AF_INET; + case 2: return AF_INET6; + default: return -1; + } + #elif defined(_BH_WINDOWS) + return -1; + #endif +} + +static inline int onyx_socket_protocol(int i) { + #if defined(_BH_LINUX) + switch (i) { // :EnumDependent + case 0: return SOCK_STREAM; + case 1: return SOCK_DGRAM; + default: return -1; + } + #elif defined(_BH_WINDOWS) + return -1; + #endif +} + +ONYX_DEF(__net_create_socket, (WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + + #ifdef _BH_LINUX + int domain = onyx_socket_domain(params->data[1].of.i32); + if (domain == -1) goto bad_settings; + + int type = onyx_socket_protocol(params->data[2].of.i32); + if (type == -1) goto bad_settings; + + *((int *) ONYX_PTR(params->data[0].of.i32)) = socket(domain, type, 0); + + results->data[0] = WASM_I32_VAL(0); + return NULL; + #endif + + #ifdef _BH_WINDOWS + #endif + +bad_settings: + results->data[0] = WASM_I32_VAL(1); // :EnumDependent + return NULL; +} + +ONYX_DEF(__net_close_socket, (WASM_I32), ()) { + #ifdef _BH_LINUX + shutdown(params->data[0].of.i32, SHUT_RDWR); + close(params->data[0].of.i32); + #endif + + #ifdef _BH_WINDOWS + #endif + + return NULL; +} + +ONYX_DEF(__net_setting, (WASM_I32, WASM_I32, WASM_I32), ()) { + #ifdef _BH_LINUX + switch (params->data[1].of.i32) { + case 1: { // :EnumDependent Non-Blocking + int s = params->data[0].of.i32; + int flags = fcntl(s, F_GETFL, 0); + if (params->data[2].of.i32) { + flags |= O_NONBLOCK; + } else { + flags &= ~O_NONBLOCK; + } + + fcntl(s, F_SETFL, flags); + break; + } + + case 2: { // :EnumDependent Broadcast + int s = params->data[0].of.i32; + setsockopt(s, SOL_SOCKET, SO_BROADCAST, (void *) ¶ms->data[2].of.i32, sizeof(int)); + break; + } + } + #endif + + return NULL; +} + +ONYX_DEF(__net_bind, (WASM_I32, WASM_I32), (WASM_I32)) { + + #ifdef _BH_LINUX + int res = -1; + + struct onyx_socket_addr *oaddr = (void *) ONYX_PTR(params->data[1].of.i32); + int family = onyx_socket_domain(oaddr->family); + int port = oaddr->port; + + switch (family) { + case AF_INET: { + struct sockaddr_in bind_addr; + memset(&bind_addr, 0, sizeof(bind_addr)); + + bind_addr.sin_family = AF_INET; + bind_addr.sin_addr.s_addr = htonl(oaddr->addr); + bind_addr.sin_port = htons(port); + + res = bind(params->data[0].of.i32, &bind_addr, sizeof(bind_addr)); + break; + } + + case AF_INET6: { + struct sockaddr_in6 bind_addr; + memset(&bind_addr, 0, sizeof(bind_addr)); + + bind_addr.sin6_family = AF_INET6; + memcpy(&bind_addr.sin6_addr.s6_addr, (void *) &oaddr->addr, 16); + bind_addr.sin6_port = htons(port); + + res = bind(params->data[0].of.i32, &bind_addr, sizeof(bind_addr)); + break; + } + + case AF_UNIX: { + struct sockaddr_un bind_addr; + memset(&bind_addr, 0, sizeof(bind_addr)); + + bind_addr.sun_family = AF_UNIX; + strncpy(&bind_addr.sun_path, (char *) &oaddr->addr, 108); + + res = bind(params->data[0].of.i32, &bind_addr, sizeof(bind_addr)); + break; + } + } + + results->data[0] = WASM_I32_VAL(res >= 0); + #endif + + #ifdef _BH_WINDOWS + #endif + + return NULL; +} + +ONYX_DEF(__net_listen, (WASM_I32, WASM_I32), ()) { + #ifdef _BH_LINUX + listen(params->data[0].of.i32, params->data[1].of.i32); + #endif + + #ifdef _BH_WINDOWS + #endif + + return NULL; +} + +ONYX_DEF(__net_accept, (WASM_I32, WASM_I32), (WASM_I32)) { + #ifdef _BH_LINUX + struct sockaddr_in client_addr; + int client_len = sizeof(client_addr); + memset(&client_addr, 0, client_len); + + int client_socket = accept(params->data[0].of.i32, &client_addr, &client_len); + + struct onyx_socket_addr* out_addr = (struct onyx_socket_addr *) ONYX_PTR(params->data[1].of.i32); + out_addr->family = client_addr.sin_family; + out_addr->port = ntohs(client_addr.sin_port); + out_addr->addr = ntohl(client_addr.sin_addr.s_addr); + + results->data[0] = WASM_I32_VAL(client_socket); + #endif + + return NULL; +} + +ONYX_DEF(__net_connect_unix, (WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + #ifdef _BH_LINUX + int hostlen = params->data[2].of.i32; + char *hostname = alloca(hostlen + 1); + memcpy(hostname, ONYX_PTR(params->data[1].of.i32), hostlen); + hostname[hostlen] = '\0'; + + struct sockaddr_un server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + + server_addr.sun_family = AF_UNIX; // See comment above + memcpy((char *)&server_addr.sun_path, hostname, hostlen); + + int result = connect(params->data[0].of.i32, &server_addr, sizeof(server_addr)); + if (result == 0) results->data[0] = WASM_I32_VAL(0); + else results->data[0] = WASM_I32_VAL(3); // :EnumDependent + #endif + + return NULL; +} + +ONYX_DEF(__net_connect_ipv4, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + #ifdef _BH_LINUX + int hostlen = params->data[2].of.i32; + char *hostname = alloca(hostlen + 1); + memcpy(hostname, ONYX_PTR(params->data[1].of.i32), hostlen); + hostname[hostlen] = '\0'; + + struct hostent *host; + host = gethostbyname(hostname); // TODO: Replace this call, as it is obselete. + if (host == NULL) { + results->data[0] = WASM_I32_VAL(2); // :EnumDependent + return NULL; + } + + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + + server_addr.sin_family = AF_INET; // See comment above + memcpy((char *)&server_addr.sin_addr.s_addr, (char *)host->h_addr, host->h_length); + server_addr.sin_port = htons(params->data[3].of.i32); + + int result = connect(params->data[0].of.i32, &server_addr, sizeof(server_addr)); + if (result == 0) results->data[0] = WASM_I32_VAL(0); + else results->data[0] = WASM_I32_VAL(3); // :EnumDependent + + return NULL; + #endif + + #ifdef _BH_WINDOWS + #endif +} + +ONYX_DEF(__net_send, (WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + #ifdef _BH_LINUX + // TODO: The flags at the end should be controllable. + int sent = send(params->data[0].of.i32, ONYX_PTR(params->data[1].of.i32), params->data[2].of.i32, MSG_NOSIGNAL); + results->data[0] = WASM_I32_VAL(sent); + #endif + + return NULL; +} + +ONYX_DEF(__net_sendto, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + #ifdef _BH_LINUX + struct sockaddr_in dest_addr; + int dest_addr_len = sizeof(dest_addr); + memset(&dest_addr, 0, dest_addr_len); + + struct onyx_socket_addr *o_addr = (struct onyx_socket_addr *) ONYX_PTR(params->data[3].of.i32); + dest_addr.sin_family = AF_INET; // TODO: See other comments related to AF_NET above. + dest_addr.sin_port = htons(o_addr->port); + dest_addr.sin_addr.s_addr = htonl(o_addr->addr); + + // TODO: The flags at the end should be controllable. + int sent = sendto(params->data[0].of.i32, ONYX_PTR(params->data[1].of.i32), params->data[2].of.i32, MSG_NOSIGNAL, &dest_addr, dest_addr_len); + results->data[0] = WASM_I32_VAL(sent); + #endif + + return NULL; +} + +ONYX_DEF(__net_recv, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + *(i32 *) ONYX_PTR(params->data[3].of.i32) = 0; + + #ifdef _BH_LINUX + // TODO: The flags at the end should be controllable. + int received = recv(params->data[0].of.i32, ONYX_PTR(params->data[1].of.i32), params->data[2].of.i32, 0); + results->data[0] = WASM_I32_VAL(received); + + if (received < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + *(i32 *) ONYX_PTR(params->data[3].of.i32) = 1; + } + } + #endif + + return NULL; +} + +ONYX_DEF(__net_recvfrom, (WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { + *(i32 *) ONYX_PTR(params->data[4].of.i32) = 0; + + #ifdef _BH_LINUX + struct onyx_socket_addr *out_addr = (struct onyx_socket_addr *) ONYX_PTR(params->data[3].of.i32); + + struct sockaddr_in client_addr; + int socket_len = sizeof(client_addr); + memset(&client_addr, 0, socket_len); + + int received = recvfrom(params->data[0].of.i32, ONYX_PTR(params->data[1].of.i32), params->data[2].of.i32, 0, &client_addr, &socket_len); + out_addr->family = client_addr.sin_family; + out_addr->port = ntohs(client_addr.sin_port); + out_addr->addr = ntohl(client_addr.sin_addr.s_addr); + + results->data[0] = WASM_I32_VAL(received); + + if (received < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + *(i32 *) ONYX_PTR(params->data[3].of.i32) = 1; + } + } + #endif + + return NULL; +} + +ONYX_DEF(__net_poll_recv, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), ()) { + #ifdef _BH_LINUX + int i, res, cursor; + struct pollfd* fds; + + fds = alloca(params->data[1].of.i32 * sizeof(struct pollfd)); + + for (i=0; idata[1].of.i32; i++) { + fds[i].fd = *(i32 *) ONYX_PTR(params->data[0].of.i32 + 4 * i); + fds[i].events = POLLIN; + fds[i].revents = 0; + } + + res = poll(fds, params->data[1].of.i32, params->data[2].of.i32); + + for (i=0; idata[1].of.i32; i++) { + *(i32 *) ONYX_PTR(params->data[3].of.i32 + 4 * i) = 0; // NO_CHANGE + + if (fds[i].revents & POLLIN) { + *(i32 *) ONYX_PTR(params->data[3].of.i32 + 4 * i) = 1; // READABLE + } + + if ((fds[i].revents & POLLHUP) + || (fds[i].revents & POLLNVAL) + || (fds[i].revents & POLLERR)) { + *(i32 *) ONYX_PTR(params->data[3].of.i32 + 4 * i) = 2; // CLOSED + } + } + + #endif + + return NULL; +} + +ONYX_DEF(__net_host_to_net_s, (WASM_I32), (WASM_I32)) { + results->data[0] = WASM_I32_VAL(htons(params->data[0].of.i32)); + return NULL; +} + +ONYX_DEF(__net_host_to_net_l, (WASM_I32), (WASM_I32)) { + results->data[0] = WASM_I32_VAL(htonl(params->data[0].of.i32)); + return NULL; +} + +ONYX_DEF(__net_net_to_host_s, (WASM_I32), (WASM_I32)) { + results->data[0] = WASM_I32_VAL(ntohs(params->data[0].of.i32)); + return NULL; +} + +ONYX_DEF(__net_net_to_host_l, (WASM_I32), (WASM_I32)) { + results->data[0] = WASM_I32_VAL(ntohl(params->data[0].of.i32)); + return NULL; +} + + +// +// C-Pointers +// +// These are wildly unsafe and break the core principles of the security +// of WebAssembly, so there should be a way to turn them off! +// +ONYX_DEF(__cptr_make, (WASM_I32), (WASM_I64)) { + wasm_val_init_ptr(&results->data[0], ONYX_PTR(params->data[0].of.i32)); + return NULL; +} + +ONYX_DEF(__cptr_read, (WASM_I64, WASM_I32, WASM_I32), ()) { + memcpy(ONYX_PTR(params->data[1].of.i32), (void *) params->data[0].of.i64, params->data[2].of.i32); + return NULL; +} + +ONYX_DEF(__cptr_read_u8, (WASM_I64), (WASM_I32)) { + results->data[0] = WASM_I32_VAL(*(u8 *) params->data[0].of.i64); + return NULL; +} + +ONYX_DEF(__cptr_read_u16, (WASM_I64), (WASM_I32)) { + results->data[0] = WASM_I32_VAL(*(u16 *) params->data[0].of.i64); + return NULL; +} + +ONYX_DEF(__cptr_read_u32, (WASM_I64), (WASM_I32)) { + results->data[0] = WASM_I32_VAL(*(u32 *) params->data[0].of.i64); + return NULL; +} + +ONYX_DEF(__cptr_read_u64, (WASM_I64), (WASM_I64)) { + results->data[0] = WASM_I64_VAL(*(u64 *) params->data[0].of.i64); + return NULL; +} + +ONYX_DEF(__cptr_extract_str, (WASM_I64, WASM_I32, WASM_I32), (WASM_I32)) { + unsigned int len = strlen((char *) params->data[0].of.i64); + + if (params->data[2].of.i32 != 0) { + strncpy(ONYX_PTR(params->data[1].of.i32), (char *) params->data[0].of.i64, params->data[2].of.i32); + } + + results->data[0] = WASM_I32_VAL(len); + return NULL; +} + +ONYX_LIBRARY { + ONYX_FUNC(__file_open_impl) + ONYX_FUNC(__file_close) + ONYX_FUNC(__file_exists) + ONYX_FUNC(__file_stat) + ONYX_FUNC(__file_remove) + ONYX_FUNC(__file_seek) + ONYX_FUNC(__file_tell) + ONYX_FUNC(__file_read) + ONYX_FUNC(__file_write) + ONYX_FUNC(__file_flush) + ONYX_FUNC(__file_size) + ONYX_FUNC(__file_get_standard) + ONYX_FUNC(__file_rename) + + ONYX_FUNC(__dir_open) + ONYX_FUNC(__dir_read) + ONYX_FUNC(__dir_close) + ONYX_FUNC(__dir_create) + ONYX_FUNC(__dir_remove) + + ONYX_FUNC(__spawn_thread) + ONYX_FUNC(__kill_thread) + + ONYX_FUNC(__process_spawn) + ONYX_FUNC(__process_read) + ONYX_FUNC(__process_write) + ONYX_FUNC(__process_kill) + ONYX_FUNC(__process_wait) + ONYX_FUNC(__process_destroy) + + ONYX_FUNC(__args_get) + ONYX_FUNC(__args_sizes_get) + + ONYX_FUNC(__exit) + ONYX_FUNC(__sleep) + ONYX_FUNC(__time) + + ONYX_FUNC(__time_localtime) + ONYX_FUNC(__time_gmtime) + ONYX_FUNC(__time_strftime) + ONYX_FUNC(__time_strptime) + + ONYX_FUNC(__net_create_socket) + ONYX_FUNC(__net_close_socket) + ONYX_FUNC(__net_setting) + ONYX_FUNC(__net_bind) + ONYX_FUNC(__net_listen) + ONYX_FUNC(__net_accept) + ONYX_FUNC(__net_connect_unix) + ONYX_FUNC(__net_connect_ipv4) + ONYX_FUNC(__net_send) + ONYX_FUNC(__net_sendto) + ONYX_FUNC(__net_recv) + ONYX_FUNC(__net_recvfrom) + ONYX_FUNC(__net_poll_recv) + ONYX_FUNC(__net_host_to_net_s) + ONYX_FUNC(__net_host_to_net_l) + ONYX_FUNC(__net_net_to_host_s) + ONYX_FUNC(__net_net_to_host_l) + + ONYX_FUNC(__cptr_make) + ONYX_FUNC(__cptr_read) + ONYX_FUNC(__cptr_read_u8) + ONYX_FUNC(__cptr_read_u16) + ONYX_FUNC(__cptr_read_u32) + ONYX_FUNC(__cptr_read_u64) + ONYX_FUNC(__cptr_extract_str) + + NULL +}; diff --git a/settings.sh b/settings.sh new file mode 100644 index 00000000..7ab78ec9 --- /dev/null +++ b/settings.sh @@ -0,0 +1,24 @@ + +# The base path for the installation. +# This is typcially /usr or /usr/local. +INSTALL_DIR="/usr" + +# Where the core libraries for Onyx will go. +CORE_DIR="$INSTALL_DIR/share/onyx" + +# Where the onyx executable will be placed. +BIN_DIR="$INSTALL_DIR/bin" + +# The compiler to use. Only GCC and TCC have been tested. +CC='gcc' + +# The architecture of your system. If your not sure, leave this alone. +ARCH="$(uname -m)" + +RUNTIME_LIBRARY="ovmwasm" +# RUNTIME_LIBRARY="wasmer" + +# Where the Wasmer library files can be found. +# They are bundled with the project, but if a different version is available, these can be changed. +WASMER_INCLUDE_DIR="$(pwd)/lib/common/include" +WASMER_LIBRARY_DIR="$(pwd)/lib/linux_$ARCH/lib" diff --git a/shared/include/bh.h b/shared/include/bh.h new file mode 100644 index 00000000..0049b269 --- /dev/null +++ b/shared/include/bh.h @@ -0,0 +1,2787 @@ +#ifndef BH_H +#define BH_H + +// NOTE: For lseek64 +#define _LARGEFILE64_SOURCE + +#if defined(_WIN32) || defined(_WIN64) + #define _BH_WINDOWS 1 +#endif + +#if defined(__unix__) + #define _BH_LINUX 1 +#endif + +#include +#include +#include + +#ifdef _BH_LINUX + #include + #include + #include + #include +#endif + +#include +#include +#include // TODO: Replace with needed functions +#include +#include +#include + +#if defined(_MSC_VER) && !defined(_WINDOWS_) + #include "small_windows.h" +#endif + +//------------------------------------------------------------------------------------- +// Better types +//------------------------------------------------------------------------------------- +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; +typedef int64_t isize; +typedef i32 b32; +typedef void* ptr; +typedef float f32; +typedef double f64; + + + + + + + + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// Better character functions +//------------------------------------------------------------------------------------- +b32 char_is_alpha(const char a); +b32 char_is_num(const char a); +b32 char_is_alphanum(const char a); +char charset_contains(const char* charset, char ch); +b32 char_is_whitespace(const char a); +b32 char_in_range(const char lo, const char hi, const char a); +i64 chars_match(char* ptr1, char* ptr2); + + + + + +//------------------------------------------------------------------------------------- +// Better math functions +//------------------------------------------------------------------------------------- +#define bh_max(a, b) ((a) > (b) ? (a) : (b)) +#define bh_min(a, b) ((a) < (b) ? (a) : (b)) +#define bh_clamp(v, a, b) (bh_min((b), bh_max((a), (v)))) +#define bh_abs(x) ((x) < 0 ? -(x) : (x)) +#define size_of(x) (isize) sizeof(x) + +static inline u64 log2_dumb(u64 n) { + switch (n) { + case 1 << 0: return 0; + case 1 << 1: return 1; + case 1 << 2: return 2; + case 1 << 3: return 3; + case 1 << 4: return 4; + case 1 << 5: return 5; + case 1 << 6: return 6; + case 1 << 7: return 7; + case 1 << 8: return 8; + case 1 << 9: return 9; + case 1 << 10: return 10; + case 1 << 11: return 11; + case 1 << 12: return 12; + case 1 << 13: return 13; + case 1 << 14: return 14; + case 1 << 15: return 15; + case 1 << 16: return 16; + case 1 << 17: return 17; + case 1 << 18: return 18; + case 1 << 19: return 19; + case 1 << 20: return 20; + case 1 << 21: return 21; + case 1 << 22: return 22; + case 1 << 23: return 23; + case 1 << 24: return 24; + case 1 << 25: return 25; + case 1 << 26: return 26; + case 1 << 27: return 27; + case 1 << 28: return 28; + case 1 << 29: return 29; + case 1 << 30: return 30; + case 1 << 31: return 31; + + default: return 0; + } +} + + + +static inline const char* bh_num_suffix(u64 i) { + if (i == 11 || i == 12 || i == 13) return "th"; + + switch (i % 10) { + case 0: return "th"; + case 1: return "st"; + case 2: return "nd"; + case 3: return "rd"; + case 4: return "th"; + case 5: return "th"; + case 6: return "th"; + case 7: return "th"; + case 8: return "th"; + case 9: return "th"; + + default: return ""; + } +} + + + + +//------------------------------------------------------------------------------------- +// Conversion functions +//------------------------------------------------------------------------------------- + +// Converts an unsigned integer to the unsigned LEB128 format +u8* uint_to_uleb128(u64 n, i32* output_length); +u8* int_to_leb128(i64 n, i32* output_length); +u8* float_to_ieee754(f32 f, b32 reverse); +u8* double_to_ieee754(f64 f, b32 reverse); + +u64 uleb128_to_uint(u8* bytes, i32 *byte_walker); + + + +//------------------------------------------------------------------------------------- +// Helpful macros +//------------------------------------------------------------------------------------- +#define bh_offset_of(Type, elem) ((isize)&(((Type)*) 0)->elem) +#define bh_align_of(Type) bh_offset_of(struct { char c; Type member; }, member) +#define bh_swap(Type, a, b) do { Type tmp = (a); (a) = (b); (b) = tmp; } while(0) + +#define bh_align(x, a) if ((x) % (a) != 0) (x) += (a) - ((x) % (a)); + +#define bh_pointer_add(ptr, amm) ((void *)((u8 *) (ptr) + (amm))) +#define BH_BIT(x) (1 << (x)) +#define BH_MASK_SET(var, set, mask) ((set) ? ((var) |= (mask)) : ((var) &= ~(mask))) + +#define fori(var, lo, hi) for (i64 var = (lo); var < (hi); var++) +#define forir(var, hi, lo) for (i64 var = (hi); var >= (lo); var--) +#define forll(T, var, start, step) for (T* var = (start); var != NULL; var = (T *) var->step) + +#if defined(BH_DEBUG) && !defined(_BH_WINDOWS) + #define DEBUG_HERE __asm("int $3") +#else + #define DEBUG_HERE +#endif + + + + + +//------------------------------------------------------------------------------------- +// Custom allocators +//------------------------------------------------------------------------------------- + +typedef enum bh_allocator_actions { + bh_allocator_action_alloc, + bh_allocator_action_free, + bh_allocator_action_resize, +} bh_allocator_actions; + +#define BH_ALLOCATOR_PROC(name) \ +ptr name(ptr data, bh_allocator_actions action, \ + isize size, isize alignment, \ + void* prev_memory, \ + u64 flags) + +typedef BH_ALLOCATOR_PROC(bh__allocator_proc); // NOTE: so bh__allocator_proc can be used instead of that type + +typedef struct bh_allocator { + bh__allocator_proc* proc; // Procedure that can handle bh_allocator_actions + ptr data; // Pointer to the other data for the allocator +} bh_allocator; + +typedef enum bh_allocator_flags { + bh_allocator_flag_clear = 1 // Sets all memory to be 0 +} bh_allocator_flags; + +ptr bh_alloc(bh_allocator a, isize size); +ptr bh_alloc_aligned(bh_allocator a, isize size, isize alignment); +ptr bh_resize(bh_allocator a, ptr data, isize new_size); +ptr bh_resize_aligned(bh_allocator a, ptr data, isize new_size, isize alignment); +void bh_free(bh_allocator a, ptr data); + +#define bh_alloc_item(allocator_, T) (T *) bh_alloc(allocator_, sizeof(T)) +#define bh_alloc_array(allocator_, T, n) (T *) bh_alloc(allocator_, sizeof(T) * (n)) + +// NOTE: This should get optimized out since alignment should be a power of two +#define bh__align(x, alignment) ((((x) / alignment) + 1) * alignment) + + + + +// HEAP ALLOCATOR +// Essentially a wrapper for malloc, free and realloc +bh_allocator bh_heap_allocator(void); +BH_ALLOCATOR_PROC(bh_heap_allocator_proc); + + + + + +// ARENA ALLOCATOR +typedef struct bh_arena { + bh_allocator backing; + ptr first_arena, current_arena; + isize size, arena_size; // in bytes +} bh_arena; + +typedef struct bh__arena_internal { + ptr next_arena; + void* data; // Not actually a pointer, just used for the offset +} bh__arena_internal; + +void bh_arena_init(bh_arena* alloc, bh_allocator backing, isize arena_size); +void bh_arena_free(bh_arena* alloc); +bh_allocator bh_arena_allocator(bh_arena* alloc); +BH_ALLOCATOR_PROC(bh_arena_allocator_proc); + + + + + + +// SCRATCH ALLOCATOR +typedef struct bh_scratch { + bh_allocator backing; + ptr memory, end, curr; +} bh_scratch; + +void bh_scratch_init(bh_scratch* scratch, bh_allocator backing, isize scratch_size); +void bh_scratch_free(bh_scratch* scratch); +bh_allocator bh_scratch_allocator(bh_scratch* scratch); +BH_ALLOCATOR_PROC(bh_scratch_allocator_proc); + + + + + + + +//------------------------------------------------------------------------------------- +// Allocator based string functions +//------------------------------------------------------------------------------------- + +b32 bh_str_starts_with(char* str, char* start); +b32 bh_str_ends_with(char* str, char* end); +char* bh_strdup(bh_allocator a, char* str); + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// Better files +//------------------------------------------------------------------------------------- +#ifndef BH_NO_FILE + +typedef enum bh_file_error { + BH_FILE_ERROR_NONE, + BH_FILE_ERROR_INVALID, + BH_FILE_ERROR_BAD_FD, +} bh_file_error; + +typedef enum bh_file_mode { + BH_FILE_MODE_READ = 1 << 0, + BH_FILE_MODE_WRITE = 1 << 1, + BH_FILE_MODE_APPEND = 1 << 2, + BH_FILE_MODE_RW = 1 << 3, + + BH_FILE_MODE_MODES = BH_FILE_MODE_READ | BH_FILE_MODE_WRITE | BH_FILE_MODE_APPEND | BH_FILE_MODE_RW +} bh_file_mode; + +typedef enum bh_file_whence { + BH_FILE_WHENCE_BEGIN = SEEK_SET, + BH_FILE_WHENCE_CURRENT = SEEK_CUR, + BH_FILE_WHENCE_END = SEEK_END, +} bh_file_whence; + +#ifdef _BH_WINDOWS + typedef HANDLE bh_file_descriptor; +#else + typedef int bh_file_descriptor; +#endif + +typedef struct bh_file { + bh_file_descriptor fd; + char const* filename; +} bh_file; + +typedef enum bh_file_standard { + BH_FILE_STANDARD_INPUT, + BH_FILE_STANDARD_OUTPUT, + BH_FILE_STANDARD_ERROR +} bh_file_standard; + +typedef struct bh_file_contents { + bh_allocator allocator; + const char *filename; + isize length; + void* data; + isize line_count; +} bh_file_contents; + + +#define BH_FILE_TYPE_DIRECTORY 3 +#define BH_FILE_TYPE_FILE 4 +#define BH_FILE_TYPE_LINK 5 +typedef struct bh_file_stats { + isize size; + u32 file_type; +} bh_file_stats; + +bh_file_error bh_file_get_standard(bh_file* file, bh_file_standard stand); + +bh_file_error bh_file_create(bh_file* file, char const* filename); +bh_file_error bh_file_open(bh_file* file, char const* filename); +bh_file_error bh_file_open_mode(bh_file* file, bh_file_mode mode, const char* filename); +bh_file_error bh_file_new(bh_file* file, bh_file_descriptor fd, const char* filename); +b32 bh_file_read_at(bh_file* file, i64 offset, void* buffer, isize buff_size, isize* bytes_read); +b32 bh_file_write_at(bh_file* file, i64 offset, void const* buffer, isize buff_size, isize* bytes_wrote); +i64 bh_file_seek(bh_file* file, i64 offset, bh_file_whence whence); +i64 bh_file_seek_to(bh_file* file, i64 offset); +i64 bh_file_seek_to_end(bh_file* file); +i64 bh_file_skip(bh_file* file, i64 bytes); +i64 bh_file_tell(bh_file* file); +bh_file_error bh_file_close(bh_file* file); +i32 bh_file_read(bh_file* file, void* buffer, isize buff_size); +i32 bh_file_write(bh_file* file, void* buffer, isize buff_size); +void bh_file_flush(bh_file* file); +i64 bh_file_size(bh_file* file); +b32 bh_file_stat(char const* filename, bh_file_stats* stat); +b32 bh_file_exists(char const* filename); +b32 bh_file_remove(char const* filename); +char* bh_path_get_full_name(char const* filename, bh_allocator a); +char* bh_path_get_parent(char const* filename, bh_allocator a); +char* bh_path_convert_separators(char* path); + +// This function returns a volatile pointer. Do not store it without copying! +// `included_folders` is bh_arr(const char *). +char* bh_lookup_file(char* filename, char* relative_to, char *suffix, b32 add_suffix, const char ** included_folders, b32 search_included_folders); + +#define bh_file_read_contents(allocator_, x) _Generic((x), \ + bh_file*: bh_file_read_contents_bh_file, \ + const char*: bh_file_read_contents_direct, \ + char*: bh_file_read_contents_direct)((allocator_), x) + +bh_file_contents bh_file_read_contents_bh_file(bh_allocator alloc, bh_file* file); +bh_file_contents bh_file_read_contents_direct(bh_allocator alloc, const char* filename); +i32 bh_file_contents_free(bh_file_contents* contents); + + +#ifdef _BH_WINDOWS + typedef struct Windows_Directory_Opened { + HANDLE hndl; + WIN32_FIND_DATAA found_file; + } Windows_Directory_Opened; + + typedef Windows_Directory_Opened *bh_dir; +#else + typedef DIR *bh_dir; +#endif + +typedef enum bh_dirent_type { + BH_DIRENT_UNKNOWN, + BH_DIRENT_BLOCK, + BH_DIRENT_CHAR, + BH_DIRENT_DIRECTORY, + BH_DIRENT_FILE, + BH_DIRENT_SYMLINK, + BH_DIRENT_OTHER, +} bh_dirent_type; + +typedef struct bh_dirent { + bh_dirent_type type; + u32 id; + u32 name_length; + char name[256]; +} bh_dirent; + +bh_dir bh_dir_open(char* path); +b32 bh_dir_read(bh_dir dir, bh_dirent* out); +void bh_dir_close(bh_dir dir); + +#endif + + + + + + + + + + +//------------------------------------------------------------------------------------- +// Alternate printing +//------------------------------------------------------------------------------------- +// Barebones implementation of printf. Does not support all format options +// Currently supports: +// %c - chars +// %_(u)d - ints where _ is: +// nothing - decimal +// o - octal +// x - hexadecimal +// %_(u)l - longs where _ is: +// nothing - decimal +// o - octal +// x - hexadecimal +// %f - floating points +// %s - null terminated strings +// %p - pointers +// %% - literal % + +typedef struct bh__print_format { + u32 base; +} bh__print_format; + +isize bh_printf(char const *fmt, ...); +isize bh_printf_va(char const *fmt, va_list va); +isize bh_printf_err(char const *fmt, ...); +isize bh_printf_err_va(char const *fmt, va_list va); +isize bh_fprintf(bh_file* f, char const *fmt, ...); +isize bh_fprintf_va(bh_file* f, char const *fmt, va_list va); +char* bh_bprintf(char const *fmt, ...); +char* bh_bprintf_va(char const *fmt, va_list va); +char* bh_aprintf(bh_allocator alloc, const char* fmt, ...); +char* bh_aprintf_va(bh_allocator alloc, const char* fmt, va_list va); +isize bh_snprintf(char *str, isize n, char const *fmt, ...); +isize bh_snprintf_va(char *str, isize n, char const *fmt, va_list va); + + + + + + + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// Flexible buffer +//------------------------------------------------------------------------------------- + +typedef struct bh_buffer { + bh_allocator allocator; + i32 length, capacity; + u8* data; +} bh_buffer; + +#ifndef BH_BUFFER_GROW_FORMULA +#define BH_BUFFER_GROW_FORMULA(x) ((x) > 0 ? ((x) << 1) : 16) +#endif + +void bh_buffer_init(bh_buffer* buffer, bh_allocator alloc, i32 length); +void bh_buffer_free(bh_buffer* buffer); +void bh_buffer_clear(bh_buffer* buffer); +void bh_buffer_grow(bh_buffer* buffer, i32 length); +void bh_buffer_append(bh_buffer* buffer, const void * data, i32 length); +void bh_buffer_concat(bh_buffer* buffer, bh_buffer other); +void bh_buffer_write_byte(bh_buffer* buffer, u8 byte); +void bh_buffer_write_u32(bh_buffer* buffer, u32 i); +void bh_buffer_write_u64(bh_buffer* buffer, u64 i); +void bh_buffer_align(bh_buffer* buffer, u32 alignment); + + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// Better dynamically-sized arrays +//------------------------------------------------------------------------------------- +#ifndef BH_NO_ARRAY + +typedef struct bh__arr { + bh_allocator allocator; + i32 length, capacity; +} bh__arr; + +#ifndef BH_ARR_GROW_FORMULA +#define BH_ARR_GROW_FORMULA(x) ((x) > 0 ? ((x) << 1) : 4) +#endif + +#define bh_arr(T) T* +#define bh__arrhead(arr) (((bh__arr *)(arr)) - 1) + +#define bh_arr_allocator(arr) (arr ? bh__arrhead(arr)->allocator : bh_heap_allocator()) +#define bh_arr_length(arr) (arr ? bh__arrhead(arr)->length : 0) +#define bh_arr_capacity(arr) (arr ? bh__arrhead(arr)->capacity : 0) +#define bh_arr_size(arr) (arr ? bh__arrhead(arr)->capacity * sizeof(*(arr)) : 0) +#define bh_arr_valid(arr, i) (arr ? (i32)(i) < bh__arrhead(arr)->length : 0) + +#define bh_arr_pop(arr) ((arr)[--bh__arrhead(arr)->length]) +#define bh_arr_last(arr) ((arr)[bh__arrhead(arr)->length - 1]) +#define bh_arr_end(arr, i) ((i) >= &(arr)[bh_arr_length(arr)]) +#define bh_arr_start(arr, i) ((i) < (arr)) + +#define bh_arr_new(allocator_, arr, cap) (bh__arr_grow((allocator_), (void**) &(arr), sizeof(*(arr)), cap)) +#define bh_arr_free(arr) (bh__arr_free((void**) &(arr))) +#define bh_arr_copy(allocator_, arr) (bh__arr_copy((allocator_), (arr), sizeof(*(arr)))) + +#define bh_arr_grow(arr, cap) (bh__arr_grow(bh_arr_allocator(arr), (void **) &(arr), sizeof(*(arr)), cap)) +#define bh_arr_shrink(arr, cap) (bh__arr_shrink((void **) &(arr), sizeof(*(arr)), cap)) +#define bh_arr_set_length(arr, n) (bh__arrhead(arr)->length = n) + +#define bh_arr_insertn(arr, i, n) (bh__arr_insertn((void **) &(arr), sizeof(*(arr)), i, n)) + +#define bh_arr_insert_end(arr, n) ( \ + bh__arr_grow(bh_arr_allocator(arr), (void **) &(arr), sizeof(*(arr)), bh_arr_length(arr) + n), \ + bh__arrhead(arr)->length += n) + +#define bh_arr_push(arr, value) ( \ + bh_arr_length(arr) + 1 > bh_arr_capacity(arr) ? bh__arr_grow(bh_arr_allocator(arr), (void **) &(arr), sizeof(*(arr)), bh_arr_length(arr) + 1) : 0, \ + arr[bh__arrhead(arr)->length++] = value) + +#define bh_arr_set_at(arr, n, value) ( \ + bh__arr_grow(bh_arr_allocator(arr), (void **) &(arr), sizeof(*(arr)), (n) + 1), \ + bh_arr_set_length((arr), bh_max(bh_arr_length(arr), (i32) (n) + 1)), \ + arr[n] = value) + +#define bh_arr_is_empty(arr) (arr ? bh__arrhead(arr)->length == 0 : 1) +#define bh_arr_clear(arr) (arr ? (bh__arrhead(arr)->length = 0) : 0) + +#define bh_arr_deleten(arr, i, n) (bh__arr_deleten((void **) &(arr), sizeof(*(arr)), i, n)) +#define bh_arr_fastdelete(arr, i) (arr[i] = arr[--bh__arrhead(arr)->length]) + +#define bh_arr_each(T, var, arr) for (T* var = (arr); !bh_arr_end((arr), var); var++) +#define bh_arr_rev_each(T, var, arr) for (T* var = &bh_arr_last((arr)); !bh_arr_start((arr), var); var--) + +#define bh_arr_zero(arr) memset(arr, 0, bh_arr_length(arr) * sizeof(*(arr))); + +b32 bh__arr_grow(bh_allocator alloc, void** arr, i32 elemsize, i32 cap); +b32 bh__arr_shrink(void** arr, i32 elemsize, i32 cap); +b32 bh__arr_free(void **arr); +void* bh__arr_copy(bh_allocator alloc, void *arr, i32 elemsize); +void bh__arr_insertn(void **arr, i32 elemsize, i32 index, i32 numelems); +void bh__arr_deleten(void **arr, i32 elemsize, i32 index, i32 numelems); + +#endif + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// STRING HASH TABLE FUNCTIONS +//------------------------------------------------------------------------------------- +#ifndef BH_NO_TABLE + +#ifdef BH_DEFINE +u64 bh__table_hash_function(const char* str, i32 len, i32 mod) { + u64 hash = 5381; + i32 c, l = 0; + if (len == 0) len = ((u32) 1 << 31) - 1; + + while ((c = *str++) && l++ < len) { + hash = (hash << 5) + hash + c; + } + + return hash % mod; +} +#endif + +typedef struct bh_table_iterator { + ptr *tab, *endtab; + i32 elemsize, arrlen; + ptr entry; +} bh_table_iterator; + +typedef struct bh__table { + bh_allocator allocator; + u64 table_size; // NOTE: u64 since padding will make it 8-bytes no matter what + ptr arrs[]; +} bh__table; + +#define bh_table(T) T* + +#ifdef BH_TABLE_SIZE_SAFE + #define bh_table_init(allocator_, tab, hs) bh__table_init(allocator_, (bh__table **)&(tab), hs) + #define bh_table_free(tab) bh__table_free((bh__table **)&(tab)) + #define bh_table_put(T, tab, key, value) (assert(sizeof(T) == sizeof(*(tab))), (*((T *) bh__table_put((bh__table *) tab, sizeof(T), key)) = (T) value)) + #define bh_table_has(T, tab, key) (assert(sizeof(T) == sizeof(*(tab))), (bh__table_has((bh__table *) tab, sizeof(T), key))) + #define bh_table_get(T, tab, key) (assert(sizeof(T) == sizeof(*(tab))), (*((T *) bh__table_get((bh__table *) tab, sizeof(T), key)))) + #define bh_table_delete(T, tab, key) (assert(sizeof(T) == sizeof(*(tab))), bh__table_delete((bh__table *) tab, sizeof(T), key)) + #define bh_table_clear(tab) (bh__table_clear((bh__table *) tab)) + + #define bh_table_iter_setup(T, tab) (assert(sizeof(T) == sizeof(*(tab))), bh__table_iter_setup((bh__table *) tab, sizeof(T))) + #define bh_table_iter_key(it) ((char *)(bh_pointer_add(it.entry, it.elemsize + sizeof(u16)))) + #define bh_table_iter_value(T, it) (*(T *)it.entry) +#else + #define bh_table_init(allocator_, tab, hs) bh__table_init(allocator_, (bh__table **)&(tab), hs) + #define bh_table_free(tab) bh__table_free((bh__table **)&(tab)) + #define bh_table_put(T, tab, key, value) (*((T *) bh__table_put((bh__table *) tab, sizeof(T), key)) = value) + #define bh_table_has(T, tab, key) (bh__table_has((bh__table *) tab, sizeof(T), key)) + #define bh_table_get(T, tab, key) (*((T *) bh__table_get((bh__table *) tab, sizeof(T), key))) + #define bh_table_delete(T, tab, key) (bh__table_delete((bh__table *) tab, sizeof(T), key)) + #define bh_table_clear(tab) (bh__table_clear((bh__table *) tab)) + + #define bh_table_iter_setup(T, tab) (bh__table_iter_setup((bh__table *) tab, sizeof(T))) + #define bh_table_iter_key(it) ((char *)(bh_pointer_add(it.entry, it.elemsize + sizeof(u16)))) + #define bh_table_iter_value(T, it) (*(T *)it.entry) +#endif + +#define bh_table_each_start(T, table) { \ + bh_table_iterator it = bh_table_iter_setup(T, (table)); \ + while (bh_table_iter_next(&it)) { \ + const char* key = bh_table_iter_key(it); \ + T value = bh_table_iter_value(T, it); +#define bh_table_each_end } } + +b32 bh__table_init(bh_allocator allocator, bh__table **table, i32 table_size); +b32 bh__table_free(bh__table **table); +ptr bh__table_put(bh__table *table, i32 elemsize, char *key); +b32 bh__table_has(bh__table *table, i32 elemsize, char *key); +ptr bh__table_get(bh__table *table, i32 elemsize, char *key); +void bh__table_delete(bh__table *table, i32 elemsize, char *key); +void bh__table_clear(bh__table *table); +bh_table_iterator bh__table_iter_setup(bh__table *table, i32 elemsize); +b32 bh_table_iter_next(bh_table_iterator* it); + +#endif +// Using stb_ds for tables now because they are better in every single way. +#define Table(T) struct { char *key; T value; } * + + + + + + + + +//------------------------------------------------------------------------------- +// IMAP (integer to integer map) +//------------------------------------------------------------------------------- +#ifndef BH_NO_IMAP + +typedef u64 bh_imap_entry_t; + +typedef struct bh__imap_lookup_result { + i64 hash_index; + i64 entry_prev; + i64 entry_index; +} bh__imap_lookup_result; + +typedef struct bh__imap_entry { + bh_imap_entry_t key, value; + i64 next; +} bh__imap_entry; + +typedef struct bh_imap { + bh_allocator allocator; + + bh_arr(i64) hashes; + bh_arr(bh__imap_entry) entries; +} bh_imap; + + +void bh_imap_init(bh_imap* imap, bh_allocator alloc, i32 hash_count); +void bh_imap_free(bh_imap* imap); +void bh_imap_put(bh_imap* imap, bh_imap_entry_t key, bh_imap_entry_t value); +b32 bh_imap_has(bh_imap* imap, bh_imap_entry_t key); +bh_imap_entry_t bh_imap_get(bh_imap* imap, bh_imap_entry_t key); +void bh_imap_delete(bh_imap* imap, bh_imap_entry_t key); +void bh_imap_clear(bh_imap* imap); + +#ifdef BH_DEFINE +#endif // BH_DEFINE + + +#endif + + + + + + + + + + + +// MANAGED HEAP ALLOCATOR +typedef struct bh_managed_heap { + bh_imap ptrs; +} bh_managed_heap; + +void bh_managed_heap_init(bh_managed_heap* mh); +void bh_managed_heap_free(bh_managed_heap* mh); +bh_allocator bh_managed_heap_allocator(bh_managed_heap* mh); +BH_ALLOCATOR_PROC(bh_managed_heap_allocator_proc); + + + + + + + + + + + +//------------------------------------------------------------------------------- +// OTHER COMMON DATA STRUCTURES +//------------------------------------------------------------------------------- +#ifndef BH_NO_DATASTRUCTURES + + + + + + + + + + + + + + + + +#endif // BH_NO_DATASTRUCTURES + + + + + +//------------------------------------------------------------------------------ +// TIME / DURATION +//------------------------------------------------------------------------------ +u64 bh_time_curr(); +u64 bh_time_duration(u64 old); + + + + + + + + + + + +#ifdef BH_DEFINE + +#undef BH_DEFINE +//------------------------------------------------------------------------------------- +// IMPLEMENTATIONS +//------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------- +// CHAR FUNCTIONS +//------------------------------------------------------------------------------------- + +b32 char_is_alpha(const char a) { + return ('a' <= a && a <= 'z') || ('A' <= a && a <= 'Z'); +} + +char charset_contains(const char* charset, char ch) { + while (*charset) { + if (*charset == ch) return ch; + charset++; + } + + return 0; +} + +b32 char_is_num(const char a) { + return ('0' <= a && a <= '9'); +} + +b32 char_is_alphanum(const char a) { + return char_is_alpha(a) || char_is_num(a); +} + +b32 char_is_whitespace(const char a) { + return charset_contains(" \t\r\n", a); +} + +b32 char_in_range(const char lo, const char hi, const char a) { + return lo <= a && a <= hi; +} + +i64 chars_match(char* ptr1, char* ptr2) { + i64 len = 0; + while (*ptr2 != '\0' && *ptr1 == *ptr2) ptr1++, ptr2++, len++; + return *ptr2 == '\0' ? len : 0; +} + + + + + + + + +//------------------------------------------------------------------------------------- +// CUSTOM ALLOCATORS IMPLEMENTATION +//------------------------------------------------------------------------------------- +ptr bh_alloc(bh_allocator a, isize size) { + return bh_alloc_aligned(a, size, 16); +} + +ptr bh_alloc_aligned(bh_allocator a, isize size, isize alignment) { + return a.proc(a.data, bh_allocator_action_alloc, size, alignment, NULL, 0); +} + +ptr bh_resize(bh_allocator a, ptr data, isize new_size) { + return bh_resize_aligned(a, data, new_size, 16); +} + +ptr bh_resize_aligned(bh_allocator a, ptr data, isize new_size, isize alignment) { + return a.proc(a.data, bh_allocator_action_resize, new_size, alignment, data, 0); +} + +void bh_free(bh_allocator a, ptr data) { + if (data != NULL) a.proc(a.data, bh_allocator_action_free, 0, 0, data, 0); +} + + + +// HEAP ALLOCATOR IMPLEMENTATION +bh_allocator bh_heap_allocator(void) { + return (bh_allocator) { + .proc = bh_heap_allocator_proc, + .data = NULL + }; +} + +BH_ALLOCATOR_PROC(bh_heap_allocator_proc) { + ptr retval = NULL; + + switch (action) { + case bh_allocator_action_alloc: { +#if defined(_BH_WINDOWS) + retval = _aligned_malloc(size, alignment); +#elif defined(_BH_LINUX) + i32 success = posix_memalign(&retval, alignment, size); +#endif + if (flags & bh_allocator_flag_clear && retval != NULL) { + memset(retval, 0, size); + } + } break; + + case bh_allocator_action_resize: { + // TODO: Maybe replace with better custom function +#if defined(_BH_WINDOWS) + retval = _aligned_realloc(prev_memory, size, alignment); +#elif defined(_BH_LINUX) + retval = realloc(prev_memory, size); +#endif + } break; + + case bh_allocator_action_free: { +#if defined(_BH_WINDOWS) + _aligned_free(prev_memory); +#elif defined(_BH_LINUX) + free(prev_memory); +#endif + } break; + } + + return retval; +} + + + + + + +// MANAGED HEAP ALLOCATOR IMPLEMENTATION +void bh_managed_heap_init(bh_managed_heap* mh) { + bh_imap_init(&mh->ptrs, bh_heap_allocator(), 512); +} + +void bh_managed_heap_free(bh_managed_heap* mh) { + bh_arr_each(bh__imap_entry, p, mh->ptrs.entries) { +#if defined(_BH_WINDOWS) + _aligned_free((void *) p->key); +#elif defined(_BH_LINUX) + free((void *) p->key); +#endif + } + + bh_imap_free(&mh->ptrs); +} + +bh_allocator bh_managed_heap_allocator(bh_managed_heap* mh) { + return (bh_allocator) { + .proc = bh_managed_heap_allocator_proc, + .data = mh + }; +} + +BH_ALLOCATOR_PROC(bh_managed_heap_allocator_proc) { + bh_managed_heap* mh = (bh_managed_heap *) data; + ptr retval = NULL; + + switch (action) { + case bh_allocator_action_alloc: { +#if defined(_BH_WINDOWS) + retval = _aligned_malloc(size, alignment); +#elif defined(_BH_LINUX) + i32 success = posix_memalign(&retval, alignment, size); +#endif + + if (flags & bh_allocator_flag_clear && retval != NULL) { + memset(retval, 0, size); + } + + if (retval != NULL) + bh_imap_put(&mh->ptrs, (u64) retval, 1); + } break; + + case bh_allocator_action_resize: { + bh_imap_delete(&mh->ptrs, (u64) prev_memory); +#if defined(_BH_WINDOWS) + retval = _aligned_realloc(prev_memory, size, alignment); +#elif defined(_BH_LINUX) + retval = realloc(prev_memory, size); +#endif + bh_imap_put(&mh->ptrs, (u64) retval, 1); + } break; + + case bh_allocator_action_free: { + bh_imap_delete(&mh->ptrs, (u64) prev_memory); +#if defined(_BH_WINDOWS) + _aligned_free(prev_memory); +#elif defined(_BH_LINUX) + free(prev_memory); +#endif + } break; + } + + return retval; +} + + + + + + + + +// ARENA ALLOCATOR IMPLEMENTATION +void bh_arena_init(bh_arena* alloc, bh_allocator backing, isize arena_size) { + arena_size = bh_max(arena_size, size_of(ptr)); + ptr data = bh_alloc(backing, arena_size); + + alloc->backing = backing; + alloc->arena_size = arena_size; + alloc->size = sizeof(ptr); + alloc->first_arena = data; + alloc->current_arena = data; + + ((bh__arena_internal *)(alloc->first_arena))->next_arena = NULL; +} + +void bh_arena_free(bh_arena* alloc) { + bh__arena_internal *walker = (bh__arena_internal *) alloc->first_arena; + bh__arena_internal *trailer = walker; + while (walker != NULL) { + walker = walker->next_arena; + bh_free(alloc->backing, trailer); + trailer = walker; + } + + alloc->first_arena = NULL; + alloc->current_arena = NULL; + alloc->arena_size = 0; + alloc->size = 0; +} + +bh_allocator bh_arena_allocator(bh_arena* alloc) { + return (bh_allocator) { + .proc = bh_arena_allocator_proc, + .data = alloc, + }; +} + +BH_ALLOCATOR_PROC(bh_arena_allocator_proc) { + bh_arena* alloc_arena = (bh_arena*) data; + + ptr retval = NULL; + + switch (action) { + case bh_allocator_action_alloc: { + bh_align(size, alignment); + bh_align(alloc_arena->size, alignment); + + if (size > alloc_arena->arena_size - size_of(ptr)) { + // Size too large for the arena + return NULL; + } + + if (alloc_arena->size + size >= alloc_arena->arena_size) { + bh__arena_internal* new_arena = (bh__arena_internal *) bh_alloc(alloc_arena->backing, alloc_arena->arena_size); + + if (new_arena == NULL) { + bh_printf_err("Arena Allocator: couldn't allocate new arena"); + return NULL; + } + + new_arena->next_arena = NULL; + ((bh__arena_internal *)(alloc_arena->current_arena))->next_arena = new_arena; + alloc_arena->current_arena = new_arena; + alloc_arena->size = sizeof(ptr); + } + + retval = bh_pointer_add(alloc_arena->current_arena, alloc_arena->size); + alloc_arena->size += size; + } break; + + case bh_allocator_action_resize: { + // Do nothing since this is a fixed allocator + } break; + + case bh_allocator_action_free: { + // Do nothing since this allocator isn't made for freeing memory + } break; + } + + return retval; +} + + + + +// SCRATCH ALLOCATOR IMPLEMENTATION +void bh_scratch_init(bh_scratch* scratch, bh_allocator backing, isize scratch_size) { + ptr memory = bh_alloc(backing, scratch_size); + + scratch->backing = backing; + scratch->memory = memory; + scratch->curr = memory; + scratch->end = bh_pointer_add(memory, scratch_size); +} + +void bh_scratch_free(bh_scratch* scratch) { + bh_free(scratch->backing, scratch->memory); + + scratch->memory = NULL; + scratch->curr = NULL; + scratch->end = NULL; +} + +bh_allocator bh_scratch_allocator(bh_scratch* scratch) { + return (bh_allocator) { + .proc = bh_scratch_allocator_proc, + .data = scratch, + }; +} + +BH_ALLOCATOR_PROC(bh_scratch_allocator_proc) { + bh_scratch* scratch = (bh_scratch*) data; + ptr retval = NULL; + + switch (action) { + case bh_allocator_action_alloc: { + if (size > ((u8 *) scratch->end) - ((u8 *) scratch->memory)) { + return NULL; + } + + retval = scratch->curr; + scratch->curr = bh_pointer_add(scratch->curr, size); + + if (scratch->curr >= scratch->end) { + scratch->curr = scratch->memory; + retval = scratch->curr; + } + } break; + + case bh_allocator_action_free: break; + + case bh_allocator_action_resize: { + if (size > ((u8 *) scratch->end) - ((u8 *) scratch->memory)) { + return NULL; + } + + retval = scratch->curr; + scratch->curr = bh_pointer_add(scratch->curr, size); + + if (scratch->curr >= scratch->end) { + scratch->curr = scratch->memory; + retval = scratch->curr; + } + + // HACK!!!!!: Using size instead of some kind of "old size" + memcpy(retval, prev_memory, size); + } break; + } + + return retval; +} + + + + +//------------------------------------------------------------------------------------- +// CONVERSION FUNCTIONS IMPLEMENTATION +//------------------------------------------------------------------------------------- +u8* uint_to_uleb128(u64 n, i32* output_length) { + static u8 buffer[16]; + + *output_length = 0; + u8* output = buffer; + u8 byte; + do { + byte = n & 0x7f; + n >>= 7; + if (n != 0) byte |= (1 << 7); + *output++ = byte; + (*output_length)++; + } while (n != 0); + + return buffer; +} + + +// Converts a signed integer to the signed LEB128 format +u8* int_to_leb128(i64 n, i32* output_length) { + static u8 buffer[16]; + + *output_length = 0; + u8* output = buffer; + b32 more = 1; + + i32 size = 64; + + u8 byte; + do { + byte = n & 0x7f; + n >>= 7; + + more = !(((n == 0) && (byte & 0x40) == 0) || ((n == -1) && (byte & 0x40) != 0)); + if (more) + byte |= 0x80; + *output++ = byte; + (*output_length)++; + } while (more); + + return buffer; +} + +// NOTE: This assumes the underlying implementation of float on the host +// system is already IEEE-754. This is safe to assume in most cases. +u8* float_to_ieee754(f32 f, b32 reverse) { + static u8 buffer[4]; + + u8* fmem = (u8*) &f; + if (reverse) { + buffer[0] = fmem[3]; + buffer[1] = fmem[2]; + buffer[2] = fmem[1]; + buffer[3] = fmem[0]; + } else { + buffer[0] = fmem[0]; + buffer[1] = fmem[1]; + buffer[2] = fmem[2]; + buffer[3] = fmem[3]; + } + + return buffer; +} + +u8* double_to_ieee754(f64 f, b32 reverse) { + static u8 buffer[8]; + + u8* fmem = (u8*) &f; + if (reverse) { + buffer[0] = fmem[7]; + buffer[1] = fmem[6]; + buffer[2] = fmem[5]; + buffer[3] = fmem[4]; + buffer[4] = fmem[3]; + buffer[5] = fmem[2]; + buffer[6] = fmem[1]; + buffer[7] = fmem[0]; + } else { + buffer[0] = fmem[0]; + buffer[1] = fmem[1]; + buffer[2] = fmem[2]; + buffer[3] = fmem[3]; + buffer[4] = fmem[4]; + buffer[5] = fmem[5]; + buffer[6] = fmem[6]; + buffer[7] = fmem[7]; + } + + return buffer; +} + +u64 uleb128_to_uint(u8* bytes, i32 *byte_count) { + u64 res = 0; + u64 shift = 0; + + while (1) { + u8 byte = bytes[(*byte_count)++]; + res |= (byte & 0x7f) << shift; + if ((byte & 0x80) == 0) break; + shift += 7; + } + return res; +} + + + +//------------------------------------------------------------------------------------- +// STRING IMPLEMENTATION +//------------------------------------------------------------------------------------- +b32 bh_str_starts_with(char* str, char* start) { + char* s = str; + char* p = start; + + while (*s != '\0' && *p != '\0' && *s == *p) s++, p++; + + return *p == '\0'; +} + +b32 bh_str_ends_with(char* str, char* end) { + i32 slen = strlen(str); + i32 elen = strlen(end); + + char* s = str + slen - 1; + char* e = end + elen - 1; + + while (*e == *s && e != end && s != str) e--, s--; + + return *e == *s; +} + +char* bh_strdup(bh_allocator a, char* str) { + u32 len = strlen(str); + char* buf = bh_alloc(a, len + 1); + + char* t = buf; + while ((*t++ = *str++)); + return buf; +} + + + + + +//------------------------------------------------------------------------------------- +// FILE IMPLEMENTATION +//------------------------------------------------------------------------------------- +#ifndef BH_NO_FILE + +static b32 bh__file_seek_wrapper(bh_file_descriptor fd, i64 offset, bh_file_whence whence, i64* new_offset); + +bh_file_error bh_file_get_standard(bh_file* file, bh_file_standard stand) { + const char* filename = NULL; + +#if defined(_BH_WINDOWS) + bh_file_descriptor sd_fd; + + switch (stand) { + case BH_FILE_STANDARD_INPUT: + sd_fd = GetStdHandle(STD_INPUT_HANDLE); + filename = "stdin"; + break; + case BH_FILE_STANDARD_OUTPUT: + sd_fd = GetStdHandle(STD_OUTPUT_HANDLE); + filename = "stdout"; + break; + case BH_FILE_STANDARD_ERROR: + sd_fd = GetStdHandle(STD_ERROR_HANDLE); + filename = "stderr"; + break; + default: + return BH_FILE_ERROR_BAD_FD; + } + file->fd = sd_fd; + +#elif defined(_BH_LINUX) + i32 sd_fd = -1; + + switch (stand) { + case BH_FILE_STANDARD_INPUT: + sd_fd = STDIN_FILENO; + filename = "stdin"; // These are constants in the data section so everything should be okay + break; + case BH_FILE_STANDARD_OUTPUT: + sd_fd = STDOUT_FILENO; + filename = "stdout"; + break; + case BH_FILE_STANDARD_ERROR: + sd_fd = STDERR_FILENO; + filename = "stderr"; + break; + default: + return BH_FILE_ERROR_BAD_FD; + } + + file->fd = sd_fd; +#endif + + + file->filename = filename; + return BH_FILE_ERROR_NONE; +} + +bh_file_error bh_file_create(bh_file* file, const char* filename) { + // Need to do this to avoid compiler complaining about types + bh_file_mode write_rw = (bh_file_mode) (BH_FILE_MODE_WRITE | BH_FILE_MODE_RW); + return bh_file_open_mode(file, write_rw, filename); +} + +bh_file_error bh_file_open(bh_file* file, const char* filename) { + return bh_file_open_mode(file, BH_FILE_MODE_READ, filename); +} + +bh_file_error bh_file_open_mode(bh_file* file, bh_file_mode mode, const char* filename) { +#if defined(_BH_WINDOWS) + DWORD desired_access; + DWORD creation_disposition; + + switch (mode & BH_FILE_MODE_MODES) { + case BH_FILE_MODE_READ: + desired_access = GENERIC_READ; + creation_disposition = OPEN_EXISTING; + break; + case BH_FILE_MODE_WRITE: + desired_access = GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + break; + case BH_FILE_MODE_APPEND: + desired_access = GENERIC_WRITE; + creation_disposition = OPEN_ALWAYS; + break; + case BH_FILE_MODE_READ | BH_FILE_MODE_RW: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_EXISTING; + break; + case BH_FILE_MODE_WRITE | BH_FILE_MODE_RW: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + break; + case BH_FILE_MODE_APPEND | BH_FILE_MODE_RW: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_ALWAYS; + break; + default: + return BH_FILE_ERROR_INVALID; + } + + + file->fd = CreateFileA(filename, + desired_access, + FILE_SHARE_READ, + NULL, + creation_disposition, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (file->fd == INVALID_HANDLE_VALUE) { + return BH_FILE_ERROR_INVALID; + } + + file->filename = filename; + return BH_FILE_ERROR_NONE; + +#elif defined(_BH_LINUX) + i32 os_mode = 0; + + switch (mode & BH_FILE_MODE_MODES) { + case BH_FILE_MODE_READ: os_mode = O_RDONLY; break; + case BH_FILE_MODE_WRITE: os_mode = O_WRONLY | O_CREAT | O_TRUNC; break; + case BH_FILE_MODE_APPEND: os_mode = O_WRONLY | O_APPEND | O_CREAT; break; + case BH_FILE_MODE_READ | BH_FILE_MODE_RW: os_mode = O_RDWR; break; + case BH_FILE_MODE_WRITE | BH_FILE_MODE_RW: os_mode = O_RDWR | O_CREAT | O_TRUNC; break; + case BH_FILE_MODE_APPEND | BH_FILE_MODE_RW: os_mode = O_RDWR | O_APPEND | O_CREAT; break; + //default: // TODO Handle errors + } + + file->fd = open(filename, os_mode, + S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH | S_IRGRP | S_IWGRP //+rw-rw-rw- + ); + if (file->fd < 0) { + return BH_FILE_ERROR_INVALID; + } + + // TODO: Set this using some allocator + file->filename = filename; + + return BH_FILE_ERROR_NONE; +#endif +} + +bh_file_error bh_file_new(bh_file* file, bh_file_descriptor fd, const char* filename) { + file->filename = filename; // This may be unsafe + file->fd = fd; + return BH_FILE_ERROR_NONE; +} + +b32 bh_file_read_at(bh_file* file, i64 offset, void* buffer, isize buff_size, isize* bytes_read) { +#if defined(_BH_WINDOWS) + bh_file_seek_to(file, offset); + BOOL res = ReadFile(file->fd, buffer, buff_size, (i32 *) bytes_read, NULL); + if (res) return 1; + else return 0; + +#elif defined(_BH_LINUX) + if (file->fd == 0) { + isize res = read(file->fd, buffer, buff_size); + if (res < 0) return 0; + if (bytes_read) *bytes_read = res; + return 1; + } + + isize res = pread(file->fd, buffer, buff_size, offset); + if (res < 0) return 0; + if (bytes_read) *bytes_read = res; + return 1; +#endif +} + +b32 bh_file_write_at(bh_file* file, i64 offset, void const* buffer, isize buff_size, isize* bytes_wrote) { + isize res; + i64 current_offset = 0; + bh__file_seek_wrapper(file->fd, 0, BH_FILE_WHENCE_CURRENT, ¤t_offset); + +#if defined(_BH_WINDOWS) + if (current_offset != offset) bh_file_seek_to(file, offset); + res = (isize) WriteFile(file->fd, buffer, buff_size, (i32 *) bytes_wrote, NULL); + return res; + +#elif defined(_BH_LINUX) + if (current_offset == offset || file->fd == 1 || file->fd == 2) { + // Standard in and out do like pwrite() + res = write(file->fd, buffer, buff_size); + } else { + res = pwrite(file->fd, buffer, buff_size, offset); + } + if (res < 0) return 0; + if (bytes_wrote) *bytes_wrote = res; + + return 1; +#endif +} + +static b32 bh__file_seek_wrapper(bh_file_descriptor fd, i64 offset, bh_file_whence whence, i64* new_offset) { +#if defined(_BH_WINDOWS) + LARGE_INTEGER new_file_pointer; + LARGE_INTEGER dest; + dest.QuadPart = offset; + + BOOL res = SetFilePointerEx(fd, dest, &new_file_pointer, whence); + *new_offset = new_file_pointer.QuadPart; + + return res; + +#elif defined(_BH_LINUX) + i64 res = lseek64(fd, offset, whence); + if (res < 0) return 0; + if (new_offset) *new_offset = res; + return 1; +#endif +} + +// Returns new offset +i64 bh_file_seek(bh_file* file, i64 offset, bh_file_whence whence) { + i64 new_offset = -1; + bh__file_seek_wrapper(file->fd, offset, whence, &new_offset); + return new_offset; +} + +i64 bh_file_seek_to(bh_file* file, i64 offset) { + i64 new_offset = -1; + bh__file_seek_wrapper(file->fd, offset, BH_FILE_WHENCE_BEGIN, &new_offset); + return new_offset; +} + +i64 bh_file_seek_to_end(bh_file* file) { + i64 new_offset = -1; + bh__file_seek_wrapper(file->fd, 0, BH_FILE_WHENCE_END, &new_offset); + return new_offset; +} + +i64 bh_file_skip(bh_file* file, i64 bytes) { + i64 new_offset = 0; + bh__file_seek_wrapper(file->fd, bytes, BH_FILE_WHENCE_CURRENT, &new_offset); + return new_offset; +} + +i64 bh_file_tell(bh_file* file) { + i64 new_offset = 0; + bh__file_seek_wrapper(file->fd, 0, BH_FILE_WHENCE_CURRENT, &new_offset); + return new_offset; +} + +bh_file_error bh_file_close(bh_file* file) { + bh_file_error err = BH_FILE_ERROR_NONE; + +#if defined(_BH_WINDOWS) + BOOL success = CloseHandle(file->fd); + if (!success) err = BH_FILE_ERROR_INVALID; + + return err; + +#elif defined(_BH_LINUX) + i32 res = close(file->fd); + if (res < 0) + err = BH_FILE_ERROR_INVALID; + + return err; +#endif +} + +b32 bh_file_read(bh_file* file, void* buffer, isize buff_size) { + return bh_file_read_at(file, bh_file_tell(file), buffer, buff_size, NULL); +} + +b32 bh_file_write(bh_file* file, void* buffer, isize buff_size) { + return bh_file_write_at(file, bh_file_tell(file), buffer, buff_size, NULL); +} + +void bh_file_flush(bh_file* file) { + #ifdef _BH_LINUX + fdatasync(file->fd); + #endif +} + +i64 bh_file_size(bh_file* file) { + i64 size = 0; + i64 prev = bh_file_tell(file); + bh_file_seek_to_end(file); + size = bh_file_tell(file); + bh_file_seek_to(file, prev); + return size; +} + +bh_file_contents bh_file_read_contents_bh_file(bh_allocator alloc, bh_file* file) { + bh_file_contents fc = { + .allocator = alloc, + .filename = bh_strdup(alloc, (char *) file->filename), + .length = 0, .data = NULL, .line_count = 0, + }; + + isize size = bh_file_size(file); + if (size <= 0) return fc; + + fc.data = bh_alloc(alloc, size + 1); + fc.length = size; + bh_file_read_at(file, 0, fc.data, fc.length, NULL); + ((u8*) fc.data)[fc.length] = '\0'; + + return fc; +} + +bh_file_contents bh_file_read_contents_direct(bh_allocator alloc, const char* filename) { + bh_file file; + bh_file_open(&file, filename); + bh_file_contents fc = bh_file_read_contents(alloc, &file); + bh_file_close(&file); + return fc; +} + +b32 bh_file_contents_free(bh_file_contents* contents) { + bh_free(contents->allocator, contents->data); + contents->length = 0; + return 1; +} + +b32 bh_file_stat(char const* filename, bh_file_stats* out) { + struct stat s; + if (stat(filename, &s) == -1) { + return 0; + } + + out->size = s.st_size; + + if ((s.st_mode & S_IFMT) == S_IFDIR) out->file_type = BH_FILE_TYPE_DIRECTORY; + if ((s.st_mode & S_IFMT) == S_IFREG) out->file_type = BH_FILE_TYPE_FILE; + +#if defined(_BH_LINUX) + if ((s.st_mode & S_IFMT) == S_IFLNK) out->file_type = BH_FILE_TYPE_LINK; +#endif + + return 1; +} + +b32 bh_file_exists(char const* filename) { + struct stat s; + return stat(filename, &s) != -1; +} + +b32 bh_file_remove(char const* filename) { +#if defined(_BH_WINDOWS) + return DeleteFileA(filename); + +#elif defined(_BH_LINUX) + return unlink(filename) == 0; +#endif +} + +char* bh_path_get_full_name(char const* filename, bh_allocator a) { +#if defined(_BH_WINDOWS) + char buffer[4096]; + GetFullPathNameA(filename, 4096, buffer, NULL); + + i32 len = strlen(buffer); + char* result = bh_alloc_array(a, char, len + 1); + memmove(result, buffer, len); + result[len] = 0; + + return result; + +#elif defined(_BH_LINUX) + char* p = realpath(filename, NULL); + + // Check if the file did not exists. + // :Cleanup should this return NULL? + if (p == NULL) return (char *) filename; + + i32 len = strlen(p); + char* result = bh_alloc_array(a, char, len + 1); + memmove(result, p, len); + result[len] = 0; + + free(p); + + return result; +#endif +} + +// NOTE: This assumes the filename is the full path, not relative to anything else. +#if defined(_BH_LINUX) + #define DIR_SEPARATOR '/' +#elif defined(_BH_WINDOWS) + #define DIR_SEPARATOR '\\' +#endif +char* bh_path_get_parent(char const* filename, bh_allocator a) { + + char* result = bh_strdup(a, (char *) filename); + char* end = result + strlen(result); + while (*end != DIR_SEPARATOR && end != result) *end-- = '\0'; + + return result; +} + +// This function returns a volatile pointer. Do not store it without copying! +char* bh_lookup_file(char* filename, char* relative_to, char *suffix, b32 add_suffix, bh_arr(const char *) included_folders, b32 search_included_folders) { + assert(relative_to != NULL); + + static char path[512]; + fori (i, 0, 512) path[i] = 0; + + static char fn[256]; + fori (i, 0, 256) fn[i] = 0; + + if (!bh_str_ends_with(filename, suffix) && add_suffix) { + bh_snprintf(fn, 256, "%s%s", filename, suffix); + } else { + bh_snprintf(fn, 256, "%s", filename); + } + + fori (i, 0, 256) if (fn[i] == '/') fn[i] = DIR_SEPARATOR; + + if (bh_str_starts_with(filename, "./")) { + if (relative_to[strlen(relative_to) - 1] != DIR_SEPARATOR) + bh_snprintf(path, 512, "%s%c%s", relative_to, DIR_SEPARATOR, fn + 2); + else + bh_snprintf(path, 512, "%s%s", relative_to, fn + 2); + + if (bh_file_exists(path)) return bh_path_get_full_name(path, bh_heap_allocator()); + + return fn; + } + + if (search_included_folders) { + bh_arr_each(const char *, folder, included_folders) { + if ((*folder)[strlen(*folder) - 1] != DIR_SEPARATOR) + bh_snprintf(path, 512, "%s%c%s", *folder, DIR_SEPARATOR, fn); + else + bh_snprintf(path, 512, "%s%s", *folder, fn); + + if (bh_file_exists(path)) return bh_path_get_full_name(path, bh_heap_allocator()); + } + } + + return fn; +} + +// +// Modifies the path in-place. +char* bh_path_convert_separators(char* path) { +#if defined(_BH_LINUX) + #define DIR_SEPARATOR '/' + #define OTHER_SEPARATOR '\\' +#elif defined(_BH_WINDOWS) + #define DIR_SEPARATOR '\\' + #define OTHER_SEPARATOR '/' +#endif + + fori (i, 0, (i64) strlen(path)) { + if (path[i] == OTHER_SEPARATOR) { + path[i] = DIR_SEPARATOR; + } + } + + return path; +} + + +bh_dir bh_dir_open(char* path) { +#ifdef _BH_WINDOWS + char new_path[512] = { 0 }; + strncpy(new_path, path, 511); + bh_path_convert_separators(new_path); + strncat(new_path, "\\*.*", 511); + + Windows_Directory_Opened* dir = malloc(sizeof(Windows_Directory_Opened)); + dir->hndl = FindFirstFileA(new_path, &dir->found_file); + if (dir->hndl == INVALID_HANDLE_VALUE) { + return NULL; + } + + return dir; +#endif + +#ifdef _BH_LINUX + DIR* dir = opendir(path); + return dir; +#endif +} + +b32 bh_dir_read(bh_dir dir, bh_dirent* out) { + +#ifdef _BH_WINDOWS + do { + BOOL success = FindNextFileA(dir->hndl, &dir->found_file); + if (!success) return 0; + } while (!strcmp(dir->found_file.cFileName, ".") || !strcmp(dir->found_file.cFileName, "..")); + + if (out == NULL) return 1; + + out->type = (dir->found_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + ? BH_DIRENT_DIRECTORY : BH_DIRENT_FILE; + out->id = 0; + out->name_length = strlen(dir->found_file.cFileName); + strncpy(out->name, dir->found_file.cFileName, 256); + + return 1; +#endif + +#ifdef _BH_LINUX + struct dirent *ent; + while (1) { + ent = readdir(dir); + if (ent == NULL) return 0; + + // Skip the current directory and parent directory + if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) break; + } + + bh_dirent_type type = 0; + switch (ent->d_type) { + case DT_UNKNOWN: break; + case DT_BLK: type = BH_DIRENT_BLOCK; break; + case DT_CHR: type = BH_DIRENT_CHAR; break; + case DT_DIR: type = BH_DIRENT_DIRECTORY; break; + case DT_LNK: type = BH_DIRENT_SYMLINK; break; + case DT_REG: type = BH_DIRENT_FILE; break; + default: type = BH_DIRENT_OTHER; break; + } + + if (out == NULL) return 1; + + out->type = type; + out->id = (u32) ent->d_ino; + out->name_length = strlen(ent->d_name); + strncpy(out->name, ent->d_name, 256); + + return 1; +#endif +} + +void bh_dir_close(bh_dir dir) { +#ifdef _BH_WINDOWS + if (dir == NULL) return; + + FindClose(dir->hndl); + free(dir); +#endif + +#ifdef _BH_LINUX + if (dir == NULL) return; + closedir(dir); +#endif +} + +#undef DIR_SEPARATOR + +#endif // ifndef BH_NO_FILE + + + + + + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// ALTERNATE PRINTF IMPLEMENTATION +//------------------------------------------------------------------------------------- +isize bh_printf(char const *fmt, ...) { + isize res; + va_list va; + va_start(va, fmt); + res = bh_printf_va(fmt, va); + va_end(va); + return res; +} + +isize bh_printf_va(char const *fmt, va_list va) { + bh_file file; + bh_file_get_standard(&file, BH_FILE_STANDARD_OUTPUT); + return bh_fprintf_va(&file, fmt, va); +} + +isize bh_printf_err(char const *fmt, ...) { + isize res; + va_list va; + va_start(va, fmt); + res = bh_printf_err_va(fmt, va); + va_end(va); + return res; +} + +isize bh_printf_err_va(char const *fmt, va_list va) { + bh_file file; + bh_file_get_standard(&file, BH_FILE_STANDARD_ERROR); + return bh_fprintf_va(&file, fmt, va); +} + +isize bh_fprintf(bh_file* f, char const *fmt, ...) { + isize res; + va_list va; + va_start(va, fmt); + res = bh_fprintf_va(f, fmt, va); + va_end(va); + return res; +} + +isize bh_fprintf_va(bh_file* f, char const *fmt, va_list va) { + static char buffer[4096]; + isize len = bh_snprintf_va(buffer, sizeof(buffer), fmt, va); + bh_file_write(f, buffer, len - 1); + return len; +} + +char* bh_bprintf(char const *fmt, ...) { + char* res; + va_list va; + va_start(va, fmt); + res = bh_bprintf_va(fmt, va); + va_end(va); + return res; +} + +char* bh_bprintf_va(char const *fmt, va_list va) { + static char buffer[4096]; + isize len = bh_snprintf_va(buffer, sizeof(buffer), fmt, va); + buffer[len - 1] = 0; + return buffer; +} + +char* bh_aprintf(bh_allocator alloc, const char* fmt, ...) { + char* res; + va_list va; + va_start(va, fmt); + res = bh_aprintf_va(alloc, fmt, va); + va_end(va); + return res; +} + +char* bh_aprintf_va(bh_allocator alloc, const char* fmt, va_list va) { + static char buffer[4096]; + isize len = bh_snprintf_va(buffer, sizeof(buffer), fmt, va); + char* res = bh_alloc(alloc, len); + memcpy(res, buffer, len); + res[len - 1] = 0; + return res; +} + +isize bh_snprintf(char *str, isize n, char const *fmt, ...) { + isize res; + va_list va; + va_start(va, fmt); + res = bh_snprintf_va(str, n, fmt, va); + va_end(va); + return res; +} + +isize bh__print_string(char* dest, isize n, char* src) { + isize len = 0; + while (n-- && (*dest++ = *src++)) len++; + return len; +} + +isize bh__printu64(char* str, isize n, bh__print_format format, u64 value) { + char buf[128]; + buf[127] = 0; + char* walker = buf + 127; + u32 base = format.base ? format.base : 10, tmp; + + while (value > 0) { + tmp = value % base; + if (tmp > 9) { + switch (tmp) { + case 10: tmp = 'a'; break; + case 11: tmp = 'b'; break; + case 12: tmp = 'c'; break; + case 13: tmp = 'd'; break; + case 14: tmp = 'e'; break; + case 15: tmp = 'f'; break; + } + } else { + tmp += '0'; + } + + *--walker = tmp; + value /= base; + } + + if (format.base == 16) { + *--walker = 'x'; + *--walker = '0'; + } + + return bh__print_string(str, n, walker); +} + +isize bh__printi64(char* str, isize n, bh__print_format format, i64 value) { + char buf[128]; + buf[127] = 0; + char* walker = buf + 127; + u32 base = format.base ? format.base : 10, tmp; + + b32 negative = value < 0 ? 1 : 0; + if (negative) value = -value; + + if (value == 0) { + *--walker = '0'; + } else { + while (value > 0) { + tmp = value % base; + if (tmp > 9) { + switch (tmp) { + case 10: tmp = 'a'; break; + case 11: tmp = 'b'; break; + case 12: tmp = 'c'; break; + case 13: tmp = 'd'; break; + case 14: tmp = 'e'; break; + case 15: tmp = 'f'; break; + } + } else { + tmp += '0'; + } + + *--walker = tmp; + value /= base; + } + } + + if (negative) { + *--walker = '-'; + } + + if (format.base == 16) { + *--walker = 'x'; + *--walker = '0'; + } + + return bh__print_string(str, n, walker); +} + +// TODO: This implementation is VERY VERY BAD AND WRONG. Fix it. +isize bh__printf64(char* str, isize n, f64 value) { + fori (i, 0, 6) value *= 10.0; + i64 v = (i64) value; + + isize l1 = bh__printi64(str, n, ((bh__print_format) { .base = 10 }), v / 1000000); + str += l1; + n -= l1; + + *str = '.'; + str += 1; + n -= 1; + + isize l2 = bh__printi64(str, n, ((bh__print_format) { .base = 10 }), bh_abs(v) % 1000000); + + return l1 + l2 + 1; +} + +// TODO: This is very hacked together but for now it will work. +isize bh_snprintf_va(char *str, isize n, char const *fmt, va_list va) { + char const *text_start = str; + isize res; + + while (*fmt) { + bh__print_format format = { 0 }; + isize len = 0; + + while (*fmt && *fmt != '%') { + *(str++) = *(fmt++); + } + + if (!*fmt) goto end_of_format; + + fmt++; + + switch (*fmt++) { + case 'o': format.base = 8; break; + case 'x': format.base = 16; break; + default: fmt--; + } + + switch (*fmt) { + case 'c': { + char c = (char) va_arg(va, int); + *(str++) = c; + } break; + + case 'd': { + i64 value = (i64) va_arg(va, int); + len = bh__printi64(str, n, format, value); + } break; + + case 'l': { + i64 value = (i64) va_arg(va, long); + len = bh__printi64(str, n, format, value); + } break; + + case 'p': { + u64 value = (u64) va_arg(va, ptr); + format.base = 16; + len = bh__printu64(str, n, format, value); + } break; + + case 's': { + char* s = va_arg(va, char *); + len = bh__print_string(str, n, s); + } break; + + case 'b': { // String with a length (not null terminated) + char* s = va_arg(va, char *); + i32 l = va_arg(va, int); + len = bh__print_string(str, bh_min(l, n), s); + } break; + + case 'f': { + f64 f = va_arg(va, f64); + len = bh__printf64(str, n, f); + } break; + + default: fmt--; + } + + fmt++; + +end_of_format: + str += len; + n -= len; + } + + return str - text_start + 1; +} + + + + + +//------------------------------------------------------------------------------------- +// FLEXIBLE BUFFER IMPLEMENTATION +//------------------------------------------------------------------------------------- +#ifndef BH_NO_BUFFER + +void bh_buffer_init(bh_buffer* buffer, bh_allocator alloc, i32 init_size) { + buffer->allocator = alloc; + buffer->length = 0; + buffer->capacity = init_size; + buffer->data = bh_alloc(alloc, init_size); +} + +void bh_buffer_free(bh_buffer* buffer) { + bh_free(buffer->allocator, buffer->data); + buffer->length = 0; + buffer->capacity = 0; +} + +void bh_buffer_clear(bh_buffer* buffer) { + buffer->length = 0; +} + +void bh_buffer_grow(bh_buffer* buffer, i32 length) { + if (buffer == NULL) return; + + if (buffer->capacity >= length) { + // NOTE: Already have enough room + return; + } + + i32 newcap = buffer->capacity; + while (newcap < length) newcap = BH_BUFFER_GROW_FORMULA(newcap); + + ptr new_data = bh_resize(buffer->allocator, buffer->data, newcap); + if (new_data == NULL) return; + + buffer->capacity = newcap; + buffer->data = new_data; +} + +void bh_buffer_append(bh_buffer* buffer, const void * data, i32 length) { + if (buffer == NULL) return; + + if (buffer->length + length > buffer->capacity) { + bh_buffer_grow(buffer, buffer->length + length); + } + + memcpy(buffer->data + buffer->length, data, length); + buffer->length += length; +} + +void bh_buffer_concat(bh_buffer* buffer, bh_buffer other) { + bh_buffer_append(buffer, other.data, other.length); +} + +void bh_buffer_write_byte(bh_buffer* buffer, u8 byte) { + bh_buffer_grow(buffer, buffer->length + 1); + buffer->data[buffer->length++] = byte; +} + +void bh_buffer_write_u32(bh_buffer* buffer, u32 i) { + bh_buffer_grow(buffer, buffer->length + 4); + *((u32 *) bh_pointer_add(buffer->data, buffer->length)) = i; + buffer->length += 4; +} + +void bh_buffer_write_u64(bh_buffer* buffer, u64 i) { + bh_buffer_grow(buffer, buffer->length + 8); + *((u64 *) bh_pointer_add(buffer->data, buffer->length)) = i; + buffer->length += 8; +} + +void bh_buffer_align(bh_buffer* buffer, u32 alignment) { + if (buffer->length % alignment != 0) { + u32 difference = alignment - (buffer->length % alignment); + buffer->length += difference; + + bh_buffer_grow(buffer, buffer->length); + } +} + + +#endif + + + + + + + + + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// ARRAY IMPLEMENTATION +//------------------------------------------------------------------------------------- +#ifndef BH_NO_ARRAY + +b32 bh__arr_grow(bh_allocator alloc, void** arr, i32 elemsize, i32 cap) { + bh__arr* arrptr; + + if (*arr == NULL) { + if (cap == 0 && elemsize == 0) return 1; + + arrptr = (bh__arr *) bh_alloc(alloc, sizeof(*arrptr) + elemsize * cap); + if (arrptr == NULL) return 0; + + arrptr->allocator = alloc; + arrptr->capacity = cap; + arrptr->length = 0; + + } else { + arrptr = bh__arrhead(*arr); + + if (arrptr->capacity < cap) { + void* p; + i32 newcap = arrptr->capacity; + while (newcap < cap) newcap = BH_ARR_GROW_FORMULA(newcap); + + p = bh_resize(arrptr->allocator, arrptr, sizeof(*arrptr) + elemsize * newcap); + + if (p) { + arrptr = (bh__arr *) p; + arrptr->capacity = newcap; + } else { + return 0; + } + } + } + + *arr = arrptr + 1; + return 1; +} + +b32 bh__arr_shrink(void** arr, i32 elemsize, i32 cap) { + if (*arr == NULL) return 0; + + bh__arr* arrptr = bh__arrhead(*arr); + cap = bh_max(cap, arrptr->length); + + if (arrptr->capacity > cap) { + void* p = bh_resize(arrptr->allocator, arrptr, sizeof(*arrptr) + elemsize * cap); + + if (p) { + arrptr = (bh__arr *) p; + arrptr->capacity = cap; + } else { + return 0; + } + } + + *arr = arrptr + 1; + return 1; +} + +b32 bh__arr_free(void **arr) { + if (*arr == NULL) return 0; + + bh__arr* arrptr = bh__arrhead(*arr); + bh_free(arrptr->allocator, arrptr); + *arr = NULL; + return 1; +} + +void* bh__arr_copy(bh_allocator alloc, void *arr, i32 elemsize) { + bh__arr* arrptr = bh__arrhead(arr); + + const i32 cap = arrptr->length; + + void* newarr = NULL; + bh__arr_grow(alloc, &newarr, elemsize, cap); + bh__arrhead(newarr)->length = cap; + bh__arrhead(newarr)->capacity = cap; + memcpy(newarr, arr, elemsize * arrptr->length); + + return newarr; +} + +void bh__arr_deleten(void **arr, i32 elemsize, i32 index, i32 numelems) { + bh__arr* arrptr = bh__arrhead(*arr); + + if (index >= arrptr->length) return; // Can't delete past the end of the array + if (numelems <= 0) return; // Can't delete nothing + + memmove( + (char *)(*arr) + elemsize * index, // Target + (char *)(*arr) + elemsize * (index + numelems), // Source + elemsize * (arrptr->length - (index + numelems))); // Length + arrptr->length -= numelems; +} + +void bh__arr_insertn(void **arr, i32 elemsize, i32 index, i32 numelems) { + if (numelems) { + if (*arr == NULL) { + bh__arr_grow(bh_arr_allocator(arr), arr, elemsize, numelems); // Making a new array + return; + } + + bh__arr* arrptr = bh__arrhead(*arr); + if (!bh__arr_grow(bh_arr_allocator(arr), arr, elemsize, arrptr->length + numelems)) return; // Fail case + arrptr = bh__arrhead(*arr); + memmove( + (char *)(*arr) + elemsize * (index + numelems), + (char *)(*arr) + elemsize * index, + elemsize * (arrptr->length - index)); + arrptr->length += numelems; + } +} + +#endif // ifndef BH_NO_ARRAY + + + + + + + + + + + + + + + +//------------------------------------------------------------------------------------- +// TABLE IMPLEMENTATION +//------------------------------------------------------------------------------------- +#ifndef BH_NO_TABLE + +b32 bh__table_init(bh_allocator allocator, bh__table **table, i32 table_size) { + *table = bh_alloc(allocator, sizeof(bh__table) + sizeof(ptr) * table_size); + if (*table == NULL) return 0; + + (*table)->allocator = allocator; + (*table)->table_size = table_size; + + for (i32 i = 0; i < table_size; i++) { + (*table)->arrs[i] = NULL; + } + + return 1; +} + +b32 bh__table_free(bh__table **table) { + if (*table == NULL) return 0; + + for (u64 i = 0; i < (*table)->table_size; i++) { + if ((*table)->arrs[i] != NULL) { + bh_arr_free((*table)->arrs[i]); + } + } + + bh_free((*table)->allocator, *table); + *table = NULL; + return 1; +} + +// Assumes NULL terminated string for key +ptr bh__table_put(bh__table *table, i32 elemsize, char *key) { + elemsize += (elemsize & 1); + + u64 index = bh__table_hash_function(key, 0, table->table_size); + u8 arr_was_new = 0; + + ptr arrptr = table->arrs[index]; + if (arrptr == NULL) { + arr_was_new = 1; + goto add_new_element; + } + u64 len = *(u64 *) arrptr; + arrptr = bh_pointer_add(arrptr, sizeof(u64)); + + u16 key_length = 0; + while (len--) { + arrptr = bh_pointer_add(arrptr, elemsize); + key_length = *(u16 *) arrptr; + arrptr = bh_pointer_add(arrptr, sizeof(u16)); + if (strncmp(key, (char *) arrptr, key_length) == 0) goto found_matching; + arrptr = bh_pointer_add(arrptr, key_length); + } + +add_new_element: + arrptr = table->arrs[index]; + i32 byte_len = bh_arr_length(arrptr); + if (byte_len == 0) byte_len = sizeof(u64); + key_length = strlen(key) + 1; + + // NOTE: Align to 16 bytes + if ((key_length + 2) % 16 != 0) { + key_length = ((((key_length + 2) >> 4) + 1) << 4) - 2; + } + + bh__arr_grow(table->allocator, &arrptr, 1, byte_len + elemsize + sizeof(u16) + key_length); + bh__arrhead(arrptr)->length = byte_len + elemsize + sizeof(u16) + key_length; + table->arrs[index] = arrptr; + + if (arr_was_new) { + *(u64 *) arrptr = 1; + } else { + (*(u64 *) arrptr)++; + } + + arrptr = bh_pointer_add(arrptr, byte_len + elemsize); + *(u16 *) arrptr = key_length; + arrptr = bh_pointer_add(arrptr, sizeof(u16)); + strncpy(arrptr, key, key_length); + +found_matching: + return bh_pointer_add(arrptr, -(sizeof(u16) + elemsize)); +} + +b32 bh__table_has(bh__table *table, i32 elemsize, char *key) { + elemsize += (elemsize & 1); + + u64 index = bh__table_hash_function(key, 0, table->table_size); + + ptr arrptr = table->arrs[index]; + if (arrptr == NULL) return 0; + + u64 len = *(u64 *) arrptr; + arrptr = bh_pointer_add(arrptr, sizeof(u64)); + + u16 key_length = 0; + while (len--) { + arrptr = bh_pointer_add(arrptr, elemsize); + key_length = *(u16 *) arrptr; + arrptr = bh_pointer_add(arrptr, sizeof(u16)); + if (strncmp(key, (char *) arrptr, key_length) == 0) return 1; + arrptr = bh_pointer_add(arrptr, key_length); + } + + return 0; +} + +ptr bh__table_get(bh__table *table, i32 elemsize, char *key) { + elemsize += (elemsize & 1); + + u64 index = bh__table_hash_function(key, 0, table->table_size); + + ptr arrptr = table->arrs[index]; + if (arrptr == NULL) return 0; + + u64 len = *(u64 *) arrptr; + arrptr = bh_pointer_add(arrptr, sizeof(u64)); + + u16 key_length = 0; + while (len--) { + arrptr = bh_pointer_add(arrptr, elemsize); + key_length = *(u16 *) arrptr; + arrptr = bh_pointer_add(arrptr, sizeof(u16)); + if (strncmp(key, (char *) arrptr, key_length) == 0) { + return bh_pointer_add(arrptr, -(sizeof(u16) + elemsize)); + } + arrptr = bh_pointer_add(arrptr, key_length); + } + + return NULL; +} + +void bh__table_delete(bh__table *table, i32 elemsize, char *key) { + elemsize += (elemsize & 1); + + u64 index = bh__table_hash_function(key, 0, table->table_size); + + ptr arrptr = table->arrs[index], walker; + if (arrptr == NULL) return; // Didn't exist + walker = arrptr; + + i32 byte_offset = 8; + i32 delete_len = 0; + + u64 len = *(u64 *) walker; + walker = bh_pointer_add(walker, sizeof(u64)); + + u16 key_length = 0; + while (len--) { + walker = bh_pointer_add(walker, elemsize); + key_length = *(u16 *) walker; + walker = bh_pointer_add(walker, sizeof(u16)); + if (strncmp(key, (char *) walker, key_length) == 0) { + delete_len = elemsize + sizeof(u16) + key_length; + goto found_matching; + } + walker = bh_pointer_add(walker, key_length); + byte_offset += elemsize + sizeof(u16) + key_length; + } + + // NOTE: Already didn't exist + return; + +found_matching: + bh__arr_deleten((void **) &arrptr, 1, byte_offset, delete_len); + table->arrs[index] = arrptr; + (*(u64 *) arrptr)--; +} + +void bh__table_clear(bh__table *table) { + for (u64 i = 0; i < table->table_size; i++) { + if (table->arrs[i] != NULL) { + // NOTE: Set length property to 0 + *((u64 *) table->arrs[i]) = 0; + bh_arr_set_length(table->arrs[i], 0); + } + } +} + +bh_table_iterator bh__table_iter_setup(bh__table *table, i32 elemsize) { + elemsize += (elemsize & 1); + + bh_table_iterator it = { + .tab = table->arrs, + .endtab = table->arrs + table->table_size, + .elemsize = elemsize, + .entry = NULL + }; + return it; +} + +b32 bh_table_iter_next(bh_table_iterator* it) { + if (it->tab == NULL) return 0; + + if (it->entry != NULL) { + it->arrlen--; + if (it->arrlen <= 0) { + it->tab++; + goto step_to_next; + } + + it->entry = bh_pointer_add(it->entry, it->elemsize); + it->entry = bh_pointer_add(it->entry, sizeof(u16) + (*(u16 *) it->entry)); + return 1; + } + +step_to_next: + // Step forward to find next valid + while (it->tab != it->endtab && *it->tab == NULL) { + it->tab++; + } + + if (it->tab == it->endtab) return 0; + + it->entry = *it->tab; + it->arrlen = *(u64 *) it->entry; + it->entry = bh_pointer_add(it->entry, sizeof(u64)); + if (it->arrlen <= 0) { + it->tab++; + goto step_to_next; + } + return 1; +} + +#endif // ifndef BH_NO_HASHTABLE + + + +//------------------------------------------------------------------------------------- +// IMAP IMPLEMENTATION +//------------------------------------------------------------------------------------- +#ifndef BH_NO_IMAP +void bh_imap_init(bh_imap* imap, bh_allocator alloc, i32 hash_count) { + imap->allocator = alloc; + + imap->hashes = NULL; + imap->entries = NULL; + + bh_arr_new(alloc, imap->hashes, hash_count); + bh_arr_new(alloc, imap->entries, 4); + + fori(count, 0, hash_count) bh_arr_push(imap->hashes, -1); +} + +void bh_imap_free(bh_imap* imap) { + bh_arr_free(imap->hashes); + bh_arr_free(imap->entries); + + imap->hashes = NULL; + imap->entries = NULL; +} + +bh__imap_lookup_result bh__imap_lookup(bh_imap* imap, bh_imap_entry_t key) { + bh__imap_lookup_result lr = { -1, -1, -1 }; + + u64 hash = 0xcbf29ce484222325ull ^ key; + u64 n = bh_arr_capacity(imap->hashes); + + lr.hash_index = hash % n; + lr.entry_index = imap->hashes[lr.hash_index]; + while (lr.entry_index >= 0) { + if (imap->entries[lr.entry_index].key == key) { + return lr; + } + + lr.entry_prev = lr.entry_index; + lr.entry_index = imap->entries[lr.entry_index].next; + } + + return lr; +} + +void bh_imap_put(bh_imap* imap, bh_imap_entry_t key, bh_imap_entry_t value) { + bh__imap_lookup_result lr = bh__imap_lookup(imap, key); + + if (lr.entry_index >= 0) { + imap->entries[lr.entry_index].value = value; + return; + } + + bh__imap_entry entry; + entry.key = key; + entry.value = value; + entry.next = imap->hashes[lr.hash_index]; + bh_arr_push(imap->entries, entry); + + imap->hashes[lr.hash_index] = bh_arr_length(imap->entries) - 1; +} + +b32 bh_imap_has(bh_imap* imap, bh_imap_entry_t key) { + bh__imap_lookup_result lr = bh__imap_lookup(imap, key); + return lr.entry_index >= 0; +} + +bh_imap_entry_t bh_imap_get(bh_imap* imap, bh_imap_entry_t key) { + bh__imap_lookup_result lr = bh__imap_lookup(imap, key); + if (lr.entry_index >= 0) { + return imap->entries[lr.entry_index].value; + } else { + return 0; + } +} + +void bh_imap_delete(bh_imap* imap, bh_imap_entry_t key) { + bh__imap_lookup_result lr = bh__imap_lookup(imap, key); + if (lr.entry_index < 0) return; + + if (lr.entry_prev < 0) { + imap->hashes[lr.hash_index] = imap->entries[lr.entry_index].next; + } else { + imap->entries[lr.entry_prev].next = imap->entries[lr.entry_index].next; + } + + // If it's that last thing in the array, just pop off the end + if (lr.entry_index == bh_arr_length(imap->entries) - 1) { + bh_arr_pop(imap->entries); + return; + } + + bh_arr_fastdelete(imap->entries, lr.entry_index); + bh__imap_lookup_result last = bh__imap_lookup(imap, imap->entries[lr.entry_index].key); + if (last.entry_prev >= 0) { + imap->entries[last.entry_prev].next = lr.entry_index; + } else { + imap->hashes[last.hash_index] = lr.entry_index; + } +} + +void bh_imap_clear(bh_imap* imap) { + // NOTE: Does not clear out an of the data that was in the map + bh_arr_each(i64, hash, imap->hashes) *hash = -1; + bh_arr_set_length(imap->entries, 0); +} + +#endif // ifndef BH_NO_IMAP + + + + + +u64 bh_time_curr() { +#if defined(_BH_WINDOWS) + LARGE_INTEGER result; + QueryPerformanceCounter(&result); + return (u64) result.QuadPart; + +#elif defined(_BH_LINUX) + struct timespec spec; + clock_gettime(CLOCK_REALTIME, &spec); + + time_t sec = spec.tv_sec; + u64 ms = spec.tv_nsec / 1000000; + if (ms > 999) { + sec++; + ms = 0; + } + + return sec * 1000 + ms; +#endif +} + +u64 bh_time_duration(u64 old) { +#if defined(_BH_WINDOWS) + u64 curr = bh_time_curr(); + u64 duration = curr - old; + + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + duration *= 1000; + duration /= freq.QuadPart; + return duration; + +#elif defined(_BH_LINUX) + u64 curr = bh_time_curr(); + return curr - old; +#endif +} + +#endif // ifdef BH_DEFINE + +#endif // ifndef BH_H diff --git a/shared/include/onyx_library.h b/shared/include/onyx_library.h new file mode 100644 index 00000000..573de304 --- /dev/null +++ b/shared/include/onyx_library.h @@ -0,0 +1,102 @@ + +#include "wasm.h" + +#if defined(_WIN32) || defined(_WIN64) + #define ONYX_EXPORT extern __declspec(dllexport) + #define ONYX_IMPORT extern __declspec(dllimport) +#endif + +#if defined(__unix__) + #define ONYX_EXPORT + #define ONYX_IMPORT +#endif + +typedef struct OnyxRuntime { + wasm_instance_t* wasm_instance; + wasm_module_t* wasm_module; + wasm_memory_t* wasm_memory; + wasm_engine_t *wasm_engine; + wasm_extern_vec_t wasm_imports; + + int argc; + char **argv; + + // HACK HACK HACK + // There should need to be this much stuff in here, but because Wasmer doesn't ship a "wasmerdll.lib" + // file for windows, it is impossible for it to link successfully against the function provided in onyx.exe. + // Therefore, we must serve as the linker and do this manually. Hopefully that library file will be + // shipped soon so this can go away... + char* (*wasm_memory_data)(wasm_memory_t *wasm_memory); + wasm_extern_t* (*wasm_extern_lookup_by_name)(wasm_module_t* module, wasm_instance_t* instance, const char* name); + wasm_func_t* (*wasm_extern_as_func)(wasm_extern_t* ext); + wasm_trap_t* (*wasm_func_call)(const wasm_func_t* wasm_func, const wasm_val_vec_t* args, wasm_val_vec_t* results); + wasm_instance_t* (*wasm_instance_new)(wasm_store_t* store, const wasm_module_t* module, const wasm_extern_vec_t* imports, wasm_trap_t** traps); + void (*onyx_print_trap)(wasm_trap_t* trap); + wasm_store_t *(*wasm_store_new)(wasm_engine_t *engine); + void (*wasm_store_delete)(wasm_store_t *store); +} OnyxRuntime; + +OnyxRuntime* runtime; + +typedef struct WasmValkindBuffer { + unsigned int count; + wasm_valkind_t types[20]; +} WasmValkindBuffer; + +typedef struct WasmFuncDefinition { + char* module_name; + char* import_name; + wasm_func_callback_t func; + + WasmValkindBuffer *params; + WasmValkindBuffer *results; +} WasmFuncDefinition; + +#define STRINGIFY1(a) #a +#define STRINGIFY2(a) STRINGIFY1(a) +#define CONCAT2(a, b) a ## _ ## b +#define CONCAT3(a, b, c) a ## _ ## b ## _ ## c +#define ONYX_MODULE_NAME_GEN(m) CONCAT2(__onyx_library, m) +#define ONYX_LINK_NAME_GEN(m) CONCAT2(onyx_library, m) +#define ONYX_FUNC_NAME(m, n) CONCAT3(__onyx_internal, m, n) +#define ONYX_DEF_NAME(m, n) CONCAT3(__onyx_internal_def, m, n) +#define ONYX_PARAM_NAME(m, n) CONCAT3(__onyx_internal_param_buffer, m, n) +#define ONYX_RESULT_NAME(m, n) CONCAT3(__onyx_internal_result_buffer, m, n) +#define ONYX_IMPORT_NAME(m, n) STRINGIFY1(m) "_" #n + +#define NUM_VALS(...) (sizeof((wasm_valkind_t []){ 0, __VA_ARGS__ }) / sizeof(wasm_valkind_t)) +#define _VALS(...) { NUM_VALS(__VA_ARGS__) - 1, __VA_ARGS__ } + +#define ONYX_DEF(name, params_types, result_types) \ + static wasm_trap_t* ONYX_FUNC_NAME(ONYX_LIBRARY_NAME, name)(const wasm_val_vec_t* params, wasm_val_vec_t* results); \ + static struct WasmValkindBuffer ONYX_PARAM_NAME(ONYX_LIBRARY_NAME, name) = _VALS params_types; \ + static struct WasmValkindBuffer ONYX_RESULT_NAME(ONYX_LIBRARY_NAME, name) = _VALS result_types; \ + static struct WasmFuncDefinition ONYX_DEF_NAME(ONYX_LIBRARY_NAME, name) = { STRINGIFY2(ONYX_LIBRARY_NAME), #name, ONYX_FUNC_NAME(ONYX_LIBRARY_NAME, name), & ONYX_PARAM_NAME(ONYX_LIBRARY_NAME, name), & ONYX_RESULT_NAME(ONYX_LIBRARY_NAME, name) }; \ + \ + static wasm_trap_t* ONYX_FUNC_NAME(ONYX_LIBRARY_NAME, name)(const wasm_val_vec_t* params, wasm_val_vec_t* results) + +#define ONYX_FUNC(name) & ONYX_DEF_NAME(ONYX_LIBRARY_NAME, name), +#define ONYX_LIBRARY \ + extern struct WasmFuncDefinition *ONYX_MODULE_NAME_GEN(ONYX_LIBRARY_NAME)[]; \ + ONYX_EXPORT WasmFuncDefinition** ONYX_LINK_NAME_GEN(ONYX_LIBRARY_NAME)(OnyxRuntime* in_runtime) { \ + runtime = in_runtime; \ + return ONYX_MODULE_NAME_GEN(ONYX_LIBRARY_NAME); \ + } \ + struct WasmFuncDefinition *ONYX_MODULE_NAME_GEN(ONYX_LIBRARY_NAME)[] = + +// Shorter names +#ifndef ONYX_NO_SHORT_NAMES +#undef BOOL +#undef INT +#undef LONG +#undef FLOAT +#undef DOUBLE +#define BOOL WASM_I32 +#define INT WASM_I32 +#define LONG WASM_I64 +#define FLOAT WASM_F32 +#define DOUBLE WASM_F64 +#define PTR WASM_I32 +#endif + +#define ONYX_PTR(p) ((void*) (p != 0 ? (runtime->wasm_memory_data(runtime->wasm_memory) + p) : NULL)) diff --git a/shared/include/small_windows.h b/shared/include/small_windows.h new file mode 100644 index 00000000..28cea0bd --- /dev/null +++ b/shared/include/small_windows.h @@ -0,0 +1,444 @@ +// Bill's Mini Windows.h from https://github.com/odin-lang/Odin/blob/master/src/gb/gb.h + +//////////////////////////////////////////////////////////////// +// +// Bill's Mini Windows.h +// +// + +#define GB_EXTERN extern +#define GB_DLL_EXPORT GB_EXTERN __declspec(dllexport) +#define GB_DLL_IMPORT GB_EXTERN __declspec(dllimport) + +#define WINAPI __stdcall +#define WINAPIV __cdecl +#define CALLBACK __stdcall +#define MAX_PATH 260 +#define CCHDEVICENAME 32 +#define CCHFORMNAME 32 + +typedef unsigned long DWORD; +typedef int WINBOOL; +#ifndef XFree86Server + #ifndef __OBJC__ + typedef WINBOOL BOOL; + #else + #define BOOL WINBOOL + #endif +typedef unsigned char BYTE; +#endif +typedef unsigned short WORD; +typedef float FLOAT; +typedef int INT; +typedef unsigned int UINT; +typedef short SHORT; +typedef long LONG; +typedef long long LONGLONG; +typedef unsigned short USHORT; +typedef unsigned long ULONG; +typedef unsigned long long ULONGLONG; + +typedef UINT WPARAM; +typedef LONG LPARAM; +typedef LONG LRESULT; +#ifndef _HRESULT_DEFINED +typedef LONG HRESULT; +#define _HRESULT_DEFINED +#endif +#ifndef XFree86Server +typedef WORD ATOM; +#endif /* XFree86Server */ +typedef void *HANDLE; +typedef HANDLE HGLOBAL; +typedef HANDLE HLOCAL; +typedef HANDLE GLOBALHANDLE; +typedef HANDLE LOCALHANDLE; +typedef void *HGDIOBJ; + +#define DECLARE_HANDLE(name) typedef HANDLE name +DECLARE_HANDLE(HACCEL); +DECLARE_HANDLE(HBITMAP); +DECLARE_HANDLE(HBRUSH); +DECLARE_HANDLE(HCOLORSPACE); +DECLARE_HANDLE(HDC); +DECLARE_HANDLE(HGLRC); +DECLARE_HANDLE(HDESK); +DECLARE_HANDLE(HENHMETAFILE); +DECLARE_HANDLE(HFONT); +DECLARE_HANDLE(HICON); +DECLARE_HANDLE(HKEY); +typedef HKEY *PHKEY; +DECLARE_HANDLE(HMENU); +DECLARE_HANDLE(HMETAFILE); +DECLARE_HANDLE(HINSTANCE); +typedef HINSTANCE HMODULE; +DECLARE_HANDLE(HPALETTE); +DECLARE_HANDLE(HPEN); +DECLARE_HANDLE(HRGN); +DECLARE_HANDLE(HRSRC); +DECLARE_HANDLE(HSTR); +DECLARE_HANDLE(HTASK); +DECLARE_HANDLE(HWND); +DECLARE_HANDLE(HWINSTA); +DECLARE_HANDLE(HKL); +DECLARE_HANDLE(HRAWINPUT); +DECLARE_HANDLE(HMONITOR); +#undef DECLARE_HANDLE + +typedef int HFILE; +typedef HICON HCURSOR; +typedef DWORD COLORREF; +typedef int (WINAPI *FARPROC)(); +typedef int (WINAPI *NEARPROC)(); +typedef int (WINAPI *PROC)(); +typedef LRESULT (CALLBACK *WNDPROC)(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +#if defined(_WIN64) +typedef unsigned __int64 ULONG_PTR; +typedef signed __int64 LONG_PTR; +#else +typedef unsigned long ULONG_PTR; +typedef signed long LONG_PTR; +#endif +typedef ULONG_PTR DWORD_PTR; + +typedef struct tagRECT { + LONG left; + LONG top; + LONG right; + LONG bottom; +} RECT; +typedef struct tagRECTL { + LONG left; + LONG top; + LONG right; + LONG bottom; +} RECTL; +typedef struct tagPOINT { + LONG x; + LONG y; +} POINT; +typedef struct tagSIZE { + LONG cx; + LONG cy; +} SIZE; +typedef struct tagPOINTS { + SHORT x; + SHORT y; +} POINTS; +typedef struct _SECURITY_ATTRIBUTES { + DWORD nLength; + HANDLE lpSecurityDescriptor; + BOOL bInheritHandle; +} SECURITY_ATTRIBUTES; +typedef enum _LOGICAL_PROCESSOR_RELATIONSHIP { + RelationProcessorCore, + RelationNumaNode, + RelationCache, + RelationProcessorPackage, + RelationGroup, + RelationAll = 0xffff +} LOGICAL_PROCESSOR_RELATIONSHIP; +typedef enum _PROCESSOR_CACHE_TYPE { + CacheUnified, + CacheInstruction, + CacheData, + CacheTrace +} PROCESSOR_CACHE_TYPE; +typedef struct _CACHE_DESCRIPTOR { + BYTE Level; + BYTE Associativity; + WORD LineSize; + DWORD Size; + PROCESSOR_CACHE_TYPE Type; +} CACHE_DESCRIPTOR; +typedef struct _SYSTEM_LOGICAL_PROCESSOR_INFORMATION { + ULONG_PTR ProcessorMask; + LOGICAL_PROCESSOR_RELATIONSHIP Relationship; + union { + struct { + BYTE Flags; + } ProcessorCore; + struct { + DWORD NodeNumber; + } NumaNode; + CACHE_DESCRIPTOR Cache; + ULONGLONG Reserved[2]; + }; +} SYSTEM_LOGICAL_PROCESSOR_INFORMATION; +typedef struct _MEMORY_BASIC_INFORMATION { + void *BaseAddress; + void *AllocationBase; + DWORD AllocationProtect; + size_t RegionSize; + DWORD State; + DWORD Protect; + DWORD Type; +} MEMORY_BASIC_INFORMATION; +typedef struct _SYSTEM_INFO { + union { + DWORD dwOemId; + struct { + WORD wProcessorArchitecture; + WORD wReserved; + }; + }; + DWORD dwPageSize; + void * lpMinimumApplicationAddress; + void * lpMaximumApplicationAddress; + DWORD_PTR dwActiveProcessorMask; + DWORD dwNumberOfProcessors; + DWORD dwProcessorType; + DWORD dwAllocationGranularity; + WORD wProcessorLevel; + WORD wProcessorRevision; +} SYSTEM_INFO; +typedef union _LARGE_INTEGER { + struct { + DWORD LowPart; + LONG HighPart; + }; + struct { + DWORD LowPart; + LONG HighPart; + } u; + LONGLONG QuadPart; +} LARGE_INTEGER; +typedef union _ULARGE_INTEGER { + struct { + DWORD LowPart; + DWORD HighPart; + }; + struct { + DWORD LowPart; + DWORD HighPart; + } u; + ULONGLONG QuadPart; +} ULARGE_INTEGER; + +typedef struct _OVERLAPPED { + ULONG_PTR Internal; + ULONG_PTR InternalHigh; + union { + struct { + DWORD Offset; + DWORD OffsetHigh; + }; + void *Pointer; + }; + HANDLE hEvent; +} OVERLAPPED; +typedef struct _FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} FILETIME; +typedef struct _WIN32_FIND_DATAW { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + wchar_t cFileName[MAX_PATH]; + wchar_t cAlternateFileName[14]; +} WIN32_FIND_DATAW; +typedef struct _WIN32_FIND_DATAA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + char cFileName[MAX_PATH]; + char cAlternateFileName[14]; +} WIN32_FIND_DATAA; +typedef struct _WIN32_FILE_ATTRIBUTE_DATA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; +} WIN32_FILE_ATTRIBUTE_DATA; +typedef enum _GET_FILEEX_INFO_LEVELS { + GetFileExInfoStandard, + GetFileExMaxInfoLevel +} GET_FILEEX_INFO_LEVELS; +typedef struct _STARTUPINFOA { + DWORD cb; + char * lpReserved; + char * lpDesktop; + char * lpTitle; + DWORD dwX; + DWORD dwY; + DWORD dwXSize; + DWORD dwYSize; + DWORD dwXCountChars; + DWORD dwYCountChars; + DWORD dwFillAttribute; + DWORD dwFlags; + WORD wShowWindow; + WORD cbReserved2; + char * lpReserved2; + HANDLE hStdInput; + HANDLE hStdOutput; + HANDLE hStdError; +} STARTUPINFOA, *LPSTARTUPINFOA; +typedef struct _PROCESS_INFORMATION { + HANDLE hProcess; + HANDLE hThread; + DWORD dwProcessId; + DWORD dwThreadId; +} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION; + +#define INFINITE 0xffffffffl +#define INVALID_HANDLE_VALUE ((void *)(intptr_t)(-1)) +#define STARTF_USESTDHANDLES 0x00000100 + +typedef DWORD WINAPI THREAD_START_ROUTINE(void *parameter); + +GB_DLL_IMPORT DWORD WINAPI GetLastError (void); +GB_DLL_IMPORT BOOL WINAPI CloseHandle (HANDLE object); +GB_DLL_IMPORT HANDLE WINAPI CreateSemaphoreA (SECURITY_ATTRIBUTES *semaphore_attributes, LONG initial_count, + LONG maximum_count, char const *name); +GB_DLL_IMPORT BOOL WINAPI ReleaseSemaphore (HANDLE semaphore, LONG release_count, LONG *previous_count); +GB_DLL_IMPORT DWORD WINAPI WaitForSingleObject(HANDLE handle, DWORD milliseconds); +GB_DLL_IMPORT HANDLE WINAPI CreateThread (SECURITY_ATTRIBUTES *semaphore_attributes, size_t stack_size, + THREAD_START_ROUTINE *start_address, void *parameter, + DWORD creation_flags, DWORD *thread_id); +GB_DLL_IMPORT DWORD WINAPI GetThreadId (HANDLE handle); +GB_DLL_IMPORT void WINAPI RaiseException (DWORD, DWORD, DWORD, ULONG_PTR const *); +GB_DLL_IMPORT BOOL WINAPI TerminateThread (HANDLE hThread, DWORD dwExitCode); +GB_DLL_IMPORT BOOL WINAPI CreateProcessA (char const * lpApplicationName, char * lpCommandLine, + SECURITY_ATTRIBUTES* lpProcessAttrs, SECURITY_ATTRIBUTES* lpThreadAttributes, + BOOL bInheritHandles, DWORD dwCreationFlags, void* lpEnvironment, + char const * lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, + LPPROCESS_INFORMATION lpProcessInformation); +GB_DLL_IMPORT BOOL WINAPI GetExitCodeProcess (HANDLE hProcess, DWORD *lpExitCode); +GB_DLL_IMPORT BOOL WINAPI CreatePipe (HANDLE *hReadPipe, HANDLE *hWritePipe, SECURITY_ATTRIBUTES* lpPipeAttributes, + DWORD nSize); +GB_DLL_IMPORT BOOL WINAPI TerminateProcess (HANDLE hProcess, UINT uExitCode); +GB_DLL_IMPORT BOOL WINAPI SetHandleInformation(HANDLE hObject, DWORD dwMask, DWORD dwFlags); + +uintptr_t _beginthreadex( // NATIVE CODE + void *security, + unsigned stack_size, + unsigned ( __stdcall *start_address )( void * ), + void *arglist, + unsigned initflag, + unsigned *thrdaddr +); + + +GB_DLL_IMPORT BOOL WINAPI GetLogicalProcessorInformation(SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buffer, DWORD *return_length); +GB_DLL_IMPORT DWORD_PTR WINAPI SetThreadAffinityMask(HANDLE thread, DWORD_PTR check_mask); +GB_DLL_IMPORT HANDLE WINAPI GetCurrentThread(void); + +#define PAGE_NOACCESS 0x01 +#define PAGE_READONLY 0x02 +#define PAGE_READWRITE 0x04 +#define PAGE_WRITECOPY 0x08 +#define PAGE_EXECUTE 0x10 +#define PAGE_EXECUTE_READ 0x20 +#define PAGE_EXECUTE_READWRITE 0x40 +#define PAGE_EXECUTE_WRITECOPY 0x80 +#define PAGE_GUARD 0x100 +#define PAGE_NOCACHE 0x200 +#define PAGE_WRITECOMBINE 0x400 + +#define MEM_COMMIT 0x1000 +#define MEM_RESERVE 0x2000 +#define MEM_DECOMMIT 0x4000 +#define MEM_RELEASE 0x8000 +#define MEM_FREE 0x10000 +#define MEM_PRIVATE 0x20000 +#define MEM_MAPPED 0x40000 +#define MEM_RESET 0x80000 +#define MEM_TOP_DOWN 0x100000 +#define MEM_LARGE_PAGES 0x20000000 +#define MEM_4MB_PAGES 0x80000000 + + + + +GB_DLL_IMPORT void * WINAPI VirtualAlloc (void *addr, size_t size, DWORD allocation_type, DWORD protect); +GB_DLL_IMPORT size_t WINAPI VirtualQuery (void const *address, MEMORY_BASIC_INFORMATION *buffer, size_t length); +GB_DLL_IMPORT BOOL WINAPI VirtualFree (void *address, size_t size, DWORD free_type); +GB_DLL_IMPORT void WINAPI GetSystemInfo(SYSTEM_INFO *system_info); + + +#define GENERIC_READ 0x80000000 +#define GENERIC_WRITE 0x40000000 +#define GENERIC_EXECUTE 0x20000000 +#define GENERIC_ALL 0x10000000 +#define FILE_SHARE_READ 0x00000001 +#define FILE_SHARE_WRITE 0x00000002 +#define FILE_SHARE_DELETE 0x00000004 +#define CREATE_NEW 1 +#define CREATE_ALWAYS 2 +#define OPEN_EXISTING 3 +#define OPEN_ALWAYS 4 +#define TRUNCATE_EXISTING 5 +#define FILE_ATTRIBUTE_READONLY 0x00000001 +#define FILE_ATTRIBUTE_NORMAL 0x00000080 +#define FILE_ATTRIBUTE_TEMPORARY 0x00000100 +#define FILE_ATTRIBUTE_DIRECTORY 0x00000010 +#define ERROR_FILE_NOT_FOUND 2l +#define ERROR_ACCESS_DENIED 5L +#define ERROR_NO_MORE_FILES 18l +#define ERROR_FILE_EXISTS 80l +#define ERROR_ALREADY_EXISTS 183l +#define STD_INPUT_HANDLE ((DWORD)-10) +#define STD_OUTPUT_HANDLE ((DWORD)-11) +#define STD_ERROR_HANDLE ((DWORD)-12) + +GB_DLL_IMPORT int MultiByteToWideChar(UINT code_page, DWORD flags, char const * multi_byte_str, int multi_byte_len, wchar_t const *wide_char_str, int wide_char_len); +GB_DLL_IMPORT int WideCharToMultiByte(UINT code_page, DWORD flags, wchar_t const *wide_char_str, int wide_char_len, char const * multi_byte_str, int multi_byte_len); +GB_DLL_IMPORT BOOL WINAPI SetFilePointerEx(HANDLE file, LARGE_INTEGER distance_to_move, + LARGE_INTEGER *new_file_pointer, DWORD move_method); +GB_DLL_IMPORT BOOL WINAPI ReadFile (HANDLE file, void *buffer, DWORD bytes_to_read, DWORD *bytes_read, OVERLAPPED *overlapped); +GB_DLL_IMPORT BOOL WINAPI WriteFile (HANDLE file, void const *buffer, DWORD bytes_to_write, DWORD *bytes_written, OVERLAPPED *overlapped); +GB_DLL_IMPORT HANDLE WINAPI CreateFileW (wchar_t const *path, DWORD desired_access, DWORD share_mode, + SECURITY_ATTRIBUTES *, DWORD creation_disposition, + DWORD flags_and_attributes, HANDLE template_file); +GB_DLL_IMPORT HANDLE WINAPI CreateFileA (char const *path, DWORD desired_access, DWORD share_mode, + SECURITY_ATTRIBUTES *, DWORD creation_disposition, + DWORD flags_and_attributes, HANDLE template_file); +GB_DLL_IMPORT HANDLE WINAPI GetStdHandle (DWORD std_handle); +GB_DLL_IMPORT BOOL WINAPI GetFileSizeEx (HANDLE file, LARGE_INTEGER *size); +GB_DLL_IMPORT BOOL WINAPI SetEndOfFile (HANDLE file); +GB_DLL_IMPORT HANDLE WINAPI FindFirstFileW (wchar_t const *path, WIN32_FIND_DATAW *data); +GB_DLL_IMPORT HANDLE WINAPI FindFirstFileA (char const *path, WIN32_FIND_DATAA *data); +GB_DLL_IMPORT BOOL WINAPI FindNextFileA (HANDLE find_find, WIN32_FIND_DATAA *data); +GB_DLL_IMPORT BOOL WINAPI FindClose (HANDLE find_file); +GB_DLL_IMPORT BOOL WINAPI GetFileAttributesExW(wchar_t const *path, GET_FILEEX_INFO_LEVELS info_level_id, WIN32_FILE_ATTRIBUTE_DATA *data); +GB_DLL_IMPORT BOOL WINAPI CopyFileW(wchar_t const *old_f, wchar_t const *new_f, BOOL fail_if_exists); +GB_DLL_IMPORT BOOL WINAPI MoveFileW(wchar_t const *old_f, wchar_t const *new_f); +GB_DLL_IMPORT BOOL WINAPI DeleteFileA (char const *path); +GB_DLL_IMPORT BOOL WINAPI CreateDirectoryA(char const *path); +GB_DLL_IMPORT BOOL WINAPI RemoveDirectoryA(char const *path); +GB_DLL_IMPORT BOOL WINAPI MoveFileA (char const *old_path, char const *new_path); + +GB_DLL_IMPORT DWORD WINAPI GetFullPathNameA(char const *lpFileName, DWORD nBufferLength, char *lpBuffer, char **lpFilePart); + +GB_DLL_IMPORT HMODULE WINAPI LoadLibraryA (char const *filename); +GB_DLL_IMPORT BOOL WINAPI FreeLibrary (HMODULE module); +GB_DLL_IMPORT FARPROC WINAPI GetProcAddress(HMODULE module, char const *name); + +GB_DLL_IMPORT BOOL WINAPI QueryPerformanceFrequency(LARGE_INTEGER *frequency); +GB_DLL_IMPORT BOOL WINAPI QueryPerformanceCounter (LARGE_INTEGER *counter); +GB_DLL_IMPORT void WINAPI GetSystemTimeAsFileTime (FILETIME *system_time_as_file_time); +GB_DLL_IMPORT void WINAPI Sleep(DWORD milliseconds); +GB_DLL_IMPORT void WINAPI ExitProcess(UINT exit_code); + +GB_DLL_IMPORT BOOL WINAPI SetEnvironmentVariableA(char const *name, char const *value); +GB_DLL_IMPORT DWORD WINAPI GetEnvironmentVariableA(char const * lpName, char * lpBuffer, DWORD nSize); + +GB_DLL_IMPORT short WINAPI htons(short hostshort); +GB_DLL_IMPORT int WINAPI htonl(int hostint); +GB_DLL_IMPORT short WINAPI ntohs(short netshort); +GB_DLL_IMPORT int WINAPI ntohl(int netint); diff --git a/shared/include/stb_ds.h b/shared/include/stb_ds.h new file mode 100644 index 00000000..e84c82d1 --- /dev/null +++ b/shared/include/stb_ds.h @@ -0,0 +1,1895 @@ +/* stb_ds.h - v0.67 - public domain data structures - Sean Barrett 2019 + + This is a single-header-file library that provides easy-to-use + dynamic arrays and hash tables for C (also works in C++). + + For a gentle introduction: + http://nothings.org/stb_ds + + To use this library, do this in *one* C or C++ file: + #define STB_DS_IMPLEMENTATION + #include "stb_ds.h" + +TABLE OF CONTENTS + + Table of Contents + Compile-time options + License + Documentation + Notes + Notes - Dynamic arrays + Notes - Hash maps + Credits + +COMPILE-TIME OPTIONS + + #define STBDS_NO_SHORT_NAMES + + This flag needs to be set globally. + + By default stb_ds exposes shorter function names that are not qualified + with the "stbds_" prefix. If these names conflict with the names in your + code, define this flag. + + #define STBDS_SIPHASH_2_4 + + This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for + 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force + stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes + hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on + 64-byte keys, and 10% slower on 256-byte keys on my test computer. + + #define STBDS_REALLOC(context,ptr,size) better_realloc + #define STBDS_FREE(context,ptr) better_free + + These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION. + + By default stb_ds uses stdlib realloc() and free() for memory management. You can + substitute your own functions instead by defining these symbols. You must either + define both, or neither. Note that at the moment, 'context' will always be NULL. + @TODO add an array/hash initialization function that takes a memory context pointer. + + #define STBDS_UNIT_TESTS + + Defines a function stbds_unit_tests() that checks the functioning of the data structures. + + Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x' + (or equivalentally '-std=c++11') when using anonymous structures as seen on the web + page or in STBDS_UNIT_TESTS. + +LICENSE + + Placed in the public domain and also MIT licensed. + See end of file for detailed license information. + +DOCUMENTATION + + Dynamic Arrays + + Non-function interface: + + Declare an empty dynamic array of type T + T* foo = NULL; + + Access the i'th item of a dynamic array 'foo' of type T, T* foo: + foo[i] + + Functions (actually macros) + + arrfree: + void arrfree(T*); + Frees the array. + + arrlen: + ptrdiff_t arrlen(T*); + Returns the number of elements in the array. + + arrlenu: + size_t arrlenu(T*); + Returns the number of elements in the array as an unsigned type. + + arrpop: + T arrpop(T* a) + Removes the final element of the array and returns it. + + arrput: + T arrput(T* a, T b); + Appends the item b to the end of array a. Returns b. + + arrins: + T arrins(T* a, int p, T b); + Inserts the item b into the middle of array a, into a[p], + moving the rest of the array over. Returns b. + + arrinsn: + void arrinsn(T* a, int p, int n); + Inserts n uninitialized items into array a starting at a[p], + moving the rest of the array over. + + arraddnptr: + T* arraddnptr(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns a pointer to the first uninitialized item added. + + arraddnindex: + size_t arraddnindex(T* a, int n) + Appends n uninitialized items onto array at the end. + Returns the index of the first uninitialized item added. + + arrdel: + void arrdel(T* a, int p); + Deletes the element at a[p], moving the rest of the array over. + + arrdeln: + void arrdeln(T* a, int p, int n); + Deletes n elements starting at a[p], moving the rest of the array over. + + arrdelswap: + void arrdelswap(T* a, int p); + Deletes the element at a[p], replacing it with the element from + the end of the array. O(1) performance. + + arrsetlen: + void arrsetlen(T* a, int n); + Changes the length of the array to n. Allocates uninitialized + slots at the end if necessary. + + arrsetcap: + size_t arrsetcap(T* a, int n); + Sets the length of allocated storage to at least n. It will not + change the length of the array. + + arrcap: + size_t arrcap(T* a); + Returns the number of total elements the array can contain without + needing to be reallocated. + + Hash maps & String hash maps + + Given T is a structure type: struct { TK key; TV value; }. Note that some + functions do not require TV value and can have other fields. For string + hash maps, TK must be 'char *'. + + Special interface: + + stbds_rand_seed: + void stbds_rand_seed(size_t seed); + For security against adversarially chosen data, you should seed the + library with a strong random number. Or at least seed it with time(). + + stbds_hash_string: + size_t stbds_hash_string(char *str, size_t seed); + Returns a hash value for a string. + + stbds_hash_bytes: + size_t stbds_hash_bytes(void *p, size_t len, size_t seed); + These functions hash an arbitrary number of bytes. The function + uses a custom hash for 4- and 8-byte data, and a weakened version + of SipHash for everything else. On 64-bit platforms you can get + specification-compliant SipHash-2-4 on all data by defining + STBDS_SIPHASH_2_4, at a significant cost in speed. + + Non-function interface: + + Declare an empty hash map of type T + T* foo = NULL; + + Access the i'th entry in a hash table T* foo: + foo[i] + + Function interface (actually macros): + + hmfree + shfree + void hmfree(T*); + void shfree(T*); + Frees the hashmap and sets the pointer to NULL. + + hmlen + shlen + ptrdiff_t hmlen(T*) + ptrdiff_t shlen(T*) + Returns the number of elements in the hashmap. + + hmlenu + shlenu + size_t hmlenu(T*) + size_t shlenu(T*) + Returns the number of elements in the hashmap. + + hmgeti + shgeti + hmgeti_ts + ptrdiff_t hmgeti(T*, TK key) + ptrdiff_t shgeti(T*, char* key) + ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar) + Returns the index in the hashmap which has the key 'key', or -1 + if the key is not present. + + hmget + hmget_ts + shget + TV hmget(T*, TK key) + TV shget(T*, char* key) + TV hmget_ts(T*, TK key, ptrdiff_t tempvar) + Returns the value corresponding to 'key' in the hashmap. + The structure must have a 'value' field + + hmgets + shgets + T hmgets(T*, TK key) + T shgets(T*, char* key) + Returns the structure corresponding to 'key' in the hashmap. + + hmgetp + shgetp + hmgetp_ts + hmgetp_null + shgetp_null + T* hmgetp(T*, TK key) + T* shgetp(T*, char* key) + T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar) + T* hmgetp_null(T*, TK key) + T* shgetp_null(T*, char *key) + Returns a pointer to the structure corresponding to 'key' in + the hashmap. Functions ending in "_null" return NULL if the key + is not present in the hashmap; the others return a pointer to a + structure holding the default value (but not the searched-for key). + + hmdefault + shdefault + TV hmdefault(T*, TV value) + TV shdefault(T*, TV value) + Sets the default value for the hashmap, the value which will be + returned by hmget/shget if the key is not present. + + hmdefaults + shdefaults + TV hmdefaults(T*, T item) + TV shdefaults(T*, T item) + Sets the default struct for the hashmap, the contents which will be + returned by hmgets/shgets if the key is not present. + + hmput + shput + TV hmput(T*, TK key, TV value) + TV shput(T*, char* key, TV value) + Inserts a pair into the hashmap. If the key is already + present in the hashmap, updates its value. + + hmputs + shputs + T hmputs(T*, T item) + T shputs(T*, T item) + Inserts a struct with T.key into the hashmap. If the struct is already + present in the hashmap, updates it. + + hmdel + shdel + int hmdel(T*, TK key) + int shdel(T*, char* key) + If 'key' is in the hashmap, deletes its entry and returns 1. + Otherwise returns 0. + + Function interface (actually macros) for strings only: + + sh_new_strdup + void sh_new_strdup(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate and free + each string key using realloc/free + + sh_new_arena + void sh_new_arena(T*); + Overwrites the existing pointer with a newly allocated + string hashmap which will automatically allocate each string + key to a string arena. Every string key ever used by this + hash table remains in the arena until the arena is freed. + Additionally, any key which is deleted and reinserted will + be allocated multiple times in the string arena. + +NOTES + + * These data structures are realloc'd when they grow, and the macro + "functions" write to the provided pointer. This means: (a) the pointer + must be an lvalue, and (b) the pointer to the data structure is not + stable, and you must maintain it the same as you would a realloc'd + pointer. For example, if you pass a pointer to a dynamic array to a + function which updates it, the function must return back the new + pointer to the caller. This is the price of trying to do this in C. + + * The following are the only functions that are thread-safe on a single data + structure, i.e. can be run in multiple threads simultaneously on the same + data structure + hmlen shlen + hmlenu shlenu + hmget_ts shget_ts + hmgeti_ts shgeti_ts + hmgets_ts shgets_ts + + * You iterate over the contents of a dynamic array and a hashmap in exactly + the same way, using arrlen/hmlen/shlen: + + for (i=0; i < arrlen(foo); ++i) + ... foo[i] ... + + * All operations except arrins/arrdel are O(1) amortized, but individual + operations can be slow, so these data structures may not be suitable + for real time use. Dynamic arrays double in capacity as needed, so + elements are copied an average of once. Hash tables double/halve + their size as needed, with appropriate hysteresis to maintain O(1) + performance. + +NOTES - DYNAMIC ARRAY + + * If you know how long a dynamic array is going to be in advance, you can avoid + extra memory allocations by using arrsetlen to allocate it to that length in + advance and use foo[n] while filling it out, or arrsetcap to allocate the memory + for that length and use arrput/arrpush as normal. + + * Unlike some other versions of the dynamic array, this version should + be safe to use with strict-aliasing optimizations. + +NOTES - HASH MAP + + * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel + and variants, the key must be an lvalue (so the macro can take the address of it). + Extensions are used that eliminate this requirement if you're using C99 and later + in GCC or clang, or if you're using C++ in GCC. But note that this can make your + code less portable. + + * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'. + + * The iteration order of your data in the hashmap is determined solely by the + order of insertions and deletions. In particular, if you never delete, new + keys are always added at the end of the array. This will be consistent + across all platforms and versions of the library. However, you should not + attempt to serialize the internal hash table, as the hash is not consistent + between different platforms, and may change with future versions of the library. + + * Use sh_new_arena() for string hashmaps that you never delete from. Initialize + with NULL if you're managing the memory for your strings, or your strings are + never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup(). + @TODO: make an arena variant that garbage collects the strings with a trivial + copy collector into a new arena whenever the table shrinks / rebuilds. Since + current arena recommendation is to only use arena if it never deletes, then + this can just replace current arena implementation. + + * If adversarial input is a serious concern and you're on a 64-bit platform, + enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass + a strong random number to stbds_rand_seed. + + * The default value for the hash table is stored in foo[-1], so if you + use code like 'hmget(T,k)->value = 5' you can accidentally overwrite + the value stored by hmdefault if 'k' is not present. + +CREDITS + + Sean Barrett -- library, idea for dynamic array API/implementation + Per Vognsen -- idea for hash table API/implementation + Rafael Sachetto -- arrpop() + github:HeroicKatora -- arraddn() reworking + + Bugfixes: + Andy Durdin + Shane Liesegang + Vinh Truong + Andreas Molzer + github:hashitaku + github:srdjanstipic + Macoy Madson + Andreas Vennstrom + Tobias Mansfield-Williams +*/ + +#ifdef STBDS_UNIT_TESTS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#ifndef INCLUDE_STB_DS_H +#define INCLUDE_STB_DS_H + +#include +#include + +#ifndef STBDS_NO_SHORT_NAMES +#define arrlen stbds_arrlen +#define arrlenu stbds_arrlenu +#define arrput stbds_arrput +#define arrpush stbds_arrput +#define arrpop stbds_arrpop +#define arrfree stbds_arrfree +#define arraddn stbds_arraddn // deprecated, use one of the following instead: +#define arraddnptr stbds_arraddnptr +#define arraddnindex stbds_arraddnindex +#define arrsetlen stbds_arrsetlen +#define arrlast stbds_arrlast +#define arrins stbds_arrins +#define arrinsn stbds_arrinsn +#define arrdel stbds_arrdel +#define arrdeln stbds_arrdeln +#define arrdelswap stbds_arrdelswap +#define arrcap stbds_arrcap +#define arrsetcap stbds_arrsetcap + +#define hmput stbds_hmput +#define hmputs stbds_hmputs +#define hmget stbds_hmget +#define hmget_ts stbds_hmget_ts +#define hmgets stbds_hmgets +#define hmgetp stbds_hmgetp +#define hmgetp_ts stbds_hmgetp_ts +#define hmgetp_null stbds_hmgetp_null +#define hmgeti stbds_hmgeti +#define hmgeti_ts stbds_hmgeti_ts +#define hmdel stbds_hmdel +#define hmlen stbds_hmlen +#define hmlenu stbds_hmlenu +#define hmfree stbds_hmfree +#define hmdefault stbds_hmdefault +#define hmdefaults stbds_hmdefaults + +#define shput stbds_shput +#define shputi stbds_shputi +#define shputs stbds_shputs +#define shget stbds_shget +#define shgeti stbds_shgeti +#define shgets stbds_shgets +#define shgetp stbds_shgetp +#define shgetp_null stbds_shgetp_null +#define shdel stbds_shdel +#define shlen stbds_shlen +#define shlenu stbds_shlenu +#define shfree stbds_shfree +#define shdefault stbds_shdefault +#define shdefaults stbds_shdefaults +#define sh_new_arena stbds_sh_new_arena +#define sh_new_strdup stbds_sh_new_strdup + +#define stralloc stbds_stralloc +#define strreset stbds_strreset +#endif + +#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE) +#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither." +#endif +#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE) +#include +#define STBDS_REALLOC(c,p,s) realloc(p,s) +#define STBDS_FREE(c,p) free(p) +#endif + +#ifdef _MSC_VER +#define STBDS_NOTUSED(v) (void)(v) +#else +#define STBDS_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// for security against attackers, seed the library with a random number, at least time() but stronger is better +extern void stbds_rand_seed(size_t seed); + +// these are the hash functions used internally if you want to test them or use them for other purposes +extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed); +extern size_t stbds_hash_string(char *str, size_t seed); + +// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'. +typedef struct stbds_string_arena stbds_string_arena; +extern char * stbds_stralloc(stbds_string_arena *a, char *str); +extern void stbds_strreset(stbds_string_arena *a); + +// have to #define STBDS_UNIT_TESTS to call this +extern void stbds_unit_tests(void); + +/////////////// +// +// Everything below here is implementation details +// + +extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap); +extern void stbds_arrfreef(void *a); +extern void stbds_hmfree_func(void *p, size_t elemsize); +extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode); +extern void * stbds_hmput_default(void *a, size_t elemsize); +extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode); +extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode); +extern void * stbds_shmode_func(size_t elemsize, int mode); + +#ifdef __cplusplus +} +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define STBDS_HAS_TYPEOF +#ifdef __cplusplus +//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang +#endif +#endif + +#if !defined(__cplusplus) +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L +#define STBDS_HAS_LITERAL_ARRAY +#endif +#endif + +// this macro takes the address of the argument, but on gcc/clang can accept rvalues +#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF) + #if __clang__ + #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value + #else + #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value + #endif +#else +#define STBDS_ADDRESSOF(typevar, value) &(value) +#endif + +#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var)) + +#define stbds_header(t) ((stbds_array_header *) (t) - 1) +#define stbds_temp(t) stbds_header(t)->temp +#define stbds_temp_key(t) (*(char **) stbds_header(t)->hash_table) + +#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n)) +#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0) +#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0) +#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0) +#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0) +#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v)) +#define stbds_arrpush stbds_arrput // synonym +#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length]) +#define stbds_arraddn(a,n) ((void)(stbds_arraddnindex(a, n))) // deprecated, use one of the following instead: +#define stbds_arraddnptr(a,n) (stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), &(a)[stbds_header(a)->length-(n)]) : (a)) +#define stbds_arraddnindex(a,n)(stbds_arrmaybegrow(a,n), (n) ? (stbds_header(a)->length += (n), stbds_header(a)->length-(n)) : stbds_arrlen(a)) +#define stbds_arraddnoff stbds_arraddnindex +#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1]) +#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL) +#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1) +#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n)) +#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1) +#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i)))) +#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v)) + +#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \ + ? (stbds_arrgrow(a,n,0),0) : 0) + +#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c))) + +#define stbds_hmput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \ + (t)[stbds_temp((t)-1)].key = (k), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_hmputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \ + (t)[stbds_temp((t)-1)] = (s)) + +#define stbds_hmgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \ + stbds_temp((t)-1)) + +#define stbds_hmgeti_ts(t,k,temp) \ + ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \ + (temp)) + +#define stbds_hmgetp(t, k) \ + ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_hmgetp_ts(t, k, temp) \ + ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp]) + +#define stbds_hmdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0) + +#define stbds_hmdefault(t, v) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v)) + +#define stbds_hmdefaults(t, s) \ + ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s)) + +#define stbds_hmfree(p) \ + ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL) + +#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k)) +#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value) +#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value) +#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0) +#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0) +#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) + +#define stbds_shput(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v)) + +#define stbds_shputi(t, k, v) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1)) + +#define stbds_shputs(t, s) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \ + (t)[stbds_temp((t)-1)] = (s), \ + (t)[stbds_temp((t)-1)].key = stbds_temp_key((t)-1)) // above line overwrites whole structure, so must rewrite key here if it was allocated internally + +#define stbds_pshput(t, p) \ + ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \ + (t)[stbds_temp((t)-1)] = (p)) + +#define stbds_shgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \ + stbds_temp((t)-1)) + +#define stbds_pshgeti(t,k) \ + ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \ + stbds_temp((t)-1)) + +#define stbds_shgetp(t, k) \ + ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)]) + +#define stbds_pshget(t, k) \ + ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)]) + +#define stbds_shdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0) +#define stbds_pshdel(t,k) \ + (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0) + +#define stbds_sh_new_arena(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA)) +#define stbds_sh_new_strdup(t) \ + ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP)) + +#define stbds_shdefault(t, v) stbds_hmdefault(t,v) +#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s) + +#define stbds_shfree stbds_hmfree +#define stbds_shlenu stbds_hmlenu + +#define stbds_shgets(t, k) (*stbds_shgetp(t,k)) +#define stbds_shget(t, k) (stbds_shgetp(t,k)->value) +#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp((t)-1)]) +#define stbds_shlen stbds_hmlen + +typedef struct +{ + size_t length; + size_t capacity; + void * hash_table; + ptrdiff_t temp; +} stbds_array_header; + +typedef struct stbds_string_block +{ + struct stbds_string_block *next; + char storage[8]; +} stbds_string_block; + +struct stbds_string_arena +{ + stbds_string_block *storage; + size_t remaining; + unsigned char block; + unsigned char mode; // this isn't used by the string arena itself +}; + +#define STBDS_HM_BINARY 0 +#define STBDS_HM_STRING 1 + +enum +{ + STBDS_SH_NONE, + STBDS_SH_DEFAULT, + STBDS_SH_STRDUP, + STBDS_SH_ARENA +}; + +#ifdef __cplusplus +// in C we use implicit assignment from these void*-returning functions to T*. +// in C++ these templates make the same code work +template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) { + return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap); +} +template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) { + return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode); +} +template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) { + return (T*)stbds_hmput_default((void *)a, elemsize); +} +template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) { + return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode); +} +template static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){ + return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode); +} +template static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) { + return (T*)stbds_shmode_func(elemsize, mode); +} +#else +#define stbds_arrgrowf_wrapper stbds_arrgrowf +#define stbds_hmget_key_wrapper stbds_hmget_key +#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts +#define stbds_hmput_default_wrapper stbds_hmput_default +#define stbds_hmput_key_wrapper stbds_hmput_key +#define stbds_hmdel_key_wrapper stbds_hmdel_key +#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m) +#endif + +#endif // INCLUDE_STB_DS_H + + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION +// + +#ifdef STB_DS_IMPLEMENTATION +#include +#include + +#ifndef STBDS_ASSERT +#define STBDS_ASSERT_WAS_UNDEFINED +#define STBDS_ASSERT(x) ((void) 0) +#endif + +#ifdef STBDS_STATISTICS +#define STBDS_STATS(x) x +size_t stbds_array_grow; +size_t stbds_hash_grow; +size_t stbds_hash_shrink; +size_t stbds_hash_rebuild; +size_t stbds_hash_probes; +size_t stbds_hash_alloc; +size_t stbds_rehash_probes; +size_t stbds_rehash_items; +#else +#define STBDS_STATS(x) +#endif + +// +// stbds_arr implementation +// + +//int *prev_allocs[65536]; +//int num_prev; + +void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap) +{ + stbds_array_header temp={0}; // force debugging + void *b; + size_t min_len = stbds_arrlen(a) + addlen; + (void) sizeof(temp); + + // compute the minimum capacity needed + if (min_len > min_cap) + min_cap = min_len; + + if (min_cap <= stbds_arrcap(a)) + return a; + + // increase needed capacity to guarantee O(1) amortized + if (min_cap < 2 * stbds_arrcap(a)) + min_cap = 2 * stbds_arrcap(a); + else if (min_cap < 4) + min_cap = 4; + + //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1); + //if (num_prev == 2201) + // num_prev = num_prev; + b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header)); + //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b; + b = (char *) b + sizeof(stbds_array_header); + if (a == NULL) { + stbds_header(b)->length = 0; + stbds_header(b)->hash_table = 0; + stbds_header(b)->temp = 0; + } else { + STBDS_STATS(++stbds_array_grow); + } + stbds_header(b)->capacity = min_cap; + + return b; +} + +void stbds_arrfreef(void *a) +{ + STBDS_FREE(NULL, stbds_header(a)); +} + +// +// stbds_hm hash table implementation +// + +#ifdef STBDS_INTERNAL_SMALL_BUCKET +#define STBDS_BUCKET_LENGTH 4 +#else +#define STBDS_BUCKET_LENGTH 8 +#endif + +#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2) +#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1) +#define STBDS_CACHE_LINE_SIZE 64 + +#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1)) + +typedef struct +{ + size_t hash [STBDS_BUCKET_LENGTH]; + ptrdiff_t index[STBDS_BUCKET_LENGTH]; +} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line + +typedef struct +{ + char * temp_key; // this MUST be the first field of the hash table + size_t slot_count; + size_t used_count; + size_t used_count_threshold; + size_t used_count_shrink_threshold; + size_t tombstone_count; + size_t tombstone_count_threshold; + size_t seed; + size_t slot_count_log2; + stbds_string_arena string; + stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct +} stbds_hash_index; + +#define STBDS_INDEX_EMPTY -1 +#define STBDS_INDEX_DELETED -2 +#define STBDS_INDEX_IN_USE(x) ((x) >= 0) + +#define STBDS_HASH_EMPTY 0 +#define STBDS_HASH_DELETED 1 + +static size_t stbds_hash_seed=0x31415926; + +void stbds_rand_seed(size_t seed) +{ + stbds_hash_seed = seed; +} + +#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \ + temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \ + var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \ + var ^= temp ^ v32 + +#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8) + +static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2) +{ + size_t pos; + STBDS_NOTUSED(slot_log2); + pos = hash & (slot_count-1); + #ifdef STBDS_INTERNAL_BUCKET_START + pos &= ~STBDS_BUCKET_MASK; + #endif + return pos; +} + +static size_t stbds_log2(size_t slot_count) +{ + size_t n=0; + while (slot_count > 1) { + slot_count >>= 1; + ++n; + } + return n; +} + +static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot) +{ + stbds_hash_index *t; + t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1); + t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE); + t->slot_count = slot_count; + t->slot_count_log2 = stbds_log2(slot_count); + t->tombstone_count = 0; + t->used_count = 0; + + #if 0 // A1 + t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + #elif 1 // A2 + //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow + //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild + //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink + + // compute without overflowing + t->used_count_threshold = slot_count - (slot_count>>2); + t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4); + t->used_count_shrink_threshold = slot_count >> 2; + + #elif 0 // B1 + t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink + #else // C1 + t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow + t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild + t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // A1 A2 B1 C1 + // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table + // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table + // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table + // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table + // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table + // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table + // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table + // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table + // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table + // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table + // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table + + if (slot_count <= STBDS_BUCKET_LENGTH) + t->used_count_shrink_threshold = 0; + // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes + STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count); + STBDS_STATS(++stbds_hash_alloc); + if (ot) { + t->string = ot->string; + // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing + t->seed = ot->seed; + } else { + size_t a,b,temp; + memset(&t->string, 0, sizeof(t->string)); + t->seed = stbds_hash_seed; + // LCG + // in 32-bit, a = 2147001325 b = 715136305 + // in 64-bit, a = 2862933555777941757 b = 3037000493 + stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd); + stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d); + stbds_hash_seed = stbds_hash_seed * a + b; + } + + { + size_t i,j; + for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *b = &t->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->hash[j] = STBDS_HASH_EMPTY; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) + b->index[j] = STBDS_INDEX_EMPTY; + } + } + + // copy out the old data, if any + if (ot) { + size_t i,j; + t->used_count = ot->used_count; + for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) { + stbds_hash_bucket *ob = &ot->storage[i]; + for (j=0; j < STBDS_BUCKET_LENGTH; ++j) { + if (STBDS_INDEX_IN_USE(ob->index[j])) { + size_t hash = ob->hash[j]; + size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2); + size_t step = STBDS_BUCKET_LENGTH; + STBDS_STATS(++stbds_rehash_items); + for (;;) { + size_t limit,z; + stbds_hash_bucket *bucket; + bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT]; + STBDS_STATS(++stbds_rehash_probes); + + for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + limit = pos & STBDS_BUCKET_MASK; + for (z = 0; z < limit; ++z) { + if (bucket->hash[z] == 0) { + bucket->hash[z] = hash; + bucket->index[z] = ob->index[j]; + goto done; + } + } + + pos += step; // quadratic probing + step += STBDS_BUCKET_LENGTH; + pos &= (t->slot_count-1); + } + } + done: + ; + } + } + } + + return t; +} + +#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n)))) +#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n)))) + +size_t stbds_hash_string(char *str, size_t seed) +{ + size_t hash = seed; + while (*str) + hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++; + + // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits + hash ^= seed; + hash = (~hash) + (hash << 18); + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31); + hash = hash * 21; + hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11); + hash += (hash << 6); + hash ^= STBDS_ROTATE_RIGHT(hash,22); + return hash+seed; +} + +#ifdef STBDS_SIPHASH_2_4 +#define STBDS_SIPHASH_C_ROUNDS 2 +#define STBDS_SIPHASH_D_ROUNDS 4 +typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1]; +#endif + +#ifndef STBDS_SIPHASH_C_ROUNDS +#define STBDS_SIPHASH_C_ROUNDS 1 +#endif +#ifndef STBDS_SIPHASH_D_ROUNDS +#define STBDS_SIPHASH_D_ROUNDS 1 +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()== +#endif + +static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed) +{ + unsigned char *d = (unsigned char *) p; + size_t i,j; + size_t v0,v1,v2,v3, data; + + // hash that works on 32- or 64-bit registers without knowing which we have + // (computes different results on 32-bit and 64-bit platform) + // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit + v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed; + v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed; + v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed; + v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed; + + #ifdef STBDS_TEST_SIPHASH_2_4 + // hardcoded with key material in the siphash test vectors + v0 ^= 0x0706050403020100ull ^ seed; + v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + v2 ^= 0x0706050403020100ull ^ seed; + v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed; + #endif + + #define STBDS_SIPROUND() \ + do { \ + v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \ + v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \ + v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \ + v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \ + } while (0) + + for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) { + data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4 + + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + } + data = len << (STBDS_SIZE_T_BITS-8); + switch (len - i) { + case 7: data |= ((size_t) d[6] << 24) << 24; // fall through + case 6: data |= ((size_t) d[5] << 20) << 20; // fall through + case 5: data |= ((size_t) d[4] << 16) << 16; // fall through + case 4: data |= (d[3] << 24); // fall through + case 3: data |= (d[2] << 16); // fall through + case 2: data |= (d[1] << 8); // fall through + case 1: data |= d[0]; // fall through + case 0: break; + } + v3 ^= data; + for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j) + STBDS_SIPROUND(); + v0 ^= data; + v2 ^= 0xff; + for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j) + STBDS_SIPROUND(); + +#ifdef STBDS_SIPHASH_2_4 + return v0^v1^v2^v3; +#else + return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply +#endif +} + +size_t stbds_hash_bytes(void *p, size_t len, size_t seed) +{ +#ifdef STBDS_SIPHASH_2_4 + return stbds_siphash_bytes(p,len,seed); +#else + unsigned char *d = (unsigned char *) p; + + if (len == 4) { + unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + #if 0 + // HASH32-A Bob Jenkin's hash function w/o large constants + hash ^= seed; + hash -= (hash<<6); + hash ^= (hash>>17); + hash -= (hash<<9); + hash ^= seed; + hash ^= (hash<<4); + hash -= (hash<<3); + hash ^= (hash<<10); + hash ^= (hash>>15); + #elif 1 + // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts. + // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm + // not really sure what's going on. + hash ^= seed; + hash = (hash ^ 61) ^ (hash >> 16); + hash = hash + (hash << 3); + hash = hash ^ (hash >> 4); + hash = hash * 0x27d4eb2d; + hash ^= seed; + hash = hash ^ (hash >> 15); + #else // HASH32-C - Murmur3 + hash ^= seed; + hash *= 0xcc9e2d51; + hash = (hash << 17) | (hash >> 15); + hash *= 0x1b873593; + hash ^= seed; + hash = (hash << 19) | (hash >> 13); + hash = hash*5 + 0xe6546b64; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= seed; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + #endif + // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2 + // Note that the larger tables have high variance as they were run fewer times + // HASH32-A // HASH32-BB // HASH32-C + // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table + // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table + // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table + // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table + // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table + // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table + // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table + // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table + // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table + // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table + // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table + // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing + + return (((size_t) hash << 16 << 16) | hash) ^ seed; + } else if (len == 8 && sizeof(size_t) == 8) { + size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); + hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4 + hash ^= seed; + hash = (~hash) + (hash << 21); + hash ^= STBDS_ROTATE_RIGHT(hash,24); + hash *= 265; + hash ^= STBDS_ROTATE_RIGHT(hash,14); + hash ^= seed; + hash *= 21; + hash ^= STBDS_ROTATE_RIGHT(hash,28); + hash += (hash << 31); + hash = (~hash) + (hash << 18); + return hash; + } else { + return stbds_siphash_bytes(p,len,seed); + } +#endif +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + +static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i) +{ + if (mode >= STBDS_HM_STRING) + return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i + keyoffset)); + else + return 0==memcmp(key, (char *) a + elemsize*i + keyoffset, keysize); +} + +#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize)) +#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize)) + +#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table) + +void stbds_hmfree_func(void *a, size_t elemsize) +{ + if (a == NULL) return; + if (stbds_hash_table(a) != NULL) { + if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) { + size_t i; + // skip 0th element, which is default + for (i=1; i < stbds_header(a)->length; ++i) + STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i)); + } + stbds_strreset(&stbds_hash_table(a)->string); + } + STBDS_FREE(NULL, stbds_header(a)->hash_table); + STBDS_FREE(NULL, stbds_header(a)); +} + +static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + stbds_hash_index *table = stbds_hash_table(raw_a); + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t limit,i; + size_t pos; + stbds_hash_bucket *bucket; + + if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + return (pos & ~STBDS_BUCKET_MASK)+i; + } + } else if (bucket->hash[i] == STBDS_HASH_EMPTY) { + return -1; + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + /* NOTREACHED */ +} + +void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) +{ + size_t keyoffset = 0; + if (a == NULL) { + // make it non-empty so we can return a temp + a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + *temp = STBDS_INDEX_EMPTY; + // adjust a to point after the default element + return STBDS_ARR_TO_HASH(a,elemsize); + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + // adjust a to point to the default element + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + if (table == 0) { + *temp = -1; + } else { + ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) { + *temp = STBDS_INDEX_EMPTY; + } else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + *temp = b->index[slot & STBDS_BUCKET_MASK]; + } + } + return a; + } +} + +void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + ptrdiff_t temp; + void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode); + stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp; + return p; +} + +void * stbds_hmput_default(void *a, size_t elemsize) +{ + // three cases: + // a is NULL <- allocate + // a has a hash table but no entries, because of shmode <- grow + // a has entries <- do nothing + if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) { + a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1); + stbds_header(a)->length += 1; + memset(a, 0, elemsize); + a=STBDS_ARR_TO_HASH(a,elemsize); + } + return a; +} + +static char *stbds_strdup(char *str); + +void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode) +{ + size_t keyoffset=0; + void *raw_a; + stbds_hash_index *table; + + if (a == NULL) { + a = stbds_arrgrowf(0, elemsize, 0, 1); + memset(a, 0, elemsize); + stbds_header(a)->length += 1; + // adjust a to point AFTER the default element + a = STBDS_ARR_TO_HASH(a,elemsize); + } + + // adjust a to point to the default element + raw_a = a; + a = STBDS_HASH_TO_ARR(a,elemsize); + + table = (stbds_hash_index *) stbds_header(a)->hash_table; + + if (table == NULL || table->used_count >= table->used_count_threshold) { + stbds_hash_index *nt; + size_t slot_count; + + slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2; + nt = stbds_make_hash_index(slot_count, table); + if (table) + STBDS_FREE(NULL, table); + else + nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0; + stbds_header(a)->hash_table = table = nt; + STBDS_STATS(++stbds_hash_grow); + } + + // we iterate hash table explicitly because we want to track if we saw a tombstone + { + size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed); + size_t step = STBDS_BUCKET_LENGTH; + size_t pos; + ptrdiff_t tombstone = -1; + stbds_hash_bucket *bucket; + + // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly + if (hash < 2) hash += 2; + + pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2); + + for (;;) { + size_t limit, i; + STBDS_STATS(++stbds_hash_probes); + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + + // start searching from pos to end of bucket + for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + if (mode >= STBDS_HM_STRING) + stbds_temp_key(a) = * (char **) ((char *) raw_a + elemsize*bucket->index[i] + keyoffset); + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // search from beginning of bucket to pos + limit = pos & STBDS_BUCKET_MASK; + for (i = 0; i < limit; ++i) { + if (bucket->hash[i] == hash) { + if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) { + stbds_temp(a) = bucket->index[i]; + return STBDS_ARR_TO_HASH(a,elemsize); + } + } else if (bucket->hash[i] == 0) { + pos = (pos & ~STBDS_BUCKET_MASK) + i; + goto found_empty_slot; + } else if (tombstone < 0) { + if (bucket->index[i] == STBDS_INDEX_DELETED) + tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i); + } + } + + // quadratic probing + pos += step; + step += STBDS_BUCKET_LENGTH; + pos &= (table->slot_count-1); + } + found_empty_slot: + if (tombstone >= 0) { + pos = tombstone; + --table->tombstone_count; + } + ++table->used_count; + + { + ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a); + // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type + if ((size_t) i+1 > stbds_arrcap(a)) + *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0); + raw_a = STBDS_ARR_TO_HASH(a,elemsize); + + STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a)); + stbds_header(a)->length = i+1; + bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT]; + bucket->hash[pos & STBDS_BUCKET_MASK] = hash; + bucket->index[pos & STBDS_BUCKET_MASK] = i-1; + stbds_temp(a) = i-1; + + switch (table->string.mode) { + case STBDS_SH_STRDUP: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break; + case STBDS_SH_ARENA: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break; + case STBDS_SH_DEFAULT: stbds_temp_key(a) = *(char **) ((char *) a + elemsize*i) = (char *) key; break; + default: memcpy((char *) a + elemsize*i, key, keysize); break; + } + } + return STBDS_ARR_TO_HASH(a,elemsize); + } +} + +void * stbds_shmode_func(size_t elemsize, int mode) +{ + void *a = stbds_arrgrowf(0, elemsize, 0, 1); + stbds_hash_index *h; + memset(a, 0, elemsize); + stbds_header(a)->length = 1; + stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL); + h->string.mode = (unsigned char) mode; + return STBDS_ARR_TO_HASH(a,elemsize); +} + +void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode) +{ + if (a == NULL) { + return 0; + } else { + stbds_hash_index *table; + void *raw_a = STBDS_HASH_TO_ARR(a,elemsize); + table = (stbds_hash_index *) stbds_header(raw_a)->hash_table; + stbds_temp(raw_a) = 0; + if (table == 0) { + return a; + } else { + ptrdiff_t slot; + slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode); + if (slot < 0) + return a; + else { + stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + int i = slot & STBDS_BUCKET_MASK; + ptrdiff_t old_index = b->index[i]; + ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last' + STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count); + --table->used_count; + ++table->tombstone_count; + stbds_temp(raw_a) = 1; + STBDS_ASSERT(table->used_count >= 0); + //STBDS_ASSERT(table->tombstone_count < table->slot_count/4); + b->hash[i] = STBDS_HASH_DELETED; + b->index[i] = STBDS_INDEX_DELETED; + + if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP) + STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index)); + + // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip + if (old_index != final_index) { + // swap delete + memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize); + + // now find the slot for the last element + if (mode == STBDS_HM_STRING) + slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode); + else + slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode); + STBDS_ASSERT(slot >= 0); + b = &table->storage[slot >> STBDS_BUCKET_SHIFT]; + i = slot & STBDS_BUCKET_MASK; + STBDS_ASSERT(b->index[i] == final_index); + b->index[i] = old_index; + } + stbds_header(raw_a)->length -= 1; + + if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_shrink); + } else if (table->tombstone_count > table->tombstone_count_threshold) { + stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table); + STBDS_FREE(NULL, table); + STBDS_STATS(++stbds_hash_rebuild); + } + + return a; + } + } + } + /* NOTREACHED */ +} + +static char *stbds_strdup(char *str) +{ + // to keep replaceable allocator simple, we don't want to use strdup. + // rolling our own also avoids problem of strdup vs _strdup + size_t len = strlen(str)+1; + char *p = (char*) STBDS_REALLOC(NULL, 0, len); + memmove(p, str, len); + return p; +} + +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN +#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u +#endif +#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX +#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20) +#endif + +char *stbds_stralloc(stbds_string_arena *a, char *str) +{ + char *p; + size_t len = strlen(str)+1; + if (len > a->remaining) { + // compute the next blocksize + size_t blocksize = a->block; + + // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that + // there are log(SIZE) allocations to free when we destroy the table + blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1); + + // if size is under 1M, advance to next blocktype + if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX)) + ++a->block; + + if (len > blocksize) { + // if string is larger than blocksize, then just allocate the full size. + // note that we still advance string_block so block size will continue + // increasing, so e.g. if somebody only calls this with 1000-long strings, + // eventually the arena will start doubling and handling those as well + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len); + memmove(sb->storage, str, len); + if (a->storage) { + // insert it after the first element, so that we don't waste the space there + sb->next = a->storage->next; + a->storage->next = sb; + } else { + sb->next = 0; + a->storage = sb; + a->remaining = 0; // this is redundant, but good for clarity + } + return sb->storage; + } else { + stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize); + sb->next = a->storage; + a->storage = sb; + a->remaining = blocksize; + } + } + + STBDS_ASSERT(len <= a->remaining); + p = a->storage->storage + a->remaining - len; + a->remaining -= len; + memmove(p, str, len); + return p; +} + +void stbds_strreset(stbds_string_arena *a) +{ + stbds_string_block *x,*y; + x = a->storage; + while (x) { + y = x->next; + STBDS_FREE(NULL, x); + x = y; + } + memset(a, 0, sizeof(*a)); +} + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// UNIT TESTS +// + +#ifdef STBDS_UNIT_TESTS +#include +#ifdef STBDS_ASSERT_WAS_UNDEFINED +#undef STBDS_ASSERT +#endif +#ifndef STBDS_ASSERT +#define STBDS_ASSERT assert +#include +#endif + +typedef struct { int key,b,c,d; } stbds_struct; +typedef struct { int key[2],b,c,d; } stbds_struct2; + +static char buffer[256]; +char *strkey(int n) +{ +#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__) + sprintf_s(buffer, sizeof(buffer), "test_%d", n); +#else + sprintf(buffer, "test_%d", n); +#endif + return buffer; +} + +void stbds_unit_tests(void) +{ +#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus) + // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing! + STBDS_ASSERT(0); +#else + const int testsize = 100000; + const int testsize2 = testsize/20; + int *arr=NULL; + struct { int key; int value; } *intmap = NULL; + struct { char *key; int value; } *strmap = NULL, s; + struct { stbds_struct key; int value; } *map = NULL; + stbds_struct *map2 = NULL; + stbds_struct2 *map3 = NULL; + stbds_string_arena sa = { 0 }; + int key3[2] = { 1,2 }; + ptrdiff_t temp; + + int i,j; + + STBDS_ASSERT(arrlen(arr)==0); + for (i=0; i < 20000; i += 50) { + for (j=0; j < i; ++j) + arrpush(arr,j); + arrfree(arr); + } + + for (i=0; i < 4; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdel(arr,i); + arrfree(arr); + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + arrdelswap(arr,i); + arrfree(arr); + } + + for (i=0; i < 5; ++i) { + arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4); + stbds_arrins(arr,i,5); + STBDS_ASSERT(arr[i] == 5); + if (i < 4) + STBDS_ASSERT(arr[4] == 4); + arrfree(arr); + } + + i = 1; + STBDS_ASSERT(hmgeti(intmap,i) == -1); + hmdefault(intmap, -2); + STBDS_ASSERT(hmgeti(intmap, i) == -1); + STBDS_ASSERT(hmget (intmap, i) == -2); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*5); + for (i=0; i < testsize; i+=1) { + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 ); + else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5); + } + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=2; i < testsize; i+=4) + hmdel(intmap, i); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 ); + else STBDS_ASSERT(hmget(intmap, i) == i*3); + for (i=0; i < testsize; i+=1) + hmdel(intmap, i); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(hmget(intmap, i) == -2 ); + hmfree(intmap); + for (i=0; i < testsize; i+=2) + hmput(intmap, i, i*3); + hmfree(intmap); + + #if defined(__clang__) || defined(__GNUC__) + #ifndef __cplusplus + intmap = NULL; + hmput(intmap, 15, 7); + hmput(intmap, 11, 3); + hmput(intmap, 9, 5); + STBDS_ASSERT(hmget(intmap, 9) == 5); + STBDS_ASSERT(hmget(intmap, 11) == 3); + STBDS_ASSERT(hmget(intmap, 15) == 7); + #endif + #endif + + for (i=0; i < testsize; ++i) + stralloc(&sa, strkey(i)); + strreset(&sa); + + { + s.key = "a", s.value = 1; + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key == s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_strdup(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + { + s.key = "a", s.value = 1; + sh_new_arena(strmap); + shputs(strmap, s); + STBDS_ASSERT(*strmap[0].key == 'a'); + STBDS_ASSERT(strmap[0].key != s.key); + STBDS_ASSERT(strmap[0].value == s.value); + shfree(strmap); + } + + for (j=0; j < 2; ++j) { + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + if (j == 0) + sh_new_strdup(strmap); + else + sh_new_arena(strmap); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + shdefault(strmap, -2); + STBDS_ASSERT(shgeti(strmap,"foo") == -1); + for (i=0; i < testsize; i+=2) + shput(strmap, strkey(i), i*3); + for (i=0; i < testsize; i+=1) + if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=2; i < testsize; i+=4) + shdel(strmap, strkey(i)); // delete half the entries + for (i=0; i < testsize; i+=1) + if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3); + for (i=0; i < testsize; i+=1) + shdel(strmap, strkey(i)); // delete the rest of the entries + for (i=0; i < testsize; i+=1) + STBDS_ASSERT(shget(strmap, strkey(i)) == -2 ); + shfree(strmap); + } + + { + struct { char *key; char value; } *hash = NULL; + char name[4] = "jen"; + shput(hash, "bob" , 'h'); + shput(hash, "sally" , 'e'); + shput(hash, "fred" , 'l'); + shput(hash, "jen" , 'x'); + shput(hash, "doug" , 'o'); + + shput(hash, name , 'l'); + shfree(hash); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmput(map, s, i*5); + } + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3 ,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmget(map, s) == 0); + else STBDS_ASSERT(hmget(map, s) == i*5); + if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0); + else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5); + //STBDS_ASSERT(hmget(map, t.key) == 0); + } + + for (i=0; i < testsize; i += 2) { + stbds_struct s = { i,i*2,i*3,i*4 }; + hmputs(map2, s); + } + hmfree(map); + + for (i=0; i < testsize; i += 1) { + stbds_struct s = { i,i*2,i*3,i*4 }; + stbds_struct t = { i,i*2,i*3+1,i*4 }; + if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0); + else STBDS_ASSERT(hmgets(map2, s.key).d == i*4); + //STBDS_ASSERT(hmgetp(map2, t.key) == 0); + } + hmfree(map2); + + for (i=0; i < testsize; i += 2) { + stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 }; + hmputs(map3, s); + } + for (i=0; i < testsize; i += 1) { + stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 }; + stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 }; + if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0); + else STBDS_ASSERT(hmgets(map3, s.key).d == i*5); + //STBDS_ASSERT(hmgetp(map3, t.key) == 0); + } +#endif +} +#endif + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2019 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/astnodes.c b/src/astnodes.c deleted file mode 100644 index f2c6faf2..00000000 --- a/src/astnodes.c +++ /dev/null @@ -1,1406 +0,0 @@ -#include "astnodes.h" -#include "parser.h" -#include "utils.h" - -static const char* ast_node_names[] = { - "ERROR", - "PACKAGE", - "INCLUDE FILE", - "INCLUDE FOLDER", - "INCLUDE ALL IN FOLDER", - "INCLUDE LIBRARY PATH", - "MEMORY RESERVATION", - - "BINDING", - "ALIAS", - "INJECTION", - "FUNCTION", - "OVERLOADED_FUNCTION", - "POLYMORPHIC PROC", - "POLYMORPH QUERY", - "INTERFACE", - "CONSTRAINT", - "CONSTRAINT SENTITEL", - "BLOCK", - "LOCAL", - "GLOBAL", - "SYMBOL", - - "UN_OP", - "BIN_OP", - - "COMPOUND", - "NAMED_VALUE", - - "TYPE_START (BAD)", - "TYPE", - "BASIC_TYPE", - "POINTER_TYPE", - "FUNCTION_TYPE", - "ARRAY TYPE", - "SLICE TYPE", - "DYNARR TYPE", - "VARARG TYPE", - "STRUCT TYPE", - "POLYMORPHIC STRUCT TYPE", - "POLYMORPHIC STRUCT CALL TYPE", - "ENUM TYPE", - "TYPE_ALIAS", - "TYPE RAW ALIAS", - "COMPOUND TYPE", - "TYPE OF", - "DISTINCT TYPE", - "TYPE_END (BAD)", - - "STRUCT MEMBER", - "ENUM VALUE", - - "NUMERIC LITERAL", - "STRING LITERAL", - "PARAM", - "ARGUMENT", - "CALL", - "INTRINSIC CALL", - "RETURN", - "ADDRESS OF", - "DEREFERENCE", - "ARRAY ACCESS", - "SLICE", - "FIELD ACCESS", - "UNARY FIELD ACCESS", - "PIPE", - "METHOD_CALL", - "RANGE", - "SIZE OF", - "ALIGN OF", - "FILE CONTENTS", - "STRUCT LITERAL", - "ARRAY LITERAL", - "IF EXPRESSION", - - "IF", - "FOR", - "WHILE", - "JUMP", - "USE", - "DEFER", - "SWITCH", - "CASE", - - "SOLIDIFY", - "STATIC IF", - "STATIC ERROR", - "ADD OVERLOAD", - "OPERATOR OVERLOAD", - "EXPORT", - "DEFINED", - "TAG", - "INIT", - "LIBRARY", - "REMOVE", - "CALL SITE", - - "CODE BLOCK", - "DIRECTIVE INSERT", - "MACRO", - "DO BLOCK", - - "FOREIGN BLOCK", - "ZERO VALUE", - - "NOTE", - - "AST_NODE_KIND_COUNT", -}; - -const char* onyx_ast_node_kind_string(AstKind kind) { - return ast_node_names[kind]; -} - -const char *binaryop_string[Binary_Op_Count] = { - "+", "-", "*", "/", "%", - "==", "!=", "<", "<=", ">", ">=", - "&", "|", "^", "<<", ">>", ">>>", - "&&", "||", - - "NONE", - "=", "+=", "-=", "*=", "/=", "%=", - "&=", "|=", "^=", "<<=", ">>=", ">>>=", - "NONE", - - "|>", "..", "->", - - "[]", "[]=", "^[]", -}; - -const char* entity_state_strings[Entity_State_Count] = { - "Error", - "Parse Builtin", - "Introduce Symbols", - "Parse", - "Resolve Symbols", - "Check Types", - "Code Gen", - "Finalized", - "Failed", -}; - -const char* entity_type_strings[Entity_Type_Count] = { - "Unknown", - "Error", - "Note", - "Add to Load Path", - "Load File", - "Binding (Declaration)", - "Use Package", - "Static If", - "String Literal", - "File Contents", - "Enum", - "Enum Value", - "Type Alias", - "Memory Reservation Type", - "Use", - "Interface", - "Constraint Check", - "Polymorphic Proc", - "Polymorph Query", - "Foreign Block", - "Macro", - "Foreign_Function Header", - "Temporary Function Header", - "Function Header", - "Global Header", - "Process Directive", - "Struct Member Default", - "Memory Reservation", - "Expression", - "Global", - "Overloaded_Function", - "Function", -}; - -AstNumLit* ast_reduce_type_compare(bh_allocator a, AstBinaryOp* node) { - AstType* left = (AstType *) ast_reduce(a, node->left); - AstType* right = (AstType *) ast_reduce(a, node->right); - - Type* left_type = type_build_from_ast(context.ast_alloc, left); - Type* right_type = type_build_from_ast(context.ast_alloc, right); - - AstNumLit* res = onyx_ast_node_new(a, sizeof(AstNumLit), Ast_Kind_NumLit); - res->token = node->token; - res->flags |= node->flags; - res->flags |= Ast_Flag_Comptime; - res->type_node = (AstType *) &basic_type_bool; - res->type = &basic_types[Basic_Kind_Bool]; - res->next = node->next; - - switch (node->operation) { - case Binary_Op_Equal: res->value.l = left_type->id == right_type->id; break; - case Binary_Op_Not_Equal: res->value.l = left_type->id != right_type->id; break; - default: assert(("Bad case in ast_reduce_type_compare", 0)); - } - - return res; -} - -#define REDUCE_BINOP_ALL(op) \ - if (type_is_small_integer(res->type) || type_is_bool(res->type)) { \ - res->value.i = left->value.i op right->value.i; \ - } else if (type_is_integer(res->type) \ - || res->type->Basic.kind == Basic_Kind_Int_Unsized \ - || res->type->kind == Type_Kind_Enum) { \ - res->value.l = left->value.l op right->value.l; \ - } else if (res->type->Basic.kind == Basic_Kind_F32) { \ - res->value.f = left->value.f op right->value.f; \ - } else if (res->type->Basic.kind == Basic_Kind_F64 || res->type->Basic.kind == Basic_Kind_Float_Unsized) { \ - res->value.d = left->value.d op right->value.d; \ - } \ - break; - -#define REDUCE_BINOP_INT(op) \ - if (type_is_small_integer(res->type) || type_is_bool(res->type)) { \ - res->value.i = left->value.i op right->value.i; \ - } else if (type_is_integer(res->type) \ - || res->type->Basic.kind == Basic_Kind_Int_Unsized \ - || res->type->kind == Type_Kind_Enum) { \ - res->value.l = left->value.l op right->value.l; \ - } \ - break; - -#define REDUCE_BINOP_BOOL(op) \ - if (type_is_bool(res->type)) { \ - res->value.i = left->value.i op right->value.i; \ - } \ - break; - -AstNumLit* ast_reduce_binop(bh_allocator a, AstBinaryOp* node) { - AstNumLit* left = (AstNumLit *) ast_reduce(a, node->left); - AstNumLit* right = (AstNumLit *) ast_reduce(a, node->right); - - if (node_is_type((AstNode *) left) && node_is_type((AstNode *) right)) { - if (node->operation == Binary_Op_Equal || node->operation == Binary_Op_Not_Equal) { - return (AstNumLit *) ast_reduce_type_compare(a, node); - } - } - - if (left->kind != Ast_Kind_NumLit || right->kind != Ast_Kind_NumLit) { - node->left = (AstTyped *) left; - node->right = (AstTyped *) right; - return (AstNumLit *) node; - } - - AstNumLit* res = onyx_ast_node_new(a, sizeof(AstNumLit), Ast_Kind_NumLit); - res->token = node->token; - res->flags |= node->flags; - res->flags |= Ast_Flag_Comptime; - res->type_node = node->type_node; - res->type = node->type; - res->next = node->next; - - switch (node->operation) { - case Binary_Op_Add: REDUCE_BINOP_ALL(+); - case Binary_Op_Minus: REDUCE_BINOP_ALL(-); - case Binary_Op_Multiply: REDUCE_BINOP_ALL(*); - case Binary_Op_Divide: REDUCE_BINOP_ALL(/); - case Binary_Op_Modulus: REDUCE_BINOP_INT(%); - - case Binary_Op_Equal: REDUCE_BINOP_ALL(==); - case Binary_Op_Not_Equal: REDUCE_BINOP_ALL(!=); - case Binary_Op_Less: REDUCE_BINOP_ALL(<); - case Binary_Op_Less_Equal: REDUCE_BINOP_ALL(<=); - case Binary_Op_Greater: REDUCE_BINOP_ALL(>); - case Binary_Op_Greater_Equal: REDUCE_BINOP_ALL(>=); - - case Binary_Op_And: REDUCE_BINOP_INT(&); - case Binary_Op_Or: REDUCE_BINOP_INT(|); - case Binary_Op_Xor: REDUCE_BINOP_INT(^); - case Binary_Op_Shl: REDUCE_BINOP_INT(<<); - case Binary_Op_Shr: REDUCE_BINOP_INT(>>); - case Binary_Op_Sar: REDUCE_BINOP_INT(>>); - - case Binary_Op_Bool_And: REDUCE_BINOP_BOOL(&&); - case Binary_Op_Bool_Or: REDUCE_BINOP_BOOL(||); - - default: break; - } - - return res; -} - -#define REDUCE_UNOP(op) \ - if (type_is_small_integer(unop->type) || type_is_bool(unop->type)) { \ - res->value.i = op (operand)->value.i; \ - } else if (type_is_integer(unop->type) || unop->type->Basic.kind == Basic_Kind_Int_Unsized) { \ - res->value.l = op (operand)->value.l; \ - } else if (unop->type->Basic.kind == Basic_Kind_F32) { \ - res->value.f = op (operand)->value.f; \ - } else if (unop->type->Basic.kind == Basic_Kind_F64 || unop->type->Basic.kind == Basic_Kind_Float_Unsized) { \ - res->value.d = op (operand)->value.d; \ - } \ - break; - -#define REDUCE_UNOP_INT(op) \ - if (type_is_small_integer(unop->type) || type_is_bool(unop->type)) { \ - res->value.i = op (operand)->value.i; \ - } else if (type_is_integer(unop->type) || res->type->Basic.kind == Basic_Kind_Int_Unsized) { \ - res->value.l = op (operand)->value.l; \ - } - -AstTyped* ast_reduce_unaryop(bh_allocator a, AstUnaryOp* unop) { - // GROSS - AstNumLit* operand = (AstNumLit *) ast_reduce(a, unop->expr); - unop->expr = (AstTyped *) operand; - - if (operand->kind != Ast_Kind_NumLit) { - return (AstTyped *) unop; - } - - AstNumLit* res = onyx_ast_node_new(a, sizeof(AstNumLit), Ast_Kind_NumLit); - res->token = unop->token; - res->flags |= Ast_Flag_Comptime; - res->type_node = unop->type_node; - res->type = unop->type; - res->next = unop->next; - - switch (unop->operation) { - case Unary_Op_Negate: REDUCE_UNOP(-); - case Unary_Op_Not: { - if (type_is_bool(res->type)) res->value.i = ! (operand)->value.i; - break; - } - case Unary_Op_Bitwise_Not: REDUCE_UNOP_INT(~); - - case Unary_Op_Cast: { - #if 0 - Type* from = operand->type; - Type* to = unop->type; - - if (from->kind == Type_Kind_Enum) from = from->Enum.backing; - if (to->kind == Type_Kind_Enum) to = to->Enum.backing; - - if (from->kind != Type_Kind_Pointer && from->kind != Type_Kind_Basic) return (AstTyped *) unop; - if (to->kind != Type_Kind_Pointer && to->kind != Type_Kind_Basic) return (AstTyped *) unop; - - b32 from_is_int=0, to_is_int=0; - i32 from_size, to_size; - - from_size = type_size_of(from); - to_size = type_size_of(to); - - if (from->kind == Type_Kind_Basic) from_is_int = (from->flags & (Basic_Flag_Integer | Basic_Flag_Pointer)) != 0; - if (to->kind == Type_Kind_Basic) to_is_int = (to->flags & (Basic_Flag_Integer | Basic_Flag_Pointer)) != 0; - - if (from->kind == Type_Kind_Pointer) from_is_int = 1; - if (to->kind == Type_Kind_Pointer) to_is_int = 1; - - if (from_is_int == to_is_int && from_size == to_size) { - // If they are already equal, nothing to do - return (AstTyped *) res; - } - - if (from_is_int && !to_is_int) { - if (from_size == to_size) { - if (from_size == 4) { - res->value.f = (f32) operand->value.i; - } - else if (from_size == 8) { - res->value.d = (f64) operand->value.l; - } - } else { - if (from_size == 4) { - res->value.d = (f64) operand->value.i; - } - else if (from_size == 8) { - res->value.f = (f32) operand->value.l; - } - } - } else { - if (from_size == to_size) { - if (from_size == 4) { - res->value.i = (i32) operand->value.f; - } - else if (from_size == 8) { - res->value.l = (i64) operand->value.d; - } - } else { - if (from_size == 4) { - res->value.l = (i64) operand->value.f; - } - else if (from_size == 8) { - res->value.i = (i32) operand->value.d; - } - } - } - break; - - #endif - } - - default: return (AstTyped *) unop; - } - - return (AstTyped *) res; -} - -AstTyped* ast_reduce(bh_allocator a, AstTyped* node) { - assert(node->flags & Ast_Flag_Comptime); - - switch (node->kind) { - case Ast_Kind_Binary_Op: return (AstTyped *) ast_reduce_binop(a, (AstBinaryOp *) node); - case Ast_Kind_Unary_Op: return (AstTyped *) ast_reduce_unaryop(a, (AstUnaryOp *) node); - case Ast_Kind_Enum_Value: return (AstTyped *) ((AstEnumValue *) node)->value; - case Ast_Kind_Alias: return (AstTyped *) ast_reduce(a, ((AstAlias *) node)->alias); - default: return node; - } -} - -void promote_numlit_to_larger(AstNumLit* num) { - assert(num->type != NULL); - - if (type_is_integer(num->type) && num->type->Basic.size <= 4) { - // NOTE: Int32, Int16, Int8 - i64 val = (i64) num->value.i; - num->value.l = val; - num->type = &basic_types[Basic_Kind_I64]; - } else if (num->type->Basic.size <= 4) { - // NOTE: Float32 - f64 val = (f64) num->value.f; - num->value.d = val; - num->type = &basic_types[Basic_Kind_F64]; - } -} - -// NOTE: Returns 1 if the conversion was successful. -b32 convert_numlit_to_type(AstNumLit* num, Type* to_type) { - if (num->type == NULL) - num->type = type_build_from_ast(context.ast_alloc, num->type_node); - assert(num->type); - - if (types_are_compatible(num->type, to_type)) return 1; - if (!type_is_numeric(to_type)) return 0; - - Type *type = to_type; - if (type->kind == Type_Kind_Enum) type = type->Enum.backing; - if (type->kind == Type_Kind_Distinct) type = type->Distinct.base_type; - - if (num->type->Basic.kind == Basic_Kind_Int_Unsized) { - - // - // Integer literal auto cast rules: - // - Up in size always works - // - Down in size only works if value is in range of smaller type. - // - Cast to float only works if value is less than the maximum precise value for float size. - // - - if (type->Basic.flags & Basic_Flag_Integer) { - if (type->Basic.flags & Basic_Flag_Unsigned) { - u64 value = (u64) num->value.l; - if (type->Basic.size == 8) { - num->type = to_type; - return 1; - } - switch (type->Basic.size) { - case 1: if (value <= 255) { - num->type = to_type; - return 1; - } - case 2: if (value <= 65535) { - num->type = to_type; - return 1; - } - case 4: if (value <= 4294967295) { - num->type = to_type; - return 1; - } - } - - onyx_report_error(num->token->pos, Error_Critical, "Unsigned integer constant with value '%l' does not fit into %d-bits.", - num->value.l, - type->Basic.size * 8); - - } else { - i64 value = (i64) num->value.l; - switch (type->Basic.size) { - case 1: if (-128ll <= value && value <= 127ll) { - num->value.i = (i32) value; - num->type = to_type; - return 1; - } break; - case 2: if (-32768ll <= value && value <= 32767ll) { - num->value.i = (i32) value; - num->type = to_type; - return 1; - } break; - case 4: if (-2147483648ll <= value && value <= 2147483647ll) { - num->value.i = (i32) value; - num->type = to_type; - return 1; - } break; - case 8: { num->type = to_type; - return 1; - } break; - } - - onyx_report_error(num->token->pos, Error_Critical, "Integer constant with value '%l' does not fit into %d-bits.", - num->value.l, - type->Basic.size * 8); - } - } - - else if (type->Basic.flags & Basic_Flag_Float) { - if (type->Basic.size == 4) { - // TODO(Brendan): Check these boundary conditions - if (bh_abs(num->value.l) >= (1 << 23)) { - onyx_report_error(num->token->pos, Error_Critical, "Integer '%l' does not fit in 32-bit float exactly.", num->value.l); - return 0; - } - - num->type = to_type; - num->value.f = (f32) num->value.l; - return 1; - } - if (type->Basic.size == 8) { - // TODO(Brendan): Check these boundary conditions - if (bh_abs(num->value.l) >= (1ull << 52)) { - onyx_report_error(num->token->pos, Error_Critical, "Integer '%l' does not fit in 64-bit float exactly.", num->value.l); - return 0; - } - - num->type = to_type; - num->value.d = (f64) num->value.l; - return 1; - } - } - } - else if (num->type->Basic.kind == Basic_Kind_Float_Unsized) { - // NOTE: Floats don't cast to integers implicitly. - if ((type->Basic.flags & Basic_Flag_Float) == 0) return 0; - - if (type->Basic.kind == Basic_Kind_F32) { - num->value.f = (f32) num->value.d; - } - - num->type = to_type; - return 1; - } - else if (num->type->Basic.kind == Basic_Kind_F32) { - // NOTE: Floats don't cast to integers implicitly. - if ((type->Basic.flags & Basic_Flag_Float) == 0) return 0; - - if (type->Basic.kind == Basic_Kind_F64) { - num->value.d = (f64) num->value.f; - num->type = to_type; - return 1; - } - } - - return 0; -} - -// TODO: This function should be able return a "yield" condition. There -// are a couple cases that need to yield in order to be correct, like -// polymorphic functions with a typeof for the return type. -TypeMatch unify_node_and_type_(AstTyped** pnode, Type* type, b32 permanent) { - AstTyped* node = *pnode; - if (type == NULL) return TYPE_MATCH_FAILED; - if (node == NULL) return TYPE_MATCH_FAILED; - - if (node->kind == Ast_Kind_Struct_Literal && (node->type_node == NULL && node->type == NULL)) { - if (node->entity != NULL) return TYPE_MATCH_SUCCESS; - if (type->kind == Type_Kind_VarArgs) type = type->VarArgs.elem; - - // - // If the structure literal has arguments, and the type is not constructable - // using a struct literal, then they cannot be unified. However, if no arguments - // are given, e.g. .{}, then any type should be matched, as that is the universal - // zero-value. - if (!type_is_sl_constructable(type)) { - AstStructLiteral *sl = (AstStructLiteral *) node; - if (bh_arr_length(sl->args.values) != 0 || bh_arr_length(sl->args.named_values) != 0) { - return TYPE_MATCH_FAILED; - } - } - - // If this shouldn't make permanent changes and submit entities, - // just assume that it works and don't submit the entities. - if (!permanent) return TYPE_MATCH_SUCCESS; - - node->type = type; - - add_entities_for_node(NULL, (AstNode *) node, NULL, NULL); - return TYPE_MATCH_SUCCESS; - } - - if (node->kind == Ast_Kind_Array_Literal && node->type == NULL) { - if (node->entity != NULL) return TYPE_MATCH_SUCCESS; - - // If this shouldn't make permanent changes and submit entities, - // just assume that it works and don't submit the entities. - if (!permanent) return TYPE_MATCH_SUCCESS; - - Type* array_type=NULL; - switch (type->kind) { - case Type_Kind_Array: array_type = type; break; - case Type_Kind_Slice: { - Type* elem_type = type->Slice.elem; - AstArrayLiteral* al = (AstArrayLiteral *) node; - array_type = type_make_array(context.ast_alloc, elem_type, bh_arr_length(al->values)); - - *pnode = (AstTyped *) make_cast(context.ast_alloc, node, type); - - break; - } - - default: assert(0); - } - - node->type = array_type; - node->flags |= Ast_Flag_Array_Literal_Typed; - - add_entities_for_node(NULL, (AstNode *) node, NULL, NULL); - return TYPE_MATCH_SUCCESS; - } - - if (node->kind == Ast_Kind_Unary_Field_Access) { - AstType* ast_type = type->ast_type; - AstNode* resolved = try_symbol_resolve_from_node((AstNode *) ast_type, node->token); - if (resolved == NULL) { - if (context.cycle_detected) { - token_toggle_end(node->token); - char *closest = find_closest_symbol_in_node((AstNode *) ast_type, node->token->text); - token_toggle_end(node->token); - - if (closest) { - onyx_report_error(node->token->pos, Error_Critical, "'%b' does not exist in '%s'. Did you mean '%s'?", - node->token->text, node->token->length, - type_get_name(type), - closest); - return TYPE_MATCH_FAILED; - } - } - - return TYPE_MATCH_YIELD; - } - - if (permanent) *pnode = (AstTyped *) resolved; - return TYPE_MATCH_SUCCESS; - } - - if (node->kind == Ast_Kind_Overloaded_Function) { - AstTyped* func = find_matching_overload_by_type(((AstOverloadedFunction *) node)->overloads, type); - if (func == NULL) return TYPE_MATCH_FAILED; - if (func == (AstTyped *) &node_that_signals_a_yield) return TYPE_MATCH_YIELD; - - // HACK: It feels like there should be a better place to flag that a procedure was definitely used. - if (func->kind == Ast_Kind_Function) - func->flags |= Ast_Flag_Function_Used; - - *pnode = func; - node = *pnode; - } - - if (node->kind == Ast_Kind_Polymorphic_Proc) { - AstFunction* func = polymorphic_proc_lookup((AstFunction *) node, PPLM_By_Function_Type, type, node->token); - if (func == NULL) return TYPE_MATCH_FAILED; - if (func == (AstFunction *) &node_that_signals_a_yield) return TYPE_MATCH_YIELD; - - *pnode = (AstTyped *) func; - node = *pnode; - } - - // HACK: NullProcHack - // The null_proc matches any procedure, and because of that, will cause a runtime error if you - // try to call it. - if (type->kind == Type_Kind_Function && (node->flags & Ast_Flag_Proc_Is_Null) != 0) return TYPE_MATCH_SUCCESS; - - // The normal case where everything works perfectly. - Type* node_type = get_expression_type(node); - if (types_are_compatible(node_type, type)) return TYPE_MATCH_SUCCESS; - - Type* any_type = type_build_from_ast(context.ast_alloc, builtin_any_type); - if (any_type == NULL) return TYPE_MATCH_YIELD; - i64 any_id = any_type->id; - - if (node_type && node_type->id != any_id && type->id == any_id) return TYPE_MATCH_SUCCESS; - - // Here are some of the ways you can unify a node with a type if the type of the - // node does not match the given type: - // - // If the nodes type is a function type and that function has an automatic return - // value placeholder, fill in that placeholder with the actual type. - // :AutoReturnType - if (node_type && node_type->kind == Type_Kind_Function - && node_type->Function.return_type == &type_auto_return - && type->kind == Type_Kind_Function) { - - node_type->Function.return_type = type->Function.return_type; - return TYPE_MATCH_SUCCESS; - } - - // If the node is an auto cast (~~) node, then check to see if the cast is legal - // to the destination type, and if it is change the type to cast to. - if (node_is_auto_cast((AstNode *) node)) { - char* dummy; - Type* from_type = get_expression_type(((AstUnaryOp *) node)->expr); - if (!from_type || !cast_is_legal(from_type, type, &dummy)) { - return TYPE_MATCH_FAILED; - - } else { - if (permanent) ((AstUnaryOp *) node)->type = type; - return TYPE_MATCH_SUCCESS; - } - } - - // If the destination type is a slice, then automatically convert arrays, dynamic - // arrays, and var args, if they are the same type. This is big convenience feature - // that makes working with arrays much easier. - // [N] T -> [] T - // [..] T -> [] T - // ..T -> [] T - else if (node_type && type->kind == Type_Kind_Slice) { - if (node_type->kind == Type_Kind_Array || node_type->kind == Type_Kind_DynArray || node_type->kind == Type_Kind_VarArgs) { - char* dummy; - b32 legal = cast_is_legal(node_type, type, &dummy); - if (permanent && legal) { - *pnode = (AstTyped *) make_cast(context.ast_alloc, node, type); - } - - return legal ? TYPE_MATCH_SUCCESS : TYPE_MATCH_FAILED; - } - } - - // If the node is a numeric literal, try to convert it to the destination type. - else if (node->kind == Ast_Kind_NumLit) { - if (convert_numlit_to_type((AstNumLit *) node, type)) return TYPE_MATCH_SUCCESS; - } - - // If the node is a compound expression, and it doesn't have a type created, - // recursive call this function with the individual components of the compound - // expression. - else if (node->kind == Ast_Kind_Compound) { - if (type->kind != Type_Kind_Compound) return TYPE_MATCH_FAILED; - - AstCompound* compound = (AstCompound *) node; - - u32 expr_count = bh_arr_length(compound->exprs); - if (expr_count != type->Compound.count) return TYPE_MATCH_FAILED; - - fori (i, 0, (i64) expr_count) { - TypeMatch tm = unify_node_and_type_(&compound->exprs[i], type->Compound.types[i], permanent); - if (tm != TYPE_MATCH_SUCCESS) { - return tm; - } - } - - compound->type = type_build_compound_type(context.ast_alloc, compound); - - return TYPE_MATCH_SUCCESS; - } - - else if (node->kind == Ast_Kind_If_Expression) { - AstIfExpression* if_expr = (AstIfExpression *) node; - - TypeMatch true_success = unify_node_and_type_(&if_expr->true_expr, type, permanent); - TypeMatch false_success = unify_node_and_type_(&if_expr->false_expr, type, permanent); - - if (true_success == TYPE_MATCH_SUCCESS && false_success == TYPE_MATCH_SUCCESS) { - if (permanent) if_expr->type = type; - return TYPE_MATCH_SUCCESS; - - } else if (true_success == TYPE_MATCH_FAILED || false_success == TYPE_MATCH_FAILED) { - return TYPE_MATCH_FAILED; - - } else { - return TYPE_MATCH_YIELD; - } - } - - else if (node->kind == Ast_Kind_Alias) { - AstAlias* alias = (AstAlias *) node; - return unify_node_and_type_(&alias->alias, type, permanent); - } - - else if (node->kind == Ast_Kind_Address_Of) { - AstAddressOf *address_of = (AstAddressOf *) node; - if (address_of->can_be_removed) { - if (!permanent) { - return unify_node_and_type_(&address_of->expr, type, permanent); - - } else { - *pnode = (AstTyped *) address_of->expr; - return unify_node_and_type_(pnode, type, permanent); - } - } - } - - else if (node->kind == Ast_Kind_Zero_Value) { - if (node_type == NULL) { - node->type = type; - return TYPE_MATCH_SUCCESS; // Shouldn't this be on the next line? And have node_type == node->type checked? - } - } - - return TYPE_MATCH_FAILED; -} - -Type* resolve_expression_type(AstTyped* node) { - if (node == NULL) return NULL; - - if (node->kind == Ast_Kind_Compound) { - bh_arr_each(AstTyped *, expr, ((AstCompound *) node)->exprs) { - resolve_expression_type(*expr); - } - - node->type = type_build_compound_type(context.ast_alloc, (AstCompound *) node); - return node->type; - } - - if (node->kind == Ast_Kind_Argument) { - node->type = resolve_expression_type(((AstArgument *) node)->value); - } - - if (node->kind == Ast_Kind_If_Expression) { - AstIfExpression* if_expr = (AstIfExpression *) node; - - Type* ltype = resolve_expression_type(if_expr->true_expr); - unify_node_and_type(&if_expr->false_expr, ltype); - - if_expr->type = ltype; - } - - if (node->kind == Ast_Kind_Alias) { - AstAlias* alias = (AstAlias *) node; - alias->type = resolve_expression_type(alias->alias); - } - - if (node_is_type((AstNode *) node)) { - return &basic_types[Basic_Kind_Type_Index]; - } - - if (node->kind == Ast_Kind_Array_Literal && node->type == NULL) { - AstArrayLiteral* al = (AstArrayLiteral *) node; - Type* elem_type = &basic_types[Basic_Kind_Void]; - if (bh_arr_length(al->values) > 0) { - elem_type = resolve_expression_type(al->values[0]); - } - - if (elem_type) { - node->type = type_make_array(context.ast_alloc, elem_type, bh_arr_length(al->values)); - node->flags |= Ast_Flag_Array_Literal_Typed; - - if (node->entity == NULL) { - add_entities_for_node(NULL, (AstNode *) node, NULL, NULL); - } - } - } - - if (node->kind == Ast_Kind_Struct_Literal && node->type == NULL) { - AstStructLiteral* sl = (AstStructLiteral *) node; - if (sl->stnode || sl->type_node) return NULL; - - // If values without names are given to a struct literal without - // a type, then we cannot implicitly build the type of the struct - // literal, as the name of every member cannot be known. Maybe we - // could implicitly do something like _1, _2, ... for the members - // that we not given names? - if (bh_arr_length(sl->args.values) > 0) { - return NULL; - } - - sl->type = type_build_implicit_type_of_struct_literal(context.ast_alloc, sl); - if (sl->type) { - add_entities_for_node(NULL, (AstNode *) sl, NULL, NULL); - } - } - - // If polymorphic procedures HAVE to have a type, most likely - // because they are part of a `typeof` expression, they are - // assigned a void type. This is cleared before the procedure - // is solidified. - if (node->kind == Ast_Kind_Polymorphic_Proc) { - node->type = &basic_types[Basic_Kind_Void]; - } - - if (node->kind == Ast_Kind_Macro) { - return resolve_expression_type((AstTyped *) ((AstMacro *) node)->body); - } - - if (node->type == NULL) - node->type = type_build_from_ast(context.ast_alloc, node->type_node); - - if (node->kind == Ast_Kind_NumLit && node->type->kind == Type_Kind_Basic) { - if (node->type->Basic.kind == Basic_Kind_Int_Unsized) { - b32 big = bh_abs(((AstNumLit *) node)->value.l) >= (1ull << 32); - b32 unsign = ((AstNumLit *) node)->was_hex_literal; - - if ( big && !unsign) convert_numlit_to_type((AstNumLit *) node, &basic_types[Basic_Kind_I64]); - else if ( big && unsign) convert_numlit_to_type((AstNumLit *) node, &basic_types[Basic_Kind_U64]); - else if (!big && !unsign) convert_numlit_to_type((AstNumLit *) node, &basic_types[Basic_Kind_I32]); - else if (!big && unsign) convert_numlit_to_type((AstNumLit *) node, &basic_types[Basic_Kind_U32]); - } - else if (node->type->Basic.kind == Basic_Kind_Float_Unsized) { - convert_numlit_to_type((AstNumLit *) node, &basic_types[Basic_Kind_F64]); - } - } - - return node->type; -} - -i64 get_expression_integer_value(AstTyped* node, b32 *is_valid) { - if (!node) return 0; - - resolve_expression_type(node); - - if (is_valid) *is_valid = 1; - - if (node->kind == Ast_Kind_NumLit && type_is_integer(node->type)) { - return ((AstNumLit *) node)->value.l; - } - - if (node->kind == Ast_Kind_NumLit && type_is_bool(node->type)) { - return ((AstNumLit *) node)->value.i; - } - - if (node->kind == Ast_Kind_Argument) { - return get_expression_integer_value(((AstArgument *) node)->value, is_valid); - } - - if (node->kind == Ast_Kind_Size_Of) { - return ((AstSizeOf *) node)->size; - } - - if (node->kind == Ast_Kind_Align_Of) { - return ((AstAlignOf *) node)->alignment; - } - - if (node->kind == Ast_Kind_Alias) { - return get_expression_integer_value(((AstAlias *) node)->alias, is_valid); - } - - if (node->kind == Ast_Kind_Enum_Value) { - return get_expression_integer_value(((AstEnumValue *) node)->value, is_valid); - } - - if (node_is_type((AstNode*) node)) { - Type* type = type_build_from_ast(context.ast_alloc, (AstType *) node); - if (type) return type->id; - } - - if (is_valid) *is_valid = 0; - return 0; -} - -char *get_expression_string_value(AstTyped* node, b32 *out_is_valid) { - resolve_expression_type(node); - - if (out_is_valid) *out_is_valid = 1; - - if (node->kind == Ast_Kind_StrLit) { - AstStrLit *str = (AstStrLit *) node; - - // CLEANUP: Maybe this should allocate on the heap? - // I guess if in all cases the memory is allocated on the heap, - // then the caller can free the memory. - i8* strdata = bh_alloc_array(global_heap_allocator, i8, str->token->length + 1); - i32 length = string_process_escape_seqs(strdata, str->token->text, str->token->length); - strdata[length] = '\0'; - - return strdata; - } - - if (node->kind == Ast_Kind_Alias) { - return get_expression_string_value(((AstAlias *) node)->alias, out_is_valid); - } - - if (out_is_valid) *out_is_valid = 0; - return NULL; -} - -static const b32 cast_legality[][12] = { - /* I8 */ { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, - /* U8 */ { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, - /* I16 */ { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, - /* U16 */ { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, - /* I32 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, - /* U32 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, - /* I64 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, - /* U64 */ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, - /* F32 */ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0 }, - /* F64 */ { 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0 }, - /* PTR */ { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0 }, - /* TYP */ { 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1,} -}; - -b32 cast_is_legal(Type* from_, Type* to_, char** err_msg) { - Type* from = from_; - Type* to = to_; - - if (from == NULL) { - if (err_msg) *err_msg = "'from' is null. (Compiler Error)"; - return 0; - } - if (to == NULL) { - if (err_msg) *err_msg = "'to' is null. (Compiler Error)"; - return 0; - } - - if (from_->id == to_->id) return 1; - - if (from->kind == Type_Kind_Enum) from = from->Enum.backing; - if (to->kind == Type_Kind_Enum) to = to->Enum.backing; - - if (from->kind == Type_Kind_Struct || to->kind == Type_Kind_Struct) { - *err_msg = "Cannot cast to or from a struct."; - return 0; - } - - // CLEANUP: These error messages should be a lot better and actually - // provide the types of the things in question. - if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_Array) { - if (!types_are_compatible(to->Slice.elem, from->Array.elem)) { - *err_msg = "Array to slice cast is not valid here because the types are different."; - return 0; - } else { - return 1; - } - } - - if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_DynArray) { - //if (!types_are_compatible(to->Slice.elem, from->DynArray.elem)) { - if (type_size_of(to->Slice.elem) != type_size_of(from->DynArray.elem)) { - *err_msg = "Dynmaic array to slice cast is not valid here because the types are different sizes."; - return 0; - } else { - return 1; - } - } - - if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_VarArgs) { - if (!types_are_compatible(to->Slice.elem, from->VarArgs.elem)) { - *err_msg = "Variadic argument to slice cast is not valid here because the types are different."; - return 0; - } else { - return 1; - } - } - - if (to->kind == Type_Kind_Distinct) { - if (!types_are_compatible(to->Distinct.base_type, from)) { - // :BadErrorMessage - *err_msg = "Cannot convert to a distinct type using the wrong base type."; - return 0; - } else { - return 1; - } - } - - if (from->kind == Type_Kind_Distinct) { - if (!types_are_compatible(from->Distinct.base_type, to)) { - // :BadErrorMessage - *err_msg = "Cannot convert from a distinct type to the wrong destination type."; - return 0; - } else { - return 1; - } - } - - if (from->kind == Type_Kind_Slice || to->kind == Type_Kind_Slice) { - if ((from->kind != Type_Kind_Slice || to->kind != Type_Kind_Slice) - || to->Slice.elem->kind != Type_Kind_Pointer || from->Slice.elem->kind != Type_Kind_Pointer - || !types_are_compatible(from->Slice.elem, to->Slice.elem)) { - *err_msg = "Cannot only cast between slice types when both are a slice of compatible pointers."; - return 0; - } else { - return 1; - } - } - - if (from->kind == Type_Kind_DynArray || to->kind == Type_Kind_DynArray) { - *err_msg = "Cannot cast to or from a dynamic array."; - return 0; - } - - if (to->kind == Type_Kind_Function) { - *err_msg = "Cannot cast to a function."; - return 0; - } - - if ( (type_is_simd(to) && !type_is_simd(from)) - || (!type_is_simd(to) && type_is_simd(from))) { - *err_msg = "Can only perform a SIMD cast between SIMD types."; - return 0; - } - - if (from->kind == Type_Kind_Basic && from->Basic.kind == Basic_Kind_Void) { - *err_msg = "Cannot cast from void."; - return 0; - } - i32 fromidx = -1, toidx = -1; - if (from->Basic.flags & Basic_Flag_Pointer || from->kind == Type_Kind_Array) { - fromidx = 10; - } - else if (from->Basic.flags & Basic_Flag_Integer) { - b32 unsign = (from->Basic.flags & Basic_Flag_Unsigned) != 0; - - fromidx = log2_dumb(from->Basic.size) * 2 + unsign; - } - else if (from->Basic.flags & Basic_Flag_Float) { - if (from->Basic.size == 4) fromidx = 8; - else if (from->Basic.size == 8) fromidx = 9; - } - else if (from->Basic.flags & Basic_Flag_Boolean) { - fromidx = 0; - } - else if (from->Basic.flags & Basic_Flag_Type_Index) { - fromidx = 11; - } - - if (to->Basic.flags & Basic_Flag_Pointer || to->kind == Type_Kind_Array) { - toidx = 10; - } - else if (to->Basic.flags & Basic_Flag_Integer) { - b32 unsign = (to->Basic.flags & Basic_Flag_Unsigned) != 0; - - toidx = log2_dumb(to->Basic.size) * 2 + unsign; - } - else if (to->Basic.flags & Basic_Flag_Float) { - if (to->Basic.size == 4) toidx = 8; - else if (to->Basic.size == 8) toidx = 9; - } - else if (to->Basic.flags & Basic_Flag_Boolean) { - toidx = 0; - } - else if (to->Basic.flags & Basic_Flag_Type_Index) { - toidx = 11; - } - - if (fromidx != -1 && toidx != -1) { - if (!cast_legality[fromidx][toidx]) { - *err_msg = bh_aprintf(global_heap_allocator, "Cast from '%s' to '%s' is not allowed.", type_get_name(from_), type_get_name(to_)); - return 0; - } - } else { - *err_msg = bh_aprintf(global_heap_allocator, "Cast from '%s' to '%s' is not allowed.", type_get_name(from_), type_get_name(to_)); - return 0; - } - - *err_msg = NULL; - return 1; -} - -char* get_function_name(AstFunction* func) { - if (func->kind != Ast_Kind_Function) return ""; - - if (func->name != NULL) return func->name; - - if (func->exported_name != NULL) { - return bh_aprintf(global_scratch_allocator, - "EXPORTED:%b", - func->exported_name->text, - func->exported_name->length); - } - - return ""; -} - -AstNode* strip_aliases(AstNode* n) { - if (n == NULL) return n; - - while (n->kind == Ast_Kind_Alias) n = (AstNode *) ((AstAlias *) n)->alias; - - return n; -} - -AstNumLit* make_bool_literal(bh_allocator a, b32 b) { - AstNumLit* bl = onyx_ast_node_new(a, sizeof(AstNumLit), Ast_Kind_NumLit); - bl->flags |= Ast_Flag_Comptime; - bl->type_node = (AstType *) &basic_type_bool; - - bl->value.i = b ? 1 : 0; - return bl; -} - -AstNumLit* make_int_literal(bh_allocator a, i64 i) { - AstNumLit* num = onyx_ast_node_new(a, sizeof(AstNumLit), Ast_Kind_NumLit); - num->flags |= Ast_Flag_Comptime; - - if (bh_abs(i) >= ((u64) 1 << 32)) - num->type_node = (AstType *) &basic_type_i64; - else - num->type_node = (AstType *) &basic_type_i32; - - num->value.l = i; - return num; -} - -AstNumLit* make_float_literal(bh_allocator a, f64 d) { - // NOTE: Use convert_numlit_to_type to make this a concrete float - AstNumLit* num = onyx_ast_node_new(a, sizeof(AstNumLit), Ast_Kind_NumLit); - num->flags |= Ast_Flag_Comptime; - num->type_node = (AstType *) &basic_type_float_unsized; - num->value.d = d; - return num; -} - -AstRangeLiteral* make_range_literal(bh_allocator a, AstTyped* low, AstTyped* high) { - AstRangeLiteral* rl = onyx_ast_node_new(a, sizeof(AstRangeLiteral), Ast_Kind_Range_Literal); - rl->type = builtin_range_type_type; - rl->low = low; - rl->high = high; - return rl; -} - -AstBinaryOp* make_binary_op(bh_allocator a, BinaryOp operation, AstTyped* left, AstTyped* right) { - AstBinaryOp* binop_node = onyx_ast_node_new(a, sizeof(AstBinaryOp), Ast_Kind_Binary_Op); - binop_node->left = left; - binop_node->right = right; - binop_node->operation = operation; - return binop_node; -} - -AstArgument* make_argument(bh_allocator a, AstTyped* value) { - AstArgument* arg = onyx_ast_node_new(a, sizeof(AstArgument), Ast_Kind_Argument); - if (value->token) arg->token = value->token; - arg->value = value; - arg->type = value->type; - arg->next = NULL; - arg->va_kind = VA_Kind_Not_VA; - return arg; -} - -AstFieldAccess* make_field_access(bh_allocator a, AstTyped* node, char* field) { - AstFieldAccess* fa = onyx_ast_node_new(a, sizeof(AstFieldAccess), Ast_Kind_Field_Access); - if (node->token) fa->token = node->token; - fa->field = field; - fa->expr = node; - - return fa; -} - -AstAddressOf* make_address_of(bh_allocator a, AstTyped* node) { - AstAddressOf* ao = onyx_ast_node_new(a, sizeof(AstAddressOf), Ast_Kind_Address_Of); - if (node->token) ao->token = node->token; - ao->expr = node; - - return ao; -} - -AstLocal* make_local(bh_allocator a, OnyxToken* token, AstType* type_node) { - AstLocal* local = onyx_ast_node_new(a, sizeof(AstLocal), Ast_Kind_Local); - local->token = token; - local->type_node = type_node; - - return local; -} - -AstNode* make_symbol(bh_allocator a, OnyxToken* sym) { - AstNode* symbol = onyx_ast_node_new(a, sizeof(AstNode), Ast_Kind_Symbol); - symbol->token = sym; - return symbol; -} - -AstUnaryOp* make_cast(bh_allocator a, AstTyped* expr, Type* to) { - AstUnaryOp* cast = onyx_ast_node_new(a, sizeof(AstUnaryOp), Ast_Kind_Unary_Op); - cast->token = expr->token; - cast->operation = Unary_Op_Cast; - cast->expr = expr; - cast->type = to; - return cast; -} - -AstZeroValue* make_zero_value(bh_allocator a, OnyxToken* token, Type* type) { - AstZeroValue* zero_value = onyx_ast_node_new(a, sizeof(AstZeroValue), Ast_Kind_Zero_Value); - zero_value->token = token; - zero_value->flags |= Ast_Flag_Comptime; - zero_value->type = type; - return zero_value; -} - -void arguments_initialize(Arguments* args) { - if (args->values == NULL) bh_arr_new(global_heap_allocator, args->values, 2); - if (args->named_values == NULL) bh_arr_new(global_heap_allocator, args->named_values, 2); - - // CLEANUP: I'm not sure if I need to initialize these to NULL values, but it doesn't hurt. - fori (i, 0, 2) { - args->values[i] = NULL; - args->named_values[i] = NULL; - } - - args->used_argument_count = -1; -} - -void arguments_ensure_length(Arguments* args, u32 count) { - // Make the array big enough - bh_arr_grow(args->values, count); - - // NULL initialize the new elements - fori (i, bh_arr_length(args->values), count) args->values[i] = NULL; - - // Set the actual length to the count, but never let it decrease in size - bh_arr_set_length(args->values, bh_max(count, (u32) bh_arr_length(args->values))); -} - -void arguments_copy(Arguments* dest, Arguments* src) { - dest->used_argument_count = -1; - dest->named_values = src->named_values; - - bh_arr_grow(dest->values, (u32) bh_arr_length(src->values)); - bh_arr_set_length(dest->values, (u32) bh_arr_length(src->values)); - bh_arr_each(AstTyped*, arg, dest->values) *arg = NULL; - fori (i, 0, bh_arr_length(src->values)) dest->values[i] = src->values[i]; -} - -// In clone, the named_values are not copied. This is used in find_matching_overload_by_arguments since it doesn't need them to be copied. -void arguments_clone(Arguments* dest, Arguments* src) { - dest->used_argument_count = -1; - dest->named_values = src->named_values; - dest->values = bh_arr_copy(global_heap_allocator, src->values); -} - -void arguments_deep_clone(bh_allocator a, Arguments* dest, Arguments* src) { - dest->used_argument_count = -1; - dest->values = NULL; - dest->named_values = NULL; - - bh_arr_new(global_heap_allocator, dest->named_values, bh_arr_length(src->named_values)); - bh_arr_new(global_heap_allocator, dest->values, bh_arr_length(src->values)); - - bh_arr_each(AstNamedValue *, nv, src->named_values) - bh_arr_push(dest->named_values, (AstNamedValue *) ast_clone(a, *nv)); - - bh_arr_each(AstTyped *, val, src->values) - bh_arr_push(dest->values, (AstTyped *) ast_clone(a, (AstNode *) *val)); -} - -void arguments_remove_baked(Arguments* args) { - fori (i, 0, bh_arr_length(args->values)) { - if (args->values[i]->kind != Ast_Kind_Argument) continue; - if (!((AstArgument *) args->values[i])->is_baked) continue; - - bh_arr_deleten(args->values, i, 1); - i--; - } - - fori (i, 0, bh_arr_length(args->named_values)) { - if (args->named_values[i]->value->kind != Ast_Kind_Argument) continue; - if (!((AstArgument *) args->named_values[i]->value)->is_baked) continue; - - bh_arr_deleten(args->named_values, i, 1); - i--; - } -} - -void arguments_clear_baked_flags(Arguments* args) { - bh_arr_each(AstTyped *, arg, args->values) { - if ((*arg)->kind == Ast_Kind_Argument) { - ((AstArgument *) *arg)->is_baked = 0; - } - } - - bh_arr_each(AstNamedValue *, arg, args->named_values) { - if ((*arg)->value->kind == Ast_Kind_Argument) { - ((AstArgument *) (*arg)->value)->is_baked = 0; - } - } -} - -// GROSS: Using void* to avoid having to cast everything. -const char* node_get_type_name(void* node) { - if (node_is_type((AstNode *) node)) return "type_expr"; - - if (((AstNode *) node)->kind == Ast_Kind_Argument) { - return node_get_type_name(((AstArgument *) node)->value); - } - - if (((AstNode *) node)->kind == Ast_Kind_Polymorphic_Proc) { - return "polymorphic procedure"; - } - - return type_get_name(((AstTyped *) node)->type); -} - -b32 static_if_resolution(AstIf* static_if) { - if (static_if->kind != Ast_Kind_Static_If) return 0; - - // assert(condition_value->kind == Ast_Kind_NumLit); // This should be right, right? - i64 value = get_expression_integer_value(static_if->cond, NULL); - - return value != 0; -} - -AstPolyCallType* convert_call_to_polycall(AstCall* call) { - // HACK HACK HACK - AstPolyCallType *pct = onyx_ast_node_new(context.ast_alloc, sizeof(AstPolyCallType), Ast_Kind_Poly_Call_Type); - pct->token = call->token; - pct->__unused = call->next; - pct->callee = (AstType *) call->callee; - pct->params = (AstNode **) call->args.values; - bh_arr_each(AstNode *, pp, pct->params) { - if ((*pp)->kind == Ast_Kind_Argument) { - *pp = (AstNode *) (*(AstArgument **) pp)->value; - } - } - - return pct; -} diff --git a/src/builtins.c b/src/builtins.c deleted file mode 100644 index b6eacef0..00000000 --- a/src/builtins.c +++ /dev/null @@ -1,555 +0,0 @@ -#include "astnodes.h" -#include "types.h" -#include "errors.h" -#include "utils.h" - -AstBasicType basic_type_void = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_Void] }; -AstBasicType basic_type_bool = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_Bool] }; -AstBasicType basic_type_i8 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I8] }; -AstBasicType basic_type_u8 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_U8] }; -AstBasicType basic_type_i16 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I16] }; -AstBasicType basic_type_u16 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_U16] }; -AstBasicType basic_type_i32 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I32] }; -AstBasicType basic_type_u32 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_U32] }; -AstBasicType basic_type_i64 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I64] }; -AstBasicType basic_type_u64 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_U64] }; -AstBasicType basic_type_f32 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_F32] }; -AstBasicType basic_type_f64 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_F64] }; -AstBasicType basic_type_rawptr = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_Rawptr] }; -AstBasicType basic_type_type_expr = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_Type_Index] }; - -// NOTE: Types used for numeric literals -AstBasicType basic_type_int_unsized = { Ast_Kind_Basic_Type, 0, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_Int_Unsized] }; -AstBasicType basic_type_float_unsized = { Ast_Kind_Basic_Type, 0, NULL, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_Float_Unsized] }; - -static OnyxToken simd_token = { Token_Type_Symbol, 0, "", { 0 } }; -AstBasicType basic_type_i8x16 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I8X16] }; -AstBasicType basic_type_i16x8 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I16X8] }; -AstBasicType basic_type_i32x4 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I32X4] }; -AstBasicType basic_type_i64x2 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_I64X2] }; -AstBasicType basic_type_f32x4 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_F32X4] }; -AstBasicType basic_type_f64x2 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_F64X2] }; -AstBasicType basic_type_v128 = { Ast_Kind_Basic_Type, Ast_Flag_Comptime, &simd_token, NULL, NULL, 0, NULL, &basic_types[Basic_Kind_V128] }; - -// HACK -// :AutoReturnType -Type type_auto_return = { 0 }; -AstBasicType basic_type_auto_return = { Ast_Kind_Basic_Type, 0, &simd_token, NULL, NULL, 0, NULL, &type_auto_return }; - -OnyxToken builtin_package_token = { Token_Type_Symbol, 7, "builtin ", { 0 } }; - -static OnyxToken builtin_heap_start_token = { Token_Type_Symbol, 12, "__heap_start ", { 0 } }; -static OnyxToken builtin_stack_top_token = { Token_Type_Symbol, 11, "__stack_top ", { 0 } }; -static OnyxToken builtin_tls_base_token = { Token_Type_Symbol, 10, "__tls_base ", { 0 } }; -static OnyxToken builtin_tls_size_token = { Token_Type_Symbol, 10, "__tls_size ", { 0 } }; -AstGlobal builtin_heap_start = { Ast_Kind_Global, Ast_Flag_Const, &builtin_heap_start_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; -AstGlobal builtin_stack_top = { Ast_Kind_Global, 0, &builtin_stack_top_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; -AstGlobal builtin_tls_base = { Ast_Kind_Global, 0, &builtin_tls_base_token, NULL, NULL, (AstType *) &basic_type_rawptr, NULL }; -AstGlobal builtin_tls_size = { Ast_Kind_Global, 0, &builtin_tls_size_token, NULL, NULL, (AstType *) &basic_type_u32, NULL }; - -AstType *builtin_string_type; -AstType *builtin_cstring_type; -AstType *builtin_range_type; -Type *builtin_range_type_type; -AstType *builtin_vararg_type; -Type *builtin_vararg_type_type; -AstTyped *builtin_context_variable; -AstType *builtin_allocator_type; -AstType *builtin_iterator_type; -AstType *builtin_callsite_type; -AstType *builtin_any_type; -AstType *builtin_code_type; -AstType *builtin_link_options_type; - -AstTyped *type_table_node = NULL; -AstTyped *foreign_blocks_node = NULL; -AstType *foreign_block_type = NULL; -AstTyped *tagged_procedures_node = NULL; -AstFunction *builtin_initialize_data_segments = NULL; -AstFunction *builtin_run_init_procedures = NULL; -bh_arr(AstFunction *) init_procedures = NULL; - -const BuiltinSymbol builtin_symbols[] = { - { NULL, "void", (AstNode *) &basic_type_void }, - { NULL, "bool", (AstNode *) &basic_type_bool }, - { NULL, "i8", (AstNode *) &basic_type_i8 }, - { NULL, "u8", (AstNode *) &basic_type_u8 }, - { NULL, "i16", (AstNode *) &basic_type_i16 }, - { NULL, "u16", (AstNode *) &basic_type_u16 }, - { NULL, "i32", (AstNode *) &basic_type_i32 }, - { NULL, "u32", (AstNode *) &basic_type_u32 }, - { NULL, "i64", (AstNode *) &basic_type_i64 }, - { NULL, "u64", (AstNode *) &basic_type_u64 }, - { NULL, "f32", (AstNode *) &basic_type_f32 }, - { NULL, "f64", (AstNode *) &basic_type_f64 }, - { NULL, "rawptr", (AstNode *) &basic_type_rawptr }, - { NULL, "type_expr", (AstNode *) &basic_type_type_expr }, - - { "simd", "i8x16", (AstNode *) &basic_type_i8x16 }, - { "simd", "i16x8", (AstNode *) &basic_type_i16x8 }, - { "simd", "i32x4", (AstNode *) &basic_type_i32x4 }, - { "simd", "i64x2", (AstNode *) &basic_type_i64x2 }, - { "simd", "f32x4", (AstNode *) &basic_type_f32x4 }, - { "simd", "f64x2", (AstNode *) &basic_type_f64x2 }, - { "simd", "v128", (AstNode *) &basic_type_v128 }, - - { "builtin", "__heap_start", (AstNode *) &builtin_heap_start }, - { "builtin", "__stack_top", (AstNode *) &builtin_stack_top }, - { "builtin", "__tls_base", (AstNode *) &builtin_tls_base }, - { "builtin", "__tls_size", (AstNode *) &builtin_tls_size }, - - { NULL, NULL, NULL }, -}; - -IntrinsicTable intrinsic_table; - -static IntrinsicMap builtin_intrinsics[] = { - { "memory_size", ONYX_INTRINSIC_MEMORY_SIZE }, - { "memory_grow", ONYX_INTRINSIC_MEMORY_GROW }, - { "memory_copy", ONYX_INTRINSIC_MEMORY_COPY }, - { "memory_fill", ONYX_INTRINSIC_MEMORY_FILL }, - - { "__initialize", ONYX_INTRINSIC_INITIALIZE }, - - { "clz_i32", ONYX_INTRINSIC_I32_CLZ }, - { "ctz_i32", ONYX_INTRINSIC_I32_CTZ }, - { "popcnt_i32", ONYX_INTRINSIC_I32_POPCNT }, - { "and_i32", ONYX_INTRINSIC_I32_AND }, - { "or_i32", ONYX_INTRINSIC_I32_OR }, - { "xor_i32", ONYX_INTRINSIC_I32_XOR }, - { "shl_i32", ONYX_INTRINSIC_I32_SHL }, - { "slr_i32", ONYX_INTRINSIC_I32_SLR }, - { "sar_i32", ONYX_INTRINSIC_I32_SAR }, - { "rotl_i32", ONYX_INTRINSIC_I32_ROTL }, - { "rotr_i32", ONYX_INTRINSIC_I32_ROTR }, - - { "clz_i64", ONYX_INTRINSIC_I64_CLZ }, - { "ctz_i64", ONYX_INTRINSIC_I64_CTZ }, - { "popcnt_i64", ONYX_INTRINSIC_I64_POPCNT }, - { "and_i64", ONYX_INTRINSIC_I64_AND }, - { "or_i64", ONYX_INTRINSIC_I64_OR }, - { "xor_i64", ONYX_INTRINSIC_I64_XOR }, - { "shl_i64", ONYX_INTRINSIC_I64_SHL }, - { "slr_i64", ONYX_INTRINSIC_I64_SLR }, - { "sar_i64", ONYX_INTRINSIC_I64_SAR }, - { "rotl_i64", ONYX_INTRINSIC_I64_ROTL }, - { "rotr_i64", ONYX_INTRINSIC_I64_ROTR }, - - { "abs_f32", ONYX_INTRINSIC_F32_ABS }, - { "ceil_f32", ONYX_INTRINSIC_F32_CEIL }, - { "floor_f32", ONYX_INTRINSIC_F32_FLOOR }, - { "trunc_f32", ONYX_INTRINSIC_F32_TRUNC }, - { "nearest_f32", ONYX_INTRINSIC_F32_NEAREST }, - { "sqrt_f32", ONYX_INTRINSIC_F32_SQRT }, - { "min_f32", ONYX_INTRINSIC_F32_MIN }, - { "max_f32", ONYX_INTRINSIC_F32_MAX }, - { "copysign_f32", ONYX_INTRINSIC_F32_COPYSIGN }, - - { "abs_f64", ONYX_INTRINSIC_F64_ABS }, - { "ceil_f64", ONYX_INTRINSIC_F64_CEIL }, - { "floor_f64", ONYX_INTRINSIC_F64_FLOOR }, - { "trunc_f64", ONYX_INTRINSIC_F64_TRUNC }, - { "nearest_f64", ONYX_INTRINSIC_F64_NEAREST }, - { "sqrt_f64", ONYX_INTRINSIC_F64_SQRT }, - { "min_f64", ONYX_INTRINSIC_F64_MIN }, - { "max_f64", ONYX_INTRINSIC_F64_MAX }, - { "copysign_f64", ONYX_INTRINSIC_F64_COPYSIGN }, - - - // SIMD Intrinsics - { "v128_const", ONYX_INTRINSIC_V128_CONST }, - { "i8x16_const", ONYX_INTRINSIC_I8X16_CONST }, - { "i16x8_const", ONYX_INTRINSIC_I16X8_CONST }, - { "i32x4_const", ONYX_INTRINSIC_I32X4_CONST }, - { "i64x2_const", ONYX_INTRINSIC_I64X2_CONST }, - { "f32x4_const", ONYX_INTRINSIC_F32X4_CONST }, - { "f64x2_const", ONYX_INTRINSIC_F64X2_CONST }, - { "i8x16_shuffle", ONYX_INTRINSIC_I8X16_SHUFFLE }, - - { "i8x16_extract_lane_s", ONYX_INTRINSIC_I8X16_EXTRACT_LANE_S }, - { "i8x16_extract_lane_u", ONYX_INTRINSIC_I8X16_EXTRACT_LANE_U }, - { "i8x16_replace_lane", ONYX_INTRINSIC_I8X16_REPLACE_LANE }, - { "i16x8_extract_lane_s", ONYX_INTRINSIC_I16X8_EXTRACT_LANE_S }, - { "i16x8_extract_lane_u", ONYX_INTRINSIC_I16X8_EXTRACT_LANE_U }, - { "i16x8_replace_lane", ONYX_INTRINSIC_I16X8_REPLACE_LANE }, - { "i32x4_extract_lane", ONYX_INTRINSIC_I32X4_EXTRACT_LANE }, - { "i32x4_replace_lane", ONYX_INTRINSIC_I32X4_REPLACE_LANE }, - { "i64x2_extract_lane", ONYX_INTRINSIC_I64X2_EXTRACT_LANE }, - { "i64x2_replace_lane", ONYX_INTRINSIC_I64X2_REPLACE_LANE }, - { "f32x4_extract_lane", ONYX_INTRINSIC_F32X4_EXTRACT_LANE }, - { "f32x4_replace_lane", ONYX_INTRINSIC_F32X4_REPLACE_LANE }, - { "f64x2_extract_lane", ONYX_INTRINSIC_F64X2_EXTRACT_LANE }, - { "f64x2_replace_lane", ONYX_INTRINSIC_F64X2_REPLACE_LANE }, - - { "i8x16_swizzle", ONYX_INTRINSIC_I8X16_SWIZZLE }, - { "i8x16_splat", ONYX_INTRINSIC_I8X16_SPLAT }, - { "i16x8_splat", ONYX_INTRINSIC_I16X8_SPLAT }, - { "i32x4_splat", ONYX_INTRINSIC_I32X4_SPLAT }, - { "i64x2_splat", ONYX_INTRINSIC_I64X2_SPLAT }, - { "f32x4_splat", ONYX_INTRINSIC_F32X4_SPLAT }, - { "f64x2_splat", ONYX_INTRINSIC_F64X2_SPLAT }, - - { "i8x16_eq", ONYX_INTRINSIC_I8X16_EQ }, - { "i8x16_neq", ONYX_INTRINSIC_I8X16_NEQ }, - { "i8x16_lt_s", ONYX_INTRINSIC_I8X16_LT_S }, - { "i8x16_lt_u", ONYX_INTRINSIC_I8X16_LT_U }, - { "i8x16_gt_s", ONYX_INTRINSIC_I8X16_GT_S }, - { "i8x16_gt_u", ONYX_INTRINSIC_I8X16_GT_U }, - { "i8x16_le_s", ONYX_INTRINSIC_I8X16_LE_S }, - { "i8x16_le_u", ONYX_INTRINSIC_I8X16_LE_U }, - { "i8x16_ge_s", ONYX_INTRINSIC_I8X16_GE_S }, - { "i8x16_ge_u", ONYX_INTRINSIC_I8X16_GE_U }, - - { "i16x8_eq", ONYX_INTRINSIC_I16X8_EQ }, - { "i16x8_neq", ONYX_INTRINSIC_I16X8_NEQ }, - { "i16x8_lt_s", ONYX_INTRINSIC_I16X8_LT_S }, - { "i16x8_lt_u", ONYX_INTRINSIC_I16X8_LT_U }, - { "i16x8_gt_s", ONYX_INTRINSIC_I16X8_GT_S }, - { "i16x8_gt_u", ONYX_INTRINSIC_I16X8_GT_U }, - { "i16x8_le_s", ONYX_INTRINSIC_I16X8_LE_S }, - { "i16x8_le_u", ONYX_INTRINSIC_I16X8_LE_U }, - { "i16x8_ge_s", ONYX_INTRINSIC_I16X8_GE_S }, - { "i16x8_ge_u", ONYX_INTRINSIC_I16X8_GE_U }, - - { "i32x4_eq", ONYX_INTRINSIC_I32X4_EQ }, - { "i32x4_neq", ONYX_INTRINSIC_I32X4_NEQ }, - { "i32x4_lt_s", ONYX_INTRINSIC_I32X4_LT_S }, - { "i32x4_lt_u", ONYX_INTRINSIC_I32X4_LT_U }, - { "i32x4_gt_s", ONYX_INTRINSIC_I32X4_GT_S }, - { "i32x4_gt_u", ONYX_INTRINSIC_I32X4_GT_U }, - { "i32x4_le_s", ONYX_INTRINSIC_I32X4_LE_S }, - { "i32x4_le_u", ONYX_INTRINSIC_I32X4_LE_U }, - { "i32x4_ge_s", ONYX_INTRINSIC_I32X4_GE_S }, - { "i32x4_ge_u", ONYX_INTRINSIC_I32X4_GE_U }, - - { "f32x4_eq", ONYX_INTRINSIC_F32X4_EQ }, - { "f32x4_neq", ONYX_INTRINSIC_F32X4_NEQ }, - { "f32x4_lt", ONYX_INTRINSIC_F32X4_LT }, - { "f32x4_gt", ONYX_INTRINSIC_F32X4_GT }, - { "f32x4_le", ONYX_INTRINSIC_F32X4_LE }, - { "f32x4_ge", ONYX_INTRINSIC_F32X4_GE }, - - { "f64x2_eq", ONYX_INTRINSIC_F64X2_EQ }, - { "f64x2_neq", ONYX_INTRINSIC_F64X2_NEQ }, - { "f64x2_lt", ONYX_INTRINSIC_F64X2_LT }, - { "f64x2_gt", ONYX_INTRINSIC_F64X2_GT }, - { "f64x2_le", ONYX_INTRINSIC_F64X2_LE }, - { "f64x2_ge", ONYX_INTRINSIC_F64X2_GE }, - - { "v128_not", ONYX_INTRINSIC_V128_NOT }, - { "v128_and", ONYX_INTRINSIC_V128_AND }, - { "v128_andnot", ONYX_INTRINSIC_V128_ANDNOT }, - { "v128_or", ONYX_INTRINSIC_V128_OR }, - { "v128_xor", ONYX_INTRINSIC_V128_XOR }, - { "v128_bitselect", ONYX_INTRINSIC_V128_BITSELECT }, - - { "i8x16_abs", ONYX_INTRINSIC_I8X16_ABS }, - { "i8x16_neg", ONYX_INTRINSIC_I8X16_NEG }, - { "i8x16_any_true", ONYX_INTRINSIC_I8X16_ANY_TRUE }, - { "i8x16_all_true", ONYX_INTRINSIC_I8X16_ALL_TRUE }, - { "i8x16_bitmask", ONYX_INTRINSIC_I8X16_BITMASK }, - { "i8x16_narrow_i16x8_s", ONYX_INTRINSIC_I8X16_NARROW_I16X8_S }, - { "i8x16_narrow_i16x8_u", ONYX_INTRINSIC_I8X16_NARROW_I16X8_U }, - { "i8x16_shl", ONYX_INTRINSIC_I8X16_SHL }, - { "i8x16_shr_s", ONYX_INTRINSIC_I8X16_SHR_S }, - { "i8x16_shr_u", ONYX_INTRINSIC_I8X16_SHR_U }, - { "i8x16_add", ONYX_INTRINSIC_I8X16_ADD }, - { "i8x16_add_sat_s", ONYX_INTRINSIC_I8X16_ADD_SAT_S }, - { "i8x16_add_sat_u", ONYX_INTRINSIC_I8X16_ADD_SAT_U }, - { "i8x16_sub", ONYX_INTRINSIC_I8X16_SUB }, - { "i8x16_sub_sat_s", ONYX_INTRINSIC_I8X16_SUB_SAT_S }, - { "i8x16_sub_sat_u", ONYX_INTRINSIC_I8X16_SUB_SAT_U }, - { "i8x16_min_s", ONYX_INTRINSIC_I8X16_MIN_S }, - { "i8x16_min_u", ONYX_INTRINSIC_I8X16_MIN_U }, - { "i8x16_max_s", ONYX_INTRINSIC_I8X16_MAX_S }, - { "i8x16_max_u", ONYX_INTRINSIC_I8X16_MAX_U }, - { "i8x16_avgr_u", ONYX_INTRINSIC_I8X16_AVGR_U }, - - { "i16x8_abs", ONYX_INTRINSIC_I16X8_ABS }, - { "i16x8_neg", ONYX_INTRINSIC_I16X8_NEG }, - { "i16x8_any_true", ONYX_INTRINSIC_I16X8_ANY_TRUE }, - { "i16x8_all_true", ONYX_INTRINSIC_I16X8_ALL_TRUE }, - { "i16x8_bitmask", ONYX_INTRINSIC_I16X8_BITMASK }, - { "i16x8_narrow_i32x4_s", ONYX_INTRINSIC_I16X8_NARROW_I32X4_S }, - { "i16x8_narrow_i32x4_u", ONYX_INTRINSIC_I16X8_NARROW_I32X4_U }, - { "i16x8_widen_low_i8x16_s", ONYX_INTRINSIC_I16X8_WIDEN_LOW_I8X16_S }, - { "i16x8_widen_high_i8x16_s", ONYX_INTRINSIC_I16X8_WIDEN_HIGH_I8X16_S }, - { "i16x8_widen_low_i8x16_u", ONYX_INTRINSIC_I16X8_WIDEN_LOW_I8X16_U }, - { "i16x8_widen_high_i8x16_u", ONYX_INTRINSIC_I16X8_WIDEN_HIGH_I8X16_U }, - { "i16x8_shl", ONYX_INTRINSIC_I16X8_SHL }, - { "i16x8_shr_s", ONYX_INTRINSIC_I16X8_SHR_S }, - { "i16x8_shr_u", ONYX_INTRINSIC_I16X8_SHR_U }, - { "i16x8_add", ONYX_INTRINSIC_I16X8_ADD }, - { "i16x8_add_sat_s", ONYX_INTRINSIC_I16X8_ADD_SAT_S }, - { "i16x8_add_sat_u", ONYX_INTRINSIC_I16X8_ADD_SAT_U }, - { "i16x8_sub", ONYX_INTRINSIC_I16X8_SUB }, - { "i16x8_sub_sat_s", ONYX_INTRINSIC_I16X8_SUB_SAT_S }, - { "i16x8_sub_sat_u", ONYX_INTRINSIC_I16X8_SUB_SAT_U }, - { "i16x8_mul", ONYX_INTRINSIC_I16X8_MUL }, - { "i16x8_min_s", ONYX_INTRINSIC_I16X8_MIN_S }, - { "i16x8_min_u", ONYX_INTRINSIC_I16X8_MIN_U }, - { "i16x8_max_s", ONYX_INTRINSIC_I16X8_MAX_S }, - { "i16x8_max_u", ONYX_INTRINSIC_I16X8_MAX_U }, - { "i16x8_avgr_u", ONYX_INTRINSIC_I16X8_AVGR_U }, - - { "i32x4_abs", ONYX_INTRINSIC_I32X4_ABS }, - { "i32x4_neg", ONYX_INTRINSIC_I32X4_NEG }, - { "i32x4_any_true", ONYX_INTRINSIC_I32X4_ANY_TRUE }, - { "i32x4_all_true", ONYX_INTRINSIC_I32X4_ALL_TRUE }, - { "i32x4_bitmask", ONYX_INTRINSIC_I32X4_BITMASK }, - { "i32x4_widen_low_i16x8_s", ONYX_INTRINSIC_I32X4_WIDEN_LOW_I16X8_S }, - { "i32x4_widen_high_i16x8_s", ONYX_INTRINSIC_I32X4_WIDEN_HIGH_I16X8_S }, - { "i32x4_widen_low_i16x8_u", ONYX_INTRINSIC_I32X4_WIDEN_LOW_I16X8_U }, - { "i32x4_widen_high_i16x8_u", ONYX_INTRINSIC_I32X4_WIDEN_HIGH_I16X8_U }, - { "i32x4_shl", ONYX_INTRINSIC_I32X4_SHL }, - { "i32x4_shr_s", ONYX_INTRINSIC_I32X4_SHR_S }, - { "i32x4_shl_u", ONYX_INTRINSIC_I32X4_SHR_U }, - { "i32x4_add", ONYX_INTRINSIC_I32X4_ADD }, - { "i32x4_sub", ONYX_INTRINSIC_I32X4_SUB }, - { "i32x4_mul", ONYX_INTRINSIC_I32X4_MUL }, - { "i32x4_min_s", ONYX_INTRINSIC_I32X4_MIN_S }, - { "i32x4_min_u", ONYX_INTRINSIC_I32X4_MIN_U }, - { "i32x4_max_s", ONYX_INTRINSIC_I32X4_MAX_S }, - { "i32x4_max_u", ONYX_INTRINSIC_I32X4_MAX_U }, - - { "i64x2_neg", ONYX_INTRINSIC_I64X2_NEG }, - { "i64x2_shl", ONYX_INTRINSIC_I64X2_SHL }, - { "i64x2_shr_s", ONYX_INTRINSIC_I64X2_SHR_S }, - { "i64x2_shr_u", ONYX_INTRINSIC_I64X2_SHR_U }, - { "i64x2_add", ONYX_INTRINSIC_I64X2_ADD }, - { "i64x2_sub", ONYX_INTRINSIC_I64X2_SUB }, - { "i64x2_mul", ONYX_INTRINSIC_I64X2_MUL }, - - { "f32x4_abs", ONYX_INTRINSIC_F32X4_ABS }, - { "f32x4_neg", ONYX_INTRINSIC_F32X4_NEG }, - { "f32x4_sqrt", ONYX_INTRINSIC_F32X4_SQRT }, - { "f32x4_add", ONYX_INTRINSIC_F32X4_ADD }, - { "f32x4_sub", ONYX_INTRINSIC_F32X4_SUB }, - { "f32x4_mul", ONYX_INTRINSIC_F32X4_MUL }, - { "f32x4_div", ONYX_INTRINSIC_F32X4_DIV }, - { "f32x4_min", ONYX_INTRINSIC_F32X4_MIN }, - { "f32x4_max", ONYX_INTRINSIC_F32X4_MAX }, - - { "f64x2_abs", ONYX_INTRINSIC_F64X2_ABS }, - { "f64x2_neg", ONYX_INTRINSIC_F64X2_NEG }, - { "f64x2_sqrt", ONYX_INTRINSIC_F64X2_SQRT }, - { "f64x2_add", ONYX_INTRINSIC_F64X2_ADD }, - { "f64x2_sub", ONYX_INTRINSIC_F64X2_SUB }, - { "f64x2_mul", ONYX_INTRINSIC_F64X2_MUL }, - { "f64x2_div", ONYX_INTRINSIC_F64X2_DIV }, - { "f64x2_min", ONYX_INTRINSIC_F64X2_MIN }, - { "f64x2_max", ONYX_INTRINSIC_F64X2_MAX }, - - { "i32x4_trunc_sat_f32x4_s", ONYX_INTRINSIC_I32X4_TRUNC_SAT_F32X4_S }, - { "i32x4_trunc_sat_f32x4_u", ONYX_INTRINSIC_I32X4_TRUNC_SAT_F32X4_U }, - { "f32x4_convert_i32x4_s", ONYX_INTRINSIC_F32X4_CONVERT_I32X4_S }, - { "f32x4_convert_i32x4_u", ONYX_INTRINSIC_F32X4_CONVERT_I32X4_U }, - - { "__atomic_wait", ONYX_INTRINSIC_ATOMIC_WAIT }, - { "__atomic_notify", ONYX_INTRINSIC_ATOMIC_NOTIFY }, - - { "__atomic_fence", ONYX_INTRINSIC_ATOMIC_FENCE }, - - { "__atomic_load", ONYX_INTRINSIC_ATOMIC_LOAD }, - { "__atomic_store", ONYX_INTRINSIC_ATOMIC_STORE }, - { "__atomic_add", ONYX_INTRINSIC_ATOMIC_ADD }, - { "__atomic_sub", ONYX_INTRINSIC_ATOMIC_SUB }, - { "__atomic_and", ONYX_INTRINSIC_ATOMIC_AND }, - { "__atomic_or", ONYX_INTRINSIC_ATOMIC_OR }, - { "__atomic_xor", ONYX_INTRINSIC_ATOMIC_XOR }, - { "__atomic_xchg", ONYX_INTRINSIC_ATOMIC_XCHG }, - { "__atomic_cmpxchg", ONYX_INTRINSIC_ATOMIC_CMPXCHG }, - - { NULL, ONYX_INTRINSIC_UNDEFINED }, -}; - -bh_arr(OverloadOption) operator_overloads[Binary_Op_Count] = { 0 }; - -void initialize_builtins(bh_allocator a) { - // HACK - builtin_package_token.text = bh_strdup(global_heap_allocator, builtin_package_token.text); - - BuiltinSymbol* bsym = (BuiltinSymbol *) &builtin_symbols[0]; - while (bsym->sym != NULL) { - if (bsym->package == NULL) - symbol_builtin_introduce(context.global_scope, bsym->sym, bsym->node); - else { - Package* p = package_lookup_or_create(bsym->package, context.global_scope, a, context.global_scope->created_at); - assert(p); - - symbol_builtin_introduce(p->scope, bsym->sym, bsym->node); - } - bsym++; - } - - Package* p = package_lookup_or_create("builtin", context.global_scope, a, context.global_scope->created_at); - - builtin_string_type = (AstType *) symbol_raw_resolve(p->scope, "str"); - if (builtin_string_type == NULL) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'str' struct not found in builtin package."); - return; - } - - builtin_cstring_type = (AstType *) symbol_raw_resolve(p->scope, "cstr"); - if (builtin_cstring_type == NULL) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'cstr' type not found in builtin package."); - return; - } - - builtin_range_type = (AstType *) symbol_raw_resolve(p->scope, "range"); - if (builtin_range_type == NULL) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'range' struct not found in builtin package."); - return; - } - - builtin_vararg_type = (AstType *) symbol_raw_resolve(p->scope, "vararg"); - if (builtin_range_type == NULL) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'vararg' struct not found in builtin package."); - return; - } - - builtin_context_variable = (AstTyped *) symbol_raw_resolve(p->scope, "context"); - if (builtin_context_variable == NULL) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'context' variable not found in builtin package."); - return; - } - - builtin_allocator_type = (AstType *) symbol_raw_resolve(p->scope, "Allocator"); - if (builtin_allocator_type == NULL) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'Allocator' struct not found in builtin package."); - return; - } - - builtin_iterator_type = (AstType *) symbol_raw_resolve(p->scope, "Iterator"); - if (builtin_iterator_type == NULL) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'Iterator' struct not found in builtin package."); - return; - } - - builtin_callsite_type = (AstType *) symbol_raw_resolve(p->scope, "CallSite"); - if (builtin_callsite_type == NULL) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'CallSite' struct not found in builtin package."); - return; - } - - builtin_any_type = (AstType *) symbol_raw_resolve(p->scope, "any"); - if (builtin_any_type == NULL) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'any' struct not found in builtin package."); - return; - } - - builtin_code_type = (AstType *) symbol_raw_resolve(p->scope, "Code"); - if (builtin_code_type == NULL) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'Code' struct not found in builtin package."); - return; - } - - builtin_initialize_data_segments = (AstFunction *) symbol_raw_resolve(p->scope, "__initialize_data_segments"); - if (builtin_initialize_data_segments == NULL || builtin_initialize_data_segments->kind != Ast_Kind_Function) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'__initialize_data_segments' procedure not found in builtin package."); - return; - } - - builtin_run_init_procedures = (AstFunction *) symbol_raw_resolve(p->scope, "__run_init_procedures"); - if (builtin_run_init_procedures == NULL || builtin_run_init_procedures->kind != Ast_Kind_Function) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'__run_init_procedures' procedure not found."); - return; - } - - builtin_link_options_type = (AstType *) symbol_raw_resolve(p->scope, "Link_Options"); - if (builtin_link_options_type == NULL) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, "'Link_Options' type not found."); - return; - } - - bh_arr_new(global_heap_allocator, init_procedures, 4); - - fori (i, 0, Binary_Op_Count) { - bh_arr_new(global_heap_allocator, operator_overloads[i], 4); - } - - IntrinsicMap* intrinsic = &builtin_intrinsics[0]; - while (intrinsic->name != NULL) { - shput(intrinsic_table, intrinsic->name, intrinsic->intrinsic); - intrinsic++; - } -} - -void initalize_special_globals() { - Package *p = package_lookup("runtime.info"); - if (p != NULL) { - type_table_node = (AstTyped *) symbol_raw_resolve(p->scope, "type_table"); - foreign_blocks_node = (AstTyped *) symbol_raw_resolve(p->scope, "foreign_blocks"); - foreign_block_type = (AstType *) symbol_raw_resolve(p->scope, "foreign_block"); - tagged_procedures_node = (AstTyped *) symbol_raw_resolve(p->scope, "tagged_procedures"); - } -} - -void introduce_build_options(bh_allocator a) { - Package* p = package_lookup_or_create("runtime", context.global_scope, a, context.global_scope->created_at); - - AstType* Runtime_Type = (AstType *) symbol_raw_resolve(p->scope, "Runtime"); - if (Runtime_Type == NULL) { - onyx_report_error((OnyxFilePos) {0}, Error_Critical, "'Runtime' type not found in package runtime."); - return; - } - - AstNumLit* runtime_type = make_int_literal(a, context.options->runtime); - runtime_type->type_node = Runtime_Type; - add_entities_for_node(NULL, (AstNode *) runtime_type, NULL, NULL); - symbol_builtin_introduce(p->scope, "runtime", (AstNode *) runtime_type); - - AstNumLit* multi_threaded = make_int_literal(a, context.options->use_multi_threading); - multi_threaded->type_node = (AstType *) &basic_type_bool; - symbol_builtin_introduce(p->scope, "Multi_Threading_Enabled", (AstNode *) multi_threaded); - - AstNumLit* wait_notify_available = make_int_literal(a, context.options->use_multi_threading && context.options->runtime == Runtime_Js); - wait_notify_available->type_node = (AstType *) &basic_type_bool; - symbol_builtin_introduce(p->scope, "Wait_Notify_Available", (AstNode *) wait_notify_available); - - i32 os; - #ifdef _BH_LINUX - os = 1; - #endif - #ifdef _BH_WINDOWS - os = 2; - #endif - - AstType* OS_Type = (AstType *) symbol_raw_resolve(p->scope, "OS"); - if (OS_Type == NULL) { - onyx_report_error((OnyxFilePos) {0}, Error_Critical, "'OS' type not found in package runtime."); - return; - } - - AstNumLit* os_type = make_int_literal(a, os); - os_type->type_node = OS_Type; - add_entities_for_node(NULL, (AstNode *) os_type, NULL, NULL); - symbol_builtin_introduce(p->scope, "compiler_os", (AstNode *) os_type); - - i32 arch = 0; - #if defined(__x86_64__) || defined(_M_X64) - arch = 1; // X86_64; - #elif defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86) - arch = 2; // X86_32; - #elif defined(__aarch64__) || defined(_M_ARM64) - arch = 3; // AARCH64; - #endif - - AstType* Arch_Type = (AstType *) symbol_raw_resolve(p->scope, "Arch"); - if (Arch_Type == NULL) { - onyx_report_error((OnyxFilePos) {0}, Error_Critical, "'Arch' type not found in package runtime."); - return; - } - - AstNumLit* arch_type = make_int_literal(a, arch); - arch_type->type_node = Arch_Type; - add_entities_for_node(NULL, (AstNode *) arch_type, NULL, NULL); - symbol_builtin_introduce(p->scope, "arch", (AstNode *) arch_type); - - if (context.options->generate_foreign_info) { - AstNumLit* foreign_info = make_int_literal(a, 1); - foreign_info->type_node = (AstType *) &basic_type_bool; - symbol_builtin_introduce(p->scope, "Generated_Foreign_Info", (AstNode *) foreign_info); - } -} - diff --git a/src/checker.c b/src/checker.c deleted file mode 100644 index 333e331c..00000000 --- a/src/checker.c +++ /dev/null @@ -1,3023 +0,0 @@ -#define BH_DEBUG -#include "parser.h" -#include "utils.h" - -// All of the `check` functions return a boolean that signals if an issue -// was reached while processing the node. These error booleans propagate -// up the call stack until they reach `check_entity`. - -#define CHECK(kind, ...) do { \ - CheckStatus cs = check_ ## kind (__VA_ARGS__); \ - if (cs > Check_Errors_Start) return cs; \ - } while (0) - -#define YIELD(loc, msg) do { \ - if (context.cycle_detected) { \ - onyx_report_error(loc, Error_Waiting_On, msg); \ - return Check_Error; \ - } else { \ - return Check_Yield_Macro; \ - } \ - } while (0) - -#define YIELD_(loc, msg, ...) do { \ - if (context.cycle_detected) { \ - onyx_report_error(loc, Error_Waiting_On, msg, __VA_ARGS__); \ - return Check_Error; \ - } else { \ - return Check_Yield_Macro; \ - } \ - } while (0) - -#define YIELD_ERROR(loc, msg) do { \ - if (context.cycle_detected) { \ - onyx_report_error(loc, Error_Critical, msg); \ - return Check_Error; \ - } else { \ - return Check_Yield_Macro; \ - } \ - } while (0) - -#define ERROR(loc, msg) do { \ - onyx_report_error(loc, Error_Critical, msg); \ - return Check_Error; \ - } while (0) - -#define ERROR_(loc, msg, ...) do { \ - onyx_report_error(loc, Error_Critical, msg, __VA_ARGS__); \ - return Check_Error; \ - } while (0) - -#define TYPE_CHECK_(expr, type, type_name) \ - TypeMatch type_name; \ - type_name = unify_node_and_type(expr, type); \ - if (type_name == TYPE_MATCH_YIELD) YIELD((*expr)->token->pos, "Waiting on type checking."); \ - if (type_name == TYPE_MATCH_FAILED) - -#define CONCAT(a, b) a##_##b -#define DEFER_LINE(a, line) CONCAT(a, line) -#define TYPE_CHECK(expr, type) TYPE_CHECK_(expr, type, DEFER_LINE(tc, __LINE__)) - -typedef enum CheckStatus { - Check_Success, // The node was successfully checked with out errors - Check_Complete, // The node is done processing - - Check_Errors_Start, - Check_Return_To_Symres, // Return this node for further symres processing - Check_Yield_Macro, - Check_Failed, // The node is done processing and should be put in the state of Failed. - Check_Error, // There was an error when checking the node -} CheckStatus; - -CheckStatus check_block(AstBlock* block); -CheckStatus check_statement_chain(AstNode** start); -CheckStatus check_statement(AstNode** pstmt); -CheckStatus check_return(AstReturn* retnode); -CheckStatus check_if(AstIfWhile* ifnode); -CheckStatus check_while(AstIfWhile* whilenode); -CheckStatus check_for(AstFor* fornode); -CheckStatus check_switch(AstSwitch* switchnode); -CheckStatus check_call(AstCall** pcall); -CheckStatus check_binaryop(AstBinaryOp** pbinop); -CheckStatus check_unaryop(AstUnaryOp** punop); -CheckStatus check_struct_literal(AstStructLiteral* sl); -CheckStatus check_array_literal(AstArrayLiteral* al); -CheckStatus check_range_literal(AstRangeLiteral** range); -CheckStatus check_compound(AstCompound* compound); -CheckStatus check_if_expression(AstIfExpression* if_expr); -CheckStatus check_expression(AstTyped** expr); -CheckStatus check_address_of(AstAddressOf** paof); -CheckStatus check_dereference(AstDereference* deref); -CheckStatus check_subscript(AstSubscript** paa); -CheckStatus check_field_access(AstFieldAccess** pfield); -CheckStatus check_method_call(AstBinaryOp** mcall); -CheckStatus check_size_of(AstSizeOf* so); -CheckStatus check_align_of(AstAlignOf* ao); -CheckStatus check_global(AstGlobal* global); -CheckStatus check_function(AstFunction* func); -CheckStatus check_overloaded_function(AstOverloadedFunction* func); -CheckStatus check_struct(AstStructType* s_node); -CheckStatus check_temp_function_header(AstFunction* func); -CheckStatus check_function_header(AstFunction* func); -CheckStatus check_memres_type(AstMemRes* memres); -CheckStatus check_memres(AstMemRes* memres); -CheckStatus check_type(AstType** ptype); -CheckStatus check_insert_directive(AstDirectiveInsert** pinsert); -CheckStatus check_directive_solidify(AstDirectiveSolidify** psolid); -CheckStatus check_do_block(AstDoBlock** pdoblock); -CheckStatus check_constraint(AstConstraint *constraint); -CheckStatus check_constraint_context(ConstraintContext *cc, Scope *scope, OnyxFilePos pos); -CheckStatus check_polyquery(AstPolyQuery *query); - -// HACK HACK HACK -b32 expression_types_must_be_known = 0; -b32 all_checks_are_final = 1; -b32 inside_for_iterator = 0; - -#define STATEMENT_LEVEL 1 -#define EXPRESSION_LEVEL 2 -u32 current_checking_level=0; - -static inline void fill_in_type(AstTyped* node) { - if (node->type == NULL) { - if (check_type(&node->type_node) > Check_Errors_Start) return; - - node->type = type_build_from_ast(context.ast_alloc, node->type_node); - } -} - -// HACK: This should be baked into a structure, not a global variable. -static Type** expected_return_type = NULL; - -CheckStatus check_return(AstReturn* retnode) { - if (retnode->expr) { - CHECK(expression, &retnode->expr); - - if (*expected_return_type == &type_auto_return) { - resolve_expression_type(retnode->expr); - if (retnode->expr->type == NULL) - YIELD_ERROR(retnode->token->pos, "Unable to determine the automatic return type here."); - - *expected_return_type = retnode->expr->type; - return Check_Success; - } - - TYPE_CHECK(&retnode->expr, *expected_return_type) { - ERROR_(retnode->token->pos, - "Expected to return a value of type '%s', returning value of type '%s'.", - type_get_name(*expected_return_type), - node_get_type_name(retnode->expr)); - } - - // - // Catch the obvious case of return '^.{ ... }', as that will never - // be legal. - if (retnode->expr->kind == Ast_Kind_Address_Of) { - AstAddressOf *aof = (AstAddressOf *) retnode->expr; - if (node_is_addressable_literal((AstNode *) aof->expr)) { - ERROR(retnode->token->pos, "Cannot return a pointer to a literal, as the space reserved for the literal will be freed upon returning."); - } - } - - } else { - if (*expected_return_type == &type_auto_return) { - *expected_return_type = &basic_types[Basic_Kind_Void]; - return Check_Success; - } - - if ((*expected_return_type)->Basic.size > 0) { - ERROR_(retnode->token->pos, - "Returning from non-void function without a value. Expected a value of type '%s'.", - type_get_name(*expected_return_type)); - } - } - - return Check_Success; -} - -CheckStatus check_if(AstIfWhile* ifnode) { - if (ifnode->initialization != NULL) CHECK(statement_chain, &ifnode->initialization); - - if (ifnode->kind == Ast_Kind_Static_If) { - if ((ifnode->flags & Ast_Flag_Static_If_Resolved) == 0) { - YIELD(ifnode->token->pos, "Waiting for static if to be resolved."); - } - - if (static_if_resolution(ifnode)) { - if (ifnode->true_stmt != NULL) { - CHECK(statement, (AstNode **) &ifnode->true_stmt); - ifnode->true_stmt->rules = Block_Rule_Macro; - } - - } else { - if (ifnode->false_stmt != NULL) { - CHECK(statement, (AstNode **) &ifnode->false_stmt); - ifnode->false_stmt->rules = Block_Rule_Macro; - } - } - - } else { - CHECK(expression, &ifnode->cond); - - if (!type_is_bool(ifnode->cond->type)) { - ERROR_(ifnode->cond->token->pos, "Expected expression of type 'bool' for condition, got '%s'", type_get_name(ifnode->cond->type)); - } - - if (ifnode->true_stmt) CHECK(statement, (AstNode **) &ifnode->true_stmt); - if (ifnode->false_stmt) CHECK(statement, (AstNode **) &ifnode->false_stmt); - } - - return Check_Success; -} - -CheckStatus check_while(AstIfWhile* whilenode) { - if (whilenode->initialization != NULL) CHECK(statement_chain, &whilenode->initialization); - - CHECK(expression, &whilenode->cond); - - if (!type_is_bool(whilenode->cond->type)) { - ERROR_(whilenode->cond->token->pos, "Expected expression of type 'bool' for condition, got '%s'", type_get_name(whilenode->cond->type)); - } - - if (whilenode->true_stmt) CHECK(statement, (AstNode **) &whilenode->true_stmt); - if (whilenode->false_stmt) { - if (whilenode->bottom_test) { - ERROR(whilenode->token->pos, "while-loops with an 'else' clause cannot be bottom tested."); - } - - CHECK(statement, (AstNode **) &whilenode->false_stmt); - } - - return Check_Success; -} - -CheckStatus check_for(AstFor* fornode) { - b32 old_inside_for_iterator; - if (fornode->flags & Ast_Flag_Has_Been_Checked) goto fornode_expr_checked; - - CHECK(expression, &fornode->iter); - resolve_expression_type(fornode->iter); - - Type* iter_type = fornode->iter->type; - if (iter_type == NULL) YIELD(fornode->token->pos, "Waiting for iteration expression type to be known."); - - OnyxFilePos error_loc = fornode->var->token->pos; - if (error_loc.filename == NULL) { - error_loc = fornode->token->pos; - } - - fornode->loop_type = For_Loop_Invalid; - if (types_are_compatible(iter_type, &basic_types[Basic_Kind_I32])) { - if (fornode->by_pointer) { - ERROR(error_loc, "Cannot iterate by pointer over a range."); - } - - AstNumLit* low_0 = make_int_literal(context.ast_alloc, 0); - AstRangeLiteral* rl = make_range_literal(context.ast_alloc, (AstTyped *) low_0, fornode->iter); - CHECK(range_literal, &rl); - fornode->iter = (AstTyped *) rl; - - fornode->var->type = builtin_range_type_type->Struct.memarr[0]->type; - fornode->var->flags |= Ast_Flag_Cannot_Take_Addr; - fornode->loop_type = For_Loop_Range; - } - else if (types_are_compatible(iter_type, builtin_range_type_type)) { - if (fornode->by_pointer) { - ERROR(error_loc, "Cannot iterate by pointer over a range."); - } - - // NOTE: Blindly copy the first range member's type which will - // be the low value. - brendanfh 2020/09/04 - fornode->var->type = builtin_range_type_type->Struct.memarr[0]->type; - fornode->var->flags |= Ast_Flag_Cannot_Take_Addr; - fornode->loop_type = For_Loop_Range; - - } - else if (iter_type->kind == Type_Kind_Array) { - if (fornode->by_pointer) fornode->var->type = type_make_pointer(context.ast_alloc, iter_type->Array.elem); - else fornode->var->type = iter_type->Array.elem; - - fornode->loop_type = For_Loop_Array; - } - else if (iter_type->kind == Type_Kind_Slice) { - if (fornode->by_pointer) fornode->var->type = type_make_pointer(context.ast_alloc, iter_type->Slice.elem); - else fornode->var->type = iter_type->Slice.elem; - - fornode->loop_type = For_Loop_Slice; - - } - else if (iter_type->kind == Type_Kind_VarArgs) { - if (fornode->by_pointer) { - ERROR_(error_loc, "Cannot iterate by pointer over '%s'.", type_get_name(iter_type)); - } - - fornode->var->type = iter_type->VarArgs.elem; - - // NOTE: Slices are VarArgs are being treated the same here. - fornode->loop_type = For_Loop_Slice; - } - else if (iter_type->kind == Type_Kind_DynArray) { - if (fornode->by_pointer) fornode->var->type = type_make_pointer(context.ast_alloc, iter_type->DynArray.elem); - else fornode->var->type = iter_type->DynArray.elem; - - fornode->loop_type = For_Loop_DynArr; - } - else if (type_struct_constructed_from_poly_struct(iter_type, builtin_iterator_type)) { - if (fornode->by_pointer) { - ERROR(error_loc, "Cannot iterate by pointer over an iterator."); - } - - // HACK: This assumes the Iterator type only has a single type argument. - fornode->var->type = iter_type->Struct.poly_sln[0].type; - fornode->loop_type = For_Loop_Iterator; - } - - if (fornode->by_pointer) - fornode->var->flags |= Ast_Flag_Cannot_Take_Addr; - - if (fornode->loop_type == For_Loop_Invalid) - ERROR_(error_loc, "Cannot iterate over a '%s'.", type_get_name(iter_type)); - - if (fornode->no_close && fornode->loop_type != For_Loop_Iterator) { - onyx_report_warning(error_loc, "Warning: #no_close here is meaningless as the iterable is not an iterator."); - } - - fornode->flags |= Ast_Flag_Has_Been_Checked; - -fornode_expr_checked: - old_inside_for_iterator = inside_for_iterator; - inside_for_iterator = 0; - iter_type = fornode->iter->type; - if (type_struct_constructed_from_poly_struct(iter_type, builtin_iterator_type)) { - inside_for_iterator = 1; - } - - do { - CheckStatus cs = check_block(fornode->stmt); - inside_for_iterator = old_inside_for_iterator; - if (cs > Check_Errors_Start) return cs; - } while(0); - - return Check_Success; -} - -static b32 add_case_to_switch_statement(AstSwitch* switchnode, u64 case_value, AstBlock* block, OnyxFilePos pos) { - assert(switchnode->switch_kind == Switch_Kind_Integer); - - switchnode->min_case = bh_min(switchnode->min_case, case_value); - switchnode->max_case = bh_max(switchnode->max_case, case_value); - - if (bh_imap_has(&switchnode->case_map, case_value)) { - onyx_report_error(pos, Error_Critical, "Multiple cases for values '%d'.", case_value); - return 1; - } - - bh_imap_put(&switchnode->case_map, case_value, (u64) block); - return 0; -} - -static CheckStatus collect_switch_case_blocks(AstSwitch* switchnode, AstBlock* root) { - AstNode *walker = root->body; - while (walker != NULL) { - switch (walker->kind) { - case Ast_Kind_Block: - collect_switch_case_blocks(switchnode, (AstBlock *) walker); - break; - - case Ast_Kind_Switch_Case: { - AstSwitchCase *case_node = (AstSwitchCase *) walker; - if (case_node->is_default) { - if (switchnode->default_case != NULL && switchnode->default_case != case_node->block) { - ERROR(case_node->token->pos, "Multiple #default cases given"); - ERROR(switchnode->default_case->token->pos, "Multiple #default cases given"); - return Check_Error; - } - - switchnode->default_case = case_node->block; - } else { - bh_arr_push(switchnode->cases, case_node); - } - break; - } - - default: - ERROR(walker->token->pos, "This statement is not allowed here."); - } - - walker = walker->next; - } - - return Check_Success; -} - -CheckStatus check_switch(AstSwitch* switchnode) { - if (switchnode->initialization != NULL) CHECK(statement_chain, &switchnode->initialization); - - CHECK(expression, &switchnode->expr); - Type* resolved_expr_type = resolve_expression_type(switchnode->expr); - - if (!(switchnode->flags & Ast_Flag_Has_Been_Checked)) { - if (resolved_expr_type == NULL) YIELD(switchnode->token->pos, "Waiting for expression type to be known."); - - switchnode->switch_kind = Switch_Kind_Integer; - if (!type_is_integer(switchnode->expr->type) && switchnode->expr->type->kind != Type_Kind_Enum) { - switchnode->switch_kind = Switch_Kind_Use_Equals; - } - - switch (switchnode->switch_kind) { - case Switch_Kind_Integer: - switchnode->min_case = 0xffffffffffffffff; - bh_imap_init(&switchnode->case_map, global_heap_allocator, 4); - break; - - case Switch_Kind_Use_Equals: - bh_arr_new(global_heap_allocator, switchnode->case_exprs, 4); - break; - - default: assert(0); - } - } - switchnode->flags |= Ast_Flag_Has_Been_Checked; - - // Should the case block code be checked here? - // Or should this just exist to resolve macros and expand #unquotes - // then the cases are consumed into the array or cases, THEN the blocks - // are actually checked? - if (switchnode->cases == NULL) { - CHECK(block, switchnode->case_block); - - bh_arr_new(global_heap_allocator, switchnode->cases, 4); - if (collect_switch_case_blocks(switchnode, switchnode->case_block) != Check_Success) { - return Check_Error; - } - - // This is important, otherwise if this block has to return to symbol resolution. - switchnode->case_block->statement_idx = 0; - } - - fori (i, switchnode->yield_return_index, bh_arr_length(switchnode->cases)) { - AstSwitchCase *sc = switchnode->cases[i]; - CHECK(block, sc->block); - - bh_arr_each(AstTyped *, value, sc->values) { - CHECK(expression, value); - - if (switchnode->switch_kind == Switch_Kind_Integer && (*value)->kind == Ast_Kind_Range_Literal) { - AstRangeLiteral* rl = (AstRangeLiteral *) (*value); - resolve_expression_type(rl->low); - resolve_expression_type(rl->high); - - if (rl->low->kind != Ast_Kind_NumLit || rl->high->kind != Ast_Kind_NumLit) { - ERROR(rl->token->pos, "case statement expected compile time known range."); - } - - promote_numlit_to_larger((AstNumLit *) rl->low); - promote_numlit_to_larger((AstNumLit *) rl->high); - - i64 lower = ((AstNumLit *) rl->low)->value.l; - i64 upper = ((AstNumLit *) rl->high)->value.l; - - // NOTE: This is inclusive!!!! - fori (case_value, lower, upper + 1) { - if (add_case_to_switch_statement(switchnode, case_value, sc->block, rl->token->pos)) - return Check_Error; - } - - continue; - } - - TYPE_CHECK(value, resolved_expr_type) { - OnyxToken* tkn = sc->block->token; - if ((*value)->token) tkn = (*value)->token; - - ERROR_(tkn->pos, "Mismatched types in switch-case. Expected '%s', got '%s'.", - type_get_name(resolved_expr_type), type_get_name((*value)->type)); - } - - switch (switchnode->switch_kind) { - case Switch_Kind_Integer: { - b32 is_valid; - i64 integer_value = get_expression_integer_value(*value, &is_valid); - if (!is_valid) - ERROR_((*value)->token->pos, "Case statement expected compile time known integer. Got '%s'.", onyx_ast_node_kind_string((*value)->kind)); - - if (add_case_to_switch_statement(switchnode, integer_value, sc->block, sc->block->token->pos)) - return Check_Error; - - break; - } - - case Switch_Kind_Use_Equals: { - // Gross - b32 found = 0; - bh_arr_each(CaseToBlock, ctb, switchnode->case_exprs) { - if (ctb->original_value == *value) { - CHECK(expression, (AstTyped **) &ctb->comparison); - found = 1; - break; - } - } - if (found) break; - - CaseToBlock ctb; - ctb.block = sc->block; - ctb.original_value = *value; - ctb.comparison = make_binary_op(context.ast_alloc, Binary_Op_Equal, switchnode->expr, *value); - ctb.comparison->token = (*value)->token; - bh_arr_push(switchnode->case_exprs, ctb); - - CHECK(binaryop, &bh_arr_last(switchnode->case_exprs).comparison); - break; - } - } - } - - switchnode->yield_return_index += 1; - } - - if (switchnode->default_case) - CHECK(block, switchnode->default_case); - - return 0; -} - -CheckStatus check_arguments(Arguments* args) { - bh_arr_each(AstTyped *, actual, args->values) - CHECK(expression, actual); - - bh_arr_each(AstNamedValue *, named_value, args->named_values) - CHECK(expression, &(*named_value)->value); - - return Check_Success; -} - -CheckStatus check_argument(AstArgument** parg) { - CHECK(expression, &(*parg)->value); - (*parg)->type = (*parg)->value->type; - - return Check_Success; -} - -static CheckStatus check_resolve_callee(AstCall* call, AstTyped** effective_callee) { - if (call->kind == Ast_Kind_Intrinsic_Call) return Check_Success; - - AstTyped* callee = (AstTyped *) strip_aliases((AstNode *) call->callee); - b32 calling_a_macro = 0; - - if (callee->kind == Ast_Kind_Overloaded_Function) { - AstTyped* new_callee = find_matching_overload_by_arguments( - ((AstOverloadedFunction *) callee)->overloads, - &call->args); - - if (new_callee == NULL) { - report_unable_to_match_overload(call, ((AstOverloadedFunction *) callee)->overloads); - return Check_Error; - } - - if (new_callee == (AstTyped *) &node_that_signals_a_yield) { - YIELD(call->token->pos, "Waiting for overloaded function option to pass type-checking."); - } - - callee = new_callee; - } - - if (callee->kind == Ast_Kind_Macro) { - calling_a_macro = 1; - call->callee = callee; - - AstTyped* new_callee = (AstTyped *) macro_resolve_header((AstMacro *) callee, &call->args, call->token, 1); - if (new_callee == NULL) return Check_Error; - if (new_callee == (AstTyped *) &node_that_signals_a_yield) { - YIELD(call->token->pos, "Waiting for macro header to pass type-checking."); - } - - arguments_remove_baked(&call->args); - callee = new_callee; - - } else while (callee->kind == Ast_Kind_Polymorphic_Proc) { - AstTyped* new_callee = (AstTyped *) polymorphic_proc_lookup((AstFunction *) callee, PPLM_By_Arguments, &call->args, call->token); - if (new_callee == NULL) return Check_Error; - if (new_callee == (AstTyped *) &node_that_signals_a_yield) { - YIELD(call->token->pos, "Waiting for polymorphic procedure header to pass type-checking."); - } - - arguments_remove_baked(&call->args); - callee = new_callee; - } - - // NOTE: Build callee's type - fill_in_type((AstTyped *) callee); - if (callee->type == NULL) { - YIELD(call->token->pos, "Trying to resolve function type for callee."); - } - - if (!calling_a_macro) call->callee = callee; - - if (callee->type->kind != Type_Kind_Function) { - ERROR_(call->token->pos, - "Attempting to call something that is not a function, '%b'.", - callee->token->text, callee->token->length); - } - - *effective_callee = callee; - return Check_Success; -} - -CheckStatus check_call(AstCall** pcall) { - // All the things that need to be done when checking a call node. - // 1. Ensure the callee is not a symbol - // 2. Check the callee expression (since it could be a variable or a field access, etc) - // 3. Check all arguments - // * Cannot pass overloaded functions (ROBUSTNESS) - // 4. If callee is an overloaded function, use the argument types to determine which overload is used. - // 5. If callee is polymorphic, use the arguments type to generate a polymorphic function. - // 7. Fill in arguments - // 8. If callee is an intrinsic, turn call into an Intrinsic_Call node - // 9. Check types of formal and actual params against each other, handling varargs - AstCall* call = *pcall; - - if (call->kind == Ast_Kind_Call) { - AstNode* callee = strip_aliases((AstNode *) call->callee); - if (callee->kind == Ast_Kind_Poly_Struct_Type) { - *pcall = (AstCall *) convert_call_to_polycall(call); - CHECK(expression, (AstTyped **) pcall); - return Check_Success; - } - } - - if (call->flags & Ast_Flag_Has_Been_Checked) return Check_Success; - - u32 current_checking_level_store = current_checking_level; - CHECK(expression, &call->callee); - CHECK(arguments, &call->args); - current_checking_level = current_checking_level_store; - - AstFunction* callee=NULL; - CHECK(resolve_callee, call, (AstTyped **) &callee); - - if (callee->kind == Ast_Kind_Function) { - if (callee->constraints.constraints != NULL && callee->constraints.constraints_met == 0) { - YIELD(call->token->pos, "Waiting for constraints to be checked on callee."); - } - } - - i32 arg_count = get_argument_buffer_size(&callee->type->Function, &call->args); - arguments_ensure_length(&call->args, arg_count); - - char* err_msg = NULL; - fill_in_arguments(&call->args, (AstNode *) callee, &err_msg, 0); - if (err_msg != NULL) ERROR(call->token->pos, err_msg); - - bh_arr(AstArgument *) arg_arr = (bh_arr(AstArgument *)) call->args.values; - bh_arr_each(AstArgument *, arg, arg_arr) { - if (*arg != NULL) continue; - - ERROR(call->token->pos, "Not all arguments were given a value."); - } - - // HACK HACK HACK - // :CallSiteIsGross - bh_arr_each(AstArgument *, arg, arg_arr) { - AstTyped** arg_value = &(*arg)->value; - - if ((*arg_value)->kind == Ast_Kind_Call_Site) { - AstCallSite* callsite = (AstCallSite *) ast_clone(context.ast_alloc, *arg_value); - callsite->callsite_token = call->token; - - // HACK CLEANUP - OnyxToken* str_token = bh_alloc(context.ast_alloc, sizeof(OnyxToken)); - str_token->text = bh_strdup(global_heap_allocator, (char *) call->token->pos.filename); - str_token->length = strlen(call->token->pos.filename); - str_token->pos = call->token->pos; - str_token->type = Token_Type_Literal_String; - - AstStrLit* filename = bh_alloc_item(context.ast_alloc, AstStrLit); - memset(filename, 0, sizeof(AstStrLit)); - filename->kind = Ast_Kind_StrLit; - filename->token = str_token; - filename->data_id = 0; - - add_entities_for_node(NULL, (AstNode *) filename, NULL, NULL); - callsite->filename = filename; - - callsite->line = make_int_literal(context.ast_alloc, call->token->pos.line); - callsite->column = make_int_literal(context.ast_alloc, call->token->pos.column); - - convert_numlit_to_type(callsite->line, &basic_types[Basic_Kind_U32]); - convert_numlit_to_type(callsite->column, &basic_types[Basic_Kind_U32]); - - *arg_value = (AstTyped *) callsite; - } - } - - // NOTE: If we are calling an intrinsic function, translate the - // call into an intrinsic call node. - if (callee->kind == Ast_Kind_Function && callee->is_intrinsic) { - call->kind = Ast_Kind_Intrinsic_Call; - call->callee = NULL; - - token_toggle_end(callee->intrinsic_name); - char* intr_name = callee->intrinsic_name->text; - - i32 index; - if ((index = shgeti(intrinsic_table, intr_name)) == -1) { - onyx_report_error(callee->token->pos, Error_Critical, "Intrinsic not supported, '%s'.", intr_name); - token_toggle_end(callee->intrinsic_name); - return Check_Error; - } - - call->intrinsic = intrinsic_table[index].value; - - token_toggle_end(callee->intrinsic_name); - } - - call->va_kind = VA_Kind_Not_VA; - call->type = callee->type->Function.return_type; - if (call->type == &type_auto_return && call->callee->kind != Ast_Kind_Macro) { - YIELD(call->token->pos, "Waiting for auto-return type to be solved."); - } - - OnyxError error; - TypeMatch tm = check_arguments_against_type(&call->args, &callee->type->Function, &call->va_kind, - call->token, get_function_name(callee), &error); - if (tm == TYPE_MATCH_FAILED) { - onyx_submit_error(error); - return Check_Error; - } - - if (tm == TYPE_MATCH_YIELD) YIELD(call->token->pos, "Waiting on argument type checking."); - - call->flags |= Ast_Flag_Has_Been_Checked; - callee->flags |= Ast_Flag_Function_Used; - - if (call->kind == Ast_Kind_Call && call->callee->kind == Ast_Kind_Macro) { - expand_macro(pcall, callee); - return Check_Return_To_Symres; - } - - return Check_Success; -} - -static void report_bad_binaryop(AstBinaryOp* binop) { - onyx_report_error(binop->token->pos, Error_Critical, "Binary operator '%s' not understood for arguments of type '%s' and '%s'.", - binaryop_string[binop->operation], - node_get_type_name(binop->left), - node_get_type_name(binop->right)); -} - -static AstCall* binaryop_try_operator_overload(AstBinaryOp* binop, AstTyped* third_argument) { - if (bh_arr_length(operator_overloads[binop->operation]) == 0) return NULL; - - if (binop->overload_args == NULL) { - binop->overload_args = bh_alloc_item(context.ast_alloc, Arguments); - bh_arr_new(context.ast_alloc, binop->overload_args->values, 3); - bh_arr_set_length(binop->overload_args->values, third_argument ? 3 : 2); - - if (binop_is_assignment(binop->operation)) { - binop->overload_args->values[0] = (AstTyped *) make_address_of(context.ast_alloc, binop->left); - - u32 current_all_checks_are_final = all_checks_are_final; - all_checks_are_final = 0; - u32 current_checking_level_store = current_checking_level; - CheckStatus cs = check_address_of((AstAddressOf **) &binop->overload_args->values[0]); - current_checking_level = current_checking_level_store; - all_checks_are_final = current_all_checks_are_final; - - if (cs == Check_Yield_Macro) return (AstCall *) &node_that_signals_a_yield; - if (cs == Check_Error) return NULL; - - binop->overload_args->values[0] = (AstTyped *) make_argument(context.ast_alloc, binop->overload_args->values[0]); - - } else { - binop->overload_args->values[0] = (AstTyped *) make_argument(context.ast_alloc, binop->left); - } - - - binop->overload_args->values[1] = (AstTyped *) make_argument(context.ast_alloc, binop->right); - if (third_argument != NULL) binop->overload_args->values[2] = (AstTyped *) make_argument(context.ast_alloc, third_argument); - } - - AstTyped* overload = find_matching_overload_by_arguments(operator_overloads[binop->operation], binop->overload_args); - if (overload == NULL || overload == (AstTyped *) &node_that_signals_a_yield) return (AstCall *) overload; - - AstCall* implicit_call = onyx_ast_node_new(context.ast_alloc, sizeof(AstCall), Ast_Kind_Call); - implicit_call->token = binop->token; - implicit_call->callee = overload; - implicit_call->va_kind = VA_Kind_Not_VA; - - arguments_clone(&implicit_call->args, binop->overload_args); - return implicit_call; -} - - -CheckStatus check_binaryop_assignment(AstBinaryOp** pbinop) { - AstBinaryOp* binop = *pbinop; - if (current_checking_level == EXPRESSION_LEVEL) - ERROR(binop->token->pos, "Assignment not valid in expression."); - - if (!is_lval((AstNode *) binop->left)) - ERROR_(binop->left->token->pos, - "Cannot assign to '%b'.", - binop->left->token->text, binop->left->token->length); - - if ((binop->left->flags & Ast_Flag_Const) != 0 && binop->left->type != NULL) - ERROR_(binop->token->pos, - "Cannot assign to constant '%b'.", - binop->left->token->text, binop->left->token->length); - - if (binop->operation == Binary_Op_Assign) { - // NOTE: Raw assignment - - // NOTE: This is the 'type inference' system. Very stupid, but very easy. - // If a left operand has an unknown type, fill it in with the type of - // the right hand side. - if (binop->left->type == NULL) { - if (binop->left->type_node != NULL && binop->left->entity && binop->left->entity->state <= Entity_State_Check_Types) { - YIELD(binop->token->pos, "Waiting for type to be constructed on left hand side."); - } - - // NOTE: There is a subtlety here. You cannot use the result of `resolve_expression_type` directly, - // as in some cases (especially with macros and polyprocs), the result is not "correct". The result - // makes them appears as though they are runtime-known values, which they are not. Using the following - // pattern does prevent this issue. - resolve_expression_type(binop->right); - - Type* right_type = get_expression_type(binop->right); - if (right_type == NULL) { - if (binop->right->entity == NULL || binop->right->entity->state > Entity_State_Check_Types) { - ERROR(binop->token->pos, "Could not resolve type of right hand side to infer."); - - } else { - YIELD(binop->token->pos, "Trying to resolve try of right hand side."); - } - } - - if (right_type->kind == Type_Kind_Compound) { - AstCompound* lhs = (AstCompound *) binop->left; - i32 expr_count = right_type->Compound.count; - if (lhs->kind != Ast_Kind_Compound || bh_arr_length(lhs->exprs) != expr_count) { - ERROR_(binop->token->pos, "Expected left hand side to have %d expressions.", expr_count); - } - - fori (i, 0, expr_count) lhs->exprs[i]->type = right_type->Compound.types[i]; - - lhs->type = type_build_compound_type(context.ast_alloc, lhs); - - } else { - binop->left->type = right_type; - } - } - - } else { - // NOTE: +=, -=, ... - // NOTE: At this point, it is assumed that operator overloads for +=, -=, etc have been tested. - - BinaryOp operation = -1; - if (binop->operation == Binary_Op_Assign_Add) operation = Binary_Op_Add; - else if (binop->operation == Binary_Op_Assign_Minus) operation = Binary_Op_Minus; - else if (binop->operation == Binary_Op_Assign_Multiply) operation = Binary_Op_Multiply; - else if (binop->operation == Binary_Op_Assign_Divide) operation = Binary_Op_Divide; - else if (binop->operation == Binary_Op_Assign_Modulus) operation = Binary_Op_Modulus; - else if (binop->operation == Binary_Op_Assign_And) operation = Binary_Op_And; - else if (binop->operation == Binary_Op_Assign_Or) operation = Binary_Op_Or; - else if (binop->operation == Binary_Op_Assign_Xor) operation = Binary_Op_Xor; - else if (binop->operation == Binary_Op_Assign_Shl) operation = Binary_Op_Shl; - else if (binop->operation == Binary_Op_Assign_Shr) operation = Binary_Op_Shr; - else if (binop->operation == Binary_Op_Assign_Sar) operation = Binary_Op_Sar; - - AstBinaryOp* new_right = make_binary_op(context.ast_alloc, operation, binop->left, binop->right); - binop->right = (AstTyped *) new_right; - new_right->token = binop->token; - binop->operation = Binary_Op_Assign; - - CHECK(binaryop, (AstBinaryOp **) &binop->right); - } - - if (binop->right->type == NULL) { - if (binop->right->entity != NULL && binop->right->entity->state <= Entity_State_Check_Types) { - YIELD(binop->token->pos, "Trying to resolve type of right hand side."); - } - } - - TYPE_CHECK(&binop->right, binop->left->type) { - ERROR_(binop->token->pos, - "Cannot assign value of type '%s' to a '%s'.", - node_get_type_name(binop->right), - node_get_type_name(binop->left)); - } - - binop->type = &basic_types[Basic_Kind_Void]; - - return Check_Success; -} - -static b32 binary_op_is_allowed(BinaryOp operation, Type* type) { - static const u8 binop_allowed[Binary_Op_Count] = { - /* Add */ Basic_Flag_Numeric | Basic_Flag_Pointer, - /* Minus */ Basic_Flag_Numeric | Basic_Flag_Pointer, - /* Multiply */ Basic_Flag_Numeric, - /* Divide */ Basic_Flag_Numeric, - /* Modulus */ Basic_Flag_Integer, - - /* Equal */ Basic_Flag_Equality, - /* Not_Equal */ Basic_Flag_Equality, - /* Less */ Basic_Flag_Ordered, - /* Less_Equal */ Basic_Flag_Ordered, - /* Greater */ Basic_Flag_Ordered, - /* Greater_Equal */ Basic_Flag_Ordered, - - /* And */ Basic_Flag_Integer, - /* Or */ Basic_Flag_Integer, - /* Xor */ Basic_Flag_Integer, - /* Shl */ Basic_Flag_Integer, - /* Shr */ Basic_Flag_Integer, - /* Sar */ Basic_Flag_Integer, - - /* Bool_And */ Basic_Flag_Boolean, - /* Bool_Or */ Basic_Flag_Boolean, - - /* Assign_Start */ 0, - /* Assign */ 0, - /* Assign_Add */ 0, - /* Assign_Minus */ 0, - /* Assign_Multiply */ 0, - /* Assign_Divide */ 0, - /* Assign_Modulus */ 0, - /* Assign_And */ 0, - /* Assign_Or */ 0, - /* Assign_Xor */ 0, - /* Assign_Shl */ 0, - /* Assign_Shr */ 0, - /* Assign_Sar */ 0, - /* Assign_End */ 0, - - /* Pipe */ 0, - /* Range */ 0, - }; - - enum BasicFlag effective_flags = 0; - switch (type->kind) { - case Type_Kind_Basic: effective_flags = type->Basic.flags; break; - case Type_Kind_Pointer: effective_flags = Basic_Flag_Pointer; break; - case Type_Kind_Enum: effective_flags = Basic_Flag_Integer; break; - case Type_Kind_Function: effective_flags = Basic_Flag_Equality; break; - } - - return (binop_allowed[operation] & effective_flags) != 0; -} - -CheckStatus check_binaryop_compare(AstBinaryOp** pbinop) { - AstBinaryOp* binop = *pbinop; - - // HACK: Since ^... to rawptr is a one way conversion, strip any pointers - // away so they can be compared as expected - Type* ltype = binop->left->type; - Type* rtype = binop->right->type; - - if (ltype == NULL) YIELD(binop->token->pos, "Waiting for left-type to be known."); - if (rtype == NULL) YIELD(binop->token->pos, "Waiting for right-type to be known."); - - if (ltype->kind == Type_Kind_Pointer) ltype = &basic_types[Basic_Kind_Rawptr]; - if (rtype->kind == Type_Kind_Pointer) rtype = &basic_types[Basic_Kind_Rawptr]; - - if (!types_are_compatible(ltype, rtype)) { - b32 left_ac = node_is_auto_cast((AstNode *) binop->left); - b32 right_ac = node_is_auto_cast((AstNode *) binop->right); - if (left_ac && right_ac) ERROR(binop->token->pos, "Cannot have auto cast on both sides of binary operator."); - - TYPE_CHECK(&binop->left, rtype) { - TYPE_CHECK(&binop->right, ltype) { - ERROR_(binop->token->pos, - "Cannot compare '%s' to '%s'.", - type_get_name(binop->left->type), - type_get_name(binop->right->type)); - } - } - } - - if (!binary_op_is_allowed(binop->operation, binop->left->type)) { - report_bad_binaryop(binop); - return Check_Error; - } - - binop->type = &basic_types[Basic_Kind_Bool]; - if (binop->flags & Ast_Flag_Comptime) { - // NOTE: Not a binary op - *pbinop = (AstBinaryOp *) ast_reduce(context.ast_alloc, (AstTyped *) binop); - } - - return Check_Success; -} - -CheckStatus check_binaryop_bool(AstBinaryOp** pbinop) { - AstBinaryOp* binop = *pbinop; - - if (!type_is_bool(binop->left->type) || !type_is_bool(binop->right->type)) { - report_bad_binaryop(binop); - return Check_Error; - } - - binop->type = &basic_types[Basic_Kind_Bool]; - - if (binop->flags & Ast_Flag_Comptime) { - // NOTE: Not a binary op - *pbinop = (AstBinaryOp *) ast_reduce(context.ast_alloc, (AstTyped *) binop); - } - return Check_Success; -} - -CheckStatus check_binaryop(AstBinaryOp** pbinop) { - AstBinaryOp* binop = *pbinop; - - if (binop->flags & Ast_Flag_Has_Been_Checked) return Check_Success; - - if (binop->operation == Binary_Op_Assign && binop->left->kind == Ast_Kind_Subscript && bh_arr_length(operator_overloads[Binary_Op_Subscript_Equals]) > 0) { - AstSubscript* sub = (AstSubscript *) binop->left; - - if (binop->potential_substitute == NULL) { - u32 current_checking_level_store = current_checking_level; - CHECK(expression, &sub->addr); - CHECK(expression, &sub->expr); - CHECK(expression, &binop->right); - current_checking_level = current_checking_level_store; - - AstBinaryOp *op = onyx_ast_node_new(context.ast_alloc, sizeof(AstBinaryOp), Ast_Kind_Binary_Op); - op->token = binop->token; - op->operation = Binary_Op_Subscript_Equals; - op->left = ((AstSubscript *) binop->left)->addr; - op->right = ((AstSubscript *) binop->left)->expr; - - binop->potential_substitute = op; - } - - AstCall* call = binaryop_try_operator_overload(binop->potential_substitute, binop->right); - if (call == (AstCall *) &node_that_signals_a_yield) YIELD(binop->token->pos, "Waiting on potential operator overload."); - if (call != NULL) { - call->next = binop->next; - *(AstCall **) pbinop = call; - - CHECK(call, (AstCall **) pbinop); - return Check_Success; - } - - } - - u32 current_checking_level_store = current_checking_level; - CHECK(expression, &binop->left); - CHECK(expression, &binop->right); - current_checking_level = current_checking_level_store; - - // :UnaryFieldAccessIsGross - if (binop->left->kind == Ast_Kind_Unary_Field_Access || binop->right->kind == Ast_Kind_Unary_Field_Access) { - TYPE_CHECK(&binop->left, binop->right->type) { - TYPE_CHECK(&binop->right, binop->left->type) { - // TODO: This should report a better error about the Unary_Field_Access not be able to be resolved given whatever type. - // - brendanfh 2021/12/31 - report_bad_binaryop(binop); - return Check_Error; - } - } - } - - if ((binop->left->flags & Ast_Flag_Comptime) && (binop->right->flags & Ast_Flag_Comptime)) { - binop->flags |= Ast_Flag_Comptime; - } - - if (expression_types_must_be_known) { - if (binop->left->type == NULL || binop->right->type == NULL) { - ERROR(binop->token->pos, "Internal compiler error: one of the operands types is unknown here."); - } - } - - // NOTE: Try operator overloading before checking everything else. - if ((binop->left->type != NULL && (binop->left->type->kind != Type_Kind_Basic || (binop->left->type->Basic.flags & Basic_Flag_SIMD) != 0)) - || (binop->right->type != NULL && (binop->right->type->kind != Type_Kind_Basic || (binop->right->type->Basic.flags & Basic_Flag_SIMD) != 0))) { - AstCall *implicit_call = binaryop_try_operator_overload(binop, NULL); - - if (implicit_call == (AstCall *) &node_that_signals_a_yield) - YIELD(binop->token->pos, "Trying to resolve operator overload."); - - if (implicit_call != NULL) { - // NOTE: Not a binary op - implicit_call->next = binop->next; - *pbinop = (AstBinaryOp *) implicit_call; - - CHECK(call, (AstCall **) pbinop); - return Check_Success; - } - } - - if (binop_is_assignment(binop->operation)) return check_binaryop_assignment(pbinop); - - if (binop->left->type == NULL && binop->left->entity && binop->left->entity->state <= Entity_State_Check_Types) { - YIELD(binop->left->token->pos, "Waiting for this type to be known"); - } - if (binop->right->type == NULL && binop->right->entity && binop->right->entity->state <= Entity_State_Check_Types) { - YIELD(binop->right->token->pos, "Waiting for this type to be known"); - } - - // NOTE: Comparision operators and boolean operators are handled separately. - if (binop_is_compare(binop->operation)) - return check_binaryop_compare(pbinop); - if (binop->operation == Binary_Op_Bool_And || binop->operation == Binary_Op_Bool_Or) - return check_binaryop_bool(pbinop); - - // NOTE: The left side cannot be compound. - // The right side always is numeric. - // The left side cannot be rawptr. - if (type_is_compound(binop->left->type)) goto bad_binaryop; - if (!type_is_numeric(binop->right->type)) goto bad_binaryop; - if (type_is_rawptr(binop->left->type)) { - ERROR(binop->token->pos, "Cannot operate on a 'rawptr'. Cast it to a another pointer type first."); - } - - // NOTE: Handle basic pointer math. - if (type_is_pointer(binop->left->type)) { - if (binop->operation != Binary_Op_Add && binop->operation != Binary_Op_Minus) goto bad_binaryop; - - resolve_expression_type(binop->right); - if (!type_is_integer(binop->right->type)) goto bad_binaryop; - - AstNumLit* numlit = make_int_literal(context.ast_alloc, type_size_of(binop->left->type->Pointer.elem)); - numlit->token = binop->right->token; - numlit->type = binop->right->type; - - AstBinaryOp* binop_node = make_binary_op(context.ast_alloc, Binary_Op_Multiply, binop->right, (AstTyped *) numlit); - binop_node->token = binop->token; - CHECK(binaryop, &binop_node); - - binop->right = (AstTyped *) binop_node; - binop->type = binop->left->type; - binop->right->type = binop->left->type; - } - - if (!types_are_compatible(binop->left->type, binop->right->type)) { - b32 left_ac = node_is_auto_cast((AstNode *) binop->left); - b32 right_ac = node_is_auto_cast((AstNode *) binop->right); - if (left_ac && right_ac) { - ERROR(binop->token->pos, "Cannot have auto cast on both sides of binary operator."); - } - - TYPE_CHECK(&binop->left, binop->right->type) { - TYPE_CHECK(&binop->right, binop->left->type) { - ERROR_(binop->token->pos, - "Mismatched types for binary operation '%s'. left: '%s', right: '%s'.", - binaryop_string[binop->operation], - node_get_type_name(binop->left), - node_get_type_name(binop->right)); - } - } - } - - binop->type = binop->left->type; - if (!binary_op_is_allowed(binop->operation, binop->type)) goto bad_binaryop; - - // NOTE: Enum flags with '&' result in a boolean value - if (binop->type->kind == Type_Kind_Enum && binop->type->Enum.is_flags && binop->operation == Binary_Op_And) { - binop->type = &basic_types[Basic_Kind_Bool]; - } - - if (all_checks_are_final) { - binop->flags |= Ast_Flag_Has_Been_Checked; - - if (binop->flags & Ast_Flag_Comptime) { - // NOTE: Not a binary op - *pbinop = (AstBinaryOp *) ast_reduce(context.ast_alloc, (AstTyped *) binop); - } - } - - return Check_Success; - -bad_binaryop: - report_bad_binaryop(binop); - - return Check_Error; -} - -CheckStatus check_unaryop(AstUnaryOp** punop) { - AstUnaryOp* unaryop = *punop; - - CHECK(expression, &unaryop->expr); - - if (unaryop->operation != Unary_Op_Negate) { - resolve_expression_type(unaryop->expr); - } - - if (unaryop->operation == Unary_Op_Cast) { - char* err; - if (unaryop->type == NULL) - YIELD(unaryop->token->pos, "Trying to resolve destination type for cast."); - - if (!cast_is_legal(unaryop->expr->type, unaryop->type, &err)) { - ERROR_(unaryop->token->pos, "Cast Error: %s", err); - } - - } else { - unaryop->type = unaryop->expr->type; - } - - if (unaryop->operation == Unary_Op_Not) { - if (!type_is_bool(unaryop->expr->type)) { - ERROR_(unaryop->token->pos, - "Bool negation operator expected bool type, got '%s'.", - node_get_type_name(unaryop->expr)); - } - } - - if (unaryop->operation == Unary_Op_Bitwise_Not) { - if (!type_is_integer(unaryop->expr->type)) { - ERROR_(unaryop->token->pos, - "Bitwise operator expected integer type, got '%s'.", - node_get_type_name(unaryop->expr)); - } - } - - if (unaryop->expr->flags & Ast_Flag_Comptime) { - unaryop->flags |= Ast_Flag_Comptime; - // NOTE: Not a unary op - *punop = (AstUnaryOp *) ast_reduce(context.ast_alloc, (AstTyped *) unaryop); - } - - return Check_Success; -} - -CheckStatus check_struct_literal(AstStructLiteral* sl) { - - if (sl->type == NULL) { - // NOTE: This is used for automatically typed struct literals. If there is no provided - // type for the struct literal, assume that it is passes successfully. When it is used - // elsewhere, it will be added as an expression entity that will be processed once the - // stnode is filled out. - if (sl->stnode == NULL) { - CHECK(arguments, &sl->args); - - return Check_Success; - } - - CHECK(expression, &sl->stnode); - if (!node_is_type((AstNode *) sl->stnode)) { - ERROR(sl->token->pos, "Type used for struct literal is not a type."); - } - - sl->type = type_build_from_ast(context.ast_alloc, (AstType *) sl->stnode); - if (sl->type == NULL) - YIELD(sl->token->pos, "Trying to resolve type of struct literal."); - } - - if (!type_is_structlike_strict(sl->type)) { - // - // If there are no given arguments to a structure literal, it is treated as a 'zero-value', - // and can be used to create a completely zeroed value of any type. - if (bh_arr_length(sl->args.values) == 0 && bh_arr_length(sl->args.named_values) == 0) { - AstZeroValue *zv = make_zero_value(context.ast_alloc, sl->token, sl->type); - bh_arr_push(sl->args.values, (AstTyped *) zv); - - sl->flags |= Ast_Flag_Has_Been_Checked; - return Check_Success; - } - - if ((sl->flags & Ast_Flag_Has_Been_Checked) != 0) { - assert(sl->args.values); - assert(sl->args.values[0]); - assert(sl->args.values[0]->kind == Ast_Kind_Zero_Value); - return Check_Success; - } - - // - // Otherwise, it is not possible to construct the type if it is not a structure. - ERROR_(sl->token->pos, - "'%s' is not constructable using a struct literal.", - type_get_name(sl->type)); - } - - i32 mem_count = type_structlike_mem_count(sl->type); - arguments_ensure_length(&sl->args, mem_count); - - // :Idempotency - if ((sl->flags & Ast_Flag_Has_Been_Checked) == 0) { - char* err_msg = NULL; - if (!fill_in_arguments(&sl->args, (AstNode *) sl, &err_msg, 1)) { - onyx_report_error(sl->token->pos, Error_Critical, err_msg); - - bh_arr_each(AstTyped *, value, sl->args.values) { - if (*value == NULL) { - i32 member_idx = value - sl->args.values; // Pointer subtraction hack - StructMember smem; - type_lookup_member_by_idx(sl->type, member_idx, &smem); - - onyx_report_error(sl->token->pos, Error_Critical, - "Value not given for %d%s member, '%s', for type '%s'.", - member_idx + 1, bh_num_suffix(member_idx + 1), - smem.name, type_get_name(sl->type)); - } - } - - return Check_Error; - } - } - sl->flags |= Ast_Flag_Has_Been_Checked; - - AstTyped** actual = sl->args.values; - StructMember smem; - - // BUG: There are problems setting the comptime flag this late in the checking because - // if the struct literal was type inferred, then the literal won't be correctly determined - // to be comptime on the first pass, which is needed for top level expressions. - sl->flags |= Ast_Flag_Comptime; - - fori (i, 0, mem_count) { - // NOTE: Not checking the return on this function because - // this for loop is bounded by the number of members in the - // type. - type_lookup_member_by_idx(sl->type, i, &smem); - Type* formal = smem.type; - - CHECK(expression, actual); - if ((*actual)->type == NULL && (*actual)->entity != NULL && (*actual)->entity->state <= Entity_State_Check_Types) { - YIELD((*actual)->token->pos, "Trying to resolve type of expression for member."); - } - - TYPE_CHECK(actual, formal) { - ERROR_(sl->token->pos, - "Mismatched types for %d%s member named '%s', expected '%s', got '%s'.", - i + 1, bh_num_suffix(i + 1), - smem.name, - type_get_name(formal), - node_get_type_name(*actual)); - } - - sl->flags &= ((*actual)->flags & Ast_Flag_Comptime) | (sl->flags &~ Ast_Flag_Comptime); - actual++; - } - - return Check_Success; -} - -CheckStatus check_array_literal(AstArrayLiteral* al) { - // :Idempotency - if ((al->flags & Ast_Flag_Array_Literal_Typed) == 0) { - if (al->atnode == NULL) return Check_Success; - // YIELD(al->token->pos, "Waiting for array literal type to be known."); - - CHECK(expression, &al->atnode); - if (!node_is_type((AstNode *) al->atnode)) - ERROR(al->token->pos, "Array type is not a type."); - - al->type = type_build_from_ast(context.ast_alloc, (AstType *) al->atnode); - if (al->type == NULL) - YIELD(al->token->pos, "Trying to resolve type of array literal."); - - al->type = type_make_array(context.ast_alloc, al->type, bh_arr_length(al->values)); - if (al->type == NULL || al->type->kind != Type_Kind_Array) - ERROR(al->token->pos, "Expected array type for array literal. This is a compiler bug."); - - al->flags |= Ast_Flag_Array_Literal_Typed; - } - - if (al->type->Array.count != (u32) bh_arr_length(al->values)) { - ERROR_(al->token->pos, "Wrong array size (%d) for number of values (%d).", - al->type->Array.count, bh_arr_length(al->values)); - } - - al->flags |= Ast_Flag_Comptime; - assert(al->type->kind == Type_Kind_Array); - - Type* elem_type = al->type->Array.elem; - bh_arr_each(AstTyped *, expr, al->values) { - CHECK(expression, expr); - - // HACK HACK HACK - if ((*expr)->type == NULL && - (*expr)->entity != NULL && - (*expr)->entity->state <= Entity_State_Check_Types) { - YIELD_(al->token->pos, "Trying to resolve type of %d%s element of array literal.", expr - al->values, bh_num_suffix(expr - al->values)); - } - - al->flags &= ((*expr)->flags & Ast_Flag_Comptime) | (al->flags &~ Ast_Flag_Comptime); - - TYPE_CHECK(expr, elem_type) { - ERROR_((*expr)->token->pos, "Mismatched types for value of in array, expected '%s', got '%s'.", - type_get_name(elem_type), - node_get_type_name(*expr)); - } - } - - return Check_Success; -} - -CheckStatus check_range_literal(AstRangeLiteral** prange) { - AstRangeLiteral* range = *prange; - if (range->flags & Ast_Flag_Has_Been_Checked) return Check_Success; - - CHECK(expression, &range->low); - CHECK(expression, &range->high); - - builtin_range_type_type = type_build_from_ast(context.ast_alloc, builtin_range_type); - if (builtin_range_type_type == NULL) YIELD(range->token->pos, "Waiting for 'range' structure to be built."); - - Type* expected_range_type = builtin_range_type_type; - StructMember smem; - - type_lookup_member(expected_range_type, "low", &smem); - TYPE_CHECK(&range->low, smem.type) { - ERROR_(range->token->pos, - "Expected left side of range to be a 32-bit integer, got '%s'.", - node_get_type_name(range->low)); - } - - type_lookup_member(expected_range_type, "high", &smem); - TYPE_CHECK(&range->high, smem.type) { - ERROR_(range->token->pos, - "Expected right side of range to be a 32-bit integer, got '%s'.", - node_get_type_name(range->high)); - } - - if (range->step == NULL) { - type_lookup_member(expected_range_type, "step", &smem); - assert(smem.initial_value != NULL); - CHECK(expression, smem.initial_value); - - range->step = *smem.initial_value; - } - - range->flags |= Ast_Flag_Has_Been_Checked; - return Check_Success; -} - -CheckStatus check_compound(AstCompound* compound) { - bh_arr_each(AstTyped *, expr, compound->exprs) { - CHECK(expression, expr); - } - - compound->type = type_build_compound_type(context.ast_alloc, compound); - return Check_Success; -} - -CheckStatus check_if_expression(AstIfExpression* if_expr) { - CHECK(expression, &if_expr->cond); - CHECK(expression, &if_expr->true_expr); - CHECK(expression, &if_expr->false_expr); - - TYPE_CHECK(&if_expr->cond, &basic_types[Basic_Kind_Bool]) { - ERROR_(if_expr->token->pos, "If-expression expected boolean for condition, got '%s'.", - type_get_name(if_expr->cond->type)); - } - - resolve_expression_type((AstTyped *) if_expr); - - if (!types_are_compatible(if_expr->true_expr->type, if_expr->false_expr->type)) { - ERROR_(if_expr->token->pos, "Mismatched types for if-expression, left side is '%s', and right side is '%s'.", - type_get_name(if_expr->true_expr->type), type_get_name(if_expr->false_expr->type)); - } - - return Check_Success; -} - -CheckStatus check_do_block(AstDoBlock** pdoblock) { - AstDoBlock* doblock = *pdoblock; - if (doblock->flags & Ast_Flag_Has_Been_Checked) return Check_Success; - - fill_in_type((AstTyped *) doblock); - - Type** old_expected_return_type = expected_return_type; - expected_return_type = &doblock->type; - - doblock->block->rules = Block_Rule_Do_Block; - CHECK(block, doblock->block); - - if (doblock->type == &type_auto_return) doblock->type = &basic_types[Basic_Kind_Void]; - - expected_return_type = old_expected_return_type; - doblock->flags |= Ast_Flag_Has_Been_Checked; - return Check_Success; -} - -CheckStatus check_address_of(AstAddressOf** paof) { - AstAddressOf* aof = *paof; - - AstTyped* expr = (AstTyped *) strip_aliases((AstNode *) aof->expr); - if (expr->kind == Ast_Kind_Subscript && bh_arr_length(operator_overloads[Binary_Op_Ptr_Subscript]) > 0) { - if (aof->potential_substitute == NULL) { - CHECK(expression, &((AstSubscript *) expr)->addr); - CHECK(expression, &((AstSubscript *) expr)->expr); - - AstBinaryOp *op = onyx_ast_node_new(context.ast_alloc, sizeof(AstBinaryOp), Ast_Kind_Binary_Op); - op->operation = Binary_Op_Ptr_Subscript; - op->left = ((AstSubscript *) expr)->addr; - op->right = ((AstSubscript *) expr)->expr; - op->token = aof->token; - - aof->potential_substitute = op; - } - - AstCall* call = binaryop_try_operator_overload(aof->potential_substitute, NULL); - if (call == (AstCall *) &node_that_signals_a_yield) YIELD(aof->token->pos, "Waiting for operator overload to possibly resolve."); - if (call != NULL) { - call->next = aof->next; - *(AstCall **) paof = call; - - CHECK(call, (AstCall **) paof); - return Check_Success; - } - } - - CHECK(expression, &aof->expr); - if (node_is_addressable_literal((AstNode *) aof->expr)) { - resolve_expression_type(aof->expr); - } - - if (aof->expr->type == NULL) { - YIELD(aof->token->pos, "Trying to resolve type of expression to take a reference."); - } - - expr = (AstTyped *) strip_aliases((AstNode *) aof->expr); - if (node_is_type((AstNode *) expr)) { - AstPointerType *pt = onyx_ast_node_new(context.ast_alloc, sizeof(AstPointerType), Ast_Kind_Pointer_Type); - pt->token = aof->token; - pt->elem = (AstType *) expr; - pt->__unused = aof->next; - *paof = (AstAddressOf *) pt; - CHECK(type, (AstType **) &pt); - return Check_Success; - } - - if ((expr->kind != Ast_Kind_Subscript - && expr->kind != Ast_Kind_Dereference - && expr->kind != Ast_Kind_Field_Access - && expr->kind != Ast_Kind_Memres - && expr->kind != Ast_Kind_Local - && expr->kind != Ast_Kind_Constraint_Sentinel - && !node_is_addressable_literal((AstNode *) expr)) - || (expr->flags & Ast_Flag_Cannot_Take_Addr) != 0) { - - if (aof->can_be_removed) { - *(AstTyped **) paof = aof->expr; - return Check_Yield_Macro; - } - - ERROR_(aof->token->pos, "Cannot take the address of something that is not an l-value. %s", onyx_ast_node_kind_string(expr->kind)); - } - - expr->flags |= Ast_Flag_Address_Taken; - - aof->type = type_make_pointer(context.ast_alloc, expr->type); - - return Check_Success; -} - -CheckStatus check_dereference(AstDereference* deref) { - CHECK(expression, &deref->expr); - - if (!type_is_pointer(deref->expr->type)) - ERROR(deref->token->pos, "Cannot dereference non-pointer value."); - - if (deref->expr->type == basic_type_rawptr.basic_type) - ERROR(deref->token->pos, "Cannot dereference 'rawptr'. Cast to another pointer type first."); - - deref->type = deref->expr->type->Pointer.elem; - - return Check_Success; -} - -CheckStatus check_subscript(AstSubscript** psub) { - AstSubscript* sub = *psub; - CHECK(expression, &sub->addr); - CHECK(expression, &sub->expr); - - if (sub->addr->type == NULL) YIELD(sub->token->pos, "Waiting to know type of left-hand side of subscript."); - - // NOTE: Try operator overloading before checking everything else. - if (sub->expr->type != NULL && - (sub->addr->type->kind != Type_Kind_Basic || sub->expr->type->kind != Type_Kind_Basic)) { - // AstSubscript is the same as AstBinaryOp for the first sizeof(AstBinaryOp) bytes - AstBinaryOp* binop = (AstBinaryOp *) sub; - AstCall *implicit_call = binaryop_try_operator_overload(binop, NULL); - - if (implicit_call == (AstCall *) &node_that_signals_a_yield) - YIELD(sub->token->pos, "Trying to resolve operator overload."); - - if (implicit_call != NULL) { - // NOTE: Not an array access - implicit_call->next = sub->next; - *psub = (AstSubscript *) implicit_call; - - CHECK(call, (AstCall **) psub); - return Check_Success; - } - } - - if (!type_is_array_accessible(sub->addr->type)) { - report_bad_binaryop((AstBinaryOp *) sub); - return Check_Error; - } - - if (sub->addr->type->kind == Type_Kind_Slice || sub->addr->type->kind == Type_Kind_DynArray || sub->addr->type->kind == Type_Kind_VarArgs) { - // If we are accessing on a slice or a dynamic array, implicitly add a field access for the data member - StructMember smem; - type_lookup_member(sub->addr->type, "data", &smem); - - AstFieldAccess* fa = make_field_access(context.ast_alloc, sub->addr, "data"); - fa->type = smem.type; - fa->offset = smem.offset; - fa->idx = smem.idx; - - sub->addr = (AstTyped *) fa; - } - - if (types_are_compatible(sub->expr->type, builtin_range_type_type)) { - Type *of = type_get_contained_type(sub->addr->type); - if (of == NULL) { - // FIXME: Slice creation should be allowed for slice types and dynamic array types, like it - // is below, but this code doesn't look at that. - report_bad_binaryop((AstBinaryOp *) sub); - ERROR(sub->token->pos, "Invalid type for left of slice creation."); - } - - sub->kind = Ast_Kind_Slice; - sub->type = type_make_slice(context.ast_alloc, of); - sub->elem_size = type_size_of(of); - - return Check_Success; - } - - resolve_expression_type(sub->expr); - if (!type_is_small_integer(sub->expr->type)) { - report_bad_binaryop((AstBinaryOp *) sub); - ERROR_(sub->token->pos, "Expected small integer type for index, got '%s'.", node_get_type_name(sub->expr)); - } - - sub->type = type_get_contained_type(sub->addr->type); - if (sub->type == NULL) { - report_bad_binaryop((AstBinaryOp *) sub); - ERROR(sub->token->pos, "Invalid type for left of array access."); - } - - sub->elem_size = type_size_of(sub->type); - return Check_Success; -} - -CheckStatus check_field_access(AstFieldAccess** pfield) { - AstFieldAccess* field = *pfield; - if (field->flags & Ast_Flag_Has_Been_Checked) return Check_Success; - - CHECK(expression, &field->expr); - if (field->expr->type == NULL) { - YIELD(field->token->pos, "Trying to resolve type of source expression."); - } - - if (!type_is_structlike(field->expr->type)) { - ERROR_(field->token->pos, - "Cannot access field '%b' on '%s'. Type is not a struct.", - field->token->text, - field->token->length, - node_get_type_name(field->expr)); - } - - // Optimization for (*foo).member. - if (field->expr->kind == Ast_Kind_Dereference) { - field->expr = ((AstDereference *) field->expr)->expr; - } - - if (field->token != NULL && field->field == NULL) { - token_toggle_end(field->token); - field->field = bh_strdup(context.ast_alloc, field->token->text); - token_toggle_end(field->token); - } - - if (field->expr->type->kind == Type_Kind_Struct) { - if (field->expr->type->Struct.status != SPS_Uses_Done) { - YIELD(field->token->pos, "Waiting for struct type to be completed before looking up members."); - } - } - - StructMember smem; - if (!type_lookup_member(field->expr->type, field->field, &smem)) { - if (field->expr->type->kind == Type_Kind_Array) { - if (!strcmp(field->field, "count")) { - *pfield = (AstFieldAccess *) make_int_literal(context.ast_alloc, field->expr->type->Array.count); - return Check_Success; - } - } - - AstNode* n = try_symbol_raw_resolve_from_type(field->expr->type, field->field); - - AstType* type_node = field->expr->type->ast_type; - if (!n) n = try_symbol_raw_resolve_from_node((AstNode *) type_node, field->field); - - if (n) { - *pfield = (AstFieldAccess *) n; - return Check_Success; - } - - char* closest = find_closest_symbol_in_node((AstNode *) type_node, field->field); - if (closest) { - ERROR_(field->token->pos, "Field '%s' does not exists on '%s'. Did you mean '%s'?", field->field, node_get_type_name(field->expr), closest); - } else { - ERROR_(field->token->pos, "Field '%s' does not exists on '%s'.", field->field, node_get_type_name(field->expr)); - } - } - - // NOTE: If this member was included into the structure through a "use x: ^T" kind of statement, - // then we have to insert a intermediate field access in order to access the correct member. - if (smem.use_through_pointer_index >= 0) { - StructMember containing_member; - assert(type_lookup_member_by_idx(field->expr->type, smem.use_through_pointer_index, &containing_member)); - - AstFieldAccess *new_access = onyx_ast_node_new(context.ast_alloc, sizeof(AstFieldAccess), Ast_Kind_Field_Access); - new_access->token = field->token; - new_access->offset = containing_member.offset; - new_access->idx = containing_member.idx; - new_access->type = containing_member.type; - new_access->expr = field->expr; - new_access->flags |= Ast_Flag_Has_Been_Checked; - - field->expr = (AstTyped *) new_access; - } - - field->offset = smem.offset; - field->idx = smem.idx; - field->type = smem.type; - field->flags |= Ast_Flag_Has_Been_Checked; - return Check_Success; -} - -CheckStatus check_method_call(AstBinaryOp** pmcall) { - AstBinaryOp* mcall = *pmcall; - CHECK(expression, &mcall->left); - if (mcall->left->type == NULL) YIELD(mcall->token->pos, "Trying to resolve type of left hand side."); - - mcall->type = mcall->left->type; - AstTyped* implicit_argument = mcall->left; - - // Symbol resolution should have ensured that this is call node. - AstCall* call_node = (AstCall *) mcall->right; - assert(call_node->kind == Ast_Kind_Call); - - // :Idempotency - if ((mcall->flags & Ast_Flag_Has_Been_Checked) == 0) { - // Implicitly take the address of the value if it is not already a pointer type. - // This could be weird to think about semantically so some testing with real code - // would be good. - brendanfh 2020/02/05 - if (implicit_argument->type->kind != Type_Kind_Pointer) { - AstAddressOf *address_of = make_address_of(context.ast_alloc, implicit_argument); - address_of->can_be_removed = 1; - implicit_argument = (AstTyped *) address_of; - } - - implicit_argument = (AstTyped *) make_argument(context.ast_alloc, implicit_argument); - - bh_arr_insertn(call_node->args.values, 0, 1); - call_node->args.values[0] = implicit_argument; - - *pmcall = (AstBinaryOp *) mcall->right; - mcall->right->next = mcall->next; - } - mcall->flags |= Ast_Flag_Has_Been_Checked; - - CHECK(call, (AstCall **) pmcall); - return Check_Success; -} - -CheckStatus check_size_of(AstSizeOf* so) { - CHECK(type, &so->so_ast_type); - - so->so_type = type_build_from_ast(context.ast_alloc, so->so_ast_type); - if (so->so_type == NULL) - YIELD(so->token->pos, "Trying to resolve type to take the size of."); - - so->size = type_size_of(so->so_type); - so->flags |= Ast_Flag_Comptime; - - return Check_Success; -} - -CheckStatus check_align_of(AstAlignOf* ao) { - CHECK(type, &ao->ao_ast_type); - - ao->ao_type = type_build_from_ast(context.ast_alloc, ao->ao_ast_type); - if (ao->ao_type == NULL) - YIELD(ao->token->pos, "Trying to resolve type to take the alignment of."); - - ao->alignment = type_alignment_of(ao->ao_type); - ao->flags |= Ast_Flag_Comptime; - - return Check_Success; -} - -CheckStatus check_expression(AstTyped** pexpr) { - AstTyped* expr = *pexpr; - if (expr->kind > Ast_Kind_Type_Start && expr->kind < Ast_Kind_Type_End) { - // This is to ensure that the type will exist when compiling. For example, a poly-call type - // would have to wait for the entity to pass through, which the code generation does not know - // about. - CHECK(type, (AstType **) pexpr); - expr = *pexpr; - - // Don't try to construct a polystruct ahead of time because you can't. - if (expr->kind != Ast_Kind_Poly_Struct_Type) { - if (type_build_from_ast(context.ast_alloc, (AstType*) expr) == NULL) { - YIELD(expr->token->pos, "Trying to construct type."); - } - } else { - type_build_from_ast(context.ast_alloc, (AstType*) expr); - } - - expr->type = &basic_types[Basic_Kind_Type_Index]; - return Check_Success; - } - - if (expr->kind == Ast_Kind_Polymorphic_Proc) { - // polymorphic procedures do not need to be checked. Their concrete instantiations - // will be checked when they are created. - return Check_Success; - } - - if (expr->kind == Ast_Kind_Macro) { - return Check_Success; - } - - if (expr->kind == Ast_Kind_Directive_Init) { - ERROR(expr->token->pos, "#init declarations are not in normal expressions, only in #after clauses."); - } - - fill_in_type(expr); - current_checking_level = EXPRESSION_LEVEL; - - CheckStatus retval = Check_Success; - switch (expr->kind) { - case Ast_Kind_Binary_Op: retval = check_binaryop((AstBinaryOp **) pexpr); break; - case Ast_Kind_Unary_Op: retval = check_unaryop((AstUnaryOp **) pexpr); break; - - case Ast_Kind_Intrinsic_Call: - case Ast_Kind_Call: retval = check_call((AstCall **) pexpr); break; - case Ast_Kind_Argument: retval = check_argument((AstArgument **) pexpr); break; - case Ast_Kind_Block: retval = check_block((AstBlock *) expr); break; - - case Ast_Kind_Symbol: - YIELD_(expr->token->pos, "Waiting to resolve symbol, '%b'.", expr->token->text, expr->token->length); - break; - - case Ast_Kind_Param: - if (expr->type == NULL) { - YIELD(expr->token->pos, "Waiting on parameter type."); - } - break; - - case Ast_Kind_Local: break; - - case Ast_Kind_Address_Of: retval = check_address_of((AstAddressOf **) pexpr); break; - case Ast_Kind_Dereference: retval = check_dereference((AstDereference *) expr); break; - case Ast_Kind_Slice: - case Ast_Kind_Subscript: retval = check_subscript((AstSubscript **) pexpr); break; - case Ast_Kind_Field_Access: retval = check_field_access((AstFieldAccess **) pexpr); break; - case Ast_Kind_Method_Call: retval = check_method_call((AstBinaryOp **) pexpr); break; - case Ast_Kind_Size_Of: retval = check_size_of((AstSizeOf *) expr); break; - case Ast_Kind_Align_Of: retval = check_align_of((AstAlignOf *) expr); break; - case Ast_Kind_Range_Literal: retval = check_range_literal((AstRangeLiteral **) pexpr); break; - - case Ast_Kind_Global: - if (expr->type == NULL) { - onyx_report_error(expr->token->pos, Error_Critical, "Global with unknown type."); - retval = Check_Error; - } - break; - - case Ast_Kind_NumLit: - assert(expr->type != NULL); - break; - - case Ast_Kind_Struct_Literal: - retval = check_struct_literal((AstStructLiteral *) expr); - break; - - case Ast_Kind_Array_Literal: - retval = check_array_literal((AstArrayLiteral *) expr); - break; - - case Ast_Kind_Function: - // NOTE: Will need something like this at some point - // AstFunction* func = (AstFunction *) expr; - // bh_arr_each(AstParam, param, func->params) { - // if (param->default_value != NULL) { - // onyx_message_add(Msg_Type_Literal, - // func->token->pos, - // "cannot use functions with default parameters in this way"); - // retval = 1; - // break; - // } - // } - if (expr->type == NULL) - YIELD(expr->token->pos, "Waiting for function type to be resolved."); - - expr->flags |= Ast_Flag_Function_Used; - break; - - case Ast_Kind_Directive_Solidify: - CHECK(directive_solidify, (AstDirectiveSolidify **) pexpr); - break; - - case Ast_Kind_Directive_Defined: - *pexpr = (AstTyped *) make_bool_literal(context.ast_alloc, ((AstDirectiveDefined *) expr)->is_defined); - fill_in_type(*pexpr); - break; - - case Ast_Kind_Compound: - CHECK(compound, (AstCompound *) expr); - break; - - case Ast_Kind_Call_Site: - // NOTE: This has to be set here because if it were to be set in the parser, - // builtin_callsite_type wouldn't be known when parsing the builtin.onyx file. - expr->type_node = builtin_callsite_type; - break; - - case Ast_Kind_If_Expression: - CHECK(if_expression, (AstIfExpression *) expr); - break; - - case Ast_Kind_Alias: - CHECK(expression, &((AstAlias *) expr)->alias); - expr->flags |= (((AstAlias *) expr)->alias->flags & Ast_Flag_Comptime); - expr->type = ((AstAlias *) expr)->alias->type; - break; - - case Ast_Kind_Directive_Insert: - retval = check_insert_directive((AstDirectiveInsert **) pexpr); - break; - - case Ast_Kind_Code_Block: - expr->flags |= Ast_Flag_Comptime; - fill_in_type(expr); - break; - - case Ast_Kind_Do_Block: - retval = check_do_block((AstDoBlock **) pexpr); - break; - - case Ast_Kind_Memres: - if (expr->type == NULL) YIELD(expr->token->pos, "Waiting to know globals type."); - break; - - case Ast_Kind_StrLit: break; - case Ast_Kind_File_Contents: break; - case Ast_Kind_Overloaded_Function: break; - case Ast_Kind_Enum_Value: break; - case Ast_Kind_Polymorphic_Proc: break; - case Ast_Kind_Package: break; - case Ast_Kind_Error: break; - case Ast_Kind_Unary_Field_Access: break; - case Ast_Kind_Constraint_Sentinel: break; - case Ast_Kind_Switch_Case: break; - case Ast_Kind_Foreign_Block: break; - case Ast_Kind_Zero_Value: break; - - default: - retval = Check_Error; - onyx_report_error(expr->token->pos, Error_Critical, "UNEXPECTED INTERNAL COMPILER ERROR"); - DEBUG_HERE; - break; - } - - return retval; -} - -CheckStatus check_global(AstGlobal* global) { - fill_in_type((AstTyped *) global); - - if (global->type == NULL) { - YIELD(global->token->pos, "Trying to resolve type for global."); - } - - return Check_Success; -} - -CheckStatus check_insert_directive(AstDirectiveInsert** pinsert) { - AstDirectiveInsert* insert = *pinsert; - if (insert->flags & Ast_Flag_Has_Been_Checked) return Check_Success; - - CHECK(expression, &insert->code_expr); - if (insert->code_expr->type == NULL) { - if (insert->code_expr->entity && insert->code_expr->entity->state >= Entity_State_Code_Gen) { - ERROR(insert->token->pos, "Expected expression of type 'Code'."); - } - - // Bad wording for the message. - YIELD(insert->token->pos, "Waiting for resolution to code expression type."); - } - - Type* code_type = type_build_from_ast(context.ast_alloc, builtin_code_type); - - TYPE_CHECK(&insert->code_expr, code_type) { - ERROR_(insert->token->pos, "#unquote expected a value of type 'Code', got '%s'.", - type_get_name(insert->code_expr->type)); - } - - AstCodeBlock* code_block = (AstCodeBlock *) insert->code_expr; - code_block = (AstCodeBlock *) strip_aliases((AstNode *) code_block); - - assert(code_block->kind == Ast_Kind_Code_Block); - - AstNode* cloned_block = ast_clone(context.ast_alloc, code_block->code); - cloned_block->next = insert->next; - *(AstNode **) pinsert = cloned_block; - - insert->flags |= Ast_Flag_Has_Been_Checked; - - return Check_Return_To_Symres; -} - -CheckStatus check_directive_solidify(AstDirectiveSolidify** psolid) { - AstDirectiveSolidify* solid = *psolid; - - bh_arr_each(AstPolySolution, sln, solid->known_polyvars) { - CHECK(expression, &sln->value); - - if (node_is_type((AstNode *) sln->value)) { - sln->type = type_build_from_ast(context.ast_alloc, sln->ast_type); - sln->kind = PSK_Type; - } else { - sln->kind = PSK_Value; - } - } - - solid->resolved_proc = polymorphic_proc_try_solidify(solid->poly_proc, solid->known_polyvars, solid->token); - if (solid->resolved_proc == (AstNode *) &node_that_signals_a_yield) { - solid->resolved_proc = NULL; - YIELD(solid->token->pos, "Waiting for partially solidified procedure."); - } - - // NOTE: Not a DirectiveSolidify. - *psolid = (AstDirectiveSolidify *) solid->resolved_proc; - - return Check_Success; -} - -CheckStatus check_remove_directive(AstDirectiveRemove *remove) { - if (!inside_for_iterator) { - ERROR(remove->token->pos, "#remove is only allowed in the body of a for-loop over an iterator."); - } - - return Check_Success; -} - -CheckStatus check_statement(AstNode** pstmt) { - AstNode* stmt = *pstmt; - - current_checking_level = STATEMENT_LEVEL; - - switch (stmt->kind) { - case Ast_Kind_Jump: return Check_Success; - - case Ast_Kind_Return: return check_return((AstReturn *) stmt); - case Ast_Kind_If: return check_if((AstIfWhile *) stmt); - case Ast_Kind_Static_If: return check_if((AstIfWhile *) stmt); - case Ast_Kind_While: return check_while((AstIfWhile *) stmt); - case Ast_Kind_For: return check_for((AstFor *) stmt); - case Ast_Kind_Switch: return check_switch((AstSwitch *) stmt); - case Ast_Kind_Block: return check_block((AstBlock *) stmt); - case Ast_Kind_Defer: return check_statement(&((AstDefer *) stmt)->stmt); - case Ast_Kind_Directive_Remove: return check_remove_directive((AstDirectiveRemove *) stmt); - case Ast_Kind_Call: { - CHECK(call, (AstCall **) pstmt); - (*pstmt)->flags |= Ast_Flag_Expr_Ignored; - return Check_Success; - } - - case Ast_Kind_Binary_Op: - CHECK(binaryop, (AstBinaryOp **) pstmt); - (*pstmt)->flags |= Ast_Flag_Expr_Ignored; - return Check_Success; - - // NOTE: Local variable declarations used to be removed after the symbol - // resolution phase because long long ago, all locals needed to be known - // in a block in order to efficiently allocate enough space and registers - // for them all. Now with LocalAllocator, this is no longer necessary. - // Therefore, locals stay in the tree and need to be passed along. - case Ast_Kind_Local: { - AstTyped* typed_stmt = (AstTyped *) stmt; - fill_in_type(typed_stmt); - if (typed_stmt->type_node != NULL && typed_stmt->type == NULL) { - CHECK(type, &typed_stmt->type_node); - - if (!node_is_type((AstNode *) typed_stmt->type_node)) { - ERROR(stmt->token->pos, "Local's type is not a type."); - } - - YIELD(typed_stmt->token->pos, "Waiting for local variable's type."); - } - - if (typed_stmt->next != NULL && typed_stmt->next->kind == Ast_Kind_Binary_Op) { - AstBinaryOp *next = (AstBinaryOp *) typed_stmt->next; - if (next->operation == Binary_Op_Assign && next->left == typed_stmt) { - typed_stmt->flags |= Ast_Flag_Decl_Followed_By_Init; - } - } - return Check_Success; - } - - default: - CHECK(expression, (AstTyped **) pstmt); - (*pstmt)->flags |= Ast_Flag_Expr_Ignored; - return Check_Success; - } -} - -CheckStatus check_statement_chain(AstNode** start) { - while (*start) { - CHECK(statement, start); - start = &(*start)->next; - } - - return Check_Success; -} - -CheckStatus check_block(AstBlock* block) { - // This used to use statement_chain, but since block optimize which statements need to be rechecked, - // it has to be its own thing. - - AstNode** start = &block->body; - fori (i, 0, block->statement_idx) { - start = &(*start)->next; - } - - while (*start) { - CheckStatus cs = check_statement(start); - switch (cs) { - case Check_Success: - start = &(*start)->next; - block->statement_idx++; - break; - - case Check_Return_To_Symres: - block->statement_idx = 0; - - default: - return cs; - } - - } - - return Check_Success; -} - -CheckStatus check_function(AstFunction* func) { - if (func->flags & Ast_Flag_Has_Been_Checked) return Check_Success; - if (func->entity_header && func->entity_header->state < Entity_State_Code_Gen) - YIELD(func->token->pos, "Waiting for procedure header to pass type-checking"); - - bh_arr_each(AstTyped *, pexpr, func->tags) { - CHECK(expression, pexpr); - - if (((*pexpr)->flags & Ast_Flag_Comptime) == 0) { - ERROR((*pexpr)->token->pos, "#tag expressions should be compile time known."); - } - } - - inside_for_iterator = 0; - expected_return_type = &func->type->Function.return_type; - if (func->body) { - CheckStatus status = check_block(func->body); - if (status == Check_Error && func->generated_from && context.cycle_detected == 0) - ERROR(func->generated_from->pos, "Error in polymorphic procedure generated from this location."); - - if (status != Check_Success) { - expected_return_type = NULL; - return status; - } - } - - if (*expected_return_type == &type_auto_return) { - *expected_return_type = &basic_types[Basic_Kind_Void]; - } - - func->flags |= Ast_Flag_Has_Been_Checked; - return Check_Success; -} - -CheckStatus check_overloaded_function(AstOverloadedFunction* func) { - b32 done = 1; - - bh_imap all_overloads; - bh_imap_init(&all_overloads, global_heap_allocator, 4); - build_all_overload_options(func->overloads, &all_overloads); - - bh_arr_each(bh__imap_entry, entry, all_overloads.entries) { - AstTyped* node = (AstTyped *) entry->key; - if (node->kind == Ast_Kind_Overloaded_Function) continue; - - if ( node->kind != Ast_Kind_Function - && node->kind != Ast_Kind_Polymorphic_Proc - && node->kind != Ast_Kind_Macro) { - onyx_report_error(node->token->pos, Error_Critical, "Overload option not procedure or macro. Got '%s'", - onyx_ast_node_kind_string(node->kind)); - - bh_imap_free(&all_overloads); - return Check_Error; - } - - if (node->kind == Ast_Kind_Function) { - AstFunction* func = (AstFunction *) node; - - if (func->entity_header && func->entity_header->state <= Entity_State_Check_Types) { - done = 0; - } - } - } - - bh_imap_free(&all_overloads); - - if (done) return Check_Success; - else YIELD(func->token->pos, "Waiting for all options to pass type-checking."); -} - -CheckStatus check_struct(AstStructType* s_node) { - if (s_node->entity_defaults && s_node->entity_defaults->state < Entity_State_Check_Types) - YIELD(s_node->token->pos, "Waiting for struct member defaults to pass symbol resolution."); - - if (s_node->min_size_) CHECK(expression, &s_node->min_size_); - if (s_node->min_alignment_) CHECK(expression, &s_node->min_alignment_); - - if (s_node->polymorphic_argument_types) { - assert(s_node->polymorphic_arguments); - - fori (i, 0, (i64) bh_arr_length(s_node->polymorphic_argument_types)) { - Type *arg_type = type_build_from_ast(context.ast_alloc, s_node->polymorphic_argument_types[i]); - if (arg_type == NULL) YIELD(s_node->polymorphic_argument_types[i]->token->pos, "Waiting to build type for polymorph argument."); - - // CLEANUP: This might be wrong... - if (s_node->polymorphic_arguments[i].value) { - TYPE_CHECK(&s_node->polymorphic_arguments[i].value, arg_type) { - ERROR_(s_node->polymorphic_arguments[i].value->token->pos, "Expected value of type %s, got %s.", - type_get_name(arg_type), - type_get_name(s_node->polymorphic_arguments[i].value->type)); - } - } - } - } - - if (s_node->constraints.constraints) { - s_node->constraints.produce_errors = (s_node->flags & Ast_Flag_Header_Check_No_Error) == 0; - - OnyxFilePos pos = s_node->token->pos; - if (s_node->polymorphic_error_loc.filename) { - pos = s_node->polymorphic_error_loc; - } - CHECK(constraint_context, &s_node->constraints, s_node->scope, pos); - } - - // - // This ensures that all procedures defined inside of a structure are - // not pruned and omitted from the binary. This is because a large - // use-case of procedures in structures is dynamically linking them - // using the type info data. - if (s_node->scope) { - fori (i, 0, shlen(s_node->scope->symbols)) { - AstNode* node = s_node->scope->symbols[i].value; - if (node->kind == Ast_Kind_Function) { - node->flags |= Ast_Flag_Function_Used; - } - } - } - - bh_arr_each(AstStructMember *, smem, s_node->members) { - if ((*smem)->type_node != NULL) { - CHECK(type, &(*smem)->type_node); - } - - if ((*smem)->type_node == NULL && (*smem)->initial_value != NULL) { - CHECK(expression, &(*smem)->initial_value); - - fill_in_type((*smem)->initial_value); - if ((*smem)->initial_value->type == NULL) - YIELD((*smem)->initial_value->token->pos, "Trying to resolve type for initial value for member."); - - resolve_expression_type((*smem)->initial_value); - if ((*smem)->type == NULL) (*smem)->type = (*smem)->initial_value->type; - - if ((*smem)->type == NULL) { - ERROR((*smem)->initial_value->token->pos, "Unable to deduce type of initial value. This is probably a compiler bug."); - } - } - } - - // NOTE: fills in the pending_type. - s_node->ready_to_build_type = 1; - type_build_from_ast(context.ast_alloc, (AstType *) s_node); - if (s_node->pending_type == NULL || !s_node->pending_type_is_valid) - YIELD(s_node->token->pos, "Waiting for type to be constructed."); - - bh_arr_each(StructMember *, smem, s_node->pending_type->Struct.memarr) { - if ((*smem)->type->kind == Type_Kind_Compound) { - ERROR(s_node->token->pos, "Compound types are not allowed as struct member types."); - } - - if ((*smem)->used) { - if (!type_struct_member_apply_use(context.ast_alloc, s_node->pending_type, *smem)) { - YIELD((*smem)->token->pos, "Waiting for use to be applied."); - } - } - } - - s_node->stcache = s_node->pending_type; - s_node->stcache->Struct.status = SPS_Uses_Done; - - return Check_Success; -} - -CheckStatus check_struct_defaults(AstStructType* s_node) { - if (s_node->entity_type && s_node->entity_type->state < Entity_State_Code_Gen) - YIELD(s_node->token->pos, "Waiting for struct type to be constructed before checking defaulted members."); - if (s_node->entity_type && s_node->entity_type->state == Entity_State_Failed) - return Check_Failed; - - if (s_node->meta_tags) { - bh_arr_each(AstTyped *, meta, s_node->meta_tags) { - CHECK(expression, meta); - resolve_expression_type(*meta); - - if (((*meta)->flags & Ast_Flag_Comptime) == 0) { - onyx_report_error((*meta)->token->pos, Error_Critical, "#tag expressions are expected to be compile-time known."); - return Check_Error; - } - } - } - - bh_arr_each(StructMember *, smem, s_node->stcache->Struct.memarr) { - if ((*smem)->initial_value && *(*smem)->initial_value) { - CHECK(expression, (*smem)->initial_value); - - TYPE_CHECK((*smem)->initial_value, (*smem)->type) { - ERROR_((*(*smem)->initial_value)->token->pos, - "Mismatched type for initial value, expected '%s', got '%s'.", - type_get_name((*smem)->type), - type_get_name((*(*smem)->initial_value)->type)); - } - - resolve_expression_type(*(*smem)->initial_value); - } - - if ((*smem)->meta_tags) { - bh_arr_each(AstTyped *, meta, (*smem)->meta_tags) { - CHECK(expression, meta); - resolve_expression_type(*meta); - - if (((*meta)->flags & Ast_Flag_Comptime) == 0) { - onyx_report_error((*meta)->token->pos, Error_Critical, "#tag expressions are expected to be compile-time known."); - return Check_Error; - } - } - } - } - - return Check_Success; -} - -CheckStatus check_temp_function_header(AstFunction* func) { - CheckStatus cs = check_function_header(func); - if (cs == Check_Error) { - if (func->flags & Ast_Flag_Header_Check_No_Error) { - onyx_clear_errors(); - } - - return Check_Failed; - } - - if (cs != Check_Success) return cs; - - return Check_Complete; -} - -CheckStatus check_function_header(AstFunction* func) { - //if (func->entity_body && func->entity_body->state < Entity_State_Check_Types) - // YIELD(func->token->pos, "Waiting for function body to complete symbol resolution to check header."); - - b32 expect_default_param = 0; - b32 has_had_varargs = 0; - - if (func->constraints.constraints != NULL && func->constraints.constraints_met == 0) { - func->constraints.produce_errors = (func->flags & Ast_Flag_Header_Check_No_Error) == 0; - CHECK(constraint_context, &func->constraints, func->scope, func->token->pos); - - // All constraints have been met. Return to symbol resolution to finish - // looking up all symbols in the function. - return Check_Return_To_Symres; - } - - bh_arr_each(AstParam, param, func->params) { - AstLocal* local = param->local; - - if (expect_default_param && param->default_value == NULL) { - ERROR(local->token->pos, - "All parameters must have default values after the first default valued parameter."); - } - - if (has_had_varargs && param->vararg_kind != VA_Kind_Not_VA) { - ERROR(local->token->pos, - "Can only have one param that is of variable argument type."); - } - - if (has_had_varargs && param->vararg_kind != VA_Kind_Not_VA) { - ERROR(local->token->pos, - "Variable arguments must be last in parameter list"); - } - - if (param->vararg_kind == VA_Kind_Untyped) { - // HACK - if (builtin_vararg_type_type == NULL) - builtin_vararg_type_type = type_build_from_ast(context.ast_alloc, builtin_vararg_type); - - local->type = builtin_vararg_type_type; - } - - if (param->default_value != NULL) { - if (param->vararg_kind != VA_Kind_Not_VA) { - ERROR(local->token->pos, "Variadic arguments cannot have default values."); - } - - CHECK(expression, ¶m->default_value); - - if (local->type_node == NULL && local->type == NULL) { - local->type = resolve_expression_type(param->default_value); - } - - expect_default_param = 1; - } - - if (local->type_node != NULL) { - // If the function has the no_error flag, then the type node should have it set too. - // This allows for polymorphic structures with constraints to fail gracefully. - local->type_node->flags |= (func->flags & Ast_Flag_Header_Check_No_Error); - CHECK(type, &local->type_node); - } - - fill_in_type((AstTyped *) local); - if (local->type == NULL) { - YIELD(local->token->pos, "Waiting for parameter type to be known."); - } - - if (local->type == (Type *) &node_that_signals_failure) { - return Check_Failed; - } - - if (local->type->kind == Type_Kind_Compound) { - ERROR(param->local->token->pos, "Compound types are not allowed as parameter types. Try splitting this into multiple parameters."); - } - - // NOTE: I decided to make parameter default values not type checked against - // the actual parameter type. The actual type checking will happen in check_call - // when the default value is used as an argument and then has to be checked against - // the parameter type - brendanfh 2021/01/06 - // if (param->default_value != NULL) { - // if (!unify_node_and_type(¶m->default_value, param->local->type)) { - // onyx_report_error(param->local->token->pos, - // "Expected default value of type '%s', was of type '%s'.", - // type_get_name(param->local->type), - // type_get_name(param->default_value->type)); - // return Check_Error; - // } - // } - - if (param->vararg_kind != VA_Kind_Not_VA) has_had_varargs = 1; - - if (local->type->kind != Type_Kind_Array && type_size_of(local->type) == 0) { - ERROR(local->token->pos, "Function parameters cannot have zero-width types."); - } - } - - if (func->return_type != NULL) CHECK(type, &func->return_type); - - func->type = type_build_function_type(context.ast_alloc, func); - if (func->type == NULL) YIELD(func->token->pos, "Waiting for function type to be constructed"); - - return Check_Success; -} - -CheckStatus check_memres_type(AstMemRes* memres) { - CHECK(type, &memres->type_node); - fill_in_type((AstTyped *) memres); - if (memres->type_node && !memres->type) YIELD(memres->token->pos, "Waiting for global type to be constructed."); - return Check_Success; -} - -CheckStatus check_memres(AstMemRes* memres) { - assert(memres->type_entity); - if (memres->type_entity->state < Entity_State_Code_Gen) YIELD(memres->token->pos, "Waiting for global to pass type construction."); - - if (memres->initial_value != NULL) { - if (memres->threadlocal) { - onyx_report_error(memres->token->pos, Error_Critical, "'#thread_local' variables cannot have an initializer at the moment."); - return Check_Error; - } - - CHECK(expression, &memres->initial_value); - - if (memres->type != NULL) { - Type* memres_type = memres->type; - TYPE_CHECK(&memres->initial_value, memres_type) { - ERROR_(memres->token->pos, - "Cannot assign value of type '%s' to a '%s'.", - node_get_type_name(memres->initial_value), - type_get_name(memres_type)); - } - - } else { - resolve_expression_type(memres->initial_value); - if (memres->initial_value->type == NULL && memres->initial_value->entity != NULL && memres->initial_value->entity->state <= Entity_State_Check_Types) { - YIELD(memres->token->pos, "Waiting for global type to be constructed."); - } - memres->type = memres->initial_value->type; - } - - if ((memres->initial_value->flags & Ast_Flag_Comptime) == 0) { - if (memres->initial_value->entity != NULL && memres->initial_value->entity->state <= Entity_State_Check_Types) { - YIELD(memres->token->pos, "Waiting for initial value to be checked."); - } - - ERROR(memres->initial_value->token->pos, "Top level expressions must be compile time known."); - } - } - - return Check_Success; -} - -CheckStatus check_type(AstType** ptype) { - if (ptype == NULL || *ptype == NULL) return Check_Success; - - AstType* type = *ptype; - AstType* original_type = type; - while (type->kind == Ast_Kind_Type_Alias) - type = ((AstTypeAlias *) type)->to; - - if (type->flags & Ast_Flag_Has_Been_Checked) return Check_Success; - - switch (type->kind) { - case Ast_Kind_Poly_Call_Type: { - AstPolyCallType* pc_node = (AstPolyCallType *) type; - - bh_arr_each(AstNode *, param, pc_node->params) { - if (!node_is_type(*param)) { - CHECK(expression, (AstTyped **) param); - resolve_expression_type((AstTyped *) *param); - fill_in_type((AstTyped *) *param); - } - } - - break; - } - - case Ast_Kind_Typeof: { - AstTypeOf *type_of = (AstTypeOf *) type; - CHECK(expression, (AstTyped **) &type_of->expr); - resolve_expression_type(type_of->expr); - - if (type_of->expr->type == NULL) { - YIELD(type_of->token->pos, "Trying to check type for type-of expression."); - } - - type_of->resolved_type = type_of->expr->type; - break; - } - - case Ast_Kind_Pointer_Type: ((AstPointerType *) type)->elem->flags |= type->flags & Ast_Flag_Header_Check_No_Error; CHECK(type, &((AstPointerType *) type)->elem); break; - case Ast_Kind_Slice_Type: ((AstSliceType *) type)->elem->flags |= type->flags & Ast_Flag_Header_Check_No_Error; CHECK(type, &((AstSliceType *) type)->elem); break; - case Ast_Kind_DynArr_Type: ((AstDynArrType *) type)->elem->flags |= type->flags & Ast_Flag_Header_Check_No_Error; CHECK(type, &((AstDynArrType *) type)->elem); break; - case Ast_Kind_VarArg_Type: ((AstVarArgType *) type)->elem->flags |= type->flags & Ast_Flag_Header_Check_No_Error; CHECK(type, &((AstVarArgType *) type)->elem); break; - - case Ast_Kind_Function_Type: { - AstFunctionType* ftype = (AstFunctionType *) type; - - CHECK(type, &ftype->return_type); - - if (ftype->param_count > 0) { - fori (i, 0, (i64) ftype->param_count) { - CHECK(type, &ftype->params[i]); - } - } - break; - } - - case Ast_Kind_Type_Compound: { - AstCompoundType* ctype = (AstCompoundType *) type; - - bh_arr_each(AstType *, type, ctype->types) CHECK(type, type); - break; - } - - case Ast_Kind_Array_Type: { - AstArrayType* atype = (AstArrayType *) type; - if (atype->count_expr) { - CHECK(expression, &atype->count_expr); - resolve_expression_type(atype->count_expr); - } - - break; - } - - case Ast_Kind_Field_Access: { - CHECK(field_access, (AstFieldAccess **) ptype); - type = *ptype; - original_type = type; - - if (!node_is_type((AstNode *) type)) { - ERROR_(original_type->token->pos, "This field access did not resolve to be a type. It resolved to be a '%s'.", onyx_ast_node_kind_string(type->kind)); - } - break; - } - } - - type = original_type; - type->flags |= Ast_Flag_Comptime; - while (type->kind == Ast_Kind_Type_Alias) { - type->flags |= Ast_Flag_Comptime; - type = ((AstTypeAlias *) type)->to; - } - - type->flags |= Ast_Flag_Has_Been_Checked; - return Check_Success; -} - -CheckStatus check_static_if(AstIf* static_if) { - expression_types_must_be_known = 1; - CheckStatus result = check_expression(&static_if->cond); - expression_types_must_be_known = 0; - if (result == Check_Yield_Macro) return Check_Yield_Macro; - - if (result > Check_Errors_Start || !(static_if->cond->flags & Ast_Flag_Comptime)) { - ERROR(static_if->token->pos, "Expected this condition to be compile time known."); - } - - if (!type_is_bool(static_if->cond->type)) { - ERROR(static_if->token->pos, "Expected this condition to be a boolean value."); - } - - static_if->flags |= Ast_Flag_Static_If_Resolved; - - b32 resolution = static_if_resolution(static_if); - - if (context.options->print_static_if_results) - bh_printf("Static if statement at %s:%d:%d resulted in %s\n", - static_if->token->pos.filename, - static_if->token->pos.line, - static_if->token->pos.column, - resolution ? "true" : "false"); - - if (resolution) { - bh_arr_each(Entity *, ent, static_if->true_entities) { - entity_heap_insert_existing(&context.entities, *ent); - } - - } else { - bh_arr_each(Entity *, ent, static_if->false_entities) { - entity_heap_insert_existing(&context.entities, *ent); - } - } - - return Check_Complete; -} - -CheckStatus check_process_directive(AstNode* directive) { - if (directive->kind == Ast_Kind_Directive_Export) { - AstDirectiveExport *export = (AstDirectiveExport *) directive; - AstTyped *exported = export->export; - if (exported->entity && exported->entity->state <= Entity_State_Check_Types) - YIELD(directive->token->pos, "Waiting for exported type to be known."); - - CHECK(expression, &export->export_name_expr); - - if (export->export_name_expr->kind != Ast_Kind_StrLit) { - ERROR_(export->token->pos, "Expected export name to be a string literal, got '%s'.", onyx_ast_node_kind_string(export->export_name_expr->kind)); - } - - export->export_name = export->export_name_expr->token; - } - - if (directive->kind == Ast_Kind_Directive_Init) { - AstDirectiveInit *init = (AstDirectiveInit *) directive; - if ((init->flags & Ast_Flag_Has_Been_Checked) == 0) { - CHECK(expression, &init->init_proc); - - if (init->init_proc->kind != Ast_Kind_Function) { - ERROR_(init->token->pos, "#init only works for functions, got '%s'", onyx_ast_node_kind_string(init->init_proc->kind)); - } - - assert(init->init_proc->type); - if (init->init_proc->type->Function.param_count != 0) { - ERROR(init->token->pos, "#init expects a function that takes 0 arguments."); - } - } - - init->flags |= Ast_Flag_Has_Been_Checked; - - if (init->dependencies) { - i32 i = 0; - bh_arr_each(AstDirectiveInit *, dependency, init->dependencies) { - AstTyped *d = (AstTyped *) strip_aliases((AstNode *) *dependency); - if (d->kind != Ast_Kind_Directive_Init) { - ERROR_(init->token->pos, "All dependencies of an #init must be another #init. The %d%s dependency was not.", i + 1, bh_num_suffix(i + 1)); - } - - assert(d->entity); - if (d->entity->state != Entity_State_Finalized) { - YIELD(init->token->pos, "Circular dependency in #init nodes. Here are the nodes involved."); - } - - i++; - } - } - - bh_arr_push(init_procedures, (AstFunction *) init->init_proc); - return Check_Complete; - } - - if (directive->kind == Ast_Kind_Directive_Library) { - AstDirectiveLibrary *library = (AstDirectiveLibrary *) directive; - - if (library->library_symbol->kind != Ast_Kind_StrLit) { - ERROR_(library->token->pos, "#library directive expected compile-time known string for library name. Got '%s'.", - onyx_ast_node_kind_string(library->library_symbol->kind)); - } - - AstStrLit *symbol = (AstStrLit *) library->library_symbol; - char* temp_name = bh_alloc_array(global_scratch_allocator, char, symbol->token->length); - i32 temp_name_len = string_process_escape_seqs(temp_name, symbol->token->text, symbol->token->length); - library->library_name = bh_strdup(global_heap_allocator, temp_name); - return Check_Success; - } - - return Check_Success; -} - -CheckStatus check_macro(AstMacro* macro) { - if (macro->body->kind == Ast_Kind_Function) { - CHECK(function_header, (AstFunction *) macro->body); - } - - return Check_Success; -} - -CheckStatus check_constraint(AstConstraint *constraint) { - switch (constraint->phase) { - case Constraint_Phase_Cloning_Expressions: { - if (constraint->interface->kind == Ast_Kind_Symbol) { - return Check_Return_To_Symres; - } - - if (constraint->interface->kind != Ast_Kind_Interface) { - // CLEANUP: This error message might not look totally right in some cases. - ERROR_(constraint->token->pos, "'%b' is not an interface. It is a '%s'.", - constraint->token->text, constraint->token->length, - onyx_ast_node_kind_string(constraint->interface->kind)); - } - - bh_arr_new(global_heap_allocator, constraint->exprs, bh_arr_length(constraint->interface->exprs)); - bh_arr_each(InterfaceConstraint, ic, constraint->interface->exprs) { - InterfaceConstraint new_ic = {0}; - new_ic.expr = (AstTyped *) ast_clone(context.ast_alloc, (AstNode *) ic->expr); - new_ic.expected_type_expr = (AstType *) ast_clone(context.ast_alloc, (AstNode *) ic->expected_type_expr); - new_ic.invert_condition = ic->invert_condition; - bh_arr_push(constraint->exprs, new_ic); - } - - assert(constraint->interface->entity && constraint->interface->entity->scope); - - constraint->scope = scope_create(context.ast_alloc, constraint->interface->entity->scope, constraint->token->pos); - - fori (i, 0, bh_arr_length(constraint->interface->params)) { - InterfaceParam *ip = &constraint->interface->params[i]; - - AstTyped *sentinel = onyx_ast_node_new(context.ast_alloc, sizeof(AstTyped), Ast_Kind_Constraint_Sentinel); - sentinel->token = ip->value_token; - sentinel->type_node = constraint->type_args[i]; - - AstAlias *type_alias = onyx_ast_node_new(context.ast_alloc, sizeof(AstAlias), Ast_Kind_Alias); - type_alias->token = ip->type_token; - type_alias->alias = (AstTyped *) constraint->type_args[i]; - - symbol_introduce(constraint->scope, ip->value_token, (AstNode *) sentinel); - symbol_introduce(constraint->scope, ip->type_token, (AstNode *) type_alias); - } - - assert(constraint->entity); - constraint->entity->scope = constraint->scope; - - constraint->phase = Constraint_Phase_Checking_Expressions; - return Check_Return_To_Symres; - } - - case Constraint_Phase_Checking_Expressions: { - fori (i, constraint->expr_idx, bh_arr_length(constraint->exprs)) { - InterfaceConstraint* ic = &constraint->exprs[i]; - - CheckStatus cs = check_expression(&ic->expr); - if (cs == Check_Return_To_Symres || cs == Check_Yield_Macro) { - return cs; - } - - if (cs == Check_Error && !ic->invert_condition) { - goto constraint_error; - } - - if (cs == Check_Success && ic->invert_condition) { - goto constraint_error; - } - - if (ic->expected_type_expr) { - cs = check_type(&ic->expected_type_expr); - if (cs == Check_Return_To_Symres || cs == Check_Yield_Macro) { - return cs; - } - - ic->expected_type = type_build_from_ast(context.ast_alloc, ic->expected_type_expr); - if (ic->expected_type == NULL) { - YIELD(ic->expected_type_expr->token->pos, "Waiting on expected type expression to be resolved."); - } - - TYPE_CHECK(&ic->expr, ic->expected_type) { - if (!ic->invert_condition) - goto constraint_error; - } - } - - constraint->expr_idx++; - continue; - - constraint_error: - // HACK HACK HACK - onyx_clear_errors(); - *constraint->report_status = Constraint_Check_Status_Failed; - return Check_Failed; - } - - // HACK HACK HACK - onyx_clear_errors(); - *constraint->report_status = Constraint_Check_Status_Success; - return Check_Complete; - } - } - - return Check_Success; -} - -CheckStatus check_constraint_context(ConstraintContext *cc, Scope *scope, OnyxFilePos pos) { - if (cc->constraint_checks) { - if (cc->constraints_met == 1) return Check_Success; - - fori (i, 0, bh_arr_length(cc->constraints)) { - if (cc->constraint_checks[i] == Constraint_Check_Status_Failed) { - if (cc->produce_errors) { - AstConstraint *constraint = cc->constraints[i]; - char constraint_map[512] = {0}; - fori (i, 0, bh_arr_length(constraint->type_args)) { - if (i != 0) strncat(constraint_map, ", ", 511); - - OnyxToken* symbol = constraint->interface->params[i].value_token; - token_toggle_end(symbol); - strncat(constraint_map, symbol->text, 511); - token_toggle_end(symbol); - - strncat(constraint_map, " is of type '", 511); - strncat(constraint_map, type_get_name(type_build_from_ast(context.ast_alloc, constraint->type_args[i])), 511); - strncat(constraint_map, "'", 511); - } - - onyx_report_error(constraint->exprs[constraint->expr_idx].expr->token->pos, Error_Critical, "Failed to satisfy constraint where %s.", constraint_map); - onyx_report_error(constraint->token->pos, Error_Critical, "Here is where the interface was used."); - onyx_report_error(pos, Error_Critical, "Here is the code that caused this constraint to be checked."); - - return Check_Error; - - } else { - // If no error are suppose to be produced, we still need to signal that - // the node reached a completed state. - return Check_Failed; - } - } - - if (cc->constraint_checks[i] == Constraint_Check_Status_Queued) { - YIELD(pos, "Waiting for constraints to be checked."); - } - } - - cc->constraints_met = 1; - return Check_Success; - - } else { - u32 count = bh_arr_length(cc->constraints); - ConstraintCheckStatus *ccs = bh_alloc_array(context.ast_alloc, ConstraintCheckStatus, count); - - cc->constraint_checks = ccs; - - fori (i, 0, count) { - ccs[i] = Constraint_Check_Status_Queued; - cc->constraints[i]->report_status = &ccs[i]; - cc->constraints[i]->phase = Constraint_Phase_Cloning_Expressions; - - add_entities_for_node(NULL, (AstNode *) cc->constraints[i], scope, NULL); - } - - return Check_Yield_Macro; - } -} - -CheckStatus check_polyquery(AstPolyQuery *query) { - if (query->function_header->scope == NULL) - query->function_header->scope = scope_create(context.ast_alloc, query->proc->parent_scope_of_poly_proc, query->token->pos); - - CheckStatus header_check = check_temp_function_header(query->function_header); - if (header_check == Check_Return_To_Symres) return Check_Return_To_Symres; - - b32 solved_something = 0; - i32 solved_count = 0; - char *err_msg = NULL; - bh_arr_each(AstPolyParam, param, query->proc->poly_params) { - AstPolySolution sln; - bh_arr_each(AstPolySolution, solved_sln, query->slns) { - if (token_equals(param->poly_sym->token, solved_sln->poly_sym->token)) { - goto poly_query_done; - } - } - - // CLEANUP: I think this can go away because it is already done in polymorph.c - bh_arr_each(AstPolySolution, known_sln, query->proc->known_slns) { - if (token_equals(param->poly_sym->token, known_sln->poly_sym->token)) { - sln = *known_sln; - goto poly_var_solved; - } - } - - TypeMatch result = find_polymorphic_sln(&sln, param, query->function_header, query->pp_lookup, query->given, &err_msg); - - switch (result) { - case TYPE_MATCH_SUCCESS: - goto poly_var_solved; - - case TYPE_MATCH_SPECIAL: - return Check_Yield_Macro; - - case TYPE_MATCH_YIELD: - case TYPE_MATCH_FAILED: { - if (query->successful_symres || solved_something) continue; - - if (query->error_on_fail || context.cycle_detected) { - onyx_report_error(query->token->pos, Error_Critical, "Error solving for polymorphic variable '%b'.", param->poly_sym->token->text, param->poly_sym->token->length); - if (err_msg != NULL) onyx_report_error(query->token->pos, Error_Critical, "%s", err_msg); - if (query->error_loc) onyx_report_error(query->error_loc->pos, Error_Critical, "Here is where the call is located."); // :ErrorMessage - } - - return Check_Failed; - } - } - -poly_var_solved: - solved_something = 1; - bh_arr_push(query->slns, sln); - insert_poly_sln_into_scope(query->function_header->scope, &sln); - -poly_query_done: - solved_count += 1; - } - - if (solved_count != bh_arr_length(query->proc->poly_params)) { - if (solved_something || query->successful_symres) { - return Check_Return_To_Symres; - } else { - return Check_Failed; - } - } - - return Check_Complete; -} - -void check_entity(Entity* ent) { - CheckStatus cs = Check_Success; - - switch (ent->type) { - case Entity_Type_Foreign_Function_Header: - case Entity_Type_Function_Header: cs = check_function_header(ent->function); break; - case Entity_Type_Temp_Function_Header: cs = check_temp_function_header(ent->function); break; - case Entity_Type_Function: cs = check_function(ent->function); break; - case Entity_Type_Overloaded_Function: cs = check_overloaded_function(ent->overloaded_function); break; - case Entity_Type_Global: cs = check_global(ent->global); break; - case Entity_Type_Struct_Member_Default: cs = check_struct_defaults((AstStructType *) ent->type_alias); break; - case Entity_Type_Memory_Reservation_Type: cs = check_memres_type(ent->mem_res); break; - case Entity_Type_Memory_Reservation: cs = check_memres(ent->mem_res); break; - case Entity_Type_Static_If: cs = check_static_if(ent->static_if); break; - case Entity_Type_Macro: cs = check_macro(ent->macro); break; - case Entity_Type_Constraint_Check: cs = check_constraint(ent->constraint); break; - case Entity_Type_Polymorph_Query: cs = check_polyquery(ent->poly_query); break; - case Entity_Type_Enum_Value: cs = check_expression(&ent->enum_value->value); break; - case Entity_Type_Process_Directive: cs = check_process_directive((AstNode *) ent->expr); break; - - case Entity_Type_Expression: - cs = check_expression(&ent->expr); - resolve_expression_type(ent->expr); - break; - - case Entity_Type_Type_Alias: - if (ent->type_alias->kind == Ast_Kind_Struct_Type) - cs = check_struct((AstStructType *) ent->type_alias); - else - cs = check_type(&ent->type_alias); - break; - - case Entity_Type_File_Contents: - if (context.options->no_file_contents) { - onyx_report_error(ent->expr->token->pos, Error_Critical, "#file_contents is disabled for this compilation."); - } - break; - - default: break; - } - - switch (cs) { - case Check_Yield_Macro: ent->macro_attempts++; break; - case Check_Success: ent->state = Entity_State_Code_Gen; goto clear_attempts; - case Check_Complete: ent->state = Entity_State_Finalized; goto clear_attempts; - case Check_Return_To_Symres: ent->state = Entity_State_Resolve_Symbols; goto clear_attempts; - case Check_Failed: ent->state = Entity_State_Failed; goto clear_attempts; - - clear_attempts: - ent->macro_attempts = 0; - ent->micro_attempts = 0; - } -} diff --git a/src/clone.c b/src/clone.c deleted file mode 100644 index 828f6613..00000000 --- a/src/clone.c +++ /dev/null @@ -1,643 +0,0 @@ -#include "astnodes.h" -#include "parser.h" -#include "utils.h" - -// Weird flags that shouldn't be used too often because they complicate things -static b32 dont_copy_structs = 0; - -static inline b32 should_clone(AstNode* node) { - if (node->flags & Ast_Flag_No_Clone) return 0; - - if (dont_copy_structs) { - if (node->kind == Ast_Kind_Struct_Type) return 0; - if (node->kind == Ast_Kind_Function) return 0; - if (node->kind == Ast_Kind_Polymorphic_Proc) return 0; - } - - switch (node->kind) { - // List of nodes that should not be copied - case Ast_Kind_Global: - case Ast_Kind_Memres: - case Ast_Kind_StrLit: - case Ast_Kind_Package: - case Ast_Kind_Overloaded_Function: - case Ast_Kind_Alias: - case Ast_Kind_Code_Block: - case Ast_Kind_Macro: - case Ast_Kind_Symbol: - case Ast_Kind_Poly_Struct_Type: - case Ast_Kind_Basic_Type: - case Ast_Kind_Enum_Type: - case Ast_Kind_Enum_Value: - return 0; - - default: return 1; - } -} - -static inline i32 ast_kind_to_size(AstNode* node) { - switch (node->kind) { - case Ast_Kind_Error: return sizeof(AstNode); - case Ast_Kind_Package: return sizeof(AstPackage); - case Ast_Kind_Load_File: return sizeof(AstInclude); - case Ast_Kind_Load_Path: return sizeof(AstInclude); - case Ast_Kind_Load_All: return sizeof(AstInclude); - case Ast_Kind_Memres: return sizeof(AstMemRes); - case Ast_Kind_Binding: return sizeof(AstBinding); - case Ast_Kind_Function: return sizeof(AstFunction); - case Ast_Kind_Overloaded_Function: return sizeof(AstOverloadedFunction); - case Ast_Kind_Polymorphic_Proc: return sizeof(AstFunction); - case Ast_Kind_Block: return sizeof(AstBlock); - case Ast_Kind_Local: return sizeof(AstLocal); - case Ast_Kind_Global: return sizeof(AstGlobal); - case Ast_Kind_Symbol: return sizeof(AstNode); - case Ast_Kind_Unary_Op: return sizeof(AstUnaryOp); - case Ast_Kind_Binary_Op: return sizeof(AstBinaryOp); - case Ast_Kind_Type_Start: return 0; - case Ast_Kind_Type: return sizeof(AstType); - case Ast_Kind_Basic_Type: return sizeof(AstBasicType); - case Ast_Kind_Pointer_Type: return sizeof(AstPointerType); - case Ast_Kind_Function_Type: return sizeof(AstFunctionType) + ((AstFunctionType *) node)->param_count * sizeof(AstType *); - case Ast_Kind_Array_Type: return sizeof(AstArrayType); - case Ast_Kind_Slice_Type: return sizeof(AstSliceType); - case Ast_Kind_DynArr_Type: return sizeof(AstDynArrType); - case Ast_Kind_VarArg_Type: return sizeof(AstVarArgType); - case Ast_Kind_Struct_Type: return sizeof(AstStructType); - case Ast_Kind_Poly_Struct_Type: return sizeof(AstPolyStructType); - case Ast_Kind_Poly_Call_Type: return sizeof(AstPolyCallType); - case Ast_Kind_Enum_Type: return sizeof(AstEnumType); - case Ast_Kind_Type_Alias: return sizeof(AstTypeAlias); - case Ast_Kind_Type_Raw_Alias: return sizeof(AstTypeRawAlias); - case Ast_Kind_Type_Compound: return sizeof(AstCompoundType); - case Ast_Kind_Typeof: return sizeof(AstTypeOf); - case Ast_Kind_Type_End: return 0; - case Ast_Kind_Struct_Member: return sizeof(AstStructMember); - case Ast_Kind_Enum_Value: return sizeof(AstEnumValue); - case Ast_Kind_NumLit: return sizeof(AstNumLit); - case Ast_Kind_StrLit: return sizeof(AstStrLit); - case Ast_Kind_Param: return sizeof(AstLocal); - case Ast_Kind_Argument: return sizeof(AstArgument); - case Ast_Kind_Call: return sizeof(AstCall); - case Ast_Kind_Intrinsic_Call: return sizeof(AstCall); - case Ast_Kind_Return: return sizeof(AstReturn); - case Ast_Kind_Address_Of: return sizeof(AstAddressOf); - case Ast_Kind_Dereference: return sizeof(AstDereference); - case Ast_Kind_Subscript: return sizeof(AstSubscript); - case Ast_Kind_Slice: return sizeof(AstSubscript); - case Ast_Kind_Field_Access: return sizeof(AstFieldAccess); - case Ast_Kind_Unary_Field_Access: return sizeof(AstUnaryFieldAccess); - case Ast_Kind_Pipe: return sizeof(AstBinaryOp); - case Ast_Kind_Range_Literal: return sizeof(AstRangeLiteral); - case Ast_Kind_Method_Call: return sizeof(AstBinaryOp); - case Ast_Kind_Size_Of: return sizeof(AstSizeOf); - case Ast_Kind_Align_Of: return sizeof(AstAlignOf); - case Ast_Kind_File_Contents: return sizeof(AstFileContents); - case Ast_Kind_Struct_Literal: return sizeof(AstStructLiteral); - case Ast_Kind_Array_Literal: return sizeof(AstArrayLiteral); - case Ast_Kind_If: return sizeof(AstIfWhile); - case Ast_Kind_For: return sizeof(AstFor); - case Ast_Kind_While: return sizeof(AstIfWhile); - case Ast_Kind_Jump: return sizeof(AstJump); - case Ast_Kind_Use: return sizeof(AstUse); - case Ast_Kind_Defer: return sizeof(AstDefer); - case Ast_Kind_Switch: return sizeof(AstSwitch); - case Ast_Kind_Switch_Case: return sizeof(AstSwitchCase); - case Ast_Kind_Directive_Solidify: return sizeof(AstDirectiveSolidify); - case Ast_Kind_Compound: return sizeof(AstCompound); - case Ast_Kind_Named_Value: return sizeof(AstNamedValue); - case Ast_Kind_Call_Site: return sizeof(AstCallSite); - case Ast_Kind_Static_If: return sizeof(AstIfWhile); - case Ast_Kind_If_Expression: return sizeof(AstIfExpression); - case Ast_Kind_Directive_Insert: return sizeof(AstDirectiveInsert); - case Ast_Kind_Directive_Defined: return sizeof(AstDirectiveDefined); - case Ast_Kind_Do_Block: return sizeof(AstDoBlock); - case Ast_Kind_Constraint: return sizeof(AstConstraint); - case Ast_Kind_Directive_Remove: return sizeof(AstDirectiveRemove); - case Ast_Kind_Count: return 0; - } - - return 0; -} - -AstNode* ast_clone_list(bh_allocator a, void* n) { - AstNode* node = (AstNode *) n; - if (node == NULL) return NULL; - - AstNode* root = ast_clone(a, node); - AstNode* curr = root->next; - AstNode** insertion = &root->next; - - while (curr != NULL) { - curr = ast_clone(a, curr); - *insertion = curr; - insertion = &curr->next; - curr = curr->next; - } - - return root; -} - -static bh_arr(AstNode *) captured_entities=NULL; -#define E(ent) do { \ - assert(captured_entities); \ - ent->entity = NULL; \ - bh_arr_push(captured_entities, (AstNode *) ent); \ - } while (0); - - -#define C(nt, mname) ((nt *) nn)->mname = (void *) ast_clone(a, ((nt *) node)->mname); - -// NOTE: Using void* to avoid a lot of unnecessary casting -AstNode* ast_clone(bh_allocator a, void* n) { - AstNode* node = (AstNode *) n; - - if (node == NULL) return NULL; - if (!should_clone(node)) return node; - - static int clone_depth = 0; - clone_depth++; - - i32 node_size = ast_kind_to_size(node); - // bh_printf("Cloning %s with size %d\n", onyx_ast_node_kind_string(node->kind), node_size); - - AstNode* nn = onyx_ast_node_new(a, node_size, node->kind); - memmove(nn, node, node_size); - - switch ((u16) node->kind) { - case Ast_Kind_Binary_Op: - case Ast_Kind_Pipe: - case Ast_Kind_Method_Call: - C(AstBinaryOp, left); - C(AstBinaryOp, right); - break; - - case Ast_Kind_Unary_Op: - C(AstUnaryOp, expr); - C(AstUnaryOp, type_node); - break; - - case Ast_Kind_Param: - case Ast_Kind_Local: - C(AstLocal, type_node); - break; - - case Ast_Kind_Call: - C(AstCall, callee); - arguments_deep_clone(a, &((AstCall *) nn)->args, &((AstCall *) node)->args); - break; - - case Ast_Kind_Argument: - C(AstArgument, value); - break; - - case Ast_Kind_Address_Of: - C(AstAddressOf, expr); - break; - - case Ast_Kind_Dereference: - C(AstDereference, expr); - break; - - case Ast_Kind_Slice: - case Ast_Kind_Subscript: - C(AstSubscript, addr); - C(AstSubscript, expr); - break; - - case Ast_Kind_Field_Access: - C(AstFieldAccess, expr); - break; - - case Ast_Kind_Size_Of: - C(AstSizeOf, so_ast_type); - break; - - case Ast_Kind_Align_Of: - C(AstAlignOf, ao_ast_type); - break; - - case Ast_Kind_Struct_Literal: { - AstStructLiteral* st = (AstStructLiteral *) node; - AstStructLiteral* dt = (AstStructLiteral *) nn; - - dt->stnode = (AstTyped *) ast_clone(a, st->stnode); - - arguments_deep_clone(a, &dt->args, &st->args); - break; - } - - case Ast_Kind_Array_Literal: { - AstArrayLiteral* st = (AstArrayLiteral *) node; - AstArrayLiteral* dt = (AstArrayLiteral *) nn; - - dt->atnode = (AstTyped *) ast_clone(a, st->atnode); - - dt->values = NULL; - bh_arr_new(global_heap_allocator, dt->values, bh_arr_length(st->values)); - bh_arr_each(AstTyped *, val, st->values) - bh_arr_push(dt->values, (AstTyped *) ast_clone(a, *val)); - - break; - } - - case Ast_Kind_Range_Literal: - C(AstRangeLiteral, low); - C(AstRangeLiteral, high); - C(AstRangeLiteral, step); - break; - - case Ast_Kind_Return: - C(AstReturn, expr); - break; - - case Ast_Kind_Block: - ((AstBlock *) nn)->body = ast_clone_list(a, ((AstBlock *) node)->body); - break; - - case Ast_Kind_Defer: - C(AstDefer, stmt); - break; - - case Ast_Kind_For: - C(AstFor, var); - C(AstFor, iter); - C(AstFor, stmt); - break; - - case Ast_Kind_If: - case Ast_Kind_While: - ((AstIfWhile *) nn)->initialization = ast_clone_list(a, ((AstIfWhile *) node)->initialization); - //fallthrough - - case Ast_Kind_Static_If: - C(AstIfWhile, cond); - - C(AstIfWhile, true_stmt); - C(AstIfWhile, false_stmt); - - if (nn->kind == Ast_Kind_Static_If) { - ((AstIfWhile *) node)->flags |= Ast_Flag_Dead; - ((AstIfWhile *) node)->flags |= Ast_Flag_Static_If_Resolved; - - ((AstIfWhile *) nn)->flags &= ~Ast_Flag_Dead; - ((AstIfWhile *) nn)->flags &= ~Ast_Flag_Static_If_Resolved; - E(nn); - } - - break; - - case Ast_Kind_Switch_Case: { - C(AstSwitchCase, block); - - AstSwitchCase *dw = (AstSwitchCase *) nn; - AstSwitchCase *sw = (AstSwitchCase *) node; - - dw->values = NULL; - bh_arr_new(global_heap_allocator, dw->values, bh_arr_length(sw->values)); - bh_arr_each(AstTyped *, value, sw->values) - bh_arr_push(dw->values, (AstTyped *) ast_clone(a, *value)); - - break; - } - - case Ast_Kind_Switch: { - AstSwitch* dw = (AstSwitch *) nn; - AstSwitch* sw = (AstSwitch *) node; - - dw->initialization = ast_clone_list(a, sw->initialization); - C(AstSwitch, expr); - - - dw->cases = NULL; - C(AstSwitch, case_block); - break; - } - - case Ast_Kind_Pointer_Type: - C(AstPointerType, elem); - break; - - case Ast_Kind_Array_Type: - C(AstArrayType, count_expr); - C(AstArrayType, elem); - break; - - case Ast_Kind_Slice_Type: - C(AstSliceType, elem); - break; - - case Ast_Kind_DynArr_Type: - C(AstDynArrType, elem); - break; - - case Ast_Kind_VarArg_Type: - C(AstVarArgType, elem); - break; - - case Ast_Kind_Type_Alias: - C(AstTypeAlias, to); - break; - - case Ast_Kind_Struct_Type: { - AstStructType* ds = (AstStructType *) nn; - AstStructType* ss = (AstStructType *) node; - - ds->members = NULL; - bh_arr_new(global_heap_allocator, ds->members, bh_arr_length(ss->members)); - - bh_arr_each(AstStructMember *, smem, ss->members) { - bh_arr_push(ds->members, (AstStructMember *) ast_clone(a, *smem)); - } - - ds->meta_tags = NULL; - bh_arr_new(global_heap_allocator, ds->meta_tags, bh_arr_length(ss->meta_tags)); - bh_arr_each(AstTyped *, tag, ss->meta_tags) { - bh_arr_push(ds->meta_tags, (AstTyped *) ast_clone(a, *tag)); - } - - if (ss->constraints.constraints) { - memset(&ds->constraints, 0, sizeof(ConstraintContext)); - bh_arr_new(global_heap_allocator, ds->constraints.constraints, bh_arr_length(ss->constraints.constraints)); - - bh_arr_each(AstConstraint *, constraint, ss->constraints.constraints) { - bh_arr_push(ds->constraints.constraints, (AstConstraint *) ast_clone(a, (AstNode *) *constraint)); - } - } - - ds->stcache = NULL; - break; - } - - case Ast_Kind_Struct_Member: { - C(AstStructMember, type_node); - C(AstStructMember, initial_value); - - AstStructMember *ds = (AstStructMember *) nn; - AstStructMember *ss = (AstStructMember *) node; - - ds->meta_tags = NULL; - bh_arr_new(global_heap_allocator, ds->meta_tags, bh_arr_length(ss->meta_tags)); - bh_arr_each(AstTyped *, tag, ss->meta_tags) { - bh_arr_push(ds->meta_tags, (AstTyped *) ast_clone(a, *tag)); - } - - break; - } - - case Ast_Kind_Poly_Call_Type: { - AstPolyCallType* pcd = (AstPolyCallType *) nn; - AstPolyCallType* pcs = (AstPolyCallType *) node; - - pcd->callee = (AstType *) ast_clone(a, pcs->callee); - pcd->params = NULL; - bh_arr_new(global_heap_allocator, pcd->params, bh_arr_length(pcs->params)); - - bh_arr_each(AstNode *, param, pcs->params) { - bh_arr_push(pcd->params, ast_clone(a, *param)); - } - - break; - } - - case Ast_Kind_Type_Compound: { - AstCompoundType* cd = (AstCompoundType *) nn; - AstCompoundType* cs = (AstCompoundType *) node; - - cd->types = NULL; - bh_arr_new(global_heap_allocator, cd->types, bh_arr_length(cs->types)); - - bh_arr_each(AstType *, type, cs->types) { - bh_arr_push(cd->types, (AstType *) ast_clone(a, (AstNode *) *type)); - } - break; - } - - case Ast_Kind_Function_Type: - C(AstFunctionType, return_type); - ((AstFunctionType *) nn)->param_count = ((AstFunctionType *) node)->param_count; - fori (i, 0, (i64) ((AstFunctionType *) nn)->param_count) { - ((AstFunctionType *) nn)->params[i] = (AstType *) ast_clone(a, ((AstFunctionType *) node)->params[i]); - } - break; - - case Ast_Kind_Binding: - bh_printf("Cloning binding: %b\n", node->token->text, node->token->length); - C(AstTyped, type_node); - C(AstBinding, node); - break; - - case Ast_Kind_Function: - case Ast_Kind_Polymorphic_Proc: { - if (clone_depth > 1) { - clone_depth--; - return node; - } - - AstFunction* df = (AstFunction *) nn; - AstFunction* sf = (AstFunction *) node; - - convert_polyproc_to_function(df); - - if (sf->is_foreign) return node; - assert(df->scope == NULL); - - df->nodes_that_need_entities_after_clone = NULL; - bh_arr_new(global_heap_allocator, df->nodes_that_need_entities_after_clone, 1); - captured_entities = df->nodes_that_need_entities_after_clone; - - df->return_type = (AstType *) ast_clone(a, sf->return_type); - df->body = (AstBlock *) ast_clone(a, sf->body); - - df->nodes_that_need_entities_after_clone = captured_entities; - captured_entities = NULL; - - df->params = NULL; - bh_arr_new(context.ast_alloc, df->params, bh_arr_length(sf->params)); - - bh_arr_each(AstParam, param, sf->params) { - AstParam new_param = { 0 }; - - dont_copy_structs = 1; - new_param.local = (AstLocal *) ast_clone(a, param->local); - new_param.local->flags &= ~Ast_Flag_Param_Symbol_Dirty; - new_param.default_value = (AstTyped *) ast_clone(a, param->default_value); - new_param.use_processed = 0; - dont_copy_structs = 0; - - new_param.vararg_kind = param->vararg_kind; - new_param.is_used = param->is_used; - bh_arr_push(df->params, new_param); - } - - if (sf->constraints.constraints) { - memset(&df->constraints, 0, sizeof(ConstraintContext)); - bh_arr_new(context.ast_alloc, df->constraints.constraints, bh_arr_length(sf->constraints.constraints)); - - bh_arr_each(AstConstraint *, constraint, sf->constraints.constraints) { - bh_arr_push(df->constraints.constraints, (AstConstraint *) ast_clone(a, (AstNode *) *constraint)); - } - } - - if (sf->tags) { - bh_arr_new(context.ast_alloc, df->tags, bh_arr_length(sf->tags)); - bh_arr_each(AstTyped *, pexpr, sf->tags) { - bh_arr_push(df->tags, (AstTyped *) ast_clone(a, (AstNode *) *pexpr)); - } - } - - break; - } - - case Ast_Kind_Constraint: { - C(AstConstraint, interface); - - AstConstraint* dc = (AstConstraint *) nn; - AstConstraint* sc = (AstConstraint *) node; - - dc->type_args = NULL; - bh_arr_new(global_heap_allocator, dc->type_args, bh_arr_length(sc->type_args)); - - bh_arr_each(AstType *, type_arg, sc->type_args) { - bh_arr_push(dc->type_args, (AstType *) ast_clone(a, (AstNode *) *type_arg)); - } - - dc->phase = Constraint_Phase_Waiting_To_Be_Queued; - break; - } - - case Ast_Kind_Use: - C(AstUse, expr); - break; - - case Ast_Kind_Directive_Solidify: { - AstDirectiveSolidify* dd = (AstDirectiveSolidify *) nn; - AstDirectiveSolidify* sd = (AstDirectiveSolidify *) node; - - dd->poly_proc = (AstFunction *) ast_clone(a, (AstNode *) sd->poly_proc); - dd->resolved_proc = NULL; - - dd->known_polyvars = NULL; - bh_arr_new(global_heap_allocator, dd->known_polyvars, bh_arr_length(sd->known_polyvars)); - - bh_arr_each(AstPolySolution, sln, sd->known_polyvars) { - AstPolySolution new_sln; - new_sln.kind = sln->kind; - new_sln.poly_sym = (AstNode *) ast_clone(a, (AstNode *) sln->poly_sym); - new_sln.ast_type = (AstType *) ast_clone(a, (AstNode *) sln->ast_type); - bh_arr_push(dd->known_polyvars, new_sln); - } - - break; - } - - case Ast_Kind_Compound: { - AstCompound* cd = (AstCompound *) nn; - AstCompound* cs = (AstCompound *) node; - - cd->exprs = NULL; - bh_arr_new(global_heap_allocator, cd->exprs, bh_arr_length(cs->exprs)); - - bh_arr_each(AstTyped *, expr, cs->exprs) { - bh_arr_push(cd->exprs, (AstTyped *) ast_clone(a, (AstNode *) *expr)); - } - break; - } - - case Ast_Kind_Named_Value: - C(AstNamedValue, value); - break; - - case Ast_Kind_If_Expression: - C(AstIfExpression, cond); - C(AstIfExpression, true_expr); - C(AstIfExpression, false_expr); - break; - - case Ast_Kind_Directive_Insert: - C(AstDirectiveInsert, code_expr); - break; - - case Ast_Kind_Directive_Defined: - C(AstDirectiveDefined, expr); - ((AstDirectiveDefined *) nn)->is_defined = 0; - break; - - case Ast_Kind_Typeof: - C(AstTypeOf, expr); - ((AstTypeOf *) nn)->resolved_type = NULL; - break; - - case Ast_Kind_Do_Block: - C(AstDoBlock, block); - ((AstDoBlock *) nn)->type_node = (AstType *) &basic_type_auto_return; - break; - - case Ast_Kind_File_Contents: - C(AstFileContents, filename_expr); - E(nn); - break; - } - - clone_depth--; - return nn; -} - -#undef C - -AstFunction* clone_function_header(bh_allocator a, AstFunction* func) { - if (func->kind != Ast_Kind_Function && func->kind != Ast_Kind_Polymorphic_Proc) return NULL; - - if (func->is_foreign) return func; - - AstFunction* new_func = onyx_ast_node_new(a, sizeof(AstFunction), func->kind); - memmove(new_func, func, sizeof(AstFunction)); - assert(new_func->scope == NULL); - - convert_polyproc_to_function(new_func); - - new_func->return_type = (AstType *) ast_clone(a, func->return_type); - - new_func->params = NULL; - bh_arr_new(global_heap_allocator, new_func->params, bh_arr_length(func->params)); - bh_arr_each(AstParam, param, func->params) { - AstParam new_param; - - dont_copy_structs = 1; - new_param.local = (AstLocal *) ast_clone(a, param->local); - new_param.local->flags &= ~Ast_Flag_Param_Symbol_Dirty; - new_param.default_value = (AstTyped *) ast_clone(a, param->default_value); - new_param.use_processed = 0; - dont_copy_structs = 0; - - new_param.vararg_kind = param->vararg_kind; - new_param.is_used = param->is_used; - bh_arr_push(new_func->params, new_param); - } - - if (func->constraints.constraints) { - memset(&new_func->constraints, 0, sizeof(ConstraintContext)); - bh_arr_new(global_heap_allocator, new_func->constraints.constraints, bh_arr_length(func->constraints.constraints)); - - bh_arr_each(AstConstraint *, constraint, func->constraints.constraints) { - bh_arr_push(new_func->constraints.constraints, (AstConstraint *) ast_clone(a, (AstNode *) *constraint)); - } - } - - return new_func; -} - -// Clones a function body from a given function. It is assumed that `dest` is -// a function from `clone_function_header`. -void clone_function_body(bh_allocator a, AstFunction* dest, AstFunction* source) { - if (dest->kind != Ast_Kind_Function) return; - if (source->kind != Ast_Kind_Polymorphic_Proc && source->kind != Ast_Kind_Function) return; - - dest->nodes_that_need_entities_after_clone = NULL; - bh_arr_new(global_heap_allocator, dest->nodes_that_need_entities_after_clone, 1); - captured_entities = dest->nodes_that_need_entities_after_clone; - - dest->body = (AstBlock *) ast_clone(a, source->body); - - dest->nodes_that_need_entities_after_clone = captured_entities; - captured_entities = NULL; -} diff --git a/src/doc.c b/src/doc.c deleted file mode 100644 index 2a8bbd44..00000000 --- a/src/doc.c +++ /dev/null @@ -1,292 +0,0 @@ -#include "doc.h" -#include "utils.h" -#include "types.h" - -static i32 cmp_doc_entry(const void * a, const void * b) { - DocEntry* d1 = (DocEntry *) a; - DocEntry* d2 = (DocEntry *) b; - - return strncmp(d1->def, d2->def, 1024); -} - -static i32 cmp_doc_package(const void * a, const void * b) { - DocPackage* d1 = (DocPackage *) a; - DocPackage* d2 = (DocPackage *) b; - - return strncmp(d1->name, d2->name, 1024); -} - -static char* node_to_doc_def(const char* sym, AstNode *node, bh_allocator a) { - static char buf[1024]; - memset(buf, 0, 1023); - - strncat(buf, sym, 1023); - strncat(buf, " :: ", 1023); - - switch (node->kind) { - case Ast_Kind_Function: { - strncat(buf, "(", 1023); - - AstFunction *func = (AstFunction *) node; - bh_arr_each(AstParam, param, func->params) { - if (param != func->params) - strncat(buf, ", ", 1023); - - token_toggle_end(param->local->token); - strncat(buf, param->local->token->text, 1023); - token_toggle_end(param->local->token); - - strncat(buf, ": ", 1023); - - strncat(buf, type_get_name(param->local->type), 1023); - - if (param->default_value != NULL) { - strncat(buf, " = ", 1023); - } - } - - strncat(buf, ") -> ", 1023); - strncat(buf, type_get_name(func->type->Function.return_type), 1023); - - break; - } - - case Ast_Kind_Struct_Type: { - strncat(buf, "struct { ", 1023); - - AstStructType* st = (AstStructType *) node; - bh_arr_each(AstStructMember *, smem, st->members) { - - token_toggle_end((*smem)->token); - strncat(buf, (*smem)->token->text, 1023); - token_toggle_end((*smem)->token); - - strncat(buf, ": ", 1023); - - strncat(buf, type_get_name((*smem)->type), 1023); - - strncat(buf, "; ", 1023); - } - - strncat(buf, "}", 1023); - break; - } - - case Ast_Kind_Basic_Type: - case Ast_Kind_Array_Type: - case Ast_Kind_Type_Alias: - case Ast_Kind_Slice_Type: - case Ast_Kind_DynArr_Type: { - strncat(buf, type_get_name(type_build_from_ast(global_heap_allocator, (AstType *) node)), 1023); - break; - } - - default: { - strncat(buf, "", 1023); - } - } - - return bh_strdup(a, buf); -} - -static DocPackage doc_package_create(Package* p, bh_allocator a) { - DocPackage dp; - dp.name = p->name; - dp.public_entries = NULL; - dp.private_entries = NULL; - - bh_arr_new(global_heap_allocator, dp.public_entries, 16); - bh_arr_new(global_heap_allocator, dp.private_entries, 16); - - fori (i, 0, shlen(p->scope->symbols)) { - char *key = p->scope->symbols[i].key; - AstNode *value = p->scope->symbols[i].value; - - DocEntry de; - if (value->token) de.pos = value->token->pos; - de.def = node_to_doc_def(key, value, a); - de.sym = (char *) key; - de.additional = NULL; - - bh_arr_push(dp.public_entries, de); - } - - fori (i, 0, shlen(p->private_scope->symbols)) { - char *key = p->scope->symbols[i].key; - AstNode *value = p->scope->symbols[i].value; - - DocEntry de; - if (value->token) de.pos = value->token->pos; - de.def = node_to_doc_def(key, value, a); - de.sym = (char *) key; - de.additional = NULL; - - bh_arr_push(dp.private_entries, de); - } - - qsort(dp.public_entries, bh_arr_length(dp.public_entries), sizeof(DocEntry), cmp_doc_entry); - qsort(dp.private_entries, bh_arr_length(dp.private_entries), sizeof(DocEntry), cmp_doc_entry); - - return dp; -} - -OnyxDocumentation onyx_docs_generate() { - OnyxDocumentation doc; - - bh_arena_init(&doc.doc_arena, global_heap_allocator, 16 * 1024); - bh_allocator a = bh_arena_allocator(&doc.doc_arena); - - doc.package_docs = NULL; - bh_arr_new(global_heap_allocator, doc.package_docs, 16); - - fori (i, 0, shlen(context.packages)) { - DocPackage dp = doc_package_create(context.packages[i].value, a); - bh_arr_push(doc.package_docs, dp); - } - - qsort(doc.package_docs, bh_arr_length(doc.package_docs), sizeof(DocPackage), cmp_doc_package); - - return doc; -} - -static void onyx_docs_emit_human(OnyxDocumentation* doc, bh_file* file) { - // NOTE: Disabling fancy line printing until I can make it better - #if 0 - bh_arr_each(DocPackage, dp, doc->package_docs) { - bh_printf("Package '%s'\n", dp->name); - - if (bh_arr_length(dp->public_entries) > 0) { - bh_printf("\xe2\x94\x9c\xe2\x94\x80 Public symbols\n"); - bh_arr_each(DocEntry, de, dp->public_entries) { - bh_printf("\xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80 %s\n", de->def); - - if (de->pos.filename != NULL) - bh_printf("\xe2\x94\x82 \xe2\x94\x82 at %s:%d,%d\n", de->pos.filename, de->pos.line, de->pos.column); - else - bh_printf("\xe2\x94\x82 \xe2\x94\x82 compiler built-in\n"); - - bh_printf("\xe2\x94\x82 \xe2\x94\x82\n"); - } - } - - if (bh_arr_length(dp->private_entries) > 0) { - bh_printf("\xe2\x94\x9c\xe2\x94\x80 Private symbols\n"); - bh_arr_each(DocEntry, de, dp->private_entries) { - bh_printf("\xe2\x94\x82 \xe2\x94\x9c\xe2\x94\x80 %s\n", de->def); - if (de->pos.filename != NULL) - bh_printf("\xe2\x94\x82 \xe2\x94\x82 at %s:%d,%d\n", de->pos.filename, de->pos.line, de->pos.column); - else - bh_printf("\xe2\x94\x82 \xe2\x94\x82 compiler built-in\n"); - - bh_printf("\xe2\x94\x82 \xe2\x94\x82\n"); - } - - bh_printf("\n"); - } - } - #else - bh_arr_each(DocPackage, dp, doc->package_docs) { - bh_fprintf(file, "Package '%s'\n", dp->name); - - if (bh_arr_length(dp->public_entries) > 0) { - bh_fprintf(file, " Public symbols\n"); - bh_arr_each(DocEntry, de, dp->public_entries) { - bh_fprintf(file, " %s\n", de->def); - - if (de->pos.filename != NULL) - bh_fprintf(file, " at %s:%d,%d\n", de->pos.filename, de->pos.line, de->pos.column); - else - bh_fprintf(file, " compiler built-in\n"); - - bh_fprintf(file, " \n"); - } - } - - if (bh_arr_length(dp->private_entries) > 0) { - bh_fprintf(file, " Private symbols\n"); - bh_arr_each(DocEntry, de, dp->private_entries) { - bh_fprintf(file, " %s\n", de->def); - if (de->pos.filename != NULL) - bh_fprintf(file, " at %s:%d,%d\n", de->pos.filename, de->pos.line, de->pos.column); - else - bh_fprintf(file, " compiler built-in\n"); - - bh_fprintf(file, " \n"); - } - - bh_fprintf(file, "\n"); - } - } - #endif -} - - -static i32 sort_tags(const void* a, const void* b) { - DocEntry *da = *((DocEntry **) a); - DocEntry *db = *((DocEntry **) b); - - return strcmp(da->sym, db->sym); -} - -static void onyx_docs_emit_tags(OnyxDocumentation* doc) { - bh_file tags_file; - if (bh_file_create(&tags_file, "./tags") != BH_FILE_ERROR_NONE) { - bh_printf("Cannot create 'tags'.\n"); - return; - } - - bh_fprintf(&tags_file, "!_TAG_FILE_FORMAT\t2\n"); - bh_fprintf(&tags_file, "!_TAG_FILE_SORTED\t1\n"); - bh_fprintf(&tags_file, "!_TAG_OUTPUT_FILESEP\tslash\n"); - bh_fprintf(&tags_file, "!_TAG_OUTPUT_MODE\tu-ctags\n"); - bh_fprintf(&tags_file, "!_TAG_PROGRAM_AUTHOR\tOnyx Compiler\n"); - bh_fprintf(&tags_file, "!_TAG_PROGRAM_NAME\tOnyx Compiler\n"); - bh_fprintf(&tags_file, "!_TAG_PROGRAM_URL\thttps://github.com/brendanfh/onyx\n"); - bh_fprintf(&tags_file, "!_TAG_PROGRAM_VERSION\t0.0.1\n"); - - bh_arr(DocEntry *) tags = NULL; - bh_arr_new(global_heap_allocator, tags, 256); - - bh_arr_each(DocPackage, dp, doc->package_docs) { - bh_arr_each(DocEntry, de, dp->public_entries) bh_arr_push(tags, de); - bh_arr_each(DocEntry, de, dp->private_entries) bh_arr_push(tags, de); - } - - qsort(tags, bh_arr_length(tags), sizeof(DocEntry *), sort_tags); - - bh_arr_each(DocEntry *, tag, tags) { - if ((*tag)->pos.filename == NULL) continue; - - i32 line_len = 0; - char *c = (*tag)->pos.line_start; - while (*c++ != '\n') line_len++; - - bh_fprintf(&tags_file, "%s\t%s\t/^%b$/\n", - (*tag)->sym, (*tag)->pos.filename, - (*tag)->pos.line_start, line_len); - } - - bh_file_close(&tags_file); -} - -static void onyx_docs_emit_html(OnyxDocumentation* doc, bh_file* file) { - bh_fprintf(file, "HTML documentation output not supported yet.\n"); - return; -} - -void onyx_docs_emit(OnyxDocumentation* doc, const char* filename) { - bh_file file; - if (bh_file_create(&file, filename) != BH_FILE_ERROR_NONE) { - bh_printf("ERROR: Failed to open file '%s' for writing documentation.\n", filename); - return; - } - - switch (doc->format) { - case Doc_Format_Human: onyx_docs_emit_human(doc, &file); break; - case Doc_Format_Html: onyx_docs_emit_html(doc, &file); break; - case Doc_Format_Tags: onyx_docs_emit_tags(doc); break; - } - - bh_file_close(&file); -} - diff --git a/src/entities.c b/src/entities.c deleted file mode 100644 index 0ceb2f92..00000000 --- a/src/entities.c +++ /dev/null @@ -1,406 +0,0 @@ -#include "bh.h" -#include "astnodes.h" -#include "utils.h" - -static inline i32 entity_phase(Entity* e1) { - if (e1->state <= Entity_State_Parse && e1->macro_attempts == 0) return 1; - if (e1->state < Entity_State_Code_Gen) return 2; - return 3; -} - -// NOTE: Returns >0 if e1 should be processed after e2. -static i32 entity_compare(Entity* e1, Entity* e2) { - i32 phase1 = entity_phase(e1); - i32 phase2 = entity_phase(e2); - - if (phase1 != phase2) - return phase1 - phase2; - else if (e1->macro_attempts != e2->macro_attempts) - return (i32) e1->macro_attempts - (i32) e2->macro_attempts; - else if (e1->state != e2->state) - return (i32) e1->state - (i32) e2->state; - else if (e1->type != e2->type) - return (i32) e1->type - (i32) e2->type; - else if (e1->micro_attempts != e2->micro_attempts) - return (i32) (e1->micro_attempts - e2->micro_attempts); - else - return (i32) (e1->id - e2->id); -} - -#define eh_parent(index) (((index) - 1) / 2) -#define eh_lchild(index) (((index) * 2) + 1) -#define eh_rchild(index) (((index) * 2) + 2) - -static void eh_shift_up(EntityHeap* entities, i32 index) { - while (index > 0 && entity_compare(entities->entities[eh_parent(index)], entities->entities[index]) > 0) { - Entity* tmp = entities->entities[eh_parent(index)]; - entities->entities[eh_parent(index)] = entities->entities[index]; - entities->entities[index] = tmp; - - index = eh_parent(index); - } -} - -static void eh_shift_down(EntityHeap* entities, i32 index) { - while (1) { - i32 min_index = index; - - i32 l = eh_lchild(index); - if (l < bh_arr_length(entities->entities) - && entity_compare(entities->entities[l], entities->entities[min_index]) < 0) { - min_index = l; - } - - i32 r = eh_rchild(index); - if (r < bh_arr_length(entities->entities) - && entity_compare(entities->entities[r], entities->entities[min_index]) < 0) { - min_index = r; - } - - if (index != min_index) { - Entity* tmp = entities->entities[min_index]; - entities->entities[min_index] = entities->entities[index]; - entities->entities[index] = tmp; - - index = min_index; - continue; - } - - break; - } -} - -void entity_heap_init(EntityHeap* entities) { - bh_arena_init(&entities->entity_arena, global_heap_allocator, 32 * 1024); -} - -static u32 next_entity_id = 0; - -// Allocates the entity in the entity heap. Don't quite feel this is necessary... -Entity* entity_heap_register(EntityHeap* entities, Entity e) { - bh_allocator alloc = bh_arena_allocator(&entities->entity_arena); - Entity* entity = bh_alloc_item(alloc, Entity); - *entity = e; - entity->id = next_entity_id++; - entity->macro_attempts = 0; - entity->micro_attempts = 0; - entity->entered_in_queue = 0; - - return entity; -} - -void entity_heap_insert_existing(EntityHeap* entities, Entity* e) { - if (e->entered_in_queue) return; - - if (entities->entities == NULL) { - bh_arr_new(global_heap_allocator, entities->entities, 128); - } - - bh_arr_push(entities->entities, e); - eh_shift_up(entities, bh_arr_length(entities->entities) - 1); - e->entered_in_queue = 1; - - entities->state_count[e->state]++; - entities->type_count[e->type]++; - entities->all_count[e->state][e->type]++; -} - -Entity* entity_heap_insert(EntityHeap* entities, Entity e) { - Entity* entity = entity_heap_register(entities, e); - entity_heap_insert_existing(entities, entity); - return entity; -} - -Entity* entity_heap_top(EntityHeap* entities) { - return entities->entities[0]; -} - -void entity_heap_change_top(EntityHeap* entities, Entity* new_top) { - entities->state_count[entities->entities[0]->state]--; - entities->state_count[new_top->state]++; - - entities->type_count[entities->entities[0]->type]--; - entities->type_count[new_top->type]++; - - entities->all_count[entities->entities[0]->state][entities->entities[0]->type]--; - entities->all_count[new_top->state][new_top->type]++; - - entities->entities[0] = new_top; - eh_shift_down(entities, 0); -} - -void entity_heap_remove_top(EntityHeap* entities) { - entities->state_count[entities->entities[0]->state]--; - entities->type_count[entities->entities[0]->type]--; - entities->all_count[entities->entities[0]->state][entities->entities[0]->type]--; - entities->entities[0]->entered_in_queue = 0; - - entities->entities[0] = entities->entities[bh_arr_length(entities->entities) - 1]; - bh_arr_pop(entities->entities); - eh_shift_down(entities, 0); -} - -void entity_change_type(EntityHeap* entities, Entity *ent, EntityType new_type) { - entities->type_count[ent->type]--; - entities->type_count[new_type]++; - ent->type = new_type; -} - -// NOTE(Brendan Hansen): Uses the entity heap in the context structure -void add_entities_for_node(bh_arr(Entity *) *target_arr, AstNode* node, Scope* scope, Package* package) { -#define ENTITY_INSERT(_ent) \ - entity = entity_heap_register(entities, _ent); \ - if (target_arr) { \ - bh_arr(Entity *) __tmp_arr = *target_arr; \ - bh_arr_push(__tmp_arr, entity); \ - *target_arr = __tmp_arr; \ - } else { \ - entity_heap_insert_existing(entities, entity); \ - } \ - - if (node->entity != NULL) return; - - EntityHeap* entities = &context.entities; - Entity* entity; - - Entity ent; - ent.id = entities->next_id++; - ent.state = Entity_State_Resolve_Symbols; - ent.package = package; - ent.scope = scope; - - switch (node->kind) { - case Ast_Kind_Load_All: - case Ast_Kind_Load_File: { - ent.type = Entity_Type_Load_File; - ent.include = (AstInclude *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Library_Path: - case Ast_Kind_Load_Path: { - ent.type = Entity_Type_Load_Path; - ent.include = (AstInclude *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Binding: { - ent.state = Entity_State_Introduce_Symbols; - ent.type = Entity_Type_Binding; - ent.binding = (AstBinding *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Function: { - if (((AstFunction *) node)->is_foreign != 0) { - ent.type = Entity_Type_Foreign_Function_Header; - ent.function = (AstFunction *) node; - ENTITY_INSERT(ent); - - } else { - ent.type = Entity_Type_Function_Header; - ent.function = (AstFunction *) node; - ENTITY_INSERT(ent); - ((AstFunction *) node)->entity_header = entity; - - ent.id = entities->next_id++; - ent.type = Entity_Type_Function; - ent.function = (AstFunction *) node; - ENTITY_INSERT(ent); - ((AstFunction *) node)->entity_body = entity; - } - break; - } - - case Ast_Kind_Overloaded_Function: { - ent.type = Entity_Type_Overloaded_Function; - ent.overloaded_function = (AstOverloadedFunction *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Global: { - ent.type = Entity_Type_Global_Header; - ent.global = (AstGlobal *) node; - ENTITY_INSERT(ent); - - ent.id = entities->next_id++; - ent.type = Entity_Type_Global; - ent.global = (AstGlobal *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_StrLit: { - ent.type = Entity_Type_String_Literal; - ent.strlit = (AstStrLit *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_File_Contents: { - ent.type = Entity_Type_File_Contents; - ent.file_contents = (AstFileContents *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Struct_Type: { - ent.type = Entity_Type_Struct_Member_Default; - ent.type_alias = (AstType *) node; - ENTITY_INSERT(ent); - ((AstStructType *) node)->entity_defaults = entity; - - ent.id = entities->next_id++; - // fallthrough - } - - case Ast_Kind_Poly_Struct_Type: - case Ast_Kind_Type_Alias: { - ent.type = Entity_Type_Type_Alias; - ent.type_alias = (AstType *) node; - ENTITY_INSERT(ent); - - if (node->kind == Ast_Kind_Struct_Type) { - ((AstStructType *) node)->entity_type = entity; - } - - break; - } - - case Ast_Kind_Enum_Type: { - ent.type = Entity_Type_Enum; - ent.enum_type = (AstEnumType *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Enum_Value: { - ent.type = Entity_Type_Enum_Value; - ent.state = Entity_State_Check_Types; - ent.enum_value = (AstEnumValue *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Use: { - if (((AstUse *) node)->expr->kind == Ast_Kind_Package) { - ent.state = Entity_State_Resolve_Symbols; - ent.type = Entity_Type_Use_Package; - } else { - ent.type = Entity_Type_Use; - } - - ent.use = (AstUse *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Memres: { - ent.type = Entity_Type_Memory_Reservation_Type; - ent.mem_res = (AstMemRes *) node; - ENTITY_INSERT(ent); - ((AstMemRes *) node)->type_entity = entity; - - ent.id = entities->next_id++; - ent.type = Entity_Type_Memory_Reservation; - ent.mem_res = (AstMemRes *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Macro: { - ent.type = Entity_Type_Macro; - ent.macro = (AstMacro *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Polymorphic_Proc: { - ent.type = Entity_Type_Polymorphic_Proc; - ent.poly_proc = (AstFunction *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Polymorph_Query: { - ent.type = Entity_Type_Polymorph_Query; - ent.state = Entity_State_Check_Types; - ent.poly_query = (AstPolyQuery *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Static_If: { - ent.state = Entity_State_Resolve_Symbols; - ent.type = Entity_Type_Static_If; - ent.static_if = (AstIf *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Directive_Error: { - ent.state = Entity_State_Error; - ent.type = Entity_Type_Error; - ent.error = (AstDirectiveError *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Directive_Export: - case Ast_Kind_Directive_Add_Overload: - case Ast_Kind_Directive_Operator: - case Ast_Kind_Directive_Init: - case Ast_Kind_Directive_Library: - case Ast_Kind_Injection: { - ent.type = Entity_Type_Process_Directive; - ent.expr = (AstTyped *) node; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Note: { - ent.type = Entity_Type_Note; - ent.expr = (AstTyped *) node; - ent.state = Entity_State_Code_Gen; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Interface: { - ent.type = Entity_Type_Interface; - ent.interface = (AstInterface *) node; - ent.state = Entity_State_Resolve_Symbols; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Constraint: { - ent.type = Entity_Type_Constraint_Check; - ent.constraint = (AstConstraint *) node; - ent.state = Entity_State_Resolve_Symbols; - ENTITY_INSERT(ent); - break; - } - - case Ast_Kind_Foreign_Block: { - ent.type = Entity_Type_Foreign_Block; - ent.foreign_block = (AstForeignBlock *) node; - ent.state = Entity_State_Resolve_Symbols; - ENTITY_INSERT(ent); - break; - } - - default: { - ent.type = Entity_Type_Expression; - ent.expr = (AstTyped *) node; - ENTITY_INSERT(ent); - break; - } - } - - node->entity = entity; -} diff --git a/src/errors.c b/src/errors.c deleted file mode 100644 index f6811ca6..00000000 --- a/src/errors.c +++ /dev/null @@ -1,156 +0,0 @@ -#include "errors.h" -#include "utils.h" - -OnyxErrors errors; - -void onyx_errors_init(bh_arr(bh_file_contents)* files) { - errors.file_contents = files; - - bh_arena_init(&errors.msg_arena, global_heap_allocator, 16 * 1024); - errors.msg_alloc = bh_arena_allocator(&errors.msg_arena); - - bh_arr_new(global_heap_allocator, errors.errors, 4); -} - -static void print_detailed_message(OnyxError* err, bh_file_contents* fc) { - bh_printf("(%s:%l,%l) %s\n", err->pos.filename, err->pos.line, err->pos.column, err->text); - - b32 colored_printing = 0; - #ifdef _BH_LINUX - colored_printing = !context.options->no_colors; - #endif - - i32 linelength = 0; - i32 first_char = 0; - char* walker = err->pos.line_start; - while (*walker == ' ' || *walker == '\t') first_char++, linelength++, walker++; - while (*walker != '\n') linelength++, walker++; - - if (colored_printing) bh_printf("\033[90m"); - i32 numlen = bh_printf(" %d | ", err->pos.line); - if (colored_printing) bh_printf("\033[94m"); - bh_printf("%b\n", err->pos.line_start, linelength); - - char* pointer_str = bh_alloc_array(global_scratch_allocator, char, linelength + numlen); - memset(pointer_str, ' ', linelength + numlen); - memcpy(pointer_str + numlen - 1, err->pos.line_start, first_char); - memset(pointer_str + first_char + numlen - 1, ' ', err->pos.column - first_char); - memset(pointer_str + err->pos.column + numlen - 1, '~', err->pos.length - 1); - pointer_str[err->pos.column + numlen - 2] = '^'; - pointer_str[err->pos.column + numlen + err->pos.length - 1] = 0; - - if (colored_printing) bh_printf("\033[91m"); - bh_printf("%s\n", pointer_str); - - if (colored_printing) bh_printf("\033[0m"); -} - -static i32 errors_sort(const void* v1, const void* v2) { - OnyxError* e1 = (OnyxError *) v1; - OnyxError* e2 = (OnyxError *) v2; - return e2->rank - e1->rank; -} - -void onyx_errors_print() { - // NOTE: If the format of the error messages is ever changed, - // update onyx_compile.vim and onyx.sublime-build to match - // the new format. This was editor error highlighting is still - // supported. - // - // - brendanfh 2020/09/03 - - qsort(errors.errors, bh_arr_length(errors.errors), sizeof(OnyxError), errors_sort); - - OnyxErrorRank last_rank = errors.errors[0].rank; - bh_arr_each(OnyxError, err, errors.errors) { - if (last_rank != err->rank) break; - - if (err->pos.filename) { - bh_file_contents file_contents = { 0 }; - bh_arr_each(bh_file_contents, fc, *errors.file_contents) { - if (!strcmp(fc->filename, err->pos.filename)) { - file_contents = *fc; - break; - } - } - - print_detailed_message(err, &file_contents); - - } else { - bh_printf("(%l,%l) %s\n", err->pos.line, err->pos.column, err->text); - } - - last_rank = err->rank; - } -} - -b32 onyx_has_errors() { - return bh_arr_length(errors.errors) > 0; -} - -void onyx_clear_errors() { - if (context.cycle_detected) return; - - bh_arr_set_length(errors.errors, 0); -} - -void onyx_submit_error(OnyxError error) { - bh_arr_push(errors.errors, error); -} - -void onyx_report_error(OnyxFilePos pos, OnyxErrorRank rank, char * format, ...) { - - va_list vargs; - va_start(vargs, format); - char* msg = bh_bprintf_va(format, vargs); - va_end(vargs); - - OnyxError err = { - .pos = pos, - .rank = rank, - .text = bh_strdup(errors.msg_alloc, msg), - }; - - bh_arr_push(errors.errors, err); -} - -void onyx_submit_warning(OnyxError error) { - bh_file_contents file_contents = { 0 }; - bh_arr_each(bh_file_contents, fc, *errors.file_contents) { - if (!strcmp(fc->filename, error.pos.filename)) { - file_contents = *fc; - break; - } - } - - print_detailed_message(&error, &file_contents); -} - -// This definitely doesn't do what I thought it did? -void onyx_report_warning(OnyxFilePos pos, char* format, ...) { - va_list vargs; - va_start(vargs, format); - char* msg = bh_bprintf_va(format, vargs); - va_end(vargs); - - OnyxError err = { - .pos = pos, - .rank = Error_Warning, - .text = msg, - }; - - bh_arr_push(errors.errors, err); - - /* - - bh_file_contents file_contents = { 0 }; - bh_arr_each(bh_file_contents, fc, *errors.file_contents) { - if (!strcmp(fc->filename, pos.filename)) { - file_contents = *fc; - break; - } - } - - print_detailed_message(&err, &file_contents); - */ -} diff --git a/src/lex.c b/src/lex.c deleted file mode 100644 index eba315ed..00000000 --- a/src/lex.c +++ /dev/null @@ -1,548 +0,0 @@ -#include "bh.h" -#include "lex.h" -#include "utils.h" -#include "errors.h" - -u64 lexer_lines_processed = 0; -u64 lexer_tokens_processed = 0; - -static const char* token_type_names[] = { - "TOKEN_TYPE_UNKNOWN", - "TOKEN_TYPE_END_STREAM", - - "TOKEN_TYPE_COMMENT", - - "package", - "struct", - "enum", - "use", - "if", - "else", - "elseif", - "return", - "global", - "cast", - "while", - "for", - "break", - "continue", - "sizeof", - "alignof", - "typeof", - "defer", - "do", - "case", - "switch", - "fallthrough", - "macro", - "interface", - "where", - - "->", - "<-", - "---", - "|>", - - ">=", - "<=", - "==", - "!=", - "+=", - "-=", - "*=", - "/=", - "%=", - "&=", - "|=", - "^=", - "&&", - "||", - "<<", - ">>", - ">>>", - "<<=", - ">>=", - ">>>=", - "..", - "~~", - - "TOKEN_TYPE_SYMBOL", - "TOKEN_TYPE_LITERAL_STRING", - "TOKEN_TYPE_LITERAL_INTEGER", - "TOKEN_TYPE_LITERAL_FLOAT", - "true", - "false", - - "NOTE", - - "TOKEN_TYPE_COUNT" -}; - -#ifndef LITERAL_TOKEN -#define LITERAL_TOKEN(token, word, token_type) \ - if (token_lit(tokenizer, &tk, token, word, token_type)) goto token_parsed; -#endif - -#ifndef INCREMENT_CURR_TOKEN -#define INCREMENT_CURR_TOKEN(tkn) { \ - if (*(tkn)->curr == '\n') { \ - (tkn)->line_number++; \ - (tkn)->line_start = (tkn)->curr + 1; \ - } \ - (tkn)->curr++; \ -} -#endif - -#define char_is_alphanum(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') || ((c) >= '0' && (c) <= '9')) -#define char_is_num(c) ((c) >= '0' && (c) <= '9') -#define char_is_alpha(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) - -static inline b32 token_lit(OnyxTokenizer* tokenizer, OnyxToken* tk, char* lit, b32 is_word, TokenType type) { - i64 len = 0; - char* ptr1 = tokenizer->curr; - char* ptr2 = lit; - while (*ptr2 != '\0' && *ptr1 == *ptr2) ptr1++, ptr2++, len++; - if (*ptr2 != '\0') return 0; - - if (is_word && (char_is_alphanum(*ptr1) || *ptr1 == '_')) - return 0; - - tk->type = type; - tk->text = tokenizer->curr; - tk->length = len; - tk->pos.line = tokenizer->line_number; - tk->pos.column = (i32)(tokenizer->curr - tokenizer->line_start) + 1; - - tokenizer->curr += len; - - return 1; -} - -const char* token_name(TokenType tkn_type) { - if (tkn_type < Token_Type_Ascii_End) { - return bh_aprintf(global_scratch_allocator, "%c", (char) tkn_type); - } else { - return token_type_names[tkn_type - Token_Type_Ascii_End]; - } -} - -void token_toggle_end(OnyxToken* tkn) { - static char backup = 0; - char tmp = tkn->text[tkn->length]; - tkn->text[tkn->length] = backup; - backup = tmp; -} - -OnyxToken* onyx_get_token(OnyxTokenizer* tokenizer) { - OnyxToken tk; - - // Skip whitespace - while (1) { - if (tokenizer->curr == tokenizer->end) break; - - switch (*tokenizer->curr) { - case ' ': - case '\n': - case '\t': - case '\r': - INCREMENT_CURR_TOKEN(tokenizer); - break; - default: - goto whitespace_skipped; - } - } - -whitespace_skipped: - - tk.type = Token_Type_Unknown; - tk.text = tokenizer->curr; - tk.length = 1; - tk.pos.line_start = tokenizer->line_start; - tk.pos.filename = tokenizer->filename; - tk.pos.line = tokenizer->line_number; - tk.pos.column = (u16)(tokenizer->curr - tokenizer->line_start) + 1; - - if (tokenizer->curr == tokenizer->end) { - tk.type = Token_Type_End_Stream; - goto token_parsed; - } - - // She-bang - if (tokenizer->curr == tokenizer->start) { - if (*tokenizer->curr == '#' && *(tokenizer->curr + 1) == '!') { - tk.type = Token_Type_Comment; - - tokenizer->curr += 2; - while (*tokenizer->curr++ != '\n' && tokenizer->curr != tokenizer->end); - - tk.length = tokenizer->curr - tk.text; - goto token_parsed; - } - } - - // Comments - if (*tokenizer->curr == '/' && *(tokenizer->curr + 1) == '/') { - tokenizer->curr += 2; - tk.type = Token_Type_Comment; - tk.text = tokenizer->curr; - - while (*tokenizer->curr != '\n' && tokenizer->curr != tokenizer->end) { - INCREMENT_CURR_TOKEN(tokenizer); - } - - tk.length = tokenizer->curr - tk.text - 2; - goto token_parsed; - } - - if (*tokenizer->curr == '/' && *(tokenizer->curr + 1) == '*') { - tokenizer->curr += 2; - tk.type = Token_Type_Comment; - tk.text = tokenizer->curr; - - i32 comment_depth = 1; - - while (comment_depth > 0 && tokenizer->curr != tokenizer->end) { - if (*tokenizer->curr == '/' && *(tokenizer->curr + 1) == '*') { - tokenizer->curr += 2; - comment_depth += 1; - } - - else if (*tokenizer->curr == '*' && *(tokenizer->curr + 1) == '/') { - tokenizer->curr += 2; - comment_depth -= 1; - } - - else { - INCREMENT_CURR_TOKEN(tokenizer); - } - } - - goto token_parsed; - } - - if (*tk.text == '"' && *(tk.text + 1) == '"' && *(tk.text + 2) == '"') { - u64 len = 0; - - INCREMENT_CURR_TOKEN(tokenizer); - INCREMENT_CURR_TOKEN(tokenizer); - INCREMENT_CURR_TOKEN(tokenizer); - - while (!(*tokenizer->curr == '"' && *(tokenizer->curr + 1) == '"' && *(tokenizer->curr + 2) == '"')) { - len++; - INCREMENT_CURR_TOKEN(tokenizer); - } - - INCREMENT_CURR_TOKEN(tokenizer); - INCREMENT_CURR_TOKEN(tokenizer); - INCREMENT_CURR_TOKEN(tokenizer); - - tk.text += 3; - tk.length = len; - tk.type = Token_Type_Literal_String; - goto token_parsed; - } - - // String literal - if (*tk.text == '"') { - u64 len = 0; - u64 slash_count = 0; - - INCREMENT_CURR_TOKEN(tokenizer); - - while (!(*tokenizer->curr == '"' && slash_count == 0)) { - len++; - - // if (*tokenizer->curr == '\n') { - // onyx_report_error(tk.pos, "String literal not terminated by end of line."); - // } - - if (*tokenizer->curr == '\\') { - slash_count += 1; - slash_count %= 2; - } else { - slash_count = 0; - } - - INCREMENT_CURR_TOKEN(tokenizer); - } - - INCREMENT_CURR_TOKEN(tokenizer); - - tk.text++; - tk.type = Token_Type_Literal_String; - tk.length = len; - goto token_parsed; - } - - // Hex literal - if (*tokenizer->curr == '0' && *(tokenizer->curr + 1) == 'x' && charset_contains("0123456789abcdefABCDEF", *(tokenizer->curr + 2))) { - INCREMENT_CURR_TOKEN(tokenizer); - INCREMENT_CURR_TOKEN(tokenizer); - u32 len = 3; - while (char_is_num(*(tokenizer->curr + 1)) || charset_contains("abcdefABCDEF", *(tokenizer->curr + 1))) { - len++; - INCREMENT_CURR_TOKEN(tokenizer); - } - - tk.type = Token_Type_Literal_Integer; - tk.length = len; - - INCREMENT_CURR_TOKEN(tokenizer); - goto token_parsed; - } - - if (*tokenizer->curr == '@') { - INCREMENT_CURR_TOKEN(tokenizer); - u32 len = 2; - while (char_is_alphanum(*(tokenizer->curr + 1)) || *(tokenizer->curr + 1) == '_') { - len++; - INCREMENT_CURR_TOKEN(tokenizer); - } - - tk.type = Token_Type_Note; - tk.length = len; - INCREMENT_CURR_TOKEN(tokenizer); - goto token_parsed; - } - - // Number literal - if (char_is_num(*tokenizer->curr) - || (*(tokenizer->curr) == '.' && char_is_num(*(tokenizer->curr + 1)))) { - tk.type = Token_Type_Literal_Integer; - - b32 hit_decimal = 0; - if (*tokenizer->curr == '.') hit_decimal = 1; - - u32 len = 1; - while (char_is_num(*(tokenizer->curr + 1)) - || (!hit_decimal && *(tokenizer->curr + 1) == '.' && *(tokenizer->curr + 2) != '.')) { - len++; - INCREMENT_CURR_TOKEN(tokenizer); - - if (*tokenizer->curr == '.') hit_decimal = 1; - } - - if (*(tokenizer->curr + 1) == 'f') { - tk.type = Token_Type_Literal_Float; - - len++; - INCREMENT_CURR_TOKEN(tokenizer); - } - - if (hit_decimal) tk.type = Token_Type_Literal_Float; - - tk.length = len; - - INCREMENT_CURR_TOKEN(tokenizer); - goto token_parsed; - } - - char curr = *tokenizer->curr; - switch (curr) { - case 'a': - LITERAL_TOKEN("alignof", 1, Token_Type_Keyword_Alignof); - break; - case 'b': - LITERAL_TOKEN("break", 1, Token_Type_Keyword_Break); - break; - case 'c': - LITERAL_TOKEN("case", 1, Token_Type_Keyword_Case); - LITERAL_TOKEN("cast", 1, Token_Type_Keyword_Cast); - LITERAL_TOKEN("continue", 1, Token_Type_Keyword_Continue); - break; - case 'd': - LITERAL_TOKEN("defer", 1, Token_Type_Keyword_Defer); - LITERAL_TOKEN("do", 1, Token_Type_Keyword_Do); - break; - case 'e': - LITERAL_TOKEN("enum", 1, Token_Type_Keyword_Enum); - LITERAL_TOKEN("elseif", 1, Token_Type_Keyword_Elseif); - LITERAL_TOKEN("else", 1, Token_Type_Keyword_Else); - break; - case 'f': - LITERAL_TOKEN("for", 1, Token_Type_Keyword_For); - LITERAL_TOKEN("false", 1, Token_Type_Literal_False); - LITERAL_TOKEN("fallthrough", 1, Token_Type_Keyword_Fallthrough); - break; - case 'g': - LITERAL_TOKEN("global", 1, Token_Type_Keyword_Global); - break; - case 'i': - LITERAL_TOKEN("if", 1, Token_Type_Keyword_If); - LITERAL_TOKEN("interface", 1, Token_Type_Keyword_Interface); - break; - case 'm': - LITERAL_TOKEN("macro", 1, Token_Type_Keyword_Macro); - break; - case 'p': - LITERAL_TOKEN("package", 1, Token_Type_Keyword_Package); - break; - case 'r': - LITERAL_TOKEN("return", 1, Token_Type_Keyword_Return); - break; - case 's': - LITERAL_TOKEN("struct", 1, Token_Type_Keyword_Struct); - LITERAL_TOKEN("sizeof", 1, Token_Type_Keyword_Sizeof); - LITERAL_TOKEN("switch", 1, Token_Type_Keyword_Switch); - break; - case 't': - LITERAL_TOKEN("true", 1, Token_Type_Literal_True); - LITERAL_TOKEN("typeof", 1, Token_Type_Keyword_Typeof); - break; - case 'u': - LITERAL_TOKEN("use", 1, Token_Type_Keyword_Use); - break; - case 'w': - LITERAL_TOKEN("while", 1, Token_Type_Keyword_While); - LITERAL_TOKEN("where", 1, Token_Type_Keyword_Where); - break; - - case '-': - LITERAL_TOKEN("->", 0, Token_Type_Right_Arrow); - LITERAL_TOKEN("---", 0, Token_Type_Empty_Block); - LITERAL_TOKEN("-=", 0, Token_Type_Minus_Equal); - break; - - case '<': - LITERAL_TOKEN("<=", 0, Token_Type_Less_Equal); - LITERAL_TOKEN("<-", 0, Token_Type_Right_Arrow); - LITERAL_TOKEN("<<=", 0, Token_Type_Shl_Equal); - LITERAL_TOKEN("<<", 0, Token_Type_Shift_Left); - break; - - case '>': - LITERAL_TOKEN(">=", 0, Token_Type_Greater_Equal); - LITERAL_TOKEN(">>>=", 0, Token_Type_Sar_Equal); - LITERAL_TOKEN(">>=", 0, Token_Type_Shr_Equal); - LITERAL_TOKEN(">>>", 0, Token_Type_Shift_Arith_Right); - LITERAL_TOKEN(">>", 0, Token_Type_Shift_Right); - break; - - case '&': - LITERAL_TOKEN("&&", 0, Token_Type_And_And); - LITERAL_TOKEN("&=", 0, Token_Type_And_Equal); - break; - - case '|': - LITERAL_TOKEN("|>", 0, Token_Type_Pipe); - LITERAL_TOKEN("||", 0, Token_Type_Or_Or); - LITERAL_TOKEN("|=", 0, Token_Type_Or_Equal); - break; - - case '=': - LITERAL_TOKEN("==", 0, Token_Type_Equal_Equal); - break; - - case '!': - LITERAL_TOKEN("!=", 0, Token_Type_Not_Equal); - break; - - case '+': - LITERAL_TOKEN("+=", 0, Token_Type_Plus_Equal); - break; - - case '*': - LITERAL_TOKEN("*=", 0, Token_Type_Star_Equal); - break; - - case '^': - LITERAL_TOKEN("^=", 0, Token_Type_Xor_Equal); - break; - - case '/': - LITERAL_TOKEN("/=", 0, Token_Type_Fslash_Equal); - break; - - case '%': - LITERAL_TOKEN("%=", 0, Token_Type_Percent_Equal); - break; - - case '.': - LITERAL_TOKEN("..", 0, Token_Type_Dot_Dot); - break; - - case '~': - LITERAL_TOKEN("~~", 0, Token_Type_Tilde_Tilde); - break; - } - - // Symbols - if (char_is_alpha(*tk.text) || *tokenizer->curr == '_') { - u64 len = 0; - while (char_is_alphanum(*tokenizer->curr) || *tokenizer->curr == '_') { - len++; - INCREMENT_CURR_TOKEN(tokenizer); - } - - tk.length = len; - tk.type = Token_Type_Symbol; - goto token_parsed; - } - - - tk.type = (TokenType) *tokenizer->curr; - INCREMENT_CURR_TOKEN(tokenizer); - -token_parsed: - tk.pos.length = (u16) tk.length; - bh_arr_push(tokenizer->tokens, tk); - - return &tokenizer->tokens[bh_arr_length(tokenizer->tokens) - 1]; -} - -OnyxTokenizer onyx_tokenizer_create(bh_allocator allocator, bh_file_contents *fc) { - OnyxTokenizer tknizer = { - .start = fc->data, - .curr = fc->data, - .end = bh_pointer_add(fc->data, fc->length), - - .filename = fc->filename, - - .line_number = 1, - .line_start = fc->data, - .tokens = NULL, - }; - - bh_arr_new(allocator, tknizer.tokens, 1 << 12); - return tknizer; -} - -void onyx_tokenizer_free(OnyxTokenizer* tokenizer) { - bh_arr_free(tokenizer->tokens); -} - -void onyx_lex_tokens(OnyxTokenizer* tokenizer) { - OnyxToken* tk; - do { - tk = onyx_get_token(tokenizer); - } while (tk->type != Token_Type_End_Stream); - - lexer_lines_processed += tokenizer->line_number - 1; - lexer_tokens_processed += bh_arr_length(tokenizer->tokens); -} - -b32 token_equals(OnyxToken* tkn1, OnyxToken* tkn2) { - if (tkn1->length != tkn2->length) return 0; - fori (i, 0, tkn1->length) - if (tkn1->text[i] != tkn2->text[i]) return 0; - return 1; -} - -b32 token_text_equals(OnyxToken* tkn, char* text) { - i32 text_len = strlen(text); - if (tkn->length != text_len) return 0; - - fori (i, 0, tkn->length) - if (tkn->text[i] != text[i]) return 0; - - return 1; -} - -b32 token_same_file(OnyxToken *tkn1, OnyxToken *tkn2) { - if (!tkn1 || !tkn2) return 0; - - if (tkn1->pos.filename == tkn2->pos.filename) return 1; - - // :Security? - return strcmp(tkn1->pos.filename, tkn2->pos.filename) == 0; -} diff --git a/src/onyx.c b/src/onyx.c deleted file mode 100644 index 9433df38..00000000 --- a/src/onyx.c +++ /dev/null @@ -1,800 +0,0 @@ -// #define BH_DEBUG -#define BH_DEFINE -#define BH_NO_TABLE -#define STB_DS_IMPLEMENTATION -#include "bh.h" - -#include "lex.h" -#include "errors.h" -#include "parser.h" -#include "utils.h" -#include "wasm_emit.h" -#include "doc.h" - -#define VERSION "v0.1.0" - - -// #ifndef CORE_INSTALLATION -// #ifdef _BH_LINUX -// #define CORE_INSTALLATION "/usr/share/onyx" -// #elif defined(_WIN32) || defined(_WIN64) -// #define CORE_INSTALLATION "\\dev\\onyx\\" -// #endif -// #endif - - - - -Context context; - - -static const char* docstring = "Onyx compiler version " VERSION "\n" - "\n" - "The compiler for the Onyx programming language, created by Brendan Hansen.\n" - "\n" - "Usage:\n" - "\tonyx compile [-o ] [--verbose] \n" -#ifdef ENABLE_RUN_WITH_WASMER - "\tonyx run -- \n" -#endif - // "\tonyx doc \n" - "\tonyx help\n" - "\n" - "Flags:\n" - "\t List of initial files\n" - "\t-o Specify the target file (default: out.wasm).\n" - "\t--runtime, -r Specifies the runtime. Can be: onyx, wasi, js, custom.\n" - "\t--verbose, -V Verbose output.\n" - "\t -VV Very verbose output.\n" - "\t -VVV Very very verbose output (to be used by compiler developers).\n" - "\t--wasm-mvp Use only WebAssembly MVP features.\n" - "\t--multi-threaded Enables multi-threading for this compilation.\n" - "\t--doc \n" - "\t--generate-foreign-info\n" - "\n" - "Developer flags:\n" - "\t--print-function-mappings Prints a mapping from WASM function index to source location.\n" - "\t--print-static-if-results Prints the conditional result of each #if statement. Useful for debugging.\n" - "\t--print-notes Prints the location of notes throughout the loaded code.\n" - "\t--no-colors Disables colors in the error message.\n" - "\t--no-file-contents Disables '#file_contents' for security.\n" - "\n"; - - -static CompileOptions compile_opts_parse(bh_allocator alloc, int argc, char *argv[]) { - CompileOptions options = { - .allocator = alloc, - .action = ONYX_COMPILE_ACTION_PRINT_HELP, - - .verbose_output = 0, - .fun_output = 0, - .print_function_mappings = 0, - .no_file_contents = 0, - - .use_post_mvp_features = 1, - .use_multi_threading = 0, - - .runtime = Runtime_Onyx, - - .files = NULL, - .target_file = "out.wasm", - - .documentation_file = NULL, - - .debug_enabled = 0, - - .passthrough_argument_count = 0, - .passthrough_argument_data = NULL, - }; - - bh_arr_new(alloc, options.files, 2); - bh_arr_new(alloc, options.included_folders, 2); - - char* core_installation; - #ifdef _BH_LINUX - core_installation = CORE_INSTALLATION; - #endif - #ifdef _BH_WINDOWS - core_installation = bh_alloc_array(global_heap_allocator, u8, 512); - GetEnvironmentVariableA("ONYX_PATH", core_installation, 512); - #endif - - // NOTE: Add the current folder - bh_arr_push(options.included_folders, core_installation); - bh_arr_push(options.included_folders, "."); - - if (argc == 1) return options; - i32 arg_parse_start = 1; - - if (!strcmp(argv[1], "help")) options.action = ONYX_COMPILE_ACTION_PRINT_HELP; - if (!strcmp(argv[1], "compile")) { - options.action = ONYX_COMPILE_ACTION_COMPILE; - arg_parse_start = 2; - } - #ifdef ENABLE_RUN_WITH_WASMER - else if (!strcmp(argv[1], "run")) { - options.action = ONYX_COMPILE_ACTION_RUN; - arg_parse_start = 2; - } - #endif - else options.action = ONYX_COMPILE_ACTION_COMPILE; - - if (options.action != ONYX_COMPILE_ACTION_PRINT_HELP) { - fori(i, arg_parse_start, argc) { - if (!strcmp(argv[i], "-o")) { - options.target_file = argv[++i]; - } - else if (!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-V")) { - options.verbose_output = 1; - } - else if (!strcmp(argv[i], "-VV")) { - options.verbose_output = 2; - } - else if (!strcmp(argv[i], "-VVV")) { - options.verbose_output = 3; - } - else if (!strcmp(argv[i], "--print-function-mappings")) { - options.print_function_mappings = 1; - } - else if (!strcmp(argv[i], "--print-static-if-results")) { - options.print_static_if_results = 1; - } - else if (!strcmp(argv[i], "--print-notes")) { - options.print_notes = 1; - } - else if (!strcmp(argv[i], "--no-colors")) { - options.no_colors = 1; - } - else if (!strcmp(argv[i], "--no-file-contents")) { - options.no_file_contents = 1; - } - else if (!strcmp(argv[i], "--wasm-mvp")) { - options.use_post_mvp_features = 0; - } - else if (!strcmp(argv[i], "--multi-threaded")) { - options.use_multi_threading = 1; - } - else if (!strcmp(argv[i], "--generate-foreign-info")) { - options.generate_foreign_info = 1; - } - else if (!strcmp(argv[i], "-I")) { - bh_arr_push(options.included_folders, argv[++i]); - } - else if (!strcmp(argv[i], "-r") || !strcmp(argv[i], "--runtime")) { - i += 1; - if (!strcmp(argv[i], "onyx")) options.runtime = Runtime_Onyx; - else if (!strcmp(argv[i], "wasi")) options.runtime = Runtime_Wasi; - else if (!strcmp(argv[i], "js")) options.runtime = Runtime_Js; - else if (!strcmp(argv[i], "custom")) options.runtime = Runtime_Custom; - else { - bh_printf("WARNING: '%s' is not a valid runtime. Defaulting to 'onyx'.\n", argv[i]); - options.runtime = Runtime_Wasi; - } - } - else if (!strcmp(argv[i], "--doc")) { - options.documentation_file = argv[++i]; - } - else if (!strcmp(argv[i], "--debug")) { - options.debug_enabled = 1; - } - else if (!strcmp(argv[i], "--")) { - options.passthrough_argument_count = argc - i - 1; - options.passthrough_argument_data = &argv[i + 1]; - break; - } -#if defined(_BH_LINUX) - // NOTE: Fun output is only enabled for Linux because Windows command line - // is not ANSI compatible and for a silly feature, I don't want to learn - // how to properly do arbitrary graphics in it. - else if (!strcmp(argv[i], "--fun") || !strcmp(argv[i], "-F")) { - options.fun_output = 1; - } -#endif - else { - bh_arr_push(options.files, argv[i]); - } - } - } - - // NOTE: Always enable multi-threading for the Onyx runtime. - if (options.runtime == Runtime_Onyx) { - options.use_multi_threading = 1; - } - - return options; -} - -static void compile_opts_free(CompileOptions* opts) { - bh_arr_free(opts->files); - bh_arr_free(opts->included_folders); -} - - - - - -typedef enum CompilerProgress { - ONYX_COMPILER_PROGRESS_ERROR, - ONYX_COMPILER_PROGRESS_FAILED_OUTPUT, - ONYX_COMPILER_PROGRESS_SUCCESS -} CompilerProgress; - -static OnyxToken implicit_load_token = { '#', 1, 0, { 0, 0, 0, 0, 0 } }; -static AstInclude* create_load(bh_allocator alloc, char* filename) { - AstInclude* include_node = onyx_ast_node_new(alloc, sizeof(AstInclude), Ast_Kind_Load_File); - include_node->name = filename; - include_node->token = &implicit_load_token; - - return include_node; -} - -// HACK -static u32 special_global_entities_remaining = 3; -static Entity *runtime_info_types_entity; -static Entity *runtime_info_foreign_entity; -static Entity *runtime_info_proc_tags_entity; - -static void context_init(CompileOptions* opts) { - types_init(); - - context.options = opts; - context.cycle_detected = 0; - context.cycle_almost_detected = 0; - - OnyxFilePos internal_location = { 0 }; - internal_location.filename = ""; - internal_location.line = 1; - internal_location.column = 1; - context.global_scope = scope_create(global_heap_allocator, NULL, internal_location); - sh_new_arena(context.packages); - - // NOTE: This will be initialized upon the first call to entity_heap_insert. - context.entities.next_id = 0; - context.entities.entities = NULL; - - onyx_errors_init(&context.loaded_files); - - context.token_alloc = global_heap_allocator; - - // NOTE: Create the arena where tokens and AST nodes will exist - // Prevents nodes from being scattered across memory due to fragmentation - bh_arena_init(&context.ast_arena, global_heap_allocator, 16 * 1024 * 1024); // 16MB - context.ast_alloc = bh_arena_allocator(&context.ast_arena); - - context.wasm_module = bh_alloc_item(global_heap_allocator, OnyxWasmModule); - *context.wasm_module = onyx_wasm_module_create(global_heap_allocator); - - entity_heap_init(&context.entities); - - // NOTE: Add builtin entities to pipeline. - entity_heap_insert(&context.entities, ((Entity) { - .state = Entity_State_Parse_Builtin, - .type = Entity_Type_Load_File, - .package = NULL, - .include = create_load(context.ast_alloc, "core/builtin"), - })); - - entity_heap_insert(&context.entities, ((Entity) { - .state = Entity_State_Parse_Builtin, - .type = Entity_Type_Load_File, - .package = NULL, - .include = create_load(context.ast_alloc, "core/runtime/build_opts"), - })); - - if (context.options->runtime != Runtime_Custom) { - runtime_info_types_entity = entity_heap_insert(&context.entities, ((Entity) { - .state = Entity_State_Parse, - .type = Entity_Type_Load_File, - .package = NULL, - .include = create_load(context.ast_alloc, "core/runtime/info/types"), - })); - runtime_info_foreign_entity = entity_heap_insert(&context.entities, ((Entity) { - .state = Entity_State_Parse, - .type = Entity_Type_Load_File, - .package = NULL, - .include = create_load(context.ast_alloc, "core/runtime/info/foreign_blocks"), - })); - runtime_info_proc_tags_entity = entity_heap_insert(&context.entities, ((Entity) { - .state = Entity_State_Parse, - .type = Entity_Type_Load_File, - .package = NULL, - .include = create_load(context.ast_alloc, "core/runtime/info/proc_tags"), - })); - } - - add_entities_for_node(NULL, (AstNode *) &builtin_stack_top, context.global_scope, NULL); - add_entities_for_node(NULL, (AstNode *) &builtin_heap_start, context.global_scope, NULL); - add_entities_for_node(NULL, (AstNode *) &builtin_tls_base, context.global_scope, NULL); - add_entities_for_node(NULL, (AstNode *) &builtin_tls_size, context.global_scope, NULL); - - // NOTE: Add all files passed by command line to the queue - bh_arr_each(const char *, filename, opts->files) { - AstInclude* load_node = create_load(context.ast_alloc, (char *) *filename); - add_entities_for_node(NULL, (AstNode *) load_node, context.global_scope, NULL); - } -} - -static void context_free() { - bh_arena_free(&context.ast_arena); - bh_arr_free(context.loaded_files); - - compile_opts_free(context.options); -} - -static void parse_source_file(bh_file_contents* file_contents) { - // :Remove passing the allocators as parameters - OnyxTokenizer tokenizer = onyx_tokenizer_create(context.token_alloc, file_contents); - onyx_lex_tokens(&tokenizer); - - file_contents->line_count = tokenizer.line_number; - - OnyxParser parser = onyx_parser_create(context.ast_alloc, &tokenizer); - onyx_parse(&parser); - onyx_parser_free(&parser); -} - -static b32 process_source_file(char* filename, OnyxFilePos error_pos) { - bh_arr_each(bh_file_contents, fc, context.loaded_files) { - // Duplicates are detected here and since these filenames will be the full path, - // string comparing them should be all that is necessary. - if (!strcmp(fc->filename, filename)) return 1; - } - - bh_file file; - bh_file_error err = bh_file_open(&file, filename); - if (err != BH_FILE_ERROR_NONE) { - if (context.cycle_detected) { - onyx_report_error(error_pos, Error_Critical, "Failed to open file %s", filename); - } - return 0; - } - - bh_file_contents fc = bh_file_read_contents(context.token_alloc, &file); - bh_file_close(&file); - - bh_arr_push(context.loaded_files, fc); - - if (context.options->verbose_output == 2) - bh_printf("Processing source file: %s (%d bytes)\n", file.filename, fc.length); - - parse_source_file(&bh_arr_last(context.loaded_files)); - return 1; -} - -static b32 process_load_entity(Entity* ent) { - assert(ent->type == Entity_Type_Load_File || ent->type == Entity_Type_Load_Path); - AstInclude* include = ent->include; - - if (include->kind == Ast_Kind_Load_File) { - // :RelativeFiles - const char* parent_file = include->token->pos.filename; - if (parent_file == NULL) parent_file = "."; - - char* parent_folder = bh_path_get_parent(parent_file, global_scratch_allocator); - - char* filename = bh_lookup_file(include->name, parent_folder, ".onyx", 1, context.options->included_folders, 1); - char* formatted_name = bh_strdup(global_heap_allocator, filename); - - return process_source_file(formatted_name, include->token->pos); - - } else if (include->kind == Ast_Kind_Load_All) { - const char* parent_file = include->token->pos.filename; - if (parent_file == NULL) parent_file = "."; - - char* parent_folder = bh_path_get_parent(parent_file, global_scratch_allocator); - char folder[512]; - if (bh_str_starts_with(include->name, "./")) { - bh_snprintf(folder, 511, "%s/%s", parent_folder, include->name + 2); - } else { - bh_snprintf(folder, 511, "%s", include->name); - } - - // This does not take into account #load_path'd folders... - - bh_path_convert_separators(folder); - bh_dir dir = bh_dir_open(folder); - if (dir == NULL) { - onyx_report_error(include->token->pos, Error_Critical, "Could not find folder '%s'.", folder); - return 0; - } - - bh_dirent entry; - b32 success = 1; - char fullpath[512]; - while (bh_dir_read(dir, &entry)) { - if (entry.type == BH_DIRENT_FILE && bh_str_ends_with(entry.name, ".onyx")) { - bh_snprintf(fullpath, 511, "%s/%s", folder, entry.name); - bh_path_convert_separators(fullpath); - u8* formatted_name = bh_path_get_full_name(fullpath, global_heap_allocator); - success = process_source_file(formatted_name, include->token->pos); - if (!success) break; - } - } - - bh_dir_close(dir); - return success; - - } else if (include->kind == Ast_Kind_Load_Path) { - bh_arr_push(context.options->included_folders, include->name); - - } else if (include->kind == Ast_Kind_Library_Path) { - bh_arr_push(context.wasm_module->library_paths, include->name); - } - - return 1; -} - -static b32 process_entity(Entity* ent) { - static char verbose_output_buffer[512]; - if (context.options->verbose_output == 3) { - if (ent->expr && ent->expr->token) - snprintf(verbose_output_buffer, 511, - "%20s | %24s (%d, %d) | %s:%i:%i \n", - entity_state_strings[ent->state], - entity_type_strings[ent->type], - (u32) ent->macro_attempts, - (u32) ent->micro_attempts, - ent->expr->token->pos.filename, - ent->expr->token->pos.line, - ent->expr->token->pos.column); - - else if (ent->expr) - snprintf(verbose_output_buffer, 511, - "%20s | %24s (%d, %d) \n", - entity_state_strings[ent->state], - entity_type_strings[ent->type], - (u32) ent->macro_attempts, - (u32) ent->micro_attempts); - } - - // CLEANUP: There should be a nicer way to track if the builtins have - // already been initialized. - static b32 builtins_initialized = 0; - - EntityState before_state = ent->state; - switch (before_state) { - case Entity_State_Error: - if (ent->type != Entity_Type_Error) { - onyx_report_error(ent->expr->token->pos, Error_Critical, "Error entity unexpected. This is definitely a compiler bug"); - } else { - onyx_report_error(ent->error->token->pos, Error_Critical, "Static error occured: '%b'", ent->error->error_msg->text, ent->error->error_msg->length); - } - break; - - case Entity_State_Parse_Builtin: - process_load_entity(ent); - ent->state = Entity_State_Finalized; - break; - - case Entity_State_Introduce_Symbols: - // Currently, introducing symbols is handled in the symbol resolution - // function. Maybe there should be a different place where that happens? - symres_entity(ent); - break; - - case Entity_State_Parse: - if (!builtins_initialized) { - builtins_initialized = 1; - initialize_builtins(context.ast_alloc); - introduce_build_options(context.ast_alloc); - } - - // GROSS - if (special_global_entities_remaining == 0) { - special_global_entities_remaining--; - initalize_special_globals(); - } - - if (process_load_entity(ent)) { - // GROSS - if (ent == runtime_info_types_entity - || ent == runtime_info_proc_tags_entity - || ent == runtime_info_foreign_entity) { - special_global_entities_remaining--; - } - - ent->state = Entity_State_Finalized; - } else { - ent->macro_attempts++; - } - break; - - case Entity_State_Resolve_Symbols: symres_entity(ent); break; - case Entity_State_Check_Types: check_entity(ent); break; - case Entity_State_Code_Gen: emit_entity(ent); break; - } - - b32 changed = ent->state != before_state; - if (context.options->verbose_output == 3) { - if (changed) printf("SUCCESS to %20s | %s", entity_state_strings[ent->state], verbose_output_buffer); - else printf("YIELD to %20s | %s", entity_state_strings[ent->state], verbose_output_buffer); - } - - return changed; -} - -// Just having fun with some visual output - brendanfh 2020/12/14 -#if defined(_BH_LINUX) -static void output_dummy_progress_bar() { - EntityHeap* eh = &context.entities; - if (bh_arr_length(eh->entities) == 0) return; - - static const char* state_colors[] = { - "\e[91m", "\e[93m", "\e[94m", "\e[93m", - "\e[97m", "\e[95m", "\e[96m", "\e[92m", "\e[91m", - }; - - printf("\e[2;1H"); - - for (i32 i = 0; i < Entity_State_Count - 1; i++) { - if (i % 2 == 0) printf("\n"); - printf("%s %25s \xe2\x96\x88 ", state_colors[i], entity_state_strings[i]); - } - - printf("\n\n"); - - for (i32 i = 0; i < Entity_Type_Count; i++) { - if (eh->type_count[i] == 0) printf("\e[90m"); - else if ((i32) eh->entities[0]->type == i) printf("\e[92m"); - else printf("\e[97m"); - - printf("%25s (%4d) | ", entity_type_strings[i], eh->type_count[i]); - - printf("\e[0K"); - for (i32 j = 0; j < Entity_State_Count; j++) { - if (eh->all_count[j][i] == 0) continue; - - printf("%s", state_colors[j]); - - i32 count = (eh->all_count[j][i] >> 5) + 1; - for (i32 c = 0; c < count * 2; c++) printf("\xe2\x96\x88"); - - printf("\e[0m"); - } - printf("\n"); - } -} -#endif - -static void dump_cycles() { - context.cycle_detected = 1; - Entity* ent; - - while (1) { - ent = entity_heap_top(&context.entities); - entity_heap_remove_top(&context.entities); - if (ent->state < Entity_State_Code_Gen) process_entity(ent); - else break; - - if (bh_arr_length(context.entities.entities) == 0) { - break; - } - } -} - -static i32 onyx_compile() { - u64 start_time = bh_time_curr(); - - if (context.options->fun_output) - printf("\e[2J"); - - while (!bh_arr_is_empty(context.entities.entities)) { - Entity* ent = entity_heap_top(&context.entities); - -#if defined(_BH_LINUX) - if (context.options->fun_output) { - output_dummy_progress_bar(); - - if (ent->expr->token) { - OnyxFilePos pos = ent->expr->token->pos; - printf("\e[0K%s on %s in %s:%d:%d\n", entity_state_strings[ent->state], entity_type_strings[ent->type], pos.filename, pos.line, pos.column); - } - - // Slowing things down for the effect - usleep(1000); - } -#endif - - entity_heap_remove_top(&context.entities); - b32 changed = process_entity(ent); - - // NOTE: VERY VERY dumb cycle breaking. Basically, remember the first entity that did - // not change (i.e. did not make any progress). Then everytime an entity doesn't change, - // check if it is the same entity. If it is, it means all other entities that were processed - // between the two occurences didn't make any progress either, and there must be a cycle. - // - brendanfh 2021/02/06 - // - // Because of the recent changes to the compiler architecture (again), this condition - // does not always hold anymore. There can be nodes that get scheduled multiple times - // before the "key" node that will unblock the progress. This means a more sophisticated - // cycle detection algorithm must be used. - // - static Entity* watermarked_node = NULL; - static u32 highest_watermark = 0; - if (!changed) { - if (!watermarked_node) { - watermarked_node = ent; - highest_watermark = bh_max(highest_watermark, ent->macro_attempts); - } - else if (watermarked_node == ent) { - if (ent->macro_attempts > highest_watermark) { - entity_heap_insert_existing(&context.entities, ent); - - if (context.cycle_almost_detected) { - dump_cycles(); - } else { - context.cycle_almost_detected = 1; - } - } - } - else if (watermarked_node->macro_attempts < ent->macro_attempts) { - watermarked_node = ent; - highest_watermark = bh_max(highest_watermark, ent->macro_attempts); - } - } else { - watermarked_node = NULL; - context.cycle_almost_detected = 0; - } - - if (onyx_has_errors()) return ONYX_COMPILER_PROGRESS_ERROR; - - if (ent->state != Entity_State_Finalized && ent->state != Entity_State_Failed) - entity_heap_insert_existing(&context.entities, ent); - } - - u64 duration = bh_time_duration(start_time); - - if (context.options->verbose_output > 0) { - // TODO: Replace these with bh_printf when padded formatting is added. - printf("\nStatistics:\n"); - printf(" Time taken: %lf seconds\n", (double) duration / 1000); - printf(" Processed %ld lines (%f lines/second).\n", lexer_lines_processed, ((f32) 1000 * lexer_lines_processed) / (duration)); - printf(" Processed %ld tokens (%f tokens/second).\n", lexer_tokens_processed, ((f32) 1000 * lexer_tokens_processed) / (duration)); - printf("\n"); - } - - return ONYX_COMPILER_PROGRESS_SUCCESS; -} - -static void link_wasm_module() { - Package *runtime_var_package = package_lookup("runtime.vars"); - assert(runtime_var_package); - - AstTyped *link_options_node = (AstTyped *) symbol_raw_resolve(runtime_var_package->scope, "link_options"); - Type *link_options_type = type_build_from_ast(context.ast_alloc, builtin_link_options_type); - - assert(unify_node_and_type(&link_options_node, link_options_type) == TYPE_MATCH_SUCCESS); - - OnyxWasmLinkOptions link_opts; - // CLEANUP: Properly handle this case. - assert(onyx_wasm_build_link_options_from_node(&link_opts, link_options_node)); - - onyx_wasm_module_link(context.wasm_module, &link_opts); -} - -static CompilerProgress onyx_flush_module() { - link_wasm_module(); - - // NOTE: Output to file - bh_file output_file; - if (bh_file_create(&output_file, context.options->target_file) != BH_FILE_ERROR_NONE) - return ONYX_COMPILER_PROGRESS_FAILED_OUTPUT; - - if (context.options->verbose_output) - bh_printf("Outputting to WASM file: %s\n", output_file.filename); - - // APPARENTLY... the WebAssembly Threading proposal says that the data segment initializations - // in a WASM module are copied into the linear memory EVERY time the module is instantiated, not - // just the first time. This means that if we are happily chugging along and modifying global state - // and then we spawn a thread, that thread will completely wipe all changes to the global and return - // it to its original state. This is horrible obviously, but the only thing that is more horrible is - // that the best way around this is to create a second WASM module that simply initializes the given - // data section. Then have a section module that is actually your code. For right now, this is going - // to be fine since the browser is really the only place that multi-threading can be used to any - // degree of competency. But still... This is god awful and I hope that there is some other way to - // around this down the line. - if (context.options->use_multi_threading && !context.options->use_post_mvp_features) { - bh_file data_file; - if (bh_file_create(&data_file, bh_aprintf(global_scratch_allocator, "%s.data", context.options->target_file)) != BH_FILE_ERROR_NONE) - return ONYX_COMPILER_PROGRESS_FAILED_OUTPUT; - - OnyxWasmModule* data_module = bh_alloc_item(global_heap_allocator, OnyxWasmModule); - *data_module = onyx_wasm_module_create(global_heap_allocator); - - data_module->data = context.wasm_module->data; - context.wasm_module->data = NULL; - - onyx_wasm_module_write_to_file(data_module, data_file); - onyx_wasm_module_write_to_file(context.wasm_module, output_file); - - bh_file_close(&data_file); - } else { - onyx_wasm_module_write_to_file(context.wasm_module, output_file); - } - - bh_file_close(&output_file); - - if (context.options->documentation_file != NULL) { - OnyxDocumentation docs = onyx_docs_generate(); - docs.format = Doc_Format_Human; - onyx_docs_emit(&docs, context.options->documentation_file); - } - - return ONYX_COMPILER_PROGRESS_SUCCESS; -} - -#ifdef ENABLE_RUN_WITH_WASMER -static b32 onyx_run() { - link_wasm_module(); - - bh_buffer code_buffer; - onyx_wasm_module_write_to_buffer(context.wasm_module, &code_buffer); - - onyx_run_initialize(context.options->debug_enabled); - - if (context.options->verbose_output > 0) - bh_printf("Running program:\n"); - - return onyx_run_wasm(code_buffer, context.options->passthrough_argument_count, context.options->passthrough_argument_data); -} -#endif - -int main(int argc, char *argv[]) { - - bh_scratch_init(&global_scratch, bh_heap_allocator(), 256 * 1024); // NOTE: 256 KiB - global_scratch_allocator = bh_scratch_allocator(&global_scratch); - - // SPEED: This used to be a managed heap allocator where all allocations - // were tracked and would be automatically freed at the end of execution. - // I don't know why I ever did that because that is the job of the operating - // system when a process exits. - global_heap_allocator = bh_heap_allocator(); - - CompileOptions compile_opts = compile_opts_parse(global_heap_allocator, argc, argv); - context_init(&compile_opts); - - CompilerProgress compiler_progress = ONYX_COMPILER_PROGRESS_ERROR; - switch (compile_opts.action) { - case ONYX_COMPILE_ACTION_PRINT_HELP: bh_printf(docstring); return 1; - - case ONYX_COMPILE_ACTION_COMPILE: - compiler_progress = onyx_compile(); - if (compiler_progress == ONYX_COMPILER_PROGRESS_SUCCESS) { - onyx_flush_module(); - } - break; - - #ifdef ENABLE_RUN_WITH_WASMER - case ONYX_COMPILE_ACTION_RUN: - compiler_progress = onyx_compile(); - if (compiler_progress == ONYX_COMPILER_PROGRESS_SUCCESS) { - if (!onyx_run()) { - compiler_progress = ONYX_COMPILER_PROGRESS_ERROR; - } - } - break; - #endif - - default: break; - } - - switch (compiler_progress) { - case ONYX_COMPILER_PROGRESS_ERROR: - onyx_errors_print(); - break; - - case ONYX_COMPILER_PROGRESS_FAILED_OUTPUT: - bh_printf_err("Failed to open file for writing: '%s'\n", compile_opts.target_file); - break; - - case ONYX_COMPILER_PROGRESS_SUCCESS: - break; - } - - context_free(); - - bh_scratch_free(&global_scratch); - // bh_managed_heap_free(&global_heap); - - return compiler_progress != ONYX_COMPILER_PROGRESS_SUCCESS; -} diff --git a/src/onyx_runtime.c b/src/onyx_runtime.c deleted file mode 100644 index 88a13553..00000000 --- a/src/onyx_runtime.c +++ /dev/null @@ -1,1444 +0,0 @@ - -#define BH_DEFINE -#include "bh.h" - -#define ONYX_LIBRARY_NAME onyx_runtime -#define ONYX_NO_SHORT_NAMES -#include "onyx_library.h" - -#ifdef _BH_LINUX - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif - -#include "types.h" // For POINTER_SIZE - -#define ONYX_FILE_ERROR_NONE 0 -#define ONYX_FILE_ERROR_NOT_FOUND 1 -#define ONYX_FILE_ERROR_EXISTS 2 -#define ONYX_FILE_ERROR_PERMISSION 3 -#define ONYX_FILE_ERROR_BAD_FILE 4 -#define ONYX_FILE_ERROR_BAD_MODE 5 - -ONYX_DEF(__file_open_impl, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - char *path_ptr = ONYX_PTR(params->data[0].of.i32); - int path_len = params->data[1].of.i32; - - char path[512] = {0}; - path_len = bh_min(path_len, 511); - strncpy(path, path_ptr, path_len); - path[path_len] = 0; - - int mode = params->data[2].of.i32; - - bh_file_mode bh_mode; - switch (mode) { - case 1: bh_mode = BH_FILE_MODE_READ; break; - case 2: bh_mode = BH_FILE_MODE_WRITE; break; - case 3: bh_mode = BH_FILE_MODE_APPEND; break; - } - - bh_file file; - bh_file_error error = bh_file_open_mode(&file, bh_mode, path); - if (error == BH_FILE_ERROR_INVALID) { - results->data[0] = WASM_I32_VAL(ONYX_FILE_ERROR_NOT_FOUND); - return NULL; - } - - *(u64 *) ONYX_PTR(params->data[3].of.i32) = (u64) file.fd; - results->data[0] = WASM_I32_VAL(ONYX_FILE_ERROR_NONE); - return NULL; -} - -ONYX_DEF(__file_close, (WASM_I64), (WASM_I32)) { - i64 fd = params->data[0].of.i64; - - bh_file file = { (bh_file_descriptor) fd }; - bh_file_error error = bh_file_close(&file); - if (error == BH_FILE_ERROR_INVALID) { - results->data[0] = WASM_I32_VAL(ONYX_FILE_ERROR_NOT_FOUND); - return NULL; - } - - results->data[0] = WASM_I32_VAL(ONYX_FILE_ERROR_NONE); - return NULL; -} - -ONYX_DEF(__file_exists, (WASM_I32, WASM_I32), (WASM_I32)) { - char *path_ptr = ONYX_PTR(params->data[0].of.i32); - int path_len = params->data[1].of.i32; - - char path[512] = {0}; - path_len = bh_min(path_len, 511); - strncpy(path, path_ptr, path_len); - path[path_len] = 0; - - results->data[0] = WASM_I32_VAL(bh_file_exists(path)); - return NULL; -} - -ONYX_DEF(__file_stat, (WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - char *path_ptr = ONYX_PTR(params->data[0].of.i32); - int path_len = params->data[1].of.i32; - - char path[512] = {0}; - path_len = bh_min(path_len, 511); - strncpy(path, path_ptr, path_len); - path[path_len] = 0; - - results->data[0] = WASM_I32_VAL(bh_file_stat(path, ONYX_PTR(params->data[2].of.i32))); - return NULL; -} - -ONYX_DEF(__file_remove, (WASM_I32, WASM_I32), (WASM_I32)) { - char *path_ptr = ONYX_PTR(params->data[0].of.i32); - int path_len = params->data[1].of.i32; - - char path[512] = {0}; - path_len = bh_min(path_len, 511); - strncpy(path, path_ptr, path_len); - path[path_len] = 0; - - results->data[0] = WASM_I32_VAL(bh_file_remove(path)); - return NULL; -} - -ONYX_DEF(__file_seek, (WASM_I64, WASM_I32, WASM_I32), (WASM_I32)) { - i64 fd = params->data[0].of.i64; - i32 offset = params->data[1].of.i32; - i32 whence = params->data[2].of.i32; - - bh_file file = { (bh_file_descriptor) fd }; - bh_file_whence bh_whence; - switch (whence) { - case 0: bh_whence = BH_FILE_WHENCE_BEGIN; break; - case 1: bh_whence = BH_FILE_WHENCE_CURRENT; break; - case 2: bh_whence = BH_FILE_WHENCE_END; break; - } - - i64 new_offset = bh_file_seek(&file, offset, whence); - results->data[0] = WASM_I32_VAL((i32) new_offset); - return NULL; -} - -ONYX_DEF(__file_tell, (WASM_I64), (WASM_I32)) { - i64 fd = params->data[0].of.i64; - bh_file file = { (bh_file_descriptor) fd }; - results->data[0] = WASM_I32_VAL(bh_file_tell(&file)); - return NULL; -} - -ONYX_DEF(__file_read, (WASM_I64, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - i64 fd = params->data[0].of.i64; - bh_file file = { (bh_file_descriptor) fd }; - - i32 curr_pos = bh_file_tell(&file); - b32 success = bh_file_read_at(&file, - bh_file_tell(&file), - ONYX_PTR(params->data[1].of.i32), - params->data[2].of.i32, - (i64 *) ONYX_PTR(params->data[3].of.i32)); - - bh_file_seek_to(&file, curr_pos + *(i32 *) ONYX_PTR(params->data[3].of.i32)); - - results->data[0] = WASM_I32_VAL(0); - if (!success) results->data[0] = WASM_I32_VAL(2); - return NULL; -} - -ONYX_DEF(__file_write, (WASM_I64, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - i64 fd = params->data[0].of.i64; - bh_file file = { (bh_file_descriptor) fd }; - - i32 curr_pos = bh_file_tell(&file); - b32 success = bh_file_write_at(&file, - bh_file_tell(&file), - ONYX_PTR(params->data[1].of.i32), - params->data[2].of.i32, - (i64 *) ONYX_PTR(params->data[3].of.i32)); - - bh_file_seek_to(&file, curr_pos + *(i32 *) ONYX_PTR(params->data[3].of.i32)); - - results->data[0] = WASM_I32_VAL(0); - if (!success) results->data[0] = WASM_I32_VAL(2); - return NULL; -} - -ONYX_DEF(__file_flush, (WASM_I64), (WASM_I32)) { - i64 fd = params->data[0].of.i64; - bh_file file = { (bh_file_descriptor) fd }; - bh_file_flush(&file); - results->data[0] = WASM_I32_VAL(0); - return NULL; -} - -ONYX_DEF(__file_size, (WASM_I64), (WASM_I32)) { - i64 fd = params->data[0].of.i64; - bh_file file = { (bh_file_descriptor) fd }; - results->data[0] = WASM_I32_VAL(bh_file_size(&file)); - return NULL; -} - -ONYX_DEF(__file_get_standard, (WASM_I32, WASM_I32), (WASM_I32)) { - bh_file_standard standard = (bh_file_standard) params->data[0].of.i32; - - bh_file file; - bh_file_error error = bh_file_get_standard(&file, standard); - if (error == BH_FILE_ERROR_NONE) { - *(u64 *) ONYX_PTR(params->data[1].of.i32) = (u64) file.fd; - } - - results->data[0] = WASM_I32_VAL(error == BH_FILE_ERROR_NONE); - return NULL; -} - -ONYX_DEF(__file_rename, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - char *old_path_ptr = ONYX_PTR(params->data[0].of.i32); - int old_path_len = params->data[1].of.i32; - - char old_path[512] = {0}; - old_path_len = bh_min(old_path_len, 511); - strncpy(old_path, old_path_ptr, old_path_len); - old_path[old_path_len] = 0; - - char *new_path_ptr = ONYX_PTR(params->data[2].of.i32); - int new_path_len = params->data[3].of.i32; - - char new_path[512] = {0}; - new_path_len = bh_min(new_path_len, 511); - strncpy(new_path, new_path_ptr, new_path_len); - new_path[new_path_len] = 0; - -#ifdef _BH_WINDOWS - results->data[0] = WASM_I32_VAL(MoveFileA(old_path, new_path)); - return NULL; -#endif - -#ifdef _BH_LINUX - results->data[0] = WASM_I32_VAL(rename(old_path, new_path) == 0); - return NULL; -#endif -} - -// -// Directories -// - -ONYX_DEF(__dir_open, (WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - char *path_ptr = ONYX_PTR(params->data[0].of.i32); - int path_len = params->data[1].of.i32; - - char path[512] = {0}; - path_len = bh_min(path_len, 511); - strncpy(path, path_ptr, path_len); - path[path_len] = 0; - -#ifdef _BH_WINDOWS - for (int i=0; ihndl = FindFirstFileA(path, &dir->found_file); - if (dir->hndl == INVALID_HANDLE_VALUE) { - results->data[0] = WASM_I32_VAL(0); - return NULL; - } - - *(u64 *) ONYX_PTR(params->data[2].of.i32) = (u64) dir; - - results->data[0] = WASM_I32_VAL(1); - return NULL; -#endif - -#ifdef _BH_LINUX - DIR* dir = opendir(path); - *(u64 *) ONYX_PTR(params->data[2].of.i32) = (u64) dir; - results->data[0] = WASM_I32_VAL(dir != NULL); - return NULL; -#endif -} - -// (DIR*, PTR) -> BOOL -ONYX_DEF(__dir_read, (WASM_I64, WASM_I32), (WASM_I32)) { -#ifdef _BH_WINDOWS - Windows_Directory_Opened* dir = (Windows_Directory_Opened *) params->data[0].of.i64; - if (dir == NULL) { - results->data[0] = WASM_I32_VAL(0); - return NULL; - } - - do { - BOOL success = FindNextFileA(dir->hndl, &dir->found_file); - if (!success) { - results->data[0] = WASM_I32_VAL(0); - return NULL; - } - } while (!strcmp(dir->found_file.cFileName, ".") || !strcmp(dir->found_file.cFileName, "..")); - - u32 out = params->data[1].of.i32; - assert(out != 0); - - *(u32 *) ONYX_PTR(out + 0) = (dir->found_file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 3 : 4; - *(u32 *) ONYX_PTR(out + 4) = 0; - *(u32 *) ONYX_PTR(out + 8) = strlen(dir->found_file.cFileName); - strncpy(ONYX_PTR(out + 12), dir->found_file.cFileName, 256); - - results->data[0] = WASM_I32_VAL(1); - return NULL; -#endif - -#ifdef _BH_LINUX - DIR* dir = (DIR *) params->data[0].of.i64; - if (dir == NULL) { - results->data[0] = WASM_I32_VAL(0); - return NULL; - } - - struct dirent *ent; - while (1) { - ent = readdir(dir); - if (ent == NULL) { - results->data[0] = WASM_I32_VAL(0); - return NULL; - } - - // Skip the current directory and parent directory - if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) break; - } - - u32 type = 0; - switch (ent->d_type) { - case DT_UNKNOWN: break; - case DT_BLK: type = 1; break; - case DT_CHR: type = 2; break; - case DT_DIR: type = 3; break; - case DT_LNK: type = 5; break; - case DT_REG: type = 4; break; - default: type = 6; break; - } - - u32 out = params->data[1].of.i32; - assert(out != 0); - - *(u32 *) ONYX_PTR(out + 0) = type; - *(u32 *) ONYX_PTR(out + 4) = (u32) ent->d_ino; - *(u32 *) ONYX_PTR(out + 8) = strlen(ent->d_name); - strncpy(ONYX_PTR(out + 12), ent->d_name, 256); - - results->data[0] = WASM_I32_VAL(1); - return NULL; -#endif -} - -ONYX_DEF(__dir_close, (WASM_I64), ()) { -#ifdef _BH_WINDOWS - Windows_Directory_Opened* dir = (Windows_Directory_Opened *) params->data[0].of.i64; - - FindClose(dir->hndl); - free(dir); - - return NULL; -#endif - -#ifdef _BH_LINUX - DIR* dir = (DIR *) params->data[0].of.i64; - if (dir == NULL) return NULL; - - closedir(dir); -#endif - return NULL; -} - -ONYX_DEF(__dir_create, (WASM_I32, WASM_I32), (WASM_I32)) { - char *path_ptr = ONYX_PTR(params->data[0].of.i32); - int path_len = params->data[1].of.i32; - - char path[512] = {0}; - path_len = bh_min(path_len, 511); - strncpy(path, path_ptr, path_len); - path[path_len] = 0; - -#ifdef _BH_WINDOWS - results->data[0] = WASM_I32_VAL(CreateDirectoryA(path)); - return NULL; -#endif - -#ifdef _BH_LINUX - results->data[0] = WASM_I32_VAL(mkdir(path, 0777) == 0); - return NULL; -#endif -} - -ONYX_DEF(__dir_remove, (WASM_I32, WASM_I32), (WASM_I32)) { - char *path_ptr = ONYX_PTR(params->data[0].of.i32); - int path_len = params->data[1].of.i32; - - char path[512] = {0}; - path_len = bh_min(path_len, 511); - strncpy(path, path_ptr, path_len); - path[path_len] = 0; - -#ifdef _BH_WINDOWS - results->data[0] = WASM_I32_VAL(RemoveDirectoryA(path)); - return NULL; -#endif - -#ifdef _BH_LINUX - results->data[0] = WASM_I32_VAL(rmdir(path) == 0); - return NULL; -#endif -} - -// -// THREADS -// -typedef struct OnyxThread { - i32 id; - i32 tls_base; - i32 funcidx; - i32 dataptr; - wasm_instance_t* instance; - - #ifdef _BH_LINUX - pthread_t thread; - #endif - - #ifdef _BH_WINDOWS - HANDLE thread_handle; - i32 thread_id; - #endif -} OnyxThread; - -static bh_arr(OnyxThread) threads = NULL; - -#ifdef _BH_LINUX -static void *onyx_run_thread(void *data) { -#endif -#ifdef _BH_WINDOWS -static i32 onyx_run_thread(void *data) { -#endif - OnyxThread *thread = (OnyxThread *) data; - - wasm_store_t *wasm_store = runtime->wasm_store_new(runtime->wasm_engine); - - wasm_trap_t* traps = NULL; - thread->instance = runtime->wasm_instance_new(wasm_store, runtime->wasm_module, &runtime->wasm_imports, &traps); - - wasm_extern_t* start_extern = runtime->wasm_extern_lookup_by_name(runtime->wasm_module, thread->instance, "_thread_start"); - wasm_func_t* start_func = runtime->wasm_extern_as_func(start_extern); - - wasm_extern_t* exit_extern = runtime->wasm_extern_lookup_by_name(runtime->wasm_module, thread->instance, "_thread_exit"); - wasm_func_t* exit_func = runtime->wasm_extern_as_func(exit_extern); - - wasm_trap_t* trap=NULL; - - // NOTE: This is cached in a local variable because there is a tiny chance that if a lot of threads are created - // then the backing array for the thread handles will move and thread* we have well not be valid. I'm betting on this - // not happening before now in the function; however, it *could* happen before we call thread_exit. This would be bad - // because of the normal reasons why accessing memory that you don't own any more is bad. - i32 thread_id = thread->id; - - { // Call the _thread_start procedure - wasm_val_t args[] = { WASM_I32_VAL(thread_id), WASM_I32_VAL(thread->tls_base), WASM_I32_VAL(thread->funcidx), WASM_I32_VAL(thread->dataptr) }; - wasm_val_vec_t results = { 0, 0 }; - wasm_val_vec_t args_array = WASM_ARRAY_VEC(args); - - trap = runtime->wasm_func_call(start_func, &args_array, &results); - if (trap != NULL) { - bh_printf("THREAD: %d\n", thread_id); - runtime->onyx_print_trap(trap); - } - } - - { // Call the _thread_exit procedure - wasm_val_t args[] = { WASM_I32_VAL(thread_id) }; - wasm_val_vec_t results = { 0, 0 }; - wasm_val_vec_t args_array = WASM_ARRAY_VEC(args); - - trap = runtime->wasm_func_call(exit_func, &args_array, &results); - } - - runtime->wasm_store_delete(wasm_store); - - return 0; -} - -ONYX_DEF(__spawn_thread, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - if (threads == NULL) bh_arr_new(bh_heap_allocator(), threads, 128); - bh_arr_insert_end(threads, 1); - OnyxThread *thread = &bh_arr_last(threads); - - thread->id = params->data[0].of.i32; - thread->tls_base = params->data[1].of.i32; - thread->funcidx = params->data[2].of.i32; - thread->dataptr = params->data[3].of.i32; - - #ifdef _BH_LINUX - pthread_create(&thread->thread, NULL, onyx_run_thread, thread); - #endif - - #ifdef _BH_WINDOWS - // thread->thread_handle = CreateThread(NULL, 0, onyx_run_thread, thread, 0, &thread->thread_id); - thread->thread_handle = (HANDLE) _beginthreadex(NULL, 0, onyx_run_thread, thread, 0, &thread->thread_id); - #endif - - results->data[0] = WASM_I32_VAL(1); - return NULL; -} - -ONYX_DEF(__kill_thread, (WASM_I32), (WASM_I32)) { - i32 thread_id = params->data[0].of.i32; - - i32 i = 0; - bh_arr_each(OnyxThread, thread, threads) { - if (thread->id == thread_id) { - #ifdef _BH_LINUX - // This leads to some weirdness and bugs... - // - pthread_kill(thread->thread, SIGKILL); - #endif - - #ifdef _BH_WINDOWS - TerminateThread(thread->thread_handle, 0); - CloseHandle(thread->thread_handle); - #endif - - bh_arr_deleten(threads, i, 1); - results->data[0] = WASM_I32_VAL(1); - return NULL; - } - - i++; - } - - results->data[0] = WASM_I32_VAL(0); - return NULL; -} - - - - -// -// PROCESS -// -#define ONYX_PROCESS_MAGIC_NUMBER 0xdeadfadebabecafe -typedef struct OnyxProcess { - u64 magic_number; - -#ifdef _BH_LINUX - // Pipes - i32 proc_to_host[2]; - i32 host_to_proc[2]; - - pid_t pid; -#endif - -#ifdef _BH_WINDOWS - HANDLE proc_to_host_read; - HANDLE proc_to_host_write; - HANDLE host_to_proc_read; - HANDLE host_to_proc_write; - - PROCESS_INFORMATION proc_info; -#endif -} OnyxProcess; - -ONYX_DEF(__process_spawn, (WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I64)) { - char* process_str = ONYX_PTR(params->data[0].of.i32); - i32 process_len = params->data[1].of.i32; - i32 args_ptr = params->data[2].of.i32; - i32 args_len = params->data[3].of.i32; - b32 blocking_io = !params->data[4].of.i32; - char *cwd_str = ONYX_PTR(params->data[5].of.i32); - i32 cwd_len = params->data[6].of.i32; - - char process_path[1024]; - process_len = bh_min(1023, process_len); - memcpy(process_path, process_str, process_len); - process_path[process_len] = '\0'; - - char starting_dir[1024]; - if (cwd_len > 0) { - cwd_len = bh_min(1023, cwd_len); - memcpy(starting_dir, cwd_str, cwd_len); - starting_dir[cwd_len] = '\0'; - } - - OnyxProcess *process = malloc(sizeof(OnyxProcess)); - memset(process, 0, sizeof(*process)); - process->magic_number = ONYX_PROCESS_MAGIC_NUMBER; - - #ifdef _BH_LINUX - // :Security - alloca a user controlled string. - char **process_args = alloca(sizeof(char *) * (args_len + 2)); - byte_t* array_loc = ONYX_PTR(args_ptr); - fori (i, 0, args_len) { - char *arg_str = ONYX_PTR(*(i32 *) (array_loc + i * 2 * POINTER_SIZE)); - i32 arg_len = *(i32 *) (array_loc + i * 2 * POINTER_SIZE + POINTER_SIZE); - - char *arg = alloca(sizeof(char) * (arg_len + 1)); - memcpy(arg, arg_str, arg_len); - arg[arg_len] = '\0'; - process_args[i + 1] = arg; - } - process_args[0] = process_path; - process_args[args_len + 1] = NULL; - - if (pipe(process->proc_to_host) || pipe(process->host_to_proc)) { - wasm_val_init_ptr(&results->data[0], NULL); // Failed to run - return NULL; - } - - pid_t pid; - switch (pid = fork()) { - case -1: // Bad fork - wasm_val_init_ptr(&results->data[0], NULL); // Failed to run - - close(process->proc_to_host[0]); - close(process->proc_to_host[1]); - close(process->host_to_proc[0]); - close(process->host_to_proc[1]); - break; - - case 0: // Child process - close(process->proc_to_host[0]); - close(process->host_to_proc[1]); - dup2(process->host_to_proc[0], 0); // Map the output to the pipe - dup2(process->proc_to_host[1], 1); // Map the output to the pipe - dup2(process->proc_to_host[1], 2); // Stderr to stdout - - if (!blocking_io) { - fcntl(0, F_SETFL, O_NONBLOCK); - fcntl(1, F_SETFL, O_NONBLOCK); - } - - if (cwd_len > 0) { - chdir(starting_dir); // Switch current working directory. - } - - execvp(process_path, process_args); - exit(-1); - break; - - default: { - process->pid = pid; - close(process->host_to_proc[0]); - close(process->proc_to_host[1]); - - wasm_val_init_ptr(&results->data[0], process); - break; - } - } - - #endif - - #ifdef _BH_WINDOWS - // CLEANUP CLEANUP CLEANUP: This is so freaking bad... - char cmdLine[2048]; - memset(cmdLine, 0, 2048); - strncat(cmdLine, "\"", 2047); - strncat(cmdLine, process_path, 2047); - strncat(cmdLine, "\"", 2047); - - byte_t* array_loc = ONYX_PTR(args_ptr); - fori (i, 0, args_len) { - char *arg_str = ONYX_PTR(*(i32 *) (array_loc + i * 2 * POINTER_SIZE)); - i32 arg_len = *(i32 *) (array_loc + i * 2 * POINTER_SIZE + 4); - - strncat(cmdLine, " \"", 2047); - strncat(cmdLine, arg_str, arg_len); - strncat(cmdLine, "\"", 2047); - } - - STARTUPINFOA startup; - memset(&startup, 0, sizeof startup); - startup.cb = sizeof(startup); - - SECURITY_ATTRIBUTES saAttr; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.lpSecurityDescriptor = NULL; - saAttr.bInheritHandle = 1; - - i32 success = 1; - success = success && CreatePipe(&process->host_to_proc_read, &process->host_to_proc_write, &saAttr, 4096); - success = success && CreatePipe(&process->proc_to_host_read, &process->proc_to_host_write, &saAttr, 4096); - if (!success) { - // printf("FAILED TO CREATE PIPES: %d\n", GetLastError()); - wasm_val_init_ptr(&results->data[0], NULL); // Failed to run @LEAK - return NULL; - } - - success = SetHandleInformation(process->proc_to_host_read, 1 /* HANDLE_FLAG_INHERIT */, 0); - success = success && SetHandleInformation(process->host_to_proc_write, 1 /* HANDLE_FLAG_INHERIT */, 0); - if (!success) { - // printf("FAILED TO CONFIGURE PIPES: %d\n", GetLastError()); - wasm_val_init_ptr(&results->data[0], NULL); // Failed to run @LEAK - return NULL; - } - - startup.hStdInput = process->host_to_proc_read; - startup.hStdOutput = process->proc_to_host_write; - startup.hStdError = process->proc_to_host_write; - - startup.dwFlags |= STARTF_USESTDHANDLES; - - memset(&process->proc_info, 0, sizeof process->proc_info); - - char *working_dir = NULL; - if (cwd_len > 0) { - working_dir = starting_dir; - } - - success = CreateProcessA(process_path, cmdLine, &saAttr, &saAttr, 1, 0, NULL, working_dir, &startup, &process->proc_info); - if (!success) { - printf("FAILED TO CREATE PROCESS: %d\n", GetLastError()); - wasm_val_init_ptr(&results->data[0], NULL); // Failed to run @LEAK - return NULL; - } - - CloseHandle(process->proc_to_host_write); - CloseHandle(process->host_to_proc_read); - wasm_val_init_ptr(&results->data[0], process); - #endif - - return NULL; -} - -ONYX_DEF(__process_read, (WASM_I64, WASM_I32, WASM_I32), (WASM_I32)) { - OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; - if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { - results->data[0] = WASM_I32_VAL(0); - return NULL; - } - - i32 output_ptr = params->data[1].of.i32; - i32 output_len = params->data[2].of.i32; - u8 *buffer = ONYX_PTR(output_ptr); - - i32 bytes_read; - #ifdef _BH_LINUX - bytes_read = read(process->proc_to_host[0], buffer, output_len); - if (bytes_read < 0) { - switch (errno) { - case EAGAIN: bytes_read = 0; break; - case EBADF: bytes_read = -1; break; - default: bytes_read = -2; break; - } - } - #endif - - #ifdef _BH_WINDOWS - i32 success = ReadFile(process->proc_to_host_read, buffer, output_len, &bytes_read, NULL); - if (!success) { - bytes_read = -1; - } - #endif - - results->data[0] = WASM_I32_VAL(bytes_read); - return NULL; -} - -ONYX_DEF(__process_write, (WASM_I64, WASM_I32, WASM_I32), (WASM_I32)) { - OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; - if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { - results->data[0] = WASM_I32_VAL(0); - return NULL; - } - - i32 input_ptr = params->data[1].of.i32; - i32 input_len = params->data[2].of.i32; - u8 *buffer = ONYX_PTR(input_ptr); - - i32 bytes_written; - #ifdef _BH_LINUX - bytes_written = write(process->host_to_proc[1], buffer, input_len); - bytes_written = bh_max(bytes_written, 0); // Silently consume errors - #endif - - #ifdef _BH_WINDOWS - i32 success = WriteFile(process->host_to_proc_write, buffer, input_len, &bytes_written, NULL); - if (!success) bytes_written = 0; - #endif - - results->data[0] = WASM_I32_VAL(bytes_written); - return NULL; -} - -ONYX_DEF(__process_kill, (WASM_I64), (WASM_I32)) { - OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; - if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { - results->data[0] = WASM_I32_VAL(0); - return NULL; - } - - #ifdef _BH_LINUX - i32 failed = kill(process->pid, SIGKILL); - results->data[0] = WASM_I32_VAL(!failed); - #endif - - #ifdef _BH_WINDOWS - i32 success = TerminateProcess(process->proc_info.hProcess, 1); - results->data[0] = WASM_I32_VAL(success ? 1 : 0); - #endif - - return NULL; -} - -ONYX_DEF(__process_wait, (WASM_I64), (WASM_I32)) { - OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; - if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { - results->data[0] = WASM_I32_VAL(1); - return NULL; - } - - #ifdef _BH_LINUX - i32 status; - waitpid(process->pid, &status, 0); - - i32 exit_code = WEXITSTATUS(status); - results->data[0] = WASM_I32_VAL(exit_code != 0 ? 2 : 0); - #endif - - #ifdef _BH_WINDOWS - DWORD exitCode; - while (1) { - if (!WaitForSingleObject(process->proc_info.hProcess, INFINITE)) { - // HACK HACK HACK - DWORD error = GetLastError(); - if (error != 109 && error != 6) { - // printf("ERROR IN WAIT FOR SINGLE: %d\n", error); - results->data[0] = WASM_I32_VAL(1); - return NULL; - } - } - - if (!GetExitCodeProcess(process->proc_info.hProcess, &exitCode)) { - // HACK HACK HACK - // Apparently, I'm doing something wrong (maybe?) where the process handle becomes - // invalid and causes error 6 "invalid handle". So I think I can safely assume that - // if that is the case, then the process exited? probably successfuly? hopefully? - // Honestly I don't know and I can't find any documentation describing when a process - // handle goes invalid, other than after you close it explicitly. But in the run_tests - // script, I'm not calling either process_kill or process_destroy, which are the only - // other functions that close the process handle. So I'm left in the dark as to why this - // is happening, but oh well. This works for now. - // - brendanfh 2021/12/03 - if (GetLastError() == 6) { - exitCode = 0; - break; - } - - results->data[0] = WASM_I32_VAL(3); - return NULL; - } - - // 259 is STILL_ACTIVE (aka STATUS_PENDING), which means that the process has not yet exited - if (exitCode != 259) break; - } - - results->data[0] = WASM_I32_VAL(exitCode != 0 ? 2 : 0); - #endif - - return NULL; -} - -ONYX_DEF(__process_destroy, (WASM_I64), ()) { - OnyxProcess *process = (OnyxProcess *) params->data[0].of.i64; - if (process == NULL || process->magic_number != ONYX_PROCESS_MAGIC_NUMBER) { - return NULL; - } - - #ifdef _BH_LINUX - close(process->proc_to_host[0]); - close(process->host_to_proc[1]); - #endif - - #ifdef _BH_WINDOWS - if (!CloseHandle(process->proc_info.hThread) - || !CloseHandle(process->proc_info.hProcess)) { - // printf("ERROR CLOSING HANDLES: %d\n", GetLastError()); - } - #endif - - free(process); - - return NULL; -} - -ONYX_DEF(__args_get, (WASM_I32, WASM_I32), ()) { - if (runtime->argc == 0 || runtime->argv == NULL) { - return NULL; - } - - i32 buffer_base = params->data[1].of.i32; - - for (int i=0; iargc; i++) { - // Should this be strncpy? What would the length be? - strcpy(ONYX_PTR(buffer_base), runtime->argv[i]); - *(i32 *) ONYX_PTR(params->data[0].of.i32 + i * 4) = buffer_base; - buffer_base += strlen(runtime->argv[i]) + 1; - } - - return NULL; -} - -ONYX_DEF(__args_sizes_get, (WASM_I32, WASM_I32), ()) { - *(i32 *) ONYX_PTR(params->data[0].of.i32) = runtime->argc; - - i32 argv_size = 0; - for (int i=0; iargc; i++) { - argv_size += strlen(runtime->argv[i]) + 1; - } - - *(i32 *) ONYX_PTR(params->data[1].of.i32) = argv_size; - return NULL; -} - -ONYX_DEF(__exit, (WASM_I32), ()) { - exit(params->data[0].of.i32); - return NULL; -} - -ONYX_DEF(__sleep, (WASM_I32), ()) { - #ifdef _BH_LINUX - usleep(params->data[0].of.i32 * 1000); - #endif - - #ifdef _BH_WINDOWS - Sleep(params->data[0].of.i32); - #endif - return NULL; -} - -ONYX_DEF(__time, (), (WASM_I64)) { - results->data[0] = WASM_I64_VAL(bh_time_curr()); - return NULL; -} - - - - -// -// Dates and Times -// -ONYX_DEF(__time_localtime, (WASM_I64, WASM_I32), ()) { - u64 t = params->data[0].of.i64; - *(struct tm *) ONYX_PTR(params->data[1].of.i32) = *localtime(&t); - return NULL; -} - -ONYX_DEF(__time_gmtime, (WASM_I64, WASM_I32), ()) { - u64 t = params->data[0].of.i64; - *(struct tm *) ONYX_PTR(params->data[1].of.i32) = *gmtime(&t); - return NULL; -} - -ONYX_DEF(__time_strftime, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - u32 len = strftime(ONYX_PTR(params->data[0].of.i32), params->data[1].of.i32, ONYX_PTR(params->data[2].of.i32), ONYX_PTR(params->data[3].of.i32)); - results->data[0] = WASM_I32_VAL(len); - return NULL; -} - -ONYX_DEF(__time_strptime, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - #if defined(_BH_LINUX) - char *rem = strptime(ONYX_PTR(params->data[0].of.i32), params->data[1].of.i32, ONYX_PTR(params->data[2].of.i32), ONYX_PTR(params->data[3].of.i32)); - results->data[0] = WASM_I32_VAL(rem != NULL); - #else - results->data[0] = WASM_I32_VAL(0); - #endif - return NULL; -} - - - -// -// Networking -// -struct onyx_socket_addr { - unsigned short family; - unsigned short port; - unsigned int addr; -}; - -static inline int onyx_socket_domain(int i) { - #if defined(_BH_LINUX) - switch (i) { // :EnumDependent - case 0: return AF_UNIX; - case 1: return AF_INET; - case 2: return AF_INET6; - default: return -1; - } - #elif defined(_BH_WINDOWS) - return -1; - #endif -} - -static inline int onyx_socket_protocol(int i) { - #if defined(_BH_LINUX) - switch (i) { // :EnumDependent - case 0: return SOCK_STREAM; - case 1: return SOCK_DGRAM; - default: return -1; - } - #elif defined(_BH_WINDOWS) - return -1; - #endif -} - -ONYX_DEF(__net_create_socket, (WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - - #ifdef _BH_LINUX - int domain = onyx_socket_domain(params->data[1].of.i32); - if (domain == -1) goto bad_settings; - - int type = onyx_socket_protocol(params->data[2].of.i32); - if (type == -1) goto bad_settings; - - *((int *) ONYX_PTR(params->data[0].of.i32)) = socket(domain, type, 0); - - results->data[0] = WASM_I32_VAL(0); - return NULL; - #endif - - #ifdef _BH_WINDOWS - #endif - -bad_settings: - results->data[0] = WASM_I32_VAL(1); // :EnumDependent - return NULL; -} - -ONYX_DEF(__net_close_socket, (WASM_I32), ()) { - #ifdef _BH_LINUX - shutdown(params->data[0].of.i32, SHUT_RDWR); - close(params->data[0].of.i32); - #endif - - #ifdef _BH_WINDOWS - #endif - - return NULL; -} - -ONYX_DEF(__net_setting, (WASM_I32, WASM_I32, WASM_I32), ()) { - #ifdef _BH_LINUX - switch (params->data[1].of.i32) { - case 1: { // :EnumDependent Non-Blocking - int s = params->data[0].of.i32; - int flags = fcntl(s, F_GETFL, 0); - if (params->data[2].of.i32) { - flags |= O_NONBLOCK; - } else { - flags &= ~O_NONBLOCK; - } - - fcntl(s, F_SETFL, flags); - break; - } - - case 2: { // :EnumDependent Broadcast - int s = params->data[0].of.i32; - setsockopt(s, SOL_SOCKET, SO_BROADCAST, (void *) ¶ms->data[2].of.i32, sizeof(int)); - break; - } - } - #endif - - return NULL; -} - -ONYX_DEF(__net_bind, (WASM_I32, WASM_I32), (WASM_I32)) { - - #ifdef _BH_LINUX - int res = -1; - - struct onyx_socket_addr *oaddr = (void *) ONYX_PTR(params->data[1].of.i32); - int family = onyx_socket_domain(oaddr->family); - int port = oaddr->port; - - switch (family) { - case AF_INET: { - struct sockaddr_in bind_addr; - memset(&bind_addr, 0, sizeof(bind_addr)); - - bind_addr.sin_family = AF_INET; - bind_addr.sin_addr.s_addr = htonl(oaddr->addr); - bind_addr.sin_port = htons(port); - - res = bind(params->data[0].of.i32, &bind_addr, sizeof(bind_addr)); - break; - } - - case AF_INET6: { - struct sockaddr_in6 bind_addr; - memset(&bind_addr, 0, sizeof(bind_addr)); - - bind_addr.sin6_family = AF_INET6; - memcpy(&bind_addr.sin6_addr.s6_addr, (void *) &oaddr->addr, 16); - bind_addr.sin6_port = htons(port); - - res = bind(params->data[0].of.i32, &bind_addr, sizeof(bind_addr)); - break; - } - - case AF_UNIX: { - struct sockaddr_un bind_addr; - memset(&bind_addr, 0, sizeof(bind_addr)); - - bind_addr.sun_family = AF_UNIX; - strncpy(&bind_addr.sun_path, (char *) &oaddr->addr, 108); - - res = bind(params->data[0].of.i32, &bind_addr, sizeof(bind_addr)); - break; - } - } - - results->data[0] = WASM_I32_VAL(res >= 0); - #endif - - #ifdef _BH_WINDOWS - #endif - - return NULL; -} - -ONYX_DEF(__net_listen, (WASM_I32, WASM_I32), ()) { - #ifdef _BH_LINUX - listen(params->data[0].of.i32, params->data[1].of.i32); - #endif - - #ifdef _BH_WINDOWS - #endif - - return NULL; -} - -ONYX_DEF(__net_accept, (WASM_I32, WASM_I32), (WASM_I32)) { - #ifdef _BH_LINUX - struct sockaddr_in client_addr; - int client_len = sizeof(client_addr); - memset(&client_addr, 0, client_len); - - int client_socket = accept(params->data[0].of.i32, &client_addr, &client_len); - - struct onyx_socket_addr* out_addr = (struct onyx_socket_addr *) ONYX_PTR(params->data[1].of.i32); - out_addr->family = client_addr.sin_family; - out_addr->port = ntohs(client_addr.sin_port); - out_addr->addr = ntohl(client_addr.sin_addr.s_addr); - - results->data[0] = WASM_I32_VAL(client_socket); - #endif - - return NULL; -} - -ONYX_DEF(__net_connect_unix, (WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - #ifdef _BH_LINUX - int hostlen = params->data[2].of.i32; - char *hostname = alloca(hostlen + 1); - memcpy(hostname, ONYX_PTR(params->data[1].of.i32), hostlen); - hostname[hostlen] = '\0'; - - struct sockaddr_un server_addr; - memset(&server_addr, 0, sizeof(server_addr)); - - server_addr.sun_family = AF_UNIX; // See comment above - memcpy((char *)&server_addr.sun_path, hostname, hostlen); - - int result = connect(params->data[0].of.i32, &server_addr, sizeof(server_addr)); - if (result == 0) results->data[0] = WASM_I32_VAL(0); - else results->data[0] = WASM_I32_VAL(3); // :EnumDependent - #endif - - return NULL; -} - -ONYX_DEF(__net_connect_ipv4, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - #ifdef _BH_LINUX - int hostlen = params->data[2].of.i32; - char *hostname = alloca(hostlen + 1); - memcpy(hostname, ONYX_PTR(params->data[1].of.i32), hostlen); - hostname[hostlen] = '\0'; - - struct hostent *host; - host = gethostbyname(hostname); // TODO: Replace this call, as it is obselete. - if (host == NULL) { - results->data[0] = WASM_I32_VAL(2); // :EnumDependent - return NULL; - } - - struct sockaddr_in server_addr; - memset(&server_addr, 0, sizeof(server_addr)); - - server_addr.sin_family = AF_INET; // See comment above - memcpy((char *)&server_addr.sin_addr.s_addr, (char *)host->h_addr, host->h_length); - server_addr.sin_port = htons(params->data[3].of.i32); - - int result = connect(params->data[0].of.i32, &server_addr, sizeof(server_addr)); - if (result == 0) results->data[0] = WASM_I32_VAL(0); - else results->data[0] = WASM_I32_VAL(3); // :EnumDependent - - return NULL; - #endif - - #ifdef _BH_WINDOWS - #endif -} - -ONYX_DEF(__net_send, (WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - #ifdef _BH_LINUX - // TODO: The flags at the end should be controllable. - int sent = send(params->data[0].of.i32, ONYX_PTR(params->data[1].of.i32), params->data[2].of.i32, MSG_NOSIGNAL); - results->data[0] = WASM_I32_VAL(sent); - #endif - - return NULL; -} - -ONYX_DEF(__net_sendto, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - #ifdef _BH_LINUX - struct sockaddr_in dest_addr; - int dest_addr_len = sizeof(dest_addr); - memset(&dest_addr, 0, dest_addr_len); - - struct onyx_socket_addr *o_addr = (struct onyx_socket_addr *) ONYX_PTR(params->data[3].of.i32); - dest_addr.sin_family = AF_INET; // TODO: See other comments related to AF_NET above. - dest_addr.sin_port = htons(o_addr->port); - dest_addr.sin_addr.s_addr = htonl(o_addr->addr); - - // TODO: The flags at the end should be controllable. - int sent = sendto(params->data[0].of.i32, ONYX_PTR(params->data[1].of.i32), params->data[2].of.i32, MSG_NOSIGNAL, &dest_addr, dest_addr_len); - results->data[0] = WASM_I32_VAL(sent); - #endif - - return NULL; -} - -ONYX_DEF(__net_recv, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - *(i32 *) ONYX_PTR(params->data[3].of.i32) = 0; - - #ifdef _BH_LINUX - // TODO: The flags at the end should be controllable. - int received = recv(params->data[0].of.i32, ONYX_PTR(params->data[1].of.i32), params->data[2].of.i32, 0); - results->data[0] = WASM_I32_VAL(received); - - if (received < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - *(i32 *) ONYX_PTR(params->data[3].of.i32) = 1; - } - } - #endif - - return NULL; -} - -ONYX_DEF(__net_recvfrom, (WASM_I32, WASM_I32, WASM_I32, WASM_I32, WASM_I32), (WASM_I32)) { - *(i32 *) ONYX_PTR(params->data[4].of.i32) = 0; - - #ifdef _BH_LINUX - struct onyx_socket_addr *out_addr = (struct onyx_socket_addr *) ONYX_PTR(params->data[3].of.i32); - - struct sockaddr_in client_addr; - int socket_len = sizeof(client_addr); - memset(&client_addr, 0, socket_len); - - int received = recvfrom(params->data[0].of.i32, ONYX_PTR(params->data[1].of.i32), params->data[2].of.i32, 0, &client_addr, &socket_len); - out_addr->family = client_addr.sin_family; - out_addr->port = ntohs(client_addr.sin_port); - out_addr->addr = ntohl(client_addr.sin_addr.s_addr); - - results->data[0] = WASM_I32_VAL(received); - - if (received < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - *(i32 *) ONYX_PTR(params->data[3].of.i32) = 1; - } - } - #endif - - return NULL; -} - -ONYX_DEF(__net_poll_recv, (WASM_I32, WASM_I32, WASM_I32, WASM_I32), ()) { - #ifdef _BH_LINUX - int i, res, cursor; - struct pollfd* fds; - - fds = alloca(params->data[1].of.i32 * sizeof(struct pollfd)); - - for (i=0; idata[1].of.i32; i++) { - fds[i].fd = *(i32 *) ONYX_PTR(params->data[0].of.i32 + 4 * i); - fds[i].events = POLLIN; - fds[i].revents = 0; - } - - res = poll(fds, params->data[1].of.i32, params->data[2].of.i32); - - for (i=0; idata[1].of.i32; i++) { - *(i32 *) ONYX_PTR(params->data[3].of.i32 + 4 * i) = 0; // NO_CHANGE - - if (fds[i].revents & POLLIN) { - *(i32 *) ONYX_PTR(params->data[3].of.i32 + 4 * i) = 1; // READABLE - } - - if ((fds[i].revents & POLLHUP) - || (fds[i].revents & POLLNVAL) - || (fds[i].revents & POLLERR)) { - *(i32 *) ONYX_PTR(params->data[3].of.i32 + 4 * i) = 2; // CLOSED - } - } - - #endif - - return NULL; -} - -ONYX_DEF(__net_host_to_net_s, (WASM_I32), (WASM_I32)) { - results->data[0] = WASM_I32_VAL(htons(params->data[0].of.i32)); - return NULL; -} - -ONYX_DEF(__net_host_to_net_l, (WASM_I32), (WASM_I32)) { - results->data[0] = WASM_I32_VAL(htonl(params->data[0].of.i32)); - return NULL; -} - -ONYX_DEF(__net_net_to_host_s, (WASM_I32), (WASM_I32)) { - results->data[0] = WASM_I32_VAL(ntohs(params->data[0].of.i32)); - return NULL; -} - -ONYX_DEF(__net_net_to_host_l, (WASM_I32), (WASM_I32)) { - results->data[0] = WASM_I32_VAL(ntohl(params->data[0].of.i32)); - return NULL; -} - - -// -// C-Pointers -// -// These are wildly unsafe and break the core principles of the security -// of WebAssembly, so there should be a way to turn them off! -// -ONYX_DEF(__cptr_make, (WASM_I32), (WASM_I64)) { - wasm_val_init_ptr(&results->data[0], ONYX_PTR(params->data[0].of.i32)); - return NULL; -} - -ONYX_DEF(__cptr_read, (WASM_I64, WASM_I32, WASM_I32), ()) { - memcpy(ONYX_PTR(params->data[1].of.i32), (void *) params->data[0].of.i64, params->data[2].of.i32); - return NULL; -} - -ONYX_DEF(__cptr_read_u8, (WASM_I64), (WASM_I32)) { - results->data[0] = WASM_I32_VAL(*(u8 *) params->data[0].of.i64); - return NULL; -} - -ONYX_DEF(__cptr_read_u16, (WASM_I64), (WASM_I32)) { - results->data[0] = WASM_I32_VAL(*(u16 *) params->data[0].of.i64); - return NULL; -} - -ONYX_DEF(__cptr_read_u32, (WASM_I64), (WASM_I32)) { - results->data[0] = WASM_I32_VAL(*(u32 *) params->data[0].of.i64); - return NULL; -} - -ONYX_DEF(__cptr_read_u64, (WASM_I64), (WASM_I64)) { - results->data[0] = WASM_I64_VAL(*(u64 *) params->data[0].of.i64); - return NULL; -} - -ONYX_DEF(__cptr_extract_str, (WASM_I64, WASM_I32, WASM_I32), (WASM_I32)) { - unsigned int len = strlen((char *) params->data[0].of.i64); - - if (params->data[2].of.i32 != 0) { - strncpy(ONYX_PTR(params->data[1].of.i32), (char *) params->data[0].of.i64, params->data[2].of.i32); - } - - results->data[0] = WASM_I32_VAL(len); - return NULL; -} - -ONYX_LIBRARY { - ONYX_FUNC(__file_open_impl) - ONYX_FUNC(__file_close) - ONYX_FUNC(__file_exists) - ONYX_FUNC(__file_stat) - ONYX_FUNC(__file_remove) - ONYX_FUNC(__file_seek) - ONYX_FUNC(__file_tell) - ONYX_FUNC(__file_read) - ONYX_FUNC(__file_write) - ONYX_FUNC(__file_flush) - ONYX_FUNC(__file_size) - ONYX_FUNC(__file_get_standard) - ONYX_FUNC(__file_rename) - - ONYX_FUNC(__dir_open) - ONYX_FUNC(__dir_read) - ONYX_FUNC(__dir_close) - ONYX_FUNC(__dir_create) - ONYX_FUNC(__dir_remove) - - ONYX_FUNC(__spawn_thread) - ONYX_FUNC(__kill_thread) - - ONYX_FUNC(__process_spawn) - ONYX_FUNC(__process_read) - ONYX_FUNC(__process_write) - ONYX_FUNC(__process_kill) - ONYX_FUNC(__process_wait) - ONYX_FUNC(__process_destroy) - - ONYX_FUNC(__args_get) - ONYX_FUNC(__args_sizes_get) - - ONYX_FUNC(__exit) - ONYX_FUNC(__sleep) - ONYX_FUNC(__time) - - ONYX_FUNC(__time_localtime) - ONYX_FUNC(__time_gmtime) - ONYX_FUNC(__time_strftime) - ONYX_FUNC(__time_strptime) - - ONYX_FUNC(__net_create_socket) - ONYX_FUNC(__net_close_socket) - ONYX_FUNC(__net_setting) - ONYX_FUNC(__net_bind) - ONYX_FUNC(__net_listen) - ONYX_FUNC(__net_accept) - ONYX_FUNC(__net_connect_unix) - ONYX_FUNC(__net_connect_ipv4) - ONYX_FUNC(__net_send) - ONYX_FUNC(__net_sendto) - ONYX_FUNC(__net_recv) - ONYX_FUNC(__net_recvfrom) - ONYX_FUNC(__net_poll_recv) - ONYX_FUNC(__net_host_to_net_s) - ONYX_FUNC(__net_host_to_net_l) - ONYX_FUNC(__net_net_to_host_s) - ONYX_FUNC(__net_net_to_host_l) - - ONYX_FUNC(__cptr_make) - ONYX_FUNC(__cptr_read) - ONYX_FUNC(__cptr_read_u8) - ONYX_FUNC(__cptr_read_u16) - ONYX_FUNC(__cptr_read_u32) - ONYX_FUNC(__cptr_read_u64) - ONYX_FUNC(__cptr_extract_str) - - NULL -}; diff --git a/src/onyxrun.c b/src/onyxrun.c deleted file mode 100644 index 5200090b..00000000 --- a/src/onyxrun.c +++ /dev/null @@ -1,42 +0,0 @@ -#define BH_DEFINE -#define BH_NO_TABLE -#define STB_DS_IMPLEMENTATION -#include "bh.h" - -#include "wasm_emit.h" - -int main(int argc, char *argv[]) { - i32 wasm_file_idx = 1; - if (argc < 2) { - fprintf(stderr, "Expected a WASM file to run.\n"); - return 1; - } - - b32 debug = 0; - if (!strcmp(argv[1], "--debug")) { - debug = 1; - wasm_file_idx = 2; - } - - if (debug && argc < 3) { - fprintf(stderr, "Expected a WASM file to run.\n"); - return 1; - } - - onyx_run_initialize(debug); - - bh_file wasm_file; - bh_file_error err = bh_file_open(&wasm_file, argv[wasm_file_idx]); - if (err != BH_FILE_ERROR_NONE) { - fprintf(stderr, "Failed to open file %s", argv[wasm_file_idx]); - return 1; - } - - bh_file_contents wasm_data = bh_file_read_contents(bh_heap_allocator(), &wasm_file); - bh_file_close(&wasm_file); - - bh_buffer data; - data.data = wasm_data.data; - data.length = wasm_data.length; - return onyx_run_wasm(data, argc - 1 - debug, argv + 1 + debug); -} diff --git a/src/parser.c b/src/parser.c deleted file mode 100644 index 1ccf7521..00000000 --- a/src/parser.c +++ /dev/null @@ -1,3475 +0,0 @@ -// The sole job of the parser for Onyx is to submit nodes to the -// entity heap for further processing. These nodes include things -// such as procedure definitions, string literals, struct definitions -// and declarations to be introduced into scopes. - -#include "lex.h" -#include "errors.h" -#include "parser.h" -#include "utils.h" - -#define make_node(nclass, kind) onyx_ast_node_new(parser->allocator, sizeof(nclass), kind) -// :LinearTokenDependent -#define peek_token(ahead) (parser->curr + ahead) - -static AstNode error_node = { Ast_Kind_Error, 0, NULL, NULL }; - -#define ENTITY_SUBMIT(node) (submit_entity_in_scope(parser, (AstNode *) (node), parser->current_scope, parser->package)) -#define ENTITY_SUBMIT_IN_SCOPE(node, scope) (submit_entity_in_scope(parser, (AstNode *) (node), scope, parser->package)) - -void submit_entity_in_scope(OnyxParser* parser, AstNode* node, Scope* scope, Package* package) { - if (bh_arr_length(parser->alternate_entity_placement_stack) == 0) { - add_entities_for_node(NULL, node, scope, package); - - } else { - bh_arr(Entity *) *entity_array = bh_arr_last(parser->alternate_entity_placement_stack); - add_entities_for_node(entity_array, node, scope, package); - } -} - -// Parsing Utilities -static void consume_token(OnyxParser* parser); -static OnyxToken* expect_token(OnyxParser* parser, TokenType token_type); -static b32 consume_token_if_next(OnyxParser* parser, TokenType token_type); -static b32 next_tokens_are(OnyxParser* parser, i32 n, ...); -static OnyxToken* find_matching_paren(OnyxToken* paren); - -static AstNumLit* parse_int_literal(OnyxParser* parser); -static AstNumLit* parse_float_literal(OnyxParser* parser); -static b32 parse_possible_struct_literal(OnyxParser* parser, AstTyped* left, AstTyped** ret); -static b32 parse_possible_array_literal(OnyxParser* parser, AstTyped* left, AstTyped** ret); -static b32 parse_possible_unary_field_access(OnyxParser* parser, AstTyped** ret); -static void parse_arguments(OnyxParser* parser, TokenType end_token, Arguments* args); -static AstTyped* parse_factor(OnyxParser* parser); -static AstTyped* parse_compound_assignment(OnyxParser* parser, AstTyped* lhs); -static AstTyped* parse_compound_expression(OnyxParser* parser, b32 assignment_allowed); -static AstTyped* parse_expression(OnyxParser* parser, b32 assignment_allowed); -static AstIfWhile* parse_if_stmt(OnyxParser* parser); -static AstIfWhile* parse_while_stmt(OnyxParser* parser); -static AstFor* parse_for_stmt(OnyxParser* parser); -static AstSwitchCase* parse_case_stmt(OnyxParser* parser); -static AstSwitch* parse_switch_stmt(OnyxParser* parser); -static i32 parse_possible_symbol_declaration(OnyxParser* parser, AstNode** ret); -static AstReturn* parse_return_stmt(OnyxParser* parser); -static AstNode* parse_use_stmt(OnyxParser* parser); -static AstBlock* parse_block(OnyxParser* parser, b32 make_a_new_scope, char* block_name); -static AstNode* parse_statement(OnyxParser* parser); -static void parse_polymorphic_variable(OnyxParser* parser, AstType*** next_insertion); -static AstType* parse_type(OnyxParser* parser); -static AstTypeOf* parse_typeof(OnyxParser* parser); -static AstStructType* parse_struct(OnyxParser* parser); -static AstInterface* parse_interface(OnyxParser* parser); -static AstConstraint* parse_constraint(OnyxParser* parser); -static void parse_constraints(OnyxParser* parser, ConstraintContext *constraints); -static void parse_function_params(OnyxParser* parser, AstFunction* func); -static b32 parse_possible_directive(OnyxParser* parser, const char* dir); -static b32 parse_possible_function_definition_no_consume(OnyxParser* parser); -static b32 parse_possible_function_definition(OnyxParser* parser, AstTyped** ret); -static b32 parse_possible_quick_function_definition_no_consume(OnyxParser* parser); -static b32 parse_possible_quick_function_definition(OnyxParser* parser, AstTyped** ret); -static AstFunction* parse_function_definition(OnyxParser* parser, OnyxToken* token); -static AstTyped* parse_global_declaration(OnyxParser* parser); -static AstEnumType* parse_enum_declaration(OnyxParser* parser); -static AstMacro* parse_macro(OnyxParser* parser); -static AstIf* parse_static_if_stmt(OnyxParser* parser, b32 parse_block_as_statements); -static AstMemRes* parse_memory_reservation(OnyxParser* parser, OnyxToken* symbol, b32 thread_local); -static AstTyped* parse_top_level_expression(OnyxParser* parser); -static AstBinding* parse_top_level_binding(OnyxParser* parser, OnyxToken* symbol); -static void parse_top_level_statement(OnyxParser* parser); -static AstPackage* parse_package_expression(OnyxParser* parser); -static void parse_top_level_statements_until(OnyxParser* parser, TokenType tt); - -static void consume_token(OnyxParser* parser) { - if (parser->hit_unexpected_token) return; - - parser->prev = parser->curr; - // :LinearTokenDependent - parser->curr++; - while (parser->curr->type == Token_Type_Comment || parser->curr->type == Token_Type_Note) { - if (parser->curr->type == Token_Type_Note) { - AstNote* note = make_node(AstNode, Ast_Kind_Note); - note->token = parser->curr; - ENTITY_SUBMIT(note); - } - - parser->curr++; - } -} - -static OnyxToken* find_matching_paren(OnyxToken* paren) { - TokenType match = 0; - switch ((u16) paren->type) { - case '(': match = ')'; break; - case '[': match = ']'; break; - case '{': match = '}'; break; - case '<': match = '>'; break; - default: return NULL; - } - - i32 paren_count = 1; - i32 i = 1; - while (paren_count > 0) { - // :LinearTokenDependent - TokenType type = (paren + i)->type; - if (type == Token_Type_End_Stream) return NULL; - - if (type == paren->type) paren_count++; - else if (type == match) paren_count--; - - i++; - } - - // :LinearTokenDependent - return paren + (i - 1); -} - -// NOTE: This advances to next token no matter what -static OnyxToken* expect_token(OnyxParser* parser, TokenType token_type) { - if (parser->hit_unexpected_token) return NULL; - - OnyxToken* token = parser->curr; - consume_token(parser); - - if (token->type != token_type) { - onyx_report_error(token->pos, Error_Critical, "expected token '%s', got '%s'.", token_name(token_type), token_name(token->type)); - parser->hit_unexpected_token = 1; - // :LinearTokenDependent - parser->curr = &parser->tokenizer->tokens[bh_arr_length(parser->tokenizer->tokens) - 1]; - return NULL; - } - - return token; -} - -static b32 consume_token_if_next(OnyxParser* parser, TokenType token_type) { - if (parser->hit_unexpected_token) return 0; - - if (parser->curr->type == token_type) { - consume_token(parser); - return 1; - } else { - return 0; - } -} - -static void consume_tokens(OnyxParser* parser, i32 n) { - fori (i, 0, n) consume_token(parser); -} - -static b32 next_tokens_are(OnyxParser* parser, i32 n, ...) { - va_list va; - va_start(va, n); - - i32 matched = 1; - - // BUG: This does not take into consideration comments and notes that can occur between any tokens. - fori (i, 0, n) { - TokenType expected_type = va_arg(va, TokenType); - if (peek_token(i)->type != expected_type) { - matched = 0; - break; - } - } - - va_end(va); - return matched; -} - - -static void expect_no_stored_tags_pos(OnyxParser *parser, OnyxFilePos pos) { - if (bh_arr_length(parser->stored_tags) > 0) { - onyx_report_error(pos, Error_Critical, "#tag is not allowed on this element."); - parser->hit_unexpected_token = 1; - } -} - -static void expect_no_stored_tags(OnyxParser *parser) { - expect_no_stored_tags_pos(parser, parser->curr->pos); -} - -static void flush_stored_tags(OnyxParser *parser, bh_arr(AstTyped *) *out_arr) { - // - // When tag_depth > 0, no tags will be added to the element. - // This happens if you have a something like so, - // - // #tag "asdf" - // #tag handler.{ (x: i32) => x * 2 } - // foo :: () { ... } - // - // In this situation, the inner procedure defined in the second - // tag should NOT consume the "asdf" tag. - // - if (bh_arr_length(parser->stored_tags) == 0 || parser->tag_depth > 0) return; - - bh_arr(AstTyped *) arr = *out_arr; - - if (arr == NULL) { - bh_arr_new(parser->allocator, arr, bh_arr_length(parser->stored_tags)); - } - - bh_arr_each(AstTyped *, pexpr, parser->stored_tags) { - bh_arr_push(arr, *pexpr); - } - - bh_arr_clear(parser->stored_tags); - - *out_arr = arr; -} - - -// TODO: Make parsing numeric literals not rely on the C standard libary. -static AstNumLit* parse_int_literal(OnyxParser* parser) { - AstNumLit* int_node = make_node(AstNumLit, Ast_Kind_NumLit); - int_node->token = expect_token(parser, Token_Type_Literal_Integer); - int_node->flags |= Ast_Flag_Comptime; - int_node->value.l = 0ll; - - token_toggle_end(int_node->token); - - char* first_invalid = NULL; - i64 value = strtoll(int_node->token->text, &first_invalid, 0); - - int_node->value.l = value; - - int_node->type_node = (AstType *) &basic_type_int_unsized; - - // NOTE: Hex literals are unsigned. - if (int_node->token->length >= 2 && int_node->token->text[1] == 'x') { - int_node->was_hex_literal = 1; - } - - token_toggle_end(int_node->token); - return int_node; -} - -static AstNumLit* parse_float_literal(OnyxParser* parser) { - AstNumLit* float_node = make_node(AstNumLit, Ast_Kind_NumLit); - float_node->token = expect_token(parser, Token_Type_Literal_Float); - float_node->flags |= Ast_Flag_Comptime; - float_node->value.d = 0.0; - - AstType* type = (AstType *) &basic_type_float_unsized; - token_toggle_end(float_node->token); - - if (float_node->token->text[float_node->token->length - 1] == 'f') { - type = (AstType *) &basic_type_f32; - float_node->value.f = strtof(float_node->token->text, NULL); - } else { - float_node->value.d = strtod(float_node->token->text, NULL); - } - - float_node->type_node = type; - - token_toggle_end(float_node->token); - return float_node; -} - -static b32 parse_possible_directive(OnyxParser* parser, const char* dir) { - if (!next_tokens_are(parser, 2, '#', Token_Type_Symbol)) return 0; - - OnyxToken* sym = peek_token(1); - - b32 match = (strlen(dir) == (u64) sym->length) && (strncmp(dir, sym->text, sym->length) == 0); - if (match) consume_tokens(parser, 2); - - return match; -} - -static b32 parse_possible_struct_literal(OnyxParser* parser, AstTyped* left, AstTyped** ret) { - if (!next_tokens_are(parser, 2, '.', '{')) return 0; - - AstStructLiteral* sl = make_node(AstStructLiteral, Ast_Kind_Struct_Literal); - sl->token = parser->curr; - sl->stnode = left; - - arguments_initialize(&sl->args); - - expect_token(parser, '.'); - expect_token(parser, '{'); - - parse_arguments(parser, '}', &sl->args); - - *ret = (AstTyped *) sl; - return 1; -} - -static b32 parse_possible_array_literal(OnyxParser* parser, AstTyped* left, AstTyped** ret) { - if (!next_tokens_are(parser, 2, '.', '[')) return 0; - - AstArrayLiteral* al = make_node(AstArrayLiteral, Ast_Kind_Array_Literal); - al->token = parser->curr; - al->atnode = left; - - bh_arr_new(global_heap_allocator, al->values, 4); - fori (i, 0, 4) al->values[i] = NULL; - - expect_token(parser, '.'); - expect_token(parser, '['); - while (!consume_token_if_next(parser, ']')) { - if (parser->hit_unexpected_token) return 1; - - AstTyped* value = parse_expression(parser, 0); - bh_arr_push(al->values, value); - - if (parser->curr->type != ']') - expect_token(parser, ','); - } - - *ret = (AstTyped *) al; - return 1; -} - -static b32 parse_possible_unary_field_access(OnyxParser* parser, AstTyped** ret) { - if (!next_tokens_are(parser, 2, '.', Token_Type_Symbol)) return 0; - - AstUnaryFieldAccess* ufl = make_node(AstUnaryFieldAccess, Ast_Kind_Unary_Field_Access); - expect_token(parser, '.'); - ufl->token = expect_token(parser, Token_Type_Symbol); - - *ret = (AstTyped *) ufl; - return 1; -} - -static void parse_arguments(OnyxParser* parser, TokenType end_token, Arguments* args) { - while (!consume_token_if_next(parser, end_token)) { - if (parser->hit_unexpected_token) return; - - // - // This has a weird condition to avoid the problem of using a quick function as an argument: - // f(x => x + 1) - // This shouldn't be a named argument, but this should: - // f(g = x => x + 1) - // - if (next_tokens_are(parser, 2, Token_Type_Symbol, '=') && peek_token(2)->type != '>') { - OnyxToken* name = expect_token(parser, Token_Type_Symbol); - expect_token(parser, '='); - - AstNamedValue* named_value = make_node(AstNamedValue, Ast_Kind_Named_Value); - named_value->token = name; - named_value->value = parse_expression(parser, 0); - - bh_arr_push(args->named_values, named_value); - - } else { - AstTyped* value = parse_expression(parser, 0); - bh_arr_push(args->values, value); - } - - if (parser->curr->type != end_token) - expect_token(parser, ','); - } -} - -static AstTyped* parse_factor(OnyxParser* parser) { - AstTyped* retval = NULL; - - switch ((u16) parser->curr->type) { - case '(': { - if (parse_possible_function_definition(parser, &retval)) { - ENTITY_SUBMIT(retval); - break; - } - if (parse_possible_quick_function_definition(parser, &retval)) { - ENTITY_SUBMIT(retval); - break; - } - - consume_token(parser); - retval = parse_compound_expression(parser, 0); - expect_token(parser, ')'); - break; - } - - case '-': { - AstUnaryOp* negate_node = make_node(AstUnaryOp, Ast_Kind_Unary_Op); - negate_node->operation = Unary_Op_Negate; - negate_node->token = expect_token(parser, '-'); - negate_node->expr = parse_factor(parser); - - retval = (AstTyped *) negate_node; - break; - } - - case '!': { - AstUnaryOp* not_node = make_node(AstUnaryOp, Ast_Kind_Unary_Op); - not_node->operation = Unary_Op_Not; - not_node->token = expect_token(parser, '!'); - not_node->expr = parse_factor(parser); - - retval = (AstTyped *) not_node; - break; - } - - case '~': { - AstUnaryOp* not_node = make_node(AstUnaryOp, Ast_Kind_Unary_Op); - not_node->operation = Unary_Op_Bitwise_Not; - not_node->token = expect_token(parser, '~'); - not_node->expr = parse_factor(parser); - - retval = (AstTyped *) not_node; - break; - } - - case '*': { - AstDereference* deref_node = make_node(AstDereference, Ast_Kind_Dereference); - deref_node->token = expect_token(parser, '*'); - deref_node->expr = parse_factor(parser); - - retval = (AstTyped *) deref_node; - break; - } - - case '^': { - AstAddressOf* aof_node = make_node(AstAddressOf, Ast_Kind_Address_Of); - aof_node->token = expect_token(parser, '^'); - aof_node->expr = parse_factor(parser); - - retval = (AstTyped *) aof_node; - break; - } - - case '.': { - if (parse_possible_struct_literal(parser, NULL, &retval)) return retval; - if (parse_possible_array_literal(parser, NULL, &retval)) return retval; - if (parse_possible_unary_field_access(parser, &retval)) return retval; - goto no_match; - } - - case Token_Type_Tilde_Tilde: { - AstUnaryOp* ac_node = make_node(AstUnaryOp, Ast_Kind_Unary_Op); - ac_node->operation = Unary_Op_Auto_Cast; - ac_node->token = expect_token(parser, Token_Type_Tilde_Tilde); - ac_node->expr = parse_factor(parser); - - retval = (AstTyped *) ac_node; - break; - } - - case Token_Type_Keyword_Cast: { - AstUnaryOp* cast_node = make_node(AstUnaryOp, Ast_Kind_Unary_Op); - cast_node->operation = Unary_Op_Cast; - cast_node->token = expect_token(parser, Token_Type_Keyword_Cast); - - expect_token(parser, '('); - cast_node->type_node = parse_type(parser); - expect_token(parser, ')'); - - cast_node->expr = parse_factor(parser); - - retval = (AstTyped *) cast_node; - break; - } - - case Token_Type_Keyword_Sizeof: { - AstSizeOf* so_node = make_node(AstSizeOf, Ast_Kind_Size_Of); - so_node->token = expect_token(parser, Token_Type_Keyword_Sizeof); - so_node->so_ast_type = (AstType *) parse_type(parser); - so_node->type_node = (AstType *) &basic_type_i32; - - retval = (AstTyped *) so_node; - break; - } - - case Token_Type_Keyword_Alignof: { - AstAlignOf* ao_node = make_node(AstAlignOf, Ast_Kind_Align_Of); - ao_node->token = expect_token(parser, Token_Type_Keyword_Alignof); - ao_node->ao_ast_type = (AstType *) parse_type(parser); - ao_node->type_node = (AstType *) &basic_type_i32; - - retval = (AstTyped *) ao_node; - break; - } - - case Token_Type_Keyword_Typeof: { - retval = (AstTyped *) parse_typeof(parser); - break; - } - - case Token_Type_Symbol: { - if (parse_possible_quick_function_definition(parser, &retval)) { - ENTITY_SUBMIT(retval); - break; - } - - OnyxToken* sym_token = expect_token(parser, Token_Type_Symbol); - AstTyped* sym_node = make_node(AstTyped, Ast_Kind_Symbol); - sym_node->token = sym_token; - - retval = sym_node; - break; - } - - case Token_Type_Literal_Integer: - retval = (AstTyped *) parse_int_literal(parser); - break; - - case Token_Type_Literal_Float: - retval = (AstTyped *) parse_float_literal(parser); - break; - - case Token_Type_Literal_String: { - AstStrLit* str_node = make_node(AstStrLit, Ast_Kind_StrLit); - str_node->token = expect_token(parser, Token_Type_Literal_String); - str_node->data_id = 0; - str_node->flags |= Ast_Flag_Comptime; - - ENTITY_SUBMIT(str_node); - - retval = (AstTyped *) str_node; - break; - } - - case Token_Type_Literal_True: { - AstNumLit* bool_node = make_node(AstNumLit, Ast_Kind_NumLit); - bool_node->type_node = (AstType *) &basic_type_bool; - bool_node->token = expect_token(parser, Token_Type_Literal_True); - bool_node->value.i = 1; - bool_node->flags |= Ast_Flag_Comptime; - retval = (AstTyped *) bool_node; - break; - } - - case Token_Type_Literal_False: { - AstNumLit* bool_node = make_node(AstNumLit, Ast_Kind_NumLit); - bool_node->type_node = (AstType *) &basic_type_bool; - bool_node->token = expect_token(parser, Token_Type_Literal_False); - bool_node->value.i = 0; - bool_node->flags |= Ast_Flag_Comptime; - retval = (AstTyped *) bool_node; - break; - } - - case Token_Type_Keyword_Package: { - retval = (AstTyped *) parse_package_expression(parser); - break; - } - - case Token_Type_Keyword_Macro: { - retval = (AstTyped *) parse_macro(parser); - break; - } - - case Token_Type_Keyword_Do: { - OnyxToken* do_token = expect_token(parser, Token_Type_Keyword_Do); - AstDoBlock* do_block = make_node(AstDoBlock, Ast_Kind_Do_Block); - do_block->token = do_token; - do_block->type_node = (AstType *) &basic_type_auto_return; - - if (parser->curr->type != '{') { - onyx_report_error(parser->curr->pos, Error_Critical, "Expected '{' after 'do', got '%s'.", token_name(parser->curr->type)); - retval = NULL; - break; - } - - do_block->block = parse_block(parser, 1, NULL); - - retval = (AstTyped *) do_block; - break; - } - - case '[': { - AstType *type = parse_type(parser); - retval = (AstTyped *) type; - break; - } - - case '$': { - AstType **tmp = (AstType **) &retval; - parse_polymorphic_variable(parser, &tmp); - break; - } - - case '#': { - if (parse_possible_directive(parser, "file_contents")) { - AstFileContents* fc = make_node(AstFileContents, Ast_Kind_File_Contents); - fc->token = parser->prev - 1; - fc->filename_expr = parse_expression(parser, 0); - fc->type = type_make_slice(parser->allocator, &basic_types[Basic_Kind_U8]); - - if (parser->current_function_stack && bh_arr_length(parser->current_function_stack) > 0) { - bh_arr_push(bh_arr_last(parser->current_function_stack)->nodes_that_need_entities_after_clone, (AstNode *) fc); - - } else { - ENTITY_SUBMIT(fc); - } - - retval = (AstTyped *) fc; - break; - } - else if (parse_possible_directive(parser, "file")) { - OnyxToken* dir_token = parser->curr - 2; - - OnyxToken* str_token = bh_alloc(parser->allocator, sizeof(OnyxToken)); - str_token->text = bh_strdup(global_heap_allocator, (char *) dir_token->pos.filename); - str_token->length = strlen(dir_token->pos.filename); - str_token->pos = dir_token->pos; - str_token->type = Token_Type_Literal_String; - - AstStrLit* filename = make_node(AstStrLit, Ast_Kind_StrLit); - filename->token = str_token; - filename->data_id = 0; - - ENTITY_SUBMIT(filename); - retval = (AstTyped *) filename; - break; - } - else if (parse_possible_directive(parser, "line")) { - OnyxToken* dir_token = parser->curr - 2; - - AstNumLit* line_num = make_int_literal(parser->allocator, dir_token->pos.line); - retval = (AstTyped *) line_num; - break; - } - else if (parse_possible_directive(parser, "column")) { - OnyxToken* dir_token = parser->curr - 2; - - AstNumLit* col_num = make_int_literal(parser->allocator, dir_token->pos.column); - retval = (AstTyped *) col_num; - break; - } - else if (parse_possible_directive(parser, "char")) { - AstNumLit* char_lit = make_node(AstNumLit, Ast_Kind_NumLit); - char_lit->flags |= Ast_Flag_Comptime; - char_lit->type_node = (AstType *) &basic_type_int_unsized; - char_lit->token = expect_token(parser, Token_Type_Literal_String); - - i8 dest = '\0'; - i32 length = string_process_escape_seqs((char *) &dest, char_lit->token->text, 1); - char_lit->value.i = (u32) dest; - - if (length != 1) { - onyx_report_error(char_lit->token->pos, Error_Critical, "Expected only a single character in character literal."); - } - - retval = (AstTyped *) char_lit; - break; - } - else if (parse_possible_directive(parser, "type")) { - AstTypeAlias* alias = make_node(AstTypeAlias, Ast_Kind_Type_Alias); - alias->token = parser->curr - 2; - alias->to = parse_type(parser); - retval = (AstTyped *) alias; - break; - } - else if (parse_possible_directive(parser, "solidify")) { - AstDirectiveSolidify* solid = make_node(AstDirectiveSolidify, Ast_Kind_Directive_Solidify); - // :LinearTokenDependent - solid->token = parser->curr - 1; - - solid->poly_proc = (AstFunction *) parse_factor(parser); - - solid->known_polyvars = NULL; - bh_arr_new(global_heap_allocator, solid->known_polyvars, 2); - - expect_token(parser, '{'); - while (!consume_token_if_next(parser, '}')) { - if (parser->hit_unexpected_token) break; - - AstNode* poly_var = make_node(AstNode, Ast_Kind_Symbol); - poly_var->token = expect_token(parser, Token_Type_Symbol); - - expect_token(parser, '='); - AstType* poly_type = (AstType *) parse_expression(parser, 0); - - bh_arr_push(solid->known_polyvars, ((AstPolySolution) { - .kind = PSK_Undefined, - .poly_sym = poly_var, - .ast_type = poly_type, - .type = NULL - })); - - if (parser->curr->type != '}') - expect_token(parser, ','); - } - - retval = (AstTyped *) solid; - break; - } - else if (parse_possible_directive(parser, "defined")) { - AstDirectiveDefined* defined = make_node(AstDirectiveDefined, Ast_Kind_Directive_Defined); - // :LinearTokenDependent - defined->token = parser->curr - 1; - defined->type_node = (AstType *) &basic_type_bool; - - expect_token(parser, '('); - defined->expr = parse_expression(parser, 0); - expect_token(parser, ')'); - - retval = (AstTyped *) defined; - break; - } - else if (parse_possible_directive(parser, "quote")) { - OnyxToken* code_token = parser->curr - 1; - - AstCodeBlock* code_block = make_node(AstCodeBlock, Ast_Kind_Code_Block); - code_block->token = code_token; - - assert(builtin_code_type != NULL); - code_block->type_node = builtin_code_type; - - if (parser->curr->type == '{') { - code_block->code = (AstNode *) parse_block(parser, 1, NULL); - ((AstBlock *) code_block->code)->rules = Block_Rule_Code_Block; - - } else { - code_block->code = (AstNode *) parse_expression(parser, 0); - } - - retval = (AstTyped *) code_block; - break; - } - else if (next_tokens_are(parser, 2, '#', '(')) { - OnyxToken* code_token = expect_token(parser, '#'); - expect_token(parser, '('); - - AstCodeBlock* code_block = make_node(AstCodeBlock, Ast_Kind_Code_Block); - code_block->token = code_token; - - assert(builtin_code_type != NULL); - code_block->type_node = builtin_code_type; - - code_block->code = (AstNode *) parse_expression(parser, 0); - - expect_token(parser, ')'); - - retval = (AstTyped *) code_block; - break; - } - else if (parse_possible_directive(parser, "unquote")) { - AstDirectiveInsert* insert = make_node(AstDirectiveInsert, Ast_Kind_Directive_Insert); - insert->token = parser->curr - 1; - insert->code_expr = parse_expression(parser, 0); - - retval = (AstTyped *) insert; - break; - } - else if (parse_possible_directive(parser, "cstr")) { - // Copy pasted from above. - AstStrLit* str_node = make_node(AstStrLit, Ast_Kind_StrLit); - str_node->token = expect_token(parser, Token_Type_Literal_String); - str_node->data_id = 0; - str_node->flags |= Ast_Flag_Comptime; - str_node->is_cstr = 1; - - ENTITY_SUBMIT(str_node); - - retval = (AstTyped *) str_node; - break; - } - - onyx_report_error(parser->curr->pos, Error_Critical, "Invalid directive in expression."); - return NULL; - } - - default: - no_match: - onyx_report_error(parser->curr->pos, Error_Critical, "Unexpected token '%s'.", token_name(parser->curr->type)); - return NULL; - } - - while (1) { - if (parser->hit_unexpected_token) return retval; - - switch ((u16) parser->curr->type) { - case '[': { - OnyxToken *open_bracket = expect_token(parser, '['); - AstTyped *expr = parse_compound_expression(parser, 0); - - AstSubscript *sub_node = make_node(AstSubscript, Ast_Kind_Subscript); - sub_node->token = open_bracket; - sub_node->addr = retval; - sub_node->expr = expr; - sub_node->__unused_operation = Binary_Op_Subscript; - - retval = (AstTyped *) sub_node; - expect_token(parser, ']'); - break; - } - - case '.': { - if (parse_possible_struct_literal(parser, retval, &retval)) return retval; - if (parse_possible_array_literal(parser, retval, &retval)) return retval; - - consume_token(parser); - AstFieldAccess* field = make_node(AstFieldAccess, Ast_Kind_Field_Access); - field->token = expect_token(parser, Token_Type_Symbol); - field->expr = retval; - - retval = (AstTyped *) field; - break; - } - - case '(': { - if (!parser->parse_calls) goto factor_parsed; - - AstCall* call_node = make_node(AstCall, Ast_Kind_Call); - call_node->token = expect_token(parser, '('); - call_node->callee = retval; - - arguments_initialize(&call_node->args); - - parse_arguments(parser, ')', &call_node->args); - - // Wrap expressions in AstArgument - bh_arr_each(AstTyped *, arg, call_node->args.values) { - if ((*arg) == NULL) continue; - *arg = (AstTyped *) make_argument(parser->allocator, *arg); - } - - bh_arr_each(AstNamedValue *, named_value, call_node->args.named_values) { - if ((*named_value)->value == NULL) continue; - (*named_value)->value = (AstTyped *) make_argument(parser->allocator, (AstTyped *) (*named_value)->value); - } - - retval = (AstTyped *) call_node; - break; - } - - case Token_Type_Keyword_If: { - AstIfExpression* if_expression = make_node(AstIfExpression, Ast_Kind_If_Expression); - if_expression->token = expect_token(parser, Token_Type_Keyword_If); - - if_expression->true_expr = retval; - if_expression->cond = parse_expression(parser, 0); - expect_token(parser, Token_Type_Keyword_Else); - if_expression->false_expr = parse_expression(parser, 0); - - retval = (AstTyped *) if_expression; - break; - } - - default: goto factor_parsed; - } - } - -factor_parsed: - - return retval; -} - -static inline i32 get_precedence(BinaryOp kind) { - switch (kind) { - case Binary_Op_Assign: return 1; - case Binary_Op_Assign_Add: return 1; - case Binary_Op_Assign_Minus: return 1; - case Binary_Op_Assign_Multiply: return 1; - case Binary_Op_Assign_Divide: return 1; - case Binary_Op_Assign_Modulus: return 1; - case Binary_Op_Assign_And: return 1; - case Binary_Op_Assign_Or: return 1; - case Binary_Op_Assign_Xor: return 1; - case Binary_Op_Assign_Shl: return 1; - case Binary_Op_Assign_Shr: return 1; - case Binary_Op_Assign_Sar: return 1; - - case Binary_Op_Pipe: return 2; - case Binary_Op_Range: return 2; - - case Binary_Op_Bool_And: return 3; - case Binary_Op_Bool_Or: return 3; - - case Binary_Op_Equal: return 4; - case Binary_Op_Not_Equal: return 4; - - case Binary_Op_Less_Equal: return 5; - case Binary_Op_Less: return 5; - case Binary_Op_Greater_Equal: return 5; - case Binary_Op_Greater: return 5; - - case Binary_Op_And: return 6; - case Binary_Op_Or: return 6; - case Binary_Op_Xor: return 6; - case Binary_Op_Shl: return 6; - case Binary_Op_Shr: return 6; - case Binary_Op_Sar: return 6; - - case Binary_Op_Add: return 7; - case Binary_Op_Minus: return 7; - - case Binary_Op_Multiply: return 8; - case Binary_Op_Divide: return 8; - - case Binary_Op_Modulus: return 9; - - case Binary_Op_Method_Call: return 10; - - default: return -1; - } -} - -static BinaryOp binary_op_from_token_type(TokenType t) { - switch ((u16) t) { - case Token_Type_Equal_Equal: return Binary_Op_Equal; - case Token_Type_Not_Equal: return Binary_Op_Not_Equal; - case Token_Type_Less_Equal: return Binary_Op_Less_Equal; - case Token_Type_Greater_Equal: return Binary_Op_Greater_Equal; - case '<': return Binary_Op_Less; - case '>': return Binary_Op_Greater; - - case '+': return Binary_Op_Add; - case '-': return Binary_Op_Minus; - case '*': return Binary_Op_Multiply; - case '/': return Binary_Op_Divide; - case '%': return Binary_Op_Modulus; - - case '&': return Binary_Op_And; - case '|': return Binary_Op_Or; - case '^': return Binary_Op_Xor; - case Token_Type_Shift_Left: return Binary_Op_Shl; - case Token_Type_Shift_Right: return Binary_Op_Shr; - case Token_Type_Shift_Arith_Right: return Binary_Op_Sar; - - case Token_Type_And_And: return Binary_Op_Bool_And; - case Token_Type_Or_Or: return Binary_Op_Bool_Or; - - case '=': return Binary_Op_Assign; - case Token_Type_Plus_Equal: return Binary_Op_Assign_Add; - case Token_Type_Minus_Equal: return Binary_Op_Assign_Minus; - case Token_Type_Star_Equal: return Binary_Op_Assign_Multiply; - case Token_Type_Fslash_Equal: return Binary_Op_Assign_Divide; - case Token_Type_Percent_Equal: return Binary_Op_Assign_Modulus; - case Token_Type_And_Equal: return Binary_Op_Assign_And; - case Token_Type_Or_Equal: return Binary_Op_Assign_Or; - case Token_Type_Xor_Equal: return Binary_Op_Assign_Xor; - case Token_Type_Shl_Equal: return Binary_Op_Assign_Shl; - case Token_Type_Shr_Equal: return Binary_Op_Assign_Shr; - case Token_Type_Sar_Equal: return Binary_Op_Assign_Sar; - - case Token_Type_Pipe: return Binary_Op_Pipe; - case Token_Type_Dot_Dot: return Binary_Op_Range; - case '[': return Binary_Op_Subscript; - case Token_Type_Right_Arrow: return Binary_Op_Method_Call; - default: return Binary_Op_Count; - } -} - -static AstTyped* parse_compound_assignment(OnyxParser* parser, AstTyped* lhs) { - if (parser->curr->type != '=') return lhs; - - AstBinaryOp* assignment = make_node(AstBinaryOp, Ast_Kind_Binary_Op); - assignment->token = expect_token(parser, '='); - assignment->operation = Binary_Op_Assign; - assignment->left = lhs; - assignment->right = parse_compound_expression(parser, 0); - - return (AstTyped *) assignment; -} - -static AstTyped* parse_compound_expression(OnyxParser* parser, b32 assignment_allowed) { - AstTyped* first = parse_expression(parser, assignment_allowed); - - if (parser->curr->type == ',') { - AstCompound* compound = make_node(AstCompound, Ast_Kind_Compound); - compound->token = parser->curr; - - bh_arr_new(global_heap_allocator, compound->exprs, 2); - bh_arr_push(compound->exprs, first); - - while (consume_token_if_next(parser, ',')) { - if (parser->hit_unexpected_token) return (AstTyped *) compound; - - AstTyped* expr = parse_expression(parser, 0); - bh_arr_push(compound->exprs, expr); - - if (assignment_allowed && parser->curr->type == '=') { - return parse_compound_assignment(parser, (AstTyped *) compound); - } - } - - return (AstTyped *) compound; - - } else { - return first; - } -} - -static AstTyped* parse_expression(OnyxParser* parser, b32 assignment_allowed) { - bh_arr(AstBinaryOp*) tree_stack = NULL; - bh_arr_new(global_heap_allocator, tree_stack, 4); - bh_arr_set_length(tree_stack, 0); - - AstTyped* left = parse_factor(parser); - AstTyped* right; - AstTyped* root = left; - - BinaryOp bin_op_kind; - OnyxToken* bin_op_tok; - - while (1) { - if (parser->hit_unexpected_token) return root; - - bin_op_kind = binary_op_from_token_type(parser->curr->type); - if (bin_op_kind == Binary_Op_Count) goto expression_done; - if (binop_is_assignment(bin_op_kind) && !assignment_allowed) goto expression_done; - if (bin_op_kind == Binary_Op_Subscript) goto expression_done; - - bin_op_tok = parser->curr; - consume_token(parser); - - AstBinaryOp* bin_op; - if (bin_op_kind == Binary_Op_Pipe) bin_op = make_node(AstBinaryOp, Ast_Kind_Pipe); - else if (bin_op_kind == Binary_Op_Method_Call) bin_op = make_node(AstBinaryOp, Ast_Kind_Method_Call); - else if (bin_op_kind == Binary_Op_Range) bin_op = (AstBinaryOp *) make_node(AstRangeLiteral, Ast_Kind_Range_Literal); - else bin_op = make_node(AstBinaryOp, Ast_Kind_Binary_Op); - - bin_op->token = bin_op_tok; - bin_op->operation = bin_op_kind; - - while ( !bh_arr_is_empty(tree_stack) && - get_precedence(bh_arr_last(tree_stack)->operation) >= get_precedence(bin_op_kind)) - bh_arr_pop(tree_stack); - - if (bh_arr_is_empty(tree_stack)) { - // NOTE: new is now the root node - bin_op->left = root; - root = (AstTyped *) bin_op; - - } else { - bin_op->left = bh_arr_last(tree_stack)->right; - bh_arr_last(tree_stack)->right = (AstTyped *) bin_op; - } - - bh_arr_push(tree_stack, bin_op); - - right = parse_factor(parser); - bin_op->right = right; - } - -expression_done: - bh_arr_free(tree_stack); - return root; -} - -static AstIfWhile* parse_if_stmt(OnyxParser* parser) { - AstIfWhile* if_node = make_node(AstIfWhile, Ast_Kind_If); - if_node->token = expect_token(parser, Token_Type_Keyword_If); - - AstIfWhile* root_if = if_node; - AstTyped* cond; - AstNode* initialization_or_cond=NULL; - b32 had_initialization = 0; - if (parse_possible_symbol_declaration(parser, &initialization_or_cond)) { - had_initialization = 1; - - } else { - // NOTE: Assignment is allowed here because instead of not parsing correctly, - // an error is reported in the typechecking, saying that assignment isn't allowed - // here, which is better than an unexpected token error. - initialization_or_cond = (AstNode *) parse_compound_expression(parser, 1); - } - - if (had_initialization || parser->curr->type == ';') { - expect_token(parser, ';'); - cond = parse_expression(parser, 1); - } else { - cond = (AstTyped *) initialization_or_cond; - initialization_or_cond = NULL; - } - - AstBlock* true_stmt = parse_block(parser, 1, NULL); - - if_node->initialization = initialization_or_cond; - if_node->cond = cond; - if (true_stmt != NULL) - if_node->true_stmt = true_stmt; - - while (consume_token_if_next(parser, Token_Type_Keyword_Elseif)) { - if (parser->hit_unexpected_token) return root_if; - - AstIfWhile* elseif_node = make_node(AstIfWhile, Ast_Kind_If); - elseif_node->token = parser->curr - 1; - - cond = parse_expression(parser, 1); - true_stmt = parse_block(parser, 1, NULL); - - elseif_node->cond = cond; - if (true_stmt != NULL) - elseif_node->true_stmt = true_stmt; - - if_node->false_stmt = (AstBlock *) elseif_node; - if_node = elseif_node; - } - - if (consume_token_if_next(parser, Token_Type_Keyword_Else)) { - AstBlock* false_stmt = parse_block(parser, 1, NULL); - if (false_stmt != NULL) - if_node->false_stmt = false_stmt; - } - - return root_if; -} - -static AstIfWhile* parse_while_stmt(OnyxParser* parser) { - OnyxToken* while_token = expect_token(parser, Token_Type_Keyword_While); - AstIfWhile* while_node = make_node(AstIfWhile, Ast_Kind_While); - while_node->token = while_token; - - if (parse_possible_directive(parser, "bottom_test")) { - while_node->bottom_test = 1; - } - - AstTyped* cond; - AstNode* initialization_or_cond=NULL; - b32 had_initialization = 0; - if (parse_possible_symbol_declaration(parser, &initialization_or_cond)) { - had_initialization = 1; - - } else { - // NOTE: Assignment is allowed here because instead of not parsing correctly, - // an error is reported in the typechecking, saying that assignment isn't allowed - // here, which is better than an unexpected token error. - initialization_or_cond = (AstNode *) parse_compound_expression(parser, 1); - } - - if (had_initialization || parser->curr->type == ';') { - expect_token(parser, ';'); - cond = parse_expression(parser, 1); - } else { - cond = (AstTyped *) initialization_or_cond; - initialization_or_cond = NULL; - } - - while_node->initialization = initialization_or_cond; - while_node->cond = cond; - while_node->true_stmt = parse_block(parser, 1, NULL); - - if (consume_token_if_next(parser, Token_Type_Keyword_Else)) { - while_node->false_stmt = parse_block(parser, 1, NULL); - } - - return while_node; -} - -static AstFor* parse_for_stmt(OnyxParser* parser) { - AstFor* for_node = make_node(AstFor, Ast_Kind_For); - for_node->token = expect_token(parser, Token_Type_Keyword_For); - - if (parse_possible_directive(parser, "no_close")) { - for_node->no_close = 1; - } - - if (consume_token_if_next(parser, '^')) { - for_node->by_pointer = 1; - } - - if (next_tokens_are(parser, 2, Token_Type_Symbol, ':')) { - OnyxToken* local_sym = expect_token(parser, Token_Type_Symbol); - AstLocal* var_node = make_local(parser->allocator, local_sym, NULL); - - for_node->var = var_node; - - expect_token(parser, ':'); - } else { - // HACK - static char it_name[] = "it "; - static OnyxToken it_token = { Token_Type_Symbol, 2, it_name, { 0 } }; - - AstLocal* var_node = make_local(parser->allocator, &it_token, NULL); - for_node->var = var_node; - } - - for_node->iter = parse_expression(parser, 1); - for_node->stmt = parse_block(parser, 1, NULL); - - return for_node; -} - -static AstSwitchCase* parse_case_stmt(OnyxParser* parser) { - AstSwitchCase *sc_node = make_node(AstSwitchCase, Ast_Kind_Switch_Case); - sc_node->token = expect_token(parser, Token_Type_Keyword_Case); - - if (parse_possible_directive(parser, "default")) { - sc_node->is_default = 1; - - } else { - bh_arr_new(global_heap_allocator, sc_node->values, 1); - - AstTyped* value = parse_expression(parser, 1); - bh_arr_push(sc_node->values, value); - while (consume_token_if_next(parser, ',')) { - if (parser->hit_unexpected_token) return sc_node; - - value = parse_expression(parser, 1); - bh_arr_push(sc_node->values, value); - } - } - - sc_node->block = parse_block(parser, 1, NULL); - - return sc_node; -} - -static AstSwitch* parse_switch_stmt(OnyxParser* parser) { - AstSwitch* switch_node = make_node(AstSwitch, Ast_Kind_Switch); - switch_node->token = expect_token(parser, Token_Type_Keyword_Switch); - - AstTyped* expr; - AstNode* initialization_or_expr=NULL; - b32 had_initialization = 0; - if (parse_possible_symbol_declaration(parser, &initialization_or_expr)) { - had_initialization = 1; - - } else { - // NOTE: Assignment is allowed here because instead of not parsing correctly, - // an error is reported in the typechecking, saying that assignment isn't allowed - // here, which is better than an unexpected token error. - initialization_or_expr = (AstNode *) parse_compound_expression(parser, 1); - } - - if (had_initialization || parser->curr->type == ';') { - expect_token(parser, ';'); - expr = parse_expression(parser, 1); - - } else { - expr = (AstTyped *) initialization_or_expr; - initialization_or_expr = NULL; - } - - switch_node->initialization = initialization_or_expr; - switch_node->expr = expr; - - switch_node->case_block = parse_block(parser, 1, NULL); - return switch_node; -} - -static i32 parse_possible_compound_symbol_declaration(OnyxParser* parser, AstNode** ret) { - u32 token_offset = 0; - while (peek_token(token_offset)->type == Token_Type_Symbol) { - token_offset += 1; - - if (peek_token(token_offset)->type == '\'') token_offset += 1; - - if (peek_token(token_offset)->type != ',') break; - token_offset += 1; - } - - if (peek_token(token_offset)->type != ':') return 0; - - // At this point, we are sure it is a compound declaration. - AstCompound* local_compound = make_node(AstCompound, Ast_Kind_Compound); - bh_arr_new(global_heap_allocator, local_compound->exprs, token_offset / 2); - - AstLocal* first_local = NULL; - AstLocal* prev_local = NULL; - - while (parser->curr->type == Token_Type_Symbol) { - if (parser->hit_unexpected_token) return 1; - - OnyxToken* local_sym = expect_token(parser, Token_Type_Symbol); - AstNode* sym_node = make_symbol(parser->allocator, local_sym); - bh_arr_push(local_compound->exprs, (AstTyped *) sym_node); - - if (!consume_token_if_next(parser, '\'')) { - AstLocal* new_local = make_local(parser->allocator, local_sym, NULL); - if (prev_local == NULL) { - first_local = new_local; - } else { - prev_local->next = (AstNode *) new_local; - } - prev_local = new_local; - } - - consume_token_if_next(parser, ','); - } - - expect_token(parser, ':'); - - if (parser->curr->type == '=') { - AstBinaryOp* assignment = make_binary_op(parser->allocator, Binary_Op_Assign, (AstTyped *) local_compound, NULL); - assignment->token = expect_token(parser, '='); - assignment->right = parse_compound_expression(parser, 0); - - prev_local->next = (AstNode *) assignment; - - } else { - AstType* type_for_all = parse_type(parser); - forll (AstLocal, local, first_local, next) { - local->type_node = type_for_all; - } - } - - *ret = (AstNode *) first_local; - return 1; -} - -// Returns: -// 0 - if this was not a symbol declaration. -// 1 - if this was a local declaration. -// 2 - if this was binding declaration. -// ret is set to the statement to insert -static i32 parse_possible_symbol_declaration(OnyxParser* parser, AstNode** ret) { - // Has to start with a symbol to be a declaration - if (parser->curr->type != Token_Type_Symbol) return 0; - - // If the token after the symbol is a comma, assume this is a compound declaration. - if (peek_token(1)->type == ',' || - (peek_token(1)->type == '\'' && peek_token(2)->type == ',')) { - return parse_possible_compound_symbol_declaration(parser, ret); - } - - if (peek_token(1)->type != ':') return 0; - - OnyxToken* symbol = expect_token(parser, Token_Type_Symbol); - expect_token(parser, ':'); - - if (parser->curr->type == ':') { - bh_arr_push(parser->current_symbol_stack, symbol); - AstBinding* binding = parse_top_level_binding(parser, symbol); - bh_arr_pop(parser->current_symbol_stack); - if (parser->hit_unexpected_token) return 2; - - ENTITY_SUBMIT(binding); - return 2; - } - - AstType* type_node = NULL; - if (parser->curr->type != '=') { - type_node = parse_type(parser); - } - - AstLocal* local = make_local(parser->allocator, symbol, type_node); - *ret = (AstNode *) local; - - if (parser->curr->type == '=') { - AstBinaryOp* assignment = make_node(AstBinaryOp, Ast_Kind_Binary_Op); - assignment->operation = Binary_Op_Assign; - assignment->token = expect_token(parser, '='); - local->next = (AstNode *) assignment; - - AstTyped* expr = parse_expression(parser, 1); - if (expr == NULL) return 1; - assignment->right = expr; - - // INVESTIGATE: I don't know why, but appearantly, this has to be a - // symbol node, not a direct link to the local. There is an error about - // being unable to resolve the type of the local if it is immediately set. - AstNode* left_symbol = make_node(AstNode, Ast_Kind_Symbol); - left_symbol->token = symbol; - assignment->left = (AstTyped *) left_symbol; - } - - return 1; -} - -static AstReturn* parse_return_stmt(OnyxParser* parser) { - AstReturn* return_node = make_node(AstReturn, Ast_Kind_Return); - return_node->token = expect_token(parser, Token_Type_Keyword_Return); - - AstTyped* expr = NULL; - - if (parser->curr->type != ';') { - expr = parse_compound_expression(parser, 0); - - if (expr == NULL || expr == (AstTyped *) &error_node) { - return (AstReturn *) &error_node; - } else { - return_node->expr = expr; - } - } - - return return_node; -} - -static AstNode* parse_use_stmt(OnyxParser* parser) { - OnyxToken* use_token = expect_token(parser, Token_Type_Keyword_Use); - AstUse* use_node = make_node(AstUse, Ast_Kind_Use); - use_node->token = use_token; - use_node->expr = parse_expression(parser, 1); - - if (consume_token_if_next(parser, '{')) { - bh_arr_new(global_heap_allocator, use_node->only, 4); - - while (!consume_token_if_next(parser, '}')) { - if (parser->hit_unexpected_token) return NULL; - - QualifiedUse qu; - qu.as_name = expect_token(parser, Token_Type_Symbol); - qu.symbol_name = qu.as_name; - - if (consume_token_if_next(parser, ':')) { - expect_token(parser, ':'); - qu.symbol_name = expect_token(parser, Token_Type_Symbol); - } - - bh_arr_push(use_node->only, qu); - - if (parser->curr->type != '}') - expect_token(parser, ','); - } - } - - if (use_node->expr->kind == Ast_Kind_Package) { - ENTITY_SUBMIT(use_node); - return NULL; - - } else { - return (AstNode *) use_node; - } -} - -static AstNode* parse_jump_stmt(OnyxParser* parser, TokenType token_type, JumpType jump_type) { - AstJump* jnode = make_node(AstJump, Ast_Kind_Jump); - jnode->token = expect_token(parser, token_type); - jnode->jump = jump_type; - - u64 count = 1; - while (consume_token_if_next(parser, token_type)) count++; - jnode->count = count; - - return (AstNode *) jnode; -} - -static AstNode* parse_statement(OnyxParser* parser) { - b32 needs_semicolon = 1; - AstNode* retval = NULL; - - switch ((u16) parser->curr->type) { - case Token_Type_Keyword_Return: - retval = (AstNode *) parse_return_stmt(parser); - break; - - case '{': - case Token_Type_Empty_Block: - case Token_Type_Keyword_Do: - needs_semicolon = 0; - retval = (AstNode *) parse_block(parser, 1, NULL); - break; - - case Token_Type_Symbol: { - i32 symbol_res = parse_possible_symbol_declaration(parser, &retval); - if (symbol_res == 2) needs_semicolon = 0; - if (symbol_res != 0) break; - - // fallthrough - } - - case '(': case '+': case '-': case '!': case '*': case '^': - case Token_Type_Literal_Integer: - case Token_Type_Literal_Float: - case Token_Type_Literal_String: - retval = (AstNode *) parse_compound_expression(parser, 1); - if (retval->kind == Ast_Kind_Call || retval->kind == Ast_Kind_Method_Call) { - if (parser->curr->type == '{') { - AstCodeBlock* code_block = make_node(AstCodeBlock, Ast_Kind_Code_Block); - code_block->token = parser->curr; - code_block->type_node = builtin_code_type; - - code_block->code = (AstNode *) parse_block(parser, 1, NULL); - ((AstBlock *) code_block->code)->rules = Block_Rule_Code_Block; - - AstCall *dest = (AstCall *) retval; - if (dest->kind == Ast_Kind_Method_Call) { - dest = (AstCall *) ((AstBinaryOp *) dest)->right; - if (dest->kind != Ast_Kind_Call) { - onyx_report_error(retval->token->pos, Error_Critical, "Expected function call on right side of '->'."); - needs_semicolon = 0; - break; - } - } - - bh_arr_push(dest->args.values, (AstTyped *) make_argument(context.ast_alloc, (AstTyped *) code_block)); - needs_semicolon = 0; - } - } - break; - - case Token_Type_Keyword_If: - needs_semicolon = 0; - retval = (AstNode *) parse_if_stmt(parser); - break; - - case Token_Type_Keyword_While: - needs_semicolon = 0; - retval = (AstNode *) parse_while_stmt(parser); - break; - - case Token_Type_Keyword_For: - needs_semicolon = 0; - retval = (AstNode *) parse_for_stmt(parser); - break; - - case Token_Type_Keyword_Switch: - needs_semicolon = 0; - retval = (AstNode *) parse_switch_stmt(parser); - break; - - case Token_Type_Keyword_Case: - needs_semicolon = 0; - retval = (AstNode *) parse_case_stmt(parser); - break; - - case Token_Type_Keyword_Break: - retval = parse_jump_stmt(parser, Token_Type_Keyword_Break, Jump_Type_Break); - break; - - case Token_Type_Keyword_Continue: - retval = parse_jump_stmt(parser, Token_Type_Keyword_Continue, Jump_Type_Continue); - break; - - case Token_Type_Keyword_Fallthrough: - retval = parse_jump_stmt(parser, Token_Type_Keyword_Fallthrough, Jump_Type_Fallthrough); - break; - - case Token_Type_Keyword_Defer: { - needs_semicolon = 0; - - AstDefer* defer = make_node(AstDefer, Ast_Kind_Defer); - defer->token = expect_token(parser, Token_Type_Keyword_Defer); - defer->stmt = parse_statement(parser); - - retval = (AstNode *) defer; - break; - } - - case Token_Type_Keyword_Use: { - needs_semicolon = 0; - - retval = (AstNode *) parse_use_stmt(parser); - break; - } - - case '#': { - if (parse_possible_directive(parser, "context_scope")) { - // :LinearTokenDependent - OnyxToken* directive_token = parser->curr - 2; - - OnyxToken* sym_token = bh_alloc_item(parser->allocator, OnyxToken); - sym_token->type = Token_Type_Symbol; - sym_token->length = 15; - sym_token->text = bh_strdup(parser->allocator, "__saved_context "); - sym_token->pos = ((OnyxFilePos) {0}); - - AstNode *sym_node = make_symbol(parser->allocator, sym_token); - - AstLocal* context_tmp = make_local(parser->allocator, sym_token, NULL); - retval = (AstNode *) context_tmp; - - AstBinaryOp* assignment = make_node(AstBinaryOp, Ast_Kind_Binary_Op); - assignment->token = directive_token; - assignment->operation = Binary_Op_Assign; - assignment->left = (AstTyped *) sym_node; - assignment->right = builtin_context_variable; - context_tmp->next = (AstNode *) assignment; - - AstBinaryOp* assignment2 = make_node(AstBinaryOp, Ast_Kind_Binary_Op); - assignment2->token = directive_token + 1; - assignment2->operation = Binary_Op_Assign; - assignment2->left = builtin_context_variable; - assignment2->right = (AstTyped *) sym_node; - - AstDefer* defer_node = make_node(AstDefer, Ast_Kind_Defer); - defer_node->token = directive_token; - defer_node->stmt = (AstNode *) assignment2; - assignment->next = (AstNode *) defer_node; - - AstBlock* context_block = parse_block(parser, 1, NULL); - defer_node->next = context_block->body; - context_block->body = (AstNode *) context_tmp; - - needs_semicolon = 0; - break; - } - - if (next_tokens_are(parser, 2, '#', Token_Type_Keyword_If)) { - AstIf* static_if = parse_static_if_stmt(parser, 1); - - assert(parser->current_function_stack && bh_arr_length(parser->current_function_stack) > 0); - bh_arr_push(bh_arr_last(parser->current_function_stack)->nodes_that_need_entities_after_clone, (AstNode *) static_if); - - needs_semicolon = 0; - retval = (AstNode *) static_if; - break; - } - - if (parse_possible_directive(parser, "persist")) { - b32 thread_local = parse_possible_directive(parser, "thread_local"); - - OnyxToken* symbol = expect_token(parser, Token_Type_Symbol); - AstMemRes* memres = parse_memory_reservation(parser, symbol, thread_local); - - AstBinding* binding = make_node(AstBinding, Ast_Kind_Binding); - binding->token = memres->token; - binding->node = (AstNode *) memres; - ENTITY_SUBMIT(binding); - break; - } - - if (parse_possible_directive(parser, "remove")) { - // :LinearTokenDependent - AstDirectiveRemove *remove = make_node(AstDirectiveRemove, Ast_Kind_Directive_Remove); - remove->token = parser->curr - 2; - retval = (AstNode *) remove; - break; - } - - if (next_tokens_are(parser, 2, '#', Token_Type_Symbol)) { - retval = (AstNode *) parse_factor(parser); - break; - } - } - - default: - break; - } - - if (needs_semicolon) expect_token(parser, ';'); - - return retval; -} - -static AstBlock* parse_block(OnyxParser* parser, b32 make_a_new_scope, char* block_name) { - AstBlock* block = make_node(AstBlock, Ast_Kind_Block); - block->rules = Block_Rule_Normal; - - // NOTE: --- is for an empty block - if (parser->curr->type == Token_Type_Empty_Block) { - block->token = expect_token(parser, Token_Type_Empty_Block); - return block; - } - - if (make_a_new_scope) { - block->binding_scope = scope_create(parser->allocator, parser->current_scope, parser->curr->pos); - block->binding_scope->name = block_name; - parser->current_scope = block->binding_scope; - } - - if (parser->curr->type == Token_Type_Keyword_Do) { - block->token = expect_token(parser, Token_Type_Keyword_Do); - block->body = parse_statement(parser); - if (make_a_new_scope) parser->current_scope = parser->current_scope->parent; - return block; - } - - block->token = expect_token(parser, '{'); - - AstNode** next = &block->body; - AstNode* stmt = NULL; - while (!consume_token_if_next(parser, '}')) { - if (parser->hit_unexpected_token) { - if (make_a_new_scope) parser->current_scope = parser->current_scope->parent; - return block; - } - - stmt = parse_statement(parser); - - if (stmt != NULL && stmt->kind != Ast_Kind_Error) { - *next = stmt; - - while (stmt->next != NULL) stmt = stmt->next; - next = &stmt->next; - } - } - - if (make_a_new_scope) parser->current_scope = parser->current_scope->parent; - return block; -} - -static void parse_polymorphic_variable(OnyxParser* parser, AstType*** next_insertion) { - bh_arr(AstPolyParam) pv = NULL; - - if (parser->polymorph_context.poly_params == NULL) - onyx_report_error(parser->curr->pos, Error_Critical, "Polymorphic variable not valid here."); - else - pv = *parser->polymorph_context.poly_params; - - consume_token(parser); - - AstNode* symbol_node = make_node(AstNode, Ast_Kind_Symbol); - symbol_node->token = expect_token(parser, Token_Type_Symbol); - - **next_insertion = (AstType *) symbol_node; - *next_insertion = NULL; - - if (pv != NULL) { - bh_arr_push(pv, ((AstPolyParam) { - .kind = PPK_Poly_Type, - .poly_sym = symbol_node, - - // These will be filled out by function_params() - .type_expr = NULL, - .idx = -1, - })); - - *parser->polymorph_context.poly_params = pv; - } -} - -static AstType* parse_compound_type(OnyxParser* parser) { - // CLEANUP this is little weird having this here because it means that this parses: - // - // foo :: (x: (something_here: i32)) -> void --- - // - if (next_tokens_are(parser, 2, Token_Type_Symbol, ':')) { - consume_tokens(parser, 2); - } - - AstType* first = parse_type(parser); - - if (parser->curr->type == ',') { - AstCompoundType* ctype = make_node(AstCompoundType, Ast_Kind_Type_Compound); - ctype->token = parser->curr; - - bh_arr_new(global_heap_allocator, ctype->types, 2); - bh_arr_push(ctype->types, first); - - while (consume_token_if_next(parser, ',')) { - if (parser->hit_unexpected_token) return (AstType *) ctype; - - if (next_tokens_are(parser, 2, Token_Type_Symbol, ':')) { - consume_tokens(parser, 2); - } - - bh_arr_push(ctype->types, parse_type(parser)); - } - - return (AstType *) ctype; - - } else { - return first; - } -} - -static AstType* parse_function_type(OnyxParser* parser, OnyxToken* proc_token) { - bh_arr(AstType *) params = NULL; - bh_arr_new(global_scratch_allocator, params, 4); - bh_arr_set_length(params, 0); - - expect_token(parser, '('); - while (!consume_token_if_next(parser, ')')) { - if (parser->hit_unexpected_token) return NULL; - - // NOTE: Allows for names to be put in the function types, just for readability. - if (next_tokens_are(parser, 2, Token_Type_Symbol, ':')) consume_tokens(parser, 2); - - AstType* param_type = parse_type(parser); - bh_arr_push(params, param_type); - - if (parser->curr->type != ')') - expect_token(parser, ','); - } - - AstType* return_type = (AstType *) &basic_type_void; - if (consume_token_if_next(parser, Token_Type_Right_Arrow)) - return_type = parse_type(parser); - - i64 param_count = bh_arr_length(params); - AstFunctionType* new = onyx_ast_node_new(parser->allocator, - sizeof(AstFunctionType) + sizeof(AstType*) * param_count, - Ast_Kind_Function_Type); - new->token = proc_token; - new->param_count = param_count; - new->return_type = return_type; - - if (param_count > 0) - fori (i, 0, param_count) new->params[i] = params[i]; - - return (AstType *) new; -} - -static AstType* parse_type(OnyxParser* parser) { - AstType* root = NULL; - AstType** next_insertion = &root; - - while (1) { - if (parser->hit_unexpected_token) return root; - - switch ((u16) parser->curr->type) { - case '^': { - AstPointerType* new = make_node(AstPointerType, Ast_Kind_Pointer_Type); - new->flags |= Basic_Flag_Pointer; - new->token = expect_token(parser, '^'); - - *next_insertion = (AstType *) new; - next_insertion = &new->elem; - break; - } - - case '[': { - AstType *new; - OnyxToken *open_bracket = expect_token(parser, '['); - - if (parser->curr->type == ']') { - new = make_node(AstSliceType, Ast_Kind_Slice_Type); - new->token = open_bracket; - - } else if (parser->curr->type == Token_Type_Dot_Dot) { - new = make_node(AstDynArrType, Ast_Kind_DynArr_Type); - new->token = open_bracket; - consume_token(parser); - - } else { - new = make_node(AstArrayType, Ast_Kind_Array_Type); - new->token = open_bracket; - - if (parser->curr->type == '$') { - AstType** insertion = (AstType **) &((AstArrayType *) new)->count_expr; - parse_polymorphic_variable(parser, &insertion); - } else { - ((AstArrayType *) new)->count_expr = parse_expression(parser, 0); - } - } - - expect_token(parser, ']'); - *next_insertion = (AstType *) new; - next_insertion = &((AstSliceType *) new)->elem; - break; - } - - case '$': { - parse_polymorphic_variable(parser, &next_insertion); - break; - } - - case Token_Type_Symbol: { - AstNode* symbol_node = make_node(AstNode, Ast_Kind_Symbol); - symbol_node->token = expect_token(parser, Token_Type_Symbol); - - *next_insertion = (AstType *) symbol_node; - - while (consume_token_if_next(parser, '.')) { - AstFieldAccess* field = make_node(AstFieldAccess, Ast_Kind_Field_Access); - field->token = expect_token(parser, Token_Type_Symbol); - field->expr = (AstTyped *) *next_insertion; - - *next_insertion = (AstType *) field; - } - - if (parser->curr->type == '(' && parser->parse_calls) { - OnyxToken* paren_token = expect_token(parser, '('); - - bh_arr(AstNode *) params = NULL; - bh_arr_new(global_heap_allocator, params, 2); - - while (!consume_token_if_next(parser, ')')) { - if (parser->hit_unexpected_token) break; - - AstNode* t = (AstNode *) parse_expression(parser, 0); - bh_arr_push(params, t); - - if (parser->curr->type != ')') - expect_token(parser, ','); - } - - AstPolyCallType* pc_type = make_node(AstPolyCallType, Ast_Kind_Poly_Call_Type); - pc_type->token = paren_token; - pc_type->callee = *next_insertion; - pc_type->params = params; - - *next_insertion = (AstType *) pc_type; - } - - next_insertion = NULL; - break; - } - - case Token_Type_Keyword_Struct: { - AstStructType* s_node = parse_struct(parser); - *next_insertion = (AstType *) s_node; - next_insertion = NULL; - break; - } - - // - // I don't think any of these cases are necesary any more? - case Token_Type_Literal_Integer: - case Token_Type_Literal_String: - case Token_Type_Literal_Float: - case Token_Type_Literal_True: - case Token_Type_Literal_False: - case '-': { - *next_insertion = (AstType *) parse_expression(parser, 0); - next_insertion = NULL; - break; - } - - case '(': { - OnyxToken* matching = find_matching_paren(parser->curr); - - // :LinearTokenDependent - if ((matching + 1)->type == Token_Type_Right_Arrow) { - *next_insertion = parse_function_type(parser, parser->curr); - - } else { - expect_token(parser, '('); - *next_insertion = parse_compound_type(parser); - expect_token(parser, ')'); - } - - next_insertion = NULL; - break; - } - - case Token_Type_Keyword_Typeof: { - *next_insertion = (AstType *) parse_typeof(parser); - next_insertion = NULL; - break; - } - - default: - onyx_report_error(parser->curr->pos, Error_Critical, "unexpected token '%b'.", parser->curr->text, parser->curr->length); - consume_token(parser); - break; - } - - if (next_insertion == NULL) break; - } - - return root; -} - -static AstTypeOf* parse_typeof(OnyxParser* parser) { - OnyxToken* token = expect_token(parser, Token_Type_Keyword_Typeof); - - AstTypeOf* type_of = make_node(AstTypeOf, Ast_Kind_Typeof); - type_of->token = token; - type_of->expr = parse_expression(parser, 0); - type_of->resolved_type = NULL; - - return type_of; -} - -static void struct_type_create_scope(OnyxParser *parser, AstStructType *s_node) { - if (!s_node->scope) { - s_node->scope = scope_create(context.ast_alloc, parser->current_scope, s_node->token->pos); - parser->current_scope = s_node->scope; - - OnyxToken* current_symbol = bh_arr_last(parser->current_symbol_stack); - s_node->scope->name = bh_aprintf(global_heap_allocator, "%b", current_symbol->text, current_symbol->length); - } -} - -static AstStructType* parse_struct(OnyxParser* parser) { - OnyxToken *s_token = expect_token(parser, Token_Type_Keyword_Struct); - - AstStructType* s_node; - AstPolyStructType* poly_struct = NULL; - - s_node = make_node(AstStructType, Ast_Kind_Struct_Type); - s_node->token = s_token; - - flush_stored_tags(parser, &s_node->meta_tags); - - // Parse polymorphic parameters - if (consume_token_if_next(parser, '(')) { - bh_arr(AstPolyStructParam) poly_params = NULL; - bh_arr_new(global_heap_allocator, poly_params, 1); - - while (!consume_token_if_next(parser, ')')) { - if (parser->hit_unexpected_token) return NULL; - - OnyxToken* sym_token = expect_token(parser, Token_Type_Symbol); - expect_token(parser, ':'); - - AstType* param_type = parse_type(parser); - - bh_arr_push(poly_params, ((AstPolyStructParam) { - .token = sym_token, - .type_node = param_type, - .type = NULL, - })); - - if (parser->curr->type != ')') - expect_token(parser, ','); - } - - poly_struct = make_node(AstPolyStructType, Ast_Kind_Poly_Struct_Type); - poly_struct->token = s_token; - poly_struct->poly_params = poly_params; - poly_struct->base_struct = s_node; - } - - // Parse constraints clause - if (parser->curr->type == Token_Type_Keyword_Where) { - parse_constraints(parser, &s_node->constraints); - } - - bh_arr_new(global_heap_allocator, s_node->members, 4); - - // Parse directives - while (parser->curr->type == '#') { - if (parser->hit_unexpected_token) return NULL; - - if (parse_possible_directive(parser, "union")) s_node->is_union = 1; - - else if (parse_possible_directive(parser, "pack")) s_node->is_packed = 1; - - else if (parse_possible_directive(parser, "align")) { - s_node->min_alignment_ = parse_expression(parser, 0); - } - - else if (parse_possible_directive(parser, "size")) { - s_node->min_size_ = parse_expression(parser, 0); - } - - else { - OnyxToken* directive_token = expect_token(parser, '#'); - OnyxToken* symbol_token = expect_token(parser, Token_Type_Symbol); - - onyx_report_error(directive_token->pos, Error_Critical, "unknown directive '#%b'.", symbol_token->text, symbol_token->length); - } - } - - expect_token(parser, '{'); - - b32 member_is_used = 0; - bh_arr(OnyxToken *) member_list_temp = NULL; - bh_arr_new(global_heap_allocator, member_list_temp, 4); - - while (!consume_token_if_next(parser, '}')) { - if (parser->hit_unexpected_token) return s_node; - - if (parse_possible_directive(parser, "persist")) { - struct_type_create_scope(parser, s_node); - - b32 thread_local = parse_possible_directive(parser, "thread_local"); - - OnyxToken* symbol = expect_token(parser, Token_Type_Symbol); - AstMemRes* memres = parse_memory_reservation(parser, symbol, thread_local); - consume_token_if_next(parser, ';'); - - AstBinding* binding = make_node(AstBinding, Ast_Kind_Binding); - binding->token = memres->token; - binding->node = (AstNode *) memres; - ENTITY_SUBMIT(binding); - continue; - } - - if (next_tokens_are(parser, 3, Token_Type_Symbol, ':', ':')) { - struct_type_create_scope(parser, s_node); - - OnyxToken* binding_name = expect_token(parser, Token_Type_Symbol); - consume_token(parser); - - AstBinding* binding = parse_top_level_binding(parser, binding_name); - if (binding) ENTITY_SUBMIT(binding); - - consume_token_if_next(parser, ';'); - continue; - } - - bh_arr(AstTyped *) meta_tags=NULL; - while (parse_possible_directive(parser, "tag")) { - if (meta_tags == NULL) bh_arr_new(global_heap_allocator, meta_tags, 1); - - parser->tag_depth += 1; - - do { - AstTyped* expr = parse_expression(parser, 0); - bh_arr_push(meta_tags, expr); - } while (consume_token_if_next(parser, ',')); - - parser->tag_depth -= 1; - } - - member_is_used = consume_token_if_next(parser, Token_Type_Keyword_Use); - - bh_arr_clear(member_list_temp); - while (!consume_token_if_next(parser, ':')) { - if (parser->hit_unexpected_token) return NULL; - bh_arr_push(member_list_temp, expect_token(parser, Token_Type_Symbol)); - - if (parser->curr->type != ':') - expect_token(parser, ','); - } - - AstType* member_type = NULL; - if (parser->curr->type != '=') - member_type = parse_type(parser); - - AstTyped* initial_value = NULL; - if (consume_token_if_next(parser, '=')) - initial_value = parse_expression(parser, 0); - - // RECONSIDER: There are seamingly arbitrary limitations put in place here which do two things: - // 1. Prevent multiple struct members being used in the same declaration. - // This makes sense because the members will be of the same type, which means - // they have the same members. Using both of the members would immediately result - // in name collisions. - // - // 2. Prevent multiple struct members having an initializer set for them. - // I think the semantics could be confusing either way, so I'm deciding to leave - // them out of discussion for now. Initialized members should be treated special and - // deserve their own line. - if (bh_arr_length(member_list_temp) > 1) { - if (member_is_used) onyx_report_error((member_list_temp[0] - 1)->pos, Error_Critical, "'use' is only allowed for a single struct member declaration. Try splitting this compound declaration into multiple lines."); - if (initial_value) onyx_report_error(initial_value->token->pos, Error_Critical, "Intialized values are only allowed on single struct member declarations. Try splitting this compound initializer into multiple lines."); - } - - bh_arr_each(OnyxToken *, member_name, member_list_temp) { - AstStructMember* mem = make_node(AstStructMember, Ast_Kind_Struct_Member); - mem->token = *member_name; - mem->type_node = member_type; - mem->initial_value = initial_value; - mem->meta_tags = meta_tags; - - if (member_is_used) mem->is_used = 1; - - bh_arr_push(s_node->members, mem); - } - - if (peek_token(0)->type != '}') { - expect_token(parser, ';'); - } - } - - if (s_node->scope) parser->current_scope = parser->current_scope->parent; - - bh_arr_free(member_list_temp); - - if (poly_struct != NULL) { - // NOTE: Not a StructType - return (AstStructType *) poly_struct; - - } else { - ENTITY_SUBMIT(s_node); - return s_node; - } -} - -static AstInterface* parse_interface(OnyxParser* parser) { - AstInterface *interface = make_node(AstInterface, Ast_Kind_Interface); - interface->token = expect_token(parser, Token_Type_Keyword_Interface); - - bh_arr_new(global_heap_allocator, interface->params, 2); - - expect_token(parser, '('); - while (!consume_token_if_next(parser, ')')) { - if (parser->hit_unexpected_token) return interface; - - InterfaceParam ip; - ip.value_token = expect_token(parser, Token_Type_Symbol); - expect_token(parser, ':'); - expect_token(parser, '$'); - ip.type_token = expect_token(parser, Token_Type_Symbol); - - bh_arr_push(interface->params, ip); - - if (parser->curr->type != ')') - expect_token(parser, ','); - } - - bh_arr_new(global_heap_allocator, interface->exprs, 2); - - expect_token(parser, '{'); - while (!consume_token_if_next(parser, '}')) { - if (parser->hit_unexpected_token) return interface; - - InterfaceConstraint ic = {0}; - if (parse_possible_directive(parser, "not")) { - ic.invert_condition = 1; - } - - if (consume_token_if_next(parser, '{')) { - ic.expr = parse_expression(parser, 0); - - expect_token(parser, '}'); - expect_token(parser, Token_Type_Right_Arrow); - - ic.expected_type_expr = parse_type(parser); - - } else { - ic.expr = parse_expression(parser, 0); - } - - bh_arr_push(interface->exprs, ic); - - expect_token(parser, ';'); - } - - return interface; -} - -static AstConstraint* parse_constraint(OnyxParser* parser) { - AstConstraint* constraint = make_node(AstConstraint, Ast_Kind_Constraint); - - parser->parse_calls = 0; - constraint->interface = (AstInterface *) parse_factor(parser); - parser->parse_calls = 1; - - constraint->token = constraint->interface->token; - - bh_arr_new(global_heap_allocator, constraint->type_args, 2); - - expect_token(parser, '('); - while (!consume_token_if_next(parser, ')')) { - if (parser->hit_unexpected_token) return constraint; - - AstType* type_node = parse_type(parser); - bh_arr_push(constraint->type_args, type_node); - - if (parser->curr->type != ')') - expect_token(parser, ','); - } - - return constraint; -} - -static void parse_constraints(OnyxParser* parser, ConstraintContext *out_constraints) { - bh_arr_new(global_heap_allocator, out_constraints->constraints, 2); - - expect_token(parser, Token_Type_Keyword_Where); - - do { - AstConstraint *constraint = parse_constraint(parser); - if (parser->hit_unexpected_token) return; - - bh_arr_push(out_constraints->constraints, constraint); - } while (consume_token_if_next(parser, ',')); -} - -static void parse_function_params(OnyxParser* parser, AstFunction* func) { - expect_token(parser, '('); - - if (consume_token_if_next(parser, ')')) return; - - u32 param_idx = 0; - assert(parser->polymorph_context.poly_params != NULL); - - bh_arr(AstParam) param_buffer=NULL; - bh_arr_new(global_heap_allocator, param_buffer, 2); - - OnyxToken* symbol; - while (!consume_token_if_next(parser, ')')) { - do { - if (parser->hit_unexpected_token) return; - - b32 param_use = 0; - b32 param_is_baked = 0; - AstParam curr_param = { 0 }; - - if (consume_token_if_next(parser, Token_Type_Keyword_Use)) param_use = 1; - if (consume_token_if_next(parser, '$')) param_is_baked = 1; - - symbol = expect_token(parser, Token_Type_Symbol); - - curr_param.vararg_kind = VA_Kind_Not_VA; - curr_param.local = make_local(parser->allocator, symbol, NULL); - curr_param.local->kind = Ast_Kind_Param; - - if (param_use) { - curr_param.is_used = 1; - param_use = 0; - } - - if (param_is_baked) { - curr_param.is_baked = 1; - param_is_baked = 0; - } - - bh_arr_push(param_buffer, curr_param); - } while (consume_token_if_next(parser, ',')); - - expect_token(parser, ':'); - - VarArgKind vararg_kind=VA_Kind_Not_VA; - AstType* type_node=NULL; - AstTyped* default_value=NULL; - - if (parser->curr->type != '=') { - if (consume_token_if_next(parser, Token_Type_Dot_Dot)) { - if (consume_token_if_next(parser, '.')) vararg_kind = VA_Kind_Untyped; - else vararg_kind = VA_Kind_Typed; - } - - if (vararg_kind != VA_Kind_Untyped) { - // CLEANUP: This is mess and it is hard to follow what is going on here. - // I think with recent rewrites, this should be easier to do. - i32 old_len = bh_arr_length(*parser->polymorph_context.poly_params); - type_node = parse_type(parser); - i32 new_len = bh_arr_length(*parser->polymorph_context.poly_params); - - if (vararg_kind == VA_Kind_Typed) { - AstVarArgType* va_type = make_node(AstVarArgType, Ast_Kind_VarArg_Type); - va_type->elem = type_node; - va_type->token = type_node->token; - type_node = (AstType *) va_type; - } - - fori (i, 0, new_len - old_len) { - (*parser->polymorph_context.poly_params)[old_len + i].type_expr = type_node; - (*parser->polymorph_context.poly_params)[old_len + i].idx = param_idx; - } - } - } - - if (vararg_kind == VA_Kind_Not_VA && consume_token_if_next(parser, '=')) { - OnyxToken* directive_token = parser->curr; - - // :Callsite currently #callsite is only valid as a default value for a funciton parameter. - if (parse_possible_directive(parser, "callsite")) { - AstCallSite* cs = make_node(AstCallSite, Ast_Kind_Call_Site); - cs->token = directive_token; - default_value = (AstTyped *) cs; - - } else { - default_value = parse_expression(parser, 0); - } - } - - bh_arr_each(AstParam, param, param_buffer) { - param->vararg_kind = vararg_kind; - param->local->type_node = type_node; - param->default_value = default_value; - - if (param->is_baked) { - bh_arr(AstPolyParam) pv = *parser->polymorph_context.poly_params; - bh_arr_push(pv, ((AstPolyParam) { - .kind = PPK_Baked_Value, - .idx = param_idx, - - .poly_sym = (AstNode *) param->local, - .type_expr = type_node, - })); - - *parser->polymorph_context.poly_params = pv; - } - - bh_arr_push(func->params, *param); - param_idx++; - } - - bh_arr_clear(param_buffer); - - if (parser->curr->type != ')') - expect_token(parser, ','); - } - - bh_arr_free(param_buffer); - return; -} - -static AstOverloadedFunction* parse_overloaded_function(OnyxParser* parser, OnyxToken* token) { - b32 locked = 0; - if (parse_possible_directive(parser, "locked")) { - locked = 1; - } - - b32 local = 0; - if (parse_possible_directive(parser, "local")) { - local = 1; - } - - // This could be checked elsewhere? - if (locked && local) { - onyx_report_error(token->pos, Error_Critical, "Only one of '#locked' and '#local' can because use at a time."); - } - - expect_token(parser, '{'); - - AstOverloadedFunction* ofunc = make_node(AstOverloadedFunction, Ast_Kind_Overloaded_Function); - ofunc->token = token; - ofunc->flags |= Ast_Flag_Comptime; - ofunc->locked = locked; - ofunc->only_local_functions = local; - - bh_arr_new(global_heap_allocator, ofunc->overloads, 4); - - u64 precedence = 0; - while (!consume_token_if_next(parser, '}')) { - if (parser->hit_unexpected_token) return ofunc; - - if (parse_possible_directive(parser, "precedence")) { - AstNumLit* pre = parse_int_literal(parser); - if (parser->hit_unexpected_token) return ofunc; - - precedence = bh_max(pre->value.l, 0); - } - - AstTyped* option = parse_expression(parser, 0); - add_overload_option(&ofunc->overloads, precedence++, option); - - if (parser->curr->type != '}') - expect_token(parser, ','); - } - - ENTITY_SUBMIT(ofunc); - return ofunc; -} - -static AstFunction* parse_function_definition(OnyxParser* parser, OnyxToken* token) { - AstFunction* func_def = make_node(AstFunction, Ast_Kind_Function); - func_def->token = token; - bh_arr_push(parser->current_function_stack, func_def); - - flush_stored_tags(parser, &func_def->tags); - - bh_arr_new(global_heap_allocator, func_def->params, 4); - - bh_arr(AstPolyParam) polymorphic_vars = NULL; - bh_arr_new(global_heap_allocator, polymorphic_vars, 4); - // defer bh_arr_free(polymorphic_vars); - - parser->polymorph_context.poly_params = &polymorphic_vars; - parse_function_params(parser, func_def); - parser->polymorph_context.poly_params = NULL; - - func_def->return_type = (AstType *) &basic_type_void; - - char* name = NULL; - if (bh_arr_length(parser->current_symbol_stack) > 0) { - OnyxToken *current_symbol = bh_arr_last(parser->current_symbol_stack); - name = bh_aprintf(global_heap_allocator, "%b", current_symbol->text, current_symbol->length); - } - - if (consume_token_if_next(parser, '=')) { - expect_token(parser, '>'); - func_def->return_type = (AstType *) &basic_type_auto_return; - - if (parser->curr->type == '{') { - func_def->body = parse_block(parser, 1, name); - - } else { - AstTyped* returned_value = parse_compound_expression(parser, 0); - if (returned_value == NULL) goto function_defined; - - AstReturn* return_node = make_node(AstReturn, Ast_Kind_Return); - return_node->token = returned_value->token; - return_node->expr = returned_value; - - AstBlock* body_block = make_node(AstBlock, Ast_Kind_Block); - body_block->token = returned_value->token; - body_block->body = (AstNode *) return_node; - - func_def->body = body_block; - } - - goto function_defined; - } - - if (consume_token_if_next(parser, Token_Type_Right_Arrow)) { - if (parse_possible_directive(parser, "auto")) { - func_def->return_type = (AstType *) &basic_type_auto_return; - } else { - func_def->return_type = parse_type(parser); - } - } - - if (parser->curr->type == Token_Type_Keyword_Where) { - parse_constraints(parser, &func_def->constraints); - } - - while (parser->curr->type == '#') { - if (parse_possible_directive(parser, "intrinsic")) { - func_def->is_intrinsic = 1; - - if (parser->curr->type == Token_Type_Literal_String) { - func_def->intrinsic_name = expect_token(parser, Token_Type_Literal_String); - } - } - - else if (parse_possible_directive(parser, "foreign")) { - func_def->foreign_module = expect_token(parser, Token_Type_Literal_String); - func_def->foreign_name = expect_token(parser, Token_Type_Literal_String); - - func_def->is_foreign = 1; - } - - // HACK: NullProcHack - else if (parse_possible_directive(parser, "null")) { - func_def->flags |= Ast_Flag_Proc_Is_Null; - } - - else { - OnyxToken* directive_token = expect_token(parser, '#'); - OnyxToken* symbol_token = expect_token(parser, Token_Type_Symbol); - - onyx_report_error(directive_token->pos, Error_Critical, "unknown directive '#%b'.", symbol_token->text, symbol_token->length); - } - } - - func_def->body = parse_block(parser, 1, name); - - // :LinearTokenDependent - func_def->closing_brace = parser->curr - 1; - -function_defined: - if (bh_arr_length(polymorphic_vars) > 0) { - func_def->kind = Ast_Kind_Polymorphic_Proc; - func_def->poly_params = polymorphic_vars; - - } else { - bh_arr_free(polymorphic_vars); - } - - bh_arr_pop(parser->current_function_stack); - return func_def; -} - -static b32 parse_possible_function_definition_no_consume(OnyxParser* parser) { - if (parser->curr->type == '(') { - OnyxToken* matching_paren = find_matching_paren(parser->curr); - if (matching_paren == NULL) return 0; - - if (next_tokens_are(parser, 4, '(', ')', '=', '>')) return 0; - - // :LinearTokenDependent - OnyxToken* token_after_paren = matching_paren + 1; - if (token_after_paren->type != Token_Type_Right_Arrow - && token_after_paren->type != '{' - && token_after_paren->type != Token_Type_Keyword_Do - && token_after_paren->type != Token_Type_Empty_Block - && token_after_paren->type != Token_Type_Keyword_Where - && (token_after_paren->type != '=' || (token_after_paren + 1)->type != '>')) - return 0; - - // :LinearTokenDependent - b32 is_params = (parser->curr + 1) == matching_paren; - OnyxToken* tmp_token = parser->curr; - while (!is_params && tmp_token < matching_paren) { - if (tmp_token->type == ':') is_params = 1; - - tmp_token++; - } - - if (!is_params) return 0; - - return 1; - } - - return 0; -} - -static b32 parse_possible_function_definition(OnyxParser* parser, AstTyped** ret) { - if (parse_possible_function_definition_no_consume(parser)) { - OnyxToken* proc_token = parser->curr; - AstFunction* func_node = parse_function_definition(parser, proc_token); - *ret = (AstTyped *) func_node; - - return 1; - } - - return 0; -} - -typedef struct QuickParam { - OnyxToken* token; - b32 is_baked; -} QuickParam; - -static b32 parse_possible_quick_function_definition_no_consume(OnyxParser* parser) { - // - // x => x + 1 case. - if (next_tokens_are(parser, 3, Token_Type_Symbol, '=', '>')) { - return 1; - } - - if (parser->curr->type != '(') return 0; - - OnyxToken* matching_paren = find_matching_paren(parser->curr); - if (matching_paren == NULL) return 0; - - // :LinearTokenDependent - OnyxToken* token_after_paren = matching_paren + 1; - if (token_after_paren->type != '=' || (token_after_paren + 1)->type != '>') - return 0; - - return 1; -} - -static b32 parse_possible_quick_function_definition(OnyxParser* parser, AstTyped** ret) { - if (!parse_possible_quick_function_definition_no_consume(parser)) return 0; - - bh_arr(QuickParam) params=NULL; - bh_arr_new(global_heap_allocator, params, 4); - OnyxToken* proc_token; - - if (parser->curr->type == Token_Type_Symbol) { - QuickParam param = { 0 }; - param.token = expect_token(parser, Token_Type_Symbol); - proc_token = param.token; - bh_arr_push(params, param); - - } else { - proc_token = expect_token(parser, '('); - while (parser->curr->type != ')') { - if (parser->hit_unexpected_token) return 0; - - QuickParam param = { 0 }; - if (consume_token_if_next(parser, '$')) param.is_baked = 1; - param.token = expect_token(parser, Token_Type_Symbol); - - bh_arr_push(params, param); - - if (parser->curr->type != ')') { - expect_token(parser, ','); - } - } - - expect_token(parser, ')'); - } - - expect_token(parser, '='); - expect_token(parser, '>'); - - bh_arr(AstNode *) poly_params=NULL; - bh_arr_new(global_heap_allocator, poly_params, bh_arr_length(params)); - bh_arr_each(QuickParam, param, params) { - char text[512]; - memset(text, 0, 512); - strncat(text, "__type_", 511); - token_toggle_end(param->token); - strncat(text, param->token->text, 511); - token_toggle_end(param->token); - - OnyxToken* new_token = bh_alloc(parser->allocator, sizeof(OnyxToken)); - new_token->type = Token_Type_Symbol; - new_token->length = 7 + param->token->length; - new_token->text = bh_strdup(parser->allocator, text); - new_token->pos = param->token->pos; - - AstNode* type_node = make_symbol(parser->allocator, new_token); - bh_arr_push(poly_params, type_node); - } - - AstFunction* poly_proc = make_node(AstFunction, Ast_Kind_Polymorphic_Proc); - - bh_arr_new(global_heap_allocator, poly_proc->params, bh_arr_length(params)); - fori (i, 0, bh_arr_length(params)) { - AstLocal* param_local = make_local(parser->allocator, params[i].token, (AstType *) poly_params[i]); - param_local->kind = Ast_Kind_Param; - - bh_arr_push(poly_proc->params, ((AstParam) { - .local = param_local, - .default_value = NULL, - - .vararg_kind = 0, - .use_processed = 0, - })); - } - - AstBlock* body_block; - AstType* return_type; - bh_arr_push(parser->current_function_stack, poly_proc); - - if (parser->curr->type == '{') { - char* name = NULL; - if (bh_arr_length(parser->current_symbol_stack) > 0) { - OnyxToken *current_symbol = bh_arr_last(parser->current_symbol_stack); - name = bh_aprintf(global_heap_allocator, "%b", current_symbol->text, current_symbol->length); - } - - body_block = parse_block(parser, 1, name); - return_type = (AstType *) &basic_type_auto_return; - - } else { - AstTyped* body = parse_compound_expression(parser, 0); - - AstReturn* return_node = make_node(AstReturn, Ast_Kind_Return); - return_node->token = body->token; - return_node->expr = body; - - body_block = make_node(AstBlock, Ast_Kind_Block); - body_block->token = body->token; - body_block->body = (AstNode *) return_node; - - AstTypeOf* return_type_of = make_node(AstTypeOf, Ast_Kind_Typeof); - return_type_of->token = body->token; - return_type_of->expr = body; - return_type = (AstType *) return_type_of; - } - - poly_proc->token = proc_token; - poly_proc->body = body_block; - poly_proc->return_type = (AstType *) return_type; - - poly_proc->token = proc_token; - bh_arr_new(global_heap_allocator, poly_proc->poly_params, bh_arr_length(params)); - fori (i, 0, bh_arr_length(params)) { - bh_arr_push(poly_proc->poly_params, ((AstPolyParam) { - .kind = PSK_Type, - .idx = i, - .poly_sym = poly_params[i], - .type_expr = (AstType *) poly_params[i], - .type = NULL, - })); - - if (params[i].is_baked) { - // This is not handled currently, as you cannot say f :: ($x: $T) yet, which is what this would have to do. - } - } - - *ret = (AstTyped *) poly_proc; - - bh_arr_pop(parser->current_function_stack); - bh_arr_free(params); - bh_arr_free(poly_params); - return 1; -} - -static AstTyped* parse_global_declaration(OnyxParser* parser) { - expect_no_stored_tags(parser); - - AstGlobal* global_node = make_node(AstGlobal, Ast_Kind_Global); - global_node->token = expect_token(parser, Token_Type_Keyword_Global); - - global_node->type_node = parse_type(parser); - - ENTITY_SUBMIT(global_node); - - return (AstTyped *) global_node; -} - -static AstEnumType* parse_enum_declaration(OnyxParser* parser) { - expect_no_stored_tags(parser); - - AstEnumType* enum_node = make_node(AstEnumType, Ast_Kind_Enum_Type); - enum_node->token = expect_token(parser, Token_Type_Keyword_Enum); - - bh_arr_new(global_heap_allocator, enum_node->values, 4); - - while (parser->curr->type == '#') { - if (parser->hit_unexpected_token) return enum_node; - - if (parse_possible_directive(parser, "flags")) { - enum_node->is_flags = 1; - } else { - OnyxToken* directive_token = expect_token(parser, '#'); - OnyxToken* symbol_token = expect_token(parser, Token_Type_Symbol); - - onyx_report_error(directive_token->pos, Error_Critical, "unknown directive '#%b'.", symbol_token->text, symbol_token->length); - } - } - - AstType* backing = (AstType *) &basic_type_u32; - if (consume_token_if_next(parser, '(')) { - AstNode* backing_sym = make_node(AstNode, Ast_Kind_Symbol); - backing_sym->token = expect_token(parser, Token_Type_Symbol); - backing = (AstType *) backing_sym; - - expect_token(parser, ')'); - } - enum_node->backing = backing; - - expect_token(parser, '{'); - - while (!consume_token_if_next(parser, '}')) { - if (parser->hit_unexpected_token) return enum_node; - - AstEnumValue* evalue = make_node(AstEnumValue, Ast_Kind_Enum_Value); - evalue->token = expect_token(parser, Token_Type_Symbol); - evalue->type_node = (AstType *) enum_node; - - if (consume_token_if_next(parser, ':')) { - expect_token(parser, ':'); - evalue->value = parse_expression(parser, 0); - } - - expect_token(parser, ';'); - - bh_arr_push(enum_node->values, evalue); - } - - return enum_node; -} - -static AstIf* parse_static_if_stmt(OnyxParser* parser, b32 parse_block_as_statements) { - expect_no_stored_tags(parser); - - AstIf* static_if_node = make_node(AstIf, Ast_Kind_Static_If); - static_if_node->token = expect_token(parser, '#'); - static_if_node->defined_in_scope = parser->current_scope; - expect_token(parser, Token_Type_Keyword_If); - - static_if_node->cond = parse_expression(parser, 0); - - bh_arr_new(global_heap_allocator, static_if_node->true_entities, 2); - bh_arr_push(parser->alternate_entity_placement_stack, &static_if_node->true_entities); - - if (parse_block_as_statements) { - static_if_node->true_stmt = parse_block(parser, 0, NULL); - - } else { - expect_token(parser, '{'); - while (!consume_token_if_next(parser, '}')) { - if (parser->hit_unexpected_token) return static_if_node; - - parse_top_level_statement(parser); - } - } - - bh_arr_pop(parser->alternate_entity_placement_stack); - - if (consume_token_if_next(parser, Token_Type_Keyword_Else)) { - bh_arr_new(global_heap_allocator, static_if_node->false_entities, 2); - bh_arr_push(parser->alternate_entity_placement_stack, &static_if_node->false_entities); - - if (parse_block_as_statements) { - static_if_node->false_stmt = parse_block(parser, 0, NULL); - - } else { - expect_token(parser, '{'); - while (!consume_token_if_next(parser, '}')) { - if (parser->hit_unexpected_token) return static_if_node; - - parse_top_level_statement(parser); - } - } - - bh_arr_pop(parser->alternate_entity_placement_stack); - } - - return static_if_node; -} - -static AstMemRes* parse_memory_reservation(OnyxParser* parser, OnyxToken* symbol, b32 threadlocal) { - expect_token(parser, ':'); - - expect_no_stored_tags(parser); - - AstMemRes* memres = make_node(AstMemRes, Ast_Kind_Memres); - memres->threadlocal = threadlocal; - memres->token = symbol; - - if (parser->curr->type != '=') - memres->type_node = parse_type(parser); - - if (consume_token_if_next(parser, '=')) - memres->initial_value = parse_expression(parser, 1); - - ENTITY_SUBMIT(memres); - return memres; -} - -static AstMacro* parse_macro(OnyxParser* parser) { - expect_no_stored_tags(parser); - - AstMacro* macro = make_node(AstMacro, Ast_Kind_Macro); - macro->token = expect_token(parser, Token_Type_Keyword_Macro); - - if (parse_possible_function_definition(parser, ¯o->body)) { - ENTITY_SUBMIT(macro); - return macro; - } - - if (parse_possible_quick_function_definition(parser, ¯o->body)) { - ENTITY_SUBMIT(macro); - return macro; - } - - onyx_report_error(parser->curr->pos, Error_Critical, "'macro' expects to be followed by a producure definition."); - return NULL; -} - -static AstDirectiveInit* parse_init_directive(OnyxParser *parser, OnyxToken *token) { - AstDirectiveInit *init = make_node(AstDirectiveInit, Ast_Kind_Directive_Init); - init->token = token; - - parser->parse_calls = 0; - while (parse_possible_directive(parser, "after")) { - if (parser->hit_unexpected_token) return init; - if (init->dependencies == NULL) bh_arr_new(global_heap_allocator, init->dependencies, 2); - - AstTyped *dependency = parse_expression(parser, 0); - bh_arr_push(init->dependencies, (AstDirectiveInit *) dependency); - } - parser->parse_calls = 1; - - init->init_proc = parse_expression(parser, 0); - ENTITY_SUBMIT(init); - return init; -} - -static AstForeignBlock* parse_foreign_block(OnyxParser* parser, OnyxToken *token) { - // :LinearTokenDependent - AstForeignBlock *fb = make_node(AstForeignBlock, Ast_Kind_Foreign_Block); - fb->token = token; - fb->module_name = expect_token(parser, Token_Type_Literal_String); - - // - // This has a fun implication that there cannot be foreign blocks in the builtin - // or type_info packages, as those are loaded before foreign_block_type has a value. - fb->type_node = foreign_block_type; - - bh_arr_new(global_heap_allocator, fb->captured_entities, 4); - bh_arr_push(parser->alternate_entity_placement_stack, &fb->captured_entities); - - expect_token(parser, '{'); - parse_top_level_statements_until(parser, '}'); - expect_token(parser, '}'); - - bh_arr_pop(parser->alternate_entity_placement_stack); - ENTITY_SUBMIT(fb); - - return fb; -} - -static AstTyped* parse_top_level_expression(OnyxParser* parser) { - if (parser->curr->type == Token_Type_Keyword_Global) return parse_global_declaration(parser); - if (parser->curr->type == Token_Type_Keyword_Struct) return (AstTyped *) parse_struct(parser); - if (parser->curr->type == Token_Type_Keyword_Interface) return (AstTyped *) parse_interface(parser); - if (parser->curr->type == Token_Type_Keyword_Enum) return (AstTyped *) parse_enum_declaration(parser); - if (parser->curr->type == Token_Type_Keyword_Macro) return (AstTyped *) parse_macro(parser); - - if (parser->curr->type == '#') { - if (parse_possible_directive(parser, "type")) { - AstTypeAlias* alias = make_node(AstTypeAlias, Ast_Kind_Type_Alias); - alias->to = parse_type(parser); - return (AstTyped *) alias; - } - - if (parse_possible_directive(parser, "match")) { - // :LinearTokenDependent - OnyxToken* directive_token = parser->curr - 2; - AstOverloadedFunction* ofunc = parse_overloaded_function(parser, directive_token); - return (AstTyped *) ofunc; - } - - if (parse_possible_directive(parser, "init")) { - // :LinearTokenDependent - AstDirectiveInit *init = parse_init_directive(parser, parser->curr - 2); - return (AstTyped *) init; - } - - if (parse_possible_directive(parser, "distinct")) { - // :LinearTokenDependent - AstDistinctType *distinct = make_node(AstDistinctType, Ast_Kind_Distinct_Type); - distinct->token = parser->curr - 2; - distinct->base_type = parse_type(parser); - return (AstTyped *) distinct; - } - - if (parse_possible_directive(parser, "foreign")) { - AstForeignBlock *foreign = parse_foreign_block(parser, parser->curr - 2); - return (AstTyped *) foreign; - } - } - - return parse_expression(parser, 1); -} - -static char* generate_name_within_scope(OnyxParser* parser, OnyxToken* symbol) { - char name[512]; - memset(name, 0, 512); - - bh_arr(char *) names=NULL; - bh_arr_new(global_heap_allocator, names, 4); - - Scope* scope = parser->current_scope; - while (scope != NULL) { - bh_arr_push(names, scope->name); - scope = scope->parent; - } - - bh_arr_each(char *, n, names) { - if (*n == NULL) continue; - - strncat(name, *n, 511); - strncat(name, ".", 511); - } - bh_arr_free(names); - - return bh_aprintf(global_heap_allocator, "%s%b", name, symbol->text, symbol->length); -} - -static AstBinding* parse_top_level_binding(OnyxParser* parser, OnyxToken* symbol) { - OnyxToken *after_second_colon = expect_token(parser, ':'); - if (after_second_colon) after_second_colon += 1; - - AstTyped* node = parse_top_level_expression(parser); - if (parser->hit_unexpected_token || node == NULL) - return NULL; - - switch (node->kind) { - case Ast_Kind_Function: - case Ast_Kind_Polymorphic_Proc: { - AstFunction* func = (AstFunction *) node; - - if (func->intrinsic_name == NULL) func->intrinsic_name = symbol; - - func->name = generate_name_within_scope(parser, symbol); - break; - } - - case Ast_Kind_Macro: { - AstMacro* macro = (AstMacro *) node; - - AstFunction* func = (AstFunction *) macro->body; - func->name = generate_name_within_scope(parser, symbol); - break; - } - - case Ast_Kind_Directive_Init: break; - - case Ast_Kind_Global: ((AstGlobal *) node)->name = generate_name_within_scope(parser, symbol); - - case Ast_Kind_Overloaded_Function: - case Ast_Kind_StrLit: - break; - - case Ast_Kind_Interface: - case Ast_Kind_Struct_Type: - case Ast_Kind_Poly_Struct_Type: - case Ast_Kind_Enum_Type: - case Ast_Kind_Distinct_Type: - ((AstStructType *) node)->name = generate_name_within_scope(parser, symbol); - goto default_case; - - case Ast_Kind_Type_Alias: - node->token = symbol; - goto default_case; - - case Ast_Kind_Package: goto default_case; - case Ast_Kind_NumLit: goto default_case; - - default: { - if (!node_is_type((AstNode *) node)) { - AstAlias* alias = make_node(AstAlias, Ast_Kind_Alias); - alias->token = node->token; - alias->alias = node; - node = (AstTyped *) alias; - } - -default_case: - ENTITY_SUBMIT(node); - } - } - - AstBinding* binding = make_node(AstBinding, Ast_Kind_Binding); - binding->token = symbol; - binding->node = (AstNode *) node; - - if (after_second_colon) expect_no_stored_tags_pos(parser, after_second_colon->pos); - return binding; -} - -static void parse_top_level_statement(OnyxParser* parser) { - AstFlags private_kind = 0; - if (bh_arr_length(parser->scope_flags) > 0) - private_kind = bh_arr_last(parser->scope_flags); - - // :CLEANUP this very repetetive code... - if (next_tokens_are(parser, 2, '#', Token_Type_Keyword_Package)) { - consume_tokens(parser, 2); - private_kind = Ast_Flag_Private_Package; - if (parser->curr->type == '{') { - bh_arr_push(parser->scope_flags, private_kind); - - expect_token(parser, '{'); - parse_top_level_statements_until(parser, '}'); - expect_token(parser, '}'); - - bh_arr_pop(parser->scope_flags); - return; - } - } - else if (parse_possible_directive(parser, "local")) { - private_kind = Ast_Flag_Private_File; - if (parser->curr->type == '{') { - bh_arr_push(parser->scope_flags, private_kind); - - expect_token(parser, '{'); - parse_top_level_statements_until(parser, '}'); - expect_token(parser, '}'); - - bh_arr_pop(parser->scope_flags); - return; - } - } - - AstBinding* binding = NULL; - - switch ((u16) parser->curr->type) { - case Token_Type_Keyword_Use: { - AstNode* use_node = parse_use_stmt(parser); - if (use_node) ENTITY_SUBMIT(use_node); - return; - } - - case Token_Type_Symbol: { - OnyxToken* symbol = expect_token(parser, Token_Type_Symbol); - - if (next_tokens_are(parser, 2, ':', ':')) { - expect_token(parser, ':'); - - bh_arr_push(parser->current_symbol_stack, symbol); - binding = parse_top_level_binding(parser, symbol); - bh_arr_pop(parser->current_symbol_stack); - - if (binding != NULL) binding->flags |= private_kind; - - goto submit_binding_to_entities; - } - - AstMemRes* memres = parse_memory_reservation(parser, symbol, 0); - - binding = make_node(AstBinding, Ast_Kind_Binding); - binding->token = symbol; - binding->flags |= private_kind; - binding->node = (AstNode *) memres; - - goto submit_binding_to_entities; - } - - case '(': { - AstTyped *retval = NULL; - if (parse_possible_function_definition(parser, &retval)) { - ENTITY_SUBMIT(retval); - return; - } - if (parse_possible_quick_function_definition(parser, &retval)) { - ENTITY_SUBMIT(retval); - return; - } - break; - } - - case '#': { - if (next_tokens_are(parser, 2, '#', Token_Type_Keyword_If)) { - AstIf* static_if = parse_static_if_stmt(parser, 0); - ENTITY_SUBMIT(static_if); - return; - } - - OnyxToken* dir_token = parser->curr; - - if (parse_possible_directive(parser, "load")) { - AstInclude* include = make_node(AstInclude, Ast_Kind_Load_File); - include->token = dir_token; - include->name_node = parse_expression(parser, 0); - - ENTITY_SUBMIT(include); - return; - } - else if (parse_possible_directive(parser, "load_all")) { - AstInclude* include = make_node(AstInclude, Ast_Kind_Load_All); - include->token = dir_token; - include->name_node = parse_expression(parser, 0); - - ENTITY_SUBMIT(include); - return; - } - else if (parse_possible_directive(parser, "load_path")) { - AstInclude* include = make_node(AstInclude, Ast_Kind_Load_Path); - include->token = dir_token; - include->name_node = parse_expression(parser, 0); - - ENTITY_SUBMIT(include); - return; - } - else if (parse_possible_directive(parser, "library_path")) { - AstInclude* include = make_node(AstInclude, Ast_Kind_Library_Path); - include->token = dir_token; - include->name_node = parse_expression(parser, 0); - - ENTITY_SUBMIT(include); - return; - } - else if (parse_possible_directive(parser, "error")) { - AstDirectiveError *error = make_node(AstDirectiveError, Ast_Kind_Directive_Error); - error->token = dir_token; - error->error_msg = expect_token(parser, Token_Type_Literal_String); - - ENTITY_SUBMIT(error); - return; - } - else if (parse_possible_directive(parser, "foreign")) { - parse_foreign_block(parser, parser->curr - 2); - return; - } - else if (parse_possible_directive(parser, "operator")) { - AstDirectiveOperator *operator = make_node(AstDirectiveOperator, Ast_Kind_Directive_Operator); - operator->token = dir_token; - - // These cases have to happen first because these are not necessarily "binary operators", - // they are just things that I want to be able to overload. []= is technically a ternary - // operator so all these things are horribly named anyway. - if (next_tokens_are(parser, 3, '^', '[', ']')) { - consume_tokens(parser, 3); - operator->operator = Binary_Op_Ptr_Subscript; - goto operator_determined; - } - - if (next_tokens_are(parser, 3, '[', ']', '=')) { - consume_tokens(parser, 3); - operator->operator = Binary_Op_Subscript_Equals; - goto operator_determined; - } - - // The default case - BinaryOp op = binary_op_from_token_type(parser->curr->type); - consume_token(parser); - if (op == Binary_Op_Subscript) expect_token(parser, ']'); // #operator [] ... needs to consume the other ']' - - if (op == Binary_Op_Count) { - onyx_report_error(parser->curr->pos, Error_Critical, "Invalid binary operator."); - } else { - operator->operator = op; - } - - operator_determined: - operator->overload = parse_expression(parser, 0); - - ENTITY_SUBMIT(operator); - return; - } - else if (parse_possible_directive(parser, "match") || parse_possible_directive(parser, "overload")) { - AstDirectiveAddOverload *add_overload = make_node(AstDirectiveAddOverload, Ast_Kind_Directive_Add_Overload); - add_overload->token = dir_token; - - if (parse_possible_directive(parser, "precedence")) { - AstNumLit* pre = parse_int_literal(parser); - if (parser->hit_unexpected_token) return; - - add_overload->precedence = bh_max(pre->value.l, 0); - } else { - add_overload->precedence = 0; - } - - parser->parse_calls = 0; - add_overload->overloaded_function = (AstNode *) parse_expression(parser, 0); - parser->parse_calls = 1; - - // Allow for - // #match - // something :: (....) { - // } - // - // This will make converting something to a overloaded - // function easier and require less copying by the programmer. - if (next_tokens_are(parser, 2, ':', ':')) { - consume_tokens(parser, 2); - } - - add_overload->overload = parse_expression(parser, 0); - - ENTITY_SUBMIT(add_overload); - return; - } - else if (parse_possible_directive(parser, "inject")) { - AstInjection *inject = make_node(AstInjection, Ast_Kind_Injection); - inject->token = dir_token; - - parser->parse_calls = 0; - inject->full_loc = parse_expression(parser, 0); - parser->parse_calls = 1; - - // See comment above - if (next_tokens_are(parser, 2, ':', ':')) { - consume_tokens(parser, 2); - } - - inject->to_inject = parse_expression(parser, 0); - - ENTITY_SUBMIT(inject); - return; - } - else if (parse_possible_directive(parser, "export")) { - AstDirectiveExport *export = make_node(AstDirectiveExport, Ast_Kind_Directive_Export); - export->token = dir_token; - parser->parse_calls = 0; - export->export_name_expr = parse_expression(parser, 0); // expect_token(parser, Token_Type_Literal_String); - parser->parse_calls = 1; - - export->export = parse_expression(parser, 0); - - ENTITY_SUBMIT(export); - return; - } - else if (parse_possible_directive(parser, "thread_local")) { - OnyxToken* symbol = expect_token(parser, Token_Type_Symbol); - AstMemRes* memres = parse_memory_reservation(parser, symbol, 1); - - binding = make_node(AstBinding, Ast_Kind_Binding); - binding->token = symbol; - binding->flags |= private_kind; - binding->node = (AstNode *) memres; - - goto submit_binding_to_entities; - } - else if (parse_possible_directive(parser, "init")) { - // :LinearTokenDependent - parse_init_directive(parser, parser->curr - 2); - return; - } - else if (parse_possible_directive(parser, "library")) { - // :LinearTokenDependent - AstDirectiveLibrary *library = make_node(AstDirectiveLibrary, Ast_Kind_Directive_Library); - library->token = parser->curr - 2; - library->library_symbol = parse_expression(parser, 0); - - ENTITY_SUBMIT(library); - return; - } - else if (parse_possible_directive(parser, "tag")) { - parser->tag_depth += 1; - - AstTyped *expr = parse_expression(parser, 0); - bh_arr_push(parser->stored_tags, expr); - - parser->tag_depth -= 1; - return; - } - else { - OnyxToken* directive_token = expect_token(parser, '#'); - OnyxToken* symbol_token = expect_token(parser, Token_Type_Symbol); - - onyx_report_error(directive_token->pos, Error_Critical, "unknown directive '#%b'.", symbol_token->text, symbol_token->length); - return; - } - } - - default: break; - } - - expect_token(parser, ';'); - return; - -submit_binding_to_entities: - { - if (!binding) return; - - Scope* target_scope = parser->package->scope; - - if (binding->flags & Ast_Flag_Private_Package) - target_scope = parser->package->private_scope; - if (binding->flags & Ast_Flag_Private_File) - target_scope = parser->file_scope; - - ENTITY_SUBMIT_IN_SCOPE(binding, target_scope); - } -} - -static AstPackage* parse_package_expression(OnyxParser* parser) { - AstPackage* package_node = make_node(AstPackage, Ast_Kind_Package); - package_node->token = expect_token(parser, Token_Type_Keyword_Package); - - bh_arr_new(global_heap_allocator, package_node->path, 2); - - while (parser->curr->type == Token_Type_Symbol) { - if (parser->hit_unexpected_token) return package_node; - - OnyxToken* symbol = expect_token(parser, Token_Type_Symbol); - - bh_arr_push(package_node->path, symbol); - - if (consume_token_if_next(parser, '.')); - else break; - } - - i32 total_package_name_length = 0; - bh_arr_each(OnyxToken *, token, package_node->path) { - total_package_name_length += (*token)->length + 1; - } - - char* package_name = bh_alloc_array(context.ast_alloc, char, total_package_name_length); - *package_name = '\0'; - - bh_arr_each(OnyxToken *, token, package_node->path) { - token_toggle_end(*token); - strncat(package_name, (*token)->text, total_package_name_length - 1); - token_toggle_end(*token); - - if (token != &bh_arr_last(package_node->path)) { - strncat(package_name, ".", total_package_name_length - 1); - } - } - - package_node->package_name = package_name; - package_node->package = package_lookup(package_name); - - return package_node; -} - -static Package* parse_file_package(OnyxParser* parser) { - if (parser->curr->type != Token_Type_Keyword_Package) { - return package_lookup_or_create("main", context.global_scope, parser->allocator, parser->curr->pos); - } - - AstPackage* package_node = parse_package_expression(parser); - - char aggregate_name[2048]; - aggregate_name[0] = '\0'; - - Package* prevpackage = NULL; - - bh_arr_each(OnyxToken *, symbol, package_node->path) { - token_toggle_end(*symbol); - - strncat(aggregate_name, (*symbol)->text, 2047); - Package* newpackage = package_lookup_or_create(aggregate_name, context.global_scope, parser->allocator, package_node->token->pos); - - AstPackage* pnode = make_node(AstPackage, Ast_Kind_Package); - pnode->token = *symbol; - pnode->package = newpackage; - pnode->package_name = newpackage->name; - - if (prevpackage != NULL) { - symbol_subpackage_introduce(prevpackage->scope, (*symbol)->text, pnode); - package_reinsert_use_packages(prevpackage); - } - - token_toggle_end(*symbol); - strncat(aggregate_name, ".", 2047); - - prevpackage = newpackage; - } - - package_node->package = prevpackage; - - return package_node->package; -} - -static void parse_top_level_statements_until(OnyxParser* parser, TokenType tt) { - while (parser->curr->type != tt) { - if (parser->hit_unexpected_token) break; - if (onyx_has_errors()) break; - parse_top_level_statement(parser); - } -} - - -// NOTE: This returns a void* so I don't need to cast it everytime I use it -void* onyx_ast_node_new(bh_allocator alloc, i32 size, AstKind kind) { - void* node = bh_alloc(alloc, size); - - memset(node, 0, size); - *(AstKind *) node = kind; - - return node; -} - -OnyxParser onyx_parser_create(bh_allocator alloc, OnyxTokenizer *tokenizer) { - OnyxParser parser; - - parser.allocator = alloc; - parser.tokenizer = tokenizer; - parser.curr = tokenizer->tokens; - parser.prev = NULL; - parser.hit_unexpected_token = 0; - parser.current_scope = NULL; - parser.alternate_entity_placement_stack = NULL; - parser.current_symbol_stack = NULL; - parser.current_function_stack = NULL; - parser.scope_flags = NULL; - parser.stored_tags = NULL; - parser.parse_calls = 1; - parser.tag_depth = 0; - - parser.polymorph_context = (PolymorphicContext) { - .root_node = NULL, - .poly_params = NULL, - }; - - bh_arr_new(global_heap_allocator, parser.alternate_entity_placement_stack, 4); - bh_arr_new(global_heap_allocator, parser.current_symbol_stack, 4); - bh_arr_new(global_heap_allocator, parser.scope_flags, 4); - bh_arr_new(global_heap_allocator, parser.stored_tags, 4); - - return parser; -} - -void onyx_parser_free(OnyxParser* parser) { -} - -void onyx_parse(OnyxParser *parser) { - // NOTE: Skip comments at the beginning of the file - while (consume_token_if_next(parser, Token_Type_Comment) || consume_token_if_next(parser, Token_Type_Note)); - - parser->package = parse_file_package(parser); - parser->file_scope = scope_create(parser->allocator, parser->package->private_scope, parser->tokenizer->tokens[0].pos); - parser->current_scope = parser->file_scope; - - parse_top_level_statements_until(parser, Token_Type_End_Stream); - - parser->current_scope = parser->current_scope->parent; -} diff --git a/src/polymorph.h b/src/polymorph.h deleted file mode 100644 index 0185b8f3..00000000 --- a/src/polymorph.h +++ /dev/null @@ -1,1091 +0,0 @@ - -// -// Polymorphic Procedures -// - -// This flag is used by some of the procedures that try working with polymorphic things, -// but need to wait until more information is known. Instead of passing a out parameter -// into each of these procedures, a single global variable is used instead. If the type -// checker ever gets multi-threaded, this would have to become a threadlocal variable. -static b32 flag_to_yield = 0; - -// This flag is used in the very special case that you are passing a polymorphic procedure -// to a polymorphic procedure, and you have enough information to instantiate said procedure -// in order to resolve the type of one of the return values. -static b32 doing_nested_polymorph_lookup = 0; - -// The name is pretty self-descriptive, but this is a node that is returned from things -// like polymorphic_proc_lookup when it is determined that everything works so far, but -// the caller must yield in order to finish checking this polymorphic procedure. -AstTyped node_that_signals_a_yield = { Ast_Kind_Function, 0 }; -AstTyped node_that_signals_failure = { Ast_Kind_Error, 0 }; - -static void ensure_polyproc_cache_is_created(AstFunction* pp) { - if (pp->concrete_funcs == NULL) sh_new_arena(pp->concrete_funcs); - if (pp->active_queries.hashes == NULL) bh_imap_init(&pp->active_queries, global_heap_allocator, 31); -} - -void insert_poly_sln_into_scope(Scope* scope, AstPolySolution *sln) { - AstNode *node = NULL; - - switch (sln->kind) { - case PSK_Type: - node = onyx_ast_node_new(context.ast_alloc, sizeof(AstTypeRawAlias), Ast_Kind_Type_Raw_Alias); - ((AstTypeRawAlias *) node)->token = sln->poly_sym->token; - ((AstTypeRawAlias *) node)->to = sln->type; - ((AstTypeRawAlias *) node)->type = &basic_types[Basic_Kind_Type_Index]; - ((AstTypeRawAlias *) node)->type_id = sln->type->id; - break; - - case PSK_Value: - // CLEANUP: Maybe clone this? - assert(sln->value->flags & Ast_Flag_Comptime); - node = (AstNode *) sln->value; - break; - } - - symbol_introduce(scope, sln->poly_sym->token, node); -} - -static void insert_poly_slns_into_scope(Scope* scope, bh_arr(AstPolySolution) slns) { - bh_arr_each(AstPolySolution, sln, slns) { - insert_poly_sln_into_scope(scope, sln); - } -} - -// NOTE: This might return a volatile string. Do not store it without copying it. -static char* build_poly_solution_key(AstPolySolution* sln) { - if (sln->kind == PSK_Type) { - return (char *) type_get_unique_name(sln->type); - - } else if (sln->kind == PSK_Value) { - static char buffer[256]; - - fori (i, 0, 256) buffer[i] = 0; - - if (sln->value->kind == Ast_Kind_NumLit) { - strncat(buffer, "NUMLIT:", 127); - strncat(buffer, bh_bprintf("%l", ((AstNumLit *) sln->value)->value.l), 127); - - } else { - // HACK: For now, the value pointer is just used. This means that - // sometimes, even through the solution is the same, it won't be - // stored the same. - bh_snprintf(buffer, 128, "%p", sln->value); - } - - return buffer; - } - - return NULL; -} - -// NOTE: This returns a volatile string. Do not store it without copying it. -static char* build_poly_slns_unique_key(bh_arr(AstPolySolution) slns) { - static char key_buf[1024]; - fori (i, 0, 1024) key_buf[i] = 0; - - bh_arr_each(AstPolySolution, sln, slns) { - token_toggle_end(sln->poly_sym->token); - - strncat(key_buf, sln->poly_sym->token->text, 1023); - strncat(key_buf, "=", 1023); - strncat(key_buf, build_poly_solution_key(sln), 1023); - strncat(key_buf, ";", 1023); - - token_toggle_end(sln->poly_sym->token); - } - - return key_buf; -} - -// NOTE: This function adds a solidified function to the entity heap for it to be processed -// later. It optionally can start the function header entity at the code generation state if -// the header has already been processed. -static b32 add_solidified_function_entities(AstSolidifiedFunction *solidified_func) { - solidified_func->func->flags |= Ast_Flag_Function_Used; - solidified_func->func->flags |= Ast_Flag_From_Polymorphism; - - Entity func_header_entity = { - .state = Entity_State_Resolve_Symbols, - .type = Entity_Type_Function_Header, - .function = solidified_func->func, - .package = NULL, - .scope = solidified_func->func->poly_scope, - }; - - Entity func_entity = { - .state = Entity_State_Resolve_Symbols, - .type = Entity_Type_Function, - .function = solidified_func->func, - .package = NULL, - .scope = solidified_func->func->poly_scope, - }; - - Entity* entity_header = entity_heap_insert(&context.entities, func_header_entity); - Entity* entity_body = entity_heap_insert(&context.entities, func_entity); - - solidified_func->func_header_entity = entity_header; - solidified_func->func->entity_header = entity_header; - solidified_func->func->entity_body = entity_body; - solidified_func->func->entity = entity_body; - return 1; -} - -// NOTE: This function is responsible for taking all of the information about generating -// a new polymorphic variant, and producing a solidified function. It optionally can only -// generate the header of the function, which is useful for cases such as checking if a -// set of arguments works for a polymorphic overload option. -static AstSolidifiedFunction generate_solidified_function( - AstFunction* pp, - bh_arr(AstPolySolution) slns, - OnyxToken* tkn, - b32 header_only) { - - AstSolidifiedFunction solidified_func; - solidified_func.func_header_entity = NULL; - - // NOTE: Use the position of token if one was provided, otherwise just use NULL. - OnyxFilePos poly_scope_pos = { 0 }; - if (tkn) poly_scope_pos = tkn->pos; - - if (header_only) { - solidified_func.func = (AstFunction *) clone_function_header(context.ast_alloc, pp); - solidified_func.func->flags |= Ast_Flag_Incomplete_Body; - - } else { - solidified_func.func = (AstFunction *) ast_clone(context.ast_alloc, pp); - } - - solidified_func.func->poly_scope = scope_create(context.ast_alloc, pp->parent_scope_of_poly_proc, poly_scope_pos); - insert_poly_slns_into_scope(solidified_func.func->poly_scope, slns); - - solidified_func.func->flags |= Ast_Flag_From_Polymorphism; - solidified_func.func->generated_from = tkn; - - // HACK: Remove the baked parameters from the function defintion so they can be - // resolved in the poly scope above the function. This does feel kinda of gross - // and I would love an alternative to tell it to just "skip" the parameter, but - // that is liable to breaking because it is one more thing to remember. - // - brendanfh 2021/01/18 - u32 removed_params = 0; - bh_arr_each(AstPolyParam, param, pp->poly_params) { - if (param->kind != PPK_Baked_Value) continue; - - bh_arr_deleten(solidified_func.func->params, param->idx - removed_params, 1); - removed_params++; - } - - return solidified_func; -} - -static void ensure_solidified_function_has_body(AstFunction* pp, AstSolidifiedFunction *solidified_func) { - if (solidified_func->func->flags & Ast_Flag_Incomplete_Body) { - clone_function_body(context.ast_alloc, solidified_func->func, pp); - - // HACK: I'm asserting that this function should return without an error, because - // the only case where it can return an error is if there was a problem with the - // header. This should never be the case in this situation, since the header would - // have to have successfully passed type checking before it would become a solidified - // procedure. - assert(add_solidified_function_entities(solidified_func)); - - solidified_func->func->flags &= ~Ast_Flag_Incomplete_Body; - } -} - -// NOTE: These are temporary data structures used to represent the pattern matching system -// of polymorphic type resolution. -typedef struct PolySolveResult { - PolySolutionKind kind; - union { - AstTyped* value; - Type* actual; - }; -} PolySolveResult; - -typedef struct PolySolveElem { - AstType* type_expr; - - PolySolutionKind kind; - union { - AstTyped* value; - Type* actual; - }; -} PolySolveElem; - -// NOTE: The job of this function is to solve for the type/value that belongs in a -// polymorphic variable. This function takes in three arguments: -// * The symbol node of the polymorphic parameter being searched for -// * The type expression that should contain the symbol node it is some where -// * The actual type to pattern match against -// -// This function utilizes a basic breadth-first search of the type_expr and actual type -// trees, always moving along them in parallel, so when the target is reached (if it is -// ever reached), the "actual" is the matched type/value. -static PolySolveResult solve_poly_type(AstNode* target, AstType* type_expr, Type* actual) { - bh_arr(PolySolveElem) elem_queue = NULL; - bh_arr_new(global_heap_allocator, elem_queue, 4); - - PolySolveResult result = { PSK_Undefined, { NULL } }; - - bh_arr_push(elem_queue, ((PolySolveElem) { - .type_expr = type_expr, - .kind = PSK_Type, - .actual = actual - })); - - while (!bh_arr_is_empty(elem_queue)) { - PolySolveElem elem = elem_queue[0]; - bh_arr_deleten(elem_queue, 0, 1); - - if (elem.type_expr == (AstType *) target) { - result.kind = elem.kind; - - assert(elem.kind != PSK_Undefined); - if (result.kind == PSK_Type) result.actual = elem.actual; - if (result.kind == PSK_Value) result.value = elem.value; - break; - } - - if (elem.kind != PSK_Type) continue; - - switch (elem.type_expr->kind) { - case Ast_Kind_Pointer_Type: { - if (elem.actual->kind != Type_Kind_Pointer) break; - - bh_arr_push(elem_queue, ((PolySolveElem) { - .type_expr = ((AstPointerType *) elem.type_expr)->elem, - .kind = PSK_Type, - .actual = elem.actual->Pointer.elem, - })); - break; - } - - case Ast_Kind_Address_Of: { - if (elem.actual->kind != Type_Kind_Pointer) break; - - bh_arr_push(elem_queue, ((PolySolveElem) { - .type_expr = (AstType *) ((AstAddressOf *) elem.type_expr)->expr, - .kind = PSK_Type, - .actual = elem.actual->Pointer.elem, - })); - break; - } - - case Ast_Kind_Array_Type: { - if (elem.actual->kind != Type_Kind_Array) break; - - bh_arr_push(elem_queue, ((PolySolveElem) { - .type_expr = (AstType*) ((AstArrayType *) elem.type_expr)->count_expr, - .kind = PSK_Value, - - // CLEANUP: Making an integer literal every time is very very very gross. This should - // at least be cached or something. - .value = (AstTyped *) make_int_literal(context.ast_alloc, elem.actual->Array.count) - })); - - bh_arr_push(elem_queue, ((PolySolveElem) { - .type_expr = ((AstArrayType *) elem.type_expr)->elem, - .kind = PSK_Type, - .actual = elem.actual->Array.elem, - })); - break; - } - - case Ast_Kind_Slice_Type: { - if (elem.actual->kind != Type_Kind_Slice && elem.actual->kind != Type_Kind_DynArray - && elem.actual->kind != Type_Kind_VarArgs && elem.actual->kind != Type_Kind_Array) break; - - bh_arr_push(elem_queue, ((PolySolveElem) { - .type_expr = ((AstSliceType *) elem.type_expr)->elem, - .kind = PSK_Type, - - // HACK: This makes the assumption that arrays, slices, dynamic arrays and varargs have the same element type at the same location. - .actual = elem.actual->Slice.elem, - })); - break; - } - - case Ast_Kind_DynArr_Type: { - if (elem.actual->kind != Type_Kind_DynArray) break; - - bh_arr_push(elem_queue, ((PolySolveElem) { - .type_expr = ((AstDynArrType *) elem.type_expr)->elem, - .kind = PSK_Type, - .actual = elem.actual->DynArray.elem, - })); - break; - } - - case Ast_Kind_VarArg_Type: - bh_arr_push(elem_queue, ((PolySolveElem) { - .type_expr = ((AstVarArgType *) elem.type_expr)->elem, - .kind = PSK_Type, - .actual = actual, - })); - break; - - case Ast_Kind_Function_Type: { - if (elem.actual->kind != Type_Kind_Function) break; - - AstFunctionType* ft = (AstFunctionType *) elem.type_expr; - - fori (i, 0, (i64) ft->param_count) { - bh_arr_push(elem_queue, ((PolySolveElem) { - .type_expr = ft->params[i], - .kind = PSK_Type, - .actual = elem.actual->Function.params[i], - })); - } - - bh_arr_push(elem_queue, ((PolySolveElem) { - .type_expr = ft->return_type, - .kind = PSK_Type, - .actual = elem.actual->Function.return_type, - })); - - break; - } - - case Ast_Kind_Call: { - AstPolyCallType *pct = convert_call_to_polycall((AstCall *) elem.type_expr); - elem.type_expr = (AstType *) pct; - - // fallthrough - } - - case Ast_Kind_Poly_Call_Type: { - if (elem.actual->kind != Type_Kind_Struct) break; - if (bh_arr_length(elem.actual->Struct.poly_sln) != bh_arr_length(((AstPolyCallType *) elem.type_expr)->params)) break; - - AstPolyCallType* pt = (AstPolyCallType *) elem.type_expr; - - fori (i, 0, bh_arr_length(pt->params)) { - PolySolutionKind kind = elem.actual->Struct.poly_sln[i].kind; - if (kind == PSK_Type) { - bh_arr_push(elem_queue, ((PolySolveElem) { - .kind = kind, - .type_expr = (AstType *) pt->params[i], - .actual = elem.actual->Struct.poly_sln[i].type, - })); - } else { - bh_arr_push(elem_queue, ((PolySolveElem) { - .kind = kind, - .type_expr = (AstType *) pt->params[i], - .value = elem.actual->Struct.poly_sln[i].value, - })); - } - } - - break; - } - - case Ast_Kind_Type_Compound: { - if (elem.actual->kind != Type_Kind_Compound) break; - if (elem.actual->Compound.count != (u32) bh_arr_length(((AstCompoundType *) elem.type_expr)->types)) break; - - AstCompoundType* ct = (AstCompoundType *) elem.type_expr; - - fori (i, 0, bh_arr_length(ct->types)) { - bh_arr_push(elem_queue, ((PolySolveElem) { - .kind = PSK_Type, - .type_expr = ct->types[i], - .actual = elem.actual->Compound.types[i], - })); - } - - break; - } - - default: break; - } - } - - bh_arr_free(elem_queue); - - return result; -} - -// NOTE: The job of this function is to take a polymorphic parameter and a set of arguments -// and solve for the argument that matches the parameter. This is needed because polymorphic -// procedure resolution has to happen before the named arguments are placed in their correct -// positions. -static AstTyped* lookup_param_in_arguments(AstFunction* func, AstPolyParam* param, Arguments* args, char** err_msg) { - bh_arr(AstTyped *) arg_arr = args->values; - bh_arr(AstNamedValue *) named_values = args->named_values; - - if ((i32) param->idx < 0) - return NULL; - - // NOTE: This check is safe because currently the arguments given without a name - // always map to the beginning indidies of the argument array. - if (param->idx >= (u64) bh_arr_length(arg_arr)) { - OnyxToken* param_name = func->params[param->idx].local->token; - - bh_arr_each(AstNamedValue *, named_value, named_values) { - if (token_equals(param_name, (*named_value)->token)) { - return (AstTyped *) (*named_value)->value; - } - } - - if (param->idx <= (u64) bh_arr_length(func->params)) { - if (func->params[param->idx].default_value) { - return (AstTyped *) func->params[param->idx].default_value; - } - } - - // CLEANUP - if (err_msg) *err_msg = "Not enough arguments to polymorphic procedure. This error message may not be entirely right."; - - } else { - return (AstTyped *) arg_arr[param->idx]; - } - - return NULL; -} - -static AstTyped* try_lookup_based_on_partial_function_type(AstFunction *pp, AstFunctionType *ft) { - if (ft->partial_function_type == NULL) { - AstType *old_return_type = ft->return_type; - ft->return_type = (AstType *) &basic_type_void; - ft->partial_function_type = type_build_from_ast(context.ast_alloc, (AstType *) ft); - ft->return_type = old_return_type; - if (!ft->partial_function_type) { - doing_nested_polymorph_lookup = 1; - return NULL; - } - - assert(ft->partial_function_type); - } - - AstTyped *result = (AstTyped *) polymorphic_proc_lookup(pp, PPLM_By_Function_Type, ft->partial_function_type, pp->token); - if (result && result->type == NULL) { - doing_nested_polymorph_lookup = 1; - result = NULL; - } - if (result == &node_that_signals_a_yield) { - doing_nested_polymorph_lookup = 1; - result = NULL; - } - - return result; -} - -// NOTE: The job of this function is to solve for type of AstPolySolution using the provided -// information. It is asssumed that the "param" is of kind PPK_Poly_Type. This function uses -// either the arguments provided, or a function type to compare against to pattern match for -// the type that the parameter but be. -static void solve_for_polymorphic_param_type(PolySolveResult* resolved, AstFunction* func, AstPolyParam* param, PolyProcLookupMethod pp_lookup, ptr actual, char** err_msg) { - Type* actual_type = NULL; - - switch (pp_lookup) { - case PPLM_By_Arguments: { - Arguments* args = (Arguments *) actual; - - AstTyped* typed_param = lookup_param_in_arguments(func, param, args, err_msg); - if (typed_param == NULL) return; - - // CLEANUP FIXME HACK TODO GROSS - if (typed_param->kind == Ast_Kind_Argument) { - AstTyped* potential = ((AstArgument *) typed_param)->value; - if (potential->kind == Ast_Kind_Polymorphic_Proc) { - if (param->idx < (u32) bh_arr_length(func->params)) { - AstType *param_type = func->params[param->idx].local->type_node; - if (param_type->kind == Ast_Kind_Function_Type) { - AstFunctionType *ft = (AstFunctionType *) param_type; - b32 all_types = 1; - fori (i, 0, (i32) ft->param_count) { - if (!node_is_type((AstNode *) ft->params[i])) { - all_types = 0; - break; - } - } - - if (all_types) { - typed_param = try_lookup_based_on_partial_function_type((AstFunction *) potential, ft); - } - } - } - } - } - - actual_type = resolve_expression_type(typed_param); - if (actual_type == NULL) return; - - break; - } - - case PPLM_By_Function_Type: { - Type* ft = (Type *) actual; - if (param->idx >= ft->Function.param_count) { - if (err_msg) *err_msg = "Incompatible polymorphic argument to function parameter."; - return; - } - - actual_type = ft->Function.params[param->idx]; - break; - } - - default: return; - } - - PolySolveResult res = solve_poly_type(param->poly_sym, param->type_expr, actual_type); - if (res.kind == PSK_Undefined) { - *err_msg = bh_aprintf(global_scratch_allocator, - "Unable to solve for polymorphic variable '%b', given the type '%s'.", - param->poly_sym->token->text, - param->poly_sym->token->length, - type_get_name(actual_type)); - } - - *resolved = res; -} - - -// NOTE: The job of this function is to look through the arguments provided and find a matching -// value that is to be baked into the polymorphic procedures poly-scope. It expected that param -// will be of kind PPK_Baked_Value. In other words, this handles the ($Baked: type) case. -// CLEANUP: This function is kind of gross at the moment, because it handles different cases for -// the argument kind. When type expressions (type_expr) become first-class types in the type -// system, this code should be able to be a lot cleaner. -static void solve_for_polymorphic_param_value(PolySolveResult* resolved, AstFunction* func, AstPolyParam* param, PolyProcLookupMethod pp_lookup, ptr actual, char** err_msg) { - if (pp_lookup != PPLM_By_Arguments) { - *err_msg = "Function type cannot be used to solved for baked parameter value."; - return; - } - - Arguments* args = (Arguments *) actual; - AstTyped* value = lookup_param_in_arguments(func, param, args, err_msg); - if (value == NULL) return; - - // HACK: Storing the original value because if this was an AstArgument, we need to flag - // it as baked if it is determined that the argument is of the correct kind and type. - AstTyped* orig_value = value; - if (value->kind == Ast_Kind_Argument) { - ((AstArgument *) orig_value)->is_baked = 0; - value = ((AstArgument *) value)->value; - } - - Type* param_type = NULL; - AstType *param_type_expr = func->params[param->idx].local->type_node; - if (param_type_expr == (AstType *) &basic_type_type_expr) { - if (!node_is_type((AstNode *) value)) { - if (err_msg) *err_msg = "Expected type expression."; - return; - } - - Type* resolved_type = type_build_from_ast(context.ast_alloc, (AstType *) value); - if (resolved_type == NULL) flag_to_yield = 1; - - *resolved = ((PolySolveResult) { PSK_Type, .actual = resolved_type }); - - } else { - resolve_expression_type(value); - - if ((value->flags & Ast_Flag_Comptime) == 0) { - if (err_msg) *err_msg = "Expected compile-time known argument."; - return; - } - - param_type = type_build_from_ast(context.ast_alloc, param_type_expr); - if (param_type == NULL) { - flag_to_yield = 1; - *err_msg = "Waiting to know type for polymorphic value."; - return; - } - - AstTyped* value_to_use = value; - if (value->kind == Ast_Kind_Macro) { - value_to_use = (AstTyped *) get_function_from_node((AstNode *) value); - } - - TypeMatch tm = unify_node_and_type(&value_to_use, param_type); - if (tm == TYPE_MATCH_FAILED) { - if (err_msg) *err_msg = bh_aprintf(global_scratch_allocator, - "The procedure '%s' expects a value of type '%s' for baked %d%s parameter, got '%s'.", - get_function_name(func), - type_get_name(param_type), - param->idx + 1, - bh_num_suffix(param->idx + 1), - node_get_type_name(value_to_use)); - return; - } - - if (tm == TYPE_MATCH_YIELD) flag_to_yield = 1; - - *resolved = ((PolySolveResult) { PSK_Value, value }); - } - - if (orig_value->kind == Ast_Kind_Argument) { - ((AstArgument *) orig_value)->is_baked = 1; - } -} - -TypeMatch find_polymorphic_sln(AstPolySolution *out, AstPolyParam *param, AstFunction *func, PolyProcLookupMethod pp_lookup, ptr actual, char** err_msg) { - // NOTE: Solve for the polymorphic parameter's value - PolySolveResult resolved = { PSK_Undefined }; - switch (param->kind) { - case PPK_Poly_Type: solve_for_polymorphic_param_type (&resolved, func, param, pp_lookup, actual, err_msg); break; - case PPK_Baked_Value: solve_for_polymorphic_param_value(&resolved, func, param, pp_lookup, actual, err_msg); break; - - default: if (err_msg) *err_msg = "Invalid polymorphic parameter kind. This is a compiler bug."; - } - - if (doing_nested_polymorph_lookup) { - doing_nested_polymorph_lookup = 0; - return TYPE_MATCH_SPECIAL; - } - - if (flag_to_yield) { - flag_to_yield = 0; - return TYPE_MATCH_YIELD; - } - - switch (resolved.kind) { - case PSK_Type: - out->kind = PSK_Type; - out->poly_sym = param->poly_sym; - out->type = resolved.actual; - return TYPE_MATCH_SUCCESS; - - case PSK_Value: - out->kind = PSK_Value; - out->poly_sym = param->poly_sym; - out->value = resolved.value; - return TYPE_MATCH_SUCCESS; - - case PSK_Undefined: - default: - // NOTE: If no error message has been assigned to why this polymorphic parameter - // resolution was unsuccessful, provide a basic dummy one. - if (err_msg && *err_msg == NULL) - *err_msg = bh_aprintf(global_scratch_allocator, - "Unable to solve for polymorphic variable '%b'.", - param->poly_sym->token->text, - param->poly_sym->token->length); - - out->kind = PSK_Undefined; - return TYPE_MATCH_FAILED; - } -} - -// NOTE: The job of this function is to take a polymorphic procedure, as well as a method of -// solving for the polymorphic variables, in order to return an array of the solutions for all -// of the polymorphic variables. -static bh_arr(AstPolySolution) find_polymorphic_slns(AstFunction* pp, PolyProcLookupMethod pp_lookup, ptr actual, OnyxToken *tkn, b32 necessary) { - ensure_polyproc_cache_is_created(pp); - if (bh_imap_has(&pp->active_queries, (u64) actual)) { - AstPolyQuery *query = (AstPolyQuery *) bh_imap_get(&pp->active_queries, (u64) actual); - assert(query->kind == Ast_Kind_Polymorph_Query); - assert(query->entity); - - if (query->entity->state == Entity_State_Finalized) return query->slns; - if (query->entity->state == Entity_State_Failed) return NULL; - - flag_to_yield = 1; - return NULL; - } - - bh_arr(AstPolySolution) slns = NULL; - bh_arr_new(global_heap_allocator, slns, bh_arr_length(pp->poly_params)); - - // NOTE: "known solutions" are given through a '#solidify' directive. If this polymorphic - // procedure is the result of a partially applied solidification, this array will be non- - // empty and these solutions will be used. - bh_arr_each(AstPolySolution, known_sln, pp->known_slns) bh_arr_push(slns, *known_sln); - - AstPolyQuery *query = onyx_ast_node_new(context.ast_alloc, sizeof(AstPolyQuery), Ast_Kind_Polymorph_Query); - query->token = pp->token; - query->proc = pp; - query->pp_lookup = pp_lookup; - query->given = actual; - query->error_loc = tkn; - query->slns = slns; - query->function_header = clone_function_header(context.ast_alloc, pp); - query->function_header->flags |= Ast_Flag_Header_Check_No_Error; - query->function_header->scope = NULL; - query->error_on_fail = necessary; - query->successful_symres = 1; - - bh_imap_put(&pp->active_queries, (u64) actual, (u64) query); - add_entities_for_node(NULL, (AstNode *) query, NULL, NULL); - - flag_to_yield = 1; - return NULL; -} - -// NOTE: The job of this function is to be a wrapper to other functions, providing an error -// message if a solution could not be found. This can't be merged with polymorphic_proc_solidify -// because polymorphic_proc_try_solidify uses the aforementioned function. -AstFunction* polymorphic_proc_lookup(AstFunction* pp, PolyProcLookupMethod pp_lookup, ptr actual, OnyxToken* tkn) { - ensure_polyproc_cache_is_created(pp); - - bh_arr(AstPolySolution) slns = find_polymorphic_slns(pp, pp_lookup, actual, tkn, 1); - if (slns == NULL) { - if (flag_to_yield) { - flag_to_yield = 0; - return (AstFunction *) &node_that_signals_a_yield; - } - - return NULL; - } - - AstFunction* result = polymorphic_proc_solidify(pp, slns, tkn); - return result; -} - -AstFunction* polymorphic_proc_solidify(AstFunction* pp, bh_arr(AstPolySolution) slns, OnyxToken* tkn) { - ensure_polyproc_cache_is_created(pp); - - // NOTE: Check if a version of this polyproc has already been created. - char* unique_key = build_poly_slns_unique_key(slns); - i32 index = shgeti(pp->concrete_funcs, unique_key); - if (index != -1) { - AstSolidifiedFunction solidified_func = pp->concrete_funcs[index].value; - - // NOTE: If this solution was originally created from a "build_only_header" call, then the body - // will not have been or type checked, or anything. This ensures that the body is copied, the - // entities are created and entered into the pipeline. - ensure_solidified_function_has_body(pp, &solidified_func); - - // NOTE: Again, if this came from a "build_only_header" call, then there was no known token and - // the "generated_from" member will be null. It is best to set it here so errors reported in that - // function can report where the polymorphic instantiation occurred. - if (solidified_func.func->generated_from == NULL) - solidified_func.func->generated_from = tkn; - - return solidified_func.func; - } - - AstSolidifiedFunction solidified_func = generate_solidified_function(pp, slns, tkn, 0); - add_solidified_function_entities(&solidified_func); - - // NOTE: Cache the function for later use, reducing duplicate functions. - shput(pp->concrete_funcs, unique_key, solidified_func); - - return (AstFunction *) &node_that_signals_a_yield; -} - -// NOTE: This can return either a AstFunction or an AstFunction, depending if enough parameters were -// supplied to remove all the polymorphic variables from the function. -AstNode* polymorphic_proc_try_solidify(AstFunction* pp, bh_arr(AstPolySolution) slns, OnyxToken* tkn) { - i32 valid_argument_count = 0; - - bh_arr_each(AstPolySolution, sln, slns) { - b32 found_match = 0; - - bh_arr_each(AstPolyParam, param, pp->poly_params) { - if (token_equals(sln->poly_sym->token, param->poly_sym->token)) { - found_match = 1; - break; - } - } - - if (found_match) { - valid_argument_count++; - } else { - if (pp->name) { - onyx_report_error(tkn->pos, Error_Critical, "'%b' is not a type variable of '%s'.", - sln->poly_sym->token->text, sln->poly_sym->token->length, pp->name); - } else { - onyx_report_error(tkn->pos, Error_Critical, "'%b' is not a type variable of '%b'.", - sln->poly_sym->token->text, sln->poly_sym->token->length, - pp->token->text, pp->token->length); - } - return (AstNode *) pp; - } - } - - if (valid_argument_count == bh_arr_length(pp->poly_params)) { - return (AstNode *) polymorphic_proc_solidify(pp, slns, tkn); - - } else { - // HACK: Some of these initializations assume that the entity for this polyproc has - // made it through the symbol resolution phase. - // - brendanfh 2020/12/25 - AstFunction* new_pp = onyx_ast_node_new(context.ast_alloc, sizeof(AstFunction), Ast_Kind_Polymorphic_Proc); - memcpy(new_pp, pp, sizeof(AstFunction)); - new_pp->token = tkn; - new_pp->poly_params = bh_arr_copy(context.ast_alloc, pp->poly_params); - - ensure_polyproc_cache_is_created(pp); - new_pp->concrete_funcs = pp->concrete_funcs; - - new_pp->known_slns = NULL; - bh_arr_new(global_heap_allocator, new_pp->known_slns, bh_arr_length(pp->known_slns) + bh_arr_length(slns)); - - bh_arr_each(AstPolySolution, sln, pp->known_slns) bh_arr_push(new_pp->known_slns, *sln); - bh_arr_each(AstPolySolution, sln, slns) bh_arr_push(new_pp->known_slns, *sln); - - return (AstNode *) new_pp; - } -} - -AstFunction* polymorphic_proc_build_only_header(AstFunction* pp, PolyProcLookupMethod pp_lookup, ptr actual) { - ensure_polyproc_cache_is_created(pp); - bh_arr(AstPolySolution) slns = find_polymorphic_slns(pp, pp_lookup, actual, NULL, 0); - if (flag_to_yield) { - flag_to_yield = 0; - return (AstFunction *) &node_that_signals_a_yield; - } - if (slns == NULL) return NULL; - - ensure_polyproc_cache_is_created(pp); - - return polymorphic_proc_build_only_header_with_slns(pp, slns, 0); -} - -AstFunction* polymorphic_proc_build_only_header_with_slns(AstFunction* pp, bh_arr(AstPolySolution) slns, b32 error_if_failed) { - AstSolidifiedFunction solidified_func; - - char* unique_key = build_poly_slns_unique_key(slns); - i32 index = shgeti(pp->concrete_funcs, unique_key); - if (index != -1) { - solidified_func = pp->concrete_funcs[index].value; - - } else { - // NOTE: This function is only going to have the header of it correctly created. - // Nothing should happen to this function's body or else the original will be corrupted. - // - brendanfh 2021/01/10 - solidified_func = generate_solidified_function(pp, slns, NULL, 1); - } - - if (solidified_func.func_header_entity) { - if (solidified_func.func_header_entity->state == Entity_State_Finalized) return solidified_func.func; - if (solidified_func.func_header_entity->state == Entity_State_Failed) return NULL; - - return (AstFunction *) &node_that_signals_a_yield; - } - - BH_MASK_SET(solidified_func.func->flags, !error_if_failed, Ast_Flag_Header_Check_No_Error); - - Entity func_header_entity = { - .state = Entity_State_Resolve_Symbols, - .type = Entity_Type_Temp_Function_Header, - .function = solidified_func.func, - .package = NULL, - .scope = solidified_func.func->poly_scope, - }; - - Entity* func_header_entity_ptr = entity_heap_insert(&context.entities, func_header_entity); - solidified_func.func_header_entity = func_header_entity_ptr; - - // NOTE: Cache the function for later use. - shput(pp->concrete_funcs, unique_key, solidified_func); - - return (AstFunction *) &node_that_signals_a_yield; -} - -typedef struct AutoPolymorphVariable { - u32 idx; - u32 variable_count; - AstType *base_type; - AstType **replace; -} AutoPolymorphVariable; - -// This should be called after all the parameter types have been symresed, but before anything -// happens to the body. -b32 potentially_convert_function_to_polyproc(AstFunction *func) { - bh_arr(AutoPolymorphVariable) auto_vars = NULL; - bh_arr_new(global_heap_allocator, auto_vars, 2); - - u32 param_idx = 0; - bh_arr_each(AstParam, param, func->params) { - AstType **to_replace = ¶m->local->type_node; - AstType *param_type = param->local->type_node; - - b32 done = 0; - while (!done && param_type) { - switch (param_type->kind) { - case Ast_Kind_Pointer_Type: to_replace = &((AstPointerType *) *to_replace)->elem; param_type = ((AstPointerType *) param_type)->elem; break; - case Ast_Kind_Array_Type: to_replace = &((AstArrayType *) *to_replace)->elem; param_type = ((AstArrayType *) param_type)->elem; break; - case Ast_Kind_Slice_Type: to_replace = &((AstSliceType *) *to_replace)->elem; param_type = ((AstSliceType *) param_type)->elem; break; - case Ast_Kind_DynArr_Type: to_replace = &((AstDynArrType *) *to_replace)->elem; param_type = ((AstDynArrType *) param_type)->elem; break; - case Ast_Kind_Alias: param_type = (AstType *) ((AstAlias *) param_type)->alias; break; - case Ast_Kind_Type_Alias: param_type = ((AstTypeAlias *) param_type)->to; break; - case Ast_Kind_Poly_Struct_Type: { - AutoPolymorphVariable apv; - apv.idx = param_idx; - apv.base_type = param->local->type_node; - apv.variable_count = bh_arr_length(((AstPolyStructType *) param_type)->poly_params); - apv.replace = to_replace; - - bh_arr_push(auto_vars, apv); - done = 1; - break; - } - - default: done = 1; break; - } - } - - param_idx++; - } - - if (bh_arr_length(auto_vars) == 0) return 0; - - param_idx = 0; - bh_arr_each(AutoPolymorphVariable, apv, auto_vars) { - AstPolyParam pp; - pp.idx = apv->idx; - pp.kind = PPK_Poly_Type; - - AstPolyCallType* pcall = onyx_ast_node_new(context.ast_alloc, sizeof(AstPolyCallType), Ast_Kind_Poly_Call_Type); - pcall->callee = *apv->replace; - pcall->token = pcall->callee->token; - bh_arr_new(global_heap_allocator, pcall->params, apv->variable_count); - - if (apv->base_type->kind == Ast_Kind_Poly_Struct_Type) { - pp.type_expr = (AstType *) pcall; - } else { - pp.type_expr = apv->base_type; - } - *apv->replace = (AstType *) pcall; - - fori (i, 0, apv->variable_count) { - OnyxToken* name_token = bh_alloc_item(context.ast_alloc, OnyxToken); - name_token->text = bh_aprintf(context.ast_alloc, "__autopoly_var_%d\0", param_idx); - name_token->length = strlen(name_token->text); - name_token->type = Token_Type_Symbol; - name_token->pos = pcall->token->pos; - - pp.poly_sym = make_symbol(context.ast_alloc, name_token); - bh_arr_push(pcall->params, pp.poly_sym); - bh_arr_push(func->poly_params, pp); - param_idx ++; - } - } - - convert_function_to_polyproc(func); - - bh_arr_each(AstParam, param, func->params) { - param->local->flags |= Ast_Flag_Param_Symbol_Dirty; - } - - return 1; -} - -// -// Polymorphic Structures -// -// -// Currently, I am not very happy about how polymorphic structure generation works. My biggest problem -// with it is that it is very different from the polymorhic procedure generation. Also, it needs to -// completely generate and check the structure right away, which means there is a lot of up-front work -// done here that could probably be done elsewhere. This really relates to a large problem in the compiler -// that types need to be known completely by the time symbol resolution is done, even though that -// information shouldn't need to be known until right before the types are checked. -// -// The above documentation is very incorrect but I don't want to fix it right now. Basically, polymorphic -// structures now have a delay instantiation phase and are not forced to be completed immediately. - -char* build_poly_struct_name(AstPolyStructType* ps_type, Type* cs_type) { - char name_buf[256]; - fori (i, 0, 256) name_buf[i] = 0; - - strncat(name_buf, ps_type->name, 255); - strncat(name_buf, "(", 255); - bh_arr_each(AstPolySolution, ptype, cs_type->Struct.poly_sln) { - if (ptype != cs_type->Struct.poly_sln) - strncat(name_buf, ", ", 255); - - // This logic will have to be other places as well. - - switch (ptype->kind) { - case PSK_Undefined: assert(0); break; - case PSK_Type: strncat(name_buf, type_get_name(ptype->type), 255); break; - case PSK_Value: { - // FIX - AstNode* value = strip_aliases((AstNode *) ptype->value); - - if (value->kind == Ast_Kind_NumLit) { - AstNumLit* nl = (AstNumLit *) value; - if (type_is_integer(nl->type)) { - strncat(name_buf, bh_bprintf("%l", nl->value.l), 127); - } else { - strncat(name_buf, "numlit (FIX ME)", 127); - } - } else if (value->kind == Ast_Kind_Code_Block) { - AstCodeBlock* code = (AstCodeBlock *) value; - OnyxFilePos code_loc = code->token->pos; - strncat(name_buf, bh_bprintf("code at %s:%d,%d", code_loc.filename, code_loc.line, code_loc.column), 127); - } else { - strncat(name_buf, "", 127); - } - - break; - } - } - } - strncat(name_buf, ")", 255); - - return bh_aprintf(global_heap_allocator, "%s", name_buf); -} - -Type* polymorphic_struct_lookup(AstPolyStructType* ps_type, bh_arr(AstPolySolution) slns, OnyxFilePos pos, b32 error_if_failed) { - // @Cleanup - assert(ps_type->scope != NULL); - - if (ps_type->concrete_structs == NULL) { - sh_new_arena(ps_type->concrete_structs); - } - - if (bh_arr_length(slns) != bh_arr_length(ps_type->poly_params)) { - onyx_report_error(pos, Error_Critical, "Wrong number of arguments for '%s'. Expected %d, got %d.", - ps_type->name, - bh_arr_length(ps_type->poly_params), - bh_arr_length(slns)); - - return NULL; - } - - i32 i = 0; - bh_arr_each(AstPolySolution, sln, slns) { - sln->poly_sym = (AstNode *) &ps_type->poly_params[i]; - i++; - } - - char* unique_key = build_poly_slns_unique_key(slns); - i32 index = shgeti(ps_type->concrete_structs, unique_key); - if (index != -1) { - AstStructType* concrete_struct = ps_type->concrete_structs[index].value; - - if (concrete_struct->entity_type->state < Entity_State_Check_Types) { - return NULL; - } - - if (concrete_struct->entity_type->state == Entity_State_Failed) { - return (Type *) &node_that_signals_failure; - } - - Type* cs_type = type_build_from_ast(context.ast_alloc, (AstType *) concrete_struct); - if (!cs_type) return NULL; - - if (cs_type->Struct.poly_sln == NULL) cs_type->Struct.poly_sln = bh_arr_copy(global_heap_allocator, slns); - if (cs_type->Struct.name == NULL) cs_type->Struct.name = build_poly_struct_name(ps_type, cs_type); - - return cs_type; - } - - Scope* sln_scope = scope_create(context.ast_alloc, ps_type->scope, ps_type->token->pos); - insert_poly_slns_into_scope(sln_scope, slns); - - AstStructType* concrete_struct = (AstStructType *) ast_clone(context.ast_alloc, ps_type->base_struct); - concrete_struct->polymorphic_error_loc = pos; - BH_MASK_SET(concrete_struct->flags, !error_if_failed, Ast_Flag_Header_Check_No_Error); - - - i64 arg_count = bh_arr_length(ps_type->poly_params); - bh_arr_new(global_heap_allocator, concrete_struct->polymorphic_argument_types, arg_count); - bh_arr_set_length(concrete_struct->polymorphic_argument_types, arg_count); - concrete_struct->polymorphic_arguments = bh_arr_copy(global_heap_allocator, slns); - - fori (i, 0, (i64) bh_arr_length(ps_type->poly_params)) { - concrete_struct->polymorphic_argument_types[i] = (AstType *) ast_clone(context.ast_alloc, ps_type->poly_params[i].type_node); - } - - shput(ps_type->concrete_structs, unique_key, concrete_struct); - add_entities_for_node(NULL, (AstNode *) concrete_struct, sln_scope, NULL); - return NULL; -} diff --git a/src/symres.c b/src/symres.c deleted file mode 100644 index 4410bb62..00000000 --- a/src/symres.c +++ /dev/null @@ -1,1727 +0,0 @@ -#define BH_DEBUG -#include "parser.h" -#include "utils.h" -#include "astnodes.h" -#include "errors.h" - -// :EliminatingSymres - notes the places where too much work is being done in symbol resolution - -// Variables used during the symbol resolution phase. -static Scope* curr_scope = NULL; -static b32 report_unresolved_symbols = 1; -static b32 resolved_a_symbol = 0; - -// Everything related to waiting on is imcomplete at the moment. -static Entity* waiting_on = NULL; - -#define SYMRES(kind, ...) do { \ - SymresStatus ss = symres_ ## kind (__VA_ARGS__); \ - if (ss > Symres_Errors_Start) return ss; \ - } while (0) - -#define SYMRES_INVISIBLE(kind, node, ...) do { \ - (node)->flags |= Ast_Flag_Symbol_Invisible; \ - SymresStatus ss = symres_ ## kind (__VA_ARGS__); \ - (node)->flags &= ~Ast_Flag_Symbol_Invisible; \ - if (ss > Symres_Errors_Start) return ss; \ - } while (0) - -typedef enum SymresStatus { - Symres_Success, - Symres_Complete, - Symres_Goto_Parse, - - Symres_Errors_Start, - Symres_Yield_Macro, - Symres_Yield_Micro, - Symres_Error, -} SymresStatus; - -static SymresStatus symres_type(AstType** type); -static SymresStatus symres_local(AstLocal** local); -static SymresStatus symres_call(AstCall** pcall); -static SymresStatus symres_size_of(AstSizeOf* so); -static SymresStatus symres_align_of(AstAlignOf* so); -static SymresStatus symres_field_access(AstFieldAccess** fa); -static SymresStatus symres_compound(AstCompound* compound); -static SymresStatus symres_expression(AstTyped** expr); -static SymresStatus symres_return(AstReturn* ret); -static SymresStatus symres_if(AstIfWhile* ifnode); -static SymresStatus symres_while(AstIfWhile* whilenode); -static SymresStatus symres_for(AstFor* fornode); -static SymresStatus symres_case(AstSwitchCase *casenode); -static SymresStatus symres_switch(AstSwitch* switchnode); -static SymresStatus symres_use(AstUse* use); -static SymresStatus symres_directive_solidify(AstDirectiveSolidify** psolid); -static SymresStatus symres_directive_defined(AstDirectiveDefined** pdefined); -static SymresStatus symres_directive_insert(AstDirectiveInsert* insert); -static SymresStatus symres_statement_chain(AstNode** walker); -static SymresStatus symres_statement(AstNode** stmt, b32 *remove); -static SymresStatus symres_block(AstBlock* block); -static SymresStatus symres_function_header(AstFunction* func); -static SymresStatus symres_function(AstFunction* func); -static SymresStatus symres_global(AstGlobal* global); -static SymresStatus symres_overloaded_function(AstOverloadedFunction* ofunc); -static SymresStatus symres_package(AstPackage* package); -static SymresStatus symres_enum(AstEnumType* enum_node); -static SymresStatus symres_memres_type(AstMemRes** memres); -static SymresStatus symres_memres(AstMemRes** memres); -static SymresStatus symres_struct_defaults(AstType* st); -static SymresStatus symres_static_if(AstIf* static_if); -static SymresStatus symres_macro(AstMacro* macro); -static SymresStatus symres_constraint(AstConstraint* constraint); -static SymresStatus symres_polyquery(AstPolyQuery *query); - -static void scope_enter(Scope* new_scope) { - curr_scope = new_scope; -} - -static void scope_leave() { - curr_scope = curr_scope->parent; -} - -static SymresStatus symres_symbol(AstNode** symbol_node) { - OnyxToken* token = (*symbol_node)->token; - AstNode* res = symbol_resolve(curr_scope, token); - - if (!res) { // :SymresStall - if (report_unresolved_symbols) { - token_toggle_end(token); - char *closest = find_closest_symbol_in_scope_and_parents(curr_scope, token->text); - token_toggle_end(token); - - if (closest) onyx_report_error(token->pos, Error_Critical, "Unable to resolve symbol '%b'. Did you mean '%s'?", token->text, token->length, closest); - else onyx_report_error(token->pos, Error_Critical, "Unable to resolve symbol '%b'.", token->text, token->length); - - return Symres_Error; - } else { - return Symres_Yield_Macro; - } - - } else { - *symbol_node = res; - resolved_a_symbol = 1; - } - - return Symres_Success; -} - -static SymresStatus symres_struct_type(AstStructType* s_node) { - if (s_node->flags & Ast_Flag_Type_Is_Resolved) return Symres_Success; - - s_node->flags |= Ast_Flag_Type_Is_Resolved; - s_node->flags |= Ast_Flag_Comptime; - - if (s_node->scope) { - // FIX: This is probably wrong for the long term. - s_node->scope->parent = curr_scope; - - scope_enter(s_node->scope); - } - - if (s_node->min_size_) SYMRES(expression, &s_node->min_size_); - if (s_node->min_alignment_) SYMRES(expression, &s_node->min_alignment_); - - if (s_node->polymorphic_argument_types) { - assert(s_node->polymorphic_arguments); - - SymresStatus ss = Symres_Success, result; - fori (i, 0, (i64) bh_arr_length(s_node->polymorphic_argument_types)) { - result = symres_type(&s_node->polymorphic_argument_types[i]); - if (result > ss) ss = result; - - if (s_node->polymorphic_arguments[i].value) { - result = symres_expression(&s_node->polymorphic_arguments[i].value); - if (result > ss) ss = result; - } - } - } - - if (s_node->constraints.constraints) { - bh_arr_each(AstConstraint *, constraint, s_node->constraints.constraints) { - SYMRES(constraint, *constraint); - } - } - - fori (i, 0, bh_arr_length(s_node->members)) { - AstStructMember *member = s_node->members[i]; - - if (member->type_node) { - SymresStatus ss = symres_type(&member->type_node); - if (ss != Symres_Success) { - s_node->flags &= ~Ast_Flag_Type_Is_Resolved; - if (s_node->scope) scope_leave(); - return ss; - } - } - } - - if (s_node->scope) scope_leave(); - return Symres_Success; -} - -static SymresStatus symres_type(AstType** type) { - // Don't make this kill all symbol resolution if the type is null. - if (!type || !*type) return Symres_Success; - - switch ((*type)->kind) { - case Ast_Kind_Symbol: SYMRES(symbol, (AstNode **) type); break; - case Ast_Kind_Basic_Type: break; - case Ast_Kind_Type_Alias: SYMRES(type, &((AstTypeAlias *) *type)->to); break; - case Ast_Kind_Field_Access: { - SYMRES(field_access, (AstFieldAccess **) type); - break; - } - - case Ast_Kind_Pointer_Type: SYMRES(type, &((AstPointerType *) *type)->elem); break; - case Ast_Kind_Slice_Type: SYMRES(type, &((AstSliceType *) *type)->elem); break; - case Ast_Kind_DynArr_Type: SYMRES(type, &((AstDynArrType *) *type)->elem); break; - case Ast_Kind_VarArg_Type: SYMRES(type, &((AstVarArgType *) *type)->elem); break; - - case Ast_Kind_Function_Type: { - AstFunctionType* ftype = (AstFunctionType *) *type; - - if (ftype->param_count > 0) { - fori (i, 0, (i64) ftype->param_count) { - SYMRES(type, &ftype->params[i]); - } - } - - SYMRES(type, &ftype->return_type); - break; - } - - case Ast_Kind_Struct_Type: SYMRES(struct_type, (AstStructType *) *type); break; - case Ast_Kind_Array_Type: { - AstArrayType* a_node = (AstArrayType *) *type; - - if (a_node->count_expr) SYMRES(expression, &a_node->count_expr); - SYMRES(type, &a_node->elem); - break; - } - - case Ast_Kind_Enum_Type: break; - - case Ast_Kind_Poly_Struct_Type: { - AstPolyStructType* pst_node = (AstPolyStructType *) *type; - - if (pst_node->scope == NULL) { - pst_node->scope = scope_create(context.ast_alloc, pst_node->entity->scope, pst_node->token->pos); - } - break; - } - - case Ast_Kind_Poly_Call_Type: { - AstPolyCallType* pc_node = (AstPolyCallType *) *type; - - SYMRES(type, &pc_node->callee); - - bh_arr_each(AstNode *, param, pc_node->params) { - if (node_is_type(*param)) { - SYMRES(type, (AstType **) param); - } else { - SYMRES(expression, (AstTyped **) param); - } - } - break; - } - - case Ast_Kind_Type_Compound: { - AstCompoundType* ctype = (AstCompoundType *) *type; - - bh_arr_each(AstType *, type, ctype->types) SYMRES(type, type); - break; - } - - case Ast_Kind_Alias: { - AstAlias* alias = (AstAlias *) *type; - SYMRES_INVISIBLE(type, alias, (AstType **) &alias->alias); - - break; - } - - case Ast_Kind_Typeof: { - AstTypeOf* type_of = (AstTypeOf *) *type; - SYMRES(expression, &type_of->expr); - break; - } - - case Ast_Kind_Distinct_Type: { - AstDistinctType *distinct = (AstDistinctType *) *type; - SYMRES(type, &distinct->base_type); - break; - } - } - - return Symres_Success; -} - -static SymresStatus symres_local(AstLocal** local) { - SYMRES(type, &(*local)->type_node); - - if ((*local)->token != NULL) - symbol_introduce(curr_scope, (*local)->token, (AstNode *) *local); - - return Symres_Success; -} - -static SymresStatus symres_arguments(Arguments* args) { - bh_arr_each(AstTyped *, arg, args->values) - SYMRES(expression, arg); - - bh_arr_each(AstNamedValue *, named_arg, args->named_values) - SYMRES(expression, &(*named_arg)->value); - - return Symres_Success; -} - -static SymresStatus symres_call(AstCall** pcall) { - AstCall *call = *pcall; - SYMRES(expression, (AstTyped **) &call->callee); - SYMRES(arguments, &call->args); - - AstNode* callee = strip_aliases((AstNode *) call->callee); - if (callee->kind == Ast_Kind_Poly_Struct_Type) { - *pcall = (AstCall *) convert_call_to_polycall(call); - SYMRES(type, (AstType **) pcall); - return Symres_Success; - } - - return Symres_Success; -} - -static SymresStatus symres_size_of(AstSizeOf* so) { - SYMRES(type, &so->type_node); - SYMRES(type, &so->so_ast_type); - - return Symres_Success; -} - -static SymresStatus symres_align_of(AstAlignOf* ao) { - SYMRES(type, &ao->type_node); - SYMRES(type, &ao->ao_ast_type); - - return Symres_Success; -} - -static SymresStatus symres_field_access(AstFieldAccess** fa) { - if ((*fa)->expr == NULL) return Symres_Error; - SYMRES(expression, &(*fa)->expr); - if ((*fa)->expr == NULL) return Symres_Error; - - AstTyped* expr = (AstTyped *) strip_aliases((AstNode *) (*fa)->expr); - - b32 force_a_lookup = 0; - - if (expr->kind == Ast_Kind_Enum_Type || expr->kind == Ast_Kind_Type_Raw_Alias) { - force_a_lookup = 1; - } - - // - // If we are trying to access a field on an alias, we have to make sure - // the alias is "ready" to have a symbol looked up inside of it. This means - // the alias should have passed symbol resolution. If not, force a lookup - // and yield if the alias was not ready. - if ((*fa)->expr->kind == Ast_Kind_Alias) { - assert((*fa)->expr->entity); - if ((*fa)->expr->entity->state < Entity_State_Check_Types) { - force_a_lookup = 1; - } - } - - AstNode* resolution = try_symbol_resolve_from_node((AstNode *) expr, (*fa)->token); - if (resolution) *((AstNode **) fa) = resolution; - else if (expr->kind == Ast_Kind_Package) { - if (report_unresolved_symbols) { - token_toggle_end((*fa)->token); - char *closest = find_closest_symbol_in_node((AstNode *) expr, (*fa)->token->text); - token_toggle_end((*fa)->token); - - AstPackage *package = (AstPackage *) strip_aliases((AstNode *) (*fa)->expr); - - if (closest) { - onyx_report_error((*fa)->token->pos, Error_Critical, "'%b' was not found in package '%s'. Did you mean '%s'?", - (*fa)->token->text, - (*fa)->token->length, - package->package->name, - closest); - } else { - onyx_report_error((*fa)->token->pos, Error_Critical, "'%b' was not found in package '%s'. Perhaps it is defined in a file that wasn't loaded?", - (*fa)->token->text, - (*fa)->token->length, - package->package->name); - } - return Symres_Error; - - } else { - return Symres_Yield_Macro; - } - } - else if (force_a_lookup) { - if (context.cycle_detected) { - onyx_report_error((*fa)->token->pos, Error_Critical, "'%b' does not exist here. This is a bad error message.", - (*fa)->token->text, - (*fa)->token->length); - return Symres_Error; - } - - return Symres_Yield_Macro; - } - - return Symres_Success; -} - -static SymresStatus symres_compound(AstCompound* compound) { - bh_arr_each(AstTyped *, expr, compound->exprs) { - SYMRES(expression, expr); - } - - return Symres_Success; -} - -static SymresStatus symres_if_expression(AstIfExpression* if_expr) { - SYMRES(expression, &if_expr->cond); - SYMRES(expression, &if_expr->true_expr); - SYMRES(expression, &if_expr->false_expr); - return Symres_Success; -} - -static SymresStatus symres_pipe(AstBinaryOp** pipe) { - AstCall* call_node = (AstCall *) (*pipe)->right; - SYMRES(expression, (AstTyped **) &call_node); - SYMRES(expression, &(*pipe)->left); - - if (call_node->kind != Ast_Kind_Call) { - onyx_report_error((*pipe)->token->pos, Error_Critical, "Pipe operator expected call on right side."); - return Symres_Error; - } - - if ((*pipe)->left == NULL) return Symres_Error; - - // :EliminatingSymres - bh_arr_insertn(call_node->args.values, 0, 1); - call_node->args.values[0] = (AstTyped *) make_argument(context.ast_alloc, (*pipe)->left); - call_node->next = (*pipe)->next; - - // NOTE: Not a BinaryOp node - *pipe = (AstBinaryOp *) call_node; - - return Symres_Success; -} - -// CLEANUP: This is an experimental feature and might be removed in the future. -// I noticed a common pattern when writing in Onyx is something that looks like this: -// -// foo.member_function(^foo, ...) -// -// I decided it would be worth adding a bit of syntactic sugar for such as call. I -// decided to use the '->' operator for this purpose. The snippet below is the exact -// same as the snippet above (after the nodes have been processed by the function below) -// -// foo->member_function(...) -static SymresStatus symres_method_call(AstBinaryOp** mcall) { - SYMRES(expression, &(*mcall)->left); - if ((*mcall)->left == NULL) return Symres_Error; - - // :EliminatingSymres - if (((*mcall)->flags & Ast_Flag_Has_Been_Symres) == 0) { - if ((*mcall)->right->kind != Ast_Kind_Call) { - onyx_report_error((*mcall)->token->pos, Error_Critical, "'->' expected procedure call on right side."); - return Symres_Error; - } - - AstFieldAccess* implicit_field_access = make_field_access(context.ast_alloc, (*mcall)->left, NULL); - implicit_field_access->token = ((AstCall *) (*mcall)->right)->callee->token; - ((AstCall *) (*mcall)->right)->callee = (AstTyped *) implicit_field_access; - (*mcall)->flags |= Ast_Flag_Has_Been_Symres; - } - - assert((*mcall)->right->kind == Ast_Kind_Call); - SYMRES(expression, (AstTyped **) &(*mcall)->right); - - return Symres_Success; -} - -static SymresStatus symres_unaryop(AstUnaryOp** unaryop) { - if ((*unaryop)->operation == Unary_Op_Cast) { - SYMRES(type, &(*unaryop)->type_node); - } - - SYMRES(expression, &(*unaryop)->expr); - - return Symres_Success; -} - -static SymresStatus symres_struct_literal(AstStructLiteral* sl) { - if (sl->stnode != NULL) SYMRES(expression, &sl->stnode); - - // :EliminatingSymres - sl->type_node = (AstType *) sl->stnode; - while (sl->type_node && sl->type_node->kind == Ast_Kind_Type_Alias) - sl->type_node = ((AstTypeAlias *) sl->type_node)->to; - - SYMRES(arguments, &sl->args); - - return Symres_Success; -} - -static SymresStatus symres_array_literal(AstArrayLiteral* al) { - if (al->atnode != NULL) SYMRES(expression, &al->atnode); - - // :EliminatingSymres - al->type_node = (AstType *) al->atnode; - while (al->type_node && al->type_node->kind == Ast_Kind_Type_Alias) - al->type_node = ((AstTypeAlias *) al->type_node)->to; - - bh_arr_each(AstTyped *, expr, al->values) - SYMRES(expression, expr); - - return Symres_Success; -} - -static SymresStatus symres_address_of(AstAddressOf** paof) { - AstAddressOf *aof = (AstAddressOf *) *paof; - SYMRES(expression, &aof->expr); - - AstTyped *expr = (AstTyped *) strip_aliases((AstNode *) aof->expr); - if (node_is_type((AstNode *) expr)) { - AstPointerType *pt = onyx_ast_node_new(context.ast_alloc, sizeof(AstPointerType), Ast_Kind_Pointer_Type); - pt->token = aof->token; - pt->elem = (AstType *) expr; - pt->__unused = aof->next; - *paof = (AstAddressOf *) pt; - SYMRES(type, (AstType **) &pt); - return Symres_Success; - } - - return Symres_Success; -} - -static SymresStatus symres_expression(AstTyped** expr) { - if (node_is_type((AstNode *) *expr)) { - SYMRES(type, (AstType **) expr); - return Symres_Success; - } - - switch ((*expr)->kind) { - case Ast_Kind_Symbol: SYMRES(symbol, (AstNode **) expr); break; - - case Ast_Kind_Binary_Op: - SYMRES(expression, &((AstBinaryOp *)(*expr))->left); - SYMRES(expression, &((AstBinaryOp *)(*expr))->right); - break; - - case Ast_Kind_Unary_Op: SYMRES(unaryop, (AstUnaryOp **) expr); break; - case Ast_Kind_Call: SYMRES(call, (AstCall **) expr); break; - case Ast_Kind_Argument: SYMRES(expression, &((AstArgument *) *expr)->value); break; - case Ast_Kind_Block: SYMRES(block, (AstBlock *) *expr); break; - case Ast_Kind_Dereference: SYMRES(expression, &((AstDereference *)(*expr))->expr); break; - case Ast_Kind_Field_Access: SYMRES(field_access, (AstFieldAccess **) expr); break; - case Ast_Kind_Pipe: SYMRES(pipe, (AstBinaryOp **) expr); break; - case Ast_Kind_Method_Call: SYMRES(method_call, (AstBinaryOp **) expr); break; - case Ast_Kind_Size_Of: SYMRES(size_of, (AstSizeOf *)*expr); break; - case Ast_Kind_Align_Of: SYMRES(align_of, (AstAlignOf *)*expr); break; - case Ast_Kind_Address_Of: SYMRES(address_of, (AstAddressOf **) expr); break; - case Ast_Kind_Alias: { - AstAlias *alias = (AstAlias *) *expr; - SYMRES_INVISIBLE(expression, alias, &alias->alias); - break; - } - - case Ast_Kind_Range_Literal: - SYMRES(expression, &((AstRangeLiteral *)(*expr))->low); - SYMRES(expression, &((AstRangeLiteral *)(*expr))->high); - - // :EliminatingSymres - SYMRES(type, &builtin_range_type); - (*expr)->type_node = builtin_range_type; - break; - - case Ast_Kind_Function: - case Ast_Kind_NumLit: - SYMRES(type, &(*expr)->type_node); - break; - - case Ast_Kind_StrLit: { - AstStrLit* str = (AstStrLit *) *expr; - if (str->is_cstr) { - SYMRES(type, &builtin_cstring_type); - str->type_node = builtin_cstring_type; - - } else { - SYMRES(type, &builtin_string_type); - str->type_node = builtin_string_type; - } - break; - } - - case Ast_Kind_Slice: - case Ast_Kind_Subscript: - SYMRES(expression, &((AstSubscript *)(*expr))->addr); - SYMRES(expression, &((AstSubscript *)(*expr))->expr); - break; - - case Ast_Kind_Struct_Literal: - SYMRES(struct_literal, (AstStructLiteral *)(*expr)); - break; - - case Ast_Kind_Array_Literal: - SYMRES(array_literal, (AstArrayLiteral *)(*expr)); - break; - - case Ast_Kind_Directive_Solidify: - SYMRES(directive_solidify, (AstDirectiveSolidify **) expr); - break; - - case Ast_Kind_Directive_Defined: - SYMRES(directive_defined, (AstDirectiveDefined **) expr); - break; - - case Ast_Kind_Compound: - SYMRES(compound, (AstCompound *) *expr); - break; - - case Ast_Kind_Package: - SYMRES(package, (AstPackage *) *expr); - break; - - case Ast_Kind_If_Expression: - SYMRES(if_expression, (AstIfExpression *) *expr); - break; - - case Ast_Kind_Directive_Insert: - SYMRES(directive_insert, (AstDirectiveInsert *) *expr); - break; - - case Ast_Kind_Do_Block: - SYMRES(block, ((AstDoBlock *) *expr)->block); - break; - - case Ast_Kind_Param: - if ((*expr)->flags & Ast_Flag_Param_Symbol_Dirty) { - assert((*expr)->token->type == Token_Type_Symbol); - *expr = (AstTyped *) make_symbol(context.ast_alloc, (*expr)->token); - SYMRES(expression, expr); - } - break; - - default: break; - } - - return Symres_Success; -} - -static SymresStatus symres_return(AstReturn* ret) { - if (ret->expr) - SYMRES(expression, &ret->expr); - - return Symres_Success; -} - -static SymresStatus symres_if(AstIfWhile* ifnode) { - if (ifnode->kind == Ast_Kind_Static_If) { - if ((ifnode->flags & Ast_Flag_Static_If_Resolved) == 0) { - if (context.cycle_detected) { - onyx_report_error(ifnode->token->pos, Error_Waiting_On, "Waiting on static if resolution."); - return Symres_Error; - } else { - return Symres_Yield_Macro; - } - } - - if (static_if_resolution(ifnode)) { - if (ifnode->true_stmt != NULL) SYMRES(statement, (AstNode **) &ifnode->true_stmt, NULL); - - } else { - if (ifnode->false_stmt != NULL) SYMRES(statement, (AstNode **) &ifnode->false_stmt, NULL); - } - - } else { - if (ifnode->initialization != NULL) { - ifnode->scope = scope_create(context.ast_alloc, curr_scope, ifnode->token->pos); - scope_enter(ifnode->scope); - - SYMRES(statement_chain, &ifnode->initialization); - } - - SYMRES(expression, &ifnode->cond); - - // NOTE: These are statements because "elseif" means the `false_stmt` has an if node. - if (ifnode->true_stmt != NULL) SYMRES(statement, (AstNode **) &ifnode->true_stmt, NULL); - if (ifnode->false_stmt != NULL) SYMRES(statement, (AstNode **) &ifnode->false_stmt, NULL); - - if (ifnode->initialization != NULL) scope_leave(); - } - - return Symres_Success; -} - -static SymresStatus symres_while(AstIfWhile* whilenode) { - if (whilenode->initialization != NULL) { - whilenode->scope = scope_create(context.ast_alloc, curr_scope, whilenode->token->pos); - scope_enter(whilenode->scope); - - SYMRES(statement_chain, &whilenode->initialization); - } - - SYMRES(expression, &whilenode->cond); - - if (whilenode->true_stmt) SYMRES(block, whilenode->true_stmt); - if (whilenode->false_stmt) SYMRES(block, whilenode->false_stmt); - - if (whilenode->initialization != NULL) scope_leave(); - - return Symres_Success; -} - -static SymresStatus symres_for(AstFor* fornode) { - fornode->scope = scope_create(context.ast_alloc, curr_scope, fornode->token->pos); - scope_enter(fornode->scope); - SYMRES(expression, &fornode->iter); - SYMRES(local, &fornode->var); - SYMRES(block, fornode->stmt); - scope_leave(); - - return Symres_Success; -} - -static SymresStatus symres_case(AstSwitchCase *casenode) { - if (!casenode->is_default) { - bh_arr_each(AstTyped *, expr, casenode->values) { - SYMRES(expression, expr); - } - } - - SYMRES(block, casenode->block); - return Symres_Success; -} - -static SymresStatus symres_switch(AstSwitch* switchnode) { - if (switchnode->initialization != NULL) { - switchnode->scope = scope_create(context.ast_alloc, curr_scope, switchnode->token->pos); - scope_enter(switchnode->scope); - - SYMRES(statement_chain, &switchnode->initialization); - } - - SYMRES(expression, &switchnode->expr); - - if (switchnode->cases == NULL) { - SYMRES(block, switchnode->case_block); - } else { - bh_arr_each(AstSwitchCase *, pcase, switchnode->cases) { - SYMRES(case, *pcase); - } - - if (switchnode->default_case) SYMRES(block, switchnode->default_case); - } - - if (switchnode->switch_kind == Switch_Kind_Use_Equals && switchnode->case_exprs) { - bh_arr_each(CaseToBlock, ctb, switchnode->case_exprs) { - SYMRES(expression, (AstTyped **) &ctb->comparison); - } - } - - if (switchnode->initialization != NULL) scope_leave(); - - return Symres_Success; -} - -// CLEANUP: A lot of duplication going on in this function. A proper -// "namespace" concept would be useful to remove a lot of the fluff -// code here. There already is try_resolve_symbol_from_node which -// may be able to do what is needed here? -static SymresStatus symres_use(AstUse* use) { - SYMRES(expression, &use->expr); - - AstTyped *use_expr = (AstTyped *) strip_aliases((AstNode *) use->expr); - - // :EliminatingSymres - if (use_expr->kind == Ast_Kind_Package) { - AstPackage* package = (AstPackage *) use_expr; - SYMRES(package, package); - - if (package->package->scope == curr_scope) return Symres_Success; - - if (use->only == NULL) { - OnyxFilePos pos = { 0 }; - if (use->token != NULL) - pos = use->token->pos; - - scope_include(curr_scope, package->package->scope, pos); - - } else { - bh_arr_each(QualifiedUse, qu, use->only) { - AstNode* thing = symbol_resolve(package->package->scope, qu->symbol_name); - if (thing == NULL) { // :SymresStall - if (report_unresolved_symbols) { - onyx_report_error(qu->symbol_name->pos, Error_Critical, - "The symbol '%b' was not found in this package.", - qu->symbol_name->text, qu->symbol_name->length); - return Symres_Error; - } else { - return Symres_Yield_Macro; - } - } - - symbol_introduce(curr_scope, qu->as_name, thing); - } - } - - if (!use->entity) { - add_entities_for_node(NULL, (AstNode *) use, curr_scope, NULL); - } - - package_track_use_package(package->package, use->entity); - return Symres_Success; - } - - if (use_expr->kind == Ast_Kind_Foreign_Block) { - AstForeignBlock* fb = (AstForeignBlock *) use_expr; - if (fb->entity->state <= Entity_State_Resolve_Symbols) return Symres_Yield_Macro; - - if (fb->scope == curr_scope) return Symres_Success; - - if (use->only == NULL) { - OnyxFilePos pos = { 0 }; - if (use->token != NULL) - pos = use->token->pos; - - scope_include(curr_scope, fb->scope, pos); - - } else { - bh_arr_each(QualifiedUse, qu, use->only) { - AstNode* thing = symbol_resolve(fb->scope, qu->symbol_name); - if (thing == NULL) { // :SymresStall - if (report_unresolved_symbols) { - onyx_report_error(qu->symbol_name->pos, Error_Critical, - "The symbol '%b' was not found in this package.", - qu->symbol_name->text, qu->symbol_name->length); - return Symres_Error; - } else { - return Symres_Yield_Macro; - } - } - - symbol_introduce(curr_scope, qu->as_name, thing); - } - } - - return Symres_Success; - } - - if (use_expr->kind == Ast_Kind_Enum_Type) { - AstEnumType* et = (AstEnumType *) use_expr; - - bh_arr_each(AstEnumValue *, ev, et->values) - symbol_introduce(curr_scope, (*ev)->token, (AstNode *) *ev); - - return Symres_Success; - } - - if (use_expr->kind == Ast_Kind_Struct_Type) { - AstStructType* st = (AstStructType *) use_expr; - if (!st->scope) return Symres_Success; - - if (use->only == NULL) { - scope_include(curr_scope, st->scope, use->token->pos); - - } else { - bh_arr_each(QualifiedUse, qu, use->only) { - AstNode* thing = symbol_resolve(st->scope, qu->symbol_name); - if (thing == NULL) { - onyx_report_error(qu->symbol_name->pos, Error_Critical, - "The symbol '%b' was not found in this scope.", - qu->symbol_name->text, qu->symbol_name->length); - return Symres_Error; - } - - symbol_introduce(curr_scope, qu->as_name, thing); - } - } - - return Symres_Success; - } - - if (use_expr->type_node == NULL && use_expr->type == NULL) goto cannot_use; - - // :EliminatingSymres - AstType* effective_type = use_expr->type_node; - if (effective_type->kind == Ast_Kind_Pointer_Type) - effective_type = ((AstPointerType *) effective_type)->elem; - - if (effective_type->kind == Ast_Kind_Struct_Type || - effective_type->kind == Ast_Kind_Poly_Call_Type) { - - if (use_expr->type == NULL) - use_expr->type = type_build_from_ast(context.ast_alloc, use_expr->type_node); - if (use_expr->type == NULL) goto cannot_use; - - Type* st = use_expr->type; - if (st->kind == Type_Kind_Pointer) - st = st->Pointer.elem; - - fori (i, 0, shlen(st->Struct.members)) { - StructMember* value = st->Struct.members[i].value; - AstFieldAccess* fa = make_field_access(context.ast_alloc, use_expr, value->name); - symbol_raw_introduce(curr_scope, value->name, use->token->pos, (AstNode *) fa); - } - - return Symres_Success; - } - -cannot_use: - onyx_report_error(use->token->pos, Error_Critical, "Cannot use this because its type is unknown."); - return Symres_Error; -} - -static SymresStatus symres_directive_solidify(AstDirectiveSolidify** psolid) { - AstDirectiveSolidify* solid = *psolid; - - SYMRES(expression, (AstTyped **) &solid->poly_proc); - if (solid->poly_proc && solid->poly_proc->kind == Ast_Kind_Directive_Solidify) { - AstFunction* potentially_resolved_proc = (AstFunction *) ((AstDirectiveSolidify *) solid->poly_proc)->resolved_proc; - if (!potentially_resolved_proc) return Symres_Yield_Micro; - - solid->poly_proc = potentially_resolved_proc; - } - - if (!solid->poly_proc || solid->poly_proc->kind != Ast_Kind_Polymorphic_Proc) { - onyx_report_error(solid->token->pos, Error_Critical, "Expected polymorphic procedure in #solidify directive."); - return Symres_Error; - } - - bh_arr_each(AstPolySolution, sln, solid->known_polyvars) { - // HACK: This assumes that 'ast_type' and 'value' are at the same offset. - SYMRES(expression, &sln->value); - } - - return Symres_Success; -} - -static SymresStatus symres_directive_defined(AstDirectiveDefined** pdefined) { - AstDirectiveDefined* defined = *pdefined; - - b32 has_to_be_resolved = context.cycle_almost_detected; - - resolved_a_symbol = 0; - SymresStatus ss = symres_expression(&defined->expr); - if (has_to_be_resolved && ss != Symres_Success && !resolved_a_symbol) { - // The symbol definitely was not found and there is no chance that it could be found. - defined->is_defined = 0; - return Symres_Success; - } - - if (ss == Symres_Success) { - defined->is_defined = 1; - return Symres_Success; - } - - return Symres_Yield_Macro; -} - -static SymresStatus symres_directive_insert(AstDirectiveInsert* insert) { - SYMRES(expression, &insert->code_expr); - return Symres_Success; -} - -static SymresStatus symres_statement(AstNode** stmt, b32 *remove) { - if (remove) *remove = 0; - - switch ((*stmt)->kind) { - case Ast_Kind_Return: SYMRES(return, (AstReturn *) *stmt); break; - case Ast_Kind_If: SYMRES(if, (AstIfWhile *) *stmt); break; - case Ast_Kind_Static_If: SYMRES(if, (AstIfWhile *) *stmt); break; - case Ast_Kind_While: SYMRES(while, (AstIfWhile *) *stmt); break; - case Ast_Kind_For: SYMRES(for, (AstFor *) *stmt); break; - case Ast_Kind_Switch: SYMRES(switch, (AstSwitch *) *stmt); break; - case Ast_Kind_Call: SYMRES(call, (AstCall **) stmt); break; - case Ast_Kind_Argument: SYMRES(expression, (AstTyped **) &((AstArgument *) *stmt)->value); break; - case Ast_Kind_Block: SYMRES(block, (AstBlock *) *stmt); break; - case Ast_Kind_Defer: SYMRES(statement, &((AstDefer *) *stmt)->stmt, NULL); break; - case Ast_Kind_Switch_Case: SYMRES(case, (AstSwitchCase *) *stmt); break; - case Ast_Kind_Jump: break; - case Ast_Kind_Directive_Remove: break; - - case Ast_Kind_Local: - // if (remove) *remove = 1; - SYMRES(local, (AstLocal **) stmt); - break; - - case Ast_Kind_Use: - if (remove) *remove = 1; - SYMRES(use, (AstUse *) *stmt); - break; - - default: SYMRES(expression, (AstTyped **) stmt); break; - } - - return Symres_Success; -} - -static SymresStatus symres_statement_chain(AstNode** walker) { - b32 remove = 0; - - while (*walker) { - SYMRES(statement, walker, &remove); - if (remove) { - remove = 0; - AstNode* tmp = (*walker)->next; - (*walker)->next = NULL; - (*walker) = tmp; - - } else { - walker = &(*walker)->next; - } - } - return Symres_Success; -} - -static SymresStatus symres_block(AstBlock* block) { - if (block->rules & Block_Rule_New_Scope) { - if (block->scope == NULL) - block->scope = scope_create(context.ast_alloc, curr_scope, block->token->pos); - - scope_enter(block->scope); - } - - if (block->binding_scope != NULL) - scope_include(curr_scope, block->binding_scope, block->token->pos); - - if (block->body) { - AstNode** start = &block->body; - fori (i, 0, block->statement_idx) { - start = &(*start)->next; - } - - b32 remove = 0; - - while (*start) { - SymresStatus cs = symres_statement(start, &remove); - - if (remove) { - remove = 0; - AstNode* tmp = (*start)->next; - (*start)->next = NULL; - (*start) = tmp; - - } else { - switch (cs) { - case Symres_Success: - start = &(*start)->next; - block->statement_idx++; - break; - - default: - return cs; - } - } - } - - block->statement_idx = 0; - } - - if (block->rules & Block_Rule_New_Scope) - scope_leave(); - - return Symres_Success; -} - -SymresStatus symres_function_header(AstFunction* func) { - func->flags |= Ast_Flag_Comptime; - - if (func->scope == NULL) - func->scope = scope_create(context.ast_alloc, curr_scope, func->token->pos); - - if (func->constraints.constraints != NULL && func->constraints.constraints_met == 0) { - bh_arr_each(AstConstraint *, constraint, func->constraints.constraints) { - SYMRES(constraint, *constraint); - } - - // Return early here to finish checking constraints in the checker. - // Will resume here after constraints have been met. - return Symres_Success; - } - - scope_enter(func->scope); - - bh_arr_each(AstParam, param, func->params) { - if (param->default_value != NULL) { - SYMRES(expression, ¶m->default_value); - if (onyx_has_errors()) return Symres_Error; - } - } - - bh_arr_each(AstParam, param, func->params) { - symbol_introduce(curr_scope, param->local->token, (AstNode *) param->local); - } - - bh_arr_each(AstParam, param, func->params) { - if (param->local->type_node != NULL) { - SYMRES_INVISIBLE(type, param->local, ¶m->local->type_node); - } - } - - if (potentially_convert_function_to_polyproc(func)) { - return Symres_Complete; - } - - if (func->nodes_that_need_entities_after_clone && bh_arr_length(func->nodes_that_need_entities_after_clone) > 0 && func->entity) { - bh_arr_each(AstNode *, node, func->nodes_that_need_entities_after_clone) { - // This makes a lot of assumptions about how these nodes are being processed, - // and I don't want to start using this with other nodes without considering - // what the ramifications of that is. - assert((*node)->kind == Ast_Kind_Static_If || (*node)->kind == Ast_Kind_File_Contents); - - // Need to curr_scope->parent because curr_scope is the function body scope. - Scope *scope = curr_scope->parent; - - if ((*node)->kind == Ast_Kind_Static_If) { - AstIf *static_if = (AstIf *) *node; - assert(static_if->defined_in_scope); - scope = static_if->defined_in_scope; - - if (func->poly_scope) { - scope = scope_create(context.ast_alloc, scope, static_if->token->pos); - scope_include(scope, func->poly_scope, static_if->token->pos); - } - } - - add_entities_for_node(NULL, *node, scope, func->entity->package); - } - - bh_arr_set_length(func->nodes_that_need_entities_after_clone, 0); - } - - SYMRES(type, &func->return_type); - - scope_leave(); - - return Symres_Success; -} - -SymresStatus symres_function(AstFunction* func) { - if (func->entity_header && func->entity_header->state < Entity_State_Check_Types) return Symres_Yield_Macro; - if (func->kind == Ast_Kind_Polymorphic_Proc) return Symres_Complete; - assert(func->scope); - - scope_enter(func->scope); - - if ((func->flags & Ast_Flag_Has_Been_Symres) == 0) { - // :EliminatingSymres - bh_arr_each(AstParam, param, func->params) { - // CLEANUP: Currently, in order to 'use' parameters, the type must be completely - // resolved and built. This is excessive because all that should need to be known - // is the names of the members, since all that happens is implicit field accesses - // are placed in the scope. So instead, there should be a way to just query all the - // member names in the structure, without needing to know their type. This would be - // easy if it were not for 'use' statements in structs. It is made even more complicated - // by this situtation: - // - // Foo :: struct (T: type_expr) { - // use t : T; - // - // something_else := 5 + 6 * 8; - // } - // - // The 'use t : T' member requires completely knowing the type of T, to know which - // members should be brought in. At the moment, that requires completely building the - // type of Foo($T). - if (param->is_used && !param->use_processed) { - if (param->local->type_node != NULL && param->local->type == NULL) { - param->local->type = type_build_from_ast(context.ast_alloc, param->local->type_node); - - if (param->local->type == NULL) return Symres_Yield_Macro; - } - - if (type_is_struct(param->local->type)) { - Type* st; - if (param->local->type->kind == Type_Kind_Struct) { - st = param->local->type; - } else { - st = param->local->type->Pointer.elem; - } - - if (st->Struct.status != SPS_Uses_Done) return Symres_Yield_Macro; - - fori (i, 0, shlen(st->Struct.members)) { - StructMember* value = st->Struct.members[i].value; - AstFieldAccess* fa = make_field_access(context.ast_alloc, (AstTyped *) param->local, value->name); - symbol_raw_introduce(curr_scope, value->name, param->local->token->pos, (AstNode *) fa); - } - - param->use_processed = 1; - - } else if (param->local->type != NULL) { - onyx_report_error(param->local->token->pos, Error_Critical, "Can only 'use' structures or pointers to structures."); - - } else { - // :ExplicitTyping - onyx_report_error(param->local->token->pos, Error_Critical, "Cannot deduce type of parameter '%b'; Try adding it explicitly.", - param->local->token->text, - param->local->token->length); - } - } - } - - bh_arr_each(AstTyped *, pexpr, func->tags) { - SYMRES(expression, pexpr); - } - - func->flags |= Ast_Flag_Has_Been_Symres; - } - - SYMRES(block, func->body); - - scope_leave(); - return Symres_Success; -} - -static SymresStatus symres_global(AstGlobal* global) { - SYMRES(type, &global->type_node); - return Symres_Success; -} - -static SymresStatus symres_overloaded_function(AstOverloadedFunction* ofunc) { - bh_arr_each(OverloadOption, overload, ofunc->overloads) { - SYMRES(expression, &overload->option); - } - return Symres_Success; -} - -static SymresStatus symres_package(AstPackage* package) { - if (package->package == NULL) { - if (!package->package_name) return Symres_Error; - - package->package = package_lookup(package->package_name); - } - - if (package->package) { - return Symres_Success; - } else { - if (report_unresolved_symbols) { - onyx_report_error(package->token->pos, Error_Critical, - "Package '%s' not found in included source files.", - package->package_name); - return Symres_Error; - } else { - return Symres_Yield_Macro; - } - } -} - -static SymresStatus symres_enum(AstEnumType* enum_node) { - if (enum_node->backing->kind == Ast_Kind_Symbol) SYMRES(symbol, (AstNode **) &enum_node->backing); - if (enum_node->backing == NULL) return Symres_Error; - - if (enum_node->scope == NULL) { - enum_node->backing_type = type_build_from_ast(context.ast_alloc, enum_node->backing); - enum_node->scope = scope_create(context.ast_alloc, curr_scope, enum_node->token->pos); - - type_build_from_ast(context.ast_alloc, (AstType *) enum_node); - } - - scope_enter(enum_node->scope); - - // :EliminatingSymres - u64 next_assign_value = enum_node->is_flags ? 1 : 0; - bh_arr_each(AstEnumValue *, value, enum_node->values) { - if ((*value)->flags & Ast_Flag_Has_Been_Checked) continue; - - (*value)->type = enum_node->etcache; - (*value)->flags |= Ast_Flag_Comptime; - - if ((*value)->value != NULL) { - SYMRES(expression, &(*value)->value); - - if ((*value)->value->kind == Ast_Kind_Enum_Value) { - (*value)->value = ((AstEnumValue *) (*value)->value)->value; - (*value)->value->type = enum_node->etcache; - } - - if ((*value)->value->kind == Ast_Kind_NumLit) { - AstNumLit *n_value = (AstNumLit *) (*value)->value; - resolve_expression_type((AstTyped *) n_value); - - if (type_is_small_integer(n_value->type)) { - next_assign_value = n_value->value.i; - } else if (type_is_integer(n_value->type)) { - next_assign_value = n_value->value.l; - } else { - onyx_report_error((*value)->token->pos, Error_Critical, "expected numeric integer literal for enum initialization, got '%s'", type_get_name(n_value->type)); - return Symres_Error; - } - - n_value->type = enum_node->etcache; - - } else { - if ((*value)->entity == NULL) { - add_entities_for_node(NULL, (AstNode *) (*value), enum_node->scope, NULL); - } - - if (context.cycle_detected) { - onyx_report_error((*value)->token->pos, Error_Critical, "Expected compile time known value for enum initialization."); - return Symres_Error; - } - - return Symres_Yield_Macro; - } - - } else { - AstNumLit* num = make_int_literal(context.ast_alloc, next_assign_value); - num->type = enum_node->etcache; - - (*value)->value = (AstTyped *) num; - } - - symbol_introduce(enum_node->scope, (*value)->token, (AstNode *) (*value)); - - (*value)->flags |= Ast_Flag_Comptime | Ast_Flag_Has_Been_Checked; - - if (enum_node->is_flags) { - next_assign_value <<= 1; - } else { - next_assign_value++; - } - } - - scope_leave(); - - // HACK this ensure that you can only lookup symbols in an Enum that are actually defined in the enum. - // However, during the symbol resolution of the values in an enum, they need to be able to see the - // enclosing scope. - enum_node->scope->parent = NULL; - - return Symres_Success; -} - -static SymresStatus symres_memres_type(AstMemRes** memres) { - SYMRES(type, &(*memres)->type_node); - return Symres_Success; -} - -static SymresStatus symres_memres(AstMemRes** memres) { - if ((*memres)->initial_value != NULL) { - SYMRES(expression, &(*memres)->initial_value); - } - return Symres_Success; -} - -static SymresStatus symres_struct_defaults(AstType* t) { - if (t->kind != Ast_Kind_Struct_Type) return Symres_Error; - - AstStructType* st = (AstStructType *) t; - if (st->scope) scope_enter(st->scope); - - if (st->meta_tags) { - bh_arr_each(AstTyped *, meta, st->meta_tags) { - SYMRES(expression, meta); - } - } - - bh_arr_each(AstStructMember *, smem, st->members) { - if ((*smem)->initial_value != NULL) { - SYMRES(expression, &(*smem)->initial_value); - } - - if ((*smem)->meta_tags != NULL) { - bh_arr_each(AstTyped *, meta, (*smem)->meta_tags) { - SYMRES(expression, meta); - } - } - } - - if (st->scope) scope_leave(); - return Symres_Success; -} - -static SymresStatus symres_polyproc(AstFunction* pp) { - pp->flags |= Ast_Flag_Comptime; - pp->parent_scope_of_poly_proc = curr_scope; - return Symres_Success; -} - -static SymresStatus symres_static_if(AstIf* static_if) { - if (static_if->flags & Ast_Flag_Dead) return Symres_Complete; - - SYMRES(expression, &static_if->cond); - return Symres_Success; -} - -static SymresStatus symres_process_directive(AstNode* directive) { - // :EliminatingSymres - switch (directive->kind) { - case Ast_Kind_Directive_Add_Overload: { - AstDirectiveAddOverload *add_overload = (AstDirectiveAddOverload *) directive; - - SYMRES(expression, (AstTyped **) &add_overload->overloaded_function); - if (add_overload->overloaded_function == NULL) return Symres_Error; // NOTE: Error message will already be generated - - if (add_overload->overloaded_function->kind != Ast_Kind_Overloaded_Function) { - onyx_report_error(add_overload->token->pos, Error_Critical, "#match directive expects a matched procedure."); - return Symres_Error; - } - - AstOverloadedFunction* ofunc = (AstOverloadedFunction *) add_overload->overloaded_function; - if (ofunc->locked) { - onyx_report_error(add_overload->token->pos, Error_Critical, "Cannot add match option here as the original #match was declared as #locked."); - onyx_report_error(ofunc->token->pos, Error_Critical, "Here is the original #match."); - return Symres_Error; - } - - if (ofunc->only_local_functions) { - if (!token_same_file(add_overload->token, ofunc->token)) { - onyx_report_error(add_overload->token->pos, Error_Critical, "Cannot add match option here as this option is not within the same file as the original #match declared with #local."); - onyx_report_error(ofunc->token->pos, Error_Critical, "Here is the original #match."); - } - } - - SYMRES(expression, (AstTyped **) &add_overload->overload); - add_overload_option(&ofunc->overloads, add_overload->precedence, add_overload->overload); - break; - } - - case Ast_Kind_Directive_Operator: { - AstDirectiveOperator *operator = (AstDirectiveOperator *) directive; - SYMRES(expression, &operator->overload); - if (!operator->overload) return Symres_Error; - - AstFunction* overload = get_function_from_node((AstNode *) operator->overload); - if (overload == NULL) { - onyx_report_error(operator->token->pos, Error_Critical, "This cannot be used as an operator overload."); - return Symres_Error; - } - - if (operator->operator != Binary_Op_Subscript_Equals && bh_arr_length(overload->params) != 2) { - onyx_report_error(operator->token->pos, Error_Critical, "Expected exactly 2 arguments for binary operator overload."); - return Symres_Error; - } - - add_overload_option(&operator_overloads[operator->operator], 0, operator->overload); - break; - } - - case Ast_Kind_Directive_Export: { - AstDirectiveExport *export = (AstDirectiveExport *) directive; - SYMRES(expression, &export->export); - SYMRES(expression, &export->export_name_expr); - - if (export->export->kind == Ast_Kind_Polymorphic_Proc) { - onyx_report_error(export->token->pos, Error_Critical, "Cannot export a polymorphic function."); - return Symres_Error; - } - - if (export->export->kind == Ast_Kind_Function) { - AstFunction *func = (AstFunction *) export->export; - func->exported_name = export->export_name; - func->is_exported = 1; - - if (func->is_exported) { - if (func->is_foreign) { - onyx_report_error(export->token->pos, Error_Critical, "Cannot export a foreign function."); - return Symres_Error; - } - - if (func->is_intrinsic) { - onyx_report_error(export->token->pos, Error_Critical, "Cannot export an intrinsic function."); - return Symres_Error; - } - } - } - - break; - } - - case Ast_Kind_Directive_Init: { - AstDirectiveInit *init = (AstDirectiveInit *) directive; - SYMRES(expression, &init->init_proc); - - if (init->dependencies) { - bh_arr_each(AstDirectiveInit *, dependency, init->dependencies) { - SYMRES(expression, (AstTyped **) dependency); - } - } - - break; - } - - case Ast_Kind_Directive_Library: { - AstDirectiveLibrary *library = (AstDirectiveLibrary *) directive; - SYMRES(expression, &library->library_symbol); - break; - } - - case Ast_Kind_Injection: { - AstInjection *inject = (AstInjection *) directive; - - if (inject->dest == NULL) { - if (inject->full_loc == NULL) return Symres_Error; - - if (inject->full_loc->kind != Ast_Kind_Field_Access) { - onyx_report_error(inject->token->pos, Error_Critical, "#inject expects a dot (a.b) expression for the injection point."); - return Symres_Error; - } - - AstFieldAccess *acc = (AstFieldAccess *) inject->full_loc; - inject->dest = acc->expr; - inject->symbol = acc->token; - } - - SYMRES(expression, &inject->dest); - SYMRES(expression, &inject->to_inject); - - Scope *scope = get_scope_from_node_or_create((AstNode *) inject->dest); - if (scope == NULL) { - onyx_report_error(inject->token->pos, Error_Critical, "Cannot #inject here."); - return Symres_Error; - } - - AstBinding *binding = onyx_ast_node_new(context.ast_alloc, sizeof(AstBinding), Ast_Kind_Binding); - binding->token = inject->symbol; - binding->node = (AstNode *) inject->to_inject; - - Package *pac = NULL; - if (inject->dest->kind == Ast_Kind_Package) { - pac = ((AstPackage *) inject->dest)->package; - } - - add_entities_for_node(NULL, (AstNode *) binding, scope, pac); - return Symres_Complete; - } - } - - return Symres_Success; -} - -static SymresStatus symres_macro(AstMacro* macro) { - macro->flags |= Ast_Flag_Comptime; - - if (macro->body->kind == Ast_Kind_Function) { - SYMRES(function_header, (AstFunction *) macro->body); - } - else if (macro->body->kind == Ast_Kind_Polymorphic_Proc) { - SYMRES(polyproc, (AstFunction *) macro->body); - } - - return Symres_Success; -} - -static SymresStatus symres_constraint(AstConstraint* constraint) { - switch (constraint->phase) { - case Constraint_Phase_Cloning_Expressions: - case Constraint_Phase_Waiting_To_Be_Queued: { - SYMRES(expression, (AstTyped **) &constraint->interface); - - bh_arr_each(AstType *, type_arg, constraint->type_args) { - SYMRES(type, type_arg); - } - - return Symres_Success; - } - - case Constraint_Phase_Checking_Expressions: { - fori (i, constraint->expr_idx, bh_arr_length(constraint->exprs)) { - SYMRES(expression, &constraint->exprs[i].expr); - - if (constraint->exprs[i].expected_type_expr) { - SYMRES(type, &constraint->exprs[i].expected_type_expr); - } - } - - return Symres_Success; - } - } - - return Symres_Success; -} - -static SymresStatus symres_polyquery(AstPolyQuery *query) { - // :EliminatingSymres - query->successful_symres = 0; - - if (query->function_header->scope == NULL) - query->function_header->scope = scope_create(context.ast_alloc, query->proc->parent_scope_of_poly_proc, query->token->pos); - - scope_enter(query->function_header->scope); - - u32 idx = 0; - bh_arr_each(AstParam, param, query->function_header->params) { - bh_arr_each(AstPolyParam, pp, query->proc->poly_params) { - if (pp->kind == PPK_Baked_Value && pp->idx == idx) goto skip_introducing_symbol; - } - - symbol_introduce(curr_scope, param->local->token, (AstNode *) param->local); - - skip_introducing_symbol: - idx++; - } - - bh_arr_each(AstParam, param, query->function_header->params) { - if (param->local->type_node != NULL) { - resolved_a_symbol = 0; - - param->local->flags |= Ast_Flag_Symbol_Invisible; - symres_type(¶m->local->type_node); - param->local->flags &= ~Ast_Flag_Symbol_Invisible; - - onyx_clear_errors(); - - if (resolved_a_symbol) query->successful_symres = 1; - } - } - - scope_leave(); - return Symres_Success; -} - -static SymresStatus symres_foreign_block(AstForeignBlock *fb) { - if (fb->scope == NULL) - fb->scope = scope_create(context.ast_alloc, curr_scope, fb->token->pos); - - bh_arr_each(Entity *, pent, fb->captured_entities) { - Entity *ent = *pent; - if (ent->type == Entity_Type_Function_Header) { - if (ent->function->body->next != NULL) { - onyx_report_error(ent->function->token->pos, Error_Critical, "Procedures declared in a #foreign block should not have bodies."); - return Symres_Error; - } - - ent->function->foreign_name = ent->function->intrinsic_name; // Hmm... This might not be right? - ent->function->foreign_module = fb->module_name; - ent->function->is_foreign = 1; - ent->function->entity = NULL; - ent->function->entity_header = NULL; - ent->function->entity_body = NULL; - - add_entities_for_node(NULL, (AstNode *) ent->function, ent->scope, ent->package); - continue; - } - - if (ent->type == Entity_Type_Binding) { - AstBinding* new_binding = onyx_ast_node_new(context.ast_alloc, sizeof(AstBinding), Ast_Kind_Binding); - new_binding->token = ent->binding->token; - new_binding->node = ent->binding->node; - - Entity e; - memset(&e, 0, sizeof(e)); - e.type = Entity_Type_Binding; - e.state = Entity_State_Introduce_Symbols; - e.binding = new_binding; - e.scope = fb->scope; - e.package = ent->package; - - entity_heap_insert(&context.entities, e); - } - - if (ent->type != Entity_Type_Function) { - entity_heap_insert_existing(&context.entities, ent); - } - } - - return Symres_Complete; -} - -static SymresStatus symres_include(AstInclude* include) { - if (include->name != NULL) return Symres_Goto_Parse; - - SYMRES(expression, &include->name_node); - - if (include->name_node->kind != Ast_Kind_StrLit) { - onyx_report_error(include->token->pos, Error_Critical, "Expected compile-time known string literal here. Got '%s'.", onyx_ast_node_kind_string(include->name_node->kind)); - return Symres_Error; - } - - OnyxToken* str_token = include->name_node->token; - if (str_token != NULL) { - token_toggle_end(str_token); - include->name = bh_strdup(context.ast_alloc, str_token->text); - string_process_escape_seqs(include->name, include->name, strlen(include->name)); - token_toggle_end(str_token); - } - - return Symres_Goto_Parse; -} - -static SymresStatus symres_file_contents(AstFileContents* fc) { - SYMRES(expression, &fc->filename_expr); - - if (fc->filename_expr->kind != Ast_Kind_StrLit) { - onyx_report_error(fc->token->pos, Error_Critical, "Expected given expression to be a compile-time stirng literal."); - return Symres_Error; - } - - return Symres_Success; -} - -void symres_entity(Entity* ent) { - if (ent->scope) scope_enter(ent->scope); - - report_unresolved_symbols = context.cycle_detected; - - SymresStatus ss = Symres_Success; - EntityState next_state = Entity_State_Check_Types; - - switch (ent->type) { - case Entity_Type_Binding: { - symbol_introduce(curr_scope, ent->binding->token, ent->binding->node); - package_reinsert_use_packages(ent->package); - next_state = Entity_State_Finalized; - break; - } - - case Entity_Type_Static_If: ss = symres_static_if(ent->static_if); break; - - case Entity_Type_Load_Path: - case Entity_Type_Load_File: ss = symres_include(ent->include); break; - case Entity_Type_File_Contents: ss = symres_file_contents(ent->file_contents); break; - - case Entity_Type_Foreign_Function_Header: - case Entity_Type_Temp_Function_Header: - case Entity_Type_Function_Header: ss = symres_function_header(ent->function); break; - case Entity_Type_Function: ss = symres_function(ent->function); break; - - case Entity_Type_Global_Header: ss = symres_global(ent->global); break; - - case Entity_Type_Use_Package: - case Entity_Type_Use: ss = symres_use(ent->use); - next_state = Entity_State_Finalized; - break; - - case Entity_Type_Polymorphic_Proc: ss = symres_polyproc(ent->poly_proc); - next_state = Entity_State_Finalized; - break; - - case Entity_Type_Overloaded_Function: ss = symres_overloaded_function(ent->overloaded_function); break; - case Entity_Type_Expression: ss = symres_expression(&ent->expr); break; - case Entity_Type_Type_Alias: ss = symres_type(&ent->type_alias); break; - case Entity_Type_Enum: ss = symres_enum(ent->enum_type); break; - case Entity_Type_Memory_Reservation_Type: ss = symres_memres_type(&ent->mem_res); break; - case Entity_Type_Memory_Reservation: ss = symres_memres(&ent->mem_res); break; - case Entity_Type_String_Literal: ss = symres_expression(&ent->expr); break; - case Entity_Type_Struct_Member_Default: ss = symres_struct_defaults((AstType *) ent->type_alias); break; - case Entity_Type_Process_Directive: ss = symres_process_directive((AstNode *) ent->expr); break; - case Entity_Type_Macro: ss = symres_macro(ent->macro); break; - case Entity_Type_Constraint_Check: ss = symres_constraint(ent->constraint); break; - case Entity_Type_Polymorph_Query: ss = symres_polyquery(ent->poly_query); break; - case Entity_Type_Foreign_Block: ss = symres_foreign_block(ent->foreign_block); - if (context.options->generate_foreign_info) { - next_state = Entity_State_Check_Types; - ss = Symres_Success; - } - break; - - default: break; - } - - if (ss == Symres_Yield_Macro) ent->macro_attempts++; - if (ss == Symres_Yield_Micro) ent->micro_attempts++; - if (ss == Symres_Complete) ent->state = Entity_State_Finalized; - if (ss == Symres_Goto_Parse) ent->state = Entity_State_Parse; - if (ss == Symres_Success) { - ent->macro_attempts = 0; - ent->micro_attempts = 0; - ent->state = next_state; - } - - curr_scope = NULL; -} diff --git a/src/types.c b/src/types.c deleted file mode 100644 index 0d9568a0..00000000 --- a/src/types.c +++ /dev/null @@ -1,1490 +0,0 @@ -#define BH_DEBUG -#include "stb_ds.h" -#include "types.h" -#include "astnodes.h" -#include "utils.h" -#include "errors.h" - -// NOTE: These have to be in the same order as Basic -Type basic_types[] = { - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_void, { Basic_Kind_Void, 0, 0, 1, "void" } }, - - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_bool, { Basic_Kind_Bool, Basic_Flag_Boolean, 1, 1, "bool" } }, - - { Type_Kind_Basic, 0, 0, NULL, { Basic_Kind_Int_Unsized, Basic_Flag_Integer, 0, 0, "unsized int" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i8, { Basic_Kind_I8, Basic_Flag_Integer, 1, 1, "i8" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_u8, { Basic_Kind_U8, Basic_Flag_Integer | Basic_Flag_Unsigned, 1, 1, "u8" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i16, { Basic_Kind_I16, Basic_Flag_Integer, 2, 2, "i16" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_u16, { Basic_Kind_U16, Basic_Flag_Integer | Basic_Flag_Unsigned, 2, 2, "u16" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i32, { Basic_Kind_I32, Basic_Flag_Integer, 4, 4, "i32" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_u32, { Basic_Kind_U32, Basic_Flag_Integer | Basic_Flag_Unsigned, 4, 4, "u32" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i64, { Basic_Kind_I64, Basic_Flag_Integer, 8, 8, "i64" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_u64, { Basic_Kind_U64, Basic_Flag_Integer | Basic_Flag_Unsigned, 8, 8, "u64" } }, - - { Type_Kind_Basic, 0, 0, NULL, { Basic_Kind_Float_Unsized, Basic_Flag_Float, 0, 0, "unsized float" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_f32, { Basic_Kind_F32, Basic_Flag_Float, 4, 4, "f32" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_f64, { Basic_Kind_F64, Basic_Flag_Float, 8, 4, "f64" } }, - - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_rawptr, { Basic_Kind_Rawptr, Basic_Flag_Pointer, POINTER_SIZE, POINTER_SIZE, "rawptr" } }, - - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i8x16, { Basic_Kind_I8X16, Basic_Flag_SIMD, 16, 16, "i8x16" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i16x8, { Basic_Kind_I16X8, Basic_Flag_SIMD, 16, 16, "i16x8" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i32x4, { Basic_Kind_I32X4, Basic_Flag_SIMD, 16, 16, "i32x4" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_i64x2, { Basic_Kind_I64X2, Basic_Flag_SIMD, 16, 16, "i64x2" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_f32x4, { Basic_Kind_F32X4, Basic_Flag_SIMD, 16, 16, "f32x4" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_f64x2, { Basic_Kind_F64X2, Basic_Flag_SIMD, 16, 16, "f64x2" } }, - { Type_Kind_Basic, 0, 0, (AstType *) &basic_type_v128, { Basic_Kind_V128, Basic_Flag_SIMD, 16, 16, "v128" } }, - - { Type_Kind_Basic, 0, 0, NULL, { Basic_Kind_Type_Index, Basic_Flag_Type_Index, 4, 4, "type_expr" } }, -}; - -// TODO: Document this!! - bh_imap type_map; -static bh_imap type_pointer_map; -static bh_imap type_array_map; -static bh_imap type_slice_map; -static bh_imap type_dynarr_map; -static bh_imap type_vararg_map; -static Table(u64) type_func_map; - -static Type* type_create(TypeKind kind, bh_allocator a, u32 extra_type_pointer_count) { - Type* type = bh_alloc(a, sizeof(Type) + sizeof(Type *) * extra_type_pointer_count); - type->kind = kind; - type->ast_type = NULL; - return type; -} - -static void type_register(Type* type) { - static u32 next_unique_id = 1; - type->id = next_unique_id++; - if (type->ast_type) type->ast_type->type_id = type->id; - - bh_imap_put(&type_map, type->id, (u64) type); -} - -void types_init() { - bh_imap_init(&type_map, global_heap_allocator, 255); - bh_imap_init(&type_pointer_map, global_heap_allocator, 255); - bh_imap_init(&type_array_map, global_heap_allocator, 255); - bh_imap_init(&type_slice_map, global_heap_allocator, 255); - bh_imap_init(&type_dynarr_map, global_heap_allocator, 255); - bh_imap_init(&type_vararg_map, global_heap_allocator, 255); - sh_new_arena(type_func_map); - - fori (i, 0, Basic_Kind_Count) type_register(&basic_types[i]); -} - -void types_dump_type_info() { - bh_arr_each(bh__imap_entry, entry, type_map.entries) { - bh_printf("%d -> %s\n", entry->key, type_get_name((Type *) entry->value)); - } -} - -b32 types_are_compatible_(Type* t1, Type* t2, b32 recurse_pointers) { - // NOTE: If they are pointing to the same thing, - // it is safe to assume they are the same type - if (t1 == t2) return 1; - if (t1 == NULL || t2 == NULL) return 0; - if (t1->id == t2->id) return 1; - - if (t1 == &type_auto_return || t2 == &type_auto_return) { - return 0; - } - - switch (t1->kind) { - case Type_Kind_Basic: - if (t2->kind == Type_Kind_Basic) { - // Signedness of an integer doesn't matter. - if ((t1->Basic.flags & Basic_Flag_Integer) && (t2->Basic.flags & Basic_Flag_Integer)) { - return t1->Basic.size == t2->Basic.size; - } - - if (t1->Basic.kind == Basic_Kind_V128 || t2->Basic.kind == Basic_Kind_V128) return 1; - } - - if (t1->Basic.kind == Basic_Kind_Rawptr && type_is_pointer(t2)) { - return 1; - } - break; - - case Type_Kind_Pointer: { - if (t2->kind == Type_Kind_Pointer) { - if (!recurse_pointers) return 1; - - if (types_are_compatible(t1->Pointer.elem, t2->Pointer.elem)) return 1; - - if (t1->Pointer.elem->kind == Type_Kind_Struct && t2->Pointer.elem->kind == Type_Kind_Struct) { - Type* t1_struct = t1->Pointer.elem; - Type* t2_struct = t2->Pointer.elem; - - bh_arr(StructMember *) members = t1_struct->Struct.memarr; - if (bh_arr_length(members) > 0 && members[0]->used) - return types_are_compatible(t2_struct,members[0]->type); - } - } - - if (t2->kind == Type_Kind_Basic && t2->Basic.kind == Basic_Kind_Rawptr) return 1; - - break; - } - - case Type_Kind_Array: { - if (t2->kind != Type_Kind_Array) return 0; - - if (t1->Array.count != 0) - if (t1->Array.count != t2->Array.count) return 0; - - return types_are_compatible(t1->Array.elem, t2->Array.elem); - } - - case Type_Kind_Struct: { - // NOTE: The check above for t1 == t2 would already catch this. - - // if (t2->kind != Type_Kind_Struct) return 0; - // if (t1->Struct.unique_id != t2->Struct.unique_id) return 0; - // if (t1->Struct.mem_count != t2->Struct.mem_count) return 0; - return 0; - } - - case Type_Kind_Enum: { - // NOTE: The check above for t1 == t2 would already catch this. - return 0; - } - - case Type_Kind_Function: { - if (t2->kind != Type_Kind_Function) return 0; - if (t1->Function.param_count != t2->Function.param_count) return 0; - - if (!types_are_compatible(t1->Function.return_type, t2->Function.return_type)) return 0; - - if (t1->Function.param_count > 0) { - fori (i, 0, t1->Function.param_count) { - if (!types_are_compatible(t1->Function.params[i], t2->Function.params[i])) return 0; - } - } - - return 1; - } - - case Type_Kind_Slice: { - if (t2->kind != Type_Kind_Slice) return 0; - return types_are_compatible(t1->Slice.elem, t2->Slice.elem); - } - - case Type_Kind_VarArgs: { - if (t2->kind != Type_Kind_VarArgs) return 0; - return types_are_compatible(t1->VarArgs.elem, t2->VarArgs.elem); - } - - case Type_Kind_DynArray: { - if (t2->kind != Type_Kind_DynArray) return 0; - return types_are_compatible(t1->DynArray.elem, t2->DynArray.elem); - } - - case Type_Kind_Compound: { - if (t2->kind != Type_Kind_Compound) return 0; - if (t1->Compound.count != t2->Compound.count) return 0; - - fori (i, 0, (i64) t1->Compound.count) { - if (!types_are_compatible(t1->Compound.types[i], t2->Compound.types[i])) return 0; - } - - return 1; - } - - case Type_Kind_Distinct: - // If the above cases didn't catch it, then these distinct types are not compatible. - return 0; - - default: - assert(("Invalid type", 0)); - break; - } - - return 0; -} - -b32 types_are_compatible(Type* t1, Type* t2) { - return types_are_compatible_(t1, t2, 1); -} - -u32 type_size_of(Type* type) { - if (type == NULL) return 0; - - switch (type->kind) { - case Type_Kind_Basic: return type->Basic.size; - case Type_Kind_Pointer: return POINTER_SIZE; - case Type_Kind_Function: return 4; - case Type_Kind_Array: return type->Array.size; - case Type_Kind_Struct: return type->Struct.size; - case Type_Kind_Enum: return type_size_of(type->Enum.backing); - case Type_Kind_Slice: return POINTER_SIZE * 2; // HACK: These should not have to be 16 bytes in size, they should only have to be 12, - case Type_Kind_VarArgs: return POINTER_SIZE * 2; // but there are alignment issues right now with that so I decided to not fight it and just make them 16 bytes in size. - case Type_Kind_DynArray: return POINTER_SIZE + 8 + 2 * POINTER_SIZE; // data (8), count (4), capacity (4), allocator { func (4), ---(4), data (8) } - case Type_Kind_Compound: return type->Compound.size; - case Type_Kind_Distinct: return type_size_of(type->Distinct.base_type); - default: return 0; - } -} - -u32 type_alignment_of(Type* type) { - if (type == NULL) return 1; - - switch (type->kind) { - case Type_Kind_Basic: return type->Basic.alignment; - case Type_Kind_Pointer: return POINTER_SIZE; - case Type_Kind_Function: return 4; - case Type_Kind_Array: return type_alignment_of(type->Array.elem); - case Type_Kind_Struct: return type->Struct.alignment; - case Type_Kind_Enum: return type_alignment_of(type->Enum.backing); - case Type_Kind_Slice: return POINTER_SIZE; - case Type_Kind_VarArgs: return POINTER_SIZE; - case Type_Kind_DynArray: return POINTER_SIZE; - case Type_Kind_Compound: return 4; // HACK - case Type_Kind_Distinct: return type_alignment_of(type->Distinct.base_type); - default: return 1; - } -} - -// If this function returns NULL, then the caller MUST yield because the type may still be constructed in the future. -// If there was an error constructing the type, then this function will report that directly. -Type* type_build_from_ast(bh_allocator alloc, AstType* type_node) { - if (type_node == NULL) return NULL; - - switch (type_node->kind) { - case Ast_Kind_Pointer_Type: { - // ((AstPointerType *) type_node)->elem->flags |= type_node->flags & Ast_Flag_Header_Check_No_Error; - Type* ptr_type = type_make_pointer(alloc, type_build_from_ast(alloc, ((AstPointerType *) type_node)->elem)); - if (ptr_type) ptr_type->ast_type = type_node; - return ptr_type; - } - - case Ast_Kind_Function_Type: { - AstFunctionType* ftype_node = (AstFunctionType *) type_node; - u64 param_count = ftype_node->param_count; - - Type* return_type = type_build_from_ast(alloc, ftype_node->return_type); - if (return_type == NULL) return NULL; - - Type* func_type = type_create(Type_Kind_Function, alloc, param_count); - func_type->ast_type = type_node; - func_type->Function.param_count = param_count; - func_type->Function.needed_param_count = param_count; - func_type->Function.vararg_arg_pos = -1; - func_type->Function.return_type = return_type; - - if (param_count > 0) { - fori (i, 0, (i64) param_count) { - func_type->Function.params[i] = type_build_from_ast(alloc, ftype_node->params[i]); - - // LEAK LEAK LEAK - if (func_type->Function.params[i] == NULL) return NULL; - } - } - - char* name = (char *) type_get_unique_name(func_type); - if (func_type->Function.return_type != &type_auto_return) { - i32 index = shgeti(type_func_map, name); - if (index != -1) { - u64 id = type_func_map[index].value; - Type* existing_type = (Type *) bh_imap_get(&type_map, id); - - // LEAK LEAK LEAK the func_type that is created - return existing_type; - } - } - - type_register(func_type); - shput(type_func_map, name, func_type->id); - - return func_type; - } - - case Ast_Kind_Array_Type: { - AstArrayType* a_node = (AstArrayType *) type_node; - - Type *elem_type = type_build_from_ast(alloc, a_node->elem); - if (elem_type == NULL) return NULL; - - u32 count = 0; - if (a_node->count_expr) { - if (a_node->count_expr->type == NULL) - a_node->count_expr->type = type_build_from_ast(alloc, a_node->count_expr->type_node); - - if (node_is_auto_cast((AstNode *) a_node->count_expr)) { - a_node->count_expr = ((AstUnaryOp *) a_node)->expr; - } - - resolve_expression_type(a_node->count_expr); - - // NOTE: Currently, the count_expr has to be an I32 literal - if (a_node->count_expr->type->kind != Type_Kind_Basic - || a_node->count_expr->type->Basic.kind != Basic_Kind_I32) { - onyx_report_error(type_node->token->pos, Error_Critical, "Array type expects type 'i32' for size, got '%s'.", - type_get_name(a_node->count_expr->type)); - return NULL; - } - - count = get_expression_integer_value(a_node->count_expr, NULL); - } - - Type* array_type = type_make_array(alloc, elem_type, count); - if (array_type) array_type->ast_type = type_node; - return array_type; - } - - case Ast_Kind_Struct_Type: { - AstStructType* s_node = (AstStructType *) type_node; - if (s_node->stcache != NULL) return s_node->stcache; - if (s_node->pending_type != NULL && s_node->pending_type_is_valid) return s_node->pending_type; - if (!s_node->ready_to_build_type) return NULL; - - Type* s_type; - if (s_node->pending_type == NULL) { - s_type = type_create(Type_Kind_Struct, alloc, 0); - s_node->pending_type = s_type; - - s_type->ast_type = type_node; - s_type->Struct.name = s_node->name; - s_type->Struct.mem_count = bh_arr_length(s_node->members); - s_type->Struct.meta_tags = s_node->meta_tags; - s_type->Struct.constructed_from = NULL; - s_type->Struct.status = SPS_Start; - type_register(s_type); - - s_type->Struct.memarr = NULL; - sh_new_arena(s_type->Struct.members); - bh_arr_new(global_heap_allocator, s_type->Struct.memarr, s_type->Struct.mem_count); - - } else { - s_type = s_node->pending_type; - } - - s_type->Struct.poly_sln = NULL; - - bh_arr_clear(s_type->Struct.memarr); - shfree(s_type->Struct.members); - sh_new_arena(s_type->Struct.members); - - s_node->pending_type_is_valid = 1; - - b32 is_union = s_node->is_union; - u32 size = 0; - u32 offset = 0; - u32 alignment = 1, mem_alignment; - u32 idx = 0; - bh_arr_each(AstStructMember *, member, s_node->members) { - if ((*member)->type == NULL) - (*member)->type = type_build_from_ast(alloc, (*member)->type_node); - - if ((*member)->type == NULL) { - s_node->pending_type_is_valid = 0; - return NULL; - } - - mem_alignment = type_alignment_of((*member)->type); - if (mem_alignment <= 0) { - onyx_report_error((*member)->token->pos, Error_Critical, "Invalid member type: %s. Has alignment %d", type_get_name((*member)->type), mem_alignment); - return NULL; - } - - if (mem_alignment > alignment) alignment = mem_alignment; - - if (!s_node->is_packed) { - bh_align(offset, mem_alignment); - } - - token_toggle_end((*member)->token); - if (shgeti(s_type->Struct.members, (*member)->token->text) != -1) { - onyx_report_error((*member)->token->pos, Error_Critical, "Duplicate struct member, '%s'.", (*member)->token->text); - return NULL; - } - - StructMember* smem = bh_alloc_item(alloc, StructMember); - smem->offset = offset; - smem->type = (*member)->type; - smem->idx = idx; - smem->name = bh_strdup(alloc, (*member)->token->text); - smem->token = (*member)->token; - smem->initial_value = &(*member)->initial_value; - smem->meta_tags = (*member)->meta_tags; - - smem->included_through_use = 0; - smem->used = (*member)->is_used; - smem->use_through_pointer_index = -1; - shput(s_type->Struct.members, (*member)->token->text, smem); - bh_arr_push(s_type->Struct.memarr, smem); - token_toggle_end((*member)->token); - - u32 type_size = type_size_of((*member)->type); - - if (!is_union) { - offset += type_size; - size = offset; - } else { - size = bh_max(size, type_size); - } - - idx++; - } - - u32 min_alignment = get_expression_integer_value(s_node->min_alignment_, NULL); - alignment = bh_max(min_alignment, alignment); - if (!s_node->is_packed) { - bh_align(size, alignment); - } - - u32 min_size = get_expression_integer_value(s_node->min_size_, NULL); - size = bh_max(min_size, size); - - s_type->Struct.alignment = alignment; - s_type->Struct.size = size; - - s_type->Struct.linear_members = NULL; - bh_arr_new(global_heap_allocator, s_type->Struct.linear_members, s_type->Struct.mem_count); - build_linear_types_with_offset(s_type, &s_type->Struct.linear_members, 0); - - s_type->Struct.status = SPS_Members_Done; - return s_type; - } - - case Ast_Kind_Enum_Type: { - AstEnumType* enum_node = (AstEnumType *) type_node; - if (enum_node->etcache) return enum_node->etcache; - if (enum_node->backing_type == NULL) return NULL; - - Type* enum_type = type_create(Type_Kind_Enum, alloc, 0); - enum_node->etcache = enum_type; - - enum_type->ast_type = type_node; - enum_type->Enum.backing = enum_node->backing_type; - enum_type->Enum.name = enum_node->name; - enum_type->Enum.is_flags = enum_node->is_flags; - - type_register(enum_type); - return enum_type; - } - - case Ast_Kind_Slice_Type: { - Type* slice_type = type_make_slice(alloc, type_build_from_ast(alloc, ((AstSliceType *) type_node)->elem)); - if (slice_type) slice_type->ast_type = type_node; - return slice_type; - } - - case Ast_Kind_DynArr_Type: { - Type* dynarr_type = type_make_dynarray(alloc, type_build_from_ast(alloc, ((AstDynArrType *) type_node)->elem)); - if (dynarr_type) dynarr_type->ast_type = type_node; - return dynarr_type; - } - - case Ast_Kind_VarArg_Type: { - Type* va_type = type_make_varargs(alloc, type_build_from_ast(alloc, ((AstVarArgType *) type_node)->elem)); - if (va_type) va_type->ast_type = type_node; - return va_type; - } - - case Ast_Kind_Basic_Type: { - return ((AstBasicType *) type_node)->basic_type; - } - - case Ast_Kind_Type_Alias: { - Type* type = type_build_from_ast(alloc, ((AstTypeAlias *) type_node)->to); - if (type && type->ast_type) type_node->type_id = type->id; - return type; - } - - case Ast_Kind_Type_Raw_Alias: - return ((AstTypeRawAlias *) type_node)->to; - - case Ast_Kind_Poly_Struct_Type: { - if (type_node->type_id != 0) return NULL; - - Type* p_type = type_create(Type_Kind_PolyStruct, alloc, 0); - p_type->ast_type = type_node; - p_type->PolyStruct.name = ((AstPolyStructType *) type_node)->name; - p_type->PolyStruct.meta_tags = ((AstPolyStructType *) type_node)->base_struct->meta_tags; - - type_register(p_type); - return NULL; - } - - case Ast_Kind_Poly_Call_Type: { - AstPolyCallType* pc_type = (AstPolyCallType *) type_node; - pc_type->callee = (AstType *) strip_aliases((AstNode *) pc_type->callee); - - if (!(pc_type->callee && pc_type->callee->kind == Ast_Kind_Poly_Struct_Type)) { - // If it is an unresolved field access or symbol, just return because an error will be printed elsewhere. - if (pc_type->callee->kind == Ast_Kind_Field_Access || pc_type->callee->kind == Ast_Kind_Symbol) return NULL; - - onyx_report_error(pc_type->token->pos, Error_Critical, "Cannot instantiate a concrete type off of a non-polymorphic type."); - onyx_report_error(pc_type->callee->token->pos, Error_Critical, "Here is the type trying to be instantiated. (%s)", onyx_ast_node_kind_string(pc_type->callee->kind)); - return NULL; - } - - AstPolyStructType* ps_type = (AstPolyStructType *) pc_type->callee; - - bh_arr(AstPolySolution) slns = NULL; - bh_arr_new(global_heap_allocator, slns, bh_arr_length(pc_type->params)); - bh_arr_each(AstNode *, given, pc_type->params) { - if (node_is_type(*given)) { - Type* param_type = type_build_from_ast(alloc, (AstType *) *given); - - // LEAK LEAK LEAK - if (param_type == NULL) return NULL; - - bh_arr_push(slns, ((AstPolySolution) { - .kind = PSK_Type, - .type = param_type, - })); - } else { - bh_arr_push(slns, ((AstPolySolution) { - .kind = PSK_Value, - .value = (AstTyped *) *given, - })); - } - } - - Type* concrete = polymorphic_struct_lookup(ps_type, slns, pc_type->token->pos, (pc_type->flags & Ast_Flag_Header_Check_No_Error) == 0); - - // This should be copied in the previous function. - // CLEANUP: Maybe don't copy it and just use this one since it is allocated on the heap? - bh_arr_free(slns); - - if (!concrete) return NULL; - if (concrete == (Type *) &node_that_signals_failure) return concrete; - concrete->Struct.constructed_from = (AstType *) ps_type; - return concrete; - } - - case Ast_Kind_Type_Compound: { - AstCompoundType* ctype = (AstCompoundType *) type_node; - - i64 type_count = bh_arr_length(ctype->types); - - Type* comp_type = type_create(Type_Kind_Compound, alloc, type_count); - comp_type->Compound.size = 0; - comp_type->Compound.count = type_count; - - fori (i, 0, type_count) { - assert(ctype->types[i] != NULL); - comp_type->Compound.types[i] = type_build_from_ast(alloc, ctype->types[i]); - - // LEAK LEAK LEAK - if (comp_type->Compound.types[i] == NULL) return NULL; - - comp_type->Compound.size += bh_max(type_size_of(comp_type->Compound.types[i]), 4); - } - - bh_align(comp_type->Compound.size, 4); - - comp_type->Compound.linear_members = NULL; - bh_arr_new(global_heap_allocator, comp_type->Compound.linear_members, comp_type->Compound.count); - build_linear_types_with_offset(comp_type, &comp_type->Compound.linear_members, 0); - - type_register(comp_type); - return comp_type; - } - - case Ast_Kind_Alias: { - AstAlias* alias = (AstAlias *) type_node; - return type_build_from_ast(alloc, (AstType *) alias->alias); - } - - case Ast_Kind_Typeof: { - AstTypeOf* type_of = (AstTypeOf *) type_node; - if (type_of->resolved_type != NULL) { - return type_of->resolved_type; - } - - return NULL; - } - - case Ast_Kind_Distinct_Type: { - AstDistinctType* distinct = (AstDistinctType *) type_node; - if (distinct->dtcache) return distinct->dtcache; - - Type *base_type = type_build_from_ast(alloc, distinct->base_type); - if (base_type == NULL) return NULL; - if (base_type->kind != Type_Kind_Basic) { - onyx_report_error(distinct->token->pos, Error_Critical, "Distinct types can only be made out of primitive types. '%s' is not a primitive type.", type_get_name(base_type)); - return NULL; - } - - Type *distinct_type = type_create(Type_Kind_Distinct, alloc, 0); - distinct_type->Distinct.base_type = base_type; - distinct_type->Distinct.name = distinct->name; - distinct->dtcache = distinct_type; - - type_register(distinct_type); - return distinct_type; - } - } - - return NULL; -} - -// CLEANUP: This needs to be merged with the very similar code from up above. -Type* type_build_function_type(bh_allocator alloc, AstFunction* func) { - u64 param_count = bh_arr_length(func->params); - - Type* return_type = type_build_from_ast(alloc, func->return_type); - if (return_type == NULL) return NULL; - - Type* func_type = type_create(Type_Kind_Function, alloc, param_count); - func_type->Function.param_count = param_count; - func_type->Function.needed_param_count = 0; - func_type->Function.vararg_arg_pos = -1; - func_type->Function.return_type = return_type; - - if (param_count > 0) { - i32 i = 0; - bh_arr_each(AstParam, param, func->params) { - if (param->default_value == NULL && param->vararg_kind == VA_Kind_Not_VA) - func_type->Function.needed_param_count++; - - if (param->vararg_kind == VA_Kind_Untyped) - func_type->Function.vararg_arg_pos = i; - - func_type->Function.params[i++] = param->local->type; - } - } - - // CopyPaste from above in type_build_from_ast - char* name = (char *) type_get_unique_name(func_type); - if (func_type->Function.return_type != &type_auto_return) { - i32 index = shgeti(type_func_map, name); - if (index != -1) { - u64 id = type_func_map[index].value; - Type* existing_type = (Type *) bh_imap_get(&type_map, id); - - // LEAK LEAK LEAK the func_type that is created - return existing_type; - } - } - - type_register(func_type); - shput(type_func_map, name, func_type->id); - - return func_type; -} - -Type* type_build_compound_type(bh_allocator alloc, AstCompound* compound) { - i64 expr_count = bh_arr_length(compound->exprs); - fori (i, 0, expr_count) { - if (compound->exprs[i]->type == NULL) return NULL; - if (compound->exprs[i]->type->kind == Type_Kind_Basic) { - if (compound->exprs[i]->type->Basic.kind == Basic_Kind_Int_Unsized || compound->exprs[i]->type->Basic.kind == Basic_Kind_Float_Unsized) { - return NULL; - } - } - } - - Type* comp_type = type_create(Type_Kind_Compound, alloc, expr_count); - comp_type->Compound.size = 0; - comp_type->Compound.count = expr_count; - - fori (i, 0, expr_count) { - assert(compound->exprs[i]->type != NULL); - comp_type->Compound.types[i] = compound->exprs[i]->type; - comp_type->Compound.size += bh_max(type_size_of(comp_type->Compound.types[i]), 4); - } - - bh_align(comp_type->Compound.size, 4); - - comp_type->Compound.linear_members = NULL; - bh_arr_new(global_heap_allocator, comp_type->Compound.linear_members, comp_type->Compound.count); - build_linear_types_with_offset(comp_type, &comp_type->Compound.linear_members, 0); - - type_register(comp_type); - return comp_type; -} - -Type* type_build_implicit_type_of_struct_literal(bh_allocator alloc, AstStructLiteral* lit) { - Type* type = type_create(Type_Kind_Struct, alloc, 0); - type->ast_type = NULL; - type->Struct.name = NULL; - type->Struct.mem_count = bh_arr_length(lit->args.named_values); - type->Struct.meta_tags = NULL; - type->Struct.constructed_from = NULL; - type->Struct.status = SPS_Start; - type->Struct.poly_sln = NULL; - type_register(type); - - type->Struct.memarr = NULL; - sh_new_arena(type->Struct.members); - bh_arr_new(global_heap_allocator, type->Struct.memarr, type->Struct.mem_count); - - u32 size = 0; - u32 offset = 0; - u32 alignment = 1; - u32 idx = 0; - bh_arr_each(AstNamedValue *, pnv, lit->args.named_values) { - AstNamedValue *nv = *pnv; - - Type* member_type = resolve_expression_type(nv->value); - if (member_type == NULL) { - return NULL; - } - - u32 mem_alignment = type_alignment_of(member_type); - if (mem_alignment <= 0) { - return NULL; - } - - alignment = bh_max(alignment, mem_alignment); - - // Should these structs be packed or not? - bh_align(offset, mem_alignment); - - token_toggle_end(nv->token); - if (shgeti(type->Struct.members, nv->token->text) != -1) { - token_toggle_end(nv->token); - return NULL; - } - - StructMember *smem = bh_alloc_item(alloc, StructMember); - smem->offset = offset; - smem->type = member_type; - smem->idx = idx; - smem->name = bh_strdup(alloc, nv->token->text); - smem->token = nv->token; - smem->initial_value = &nv->value; - smem->meta_tags = NULL; - smem->included_through_use = 0; - smem->used = 0; - smem->use_through_pointer_index = -1; - shput(type->Struct.members, nv->token->text, smem); - bh_arr_push(type->Struct.memarr, smem); - token_toggle_end(nv->token); - - u32 type_size = type_size_of(member_type); - offset += type_size; - size = offset; - idx++; - } - - type->Struct.alignment = alignment; - type->Struct.size = size; - type->Struct.linear_members = NULL; - bh_arr_new(global_heap_allocator, type->Struct.linear_members, type->Struct.mem_count); - build_linear_types_with_offset(type, &type->Struct.linear_members, 0); - - type->Struct.status = SPS_Uses_Done; - return type; -} - -Type* type_make_pointer(bh_allocator alloc, Type* to) { - if (to == NULL) return NULL; - if (to == (Type *) &node_that_signals_failure) return to; - - assert(to->id > 0); - u64 ptr_id = bh_imap_get(&type_pointer_map, to->id); - if (ptr_id > 0) { - Type* ptr_type = (Type *) bh_imap_get(&type_map, ptr_id); - return ptr_type; - - } else { - Type* ptr_type = type_create(Type_Kind_Pointer, alloc, 0); - ptr_type->Pointer.base.flags |= Basic_Flag_Pointer; - ptr_type->Pointer.base.size = POINTER_SIZE; - ptr_type->Pointer.elem = to; - - type_register(ptr_type); - bh_imap_put(&type_pointer_map, to->id, ptr_type->id); - - return ptr_type; - } -} - -Type* type_make_array(bh_allocator alloc, Type* to, u32 count) { - if (to == NULL) return NULL; - if (to == (Type *) &node_that_signals_failure) return to; - - assert(to->id > 0); - u64 key = ((((u64) to->id) << 32) | (u64) count); - u64 array_id = bh_imap_get(&type_array_map, key); - if (array_id > 0) { - Type* array_type = (Type *) bh_imap_get(&type_map, array_id); - return array_type; - - } else { - Type* arr_type = type_create(Type_Kind_Array, alloc, 0); - arr_type->Array.count = count; - arr_type->Array.elem = to; - arr_type->Array.size = count * type_size_of(to); - - type_register(arr_type); - bh_imap_put(&type_array_map, key, arr_type->id); - - return arr_type; - } -} - -Type* type_make_slice(bh_allocator alloc, Type* of) { - if (of == NULL) return NULL; - if (of == (Type *) &node_that_signals_failure) return of; - - assert(of->id > 0); - u64 slice_id = bh_imap_get(&type_slice_map, of->id); - if (slice_id > 0) { - Type* slice_type = (Type *) bh_imap_get(&type_map, slice_id); - return slice_type; - - } else { - Type* slice_type = type_create(Type_Kind_Slice, alloc, 0); - type_register(slice_type); - bh_imap_put(&type_slice_map, of->id, slice_type->id); - - type_make_pointer(alloc, of); - slice_type->Slice.elem = of; - - return slice_type; - } -} - -Type* type_make_dynarray(bh_allocator alloc, Type* of) { - if (of == NULL) return NULL; - if (of == (Type *) &node_that_signals_failure) return of; - - assert(of->id > 0); - u64 dynarr_id = bh_imap_get(&type_dynarr_map, of->id); - if (dynarr_id > 0) { - Type* dynarr = (Type *) bh_imap_get(&type_map, dynarr_id); - return dynarr; - - } else { - Type* dynarr = type_create(Type_Kind_DynArray, alloc, 0); - type_register(dynarr); - bh_imap_put(&type_dynarr_map, of->id, dynarr->id); - - type_make_pointer(alloc, of); - dynarr->DynArray.elem = of; - - return dynarr; - } -} - -Type* type_make_varargs(bh_allocator alloc, Type* of) { - if (of == NULL) return NULL; - if (of == (Type *) &node_that_signals_failure) return of; - - assert(of->id > 0); - u64 vararg_id = bh_imap_get(&type_vararg_map, of->id); - if (vararg_id > 0) { - Type* va_type = (Type *) bh_imap_get(&type_map, vararg_id); - return va_type; - - } else { - Type* va_type = type_create(Type_Kind_VarArgs, alloc, 0); - type_register(va_type); - bh_imap_put(&type_vararg_map, of->id, va_type->id); - - type_make_pointer(alloc, of); - va_type->VarArgs.elem = of; - - return va_type; - } -} - -void build_linear_types_with_offset(Type* type, bh_arr(TypeWithOffset)* pdest, u32 offset) { - if (type_is_structlike_strict(type)) { - u32 mem_count = type_structlike_mem_count(type); - StructMember smem = { 0 }; - fori (i, 0, mem_count) { - type_lookup_member_by_idx(type, i, &smem); - build_linear_types_with_offset(smem.type, pdest, offset + smem.offset); - } - - } else if (type->kind == Type_Kind_Compound) { - u32 elem_offset = 0; - fori (i, 0, type->Compound.count) { - build_linear_types_with_offset(type->Compound.types[i], pdest, offset + elem_offset); - elem_offset += bh_max(type_size_of(type->Compound.types[i]), 4); - } - - } else { - bh_arr(TypeWithOffset) dest = *pdest; - - TypeWithOffset two; - two.type = type; - two.offset = offset; - bh_arr_push(dest, two); - - *pdest = dest; - } -} - -b32 type_struct_member_apply_use(bh_allocator alloc, Type *s_type, StructMember *smem) { - Type* used_type = smem->type; - - b32 type_is_pointer = 0; - if (used_type->kind == Type_Kind_Pointer) { - type_is_pointer = 1; - used_type = type_get_contained_type(used_type); - } - - if (used_type->kind != Type_Kind_Struct) { - onyx_report_error(smem->token->pos, Error_Critical, "Can only use things of structure, or pointer to structure type."); - return 0; - } - - if (used_type->Struct.status == SPS_Start) return 0; - - bh_arr_each(StructMember*, psmem, used_type->Struct.memarr) { - if (shgeti(s_type->Struct.members, (*psmem)->name) != -1) { - onyx_report_error(smem->token->pos, Error_Critical, "Used name '%s' conflicts with existing struct member.", (*psmem)->name); - return 0; - } - - StructMember* new_smem = bh_alloc_item(alloc, StructMember); - new_smem->type = (*psmem)->type; - new_smem->name = (*psmem)->name; - new_smem->meta_tags = (*psmem)->meta_tags; - new_smem->used = 0; - new_smem->included_through_use = 1; - - if (type_is_pointer) { - new_smem->offset = (*psmem)->offset; - new_smem->idx = (*psmem)->idx; - new_smem->initial_value = NULL; - new_smem->use_through_pointer_index = smem->idx; - } else { - new_smem->offset = smem->offset + (*psmem)->offset; - new_smem->idx = -1; // Dummy value because I don't think this is needed. - new_smem->initial_value = (*psmem)->initial_value; - new_smem->use_through_pointer_index = -1; - } - - shput(s_type->Struct.members, (*psmem)->name, new_smem); - } - - return 1; -} - -const char* type_get_unique_name(Type* type) { - if (type == NULL) return "unknown"; - - switch (type->kind) { - case Type_Kind_Basic: return type->Basic.name; - case Type_Kind_Pointer: return bh_aprintf(global_scratch_allocator, "^%s", type_get_unique_name(type->Pointer.elem)); - case Type_Kind_Array: return bh_aprintf(global_scratch_allocator, "[%d] %s", type->Array.count, type_get_unique_name(type->Array.elem)); - case Type_Kind_Struct: - if (type->Struct.name) - return bh_aprintf(global_scratch_allocator, "%s@%l", type->Struct.name, type->id); - else - return bh_aprintf(global_scratch_allocator, "%s@%l", "", type->id); - case Type_Kind_Enum: - if (type->Enum.name) - return bh_aprintf(global_scratch_allocator, "%s@%l", type->Enum.name, type->id); - else - return bh_aprintf(global_scratch_allocator, "%s@%l", "", type->id); - - case Type_Kind_Slice: return bh_aprintf(global_scratch_allocator, "[] %s", type_get_unique_name(type->Slice.elem)); - case Type_Kind_VarArgs: return bh_aprintf(global_scratch_allocator, "..%s", type_get_unique_name(type->VarArgs.elem)); - case Type_Kind_DynArray: return bh_aprintf(global_scratch_allocator, "[..] %s", type_get_unique_name(type->DynArray.elem)); - - case Type_Kind_Function: { - char buf[1024]; - memset(buf, 0, 1024); - - strncat(buf, "(", 1023); - fori (i, 0, type->Function.param_count) { - strncat(buf, type_get_unique_name(type->Function.params[i]), 1023); - - if (i >= type->Function.needed_param_count) - strncat(buf, "?", 1023); - - if (i != type->Function.param_count - 1) - strncat(buf, ", ", 1023); - } - - strncat(buf, ") -> ", 1023); - strncat(buf, type_get_unique_name(type->Function.return_type), 1023); - - return bh_aprintf(global_scratch_allocator, "%s", buf); - } - - case Type_Kind_Compound: { - char buf[1024]; - memset(buf, 0, 1024); - - strncat(buf, "(", 1023); - fori (i, 0, type->Compound.count) { - strncat(buf, type_get_unique_name(type->Compound.types[i]), 1023); - if (i != type->Compound.count - 1) - strncat(buf, ", ", 1023); - } - strncat(buf, ")", 1023); - - return bh_aprintf(global_scratch_allocator, "%s", buf); - } - - case Type_Kind_Distinct: { - return bh_aprintf(global_scratch_allocator, "%s@%l", type->Distinct.name, type->id); - } - - default: return "unknown (not null)"; - } -} - -const char* type_get_name(Type* type) { - if (type == NULL) return "unknown"; - - switch (type->kind) { - case Type_Kind_Basic: return type->Basic.name; - case Type_Kind_Pointer: return bh_aprintf(global_scratch_allocator, "^%s", type_get_name(type->Pointer.elem)); - case Type_Kind_Array: return bh_aprintf(global_scratch_allocator, "[%d] %s", type->Array.count, type_get_name(type->Array.elem)); - case Type_Kind_Struct: - if (type->Struct.name) - return type->Struct.name; - else - return ""; - case Type_Kind_Enum: - if (type->Enum.name) - return type->Enum.name; - else - return ""; - - case Type_Kind_Slice: return bh_aprintf(global_scratch_allocator, "[] %s", type_get_name(type->Slice.elem)); - case Type_Kind_VarArgs: return bh_aprintf(global_scratch_allocator, "..%s", type_get_name(type->VarArgs.elem)); - case Type_Kind_DynArray: return bh_aprintf(global_scratch_allocator, "[..] %s", type_get_name(type->DynArray.elem)); - - case Type_Kind_Function: { - char buf[512]; - fori (i, 0, 512) buf[i] = 0; - - strncat(buf, "(", 511); - fori (i, 0, type->Function.param_count) { - strncat(buf, type_get_name(type->Function.params[i]), 511); - if (i != type->Function.param_count - 1) - strncat(buf, ", ", 511); - } - - strncat(buf, ") -> ", 511); - strncat(buf, type_get_name(type->Function.return_type), 511); - - return bh_aprintf(global_scratch_allocator, "%s", buf); - } - - case Type_Kind_Compound: { - char buf[512]; - fori (i, 0, 512) buf[i] = 0; - - strncat(buf, "(", 511); - fori (i, 0, type->Compound.count) { - strncat(buf, type_get_name(type->Compound.types[i]), 511); - if (i != type->Compound.count - 1) - strncat(buf, ", ", 511); - } - strncat(buf, ")", 511); - - return bh_aprintf(global_scratch_allocator, "%s", buf); - } - - case Type_Kind_Distinct: { - return bh_aprintf(global_scratch_allocator, "%s", type->Distinct.name); - } - - default: return "unknown"; - } -} - -u32 type_get_alignment_log2(Type* type) { - i32 store_size = type_alignment_of(type); - if (store_size == 1) return 0; - else if (store_size == 2) return 1; - else if (store_size == 4) return 2; - else if (store_size == 8) return 3; - else if (store_size == 16) return 4; - return 2; -} - -Type* type_get_contained_type(Type* type) { - if (type == NULL) return NULL; - switch (type->kind) { - case Type_Kind_Pointer: return type->Pointer.elem; - case Type_Kind_Array: return type->Array.elem; - case Type_Kind_Slice: return type->Slice.elem; - case Type_Kind_DynArray: return type->DynArray.elem; - case Type_Kind_VarArgs: return type->VarArgs.elem; - default: return NULL; - } -} - -static const StructMember slice_members[] = { - { 0, 0, NULL, "data", NULL, NULL, -1, 0, 0 }, - { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "count", NULL, NULL, -1, 0, 0 }, - { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "size", NULL, NULL, -1, 0, 0 }, - { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "length", NULL, NULL, -1, 0, 0 }, -}; - -static const StructMember array_members[] = { - { 0, 0, NULL, "data", NULL, NULL, -1, 0, 0 }, - { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "count", NULL, NULL, -1, 0, 0 }, - { POINTER_SIZE + 4, 2, &basic_types[Basic_Kind_U32], "capacity", NULL, NULL, -1, 0, 0 }, - { POINTER_SIZE + 8, 3, NULL, "allocator", NULL, NULL, -1, 0, 0 }, - { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "size", NULL, NULL, -1, 0, 0 }, - { POINTER_SIZE, 1, &basic_types[Basic_Kind_U32], "length", NULL, NULL, -1, 0, 0 }, -}; - -b32 type_lookup_member(Type* type, char* member, StructMember* smem) { - if (type->kind == Type_Kind_Pointer) type = type->Pointer.elem; - - switch (type->kind) { - case Type_Kind_Struct: { - TypeStruct* stype = &type->Struct; - - i32 index = shgeti(stype->members, member); - if (index == -1) return 0; - *smem = *stype->members[index].value; - return 1; - } - - case Type_Kind_VarArgs: - case Type_Kind_Slice: { - fori (i, 0, (i64) (sizeof(slice_members) / sizeof(StructMember))) { - if (strcmp(slice_members[i].name, member) == 0) { - *smem = slice_members[i]; - if (smem->idx == 0) smem->type = type_make_pointer(context.ast_alloc, type->Slice.elem); - - return 1; - } - } - return 0; - } - - case Type_Kind_DynArray: { - fori (i, 0, (i64) (sizeof(array_members) / sizeof(StructMember))) { - if (strcmp(array_members[i].name, member) == 0) { - *smem = array_members[i]; - if (smem->idx == 0) smem->type = type_make_pointer(context.ast_alloc, type->DynArray.elem); - if (smem->idx == 3) smem->type = type_build_from_ast(context.ast_alloc, builtin_allocator_type); - - return 1; - } - } - return 0; - } - - default: return 0; - } -} - -b32 type_lookup_member_by_idx(Type* type, i32 idx, StructMember* smem) { - if (type->kind == Type_Kind_Pointer) type = type->Pointer.elem; - - switch (type->kind) { - case Type_Kind_Struct: { - TypeStruct* stype = &type->Struct; - - if (idx > stype->mem_count) return 0; - *smem = *stype->memarr[idx]; - return 1; - } - - // HACK: This relies on the fact that the structures for Slice and VarArgs - // are identical. - brendanfh 2020/09/07 - case Type_Kind_VarArgs: - case Type_Kind_Slice: { - if (idx > 2) return 0; - - *smem = slice_members[idx]; - if (smem->idx == 0) smem->type = type_make_pointer(context.ast_alloc, type->Slice.elem); - - return 1; - } - - case Type_Kind_DynArray: { - if (idx > 4) return 0; - - *smem = array_members[idx]; - if (idx == 0) smem->type = type_make_pointer(context.ast_alloc, type->DynArray.elem); - if (idx == 3) smem->type = type_build_from_ast(context.ast_alloc, builtin_allocator_type); - - return 1; - } - - default: return 0; - } -} - -i32 type_linear_member_count(Type* type) { - switch (type->kind) { - case Type_Kind_Slice: - case Type_Kind_VarArgs: return 2; - case Type_Kind_DynArray: return 5; - case Type_Kind_Compound: return bh_arr_length(type->Compound.linear_members); - case Type_Kind_Struct: return bh_arr_length(type->Struct.linear_members); - default: return 1; - } -} - -b32 type_linear_member_lookup(Type* type, i32 idx, TypeWithOffset* two) { - switch (type->kind) { - case Type_Kind_Slice: - case Type_Kind_VarArgs: { - if (idx == 0) { - two->type = type_make_pointer(context.ast_alloc, type->Slice.elem); - two->offset = 0; - } - if (idx == 1) { - two->type = &basic_types[Basic_Kind_U32]; - two->offset = POINTER_SIZE; - } - - return 1; - } - case Type_Kind_DynArray: { - if (idx == 0) { - two->type = type_make_pointer(context.ast_alloc, type->DynArray.elem); - two->offset = 0; - } - if (idx == 1) { - two->type = &basic_types[Basic_Kind_U32]; - two->offset = POINTER_SIZE; - } - if (idx == 2) { - two->type = &basic_types[Basic_Kind_U32]; - two->offset = POINTER_SIZE + 4; - } - if (idx == 3 || idx == 4) { - Type* allocator_type = type_build_from_ast(context.ast_alloc, builtin_allocator_type); - type_linear_member_lookup(allocator_type, idx - 3, two); - two->offset += POINTER_SIZE + 8; - } - - return 1; - } - case Type_Kind_Compound: *two = type->Compound.linear_members[idx]; return 1; - case Type_Kind_Struct: *two = type->Struct.linear_members[idx]; return 1; - - case Type_Kind_Distinct: - two->type = type->Distinct.base_type; - two->offset = 0; - return 1; - - default: { - if (idx > 0) return 0; - two->offset = 0; - two->type = type; - return 1; - } - } -} - -i32 type_get_idx_of_linear_member_with_offset(Type* type, u32 offset) { - switch (type->kind) { - case Type_Kind_Slice: - case Type_Kind_VarArgs: { - if (offset == 0) return 0; - if (offset == POINTER_SIZE) return 1; - return -1; - } - case Type_Kind_DynArray: { - if (offset == 0) return 0; - if (offset == POINTER_SIZE) return 1; - if (offset == POINTER_SIZE + 4) return 2; - if (offset == POINTER_SIZE + 8) return 3; - if (offset == POINTER_SIZE * 2 + 8) return 4; - return -1; - } - case Type_Kind_Compound: { - i32 idx = 0; - bh_arr_each(TypeWithOffset, two, type->Compound.linear_members) { - if (two->offset == offset) return idx; - idx++; - } - - return -1; - } - case Type_Kind_Struct: { - i32 idx = 0; - bh_arr_each(TypeWithOffset, two, type->Struct.linear_members) { - if (two->offset == offset) return idx; - idx++; - } - - return -1; - } - default: return -1; - } -} - -b32 type_struct_is_simple(Type* type) { - if (type->kind != Type_Kind_Struct) return 0; - - b32 is_simple = 1; - bh_arr_each(StructMember *, mem, type->Struct.memarr) { - if (type_is_compound((*mem)->type) || (*mem)->type->kind == Type_Kind_Array) { - is_simple = 0; - break; - } - } - - return is_simple; -} - -b32 type_is_pointer(Type* type) { - if (type == NULL) return 0; - return type->kind == Type_Kind_Pointer; -} - -b32 type_is_rawptr(Type* type) { - if (type == NULL) return 0; - return type->kind == Type_Kind_Basic && type->Basic.kind == Basic_Kind_Rawptr; -} - -b32 type_is_array(Type* type) { - if (type == NULL) return 0; - return type->kind == Type_Kind_Array; -} - -b32 type_is_struct(Type* type) { - if (type == NULL) return 0; - if (type->kind == Type_Kind_Struct) return 1; - if (type->kind == Type_Kind_Pointer) - if (type->Pointer.elem != NULL && type->Pointer.elem->kind == Type_Kind_Struct) return 1; - return 0; -} - -b32 type_is_bool(Type* type) { - if (type == NULL) return 0; - return type != NULL && type->kind == Type_Kind_Basic && type->Basic.kind == Basic_Kind_Bool; -} - -b32 type_is_small_integer(Type* type) { - if (type == NULL) return 0; - if (type->kind == Type_Kind_Enum) return type_is_small_integer(type->Enum.backing); - if (type->kind == Type_Kind_Distinct) return type_is_small_integer(type->Distinct.base_type); - if (type->kind != Type_Kind_Basic) return 0; - - return type->Basic.kind >= Basic_Kind_I8 && type->Basic.kind <= Basic_Kind_U32; -} - -b32 type_is_integer(Type* type) { - if (type == NULL) return 0; - if (type->kind == Type_Kind_Enum) return type_is_integer(type->Enum.backing); - if (type->kind == Type_Kind_Distinct) return type_is_integer(type->Distinct.base_type); - if (type->kind != Type_Kind_Basic) return 0; - - return (type->Basic.kind >= Basic_Kind_I8 && type->Basic.kind <= Basic_Kind_U64) - || type->Basic.kind == Basic_Kind_Type_Index; -} - -b32 type_is_numeric(Type* type) { - if (type == NULL) return 0; - if (type->kind == Type_Kind_Enum) return 1; - if (type->kind == Type_Kind_Distinct) return type_is_numeric(type->Distinct.base_type); - if (type->kind != Type_Kind_Basic) return 0; - - return type->Basic.kind >= Basic_Kind_Int_Unsized && type->Basic.kind <= Basic_Kind_F64; -} - -b32 type_is_compound(Type* type) { - if (type == NULL) return 0; - - if (type->kind == Type_Kind_Struct) { - // - // This is for the kind of common case where a structure simply wraps a - // single non-compound value; in this situation, the structure can be - // "dissolved" at compile-time and turn into the underlying type. - // - - if (bh_arr_length(type->Struct.linear_members) != 1) return 1; - return type_is_compound(type->Struct.linear_members[0].type); - } - - return type->kind != Type_Kind_Basic - && type->kind != Type_Kind_Pointer - && type->kind != Type_Kind_Enum - && type->kind != Type_Kind_Function - && type->kind != Type_Kind_Array - && type->kind != Type_Kind_Distinct; -} - -b32 type_is_simd(Type* type) { - if (type == NULL) return 0; - if (type->kind != Type_Kind_Basic) return 0; - return type->Basic.flags & Basic_Flag_SIMD; -} - -b32 type_results_in_void(Type* type) { - return (type == NULL) - || (type->kind == Type_Kind_Basic && type->Basic.kind == Basic_Kind_Void) - || ( (type->kind == Type_Kind_Function) - && (type->Function.return_type->kind == Type_Kind_Basic) - && (type->Function.return_type->Basic.kind == Basic_Kind_Void)); -} - -b32 type_is_array_accessible(Type* type) { - if (type == NULL) return 0; - if (type_is_pointer(type)) return 1; - if (type->kind == Type_Kind_Array) return 1; - if (type->kind == Type_Kind_Slice) return 1; - if (type->kind == Type_Kind_DynArray) return 1; - if (type->kind == Type_Kind_VarArgs) return 1; - return 0; -} - -b32 type_is_structlike(Type* type) { - if (type == NULL) return 0; - if (type->kind == Type_Kind_Array) return 1; - if (type->kind == Type_Kind_Struct) return 1; - if (type->kind == Type_Kind_Slice) return 1; - if (type->kind == Type_Kind_DynArray) return 1; - if (type->kind == Type_Kind_VarArgs) return 1; - if (type->kind == Type_Kind_Pointer) { - if (type->Pointer.elem->kind == Type_Kind_Struct) return 1; - if (type->Pointer.elem->kind == Type_Kind_Slice) return 1; - if (type->Pointer.elem->kind == Type_Kind_DynArray) return 1; - } - return 0; -} - -b32 type_is_structlike_strict(Type* type) { - if (type == NULL) return 0; - if (type->kind == Type_Kind_Struct) return 1; - if (type->kind == Type_Kind_Slice) return 1; - if (type->kind == Type_Kind_DynArray) return 1; - if (type->kind == Type_Kind_VarArgs) return 1; - return 0; -} - -u32 type_structlike_mem_count(Type* type) { - if (type == NULL) return 0; - switch (type->kind) { - case Type_Kind_Struct: return type->Struct.mem_count; - case Type_Kind_Slice: return 2; - case Type_Kind_VarArgs: return 2; - case Type_Kind_DynArray: return 4; - default: return 0; - } -} - -u32 type_structlike_is_simple(Type* type) { - if (type == NULL) return 0; - switch (type->kind) { - case Type_Kind_Struct: return type_struct_is_simple(type); - case Type_Kind_Slice: return 1; - case Type_Kind_VarArgs: return 1; - case Type_Kind_DynArray: return 0; - default: return 0; - } -} - -b32 type_is_sl_constructable(Type* type) { - if (type == NULL) return 0; - switch (type->kind) { - case Type_Kind_Struct: return 1; - case Type_Kind_Slice: return 1; - case Type_Kind_DynArray: return 1; - default: return 0; - } -} - -b32 type_struct_constructed_from_poly_struct(Type* struct_type, struct AstType* from) { - if (struct_type == NULL) return 0; - if (struct_type->kind != Type_Kind_Struct) return 0; - - return struct_type->Struct.constructed_from == from; -} diff --git a/src/utils.c b/src/utils.c deleted file mode 100644 index 7f4ea49b..00000000 --- a/src/utils.c +++ /dev/null @@ -1,1206 +0,0 @@ -#define BH_DEBUG - -#include "utils.h" -#include "lex.h" -#include "astnodes.h" -#include "errors.h" -#include "parser.h" -#include "astnodes.h" -#include "errors.h" - -bh_scratch global_scratch; -bh_allocator global_scratch_allocator; - -bh_managed_heap global_heap; -bh_allocator global_heap_allocator; - -// -// Program info and packages -// -Package* package_lookup(char* package_name) { - i32 index = shgeti(context.packages, package_name); - if (index != -1) { - return context.packages[index].value; - } else { - return NULL; - } -} - -Package* package_lookup_or_create(char* package_name, Scope* parent_scope, bh_allocator alloc, OnyxFilePos pos) { - i32 index = shgeti(context.packages, package_name); - if (index != -1) { - return context.packages[index].value; - - } else { - Package* package = bh_alloc_item(alloc, Package); - - char* pac_name = bh_alloc_array(alloc, char, strlen(package_name) + 1); - memcpy(pac_name, package_name, strlen(package_name) + 1); - pac_name[strlen(package_name)] = '\0'; - - package->name = pac_name; - package->use_package_entities = NULL; - - if (!strcmp(pac_name, "builtin")) { - package->private_scope = scope_create(alloc, context.global_scope, pos); - package->scope = context.global_scope; - } else { - package->scope = scope_create(alloc, parent_scope, pos); - package->private_scope = scope_create(alloc, package->scope, pos); - } - - shput(context.packages, pac_name, package); - - if (!charset_contains(pac_name, '.')) { - AstPackage* package_node = onyx_ast_node_new(alloc, sizeof(AstPackage), Ast_Kind_Package); - package_node->package_name = package->name; - package_node->package = package; - - symbol_raw_introduce(context.global_scope, pac_name, pos, (AstNode *) package_node); - } - - return package; - } -} - -void package_track_use_package(Package* package, Entity* entity) { - assert(entity); - - if (package->use_package_entities == NULL) { - bh_arr_new(global_heap_allocator, package->use_package_entities, 4); - } - - bh_arr_push(package->use_package_entities, entity); -} - -void package_reinsert_use_packages(Package* package) { - if (!package) return; - if (!package->use_package_entities) return; - - bh_arr_each(Entity *, use_package, package->use_package_entities) { - (*use_package)->state = Entity_State_Resolve_Symbols; - (*use_package)->macro_attempts = 0; - entity_heap_insert_existing(&context.entities, *use_package); - } - - bh_arr_set_length(package->use_package_entities, 0); -} - - -// -// Scoping -// -static u64 next_scope_id = 1; - -Scope* scope_create(bh_allocator a, Scope* parent, OnyxFilePos created_at) { - Scope* scope = bh_alloc_item(a, Scope); - scope->id = next_scope_id++; - scope->parent = parent; - scope->created_at = created_at; - scope->name = NULL; - - scope->symbols = NULL; - sh_new_arena(scope->symbols); - - return scope; -} - -void scope_include(Scope* target, Scope* source, OnyxFilePos pos) { - fori (i, 0, shlen(source->symbols)) { - symbol_raw_introduce(target, source->symbols[i].key, pos, source->symbols[i].value); - } -} - -b32 symbol_introduce(Scope* scope, OnyxToken* tkn, AstNode* symbol) { - token_toggle_end(tkn); - - b32 ret = symbol_raw_introduce(scope, tkn->text, tkn->pos, symbol); - - token_toggle_end(tkn); - return ret; -} - -b32 symbol_raw_introduce(Scope* scope, char* name, OnyxFilePos pos, AstNode* symbol) { - if (strcmp(name, "_")) { - i32 index = shgeti(scope->symbols, name); - if (index != -1) { - AstNode *node = scope->symbols[index].value; - if (node != symbol) { - onyx_report_error(pos, Error_Critical, "Redeclaration of symbol '%s'.", name); - - if (node->token) { - onyx_report_error(node->token->pos, Error_Critical, "Previous declaration was here."); - } - - return 0; - } - return 1; - } - } - - shput(scope->symbols, name, symbol); - return 1; -} - -void symbol_builtin_introduce(Scope* scope, char* sym, AstNode *node) { - shput(scope->symbols, sym, node); -} - -void symbol_subpackage_introduce(Scope* scope, char* sym, AstPackage* package) { - i32 index = shgeti(scope->symbols, sym); - if (index != -1) { - AstNode* maybe_package = scope->symbols[index].value; - - // CLEANUP: Make this assertion an actual error message. - assert(maybe_package->kind == Ast_Kind_Package); - } else { - shput(scope->symbols, sym, (AstNode *) package); - } -} - -AstNode* symbol_raw_resolve(Scope* start_scope, char* sym) { - Scope* scope = start_scope; - - while (scope != NULL) { - i32 index = shgeti(scope->symbols, sym); - if (index != -1) { - AstNode* res = scope->symbols[index].value; - - if ((res->flags & Ast_Flag_Symbol_Invisible) == 0) { - return res; - } - } - - scope = scope->parent; - } - - return NULL; -} - -AstNode* symbol_resolve(Scope* start_scope, OnyxToken* tkn) { - token_toggle_end(tkn); - AstNode* res = symbol_raw_resolve(start_scope, tkn->text); - token_toggle_end(tkn); - - return res; -} - -AstNode* try_symbol_raw_resolve_from_node(AstNode* node, char* symbol) { - b32 used_pointer = 0; - - while (1) { - if (!node) return NULL; - - switch (node->kind) { - case Ast_Kind_Type_Raw_Alias: node = (AstNode *) ((AstTypeRawAlias *) node)->to->ast_type; break; - case Ast_Kind_Type_Alias: node = (AstNode *) ((AstTypeAlias *) node)->to; break; - case Ast_Kind_Alias: node = (AstNode *) ((AstAlias *) node)->alias; break; - case Ast_Kind_Pointer_Type: { - if (used_pointer) goto all_types_peeled_off; - used_pointer = 1; - - node = (AstNode *) ((AstPointerType *) node)->elem; - break; - } - - default: goto all_types_peeled_off; - } - } - -all_types_peeled_off: - if (!node) return NULL; - - switch (node->kind) { - case Ast_Kind_Package: { - AstPackage* package = (AstPackage *) node; - - // CLEANUP - if (package->package == NULL) { - package->package = package_lookup(package->package_name); - } - - if (package->package == NULL) { - return NULL; - } - - return symbol_raw_resolve(package->package->scope, symbol); - } - - case Ast_Kind_Foreign_Block: { - AstForeignBlock* fb = (AstForeignBlock *) node; - - if (fb->scope == NULL) - return NULL; - - return symbol_raw_resolve(fb->scope, symbol); - } - - case Ast_Kind_Enum_Type: { - AstEnumType* etype = (AstEnumType *) node; - return symbol_raw_resolve(etype->scope, symbol); - } - - case Ast_Kind_Struct_Type: { - AstStructType* stype = (AstStructType *) node; - - // HACK HACK - // Temporarily disable the parent scope so that you can't access things - // "above" the structures scope. This leads to unintended behavior, as when - // you are accessing a static element on a structure, you don't expect to - // bleed to the top level scope. - AstNode *result = NULL; - if (stype->scope) { - Scope *tmp_parent = stype->scope->parent; - stype->scope->parent = NULL; - result = symbol_raw_resolve(stype->scope, symbol); - stype->scope->parent = tmp_parent; - } - - if (result == NULL && stype->stcache != NULL) { - Type* struct_type = stype->stcache; - assert(struct_type->kind == Type_Kind_Struct); - - bh_arr_each(AstPolySolution, sln, struct_type->Struct.poly_sln) { - if (token_text_equals(sln->poly_sym->token, symbol)) { - if (sln->kind == PSK_Type) { - result = (AstNode *) sln->type->ast_type; - } else { - result = (AstNode *) sln->value; - } - } - } - } - - return result; - } - - case Ast_Kind_Poly_Struct_Type: { - AstStructType* stype = ((AstPolyStructType *) node)->base_struct; - return symbol_raw_resolve(stype->scope, symbol); - } - - case Ast_Kind_Poly_Call_Type: { - AstNode* callee = (AstNode *) ((AstPolyCallType *) node)->callee; - return try_symbol_raw_resolve_from_node(callee, symbol); - } - } - - return NULL; -} - -AstNode* try_symbol_resolve_from_node(AstNode* node, OnyxToken* token) { - token_toggle_end(token); - AstNode* result = try_symbol_raw_resolve_from_node(node, token->text); - token_toggle_end(token); - - return result; -} - -AstNode* try_symbol_raw_resolve_from_type(Type *type, char* symbol) { - while (type->kind == Type_Kind_Pointer) { - type = type->Pointer.elem; - } - - if (type->kind == Type_Kind_Struct) { - if (type->Struct.poly_sln == NULL) return NULL; - - bh_arr_each(AstPolySolution, sln, type->Struct.poly_sln) { - if (token_text_equals(sln->poly_sym->token, symbol)) { - if (sln->kind == PSK_Type) { - AstTypeRawAlias* alias = onyx_ast_node_new(context.ast_alloc, sizeof(AstTypeRawAlias), Ast_Kind_Type_Raw_Alias); - alias->type = &basic_types[Basic_Kind_Type_Index]; - alias->to = sln->type; - return (AstNode *) alias; - - } else { - return (AstNode *) sln->value; - } - } - } - } - - return NULL; -} - -void scope_clear(Scope* scope) { - sh_new_arena(scope->symbols); -} - -// Polymorphic procedures are in their own file to clean up this file. -#include "polymorph.h" - -// -// Overloaded Procedures -// - -// -// @Cleanup: Everything having to do with overload resolving! -// Things that need to be available: -// * A copy of the arguments list that can be mutated -// - The named_values do not need to be copied, because they are not modified when fill_in_arguments happens. -// - Only values needs to have a copy available -// - This copy needs to be reset after checking every option -// -// Steps needed to check if an overload option is "the one": -// 1. Figure out what the overload is -// a. If polymorphic, generate the header for the procedure only -// 2. Place the arguments in the copy, according to the overload option -// 3. Ensure the option has a type filled out -// 4. For each argument -// a. Ensure it has a place to go (not too many arguments) -// b. Ensure it has a type -// c. Ensure the types match (currently there could be a problem if an option is attempted and doesn't work all the way that polymorphic procedures as arguments could still be solidified) -// -// Additional features that this needs to account for: -// * Resolving an overload from a list of parameter types -// * Resolving an overload from a TypeFunction (so an overloaded procedure can be passed as a parameter) -// - -void add_overload_option(bh_arr(OverloadOption)* poverloads, u64 precedence, AstTyped* overload) { - bh_arr(OverloadOption) overloads = *poverloads; - - i32 index = -1; - fori (i, 0, bh_arr_length(overloads)) { - if (overloads[i].precedence > precedence) { - index = i; - break; - } - } - - if (index < 0) { - bh_arr_push(overloads, ((OverloadOption) { - .precedence = precedence, - .option = overload, - })); - - } else { - bh_arr_insertn(overloads, index, 1); - overloads[index].precedence = precedence; - overloads[index].option = overload; - } - - *poverloads = overloads; -} - -// NOTE: The job of this function is to take a set of overloads, and traverse it to add all possible -// overloads that are reachable. This is slightly more difficult than it may seem. In this language, -// overloaded procedures have a strict ordering to their overloads, which determines how the correct -// match will be found. This was not very complicated until overloaded procedures could be used as -// overload options. This means that you could create an "infinite loop" of overloads like so: -// -// o1 :: { o2 :: { -// (...) { ... }, (...) { ... }, -// o2 o1 -// } } -// -// Obviously, this is not really an infinite loop. It just means that all options are available if -// o1 or o2 are called. The difference between o1 and o2 is the order that the overloads will be -// searched. To build the the list of overloads, a hashmap is used to prevent the problem from being -// O(n^2), even though n would (probably) be small. bh_imap has the useful property that it maintains -// an "entries" array that, so long as nothing is ever removed from it, will maintain the order in -// which entries were put into the map. This is useful because a simple recursive algorithm can -// collect all the overloads into the map, and also use the map to provide a base case. -void build_all_overload_options(bh_arr(OverloadOption) overloads, bh_imap* all_overloads) { - bh_arr_each(OverloadOption, overload, overloads) { - if (bh_imap_has(all_overloads, (u64) overload->option)) continue; - - bh_imap_put(all_overloads, (u64) overload->option, 1); - - if (overload->option->kind == Ast_Kind_Overloaded_Function) { - AstOverloadedFunction* sub_overload = (AstOverloadedFunction *) overload->option; - build_all_overload_options(sub_overload->overloads, all_overloads); - } - } -} - -AstTyped* find_matching_overload_by_arguments(bh_arr(OverloadOption) overloads, Arguments* param_args) { - Arguments args; - arguments_clone(&args, param_args); - arguments_ensure_length(&args, bh_arr_length(args.values) + bh_arr_length(args.named_values)); - - // CLEANUP SPEED: This currently rebuilds the complete set of overloads every time one is looked up. - // This should be cached in the AstOverloadedFunction or somewhere like that. - bh_imap all_overloads; - bh_imap_init(&all_overloads, global_heap_allocator, bh_arr_length(overloads) * 2); - build_all_overload_options(overloads, &all_overloads); - - AstTyped *matched_overload = NULL; - - bh_arr_each(bh__imap_entry, entry, all_overloads.entries) { - AstTyped* node = (AstTyped *) strip_aliases((AstNode *) entry->key); - arguments_copy(&args, param_args); - - AstFunction* overload = NULL; - switch (node->kind) { - case Ast_Kind_Macro: overload = macro_resolve_header((AstMacro *) node, param_args, NULL, 0); break; - case Ast_Kind_Polymorphic_Proc: overload = polymorphic_proc_build_only_header((AstFunction *) node, PPLM_By_Arguments, param_args); break; - case Ast_Kind_Function: - overload = (AstFunction *) node; - arguments_clear_baked_flags(&args); - break; - } - - // NOTE: Overload is not something that is known to be overloadable. - if (overload == NULL) continue; - if (overload == (AstFunction *) &node_that_signals_a_yield) return (AstTyped *) overload; - if (overload->kind != Ast_Kind_Function) continue; - if (overload->type == NULL) { - // If it was not possible to create the type for this procedure, tell the - // caller that this should yield and try again later. - - // return and not continue because if the overload that didn't have a type will - // work in the future, then it has to take precedence over the other options available. - return (AstTyped *) &node_that_signals_a_yield; - } - assert(overload->type->kind == Type_Kind_Function); - - arguments_remove_baked(&args); - arguments_ensure_length(&args, get_argument_buffer_size(&overload->type->Function, &args)); - - // NOTE: If the arguments cannot be placed successfully in the parameters list - if (!fill_in_arguments(&args, (AstNode *) overload, NULL, 0)) continue; - - VarArgKind va_kind; - TypeMatch tm = check_arguments_against_type(&args, &overload->type->Function, &va_kind, NULL, NULL, NULL); - if (tm == TYPE_MATCH_SUCCESS) { - matched_overload = node; - break; - } - - if (tm == TYPE_MATCH_YIELD) { - return (AstTyped *) &node_that_signals_a_yield; - } - } - - bh_imap_free(&all_overloads); - bh_arr_free(args.values); - return matched_overload; -} - -AstTyped* find_matching_overload_by_type(bh_arr(OverloadOption) overloads, Type* type) { - if (type->kind != Type_Kind_Function) return NULL; - - bh_imap all_overloads; - bh_imap_init(&all_overloads, global_heap_allocator, bh_arr_length(overloads) * 2); - build_all_overload_options(overloads, &all_overloads); - - AstTyped *matched_overload = NULL; - - bh_arr_each(bh__imap_entry, entry, all_overloads.entries) { - AstTyped* node = (AstTyped *) entry->key; - if (node->kind == Ast_Kind_Overloaded_Function) continue; - - TypeMatch tm = unify_node_and_type(&node, type); - if (tm == TYPE_MATCH_SUCCESS) { - matched_overload = node; - break; - } - - if (tm == TYPE_MATCH_YIELD) { - return (AstTyped *) &node_that_signals_a_yield; - } - } - - bh_imap_free(&all_overloads); - return matched_overload; -} - -void report_unable_to_match_overload(AstCall* call, bh_arr(OverloadOption) overloads) { - char* arg_str = bh_alloc(global_scratch_allocator, 1024); - arg_str[0] = '\0'; - - bh_arr_each(AstTyped *, arg, call->args.values) { - strncat(arg_str, node_get_type_name(*arg), 1023); - - if (arg != &bh_arr_last(call->args.values)) - strncat(arg_str, ", ", 1023); - } - - if (bh_arr_length(call->args.named_values) > 0) { - if (bh_arr_length(call->args.values) > 0) { - strncat(arg_str, ", ", 1023); - } - - bh_arr_each(AstNamedValue *, named_value, call->args.named_values) { - token_toggle_end((*named_value)->token); - strncat(arg_str, (*named_value)->token->text, 1023); - token_toggle_end((*named_value)->token); - - strncat(arg_str, "=", 1023); - strncat(arg_str, node_get_type_name((*named_value)->value), 1023); // CHECK: this might say 'unknown'. - - if (named_value != &bh_arr_last(call->args.named_values)) - strncat(arg_str, ", ", 1023); - } - } - - onyx_report_error(call->token->pos, Error_Critical, "Unable to match overloaded function with provided argument types: (%s)", arg_str); - - bh_free(global_scratch_allocator, arg_str); - - // CLEANUP SPEED: This currently rebuilds the complete set of overloads every time one is looked up. - // This should be cached in the AstOverloadedFunction or somewhere like that. - bh_imap all_overloads; - bh_imap_init(&all_overloads, global_heap_allocator, bh_arr_length(overloads) * 2); - build_all_overload_options(overloads, &all_overloads); - - i32 i = 1; - bh_arr_each(bh__imap_entry, entry, all_overloads.entries) { - AstTyped* node = (AstTyped *) strip_aliases((AstNode *) entry->key); - onyx_report_error(node->token->pos, Error_Critical, "Here is one of the overloads. %d/%d", i++, bh_arr_length(all_overloads.entries)); - } - - bh_imap_free(&all_overloads); -} - - -// -// Macros -// -// -// TODO: Write this documentation -void expand_macro(AstCall** pcall, AstFunction* template) { - AstCall* call = *pcall; - AstMacro* macro = (AstMacro *) call->callee; - assert(macro->kind == Ast_Kind_Macro); - - assert(template->kind == Ast_Kind_Function); - assert(template->type != NULL); - assert(template->type->kind == Type_Kind_Function); - - AstBlock* expansion = (AstBlock *) ast_clone(context.ast_alloc, template->body); - expansion->rules = Block_Rule_Macro; - expansion->scope = NULL; - expansion->next = call->next; - - AstNode* subst = (AstNode *) expansion; - - if (template->type->Function.return_type != &basic_types[Basic_Kind_Void]) { - AstDoBlock* doblock = (AstDoBlock *) onyx_ast_node_new(context.ast_alloc, sizeof(AstDoBlock), Ast_Kind_Do_Block); - doblock->token = expansion->token; - doblock->block = expansion; - doblock->type = template->type->Function.return_type; - doblock->next = expansion->next; - expansion->next = NULL; - - subst = (AstNode *) doblock; - } - - Scope* argument_scope = scope_create(context.ast_alloc, NULL, call->token->pos); - if (expansion->binding_scope != NULL) - scope_include(argument_scope, expansion->binding_scope, call->token->pos); - expansion->binding_scope = argument_scope; - - // HACK HACK HACK This is probably very wrong. I don't know what guarentees that - // the paramters and arguments are going to be in the same order exactly. - Type *any_type = type_build_from_ast(context.ast_alloc, builtin_any_type); - fori (i, 0, bh_arr_length(call->args.values)) { - AstNode *value = (AstNode *) ((AstArgument *) call->args.values[i])->value; - assert(template->params[i].local->type); - - Type *param_type = template->params[i].local->type; - if (param_type == any_type - || (param_type->kind == Type_Kind_VarArgs && param_type->VarArgs.elem == any_type)) { - onyx_report_error(macro->token->pos, Error_Critical, "Currently, macros do not support arguments of type 'any' or '..any'."); - } - - symbol_introduce(argument_scope, template->params[i].local->token, value); - } - - if (template->poly_scope != NULL) - scope_include(argument_scope, template->poly_scope, call->token->pos); - - *(AstNode **) pcall = subst; - return; -} - -AstFunction* macro_resolve_header(AstMacro* macro, Arguments* args, OnyxToken* callsite, b32 error_if_failed) { - switch (macro->body->kind) { - case Ast_Kind_Function: return (AstFunction *) macro->body; - - case Ast_Kind_Polymorphic_Proc: { - AstFunction* pp = (AstFunction *) macro->body; - ensure_polyproc_cache_is_created(pp); - - bh_arr(AstPolySolution) slns = find_polymorphic_slns(pp, PPLM_By_Arguments, args, callsite, error_if_failed); - - if (slns == NULL) { - if (flag_to_yield) { - flag_to_yield = 0; - return (AstFunction *) &node_that_signals_a_yield; - } - - return NULL; - } - - return polymorphic_proc_build_only_header_with_slns(pp, slns, error_if_failed); - } - - default: assert(("Bad macro body type.", 0)); - } - - return NULL; -} - - -// -// Arguments resolving -// -static i32 lookup_idx_by_name(AstNode* provider, char* name) { - switch (provider->kind) { - case Ast_Kind_Struct_Literal: { - AstStructLiteral* sl = (AstStructLiteral *) provider; - assert(sl->type); - - StructMember s; - if (!type_lookup_member(sl->type, name, &s)) return -1; - if (s.included_through_use) return -1; - - return s.idx; - } - - case Ast_Kind_Function: { - AstFunction* func = (AstFunction *) provider; - - i32 param_idx = -1; - i32 idx = 0; - bh_arr_each(AstParam, param, func->params) { - if (token_text_equals(param->local->token, name)) { - param_idx = idx; - break; - } - - idx++; - } - - return param_idx; - } - - default: return -1; - } -} - -static AstNode* lookup_default_value_by_idx(AstNode* provider, i32 idx) { - switch (provider->kind) { - case Ast_Kind_Struct_Literal: { - AstStructLiteral* sl = (AstStructLiteral *) provider; - assert(sl->type); - - if (sl->type->kind == Type_Kind_Struct) { - bh_arr(StructMember *) memarr = sl->type->Struct.memarr; - if (idx >= bh_arr_length(memarr)) return NULL; - - return (AstNode *) *memarr[idx]->initial_value; - } - - return NULL; - } - - case Ast_Kind_Function: { - AstFunction* func = (AstFunction *) provider; - - AstTyped* default_value = func->params[idx].default_value; - if (default_value == NULL) return NULL; - - AstArgument* arg = make_argument(context.ast_alloc, default_value); - return (AstNode *) arg; - } - - default: return NULL; - } -} - -static i32 maximum_argument_count(AstNode* provider) { - switch (provider->kind) { - case Ast_Kind_Struct_Literal: { - AstStructLiteral* sl = (AstStructLiteral *) provider; - assert(sl->type); - - return type_structlike_mem_count(sl->type); - } - } - - // NOTE: This returns int_max for anything other than struct literals because the - // bounds checking on the arguments will be done elsewhere. - return 0x7fffffff; -} - -static i32 non_baked_argument_count(Arguments* args) { - if (args->used_argument_count >= 0) return args->used_argument_count; - - i32 count = 0; - - bh_arr_each(AstTyped *, actual, args->values) { - if ((*actual)->kind != Ast_Kind_Argument) count++; - else if (!((AstArgument *) (*actual))->is_baked) count++; - } - - bh_arr_each(AstNamedValue *, named_value, args->named_values) { - if ((*named_value)->value->kind != Ast_Kind_Argument) count++; - else if (!((AstArgument *) (*named_value)->value)->is_baked) count++; - } - - args->used_argument_count = count; - return count; -} - -i32 get_argument_buffer_size(TypeFunction* type, Arguments* args) { - i32 non_vararg_param_count = (i32) type->param_count; - if (non_vararg_param_count > 0) { - if (type->params[type->param_count - 1] == builtin_vararg_type_type) non_vararg_param_count--; - if (type->params[type->param_count - 1]->kind == Type_Kind_VarArgs) non_vararg_param_count--; - } - - return bh_max(non_vararg_param_count, non_baked_argument_count(args)); -} - -// NOTE: The values array can be partially filled out, and is the resulting array. -// Returns if all the values were filled in. -b32 fill_in_arguments(Arguments* args, AstNode* provider, char** err_msg, b32 insert_zero_values) { - - { // Delete baked arguments - // :ArgumentResolvingIsComplicated - i32 max = bh_arr_length(args->values); - fori (i, 0, max) { - AstTyped* value = args->values[i]; - if (value == NULL) continue; - - if (value->kind == Ast_Kind_Argument) { - if (((AstArgument *) value)->is_baked) { - i--; - max--; - bh_arr_deleten(args->values, i, 1); - } - } - } - } - - if (args->named_values != NULL) { - bh_arr_each(AstNamedValue *, p_named_value, args->named_values) { - AstNamedValue* named_value = *p_named_value; - - if (named_value->value->kind == Ast_Kind_Argument) { - if (((AstArgument *) named_value->value)->is_baked) { - // :ArgumentResolvingIsComplicated - bh_arr_set_length(args->values, bh_arr_length(args->values) - 1); - continue; - } - } - - token_toggle_end(named_value->token); - i32 idx = lookup_idx_by_name(provider, named_value->token->text); - if (idx == -1) { - if (err_msg) *err_msg = bh_aprintf(global_scratch_allocator, "'%s' is not a valid named parameter here.", named_value->token->text); - token_toggle_end(named_value->token); - return 0; - } - - // assert(idx < bh_arr_length(args->values)); - if (idx >= bh_arr_length(args->values)) { - if (err_msg) *err_msg = bh_aprintf(global_scratch_allocator, "Error placing value with name '%s' at index '%d'.", named_value->token->text, idx); - token_toggle_end(named_value->token); - return 0; - } - - if (args->values[idx] != NULL && args->values[idx] != named_value->value) { - if (err_msg) *err_msg = bh_aprintf(global_scratch_allocator, "Multiple values given for parameter named '%s'.", named_value->token->text); - token_toggle_end(named_value->token); - return 0; - } - - args->values[idx] = named_value->value; - token_toggle_end(named_value->token); - } - } - - b32 success = 1; - fori (idx, 0, bh_arr_length(args->values)) { - if (args->values[idx] == NULL) args->values[idx] = (AstTyped *) lookup_default_value_by_idx(provider, idx); - if (args->values[idx] == NULL) { - if (insert_zero_values) { - assert(provider->token); - args->values[idx] = (AstTyped *) make_zero_value(context.ast_alloc, provider->token, NULL); - } else { - if (err_msg) *err_msg = bh_aprintf(global_scratch_allocator, "No value given for %d%s argument.", idx + 1, bh_num_suffix(idx + 1)); - success = 0; - } - } - } - - i32 maximum_arguments = maximum_argument_count(provider); - if (bh_arr_length(args->values) > maximum_arguments) { - if (err_msg) *err_msg = bh_aprintf(global_scratch_allocator, "Too many values provided. Expected at most %d.", maximum_arguments); - success = 0; - } - - return success; -} - - -// -// Argument checking -// - -typedef enum ArgState { - AS_Expecting_Exact, - AS_Expecting_Typed_VA, - AS_Expecting_Untyped_VA, -} ArgState; - -TypeMatch check_arguments_against_type(Arguments* args, TypeFunction* func_type, VarArgKind* va_kind, - OnyxToken* location, char* func_name, OnyxError* error) { - b32 permanent = location != NULL; - if (func_name == NULL) func_name = "UNKNOWN FUNCTION"; - - if (error) error->rank = Error_Critical; - - bh_arr(AstArgument *) arg_arr = (bh_arr(AstArgument *)) args->values; - i32 arg_count = get_argument_buffer_size(func_type, args); - - Type **formal_params = func_type->params; - Type* variadic_type = NULL; - i64 any_type_id = type_build_from_ast(context.ast_alloc, builtin_any_type)->id; - - ArgState arg_state = AS_Expecting_Exact; - u32 arg_pos = 0; - while (1) { - switch (arg_state) { - case AS_Expecting_Exact: { - if (arg_pos >= func_type->param_count) goto type_checking_done; - - if (formal_params[arg_pos]->kind == Type_Kind_VarArgs) { - variadic_type = formal_params[arg_pos]->VarArgs.elem; - arg_state = AS_Expecting_Typed_VA; - continue; - } - - if ((i16) arg_pos == func_type->vararg_arg_pos) { - arg_state = AS_Expecting_Untyped_VA; - continue; - } - - if (arg_pos >= (u32) bh_arr_length(arg_arr)) goto type_checking_done; - - assert(arg_arr[arg_pos]->kind == Ast_Kind_Argument); - TypeMatch tm = unify_node_and_type_(&arg_arr[arg_pos]->value, formal_params[arg_pos], permanent); - if (tm == TYPE_MATCH_YIELD) return tm; - if (tm == TYPE_MATCH_FAILED) { - if (error != NULL) { - error->pos = arg_arr[arg_pos]->token->pos; - error->text = bh_aprintf(global_heap_allocator, - "The procedure '%s' expects a value of type '%s' for %d%s parameter, got '%s'.", - func_name, - type_get_name(formal_params[arg_pos]), - arg_pos + 1, - bh_num_suffix(arg_pos + 1), - node_get_type_name(arg_arr[arg_pos]->value)); - } - return tm; - } - - if (arg_arr[arg_pos]->value->type && arg_arr[arg_pos]->value->type->id != any_type_id && formal_params[arg_pos]->id == any_type_id) { - resolve_expression_type(arg_arr[arg_pos]->value); - if (error != NULL) { - arg_arr[arg_pos]->pass_as_any = 1; - } - } - - arg_arr[arg_pos]->va_kind = VA_Kind_Not_VA; - break; - } - - case AS_Expecting_Typed_VA: { - if (variadic_type->id == any_type_id) *va_kind = VA_Kind_Any; - - if (arg_pos >= (u32) bh_arr_length(arg_arr)) goto type_checking_done; - - if (variadic_type->id == any_type_id) { - resolve_expression_type(arg_arr[arg_pos]->value); - if (arg_arr[arg_pos]->value->type == NULL) { - if (error != NULL) { - error->pos = arg_arr[arg_pos]->token->pos; - error->text = "Unable to resolve type of argument."; - } - return TYPE_MATCH_FAILED; - } - - arg_arr[arg_pos]->va_kind = VA_Kind_Any; - break; - } - - *va_kind = VA_Kind_Typed; - - assert(arg_arr[arg_pos]->kind == Ast_Kind_Argument); - TypeMatch tm = unify_node_and_type_(&arg_arr[arg_pos]->value, variadic_type, permanent); - if (tm == TYPE_MATCH_YIELD) return tm; - if (tm == TYPE_MATCH_FAILED) { - if (error != NULL) { - error->pos = arg_arr[arg_pos]->token->pos, - error->text = bh_aprintf(global_heap_allocator, - "The procedure '%s' expects a value of type '%s' for the variadic parameter, got '%s'.", - func_name, - type_get_name(variadic_type), - node_get_type_name(arg_arr[arg_pos]->value)); - } - return tm; - } - - arg_arr[arg_pos]->va_kind = VA_Kind_Typed; - break; - } - - case AS_Expecting_Untyped_VA: { - *va_kind = VA_Kind_Untyped; - - if (arg_pos >= (u32) bh_arr_length(arg_arr)) goto type_checking_done; - - assert(arg_arr[arg_pos]->kind == Ast_Kind_Argument); - resolve_expression_type(arg_arr[arg_pos]->value); - if (arg_arr[arg_pos]->value->type == NULL) { - if (error != NULL) { - error->pos = arg_arr[arg_pos]->token->pos; - error->text = "Unable to resolve type for argument."; - } - return TYPE_MATCH_FAILED; - } - - arg_arr[arg_pos]->va_kind = VA_Kind_Untyped; - break; - } - } - - arg_pos++; - } - -type_checking_done: - if (arg_pos < func_type->needed_param_count) { - if (error != NULL) { - error->pos = location->pos; - error->text = "Too few arguments to function call."; - } - return TYPE_MATCH_FAILED; - } - - if (arg_pos < (u32) arg_count) { - if (error != NULL) { - error->pos = location->pos; - error->text = bh_aprintf(global_heap_allocator, "Too many arguments to function call. %d %d", arg_pos, arg_count); - } - return TYPE_MATCH_FAILED; - } - - return TYPE_MATCH_SUCCESS; -} - - - - - -// -// String parsing helpers -// -u32 char_to_base16_value(char x) { - if (x >= '0' && x <= '9') return (u32) (x - '0'); - if (x >= 'A' && x <= 'F') return (u32) (x - 'A' + 10); - if (x >= 'a' && x <= 'f') return (u32) (x - 'a' + 10); - return 0xffffffff; -} - -i32 string_process_escape_seqs(char* dest, char* src, i32 len) { - i32 total_len = 0; - for (i32 i = 0; i < len; i++) { - if (src[i] == '\\') { - i++; - switch (src[i]) { - case '0': *dest++ = '\0'; total_len++; break; - case 'a': *dest++ = '\a'; total_len++; break; - case 'b': *dest++ = '\b'; total_len++; break; - case 'f': *dest++ = '\f'; total_len++; break; - case 'n': *dest++ = '\n'; total_len++; break; - case 't': *dest++ = '\t'; total_len++; break; - case 'r': *dest++ = '\r'; total_len++; break; - case 'v': *dest++ = '\v'; total_len++; break; - case 'e': *dest++ = '\e'; total_len++; break; - case '"': *dest++ = '"'; total_len++; break; - case '\\': *dest++ = '\\'; total_len++; break; - case 'x': { - u8 ch1 = src[i + 1]; - u8 ch2 = src[i + 2]; - *dest++ = (i8) (char_to_base16_value(ch1) << 4 | char_to_base16_value(ch2)); - total_len++; - i += 2; - break; - } - default: *dest++ = '\\'; - *dest++ = src[i]; - total_len += 2; - } - } else { - *dest++ = src[i]; - total_len += 1; - } - } - - // NOTE: Gotta null terminate - *dest = 0; - - return total_len; -} - -static Scope **get_scope_from_node_helper(AstNode *node) { - b32 used_pointer = 0; - - while (1) { - if (!node) return NULL; - - switch (node->kind) { - case Ast_Kind_Type_Raw_Alias: node = (AstNode *) ((AstTypeRawAlias *) node)->to->ast_type; break; - case Ast_Kind_Type_Alias: node = (AstNode *) ((AstTypeAlias *) node)->to; break; - case Ast_Kind_Alias: node = (AstNode *) ((AstAlias *) node)->alias; break; - case Ast_Kind_Pointer_Type: { - if (used_pointer) goto all_types_peeled_off; - used_pointer = 1; - - node = (AstNode *) ((AstPointerType *) node)->elem; - break; - } - - default: goto all_types_peeled_off; - } - } - -all_types_peeled_off: - if (!node) return NULL; - - switch (node->kind) { - case Ast_Kind_Package: { - AstPackage* package = (AstPackage *) node; - if (package->package == NULL) return NULL; - - return &package->package->scope; - } - - case Ast_Kind_Enum_Type: { - AstEnumType* etype = (AstEnumType *) node; - return &etype->scope; - } - - case Ast_Kind_Struct_Type: { - AstStructType* stype = (AstStructType *) node; - return &stype->scope; - } - - case Ast_Kind_Poly_Struct_Type: { - AstPolyStructType* pstype = (AstPolyStructType *) node; - AstStructType* stype = pstype->base_struct; - return &stype->scope; - } - } - - return NULL; -} - -Scope *get_scope_from_node(AstNode *node) { - if (!node) return NULL; - - Scope **pscope = get_scope_from_node_helper(node); - if (!pscope) return NULL; - return *pscope; -} - -Scope *get_scope_from_node_or_create(AstNode *node) { - if (!node) return NULL; - - Scope **pscope = get_scope_from_node_helper(node); - if (!pscope) return NULL; - - // Create the scope if it does not exist. - // This uses a NULL parent, which I think is what - // is used in other parts of the compiler for struct/enum - // scopes? - if (!*pscope) { - assert(node->token); - *pscope = scope_create(context.ast_alloc, NULL, node->token->pos); - } - - return *pscope; -} - -u32 levenshtein_distance(const char *str1, const char *str2) { - i32 m = strlen(str1) + 1; - i32 n = strlen(str2) + 1; - - i32 *d = bh_alloc_array(global_scratch_allocator, i32, m * n); - fori (i, 0, m * n) d[i] = 0; - - fori (i, 0, m) d[i * n + 0] = i; - fori (j, 0, n) d[0 * n + j] = j; - - fori (j, 1, n) { - fori (i, 1, m) { - i32 subst_cost = str1[i - 1] == str2[j - 1] ? 0 : 1; - - i32 a = d[(i - 1) * n + j] + 1; - i32 b = d[i * n + (j - 1)] + 1; - i32 c = d[(i - 1) * n + (j - 1)] + subst_cost; - - d[i * n + j] = bh_min(bh_min(a, b), c); - } - } - - return d[m * n - 1]; -} - -char *find_closest_symbol_in_scope(Scope *scope, char *sym, u32 *out_distance) { - *out_distance = 0x7fffffff; - - if (scope == NULL) return NULL; - - char* closest = NULL; - fori (i, 0, shlen(scope->symbols)) { - if (scope->symbols[i].value->flags & Ast_Flag_Symbol_Invisible) continue; - - char *key = scope->symbols[i].key; - u32 d = levenshtein_distance(key, sym); - if (d < *out_distance) { - *out_distance = d; - closest = (char *) key; - } - } - - return closest; -} - -char *find_closest_symbol_in_scope_and_parents(Scope *scope, char *sym) { - u32 min_dist = 0x7fffffff; - u32 tmp_dist; - - char *closest = NULL; - while (scope != NULL) { - char *tmp_closest = find_closest_symbol_in_scope(scope, sym, &tmp_dist); - if (tmp_dist < min_dist) { - min_dist = tmp_dist; - closest = tmp_closest; - } - - scope = scope->parent; - } - - return closest; -} - -char *find_closest_symbol_in_node(AstNode* node, char *sym) { - Scope *scope = get_scope_from_node(node); - if (!scope) { - if (node->kind == Ast_Kind_Poly_Call_Type) { - AstPolyCallType* pcall = (AstPolyCallType *) node; - return find_closest_symbol_in_node((AstNode *) pcall->callee, sym); - } - - return NULL; - } - - u32 dist; - return find_closest_symbol_in_scope(scope, sym, &dist); -} - diff --git a/src/wasm_emit.c b/src/wasm_emit.c deleted file mode 100644 index 0dc1c4a6..00000000 --- a/src/wasm_emit.c +++ /dev/null @@ -1,4588 +0,0 @@ -// -// There are several things I'm seeing in this file that I want to clean up. -// They are: -// [x] remove the need to know if the stack is needed before generating the function. -// Just leave 5 nops at the beginning because they will be automatically removed -// by the WASM outputter. -// [x] remove the need to have "allocate_exprs" on blocks and in functions. This will -// be easy once the above is done. -// [x] there should be a better way to emit pending deferred statements because there -// is some code duplication between emit_return and emit_structured_jump. -// [ ] Change the calling convention so it is easier to use from both JS and in the compiler. - - - - -#define BH_DEBUG -#include "wasm_emit.h" -#include "utils.h" - -#define WASM_TYPE_INT32 0x7F -#define WASM_TYPE_INT64 0x7E -#define WASM_TYPE_FLOAT32 0x7D -#define WASM_TYPE_FLOAT64 0x7C -#define WASM_TYPE_VAR128 0x7B -#define WASM_TYPE_PTR WASM_TYPE_INT32 -#define WASM_TYPE_FUNC WASM_TYPE_INT32 -#define WASM_TYPE_VOID 0x00 - -static WasmType onyx_type_to_wasm_type(Type* type) { - if (type->kind == Type_Kind_Struct) { - if (type_linear_member_count(type) == 1) { - return onyx_type_to_wasm_type(type->Struct.linear_members[0].type); - } - - return WASM_TYPE_VOID; - } - - if (type->kind == Type_Kind_Slice) { - return WASM_TYPE_VOID; - } - - if (type->kind == Type_Kind_Compound) { - return WASM_TYPE_VOID; - } - - if (type->kind == Type_Kind_Enum) { - return onyx_type_to_wasm_type(type->Enum.backing); - } - - if (type->kind == Type_Kind_Distinct) { - return onyx_type_to_wasm_type(type->Distinct.base_type); - } - - if (type->kind == Type_Kind_Pointer) { - return WASM_TYPE_PTR; - } - - if (type->kind == Type_Kind_Array) { - return WASM_TYPE_PTR; - } - - if (type->kind == Type_Kind_Function) { - return WASM_TYPE_FUNC; - } - - if (type->kind == Type_Kind_Basic) { - TypeBasic* basic = &type->Basic; - if (basic->flags & Basic_Flag_Boolean) return WASM_TYPE_INT32; - if (basic->flags & Basic_Flag_Integer) { - if (basic->size <= 4) return WASM_TYPE_INT32; - if (basic->size == 8) return WASM_TYPE_INT64; - } - if (basic->flags & Basic_Flag_Pointer) return WASM_TYPE_INT32; - if (basic->flags & Basic_Flag_Float) { - if (basic->size <= 4) return WASM_TYPE_FLOAT32; - if (basic->size == 8) return WASM_TYPE_FLOAT64; - } - if (basic->flags & Basic_Flag_SIMD) return WASM_TYPE_VAR128; - if (basic->flags & Basic_Flag_Type_Index) return WASM_TYPE_INT32; - if (basic->size == 0) return WASM_TYPE_VOID; - } - - return WASM_TYPE_VOID; -} - -static i32 generate_type_idx(OnyxWasmModule* mod, Type* ft); -static i32 get_element_idx(OnyxWasmModule* mod, AstFunction* func); - -#define LOCAL_I32 0x000000000 -#define LOCAL_I64 0x100000000 -#define LOCAL_F32 0x300000000 -#define LOCAL_F64 0x700000000 -#define LOCAL_V128 0xf00000000 - -static b32 local_is_wasm_local(AstTyped* local) { - if (local->kind == Ast_Kind_Local && local->flags & Ast_Flag_Address_Taken) return 0; - if (local->type->kind == Type_Kind_Basic) return 1; - if (local->type->kind == Type_Kind_Enum && local->type->Enum.backing->kind == Type_Kind_Basic) return 1; - if (local->type->kind == Type_Kind_Distinct && local->type->Distinct.base_type->kind == Type_Kind_Basic) return 1; - if (local->type->kind == Type_Kind_Pointer) return 1; - return 0; -} - -static u64 local_raw_allocate(LocalAllocator* la, WasmType wt) { - i32 idx = 0; - if (wt == WASM_TYPE_INT32) idx = 0; - if (wt == WASM_TYPE_INT64) idx = 1; - if (wt == WASM_TYPE_FLOAT32) idx = 2; - if (wt == WASM_TYPE_FLOAT64) idx = 3; - if (wt == WASM_TYPE_VAR128) idx = 4; - - u64 flag_bits = LOCAL_IS_WASM; - if (wt == WASM_TYPE_INT32) flag_bits |= LOCAL_I32; - if (wt == WASM_TYPE_INT64) flag_bits |= LOCAL_I64; - if (wt == WASM_TYPE_FLOAT32) flag_bits |= LOCAL_F32; - if (wt == WASM_TYPE_FLOAT64) flag_bits |= LOCAL_F64; - if (wt == WASM_TYPE_VAR128) flag_bits |= LOCAL_V128; - - if (la->freed[idx] > 0) { - la->freed[idx]--; - return flag_bits | ((u64) (la->allocated[idx] - la->freed[idx] - 1 + la->param_count)); - - } else { - la->allocated[idx]++; - return flag_bits | ((u64) (la->allocated[idx] - 1 + la->param_count)); - } -} - -static void local_raw_free(LocalAllocator* la, WasmType wt) { - i32 idx = 0; - - if (wt == WASM_TYPE_INT32) idx = 0; - if (wt == WASM_TYPE_INT64) idx = 1; - if (wt == WASM_TYPE_FLOAT32) idx = 2; - if (wt == WASM_TYPE_FLOAT64) idx = 3; - if (wt == WASM_TYPE_VAR128) idx = 4; - - assert(la->allocated[idx] > 0 && la->freed[idx] < la->allocated[idx]); - - la->freed[idx]++; -} - -static u64 local_allocate(LocalAllocator* la, AstTyped* local) { - if (local_is_wasm_local(local)) { - WasmType wt = onyx_type_to_wasm_type(local->type); - return local_raw_allocate(la, wt); - - } else { - u32 size = type_size_of(local->type); - u32 alignment = type_alignment_of(local->type); - - bh_align(la->curr_stack, alignment); - - if (la->max_stack < la->curr_stack) - la->max_stack = la->curr_stack; - - bh_align(size, alignment); - - if (la->max_stack - la->curr_stack >= (i32) size) { - la->curr_stack += size; - - } else { - la->max_stack += size - (la->max_stack - la->curr_stack); - la->curr_stack = la->max_stack; - } - - return la->curr_stack - size; - } -} - -static void local_free(LocalAllocator* la, AstTyped* local) { - if (local_is_wasm_local(local)) { - WasmType wt = onyx_type_to_wasm_type(local->type); - local_raw_free(la, wt); - - } else { - u32 size = type_size_of(local->type); - u32 alignment = type_alignment_of(local->type); - bh_align(size, alignment); - - la->curr_stack -= size; - } -} - -static u64 local_lookup_idx(LocalAllocator* la, u64 value) { - assert(value & LOCAL_IS_WASM); - - u32 idx = value & 0xFFFFFFFF; - if (value & 0x100000000) idx += la->allocated[0]; - if (value & 0x200000000) idx += la->allocated[1]; - if (value & 0x400000000) idx += la->allocated[2]; - if (value & 0x800000000) idx += la->allocated[3]; - - return (u64) idx; -} - - -static inline b32 should_emit_function(AstFunction* fd) { - // NOTE: Don't output intrinsic functions - if (fd->is_intrinsic) return 0; - - // NOTE: Don't output functions that are not used, only if - // they are also not exported. - if ((fd->flags & Ast_Flag_Function_Used) == 0) { - if (fd->is_exported || bh_arr_length(fd->tags) > 0) { - return 1; - } else { - return 0; - } - } - - return 1; -} - - -// -// Debug Info Generation -// - -#ifdef ENABLE_DEBUG_INFO - -static u32 debug_introduce_symbol(OnyxWasmModule *mod, OnyxToken *token, DebugSymLoc loc, u64 num, Type* type) { - - u32 id = mod->debug_context->next_sym_id++; - - DebugSymInfo sym_info; - sym_info.sym_id = id; - sym_info.location_type = loc; - sym_info.location_num = num; - sym_info.type = type->id; - - if (token) { - token_toggle_end(token); - sym_info.name = bh_strdup(context.ast_alloc, token->text); - token_toggle_end(token); - } else { - sym_info.name = NULL; - } - - bh_arr_push(mod->debug_context->sym_info, sym_info); - - if (loc == DSL_REGISTER) { - assert(mod->local_alloc); - DebugSymPatch patch; - patch.func_idx = mod->current_func_idx; - patch.sym_id = id; - patch.local_idx = num; - bh_arr_push(mod->debug_context->sym_patches, patch); - } - - bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_SYM); - u32 leb_len=0; - u8 *bytes = uint_to_uleb128(id, &leb_len); - bh_buffer_append(&mod->debug_context->op_buffer, bytes, leb_len); - - mod->debug_context->last_op_was_rep = 0; - return id; -} - -static u32 debug_get_file_id(OnyxWasmModule *mod, const char *name) { - assert(mod && mod->debug_context); - - i32 index = shgeti(mod->debug_context->file_info, name); - if (index == -1) { - u32 id = mod->debug_context->next_file_id++; - DebugFileInfo file_info; - file_info.file_id = id; - - bh_arr_each(bh_file_contents, fc, context.loaded_files) { - if (!strcmp(fc->filename, name)) { - file_info.line_count = fc->line_count; - } - } - shput(mod->debug_context->file_info, name, file_info); - - return id; - } - - return mod->debug_context->file_info[index].value.file_id; -} - -static void debug_set_position(OnyxWasmModule *mod, OnyxToken *token) { - i32 file_id = debug_get_file_id(mod, token->pos.filename); - - bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_SET); - mod->debug_context->last_op_was_rep = 0; - - u32 leb_len=0; - u8 *bytes = uint_to_uleb128(file_id, &leb_len); - bh_buffer_append(&mod->debug_context->op_buffer, bytes, leb_len); - - bytes = uint_to_uleb128(token->pos.line, &leb_len); - bh_buffer_append(&mod->debug_context->op_buffer, bytes, leb_len); - - mod->debug_context->last_token = token; -} - -// Called for every instruction being emitted -// This has to emit either: -// - INC -// - DEC -// - REP -// - SET, REP 0 -static void debug_emit_instruction(OnyxWasmModule *mod, OnyxToken *token) { - DebugContext *ctx = mod->debug_context; - assert(ctx && ctx->last_token); - - // Sanity check - if (ctx->last_op_was_rep) { - assert((ctx->op_buffer.data[ctx->op_buffer.length - 1] & DOT_REP) == DOT_REP); - } - i32 file_id, old_file_id; - - b32 repeat_previous = 0; - if (!token || !token->pos.filename) { - repeat_previous = 1; - - } else { - file_id = debug_get_file_id(mod, token->pos.filename); - old_file_id = debug_get_file_id(mod, ctx->last_token->pos.filename); - if (old_file_id == file_id && token->pos.line == ctx->last_token->pos.line) { - repeat_previous = 1; - } - } - - if (repeat_previous) { - // Output / increment REP instruction - if (ctx->last_op_was_rep) { - u8 rep_count = ctx->op_buffer.data[ctx->op_buffer.length - 1] & 0b00111111; - if (rep_count != 63) { - rep_count += 1; - ctx->op_buffer.data[ctx->op_buffer.length - 1] = DOT_REP | rep_count; - return; - } - } - - bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_REP); - ctx->last_op_was_rep = 1; - return; - } - - // At this point, token is definitely non-null. - ctx->last_op_was_rep = 0; - - if (old_file_id == file_id) { - // We see if we can INC/DEC to get to the line number - if (ctx->last_token->pos.line < token->pos.line) { - u32 diff = token->pos.line - ctx->last_token->pos.line; - if (diff <= 64) { - bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_INC | (diff - 1)); - goto done; - } - } - - if (ctx->last_token->pos.line > token->pos.line) { - u32 diff = ctx->last_token->pos.line - token->pos.line; - if (diff <= 64) { - bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_DEC | (diff - 1)); - goto done; - } - } - } - - // Otherwise, we need to output a SET, followed by a REP 0, - // which is what set_position does. - debug_set_position(mod, token); - bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_REP); - ctx->last_op_was_rep = 1; - - done: - ctx->last_token = token; -} - -static void debug_begin_function(OnyxWasmModule *mod, u32 func_idx, OnyxToken *token, char *name) { - u32 file_id = debug_get_file_id(mod, token->pos.filename); - u32 line = token->pos.line; - - assert(mod->debug_context); - - DebugFuncContext func; - func.func_index = func_idx; - func.file_id = file_id; - func.line = line; - func.op_offset = mod->debug_context->op_buffer.length; - func.stack_ptr_idx = 0; - func.name_length = strlen(name); - func.name = name; - bh_arr_push(mod->debug_context->funcs, func); - - bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_PUSHF); - debug_set_position(mod, token); -} - -static void debug_end_function(OnyxWasmModule *mod) { - bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_POPF); - bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_END); - mod->debug_context->last_op_was_rep = 0; - mod->debug_context->last_token = NULL; -} - -static void debug_enter_symbol_frame(OnyxWasmModule *mod) { - bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_PUSHF); - mod->debug_context->last_op_was_rep = 0; -} - -static void debug_leave_symbol_frame(OnyxWasmModule *mod) { - bh_buffer_write_byte(&mod->debug_context->op_buffer, DOT_POPF); - mod->debug_context->last_op_was_rep = 0; -} - -#else - -#define debug_introduce_symbol(mod, name, loc, num, type) (void)0 -#define debug_get_file_id(mod, name) (void)0 -#define debug_set_position(mod, token) (void)0 -#define debug_emit_instruction(mod, token) (void)0 -#define debug_begin_function(mod, idx, token, name) (void)0 -#define debug_end_function(mod) (void)0 -#define debug_enter_symbol_frame(mod) (void)0 -#define debug_leave_symbol_frame(mod) (void)0 - -#endif - - -typedef enum StructuredBlockType StructuredBlockType; -enum StructuredBlockType { - SBT_Basic_Block, // Cannot be targeted using jump - SBT_Breakable_Block, // Targeted using break - SBT_Continue_Block, // Targeted using continue - SBT_Fallthrough_Block, // Targeted using fallthrough - SBT_Return_Block, // Targeted using return, (used for expression macros) - - SBT_Basic_If, // Cannot be targeted using jump - SBT_Breakable_If, // Targeted using break - - SBT_Basic_Loop, // Cannot be targeted using jump - SBT_Continue_Loop, // Targeted using continue - - SBT_Count, -}; - -#ifdef ENABLE_DEBUG_INFO - #define WI(token, instr) (debug_emit_instruction(mod, token), bh_arr_push(code, ((WasmInstruction){ instr, 0x00 }))) - #define WID(token, instr, data) (debug_emit_instruction(mod, token), bh_arr_push(code, ((WasmInstruction){ instr, data }))) - #define WIL(token, instr, data) (debug_emit_instruction(mod, token), bh_arr_push(code, ((WasmInstruction){ instr, { .l = data } }))) - #define WIP(token, instr, data) (debug_emit_instruction(mod, token), bh_arr_push(code, ((WasmInstruction){ instr, { .p = data } }))) - #define WIR(token, full_instr) (debug_emit_instruction(mod, token), bh_arr_push(code, full_instr)) -#else - #define WI(token, instr) (bh_arr_push(code, ((WasmInstruction){ instr, 0x00 }))) - #define WID(token, instr, data) (bh_arr_push(code, ((WasmInstruction){ instr, data }))) - #define WIL(token, instr, data) (bh_arr_push(code, ((WasmInstruction){ instr, { .l = data } }))) - #define WIP(token, instr, data) (bh_arr_push(code, ((WasmInstruction){ instr, { .p = data } }))) - #define WIR(token, full_instr) (bh_arr_push(code, full_instr)) -#endif - -#define EMIT_FUNC(kind, ...) static void emit_ ## kind (OnyxWasmModule* mod, bh_arr(WasmInstruction)* pcode, __VA_ARGS__) -#define EMIT_FUNC_NO_ARGS(kind) static void emit_ ## kind (OnyxWasmModule* mod, bh_arr(WasmInstruction)* pcode) -#define STACK_SWAP(token, type1, type2) { \ - u64 t0 = local_raw_allocate(mod->local_alloc, type1); \ - u64 t1 = local_raw_allocate(mod->local_alloc, type2); \ - \ - WIL(token, WI_LOCAL_SET, t0); \ - WIL(token, WI_LOCAL_SET, t1); \ - WIL(token, WI_LOCAL_GET, t0); \ - WIL(token, WI_LOCAL_GET, t1); \ - \ - local_raw_free(mod->local_alloc, type1); \ - local_raw_free(mod->local_alloc, type2); \ - } -#define SUBMIT_PATCH(patch_arr, offset) bh_arr_push((patch_arr), ((PatchInfo) { bh_arr_length(code) - offset })) -#define NEXT_DATA_ID(mod) ((u32) bh_arr_length((mod)->data) + 1) - -EMIT_FUNC(function_body, AstFunction* fd); -EMIT_FUNC(block, AstBlock* block, b32 generate_block_headers); -EMIT_FUNC(statement, AstNode* stmt); -EMIT_FUNC(local_allocation, AstTyped* stmt); -EMIT_FUNC_NO_ARGS(free_local_allocations); -EMIT_FUNC(data_relocation, u32 data_id); -EMIT_FUNC(assignment, AstBinaryOp* assign); -EMIT_FUNC(assignment_of_array, AstTyped* left, AstTyped* right); -EMIT_FUNC(compound_assignment, AstBinaryOp* assign); -EMIT_FUNC(store_instruction, Type* type, u32 offset); -EMIT_FUNC(load_instruction, Type* type, u32 offset); -EMIT_FUNC(if, AstIfWhile* if_node); -EMIT_FUNC(while, AstIfWhile* while_node); -EMIT_FUNC(for, AstFor* for_node); -EMIT_FUNC(switch, AstSwitch* switch_node); -EMIT_FUNC(defer, AstDefer* defer); -EMIT_FUNC(defer_code, WasmInstruction* deferred_code, u32 code_count); -EMIT_FUNC(deferred_stmt, DeferredStmt deferred_stmt); -EMIT_FUNC_NO_ARGS(deferred_stmts); -EMIT_FUNC(remove_directive, AstDirectiveRemove* remove); -EMIT_FUNC(binop, AstBinaryOp* binop); -EMIT_FUNC(unaryop, AstUnaryOp* unop); -EMIT_FUNC(call, AstCall* call); -EMIT_FUNC(intrinsic_call, AstCall* call); -EMIT_FUNC(subscript_location, AstSubscript* sub, u64* offset_return); -EMIT_FUNC(field_access_location, AstFieldAccess* field, u64* offset_return); -EMIT_FUNC(local_location, AstLocal* local, u64* offset_return); -EMIT_FUNC(memory_reservation_location, AstMemRes* memres); -EMIT_FUNC(location_return_offset, AstTyped* expr, u64* offset_return); -EMIT_FUNC(location, AstTyped* expr); -EMIT_FUNC(compound_load, Type* type, u64 offset); -EMIT_FUNC(struct_lval, AstTyped* lval); -EMIT_FUNC(struct_literal, AstStructLiteral* sl); -EMIT_FUNC(compound_store, Type* type, u64 offset, b32 location_first); -EMIT_FUNC(array_store, Type* type, u32 offset); -EMIT_FUNC(array_literal, AstArrayLiteral* al); -EMIT_FUNC(range_literal, AstRangeLiteral* range); -EMIT_FUNC(if_expression, AstIfExpression* if_expr); -EMIT_FUNC(do_block, AstDoBlock* doblock); -EMIT_FUNC(expression, AstTyped* expr); -EMIT_FUNC(cast, AstUnaryOp* cast); -EMIT_FUNC(return, AstReturn* ret); -EMIT_FUNC(stack_enter, u64 stacksize); -EMIT_FUNC(zero_value, WasmType wt); -EMIT_FUNC(zero_value_for_type, Type* type, OnyxToken* where); - -EMIT_FUNC(enter_structured_block, StructuredBlockType sbt, OnyxToken* block_token); -EMIT_FUNC_NO_ARGS(leave_structured_block); - -static u32 emit_data_entry(OnyxWasmModule *mod, WasmDatum *datum); - -static void emit_constexpr(ConstExprContext *ctx, AstTyped *node, u32 offset); -static b32 emit_constexpr_(ConstExprContext *ctx, AstTyped *node, u32 offset); - -#include "wasm_intrinsics.h" -#include "wasm_type_table.h" - -EMIT_FUNC(function_body, AstFunction* fd) { - if (fd->body == NULL) return; - - emit_block(mod, pcode, fd->body, 0); -} - -EMIT_FUNC(block, AstBlock* block, b32 generate_block_headers) { - bh_arr(WasmInstruction) code = *pcode; - - generate_block_headers = generate_block_headers && (block->rules & Block_Rule_Emit_Instructions); - - if (generate_block_headers) { - emit_enter_structured_block(mod, &code, (block->rules & Block_Rule_Override_Return) - ? SBT_Return_Block - : SBT_Breakable_Block, - block->token); - debug_enter_symbol_frame(mod); - } - - forll (AstNode, stmt, block->body, next) { - emit_statement(mod, &code, stmt); - } - - // HACK: I'm not convinced this is the best way to handle this case. Essentially, if - // a deferred statement uses a local that is freed from the a macro block, then the - // local won't be valid anymore and could have garbage data. This ensures that the - // freeing of the local variables and flushing of the deferred statements happen together, - // so a deferred statement can never try to use a local that has been freed. - // - brendanfh 2021/08/29 - if (block->rules & Block_Rule_Clear_Defer) { - emit_deferred_stmts(mod, &code); - - // if (block->rules & Block_Rule_New_Scope) - emit_free_local_allocations(mod, &code); - } - - if (generate_block_headers) { - emit_leave_structured_block(mod, &code); - debug_leave_symbol_frame(mod); - } - - *pcode = code; -} - -EMIT_FUNC(enter_structured_block, StructuredBlockType sbt, OnyxToken* token) { - bh_arr(WasmInstruction) code = *pcode; - - static const StructuredBlockType jump_numbers[SBT_Count] = { - /* SBT_Basic_Block */ 0, - /* SBT_Breakable_Block */ 1, - /* SBT_Continue_Block */ 2, - /* SBT_Fallthrough_Block */ 3, - /* SBT_Return_Block */ 4, - - /* SBT_Basic_If */ 0, - /* SBT_Breakable_If */ 1, - - /* SBT_Basic_Loop */ 0, - /* SBT_Continue_Loop */ 2, - }; - - static const WasmInstructionType block_instrs[SBT_Count] = { - /* SBT_Basic_Block */ WI_BLOCK_START, - /* SBT_Breakable_Block */ WI_BLOCK_START, - /* SBT_Continue_Block */ WI_BLOCK_START, - /* SBT_Fallthrough_Block */ WI_BLOCK_START, - /* SBT_Return_Block */ WI_BLOCK_START, - - /* SBT_Basic_If */ WI_IF_START, - /* SBT_Breakable_If */ WI_IF_START, - - /* SBT_Basic_Loop */ WI_LOOP_START, - /* SBT_Continue_Loop */ WI_LOOP_START, - }; - - - WID(token, block_instrs[sbt], 0x40); - bh_arr_push(mod->structured_jump_target, jump_numbers[sbt]); - - *pcode = code; -} - -EMIT_FUNC_NO_ARGS(leave_structured_block) { - bh_arr(WasmInstruction) code = *pcode; - - WI(NULL, WI_BLOCK_END); - bh_arr_pop(mod->structured_jump_target); - - *pcode = code; -} - -i64 get_structured_jump_label(OnyxWasmModule* mod, JumpType jump_type, u32 jump_count) { - // :CLEANUP These numbers should become constants because they are shared with - // enter_structured_block's definitions. - static const u8 wants[Jump_Type_Count] = { 1, 2, 3, 4 }; - - i64 labelidx = 0; - u8 wanted = wants[jump_type]; - b32 success = 0; - - i32 len = bh_arr_length(mod->structured_jump_target) - 1; - for (u8* t = &bh_arr_last(mod->structured_jump_target); len >= 0; len--, t--) { - if (*t == wanted) jump_count--; - if (jump_count == 0) { - success = 1; - break; - } - - labelidx++; - } - - return (success == 0) ? -1 : labelidx; -} - -EMIT_FUNC(structured_jump, AstJump* jump) { - bh_arr(WasmInstruction) code = *pcode; - - i64 labelidx = get_structured_jump_label(mod, jump->jump, jump->count); - - if (bh_arr_length(mod->deferred_stmts) > 0) { - i32 i = bh_arr_length(mod->deferred_stmts) - 1; - i32 d = bh_arr_length(mod->structured_jump_target) - (labelidx + 1); - - while (i >= 0 && (i32) mod->deferred_stmts[i].depth > d) { - emit_deferred_stmt(mod, &code, mod->deferred_stmts[i]); - i--; - } - } - - if (labelidx >= 0) { - // NOTE: If the previous instruction was a non conditional jump, - // don't emit another jump since it will never be reached. - if (bh_arr_last(code).type != WI_JUMP) - WID(jump->token, WI_JUMP, labelidx); - } else { - onyx_report_error(jump->token->pos, Error_Critical, "Invalid structured jump."); - } - - *pcode = code; -} - -EMIT_FUNC(statement, AstNode* stmt) { - bh_arr(WasmInstruction) code = *pcode; - -#ifdef ENABLE_DEBUG_INFO - debug_set_position(mod, stmt->token); -#endif - - switch (stmt->kind) { - case Ast_Kind_Return: emit_return(mod, &code, (AstReturn *) stmt); break; - case Ast_Kind_If: emit_if(mod, &code, (AstIfWhile *) stmt); break; - case Ast_Kind_Static_If: emit_if(mod, &code, (AstIfWhile *) stmt); break; - case Ast_Kind_While: emit_while(mod, &code, (AstIfWhile *) stmt); break; - case Ast_Kind_For: emit_for(mod, &code, (AstFor *) stmt); break; - case Ast_Kind_Switch: emit_switch(mod, &code, (AstSwitch *) stmt); break; - case Ast_Kind_Jump: emit_structured_jump(mod, &code, (AstJump *) stmt); break; - case Ast_Kind_Block: emit_block(mod, &code, (AstBlock *) stmt, 1); break; - case Ast_Kind_Defer: emit_defer(mod, &code, (AstDefer *) stmt); break; - case Ast_Kind_Local: emit_local_allocation(mod, &code, (AstTyped *) stmt); break; - - case Ast_Kind_Directive_Remove: emit_remove_directive(mod, &code, (AstDirectiveRemove *) stmt); break; - case Ast_Kind_Directive_Insert: break; - - default: emit_expression(mod, &code, (AstTyped *) stmt); break; - } - - *pcode = code; -} - -EMIT_FUNC(local_allocation, AstTyped* stmt) { - u64 local_idx = local_allocate(mod->local_alloc, stmt); - bh_imap_put(&mod->local_map, (u64) stmt, local_idx); - - if (local_is_wasm_local(stmt)) { - debug_introduce_symbol(mod, stmt->token, DSL_REGISTER, local_idx, stmt->type); - } else { - debug_introduce_symbol(mod, stmt->token, DSL_STACK, local_idx, stmt->type); - } - - if (stmt->kind == Ast_Kind_Local && !(stmt->flags & Ast_Flag_Decl_Followed_By_Init)) { - bh_arr(WasmInstruction) code = *pcode; - if (local_is_wasm_local(stmt)) { - emit_zero_value(mod, &code, onyx_type_to_wasm_type(stmt->type)); - WIL(stmt->token, WI_LOCAL_SET, local_idx); - - } else { - emit_location(mod, &code, stmt); - WID(stmt->token, WI_I32_CONST, 0); - WID(stmt->token, WI_I32_CONST, type_size_of(stmt->type)); - if (context.options->use_post_mvp_features) { - WID(stmt->token, WI_MEMORY_FILL, 0x00); - } else { - emit_intrinsic_memory_fill(mod, &code); - } - } - - *pcode = code; - } - - bh_arr_push(mod->local_allocations, ((AllocatedSpace) { - .depth = bh_arr_length(mod->structured_jump_target), - .expr = stmt, - })); -} - -EMIT_FUNC_NO_ARGS(free_local_allocations) { - if (bh_arr_length(mod->local_allocations) == 0) return; - - u64 depth = bh_arr_length(mod->structured_jump_target); - while (bh_arr_length(mod->local_allocations) > 0 && bh_arr_last(mod->local_allocations).depth >= depth) { - // CHECK: Not sure this next line is okay to be here... - bh_imap_delete(&mod->local_map, (u64) bh_arr_last(mod->local_allocations).expr); - - local_free(mod->local_alloc, bh_arr_last(mod->local_allocations).expr); - bh_arr_pop(mod->local_allocations); - } -} - -EMIT_FUNC(data_relocation, u32 data_id) { - bh_arr(WasmInstruction) code = *pcode; - - u32 instr_idx = bh_arr_length(code); - WID(NULL, WI_PTR_CONST, 0); - assert(mod->current_func_idx >= 0); - - DatumPatchInfo patch; - patch.kind = Datum_Patch_Instruction; - patch.index = mod->current_func_idx; - patch.location = instr_idx; - patch.data_id = data_id; - patch.offset = 0; - bh_arr_push(mod->data_patches, patch); - - *pcode = code; -} - -EMIT_FUNC(assignment, AstBinaryOp* assign) { - bh_arr(WasmInstruction) code = *pcode; - - if (assign->right->type->kind == Type_Kind_Array) { - emit_assignment_of_array(mod, &code, assign->left, assign->right); - *pcode = code; - return; - } - - if (assign->right->type->kind == Type_Kind_Compound) { - emit_compound_assignment(mod, &code, assign); - *pcode = code; - return; - } - - AstTyped* lval = assign->left; - - if (lval->kind == Ast_Kind_Local || lval->kind == Ast_Kind_Param) { - if (bh_imap_get(&mod->local_map, (u64) lval) & LOCAL_IS_WASM) { - emit_expression(mod, &code, assign->right); - - u64 localidx = bh_imap_get(&mod->local_map, (u64) lval); - - if (lval->kind == Ast_Kind_Param && type_is_structlike_strict(lval->type)) { - u32 mem_count = type_structlike_mem_count(lval->type); - fori (i, 0, mem_count) WIL(assign->token, WI_LOCAL_SET, localidx + i); - - } else { - WIL(assign->token, WI_LOCAL_SET, localidx); - } - - *pcode = code; - return; - } - } - - if (lval->kind == Ast_Kind_Field_Access) { - AstFieldAccess* fa = (AstFieldAccess *) lval; - if (fa->expr->kind == Ast_Kind_Param && type_is_structlike_strict(fa->expr->type)) { - emit_expression(mod, &code, assign->right); - - u64 localidx = bh_imap_get(&mod->local_map, (u64) fa->expr); - WIL(assign->token, WI_LOCAL_SET, localidx + fa->idx); - - *pcode = code; - return; - } - } - - if (lval->kind == Ast_Kind_Global) { - emit_expression(mod, &code, assign->right); - - i32 globalidx = (i32) bh_imap_get(&mod->index_map, (u64) lval); - WID(assign->token, WI_GLOBAL_SET, globalidx); - - *pcode = code; - return; - } - - u64 offset = 0; - emit_location_return_offset(mod, &code, lval, &offset); - emit_expression(mod, &code, assign->right); - emit_store_instruction(mod, &code, lval->type, offset); - - *pcode = code; -} - -EMIT_FUNC(assignment_of_array, AstTyped* left, AstTyped* right) { - bh_arr(WasmInstruction) code = *pcode; - - Type* rtype = right->type; - assert(rtype->kind == Type_Kind_Array); - - if (right->kind == Ast_Kind_Array_Literal) { - Type* elem_type = rtype; - u32 elem_count = 1; - while (elem_type->kind == Type_Kind_Array) { - elem_count *= elem_type->Array.count; - elem_type = elem_type->Array.elem; - } - u32 elem_size = type_size_of(elem_type); - - u64 lptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - - emit_location(mod, &code, left); - WIL(left->token, WI_LOCAL_SET, lptr_local); - - AstArrayLiteral* al = (AstArrayLiteral *) right; - fori (i, 0, elem_count) { - WIL(left->token, WI_LOCAL_GET, lptr_local); - emit_expression(mod, &code, al->values[i]); - emit_store_instruction(mod, &code, elem_type, i * elem_size); - } - - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - - } else { - u64 offset = 0; - emit_location_return_offset(mod, &code, left, &offset); - emit_expression(mod, &code, right); - emit_array_store(mod, &code, rtype, offset); - } - - *pcode = code; - return; -} - -EMIT_FUNC(compound_assignment, AstBinaryOp* assign) { - bh_arr(WasmInstruction) code = *pcode; - - emit_expression(mod, &code, assign->right); - - AstCompound* compound_lval = (AstCompound *) assign->left; - bh_arr_rev_each(AstTyped *, plval, compound_lval->exprs) { - AstTyped *lval = *plval; - - if (type_is_structlike_strict(lval->type)) { - emit_struct_lval(mod, &code, lval); - continue; - } - - if (lval->kind == Ast_Kind_Local || lval->kind == Ast_Kind_Param) { - if (bh_imap_get(&mod->local_map, (u64) lval) & LOCAL_IS_WASM) { - u64 localidx = bh_imap_get(&mod->local_map, (u64) lval); - WIL(assign->token, WI_LOCAL_SET, localidx); - continue; - } - } - - WasmType wt = onyx_type_to_wasm_type(lval->type); - u64 expr_tmp = local_raw_allocate(mod->local_alloc, wt); - WIL(assign->token, WI_LOCAL_SET, expr_tmp); - u64 offset = 0; - emit_location_return_offset(mod, &code, lval, &offset); - WIL(assign->token, WI_LOCAL_GET, expr_tmp); - - local_raw_free(mod->local_alloc, wt); - emit_store_instruction(mod, &code, lval->type, offset); - } - - *pcode = code; - return; -} - -EMIT_FUNC(store_instruction, Type* type, u32 offset) { - bh_arr(WasmInstruction) code = *pcode; - - if (type_is_compound(type)) { - emit_compound_store(mod, pcode, type, offset, 0); - return; - } - - if (type->kind == Type_Kind_Struct) { - assert(bh_arr_length(type->Struct.linear_members) == 1); - type = type->Struct.linear_members[0].type; - } - - if (type->kind == Type_Kind_Array) { - emit_array_store(mod, pcode, type, offset); - return; - } - - if (type->kind == Type_Kind_Enum) type = type->Enum.backing; - if (type->kind == Type_Kind_Distinct) type = type->Distinct.base_type; - if (type->kind == Type_Kind_Function) type = &basic_types[Basic_Kind_U32]; - - u32 alignment = type_get_alignment_log2(type); - - i32 store_size = type_size_of(type); - i32 is_basic = type->kind == Type_Kind_Basic || type->kind == Type_Kind_Pointer; - - if (is_basic && (type->Basic.flags & Basic_Flag_Pointer)) { - WID(NULL, WI_I32_STORE, ((WasmInstructionData) { 2, offset })); - } else if (is_basic && ((type->Basic.flags & Basic_Flag_Integer) - || (type->Basic.flags & Basic_Flag_Boolean) - || (type->Basic.flags & Basic_Flag_Type_Index))) { - if (store_size == 1) WID(NULL, WI_I32_STORE_8, ((WasmInstructionData) { alignment, offset })); - else if (store_size == 2) WID(NULL, WI_I32_STORE_16, ((WasmInstructionData) { alignment, offset })); - else if (store_size == 4) WID(NULL, WI_I32_STORE, ((WasmInstructionData) { alignment, offset })); - else if (store_size == 8) WID(NULL, WI_I64_STORE, ((WasmInstructionData) { alignment, offset })); - } else if (is_basic && (type->Basic.flags & Basic_Flag_Float)) { - if (store_size == 4) WID(NULL, WI_F32_STORE, ((WasmInstructionData) { alignment, offset })); - else if (store_size == 8) WID(NULL, WI_F64_STORE, ((WasmInstructionData) { alignment, offset })); - } else if (is_basic && (type->Basic.flags & Basic_Flag_SIMD)) { - WID(NULL, WI_V128_STORE, ((WasmInstructionData) { alignment, offset })); - } else { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, - "Failed to generate store instruction for type '%s'.", - type_get_name(type)); - } - - *pcode = code; -} - -EMIT_FUNC(load_instruction, Type* type, u32 offset) { - bh_arr(WasmInstruction) code = *pcode; - - if (type_is_compound(type)) { - emit_compound_load(mod, pcode, type, offset); - return; - } - - if (type->kind == Type_Kind_Struct) { - assert(bh_arr_length(type->Struct.linear_members) == 1); - type = type->Struct.linear_members[0].type; - } - - if (type->kind == Type_Kind_Array) { - if (offset != 0) { - WID(NULL, WI_PTR_CONST, offset); - WI(NULL, WI_PTR_ADD); - } - - *pcode = code; - return; - } - - if (type->kind == Type_Kind_Enum) type = type->Enum.backing; - if (type->kind == Type_Kind_Distinct) type = type->Distinct.base_type; - if (type->kind == Type_Kind_Function) type = &basic_types[Basic_Kind_U32]; - - i32 load_size = type_size_of(type); - i32 is_basic = type->kind == Type_Kind_Basic || type->kind == Type_Kind_Pointer; - - WasmInstructionType instr = WI_NOP; - i32 alignment = type_get_alignment_log2(type); - - if (is_basic && (type->Basic.flags & Basic_Flag_Pointer)) { - instr = WI_I32_LOAD; - alignment = 2; - } - else if (is_basic && ((type->Basic.flags & Basic_Flag_Integer) - || (type->Basic.flags & Basic_Flag_Boolean) - || (type->Basic.flags & Basic_Flag_Type_Index))) { - if (load_size == 1) instr = WI_I32_LOAD_8_S; - else if (load_size == 2) instr = WI_I32_LOAD_16_S; - else if (load_size == 4) instr = WI_I32_LOAD; - else if (load_size == 8) instr = WI_I64_LOAD; - - if (load_size < 4 && (type->Basic.flags & Basic_Flag_Unsigned)) instr += 1; - } - else if (is_basic && (type->Basic.flags & Basic_Flag_Float)) { - if (load_size == 4) instr = WI_F32_LOAD; - else if (load_size == 8) instr = WI_F64_LOAD; - } - else if (is_basic && (type->Basic.flags & Basic_Flag_SIMD)) { - instr = WI_V128_LOAD; - } - - WID(NULL, instr, ((WasmInstructionData) { alignment, offset })); - - if (instr == WI_NOP) { - onyx_report_error((OnyxFilePos) { 0 }, Error_Critical, - "Failed to generate load instruction for type '%s'.", - type_get_name(type)); - } - - *pcode = code; -} - -EMIT_FUNC(if, AstIfWhile* if_node) { - bh_arr(WasmInstruction) code = *pcode; - - if (if_node->initialization != NULL) { - // bh_imap_put(&mod->local_map, (u64) if_node->local, local_allocate(mod->local_alloc, (AstTyped *) if_node->local)); - - forll (AstNode, stmt, if_node->initialization, next) { - emit_statement(mod, &code, stmt); - } - } - - if (if_node->kind == Ast_Kind_Static_If) { - if (static_if_resolution(if_node)) { - if (if_node->true_stmt) emit_block(mod, &code, if_node->true_stmt, 1); - } else { - if (if_node->false_stmt) emit_block(mod, &code, if_node->false_stmt, 1); - } - - *pcode = code; - return; - } - - emit_expression(mod, &code, if_node->cond); - - emit_enter_structured_block(mod, &code, SBT_Basic_If, if_node->token); - if (if_node->true_stmt) emit_block(mod, &code, if_node->true_stmt, 0); - - if (if_node->false_stmt) { - WI(if_node->false_stmt->token, WI_ELSE); - - if (if_node->false_stmt->kind == Ast_Kind_If) { - emit_if(mod, &code, (AstIfWhile *) if_node->false_stmt); - } else { - emit_block(mod, &code, if_node->false_stmt, 0); - } - } - - emit_leave_structured_block(mod, &code); - - *pcode = code; -} - -EMIT_FUNC(while, AstIfWhile* while_node) { - bh_arr(WasmInstruction) code = *pcode; - - if (while_node->initialization != NULL) { - forll (AstNode, stmt, while_node->initialization, next) { - emit_statement(mod, &code, stmt); - } - } - - if (while_node->false_stmt == NULL) { - emit_enter_structured_block(mod, &code, SBT_Breakable_Block, while_node->token); - emit_enter_structured_block(mod, &code, SBT_Continue_Loop, while_node->token); - - if (!while_node->bottom_test) { - emit_expression(mod, &code, while_node->cond); - WI(NULL, WI_I32_EQZ); - WID(NULL, WI_COND_JUMP, 0x01); - } - - emit_block(mod, &code, while_node->true_stmt, 0); - - if (while_node->bottom_test) { - emit_expression(mod, &code, while_node->cond); - WID(while_node->cond->token, WI_COND_JUMP, 0x00); - - } else { - if (bh_arr_last(code).type != WI_JUMP) - WID(while_node->cond->token, WI_JUMP, 0x00); - } - - emit_leave_structured_block(mod, &code); - emit_leave_structured_block(mod, &code); - - } else { - emit_expression(mod, &code, while_node->cond); - - emit_enter_structured_block(mod, &code, SBT_Breakable_If, while_node->token); - emit_enter_structured_block(mod, &code, SBT_Continue_Loop, while_node->token); - - emit_block(mod, &code, while_node->true_stmt, 0); - - emit_expression(mod, &code, while_node->cond); - WID(while_node->cond->token, WI_COND_JUMP, 0x00); - - emit_leave_structured_block(mod, &code); - WI(while_node->false_stmt->token, WI_ELSE); - - emit_block(mod, &code, while_node->false_stmt, 0); - - emit_leave_structured_block(mod, &code); - } - - *pcode = code; -} - -EMIT_FUNC(for_range, AstFor* for_node, u64 iter_local) { - bh_arr(WasmInstruction) code = *pcode; - - // NOTE: There are some aspects of the code below that rely on the - // low, high, and step members to be i32's. This restriction can be lifted, - // but it is important to change the code here. - // -brendanfh 2020/09/04 - - // NOTE: This might not be a range literal - AstStructLiteral *range = (AstStructLiteral *) for_node->iter; - u64 offset = 0; - - StructMember low_mem, high_mem, step_mem; - type_lookup_member(builtin_range_type_type, "low", &low_mem); - type_lookup_member(builtin_range_type_type, "high", &high_mem); - type_lookup_member(builtin_range_type_type, "step", &step_mem); - u64 low_local = local_raw_allocate(mod->local_alloc, onyx_type_to_wasm_type(low_mem.type)); - u64 high_local = local_raw_allocate(mod->local_alloc, onyx_type_to_wasm_type(high_mem.type)); - u64 step_local = local_raw_allocate(mod->local_alloc, onyx_type_to_wasm_type(step_mem.type)); - - WIL(for_node->token, WI_LOCAL_SET, step_local); - WIL(for_node->token, WI_LOCAL_SET, high_local); - WIL(for_node->token, WI_LOCAL_TEE, low_local); - WIL(for_node->token, WI_LOCAL_SET, iter_local); - - emit_enter_structured_block(mod, &code, SBT_Breakable_Block, for_node->token); - emit_enter_structured_block(mod, &code, SBT_Basic_Loop, for_node->token); - emit_enter_structured_block(mod, &code, SBT_Continue_Block, for_node->token); - - if (range->kind == Ast_Kind_Struct_Literal && (range->args.values[2]->flags & Ast_Flag_Comptime) != 0) { - AstNumLit *step_value = (AstNumLit *) range->args.values[2]; - assert(step_value->kind == Ast_Kind_NumLit); - - if (step_value->value.l >= 0) { - WIL(for_node->token, WI_LOCAL_GET, iter_local); - WIL(for_node->token, WI_LOCAL_GET, high_local); - WI(for_node->token, WI_I32_GE_S); - WID(for_node->token, WI_COND_JUMP, 0x02); - } else { - WIL(for_node->token, WI_LOCAL_GET, iter_local); - WIL(for_node->token, WI_LOCAL_GET, high_local); - WI(for_node->token, WI_I32_LT_S); - WID(for_node->token, WI_COND_JUMP, 0x02); - } - - } else { - WIL(for_node->token, WI_LOCAL_GET, step_local); - WID(for_node->token, WI_I32_CONST, 0); - WI(for_node->token, WI_I32_GE_S); - WID(for_node->token, WI_IF_START, 0x40); - WIL(for_node->token, WI_LOCAL_GET, iter_local); - WIL(for_node->token, WI_LOCAL_GET, high_local); - WI(for_node->token, WI_I32_GE_S); - WID(for_node->token, WI_COND_JUMP, 0x03); - WI(for_node->token, WI_ELSE); - WIL(for_node->token, WI_LOCAL_GET, iter_local); - WIL(for_node->token, WI_LOCAL_GET, high_local); - WI(for_node->token, WI_I32_LT_S); - WID(for_node->token, WI_COND_JUMP, 0x03); - WI(for_node->token, WI_IF_END); - } - - - emit_block(mod, &code, for_node->stmt, 0); - - emit_leave_structured_block(mod, &code); - - WIL(for_node->token, WI_LOCAL_GET, iter_local); - WIL(for_node->token, WI_LOCAL_GET, step_local); - WI(for_node->token, WI_I32_ADD); - WIL(for_node->token, WI_LOCAL_SET, iter_local); - - if (bh_arr_last(code).type != WI_JUMP) - WID(for_node->token, WI_JUMP, 0x00); - - emit_leave_structured_block(mod, &code); - emit_leave_structured_block(mod, &code); - - local_raw_free(mod->local_alloc, onyx_type_to_wasm_type(low_mem.type)); - local_raw_free(mod->local_alloc, onyx_type_to_wasm_type(high_mem.type)); - local_raw_free(mod->local_alloc, onyx_type_to_wasm_type(step_mem.type)); - - *pcode = code; -} - -EMIT_FUNC(for_array, AstFor* for_node, u64 iter_local) { - bh_arr(WasmInstruction) code = *pcode; - - u64 end_ptr_local, ptr_local; - end_ptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - - if (for_node->by_pointer) { - ptr_local = iter_local; - } else { - ptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - } - - AstLocal* var = for_node->var; - b32 it_is_local = (b32) ((iter_local & LOCAL_IS_WASM) != 0); - u64 offset = 0; - - u64 elem_size; - if (for_node->by_pointer) elem_size = type_size_of(var->type->Pointer.elem); - else elem_size = type_size_of(var->type); - - WIL(for_node->token, WI_LOCAL_TEE, ptr_local); - WIL(for_node->token, WI_PTR_CONST, for_node->iter->type->Array.count * elem_size); - WI(for_node->token, WI_PTR_ADD); - WIL(for_node->token, WI_LOCAL_SET, end_ptr_local); - - emit_enter_structured_block(mod, &code, SBT_Breakable_Block, for_node->token); - emit_enter_structured_block(mod, &code, SBT_Basic_Loop, for_node->token); - emit_enter_structured_block(mod, &code, SBT_Continue_Block, for_node->token); - - WIL(for_node->token, WI_LOCAL_GET, ptr_local); - WIL(for_node->token, WI_LOCAL_GET, end_ptr_local); - WI(for_node->token, WI_PTR_GE); - WID(for_node->token, WI_COND_JUMP, 0x02); - - if (!for_node->by_pointer) { - if (!it_is_local) emit_local_location(mod, &code, var, &offset); - - WIL(for_node->token, WI_LOCAL_GET, ptr_local); - emit_load_instruction(mod, &code, var->type, 0); - - if (!it_is_local) emit_store_instruction(mod, &code, var->type, offset); - else WIL(for_node->token, WI_LOCAL_SET, iter_local); - } - - emit_block(mod, &code, for_node->stmt, 0); - - emit_leave_structured_block(mod, &code); - - WIL(for_node->token, WI_LOCAL_GET, ptr_local); - WIL(for_node->token, WI_PTR_CONST, elem_size); - WI(for_node->token, WI_PTR_ADD); - WIL(for_node->token, WI_LOCAL_SET, ptr_local); - - if (bh_arr_last(code).type != WI_JUMP) - WID(for_node->token, WI_JUMP, 0x00); - - emit_leave_structured_block(mod, &code); - emit_leave_structured_block(mod, &code); - - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - if (!for_node->by_pointer) local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - - *pcode = code; -} - -EMIT_FUNC(for_slice, AstFor* for_node, u64 iter_local) { - bh_arr(WasmInstruction) code = *pcode; - - u64 end_ptr_local, ptr_local; - end_ptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - - if (for_node->by_pointer) { - ptr_local = iter_local; - } else { - ptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - } - - AstLocal* var = for_node->var; - b32 it_is_local = (b32) ((iter_local & LOCAL_IS_WASM) != 0); - u64 offset = 0; - - u64 elem_size; - if (for_node->by_pointer) elem_size = type_size_of(var->type->Pointer.elem); - else elem_size = type_size_of(var->type); - - WIL(for_node->token, WI_LOCAL_SET, end_ptr_local); - WIL(for_node->token, WI_LOCAL_TEE, ptr_local); - WIL(for_node->token, WI_LOCAL_GET, end_ptr_local); - if (elem_size != 1) { - WID(for_node->token, WI_PTR_CONST, elem_size); - WI(for_node->token, WI_PTR_MUL); - } - WI(for_node->token, WI_PTR_ADD); - WIL(for_node->token, WI_LOCAL_SET, end_ptr_local); - - emit_enter_structured_block(mod, &code, SBT_Breakable_Block, for_node->token); - emit_enter_structured_block(mod, &code, SBT_Basic_Loop, for_node->token); - emit_enter_structured_block(mod, &code, SBT_Continue_Block, for_node->token); - - WIL(for_node->token, WI_LOCAL_GET, ptr_local); - WIL(for_node->token, WI_LOCAL_GET, end_ptr_local); - WI(for_node->token, WI_PTR_GE); - WID(for_node->token, WI_COND_JUMP, 0x02); - - if (!for_node->by_pointer) { - if (!it_is_local) emit_local_location(mod, &code, var, &offset); - - WIL(for_node->token, WI_LOCAL_GET, ptr_local); - emit_load_instruction(mod, &code, var->type, 0); - - if (!it_is_local) emit_store_instruction(mod, &code, var->type, offset); - else WIL(for_node->token, WI_LOCAL_SET, iter_local); - } - - emit_block(mod, &code, for_node->stmt, 0); - - emit_leave_structured_block(mod, &code); - - WIL(for_node->token, WI_LOCAL_GET, ptr_local); - WIL(for_node->token, WI_PTR_CONST, elem_size); - WI(for_node->token, WI_PTR_ADD); - WIL(for_node->token, WI_LOCAL_SET, ptr_local); - - if (bh_arr_last(code).type != WI_JUMP) - WID(for_node->token, WI_JUMP, 0x00); - - emit_leave_structured_block(mod, &code); - emit_leave_structured_block(mod, &code); - - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - if (!for_node->by_pointer) local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - - *pcode = code; -} - -EMIT_FUNC(for_iterator, AstFor* for_node, u64 iter_local) { - bh_arr(WasmInstruction) code = *pcode; - - // Allocate temporaries for iterator contents - u64 iterator_data_ptr = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - u64 iterator_next_func = local_raw_allocate(mod->local_alloc, WASM_TYPE_FUNC); - u64 iterator_close_func = local_raw_allocate(mod->local_alloc, WASM_TYPE_FUNC); - u64 iterator_remove_func = local_raw_allocate(mod->local_alloc, WASM_TYPE_FUNC); - u64 iterator_done_bool = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32); - WIL(for_node->token, WI_LOCAL_SET, iterator_remove_func); - WIL(for_node->token, WI_LOCAL_SET, iterator_close_func); - WIL(for_node->token, WI_LOCAL_SET, iterator_next_func); - WIL(for_node->token, WI_LOCAL_SET, iterator_data_ptr); - - - { - // - // This pushes an entry onto the stack of for loops that have - // are iterator that can have a '#remove' directive in them. - ForRemoveInfo remove_info; - remove_info.iterator_data_ptr = iterator_data_ptr; - remove_info.iterator_remove_func = iterator_remove_func; - - TypeWithOffset remove_func_type; - type_linear_member_lookup(for_node->iter->type, 3, &remove_func_type); - remove_info.remove_func_type_idx = generate_type_idx(mod, remove_func_type.type); - - bh_arr_push(mod->for_remove_info, remove_info); - } - - AstLocal* var = for_node->var; - b32 it_is_local = (b32) ((iter_local & LOCAL_IS_WASM) != 0); - u64 offset = 0; - - // Enter a deferred statement for the auto-close - emit_enter_structured_block(mod, &code, SBT_Basic_Block, for_node->token); - - if (!for_node->no_close) { - TypeWithOffset close_func_type; - type_linear_member_lookup(for_node->iter->type, 2, &close_func_type); - i32 close_type_idx = generate_type_idx(mod, close_func_type.type); - - WasmInstruction* close_instructions = bh_alloc_array(global_heap_allocator, WasmInstruction, 8); - close_instructions[0] = (WasmInstruction) { WI_LOCAL_GET, { .l = iterator_close_func } }; - close_instructions[1] = (WasmInstruction) { WI_I32_CONST, { .l = mod->null_proc_func_idx } }; - close_instructions[2] = (WasmInstruction) { WI_I32_NE, { .l = 0x00 } }; - close_instructions[3] = (WasmInstruction) { WI_IF_START, { .l = 0x40 } }; - close_instructions[4] = (WasmInstruction) { WI_LOCAL_GET, { .l = iterator_data_ptr } }; - close_instructions[5] = (WasmInstruction) { WI_LOCAL_GET, { .l = iterator_close_func } }; - close_instructions[6] = (WasmInstruction) { WI_CALL_INDIRECT, { .l = close_type_idx } }; - close_instructions[7] = (WasmInstruction) { WI_IF_END, { .l = 0x00 } }; - - emit_defer_code(mod, &code, close_instructions, 8); - } - - emit_enter_structured_block(mod, &code, SBT_Breakable_Block, for_node->token); - emit_enter_structured_block(mod, &code, SBT_Continue_Loop, for_node->token); - - if (!it_is_local) emit_local_location(mod, &code, var, &offset); - - { - WIL(for_node->token, WI_LOCAL_GET, iterator_data_ptr); - WIL(for_node->token, WI_LOCAL_GET, iterator_next_func); - - // CLEANUP: Calling a function is way too f-ing complicated. FACTOR IT!! - u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top); - - TypeWithOffset next_func_type; - type_linear_member_lookup(for_node->iter->type, 1, &next_func_type); - Type* return_type = next_func_type.type->Function.return_type; - - u32 return_size = type_size_of(return_type); - u32 return_align = type_alignment_of(return_type); - bh_align(return_size, return_align); - - u64 reserve_size = return_size; - bh_align(reserve_size, 16); - - WID(for_node->token, WI_GLOBAL_GET, stack_top_idx); - WID(for_node->token, WI_PTR_CONST, reserve_size); - WI(for_node->token, WI_PTR_ADD); - WID(for_node->token, WI_GLOBAL_SET, stack_top_idx); - - i32 type_idx = generate_type_idx(mod, next_func_type.type); - WID(for_node->token, WI_CALL_INDIRECT, ((WasmInstructionData) { type_idx, 0x00 })); - - WID(for_node->token, WI_GLOBAL_GET, stack_top_idx); - WID(for_node->token, WI_PTR_CONST, reserve_size); - WI(for_node->token, WI_PTR_SUB); - WID(for_node->token, WI_GLOBAL_SET, stack_top_idx); - - WID(for_node->token, WI_GLOBAL_GET, stack_top_idx); - emit_load_instruction(mod, &code, return_type, reserve_size - return_size); - } - - WIL(for_node->token, WI_LOCAL_SET, iterator_done_bool); - - if (!it_is_local) emit_store_instruction(mod, &code, var->type, offset); - else WIL(for_node->token, WI_LOCAL_SET, iter_local); - - WIL(for_node->token, WI_LOCAL_GET, iterator_done_bool); - WI(for_node->token, WI_I32_EQZ); - WID(for_node->token, WI_COND_JUMP, 0x01); - - emit_block(mod, &code, for_node->stmt, 0); - WID(for_node->token, WI_JUMP, 0x00); - - emit_leave_structured_block(mod, &code); - emit_leave_structured_block(mod, &code); - - emit_deferred_stmts(mod, &code); - emit_leave_structured_block(mod, &code); - - bh_arr_pop(mod->for_remove_info); - - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - local_raw_free(mod->local_alloc, WASM_TYPE_FUNC); - local_raw_free(mod->local_alloc, WASM_TYPE_FUNC); - local_raw_free(mod->local_alloc, WASM_TYPE_INT32); - *pcode = code; -} - -EMIT_FUNC(for, AstFor* for_node) { - bh_arr(WasmInstruction) code = *pcode; - - AstLocal* var = for_node->var; - u64 iter_local = local_allocate(mod->local_alloc, (AstTyped *) var); - bh_imap_put(&mod->local_map, (u64) var, iter_local); - - debug_enter_symbol_frame(mod); - debug_introduce_symbol(mod, var->token, - local_is_wasm_local((AstTyped *) var) ? DSL_REGISTER : DSL_STACK, - iter_local, var->type); - - emit_expression(mod, &code, for_node->iter); - - switch (for_node->loop_type) { - case For_Loop_Range: emit_for_range(mod, &code, for_node, iter_local); break; - case For_Loop_Array: emit_for_array(mod, &code, for_node, iter_local); break; - // NOTE: A dynamic array is just a slice with a capacity and allocator on the end. - // Just dropping the extra fields will mean we can just use the slice implementation. - // - brendanfh 2020/09/04 - // - brendanfh 2021/04/13 - case For_Loop_DynArr: WI(for_node->token, WI_DROP); WI(for_node->token, WI_DROP); WI(for_node->token, WI_DROP); - case For_Loop_Slice: emit_for_slice(mod, &code, for_node, iter_local); break; - case For_Loop_Iterator: emit_for_iterator(mod, &code, for_node, iter_local); break; - default: onyx_report_error(for_node->token->pos, Error_Critical, "Invalid for loop type. You should probably not be seeing this..."); - } - - local_free(mod->local_alloc, (AstTyped *) var); - debug_leave_symbol_frame(mod); - - *pcode = code; -} - -EMIT_FUNC(switch, AstSwitch* switch_node) { - bh_arr(WasmInstruction) code = *pcode; - - bh_imap block_map; - bh_imap_init(&block_map, global_heap_allocator, bh_arr_length(switch_node->cases)); - - if (switch_node->initialization != NULL) { - forll (AstNode, stmt, switch_node->initialization, next) { - emit_statement(mod, &code, stmt); - } - } - - emit_enter_structured_block(mod, &code, SBT_Breakable_Block, switch_node->token); - - u64 block_num = 0; - bh_arr_each(AstSwitchCase *, sc, switch_node->cases) { - if (bh_imap_has(&block_map, (u64) (*sc)->block)) continue; - - emit_enter_structured_block(mod, &code, SBT_Fallthrough_Block, (*sc)->block->token); - - bh_imap_put(&block_map, (u64) (*sc)->block, block_num); - block_num++; - } - - switch (switch_node->switch_kind) { - case Switch_Kind_Integer: { - u64 count = switch_node->max_case + 1 - switch_node->min_case; - BranchTable* bt = bh_alloc(mod->extended_instr_alloc, sizeof(BranchTable) + sizeof(u32) * count); - bt->count = count; - bt->default_case = block_num; - fori (i, 0, bt->count) bt->cases[i] = bt->default_case; - - bh_arr_each(bh__imap_entry, sc, switch_node->case_map.entries) { - bt->cases[sc->key - switch_node->min_case] = bh_imap_get(&block_map, (u64) sc->value); - } - - // NOTE: We enter a new block here in order to setup the correct - // indicies for the jump targets in the branch table. For example, - // - // - // jump_table - // label0: - // ... - // label1: - // ... - // - // If we didn't enter a new block, then jumping to label 0, would jump - // to the second block, and so on. - WID(switch_node->expr->token, WI_BLOCK_START, 0x40); - emit_expression(mod, &code, switch_node->expr); - if (switch_node->min_case != 0) { - WID(switch_node->expr->token, WI_I32_CONST, switch_node->min_case); - WI(switch_node->expr->token, WI_I32_SUB); - } - WIP(switch_node->expr->token, WI_JUMP_TABLE, bt); - WI(switch_node->expr->token, WI_BLOCK_END); - break; - } - - case Switch_Kind_Use_Equals: { - WID(switch_node->expr->token, WI_BLOCK_START, 0x40); - - bh_arr_each(CaseToBlock, ctb, switch_node->case_exprs) { - emit_expression(mod, &code, (AstTyped *) ctb->comparison); - - u64 bn = bh_imap_get(&block_map, (u64) ctb->block); - WID(switch_node->expr->token, WI_IF_START, 0x40); - WID(switch_node->expr->token, WI_JUMP, bn + 1); - WI(switch_node->expr->token, WI_IF_END); - } - - WID(switch_node->expr->token, WI_JUMP, block_num); - WI(switch_node->expr->token, WI_BLOCK_END); - break; - } - } - - bh_arr_each(AstSwitchCase *, psc, switch_node->cases) { - AstSwitchCase *sc = *psc; - if (bh_imap_get(&block_map, (u64) sc->block) == 0xdeadbeef) continue; - - u64 bn = bh_imap_get(&block_map, (u64) sc->block); - - // Maybe the Symbol Frame idea should be controlled as a block_flag? - debug_enter_symbol_frame(mod); - emit_block(mod, &code, sc->block, 0); - debug_leave_symbol_frame(mod); - - if (bh_arr_last(code).type != WI_JUMP) - WID(NULL, WI_JUMP, block_num - bn); - - emit_leave_structured_block(mod, &code); - - bh_imap_put(&block_map, (u64) sc->block, 0xdeadbeef); - } - - if (switch_node->default_case != NULL) { - emit_block(mod, &code, switch_node->default_case, 0); - } - - emit_leave_structured_block(mod, &code); - - bh_imap_free(&block_map); - *pcode = code; -} - -EMIT_FUNC(defer, AstDefer* defer) { - bh_arr_push(mod->deferred_stmts, ((DeferredStmt) { - .type = Deferred_Stmt_Node, - .depth = bh_arr_length(mod->structured_jump_target), - .defer_node= defer, - .stmt = defer->stmt, - })); -} - -EMIT_FUNC(defer_code, WasmInstruction* deferred_code, u32 code_count) { - bh_arr_push(mod->deferred_stmts, ((DeferredStmt) { - .type = Deferred_Stmt_Code, - .depth = bh_arr_length(mod->structured_jump_target), - .defer_node= NULL, - .instructions = deferred_code, - .instruction_count = code_count, - })); -} - -EMIT_FUNC(deferred_stmt, DeferredStmt deferred_stmt) { - bh_arr(WasmInstruction) code = *pcode; - - if (deferred_stmt.defer_node) { - WI(deferred_stmt.defer_node->token, WI_NOP); - } - - switch (deferred_stmt.type) { - case Deferred_Stmt_Node: emit_statement(mod, &code, deferred_stmt.stmt); break; - case Deferred_Stmt_Code: { - fori (i, 0, deferred_stmt.instruction_count) { - WIR(NULL, deferred_stmt.instructions[i]); - } - break; - } - } - - *pcode = code; -} - -EMIT_FUNC_NO_ARGS(deferred_stmts) { - if (bh_arr_length(mod->deferred_stmts) <= 0) return; - - u64 depth = bh_arr_length(mod->structured_jump_target); - - while (bh_arr_length(mod->deferred_stmts) > 0 && bh_arr_last(mod->deferred_stmts).depth >= depth) { - DeferredStmt stmt = bh_arr_pop(mod->deferred_stmts); - emit_deferred_stmt(mod, pcode, stmt); - } -} - -EMIT_FUNC(remove_directive, AstDirectiveRemove* remove) { - assert(bh_arr_length(mod->for_remove_info) > 0); - - bh_arr(WasmInstruction) code = *pcode; - - ForRemoveInfo remove_info = bh_arr_last(mod->for_remove_info); - - WIL(remove->token, WI_LOCAL_GET, remove_info.iterator_remove_func); - WIL(remove->token, WI_I32_CONST, mod->null_proc_func_idx); - WI(remove->token, WI_I32_NE); - WID(remove->token, WI_IF_START, 0x40); - WIL(remove->token, WI_LOCAL_GET, remove_info.iterator_data_ptr); - WIL(remove->token, WI_LOCAL_GET, remove_info.iterator_remove_func); - WIL(remove->token, WI_CALL_INDIRECT, remove_info.remove_func_type_idx); - WI(remove->token, WI_IF_END); - - *pcode = code; -} - -// NOTE: These need to be in the same order as -// the OnyxBinaryOp enum -static const WasmInstructionType binop_map[][4] = { - // I32 I64 F32 F64 - /* ADD */ { WI_I32_ADD, WI_I64_ADD, WI_F32_ADD, WI_F64_ADD }, - /* SUB */ { WI_I32_SUB, WI_I64_SUB, WI_F32_SUB, WI_F64_SUB }, - /* MUL */ { WI_I32_MUL, WI_I64_MUL, WI_F32_MUL, WI_F64_MUL }, - /* DIV */ { WI_I32_DIV_S, WI_I64_DIV_S, WI_F32_DIV, WI_F64_DIV }, - /* REM */ { WI_I32_REM_S, WI_I64_REM_S, WI_NOP, WI_NOP }, - - /* EQ */ { WI_I32_EQ, WI_I64_EQ, WI_F32_EQ, WI_F64_EQ }, - /* NEQ */ { WI_I32_NE, WI_I64_NE, WI_F32_NE , WI_F64_NE }, - /* LT */ { WI_I32_LT_S, WI_I64_LT_S, WI_F32_LT, WI_F64_LT }, - /* LTE */ { WI_I32_LE_S, WI_I64_LE_S, WI_F32_LE, WI_F64_LE }, - /* GT */ { WI_I32_GT_S, WI_I64_GT_S, WI_F32_GT, WI_F64_GT }, - /* GTE */ { WI_I32_GE_S, WI_I64_GE_S, WI_F32_GE, WI_F64_GE }, - - /* AND */ { WI_I32_AND, WI_I64_AND, WI_NOP, WI_NOP }, - /* OR */ { WI_I32_OR, WI_I64_OR, WI_NOP, WI_NOP }, - /* XOR */ { WI_I32_XOR, WI_I64_XOR, WI_NOP, WI_NOP }, - /* SHL */ { WI_I32_SHL, WI_I64_SHL, WI_NOP, WI_NOP }, - /* SHR */ { WI_I32_SHR_U, WI_I64_SHR_U, WI_NOP, WI_NOP }, - /* SAR */ { WI_I32_SHR_S, WI_I64_SHR_S, WI_NOP, WI_NOP }, - - /* BAND */ { WI_I32_AND, WI_I64_AND, WI_NOP, WI_NOP }, - /* BOR */ { WI_I32_OR, WI_I64_OR, WI_NOP, WI_NOP }, -}; - -EMIT_FUNC(binop, AstBinaryOp* binop) { - bh_arr(WasmInstruction) code = *pcode; - - if (binop_is_assignment(binop->operation)) { - emit_assignment(mod, &code, binop); - *pcode = code; - return; - } - - b32 is_sign_significant = 0; - switch (binop->operation) { - case Binary_Op_Divide: case Binary_Op_Modulus: - case Binary_Op_Less: case Binary_Op_Less_Equal: - case Binary_Op_Greater: case Binary_Op_Greater_Equal: - is_sign_significant = 1; - } - - WasmType operator_type = onyx_type_to_wasm_type(binop->left->type); - i32 optype = 0; - if (operator_type == WASM_TYPE_INT32) optype = 0; - else if (operator_type == WASM_TYPE_INT64) optype = 1; - else if (operator_type == WASM_TYPE_FLOAT32) optype = 2; - else if (operator_type == WASM_TYPE_FLOAT64) optype = 3; - - WasmInstructionType binop_instr = binop_map[(i32) binop->operation][optype]; - - assert(binop_instr != WI_NOP); - - // NOTE: Use unsigned variant if needed - // Unsigned instructions are always right after - // the signed equivalent - if (is_sign_significant) { - if (binop->left->type->Basic.flags & Basic_Flag_Unsigned) { - binop_instr = (WasmInstructionType) ((i32) binop_instr + 1); - } - } - - emit_expression(mod, &code, binop->left); - emit_expression(mod, &code, binop->right); - - WI(binop->token, binop_instr); - - *pcode = code; -} - -EMIT_FUNC(unaryop, AstUnaryOp* unop) { - bh_arr(WasmInstruction) code = *pcode; - - switch (unop->operation) { - case Unary_Op_Negate: { - TypeBasic* type = &unop->type->Basic; - - if (type->kind == Basic_Kind_I32 - || type->kind == Basic_Kind_I16 - || type->kind == Basic_Kind_I8) { - WID(unop->token, WI_I32_CONST, 0x00); - emit_expression(mod, &code, unop->expr); - WI(unop->token, WI_I32_SUB); - - } - else if (type->kind == Basic_Kind_I64) { - WID(unop->token, WI_I64_CONST, 0x00); - emit_expression(mod, &code, unop->expr); - WI(unop->token, WI_I64_SUB); - - } - else { - emit_expression(mod, &code, unop->expr); - - if (type->kind == Basic_Kind_F32) WI(unop->token, WI_F32_NEG); - if (type->kind == Basic_Kind_F64) WI(unop->token, WI_F64_NEG); - } - - break; - } - - case Unary_Op_Not: - emit_expression(mod, &code, unop->expr); - - WI(unop->token, WI_I32_EQZ); - break; - - case Unary_Op_Bitwise_Not: { - emit_expression(mod, &code, unop->expr); - - TypeBasic* type = &unop->type->Basic; - - if (type->kind == Basic_Kind_I8 || type->kind == Basic_Kind_U8) { - WID(unop->token, WI_I32_CONST, 0xff); - WI(unop->token, WI_I32_XOR); - } - else if (type->kind == Basic_Kind_I16 || type->kind == Basic_Kind_U16) { - WID(unop->token, WI_I32_CONST, 0xffff); - WI(unop->token, WI_I32_XOR); - } - else if (type->kind == Basic_Kind_I32 || type->kind == Basic_Kind_U32) { - WID(unop->token, WI_I32_CONST, 0xffffffff); - WI(unop->token, WI_I32_XOR); - } - else if (type->kind == Basic_Kind_I64 || type->kind == Basic_Kind_U64) { - WIL(unop->token, WI_I64_CONST, 0xffffffffffffffff); - WI(unop->token, WI_I64_XOR); - } - - break; - } - - case Unary_Op_Auto_Cast: - case Unary_Op_Cast: emit_cast(mod, &code, unop); break; - } - - *pcode = code; -} - -// Calling a procedure in Onyx. -// -// This documentation should be placed elsewhere, but for right now I'm going to write it in the relevant -// piece of code. Calling a procedure is relatively simple, at least compared to other calling conventions -// out there, mostly due the fact that this is WebAssembly, where registers are "infinite" and there's no -// really need to use stack canaries for security. -// -// The biggest piece to understand is how the stack gets laid out for the called procedure. To be confusing, -// there are two stacks at play: the WASM expression stack, and the linear memory stack. Here is the general -// lay out for calling a procedure with the following signature. -// -// foo :: (x: i32, y: str, z: [..] i32, va: ..i32) -> (i32, i32) -// -// WASM stack: top is last pushed -// -// vararg count -// vararg pointer to variadic arguments in the linear memory -// pointer to struct-like arguments (z) -// simple structs/primitives (y, x) -// -// Linear memory stack: -// -// ... | struct-like arguments (z) | variadic arguments (va) | return space | ... -// -// The interesting part from above is the fact that 'y' gets passed on the WASM stack, not the linear memory -// stack, even though a 'str' in Onyx is 2-component structure. This is because so-called "simple" structures, -// i.e. structures that are completely flat, with no sub-structures, are passed as multiple primitives. I do -// this because many times for interoperability, it is nicer to get two primitive values for the pointer and -// count of a slice, instead of a pointer. -EMIT_FUNC(call, AstCall* call) { - bh_arr(WasmInstruction) code = *pcode; - - u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top); - u64 stack_top_store_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - - OnyxToken* call_token = call->token; - - // Because it would be inefficient to increment and decrement the global stack pointer for every argument, - // a simple set of instructions increments it once to the size it will need to be. However, because it is - // impossible to know what size the reserved memory will be, a location patch is taken in order to fill it - // in later. - u32 reserve_space_patch = bh_arr_length(code); - WID(call_token, WI_GLOBAL_GET, stack_top_idx); - WIL(call_token, WI_LOCAL_TEE, stack_top_store_local); - WID(call_token, WI_PTR_CONST, 0); // This will be filled in later. - WI(call_token, WI_PTR_ADD); - WID(call_token, WI_GLOBAL_SET, stack_top_idx); - - u32 reserve_size = 0; - u32 vararg_count = 0; - i32 vararg_offset = -1; - - u32* vararg_any_offsets=NULL; - u32* vararg_any_types=NULL; - if (call->va_kind == VA_Kind_Any) { - vararg_any_offsets = bh_alloc_array(global_scratch_allocator, u32, bh_arr_length(call->args.values)); - vararg_any_types = bh_alloc_array(global_scratch_allocator, u32, bh_arr_length(call->args.values)); - } - - bh_arr_each(AstTyped *, parg, call->args.values) { - AstArgument* arg = (AstArgument *) *parg; - if (arg->is_baked) continue; - - b32 place_on_stack = 0; - - if (type_get_param_pass(arg->value->type) == Param_Pass_By_Implicit_Pointer) { - // This arguments needs to be written to the stack because it is not a simple structure. - place_on_stack = 1; - } - - if (arg->va_kind != VA_Kind_Not_VA) { - // This is a variadic argument and needs to be written to the stack. If the starting - // location of the vararg array hasn't been noted, note it. - if (vararg_offset < 0) vararg_offset = reserve_size; - - place_on_stack = 1; - vararg_count += 1; - } - - if (arg->pass_as_any) { - place_on_stack = 1; - } - - if (place_on_stack) WIL(call_token, WI_LOCAL_GET, stack_top_store_local); - - emit_expression(mod, &code, arg->value); - - if (place_on_stack) { - emit_store_instruction(mod, &code, arg->value->type, reserve_size); - - if (arg->va_kind == VA_Kind_Not_VA) { - // Non-variadic arguments on the stack need a pointer to them placed on the WASM stack. - WIL(call_token, WI_LOCAL_GET, stack_top_store_local); - WID(call_token, WI_PTR_CONST, reserve_size); - WI(call_token, WI_PTR_ADD); - } - - if (arg->va_kind == VA_Kind_Any) { - vararg_any_offsets[vararg_count - 1] = reserve_size; - vararg_any_types[vararg_count - 1] = arg->value->type->id; - } - - if (arg->pass_as_any) { - WIL(call_token, WI_I32_CONST, arg->value->type->id); - } - - reserve_size += type_size_of(arg->value->type); - } - } - - switch (call->va_kind) { - case VA_Kind_Any: { - vararg_offset = reserve_size; - - i32 any_size = type_size_of(type_build_from_ast(context.ast_alloc, builtin_any_type)); - - fori (i, 0, vararg_count) { - WIL(call_token, WI_LOCAL_GET, stack_top_store_local); - WIL(call_token, WI_LOCAL_GET, stack_top_store_local); - WID(call_token, WI_PTR_CONST, vararg_any_offsets[i]); - WI(call_token, WI_PTR_ADD); - emit_store_instruction(mod, &code, &basic_types[Basic_Kind_Rawptr], vararg_offset + i * any_size); - - WIL(call_token, WI_LOCAL_GET, stack_top_store_local); - WID(call_token, WI_I32_CONST, vararg_any_types[i]); - emit_store_instruction(mod, &code, &basic_types[Basic_Kind_Type_Index], vararg_offset + i * any_size + POINTER_SIZE); - - reserve_size += any_size; - } - - // fallthrough - } - - case VA_Kind_Typed: { - WIL(call_token, WI_LOCAL_GET, stack_top_store_local); - if (vararg_offset > 0) { - WID(call_token, WI_PTR_CONST, vararg_offset); - WI(call_token, WI_PTR_ADD); - } - WID(call_token, WI_I32_CONST, vararg_count); - break; - } - - case VA_Kind_Untyped: { - WIL(call_token, WI_LOCAL_GET, stack_top_store_local); - WIL(call_token, WI_LOCAL_GET, stack_top_store_local); - if (vararg_offset > 0) { - WID(call_token, WI_PTR_CONST, vararg_offset); - WI(call_token, WI_PTR_ADD); - } - emit_store_instruction(mod, &code, &basic_types[Basic_Kind_Rawptr], reserve_size); - - // NOTE: There may be 4 uninitialized bytes here, because pointers are only 4 bytes in WASM. - - WIL(call_token, WI_LOCAL_GET, stack_top_store_local); - WID(call_token, WI_I32_CONST, vararg_count); - emit_store_instruction(mod, &code, &basic_types[Basic_Kind_I32], reserve_size + POINTER_SIZE); - - WIL(call_token, WI_LOCAL_GET, stack_top_store_local); - if (reserve_size > 0) { - WID(call_token, WI_PTR_CONST, reserve_size); - WI(call_token, WI_PTR_ADD); - } - - reserve_size += 4 + POINTER_SIZE; - break; - } - } - - CallingConvention cc = type_function_get_cc(call->callee->type); - assert(cc != CC_Undefined); - - Type* return_type = call->callee->type->Function.return_type; - u32 return_size = type_size_of(return_type); - assert(return_size % type_alignment_of(return_type) == 0); - - if (cc == CC_Return_Stack) reserve_size += return_size; - - if (call->callee->kind == Ast_Kind_Function) { - i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) call->callee); - WIL(NULL, WI_CALL, func_idx); - - } else { - emit_expression(mod, &code, call->callee); - - i32 type_idx = generate_type_idx(mod, call->callee->type); - WID(NULL, WI_CALL_INDIRECT, ((WasmInstructionData) { type_idx, 0x00 })); - } - - if (reserve_size > 0) { - WIL(call_token, WI_LOCAL_GET, stack_top_store_local); - WID(call_token, WI_GLOBAL_SET, stack_top_idx); - - bh_align(reserve_size, 16); - code[reserve_space_patch + 2].data.l = reserve_size; - - } else { - fori (i, 0, 5) code[reserve_space_patch + i].type = WI_NOP; - } - - if (cc == CC_Return_Stack) { - WID(call_token, WI_GLOBAL_GET, stack_top_idx); - emit_load_instruction(mod, &code, return_type, reserve_size - return_size); - } - - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - *pcode = code; -} - -// BUG: This implementation assumes that the host system C's implementation is using -// little endian integers. -#define SIMD_INT_CONST_INTRINSIC(type, count) { \ - type* byte_buffer = bh_alloc(mod->extended_instr_alloc, 16); \ - bh_arr(AstArgument *) arg_arr = (bh_arr(AstArgument *)) call->args.values; \ - fori (i, 0, count) { \ - if (arg_arr[i]->value->kind != Ast_Kind_NumLit) { \ - onyx_report_error(arg_arr[i]->token->pos, Error_Critical, \ - "SIMD constants expect compile time constants as parameters. The %d%s parameter was not.", \ - i, bh_num_suffix(i)); \ - *pcode = code; \ - return; \ - } \ - byte_buffer[i] = (type) ((AstNumLit *) arg_arr[i]->value)->value.l; \ - } \ - WIP(call->token, WI_V128_CONST, byte_buffer); \ - } - -#define SIMD_EXTRACT_LANE_INSTR(instr, arg_arr) \ - emit_expression(mod, &code, arg_arr[0]->value);\ - if (arg_arr[1]->value->kind != Ast_Kind_NumLit) { \ - onyx_report_error(arg_arr[1]->token->pos, Error_Critical, "SIMD lane instructions expect a compile time lane number."); \ - *pcode = code; \ - return; \ - } \ - WID(call->token, instr, (u8) ((AstNumLit *) arg_arr[1]->value)->value.i); - -#define SIMD_REPLACE_LANE_INSTR(instr, arg_arr) { \ - emit_expression(mod, &code, arg_arr[0]->value);\ - if (arg_arr[1]->value->kind != Ast_Kind_NumLit) { \ - onyx_report_error(arg_arr[1]->token->pos, Error_Critical, "SIMD lane instructions expect a compile time lane number."); \ - *pcode = code; \ - return; \ - } \ - u8 lane = (u8) ((AstNumLit *) arg_arr[1]->value)->value.i; \ - emit_expression(mod, &code, arg_arr[2]->value); \ - WID(call->token, instr, lane); \ -} - - -EMIT_FUNC(intrinsic_call, AstCall* call) { - bh_arr(WasmInstruction) code = *pcode; - - b32 place_arguments_normally = 1; - - switch (call->intrinsic) { - case ONYX_INTRINSIC_V128_CONST: - case ONYX_INTRINSIC_I8X16_CONST: case ONYX_INTRINSIC_I16X8_CONST: - case ONYX_INTRINSIC_I32X4_CONST: case ONYX_INTRINSIC_I64X2_CONST: - case ONYX_INTRINSIC_F32X4_CONST: case ONYX_INTRINSIC_F64X2_CONST: - case ONYX_INTRINSIC_I8X16_EXTRACT_LANE_S: case ONYX_INTRINSIC_I8X16_EXTRACT_LANE_U: - case ONYX_INTRINSIC_I16X8_EXTRACT_LANE_S: case ONYX_INTRINSIC_I16X8_EXTRACT_LANE_U: - case ONYX_INTRINSIC_I32X4_EXTRACT_LANE: case ONYX_INTRINSIC_I64X2_EXTRACT_LANE: - case ONYX_INTRINSIC_F32X4_EXTRACT_LANE: case ONYX_INTRINSIC_F64X2_EXTRACT_LANE: - case ONYX_INTRINSIC_I8X16_REPLACE_LANE: case ONYX_INTRINSIC_I16X8_REPLACE_LANE: - case ONYX_INTRINSIC_I32X4_REPLACE_LANE: case ONYX_INTRINSIC_I64X2_REPLACE_LANE: - case ONYX_INTRINSIC_F32X4_REPLACE_LANE: case ONYX_INTRINSIC_F64X2_REPLACE_LANE: - case ONYX_INTRINSIC_I8X16_SHUFFLE: - place_arguments_normally = 0; - - default: break; - } - - if (place_arguments_normally) { - bh_arr_each(AstTyped *, arg, call->args.values) { - emit_expression(mod, &code, *arg); - } - } - - switch (call->intrinsic) { - case ONYX_INTRINSIC_MEMORY_SIZE: WID(call->token, WI_MEMORY_SIZE, 0x00); break; - case ONYX_INTRINSIC_MEMORY_GROW: WID(call->token, WI_MEMORY_GROW, 0x00); break; - case ONYX_INTRINSIC_MEMORY_COPY: - if (context.options->use_post_mvp_features) { - WIL(call->token, WI_MEMORY_COPY, 0x00); - } else { - emit_intrinsic_memory_copy(mod, &code); - } - break; - case ONYX_INTRINSIC_MEMORY_FILL: - if (context.options->use_post_mvp_features) { - WIL(call->token, WI_MEMORY_FILL, 0x00); - } else { - emit_intrinsic_memory_fill(mod, &code); - } - break; - - case ONYX_INTRINSIC_INITIALIZE: { - Type* type_to_initialize = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; - emit_initialize_type(mod, &code, type_to_initialize, call->token); - break; - } - - case ONYX_INTRINSIC_I32_CLZ: WI(call->token, WI_I32_CLZ); break; - case ONYX_INTRINSIC_I32_CTZ: WI(call->token, WI_I32_CTZ); break; - case ONYX_INTRINSIC_I32_POPCNT: WI(call->token, WI_I32_POPCNT); break; - case ONYX_INTRINSIC_I32_AND: WI(call->token, WI_I32_AND); break; - case ONYX_INTRINSIC_I32_OR: WI(call->token, WI_I32_OR); break; - case ONYX_INTRINSIC_I32_XOR: WI(call->token, WI_I32_XOR); break; - case ONYX_INTRINSIC_I32_SHL: WI(call->token, WI_I32_SHL); break; - case ONYX_INTRINSIC_I32_SLR: WI(call->token, WI_I32_SHR_U); break; - case ONYX_INTRINSIC_I32_SAR: WI(call->token, WI_I32_SHR_S); break; - case ONYX_INTRINSIC_I32_ROTL: WI(call->token, WI_I32_ROTL); break; - case ONYX_INTRINSIC_I32_ROTR: WI(call->token, WI_I32_ROTR); break; - - case ONYX_INTRINSIC_I64_CLZ: WI(call->token, WI_I64_CLZ); break; - case ONYX_INTRINSIC_I64_CTZ: WI(call->token, WI_I64_CTZ); break; - case ONYX_INTRINSIC_I64_POPCNT: WI(call->token, WI_I64_POPCNT); break; - case ONYX_INTRINSIC_I64_AND: WI(call->token, WI_I64_AND); break; - case ONYX_INTRINSIC_I64_OR: WI(call->token, WI_I64_OR); break; - case ONYX_INTRINSIC_I64_XOR: WI(call->token, WI_I64_XOR); break; - case ONYX_INTRINSIC_I64_SHL: WI(call->token, WI_I64_SHL); break; - case ONYX_INTRINSIC_I64_SLR: WI(call->token, WI_I64_SHR_U); break; - case ONYX_INTRINSIC_I64_SAR: WI(call->token, WI_I64_SHR_S); break; - case ONYX_INTRINSIC_I64_ROTL: WI(call->token, WI_I64_ROTL); break; - case ONYX_INTRINSIC_I64_ROTR: WI(call->token, WI_I64_ROTR); break; - - case ONYX_INTRINSIC_F32_ABS: WI(call->token, WI_F32_ABS); break; - case ONYX_INTRINSIC_F32_CEIL: WI(call->token, WI_F32_CEIL); break; - case ONYX_INTRINSIC_F32_FLOOR: WI(call->token, WI_F32_FLOOR); break; - case ONYX_INTRINSIC_F32_TRUNC: WI(call->token, WI_F32_TRUNC); break; - case ONYX_INTRINSIC_F32_NEAREST: WI(call->token, WI_F32_NEAREST); break; - case ONYX_INTRINSIC_F32_SQRT: WI(call->token, WI_F32_SQRT); break; - case ONYX_INTRINSIC_F32_MIN: WI(call->token, WI_F32_MIN); break; - case ONYX_INTRINSIC_F32_MAX: WI(call->token, WI_F32_MAX); break; - case ONYX_INTRINSIC_F32_COPYSIGN: WI(call->token, WI_F32_COPYSIGN); break; - - case ONYX_INTRINSIC_F64_ABS: WI(call->token, WI_F64_ABS); break; - case ONYX_INTRINSIC_F64_CEIL: WI(call->token, WI_F64_CEIL); break; - case ONYX_INTRINSIC_F64_FLOOR: WI(call->token, WI_F64_FLOOR); break; - case ONYX_INTRINSIC_F64_TRUNC: WI(call->token, WI_F64_TRUNC); break; - case ONYX_INTRINSIC_F64_NEAREST: WI(call->token, WI_F64_NEAREST); break; - case ONYX_INTRINSIC_F64_SQRT: WI(call->token, WI_F64_SQRT); break; - case ONYX_INTRINSIC_F64_MIN: WI(call->token, WI_F64_MIN); break; - case ONYX_INTRINSIC_F64_MAX: WI(call->token, WI_F64_MAX); break; - case ONYX_INTRINSIC_F64_COPYSIGN: WI(call->token, WI_F64_COPYSIGN); break; - - case ONYX_INTRINSIC_I8X16_CONST: - case ONYX_INTRINSIC_V128_CONST: SIMD_INT_CONST_INTRINSIC(u8, 16); break; - case ONYX_INTRINSIC_I16X8_CONST: SIMD_INT_CONST_INTRINSIC(u16, 8); break; - case ONYX_INTRINSIC_I32X4_CONST: SIMD_INT_CONST_INTRINSIC(u32, 4); break; - case ONYX_INTRINSIC_I64X2_CONST: SIMD_INT_CONST_INTRINSIC(u64, 2); break; - case ONYX_INTRINSIC_F32X4_CONST: { - f32* byte_buffer = bh_alloc(mod->extended_instr_alloc, 16); - bh_arr(AstArgument *) arg_arr = (bh_arr(AstArgument *)) call->args.values; - fori (i, 0, 4) { - if (arg_arr[i]->value->kind != Ast_Kind_NumLit) { - onyx_report_error(arg_arr[i]->token->pos, Error_Critical, - "SIMD constants expect compile time constants as parameters. The %d%s parameter was not.", - i, bh_num_suffix(i)); - *pcode = code; - return; - } - byte_buffer[i] = (f32) ((AstNumLit *) arg_arr[i]->value)->value.f; - } - WIP(call->token, WI_V128_CONST, byte_buffer); - break; - } - - case ONYX_INTRINSIC_F64X2_CONST: { - f64* byte_buffer = bh_alloc(mod->extended_instr_alloc, 16); - bh_arr(AstArgument *) arg_arr = (bh_arr(AstArgument *)) call->args.values; - fori (i, 0, 2) { - if (arg_arr[i]->value->kind != Ast_Kind_NumLit) { - onyx_report_error(arg_arr[i]->token->pos, Error_Critical, - "SIMD constants expect compile time constants as parameters. The %d%s parameter was not.", - i, bh_num_suffix(i)); - *pcode = code; - return; - } - byte_buffer[i] = (f64) ((AstNumLit *) arg_arr[i]->value)->value.d; - } - WIP(call->token, WI_V128_CONST, byte_buffer); - break; - } - - case ONYX_INTRINSIC_I8X16_SHUFFLE: { - u8* byte_buffer = bh_alloc(mod->extended_instr_alloc, 16); - bh_arr(AstArgument *) arg_arr = (bh_arr(AstArgument *)) call->args.values; - - // NOTE: There are two parameters that have to be outputted before - // the immediate bytes - emit_expression(mod, &code, arg_arr[0]->value); - emit_expression(mod, &code, arg_arr[1]->value); - - fori (i, 0, 16) { - if (arg_arr[i + 2]->value->kind != Ast_Kind_NumLit) { - onyx_report_error(arg_arr[i + 2]->token->pos, Error_Critical, - "SIMD constants expect compile time constants as parameters. The %d%s parameter was not.", - i, bh_num_suffix(i)); - *pcode = code; - return; - } - byte_buffer[i] = (u8) ((AstNumLit *) arg_arr[i + 2]->value)->value.i; - } - WIP(call->token, WI_I8X16_SHUFFLE, byte_buffer); - break; - } - - // CLEANUP ALL OF THIS - case ONYX_INTRINSIC_I8X16_EXTRACT_LANE_S: SIMD_EXTRACT_LANE_INSTR(WI_I8X16_EXTRACT_LANE_S, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_I8X16_EXTRACT_LANE_U: SIMD_EXTRACT_LANE_INSTR(WI_I8X16_EXTRACT_LANE_U, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_I8X16_REPLACE_LANE: SIMD_REPLACE_LANE_INSTR(WI_I8X16_REPLACE_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_I16X8_EXTRACT_LANE_S: SIMD_EXTRACT_LANE_INSTR(WI_I16X8_EXTRACT_LANE_S, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_I16X8_EXTRACT_LANE_U: SIMD_EXTRACT_LANE_INSTR(WI_I16X8_EXTRACT_LANE_U, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_I16X8_REPLACE_LANE: SIMD_REPLACE_LANE_INSTR(WI_I16X8_REPLACE_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_I32X4_EXTRACT_LANE: SIMD_EXTRACT_LANE_INSTR(WI_I32X4_EXTRACT_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_I32X4_REPLACE_LANE: SIMD_REPLACE_LANE_INSTR(WI_I32X4_REPLACE_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_I64X2_EXTRACT_LANE: SIMD_EXTRACT_LANE_INSTR(WI_I64X2_EXTRACT_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_I64X2_REPLACE_LANE: SIMD_REPLACE_LANE_INSTR(WI_I64X2_REPLACE_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_F32X4_EXTRACT_LANE: SIMD_EXTRACT_LANE_INSTR(WI_F32X4_EXTRACT_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_F32X4_REPLACE_LANE: SIMD_REPLACE_LANE_INSTR(WI_F32X4_REPLACE_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_F64X2_EXTRACT_LANE: SIMD_EXTRACT_LANE_INSTR(WI_F64X2_EXTRACT_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; - case ONYX_INTRINSIC_F64X2_REPLACE_LANE: SIMD_REPLACE_LANE_INSTR(WI_F64X2_REPLACE_LANE, ((bh_arr(AstArgument *)) call->args.values)); break; - - case ONYX_INTRINSIC_I8X16_SWIZZLE: WI(call->token, WI_I8X16_SWIZZLE); break; - case ONYX_INTRINSIC_I8X16_SPLAT: WI(call->token, WI_I8X16_SPLAT); break; - case ONYX_INTRINSIC_I16X8_SPLAT: WI(call->token, WI_I16X8_SPLAT); break; - case ONYX_INTRINSIC_I32X4_SPLAT: WI(call->token, WI_I32X4_SPLAT); break; - case ONYX_INTRINSIC_I64X2_SPLAT: WI(call->token, WI_I64X2_SPLAT); break; - case ONYX_INTRINSIC_F32X4_SPLAT: WI(call->token, WI_F32X4_SPLAT); break; - case ONYX_INTRINSIC_F64X2_SPLAT: WI(call->token, WI_F64X2_SPLAT); break; - - case ONYX_INTRINSIC_I8X16_EQ: WI(call->token, WI_I8X16_EQ); break; - case ONYX_INTRINSIC_I8X16_NEQ: WI(call->token, WI_I8X16_NEQ); break; - case ONYX_INTRINSIC_I8X16_LT_S: WI(call->token, WI_I8X16_LT_S); break; - case ONYX_INTRINSIC_I8X16_LT_U: WI(call->token, WI_I8X16_LT_U); break; - case ONYX_INTRINSIC_I8X16_GT_S: WI(call->token, WI_I8X16_GT_S); break; - case ONYX_INTRINSIC_I8X16_GT_U: WI(call->token, WI_I8X16_GT_U); break; - case ONYX_INTRINSIC_I8X16_LE_S: WI(call->token, WI_I8X16_LE_S); break; - case ONYX_INTRINSIC_I8X16_LE_U: WI(call->token, WI_I8X16_LE_U); break; - case ONYX_INTRINSIC_I8X16_GE_S: WI(call->token, WI_I8X16_GE_S); break; - case ONYX_INTRINSIC_I8X16_GE_U: WI(call->token, WI_I8X16_GE_U); break; - - case ONYX_INTRINSIC_I16X8_EQ: WI(call->token, WI_I16X8_EQ); break; - case ONYX_INTRINSIC_I16X8_NEQ: WI(call->token, WI_I16X8_NEQ); break; - case ONYX_INTRINSIC_I16X8_LT_S: WI(call->token, WI_I16X8_LT_S); break; - case ONYX_INTRINSIC_I16X8_LT_U: WI(call->token, WI_I16X8_LT_U); break; - case ONYX_INTRINSIC_I16X8_GT_S: WI(call->token, WI_I16X8_GT_S); break; - case ONYX_INTRINSIC_I16X8_GT_U: WI(call->token, WI_I16X8_GT_U); break; - case ONYX_INTRINSIC_I16X8_LE_S: WI(call->token, WI_I16X8_LE_S); break; - case ONYX_INTRINSIC_I16X8_LE_U: WI(call->token, WI_I16X8_LE_U); break; - case ONYX_INTRINSIC_I16X8_GE_S: WI(call->token, WI_I16X8_GE_S); break; - case ONYX_INTRINSIC_I16X8_GE_U: WI(call->token, WI_I16X8_GE_U); break; - - case ONYX_INTRINSIC_I32X4_EQ: WI(call->token, WI_I32X4_EQ); break; - case ONYX_INTRINSIC_I32X4_NEQ: WI(call->token, WI_I32X4_NEQ); break; - case ONYX_INTRINSIC_I32X4_LT_S: WI(call->token, WI_I32X4_LT_S); break; - case ONYX_INTRINSIC_I32X4_LT_U: WI(call->token, WI_I32X4_LT_U); break; - case ONYX_INTRINSIC_I32X4_GT_S: WI(call->token, WI_I32X4_GT_S); break; - case ONYX_INTRINSIC_I32X4_GT_U: WI(call->token, WI_I32X4_GT_U); break; - case ONYX_INTRINSIC_I32X4_LE_S: WI(call->token, WI_I32X4_LE_S); break; - case ONYX_INTRINSIC_I32X4_LE_U: WI(call->token, WI_I32X4_LE_U); break; - case ONYX_INTRINSIC_I32X4_GE_S: WI(call->token, WI_I32X4_GE_S); break; - case ONYX_INTRINSIC_I32X4_GE_U: WI(call->token, WI_I32X4_GE_U); break; - - case ONYX_INTRINSIC_F32X4_EQ: WI(call->token, WI_F32X4_EQ); break; - case ONYX_INTRINSIC_F32X4_NEQ: WI(call->token, WI_F32X4_NEQ); break; - case ONYX_INTRINSIC_F32X4_LT: WI(call->token, WI_F32X4_LT); break; - case ONYX_INTRINSIC_F32X4_GT: WI(call->token, WI_F32X4_GT); break; - case ONYX_INTRINSIC_F32X4_LE: WI(call->token, WI_F32X4_LE); break; - case ONYX_INTRINSIC_F32X4_GE: WI(call->token, WI_F32X4_GE); break; - - case ONYX_INTRINSIC_F64X2_EQ: WI(call->token, WI_F64X2_EQ); break; - case ONYX_INTRINSIC_F64X2_NEQ: WI(call->token, WI_F64X2_NEQ); break; - case ONYX_INTRINSIC_F64X2_LT: WI(call->token, WI_F64X2_LT); break; - case ONYX_INTRINSIC_F64X2_GT: WI(call->token, WI_F64X2_GT); break; - case ONYX_INTRINSIC_F64X2_LE: WI(call->token, WI_F64X2_LE); break; - case ONYX_INTRINSIC_F64X2_GE: WI(call->token, WI_F64X2_GE); break; - - case ONYX_INTRINSIC_V128_NOT: WI(call->token, WI_V128_NOT); break; - case ONYX_INTRINSIC_V128_AND: WI(call->token, WI_V128_AND); break; - case ONYX_INTRINSIC_V128_ANDNOT: WI(call->token, WI_V128_ANDNOT); break; - case ONYX_INTRINSIC_V128_OR: WI(call->token, WI_V128_OR); break; - case ONYX_INTRINSIC_V128_XOR: WI(call->token, WI_V128_XOR); break; - case ONYX_INTRINSIC_V128_BITSELECT: WI(call->token, WI_V128_BITSELECT); break; - - case ONYX_INTRINSIC_I8X16_ABS: WI(call->token, WI_I8X16_ABS); break; - case ONYX_INTRINSIC_I8X16_NEG: WI(call->token, WI_I8X16_NEG); break; - case ONYX_INTRINSIC_I8X16_ANY_TRUE: WI(call->token, WI_I8X16_ANY_TRUE); break; - case ONYX_INTRINSIC_I8X16_ALL_TRUE: WI(call->token, WI_I8X16_ALL_TRUE); break; - case ONYX_INTRINSIC_I8X16_BITMASK: WI(call->token, WI_I8X16_BITMASK); break; - case ONYX_INTRINSIC_I8X16_NARROW_I16X8_S: WI(call->token, WI_I8X16_NARROW_I16X8_S); break; - case ONYX_INTRINSIC_I8X16_NARROW_I16X8_U: WI(call->token, WI_I8X16_NARROW_I16X8_U); break; - case ONYX_INTRINSIC_I8X16_SHL: WI(call->token, WI_I8X16_SHL); break; - case ONYX_INTRINSIC_I8X16_SHR_S: WI(call->token, WI_I8X16_SHR_S); break; - case ONYX_INTRINSIC_I8X16_SHR_U: WI(call->token, WI_I8X16_SHR_U); break; - case ONYX_INTRINSIC_I8X16_ADD: WI(call->token, WI_I8X16_ADD); break; - case ONYX_INTRINSIC_I8X16_ADD_SAT_S: WI(call->token, WI_I8X16_ADD_SAT_S); break; - case ONYX_INTRINSIC_I8X16_ADD_SAT_U: WI(call->token, WI_I8X16_ADD_SAT_U); break; - case ONYX_INTRINSIC_I8X16_SUB: WI(call->token, WI_I8X16_SUB); break; - case ONYX_INTRINSIC_I8X16_SUB_SAT_S: WI(call->token, WI_I8X16_SUB_SAT_S); break; - case ONYX_INTRINSIC_I8X16_SUB_SAT_U: WI(call->token, WI_I8X16_SUB_SAT_U); break; - case ONYX_INTRINSIC_I8X16_MIN_S: WI(call->token, WI_I8X16_MIN_S); break; - case ONYX_INTRINSIC_I8X16_MIN_U: WI(call->token, WI_I8X16_MIN_U); break; - case ONYX_INTRINSIC_I8X16_MAX_S: WI(call->token, WI_I8X16_MAX_S); break; - case ONYX_INTRINSIC_I8X16_MAX_U: WI(call->token, WI_I8X16_MAX_U); break; - case ONYX_INTRINSIC_I8X16_AVGR_U: WI(call->token, WI_I8X16_AVGR_U); break; - - case ONYX_INTRINSIC_I16X8_ABS: WI(call->token, WI_I16X8_ABS); break; - case ONYX_INTRINSIC_I16X8_NEG: WI(call->token, WI_I16X8_NEG); break; - case ONYX_INTRINSIC_I16X8_ANY_TRUE: WI(call->token, WI_I16X8_ANY_TRUE); break; - case ONYX_INTRINSIC_I16X8_ALL_TRUE: WI(call->token, WI_I16X8_ALL_TRUE); break; - case ONYX_INTRINSIC_I16X8_BITMASK: WI(call->token, WI_I16X8_BITMASK); break; - case ONYX_INTRINSIC_I16X8_NARROW_I32X4_S: WI(call->token, WI_I16X8_NARROW_I32X4_S); break; - case ONYX_INTRINSIC_I16X8_NARROW_I32X4_U: WI(call->token, WI_I16X8_NARROW_I32X4_U); break; - case ONYX_INTRINSIC_I16X8_WIDEN_LOW_I8X16_S: WI(call->token, WI_I16X8_WIDEN_LOW_I8X16_S); break; - case ONYX_INTRINSIC_I16X8_WIDEN_HIGH_I8X16_S: WI(call->token, WI_I16X8_WIDEN_HIGH_I8X16_S); break; - case ONYX_INTRINSIC_I16X8_WIDEN_LOW_I8X16_U: WI(call->token, WI_I16X8_WIDEN_LOW_I8X16_U); break; - case ONYX_INTRINSIC_I16X8_WIDEN_HIGH_I8X16_U: WI(call->token, WI_I16X8_WIDEN_HIGH_I8X16_U); break; - case ONYX_INTRINSIC_I16X8_SHL: WI(call->token, WI_I16X8_SHL); break; - case ONYX_INTRINSIC_I16X8_SHR_S: WI(call->token, WI_I16X8_SHR_S); break; - case ONYX_INTRINSIC_I16X8_SHR_U: WI(call->token, WI_I16X8_SHR_U); break; - case ONYX_INTRINSIC_I16X8_ADD: WI(call->token, WI_I16X8_ADD); break; - case ONYX_INTRINSIC_I16X8_ADD_SAT_S: WI(call->token, WI_I16X8_ADD_SAT_S); break; - case ONYX_INTRINSIC_I16X8_ADD_SAT_U: WI(call->token, WI_I16X8_ADD_SAT_U); break; - case ONYX_INTRINSIC_I16X8_SUB: WI(call->token, WI_I16X8_SUB); break; - case ONYX_INTRINSIC_I16X8_SUB_SAT_S: WI(call->token, WI_I16X8_SUB_SAT_S); break; - case ONYX_INTRINSIC_I16X8_SUB_SAT_U: WI(call->token, WI_I16X8_SUB_SAT_U); break; - case ONYX_INTRINSIC_I16X8_MUL: WI(call->token, WI_I16X8_MUL); break; - case ONYX_INTRINSIC_I16X8_MIN_S: WI(call->token, WI_I16X8_MIN_S); break; - case ONYX_INTRINSIC_I16X8_MIN_U: WI(call->token, WI_I16X8_MIN_U); break; - case ONYX_INTRINSIC_I16X8_MAX_S: WI(call->token, WI_I16X8_MAX_S); break; - case ONYX_INTRINSIC_I16X8_MAX_U: WI(call->token, WI_I16X8_MAX_U); break; - case ONYX_INTRINSIC_I16X8_AVGR_U: WI(call->token, WI_I16X8_AVGR_U); break; - - case ONYX_INTRINSIC_I32X4_ABS: WI(call->token, WI_I32X4_ABS); break; - case ONYX_INTRINSIC_I32X4_NEG: WI(call->token, WI_I32X4_NEG); break; - case ONYX_INTRINSIC_I32X4_ANY_TRUE: WI(call->token, WI_I32X4_ANY_TRUE); break; - case ONYX_INTRINSIC_I32X4_ALL_TRUE: WI(call->token, WI_I32X4_ALL_TRUE); break; - case ONYX_INTRINSIC_I32X4_BITMASK: WI(call->token, WI_I32X4_BITMASK); break; - case ONYX_INTRINSIC_I32X4_WIDEN_LOW_I16X8_S: WI(call->token, WI_I32X4_WIDEN_LOW_I16X8_S); break; - case ONYX_INTRINSIC_I32X4_WIDEN_HIGH_I16X8_S: WI(call->token, WI_I32X4_WIDEN_HIGH_I16X8_S); break; - case ONYX_INTRINSIC_I32X4_WIDEN_LOW_I16X8_U: WI(call->token, WI_I32X4_WIDEN_LOW_I16X8_U); break; - case ONYX_INTRINSIC_I32X4_WIDEN_HIGH_I16X8_U: WI(call->token, WI_I32X4_WIDEN_HIGH_I16X8_U); break; - case ONYX_INTRINSIC_I32X4_SHL: WI(call->token, WI_I32X4_SHL); break; - case ONYX_INTRINSIC_I32X4_SHR_S: WI(call->token, WI_I32X4_SHR_S); break; - case ONYX_INTRINSIC_I32X4_SHR_U: WI(call->token, WI_I32X4_SHR_U); break; - case ONYX_INTRINSIC_I32X4_ADD: WI(call->token, WI_I32X4_ADD); break; - case ONYX_INTRINSIC_I32X4_SUB: WI(call->token, WI_I32X4_SUB); break; - case ONYX_INTRINSIC_I32X4_MUL: WI(call->token, WI_I32X4_MUL); break; - case ONYX_INTRINSIC_I32X4_MIN_S: WI(call->token, WI_I32X4_MIN_S); break; - case ONYX_INTRINSIC_I32X4_MIN_U: WI(call->token, WI_I32X4_MIN_U); break; - case ONYX_INTRINSIC_I32X4_MAX_S: WI(call->token, WI_I32X4_MAX_S); break; - case ONYX_INTRINSIC_I32X4_MAX_U: WI(call->token, WI_I32X4_MAX_U); break; - - case ONYX_INTRINSIC_I64X2_NEG: WI(call->token, WI_I64X2_NEG); break; - case ONYX_INTRINSIC_I64X2_SHL: WI(call->token, WI_I64X2_SHL); break; - case ONYX_INTRINSIC_I64X2_SHR_S: WI(call->token, WI_I64X2_SHR_S); break; - case ONYX_INTRINSIC_I64X2_SHR_U: WI(call->token, WI_I64X2_SHR_U); break; - case ONYX_INTRINSIC_I64X2_ADD: WI(call->token, WI_I64X2_ADD); break; - case ONYX_INTRINSIC_I64X2_SUB: WI(call->token, WI_I64X2_SUB); break; - case ONYX_INTRINSIC_I64X2_MUL: WI(call->token, WI_I64X2_MUL); break; - - case ONYX_INTRINSIC_F32X4_ABS: WI(call->token, WI_F32X4_ABS); break; - case ONYX_INTRINSIC_F32X4_NEG: WI(call->token, WI_F32X4_NEG); break; - case ONYX_INTRINSIC_F32X4_SQRT: WI(call->token, WI_F32X4_SQRT); break; - case ONYX_INTRINSIC_F32X4_ADD: WI(call->token, WI_F32X4_ADD); break; - case ONYX_INTRINSIC_F32X4_SUB: WI(call->token, WI_F32X4_SUB); break; - case ONYX_INTRINSIC_F32X4_MUL: WI(call->token, WI_F32X4_MUL); break; - case ONYX_INTRINSIC_F32X4_DIV: WI(call->token, WI_F32X4_DIV); break; - case ONYX_INTRINSIC_F32X4_MIN: WI(call->token, WI_F32X4_MIN); break; - case ONYX_INTRINSIC_F32X4_MAX: WI(call->token, WI_F32X4_MAX); break; - - case ONYX_INTRINSIC_F64X2_ABS: WI(call->token, WI_F64X2_ABS); break; - case ONYX_INTRINSIC_F64X2_NEG: WI(call->token, WI_F64X2_NEG); break; - case ONYX_INTRINSIC_F64X2_SQRT: WI(call->token, WI_F64X2_SQRT); break; - case ONYX_INTRINSIC_F64X2_ADD: WI(call->token, WI_F64X2_ADD); break; - case ONYX_INTRINSIC_F64X2_SUB: WI(call->token, WI_F64X2_SUB); break; - case ONYX_INTRINSIC_F64X2_MUL: WI(call->token, WI_F64X2_MUL); break; - case ONYX_INTRINSIC_F64X2_DIV: WI(call->token, WI_F64X2_DIV); break; - case ONYX_INTRINSIC_F64X2_MIN: WI(call->token, WI_F64X2_MIN); break; - case ONYX_INTRINSIC_F64X2_MAX: WI(call->token, WI_F64X2_MAX); break; - - case ONYX_INTRINSIC_I32X4_TRUNC_SAT_F32X4_S: WI(call->token, WI_I32X4_TRUNC_SAT_F32X4_S); break; - case ONYX_INTRINSIC_I32X4_TRUNC_SAT_F32X4_U: WI(call->token, WI_I32X4_TRUNC_SAT_F32X4_U); break; - case ONYX_INTRINSIC_F32X4_CONVERT_I32X4_S: WI(call->token, WI_F32X4_CONVERT_I32X4_S); break; - case ONYX_INTRINSIC_F32X4_CONVERT_I32X4_U: WI(call->token, WI_F32X4_CONVERT_I32X4_U); break; - - case ONYX_INTRINSIC_ATOMIC_WAIT: { - Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; - emit_intrinsic_atomic_wait(mod, &code, atomic_type, call->token); - break; - } - - case ONYX_INTRINSIC_ATOMIC_NOTIFY: { - emit_intrinsic_atomic_notify(mod, &code); - break; - } - - case ONYX_INTRINSIC_ATOMIC_FENCE: { - emit_intrinsic_atomic_fence(mod, &code); - break; - } - - case ONYX_INTRINSIC_ATOMIC_LOAD: { - Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; - emit_intrinsic_atomic_load(mod, &code, atomic_type, call->token); - break; - } - - case ONYX_INTRINSIC_ATOMIC_STORE: { - Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; - emit_intrinsic_atomic_store(mod, &code, atomic_type, call->token); - break; - } - - case ONYX_INTRINSIC_ATOMIC_ADD: { - Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; - emit_intrinsic_atomic_add(mod, &code, atomic_type, call->token); - break; - } - - case ONYX_INTRINSIC_ATOMIC_SUB: { - Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; - emit_intrinsic_atomic_sub(mod, &code, atomic_type, call->token); - break; - } - - case ONYX_INTRINSIC_ATOMIC_AND: { - Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; - emit_intrinsic_atomic_and(mod, &code, atomic_type, call->token); - break; - } - - case ONYX_INTRINSIC_ATOMIC_OR: { - Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; - emit_intrinsic_atomic_or(mod, &code, atomic_type, call->token); - break; - } - - case ONYX_INTRINSIC_ATOMIC_XOR: { - Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; - emit_intrinsic_atomic_xor(mod, &code, atomic_type, call->token); - break; - } - - case ONYX_INTRINSIC_ATOMIC_XCHG: { - Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; - emit_intrinsic_atomic_xchg(mod, &code, atomic_type, call->token); - break; - } - - case ONYX_INTRINSIC_ATOMIC_CMPXCHG: { - Type* atomic_type = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem; - emit_intrinsic_atomic_cmpxchg(mod, &code, atomic_type, call->token); - break; - } - - default: assert(("Unsupported intrinsic", 0)); - } - - *pcode = code; -} - -EMIT_FUNC(subscript_location, AstSubscript* sub, u64* offset_return) { - bh_arr(WasmInstruction) code = *pcode; - - emit_expression(mod, &code, sub->expr); - if (sub->elem_size != 1) { - WID(sub->token, WI_PTR_CONST, sub->elem_size); - WI(sub->token, WI_PTR_MUL); - } - - // CLEANUP: This is one dense clusterf**k of code... - u64 offset = 0; - if (sub->addr->kind == Ast_Kind_Subscript - && sub->addr->type->kind == Type_Kind_Array) { - emit_subscript_location(mod, &code, (AstSubscript *) sub->addr, &offset); - } else if (sub->addr->kind == Ast_Kind_Field_Access - && sub->addr->type->kind == Type_Kind_Array) { - emit_field_access_location(mod, &code, (AstFieldAccess *) sub->addr, &offset); - } else if ((sub->addr->kind == Ast_Kind_Local || sub->addr->kind == Ast_Kind_Param) - && sub->addr->type->kind == Type_Kind_Array) { - emit_local_location(mod, &code, (AstLocal *) sub->addr, &offset); - } else if (sub->addr->kind == Ast_Kind_Memres - && sub->addr->type->kind != Type_Kind_Array) { - emit_memory_reservation_location(mod, &code, (AstMemRes *) sub->addr); - } else { - emit_expression(mod, &code, sub->addr); - } - - WI(sub->token, WI_PTR_ADD); - - *offset_return += offset; - - *pcode = code; -} - -EMIT_FUNC(field_access_location, AstFieldAccess* field, u64* offset_return) { - bh_arr(WasmInstruction) code = *pcode; - - u64 offset = field->offset; - AstTyped* source_expr = field->expr; - while (source_expr->kind == Ast_Kind_Field_Access - && type_is_structlike_strict(source_expr->type)) { - offset += ((AstFieldAccess *) source_expr)->offset; - source_expr = (AstTyped *) ((AstFieldAccess *) source_expr)->expr; - } - - if (source_expr->kind == Ast_Kind_Subscript - && source_expr->type->kind != Type_Kind_Pointer) { - u64 o2 = 0; - emit_subscript_location(mod, &code, (AstSubscript *) source_expr, &o2); - offset += o2; - - } else if ((source_expr->kind == Ast_Kind_Local || source_expr->kind == Ast_Kind_Param) - && source_expr->type->kind != Type_Kind_Pointer) { - u64 o2 = 0; - emit_local_location(mod, &code, (AstLocal *) source_expr, &o2); - offset += o2; - - } else if (source_expr->kind == Ast_Kind_Memres - && source_expr->type->kind != Type_Kind_Pointer) { - emit_memory_reservation_location(mod, &code, (AstMemRes *) source_expr); - - } else { - emit_expression(mod, &code, source_expr); - } - - *offset_return = offset; - - *pcode = code; -} - -EMIT_FUNC(memory_reservation_location, AstMemRes* memres) { - bh_arr(WasmInstruction) code = *pcode; - - if (memres->threadlocal) { - u64 tls_base_idx = bh_imap_get(&mod->index_map, (u64) &builtin_tls_base); - WID(NULL, WI_PTR_CONST, memres->tls_offset); - WIL(NULL, WI_GLOBAL_GET, tls_base_idx); - WI(NULL, WI_PTR_ADD); - - } else { - // :ProperLinking - assert(memres->data_id != 0); - emit_data_relocation(mod, &code, memres->data_id); - } - - *pcode = code; -} - -EMIT_FUNC(local_location, AstLocal* local, u64* offset_return) { - bh_arr(WasmInstruction) code = *pcode; - - u64 local_offset = (u64) bh_imap_get(&mod->local_map, (u64) local); - - if (local_offset & LOCAL_IS_WASM) { - // This is a weird condition but it is relied on in a couple places including - // passing non-simple structs by value. -brendanfh 2020/09/18 - WIL(NULL, WI_LOCAL_GET, local_offset); - - } else { - WIL(NULL, WI_LOCAL_GET, mod->stack_base_idx); - - *offset_return += local_offset; - } - - *pcode = code; -} - -EMIT_FUNC(struct_lval, AstTyped* lval) { - bh_arr(WasmInstruction) code = *pcode; - - assert(type_is_structlike_strict(lval->type)); - - u64 offset = 0; - emit_location_return_offset(mod, &code, lval, &offset); - emit_compound_store(mod, &code, lval->type, offset, 1); - - *pcode = code; -} - -EMIT_FUNC(compound_load, Type* type, u64 offset) { - bh_arr(WasmInstruction) code = *pcode; - i32 mem_count = type_linear_member_count(type); - TypeWithOffset two; - - if (mem_count == 1) { - type_linear_member_lookup(type, 0, &two); - emit_load_instruction(mod, &code, two.type, offset + two.offset); // two.offset should be 0 - } else { - u64 tmp_idx = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - WIL(NULL, WI_LOCAL_TEE, tmp_idx); - - fori (i, 0, mem_count) { - type_linear_member_lookup(type, i, &two); - if (i != 0) WIL(NULL, WI_LOCAL_GET, tmp_idx); - emit_load_instruction(mod, &code, two.type, offset + two.offset); - } - - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - } - - *pcode = code; - return; -} - -EMIT_FUNC(compound_store, Type* type, u64 offset, b32 location_first) { - bh_arr(WasmInstruction) code = *pcode; - - TypeWithOffset two; - - u64 loc_idx = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - if (location_first) WIL(NULL, WI_LOCAL_SET, loc_idx); - - i32 elem_count = type_linear_member_count(type); - u64 *temp_locals = bh_alloc_array(global_scratch_allocator, u64, elem_count); - - forir (i, elem_count - 1, 0) { - type_linear_member_lookup(type, i, &two); - - WasmType wt = onyx_type_to_wasm_type(two.type); - temp_locals[i] = local_raw_allocate(mod->local_alloc, wt); - WIL(NULL, WI_LOCAL_SET, temp_locals[i]); - } - - if (!location_first) WIL(NULL, WI_LOCAL_SET, loc_idx); - - fori (i, 0, elem_count) { - type_linear_member_lookup(type, i, &two); - - u64 tmp_idx = temp_locals[i]; - WIL(NULL, WI_LOCAL_GET, loc_idx); - WIL(NULL, WI_LOCAL_GET, tmp_idx); - emit_store_instruction(mod, &code, two.type, offset + two.offset); - - WasmType wt = onyx_type_to_wasm_type(two.type); - local_raw_free(mod->local_alloc, wt); - } - - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - - // This shouldn't be necessary because the scratch allocator doesn't free. - bh_free(global_scratch_allocator, temp_locals); - - *pcode = code; -} - -EMIT_FUNC(struct_literal, AstStructLiteral* sl) { - bh_arr(WasmInstruction) code = *pcode; - - bh_arr_each(AstTyped *, val, sl->args.values) { - emit_expression(mod, &code, *val); - } - - *pcode = code; -} - -EMIT_FUNC(array_store, Type* type, u32 offset) { - assert(type->kind == Type_Kind_Array); - bh_arr(WasmInstruction) code = *pcode; - - Type* elem_type = type; - u32 elem_count = 1; - while (elem_type->kind == Type_Kind_Array) { - elem_count *= elem_type->Array.count; - elem_type = elem_type->Array.elem; - } - u32 elem_size = type_size_of(elem_type); - - u64 lptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - u64 rptr_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - WIL(NULL, WI_LOCAL_SET, rptr_local); - WIL(NULL, WI_LOCAL_SET, lptr_local); - - WIL(NULL, WI_LOCAL_GET, rptr_local); - WID(NULL, WI_I32_CONST, 0); - WI(NULL, WI_I32_NE); - emit_enter_structured_block(mod, &code, SBT_Basic_If, NULL); - - // - // CLEANUP: Most of these cases could be much shorter if they used existing intrinsics. - // - if (elem_count <= 2) { - // Inline copying for a small number of elements. It still may be faster to do this in a tight loop. - - fori (i, 0, elem_count) { - if (bh_arr_last(code).type == WI_LOCAL_SET && (u64) bh_arr_last(code).data.l == lptr_local) - bh_arr_last(code).type = WI_LOCAL_TEE; - else - WIL(NULL, WI_LOCAL_GET, lptr_local); - - WIL(NULL, WI_LOCAL_GET, rptr_local); - emit_load_instruction(mod, &code, elem_type, i * elem_size); - - emit_store_instruction(mod, &code, elem_type, i * elem_size + offset); - } - - } else if (context.options->use_post_mvp_features) { - // Use a simple memory copy if it is available. This may be what happens in the case below too at a later time. - - if (bh_arr_last(code).type == WI_LOCAL_SET && (u64) bh_arr_last(code).data.l == lptr_local) - bh_arr_last(code).type = WI_LOCAL_TEE; - else - WIL(NULL, WI_LOCAL_GET, lptr_local); - - if (offset != 0) { - WIL(NULL, WI_PTR_CONST, offset); - WI(NULL, WI_PTR_ADD); - } - - WIL(NULL, WI_LOCAL_GET, rptr_local); - WIL(NULL, WI_I32_CONST, elem_count * elem_size); - WI(NULL, WI_MEMORY_COPY); - - } else { - // Emit a loop that copies the memory. This could be switched to a tight loop that just copies word per word. - - u64 offset_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - WIL(NULL, WI_PTR_CONST, 0); - WIL(NULL, WI_LOCAL_SET, offset_local); - - WID(NULL, WI_BLOCK_START, 0x40); - WID(NULL, WI_LOOP_START, 0x40); - WIL(NULL, WI_LOCAL_GET, offset_local); - WIL(NULL, WI_LOCAL_GET, lptr_local); - WI(NULL, WI_PTR_ADD); - - WIL(NULL, WI_LOCAL_GET, offset_local); - WIL(NULL, WI_LOCAL_GET, rptr_local); - WI(NULL, WI_PTR_ADD); - - emit_load_instruction(mod, &code, elem_type, 0); - emit_store_instruction(mod, &code, elem_type, offset); - - WIL(NULL, WI_LOCAL_GET, offset_local); - WIL(NULL, WI_PTR_CONST, elem_size); - WI(NULL, WI_PTR_ADD); - WIL(NULL, WI_LOCAL_TEE, offset_local); - - WIL(NULL, WI_PTR_CONST, elem_count * elem_size); - WI(NULL, WI_PTR_GE); - WID(NULL, WI_COND_JUMP, 0x01); - - WID(NULL, WI_JUMP, 0x00); - - WI(NULL, WI_LOOP_END); - WI(NULL, WI_BLOCK_END); - - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - } - - WI(NULL, WI_ELSE); - - { // If the source ptr is null (0), then just copy in 0 bytes. - WIL(NULL, WI_LOCAL_GET, lptr_local); - if (offset != 0) { - WIL(NULL, WI_PTR_CONST, offset); - WI(NULL, WI_PTR_ADD); - } - - WIL(NULL, WI_I32_CONST, 0); - - WIL(NULL, WI_I32_CONST, elem_count * elem_size); - - if (context.options->use_post_mvp_features) { - WI(NULL, WI_MEMORY_FILL); - } else { - emit_intrinsic_memory_fill(mod, &code); - } - } - - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - - emit_leave_structured_block(mod, &code); - *pcode = code; - return; -} - -EMIT_FUNC(array_literal, AstArrayLiteral* al) { - bh_arr(WasmInstruction) code = *pcode; - - u64 local_offset = (u64) bh_imap_get(&mod->local_map, (u64) al); - assert((local_offset & LOCAL_IS_WASM) == 0); - - assert(al->type->kind == Type_Kind_Array); - - u32 elem_size = type_size_of(al->type->Array.elem); - - fori (i, 0, al->type->Array.count) { - WIL(al->token, WI_LOCAL_GET, mod->stack_base_idx); - emit_expression(mod, &code, al->values[i]); - emit_store_instruction(mod, &code, al->type->Array.elem, local_offset + i * elem_size); - } - - WIL(al->token, WI_LOCAL_GET, mod->stack_base_idx); - WIL(al->token, WI_PTR_CONST, local_offset); - WI(al->token, WI_PTR_ADD); - - *pcode = code; -} - -EMIT_FUNC(range_literal, AstRangeLiteral* range) { - bh_arr(WasmInstruction) code = *pcode; - - emit_expression(mod, &code, range->low); - emit_expression(mod, &code, range->high); - emit_expression(mod, &code, range->step); - - *pcode = code; -} - -EMIT_FUNC(if_expression, AstIfExpression* if_expr) { - bh_arr(WasmInstruction) code = *pcode; - - u64 offset = 0; - u64 result_local = local_allocate(mod->local_alloc, (AstTyped *) if_expr); - b32 result_is_local = (b32) ((result_local & LOCAL_IS_WASM) != 0); - bh_imap_put(&mod->local_map, (u64) if_expr, result_local); - - emit_expression(mod, &code, if_expr->cond); - - emit_enter_structured_block(mod, &code, SBT_Basic_If, if_expr->token); - if (!result_is_local) emit_local_location(mod, &code, (AstLocal *) if_expr, &offset); - - emit_expression(mod, &code, if_expr->true_expr); - - if (!result_is_local) emit_store_instruction(mod, &code, if_expr->type, offset); - else WIL(if_expr->token, WI_LOCAL_SET, result_local); - - offset = 0; - WI(if_expr->token, WI_ELSE); - if (!result_is_local) emit_local_location(mod, &code, (AstLocal *) if_expr, &offset); - - emit_expression(mod, &code, if_expr->false_expr); - - if (!result_is_local) emit_store_instruction(mod, &code, if_expr->type, offset); - else WIL(if_expr->token, WI_LOCAL_SET, result_local); - - emit_leave_structured_block(mod, &code); - - offset = 0; - if (!result_is_local) { - emit_local_location(mod, &code, (AstLocal *) if_expr, &offset); - emit_load_instruction(mod, &code, if_expr->type, offset); - - } else { - WIL(if_expr->token, WI_LOCAL_GET, result_local); - } - - local_free(mod->local_alloc, (AstTyped *) if_expr); - - *pcode = code; -} - -EMIT_FUNC(do_block, AstDoBlock* doblock) { - bh_arr(WasmInstruction) code = *pcode; - - u64 result_local = local_allocate(mod->local_alloc, (AstTyped *) doblock); - b32 result_is_local = (b32) ((result_local & LOCAL_IS_WASM) != 0); - - bh_imap_put(&mod->local_map, (u64) doblock, result_local); - bh_arr_push(mod->return_location_stack, (AstLocal *) doblock); - - emit_block(mod, &code, doblock->block, 1); - - u64 offset = 0; - if (!result_is_local) { - emit_local_location(mod, &code, (AstLocal *) doblock, &offset); - emit_load_instruction(mod, &code, doblock->type, offset); - - } else { - WIL(doblock->block->token, WI_LOCAL_GET, result_local); - } - - bh_arr_pop(mod->return_location_stack); - local_free(mod->local_alloc, (AstTyped *) doblock); - - *pcode = code; -} - -EMIT_FUNC(location_return_offset, AstTyped* expr, u64* offset_return) { - bh_arr(WasmInstruction) code = *pcode; - - expr = (AstTyped *) strip_aliases((AstNode *) expr); - - switch (expr->kind) { - case Ast_Kind_Param: - case Ast_Kind_Local: - case Ast_Kind_Array_Literal: - case Ast_Kind_Struct_Literal: { - emit_local_location(mod, &code, (AstLocal *) expr, offset_return); - break; - } - - case Ast_Kind_Dereference: { - emit_expression(mod, &code, ((AstDereference *) expr)->expr); - *offset_return = 0; - break; - } - - case Ast_Kind_Subscript: { - AstSubscript* sub = (AstSubscript *) expr; - emit_subscript_location(mod, &code, sub, offset_return); - break; - } - - case Ast_Kind_Field_Access: { - AstFieldAccess* field = (AstFieldAccess *) expr; - emit_field_access_location(mod, &code, field, offset_return); - break; - } - - case Ast_Kind_Memres: { - AstMemRes* memres = (AstMemRes *) expr; - emit_memory_reservation_location(mod, &code, memres); - *offset_return = 0; - break; - } - - default: { - onyx_report_error(expr->token->pos, Error_Critical, "Unable to generate location for '%s'.", onyx_ast_node_kind_string(expr->kind)); - break; - } - } - - *pcode = code; -} - -EMIT_FUNC(location, AstTyped* expr) { - bh_arr(WasmInstruction) code = *pcode; - - u64 offset = 0; - emit_location_return_offset(mod, &code, expr, &offset); - if (offset != 0) { - WID(NULL, WI_PTR_CONST, offset); - WI(NULL, WI_PTR_ADD); - } - - *pcode = code; -} - -EMIT_FUNC(expression, AstTyped* expr) { - bh_arr(WasmInstruction) code = *pcode; - - if (node_is_type((AstNode *) expr)) { - AstType* type = (AstType *) expr; - if (type->flags & Ast_Flag_Expr_Ignored) return; - - if (type->type_id != 0) { - WID(NULL, WI_I32_CONST, ((AstType *) expr)->type_id); - } else { - Type* t = type_build_from_ast(context.ast_alloc, type); - WID(NULL, WI_I32_CONST, t->id); - } - - - *pcode = code; - return; - } - - switch (expr->kind) { - case Ast_Kind_Param: { - AstLocal* param = (AstLocal *) expr; - u64 localidx = bh_imap_get(&mod->local_map, (u64) param); - - switch (type_get_param_pass(param->type)) { - case Param_Pass_By_Value: { - if (type_is_structlike_strict(expr->type)) { - u32 mem_count = type_structlike_mem_count(expr->type); - fori (idx, 0, mem_count) WIL(NULL, WI_LOCAL_GET, localidx + idx); - - } else { - assert(localidx & LOCAL_IS_WASM); - WIL(NULL, WI_LOCAL_GET, localidx); - } - break; - } - - case Param_Pass_By_Implicit_Pointer: { - assert(localidx & LOCAL_IS_WASM); - WIL(NULL, WI_LOCAL_GET, localidx); - emit_load_instruction(mod, &code, expr->type, 0); - break; - } - - default: assert(0); - } - - break; - } - - case Ast_Kind_Local: { - u64 tmp = bh_imap_get(&mod->local_map, (u64) expr); - - if (tmp & LOCAL_IS_WASM) { - if (bh_arr_last(code).type == WI_LOCAL_SET && (u64) bh_arr_last(code).data.l == tmp) { - bh_arr_last(code).type = WI_LOCAL_TEE; - } else { - WIL(NULL, WI_LOCAL_GET, tmp); - } - - } else { - u64 offset = 0; - emit_local_location(mod, &code, (AstLocal *) expr, &offset); - - if (expr->type->kind != Type_Kind_Array) { - emit_load_instruction(mod, &code, expr->type, offset); - } else if (offset != 0) { - WID(NULL, WI_PTR_CONST, offset); - WI(NULL, WI_PTR_ADD); - } - } - - break; - } - - case Ast_Kind_Global: { - i32 globalidx = (i32) bh_imap_get(&mod->index_map, (u64) expr); - - WID(NULL, WI_GLOBAL_GET, globalidx); - break; - } - - case Ast_Kind_NumLit: { - AstNumLit* lit = (AstNumLit *) expr; - WasmType lit_type = onyx_type_to_wasm_type(lit->type); - WasmInstruction instr = { WI_NOP, 0 }; - - if (lit_type == WASM_TYPE_INT32) { - instr.type = WI_I32_CONST; - instr.data.i1 = lit->value.i; - } else if (lit_type == WASM_TYPE_INT64) { - instr.type = WI_I64_CONST; - instr.data.l = lit->value.l; - } else if (lit_type == WASM_TYPE_FLOAT32) { - instr.type = WI_F32_CONST; - instr.data.f = lit->value.f; - } else if (lit_type == WASM_TYPE_FLOAT64) { - instr.type = WI_F64_CONST; - instr.data.d = lit->value.d; - } - - WIR(NULL, instr); - break; - } - - case Ast_Kind_StrLit: { - // :ProperLinking - AstStrLit *strlit = (AstStrLit *) expr; - assert(strlit->data_id > 0); - emit_data_relocation(mod, &code, strlit->data_id); - - if (strlit->is_cstr == 0) - WID(NULL, WI_I32_CONST, strlit->length); - break; - } - - case Ast_Kind_Struct_Literal: { - emit_struct_literal(mod, &code, (AstStructLiteral *) expr); - break; - } - - case Ast_Kind_Array_Literal: { - emit_local_allocation(mod, &code, expr); - emit_array_literal(mod, &code, (AstArrayLiteral *) expr); - break; - } - - case Ast_Kind_Range_Literal: { - emit_range_literal(mod, &code, (AstRangeLiteral *) expr); - break; - } - - case Ast_Kind_Function: { - i32 elemidx = get_element_idx(mod, (AstFunction *) expr); - - WID(NULL, WI_I32_CONST, elemidx); - break; - } - - case Ast_Kind_Block: emit_block(mod, &code, (AstBlock *) expr, 1); break; - case Ast_Kind_Do_Block: emit_do_block(mod, &code, (AstDoBlock *) expr); break; - case Ast_Kind_Call: emit_call(mod, &code, (AstCall *) expr); break; - case Ast_Kind_Argument: emit_expression(mod, &code, ((AstArgument *) expr)->value); break; - case Ast_Kind_Intrinsic_Call: emit_intrinsic_call(mod, &code, (AstCall *) expr); break; - case Ast_Kind_Binary_Op: emit_binop(mod, &code, (AstBinaryOp *) expr); break; - case Ast_Kind_Unary_Op: emit_unaryop(mod, &code, (AstUnaryOp *) expr); break; - case Ast_Kind_Alias: emit_expression(mod, &code, ((AstAlias *) expr)->alias); break; - - case Ast_Kind_Address_Of: { - AstAddressOf* aof = (AstAddressOf *) expr; - - if (node_is_addressable_literal((AstNode *) aof->expr)) { - aof->expr->flags |= Ast_Flag_Decl_Followed_By_Init; - aof->expr->flags |= Ast_Flag_Address_Taken; - emit_local_allocation(mod, &code, aof->expr); - emit_location(mod, &code, aof->expr); - emit_expression(mod, &code, aof->expr); - emit_store_instruction(mod, &code, aof->expr->type, 0); - } - - emit_location(mod, &code, aof->expr); - break; - } - - case Ast_Kind_Dereference: { - AstDereference* deref = (AstDereference *) expr; - emit_expression(mod, &code, deref->expr); - emit_load_instruction(mod, &code, deref->type, 0); - break; - } - - case Ast_Kind_Subscript: { - AstSubscript* sub = (AstSubscript *) expr; - u64 offset = 0; - emit_subscript_location(mod, &code, sub, &offset); - emit_load_instruction(mod, &code, sub->type, offset); - break; - } - - case Ast_Kind_Field_Access: { - AstFieldAccess* field = (AstFieldAccess* ) expr; - - if (field->expr->kind == Ast_Kind_Param) { - if (type_get_param_pass(field->expr->type) == Param_Pass_By_Value && !type_is_pointer(field->expr->type)) { - u64 localidx = bh_imap_get(&mod->local_map, (u64) field->expr) + field->idx; - assert(localidx & LOCAL_IS_WASM); - WIL(NULL, WI_LOCAL_GET, localidx); - break; - } - } - - if (is_lval((AstNode *) field->expr) || type_is_pointer(field->expr->type)) { - u64 offset = 0; - emit_field_access_location(mod, &code, field, &offset); - emit_load_instruction(mod, &code, field->type, offset); - - } else { - emit_expression(mod, &code, field->expr); - - i32 idx = type_get_idx_of_linear_member_with_offset(field->expr->type, field->offset); - i32 field_linear_members = type_linear_member_count(field->type); - i32 total_linear_members = type_linear_member_count(field->expr->type); - - if (idx == 0) { - // Easy case: the member is the first one and all other members just have to be dropped. - fori (i, 0, total_linear_members - field_linear_members) WI(NULL, WI_DROP); - - } else { - // Tough case: Stack shuffling to make the member the only thing on the stack. - // This is very similar to the compound_load/compound_store procedures but it is different enough - // that I cannot find a good way to factor them all without just introducing a ton of complexity. - fori (i, 0, total_linear_members - idx - field_linear_members) WI(NULL, WI_DROP); - - u64 *temporaries = bh_alloc_array(global_scratch_allocator, u64, field_linear_members); - fori (i, 0, field_linear_members) temporaries[i] = 0; - - TypeWithOffset two = { 0 }; - forir (i, field_linear_members - 1, 0) { - type_linear_member_lookup(field->type, i, &two); - - WasmType wt = onyx_type_to_wasm_type(two.type); - temporaries[i] = local_raw_allocate(mod->local_alloc, wt); - WIL(NULL, WI_LOCAL_SET, temporaries[i]); - } - - fori (i, 0, idx) WI(NULL, WI_DROP); - - fori (i, 0, field_linear_members) { - type_linear_member_lookup(field->type, i, &two); - - WIL(NULL, WI_LOCAL_GET, temporaries[i]); - - WasmType wt = onyx_type_to_wasm_type(two.type); - local_raw_free(mod->local_alloc, wt); - } - - bh_free(global_scratch_allocator, temporaries); - } - } - - break; - } - - case Ast_Kind_Slice: { - AstSubscript* sl = (AstSubscript *) expr; - - emit_expression(mod, &code, sl->expr); - - u64 lo_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32); - u64 hi_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32); - - WI(NULL, WI_DROP); - WIL(NULL, WI_LOCAL_SET, hi_local); - WIL(NULL, WI_LOCAL_TEE, lo_local); - if (sl->elem_size != 1) { - WID(NULL, WI_I32_CONST, sl->elem_size); - WI(NULL, WI_I32_MUL); - } - emit_expression(mod, &code, sl->addr); - WI(NULL, WI_I32_ADD); - WIL(NULL, WI_LOCAL_GET, hi_local); - WIL(NULL, WI_LOCAL_GET, lo_local); - WI(NULL, WI_I32_SUB); - - local_raw_free(mod->local_alloc, lo_local); - local_raw_free(mod->local_alloc, hi_local); - break; - } - - case Ast_Kind_Size_Of: { - AstSizeOf* so = (AstSizeOf *) expr; - WID(NULL, WI_I32_CONST, so->size); - break; - } - - case Ast_Kind_Align_Of: { - AstAlignOf* ao = (AstAlignOf *) expr; - WID(NULL, WI_I32_CONST, ao->alignment); - break; - } - - case Ast_Kind_Enum_Value: { - AstEnumValue* ev = (AstEnumValue *) expr; - AstNumLit * num = (AstNumLit *) ev->value; - assert(num->kind == Ast_Kind_NumLit); - - WasmType backing_type = onyx_type_to_wasm_type(ev->type); - if (backing_type == WASM_TYPE_INT32) WID(NULL, WI_I32_CONST, num->value.i); - else if (backing_type == WASM_TYPE_INT64) WID(NULL, WI_I64_CONST, num->value.l); - else onyx_report_error(ev->token->pos, Error_Critical, "Invalid backing type for enum."); - break; - } - - case Ast_Kind_Memres: { - AstMemRes* memres = (AstMemRes *) expr; - emit_memory_reservation_location(mod, &code, memres); - emit_load_instruction(mod, &code, memres->type, 0); - break; - } - - case Ast_Kind_File_Contents: { - AstFileContents* fc = (AstFileContents *) expr; - - assert(fc->data_id > 0); - assert(fc->size > 0); - - // :ProperLinking - emit_data_relocation(mod, &code, fc->data_id); - WID(NULL, WI_I32_CONST, fc->size); - break; - } - - case Ast_Kind_Compound: { - AstCompound* compound = (AstCompound *) expr; - - bh_arr_each(AstTyped *, expr, compound->exprs) { - emit_expression(mod, &code, *expr); - } - break; - } - - case Ast_Kind_Call_Site: { - AstCallSite* callsite = (AstCallSite *) expr; - - emit_expression(mod, &code, (AstTyped *) callsite->filename); - emit_expression(mod, &code, (AstTyped *) callsite->line); - emit_expression(mod, &code, (AstTyped *) callsite->column); - break; - } - - case Ast_Kind_If_Expression: { - AstIfExpression* if_expr = (AstIfExpression *) expr; - emit_if_expression(mod, &code, if_expr); - break; - } - - case Ast_Kind_Switch_Case: { - // This error message should be moved to checking, but this is the - // best place to do it right now. - onyx_report_error(expr->token->pos, Error_Critical, "'case' statements are only allowed in a 'switch' statement."); - break; - } - - case Ast_Kind_Code_Block: { - // Like above, this error message should be moved to checking, but - // this is the best place to do it right now. - onyx_report_error(expr->token->pos, Error_Critical, "'#quote' blocks are only to be used at compile-time. Using them as a runtime value is not allowed."); - break; - } - - case Ast_Kind_Foreign_Block: { - AstForeignBlock *fb = (AstForeignBlock *) expr; - WID(NULL, WI_I32_CONST, fb->foreign_block_number); - break; - } - - case Ast_Kind_Zero_Value: { - AstZeroValue *zv = (AstZeroValue *) expr; - assert(zv->type); - emit_zero_value_for_type(mod, &code, zv->type, zv->token); - break; - } - - default: - bh_printf("Unhandled case: %d\n", expr->kind); - DEBUG_HERE; - assert(0); - } - - if ((expr->flags & Ast_Flag_Expr_Ignored) != 0 && !type_results_in_void(expr->type)) { - i32 mem_count = 1; - if (type_is_compound(expr->type)) mem_count = type_linear_member_count(expr->type); - fori (i, 0, mem_count) WI(NULL, WI_DROP); - } - - *pcode = code; -} - -static const WasmInstructionType cast_map[][12] = { - // I8 U8 I16 U16 I32 U32 I64 U64 F32 F64 PTR - /* I8 */ { WI_NOP, WI_NOP, WI_I32_EXTEND_8_S, WI_NOP, WI_I32_EXTEND_8_S, WI_NOP, WI_I64_FROM_I32_S, WI_I64_FROM_I32_S, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE }, - /* U8 */ { WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_I64_FROM_I32_U, WI_I64_FROM_I32_U, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE }, - /* I16 */ { WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_I32_EXTEND_16_S, WI_NOP, WI_I64_FROM_I32_S, WI_I64_FROM_I32_S, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE }, - /* U16 */ { WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_I64_FROM_I32_U, WI_I64_FROM_I32_U, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE }, - /* I32 */ { WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_I64_FROM_I32_S, WI_I64_FROM_I32_S, WI_F32_FROM_I32_S, WI_F64_FROM_I32_S, WI_NOP, WI_NOP }, - /* U32 */ { WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_NOP, WI_I64_FROM_I32_U, WI_I64_FROM_I32_U, WI_F32_FROM_I32_U, WI_F64_FROM_I32_U, WI_NOP, WI_NOP }, - /* I64 */ { WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_NOP, WI_NOP, WI_F32_FROM_I64_S, WI_F64_FROM_I64_S, WI_I32_FROM_I64, WI_UNREACHABLE }, - /* U64 */ { WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_I32_FROM_I64, WI_NOP, WI_NOP, WI_F32_FROM_I64_U, WI_F64_FROM_I64_U, WI_I32_FROM_I64, WI_UNREACHABLE }, - /* F32 */ { WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_I32_FROM_F32_S, WI_I32_FROM_F32_U, WI_I64_FROM_F32_S, WI_I64_FROM_F32_U, WI_NOP, WI_F64_FROM_F32, WI_UNREACHABLE, WI_UNREACHABLE }, - /* F64 */ { WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_I32_FROM_F64_S, WI_I32_FROM_F64_U, WI_I64_FROM_F64_S, WI_I64_FROM_F64_U, WI_F32_FROM_F64, WI_NOP, WI_UNREACHABLE, WI_UNREACHABLE }, - /* PTR */ { WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_NOP, WI_NOP, WI_I64_FROM_I32_U, WI_I64_FROM_I32_U, WI_UNREACHABLE, WI_UNREACHABLE, WI_NOP, WI_UNREACHABLE }, - /* TYP */ { WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_NOP, WI_NOP, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_UNREACHABLE, WI_NOP }, -}; - -EMIT_FUNC(cast, AstUnaryOp* cast) { - bh_arr(WasmInstruction) code = *pcode; - - emit_expression(mod, &code, cast->expr); - - Type* from = cast->expr->type; - Type* to = cast->type; - if (from->kind == Type_Kind_Enum) from = from->Enum.backing; - if (to->kind == Type_Kind_Enum) to = to->Enum.backing; - - if (type_is_simd(from) && type_is_simd(to)) { - *pcode = code; - return; - } - - if (to->kind == Type_Kind_Basic && to->Basic.kind == Basic_Kind_Void) { - WI(NULL, WI_DROP); - *pcode = code; - return; - } - - if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_Array) { - WID(NULL, WI_I32_CONST, from->Array.count); - *pcode = code; - return; - } - - if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_DynArray) { - WI(NULL, WI_DROP); - WI(NULL, WI_DROP); - WI(NULL, WI_DROP); - *pcode = code; - return; - } - - if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_Slice) { - // Nothing needs to be done because they are identical - *pcode = code; - return; - } - - if (to->kind == Type_Kind_Slice && from->kind == Type_Kind_VarArgs) { - // Nothing needs to be done because they are identical - *pcode = code; - return; - } - - if (to->kind == Type_Kind_Distinct || from->kind == Type_Kind_Distinct) { - // Nothing needs to be done because they are identical - *pcode = code; - return; - } - - i32 fromidx = -1, toidx = -1; - if (from->Basic.flags & Basic_Flag_Pointer || from->kind == Type_Kind_Array) { - fromidx = 10; - } - else if (from->Basic.flags & Basic_Flag_Integer) { - b32 unsign = (from->Basic.flags & Basic_Flag_Unsigned) != 0; - - fromidx = log2_dumb(from->Basic.size) * 2 + unsign; - } - else if (from->Basic.flags & Basic_Flag_Float) { - if (from->Basic.size == 4) fromidx = 8; - else if (from->Basic.size == 8) fromidx = 9; - } - else if (from->Basic.flags & Basic_Flag_Boolean) { - fromidx = 0; - } - else if (from->Basic.flags & Basic_Flag_Type_Index) { - fromidx = 11; - } - - if (to->Basic.flags & Basic_Flag_Pointer || to->kind == Type_Kind_Array) { - toidx = 10; - } - else if (to->Basic.flags & Basic_Flag_Integer) { - b32 unsign = (to->Basic.flags & Basic_Flag_Unsigned) != 0; - - toidx = log2_dumb(to->Basic.size) * 2 + unsign; - } - else if (to->Basic.flags & Basic_Flag_Float) { - if (to->Basic.size == 4) toidx = 8; - else if (to->Basic.size == 8) toidx = 9; - } - else if (to->Basic.flags & Basic_Flag_Boolean) { - toidx = 0; - } - else if (to->Basic.flags & Basic_Flag_Type_Index) { - toidx = 11; - } - - if (fromidx != -1 && toidx != -1) { - WasmInstructionType cast_op = cast_map[fromidx][toidx]; - assert(cast_op != WI_UNREACHABLE); - - if (cast_op != WI_NOP) { - WI(NULL, cast_op); - } - } - - *pcode = code; -} - -EMIT_FUNC(return, AstReturn* ret) { - bh_arr(WasmInstruction) code = *pcode; - - // If we have an expression to return, we see if it should be placed on the linear memory stack, or the WASM stack. - if (ret->expr) { - if (bh_arr_length(mod->return_location_stack) > 0) { - AstLocal* dest = bh_arr_last(mod->return_location_stack); - u64 dest_loc = bh_imap_get(&mod->local_map, (u64) dest); - b32 dest_is_local = (b32) ((dest_loc & LOCAL_IS_WASM) != 0); - - u64 offset = 0; - if (!dest_is_local) emit_local_location(mod, &code, dest, &offset); - - emit_expression(mod, &code, ret->expr); - - if (!dest_is_local) emit_store_instruction(mod, &code, dest->type, offset); - else WIL(NULL, WI_LOCAL_SET, dest_loc); - - } else if (mod->curr_cc == CC_Return_Stack) { - WIL(NULL, WI_LOCAL_GET, mod->stack_base_idx); - WID(NULL, WI_I32_CONST, type_size_of(ret->expr->type)); - WI(NULL, WI_I32_SUB); - - emit_expression(mod, &code, ret->expr); - emit_store_instruction(mod, &code, ret->expr->type, 0); - - } else { - emit_expression(mod, &code, ret->expr); - } - } - - // Clear the normal deferred statements - emit_deferred_stmts(mod, &code); - - i64 jump_label = get_structured_jump_label(mod, Jump_Type_Return, 1); - if (jump_label >= 0) { - WIL(NULL, WI_JUMP, jump_label); - - } else { - // Clear the rest of the deferred statements - if (bh_arr_length(mod->deferred_stmts) > 0) { - i32 i = bh_arr_length(mod->deferred_stmts) - 1; - while (i >= 0) { - emit_deferred_stmt(mod, &code, mod->deferred_stmts[i]); - i--; - } - } - - // Make a patch for the two instructions needed to restore the stack pointer - SUBMIT_PATCH(mod->stack_leave_patches, 0); - WI(NULL, WI_NOP); - WI(NULL, WI_NOP); - - WI(NULL, WI_RETURN); - } - - *pcode = code; -} - -EMIT_FUNC(stack_enter, u64 stacksize) { - bh_arr(WasmInstruction) code = *pcode; - - bh_align(stacksize, 16); - - u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top); - - // HACK: slightly... There will be space for 5 instructions - if (stacksize == 0) { - code[0] = (WasmInstruction) { WI_GLOBAL_GET, { .l = stack_top_idx } }; - code[1] = (WasmInstruction) { WI_LOCAL_SET, { .l = mod->stack_base_idx} }; - code[2] = (WasmInstruction) { WI_NOP, 0 }; - code[3] = (WasmInstruction) { WI_NOP, 0 }; - code[4] = (WasmInstruction) { WI_NOP, 0 }; - } else { - code[0] = (WasmInstruction) { WI_GLOBAL_GET, { .l = stack_top_idx } }; - code[1] = (WasmInstruction) { WI_LOCAL_TEE, { .l = mod->stack_base_idx} }; - code[2] = (WasmInstruction) { WI_I32_CONST, { .l = stacksize } }; - code[3] = (WasmInstruction) { WI_I32_ADD, 0 }; - code[4] = (WasmInstruction) { WI_GLOBAL_SET, { .l = stack_top_idx } }; - } - - *pcode = code; -} - -EMIT_FUNC(zero_value, WasmType wt) { - bh_arr(WasmInstruction) code = *pcode; - - switch (wt) { - case WASM_TYPE_INT32: WIL(NULL, WI_I32_CONST, 0); break; - case WASM_TYPE_INT64: WIL(NULL, WI_I64_CONST, 0); break; - case WASM_TYPE_FLOAT32: WIL(NULL, WI_F32_CONST, 0); break; - case WASM_TYPE_FLOAT64: WIL(NULL, WI_F64_CONST, 0); break; - case WASM_TYPE_VAR128: { - static u8 zero_v128[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; - WIP(NULL, WI_V128_CONST, &zero_v128); - break; - } - } - - *pcode = code; -} - -EMIT_FUNC(zero_value_for_type, Type* type, OnyxToken* where) { - bh_arr(WasmInstruction) code = *pcode; - - if (type_is_structlike_strict(type)) { - i32 mem_count = type_linear_member_count(type); - TypeWithOffset two; - - fori (i, 0, mem_count) { - type_linear_member_lookup(type, i, &two); - emit_zero_value_for_type(mod, &code, two.type, where); - } - } - else if (type->kind == Type_Kind_Function) { - WID(NULL, WI_I32_CONST, mod->null_proc_func_idx); - } - else { - WasmType wt = onyx_type_to_wasm_type(type); - if (wt == WASM_TYPE_VOID) { - onyx_report_error(where->pos, Error_Critical, "Cannot produce a zero-value for this type."); - } - emit_zero_value(mod, &code, wt); - } - - *pcode = code; -} - -static i32 generate_type_idx(OnyxWasmModule* mod, Type* ft) { - if (ft->kind != Type_Kind_Function) return -1; - - static char type_repr_buf[128]; - char* t = type_repr_buf; - - Type** param_type = ft->Function.params; - i32 param_count = ft->Function.param_count; - i32 params_left = param_count; - - while (params_left-- > 0) { - if (type_get_param_pass(*param_type) == Param_Pass_By_Implicit_Pointer) { - *(t++) = (char) onyx_type_to_wasm_type(&basic_types[Basic_Kind_Rawptr]); - - } - else if (type_is_structlike_strict(*param_type)) { - u32 mem_count = type_structlike_mem_count(*param_type); - StructMember smem; - - fori (i, 0, mem_count) { - type_lookup_member_by_idx(*param_type, i, &smem); - *(t++) = (char) onyx_type_to_wasm_type(smem.type); - } - - param_count += mem_count - 1; - - } else { - *(t++) = (char) onyx_type_to_wasm_type(*param_type); - } - - param_type++; - } - *(t++) = ':'; - - // HACK: Slightly: the wasm type for structs has to be 0x00 - WasmType return_type = onyx_type_to_wasm_type(ft->Function.return_type); - if (ft->Function.return_type->kind == Type_Kind_Struct && !type_is_compound(ft->Function.return_type)) { - return_type = onyx_type_to_wasm_type(ft->Function.return_type->Struct.linear_members[0].type); - } - - *(t++) = (char) return_type; - *t = '\0'; - - i32 type_idx = 0; - i32 index = shgeti(mod->type_map, type_repr_buf); - if (index != -1) { - type_idx = mod->type_map[index].value; - } else { - // NOTE: Make a new type - WasmFuncType* type = (WasmFuncType*) bh_alloc(mod->allocator, sizeof(WasmFuncType) + sizeof(WasmType) * param_count); - type->return_type = return_type; - type->param_count = param_count; - - fori (i, 0, type->param_count) { - type->param_types[i] = type_repr_buf[i]; - } - - bh_arr_push(mod->types, type); - - shput(mod->type_map, type_repr_buf, mod->next_type_idx); - type_idx = mod->next_type_idx; - mod->next_type_idx++; - } - - return type_idx; -} - -static i32 get_element_idx(OnyxWasmModule* mod, AstFunction* func) { - if (bh_imap_has(&mod->elem_map, (u64) func)) { - return bh_imap_get(&mod->elem_map, (u64) func); - } else { - i32 idx = mod->next_elem_idx; - bh_imap_put(&mod->elem_map, (u64) func, idx); - - i32 func_idx = bh_imap_get(&mod->index_map, (u64) func); - bh_arr_push(mod->elems, func_idx); - - mod->next_elem_idx++; - - return idx; - } -} - -static void emit_function(OnyxWasmModule* mod, AstFunction* fd) { - if (!should_emit_function(fd)) return; - - i32 type_idx = generate_type_idx(mod, fd->type); - - WasmFunc wasm_func = { 0 }; - wasm_func.type_idx = type_idx; - wasm_func.location = fd->token; - - bh_arr_new(mod->allocator, wasm_func.code, 16); - - i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) fd); - mod->current_func_idx = func_idx; - - debug_begin_function(mod, func_idx, fd->token, get_function_name(fd)); - - if (fd == builtin_initialize_data_segments && context.options->use_post_mvp_features) { - emit_initialize_data_segments_body(mod, &wasm_func.code); - - debug_emit_instruction(mod, NULL); - bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 })); - - bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func); - mod->current_func_idx = -1; - - debug_end_function(mod); - return; - } - - if (fd == builtin_run_init_procedures) { - emit_run_init_procedures(mod, &wasm_func.code); - - debug_emit_instruction(mod, NULL); - bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 })); - - bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func); - mod->current_func_idx = -1; - - debug_end_function(mod); - return; - } - - if (fd->body != NULL) { - mod->local_alloc = &wasm_func.locals; - - // NOTE: Generate the local map - u64 localidx = 0; - bh_arr_each(AstParam, param, fd->params) { - switch (type_get_param_pass(param->local->type)) { - case Param_Pass_By_Value: { - if (type_is_structlike_strict(param->local->type)) { - debug_introduce_symbol(mod, param->local->token, DSL_REGISTER, localidx | LOCAL_IS_WASM, param->local->type); - bh_imap_put(&mod->local_map, (u64) param->local, localidx | LOCAL_IS_WASM); - localidx += type_structlike_mem_count(param->local->type); - - break; - } - // fallthrough - } - - case Param_Pass_By_Implicit_Pointer: { - debug_introduce_symbol(mod, param->local->token, DSL_REGISTER, localidx | LOCAL_IS_WASM, param->local->type); - bh_imap_put(&mod->local_map, (u64) param->local, localidx++ | LOCAL_IS_WASM); - break; - } - - default: assert(0); - } - } - - mod->local_alloc->param_count = localidx; - - mod->curr_cc = type_function_get_cc(fd->type); - assert(mod->curr_cc != CC_Undefined); - - bh_arr_clear(mod->stack_leave_patches); - - debug_emit_instruction(mod, fd->token); - debug_emit_instruction(mod, fd->token); - debug_emit_instruction(mod, fd->token); - debug_emit_instruction(mod, fd->token); - debug_emit_instruction(mod, fd->token); - bh_arr_insert_end(wasm_func.code, 5); - fori (i, 0, 5) wasm_func.code[i] = (WasmInstruction) { WI_NOP, 0 }; - - // TODO: Emit debug info for the above instructions - - mod->stack_base_idx = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - - // Generate code - emit_function_body(mod, &wasm_func.code, fd); - - if (mod->local_alloc->max_stack > 0 || mod->curr_cc == CC_Return_Stack) { - emit_stack_enter(mod, &wasm_func.code, mod->local_alloc->max_stack); - - // Place all stack leaves in patch locations. These will (probably) all be - // right before a "return" instruction. - debug_emit_instruction(mod, NULL); - debug_emit_instruction(mod, NULL); - - u64 stack_top_idx = bh_imap_get(&mod->index_map, (u64) &builtin_stack_top); - bh_arr_push(wasm_func.code, ((WasmInstruction) { WI_LOCAL_GET, { .l = mod->stack_base_idx } })); - bh_arr_push(wasm_func.code, ((WasmInstruction) { WI_GLOBAL_SET, { .l = stack_top_idx } })); - - bh_arr_each(PatchInfo, patch, mod->stack_leave_patches) { - wasm_func.code[patch->instruction_index + 0] = (WasmInstruction) { WI_LOCAL_GET, { .l = mod->stack_base_idx } }; - wasm_func.code[patch->instruction_index + 1] = (WasmInstruction) { WI_GLOBAL_SET, { .l = stack_top_idx } }; - } - } - } - - WasmFuncType* ft = mod->types[type_idx]; - emit_zero_value(mod, &wasm_func.code, ft->return_type); - - if (fd->closing_brace) { - debug_emit_instruction(mod, fd->closing_brace); - bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_NOP, 0x00 })); - } - - debug_emit_instruction(mod, NULL); - bh_arr_push(wasm_func.code, ((WasmInstruction){ WI_BLOCK_END, 0x00 })); - - bh_imap_clear(&mod->local_map); - - bh_arr_set_at(mod->funcs, func_idx - mod->foreign_function_count, wasm_func); - mod->current_func_idx = -1; - - debug_end_function(mod); -} - -static void emit_foreign_function(OnyxWasmModule* mod, AstFunction* fd) { - if (!should_emit_function(fd)) return; - - i32 type_idx = generate_type_idx(mod, fd->type); - - WasmImport import = { - .kind = WASM_FOREIGN_FUNCTION, - .idx = type_idx, - .mod = bh_aprintf(global_heap_allocator, "%b", fd->foreign_module->text, fd->foreign_module->length), - .name = bh_aprintf(global_heap_allocator, "%b", fd->foreign_name->text, fd->foreign_name->length), - }; - - bh_arr_push(mod->imports, import); - return; -} - -static void emit_export_directive(OnyxWasmModule* mod, AstDirectiveExport* export) { - assert(export->export_name); - assert(export->export); - - token_toggle_end(export->export_name); - - i64 idx = bh_imap_get(&mod->index_map, (u64) export->export); - - WasmExport wasm_export; - wasm_export.idx = (i32) idx; - - switch (export->export->kind) { - case Ast_Kind_Function: wasm_export.kind = WASM_FOREIGN_FUNCTION; - break; - - case Ast_Kind_Global: wasm_export.kind = WASM_FOREIGN_GLOBAL; - break; - } - - shput(mod->exports, export->export_name->text, wasm_export); - mod->export_count++; - - token_toggle_end(export->export_name); - return; -} - -static void emit_global(OnyxWasmModule* module, AstGlobal* global) { - WasmType global_type = onyx_type_to_wasm_type(global->type); - - WasmGlobal glob = { - .type = global_type, - .mutable = (global->flags & Ast_Flag_Const) == 0, - .initial_value = NULL, - }; - - i32 global_idx = (i32) bh_imap_get(&module->index_map, (u64) global); - - bh_arr_new(global_heap_allocator, glob.initial_value, 1); - - switch (global_type) { - case WASM_TYPE_INT32: bh_arr_push(glob.initial_value, ((WasmInstruction) { WI_I32_CONST, 0 })); break; - case WASM_TYPE_INT64: bh_arr_push(glob.initial_value, ((WasmInstruction) { WI_I64_CONST, 0 })); break; - case WASM_TYPE_FLOAT32: bh_arr_push(glob.initial_value, ((WasmInstruction) { WI_F32_CONST, 0 })); break; - case WASM_TYPE_FLOAT64: bh_arr_push(glob.initial_value, ((WasmInstruction) { WI_F64_CONST, 0 })); break; - - default: assert(("Invalid global type", 0)); break; - } - - bh_arr_set_at(module->globals, global_idx, glob); - - if (global == &builtin_stack_top) - module->stack_top_ptr = &module->globals[global_idx].initial_value[0].data.i1; - - if (global == &builtin_heap_start) - module->heap_start_ptr = &module->globals[global_idx].initial_value[0].data.i1; - - if (global == &builtin_tls_size) - module->globals[global_idx].initial_value[0].data.i1 = module->next_tls_offset; -} - -static void emit_string_literal(OnyxWasmModule* mod, AstStrLit* strlit) { - - // NOTE: Allocating more than necessary, but there are no cases - // in a string literal that create more bytes than already - // existed. You can create less however ('\n' => 0x0a). - i8* strdata = bh_alloc_array(global_heap_allocator, i8, strlit->token->length + 1); - i32 length = string_process_escape_seqs(strdata, strlit->token->text, strlit->token->length); - - i32 index = shgeti(mod->string_literals, (char *) strdata); - if (index != -1) { - StrLitInfo sti = mod->string_literals[index].value; - strlit->data_id = sti.data_id; - strlit->length = sti.len; - - bh_free(global_heap_allocator, strdata); - return; - } - - // :ProperLinking - u32 actual_length = length + (strlit->is_cstr ? 1 : 0); - WasmDatum datum = { - .alignment = 1, - .length = actual_length, - .data = strdata, - }; - - strlit->data_id = emit_data_entry(mod, &datum); - strlit->length = length; - - // :ProperLinking - shput(mod->string_literals, (char *) strdata, ((StrLitInfo) { strlit->data_id, strlit->length })); -} - -static u32 emit_data_entry(OnyxWasmModule *mod, WasmDatum *datum) { - datum->offset_ = 0; - datum->id = NEXT_DATA_ID(mod); - bh_arr_push(mod->data, *datum); - return datum->id; -} - -static void emit_constexpr(ConstExprContext *ctx, AstTyped *node, u32 offset) { - if (!emit_constexpr_(ctx, node, offset)) { - onyx_report_error(node->token->pos, Error_Critical, - "Cannot generate constant data for '%s'.", - onyx_ast_node_kind_string(node->kind)); - } -} - -static b32 emit_constexpr_(ConstExprContext *ctx, AstTyped *node, u32 offset) { -#define CE(type, off) (*((type *) bh_pointer_add(ctx->data, offset + (off)))) - assert(ctx->data_id); - assert(ctx->data); - - b32 retval = 1; - node = (AstTyped *) strip_aliases((AstNode *) node); - - if (node_is_type((AstNode *) node)) { - Type* constructed_type = type_build_from_ast(context.ast_alloc, (AstType *) node); - CE(i32, 0) = constructed_type->id; - return 1; - } - - switch (node->kind) { - case Ast_Kind_Array_Literal: { - AstArrayLiteral* al = (AstArrayLiteral *) node; - - i32 i = 0; - i32 elem_size = type_size_of(al->type->Array.elem); - - bh_arr_each(AstTyped *, expr, al->values) { - retval &= emit_constexpr_(ctx, *expr, i * elem_size + offset); - i++; - } - - break; - } - - case Ast_Kind_Struct_Literal: { - AstStructLiteral* sl = (AstStructLiteral *) node; - - Type* sl_type = sl->type; - // ROBUSTNESS: Handle cases for slices and dynamic arrays - if (sl_type->kind != Type_Kind_Struct && sl_type->kind != Type_Kind_Slice && sl_type->kind != Type_Kind_DynArray) { - retval = 0; - break; - } - - i32 mem_count = type_structlike_mem_count(sl_type); - StructMember smem; - - fori (i, 0, mem_count) { - type_lookup_member_by_idx(sl_type, i, &smem); - retval &= emit_constexpr_(ctx, sl->args.values[i], smem.offset + offset); - } - - break; - } - - case Ast_Kind_StrLit: { - AstStrLit* sl = (AstStrLit *) node; - - // NOTE: This assumes the data_id and the length fields have been filled out - // by emit_string_literal. - if (POINTER_SIZE == 4) { - CE(u32, 0) = 0; - CE(u32, 4) = sl->length; - } else { - CE(u64, 0) = 0; - CE(u64, 8) = sl->length; - } - - assert(sl->data_id > 0); - - DatumPatchInfo patch; - patch.kind = Datum_Patch_Data; - patch.index = ctx->data_id; - patch.location = offset; - patch.data_id = sl->data_id; - patch.offset = 0; - bh_arr_push(ctx->module->data_patches, patch); - - break; - } - - case Ast_Kind_Enum_Value: { - AstEnumValue* ev = (AstEnumValue *) node; - retval &= emit_constexpr_(ctx, (AstTyped *) ev->value, offset); - break; - } - - case Ast_Kind_Function: { - AstFunction* func = (AstFunction *) node; - CE(u32, 0) = get_element_idx(ctx->module, func); - break; - } - - case Ast_Kind_Size_Of: { - AstSizeOf* so = (AstSizeOf *) node; - CE(u32, 0) = so->size; - break; - } - - case Ast_Kind_Align_Of: { - AstAlignOf* ao = (AstAlignOf *) node; - CE(u32, 0) = ao->alignment; - break; - } - - case Ast_Kind_Zero_Value: { - memset(bh_pointer_add(ctx->data, offset), 0, type_size_of(node->type)); - break; - } - - case Ast_Kind_NumLit: { - // NOTE: This makes a big assumption that we are running on a - // little endian machine, since WebAssembly is little endian - // by specification. This is probably a safe assumption, but - // should probably be fixed at some point. - // - brendanfh 2020/12/15 - - Type* effective_type = node->type; - - if (effective_type->kind == Type_Kind_Enum) - effective_type = effective_type->Enum.backing; - - switch (effective_type->Basic.kind) { - case Basic_Kind_Bool: - case Basic_Kind_I8: - case Basic_Kind_U8: - CE(i8, 0) = (i8) ((AstNumLit *) node)->value.i; - return retval; - - case Basic_Kind_I16: - case Basic_Kind_U16: - CE(i16, 0) = (i16) ((AstNumLit *) node)->value.i; - return retval; - - case Basic_Kind_I32: - case Basic_Kind_U32: - case Basic_Kind_Rawptr: - CE(i32, 0) = ((AstNumLit *) node)->value.i; - return retval; - - case Basic_Kind_I64: - case Basic_Kind_U64: - CE(i64, 0) = ((AstNumLit *) node)->value.l; - return retval; - - case Basic_Kind_F32: - CE(f32, 0) = ((AstNumLit *) node)->value.f; - return retval; - - case Basic_Kind_F64: - CE(f64, 0) = ((AstNumLit *) node)->value.d; - return retval; - - default: break; - } - - //fallthrough - } - - case Ast_Kind_Code_Block: break; - - default: retval = 0; - } - - return retval; - -#undef CE -} - -static void emit_memory_reservation(OnyxWasmModule* mod, AstMemRes* memres) { - // :ProperLinking - Type* effective_type = memres->type; - - u64 alignment = type_alignment_of(effective_type); - u64 size = type_size_of(effective_type); - - if (type_table_node != NULL && (AstMemRes *) type_table_node == memres) { - u64 table_location = build_type_table(mod); - memres->data_id = table_location; - return; - } - - if (foreign_blocks_node != NULL && (AstMemRes *) foreign_blocks_node == memres) { - u64 foreign_blocks_location = build_foreign_blocks(mod); - memres->data_id = foreign_blocks_location; - return; - } - - if (tagged_procedures_node != NULL && (AstMemRes *) tagged_procedures_node == memres) { - u64 tagged_procedures_location = build_tagged_procedures(mod); - memres->data_id = tagged_procedures_location; - return; - } - - if (memres->threadlocal) { - memres->tls_offset = mod->next_tls_offset; - bh_align(memres->tls_offset, alignment); - mod->next_tls_offset = memres->tls_offset + size; - - } else { - // :ProperLinking - u8* data = NULL; - if (memres->initial_value != NULL) { - assert(!memres->threadlocal); - data = bh_alloc(global_heap_allocator, size); - } - - WasmDatum datum = { - .alignment = alignment, - .length = size, - .data = data, - }; - memres->data_id = emit_data_entry(mod, &datum); - - if (memres->initial_value != NULL) { - ConstExprContext constexpr_ctx; - constexpr_ctx.module = mod; - constexpr_ctx.data = data; - constexpr_ctx.data_id = memres->data_id; - emit_constexpr(&constexpr_ctx, memres->initial_value, 0); - } - } -} - -static void emit_file_contents(OnyxWasmModule* mod, AstFileContents* fc) { - // INVESTIGATE: I think that filename should always be NULL when this function starts because - // a file contents entity is only processed once and therefore should only go through this step - // once. But somehow filename isn't NULL occasionally so I have to check for that... - // - brendanfh 2021/05/23 - if (fc->filename == NULL) { - const char* parent_file = fc->token->pos.filename; - if (parent_file == NULL) parent_file = "."; - - char* parent_folder = bh_path_get_parent(parent_file, global_scratch_allocator); - - OnyxToken *filename_token = fc->filename_expr->token; - - token_toggle_end(filename_token); - char* temp_fn = bh_alloc_array(global_scratch_allocator, char, filename_token->length); - i32 temp_fn_len = string_process_escape_seqs(temp_fn, filename_token->text, filename_token->length); - char* filename = bh_lookup_file(temp_fn, parent_folder, "", 0, NULL, 0); - fc->filename = bh_strdup(global_heap_allocator, filename); - token_toggle_end(filename_token); - } - - i32 index = shgeti(mod->loaded_file_info, fc->filename); - if (index != -1) { - StrLitInfo info = mod->loaded_file_info[index].value; - fc->data_id = info.data_id; - fc->size = info.len; - return; - } - - if (!bh_file_exists(fc->filename)) { - onyx_report_error(fc->token->pos, Error_Critical, - "Unable to open file for reading, '%s'.", - fc->filename); - return; - } - - // :RelativeFiles This should be relative to the current directory by default; However, - // if the filename is prefixed with a './' or '.\\' then it should be relative to the - // file in which is was inclded. The loaded file info above should probably use the full - // file path in order to avoid duplicates. - bh_file_contents contents = bh_file_read_contents(global_heap_allocator, fc->filename); - u8* actual_data = bh_alloc(global_heap_allocator, contents.length + 1); - u32 length = contents.length + 1; - memcpy(actual_data, contents.data, contents.length); - actual_data[contents.length] = 0; - bh_file_contents_free(&contents); - - WasmDatum datum = { - .alignment = 16, - .length = length, - .data = actual_data, - }; - fc->data_id = emit_data_entry(mod, &datum); - fc->size = length - 1; - - shput(mod->loaded_file_info, fc->filename, ((StrLitInfo) { - .data_id = fc->data_id, - .len = fc->size, - })); -} - -OnyxWasmModule onyx_wasm_module_create(bh_allocator alloc) { - OnyxWasmModule module = { - .allocator = alloc, - - .type_map = NULL, - .next_type_idx = 0, - .types = NULL, - - .funcs = NULL, - .next_func_idx = 0, - - .exports = NULL, - .export_count = 0, - - .imports = NULL, - - .globals = NULL, - .next_global_idx = 0, - - .data = NULL, - .data_patches = NULL, - - .next_tls_offset = 0, - .tls_size_ptr = NULL, - - .elems = NULL, - .next_elem_idx = 0, - - .needs_memory_section = 0, - .memory_min_size = 0, - .memory_max_size = 0, - - .structured_jump_target = NULL, - .return_location_stack = NULL, - .local_allocations = NULL, - .stack_leave_patches = NULL, - .deferred_stmts = NULL, - - .heap_start_ptr = NULL, - - .stack_top_ptr = NULL, - .stack_base_idx = 0, - - .foreign_function_count = 0, - - .null_proc_func_idx = -1, - - .libraries = NULL, - .library_paths = NULL, - - .foreign_blocks = NULL, - .next_foreign_block_idx = 0, - - .procedures_with_tags = NULL - }; - - bh_arena* eid = bh_alloc(global_heap_allocator, sizeof(bh_arena)); - bh_arena_init(eid, global_heap_allocator, 8196); - module.extended_instr_data = eid; - module.extended_instr_alloc = bh_arena_allocator(eid); - - bh_arr_new(alloc, module.types, 4); - bh_arr_new(alloc, module.funcs, 4); - bh_arr_new(alloc, module.imports, 4); - bh_arr_new(alloc, module.globals, 4); - bh_arr_new(alloc, module.data, 4); - bh_arr_new(alloc, module.elems, 4); - bh_arr_new(alloc, module.libraries, 4); - bh_arr_new(alloc, module.library_paths, 4); - - bh_arr_new(global_heap_allocator, module.return_location_stack, 4); - bh_arr_new(global_heap_allocator, module.structured_jump_target, 16); - bh_arr_set_length(module.structured_jump_target, 0); - - sh_new_arena(module.type_map); - sh_new_arena(module.exports); - sh_new_arena(module.loaded_file_info); - sh_new_arena(module.string_literals); - - bh_imap_init(&module.index_map, global_heap_allocator, 128); - bh_imap_init(&module.local_map, global_heap_allocator, 16); - bh_imap_init(&module.elem_map, global_heap_allocator, 16); - - bh_arr_new(global_heap_allocator, module.deferred_stmts, 4); - bh_arr_new(global_heap_allocator, module.local_allocations, 4); - bh_arr_new(global_heap_allocator, module.stack_leave_patches, 4); - bh_arr_new(global_heap_allocator, module.foreign_blocks, 4); - bh_arr_new(global_heap_allocator, module.procedures_with_tags, 4); - bh_arr_new(global_heap_allocator, module.data_patches, 4); - -#ifdef ENABLE_DEBUG_INFO - module.debug_context = bh_alloc_item(context.ast_alloc, DebugContext); - module.debug_context->allocator = global_heap_allocator; - module.debug_context->next_file_id = 0; - module.debug_context->next_sym_id = 0; - module.debug_context->last_token = NULL; - - sh_new_arena(module.debug_context->file_info); - bh_arr_new(global_heap_allocator, module.debug_context->sym_info, 32); - bh_arr_new(global_heap_allocator, module.debug_context->sym_patches, 32); - bh_arr_new(global_heap_allocator, module.debug_context->funcs, 16); - - bh_buffer_init(&module.debug_context->op_buffer, global_heap_allocator, 1024); -#endif - - return module; -} - -void emit_entity(Entity* ent) { - OnyxWasmModule* module = context.wasm_module; - module->current_func_idx = -1; - - switch (ent->type) { - case Entity_Type_Foreign_Function_Header: - if (!should_emit_function(ent->function)) break; - - module->foreign_function_count++; - emit_foreign_function(module, ent->function); - // fallthrough - - case Entity_Type_Function_Header: - if (!should_emit_function(ent->function)) break; - - if (context.options->print_function_mappings) { - bh_printf("%d -> %s:%d:%d\n", - module->next_func_idx, - ent->expr->token->pos.filename, - ent->expr->token->pos.line, - ent->expr->token->pos.column); - } - - bh_imap_put(&module->index_map, (u64) ent->function, module->next_func_idx++); - - if (ent->function->flags & Ast_Flag_Proc_Is_Null) { - if (module->null_proc_func_idx == -1) module->null_proc_func_idx = get_element_idx(module, ent->function); - } - - if (ent->function->tags != NULL) { - bh_arr_push(module->procedures_with_tags, ent->function); - } - break; - - case Entity_Type_Global_Header: - bh_imap_put(&module->index_map, (u64) ent->global, module->next_global_idx++); - break; - - case Entity_Type_String_Literal: { - emit_string_literal(module, (AstStrLit *) ent->strlit); - break; - } - - case Entity_Type_File_Contents: { - emit_file_contents(module, (AstFileContents *) ent->file_contents); - break; - } - - case Entity_Type_Memory_Reservation: { - emit_memory_reservation(module, (AstMemRes *) ent->mem_res); - break; - } - - case Entity_Type_Process_Directive: { - if (ent->expr->kind == Ast_Kind_Directive_Export) { - emit_export_directive(module, (AstDirectiveExport *) ent->expr); - } - - if (ent->expr->kind == Ast_Kind_Directive_Library) { - bh_arr_push(module->libraries, ent->library->library_name); - } - break; - } - - case Entity_Type_Foreign_Block: { - ent->foreign_block->foreign_block_number = module->next_foreign_block_idx++; - bh_arr_push(module->foreign_blocks, (AstForeignBlock *) ent->foreign_block); - break; - } - - case Entity_Type_Function: emit_function(module, ent->function); break; - case Entity_Type_Global: emit_global(module, ent->global); break; - - // Cleanup: Maybe these should be printed elsewhere? - // Also, they should be sorted? Or have that ability? - case Entity_Type_Note: { - if (!context.options->print_notes) break; - - AstNote* note = (AstNote *) ent->expr; - OnyxFilePos pos = note->token->pos; - - bh_printf("Note: %b %s:%d:%d\n", note->token->text, note->token->length, pos.filename, pos.line, pos.column); - - break; - } - - default: break; - } - - ent->state = Entity_State_Finalized; -} - -void onyx_wasm_module_link(OnyxWasmModule *module, OnyxWasmLinkOptions *options) { - // If the pointer size is going to change, - // the code will probably need to be altered. - assert(POINTER_SIZE == 4); - - module->memory_min_size = options->memory_min_size; - module->memory_max_size = options->memory_max_size; - - if (context.options->use_multi_threading || options->import_memory) { - module->needs_memory_section = 0; - - WasmImport mem_import = { - .kind = WASM_FOREIGN_MEMORY, - .min = options->memory_min_size, - .max = options->memory_max_size, // NOTE: Why not use all 4 Gigs of memory? - .shared = context.options->runtime == Runtime_Js, - - .mod = options->import_memory_module_name, - .name = options->import_memory_import_name, - }; - - bh_arr_push(module->imports, mem_import); - - } else { - module->needs_memory_section = 1; - } - - if (options->export_memory) { - WasmExport mem_export = { - .kind = WASM_FOREIGN_MEMORY, - .idx = 0, - }; - - shput(module->exports, options->export_memory_name, mem_export); - module->export_count++; - } - - if (options->export_func_table) { - WasmExport func_table_export = { - .kind = WASM_FOREIGN_TABLE, - .idx = 0, - }; - - shput(module->exports, options->export_func_table_name, func_table_export); - module->export_count++; - } - - u32 datum_offset = options->null_reserve_size; - bh_arr_each(WasmDatum, datum, module->data) { - assert(datum->id > 0); - - bh_align(datum_offset, datum->alignment); - datum->offset_ = datum_offset; - - datum_offset += datum->length; - } - - bh_arr_each(DatumPatchInfo, patch, module->data_patches) { - assert(patch->data_id > 0); - WasmDatum *datum = &module->data[patch->data_id - 1]; - assert(datum->id == patch->data_id); - - switch (patch->kind) { - case Datum_Patch_Instruction: { - WasmFunc *func = &module->funcs[patch->index - module->foreign_function_count]; - - assert(func->code[patch->location].type == WI_PTR_CONST); - func->code[patch->location].data.l = (u64) datum->offset_ + patch->offset; - break; - } - - case Datum_Patch_Data: { - WasmDatum *datum_to_alter = &module->data[patch->index - 1]; - assert(datum_to_alter->id == patch->index); - - *((u32 *) bh_pointer_add(datum_to_alter->data, patch->location)) = (u32) datum->offset_ + patch->offset; - break; - } - - case Datum_Patch_Relative: { - WasmDatum *datum_to_alter = &module->data[patch->index - 1]; - assert(datum_to_alter->id == patch->index); - - u32 *addr = (u32 *) bh_pointer_add(datum_to_alter->data, patch->location); - if (*addr != 0) { - *addr += (u32) datum->offset_ + patch->offset; - } - - break; - } - - default: assert(0); - } - } - - assert(module->stack_top_ptr && module->heap_start_ptr); - - *module->stack_top_ptr = datum_offset; - bh_align(*module->stack_top_ptr, options->stack_alignment); - - *module->heap_start_ptr = *module->stack_top_ptr + options->stack_size; - bh_align(*module->heap_start_ptr, 16); -} - -void onyx_wasm_module_free(OnyxWasmModule* module) { - if (module->extended_instr_data != NULL) - bh_arena_free(module->extended_instr_data); - - bh_arr_free(module->types); - bh_arr_free(module->funcs); - bh_imap_free(&module->local_map); - bh_imap_free(&module->index_map); - shfree(module->type_map); - shfree(module->exports); -} - - -b32 onyx_wasm_build_link_options_from_node(OnyxWasmLinkOptions *opts, AstTyped *node) { - node = (AstTyped *) strip_aliases((AstNode *) node); - - assert(node && node->kind == Ast_Kind_Struct_Literal); - assert(builtin_link_options_type); - - Type *link_options_type = type_build_from_ast(context.ast_alloc, builtin_link_options_type); - - AstStructLiteral *input = (AstStructLiteral *) node; - - StructMember smem; - b32 out_is_valid; - - // TODO: These should be properly error handled. - assert(type_lookup_member(link_options_type, "stack_first", &smem)); - opts->stack_first = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid) != 0; - if (!out_is_valid) return 0; - - assert(type_lookup_member(link_options_type, "stack_size", &smem)); - opts->stack_size = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid); - if (!out_is_valid) return 0; - - assert(type_lookup_member(link_options_type, "stack_alignment", &smem)); - opts->stack_alignment = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid); - if (!out_is_valid) return 0; - - assert(type_lookup_member(link_options_type, "null_reserve_size", &smem)); - opts->null_reserve_size = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid); - if (!out_is_valid) return 0; - - assert(type_lookup_member(link_options_type, "import_memory", &smem)); - opts->import_memory = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid) != 0; - if (!out_is_valid) return 0; - - assert(type_lookup_member(link_options_type, "import_memory_module_name", &smem)); - opts->import_memory_module_name = get_expression_string_value(input->args.values[smem.idx], &out_is_valid); - if (!out_is_valid) return 0; - - assert(type_lookup_member(link_options_type, "import_memory_import_name", &smem)); - opts->import_memory_import_name = get_expression_string_value(input->args.values[smem.idx], &out_is_valid); - if (!out_is_valid) return 0; - - assert(type_lookup_member(link_options_type, "export_memory", &smem)); - opts->export_memory = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid) != 0; - if (!out_is_valid) return 0; - - assert(type_lookup_member(link_options_type, "export_memory_name", &smem)); - opts->export_memory_name = get_expression_string_value(input->args.values[smem.idx], &out_is_valid); - if (!out_is_valid) return 0; - - assert(type_lookup_member(link_options_type, "export_func_table", &smem)); - opts->export_func_table = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid) != 0; - if (!out_is_valid) return 0; - - assert(type_lookup_member(link_options_type, "export_func_table_name", &smem)); - opts->export_func_table_name = get_expression_string_value(input->args.values[smem.idx], &out_is_valid); - if (!out_is_valid) return 0; - - assert(type_lookup_member(link_options_type, "memory_min_size", &smem)); - opts->memory_min_size = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid); - if (!out_is_valid) return 0; - - assert(type_lookup_member(link_options_type, "memory_max_size", &smem)); - opts->memory_max_size = get_expression_integer_value(input->args.values[smem.idx], &out_is_valid); - if (!out_is_valid) return 0; - - return 1; -} - -#include "wasm_output.h" diff --git a/src/wasm_intrinsics.h b/src/wasm_intrinsics.h deleted file mode 100644 index 49dae8c0..00000000 --- a/src/wasm_intrinsics.h +++ /dev/null @@ -1,452 +0,0 @@ -// This file is directly included in src/onxywasm.c -// It is here purely to decrease the amount of clutter in the main file. - - -// IMPROVE: This implementation assumes that the source and destination buffers do not overlap. -// The specification for memory.copy in WASM does work even if the buffers overlap. -// Also, this implementation copies byte-by-byte, which is terrible. It should copy -// quad word by quad word, and then the additional bytes if the count was not divisible by 8. -// :32BitPointers -EMIT_FUNC_NO_ARGS(intrinsic_memory_copy) { - bh_arr(WasmInstruction) code = *pcode; - - // The stack should look like this: - // - // - // - - u64 count_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32); - u64 source_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - u64 dest_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - - WIL(NULL, WI_LOCAL_SET, count_local); - WIL(NULL, WI_LOCAL_SET, source_local); - WIL(NULL, WI_LOCAL_SET, dest_local); - - // count is greater than 0 - WIL(NULL, WI_LOCAL_GET, count_local); - WID(NULL, WI_I32_CONST, 0); - WI(NULL, WI_I32_GT_S); - - WID(NULL, WI_IF_START, 0x40); - WID(NULL, WI_LOOP_START, 0x40); - - WIL(NULL, WI_LOCAL_GET, count_local); - WID(NULL, WI_I32_CONST, 1); - WI(NULL, WI_I32_SUB); - WIL(NULL, WI_LOCAL_SET, count_local); - - WIL(NULL, WI_LOCAL_GET, dest_local); - WIL(NULL, WI_LOCAL_GET, count_local); - WI(NULL, WI_PTR_ADD); - - WIL(NULL, WI_LOCAL_GET, source_local); - WIL(NULL, WI_LOCAL_GET, count_local); - WI(NULL, WI_PTR_ADD); - - WID(NULL, WI_I32_LOAD_8_U, ((WasmInstructionData) { 0, 0 })); - WID(NULL, WI_I32_STORE_8, ((WasmInstructionData) { 0, 0 })); - - WIL(NULL, WI_LOCAL_GET, count_local); - WID(NULL, WI_I32_CONST, 0); - WI(NULL, WI_I32_GT_S); - WID(NULL, WI_COND_JUMP, 0x00); - - WI(NULL, WI_LOOP_END); - WI(NULL, WI_IF_END); - - local_raw_free(mod->local_alloc, WASM_TYPE_INT32); - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - - *pcode = code; -} - -EMIT_FUNC_NO_ARGS(intrinsic_memory_fill) { - bh_arr(WasmInstruction) code = *pcode; - - // The stack should look like this: - // - // - // - - u64 count_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32); - u64 byte_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32); - u64 dest_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - - WIL(NULL, WI_LOCAL_SET, count_local); - WIL(NULL, WI_LOCAL_SET, byte_local); - WIL(NULL, WI_LOCAL_SET, dest_local); - - // count is greater than 0 - WIL(NULL, WI_LOCAL_GET, count_local); - WID(NULL, WI_I32_CONST, 0); - WI(NULL, WI_I32_GT_S); - - WID(NULL, WI_IF_START, 0x40); - WID(NULL, WI_LOOP_START, 0x40); - - WIL(NULL, WI_LOCAL_GET, count_local); - WID(NULL, WI_I32_CONST, 1); - WI(NULL, WI_I32_SUB); - WIL(NULL, WI_LOCAL_SET, count_local); - - WIL(NULL, WI_LOCAL_GET, dest_local); - WIL(NULL, WI_LOCAL_GET, count_local); - WI(NULL, WI_PTR_ADD); - - WIL(NULL, WI_LOCAL_GET, byte_local); - WID(NULL, WI_I32_STORE_8, ((WasmInstructionData) { 0, 0 })); - - WIL(NULL, WI_LOCAL_GET, count_local); - WID(NULL, WI_I32_CONST, 0); - WI(NULL, WI_I32_GT_S); - WID(NULL, WI_COND_JUMP, 0x00); - - WI(NULL, WI_LOOP_END); - WI(NULL, WI_IF_END); - - local_raw_free(mod->local_alloc, WASM_TYPE_INT32); - local_raw_free(mod->local_alloc, WASM_TYPE_INT32); - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - - *pcode = code; -} - -EMIT_FUNC(initialize_type, Type* type, OnyxToken* where) { - bh_arr(WasmInstruction) code = *pcode; - - switch (type->kind) { - case Type_Kind_Pointer: - case Type_Kind_Basic: { - WasmType basic_type = onyx_type_to_wasm_type(type); - emit_zero_value(mod, &code, basic_type); - emit_store_instruction(mod, &code, type, 0); - break; - } - - case Type_Kind_Struct: { - u64 value_ptr = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR); - WIL(NULL, WI_LOCAL_SET, value_ptr); - - bh_arr_each(StructMember *, psmem, type->Struct.memarr) { - StructMember* smem = *psmem; - if (smem->initial_value == NULL || *smem->initial_value == NULL) continue; - - WIL(NULL, WI_LOCAL_GET, value_ptr); - emit_expression(mod, &code, *smem->initial_value); - emit_store_instruction(mod, &code, smem->type, smem->offset); - } - - local_raw_free(mod->local_alloc, WASM_TYPE_PTR); - break; - } - - default: - onyx_report_error(where->pos, Error_Critical, - "Unable to initialize type, '%s'. The reason for this is largely due to the compiler not knowing what the initial value should be.", - type_get_name(type)); - break; - } - - *pcode = code; -} - -EMIT_FUNC(intrinsic_atomic_wait, Type* type, OnyxToken* where) { - if (type->kind != Type_Kind_Basic) goto bad_type; - - bh_arr(WasmInstruction) code = *pcode; - - switch (type->Basic.kind) { - case Basic_Kind_I32: - case Basic_Kind_U32: WID(NULL, WI_ATOMIC_WAIT32, ((WasmInstructionData) { 2, 0 })); break; - - case Basic_Kind_I64: - case Basic_Kind_U64: WID(NULL, WI_ATOMIC_WAIT64, ((WasmInstructionData) { 3, 0 })); break; - - default: goto bad_type; - } - - *pcode = code; - return; - -bad_type: - onyx_report_error(where->pos, Error_Critical, "Bad type for atomic wait, '%s'. Only i32 and i64 are supported.", type_get_name(type)); -} - -EMIT_FUNC_NO_ARGS(intrinsic_atomic_notify) { - bh_arr(WasmInstruction) code = *pcode; - WID(NULL, WI_ATOMIC_NOTIFY, ((WasmInstructionData) { 2, 0 })); - *pcode = code; -} - -EMIT_FUNC_NO_ARGS(intrinsic_atomic_fence) { - bh_arr(WasmInstruction) code = *pcode; - WI(NULL, WI_ATOMIC_FENCE); - *pcode = code; -} - -EMIT_FUNC(intrinsic_atomic_load, Type* type, OnyxToken* where) { - if (type->kind != Type_Kind_Basic) goto bad_type; - - bh_arr(WasmInstruction) code = *pcode; - - switch (type->Basic.kind) { - case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_LOAD8_U, ((WasmInstructionData) { 0, 0 })); break; - case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_LOAD16_U, ((WasmInstructionData) { 1, 0 })); break; - - case Basic_Kind_I32: - case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_LOAD, ((WasmInstructionData) { 2, 0 })); break; - - case Basic_Kind_I64: - case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_LOAD, ((WasmInstructionData) { 3, 0 })); break; - - default: goto bad_type; - } - - *pcode = code; - return; - -bad_type: - onyx_report_error(where->pos, Error_Critical, "Bad type for atomic load, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); -} - -EMIT_FUNC(intrinsic_atomic_store, Type* type, OnyxToken* where) { - if (type->kind != Type_Kind_Basic) goto bad_type; - - bh_arr(WasmInstruction) code = *pcode; - - switch (type->Basic.kind) { - case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_STORE8, ((WasmInstructionData) { 0, 0 })); break; - case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_STORE16, ((WasmInstructionData) { 1, 0 })); break; - - case Basic_Kind_I32: - case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_STORE, ((WasmInstructionData) { 2, 0 })); break; - - case Basic_Kind_I64: - case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_STORE, ((WasmInstructionData) { 3, 0 })); break; - - default: goto bad_type; - } - - *pcode = code; - return; - -bad_type: - onyx_report_error(where->pos, Error_Critical, "Bad type for atomic store, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); -} - -EMIT_FUNC(intrinsic_atomic_add, Type* type, OnyxToken* where) { - if (type->kind != Type_Kind_Basic) goto bad_type; - - bh_arr(WasmInstruction) code = *pcode; - - switch (type->Basic.kind) { - case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_ADD8_U, ((WasmInstructionData) { 0, 0 })); break; - case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_ADD16_U, ((WasmInstructionData) { 1, 0 })); break; - - case Basic_Kind_I32: - case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_ADD, ((WasmInstructionData) { 2, 0 })); break; - - case Basic_Kind_I64: - case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_ADD, ((WasmInstructionData) { 3, 0 })); break; - - default: goto bad_type; - } - - *pcode = code; - return; - -bad_type: - onyx_report_error(where->pos, Error_Critical, "Bad type for atomic add, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); -} - -EMIT_FUNC(intrinsic_atomic_sub, Type* type, OnyxToken* where) { - if (type->kind != Type_Kind_Basic) goto bad_type; - - bh_arr(WasmInstruction) code = *pcode; - - switch (type->Basic.kind) { - case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_SUB8_U, ((WasmInstructionData) { 0, 0 })); break; - case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_SUB16_U, ((WasmInstructionData) { 1, 0 })); break; - - case Basic_Kind_I32: - case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_SUB, ((WasmInstructionData) { 2, 0 })); break; - - case Basic_Kind_I64: - case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_SUB, ((WasmInstructionData) { 3, 0 })); break; - - default: goto bad_type; - } - - *pcode = code; - return; - -bad_type: - onyx_report_error(where->pos, Error_Critical, "Bad type for atomic sub, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); -} - -EMIT_FUNC(intrinsic_atomic_and, Type* type, OnyxToken* where) { - if (type->kind != Type_Kind_Basic) goto bad_type; - - bh_arr(WasmInstruction) code = *pcode; - - switch (type->Basic.kind) { - case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_AND8_U, ((WasmInstructionData) { 0, 0 })); break; - case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_AND16_U, ((WasmInstructionData) { 1, 0 })); break; - - case Basic_Kind_I32: - case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_AND, ((WasmInstructionData) { 2, 0 })); break; - - case Basic_Kind_I64: - case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_AND, ((WasmInstructionData) { 3, 0 })); break; - - default: goto bad_type; - } - - *pcode = code; - return; - -bad_type: - onyx_report_error(where->pos, Error_Critical, "Bad type for atomic and, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); -} - -EMIT_FUNC(intrinsic_atomic_or, Type* type, OnyxToken* where) { - if (type->kind != Type_Kind_Basic) goto bad_type; - - bh_arr(WasmInstruction) code = *pcode; - - switch (type->Basic.kind) { - case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_OR8_U, ((WasmInstructionData) { 0, 0 })); break; - case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_OR16_U, ((WasmInstructionData) { 1, 0 })); break; - - case Basic_Kind_I32: - case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_OR, ((WasmInstructionData) { 2, 0 })); break; - - case Basic_Kind_I64: - case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_OR, ((WasmInstructionData) { 3, 0 })); break; - - default: goto bad_type; - } - - *pcode = code; - return; - -bad_type: - onyx_report_error(where->pos, Error_Critical, "Bad type for atomic or, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); -} - -EMIT_FUNC(intrinsic_atomic_xor, Type* type, OnyxToken* where) { - if (type->kind != Type_Kind_Basic) goto bad_type; - - bh_arr(WasmInstruction) code = *pcode; - - switch (type->Basic.kind) { - case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_XOR8_U, ((WasmInstructionData) { 0, 0 })); break; - case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_XOR16_U, ((WasmInstructionData) { 1, 0 })); break; - - case Basic_Kind_I32: - case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_XOR, ((WasmInstructionData) { 2, 0 })); break; - - case Basic_Kind_I64: - case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_XOR, ((WasmInstructionData) { 3, 0 })); break; - - default: goto bad_type; - } - - *pcode = code; - return; - -bad_type: - onyx_report_error(where->pos, Error_Critical, "Bad type for atomic xor, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); -} - -EMIT_FUNC(intrinsic_atomic_xchg, Type* type, OnyxToken* where) { - if (type->kind != Type_Kind_Basic) goto bad_type; - - bh_arr(WasmInstruction) code = *pcode; - - switch (type->Basic.kind) { - case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_XCHG8_U, ((WasmInstructionData) { 0, 0 })); break; - case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_XCHG16_U, ((WasmInstructionData) { 1, 0 })); break; - - case Basic_Kind_I32: - case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_XCHG, ((WasmInstructionData) { 2, 0 })); break; - - case Basic_Kind_I64: - case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_XCHG, ((WasmInstructionData) { 3, 0 })); break; - - default: goto bad_type; - } - - *pcode = code; - return; - -bad_type: - onyx_report_error(where->pos, Error_Critical, "Bad type for atomic xchg, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); -} - -EMIT_FUNC(intrinsic_atomic_cmpxchg, Type* type, OnyxToken* where) { - if (type->kind != Type_Kind_Basic) goto bad_type; - - bh_arr(WasmInstruction) code = *pcode; - - switch (type->Basic.kind) { - case Basic_Kind_U8: WID(NULL, WI_ATOMIC_I32_CMPXCHG8_U, ((WasmInstructionData) { 0, 0 })); break; - case Basic_Kind_U16: WID(NULL, WI_ATOMIC_I32_CMPXCHG16_U, ((WasmInstructionData) { 1, 0 })); break; - - case Basic_Kind_I32: - case Basic_Kind_U32: WID(NULL, WI_ATOMIC_I32_CMPXCHG, ((WasmInstructionData) { 2, 0 })); break; - - case Basic_Kind_I64: - case Basic_Kind_U64: WID(NULL, WI_ATOMIC_I64_CMPXCHG, ((WasmInstructionData) { 3, 0 })); break; - - default: goto bad_type; - } - - *pcode = code; - return; - -bad_type: - onyx_report_error(where->pos, Error_Critical, "Bad type for atomic cmpxchg, '%s'. Only u8, u16, u32, i32, u64, and i64 are supported.", type_get_name(type)); -} - -EMIT_FUNC_NO_ARGS(initialize_data_segments_body) { - // :ProperLinking - if (!context.options->use_multi_threading || !context.options->use_post_mvp_features) return; - - bh_arr(WasmInstruction) code = *pcode; - - // - // Because this code is generated direction in the function - // it is assumed that EVERY data entry will be entered by - // this point. If data section entries can be entered after - // function body generation starts, this code will have to - // move to a link phase thing. - i32 index = 0; - bh_arr_each(WasmDatum, datum, mod->data) { - assert(datum->id > 0); - if (datum->data == NULL) { index++; continue; } - - emit_data_relocation(mod, &code, datum->id); - WID(NULL, WI_PTR_CONST, 0); - WID(NULL, WI_I32_CONST, datum->length); - WID(NULL, WI_MEMORY_INIT, ((WasmInstructionData) { index, 0 })); - - index += 1; - } - - *pcode = code; -} - -EMIT_FUNC_NO_ARGS(run_init_procedures) { - bh_arr(WasmInstruction) code = *pcode; - - bh_arr_each(AstFunction *, func, init_procedures) { - i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) *func); - debug_emit_instruction(mod, NULL); - bh_arr_push(code, ((WasmInstruction){ WI_CALL, func_idx })); - } - - *pcode = code; -} diff --git a/src/wasm_output.h b/src/wasm_output.h deleted file mode 100644 index 74213c57..00000000 --- a/src/wasm_output.h +++ /dev/null @@ -1,1061 +0,0 @@ -// This file is included in src/onyxwasm.c. -// It is separated because of its fundamentally different goals. - -//------------------------------------------------- -// BINARY OUPUT -//------------------------------------------------- - -#define WASM_SECTION_ID_CUSTOM 0 -#define WASM_SECTION_ID_TYPE 1 -#define WASM_SECTION_ID_IMPORT 2 -#define WASM_SECTION_ID_FUNCTION 3 -#define WASM_SECTION_ID_TABLE 4 -#define WASM_SECTION_ID_MEMORY 5 -#define WASM_SECTION_ID_GLOBAL 6 -#define WASM_SECTION_ID_EXPORT 7 -#define WASM_SECTION_ID_START 8 -#define WASM_SECTION_ID_ELEMENT 9 -#define WASM_SECTION_ID_DATACOUNT 12 -#define WASM_SECTION_ID_CODE 10 -#define WASM_SECTION_ID_DATA 11 - -typedef i32 vector_func(void*, bh_buffer*); - -static const u8 ONYX_MAGIC_STRING[] = "ONYX"; -static const u8 WASM_MAGIC_STRING[] = { 0x00, 0x61, 0x73, 0x6D }; -static const u8 WASM_VERSION[] = { 0x01, 0x00, 0x00, 0x00 }; - -static inline i32 output_unsigned_integer(u64 i, bh_buffer *buff) { - i32 leb_len; - u8* leb = uint_to_uleb128(i, &leb_len); - bh_buffer_append(buff, leb, leb_len); - return leb_len; -} - -static inline i32 output_custom_section_name(char *name, bh_buffer *buff) { - u32 len = strlen(name); - i32 len_len = output_unsigned_integer(len, buff); - bh_buffer_append(buff, name, len); - return len_len + len; -} - -static void output_instruction(WasmFunc* func, WasmInstruction* instr, bh_buffer* buff); - -static i32 output_vector(void** arr, i32 stride, i32 arrlen, vector_func elem, bh_buffer* vec_buff) { - i32 len; - u8* leb = uint_to_uleb128((u64) arrlen, &len); - bh_buffer_append(vec_buff, leb, len); - - i32 i = 0; - while (i < arrlen) { - elem(*arr, vec_buff); - arr = bh_pointer_add(arr, stride); - i++; - } - - return vec_buff->length; -} - -static i32 output_name(const char* start, i32 length, bh_buffer* buff) { - i32 leb_len, prev_len = buff->length; - u8* leb = uint_to_uleb128((u64) length, &leb_len); - bh_buffer_append(buff, leb, leb_len); - bh_buffer_append(buff, start, length); - return buff->length - prev_len; -} - -static i32 output_limits(i32 min, i32 max, b32 shared, bh_buffer* buff) { - i32 leb_len, prev_len = buff->length; - u8* leb; - - u8 mem_type = 0x00; - if (max >= 0) mem_type |= 0x01; - if (shared) mem_type |= 0x02; - - bh_buffer_write_byte(buff, mem_type); - - leb = uint_to_uleb128((u64) min, &leb_len); - bh_buffer_append(buff, leb, leb_len); - - if (max >= 0) { - leb = uint_to_uleb128((u64) max, &leb_len); - bh_buffer_append(buff, leb, leb_len); - } - - return buff->length - prev_len; -} - -static i32 output_functype(WasmFuncType* type, bh_buffer* buff) { - i32 prev_len = buff->length; - - bh_buffer_write_byte(buff, 0x60); - - i32 len; - u8* leb_buff = uint_to_uleb128(type->param_count, &len); - bh_buffer_append(buff, leb_buff, len); - bh_buffer_append(buff, type->param_types, type->param_count); - - if (type->return_type != WASM_TYPE_VOID) { - bh_buffer_write_byte(buff, 0x01); - bh_buffer_write_byte(buff, type->return_type); - } else { - bh_buffer_write_byte(buff, 0x00); - } - - return buff->length - prev_len; -} - -static i32 output_typesection(OnyxWasmModule* module, bh_buffer* buff) { - i32 prev_len = buff->length; - bh_buffer_write_byte(buff, 0x01); - - bh_buffer vec_buff; - bh_buffer_init(&vec_buff, buff->allocator, 128); - - i32 vec_len = output_vector( - (void**) module->types, - sizeof(WasmFuncType*), - bh_arr_length(module->types), - (vector_func *) output_functype, - &vec_buff); - - i32 leb_len; - u8* leb = uint_to_uleb128((u64) vec_len, &leb_len); - bh_buffer_append(buff, leb, leb_len); - - bh_buffer_concat(buff, vec_buff); - bh_buffer_free(&vec_buff); - - return buff->length - prev_len; -} - -static i32 output_funcsection(OnyxWasmModule* module, bh_buffer* buff) { - i32 prev_len = buff->length; - bh_buffer_write_byte(buff, WASM_SECTION_ID_FUNCTION); - - bh_buffer vec_buff; - bh_buffer_init(&vec_buff, buff->allocator, 128); - - i32 leb_len; - u8* leb = uint_to_uleb128((u64) (bh_arr_length(module->funcs)), &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - - bh_arr_each(WasmFunc, func, module->funcs) { - leb = uint_to_uleb128((u64) (func->type_idx), &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - } - - leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - bh_buffer_concat(buff, vec_buff); - bh_buffer_free(&vec_buff); - - return buff->length - prev_len; -} - -static i32 output_tablesection(OnyxWasmModule* module, bh_buffer* buff) { - // if (bh_arr_length(module->elems) == 0) return 0; - - i32 prev_len = buff->length; - bh_buffer_write_byte(buff, WASM_SECTION_ID_TABLE); - - bh_buffer vec_buff; - bh_buffer_init(&vec_buff, buff->allocator, 128); - - i32 leb_len; - u8* leb = uint_to_uleb128((u64) 1, &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - - // NOTE: funcrefs are the only valid table element type - bh_buffer_write_byte(&vec_buff, 0x70); - output_limits(bh_arr_length(module->elems), -1, 0, &vec_buff); - - leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - bh_buffer_concat(buff, vec_buff); - bh_buffer_free(&vec_buff); - - return buff->length - prev_len; -} - -static i32 output_memorysection(OnyxWasmModule* module, bh_buffer* buff) { - // :ProperLinking - // if (context.options->use_multi_threading) return 0; - if (!module->needs_memory_section) return 0; - - i32 prev_len = buff->length; - bh_buffer_write_byte(buff, WASM_SECTION_ID_MEMORY); - - bh_buffer vec_buff; - bh_buffer_init(&vec_buff, buff->allocator, 128); - - i32 leb_len; - u8* leb = uint_to_uleb128((u64) 1, &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - - output_limits(module->memory_min_size, -1, 0, &vec_buff); - - leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - bh_buffer_concat(buff, vec_buff); - bh_buffer_free(&vec_buff); - - return buff->length - prev_len; -} - -static i32 output_globalsection(OnyxWasmModule* module, bh_buffer* buff) { - i32 prev_len = buff->length; - bh_buffer_write_byte(buff, WASM_SECTION_ID_GLOBAL); - - bh_buffer vec_buff; - bh_buffer_init(&vec_buff, buff->allocator, 128); - - i32 leb_len; - u8* leb = uint_to_uleb128((u64) (bh_arr_length(module->globals)), &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - - bh_arr_each(WasmGlobal, global, module->globals) { - bh_buffer_write_byte(&vec_buff, global->type); - bh_buffer_write_byte(&vec_buff, 0x01); - - bh_arr_each(WasmInstruction, instr, global->initial_value) - output_instruction(NULL, instr, &vec_buff); - - // NOTE: Initial value expression terminator - bh_buffer_write_byte(&vec_buff, (u8) WI_BLOCK_END); - } - - leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - bh_buffer_concat(buff, vec_buff); - bh_buffer_free(&vec_buff); - - return buff->length - prev_len; -} - -static i32 output_importsection(OnyxWasmModule* module, bh_buffer* buff) { - i32 prev_len = buff->length; - bh_buffer_write_byte(buff, WASM_SECTION_ID_IMPORT); - - bh_buffer vec_buff; - bh_buffer_init(&vec_buff, buff->allocator, 128); - - i32 leb_len; - u8* leb = uint_to_uleb128((u64) (bh_arr_length(module->imports)), &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - - bh_arr_each(WasmImport, import, module->imports) { - output_name(import->mod, strlen(import->mod), &vec_buff); - output_name(import->name, strlen(import->name), &vec_buff); - bh_buffer_write_byte(&vec_buff, (u8) import->kind); - - switch (import->kind) { - case WASM_FOREIGN_FUNCTION: - leb = uint_to_uleb128((u64) import->idx, &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - break; - - case WASM_FOREIGN_MEMORY: - output_limits(import->min, import->max, import->shared, &vec_buff); - break; - - case WASM_FOREIGN_TABLE: assert(0); - } - } - - leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - bh_buffer_concat(buff, vec_buff); - bh_buffer_free(&vec_buff); - - return buff->length - prev_len; -} - -static i32 output_exportsection(OnyxWasmModule* module, bh_buffer* buff) { - i32 prev_len = buff->length; - bh_buffer_write_byte(buff, WASM_SECTION_ID_EXPORT); - - bh_buffer vec_buff; - bh_buffer_init(&vec_buff, buff->allocator, 128); - - i32 leb_len; - u8* leb = uint_to_uleb128((u64) (module->export_count), &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - - i32 key_len = 0; - fori (i, 0, shlen(module->exports)) { - char *key = module->exports[i].key; - WasmExport value = module->exports[i].value; - - key_len = strlen(key); - output_name(key, key_len, &vec_buff); - - bh_buffer_write_byte(&vec_buff, (u8) (value.kind)); - leb = uint_to_uleb128((u64) value.idx, &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - } - - leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - bh_buffer_concat(buff, vec_buff); - bh_buffer_free(&vec_buff); - - return buff->length - prev_len; -} - -static i32 output_startsection(OnyxWasmModule* module, bh_buffer* buff) { - i32 prev_len = buff->length; - - i32 start_idx = -1; - fori (i, 0, shlen(module->exports)) { - char *key = module->exports[i].key; - WasmExport value = module->exports[i].value; - - if (value.kind == WASM_FOREIGN_FUNCTION) { - if (strncmp("main", key, 5) == 0) { - start_idx = value.idx; - break; - } - } - } - - if (start_idx != -1) { - bh_buffer_write_byte(buff, WASM_SECTION_ID_START); - - i32 start_leb_len, section_leb_len; - uint_to_uleb128((u64) start_idx, &start_leb_len); - u8* section_leb = uint_to_uleb128((u64) start_leb_len, §ion_leb_len); - bh_buffer_append(buff, section_leb, section_leb_len); - - u8* start_leb = uint_to_uleb128((u64) start_idx, &start_leb_len); - bh_buffer_append(buff, start_leb, start_leb_len); - } - - return buff->length - prev_len; -} - -static i32 output_elemsection(OnyxWasmModule* module, bh_buffer* buff) { - if (bh_arr_length(module->elems) == 0) return 0; - - i32 prev_len = buff->length; - - bh_buffer_write_byte(buff, WASM_SECTION_ID_ELEMENT); - - bh_buffer vec_buff; - bh_buffer_init(&vec_buff, buff->allocator, 128); - - i32 leb_len; - u8* leb; - - // NOTE: 0x01 count of elems - bh_buffer_write_byte(&vec_buff, 0x01); - - // NOTE: 0x00 table index - bh_buffer_write_byte(&vec_buff, 0x00); - - bh_buffer_write_byte(&vec_buff, WI_I32_CONST); - bh_buffer_write_byte(&vec_buff, 0x00); - bh_buffer_write_byte(&vec_buff, WI_BLOCK_END); - - leb = uint_to_uleb128((u64) bh_arr_length(module->elems), &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - - bh_arr_each(i32, elem, module->elems) { - leb = uint_to_uleb128((u64) *elem, &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - } - - leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - bh_buffer_concat(buff, vec_buff); - bh_buffer_free(&vec_buff); - - return buff->length - prev_len; -} - -static i32 output_locals(WasmFunc* func, bh_buffer* buff) { - i32 prev_len = buff->length; - - // NOTE: Output vector length - i32 total_locals = - (i32) (func->locals.allocated[0] != 0) + - (i32) (func->locals.allocated[1] != 0) + - (i32) (func->locals.allocated[2] != 0) + - (i32) (func->locals.allocated[3] != 0) + - (i32) (func->locals.allocated[4] != 0); - - i32 leb_len; - u8* leb = uint_to_uleb128((u64) total_locals, &leb_len); - bh_buffer_append(buff, leb, leb_len); - - if (func->locals.allocated[0] != 0) { - leb = uint_to_uleb128((u64) func->locals.allocated[0], &leb_len); - bh_buffer_append(buff, leb, leb_len); - bh_buffer_write_byte(buff, WASM_TYPE_INT32); - } - if (func->locals.allocated[1] != 0) { - leb = uint_to_uleb128((u64) func->locals.allocated[1], &leb_len); - bh_buffer_append(buff, leb, leb_len); - bh_buffer_write_byte(buff, WASM_TYPE_INT64); - } - if (func->locals.allocated[2] != 0) { - leb = uint_to_uleb128((u64) func->locals.allocated[2], &leb_len); - bh_buffer_append(buff, leb, leb_len); - bh_buffer_write_byte(buff, WASM_TYPE_FLOAT32); - } - if (func->locals.allocated[3] != 0) { - leb = uint_to_uleb128((u64) func->locals.allocated[3], &leb_len); - bh_buffer_append(buff, leb, leb_len); - bh_buffer_write_byte(buff, WASM_TYPE_FLOAT64); - } - if (func->locals.allocated[4] != 0) { - leb = uint_to_uleb128((u64) func->locals.allocated[4], &leb_len); - bh_buffer_append(buff, leb, leb_len); - bh_buffer_write_byte(buff, WASM_TYPE_VAR128); - } - - return buff->length - prev_len; -} - -static void output_instruction(WasmFunc* func, WasmInstruction* instr, bh_buffer* buff) { - i32 leb_len; - u8* leb; - - if (instr->type == WI_UNREACHABLE) assert(("EMITTING UNREACHABLE!!", 0)); - - if (instr->type == WI_NOP && !context.options->debug_enabled) return; - - if (instr->type & SIMD_INSTR_MASK) { - bh_buffer_write_byte(buff, 0xFD); - leb = uint_to_uleb128((u64) (instr->type &~ SIMD_INSTR_MASK), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - } else if (instr->type & EXT_INSTR_MASK) { - bh_buffer_write_byte(buff, 0xFC); - leb = uint_to_uleb128((u64) (instr->type &~ EXT_INSTR_MASK), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - } else if (instr->type & ATOMIC_INSTR_MASK) { - bh_buffer_write_byte(buff, 0xFE); - leb = uint_to_uleb128((u64) (instr->type &~ ATOMIC_INSTR_MASK), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - if (instr->type == WI_ATOMIC_FENCE) { - bh_buffer_write_byte(buff, 0x00); - - } else { - leb = uint_to_uleb128((u64) instr->data.i1, &leb_len); - bh_buffer_append(buff, leb, leb_len); - - leb = uint_to_uleb128((u64) instr->data.i2, &leb_len); - bh_buffer_append(buff, leb, leb_len); - } - - } else { - bh_buffer_write_byte(buff, (u8) instr->type); - } - - switch (instr->type) { - case WI_LOCAL_GET: - case WI_LOCAL_SET: - case WI_LOCAL_TEE: { - u64 actual_idx = local_lookup_idx(&func->locals, instr->data.l); - leb = uint_to_uleb128(actual_idx, &leb_len); - bh_buffer_append(buff, leb, leb_len); - break; - } - - case WI_GLOBAL_GET: - case WI_GLOBAL_SET: - case WI_CALL: - case WI_BLOCK_START: - case WI_LOOP_START: - case WI_JUMP: - case WI_COND_JUMP: - case WI_IF_START: - case WI_MEMORY_SIZE: - case WI_MEMORY_GROW: - case WI_MEMORY_FILL: - leb = uint_to_uleb128((u64) instr->data.i1, &leb_len); - bh_buffer_append(buff, leb, leb_len); - break; - - case WI_MEMORY_INIT: - case WI_MEMORY_COPY: - leb = uint_to_uleb128((u64) instr->data.i1, &leb_len); - bh_buffer_append(buff, leb, leb_len); - - leb = uint_to_uleb128((u64) instr->data.i2, &leb_len); - bh_buffer_append(buff, leb, leb_len); - break; - - case WI_JUMP_TABLE: { - BranchTable* bt = (BranchTable *) instr->data.p; - - leb = uint_to_uleb128((u64) bt->count, &leb_len); - bh_buffer_append(buff, leb, leb_len); - - fori (i, 0, bt->count) { - leb = uint_to_uleb128((u64) bt->cases[i], &leb_len); - bh_buffer_append(buff, leb, leb_len); - } - - leb = uint_to_uleb128((u64) bt->default_case, &leb_len); - bh_buffer_append(buff, leb, leb_len); - break; - } - - - case WI_CALL_INDIRECT: - case WI_I32_STORE: case WI_I32_STORE_8: case WI_I32_STORE_16: - case WI_I64_STORE: case WI_I64_STORE_8: case WI_I64_STORE_16: case WI_I64_STORE_32: - case WI_F32_STORE: case WI_F64_STORE: - case WI_V128_STORE: - case WI_I32_LOAD: - case WI_I32_LOAD_8_S: case WI_I32_LOAD_8_U: - case WI_I32_LOAD_16_S: case WI_I32_LOAD_16_U: - case WI_I64_LOAD: - case WI_I64_LOAD_8_S: case WI_I64_LOAD_8_U: - case WI_I64_LOAD_16_S: case WI_I64_LOAD_16_U: - case WI_I64_LOAD_32_S: case WI_I64_LOAD_32_U: - case WI_F32_LOAD: case WI_F64_LOAD: - case WI_V128_LOAD: - leb = uint_to_uleb128((u64) instr->data.i1, &leb_len); - bh_buffer_append(buff, leb, leb_len); - leb = uint_to_uleb128((u64) instr->data.i2, &leb_len); - bh_buffer_append(buff, leb, leb_len); - break; - - case WI_I32_CONST: - leb = int_to_leb128((i64) instr->data.i1, &leb_len); - bh_buffer_append(buff, leb, leb_len); - break; - case WI_I64_CONST: - leb = int_to_leb128((i64) instr->data.l, &leb_len); - bh_buffer_append(buff, leb, leb_len); - break; - case WI_F32_CONST: - leb = float_to_ieee754(instr->data.f, 0); - bh_buffer_append(buff, leb, 4); - break; - case WI_F64_CONST: - leb = double_to_ieee754(instr->data.d, 0); - bh_buffer_append(buff, leb, 8); - break; - - case WI_V128_CONST: - case WI_I8X16_SHUFFLE: - fori (i, 0, 16) bh_buffer_write_byte(buff, ((u8*) instr->data.p)[i]); - break; - - case WI_I8X16_EXTRACT_LANE_S: case WI_I8X16_EXTRACT_LANE_U: case WI_I8X16_REPLACE_LANE: - case WI_I16X8_EXTRACT_LANE_S: case WI_I16X8_EXTRACT_LANE_U: case WI_I16X8_REPLACE_LANE: - case WI_I32X4_EXTRACT_LANE: case WI_I32X4_REPLACE_LANE: - case WI_I64X2_EXTRACT_LANE: case WI_I64X2_REPLACE_LANE: - case WI_F32X4_EXTRACT_LANE: case WI_F32X4_REPLACE_LANE: - case WI_F64X2_EXTRACT_LANE: case WI_F64X2_REPLACE_LANE: - bh_buffer_write_byte(buff, (u8) instr->data.i1); - break; - - default: break; - } -} - -static i32 output_code(WasmFunc* func, bh_buffer* buff) { - - bh_buffer code_buff; - bh_buffer_init(&code_buff, buff->allocator, 128); - - // Output locals - output_locals(func, &code_buff); - - assert(func->code); - - // Output code - bh_arr_each(WasmInstruction, instr, func->code) output_instruction(func, instr, &code_buff); - - i32 leb_len; - u8* leb = uint_to_uleb128((u64) code_buff.length, &leb_len); - bh_buffer_append(buff, leb, leb_len); - - bh_buffer_concat(buff, code_buff); - bh_buffer_free(&code_buff); - - return 0; -} - -static i32 output_codesection(OnyxWasmModule* module, bh_buffer* buff) { - i32 prev_len = buff->length; - - bh_buffer_write_byte(buff, WASM_SECTION_ID_CODE); - - bh_buffer vec_buff; - bh_buffer_init(&vec_buff, buff->allocator, 128); - - i32 leb_len; - u8* leb = uint_to_uleb128((u64) bh_arr_length(module->funcs), &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - - bh_arr_each(WasmFunc, func, module->funcs) output_code(func, &vec_buff); - - leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - bh_buffer_concat(buff, vec_buff); - bh_buffer_free(&vec_buff); - - return buff->length - prev_len; -} - -static i32 output_datacountsection(OnyxWasmModule* module, bh_buffer* buff) { - if (!context.options->use_post_mvp_features) return 0; - - i32 prev_len = buff->length; - - bh_buffer_write_byte(buff, WASM_SECTION_ID_DATACOUNT); - - bh_buffer vec_buff; - bh_buffer_init(&vec_buff, buff->allocator, 128); - - i32 leb_len; - u8* leb = uint_to_uleb128((u64) bh_arr_length(module->data), &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - - leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - bh_buffer_concat(buff, vec_buff); - bh_buffer_free(&vec_buff); - - return buff->length - prev_len; -} - -static i32 output_datasection(OnyxWasmModule* module, bh_buffer* buff) { - i32 prev_len = buff->length; - - bh_buffer_write_byte(buff, WASM_SECTION_ID_DATA); - - bh_buffer vec_buff; - bh_buffer_init(&vec_buff, buff->allocator, 128); - - i32 leb_len; - u8* leb = uint_to_uleb128((u64) bh_arr_length(module->data), &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - - bh_arr_each(WasmDatum, datum, module->data) { - i32 memory_flags = 0x00; - // :ProperLinking - if (context.options->use_multi_threading) memory_flags |= 0x01; - - bh_buffer_write_byte(&vec_buff, memory_flags); - - // :ProperLinking - if (!context.options->use_multi_threading) { - bh_buffer_write_byte(&vec_buff, WI_I32_CONST); - leb = int_to_leb128((i64) datum->offset_, &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - bh_buffer_write_byte(&vec_buff, WI_BLOCK_END); - } - - if (datum->data != NULL) { - leb = uint_to_uleb128((u64) datum->length, &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - fori (i, 0, datum->length) bh_buffer_write_byte(&vec_buff, ((u8 *) datum->data)[i]); - } else { - leb = uint_to_uleb128(0, &leb_len); - bh_buffer_append(&vec_buff, leb, leb_len); - } - } - - leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len); - bh_buffer_append(buff, leb, leb_len); - - bh_buffer_concat(buff, vec_buff); - bh_buffer_free(&vec_buff); - - return buff->length - prev_len; -} - -static i32 output_onyx_libraries_section(OnyxWasmModule* module, bh_buffer* buff) { - if (bh_arr_length(module->libraries) == 0) return 0; - i32 prev_len = buff->length; - - bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); - - bh_buffer libs_buff; - bh_buffer_init(&libs_buff, buff->allocator, 128); - - output_custom_section_name("_onyx_libs", &libs_buff); - - output_unsigned_integer(bh_arr_length(module->library_paths), &libs_buff); - - bh_arr_each(char *, lib, module->library_paths) { - assert(*lib != NULL); - - u32 lib_len = strlen(*lib); - output_unsigned_integer(lib_len, &libs_buff); - bh_buffer_append(&libs_buff, *lib, lib_len); - } - - output_unsigned_integer(bh_arr_length(module->libraries), &libs_buff); - - bh_arr_each(char *, lib, module->libraries) { - assert(*lib != NULL); - - u32 lib_len = strlen(*lib); - output_unsigned_integer(lib_len, &libs_buff); - bh_buffer_append(&libs_buff, *lib, lib_len); - } - - output_unsigned_integer(libs_buff.length, buff); - - bh_buffer_concat(buff, libs_buff); - bh_buffer_free(&libs_buff); - - return buff->length - prev_len; -} - -static i32 output_onyx_func_offset_section(OnyxWasmModule* module, bh_buffer* buff) { - i32 prev_len = buff->length; - - bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); - - bh_buffer section_buff; - bh_buffer_init(§ion_buff, buff->allocator, 128); - - output_custom_section_name("_onyx_func_offsets", §ion_buff); - - i32 func_count = bh_arr_length(module->funcs) + module->foreign_function_count; - - bh_buffer name_buff; - bh_buffer_init(&name_buff, buff->allocator, 1024); - u32 str_cursor = func_count * 4; - fori (i, 0, func_count) { - bh_buffer_write_u32(§ion_buff, str_cursor); - - if (i < module->foreign_function_count) { - bh_buffer_append(&name_buff, "", 20); - str_cursor += 20; - } else { - WasmFunc *func = &module->funcs[i - module->foreign_function_count]; - assert(func->location); - char *str = bh_bprintf("%s:%d,%d\0", func->location->pos.filename, func->location->pos.line, func->location->pos.column); - i32 len = strlen(str); - bh_buffer_append(&name_buff, str, len + 1); - str_cursor += len + 1; - } - } - - bh_buffer_concat(§ion_buff, name_buff); - - output_unsigned_integer(section_buff.length, buff); - - bh_buffer_concat(buff, section_buff); - bh_buffer_free(§ion_buff); - - return buff->length - prev_len; -} - -#ifdef ENABLE_DEBUG_INFO -static i32 output_ovm_debug_sections(OnyxWasmModule* module, bh_buffer* buff) { - if (!module->debug_context || !context.options->debug_enabled) return 0; - - DebugContext *ctx = module->debug_context; - - bh_buffer section_buff; - bh_buffer_init(§ion_buff, buff->allocator, 128); - - { - // ovm_debug_files section - bh_buffer_clear(§ion_buff); - bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); - - output_custom_section_name("ovm_debug_files", §ion_buff); - - i32 file_count = shlenu(ctx->file_info); - output_unsigned_integer(file_count, §ion_buff); - - fori (i, 0, file_count) { - Table(DebugFileInfo) entry = (void *) &ctx->file_info[i]; - output_unsigned_integer(entry->value.file_id, §ion_buff); - output_unsigned_integer(entry->value.line_count, §ion_buff); - output_name(entry->key, strlen(entry->key), §ion_buff); - } - - output_unsigned_integer(section_buff.length, buff); - - bh_buffer_concat(buff, section_buff); - } - - { - // ovm_debug_funcs section - bh_buffer_clear(§ion_buff); - bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); - - output_custom_section_name("ovm_debug_funcs", §ion_buff); - - i32 func_count = bh_arr_length(ctx->funcs); - output_unsigned_integer(func_count, §ion_buff); - - fori (i, 0, func_count) { - DebugFuncContext *func = &ctx->funcs[i]; - output_unsigned_integer(func->func_index, §ion_buff); - output_unsigned_integer(func->file_id, §ion_buff); - output_unsigned_integer(func->line, §ion_buff); - output_name(func->name, func->name_length, §ion_buff); - output_unsigned_integer(1, §ion_buff); - output_unsigned_integer(func->op_offset, §ion_buff); - - LocalAllocator *locals = &module->funcs[i].locals; - if (func->stack_ptr_idx > 0) { - u32 local_idx = local_lookup_idx(locals, func->stack_ptr_idx); - output_unsigned_integer(local_idx, §ion_buff); - } else { - output_unsigned_integer(0, §ion_buff); - } - - output_unsigned_integer(0, §ion_buff); - } - - output_unsigned_integer(section_buff.length, buff); - - bh_buffer_concat(buff, section_buff); - } - - { - // ovm_debug_syms section - - // First, apply patches for register locations - bh_arr_each(DebugSymPatch, patch, ctx->sym_patches) { - // CLEANUP: This is (kind of) incorrect, as there is nothing guarenteeing - // that the symbol with id a will be a position a, other than the way - // that this has been implemented right now. - assert(ctx->sym_info[patch->sym_id].location_type == DSL_REGISTER); - - LocalAllocator *locals = &module->funcs[patch->func_idx - module->foreign_function_count].locals; - ctx->sym_info[patch->sym_id].location_num = local_lookup_idx(locals, patch->local_idx); - } - - bh_buffer_clear(§ion_buff); - bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); - - output_custom_section_name("ovm_debug_syms", §ion_buff); - - i32 sym_count = bh_arr_length(ctx->sym_info); - output_unsigned_integer(sym_count, §ion_buff); - - fori (i, 0, sym_count) { - DebugSymInfo *sym = &ctx->sym_info[i]; - output_unsigned_integer(sym->sym_id, §ion_buff); - if (sym->name) { - output_name(sym->name, strlen(sym->name), §ion_buff); - } else { - output_unsigned_integer(0, §ion_buff); - } - output_unsigned_integer(sym->location_type, §ion_buff); - output_unsigned_integer(sym->location_num, §ion_buff); - output_unsigned_integer(sym->type, §ion_buff); - } - - output_unsigned_integer(section_buff.length, buff); - - bh_buffer_concat(buff, section_buff); - } - - { - // ovm_debug_types section - bh_buffer_clear(§ion_buff); - bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); - - output_custom_section_name("ovm_debug_types", §ion_buff); - - i32 type_count = bh_arr_length(type_map.entries); - output_unsigned_integer(type_count, §ion_buff); - - bh_arr_each(bh__imap_entry, entry, type_map.entries) { - u32 id = entry->key; - Type *type = (Type *) entry->value; - const char *name = type_get_name(type); - - output_unsigned_integer(id, §ion_buff); - output_name(name, strlen(name), §ion_buff); - output_unsigned_integer(type_size_of(type), §ion_buff); - - if (type->kind == Type_Kind_Basic) { - // Type indicies are special because they are encoded - // as a "distinct" unsigned 32-bit integer, which is - // effectively how they are used in the code anyway. - // - // This is probably a change that will be made throughout - // the entire compiler, but for now they will remain as - // a special type. - if (type->Basic.kind == Basic_Kind_Type_Index) { - output_unsigned_integer(5, §ion_buff); - output_unsigned_integer(2, §ion_buff); - output_unsigned_integer(basic_types[Basic_Kind_U32].id, §ion_buff); - continue; - } - - if (type->Basic.kind == Basic_Kind_Rawptr) { - // rawptr -> ^void - output_unsigned_integer(2, §ion_buff); - output_unsigned_integer(1, §ion_buff); - output_unsigned_integer(basic_types[Basic_Kind_Void].id, §ion_buff); - continue; - } - - output_unsigned_integer(1, §ion_buff); - if (type->Basic.kind == Basic_Kind_Void) output_unsigned_integer(0, §ion_buff); - else if (type_is_bool(type)) output_unsigned_integer(4, §ion_buff); - else if (type_is_integer(type)) { - if (type->Basic.flags & Basic_Flag_Unsigned) output_unsigned_integer(2, §ion_buff); - else output_unsigned_integer(1, §ion_buff); - } - else if (type->Basic.flags & Basic_Flag_Float) output_unsigned_integer(3, §ion_buff); - else if (type_is_simd(type)) output_unsigned_integer(6, §ion_buff); - else { - output_unsigned_integer(0, §ion_buff); - } - - continue; - } - - if (type->kind == Type_Kind_Pointer) { - output_unsigned_integer(2, §ion_buff); - output_unsigned_integer(1, §ion_buff); - output_unsigned_integer(type->Pointer.elem->id, §ion_buff); - continue; - } - - if (type->kind == Type_Kind_Enum) { - output_unsigned_integer(5, §ion_buff); - output_unsigned_integer(2, §ion_buff); - output_unsigned_integer(type->Enum.backing->id, §ion_buff); - continue; - } - - if (type->kind == Type_Kind_Array) { - output_unsigned_integer(4, §ion_buff); - output_unsigned_integer(type->Array.count, §ion_buff); - output_unsigned_integer(type->Array.elem->id, §ion_buff); - continue; - } - - if (type_is_structlike_strict(type)) { - output_unsigned_integer(3, §ion_buff); - - i32 mem_count = type_structlike_mem_count(type); - output_unsigned_integer(mem_count, §ion_buff); - - fori (i, 0, mem_count) { - StructMember smem; - type_lookup_member_by_idx(type, i, &smem); - - output_unsigned_integer(smem.offset, §ion_buff); - output_unsigned_integer(smem.type->id, §ion_buff); - output_name(smem.name, strlen(smem.name), §ion_buff); - } - - continue; - } - - if (type->kind == Type_Kind_Function) { - output_unsigned_integer(6, §ion_buff); - output_unsigned_integer(type->Function.param_count, §ion_buff); - - fori (i, 0, (i32) type->Function.param_count) { - output_unsigned_integer(type->Function.params[i]->id, §ion_buff); - } - - output_unsigned_integer(type->Function.return_type->id, §ion_buff); - continue; - } - - if (type->kind == Type_Kind_Distinct) { - output_unsigned_integer(5, §ion_buff); - output_unsigned_integer(2, §ion_buff); - output_unsigned_integer(type->Distinct.base_type->id, §ion_buff); - continue; - } - - // No debug information will be given about the poly struct - // or compound types. - // Outside of runtime type information, they provide no useful - // debugging information (I don't think at least...). - if (type->kind == Type_Kind_PolyStruct || - type->kind == Type_Kind_Compound) { - output_unsigned_integer(1, §ion_buff); - output_unsigned_integer(0, §ion_buff); - continue; - } - - assert(("Unhandled type", 0)); - } - - output_unsigned_integer(section_buff.length, buff); - - bh_buffer_concat(buff, section_buff); - } - - { - // ovm_debug_ops section - bh_buffer_clear(§ion_buff); - bh_buffer_write_byte(buff, WASM_SECTION_ID_CUSTOM); - - output_custom_section_name("ovm_debug_ops", §ion_buff); - bh_buffer_concat(§ion_buff, ctx->op_buffer); - - output_unsigned_integer(section_buff.length, buff); - - bh_buffer_concat(buff, section_buff); - } - - bh_buffer_free(§ion_buff); - return 0; -} -#endif - -void onyx_wasm_module_write_to_buffer(OnyxWasmModule* module, bh_buffer* buffer) { - bh_buffer_init(buffer, global_heap_allocator, 128); - if (context.options->runtime == Runtime_Onyx) { - bh_buffer_append(buffer, ONYX_MAGIC_STRING, 4); - } else { - bh_buffer_append(buffer, WASM_MAGIC_STRING, 4); - } - bh_buffer_append(buffer, WASM_VERSION, 4); - -#ifdef ENABLE_DEBUG_INFO - output_ovm_debug_sections(module, buffer); -#endif - output_typesection(module, buffer); - output_importsection(module, buffer); - output_funcsection(module, buffer); - output_tablesection(module, buffer); - output_memorysection(module, buffer); - output_globalsection(module, buffer); - output_exportsection(module, buffer); - output_startsection(module, buffer); - output_elemsection(module, buffer); - output_datacountsection(module, buffer); - output_codesection(module, buffer); - output_datasection(module, buffer); - output_onyx_libraries_section(module, buffer); - - // TODO: Consider if this should always be included? - // It can amount to a lot of extra data. - output_onyx_func_offset_section(module, buffer); -} - -void onyx_wasm_module_write_to_file(OnyxWasmModule* module, bh_file file) { - bh_buffer master_buffer; - onyx_wasm_module_write_to_buffer(module, &master_buffer); - - bh_file_write(&file, master_buffer.data, master_buffer.length); -} diff --git a/src/wasm_runtime.c b/src/wasm_runtime.c deleted file mode 100644 index 09797d4d..00000000 --- a/src/wasm_runtime.c +++ /dev/null @@ -1,434 +0,0 @@ -#include "bh.h" -#include "utils.h" -#include "astnodes.h" -#include "wasm.h" -#include "onyx_library.h" - -#ifndef USE_OVM_DEBUGGER - #include "wasmer.h" -#endif - -#ifdef _BH_LINUX - #include - #include - #include - #include -#endif - -static wasm_config_t* wasm_config; -static wasm_engine_t* wasm_engine; -static wasm_store_t* wasm_store; -static wasm_extern_vec_t wasm_imports; -static bh_buffer wasm_raw_bytes; -wasm_instance_t* wasm_instance; -wasm_module_t* wasm_module; -wasm_memory_t* wasm_memory; - -OnyxRuntime wasm_runtime; - -b32 wasm_name_equals(const wasm_name_t* name1, const wasm_name_t* name2) { - if (name1->size != name2->size) return 0; - return !strncmp(name1->data, name2->data, name1->size); -} - -b32 wasm_name_equals_string(const wasm_name_t* name1, const char* name2) { - u32 name2_size = strlen(name2); - if (name1->size != name2_size) return 0; - return !strncmp(name1->data, name2, name1->size); -} - -wasm_extern_t* wasm_extern_lookup_by_name(wasm_module_t* module, wasm_instance_t* instance, const char* name) { - i32 name_len = strlen(name); - - i32 idx = -1; - wasm_exporttype_vec_t export_types; - wasm_module_exports(module, &export_types); - fori (i, 0, (i64) export_types.size) { - wasm_exporttype_t* export_type = export_types.data[i]; - const wasm_name_t* export_name = wasm_exporttype_name(export_type); - - if (!strncmp(export_name->data, name, name_len)) { - idx = i; - break; - } - } - - if (idx == -1) return NULL; - - wasm_extern_vec_t exports; - wasm_instance_exports(instance, &exports); - - return exports.data[idx]; -} - - - -typedef void *(*LibraryLinker)(OnyxRuntime *runtime); - -typedef struct LibraryLinkContext { - bh_buffer wasm_bytes; - bh_arr(char *) library_paths; -} LibraryLinkContext; - -static WasmFuncDefinition** onyx_load_library(LibraryLinkContext *ctx, char *name) { - #ifdef _BH_LINUX - #define DIR_SEPARATOR '/' - #endif - #ifdef _BH_WINDOWS - #define DIR_SEPARATOR '\\' - #endif - - char *library = name; - fori (i, 0, (i32) strlen(name)) { - if (name[i] == DIR_SEPARATOR) library = &name[i + 1]; - } - - char *library_load_name_tmp = bh_bprintf("onyx_library_%s", library); - char *library_load_name = alloca(strlen(library_load_name_tmp) + 1); - strcpy(library_load_name, library_load_name_tmp); - LibraryLinker library_load; - - #ifdef _BH_LINUX - char *library_name = bh_lookup_file(name, ".", ".so", 1, (const char **) ctx->library_paths, 1); - void* handle = dlopen(library_name, RTLD_LAZY); - if (handle == NULL) { - printf("ERROR LOADING LIBRARY %s: %s\n", name, dlerror()); - return NULL; - } - - library_load = (LibraryLinker) dlsym(handle, library_load_name); - if (library_load == NULL) { - printf("ERROR RESOLVING '%s': %s\n", library_load_name, dlerror()); - return NULL; - } - #endif - - #ifdef _BH_WINDOWS - char *library_name = bh_lookup_file(name, ".", ".dll", 1, (const char **) ctx->library_paths, 1); - HMODULE handle = LoadLibraryA(library_name); - if (handle == NULL) { - printf("ERROR LOADING LIBRARY %s: %d\n", name, GetLastError()); - return NULL; - } - - library_load = (LibraryLinker) GetProcAddress(handle, library_load_name); - if (library_load == NULL) { - printf("ERROR RESOLVING '%s': %d\n", library_load_name, GetLastError()); - return NULL; - } - #endif - - return library_load(runtime); -} - -static void lookup_and_load_custom_libraries(LibraryLinkContext *ctx, bh_arr(WasmFuncDefinition **)* p_out) { - bh_arr(WasmFuncDefinition **) out = *p_out; - - bh_buffer wasm_bytes = ctx->wasm_bytes; - - i32 cursor = 8; // skip the magic number and version - while (cursor < wasm_bytes.length) { - u64 section_number = uleb128_to_uint(wasm_bytes.data, &cursor); - u64 section_size = uleb128_to_uint(wasm_bytes.data, &cursor); - - i32 section_start = cursor; - if (section_number == 0) { - u64 name_len = uleb128_to_uint(wasm_bytes.data, &cursor); - if (!strncmp(wasm_bytes.data + cursor, "_onyx_libs", name_len)) { - cursor += name_len; - u64 lib_count = uleb128_to_uint(wasm_bytes.data, &cursor); - - fori (i, 0, (i64) lib_count) { - u64 lib_path_length = uleb128_to_uint(wasm_bytes.data, &cursor); - lib_path_length = bh_min(lib_path_length, 512); - - char *lib_path = malloc(lib_path_length); - strncpy(lib_path, wasm_bytes.data + cursor, lib_path_length); - lib_path[lib_path_length] = '\0'; - bh_path_convert_separators(lib_path); - cursor += lib_path_length; - - bh_arr_push(ctx->library_paths, lib_path); - } - - lib_count = uleb128_to_uint(wasm_bytes.data, &cursor); - - fori (i, 0, (i64) lib_count) { - u64 lib_name_length = uleb128_to_uint(wasm_bytes.data, &cursor); - lib_name_length = bh_min(lib_name_length, 256); - - char library_name[256]; - strncpy(library_name, wasm_bytes.data + cursor, lib_name_length); - library_name[lib_name_length] = '\0'; - cursor += lib_name_length; - - WasmFuncDefinition** lib = onyx_load_library(ctx, library_name); - if (lib) { - bh_arr_push(out, lib); - } - } - break; - } - } - - cursor = section_start + section_size; - } - - *p_out = out; -} - -static void onyx_print_trap(wasm_trap_t* trap) { - wasm_message_t msg; - wasm_trap_message(trap, &msg); - bh_printf("TRAP: %b\n", msg.data, msg.size); - - i32 func_name_section = 0; - - i32 cursor = 8; // skip the magic number and version - while (cursor < wasm_raw_bytes.length) { - u64 section_number = uleb128_to_uint(wasm_raw_bytes.data, &cursor); - u64 section_size = uleb128_to_uint(wasm_raw_bytes.data, &cursor); - - i32 section_start = cursor; - if (section_number == 0) { - u64 name_len = uleb128_to_uint(wasm_raw_bytes.data, &cursor); - if (!strncmp(wasm_raw_bytes.data + cursor, "_onyx_func_offsets", name_len)) { - cursor += name_len; - func_name_section = cursor; - break; - } - } - - cursor = section_start + section_size; - } - - if (func_name_section == 0) return; - - bh_printf("TRACE:\n"); - wasm_frame_vec_t frames; - wasm_trap_trace(trap, &frames); - fori (i, 0, (i32) frames.size) { - i32 func_idx = wasm_frame_func_index(frames.data[i]); - i32 mod_offset = wasm_frame_module_offset(frames.data[i]); - - i32 cursor = func_name_section + 4 * func_idx; - i32 func_offset = *(i32 *) (wasm_raw_bytes.data + cursor); - char* func_name = wasm_raw_bytes.data + func_name_section + func_offset; - - bh_printf(" func[%d]:%p at %s\n", func_idx, mod_offset, func_name); - } -} - -static void cleanup_wasm_objects() { - if (wasm_instance) wasm_instance_delete(wasm_instance); - if (wasm_module) wasm_module_delete(wasm_module); - if (wasm_store) wasm_store_delete(wasm_store); - if (wasm_engine) wasm_engine_delete(wasm_engine); -} - -// -// This could be cleaned up a bit, as this function directly modifies various global variables. -// Those being wasm_memory and wasm_imports. -static b32 link_wasm_imports(bh_arr(WasmFuncDefinition **) linkable_functions, wasm_module_t *wasm_module) { - wasm_importtype_vec_t module_imports; - wasm_module_imports(wasm_module, &module_imports); - - wasm_extern_vec_new_uninitialized(&wasm_imports, module_imports.size); // @Free - - fori (i, 0, (i32) module_imports.size) { - const wasm_name_t* module_name = wasm_importtype_module(module_imports.data[i]); - const wasm_name_t* import_name = wasm_importtype_name(module_imports.data[i]); - - wasm_extern_t* import = NULL; - - if (wasm_name_equals_string(module_name, "onyx")) { - if (wasm_name_equals_string(import_name, "memory")) { - if (wasm_memory == NULL) { - wasm_limits_t limits = { 1024, 65536 }; - wasm_memorytype_t* memory_type = wasm_memorytype_new(&limits); - wasm_memory = wasm_memory_new(wasm_store, memory_type); - } - - import = wasm_memory_as_extern(wasm_memory); - goto import_found; - } - } - - bh_arr_each(WasmFuncDefinition **, library_funcs, linkable_functions) { - WasmFuncDefinition **pcurrent_function = *library_funcs; - while (*pcurrent_function != NULL) { - WasmFuncDefinition *cf = *pcurrent_function; - if (wasm_name_equals_string(module_name, cf->module_name) && wasm_name_equals_string(import_name, cf->import_name)) { - wasm_valtype_vec_t wasm_params; - wasm_valtype_vec_new_uninitialized(&wasm_params, cf->params->count); - fori (k, 0, cf->params->count) wasm_params.data[k] = wasm_valtype_new(cf->params->types[k]); - - wasm_valtype_vec_t wasm_results; - wasm_valtype_vec_new_uninitialized(&wasm_results, cf->results->count); - fori (k, 0, cf->results->count) wasm_results.data[k] = wasm_valtype_new(cf->results->types[k]); - - wasm_functype_t* wasm_functype = wasm_functype_new(&wasm_params, &wasm_results); - - wasm_func_t* wasm_func = wasm_func_new(wasm_store, wasm_functype, cf->func); - import = wasm_func_as_extern(wasm_func); - goto import_found; - } - - pcurrent_function += 1; - } - } - - goto bad_import; - - import_found: - wasm_imports.data[i] = import; - continue; - - - bad_import: - bh_printf("Couldn't find import %b.%b.\n", module_name->data, module_name->size, import_name->data, import_name->size); - return 0; - } - - return 1; -} - -void onyx_run_initialize(b32 debug_enabled) { - wasm_config = wasm_config_new(); - if (!wasm_config) { - cleanup_wasm_objects(); - return; - } - -#ifdef USE_OVM_DEBUGGER - void wasm_config_enable_debug(wasm_config_t *config, int value); - wasm_config_enable_debug(wasm_config, debug_enabled); -#endif - -#ifndef USE_OVM_DEBUGGER - if (debug_enabled) { - printf("Warning: --debug does nothing if libovmwasm.so is not being used!\n"); - } - - // Prefer the LLVM compile because it is faster. This should be configurable from the command line and/or a top-level directive. - if (wasmer_is_compiler_available(LLVM)) { - wasm_config_set_compiler(wasm_config, LLVM); - } - - wasmer_features_t* features = wasmer_features_new(); - wasmer_features_simd(features, 1); - wasmer_features_threads(features, 1); - wasmer_features_bulk_memory(features, 1); - wasm_config_set_features(wasm_config, features); -#endif - - wasm_engine = wasm_engine_new_with_config(wasm_config); - if (!wasm_engine) { - cleanup_wasm_objects(); - return; - } - - wasm_store = wasm_store_new(wasm_engine); - if (!wasm_store) { - cleanup_wasm_objects(); - return; - } - - // See comment in onyx_library.h about us being the linker. - wasm_runtime.wasm_memory_data = &wasm_memory_data; - wasm_runtime.wasm_extern_lookup_by_name = &wasm_extern_lookup_by_name; - wasm_runtime.wasm_extern_as_func = &wasm_extern_as_func; - wasm_runtime.wasm_func_call = &wasm_func_call; - wasm_runtime.wasm_instance_new = &wasm_instance_new; - wasm_runtime.wasm_store_new = &wasm_store_new; - wasm_runtime.wasm_store_delete = &wasm_store_delete; - wasm_runtime.onyx_print_trap = &onyx_print_trap; -} - -b32 onyx_run_wasm(bh_buffer wasm_bytes, int argc, char *argv[]) { - runtime = &wasm_runtime; - - if (strncmp(wasm_bytes.data, "ONYX", 4)) { - printf("Bad magic bytes for Onyx binary.\n"); - return 0; - } - - wasm_bytes.data[0] = '\0'; - wasm_bytes.data[1] = 'a'; - wasm_bytes.data[2] = 's'; - wasm_bytes.data[3] = 'm'; - wasm_raw_bytes = wasm_bytes; - - bh_arr(WasmFuncDefinition **) linkable_functions = NULL; - bh_arr_new(bh_heap_allocator(), linkable_functions, 4); - - { - LibraryLinkContext lib_ctx; - lib_ctx.wasm_bytes = wasm_bytes; - lib_ctx.library_paths = NULL; - lookup_and_load_custom_libraries(&lib_ctx, &linkable_functions); - - bh_arr_free(lib_ctx.library_paths); - } - - wasm_byte_vec_t wasm_data; - wasm_data.size = wasm_bytes.length; - wasm_data.data = wasm_bytes.data; - - wasm_module = wasm_module_new(wasm_store, &wasm_data); - if (!wasm_module) { - cleanup_wasm_objects(); - return 0; - } - - wasm_imports = (wasm_extern_vec_t) WASM_EMPTY_VEC; - if (!link_wasm_imports(linkable_functions, wasm_module)) { - return 0; - } - - wasm_trap_t* traps = NULL; - - wasm_instance = wasm_instance_new(wasm_store, wasm_module, &wasm_imports, &traps); - if (!wasm_instance) { - cleanup_wasm_objects(); - return 0; - } - - wasm_runtime.wasm_engine = wasm_engine; - wasm_runtime.wasm_module = wasm_module; - wasm_runtime.wasm_imports = wasm_imports; - wasm_runtime.wasm_memory = wasm_memory; - wasm_runtime.wasm_instance = wasm_instance; - - wasm_runtime.argc = argc; - wasm_runtime.argv = argv; - - wasm_extern_t* start_extern = wasm_extern_lookup_by_name(wasm_module, wasm_instance, "_start"); - wasm_func_t* start_func = wasm_extern_as_func(start_extern); - - wasm_val_vec_t args; - wasm_val_vec_t results; - wasm_val_vec_new_uninitialized(&args, 0); - wasm_val_vec_new_uninitialized(&results, 1); - - wasm_trap_t *run_trap = wasm_func_call(start_func, &args, &results); - -#if 1 - if (run_trap != NULL) onyx_print_trap(run_trap); -#endif - - goto end; - - bh_printf("An error occured trying to run the WASM module...\n"); - -#ifndef USE_OVM_DEBUGGER - i32 len = wasmer_last_error_length(); - char *buf = alloca(len + 1); - wasmer_last_error_message(buf, len); - bh_printf("%b\n", buf, len); -#endif - -end: - cleanup_wasm_objects(); - return run_trap == NULL; -} diff --git a/src/wasm_type_table.h b/src/wasm_type_table.h deleted file mode 100644 index 2cb1c671..00000000 --- a/src/wasm_type_table.h +++ /dev/null @@ -1,902 +0,0 @@ -// This file is directly included in src/onxywasm.c -// It is here purely to decrease the amount of clutter in the main file. - -typedef struct StructMethodData { - u32 name_loc; - u32 name_len; - u32 type; - u32 data_loc; -} StructMethodData; - -static u64 build_type_table(OnyxWasmModule* module) { - - bh_arr(u32) base_patch_locations=NULL; - bh_arr_new(global_heap_allocator, base_patch_locations, 256); - -#define PATCH (bh_arr_push(base_patch_locations, table_buffer.length)) -#define WRITE_PTR(val) \ - bh_buffer_align(&table_buffer, POINTER_SIZE); \ - PATCH; \ - if (POINTER_SIZE == 4) bh_buffer_write_u32(&table_buffer, val); \ - if (POINTER_SIZE == 8) bh_buffer_write_u64(&table_buffer, val); -#define WRITE_SLICE(ptr, count) \ - WRITE_PTR(ptr); \ - if (POINTER_SIZE == 4) bh_buffer_write_u32(&table_buffer, count); \ - if (POINTER_SIZE == 8) bh_buffer_write_u64(&table_buffer, count); - - // This is the data behind the "type_table" slice in runtime/info/types.onyx - #if (POINTER_SIZE == 4) - #define Table_Info_Type u32 - #else - #define Table_Info_Type u64 - #endif - u32 type_count = bh_arr_length(type_map.entries) + 1; - Table_Info_Type* table_info = bh_alloc_array(global_heap_allocator, Table_Info_Type, type_count); // HACK - memset(table_info, 0, type_count * sizeof(Table_Info_Type)); - - bh_buffer table_buffer; - bh_buffer_init(&table_buffer, global_heap_allocator, 4096); - - u32 type_table_info_data_id = NEXT_DATA_ID(module); - - ConstExprContext constexpr_ctx; - constexpr_ctx.module = module; - constexpr_ctx.data_id = type_table_info_data_id; - - // Write a "NULL" at the beginning so nothing will have to point to the first byte of the buffer. - bh_buffer_write_u64(&table_buffer, 0); - - bh_arr_each(bh__imap_entry, type_entry, type_map.entries) { - u64 type_idx = type_entry->key; - Type* type = (Type *) type_entry->value; - - switch (type->kind) { - case Type_Kind_Basic: { - table_info[type_idx] = table_buffer.length; - bh_buffer_write_u32(&table_buffer, type->kind); - bh_buffer_write_u32(&table_buffer, type_size_of(type)); - bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); - bh_buffer_write_u32(&table_buffer, type->Basic.kind); - break; - } - - case Type_Kind_Pointer: { - table_info[type_idx] = table_buffer.length; - bh_buffer_write_u32(&table_buffer, type->kind); - bh_buffer_write_u32(&table_buffer, type_size_of(type)); - bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); - bh_buffer_write_u32(&table_buffer, type->Pointer.elem->id); - break; - } - - case Type_Kind_Array: { - table_info[type_idx] = table_buffer.length; - bh_buffer_write_u32(&table_buffer, type->kind); - bh_buffer_write_u32(&table_buffer, type_size_of(type)); - bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); - bh_buffer_write_u32(&table_buffer, type->Array.elem->id); - bh_buffer_write_u32(&table_buffer, type->Array.count); - break; - } - - case Type_Kind_Slice: { - table_info[type_idx] = table_buffer.length; - bh_buffer_write_u32(&table_buffer, type->kind); - bh_buffer_write_u32(&table_buffer, type_size_of(type)); - bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); - bh_buffer_write_u32(&table_buffer, type->Slice.elem->id); - break; - } - - case Type_Kind_DynArray: { - table_info[type_idx] = table_buffer.length; - bh_buffer_write_u32(&table_buffer, type->kind); - bh_buffer_write_u32(&table_buffer, type_size_of(type)); - bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); - bh_buffer_write_u32(&table_buffer, type->DynArray.elem->id); - break; - } - - case Type_Kind_VarArgs: { - table_info[type_idx] = table_buffer.length; - bh_buffer_write_u32(&table_buffer, type->kind); - bh_buffer_write_u32(&table_buffer, type_size_of(type)); - bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); - bh_buffer_write_u32(&table_buffer, type->VarArgs.elem->id); - break; - } - - case Type_Kind_Compound: { - u32 components_base = table_buffer.length; - - u32 components_count = type->Compound.count; - fori (i, 0, components_count) { - u32 type_idx = type->Compound.types[i]->id; - bh_buffer_write_u32(&table_buffer, type_idx); - } - - bh_buffer_align(&table_buffer, 8); - table_info[type_idx] = table_buffer.length; - bh_buffer_write_u32(&table_buffer, type->kind); - bh_buffer_write_u32(&table_buffer, type_size_of(type)); - bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); - WRITE_SLICE(components_base, components_count); - break; - } - - case Type_Kind_Function: { - u32 parameters_base = table_buffer.length; - - u32 parameters_count = type->Function.param_count; - fori (i, 0, parameters_count) { - u32 type_idx = type->Function.params[i]->id; - bh_buffer_write_u32(&table_buffer, type_idx); - } - - table_info[type_idx] = table_buffer.length; - bh_buffer_write_u32(&table_buffer, type->kind); - bh_buffer_write_u32(&table_buffer, type_size_of(type)); - bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); - bh_buffer_write_u32(&table_buffer, type->Function.return_type->id); - - WRITE_SLICE(parameters_base, parameters_count); - - bh_buffer_write_u32(&table_buffer, type->Function.vararg_arg_pos > 0 ? 1 : 0); - break; - } - - case Type_Kind_Enum: { - AstEnumType* ast_enum = (AstEnumType *) type->ast_type; - u32 member_count = bh_arr_length(ast_enum->values); - u32* name_locations = bh_alloc_array(global_scratch_allocator, u32, member_count); - - u32 i = 0; - bh_arr_each(AstEnumValue *, value, ast_enum->values) { - name_locations[i++] = table_buffer.length; - - bh_buffer_append(&table_buffer, (*value)->token->text, (*value)->token->length); - } - bh_buffer_align(&table_buffer, 8); - - u32 member_base = table_buffer.length; - i = 0; - bh_arr_each(AstEnumValue *, value, ast_enum->values) { - u32 name_loc = name_locations[i++]; - - bh_buffer_align(&table_buffer, 8); - WRITE_SLICE(name_loc, (*value)->token->length); - - assert((*value)->value->kind == Ast_Kind_NumLit); - AstNumLit *num = (AstNumLit *) (*value)->value; - bh_buffer_write_u64(&table_buffer, num->value.l); - } - - u32 name_base = table_buffer.length; - u32 name_length = strlen(type->Enum.name); - bh_buffer_append(&table_buffer, type->Enum.name, name_length); - bh_buffer_align(&table_buffer, 8); - - table_info[type_idx] = table_buffer.length; - bh_buffer_write_u32(&table_buffer, type->kind); - bh_buffer_write_u32(&table_buffer, type_size_of(type)); - bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); - bh_buffer_write_u32(&table_buffer, type->Enum.backing->id); - WRITE_SLICE(name_base, name_length); - WRITE_SLICE(member_base, member_count); - bh_buffer_write_u32(&table_buffer, type->Enum.is_flags ? 1 : 0); - break; - } - - case Type_Kind_Struct: { - TypeStruct* s = &type->Struct; - u32* name_locations = bh_alloc_array(global_scratch_allocator, u32, s->mem_count); - u32* param_locations = bh_alloc_array(global_scratch_allocator, u32, bh_arr_length(s->poly_sln)); - u32* value_locations = bh_alloc_array(global_scratch_allocator, u32, s->mem_count); - u32* meta_locations = bh_alloc_array(global_scratch_allocator, u32, s->mem_count); - u32* struct_tag_locations = bh_alloc_array(global_scratch_allocator, u32, bh_arr_length(s->meta_tags)); - memset(value_locations, 0, s->mem_count * sizeof(u32)); - memset(meta_locations, 0, s->mem_count * sizeof(u32)); - memset(struct_tag_locations, 0, bh_arr_length(s->meta_tags) * sizeof(u32)); - - // Member names - u32 i = 0; - bh_arr_each(StructMember*, pmem, s->memarr) { - StructMember* mem = *pmem; - - name_locations[i++] = table_buffer.length; - bh_buffer_append(&table_buffer, mem->name, strlen(mem->name)); - } - - bh_buffer_align(&table_buffer, 8); - - // Polymorphic solutions - i = 0; - bh_arr_each(AstPolySolution, sln, s->poly_sln) { - bh_buffer_align(&table_buffer, 8); - param_locations[i++] = table_buffer.length; - - switch (sln->kind) { - case PSK_Type: { - // NOTE: This assumes a little endian compiler (which is assumed in other part of the code too) - bh_buffer_append(&table_buffer, &sln->type->id, 4); - break; - } - - case PSK_Value: { - assert(sln->value->type); - u32 size = type_size_of(sln->value->type); - - bh_buffer_grow(&table_buffer, table_buffer.length + size); - constexpr_ctx.data = table_buffer.data; - if (emit_constexpr_(&constexpr_ctx, sln->value, table_buffer.length)) { - table_buffer.length += size; - break; - } - - // fallthrough - } - - default: { - // Set to null if this is not known how to encode - param_locations[i-1] = 0; - break; - } - } - } - - bh_buffer_align(&table_buffer, 8); - - // Member default values - i = 0; - bh_arr_each(StructMember*, pmem, s->memarr) { - StructMember* mem = *pmem; - - if (mem->initial_value == NULL || *mem->initial_value == NULL) { - i++; - continue; - } - - AstTyped* value = *mem->initial_value; - assert(value->type); - - if ((value->flags & Ast_Flag_Comptime) == 0) { - // onyx_report_warning(value->token->pos, "Warning: skipping generating default value for '%s' in '%s' because it is not compile-time known.\n", mem->name, s->name); - i++; - continue; - } - - u32 size = type_size_of(value->type); - bh_buffer_align(&table_buffer, type_alignment_of(value->type)); - - bh_buffer_grow(&table_buffer, table_buffer.length + size); - constexpr_ctx.data = table_buffer.data; - if (!emit_constexpr_(&constexpr_ctx, value, table_buffer.length)) { - // Failed to generate raw data - // onyx_report_warning(value->token->pos, "Warning: failed to generate default value for '%s' in '%s'.\n", mem->name, s->name); - value_locations[i++] = 0; - - } else { - // Success - value_locations[i++] = table_buffer.length; - table_buffer.length += size; - } - } - - // Member tags - i = 0; - bh_arr_each(StructMember*, pmem, s->memarr) { - StructMember* mem = *pmem; - - if (mem->meta_tags == NULL) { - i += 1; - continue; - } - - bh_arr(AstTyped *) meta_tags = mem->meta_tags; - assert(meta_tags); - - bh_arr(u64) meta_tag_locations=NULL; - bh_arr_new(global_heap_allocator, meta_tag_locations, bh_arr_length(meta_tags)); - - int j = 0; - bh_arr_each(AstTyped *, meta, meta_tags) { - AstTyped* value = *meta; - assert(value->flags & Ast_Flag_Comptime); - assert(value->type); - - u32 size = type_size_of(value->type); - bh_buffer_align(&table_buffer, type_alignment_of(value->type)); - meta_tag_locations[j] = table_buffer.length; - - bh_buffer_grow(&table_buffer, table_buffer.length + size); - constexpr_ctx.data = table_buffer.data; - assert(emit_constexpr_(&constexpr_ctx, value, table_buffer.length)); - table_buffer.length += size; - - j += 1; - } - - bh_buffer_align(&table_buffer, 8); - meta_locations[i] = table_buffer.length; - - fori (k, 0, bh_arr_length(meta_tags)) { - WRITE_SLICE(meta_tag_locations[k], meta_tags[k]->type->id); - } - - bh_arr_free(meta_tag_locations); - i += 1; - } - - bh_buffer_align(&table_buffer, 8); - u32 members_base = table_buffer.length; - - // Member array - i = 0; - bh_arr_each(StructMember*, pmem, s->memarr) { - StructMember* mem = *pmem; - - u32 name_loc = name_locations[i]; - u32 value_loc = value_locations[i]; - u32 meta_loc = meta_locations[i++]; - - WRITE_SLICE(name_loc, strlen(mem->name)); - bh_buffer_write_u32(&table_buffer, mem->offset); - bh_buffer_write_u32(&table_buffer, mem->type->id); - bh_buffer_write_byte(&table_buffer, mem->used ? 1 : 0); - - WRITE_PTR(value_loc); - - WRITE_SLICE(meta_loc, bh_arr_length(mem->meta_tags)); - } - - bh_buffer_align(&table_buffer, 8); - u32 params_base = table_buffer.length; - - // Polymorphic solution any array - i = 0; - bh_arr_each(AstPolySolution, sln, s->poly_sln) { - WRITE_PTR(param_locations[i++]); - - if (sln->kind == PSK_Type) bh_buffer_write_u32(&table_buffer, basic_types[Basic_Kind_Type_Index].id); - else bh_buffer_write_u32(&table_buffer, sln->value->type->id); - } - - // Struct tag array - i = 0; - bh_arr_each(AstTyped *, tag, s->meta_tags) { - AstTyped* value = *tag; - assert(value->flags & Ast_Flag_Comptime); - assert(value->type); - - u32 size = type_size_of(value->type); - bh_buffer_align(&table_buffer, type_alignment_of(value->type)); - struct_tag_locations[i] = table_buffer.length; - - bh_buffer_grow(&table_buffer, table_buffer.length + size); - constexpr_ctx.data = table_buffer.data; - assert(emit_constexpr_(&constexpr_ctx, value, table_buffer.length)); - table_buffer.length += size; - - i += 1; - } - - // Struct methods - bh_arr(StructMethodData) method_data=NULL; - AstType *ast_type = type->ast_type; - if (ast_type && ast_type->kind == Ast_Kind_Struct_Type) { - AstStructType *struct_type = (AstStructType *) ast_type; - Scope* struct_scope = struct_type->scope; - - if (struct_scope == NULL) goto no_methods; - - fori (i, 0, shlen(struct_scope->symbols)) { - AstFunction* node = (AstFunction *) strip_aliases(struct_scope->symbols[i].value); - if (node->kind != Ast_Kind_Function) continue; - assert(node->entity); - assert(node->entity->function == node); - - // Name - char *name = struct_scope->symbols[i].key; - u32 name_loc = table_buffer.length; - u32 name_len = strlen(name); - bh_buffer_append(&table_buffer, name, name_len); - - // any data member - bh_buffer_align(&table_buffer, 4); - u32 data_loc = table_buffer.length; - u32 func_idx = get_element_idx(module, node); - bh_buffer_write_u32(&table_buffer, func_idx); - - bh_arr_push(method_data, ((StructMethodData) { - .name_loc = name_loc, - .name_len = name_len, - .type = node->type->id, - .data_loc = data_loc, - })); - } - } - - no_methods: - - bh_buffer_align(&table_buffer, 4); - u32 method_data_base = table_buffer.length; - - i = 0; - bh_arr_each(StructMethodData, method, method_data) { - WRITE_SLICE(method->name_loc, method->name_len); - WRITE_PTR(method->data_loc); - bh_buffer_write_u32(&table_buffer, method->type); - } - - bh_buffer_align(&table_buffer, 8); - u32 struct_tag_base = table_buffer.length; - - fori (i, 0, bh_arr_length(s->meta_tags)) { - WRITE_SLICE(struct_tag_locations[i], s->meta_tags[i]->type->id); - } - - // Struct name - u32 name_base = 0; - u32 name_length = 0; - if (s->name) { - name_length = strlen(s->name); - name_base = table_buffer.length; - bh_buffer_append(&table_buffer, s->name, name_length); - } - - bh_buffer_align(&table_buffer, 8); - table_info[type_idx] = table_buffer.length; - bh_buffer_write_u32(&table_buffer, type->kind); - bh_buffer_write_u32(&table_buffer, type_size_of(type)); - bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); - - if (type->Struct.constructed_from != NULL) { - bh_buffer_write_u32(&table_buffer, type->Struct.constructed_from->type_id); - } else { - bh_buffer_write_u32(&table_buffer, 0); - } - - WRITE_SLICE(name_base, name_length); - WRITE_SLICE(members_base, s->mem_count); - WRITE_SLICE(params_base, bh_arr_length(s->poly_sln)); - WRITE_SLICE(struct_tag_base, bh_arr_length(s->meta_tags)); - WRITE_SLICE(method_data_base, bh_arr_length(method_data)); - - bh_arr_free(method_data); - break; - } - - case Type_Kind_PolyStruct: { - u32* tag_locations = bh_alloc_array(global_scratch_allocator, u32, bh_arr_length(type->PolyStruct.meta_tags)); - memset(tag_locations, 0, sizeof(u32) * bh_arr_length(type->PolyStruct.meta_tags)); - - u32 name_base = table_buffer.length; - u32 name_length = strlen(type->PolyStruct.name); - bh_buffer_append(&table_buffer, type->PolyStruct.name, name_length); - - u32 tags_count = bh_arr_length(type->PolyStruct.meta_tags); - i32 i = 0; - bh_arr_each(AstTyped *, tag, type->PolyStruct.meta_tags) { - AstTyped* value = *tag; - - // Polymorphic structs are weird in this case, because the tag might not be constructed generically for - // the polymorphic structure so it should only be constructed for actual solidified structures. - // See core/containers/map.onyx with Custom_Format for an example. - if (!(value->flags & Ast_Flag_Comptime)) { - tags_count--; - continue; - } - - assert(value->type); - - u32 size = type_size_of(value->type); - bh_buffer_align(&table_buffer, type_alignment_of(value->type)); - tag_locations[i] = table_buffer.length; - - bh_buffer_grow(&table_buffer, table_buffer.length + size); - - constexpr_ctx.data = table_buffer.data; - assert(emit_constexpr_(&constexpr_ctx, value, table_buffer.length)); - table_buffer.length += size; - - i += 1; - } - - bh_buffer_align(&table_buffer, 8); - u32 tags_base = table_buffer.length; - - fori (i, 0, tags_count) { - WRITE_SLICE(tag_locations[i], type->PolyStruct.meta_tags[i]->type->id); - } - - bh_buffer_align(&table_buffer, 8); - table_info[type_idx] = table_buffer.length; - bh_buffer_write_u32(&table_buffer, type->kind); - bh_buffer_write_u32(&table_buffer, 0); - bh_buffer_write_u32(&table_buffer, 0); - WRITE_SLICE(name_base, name_length); - WRITE_SLICE(tags_base, tags_count); - - break; - } - - case Type_Kind_Distinct: { - u32 name_base = table_buffer.length; - u32 name_length = strlen(type->Distinct.name); - bh_buffer_append(&table_buffer, type->Distinct.name, name_length); - bh_buffer_align(&table_buffer, 8); - - table_info[type_idx] = table_buffer.length; - bh_buffer_write_u32(&table_buffer, type->kind); - bh_buffer_write_u32(&table_buffer, type_size_of(type)); - bh_buffer_write_u32(&table_buffer, type_alignment_of(type)); - bh_buffer_write_u32(&table_buffer, type->Distinct.base_type->id); - WRITE_SLICE(name_base, name_length); - break; - } - } - } - - if (context.options->verbose_output == 1) { - bh_printf("Type table size: %d bytes.\n", table_buffer.length); - } - - WasmDatum type_info_data = { - .alignment = 8, - .length = table_buffer.length, - .data = table_buffer.data, - }; - emit_data_entry(module, &type_info_data); - assert(type_info_data.id == type_table_info_data_id); - - bh_arr_each(u32, patch_loc, base_patch_locations) { - DatumPatchInfo patch; - patch.kind = Datum_Patch_Relative; - patch.data_id = type_info_data.id; - patch.offset = 0; - patch.index = type_info_data.id; - patch.location = *patch_loc; - bh_arr_push(module->data_patches, patch); - } - - WasmDatum type_table_data = { - .alignment = POINTER_SIZE, - .length = type_count * POINTER_SIZE, - .data = table_info, - }; - emit_data_entry(module, &type_table_data); - - fori (i, 0, type_count) { - DatumPatchInfo patch; - patch.kind = Datum_Patch_Data; - patch.data_id = type_info_data.id; - patch.offset = table_info[i]; - patch.index = type_table_data.id; - patch.location = i * POINTER_SIZE; - bh_arr_push(module->data_patches, patch); - } - - Table_Info_Type* tmp_data = bh_alloc(global_heap_allocator, 2 * POINTER_SIZE); - tmp_data[0] = 0; - tmp_data[1] = type_count; - WasmDatum type_table_global_data = { - .alignment = POINTER_SIZE, - .length = 2 * POINTER_SIZE, - .data = tmp_data, - }; - emit_data_entry(module, &type_table_global_data); - - { - DatumPatchInfo patch; - patch.kind = Datum_Patch_Data; - patch.data_id = type_table_data.id; - patch.offset = 0; - patch.index = type_table_global_data.id; - patch.location = 0; - bh_arr_push(module->data_patches, patch); - } - - return type_table_global_data.id; - -#undef WRITE_SLICE -#undef WRITE_PTR -#undef PATCH -} - - - -static u64 build_foreign_blocks(OnyxWasmModule* module) { - bh_arr(u32) base_patch_locations=NULL; - bh_arr_new(global_heap_allocator, base_patch_locations, 256); - -#define PATCH (bh_arr_push(base_patch_locations, foreign_buffer.length)) -#define WRITE_PTR(val) \ - bh_buffer_align(&foreign_buffer, POINTER_SIZE); \ - PATCH; \ - if (POINTER_SIZE == 4) bh_buffer_write_u32(&foreign_buffer, val); \ - if (POINTER_SIZE == 8) bh_buffer_write_u64(&foreign_buffer, val); -#define WRITE_SLICE(ptr, count) \ - WRITE_PTR(ptr); \ - if (POINTER_SIZE == 4) bh_buffer_write_u32(&foreign_buffer, count); \ - if (POINTER_SIZE == 8) bh_buffer_write_u64(&foreign_buffer, count); - - // This is the data behind the "type_table" slice in type_info.onyx - #if (POINTER_SIZE == 4) - #define Foreign_Block_Type u32 - #else - #define Foreign_Block_Type u64 - #endif - u32 block_count = bh_arr_length(module->foreign_blocks); - Foreign_Block_Type* foreign_info = bh_alloc_array(global_heap_allocator, Foreign_Block_Type, block_count); // HACK - memset(foreign_info, 0, block_count * sizeof(Foreign_Block_Type)); - - bh_buffer foreign_buffer; - bh_buffer_init(&foreign_buffer, global_heap_allocator, 4096); - - // - // This is necessary because 0 is an invalid offset to store in this - // buffer, as 0 will map to NULL. This could be a single byte insertion, - // but 64 bytes keeps better alignment. - bh_buffer_write_u64(&foreign_buffer, 0); - - u32 index = 0; - bh_arr_each(AstForeignBlock *, pfb, module->foreign_blocks) { - AstForeignBlock *fb = *pfb; - - u32 funcs_length = 0; - - u32 *name_offsets = bh_alloc_array(global_scratch_allocator, u32, shlen(fb->scope->symbols)); - u32 *name_lengths = bh_alloc_array(global_scratch_allocator, u32, shlen(fb->scope->symbols)); - u32 *func_types = bh_alloc_array(global_scratch_allocator, u32, shlen(fb->scope->symbols)); - - fori (i, 0, shlen(fb->scope->symbols)) { - AstFunction *func = (AstFunction *) fb->scope->symbols[i].value; - if (func->kind != Ast_Kind_Function) continue; - - u32 func_name_base = foreign_buffer.length; - u32 func_name_length = func->foreign_name->length; - bh_buffer_append(&foreign_buffer, func->foreign_name->text, func_name_length); - - name_offsets[funcs_length] = func_name_base; - name_lengths[funcs_length] = func_name_length; - func_types[funcs_length] = func->type->id; - funcs_length++; - } - - bh_buffer_align(&foreign_buffer, 8); - u32 funcs_base = foreign_buffer.length; - - fori (i, 0, (i64) funcs_length) { - bh_buffer_align(&foreign_buffer, POINTER_SIZE); - WRITE_SLICE(name_offsets[i], name_lengths[i]); - bh_buffer_write_u32(&foreign_buffer, func_types[i]); - } - - u32 name_base = foreign_buffer.length; - u32 name_length = fb->module_name->length; - bh_buffer_append(&foreign_buffer, fb->module_name->text, name_length); - bh_buffer_align(&foreign_buffer, 8); - - foreign_info[index] = foreign_buffer.length; - WRITE_SLICE(name_base, name_length); - WRITE_SLICE(funcs_base, funcs_length); - index++; - } - - - if (context.options->verbose_output == 1) { - bh_printf("Foreign blocks size: %d bytes.\n", foreign_buffer.length); - } - - WasmDatum foreign_info_data = { - .alignment = 8, - .length = foreign_buffer.length, - .data = foreign_buffer.data, - }; - emit_data_entry(module, &foreign_info_data); - - bh_arr_each(u32, patch_loc, base_patch_locations) { - DatumPatchInfo patch; - patch.kind = Datum_Patch_Relative; - patch.data_id = foreign_info_data.id; - patch.offset = 0; - patch.index = foreign_info_data.id; - patch.location = *patch_loc; - bh_arr_push(module->data_patches, patch); - } - - WasmDatum foreign_table_data = { - .alignment = POINTER_SIZE, - .length = block_count * POINTER_SIZE, - .data = foreign_info, - }; - emit_data_entry(module, &foreign_table_data); - - fori (i, 0, block_count) { - DatumPatchInfo patch; - patch.kind = Datum_Patch_Data; - patch.data_id = foreign_info_data.id; - patch.offset = foreign_info[i]; - patch.index = foreign_table_data.id; - patch.location = i * POINTER_SIZE; - bh_arr_push(module->data_patches, patch); - } - - Foreign_Block_Type* tmp_data = bh_alloc(global_heap_allocator, 2 * POINTER_SIZE); - tmp_data[0] = 0; - tmp_data[1] = block_count; - WasmDatum foreign_table_global_data = { - .alignment = POINTER_SIZE, - .length = 2 * POINTER_SIZE, - .data = tmp_data, - }; - emit_data_entry(module, &foreign_table_global_data); - - { - DatumPatchInfo patch; - patch.kind = Datum_Patch_Data; - patch.data_id = foreign_table_data.id; - patch.location = 0; - patch.index = foreign_table_global_data.id; - patch.offset = 0; - bh_arr_push(module->data_patches, patch); - } - - return foreign_table_global_data.id; - -#undef WRITE_SLICE -#undef WRITE_PTR -#undef PATCH -} - - - -static u64 build_tagged_procedures(OnyxWasmModule *module) { - bh_arr(u32) base_patch_locations=NULL; - bh_arr_new(global_heap_allocator, base_patch_locations, 256); - -#define PATCH (bh_arr_push(base_patch_locations, tag_proc_buffer.length)) -#define WRITE_PTR(val) \ - bh_buffer_align(&tag_proc_buffer, POINTER_SIZE); \ - PATCH; \ - if (POINTER_SIZE == 4) bh_buffer_write_u32(&tag_proc_buffer, val); \ - if (POINTER_SIZE == 8) bh_buffer_write_u64(&tag_proc_buffer, val); -#define WRITE_SLICE(ptr, count) \ - WRITE_PTR(ptr); \ - if (POINTER_SIZE == 4) bh_buffer_write_u32(&tag_proc_buffer, count); \ - if (POINTER_SIZE == 8) bh_buffer_write_u64(&tag_proc_buffer, count); - - #if (POINTER_SIZE == 4) - #define Tagged_Procedure_Type u32 - #else - #define Tagged_Procedure_Type u64 - #endif - u32 proc_count = bh_arr_length(module->procedures_with_tags); - Tagged_Procedure_Type* tag_proc_info = bh_alloc_array(global_heap_allocator, Tagged_Procedure_Type, proc_count); // HACK - memset(tag_proc_info, 0, proc_count * sizeof(Tagged_Procedure_Type)); - - bh_buffer tag_proc_buffer; - bh_buffer_init(&tag_proc_buffer, global_heap_allocator, 4096); - - u32 proc_info_data_id = NEXT_DATA_ID(module); - - ConstExprContext constexpr_ctx; - constexpr_ctx.module = module; - constexpr_ctx.data_id = proc_info_data_id; - - // - // This is necessary because 0 is an invalid offset to store in this - // buffer, as 0 will map to NULL. This could be a single byte insertion, - // but 64 bytes keeps better alignment. - bh_buffer_write_u64(&tag_proc_buffer, 0); - - u32 index = 0; - bh_arr_each(AstFunction *, pfunc, module->procedures_with_tags) { - AstFunction *func = *pfunc; - if (!should_emit_function(func)) { - proc_count--; - continue; - } - - u32 tag_count = bh_arr_length(func->tags); - u32 *tag_data_offsets = bh_alloc_array(global_scratch_allocator, u32, tag_count); - u32 *tag_data_types = bh_alloc_array(global_scratch_allocator, u32, tag_count); - - u32 tag_index = 0; - bh_arr_each(AstTyped *, ptag, func->tags) { - AstTyped *tag = *ptag; - bh_buffer_align(&tag_proc_buffer, type_alignment_of(tag->type)); - - tag_data_offsets[tag_index ] = tag_proc_buffer.length; - tag_data_types [tag_index++] = tag->type->id; - - u32 size = type_size_of(tag->type); - bh_buffer_grow(&tag_proc_buffer, tag_proc_buffer.length + size); - - constexpr_ctx.data = tag_proc_buffer.data; - emit_constexpr(&constexpr_ctx, tag, tag_proc_buffer.length); - tag_proc_buffer.length += size; - } - - bh_buffer_align(&tag_proc_buffer, 4); - u32 tag_array_base = tag_proc_buffer.length; - fori (i, 0, tag_count) { - PATCH; - bh_buffer_write_u32(&tag_proc_buffer, tag_data_offsets[i]); - bh_buffer_write_u32(&tag_proc_buffer, tag_data_types[i]); - } - - bh_buffer_align(&tag_proc_buffer, 4); - tag_proc_info[index++] = tag_proc_buffer.length; - - bh_buffer_write_u32(&tag_proc_buffer, get_element_idx(module, func)); - bh_buffer_write_u32(&tag_proc_buffer, func->type->id); - WRITE_SLICE(tag_array_base, tag_count); - } - - if (context.options->verbose_output == 1) { - bh_printf("Tagged procedure size: %d bytes.\n", tag_proc_buffer.length); - } - - WasmDatum proc_info_data = { - .alignment = 8, - .length = tag_proc_buffer.length, - .data = tag_proc_buffer.data, - }; - emit_data_entry(module, &proc_info_data); - assert(proc_info_data.id == proc_info_data_id); - - bh_arr_each(u32, patch_loc, base_patch_locations) { - DatumPatchInfo patch; - patch.kind = Datum_Patch_Relative; - patch.data_id = proc_info_data.id; - patch.location = *patch_loc; - patch.index = proc_info_data.id; - patch.offset = 0; - bh_arr_push(module->data_patches, patch); - } - - WasmDatum proc_table_data = { - .alignment = POINTER_SIZE, - .length = proc_count * POINTER_SIZE, - .data = tag_proc_info, - }; - emit_data_entry(module, &proc_table_data); - - fori (i, 0, proc_count) { - DatumPatchInfo patch; - patch.kind = Datum_Patch_Data; - patch.data_id = proc_info_data.id; - patch.offset = tag_proc_info[i]; - patch.index = proc_table_data.id; - patch.location = i * POINTER_SIZE; - bh_arr_push(module->data_patches, patch); - } - - Tagged_Procedure_Type* tmp_data = bh_alloc(global_heap_allocator, 2 * POINTER_SIZE); - tmp_data[0] = 0; - tmp_data[1] = proc_count; - WasmDatum proc_table_global_data = { - .alignment = POINTER_SIZE, - .length = 2 * POINTER_SIZE, - .data = tmp_data, - }; - emit_data_entry(module, &proc_table_global_data); - - { - DatumPatchInfo patch; - patch.kind = Datum_Patch_Data; - patch.offset = 0; - patch.data_id = proc_table_data.id; - patch.index = proc_table_global_data.id; - patch.location = 0; - bh_arr_push(module->data_patches, patch); - } - - return proc_table_global_data.id; - -#undef WRITE_SLICE -#undef WRITE_PTR -#undef PATCH -} -