diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 6077200d1..30a6b1b8b 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -2065,6 +2065,7 @@ const char *symtab_find(const char *symbol, uint32_t len, uint32_t fnv1hash, Tok void *llvm_target_machine_create(void); void target_setup(BuildTarget *build_target); int target_alloca_addr_space(); +bool os_is_apple(OsType os_type); const char *macos_sysroot(void); MacSDK *macos_sysroot_sdk_information(const char *sdk_path); diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 1601e1ebd..a87a76342 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -761,6 +761,7 @@ typedef enum BUILTIN_VOLATILE_STORE, BUILTIN_MEMCOPY, BUILTIN_MEMSET, + BUILTIN_SYSCALL, BUILTIN_NONE, NUMBER_OF_BUILTINS = BUILTIN_NONE, } BuiltinFunction; diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 3c80285f4..2f342e436 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -4373,6 +4373,7 @@ unsigned llvm_get_intrinsic(BuiltinFunction func) return intrinsic_id.memset; case BUILTIN_VOLATILE_STORE: case BUILTIN_VOLATILE_LOAD: + case BUILTIN_SYSCALL: UNREACHABLE } UNREACHABLE @@ -4393,6 +4394,103 @@ LLVMAtomicOrdering llvm_atomic_ordering(Atomicity atomicity) UNREACHABLE } +static inline void llvm_syscall_write_regs_to_scratch(const char** registers, unsigned args) +{ + for (unsigned i = 0; i < args; i++) + { + scratch_buffer_append(",{"); + scratch_buffer_append(registers[i]); + scratch_buffer_append("}"); + } +} + +static inline LLVMValueRef llvm_syscall_asm(GenContext *c, LLVMTypeRef func_type, char *call) +{ + return LLVMGetInlineAsm(func_type, call, strlen(call), + scratch_buffer_to_string(), scratch_buffer.len, + true, true, LLVMInlineAsmDialectATT +#if LLVM_VERSION_MAJOR >= 13 + , false +#endif + ); + +} + +static inline void llvm_emit_syscall(GenContext *c, BEValue *be_value, Expr *expr) +{ + unsigned arguments = vec_size(expr->call_expr.arguments); + assert(arguments < 10 && "Only has room for 10"); + LLVMValueRef arg_results[10]; + LLVMTypeRef arg_types[10]; + Expr **args = expr->call_expr.arguments; + LLVMTypeRef type = llvm_get_type(c, type_uptr); + for (unsigned i = 0; i < arguments; i++) + { + llvm_emit_expr(c, be_value, args[i]); + llvm_value_rvalue(c, be_value); + arg_results[i] = be_value->value; + arg_types[i] = type; + } + LLVMTypeRef func_type = LLVMFunctionType(type, arg_types, arguments, false); + scratch_buffer_clear(); + LLVMValueRef inline_asm; + switch (platform_target.arch) + { + case ARCH_TYPE_AARCH64: + case ARCH_TYPE_AARCH64_BE: + scratch_buffer_append("={x0}"); + assert(arguments < 8); + if (os_is_apple(platform_target.os)) + { + static char const *regs[] = { "x16", "x0", "x1", "x2", "x3", "x4", "x5" }; + llvm_syscall_write_regs_to_scratch(regs, arguments); + } + else + { + static char const *regs[] = { "x8", "x0", "x1", "x2", "x3", "x4", "x5" }; + llvm_syscall_write_regs_to_scratch(regs, arguments); + } + inline_asm = llvm_syscall_asm(c, func_type, "svc #0x80"); + break; + case ARCH_TYPE_X86: + scratch_buffer_append("={eax}"); + assert(arguments < 8); + for (unsigned i = 0; i < arguments && i < 6; i++) + { + scratch_buffer_append(",{"); + static char const *regs[] = { "eax", "ebx", "ecx", "edx", "esi", "edi" }; + scratch_buffer_append(regs[i]); + scratch_buffer_append("}"); + } + if (arguments == 7) + { + char *asm_str = "push %[arg6]\npush %%ebp\nmov 4(%%esp), %%ebp\nint $0x80\npop %%ebp\nadd $4, %%esp"; + scratch_buffer_append(",rm"); + inline_asm = LLVMGetInlineAsm(func_type, asm_str, strlen(asm_str), scratch_buffer_to_string(), scratch_buffer.len, true, false, LLVMInlineAsmDialectATT, false); + } + inline_asm = LLVMGetInlineAsm(func_type, "int $0x80", 9, scratch_buffer_to_string(), scratch_buffer.len, true, false, LLVMInlineAsmDialectATT, false); + break; + case ARCH_TYPE_X86_64: + scratch_buffer_append("={rax}"); + assert(arguments < 8); + for (unsigned i = 0; i < arguments; i++) + { + scratch_buffer_append(",{"); + static char const *regs[] = { "rax", "rdi", "rsi", "rdx", "r10", "r8", "r9" }; + scratch_buffer_append(regs[i]); + scratch_buffer_append("}"); + } + // Check clobbers on different OSes + scratch_buffer_append(",~{rcx},~{r11},~{memory}"); + inline_asm = LLVMGetInlineAsm(func_type, "syscall", 7, scratch_buffer_to_string(), scratch_buffer.len, true, false, LLVMInlineAsmDialectATT, false); + break; + case ARCH_UNSUPPORTED: + default: + UNREACHABLE + } + LLVMValueRef result = LLVMBuildCall2(c->builder, func_type, inline_asm, arg_results, arguments, "syscall"); + llvm_value_set(be_value, result, type_uptr); +} void llvm_emit_builtin_call(GenContext *c, BEValue *result_value, Expr *expr) { BuiltinFunction func = exprptr(expr->call_expr.function)->builtin_expr.builtin; @@ -4437,6 +4535,11 @@ void llvm_emit_builtin_call(GenContext *c, BEValue *result_value, Expr *expr) LLVMSetVolatile(result_value->value, true); return; } + if (func == BUILTIN_SYSCALL) + { + llvm_emit_syscall(c, result_value, expr); + return; + } llvm_emit_intrinsic_expr(c, llvm_get_intrinsic(func), result_value, expr); } diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 9de58bf3f..bc58c8728 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -2281,6 +2281,7 @@ static inline unsigned builtin_expected_args(BuiltinFunction func) case BUILTIN_TRAP: case BUILTIN_STACKTRACE: return 0; + case BUILTIN_SYSCALL: case BUILTIN_CEIL: case BUILTIN_TRUNC: case BUILTIN_SQRT: @@ -2400,8 +2401,9 @@ static inline bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *ex Expr **args = expr->call_expr.arguments; unsigned arg_count = vec_size(args); + bool is_vararg = func == BUILTIN_SYSCALL; // 1. Handle arg count, so at least we know that is ok. - if (expected_args != arg_count) + if (expected_args != arg_count && !is_vararg) { if (arg_count == 0) { @@ -2416,7 +2418,11 @@ static inline bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *ex SEMA_ERROR(args[expected_args], "Too many arguments."); return false; } - + if (is_vararg && expected_args > arg_count) + { + SEMA_ERROR(expr, "Expected at least %d arguments to builtin.\n", expected_args); + return false; + } bool failable = false; // 2. We can now check all the arguments, since they in general work on the @@ -2430,10 +2436,33 @@ static inline bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *ex Type *rtype = NULL; switch (func) { + case BUILTIN_UNREACHABLE: case BUILTIN_TRAP: rtype = type_void; break; + case BUILTIN_SYSCALL: + if (arg_count > 7) + { + SEMA_ERROR(args[7], "Only 7 arguments supported for $$syscall."); + } + rtype = type_uptr; + for (unsigned i = 0; i < arg_count; i++) + { + if (!cast_implicit(args[i], type_uptr)) return false; + } + switch (platform_target.arch) + { + case ARCH_TYPE_AARCH64: + case ARCH_TYPE_AARCH64_BE: + case ARCH_TYPE_X86: + case ARCH_TYPE_X86_64: + break; + default: + SEMA_ERROR(expr, "Target does not support $$syscall."); + return false; + } + break; case BUILTIN_MEMCOPY: if (!sema_check_builtin_args(args, (BuiltinArg[]) { BA_POINTER, BA_POINTER, BA_SIZE, BA_BOOL, BA_SIZE, BA_SIZE }, diff --git a/src/compiler/symtab.c b/src/compiler/symtab.c index 1c4bd533d..d98ee9bba 100644 --- a/src/compiler/symtab.c +++ b/src/compiler/symtab.c @@ -195,6 +195,7 @@ void symtab_init(uint32_t capacity) builtin_list[BUILTIN_FABS] = KW_DEF("fabs"); builtin_list[BUILTIN_VOLATILE_STORE] = KW_DEF("volatile_store"); builtin_list[BUILTIN_VOLATILE_LOAD] = KW_DEF("volatile_load"); + builtin_list[BUILTIN_SYSCALL] = KW_DEF("syscall"); builtin_list[BUILTIN_MEMCOPY] = KW_DEF("memcpy"); builtin_list[BUILTIN_MEMSET] = KW_DEF("memset"); diff --git a/src/compiler/target.c b/src/compiler/target.c index 1466f243b..db6840b5a 100644 --- a/src/compiler/target.c +++ b/src/compiler/target.c @@ -199,7 +199,7 @@ void llvm_dump(void) } -static inline bool os_is_apple(OsType os_type) +bool os_is_apple(OsType os_type) { return os_type == OS_TYPE_TVOS || os_type == OS_TYPE_WATCHOS || os_type == OS_TYPE_MACOSX || os_type == OS_TYPE_IOS;