Skip to content

Commit f4817f4

Browse files
HertzDevilgithub-actions[bot]
authored andcommitted
Add __crystal_raise_cast_failed for non-interpreted code (#15708)
This completely circumvents the slow `MainVisitor` invocation per compiler-generated `raise(TypeCastError.new(...))` call by reimplementing the raise using a top-level fun plus LLVM instructions. It also partially addresses the issue where the codegen phase could theoretically instantiate new types due to the use of a `MainVisitor` (the only remaining place is `#cant_pass_closure_to_c_exception_call`). There is now an internal `~type_id_to_class_name` function that returns the type name corresponding to a given instance type ID. It works even when `Class#name` is not implemented in the standard library. This might be useful for other purposes, such as debugger tooling. (cherry picked from commit e3c1224)
1 parent d2369ac commit f4817f4

File tree

2 files changed

+88
-37
lines changed

2 files changed

+88
-37
lines changed

src/compiler/crystal/codegen/codegen.cr

Lines changed: 79 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ require "./llvm_builder_helper"
77
require "./abi/*"
88

99
module Crystal
10-
MAIN_NAME = "__crystal_main"
11-
RAISE_NAME = "__crystal_raise"
12-
RAISE_OVERFLOW_NAME = "__crystal_raise_overflow"
13-
MALLOC_NAME = "__crystal_malloc64"
14-
MALLOC_ATOMIC_NAME = "__crystal_malloc_atomic64"
15-
REALLOC_NAME = "__crystal_realloc64"
16-
GET_EXCEPTION_NAME = "__crystal_get_exception"
17-
ONCE_INIT = "__crystal_once_init"
18-
ONCE = "__crystal_once"
10+
MAIN_NAME = "__crystal_main"
11+
RAISE_NAME = "__crystal_raise"
12+
RAISE_OVERFLOW_NAME = "__crystal_raise_overflow"
13+
RAISE_CAST_FAILED_NAME = "__crystal_raise_cast_failed"
14+
MALLOC_NAME = "__crystal_malloc64"
15+
MALLOC_ATOMIC_NAME = "__crystal_malloc_atomic64"
16+
REALLOC_NAME = "__crystal_realloc64"
17+
GET_EXCEPTION_NAME = "__crystal_get_exception"
18+
ONCE_INIT = "__crystal_once_init"
19+
ONCE = "__crystal_once"
1920

2021
class Program
2122
def run(code, filename : String? = nil, debug = Debug::Default)
@@ -265,6 +266,7 @@ module Crystal
265266
@malloc_atomic_fun : LLVMTypedFunction?
266267
@realloc_fun : LLVMTypedFunction?
267268
@raise_overflow_fun : LLVMTypedFunction?
269+
@raise_cast_failed_fun : LLVMTypedFunction?
268270
@c_malloc_fun : LLVMTypedFunction?
269271
@c_realloc_fun : LLVMTypedFunction?
270272

@@ -470,7 +472,7 @@ module Crystal
470472
case node.name
471473
when MALLOC_NAME, MALLOC_ATOMIC_NAME, REALLOC_NAME, RAISE_NAME,
472474
@codegen.personality_name, GET_EXCEPTION_NAME, RAISE_OVERFLOW_NAME,
473-
ONCE_INIT, ONCE
475+
RAISE_CAST_FAILED_NAME, ONCE_INIT, ONCE
474476
@codegen.accept node
475477
end
476478

@@ -1488,11 +1490,7 @@ module Crystal
14881490
cond cmp, matches_block, doesnt_match_block
14891491

14901492
position_at_end doesnt_match_block
1491-
1492-
temp_var_name = @program.new_temp_var_name
1493-
context.vars[temp_var_name] = LLVMVar.new(last_value, obj_type, already_loaded: true)
1494-
accept type_cast_exception_call(obj_type, to_type, node, temp_var_name)
1495-
context.vars.delete temp_var_name
1493+
codegen_raise_cast_failed(type_id, to_type, node)
14961494

14971495
position_at_end matches_block
14981496
@last = downcast last_value, resulting_type, obj_type, true
@@ -1548,28 +1546,6 @@ module Crystal
15481546
false
15491547
end
15501548

1551-
def type_cast_exception_call(from_type, to_type, node, var_name)
1552-
pieces = [
1553-
StringLiteral.new("Cast from ").at(node),
1554-
Call.new(Var.new(var_name).at(node), "class").at(node),
1555-
StringLiteral.new(" to #{to_type} failed").at(node),
1556-
] of ASTNode
1557-
1558-
if location = node.location
1559-
pieces << StringLiteral.new(", at #{location.expanded_location}:#{location.line_number}").at(node)
1560-
end
1561-
1562-
ex = Call.new(Path.global("TypeCastError").at(node), "new", StringInterpolation.new(pieces).at(node)).at(node)
1563-
call = Call.global("raise", ex).at(node)
1564-
call = @program.normalize(call)
1565-
1566-
meta_vars = MetaVars.new
1567-
meta_vars[var_name] = MetaVar.new(var_name, type: from_type)
1568-
visitor = MainVisitor.new(@program, meta_vars)
1569-
@program.visit_main call, visitor: visitor
1570-
call
1571-
end
1572-
15731549
def cant_pass_closure_to_c_exception_call
15741550
@cant_pass_closure_to_c_exception_call ||= begin
15751551
call = Call.global("raise", StringLiteral.new("passing a closure to C is not allowed")).at(UNKNOWN_LOCATION)
@@ -1579,6 +1555,63 @@ module Crystal
15791555
end
15801556
end
15811557

1558+
def codegen_raise_cast_failed(type_id, to_type, node)
1559+
location = node.location
1560+
set_current_debug_location(location) if location && @debug.line_numbers?
1561+
1562+
func = crystal_raise_cast_failed_fun
1563+
call_args = [
1564+
cast_to_void_pointer(type_id_to_class_name(type_id)),
1565+
cast_to_void_pointer(build_string_constant(to_type.to_s)),
1566+
location ? cast_to_void_pointer(build_string_constant(location.expanded_location.to_s)) : llvm_context.void_pointer.null,
1567+
] of LLVM::Value
1568+
1569+
if (rescue_block = @rescue_block)
1570+
invoke_out_block = new_block "invoke_out"
1571+
invoke func, call_args, invoke_out_block, rescue_block
1572+
position_at_end invoke_out_block
1573+
else
1574+
call func, call_args
1575+
end
1576+
1577+
unreachable
1578+
end
1579+
1580+
def type_id_to_class_name(type_id)
1581+
fun_name = "~type_id_to_class_name"
1582+
func = typed_fun?(@main_mod, fun_name) || create_type_id_to_class_name_fun(fun_name)
1583+
func = check_main_fun fun_name, func
1584+
call func, type_id
1585+
end
1586+
1587+
# See also: `#create_metaclass_fun`
1588+
def create_type_id_to_class_name_fun(name)
1589+
in_main do
1590+
define_main_function(name, [llvm_context.int32], llvm_type(@program.string)) do |func|
1591+
set_internal_fun_debug_location(func, name)
1592+
1593+
arg = func.params.first
1594+
1595+
current_block = insert_block
1596+
1597+
cases = {} of LLVM::Value => LLVM::BasicBlock
1598+
@program.llvm_id.@ids.each do |type, (_, type_id)|
1599+
block = new_block "type_#{type_id}"
1600+
cases[int32(type_id)] = block
1601+
position_at_end block
1602+
ret build_string_constant(type.to_s)
1603+
end
1604+
1605+
otherwise = new_block "otherwise"
1606+
position_at_end otherwise
1607+
unreachable
1608+
1609+
position_at_end current_block
1610+
@builder.switch arg, otherwise, cases
1611+
end
1612+
end
1613+
end
1614+
15821615
def visit(node : IsA)
15831616
codegen_type_filter node, &.filter_by(node.const.type)
15841617
end
@@ -2315,6 +2348,15 @@ module Crystal
23152348
end
23162349
end
23172350

2351+
def crystal_raise_cast_failed_fun
2352+
@raise_cast_failed_fun ||= typed_fun?(@main_mod, RAISE_CAST_FAILED_NAME)
2353+
if raise_cast_failed_fun = @raise_cast_failed_fun
2354+
check_main_fun RAISE_CAST_FAILED_NAME, raise_cast_failed_fun
2355+
else
2356+
raise Error.new("Missing __crystal_raise_cast_failed function, either use std-lib's prelude or define it")
2357+
end
2358+
end
2359+
23182360
# Fallbacks to libc malloc and realloc when the expected __crystal_*
23192361
# functions aren't defined (e.g. empty prelude). We only use them in tests
23202362
# that don't require the prelude, so they don't require the GC.

src/raise.cr

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,4 +301,13 @@ end
301301
def __crystal_raise_cast_failed(obj, type_name : String, location : String)
302302
raise TypeCastError.new("Cast from #{obj.class} to #{type_name} failed, at #{location}")
303303
end
304+
{% else %}
305+
# :nodoc:
306+
fun __crystal_raise_cast_failed(from_type : Void*, to_type : Void*, location : Void*) : NoReturn
307+
if location
308+
raise TypeCastError.new("Cast from #{from_type.as(String)} to #{to_type.as(String)} failed, at #{location.as(String)}")
309+
else
310+
raise TypeCastError.new("Cast from #{from_type.as(String)} to #{to_type.as(String)} failed")
311+
end
312+
end
304313
{% end %}

0 commit comments

Comments
 (0)