Merge branch 'main' into specialize-exprs

This commit is contained in:
Agus Zubiaga 2024-11-27 11:35:14 -03:00
commit 1dc9dd0a90
No known key found for this signature in database
23 changed files with 286 additions and 160 deletions

View file

@ -24,7 +24,12 @@ jobs:
run: nix develop -c ./ci/roc_test_builtins.sh
- name: test wasm32 cli_tests
run: nix develop -c cargo test --locked --release --features="wasm32-cli-run"
# skipping glue tests due to difficult multithreading bug, we run them single threaded in the next step
run: nix develop -c cargo test --locked --release --features="wasm32-cli-run" -- --skip glue_cli_tests
- name: wasm32 glue_cli_tests
# single threaded due to difficult bug when multithreading
run: nix develop -c cargo test --locked --release --features="wasm32-cli-run" glue_cli_tests -- --test-threads=1
- name: test the dev backend # these tests require an explicit feature flag
run: nix develop -c cargo nextest run --locked --release --package test_gen --no-default-features --features gen-dev --no-fail-fast

1
.gitignore vendored
View file

@ -59,6 +59,7 @@ metadata
#editors
.idea/
.vscode/
.helix/
.ignore
.exrc
.vimrc

View file

@ -8,7 +8,7 @@ Defs {
EitherIndex(2147483648),
],
regions: [
@0-26,
@0-28,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
@ -25,13 +25,13 @@ Defs {
@0-4 Identifier {
ident: "main",
},
@11-26 Defs(
@11-28 Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@15-26,
@16-27,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
@ -43,17 +43,17 @@ Defs {
type_defs: [],
value_defs: [
Body(
@15-26 Identifier {
@16-27 Identifier {
ident: "1",
},
@15-26 ParensAround(
@16-27 ParensAround(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@20-25,
@21-26,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
@ -65,48 +65,50 @@ Defs {
type_defs: [],
value_defs: [
Body(
@20-25 Identifier {
@21-26 Identifier {
ident: "0",
},
@20-25 Apply(
@22-23 Var {
module_name: "Num",
ident: "add",
},
[
@20-21 Num(
"1",
@21-26 ParensAround(
Apply(
@23-24 Var {
module_name: "Num",
ident: "add",
},
[
@21-22 Num(
"1",
),
@25-26 Num(
"1",
),
],
BinOp(
Plus,
),
@24-25 Num(
"1",
),
],
BinOp(
Plus,
),
),
),
],
},
@15-26 LowLevelDbg(
@16-27 LowLevelDbg(
(
"test.roc:3",
" ",
),
@20-25 Apply(
@20-25 Var {
@21-26 Apply(
@21-26 Var {
module_name: "Inspect",
ident: "toStr",
},
[
@20-25 Var {
@21-26 Var {
module_name: "",
ident: "0",
},
],
Space,
),
@20-25 Var {
@21-26 Var {
module_name: "",
ident: "0",
},
@ -116,25 +118,25 @@ Defs {
),
],
},
@11-26 LowLevelDbg(
@11-28 LowLevelDbg(
(
"test.roc:2",
"in =\n ",
"n =\n ",
),
@15-26 Apply(
@15-26 Var {
@16-27 Apply(
@16-27 Var {
module_name: "Inspect",
ident: "toStr",
},
[
@15-26 Var {
@16-27 Var {
module_name: "",
ident: "1",
},
],
Space,
),
@15-26 Var {
@16-27 Var {
module_name: "",
ident: "1",
},

View file

@ -464,7 +464,7 @@ mod suffixed_tests {
run_test!(
r#"
main =
dbg (dbg 1 + 1)
dbg (dbg (1 + 1))
"#
);
}

View file

@ -447,7 +447,7 @@ impl<'a> Formattable for Expr<'a> {
buf.push_str("dbg");
}
DbgStmt(condition, continuation) => {
fmt_dbg_stmt(buf, condition, continuation, self.is_multiline(), indent);
fmt_dbg_stmt(buf, condition, continuation, parens, indent);
}
LowLevelDbg(_, _, _) => unreachable!(
"LowLevelDbg should only exist after desugaring, not during formatting"
@ -1022,42 +1022,15 @@ fn fmt_dbg_stmt<'a>(
buf: &mut Buf,
condition: &'a Loc<Expr<'a>>,
continuation: &'a Loc<Expr<'a>>,
_: bool,
parens: Parens,
indent: u16,
) {
buf.ensure_ends_with_newline();
buf.indent(indent);
buf.push_str("dbg");
buf.spaces(1);
fn should_outdent(mut expr: &Expr) -> bool {
loop {
match expr {
Expr::ParensAround(_) | Expr::List(_) | Expr::Record(_) | Expr::Tuple(_) => {
return true
}
Expr::SpaceAfter(inner, _) => {
expr = inner;
}
_ => return false,
}
}
}
let inner_indent = if should_outdent(&condition.value) {
indent
} else {
indent + INDENT
};
let cond_value = condition.value.extract_spaces();
let is_defs = matches!(cond_value.item, Expr::Defs(_, _));
let newlines = if is_defs { Newlines::Yes } else { Newlines::No };
condition.format_with_options(buf, Parens::NotNeeded, newlines, inner_indent);
Expr::Apply(
&Loc::at_zero(Expr::Dbg),
&[condition],
called_via::CalledVia::Space,
)
.format_with_options(buf, parens, Newlines::Yes, indent);
// Always put a blank line after the `dbg` line(s)
buf.ensure_ends_with_blank_line();

View file

@ -1846,6 +1846,26 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
neg_reg64_reg64(buf, dst, src);
}
#[inline(always)]
fn neg_freg64_freg64(
buf: &mut Vec<'_, u8>,
_relocs: &mut Vec<'_, Relocation>,
dst: AArch64FloatReg,
src: AArch64FloatReg,
) {
fneg_freg_freg(buf, FloatWidth::F64, dst, src);
}
#[inline(always)]
fn neg_freg32_freg32(
buf: &mut Vec<'_, u8>,
_relocs: &mut Vec<'_, Relocation>,
dst: AArch64FloatReg,
src: AArch64FloatReg,
) {
fneg_freg_freg(buf, FloatWidth::F32, dst, src);
}
#[inline(always)]
fn sub_reg64_reg64_imm32(
buf: &mut Vec<'_, u8>,
@ -3953,6 +3973,24 @@ fn fsub_freg_freg_freg(
buf.extend(inst.bytes());
}
/// `FNEG Sd/Dd, Sn/Dn`
#[inline(always)]
fn fneg_freg_freg(
buf: &mut Vec<'_, u8>,
ftype: FloatWidth,
dst: AArch64FloatReg,
src: AArch64FloatReg,
) {
let inst =
FloatingPointDataProcessingOneSource::new(FloatingPointDataProcessingOneSourceParams {
ptype: ftype,
opcode: 0b00010,
rn: src,
rd: dst,
});
buf.extend(inst.bytes());
}
/// `FCMP Sn/Dn, Sm/Dm` -> Compare Sn/Dn and Sm/Dm, setting condition flags.
#[inline(always)]
fn fcmp_freg_freg(

View file

@ -557,6 +557,18 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
fn sqrt_freg32_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg);
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn neg_freg64_freg64(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
dst: FloatReg,
src: FloatReg,
);
fn neg_freg32_freg32(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
dst: FloatReg,
src: FloatReg,
);
fn mul_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
@ -1791,7 +1803,24 @@ impl<
let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src);
ASM::neg_reg64_reg64(&mut self.buf, dst_reg, src_reg);
}
x => todo!("NumNeg: layout, {:?}", x),
LayoutRepr::F32 => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
ASM::neg_freg32_freg32(&mut self.buf, &mut self.relocs, dst_reg, src_reg);
}
LayoutRepr::F64 => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src);
ASM::neg_freg64_freg64(&mut self.buf, &mut self.relocs, dst_reg, src_reg);
}
LayoutRepr::DEC => self.build_fn_call(
dst,
bitcode::DEC_NEGATE.to_string(),
&[*src],
&[Layout::DEC],
&Layout::DEC,
),
other => internal_error!("unreachable: NumNeg for layout, {:?}", other),
}
}

View file

@ -2602,6 +2602,28 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
neg_reg64(buf, dst);
}
#[inline(always)]
fn neg_freg64_freg64(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
dst: X86_64FloatReg,
src: X86_64FloatReg,
) {
Self::mov_freg64_imm64(buf, relocs, dst, f64::from_bits(0x8000_0000_0000_0000));
xorpd_freg64_freg64(buf, dst, src);
}
#[inline(always)]
fn neg_freg32_freg32(
buf: &mut Vec<'_, u8>,
relocs: &mut Vec<'_, Relocation>,
dst: X86_64FloatReg,
src: X86_64FloatReg,
) {
Self::mov_freg32_imm32(buf, relocs, dst, f32::from_bits(0x8000_0000));
xorps_freg32_freg32(buf, dst, src);
}
#[inline(always)]
fn sub_reg64_reg64_imm32(
buf: &mut Vec<'_, u8>,
@ -3352,6 +3374,49 @@ fn sqrtss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64F
}
}
/// `XORPD xmm1, xmm2/m128` -> Bitwise exclusive-OR of xmm2/m128 and xmm1.
#[inline(always)]
fn xorpd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend([
0x66,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x57,
0xC0 | (dst_mod << 3) | src_mod,
])
} else {
buf.extend([0x66, 0x0F, 0x57, 0xC0 | (dst_mod << 3) | src_mod]);
}
}
/// `XORPS xmm1,xmm2/m128` -> Bitwise exclusive-OR of xmm2/m128 and xmm1.
#[inline(always)]
fn xorps_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend([
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x57,
0xC0 | (dst_mod << 3) | src_mod,
]);
} else {
buf.extend([0x0F, 0x57, 0xC0 | (dst_mod << 3) | src_mod]);
}
}
/// `TEST r/m64,r64` -> AND r64 with r/m64; set SF, ZF, PF according to result.
#[allow(dead_code)]
#[inline(always)]

View file

@ -2215,6 +2215,7 @@ fn build_dec_unary_op<'a, 'ctx>(
match op {
NumAbs => dec_unary_op(env, bitcode::DEC_ABS, arg),
NumNeg => dec_unary_op(env, bitcode::DEC_NEGATE, arg),
NumAcos => dec_unary_op(env, bitcode::DEC_ACOS, arg),
NumAsin => dec_unary_op(env, bitcode::DEC_ASIN, arg),
NumAtan => dec_unary_op(env, bitcode::DEC_ATAN, arg),

View file

@ -1624,6 +1624,7 @@ impl<'a> LowLevelCall<'a> {
}
F32 => backend.code_builder.f32_neg(),
F64 => backend.code_builder.f64_neg(),
Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_NEGATE),
_ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout),
}
}

View file

@ -545,10 +545,6 @@ fn stmt_start<'a>(
EExpr::Expect,
expect_help(options, preceding_comment)
)),
loc(specialize_err(
EExpr::Dbg,
dbg_stmt_help(options, preceding_comment)
)),
loc(specialize_err(EExpr::Return, return_help(options))),
loc(specialize_err(EExpr::Import, map(import(), Stmt::ValueDef))),
map(
@ -2668,34 +2664,6 @@ fn return_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Stmt<'a>, ERetu
.trace("return_help")
}
fn dbg_stmt_help<'a>(
options: ExprParseOptions,
preceding_comment: Region,
) -> impl Parser<'a, Stmt<'a>, EExpect<'a>> {
(move |arena: &'a Bump, state: State<'a>, min_indent| {
let (_, _, state) =
parser::keyword(keyword::DBG, EExpect::Dbg).parse(arena, state, min_indent)?;
let (_, condition, state) = parse_block(
options,
arena,
state,
true,
EExpect::IndentCondition,
EExpect::Condition,
)
.map_err(|(_, f)| (MadeProgress, f))?;
let stmt = Stmt::ValueDef(ValueDef::Dbg {
condition: arena.alloc(condition),
preceding_comment,
});
Ok((MadeProgress, stmt, state))
})
.trace("dbg_stmt_help")
}
fn dbg_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpect<'a>> {
(move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
let (_, _, next_state) =
@ -3110,12 +3078,43 @@ fn stmts_to_defs<'a>(
}
Stmt::Expr(e) => {
if i + 1 < stmts.len() {
defs.push_value_def(
ValueDef::Stmt(arena.alloc(Loc::at(sp_stmt.item.region, e))),
sp_stmt.item.region,
sp_stmt.before,
&[],
);
if let Expr::Apply(
Loc {
value: Expr::Dbg, ..
},
args,
_,
) = e
{
if args.len() != 1 {
// TODO: this should be done in can, not parsing!
return Err(EExpr::Dbg(
EExpect::DbgArity(sp_stmt.item.region.start()),
sp_stmt.item.region.start(),
));
}
let condition = &args[0];
let rest = stmts_to_expr(&stmts[i + 1..], arena)?;
let e = Expr::DbgStmt(condition, arena.alloc(rest));
let e = if sp_stmt.before.is_empty() {
e
} else {
arena.alloc(e).before(sp_stmt.before)
};
last_expr = Some(Loc::at(sp_stmt.item.region, e));
// don't re-process the rest of the statements; they got consumed by the dbg expr
break;
} else {
defs.push_value_def(
ValueDef::Stmt(arena.alloc(Loc::at(sp_stmt.item.region, e))),
sp_stmt.item.region,
sp_stmt.before,
&[],
);
}
} else {
let e = if sp_stmt.before.is_empty() {
e

View file

@ -1465,6 +1465,7 @@ impl<'a> Normalize<'a> for EExpect<'a> {
EExpect::Continuation(arena.alloc(inner_err.normalize(arena)), Position::zero())
}
EExpect::IndentCondition(_) => EExpect::IndentCondition(Position::zero()),
EExpect::DbgArity(_) => EExpect::DbgArity(Position::zero()),
}
}
}

View file

@ -513,6 +513,7 @@ pub enum EExpect<'a> {
Condition(&'a EExpr<'a>, Position),
Continuation(&'a EExpr<'a>, Position),
IndentCondition(Position),
DbgArity(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]

View file

@ -1607,7 +1607,7 @@ fn tail_call_elimination() {
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
fn int_negate() {
fn num_negate() {
assert_evals_to!("Num.neg 123i8", -123, i8);
assert_evals_to!("Num.neg Num.maxI8", -i8::MAX, i8);
assert_evals_to!("Num.neg (Num.minI8 + 1)", i8::MAX, i8);
@ -1623,6 +1623,26 @@ fn int_negate() {
assert_evals_to!("Num.neg 123", -123, i64);
assert_evals_to!("Num.neg Num.maxI64", -i64::MAX, i64);
assert_evals_to!("Num.neg (Num.minI64 + 1)", i64::MAX, i64);
assert_evals_to!("Num.neg 12.3f32", -12.3, f32);
assert_evals_to!("Num.neg 0.0f32", -0.0, f32);
assert_evals_to!("Num.neg Num.maxF32", -f32::MAX, f32);
assert_evals_to!("Num.neg Num.minF32", -f32::MIN, f32);
assert_evals_to!("Num.neg Num.infinityF32", -f32::INFINITY, f32);
// can't test equality for nan
assert_evals_to!("Num.isNaN (Num.neg Num.nanF32)", true, bool);
assert_evals_to!("Num.neg 12.3f64", -12.3, f64);
assert_evals_to!("Num.neg 0.0f64", -0.0, f64);
assert_evals_to!("Num.neg Num.maxF64", -f64::MAX, f64);
assert_evals_to!("Num.neg Num.minF64", -f64::MIN, f64);
assert_evals_to!("Num.neg Num.infinityF64", -f64::INFINITY, f64);
// can't test equality for nan
assert_evals_to!("Num.isNaN (Num.neg Num.nanF64)", true, bool);
assert_evals_to!("Num.neg 123dec", RocDec::from(-123), RocDec);
// 0 is signless, unlike f32/f64
assert_evals_to!("Num.neg 0dec", RocDec::from(0), RocDec);
}
#[test]

View file

@ -1,6 +1,6 @@
SpaceAfter(
Apply(
@0-5 Dbg,
@0-3 Dbg,
[
@4-5 Num(
"1",

View file

@ -1,3 +1,3 @@
dbg 1 == 1
dbg (1 == 1)
4

View file

@ -1,20 +1,22 @@
SpaceBefore(
SpaceAfter(
DbgStmt(
@5-11 BinOps(
[
(
@5-6 Num(
"1",
@6-12 ParensAround(
BinOps(
[
(
@6-7 Num(
"1",
),
@8-10 Equals,
),
@7-9 Equals,
],
@11-12 Num(
"1",
),
],
@10-11 Num(
"1",
),
),
@13-14 SpaceBefore(
@15-16 SpaceBefore(
Num(
"4",
),

View file

@ -1,4 +1,4 @@
dbg 1 == 1
dbg (1 == 1)
4

View file

@ -1,53 +1,38 @@
SpaceAfter(
DbgStmt(
@5-10 SpaceBefore(
Defs(
Defs {
tags: [
EitherIndex(2147483648),
],
regions: [
@5-6,
],
space_before: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
space_after: [
Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
],
spaces: [],
type_defs: [],
value_defs: [
Stmt(
@5-6 Var {
module_name: "",
ident: "q",
},
),
],
},
@8-10 SpaceBefore(
Var {
@6-14 SpaceBefore(
ParensAround(
Apply(
@6-7 Var {
module_name: "",
ident: "qt",
ident: "q",
},
[
Newline,
@12-14 SpaceBefore(
Var {
module_name: "",
ident: "qt",
},
[
Newline,
],
),
],
Space,
),
),
[
Newline,
],
),
@11-16 SpaceBefore(
@16-21 SpaceBefore(
Apply(
@11-12 Var {
@16-17 Var {
module_name: "",
ident: "g",
},
[
@14-16 SpaceBefore(
@19-21 SpaceBefore(
Var {
module_name: "",
ident: "qt",

View file

@ -1,5 +1,5 @@
dbg
q
qt
(q
qt)
g
qt

View file

@ -1512,6 +1512,7 @@ fn to_dbg_or_expect_report<'a>(
to_space_report(alloc, lines, filename, err, *pos)
}
roc_parse::parser::EExpect::DbgArity(_) => todo!(),
roc_parse::parser::EExpect::Dbg(_) => unreachable!("another branch would be taken"),
roc_parse::parser::EExpect::Expect(_) => unreachable!("another branch would be taken"),

View file

@ -2,7 +2,7 @@
- [builtins](/builtins) - docs for modules built into the language—`Str`, `Num`, etc.
- [basic-webserver](https://roc-lang.github.io/basic-webserver/) - a platform for making Web servers ([source code](https://github.com/roc-lang/basic-webserver))
- [basic-cli](/packages/basic-cli/0.15.0) - a platform for making command-line interfaces ([source code](https://github.com/roc-lang/basic-cli))
- [basic-cli](/packages/basic-cli/0.16.0) - a platform for making command-line interfaces ([source code](https://github.com/roc-lang/basic-cli))
- [plans](/plans) - current plans for future changes to the language
In the future, a language reference will be on this page too.