Skip to content

Commit

Permalink
feat: Some improvements to reflection (#112)
Browse files Browse the repository at this point in the history
* debug

* changes

* some more stuff
  • Loading branch information
tibordp authored Oct 2, 2024
1 parent 16044db commit e4c30fb
Show file tree
Hide file tree
Showing 47 changed files with 1,714 additions and 466 deletions.
16 changes: 13 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ SYSROOT = sysroot

ifdef RELEASE
BUILD_DIR = $(BUILD_ROOT)/release
CARGO_FLAGS += --release
CARGO_FLAGS += --profile release
CARGO_TARGET_DIR = target/release
CFLAGS += -O3
else ifdef FAST_DEBUG
# Compile in debug mode, but with alumina-boot compiled in release mode.
# It is significantly faster.
BUILD_DIR = $(BUILD_ROOT)/fast-debug
CARGO_FLAGS += --release
CARGO_FLAGS += --profile release
CARGO_TARGET_DIR = target/release
CFLAGS += -g0
ALUMINA_FLAGS += --debug
Expand All @@ -32,6 +32,7 @@ else ifdef COVERAGE
export RUSTFLAGS += -Cinstrument-coverage
export LLVM_PROFILE_FILE = $(BUILD_ROOT)/coverage/profiles/%p-%m.profraw
else
CARGO_FLAGS += --profile dev
BUILD_DIR = $(BUILD_ROOT)/debug
CARGO_TARGET_DIR = target/debug
CFLAGS += -g3 -fPIE -rdynamic
Expand Down Expand Up @@ -294,7 +295,7 @@ quick: $(BUILD_DIR)/quick

## ------------------------------ Benchmarking -------------------------

.PHONY: bench-std bench-std-cc flamegraph
.PHONY: bench-std bench-std-cc flamegraph samply

BENCH_CMD = ./tools/bench.py -n$(if $(TIMES),$(TIMES),20) $(if $(MARKDOWN),--markdown,)

Expand All @@ -311,6 +312,15 @@ bench-std-cached: $(ALU_TEST_STD_DEPS)
bench-std-cc: $(STDLIB_TESTS).c $(MINICORO)
$(BENCH_CMD) $(CC) $(CFLAGS) -o/dev/null $^ $(LDFLAGS)

$(BUILD_DIR)/flamegraph.svg: $(ALUMINA_BOOT) $(SYSROOT_FILES)
cargo flamegraph $(CARGO_FLAGS) \
-F 10000 --dev -o $@ -- $(ALUMINA_FLAGS) --sysroot $(SYSROOT) -Ztimings --cfg test --cfg test_std --output /dev/null

flamegraph: $(BUILD_DIR)/flamegraph.svg

samply: $(ALUMINA_BOOT) $(SYSROOT_FILES)
samply record -r 10000 --reuse-threads --iteration-count 5 $(ALUMINA_BOOT) $(ALUMINA_FLAGS_TEST_STD) -Ztimings --cfg test --cfg test_std --output /dev/null

## ------------------------------ Diag tests ----------------------------

DIAG_CMD = ./tools/diag.py
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Non-exhaustive list of distinguishing features:

- Module system and 2-pass compilation (no header files and forward declarations needed)
- Generics (duck-typed, similar to C++ templates but without SFINAE), protocols and mixins
- Specialization is possible with [`when` expressions](./examples/when_expression.alu)
- Specialization is possible with `when` expressions
- Opt-in dynamic polymorphism with dynamic dispatch ([`dyn` pointers](./examples/dyn.alu))
- Limited operator overloading (via `Equatable` and `Comparable` protocols)
- Block expressions
Expand All @@ -23,7 +23,7 @@ Non-exhaustive list of distinguishing features:
- [Compile-time constant evaluation](./examples/constants.alu) (including loops, function calls, etc.)
- [Unified call syntax](https://en.wikipedia.org/wiki/Uniform_Function_Call_Syntax) for functions and macros in scope
- [Defer expressions](./examples/defer_and_move.alu)
- [Ergonomic reflection and meta-programming](./examples/reflection.alu)
- [Compile-time reflection and meta-programming](./examples/reflection.alu)

Alumina is heavily inspired by Rust, especially in terms of syntax and standard library API. Unlike Rust, however, Alumina is not memory-safe and it requires manual memory management.

Expand Down
51 changes: 49 additions & 2 deletions docs/lang_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1513,6 +1513,20 @@ use std::fmt::hex;
println!("The number is {}", 0xdeadbeef.hex());
```

There is a built-in adapter [debug](https://docs.alumina-lang.net/std/builtins/fmt.html#item.debug) that can print most values without them needing to implement the `Formattable` protocol. This is useful for debugging and logging, but may not always be the canonical or most user-friendly output.

```rust
use std::fmt::debug;

struct Point3D {
x: i32,
y: i32,
z: i32
}

println!("{}", Point3D { x: 1, y: 2, z: 3 }.debug()); // "Point3D { x: 1, y: 2, z: 3 }"
```

## Type coercion

Values of certain types can be coerced to other types without requiring an explicit conversion or cast.
Expand Down Expand Up @@ -1686,7 +1700,7 @@ fn set<T: Struct | Union, F>(obj: &mut T, name: &[u8], value: F) {
for const i in 0usize..fields.len() {
let field_ty = fields.(i).type();

if fields.(i).name() == name {
if fields.(i).name() == Option::some(name) {
when field_ty.is_same_as(value_ty) {
*fields.(i).as_mut_ptr(obj) = value;
return;
Expand Down Expand Up @@ -1715,6 +1729,9 @@ foo.set("quux", true);
// These would panic at runtime
// foo.set("bar", true);
// foo.set("unknown", 42);

println!("bar = {}", foo.bar);
println!("quux = {}", foo.quux);
```

The `when` expression is used to select the appropriate branch based on the actual type of the field. Most reflection operations are at zero runtime cost, though they may increase the binary size to include various type metadata, such as field names and attributes.
Expand Down Expand Up @@ -1777,7 +1794,7 @@ mod tests {
}

#[test]
#[test::should_fail]
#[test::should_panic]
fn test_panic() {
panic!("oops");
}
Expand Down Expand Up @@ -1886,6 +1903,36 @@ assert!(FancyInt { inner: 1 } < FancyInt { inner: 2 });
assert!(FancyInt { inner: 3 } == FancyInt { inner: 3 });
```

`Equatable` and `Comparable` protocols may be automatically derived for sufficiently simple types (i.e. enums and structs where all fields are `Equatable` or `Comparable`) by using [`DefaultEquatable`](https://docs.alumina-lang.net/std/cmp/DefaultEquatable.html) and [`LexicographicComparable`](https://docs.alumina-lang.net/std/cmp/LexicographicComparable.html) mixins.

For structs the comparison will be field-wise in order of definition.

```rust
use std::cmp::{DefaultEquatable, LexicographicComparable};

struct Point {
x: i32,
y: i32,
}

impl Point {
mixin DefaultEquatable<Point>;
}

struct Date {
year: i32,
month: i32,
day: i32,
}

impl Date {
mixin LexicographicComparable<Date>;
}

assert!(Point { x: 1, y: 2 } == Point { x: 1, y: 2 });
assert!(Date { year: 2021, month: 1, day: 2 } < Date { year: 2021, month: 2, day: 1 });
```

# Miscellaneous

## Lints (warnings)
Expand Down
2 changes: 1 addition & 1 deletion examples/unit_tests.alu
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ mod tests {
}

#[test]
#[test::should_fail]
#[test::should_panic]
fn test_panic() {
my_panic();
}
Expand Down
2 changes: 1 addition & 1 deletion libraries/json/marshal.alu
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ impl JsonUnmarshaller<It: Iterator<It, Result<Token>>> {

self._expect_token!(TokenKind::Colon);

let (actual_key, value): typeof(out.iter().next().unwrap());
let (actual_key, value): typeof(out.iter().next()._inner);
self.unmarshal(&value)?;

actual_key = when actual_key is StringBuf {
Expand Down
14 changes: 11 additions & 3 deletions src/alumina-boot/src/ast/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,7 @@ impl<'ast, 'src> AluminaVisitor<'src> for ExpressionVisitor<'ast, 'src> {
self.scope
.add_item(
Some(name),
NamedItem::new_default(NamedItemKind::Local(id), name_node, None),
NamedItem::new_default(NamedItemKind::ConstLocal(id), name_node, None),
)
.with_span_from(&self.scope, node)?;

Expand All @@ -1017,7 +1017,11 @@ impl<'ast, 'src> AluminaVisitor<'src> for ExpressionVisitor<'ast, 'src> {
self.scope
.add_item(
Some(name),
NamedItem::new_default(NamedItemKind::Local(elem_id), name_node, None),
NamedItem::new_default(
NamedItemKind::ConstLocal(elem_id),
name_node,
None,
),
)
.with_span_from(&self.scope, name_node)?;
}
Expand Down Expand Up @@ -1695,7 +1699,10 @@ impl<'ast, 'src> AluminaVisitor<'src> for LambdaVisitor<'ast, 'src> {
.with_span_from(&self.scope, node)?
{
ItemResolution::Item(NamedItem {
kind: NamedItemKind::Local(id) | NamedItemKind::Parameter(id),
kind:
NamedItemKind::Local(id)
| NamedItemKind::Parameter(id)
| NamedItemKind::ConstLocal(id),
..
}) => ExprKind::Local(id),
ItemResolution::Item(NamedItem {
Expand Down Expand Up @@ -1792,6 +1799,7 @@ pub fn resolve_name<'ast, 'src>(
NamedItemKind::Function(fun) => ExprKind::Fn(FnKind::Normal(fun), None),
NamedItemKind::Method(fun) => ExprKind::Fn(FnKind::Normal(fun), None),
NamedItemKind::Local(var) => ExprKind::Local(var),
NamedItemKind::ConstLocal(var) => ExprKind::Local(var),
NamedItemKind::BoundValue(self_id, var, bound_type) => {
ExprKind::BoundParam(self_id, var, bound_type)
}
Expand Down
12 changes: 11 additions & 1 deletion src/alumina-boot/src/codegen/elide_zst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,17 @@ impl<'ir> ZstElider<'ir> {
let indexee = self.elide_zst_expr(lhs)?;
let index = self.elide_zst_expr(rhs)?;

if indexee.ty.is_zero_sized() {
if !expr.ty.is_zero_sized() && indexee.ty.is_zero_sized() {
// Special case for indexing into a zero-length array of non-ZST elements
builder.block(
[Statement::Expression(indexee), Statement::Expression(index)],
builder.deref(
builder.dangling(types.pointer(expr.ty, expr.is_const), expr.span),
expr.span,
),
expr.span,
)
} else if expr.ty.is_zero_sized() {
builder.block(
[Statement::Expression(indexee), Statement::Expression(index)],
builder.void(expr.ty, expr.value_type, expr.span),
Expand Down
5 changes: 5 additions & 0 deletions src/alumina-boot/src/codegen/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,11 @@ impl<'ir, 'gen> FunctionWriter<'ir, 'gen> {
IntrinsicValueKind::InConstContext => {
w!(self.fn_bodies, "({})0", self.ctx.get_type(expr.ty));
}
IntrinsicValueKind::Expect(expr, val) => {
w!(self.fn_bodies, "__builtin_expect(");
self.write_expr(diag, expr, false)?;
w!(self.fn_bodies, ", {})", *val as i32);
}
IntrinsicValueKind::ConstPanic(_)
| IntrinsicValueKind::StopIteration
| IntrinsicValueKind::ConstAlloc(_, _)
Expand Down
1 change: 1 addition & 0 deletions src/alumina-boot/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const DIAGNOSTIC_SUPRESSIONS: &[(&str, &str)] = &[
("clang", "-Wincompatible-library-redeclaration"),
("clang", "-Wunused-value"),
("clang", "-Wincompatible-pointer-types"),
("clang", "-Wnull-dereference"),
("GCC", "-Wbuiltin-declaration-mismatch"),
];

Expand Down
4 changes: 3 additions & 1 deletion src/alumina-boot/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ pub enum CodeDiagnostic {
CyclicProtocolBound,
#[error("multiple `main` functions found")]
MultipleMainFunctions,
#[error("unpopulated item")]
#[error("cyclic dependency during type resolution")]
UnpopulatedItem,
#[error(
"generic type parameters cannot be used in this context (did you mean to call a function?)"
Expand Down Expand Up @@ -328,6 +328,8 @@ pub enum CodeDiagnostic {
TupleCallArgType,
#[error("too many loop variables (iterator yields {})", .0)]
TooManyLoopVars(String),
#[error("too many enum variants for underlying type `{}`", .0)]
TooManyEnumVariants(String),

// Warnings
#[error("protocol is used as a concrete type (did you mean to use `&dyn {}`?)", .0)]
Expand Down
4 changes: 3 additions & 1 deletion src/alumina-boot/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,9 @@ impl Compiler {

// Finally generate static initialization code
let mut monomorphizer = Mono::new(&mut mono_ctx, false, None);
dce.visit_item(monomorphizer.generate_static_constructor(dce.alive_items())?)?;
if let Some(item) = monomorphizer.generate_static_constructor(dce.alive_items())? {
dce.visit_item(item)?;
}

let items: Vec<_> = dce.alive_items().iter().copied().collect();
timing!(self, cur_time, Stage::Optimizations);
Expand Down
3 changes: 2 additions & 1 deletion src/alumina-boot/src/ir/const_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1306,7 +1306,7 @@ impl<'ir> ConstEvaluator<'ir> {

let cond_value = match condv {
Value::Bool(b) => b,
_ => bug!(self),
_ => unsupported!(self),
};

if cond_value {
Expand Down Expand Up @@ -1515,6 +1515,7 @@ impl<'ir> ConstEvaluator<'ir> {
_ => Err(ConstEvalErrorKind::InvalidFree).with_backtrace(&self.diag),
}
}
IntrinsicValueKind::Expect(inner, _) => self.const_eval_rvalue(inner),
},
ExprKind::Item(item) => match item.get() {
Ok(Item::Function(_)) => Ok(Value::FunctionPointer(item)),
Expand Down
6 changes: 3 additions & 3 deletions src/alumina-boot/src/ir/dce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@ impl<'ir> ExpressionVisitor<'ir> for DeadCodeEliminator<'ir> {
| IntrinsicValueKind::InConstContext
| IntrinsicValueKind::Uninitialized
| IntrinsicValueKind::Asm(_) => Ok(()),
IntrinsicValueKind::Transmute(inner) | IntrinsicValueKind::Volatile(inner) => {
self.visit_expr(inner)
}
IntrinsicValueKind::Transmute(inner)
| IntrinsicValueKind::Volatile(inner)
| IntrinsicValueKind::Expect(inner, _) => self.visit_expr(inner),
// These should be unreachable
IntrinsicValueKind::ConstPanic(_)
| IntrinsicValueKind::ConstWrite(_, _)
Expand Down
Loading

0 comments on commit e4c30fb

Please sign in to comment.