Skip to content

Commit 3cdb9a9

Browse files
authored
Fix VaList and disable va_arg for AArch64 (#9422)
va_arg is broken on most platforms, including AArch64
1 parent 75ab8db commit 3cdb9a9

File tree

10 files changed

+114
-42
lines changed

10 files changed

+114
-42
lines changed

spec/compiler/codegen/primitives_spec.cr

+25-25
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,8 @@ describe "Code gen: primitives" do
239239
end
240240

241241
describe "va_arg" do
242-
# On Windows llvm's va_arg instruction works incorrectly.
243-
{% unless flag?(:win32) %}
242+
# On Windows and AArch64 llvm's va_arg instruction works incorrectly.
243+
{% unless flag?(:win32) || flag?(:aarch64) %}
244244
it "uses llvm's va_arg instruction" do
245245
mod = codegen(%(
246246
struct VaList
@@ -255,33 +255,33 @@ describe "Code gen: primitives" do
255255
str = mod.to_s
256256
str.should contain("va_arg %VaList* %list")
257257
end
258-
{% end %}
259258

260-
pending_win32 "works with C code" do
261-
test_c(
262-
%(
263-
extern int foo_f(int,...);
264-
int foo() {
265-
return foo_f(3,1,2,3);
266-
}
267-
),
268-
%(
269-
lib LibFoo
270-
fun foo() : LibC::Int
271-
end
259+
it "works with C code" do
260+
test_c(
261+
%(
262+
extern int foo_f(int,...);
263+
int foo() {
264+
return foo_f(3,1,2,3);
265+
}
266+
),
267+
%(
268+
lib LibFoo
269+
fun foo() : LibC::Int
270+
end
272271
273-
fun foo_f(count : Int32, ...) : LibC::Int
274-
sum = 0
275-
VaList.open do |list|
276-
count.times do |i|
277-
sum += list.next(Int32)
272+
fun foo_f(count : Int32, ...) : LibC::Int
273+
sum = 0
274+
VaList.open do |list|
275+
count.times do |i|
276+
sum += list.next(Int32)
277+
end
278278
end
279+
sum
279280
end
280-
sum
281-
end
282281
283-
LibFoo.foo
284-
), &.to_i.should eq(6))
285-
end
282+
LibFoo.foo
283+
), &.to_i.should eq(6))
284+
end
285+
{% end %}
286286
end
287287
end

spec/spec_helper.cr

+1-5
Original file line numberDiff line numberDiff line change
@@ -257,11 +257,7 @@ def run(code, filename = nil, inject_primitives = true, debug = Crystal::Debug::
257257
end
258258

259259
def test_c(c_code, crystal_code, *, file = __FILE__)
260-
with_tempfile("temp_abi.c", "temp_abi.o", file: file) do |c_filename, o_filename|
261-
File.write(c_filename, c_code)
262-
263-
`#{Crystal::Compiler::CC} #{Process.quote(c_filename)} -c -o #{Process.quote(o_filename)}`.should be_truthy
264-
260+
with_temp_c_object_file(c_code, file: file) do |o_filename|
265261
yield run(%(
266262
require "prelude"
267263

spec/std/spec_helper.cr

+11
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,14 @@ def compile_and_run_source(source, flags = %w(), file = __FILE__)
108108
compile_and_run_file(source_file, flags, file: file)
109109
end
110110
end
111+
112+
def compile_and_run_source_with_c(c_code, crystal_code, flags = %w(--debug), file = __FILE__)
113+
with_temp_c_object_file(c_code, file: file) do |o_filename|
114+
yield compile_and_run_source(%(
115+
require "prelude"
116+
117+
@[Link(ldflags: #{o_filename.inspect})]
118+
#{crystal_code}
119+
))
120+
end
121+
end

spec/std/va_list_spec.cr

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
require "./spec_helper"
2+
3+
describe VaList do
4+
it "works with C code" do
5+
compile_and_run_source_with_c(
6+
%(
7+
#include <stdarg.h>
8+
extern int foo_f(int,...);
9+
int foo() {
10+
return foo_f(3,1,2,3);
11+
}
12+
13+
int read_arg(va_list *ap) {
14+
return va_arg(*ap, int);
15+
}
16+
),
17+
%(
18+
lib LibFoo
19+
fun foo : LibC::Int
20+
fun read_arg(ap : LibC::VaList*) : LibC::Int
21+
end
22+
23+
fun foo_f(count : LibC::Int, ...) : LibC::Int
24+
sum = 0
25+
VaList.open do |list|
26+
ap = list.to_unsafe
27+
count.times do |i|
28+
sum += LibFoo.read_arg(pointerof(ap))
29+
end
30+
end
31+
sum
32+
end
33+
34+
puts LibFoo.foo
35+
)) do |status, output|
36+
status.success?.should be_true
37+
output.to_i.should eq(6)
38+
end
39+
end
40+
end

spec/support/tempfile.cr

+10
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ def with_temp_executable(name, file = __FILE__)
4343
end
4444
end
4545

46+
def with_temp_c_object_file(c_code, file = __FILE__)
47+
with_tempfile("temp_c.c", "temp_c.o", file: file) do |c_filename, o_filename|
48+
File.write(c_filename, c_code)
49+
50+
`#{ENV["CC"]? || "cc"} #{Process.quote(c_filename)} -c -o #{Process.quote(o_filename)}`.should be_truthy
51+
52+
yield o_filename
53+
end
54+
end
55+
4656
if SPEC_TEMPFILE_CLEANUP
4757
at_exit do
4858
FileUtils.rm_r(SPEC_TEMPFILE_PATH) if Dir.exists?(SPEC_TEMPFILE_PATH)

spec/win32_std_spec.cr

+1
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ require "./std/uint_spec.cr"
221221
require "./std/uri/punycode_spec.cr"
222222
require "./std/uri_spec.cr"
223223
require "./std/uuid_spec.cr"
224+
# require "./std/va_list_spec.cr"
224225
require "./std/weak_ref_spec.cr"
225226
require "./std/xml/builder_spec.cr"
226227
require "./std/xml/html_spec.cr"

src/compiler/crystal/semantic/main_visitor.cr

-3
Original file line numberDiff line numberDiff line change
@@ -2431,9 +2431,6 @@ module Crystal
24312431
end
24322432

24332433
def visit_va_arg(node)
2434-
if program.has_flag? "windows"
2435-
node.raise "va_arg is not yet supported on Windows"
2436-
end
24372434
arg = call.not_nil!.args[0]? || node.raise("requires type argument")
24382435
node.type = arg.type.instance_type
24392436
end
+8-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
lib LibC
2-
type VaList = Void*
2+
# based on https://github.com/llvm/llvm-project/blob/bf1cdc2c6c0460b7121ac653c796ef4995b1dfa9/clang/lib/AST/ASTContext.cpp#L7678-L7739
3+
struct VaList
4+
__stack : Void*
5+
__gr_top : Void*
6+
__vr_top : Void*
7+
__gr_offs : Int32
8+
__vr_offs : Int32
9+
end
310
end
+7-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
lib LibC
2-
struct VaListTag
3-
gp_offset : UInt
4-
fp_offset : UInt
5-
overflow_arg_area : Void*
6-
reg_save_area : Void*
2+
# based on https://github.com/llvm/llvm-project/blob/bf1cdc2c6c0460b7121ac653c796ef4995b1dfa9/clang/lib/AST/ASTContext.cpp#L7678-L7739
3+
struct VaList
4+
__stack : Void*
5+
__gr_top : Void*
6+
__vr_top : Void*
7+
__gr_offs : Int32
8+
__vr_offs : Int32
79
end
8-
9-
type VaList = VaListTag[1]
1010
end

src/va_list.cr

+11-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@ struct VaList
1818
end
1919
end
2020

21-
{% if compare_versions(Crystal::VERSION, "0.33.0-0") > 0 %}
21+
{% if flag?(:aarch64) || flag?(:win32) %}
22+
{% platform = flag?(:aarch64) ? "AArch64" : "Windows" %}
23+
{% clang_impl = flag?(:aarch64) ? "https://github.com/llvm/llvm-project/blob/a574edbba2b24fcfb733aa2d82308131f5b7d2d6/clang/lib/CodeGen/TargetInfo.cpp#L5677-L5921" : "https://github.com/llvm/llvm-project/blob/a574edbba2b24fcfb733aa2d82308131f5b7d2d6/clang/lib/CodeGen/TargetInfo.cpp#L5958-L5964" %}
24+
# Do not call this, instead use C wrappers calling the va_arg macro for the types you need.
25+
#
26+
# Clang implements va_arg on {{platform.id}} like this: {{clang_impl.id}}
27+
# If somebody wants to fix the LLVM IR va_arg instruction on {{platform}} upstream, or port the above here, that would be welcome.
28+
def next(type)
29+
\{% raise "Cannot get variadic argument on {{platform.id}}. As a workaround implement wrappers in C calling the va_arg macro for the types you need and bind to those." %}
30+
end
31+
{% else %}
2232
@[Primitive(:va_arg)]
2333
def next(type)
2434
end

0 commit comments

Comments
 (0)