Skip to content

Add __crystal_raise_cast_failed for non-interpreted code #15708

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 79 additions & 37 deletions src/compiler/crystal/codegen/codegen.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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?

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
@[email protected] 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
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions src/raise.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}