Compare commits

...

3 Commits

Author SHA1 Message Date
David Mak 025fb089c9 meta: Respect opt flags when performing whole-module optimization 2023-09-21 15:34:05 +08:00
David Mak e8b7d19b47 standalone: Update demos
- Add `newline` parameter to all output_* functions
- Add `output_str` for printing a string
- Add demo_test.py to test interop
2023-09-21 15:30:03 +08:00
David Mak f1664e7158 core: Fix passing structure arguments to extern functions
All parameters with a structure type in extern functions are marked as
`byref` instead of `byval`, as most ABIs require the first several
arguments to be passed in registers before spilling into the stack.

`byval` breaks this contract by explicitly requiring all arguments to be
 passed in the stack, breaking interop with libraries written in other
 languages.
2023-09-21 15:25:24 +08:00
24 changed files with 233 additions and 64 deletions

View File

@ -660,7 +660,8 @@ impl Nac3 {
let pass_options = PassBuilderOptions::create();
pass_options.set_merge_functions(true);
let result = main.run_passes("default<O3>", &target_machine, pass_options);
let passes = format!("default<O{}>", self.llvm_options.opt_level as u32);
let result = main.run_passes(passes.as_str(), &target_machine, pass_options);
if let Err(err) = result {
panic!("Failed to run optimization for module `main`: {}", err.to_string());
}

View File

@ -339,6 +339,7 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
let mut return_slot = None;
if fun.count_params() > 0 {
let sret_id = Attribute::get_named_enum_kind_id("sret");
let byref_id = Attribute::get_named_enum_kind_id("byref");
let byval_id = Attribute::get_named_enum_kind_id("byval");
let offset = if fun.get_enum_attribute(AttributeLoc::Param(0), sret_id).is_some() {
@ -350,7 +351,8 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
0
};
for (i, param) in params.iter().enumerate() {
if fun.get_enum_attribute(AttributeLoc::Param((i + offset) as u32), byval_id).is_some() {
let loc = AttributeLoc::Param((i + offset) as u32);
if fun.get_enum_attribute(loc, byref_id).is_some() || fun.get_enum_attribute(loc, byval_id).is_some() {
// lazy update
if loc_params.is_empty() {
loc_params.extend(params[0..i+offset].iter().copied());
@ -715,11 +717,11 @@ pub fn gen_call<'ctx, 'a, G: CodeGenerator>(
Some(ctx.get_llvm_type(generator, fun.0.ret))
};
let has_sret = ret_type.map_or(false, |ret_type| need_sret(ctx.ctx, ret_type));
let mut byvals = Vec::new();
let mut byrefs = Vec::new();
let mut params =
args.iter().enumerate().map(|(i, arg)| match ctx.get_llvm_type(generator, arg.ty) {
BasicTypeEnum::StructType(ty) if is_extern => {
byvals.push((i, ty));
byrefs.push((i, ty));
ty.ptr_type(AddressSpace::default()).into()
},
x => x
@ -739,9 +741,19 @@ pub fn gen_call<'ctx, 'a, G: CodeGenerator>(
} else {
0
};
for (i, ty) in byvals {
fun_val.add_attribute(AttributeLoc::Param((i as u32) + offset),
ctx.ctx.create_type_attribute(Attribute::get_named_enum_kind_id("byval"), ty.as_any_type_enum()));
// The attribute ID used to mark arguments of a structure type.
// Structure-Typed parameters of extern functions must **not** be marked as `byval`, as
// `byval` explicitly specifies that the argument is to be passed on the stack, which breaks
// on most ABIs where the first several arguments are expected to be passed in registers.
let passing_attr_id = Attribute::get_named_enum_kind_id(
if is_extern { "byref" } else { "byval" }
);
for (i, ty) in byrefs {
fun_val.add_attribute(
AttributeLoc::Param((i as u32) + offset),
ctx.ctx.create_type_attribute(passing_attr_id, ty.as_any_type_enum())
);
}
fun_val
});

View File

@ -327,7 +327,6 @@ impl WorkerRegistry {
self.llvm_options.opt_level
).expect(format!("could not create target machine from properties {:?}", self.llvm_options.target).as_str());
let passes = format!("default<O{}>", self.llvm_options.opt_level as u32);
let result = module.run_passes(passes.as_str(), &target_machine, pass_options);
if let Err(err) = result {
panic!("Failed to run optimization for module `{}`: {}",

View File

@ -20,20 +20,41 @@
#define usize uint16_t
#endif
void output_int32(const int32_t x) {
printf("%d\n", x);
struct cslice {
const void *data;
usize len;
};
void output_int32(const int32_t x, bool newline) {
printf("%d", x);
if (newline) {
fputc('\n', stdout);
}
}
void output_int64(const int64_t x) {
printf("%ld\n", x);
printf("%ld", x);
if (newline) {
fputc('\n', stdout);
}
}
void output_uint32(const uint32_t x) {
printf("%d\n", x);
printf("%d", x);
if (newline) {
fputc('\n', stdout);
}
}
void output_uint64(const uint64_t x) {
printf("%ld\n", x);
printf("%ld", x);
if (newline) {
fputc('\n', stdout);
}
}
void output_asciiart(const int32_t x) {
@ -43,20 +64,29 @@ void output_asciiart(const int32_t x) {
} else {
fputc(chars[x], stdout);
}
if (newline) {
fputc('\n', stdout);
}
}
struct cslice_int32 {
const int32_t* data;
usize len;
};
void output_str(struct cslice *slice, bool newline) {
for (usize i = 0; i < slice->len; ++i) {
fputc(((const char *) slice->data)[i], stdout);
}
void output_int32_list(struct cslice_int32* slice) {
if (newline) {
fputc('\n', stdout);
}
}
void output_int32_list(struct cslice *slice) {
fputc('[', stdout);
for (usize i = 0; i < slice->len; ++i) {
if (i == slice->len - 1) {
printf("%d", slice->data[i]);
printf("%d", ((const int32_t *) slice->data)[i]);
} else {
printf("%d, ", slice->data[i]);
printf("%d, ", ((const int32_t *) slice->data)[i]);
}
}
puts("]");

View File

@ -1,3 +1,7 @@
use std::io;
use std::io::Write;
use std::process::exit;
mod cslice {
// copied from https://github.com/dherman/cslice
use std::marker::PhantomData;
@ -19,30 +23,60 @@ mod cslice {
}
#[no_mangle]
pub extern "C" fn output_int32(x: i32) {
println!("{}", x);
pub extern "C" fn output_int32(x: i32, newline: bool) {
let str = format!("{x}");
if newline {
println!("{str}");
} else {
print!("{str}");
}
}
#[no_mangle]
pub extern "C" fn output_int64(x: i64) {
println!("{}", x);
pub extern "C" fn output_int64(x: i64, newline: bool) {
let str = format!("{x}");
if newline {
println!("{str}");
} else {
print!("{str}");
}
}
#[no_mangle]
pub extern "C" fn output_uint32(x: u32) {
println!("{}", x);
pub extern "C" fn output_uint32(x: u32, newline: bool) {
let str = format!("{x}");
if newline {
println!("{str}");
} else {
print!("{str}");
}
}
#[no_mangle]
pub extern "C" fn output_uint64(x: u64) {
println!("{}", x);
pub extern "C" fn output_uint64(x: u64, newline: bool) {
let str = format!("{x}");
if newline {
println!("{str}");
} else {
print!("{str}");
}
}
#[no_mangle]
pub extern "C" fn output_float64(x: f64) {
pub extern "C" fn output_float64(x: f64, newline: bool) {
// debug output to preserve the digits after the decimal points
// to match python `print` function
println!("{:?}", x);
let str = format!("{:?}", x);
if newline {
println!("{str}");
} else {
print!("{str}");
}
}
#[no_mangle]
@ -56,7 +90,18 @@ pub extern "C" fn output_asciiart(x: i32) {
}
#[no_mangle]
pub extern "C" fn output_int32_list(x: &cslice::CSlice<i32>) {
pub extern "C" fn output_str(x: &cslice::CSlice<u8>, newline: bool) {
for e in x.as_ref().iter() {
print!("{}", char::from(*e));
}
if newline {
println!("");
}
}
#[no_mangle]
pub extern "C" fn output_int32_list(x: &cslice::CSlice<i32>, newline: bool) {
print!("[");
let mut it = x.as_ref().iter().peekable();
while let Some(e) = it.next() {
@ -66,7 +111,11 @@ pub extern "C" fn output_int32_list(x: &cslice::CSlice<i32>) {
print!("{}, ", e);
}
}
println!("]");
print!("]");
if newline {
println!("");
}
}
#[no_mangle]
@ -75,10 +124,19 @@ pub extern "C" fn __nac3_personality(_state: u32, _exception_object: u32, _conte
}
#[no_mangle]
pub extern "C" fn __nac3_raise(_state: u32, _exception_object: u32, _context: u32) -> u32 {
unimplemented!();
pub extern "C" fn __nac3_raise(state: u32, exception_object: u32, context: u32) -> u32 {
writeln!(io::stderr(),
"__nac3_raise(state: {:#010x}, _exception_object: {:#010x}, _context: {:#010x})",
state,
exception_object,
context
).unwrap();
exit(101);
}
#[no_mangle]
pub extern "C" fn __nac3_end_catch() {}
extern "C" {
fn run() -> i32;
}

View File

@ -48,6 +48,12 @@ def patch(module):
else:
sys.stdout.write(" .,-:;i+hHM$*#@ "[x])
def output(x, newline: bool=True):
if newline:
print(x)
else:
print(x, end="")
def extern(fun):
name = fun.__name__
if name == "output_asciiart":
@ -58,9 +64,10 @@ def patch(module):
"output_int32_list",
"output_uint32",
"output_uint64",
"output_float64"
"output_float64",
"output_str",
}:
return print
return output
else:
raise NotImplementedError

View File

@ -1,9 +1,9 @@
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
@extern
def output_int64(x: int64):
def output_int64(x: int64, newline: bool=True):
...

View File

@ -1,5 +1,5 @@
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
def f1(a: int32 = 4):

View File

@ -0,0 +1,61 @@
@extern
def output_int32(x: int32, newline: bool=True):
...
@extern
def output_int64(x: int64, newline: bool=True):
...
@extern
def output_uint32(x: uint32, newline: bool=True):
...
@extern
def output_uint64(x: uint64, newline: bool=True):
...
@extern
def output_int32_list(x: list[int32], newline: bool=True):
...
@extern
def output_asciiart(x: int32):
...
@extern
def output_str(x: str, newline: bool=True):
...
def test_output_int32():
output_int32(-128)
def test_output_int64():
output_int64(int64(-256))
def test_output_uint32():
output_uint32(uint32(128))
def test_output_uint64():
output_uint64(uint64(256))
def test_output_asciiart():
for i in range(17):
output_asciiart(i)
output_asciiart(0)
def test_output_int32_list():
output_int32_list([0, 1, 3, 5, 10])
def test_output_str_family():
output_str("hello ", newline=False)
output_str("world")
def run() -> int32:
test_output_int32()
test_output_int64()
test_output_uint32()
test_output_uint64()
test_output_asciiart()
test_output_int32_list()
test_output_str_family()
return 0

View File

@ -1,7 +1,7 @@
from __future__ import annotations
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
class A:

View File

@ -1,5 +1,5 @@
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...

View File

@ -1,9 +1,9 @@
@extern
def output_int32_list(x: list[int32]):
def output_int32_list(x: list[int32], newline: bool=True):
...
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
class A:

View File

@ -1,7 +1,7 @@
# For Loop using an increasing range() expression as its iterable
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
def run() -> int32:

View File

@ -1,7 +1,7 @@
# For Loop using a decreasing range() expression as its iterable
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
def run() -> int32:

View File

@ -1,7 +1,7 @@
# For Loop using a list as its iterable
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
def run() -> int32:

View File

@ -1,7 +1,7 @@
# For Loop using an range() expression as its iterable, additionally reassigning the target on each iteration
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
def run() -> int32:

View File

@ -1,17 +1,17 @@
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
@extern
def output_uint32(x: uint32):
def output_uint32(x: uint32, newline: bool=True):
...
@extern
def output_int64(x: int64):
def output_int64(x: int64, newline: bool=True):
...
@extern
def output_uint64(x: uint64):
def output_uint64(x: uint64, newline: bool=True):
...
@extern
def output_float64(x: float):
def output_float64(x: float, newline: bool=True):
...

View File

@ -1,19 +1,19 @@
from __future__ import annotations
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
@extern
def output_uint32(x: uint32):
def output_uint32(x: uint32, newline: bool=True):
...
@extern
def output_int64(x: int64):
def output_int64(x: int64, newline: bool=True):
...
@extern
def output_uint64(x: uint64):
def output_uint64(x: uint64, newline: bool=True):
...
@extern
def output_float64(x: float):
def output_float64(x: float, newline: bool=True):
...
def run() -> int32:

View File

@ -1,5 +1,5 @@
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
class A:

View File

@ -1,5 +1,5 @@
@extern
def output_float64(f: float):
def output_float64(f: float, newline: bool=True):
...

View File

@ -1,7 +1,7 @@
from __future__ import annotations
@extern
def output_int32(a: int32):
def output_int32(a: int32, newline: bool=True):
...
class A:

View File

@ -1,9 +1,9 @@
@extern
def output_int32_list(x: list[int32]):
def output_int32_list(x: list[int32], newline: bool=True):
...
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
class A:

View File

@ -1,5 +1,5 @@
@extern
def output_int32(x: int32):
def output_int32(x: int32, newline: bool=True):
...
class A:

View File

@ -370,7 +370,8 @@ fn main() {
let pass_options = PassBuilderOptions::create();
pass_options.set_merge_functions(true);
let result = main.run_passes("default<O3>", &target_machine, pass_options);
let passes = format!("default<O{}>", opt_level as u32);
let result = main.run_passes(passes.as_str(), &target_machine, pass_options);
if let Err(err) = result {
panic!("Failed to run optimization for module `main`: {}", err.to_string());
}