From eec4cbc746ed90a380ad926a8529663a659d5169 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Fri, 25 Apr 2025 15:53:24 +0800 Subject: [PATCH] Add `__crystal_raise_cast_failed` for non-interpreted code --- src/compiler/crystal/codegen/codegen.cr | 116 ++++++++++++++++-------- src/raise.cr | 9 ++ 2 files changed, 88 insertions(+), 37 deletions(-) diff --git a/src/compiler/crystal/codegen/codegen.cr b/src/compiler/crystal/codegen/codegen.cr index 80c6cd2f9720..0e7aa7e60e98 100644 --- a/src/compiler/crystal/codegen/codegen.cr +++ b/src/compiler/crystal/codegen/codegen.cr @@ -7,15 +7,16 @@ require "./llvm_builder_helper" require "./abi/*" module Crystal - MAIN_NAME = "__crystal_main" - RAISE_NAME = "__crystal_raise" - RAISE_OVERFLOW_NAME = "__crystal_raise_overflow" - MALLOC_NAME = "__crystal_malloc64" - MALLOC_ATOMIC_NAME = "__crystal_malloc_atomic64" - REALLOC_NAME = "__crystal_realloc64" - GET_EXCEPTION_NAME = "__crystal_get_exception" - ONCE_INIT = "__crystal_once_init" - ONCE = "__crystal_once" + MAIN_NAME = "__crystal_main" + RAISE_NAME = "__crystal_raise" + RAISE_OVERFLOW_NAME = "__crystal_raise_overflow" + RAISE_CAST_FAILED_NAME = "__crystal_raise_cast_failed" + MALLOC_NAME = "__crystal_malloc64" + MALLOC_ATOMIC_NAME = "__crystal_malloc_atomic64" + REALLOC_NAME = "__crystal_realloc64" + GET_EXCEPTION_NAME = "__crystal_get_exception" + ONCE_INIT = "__crystal_once_init" + ONCE = "__crystal_once" class Program def run(code, filename : String? = nil, debug = Debug::Default) @@ -265,6 +266,7 @@ module Crystal @malloc_atomic_fun : LLVMTypedFunction? @realloc_fun : LLVMTypedFunction? @raise_overflow_fun : LLVMTypedFunction? + @raise_cast_failed_fun : LLVMTypedFunction? @c_malloc_fun : LLVMTypedFunction? @c_realloc_fun : LLVMTypedFunction? @@ -470,7 +472,7 @@ module Crystal case node.name when MALLOC_NAME, MALLOC_ATOMIC_NAME, REALLOC_NAME, RAISE_NAME, @codegen.personality_name, GET_EXCEPTION_NAME, RAISE_OVERFLOW_NAME, - ONCE_INIT, ONCE + RAISE_CAST_FAILED_NAME, ONCE_INIT, ONCE @codegen.accept node end @@ -1488,11 +1490,7 @@ module Crystal cond cmp, matches_block, doesnt_match_block position_at_end doesnt_match_block - - temp_var_name = @program.new_temp_var_name - context.vars[temp_var_name] = LLVMVar.new(last_value, obj_type, already_loaded: true) - accept type_cast_exception_call(obj_type, to_type, node, temp_var_name) - context.vars.delete temp_var_name + codegen_raise_cast_failed(type_id, to_type, node) position_at_end matches_block @last = downcast last_value, resulting_type, obj_type, true @@ -1548,28 +1546,6 @@ module Crystal false end - def type_cast_exception_call(from_type, to_type, node, var_name) - pieces = [ - StringLiteral.new("Cast from ").at(node), - Call.new(Var.new(var_name).at(node), "class").at(node), - StringLiteral.new(" to #{to_type} failed").at(node), - ] of ASTNode - - if location = node.location - pieces << StringLiteral.new(", at #{location.expanded_location}:#{location.line_number}").at(node) - end - - ex = Call.new(Path.global("TypeCastError").at(node), "new", StringInterpolation.new(pieces).at(node)).at(node) - call = Call.global("raise", ex).at(node) - call = @program.normalize(call) - - meta_vars = MetaVars.new - meta_vars[var_name] = MetaVar.new(var_name, type: from_type) - visitor = MainVisitor.new(@program, meta_vars) - @program.visit_main call, visitor: visitor - call - end - def cant_pass_closure_to_c_exception_call @cant_pass_closure_to_c_exception_call ||= begin call = Call.global("raise", StringLiteral.new("passing a closure to C is not allowed")).at(UNKNOWN_LOCATION) @@ -1579,6 +1555,63 @@ module Crystal end end + def codegen_raise_cast_failed(type_id, to_type, node) + location = node.location + set_current_debug_location(location) if location && @debug.line_numbers? + + func = crystal_raise_cast_failed_fun + call_args = [ + cast_to_void_pointer(type_id_to_class_name(type_id)), + cast_to_void_pointer(build_string_constant(to_type.to_s)), + location ? cast_to_void_pointer(build_string_constant(location.expanded_location.to_s)) : llvm_context.void_pointer.null, + ] of LLVM::Value + + if (rescue_block = @rescue_block) + invoke_out_block = new_block "invoke_out" + invoke func, call_args, invoke_out_block, rescue_block + position_at_end invoke_out_block + else + call func, call_args + end + + unreachable + end + + def type_id_to_class_name(type_id) + fun_name = "~type_id_to_class_name" + func = typed_fun?(@main_mod, fun_name) || create_type_id_to_class_name_fun(fun_name) + func = check_main_fun fun_name, func + call func, type_id + end + + # See also: `#create_metaclass_fun` + def create_type_id_to_class_name_fun(name) + in_main do + define_main_function(name, [llvm_context.int32], llvm_type(@program.string)) do |func| + set_internal_fun_debug_location(func, name) + + arg = func.params.first + + current_block = insert_block + + cases = {} of LLVM::Value => LLVM::BasicBlock + @program.llvm_id.@ids.each do |type, (_, type_id)| + block = new_block "type_#{type_id}" + cases[int32(type_id)] = block + position_at_end block + ret build_string_constant(type.to_s) + end + + otherwise = new_block "otherwise" + position_at_end otherwise + unreachable + + position_at_end current_block + @builder.switch arg, otherwise, cases + end + end + end + def visit(node : IsA) codegen_type_filter node, &.filter_by(node.const.type) end @@ -2315,6 +2348,15 @@ module Crystal end end + def crystal_raise_cast_failed_fun + @raise_cast_failed_fun ||= typed_fun?(@main_mod, RAISE_CAST_FAILED_NAME) + if raise_cast_failed_fun = @raise_cast_failed_fun + check_main_fun RAISE_CAST_FAILED_NAME, raise_cast_failed_fun + else + raise Error.new("Missing __crystal_raise_cast_failed function, either use std-lib's prelude or define it") + end + end + # Fallbacks to libc malloc and realloc when the expected __crystal_* # functions aren't defined (e.g. empty prelude). We only use them in tests # that don't require the prelude, so they don't require the GC. diff --git a/src/raise.cr b/src/raise.cr index 7ebb18f1320e..ab5d9a576fbf 100644 --- a/src/raise.cr +++ b/src/raise.cr @@ -301,4 +301,13 @@ end def __crystal_raise_cast_failed(obj, type_name : String, location : String) raise TypeCastError.new("Cast from #{obj.class} to #{type_name} failed, at #{location}") end +{% else %} + # :nodoc: + fun __crystal_raise_cast_failed(from_type : Void*, to_type : Void*, location : Void*) : NoReturn + if location + raise TypeCastError.new("Cast from #{from_type.as(String)} to #{to_type.as(String)} failed, at #{location.as(String)}") + else + raise TypeCastError.new("Cast from #{from_type.as(String)} to #{to_type.as(String)} failed") + end + end {% end %}