Merge pull request #5093 from roc-lang/glue-getters-rtfeldman

Glue for functions and closures
This commit is contained in:
Richard Feldman 2023-03-21 08:12:19 -04:00 committed by GitHub
commit 2276c78d9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 3864 additions and 1101 deletions

View file

@ -18,9 +18,9 @@ jobs:
run: | run: |
./ci/get_releases_json.sh ./ci/get_releases_json.sh
- run: curl -OL $(./ci/get_latest_release_url.sh linux_x86_64) - run: curl -OL $(./ci/get_latest_release_url.sh linuxTESTING_x86_64)
- run: curl -OL $(./ci/get_latest_release_url.sh macos_x86_64) - run: curl -OL $(./ci/get_latest_release_url.sh macosTESTING_x86_64)
- run: curl -OL $(./ci/get_latest_release_url.sh silicon) - run: curl -OL $(./ci/get_latest_release_url.sh macosTESTING_apple_silicon)
- name: Save roc_nightly archives - name: Save roc_nightly archives
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@ -39,15 +39,15 @@ jobs:
- name: build basic-cli with surgical linker and also with legacy linker - name: build basic-cli with surgical linker and also with legacy linker
env: env:
CARGO_BUILD_TARGET: x86_64-unknown-linux-musl CARGO_BUILD_TARGET: x86_64-unknown-linux-musl
run: ./ci/build_basic_cli.sh linux_x86_64 "--linker legacy" run: ./ci/build_basic_cli.sh linuxTESTING_x86_64 "--linker legacy"
- name: Save .rh1, .rm2 and .o file - name: Save .rh, .rm and .o file
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: linux-x86_64-files name: linux-x86_64-files
path: | path: |
basic-cli/src/metadata_linux-x86_64.rm2 basic-cli/src/metadata_linux-x86_64.rm
basic-cli/src/linux-x86_64.rh1 basic-cli/src/linux-x86_64.rh
basic-cli/src/linux-x86_64.o basic-cli/src/linux-x86_64.o
build-macos-x86_64-files: build-macos-x86_64-files:
@ -59,7 +59,7 @@ jobs:
- name: Download the previously uploaded roc_nightly archives - name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
- run: ./ci/build_basic_cli.sh macos_x86_64 - run: ./ci/build_basic_cli.sh macosTESTING_x86_64
- name: Save .o files - name: Save .o files
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@ -78,7 +78,7 @@ jobs:
- name: Download the previously uploaded roc_nightly archives - name: Download the previously uploaded roc_nightly archives
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
- run: ./ci/build_basic_cli.sh silicon - run: ./ci/build_basic_cli.sh macosTESTING_apple_silicon
- name: Save macos-arm64.o file - name: Save macos-arm64.o file
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
@ -120,13 +120,9 @@ jobs:
- run: cp macos-x86_64-files/* ./basic-cli/src - run: cp macos-x86_64-files/* ./basic-cli/src
# gz for quick test, br takes longer # change to tar.gz for fast test build
- run: | - run: |
if [ "$GITHUB_EVENT_NAME" == 'workflow_dispatch' ]; then
echo "ARCHIVE_FORMAT='.tar.br'" >> "$GITHUB_ENV" echo "ARCHIVE_FORMAT='.tar.br'" >> "$GITHUB_ENV"
else
echo "ARCHIVE_FORMAT='.tar.gz'" >> "$GITHUB_ENV"
fi
- run: ./roc_nightly/roc build --bundle=${{ env.ARCHIVE_FORMAT }} ./basic-cli/src/main.roc - run: ./roc_nightly/roc build --bundle=${{ env.ARCHIVE_FORMAT }} ./basic-cli/src/main.roc

1
Cargo.lock generated
View file

@ -3495,6 +3495,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_error_macros", "roc_error_macros",
"roc_load", "roc_load",
"roc_module",
"roc_mono", "roc_mono",
"roc_packaging", "roc_packaging",
"roc_reporting", "roc_reporting",

View file

@ -5,6 +5,10 @@ set -euxo pipefail
git clone https://github.com/roc-lang/basic-cli.git git clone https://github.com/roc-lang/basic-cli.git
cd basic-cli
git checkout new-release
cd ..
if [ "$(uname -m)" == "x86_64" ] && [ "$(uname -s)" == "Linux" ]; then if [ "$(uname -m)" == "x86_64" ] && [ "$(uname -s)" == "Linux" ]; then
sudo apt-get install musl-tools sudo apt-get install musl-tools
cd basic-cli/src # we cd to install the target for the right rust version cd basic-cli/src # we cd to install the target for the right rust version

View file

@ -474,6 +474,7 @@ mod cli_run {
} }
#[test] #[test]
#[serial(basic_cli_url)]
#[cfg_attr(windows, ignore)] #[cfg_attr(windows, ignore)]
fn hello_world() { fn hello_world() {
test_roc_app_slim( test_roc_app_slim(
@ -879,6 +880,7 @@ mod cli_run {
#[test] #[test]
#[serial(parser_package)] #[serial(parser_package)]
#[serial(basic_cli_url)]
#[cfg_attr(windows, ignore)] #[cfg_attr(windows, ignore)]
fn parse_letter_counts() { fn parse_letter_counts() {
test_roc_app_slim( test_roc_app_slim(

View file

@ -25,10 +25,10 @@ const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed_generic([*]u8) void; extern fn roc__mainForHost_1_exposed_generic([*]u8) void;
extern fn roc__mainForHost_size() i64; extern fn roc__mainForHost_1_exposed_size() i64;
extern fn roc__mainForHost_1__Fx_caller(*const u8, [*]u8, [*]u8) void; extern fn roc__mainForHost_0_caller(*const u8, [*]u8, [*]u8) void;
extern fn roc__mainForHost_1__Fx_size() i64; extern fn roc__mainForHost_0_size() i64;
extern fn roc__mainForHost_1__Fx_result_size() i64; extern fn roc__mainForHost_0_result_size() i64;
const Align = 2 * @alignOf(usize); const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
@ -123,7 +123,7 @@ pub fn main() !u8 {
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr // The size might be zero; if so, make it at least 8 so that we don't have a nullptr
const size = std.math.max(@intCast(usize, roc__mainForHost_size()), 8); const size = std.math.max(@intCast(usize, roc__mainForHost_1_exposed_size()), 8);
const raw_output = roc_alloc(@intCast(usize, size), @alignOf(u64)).?; const raw_output = roc_alloc(@intCast(usize, size), @alignOf(u64)).?;
var output = @ptrCast([*]u8, raw_output); var output = @ptrCast([*]u8, raw_output);
@ -155,7 +155,7 @@ fn call_the_closure(closure_data_pointer: [*]u8) void {
const allocator = std.heap.page_allocator; const allocator = std.heap.page_allocator;
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr // The size might be zero; if so, make it at least 8 so that we don't have a nullptr
const size = std.math.max(roc__mainForHost_1__Fx_result_size(), 8); const size = std.math.max(roc__mainForHost_0_result_size(), 8);
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable; const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
var output = @ptrCast([*]u8, raw_output); var output = @ptrCast([*]u8, raw_output);
@ -165,7 +165,7 @@ fn call_the_closure(closure_data_pointer: [*]u8) void {
const flags: u8 = 0; const flags: u8 = 0;
roc__mainForHost_1__Fx_caller(&flags, closure_data_pointer, output); roc__mainForHost_0_caller(&flags, closure_data_pointer, output);
// The closure returns result, nothing interesting to do with it // The closure returns result, nothing interesting to do with it
return; return;

View file

@ -196,7 +196,12 @@ fn gen_from_mono_module_llvm<'a>(
OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary, OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary,
}, },
exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(), exposed_to_host: loaded
.exposed_to_host
.top_level_values
.keys()
.copied()
.collect(),
}; };
// does not add any externs for this mode (we have a host) but cleans up some functions around // does not add any externs for this mode (we have a host) but cleans up some functions around
@ -224,6 +229,7 @@ fn gen_from_mono_module_llvm<'a>(
loaded.procedures, loaded.procedures,
entry_point, entry_point,
Some(&app_ll_file), Some(&app_ll_file),
&loaded.glue_layouts,
); );
env.dibuilder.finalize(); env.dibuilder.finalize();
@ -500,7 +506,7 @@ fn gen_from_mono_module_dev_wasm32<'a>(
let exposed_to_host = loaded let exposed_to_host = loaded
.exposed_to_host .exposed_to_host
.values .top_level_values
.keys() .keys()
.copied() .copied()
.collect::<MutSet<_>>(); .collect::<MutSet<_>>();
@ -571,7 +577,7 @@ fn gen_from_mono_module_dev_assembly<'a>(
let env = roc_gen_dev::Env { let env = roc_gen_dev::Env {
arena, arena,
module_id, module_id,
exposed_to_host: exposed_to_host.values.keys().copied().collect(), exposed_to_host: exposed_to_host.top_level_values.keys().copied().collect(),
lazy_literals, lazy_literals,
generate_allocators, generate_allocators,
}; };
@ -771,25 +777,10 @@ fn build_loaded_file<'a>(
// Also, we should no longer need to do this once we have platforms on // Also, we should no longer need to do this once we have platforms on
// a package repository, as we can then get prebuilt platforms from there. // a package repository, as we can then get prebuilt platforms from there.
let exposed_values = loaded let dll_stub_symbols = roc_linker::ExposedSymbols::from_exposed_to_host(
.exposed_to_host &loaded.interns,
.values &loaded.exposed_to_host,
.keys() );
.map(|x| x.as_str(&loaded.interns).to_string())
.collect();
let exposed_closure_types = loaded
.exposed_to_host
.closure_types
.iter()
.map(|x| {
format!(
"{}_{}",
x.module_string(&loaded.interns),
x.as_str(&loaded.interns)
)
})
.collect();
let join_handle = spawn_rebuild_thread( let join_handle = spawn_rebuild_thread(
code_gen_options.opt_level, code_gen_options.opt_level,
@ -798,8 +789,7 @@ fn build_loaded_file<'a>(
preprocessed_host_path.clone(), preprocessed_host_path.clone(),
output_exe_path.clone(), output_exe_path.clone(),
target, target,
exposed_values, dll_stub_symbols,
exposed_closure_types,
); );
Some(join_handle) Some(join_handle)
@ -993,6 +983,13 @@ fn invalid_prebuilt_platform(prebuilt_requested: bool, preprocessed_host_path: P
false => "", false => "",
}; };
let preprocessed_host_path_str = preprocessed_host_path.to_string_lossy();
let extra_err_msg = if preprocessed_host_path_str.ends_with(".rh") {
"\n\n\tNote: If the platform does have an .rh1 file but no .rh file, it's because it's been built with an older version of roc. Contact the author to release a new build of the platform using a roc release newer than March 21 2023.\n"
} else {
""
};
eprintln!( eprintln!(
indoc::indoc!( indoc::indoc!(
r#" r#"
@ -1000,13 +997,14 @@ fn invalid_prebuilt_platform(prebuilt_requested: bool, preprocessed_host_path: P
{} {}
However, it was not there! However, it was not there!{}
If you have the platform's source code locally, you may be able to generate it by re-running this command with --prebuilt-platform=false If you have the platform's source code locally, you may be able to generate it by re-running this command with --prebuilt-platform=false
"# "#
), ),
prefix, prefix,
preprocessed_host_path.to_string_lossy(), preprocessed_host_path.to_string_lossy(),
extra_err_msg
); );
} }
@ -1018,8 +1016,7 @@ fn spawn_rebuild_thread(
preprocessed_host_path: PathBuf, preprocessed_host_path: PathBuf,
output_exe_path: PathBuf, output_exe_path: PathBuf,
target: &Triple, target: &Triple,
exported_symbols: Vec<String>, dll_stub_symbols: Vec<String>,
exported_closure_types: Vec<String>,
) -> std::thread::JoinHandle<u128> { ) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone(); let thread_local_target = target.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
@ -1042,13 +1039,12 @@ fn spawn_rebuild_thread(
preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path); preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
} }
LinkingStrategy::Surgical => { LinkingStrategy::Surgical => {
build_and_preprocess_host( build_and_preprocess_host_lowlevel(
opt_level, opt_level,
&thread_local_target, &thread_local_target,
platform_main_roc.as_path(), platform_main_roc.as_path(),
preprocessed_host_path.as_path(), preprocessed_host_path.as_path(),
exported_symbols, &dll_stub_symbols,
exported_closure_types,
); );
// Copy preprocessed host to executable location. // Copy preprocessed host to executable location.
@ -1074,15 +1070,31 @@ pub fn build_and_preprocess_host(
target: &Triple, target: &Triple,
platform_main_roc: &Path, platform_main_roc: &Path,
preprocessed_host_path: &Path, preprocessed_host_path: &Path,
exposed_to_host: Vec<String>, exposed_symbols: roc_linker::ExposedSymbols,
exported_closure_types: Vec<String>,
) { ) {
let (stub_lib, stub_dll_symbols) = roc_linker::generate_stub_lib_from_loaded( let stub_dll_symbols = exposed_symbols.stub_dll_symbols();
build_and_preprocess_host_lowlevel(
opt_level,
target, target,
platform_main_roc, platform_main_roc,
exposed_to_host, preprocessed_host_path,
exported_closure_types, &stub_dll_symbols,
); )
}
fn build_and_preprocess_host_lowlevel(
opt_level: OptLevel,
target: &Triple,
platform_main_roc: &Path,
preprocessed_host_path: &Path,
stub_dll_symbols: &[String],
) {
let stub_lib =
roc_linker::generate_stub_lib_from_loaded(target, platform_main_roc, stub_dll_symbols);
debug_assert!(stub_lib.exists());
rebuild_host(opt_level, target, platform_main_roc, Some(&stub_lib)); rebuild_host(opt_level, target, platform_main_roc, Some(&stub_lib));
roc_linker::preprocess_host( roc_linker::preprocess_host(
@ -1090,7 +1102,7 @@ pub fn build_and_preprocess_host(
platform_main_roc, platform_main_roc,
preprocessed_host_path, preprocessed_host_path,
&stub_lib, &stub_lib,
&stub_dll_symbols, stub_dll_symbols,
) )
} }

View file

@ -39,7 +39,7 @@ use roc_debug_flags::dbg_do;
use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION; use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{ use roc_mono::ir::{
BranchInfo, CallType, CrashTag, EntryPoint, HostExposedLambdaSet, JoinPointId, BranchInfo, CallType, CrashTag, EntryPoint, GlueLayouts, HostExposedLambdaSet, JoinPointId,
ListLiteralElement, ModifyRc, OptLevel, ProcLayout, SingleEntryPoint, ListLiteralElement, ModifyRc, OptLevel, ProcLayout, SingleEntryPoint,
}; };
use roc_mono::layout::{ use roc_mono::layout::{
@ -3841,6 +3841,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
Some(env.context.i64_type().as_basic_type_enum()), Some(env.context.i64_type().as_basic_type_enum()),
&[], &[],
); );
let size_function_name: String = format!("roc__{}_size", ident_string); let size_function_name: String = format!("roc__{}_size", ident_string);
let size_function = add_func( let size_function = add_func(
@ -3972,13 +3973,17 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
&param_types[..param_types.len().saturating_sub(1)], &param_types[..param_types.len().saturating_sub(1)],
) )
} }
(RocReturn::Return | RocReturn::ByPointer, CCReturn::Void) => { (RocReturn::Return, CCReturn::Void) => {
// the roc function returns a unit value. like `{}` or `{ { {}, {} }, {} }`.
// In C, this is modelled as a function returning void
(&params[..], &param_types[..])
}
(RocReturn::ByPointer, CCReturn::Void) => {
// the roc function returns a unit value. like `{}` or `{ { {}, {} }, {} }`. // the roc function returns a unit value. like `{}` or `{ { {}, {} }, {} }`.
// In C, this is modelled as a function returning void // In C, this is modelled as a function returning void
( (
&params[..], &params[..],
// &param_types[..param_types.len().saturating_sub(1)], &param_types[..param_types.len().saturating_sub(1)],
&param_types[..],
) )
} }
_ => (&params[..], &param_types[..]), _ => (&params[..], &param_types[..]),
@ -4149,7 +4154,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
Some(env.context.i64_type().as_basic_type_enum()), Some(env.context.i64_type().as_basic_type_enum()),
&[], &[],
); );
let size_function_name: String = format!("roc__{}_size", ident_string); let size_function_name: String = format!("{}_size", c_function_name);
let size_function = add_func( let size_function = add_func(
env.context, env.context,
@ -4609,8 +4614,9 @@ pub fn build_procedures<'a, 'ctx, 'env>(
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
entry_point: EntryPoint<'a>, entry_point: EntryPoint<'a>,
debug_output_file: Option<&Path>, debug_output_file: Option<&Path>,
glue_layouts: &GlueLayouts<'a>,
) { ) {
build_procedures_help( let mod_solutions = build_procedures_help(
env, env,
layout_interner, layout_interner,
opt_level, opt_level,
@ -4618,6 +4624,43 @@ pub fn build_procedures<'a, 'ctx, 'env>(
entry_point, entry_point,
debug_output_file, debug_output_file,
); );
let niche = Niche::NONE;
for (symbol, top_level) in glue_layouts.getters.iter().copied() {
let it = top_level.arguments.iter().copied();
let bytes = roc_alias_analysis::func_name_bytes_help(symbol, it, niche, top_level.result);
let func_name = FuncName(&bytes);
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
let mut it = func_solutions.specs();
let Some(func_spec) = it.next() else {
// TODO this means a function was not considered host-exposed in mono
continue;
};
debug_assert!(
it.next().is_none(),
"we expect only one specialization of this symbol"
);
// NOTE fake layout; it is only used for debug prints
let getter_fn =
function_value_by_func_spec(env, *func_spec, symbol, &[], niche, Layout::UNIT);
let name = getter_fn.get_name().to_str().unwrap();
let getter_name = symbol.as_str(&env.interns);
// Add the getter function to the module.
let _ = expose_function_to_host_help_c_abi(
env,
layout_interner,
name,
getter_fn,
top_level.arguments,
top_level.result,
getter_name,
);
}
} }
pub fn build_wasm_test_wrapper<'a, 'ctx, 'env>( pub fn build_wasm_test_wrapper<'a, 'ctx, 'env>(
@ -4937,13 +4980,10 @@ fn expose_alias_to_host<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>, layout_interner: &mut STLayoutInterner<'a>,
mod_solutions: &'a ModSolutions, mod_solutions: &'a ModSolutions,
proc_name: LambdaName, fn_name: &str,
alias_symbol: Symbol, alias_symbol: Symbol,
hels: &HostExposedLambdaSet<'a>, hels: &HostExposedLambdaSet<'a>,
) { ) {
let ident_string = proc_name.name().as_str(&env.interns);
let fn_name: String = format!("{}_1", ident_string);
match hels.raw_function_layout { match hels.raw_function_layout {
RawFunctionLayout::Function(arguments, closure, result) => { RawFunctionLayout::Function(arguments, closure, result) => {
// define closure size and return value size, e.g. // define closure size and return value size, e.g.
@ -4989,7 +5029,7 @@ fn expose_alias_to_host<'a, 'ctx, 'env>(
build_closure_caller( build_closure_caller(
env, env,
layout_interner, layout_interner,
&fn_name, fn_name,
evaluator, evaluator,
alias_symbol, alias_symbol,
arguments, arguments,
@ -5008,7 +5048,7 @@ fn expose_alias_to_host<'a, 'ctx, 'env>(
build_host_exposed_alias_size_help( build_host_exposed_alias_size_help(
env, env,
&fn_name, fn_name,
alias_symbol, alias_symbol,
Some("result"), Some("result"),
result_type, result_type,
@ -5055,13 +5095,8 @@ fn build_closure_caller<'a, 'ctx, 'env>(
// STEP 1: build function header // STEP 1: build function header
// e.g. `roc__main_1_Fx_caller` // e.g. `roc__mainForHost_0_caller` (def_name is `mainForHost_0`)
let function_name = format!( let function_name = format!("roc__{}_caller", def_name);
"roc__{}_{}_{}_caller",
def_name,
alias_symbol.module_string(&env.interns),
alias_symbol.as_str(&env.interns)
);
let function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types); let function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types);
@ -5172,7 +5207,7 @@ fn build_host_exposed_alias_size<'a, 'r, 'ctx, 'env>(
fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>( fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
env: &'a Env<'a, 'ctx, 'env>, env: &'a Env<'a, 'ctx, 'env>,
def_name: &str, def_name: &str,
alias_symbol: Symbol, _alias_symbol: Symbol,
opt_label: Option<&str>, opt_label: Option<&str>,
basic_type: BasicTypeEnum<'ctx>, basic_type: BasicTypeEnum<'ctx>,
) { ) {
@ -5182,20 +5217,9 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
let i64 = env.context.i64_type().as_basic_type_enum(); let i64 = env.context.i64_type().as_basic_type_enum();
let size_function_spec = FunctionSpec::cconv(env, CCReturn::Return, Some(i64), &[]); let size_function_spec = FunctionSpec::cconv(env, CCReturn::Return, Some(i64), &[]);
let size_function_name: String = if let Some(label) = opt_label { let size_function_name: String = if let Some(label) = opt_label {
format!( format!("roc__{}_{}_size", def_name, label)
"roc__{}_{}_{}_{}_size",
def_name,
alias_symbol.module_string(&env.interns),
alias_symbol.as_str(&env.interns),
label
)
} else { } else {
format!( format!("roc__{}_size", def_name,)
"roc__{}_{}_{}_size",
def_name,
alias_symbol.module_string(&env.interns),
alias_symbol.as_str(&env.interns)
)
}; };
let size_function = add_func( let size_function = add_func(
@ -5214,7 +5238,7 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
builder.build_return(Some(&size)); builder.build_return(Some(&size));
} }
pub fn build_proc<'a, 'ctx, 'env>( fn build_proc<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>, layout_interner: &mut STLayoutInterner<'a>,
mod_solutions: &'a ModSolutions, mod_solutions: &'a ModSolutions,
@ -5237,11 +5261,14 @@ pub fn build_proc<'a, 'ctx, 'env>(
} }
Binary | BinaryDev => { Binary | BinaryDev => {
for (alias_name, hels) in aliases.iter() { for (alias_name, hels) in aliases.iter() {
let ident_string = proc.name.name().as_str(&env.interns);
let fn_name: String = format!("{}_{}", ident_string, hels.id.0);
expose_alias_to_host( expose_alias_to_host(
env, env,
layout_interner, layout_interner,
mod_solutions, mod_solutions,
proc.name, &fn_name,
*alias_name, *alias_name,
hels, hels,
) )
@ -6106,7 +6133,7 @@ pub fn add_func<'ctx>(
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
if let Some(func) = module.get_function(name) { if let Some(func) = module.get_function(name) {
panic!("Attempting to redefine LLVM function {}, which was already defined in this module as:\n\n{:?}", name, func); panic!("Attempting to redefine LLVM function {}, which was already defined in this module as:\n\n{:#?}", name, func);
} }
} }

View file

@ -18,8 +18,8 @@ const SKIP_SUBS_CACHE: bool = {
pub use roc_load_internal::docs; pub use roc_load_internal::docs;
pub use roc_load_internal::file::{ pub use roc_load_internal::file::{
EntryPoint, ExecutionMode, ExpectMetadata, Expectations, LoadConfig, LoadResult, LoadStart, EntryPoint, ExecutionMode, ExpectMetadata, Expectations, ExposedToHost, LoadConfig, LoadResult,
LoadedModule, LoadingProblem, MonomorphizedModule, Phase, Threading, LoadStart, LoadedModule, LoadingProblem, MonomorphizedModule, Phase, Threading,
}; };
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]

View file

@ -1,7 +1,7 @@
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
use crate::docs::ModuleDocumentation; use crate::docs::ModuleDocumentation;
use bumpalo::Bump; use bumpalo::{collections::CollectIn, Bump};
use crossbeam::channel::{bounded, Sender}; use crossbeam::channel::{bounded, Sender};
use crossbeam::deque::{Injector, Stealer, Worker}; use crossbeam::deque::{Injector, Stealer, Worker};
use crossbeam::thread; use crossbeam::thread;
@ -31,9 +31,10 @@ use roc_module::symbol::{
PackageQualified, Symbol, PackageQualified, Symbol,
}; };
use roc_mono::ir::{ use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, ProcsBase, CapturedSymbols, ExternalSpecializations, GlueLayouts, LambdaSetId, PartialProc, Proc,
UpdateModeIds, ProcLayout, Procs, ProcsBase, UpdateModeIds,
}; };
use roc_mono::layout::LayoutInterner;
use roc_mono::layout::{ use roc_mono::layout::{
GlobalLayoutInterner, LambdaName, Layout, LayoutCache, LayoutProblem, Niche, STLayoutInterner, GlobalLayoutInterner, LambdaName, Layout, LayoutCache, LayoutProblem, Niche, STLayoutInterner,
}; };
@ -116,11 +117,11 @@ pub enum ExecutionMode {
impl ExecutionMode { impl ExecutionMode {
fn goal_phase(&self) -> Phase { fn goal_phase(&self) -> Phase {
use ExecutionMode::*;
match self { match self {
ExecutionMode::Executable => Phase::MakeSpecializations, Executable => Phase::MakeSpecializations,
ExecutionMode::Check | ExecutionMode::ExecutableIfCheck | ExecutionMode::Test => { Check | ExecutableIfCheck | Test => Phase::SolveTypes,
Phase::SolveTypes
}
} }
} }
@ -765,6 +766,7 @@ pub struct MonomorphizedModule<'a> {
pub timings: MutMap<ModuleId, ModuleTiming>, pub timings: MutMap<ModuleId, ModuleTiming>,
pub expectations: VecMap<ModuleId, Expectations>, pub expectations: VecMap<ModuleId, Expectations>,
pub uses_prebuilt_platform: bool, pub uses_prebuilt_platform: bool,
pub glue_layouts: GlueLayouts<'a>,
} }
/// Values used to render expect output /// Values used to render expect output
@ -795,9 +797,12 @@ pub struct Expectations {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct ExposedToHost { pub struct ExposedToHost {
/// usually `mainForHost` /// usually `mainForHost`
pub values: MutMap<Symbol, Variable>, pub top_level_values: MutMap<Symbol, Variable>,
/// exposed closure types, typically `Fx` /// exposed closure types, typically `Fx`
pub closure_types: Vec<Symbol>, pub closure_types: Vec<Symbol>,
/// lambda_sets
pub lambda_sets: Vec<(Symbol, LambdaSetId)>,
pub getters: Vec<Symbol>,
} }
impl<'a> MonomorphizedModule<'a> { impl<'a> MonomorphizedModule<'a> {
@ -2775,7 +2780,7 @@ fn update<'a>(
!matches!(state.exec_mode, ExecutionMode::Test); !matches!(state.exec_mode, ExecutionMode::Test);
if add_to_host_exposed { if add_to_host_exposed {
state.exposed_to_host.values.extend( state.exposed_to_host.top_level_values.extend(
solved_module solved_module
.exposed_vars_by_symbol .exposed_vars_by_symbol
.iter() .iter()
@ -3102,7 +3107,7 @@ fn update<'a>(
debug_print_ir!(state, &layout_interner, ROC_PRINT_IR_AFTER_RESET_REUSE); debug_print_ir!(state, &layout_interner, ROC_PRINT_IR_AFTER_RESET_REUSE);
let host_exposed_procs = bumpalo::collections::Vec::from_iter_in( let host_exposed_procs = bumpalo::collections::Vec::from_iter_in(
state.exposed_to_host.values.keys().copied(), state.exposed_to_host.top_level_values.keys().copied(),
arena, arena,
); );
@ -3272,8 +3277,8 @@ fn finish_specialization<'a>(
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
subs: Subs, subs: Subs,
layout_interner: STLayoutInterner<'a>, mut layout_interner: STLayoutInterner<'a>,
exposed_to_host: ExposedToHost, mut exposed_to_host: ExposedToHost,
module_expectations: VecMap<ModuleId, Expectations>, module_expectations: VecMap<ModuleId, Expectations>,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> { ) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
if false { if false {
@ -3303,38 +3308,16 @@ fn finish_specialization<'a>(
all_ident_ids, all_ident_ids,
}; };
let State {
toplevel_expects,
procedures,
module_cache,
output_path,
platform_path,
platform_data,
exec_mode,
..
} = state;
let ModuleCache {
type_problems,
can_problems,
sources,
..
} = module_cache;
let sources: MutMap<ModuleId, (PathBuf, Box<str>)> = sources
.into_iter()
.map(|(id, (path, src))| (id, (path, src.into())))
.collect();
let entry_point = { let entry_point = {
match exec_mode { let interns: &mut Interns = &mut interns;
ExecutionMode::Test => EntryPoint::Test, match state.exec_mode {
ExecutionMode::Test => Ok(EntryPoint::Test),
ExecutionMode::Executable | ExecutionMode::ExecutableIfCheck => { ExecutionMode::Executable | ExecutionMode::ExecutableIfCheck => {
use PlatformPath::*; use PlatformPath::*;
let platform_path = match platform_path { let platform_path = match &state.platform_path {
Valid(To::ExistingPackage(shorthand)) => { Valid(To::ExistingPackage(shorthand)) => {
match (*state.arc_shorthands).lock().get(shorthand) { match state.arc_shorthands.lock().get(shorthand) {
Some(shorthand_path) => shorthand_path.root_module().to_path_buf(), Some(shorthand_path) => shorthand_path.root_module().to_path_buf(),
None => unreachable!(), None => unreachable!(),
} }
@ -3346,13 +3329,14 @@ fn finish_specialization<'a>(
} }
}; };
let exposed_symbols_and_layouts = match platform_data { let exposed_symbols_and_layouts = match state.platform_data {
None => { None => {
let src = &exposed_to_host.values; let src = &state.exposed_to_host.top_level_values;
let mut buf = bumpalo::collections::Vec::with_capacity_in(src.len(), arena); let mut buf = bumpalo::collections::Vec::with_capacity_in(src.len(), arena);
for &symbol in src.keys() { for &symbol in src.keys() {
let proc_layout = proc_layout_for(procedures.keys().copied(), symbol); let proc_layout =
proc_layout_for(state.procedures.keys().copied(), symbol);
buf.push((symbol, proc_layout)); buf.push((symbol, proc_layout));
} }
@ -3371,7 +3355,8 @@ fn finish_specialization<'a>(
for (loc_name, _loc_typed_ident) in provides { for (loc_name, _loc_typed_ident) in provides {
let ident_id = ident_ids.get_or_insert(loc_name.value.as_str()); let ident_id = ident_ids.get_or_insert(loc_name.value.as_str());
let symbol = Symbol::new(module_id, ident_id); let symbol = Symbol::new(module_id, ident_id);
let proc_layout = proc_layout_for(procedures.keys().copied(), symbol); let proc_layout =
proc_layout_for(state.procedures.keys().copied(), symbol);
buf.push((symbol, proc_layout)); buf.push((symbol, proc_layout));
} }
@ -3380,14 +3365,84 @@ fn finish_specialization<'a>(
} }
}; };
EntryPoint::Executable { Ok(EntryPoint::Executable {
exposed_to_host: exposed_symbols_and_layouts, exposed_to_host: exposed_symbols_and_layouts,
platform_path, platform_path,
} })
} }
ExecutionMode::Check => unreachable!(), ExecutionMode::Check => unreachable!(),
} }
}; }?;
let State {
toplevel_expects,
procedures,
module_cache,
output_path,
platform_data,
..
} = state;
let ModuleCache {
type_problems,
can_problems,
sources,
..
} = module_cache;
let sources: MutMap<ModuleId, (PathBuf, Box<str>)> = sources
.into_iter()
.map(|(id, (path, src))| (id, (path, src.into())))
.collect();
let module_id = state.root_id;
let mut glue_getters = Vec::new();
// the REPL does not have any platform data
if let (
EntryPoint::Executable {
exposed_to_host: exposed_top_levels,
..
},
Some(platform_data),
) = (&entry_point, platform_data.as_ref())
{
// Expose glue for the platform, not for the app module!
let module_id = platform_data.module_id;
for (_name, proc_layout) in exposed_top_levels.iter() {
let ret = &proc_layout.result;
for in_layout in proc_layout.arguments.iter().chain([ret]) {
let layout = layout_interner.get(*in_layout);
let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap();
let all_glue_procs = roc_mono::ir::generate_glue_procs(
module_id,
ident_ids,
arena,
&mut layout_interner,
arena.alloc(layout),
);
let lambda_set_names = all_glue_procs
.extern_names
.iter()
.map(|(lambda_set_id, _)| (*_name, *lambda_set_id));
exposed_to_host.lambda_sets.extend(lambda_set_names);
let getter_names = all_glue_procs
.getters
.iter()
.flat_map(|(_, glue_procs)| glue_procs.iter().map(|glue_proc| glue_proc.name));
exposed_to_host.getters.extend(getter_names);
glue_getters.extend(all_glue_procs.getters.iter().flat_map(|(_, glue_procs)| {
glue_procs
.iter()
.map(|glue_proc| (glue_proc.name, glue_proc.proc_layout))
}));
}
}
}
let output_path = match output_path { let output_path = match output_path {
Some(path_str) => Path::new(path_str).into(), Some(path_str) => Path::new(path_str).into(),
@ -3407,7 +3462,7 @@ fn finish_specialization<'a>(
output_path, output_path,
expectations: module_expectations, expectations: module_expectations,
exposed_to_host, exposed_to_host,
module_id: state.root_id, module_id,
subs, subs,
interns, interns,
layout_interner, layout_interner,
@ -3416,6 +3471,9 @@ fn finish_specialization<'a>(
sources, sources,
timings: state.timings, timings: state.timings,
toplevel_expects, toplevel_expects,
glue_layouts: GlueLayouts {
getters: glue_getters,
},
uses_prebuilt_platform, uses_prebuilt_platform,
}) })
} }
@ -3438,6 +3496,7 @@ fn proc_layout_for<'a>(
} }
} }
#[allow(clippy::too_many_arguments)]
fn finish( fn finish(
mut state: State, mut state: State,
solved: Solved<Subs>, solved: Solved<Subs>,
@ -5637,10 +5696,14 @@ fn make_specializations<'a>(
let mut procs = Procs::new_in(arena); let mut procs = Procs::new_in(arena);
let host_exposed_symbols: bumpalo::collections::Vec<_> =
procs_base.get_host_exposed_symbols().collect_in(arena);
for (symbol, partial_proc) in procs_base.partial_procs.into_iter() { for (symbol, partial_proc) in procs_base.partial_procs.into_iter() {
procs.partial_procs.insert(symbol, partial_proc); procs.partial_procs.insert(symbol, partial_proc);
} }
procs.host_exposed_symbols = host_exposed_symbols.into_bump_slice();
procs.module_thunks = procs_base.module_thunks; procs.module_thunks = procs_base.module_thunks;
procs.runtime_errors = procs_base.runtime_errors; procs.runtime_errors = procs_base.runtime_errors;
procs.imported_module_thunks = procs_base.imported_module_thunks; procs.imported_module_thunks = procs_base.imported_module_thunks;
@ -5740,7 +5803,7 @@ fn build_pending_specializations<'a>(
let symbol = declarations.symbols[index].value; let symbol = declarations.symbols[index].value;
let expr_var = declarations.variables[index]; let expr_var = declarations.variables[index];
let is_host_exposed = exposed_to_host.values.contains_key(&symbol); let is_host_exposed = exposed_to_host.top_level_values.contains_key(&symbol);
// TODO remove clones (with drain) // TODO remove clones (with drain)
let annotation = declarations.annotations[index].clone(); let annotation = declarations.annotations[index].clone();
@ -6647,7 +6710,7 @@ fn to_parse_problem_report<'a>(
buf buf
} }
fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> String { fn to_missing_platform_report(module_id: ModuleId, other: &PlatformPath) -> String {
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE}; use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator; use ven_pretty::DocAllocator;
use PlatformPath::*; use PlatformPath::*;

View file

@ -944,6 +944,16 @@ pub struct ProcsBase<'a> {
pub imported_module_thunks: &'a [Symbol], pub imported_module_thunks: &'a [Symbol],
} }
impl<'a> ProcsBase<'a> {
pub fn get_host_exposed_symbols(&self) -> impl Iterator<Item = Symbol> + '_ {
self.host_specializations
.symbol_or_lambdas
.iter()
.copied()
.map(|n| n.name())
}
}
/// The current set of functions under specialization. They form a stack where the latest /// The current set of functions under specialization. They form a stack where the latest
/// specialization to be seen is at the head of the stack. /// specialization to be seen is at the head of the stack.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -962,14 +972,16 @@ impl<'a> SpecializationStack<'a> {
pub struct Procs<'a> { pub struct Procs<'a> {
pub partial_procs: PartialProcs<'a>, pub partial_procs: PartialProcs<'a>,
ability_member_aliases: AbilityAliases, ability_member_aliases: AbilityAliases,
pub imported_module_thunks: &'a [Symbol],
pub module_thunks: &'a [Symbol],
pending_specializations: PendingSpecializations<'a>, pending_specializations: PendingSpecializations<'a>,
specialized: Specialized<'a>, specialized: Specialized<'a>,
pub runtime_errors: BumpMap<Symbol, &'a str>, pub runtime_errors: BumpMap<Symbol, &'a str>,
pub externals_we_need: BumpMap<ModuleId, ExternalSpecializations<'a>>, pub externals_we_need: BumpMap<ModuleId, ExternalSpecializations<'a>>,
symbol_specializations: SymbolSpecializations<'a>, symbol_specializations: SymbolSpecializations<'a>,
specialization_stack: SpecializationStack<'a>, specialization_stack: SpecializationStack<'a>,
pub imported_module_thunks: &'a [Symbol],
pub module_thunks: &'a [Symbol],
pub host_exposed_symbols: &'a [Symbol],
} }
impl<'a> Procs<'a> { impl<'a> Procs<'a> {
@ -977,14 +989,16 @@ impl<'a> Procs<'a> {
Self { Self {
partial_procs: PartialProcs::new_in(arena), partial_procs: PartialProcs::new_in(arena),
ability_member_aliases: AbilityAliases::new_in(arena), ability_member_aliases: AbilityAliases::new_in(arena),
imported_module_thunks: &[],
module_thunks: &[],
pending_specializations: PendingSpecializations::Finding(Suspended::new_in(arena)), pending_specializations: PendingSpecializations::Finding(Suspended::new_in(arena)),
specialized: Specialized::default(), specialized: Specialized::default(),
runtime_errors: BumpMap::new_in(arena), runtime_errors: BumpMap::new_in(arena),
externals_we_need: BumpMap::new_in(arena), externals_we_need: BumpMap::new_in(arena),
symbol_specializations: Default::default(), symbol_specializations: Default::default(),
specialization_stack: SpecializationStack(Vec::with_capacity_in(16, arena)), specialization_stack: SpecializationStack(Vec::with_capacity_in(16, arena)),
imported_module_thunks: &[],
module_thunks: &[],
host_exposed_symbols: &[],
} }
} }
@ -3002,15 +3016,8 @@ fn specialize_host_specializations<'a>(
let offset_variable = StorageSubs::merge_into(store, env.subs); let offset_variable = StorageSubs::merge_into(store, env.subs);
for (symbol, variable, host_exposed_aliases) in it { for (symbol, variable, _host_exposed_aliases) in it {
specialize_external_help( specialize_external_help(env, procs, layout_cache, symbol, offset_variable(variable))
env,
procs,
layout_cache,
symbol,
offset_variable(variable),
&host_exposed_aliases,
)
} }
} }
@ -3035,7 +3042,7 @@ fn specialize_external_specializations<'a>(
// duplicate specializations, and the insertion into a hash map // duplicate specializations, and the insertion into a hash map
// below will deduplicate them. // below will deduplicate them.
specialize_external_help(env, procs, layout_cache, symbol, imported_variable, &[]) specialize_external_help(env, procs, layout_cache, symbol, imported_variable)
} }
} }
} }
@ -3046,7 +3053,6 @@ fn specialize_external_help<'a>(
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
name: LambdaName<'a>, name: LambdaName<'a>,
variable: Variable, variable: Variable,
host_exposed_aliases: &[(Symbol, Variable)],
) { ) {
let partial_proc_id = match procs.partial_procs.symbol_to_id(name.name()) { let partial_proc_id = match procs.partial_procs.symbol_to_id(name.name()) {
Some(v) => v, Some(v) => v,
@ -3066,7 +3072,7 @@ fn specialize_external_help<'a>(
debug_assert!(top_level.arguments.is_empty()); debug_assert!(top_level.arguments.is_empty());
} }
if !host_exposed_aliases.is_empty() { if procs.host_exposed_symbols.contains(&proc.name.name()) {
// layouts that are (transitively) used in the type of `mainForHost`. // layouts that are (transitively) used in the type of `mainForHost`.
let mut host_exposed_layouts: Vec<_> = top_level let mut host_exposed_layouts: Vec<_> = top_level
.arguments .arguments
@ -3079,7 +3085,6 @@ fn specialize_external_help<'a>(
host_exposed_layouts.sort(); host_exposed_layouts.sort();
host_exposed_layouts.dedup(); host_exposed_layouts.dedup();
// TODO: In the future, we will generate glue procs here
for in_layout in host_exposed_layouts { for in_layout in host_exposed_layouts {
let layout = layout_cache.interner.get(in_layout); let layout = layout_cache.interner.get(in_layout);
@ -3093,16 +3098,27 @@ fn specialize_external_help<'a>(
// for now, getters are not processed here // for now, getters are not processed here
let GlueProcs { let GlueProcs {
getters: _, getters,
extern_names, extern_names,
} = all_glue_procs; } = all_glue_procs;
for (_layout, glue_procs) in getters {
for glue_proc in glue_procs {
procs.specialized.insert_specialized(
glue_proc.proc.name.name(),
glue_proc.proc_layout,
glue_proc.proc,
);
}
}
let mut aliases = BumpMap::default(); let mut aliases = BumpMap::default();
for (id, mut raw_function_layout) in extern_names { for (id, mut raw_function_layout) in extern_names {
let symbol = env.unique_symbol(); let symbol = env.unique_symbol();
let lambda_name = LambdaName::no_niche(symbol); let lambda_name = LambdaName::no_niche(symbol);
// fix the recursion in the rocLovesRust example
if false { if false {
raw_function_layout = match raw_function_layout { raw_function_layout = match raw_function_layout {
RawFunctionLayout::Function(a, mut lambda_set, _) => { RawFunctionLayout::Function(a, mut lambda_set, _) => {
@ -3137,37 +3153,6 @@ fn specialize_external_help<'a>(
aliases.insert(key, hels); aliases.insert(key, hels);
} }
// pre-glue: generate named callers for as-exposed aliases
for (alias_name, variable) in host_exposed_aliases {
let raw_function_layout = layout_cache
.raw_from_var(env.arena, *variable, env.subs)
.unwrap();
let symbol = env.unique_symbol();
let lambda_name = LambdaName::no_niche(symbol);
let (proc_name, (proc_layout, proc)) = generate_host_exposed_function(
env,
procs,
layout_cache,
lambda_name,
raw_function_layout,
);
procs
.specialized
.insert_specialized(proc_name, proc_layout, proc);
let hels = HostExposedLambdaSet {
id: LambdaSetId::default(),
symbol: proc_name,
proc_layout,
raw_function_layout,
};
aliases.insert(*alias_name, hels);
}
match &mut proc.host_exposed_layouts { match &mut proc.host_exposed_layouts {
HostExposedLayouts::HostExposed { aliases: old, .. } => old.extend(aliases), HostExposedLayouts::HostExposed { aliases: old, .. } => old.extend(aliases),
hep @ HostExposedLayouts::NotHostExposed => { hep @ HostExposedLayouts::NotHostExposed => {
@ -11178,12 +11163,14 @@ where
} }
} }
#[derive(Debug, Default)]
pub struct GlueLayouts<'a> { pub struct GlueLayouts<'a> {
pub getters: std::vec::Vec<(Symbol, ProcLayout<'a>)>, pub getters: std::vec::Vec<(Symbol, ProcLayout<'a>)>,
} }
type GlueProcId = u16; type GlueProcId = u16;
#[derive(Debug)]
pub struct GlueProc<'a> { pub struct GlueProc<'a> {
pub name: Symbol, pub name: Symbol,
pub proc_layout: ProcLayout<'a>, pub proc_layout: ProcLayout<'a>,
@ -11204,10 +11191,6 @@ impl LambdaSetId {
debug_assert!(self.0 < u32::MAX); debug_assert!(self.0 < u32::MAX);
Self(self.0 + 1) Self(self.0 + 1)
} }
pub const fn invalid() -> Self {
Self(u32::MAX)
}
} }
pub fn generate_glue_procs<'a, 'i, I>( pub fn generate_glue_procs<'a, 'i, I>(
@ -11230,34 +11213,8 @@ where
let mut stack: Vec<'a, Layout<'a>> = Vec::from_iter_in([*layout], arena); let mut stack: Vec<'a, Layout<'a>> = Vec::from_iter_in([*layout], arena);
let mut next_unique_id = 0; let mut next_unique_id = 0;
macro_rules! handle_struct_field_layouts {
($field_layouts: expr) => {{
if $field_layouts.iter().any(|l| {
layout_interner
.get(*l)
.has_varying_stack_size(layout_interner, arena)
}) {
let procs = generate_glue_procs_for_struct_fields(
layout_interner,
home,
&mut next_unique_id,
ident_ids,
arena,
layout,
$field_layouts,
);
answer.getters.push((*layout, procs));
}
for in_layout in $field_layouts.iter().rev() {
stack.push(layout_interner.get(*in_layout));
}
}};
}
macro_rules! handle_tag_field_layouts { macro_rules! handle_tag_field_layouts {
($tag_id:expr, $union_layout:expr, $field_layouts: expr) => {{ ($tag_id:expr, $layout:expr, $union_layout:expr, $field_layouts: expr) => {{
if $field_layouts.iter().any(|l| { if $field_layouts.iter().any(|l| {
layout_interner layout_interner
.get(*l) .get(*l)
@ -11270,12 +11227,12 @@ where
ident_ids, ident_ids,
arena, arena,
$tag_id, $tag_id,
layout, &$layout,
$union_layout, $union_layout,
$field_layouts, $field_layouts,
); );
answer.getters.push((*layout, procs)); answer.getters.push(($layout, procs));
} }
for in_layout in $field_layouts.iter().rev() { for in_layout in $field_layouts.iter().rev() {
@ -11295,7 +11252,27 @@ where
Builtin::List(element) => stack.push(layout_interner.get(element)), Builtin::List(element) => stack.push(layout_interner.get(element)),
}, },
Layout::Struct { field_layouts, .. } => { Layout::Struct { field_layouts, .. } => {
handle_struct_field_layouts!(field_layouts); if field_layouts.iter().any(|l| {
layout_interner
.get(*l)
.has_varying_stack_size(layout_interner, arena)
}) {
let procs = generate_glue_procs_for_struct_fields(
layout_interner,
home,
&mut next_unique_id,
ident_ids,
arena,
&layout,
field_layouts,
);
answer.getters.push((layout, procs));
}
for in_layout in field_layouts.iter().rev() {
stack.push(layout_interner.get(*in_layout));
}
} }
Layout::Boxed(boxed) => { Layout::Boxed(boxed) => {
stack.push(layout_interner.get(boxed)); stack.push(layout_interner.get(boxed));
@ -11312,7 +11289,7 @@ where
} }
} }
UnionLayout::NonNullableUnwrapped(field_layouts) => { UnionLayout::NonNullableUnwrapped(field_layouts) => {
handle_tag_field_layouts!(0, union_layout, field_layouts); handle_tag_field_layouts!(0, layout, union_layout, field_layouts);
} }
UnionLayout::NullableWrapped { UnionLayout::NullableWrapped {
other_tags, other_tags,
@ -11321,7 +11298,7 @@ where
let tag_ids = let tag_ids =
(0..nullable_id).chain(nullable_id + 1..other_tags.len() as u16 + 1); (0..nullable_id).chain(nullable_id + 1..other_tags.len() as u16 + 1);
for (i, field_layouts) in tag_ids.zip(other_tags) { for (i, field_layouts) in tag_ids.zip(other_tags) {
handle_tag_field_layouts!(i, union_layout, *field_layouts); handle_tag_field_layouts!(i, layout, union_layout, *field_layouts);
} }
} }
UnionLayout::NullableUnwrapped { other_fields, .. } => { UnionLayout::NullableUnwrapped { other_fields, .. } => {
@ -11357,8 +11334,8 @@ fn generate_glue_procs_for_struct_fields<'a, 'i, I>(
next_unique_id: &mut GlueProcId, next_unique_id: &mut GlueProcId,
ident_ids: &mut IdentIds, ident_ids: &mut IdentIds,
arena: &'a Bump, arena: &'a Bump,
unboxed_struct_layout: &'a Layout<'a>, unboxed_struct_layout: &Layout<'a>,
field_layouts: &'a [InLayout<'a>], field_layouts: &[InLayout<'a>],
) -> Vec<'a, GlueProc<'a>> ) -> Vec<'a, GlueProc<'a>>
where where
I: LayoutInterner<'a>, I: LayoutInterner<'a>,
@ -11368,6 +11345,17 @@ where
let boxed_struct_layout = layout_interner.insert(boxed_struct_layout); let boxed_struct_layout = layout_interner.insert(boxed_struct_layout);
let mut answer = bumpalo::collections::Vec::with_capacity_in(field_layouts.len(), arena); let mut answer = bumpalo::collections::Vec::with_capacity_in(field_layouts.len(), arena);
let field_layouts = match layout_interner.get(interned_unboxed_struct_layout) {
Layout::Struct { field_layouts, .. } => field_layouts,
other => {
unreachable!(
"{:?} {:?}",
layout_interner.dbg(interned_unboxed_struct_layout),
other
)
}
};
for (index, field) in field_layouts.iter().enumerate() { for (index, field) in field_layouts.iter().enumerate() {
let proc_layout = ProcLayout { let proc_layout = ProcLayout {
arguments: arena.alloc([boxed_struct_layout]), arguments: arena.alloc([boxed_struct_layout]),
@ -11443,7 +11431,8 @@ fn unique_glue_symbol(
let _result = write!(&mut string, "roc__getter_{}_{}", module_name, unique_id); let _result = write!(&mut string, "roc__getter_{}_{}", module_name, unique_id);
debug_assert_eq!(_result, Ok(())); // This should never fail, but doesn't hurt to debug-check! debug_assert_eq!(_result, Ok(())); // This should never fail, but doesn't hurt to debug-check!
let ident_id = ident_ids.get_or_insert(string.into_bump_str()); let bump_string = string.into_bump_str();
let ident_id = ident_ids.get_or_insert(bump_string);
Symbol::new(home, ident_id) Symbol::new(home, ident_id)
} }
@ -11456,7 +11445,7 @@ fn generate_glue_procs_for_tag_fields<'a, 'i, I>(
ident_ids: &mut IdentIds, ident_ids: &mut IdentIds,
arena: &'a Bump, arena: &'a Bump,
tag_id: TagIdIntType, tag_id: TagIdIntType,
unboxed_struct_layout: &'a Layout<'a>, unboxed_struct_layout: &Layout<'a>,
union_layout: UnionLayout<'a>, union_layout: UnionLayout<'a>,
field_layouts: &'a [InLayout<'a>], field_layouts: &'a [InLayout<'a>],
) -> Vec<'a, GlueProc<'a>> ) -> Vec<'a, GlueProc<'a>>

View file

@ -2862,6 +2862,18 @@ impl<'a> Layout<'a> {
Dec => Layout::DEC, Dec => Layout::DEC,
} }
} }
pub fn is_recursive_tag_union(self) -> bool {
matches!(
self,
Layout::Union(
UnionLayout::NullableUnwrapped { .. }
| UnionLayout::Recursive(_)
| UnionLayout::NullableWrapped { .. }
| UnionLayout::NonNullableUnwrapped { .. },
)
)
}
} }
impl<'a> Builtin<'a> { impl<'a> Builtin<'a> {
@ -3597,18 +3609,6 @@ fn get_recursion_var(subs: &Subs, var: Variable) -> Option<Variable> {
} }
} }
fn is_recursive_tag_union(layout: &Layout) -> bool {
matches!(
layout,
Layout::Union(
UnionLayout::NullableUnwrapped { .. }
| UnionLayout::Recursive(_)
| UnionLayout::NullableWrapped { .. }
| UnionLayout::NonNullableUnwrapped { .. },
)
)
}
fn union_sorted_non_recursive_tags_help<'a, L>( fn union_sorted_non_recursive_tags_help<'a, L>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
tags_list: &[(&'_ L, &[Variable])], tags_list: &[(&'_ L, &[Variable])],
@ -3901,7 +3901,7 @@ where
== env == env
.subs .subs
.get_root_key_without_compacting(opt_rec_var.unwrap()) .get_root_key_without_compacting(opt_rec_var.unwrap())
&& is_recursive_tag_union(&layout); && layout.is_recursive_tag_union();
let arg_layout = if self_recursion { let arg_layout = if self_recursion {
Layout::NAKED_RECURSIVE_PTR Layout::NAKED_RECURSIVE_PTR

View file

@ -103,7 +103,7 @@ pub fn helper(
// println!("=================================\n"); // println!("=================================\n");
} }
debug_assert_eq!(exposed_to_host.values.len(), 1); debug_assert_eq!(exposed_to_host.top_level_values.len(), 1);
let entry_point = match loaded.entry_point { let entry_point = match loaded.entry_point {
EntryPoint::Executable { EntryPoint::Executable {
exposed_to_host, exposed_to_host,
@ -188,7 +188,7 @@ pub fn helper(
let env = roc_gen_dev::Env { let env = roc_gen_dev::Env {
arena, arena,
module_id, module_id,
exposed_to_host: exposed_to_host.values.keys().copied().collect(), exposed_to_host: exposed_to_host.top_level_values.keys().copied().collect(),
lazy_literals, lazy_literals,
generate_allocators: true, // Needed for testing, since we don't have a platform generate_allocators: true, // Needed for testing, since we don't have a platform
}; };

View file

@ -114,10 +114,10 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>(
.. ..
} = loaded; } = loaded;
debug_assert_eq!(exposed_to_host.values.len(), 1); debug_assert_eq!(exposed_to_host.top_level_values.len(), 1);
let exposed_to_host = exposed_to_host let exposed_to_host = exposed_to_host
.values .top_level_values
.keys() .keys()
.copied() .copied()
.collect::<MutSet<_>>(); .collect::<MutSet<_>>();

View file

@ -2,6 +2,10 @@ procedure Test.1 (Test.4):
inc Test.4; inc Test.4;
ret Test.4; ret Test.4;
procedure Test.21 (Test.23, #Attr.12):
let Test.22 : Str = CallByName Test.5 Test.23 #Attr.12;
ret Test.22;
procedure Test.5 (Test.12, Test.4): procedure Test.5 (Test.12, Test.4):
dec Test.4; dec Test.4;
let Test.14 : Str = ""; let Test.14 : Str = "";

View file

@ -6,6 +6,22 @@ procedure Test.1 (Test.2, Test.3):
let Test.21 : {U16, {}} = Struct {Test.2, Test.3}; let Test.21 : {U16, {}} = Struct {Test.2, Test.3};
ret Test.21; ret Test.21;
procedure Test.30 (Test.31):
let Test.32 : {U8, {}} = Unbox Test.31;
dec Test.31;
let Test.33 : U8 = StructAtIndex 0 Test.32;
ret Test.33;
procedure Test.34 (Test.35):
let Test.36 : {U8, {}} = Unbox Test.35;
dec Test.35;
let Test.37 : {} = StructAtIndex 1 Test.36;
ret Test.37;
procedure Test.38 (Test.40, #Attr.12):
let Test.39 : Str = CallByName Test.4 Test.40 #Attr.12;
ret Test.39;
procedure Test.4 (Test.13, #Attr.12): procedure Test.4 (Test.13, #Attr.12):
let Test.3 : {} = StructAtIndex 1 #Attr.12; let Test.3 : {} = StructAtIndex 1 #Attr.12;
let Test.2 : U16 = StructAtIndex 0 #Attr.12; let Test.2 : U16 = StructAtIndex 0 #Attr.12;
@ -22,6 +38,10 @@ procedure Test.4 (Test.13, #Attr.12):
let Test.15 : Str = CallByName Test.4 Test.16 Test.5; let Test.15 : Str = CallByName Test.4 Test.16 Test.5;
ret Test.15; ret Test.15;
procedure Test.41 (Test.43, #Attr.12):
let Test.42 : {U16, {}} = CallByName Test.6 Test.43;
ret Test.42;
procedure Test.6 (Test.17): procedure Test.6 (Test.17):
let Test.19 : U16 = 1i64; let Test.19 : U16 = 1i64;
let Test.20 : {} = Struct {}; let Test.20 : {} = Struct {};

View file

@ -6,6 +6,22 @@ procedure Test.1 (Test.2, Test.3):
let Test.21 : {U8, {}} = Struct {Test.2, Test.3}; let Test.21 : {U8, {}} = Struct {Test.2, Test.3};
ret Test.21; ret Test.21;
procedure Test.30 (Test.31):
let Test.32 : {U8, {}} = Unbox Test.31;
dec Test.31;
let Test.33 : U8 = StructAtIndex 0 Test.32;
ret Test.33;
procedure Test.34 (Test.35):
let Test.36 : {U8, {}} = Unbox Test.35;
dec Test.35;
let Test.37 : {} = StructAtIndex 1 Test.36;
ret Test.37;
procedure Test.38 (Test.40, #Attr.12):
let Test.39 : Str = CallByName Test.4 Test.40 #Attr.12;
ret Test.39;
procedure Test.4 (Test.13, #Attr.12): procedure Test.4 (Test.13, #Attr.12):
let Test.3 : {} = StructAtIndex 1 #Attr.12; let Test.3 : {} = StructAtIndex 1 #Attr.12;
let Test.2 : U8 = StructAtIndex 0 #Attr.12; let Test.2 : U8 = StructAtIndex 0 #Attr.12;
@ -22,6 +38,10 @@ procedure Test.4 (Test.13, #Attr.12):
let Test.24 : Str = CallByName Test.8 Test.25; let Test.24 : Str = CallByName Test.8 Test.25;
ret Test.24; ret Test.24;
procedure Test.41 (Test.43, #Attr.12):
let Test.42 : {U8, {}} = CallByName Test.6 Test.43;
ret Test.42;
procedure Test.6 (Test.17): procedure Test.6 (Test.17):
let Test.19 : U8 = 1i64; let Test.19 : U8 = 1i64;
let Test.20 : {} = Struct {}; let Test.20 : {} = Struct {};

View file

@ -148,7 +148,7 @@ fn compiles_to_ir(test_name: &str, src: &str, mode: &str, no_check: bool) {
assert!(type_problems.is_empty()); assert!(type_problems.is_empty());
let main_fn_symbol = exposed_to_host.values.keys().copied().next(); let main_fn_symbol = exposed_to_host.top_level_values.keys().copied().next();
if !no_check { if !no_check {
check_procedures(arena, &interns, &mut layout_interner, &procedures); check_procedures(arena, &interns, &mut layout_interner, &procedures);
@ -2553,6 +2553,7 @@ fn recursively_build_effect() {
} }
#[mono_test] #[mono_test]
#[ignore = "roc glue code generation cannot handle a type that this test generates"]
fn recursive_lambda_set_has_nested_non_recursive_lambda_sets_issue_5026() { fn recursive_lambda_set_has_nested_non_recursive_lambda_sets_issue_5026() {
indoc!( indoc!(
r#" r#"

View file

@ -1,11 +1,14 @@
use crate::rust_glue; use crate::rust_glue;
use crate::types::{Env, Types}; use crate::types::Types;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::MutMap;
use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading}; use roc_load::{ExecutionMode, LoadConfig, LoadedModule, LoadingProblem, Threading};
use roc_mono::layout::GlobalLayoutInterner; use roc_mono::ir::{generate_glue_procs, GlueProc};
use roc_mono::layout::{GlobalLayoutInterner, LayoutCache, LayoutInterner};
use roc_packaging::cache::{self, RocCacheDir}; use roc_packaging::cache::{self, RocCacheDir};
use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE}; use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
use roc_target::{Architecture, OperatingSystem, TargetInfo}; use roc_target::{Architecture, TargetInfo};
use roc_types::subs::{Subs, Variable};
use std::fs::File; use std::fs::File;
use std::io::{self, ErrorKind, Write}; use std::io::{self, ErrorKind, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -77,6 +80,120 @@ pub fn generate(input_path: &Path, output_path: &Path) -> io::Result<i32> {
} }
} }
fn number_lambda_sets(subs: &Subs, initial: Variable) -> Vec<Variable> {
let mut lambda_sets = vec![];
let mut stack = vec![initial];
macro_rules! var_slice {
($variable_subs_slice:expr) => {{
let slice = $variable_subs_slice;
subs.variables[slice.indices()].iter().rev()
}};
}
while let Some(var) = stack.pop() {
use roc_types::subs::Content::*;
use roc_types::subs::FlatType::*;
use roc_types::subs::GetSubsSlice;
use roc_types::types::Uls;
match subs.get_content_without_compacting(var) {
RigidVar(_) | RigidAbleVar(_, _) | FlexVar(_) | FlexAbleVar(_, _) | Error => (),
RecursionVar { .. } => {
// we got here, so we've treated this type already
}
Structure(flat_type) => match flat_type {
Apply(_, args) => {
stack.extend(var_slice!(*args));
}
Func(arg_vars, closure_var, ret_var) => {
lambda_sets.push(subs.get_root_key_without_compacting(*closure_var));
stack.push(*ret_var);
stack.push(*closure_var);
stack.extend(var_slice!(arg_vars));
}
EmptyRecord => (),
EmptyTagUnion => (),
EmptyTuple => (),
Record(fields, ext) => {
let fields = *fields;
let ext = *ext;
stack.push(ext);
stack.extend(var_slice!(fields.variables()));
}
Tuple(_, _) => todo!(),
TagUnion(tags, ext) => {
let tags = *tags;
let ext = *ext;
stack.push(ext.var());
for slice_index in tags.variables() {
let slice = subs.variable_slices[slice_index.index as usize];
stack.extend(var_slice!(slice));
}
}
FunctionOrTagUnion(_, _, ext) => {
stack.push(ext.var());
}
RecursiveTagUnion(rec_var, tags, ext) => {
let tags = *tags;
let ext = *ext;
let rec_var = *rec_var;
stack.push(ext.var());
for slice_index in tags.variables() {
let slice = subs.variable_slices[slice_index.index as usize];
stack.extend(var_slice!(slice));
}
stack.push(rec_var);
}
},
Alias(_, args, var, _) => {
let var = *var;
let args = *args;
stack.extend(var_slice!(args.all_variables()));
stack.push(var);
}
LambdaSet(roc_types::subs::LambdaSet {
solved,
recursion_var,
unspecialized,
ambient_function: _,
}) => {
for slice_index in solved.variables() {
let slice = subs.variable_slices[slice_index.index as usize];
stack.extend(var_slice!(slice));
}
if let Some(rec_var) = recursion_var.into_variable() {
stack.push(rec_var);
}
for Uls(var, _, _) in subs.get_subs_slice(*unspecialized) {
stack.push(*var);
}
}
&RangedNumber(_) => {}
}
}
lambda_sets
}
pub fn load_types( pub fn load_types(
full_file_path: PathBuf, full_file_path: PathBuf,
threading: Threading, threading: Threading,
@ -91,6 +208,7 @@ pub fn load_types(
mut declarations_by_id, mut declarations_by_id,
mut solved, mut solved,
interns, interns,
exposed_to_host,
.. ..
} = roc_load::load_and_typecheck( } = roc_load::load_and_typecheck(
arena, arena,
@ -129,42 +247,90 @@ pub fn load_types(
); );
} }
// Get the variables for all the exposed_to_host symbols
let variables = (0..decls.len()).filter_map(|index| { let variables = (0..decls.len()).filter_map(|index| {
use roc_can::expr::DeclarationTag::*; if exposed_to_host.contains_key(&decls.symbols[index].value) {
Some(decls.variables[index])
match decls.declarations[index] { } else {
Value | Function(_) | Recursive(_) | TailRecursive(_) => Some(decls.variables[index]),
Destructure(_) => {
// figure out if we need to export non-identifier defs - when would that
// happen?
None None
} }
MutualRecursion { .. } => {
// handled by future iterations
None
}
Expectation | ExpectationFx => {
// not publicly visible
None
}
}
}); });
let layout_interner = GlobalLayoutInterner::with_capacity(128, target_info); let operating_system = target_info.operating_system;
let architectures = Architecture::iter(); let architectures = Architecture::iter();
let mut types_and_targets = Vec::with_capacity(architectures.len()); let mut types_and_targets = Vec::with_capacity(architectures.len());
for arch in architectures {
let layout_interner = GlobalLayoutInterner::with_capacity(128, target_info);
for architecture in architectures {
let mut interns = interns.clone(); // TODO there may be a way to avoid this.
let target_info = TargetInfo { let target_info = TargetInfo {
architecture: arch, architecture,
operating_system: OperatingSystem::Unix, operating_system,
}; };
let mut layout_cache = LayoutCache::new(layout_interner.fork(), target_info);
let mut glue_procs_by_layout = MutMap::default();
let types = { let mut extern_names = MutMap::default();
let mut env = Env::new(arena, subs, &interns, layout_interner.fork(), target_info);
env.vars_to_types(variables.clone()) // Populate glue getters/setters for all relevant variables
}; for var in variables.clone() {
for (i, v) in number_lambda_sets(subs, var).iter().enumerate() {
extern_names.insert(*v, i.to_string());
}
let in_layout = layout_cache
.from_var(arena, var, subs)
.expect("Something weird ended up in the content");
let layout = layout_cache.interner.get(in_layout);
// dbg!(layout);
if layout.has_varying_stack_size(&layout_cache.interner, arena) {
let ident_ids = interns.all_ident_ids.get_mut(&home).unwrap();
let answer = generate_glue_procs(
home,
ident_ids,
arena,
&mut layout_interner.fork(),
arena.alloc(layout),
);
// Even though generate_glue_procs does more work than we need it to,
// it's important that we use it in order to make sure we get exactly
// the same names that mono::ir did for code gen!
for (layout, glue_procs) in answer.getters {
let mut names =
bumpalo::collections::Vec::with_capacity_in(glue_procs.len(), arena);
// Record all the getter/setter names associated with this layout
for GlueProc { name, .. } in glue_procs {
// Given a struct layout (including lambda sets!) the offsets - and therefore
// getters/setters - are deterministic, so we can use layout as the hash key
// for these getters/setters. We also only need to store the name because
// since they are getters and setters, we can know their types (from a
// TypeId perspective) deterministically based on knowing the types of
// the structs and fields.
//
// Store them as strings, because symbols won't be useful to glue generators!
names.push(name.as_str(&interns).to_string());
}
glue_procs_by_layout.insert(layout, names.into_bump_slice());
}
}
}
let types = Types::new(
arena,
subs,
variables.clone(),
arena.alloc(interns),
glue_procs_by_layout,
layout_cache,
target_info,
);
types_and_targets.push((types, target_info)); types_and_targets.push((types, target_info));
} }

View file

@ -1,4 +1,7 @@
use crate::types::{RocNum, RocTagUnion, RocType, TypeId, Types}; use crate::types::{
Accessors, RocFn, RocNum, RocSingleTagPayload, RocStructFields, RocTagUnion, RocType, TypeId,
Types,
};
use indexmap::IndexMap; use indexmap::IndexMap;
use roc_target::{Architecture, TargetInfo}; use roc_target::{Architecture, TargetInfo};
use std::fmt::{Display, Write}; use std::fmt::{Display, Write};
@ -248,16 +251,9 @@ fn add_type(target_info: TargetInfo, id: TypeId, types: &Types, impls: &mut Impl
RocTagUnion::SingleTagStruct { RocTagUnion::SingleTagStruct {
name, name,
tag_name, tag_name,
payload_fields, payload,
} => { } => {
add_single_tag_struct( add_single_tag_struct(name, tag_name, payload, types, impls, target_info);
name,
tag_name,
payload_fields,
types,
impls,
target_info,
);
} }
RocTagUnion::NonNullableUnwrapped { RocTagUnion::NonNullableUnwrapped {
name, name,
@ -289,21 +285,20 @@ fn add_type(target_info: TargetInfo, id: TypeId, types: &Types, impls: &mut Impl
| RocType::RocDict(_, _) | RocType::RocDict(_, _)
| RocType::RocSet(_) | RocType::RocSet(_)
| RocType::RocList(_) | RocType::RocList(_)
| RocType::RocBox(_) => {} | RocType::RocBox(_)
| RocType::Unsized => {}
RocType::RecursivePointer { .. } => { RocType::RecursivePointer { .. } => {
// This is recursively pointing to a type that should already have been added, // This is recursively pointing to a type that should already have been added,
// so no extra work needs to happen. // so no extra work needs to happen.
} }
RocType::Function { .. } => { RocType::Function(roc_fn) => add_function(target_info, roc_fn, types, impls),
// TODO actually generate glue functions!
}
} }
} }
fn add_single_tag_struct( fn add_single_tag_struct(
name: &str, name: &str,
tag_name: &str, tag_name: &str,
payload_fields: &[TypeId], payload: &RocSingleTagPayload,
types: &Types, types: &Types,
impls: &mut IndexMap<Option<String>, IndexMap<String, Vec<TargetInfo>>>, impls: &mut IndexMap<Option<String>, IndexMap<String, Vec<TargetInfo>>>,
target_info: TargetInfo, target_info: TargetInfo,
@ -314,45 +309,93 @@ fn add_single_tag_struct(
// because they have only one alternative. However, still // because they have only one alternative. However, still
// offer the usual tag union APIs. // offer the usual tag union APIs.
{ {
let derive = derive_str( let mut buf = String::new();
&RocType::Struct {
// Make a dummy RocType::Struct so that we can pass it to deriving
// and have that work out as normal.
let struct_type = match payload {
RocSingleTagPayload::HasClosure { payload_getters } => {
{
if payload_getters.is_empty() {
// A single tag with no payload is a zero-sized unit type, so
// represent it as a zero-sized struct (e.g. "struct Foo()").
buf.push_str("();\n");
} else {
buf.push_str("{\n");
for (_index, _getter_fn) in payload_getters.iter().enumerate() {
// TODO these should be added as separate functions in the impl!
todo!("TODO generate payload getters");
}
buf.push_str("}\n");
}
let fields = payload_getters
.iter()
.map(|(type_id, getter)| {
(
String::new(),
*type_id,
Accessors {
getter: getter.clone(),
},
)
})
.collect();
RocType::Struct {
// Deriving doesn't depend on the struct's name, // Deriving doesn't depend on the struct's name,
// so no need to clone name here. // so no need to clone name here.
name: String::new(), name: String::new(),
fields: payload_fields fields: RocStructFields::HasClosure { fields },
.iter() }
.map(|type_id| (String::new(), *type_id)) }
.collect(), }
}, RocSingleTagPayload::HasNoClosure {
types, payload_fields: payloads,
false, } => {
); if payloads.is_empty() {
let mut body = format!("#[repr(transparent)]\n{derive}\npub struct {name} ");
if payload_fields.is_empty() {
// A single tag with no payload is a zero-sized unit type, so // A single tag with no payload is a zero-sized unit type, so
// represent it as a zero-sized struct (e.g. "struct Foo()"). // represent it as a zero-sized struct (e.g. "struct Foo()").
body.push_str("();\n"); buf.push_str("();\n");
} else { } else {
body.push_str("{\n"); buf.push_str("{\n");
for (index, field_id) in payload_fields.iter().enumerate() { for (index, field_id) in payloads.iter().enumerate() {
let field_type = type_name(*field_id, types); let field_type = type_name(*field_id, types);
// These are all private fields, since this is a tag union. // These are all private fields, since this is a tag union.
// ignore returned result, writeln can not fail as it is used here // ignore returned result, writeln can not fail as it is used here
let _ = writeln!(body, "{INDENT}f{index}: {field_type},"); let _ = writeln!(buf, "{INDENT}f{index}: {field_type},");
} }
body.push_str("}\n"); buf.push_str("}\n");
} }
let fields = payloads
.iter()
.map(|type_id| (String::new(), *type_id))
.collect();
RocType::Struct {
// Deriving doesn't depend on the struct's name,
// so no need to clone name here.
name: String::new(),
fields: RocStructFields::HasNoClosure { fields },
}
}
};
let derive = derive_str(&struct_type, types, false);
let body = format!("#[repr(transparent)]\n{derive}\npub struct {name} {buf}");
add_decl(impls, None, target_info, body); add_decl(impls, None, target_info, body);
} }
// the impl for the single-tag union itself // the impl for the single-tag union itself
{ match payload {
RocSingleTagPayload::HasNoClosure { payload_fields } => {
let opt_impl = Some(format!("impl {name}")); let opt_impl = Some(format!("impl {name}"));
if payload_fields.is_empty() { if payload_fields.is_empty() {
@ -502,13 +545,16 @@ fn add_single_tag_struct(
} }
} }
} }
RocSingleTagPayload::HasClosure { payload_getters: _ } => todo!(),
}
// The Debug impl for the single-tag union // The Debug impl for the single-tag union
{ match payload {
RocSingleTagPayload::HasNoClosure { payload_fields } => {
let opt_impl = Some(format!("impl core::fmt::Debug for {name}")); let opt_impl = Some(format!("impl core::fmt::Debug for {name}"));
let mut buf = let mut buf = "fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {"
"fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {".to_string(); .to_string();
if payload_fields.is_empty() { if payload_fields.is_empty() {
// ignore returned result, write can not fail as it is used here // ignore returned result, write can not fail as it is used here
@ -533,6 +579,8 @@ fn add_single_tag_struct(
add_decl(impls, opt_impl, target_info, buf); add_decl(impls, opt_impl, target_info, buf);
} }
RocSingleTagPayload::HasClosure { payload_getters: _ } => todo!(),
}
} }
fn add_discriminant( fn add_discriminant(
@ -958,9 +1006,11 @@ pub struct {name} {{
"arg".to_string() "arg".to_string()
}; };
} }
RocType::Struct { fields, name } => { RocType::Struct {
let answer = fields: RocStructFields::HasNoClosure { fields },
tag_union_struct_help(name, fields.iter(), *payload_id, types, false); name,
} => {
let answer = tag_union_struct_help(name, fields, *payload_id, types, false);
owned_ret = answer.owned_ret; owned_ret = answer.owned_ret;
borrowed_ret = answer.borrowed_ret; borrowed_ret = answer.borrowed_ret;
@ -969,9 +1019,11 @@ pub struct {name} {{
payload_args = answer.payload_args; payload_args = answer.payload_args;
args_to_payload = answer.args_to_payload; args_to_payload = answer.args_to_payload;
} }
RocType::TagUnionPayload { fields, name } => { RocType::TagUnionPayload {
let answer = fields: RocStructFields::HasNoClosure { fields },
tag_union_struct_help(name, fields.iter(), *payload_id, types, true); name,
} => {
let answer = tag_union_struct_help(name, fields, *payload_id, types, true);
owned_ret = answer.owned_ret; owned_ret = answer.owned_ret;
borrowed_ret = answer.borrowed_ret; borrowed_ret = answer.borrowed_ret;
@ -980,7 +1032,102 @@ pub struct {name} {{
payload_args = answer.payload_args; payload_args = answer.payload_args;
args_to_payload = answer.args_to_payload; args_to_payload = answer.args_to_payload;
} }
RocType::Function { .. } => todo!(), RocType::Struct {
fields: RocStructFields::HasClosure { fields: _ },
name: _,
} => {
todo!("struct");
}
RocType::TagUnionPayload {
fields: RocStructFields::HasClosure { fields },
name: _, // TODO call this payload_struct_name and use it to define the struct...or don't define it at all, maybe, since there are only getters and setters?
} => {
// TODO don't generate op.into_StdoutWrite() - only getters/setters instead!
for (field_name, field, accessor) in fields {
let getter_name = &accessor.getter;
let ret = type_name(*field, types);
let returns_via_pointer = true;
let body = if let RocType::Function(_) = types.get_type(*field) {
format!(
r#"
extern "C" {{
#[link_name = "{getter_name}_size"]
fn size() -> usize;
#[link_name = "{getter_name}_generic"]
fn getter(_: *mut u8, _: *const {name});
}}
// allocate memory to store this variably-sized value
// allocates with roc_alloc, but that likely still uses the heap
let it = std::iter::repeat(0xAAu8).take(size());
let mut bytes = roc_std::RocList::from_iter(it);
getter(bytes.as_mut_ptr(), self);
{ret} {{
closure_data: bytes,
}}
"#
)
} else if returns_via_pointer {
format!(
r#"
extern "C" {{
#[link_name = "{getter_name}_generic"]
fn getter(_: *mut {ret}, _: *const {name});
}}
let mut ret = core::mem::MaybeUninit::uninit();
getter(ret.as_mut_ptr(), self);
ret.assume_init()
"#
)
} else {
format!(
r#"
extern "C" {{
#[link_name = "{getter_name}"]
fn getter(_: *const {name}) -> {ret};
}}
getter(self)
"#
)
};
add_decl(
impls,
opt_impl.clone(),
target_info,
format!(
r#"/// Unsafely assume this `{name}` has a `.discriminant()` of `{tag_name}` and return its payload at index {field_name}.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `{tag_name}`.
pub unsafe fn get_{tag_name}_{field_name}(&self) -> {ret} {{
debug_assert_eq!(self.discriminant(), {discriminant_name}::{tag_name});
{body}
}}"#,
),
);
}
// TODO revise these - they're all copy/pasted from somewhere else
owned_ret_type = type_name(*payload_id, types);
borrowed_ret_type = format!("&{}", owned_ret_type);
owned_ret = "payload".to_string();
borrowed_ret = format!("&{owned_ret}");
payload_args = format!("arg: {owned_ret_type}");
args_to_payload = if cannot_derive_copy(payload_type, types) {
"core::mem::ManuallyDrop::new(arg)".to_string()
} else {
"arg".to_string()
};
}
RocType::Unsized => todo!(),
RocType::Function(RocFn { .. }) => todo!(),
}; };
{ {
@ -1047,7 +1194,7 @@ pub struct {name} {{
) )
} else { } else {
format!( format!(
r#"/// Unsafely assume the given `{name}` has a `.discriminant()` of `{tag_name}` and convert it to `{tag_name}`'s payload. r#"/// Unsafely assume this `{name}` has a `.discriminant()` of `{tag_name}` and convert it to `{tag_name}`'s payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!) /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `{tag_name}`. /// Panics in debug builds if the `.discriminant()` doesn't return `{tag_name}`.
pub unsafe fn into_{tag_name}({self_for_into}) -> {owned_ret_type} {{ pub unsafe fn into_{tag_name}({self_for_into}) -> {owned_ret_type} {{
@ -1078,7 +1225,7 @@ pub struct {name} {{
) )
} else { } else {
format!( format!(
r#"/// Unsafely assume the given `{name}` has a `.discriminant()` of `{tag_name}` and return its payload. r#"/// Unsafely assume this `{name}` has a `.discriminant()` of `{tag_name}` and return its payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!) /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `{tag_name}`. /// Panics in debug builds if the `.discriminant()` doesn't return `{tag_name}`.
pub unsafe fn as_{tag_name}(&self) -> {borrowed_ret_type} {{ pub unsafe fn as_{tag_name}(&self) -> {borrowed_ret_type} {{
@ -1579,16 +1726,24 @@ pub struct {name} {{
RocType::TagUnionPayload { fields, .. } => { RocType::TagUnionPayload { fields, .. } => {
let mut buf = Vec::new(); let mut buf = Vec::new();
match fields {
RocStructFields::HasNoClosure { fields } => {
for (label, _) in fields { for (label, _) in fields {
// Needs an "f" prefix // Needs an "f" prefix
buf.push(format!( buf.push(format!(
".field(&({deref_str}{actual_self}.{tag_name}).f{label})" ".field(&({deref_str}{actual_self}.{tag_name}).f{label})"
)); ));
} }
}
RocStructFields::HasClosure { fields: _ } => {
buf.push("// TODO HAS CLOSURE".to_string());
}
}
buf.join("\n") buf.join("\n")
} }
RocType::Function { .. } => todo!(), RocType::Unsized => todo!(),
RocType::Function(RocFn { .. }) => todo!(),
}; };
format!( format!(
@ -1681,10 +1836,104 @@ fn add_enumeration<I: ExactSizeIterator<Item = S>, S: AsRef<str> + Display>(
add_decl(impls, None, target_info, buf); add_decl(impls, None, target_info, buf);
} }
fn add_struct<S: Display>( fn add_function(
// name: &str,
target_info: TargetInfo,
roc_fn: &RocFn,
types: &Types,
impls: &mut Impls,
) {
let name = escape_kw(roc_fn.function_name.to_string());
// let derive = derive_str(types.get_type(struct_id), types, true);
let derive = "";
let pub_str = "pub ";
let mut buf;
let repr = "C";
buf = format!("{derive}\n#[repr({repr})]\n{pub_str}struct {name} {{\n");
let fields = [("closure_data", &roc_fn.lambda_set)];
for (label, type_id) in fields {
let type_str = type_name(*type_id, types);
// Tag union payloads have numbered fields, so we prefix them
// with an "f" because Rust doesn't allow struct fields to be numbers.
let label = escape_kw(label.to_string());
writeln!(buf, "{INDENT}pub {label}: {type_str},",).unwrap();
}
buf.push('}');
buf.push('\n');
buf.push('\n');
let extern_name = &roc_fn.extern_name;
let return_type_str = type_name(roc_fn.ret, types);
writeln!(buf, "impl {name} {{").unwrap();
write!(buf, "{INDENT}pub fn force_thunk(mut self").unwrap();
for (i, argument_type) in roc_fn.args.iter().enumerate() {
write!(buf, ", arg_{i}: {}", type_name(*argument_type, types)).unwrap();
}
writeln!(buf, ") -> {return_type_str} {{").unwrap();
writeln!(buf, "{INDENT}{INDENT}extern \"C\" {{").unwrap();
// fn extern_name(output: *mut return_type, arg1: arg1_type, ..., closure_data: *mut u8);
write!(buf, "{INDENT}{INDENT}{INDENT} fn {extern_name}(").unwrap();
for (i, argument_type) in roc_fn.args.iter().enumerate() {
write!(buf, "arg_{i}: &{}, ", type_name(*argument_type, types)).unwrap();
}
writeln!(
buf,
"closure_data: *mut u8, output: *mut {return_type_str});"
)
.unwrap();
// {argument_types} "
writeln!(buf, "{INDENT}{INDENT}}}").unwrap();
writeln!(buf).unwrap();
writeln!(
buf,
"{INDENT}{INDENT}let mut output = std::mem::MaybeUninit::uninit();"
)
.unwrap();
writeln!(
buf,
"{INDENT}{INDENT}let ptr = self.closure_data.as_mut_ptr();"
)
.unwrap();
write!(buf, "{INDENT}{INDENT}unsafe {{ {extern_name}(").unwrap();
for (i, _) in roc_fn.args.iter().enumerate() {
write!(buf, "&arg_{i}, ").unwrap();
}
writeln!(buf, "ptr, output.as_mut_ptr()) }};").unwrap();
writeln!(buf, "{INDENT}{INDENT}unsafe {{ output.assume_init() }}").unwrap();
writeln!(buf, "{INDENT}}}").unwrap();
buf.push('}');
add_decl(impls, None, target_info, buf);
}
fn add_struct(
name: &str, name: &str,
target_info: TargetInfo, target_info: TargetInfo,
fields: &[(S, TypeId)], fields: &RocStructFields,
struct_id: TypeId, struct_id: TypeId,
types: &Types, types: &Types,
impls: &mut Impls, impls: &mut Impls,
@ -1693,12 +1942,17 @@ fn add_struct<S: Display>(
let name = escape_kw(name.to_string()); let name = escape_kw(name.to_string());
let derive = derive_str(types.get_type(struct_id), types, true); let derive = derive_str(types.get_type(struct_id), types, true);
let pub_str = if is_tag_union_payload { "" } else { "pub " }; let pub_str = if is_tag_union_payload { "" } else { "pub " };
let mut buf;
match fields {
RocStructFields::HasNoClosure { fields } => {
let repr = if fields.len() == 1 { let repr = if fields.len() == 1 {
"transparent" "transparent"
} else { } else {
"C" "C"
}; };
let mut buf = format!("{derive}\n#[repr({repr})]\n{pub_str}struct {name} {{\n");
buf = format!("{derive}\n#[repr({repr})]\n{pub_str}struct {name} {{\n");
for (label, type_id) in fields { for (label, type_id) in fields {
let type_str = type_name(*type_id, types); let type_str = type_name(*type_id, types);
@ -1715,6 +1969,11 @@ fn add_struct<S: Display>(
} }
buf.push('}'); buf.push('}');
}
RocStructFields::HasClosure { fields: _ } => {
buf = "//TODO HAS CLOSURE 2".to_string();
}
}
add_decl(impls, None, target_info, buf); add_decl(impls, None, target_info, buf);
} }
@ -1746,6 +2005,7 @@ fn type_name(id: TypeId, types: &Types) -> String {
RocType::RocSet(elem_id) => format!("roc_std::RocSet<{}>", type_name(*elem_id, types)), RocType::RocSet(elem_id) => format!("roc_std::RocSet<{}>", type_name(*elem_id, types)),
RocType::RocList(elem_id) => format!("roc_std::RocList<{}>", type_name(*elem_id, types)), RocType::RocList(elem_id) => format!("roc_std::RocList<{}>", type_name(*elem_id, types)),
RocType::RocBox(elem_id) => format!("roc_std::RocBox<{}>", type_name(*elem_id, types)), RocType::RocBox(elem_id) => format!("roc_std::RocBox<{}>", type_name(*elem_id, types)),
RocType::Unsized => "roc_std::RocList<u8>".to_string(),
RocType::RocResult(ok_id, err_id) => { RocType::RocResult(ok_id, err_id) => {
format!( format!(
"roc_std::RocResult<{}, {}>", "roc_std::RocResult<{}, {}>",
@ -1763,7 +2023,7 @@ fn type_name(id: TypeId, types: &Types) -> String {
| RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { name, .. }) | RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { name, .. })
| RocType::TagUnion(RocTagUnion::SingleTagStruct { name, .. }) => escape_kw(name.clone()), | RocType::TagUnion(RocTagUnion::SingleTagStruct { name, .. }) => escape_kw(name.clone()),
RocType::RecursivePointer(content) => type_name(*content, types), RocType::RecursivePointer(content) => type_name(*content, types),
RocType::Function { name, .. } => escape_kw(name.clone()), RocType::Function(RocFn { function_name, .. }) => escape_kw(function_name.clone()),
} }
} }
@ -1771,27 +2031,136 @@ fn type_name(id: TypeId, types: &Types) -> String {
/// case of a struct that's a payload for a recursive tag union, typ.has_enumeration() /// case of a struct that's a payload for a recursive tag union, typ.has_enumeration()
/// will return true, but actually we want to derive Debug here anyway. /// will return true, but actually we want to derive Debug here anyway.
fn derive_str(typ: &RocType, types: &Types, include_debug: bool) -> String { fn derive_str(typ: &RocType, types: &Types, include_debug: bool) -> String {
let mut buf = "#[derive(Clone, ".to_string(); let mut derives = vec!["Clone"];
if !cannot_derive_copy(typ, types) { if !cannot_derive_copy(typ, types) {
buf.push_str("Copy, "); derives.push("Copy");
} }
if include_debug { if include_debug {
buf.push_str("Debug, "); derives.push("Debug");
}
if !has_functions(typ, types) {
derives.push("PartialEq");
derives.push("PartialOrd");
if !has_float(typ, types) {
derives.push("Eq");
derives.push("Ord");
derives.push("Hash");
} }
if !cannot_derive_default(typ, types) { if !cannot_derive_default(typ, types) {
buf.push_str("Default, "); derives.push("Default");
}
} }
if !has_float(typ, types) { derives.sort();
buf.push_str("Eq, Ord, Hash, ");
format!("#[derive({})]", derives.join(", "))
} }
buf.push_str("PartialEq, PartialOrd)]"); fn has_functions(start: &RocType, types: &Types) -> bool {
let mut seen: Vec<TypeId> = vec![];
let mut stack = vec![start];
buf macro_rules! push {
($id:expr) => {{
if !seen.contains($id) {
seen.push(*$id);
stack.push(types.get_type(*$id));
}
}};
}
while let Some(typ) = stack.pop() {
match typ {
RocType::RocStr
| RocType::Bool
| RocType::Unit
| RocType::Num(_)
| RocType::EmptyTagUnion
| RocType::Unsized
| RocType::TagUnion(RocTagUnion::Enumeration { .. }) => { /* terminal */ }
RocType::TagUnion(RocTagUnion::NonRecursive { tags, .. })
| RocType::TagUnion(RocTagUnion::Recursive { tags, .. })
| RocType::TagUnion(RocTagUnion::NullableWrapped { tags, .. }) => {
for (_, opt_payload_id) in tags.iter() {
if let Some(payload_id) = opt_payload_id {
push!(payload_id);
}
}
}
RocType::RocBox(type_id)
| RocType::RocList(type_id)
| RocType::RocSet(type_id)
| RocType::TagUnion(RocTagUnion::NullableUnwrapped {
non_null_payload: type_id,
..
})
| RocType::TagUnion(RocTagUnion::NonNullableUnwrapped {
payload: type_id, ..
}) => {
push!(type_id);
}
RocType::TagUnion(RocTagUnion::SingleTagStruct {
payload: RocSingleTagPayload::HasNoClosure { payload_fields },
..
}) => {
for payload_id in payload_fields {
push!(payload_id);
}
}
RocType::TagUnion(RocTagUnion::SingleTagStruct {
payload: RocSingleTagPayload::HasClosure { payload_getters },
..
}) => {
for (payload_id, _) in payload_getters {
push!(payload_id);
}
}
RocType::TagUnionPayload {
fields: RocStructFields::HasNoClosure { fields },
..
}
| RocType::Struct {
fields: RocStructFields::HasNoClosure { fields },
..
} => {
for (_, type_id) in fields {
push!(type_id);
}
}
RocType::TagUnionPayload {
fields: RocStructFields::HasClosure { fields },
..
}
| RocType::Struct {
fields: RocStructFields::HasClosure { fields },
..
} => {
for (_, type_id, _) in fields {
push!(type_id);
}
}
RocType::RecursivePointer(type_id) => {
push!(type_id);
}
RocType::RocResult(id1, id2) | RocType::RocDict(id1, id2) => {
push!(id1);
push!(id2);
}
RocType::Function(_) => return true,
}
}
false
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -1889,9 +2258,10 @@ pub struct {name} {{
owned_ret = "payload".to_string(); owned_ret = "payload".to_string();
borrowed_ret = format!("&{owned_ret}"); borrowed_ret = format!("&{owned_ret}");
} }
RocType::Struct { fields, name } => { RocType::Struct { fields, name } => match fields {
RocStructFields::HasNoClosure { fields } => {
let answer = let answer =
tag_union_struct_help(name, fields.iter(), non_null_payload, types, false); tag_union_struct_help(name, fields, non_null_payload, types, false);
payload_args = answer.payload_args; payload_args = answer.payload_args;
args_to_payload = answer.args_to_payload; args_to_payload = answer.args_to_payload;
@ -1900,9 +2270,12 @@ pub struct {name} {{
owned_ret_type = answer.owned_ret_type; owned_ret_type = answer.owned_ret_type;
borrowed_ret_type = answer.borrowed_ret_type; borrowed_ret_type = answer.borrowed_ret_type;
} }
RocType::TagUnionPayload { fields, name } => {
let answer = RocStructFields::HasClosure { .. } => todo!(),
tag_union_struct_help(name, fields.iter(), non_null_payload, types, true); },
RocType::TagUnionPayload { fields, name } => match fields {
RocStructFields::HasNoClosure { fields } => {
let answer = tag_union_struct_help(name, fields, non_null_payload, types, true);
payload_args = answer.payload_args; payload_args = answer.payload_args;
args_to_payload = answer.args_to_payload; args_to_payload = answer.args_to_payload;
@ -1911,7 +2284,10 @@ pub struct {name} {{
owned_ret_type = answer.owned_ret_type; owned_ret_type = answer.owned_ret_type;
borrowed_ret_type = answer.borrowed_ret_type; borrowed_ret_type = answer.borrowed_ret_type;
} }
RocStructFields::HasClosure { .. } => todo!(),
},
RocType::Function { .. } => todo!(), RocType::Function { .. } => todo!(),
RocType::Unsized => todo!(),
}; };
// Add a convenience constructor function for the tag with the payload, e.g. // Add a convenience constructor function for the tag with the payload, e.g.
@ -1986,7 +2362,7 @@ pub struct {name} {{
opt_impl.clone(), opt_impl.clone(),
target_info, target_info,
format!( format!(
r#"/// Unsafely assume the given `{name}` has a `.discriminant()` of `{non_null_tag}` and convert it to `{non_null_tag}`'s payload. r#"/// Unsafely assume this `{name}` has a `.discriminant()` of `{non_null_tag}` and convert it to `{non_null_tag}`'s payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!) /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return {non_null_tag}. /// Panics in debug builds if the `.discriminant()` doesn't return {non_null_tag}.
pub unsafe fn into_{non_null_tag}(self) -> {owned_ret_type} {{ pub unsafe fn into_{non_null_tag}(self) -> {owned_ret_type} {{
@ -2005,7 +2381,7 @@ pub struct {name} {{
opt_impl.clone(), opt_impl.clone(),
target_info, target_info,
format!( format!(
r#"/// Unsafely assume the given `{name}` has a `.discriminant()` of `{non_null_tag}` and return its payload. r#"/// Unsafely assume this `{name}` has a `.discriminant()` of `{non_null_tag}` and return its payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!) /// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `{non_null_tag}`. /// Panics in debug builds if the `.discriminant()` doesn't return `{non_null_tag}`.
pub unsafe fn as_{non_null_tag}(&self) -> {borrowed_ret_type} {{ pub unsafe fn as_{non_null_tag}(&self) -> {borrowed_ret_type} {{
@ -2138,22 +2514,33 @@ pub struct {name} {{
RocType::Struct { fields, .. } => { RocType::Struct { fields, .. } => {
let mut buf = Vec::new(); let mut buf = Vec::new();
match fields {
RocStructFields::HasNoClosure { fields } => {
for (label, _) in fields { for (label, _) in fields {
buf.push(format!(".field(&(&*{extra_deref}self.pointer).{label})")); buf.push(format!(".field(&(&*{extra_deref}self.pointer).{label})"));
} }
}
RocStructFields::HasClosure { fields: _ } => todo!(),
}
buf.join(&format!("\n{INDENT}{INDENT}{INDENT}{INDENT}{INDENT}")) buf.join(&format!("\n{INDENT}{INDENT}{INDENT}{INDENT}{INDENT}"))
} }
RocType::TagUnionPayload { fields, .. } => { RocType::TagUnionPayload { fields, .. } => {
let mut buf = Vec::new(); let mut buf = Vec::new();
match fields {
RocStructFields::HasNoClosure { fields } => {
for (label, _) in fields { for (label, _) in fields {
// Needs an "f" prefix // Needs an "f" prefix
buf.push(format!(".field(&(&*{extra_deref}self.pointer).f{label})")); buf.push(format!(".field(&(&*{extra_deref}self.pointer).f{label})"));
} }
}
RocStructFields::HasClosure { fields: _ } => todo!(),
}
buf.join(&format!("\n{INDENT}{INDENT}{INDENT}{INDENT}{INDENT}")) buf.join(&format!("\n{INDENT}{INDENT}{INDENT}{INDENT}{INDENT}"))
} }
RocType::Unsized => todo!(),
RocType::Function { .. } => todo!(), RocType::Function { .. } => todo!(),
}; };
@ -2221,17 +2608,13 @@ struct StructIngredients {
borrowed_ret_type: String, borrowed_ret_type: String,
} }
fn tag_union_struct_help<'a, I: Iterator<Item = &'a (L, TypeId)>, L: Display + PartialOrd + 'a>( fn tag_union_struct_help(
name: &str, name: &str,
fields: I, fields: &[(String, TypeId)],
payload_id: TypeId, payload_id: TypeId,
types: &Types, types: &Types,
is_tag_union_payload: bool, is_tag_union_payload: bool,
) -> StructIngredients { ) -> StructIngredients {
let mut sorted_fields = fields.collect::<Vec<&(L, TypeId)>>();
sorted_fields.sort_by(|(label1, _), (label2, _)| label1.partial_cmp(label2).unwrap());
let mut ret_types = if is_tag_union_payload { let mut ret_types = if is_tag_union_payload {
// This will be a tuple we create when iterating over the fields. // This will be a tuple we create when iterating over the fields.
Vec::new() Vec::new()
@ -2241,13 +2624,13 @@ fn tag_union_struct_help<'a, I: Iterator<Item = &'a (L, TypeId)>, L: Display + P
let mut ret_values = Vec::new(); let mut ret_values = Vec::new();
for (label, type_id) in sorted_fields.iter() { for (label, type_id) in fields.iter() {
let label = if is_tag_union_payload { let label = if is_tag_union_payload {
// Tag union payload fields need "f" prefix // Tag union payload fields need "f" prefix
// because they're numbers // because they're numbers
format!("f{}", label) format!("f{}", label)
} else { } else {
escape_kw(format!("{}", label)) escape_kw(label.to_string())
}; };
ret_values.push(format!("payload.{label}")); ret_values.push(format!("payload.{label}"));
@ -2266,7 +2649,7 @@ fn tag_union_struct_help<'a, I: Iterator<Item = &'a (L, TypeId)>, L: Display + P
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "); .join(", ");
let args_to_payload = if is_tag_union_payload { let args_to_payload = if is_tag_union_payload {
let prefixed_fields = sorted_fields let prefixed_fields = fields
.iter() .iter()
.enumerate() .enumerate()
.map(|(index, (label, _))| { .map(|(index, (label, _))| {
@ -2361,7 +2744,16 @@ fn cannot_derive_default(roc_type: &RocType, types: &Types) -> bool {
| RocType::TagUnion { .. } | RocType::TagUnion { .. }
| RocType::RocResult(_, _) | RocType::RocResult(_, _)
| RocType::RecursivePointer { .. } | RocType::RecursivePointer { .. }
| RocType::Function { .. } => true, | RocType::Struct {
fields: RocStructFields::HasClosure { .. },
..
}
| RocType::TagUnionPayload {
fields: RocStructFields::HasClosure { .. },
..
}
| RocType::Unsized => true,
RocType::Function { .. } => true,
RocType::RocStr | RocType::Bool | RocType::Num(_) => false, RocType::RocStr | RocType::Bool | RocType::Num(_) => false,
RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => { RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => {
cannot_derive_default(types.get_type(*id), types) cannot_derive_default(types.get_type(*id), types)
@ -2370,10 +2762,16 @@ fn cannot_derive_default(roc_type: &RocType, types: &Types) -> bool {
cannot_derive_default(types.get_type(*key_id), types) cannot_derive_default(types.get_type(*key_id), types)
|| cannot_derive_default(types.get_type(*val_id), types) || cannot_derive_default(types.get_type(*val_id), types)
} }
RocType::Struct { fields, .. } => fields RocType::Struct {
fields: RocStructFields::HasNoClosure { fields },
..
} => fields
.iter() .iter()
.any(|(_, type_id)| cannot_derive_default(types.get_type(*type_id), types)), .any(|(_, type_id)| cannot_derive_default(types.get_type(*type_id), types)),
RocType::TagUnionPayload { fields, .. } => fields RocType::TagUnionPayload {
fields: RocStructFields::HasNoClosure { fields },
..
} => fields
.iter() .iter()
.any(|(_, type_id)| cannot_derive_default(types.get_type(*type_id), types)), .any(|(_, type_id)| cannot_derive_default(types.get_type(*type_id), types)),
} }
@ -2387,6 +2785,7 @@ fn cannot_derive_copy(roc_type: &RocType, types: &Types) -> bool {
| RocType::Bool | RocType::Bool
| RocType::Num(_) | RocType::Num(_)
| RocType::TagUnion(RocTagUnion::Enumeration { .. }) | RocType::TagUnion(RocTagUnion::Enumeration { .. })
| RocType::Unsized
| RocType::Function { .. } => false, | RocType::Function { .. } => false,
RocType::RocStr RocType::RocStr
| RocType::RocList(_) | RocType::RocList(_)
@ -2398,9 +2797,18 @@ fn cannot_derive_copy(roc_type: &RocType, types: &Types) -> bool {
| RocType::TagUnion(RocTagUnion::Recursive { .. }) | RocType::TagUnion(RocTagUnion::Recursive { .. })
| RocType::RecursivePointer { .. } | RocType::RecursivePointer { .. }
| RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { .. }) => true, | RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { .. }) => true,
RocType::TagUnion(RocTagUnion::SingleTagStruct { payload_fields, .. }) => payload_fields RocType::TagUnion(RocTagUnion::SingleTagStruct {
payload: RocSingleTagPayload::HasNoClosure { payload_fields },
..
}) => payload_fields
.iter() .iter()
.any(|type_id| cannot_derive_copy(types.get_type(*type_id), types)), .any(|type_id| cannot_derive_copy(types.get_type(*type_id), types)),
RocType::TagUnion(RocTagUnion::SingleTagStruct {
payload: RocSingleTagPayload::HasClosure { payload_getters },
..
}) => payload_getters
.iter()
.any(|(type_id, _)| cannot_derive_copy(types.get_type(*type_id), types)),
RocType::TagUnion(RocTagUnion::NonRecursive { tags, .. }) => { RocType::TagUnion(RocTagUnion::NonRecursive { tags, .. }) => {
tags.iter().any(|(_, payloads)| { tags.iter().any(|(_, payloads)| {
payloads payloads
@ -2412,12 +2820,26 @@ fn cannot_derive_copy(roc_type: &RocType, types: &Types) -> bool {
cannot_derive_copy(types.get_type(*ok_id), types) cannot_derive_copy(types.get_type(*ok_id), types)
|| cannot_derive_copy(types.get_type(*err_id), types) || cannot_derive_copy(types.get_type(*err_id), types)
} }
RocType::Struct { fields, .. } => fields RocType::Struct {
fields: RocStructFields::HasNoClosure { fields },
..
}
| RocType::TagUnionPayload {
fields: RocStructFields::HasNoClosure { fields },
..
} => fields
.iter() .iter()
.any(|(_, type_id)| cannot_derive_copy(types.get_type(*type_id), types)), .any(|(_, type_id)| cannot_derive_copy(types.get_type(*type_id), types)),
RocType::TagUnionPayload { fields, .. } => fields RocType::Struct {
fields: RocStructFields::HasClosure { fields },
..
}
| RocType::TagUnionPayload {
fields: RocStructFields::HasClosure { fields },
..
} => fields
.iter() .iter()
.any(|(_, type_id)| cannot_derive_copy(types.get_type(*type_id), types)), .any(|(_, type_id, _)| cannot_derive_copy(types.get_type(*type_id), types)),
} }
} }
@ -2441,7 +2863,8 @@ fn has_float_help(roc_type: &RocType, types: &Types, do_not_recurse: &[TypeId])
| RocType::RocStr | RocType::RocStr
| RocType::Bool | RocType::Bool
| RocType::TagUnion(RocTagUnion::Enumeration { .. }) | RocType::TagUnion(RocTagUnion::Enumeration { .. })
| RocType::Function { .. } => false, | RocType::Function { .. }
| RocType::Unsized => false,
RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => { RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => {
has_float_help(types.get_type(*id), types, do_not_recurse) has_float_help(types.get_type(*id), types, do_not_recurse)
} }
@ -2453,30 +2876,61 @@ fn has_float_help(roc_type: &RocType, types: &Types, do_not_recurse: &[TypeId])
has_float_help(types.get_type(*key_id), types, do_not_recurse) has_float_help(types.get_type(*key_id), types, do_not_recurse)
|| has_float_help(types.get_type(*val_id), types, do_not_recurse) || has_float_help(types.get_type(*val_id), types, do_not_recurse)
} }
RocType::Struct { fields, .. } => fields RocType::TagUnionPayload {
fields: RocStructFields::HasNoClosure { fields },
name: _,
}
| RocType::Struct {
fields: RocStructFields::HasNoClosure { fields },
name: _,
} => fields
.iter() .iter()
.any(|(_, type_id)| has_float_help(types.get_type(*type_id), types, do_not_recurse)), .any(|(_, type_id)| has_float_help(types.get_type(*type_id), types, do_not_recurse)),
RocType::TagUnionPayload { fields, .. } => fields RocType::TagUnionPayload {
fields: RocStructFields::HasClosure { fields },
name: _,
}
| RocType::Struct {
fields: RocStructFields::HasClosure { fields },
name: _,
} => fields
.iter() .iter()
.any(|(_, type_id)| has_float_help(types.get_type(*type_id), types, do_not_recurse)), .any(|(_, type_id, _)| has_float_help(types.get_type(*type_id), types, do_not_recurse)),
RocType::TagUnion(RocTagUnion::SingleTagStruct { payload_fields, .. }) => payload_fields RocType::TagUnion(RocTagUnion::SingleTagStruct {
payload: RocSingleTagPayload::HasNoClosure { payload_fields },
..
}) => payload_fields
.iter() .iter()
.any(|type_id| has_float_help(types.get_type(*type_id), types, do_not_recurse)), .any(|type_id| has_float_help(types.get_type(*type_id), types, do_not_recurse)),
RocType::TagUnion(RocTagUnion::Recursive { tags, .. }) RocType::TagUnion(RocTagUnion::SingleTagStruct {
| RocType::TagUnion(RocTagUnion::NonRecursive { tags, .. }) => { payload: RocSingleTagPayload::HasClosure { payload_getters },
tags.iter().any(|(_, payloads)| { ..
}) => payload_getters
.iter()
.any(|(type_id, _)| has_float_help(types.get_type(*type_id), types, do_not_recurse)),
RocType::TagUnion(RocTagUnion::Recursive {
tags,
name: _,
discriminant_offset: _,
discriminant_size: _,
})
| RocType::TagUnion(RocTagUnion::NonRecursive {
tags,
name: _,
discriminant_offset: _,
discriminant_size: _,
})
| RocType::TagUnion(RocTagUnion::NullableWrapped {
tags,
name: _,
index_of_null_tag: _,
discriminant_size: _,
discriminant_offset: _,
}) => tags.iter().any(|(_, payloads)| {
payloads payloads
.iter() .iter()
.any(|id| has_float_help(types.get_type(*id), types, do_not_recurse)) .any(|id| has_float_help(types.get_type(*id), types, do_not_recurse))
}) }),
}
RocType::TagUnion(RocTagUnion::NullableWrapped { tags, .. }) => {
tags.iter().any(|(_, payloads)| {
payloads
.iter()
.any(|id| has_float_help(types.get_type(*id), types, do_not_recurse))
})
}
RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { payload, .. }) RocType::TagUnion(RocTagUnion::NonNullableUnwrapped { payload, .. })
| RocType::TagUnion(RocTagUnion::NullableUnwrapped { | RocType::TagUnion(RocTagUnion::NullableUnwrapped {
non_null_payload: payload, non_null_payload: payload,

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
// ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc glue` CLI command // ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc glue` CLI command
#![allow(unused_unsafe)] #![allow(unused_unsafe)]
#![allow(unused_variables)]
#![allow(dead_code)] #![allow(dead_code)]
#![allow(unused_mut)] #![allow(unused_mut)]
#![allow(non_snake_case)] #![allow(non_snake_case)]
@ -15,3 +16,8 @@
#![allow(clippy::redundant_static_lifetimes)] #![allow(clippy::redundant_static_lifetimes)]
#![allow(clippy::needless_borrow)] #![allow(clippy::needless_borrow)]
#![allow(clippy::clone_on_copy)] #![allow(clippy::clone_on_copy)]
type Op_StderrWrite = roc_std::RocStr;
type Op_StdoutWrite = roc_std::RocStr;
type TODO_roc_function_69 = roc_std::RocStr;
type TODO_roc_function_70 = roc_std::RocStr;

View file

@ -1,10 +0,0 @@
app "app"
packages { pf: "platform.roc" }
imports []
provides [main] to pf
main =
Dict.empty {}
|> Dict.insert "foo" "this will be overwritten"
|> Dict.insert "baz" "blah"
|> Dict.insert "foo" "bar"

View file

@ -1,9 +0,0 @@
platform "test-platform"
requires {} { main : _ }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Dict Str Str
mainForHost = main

View file

@ -1,93 +0,0 @@
mod test_glue;
use roc_std::{RocDict, RocStr};
extern "C" {
#[link_name = "roc__mainForHost_1_exposed_generic"]
fn roc_main(_: *mut RocDict<RocStr, RocStr>);
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
use std::cmp::Ordering;
use std::collections::hash_set::HashSet;
let dict = unsafe {
let mut ret: core::mem::MaybeUninit<RocDict<RocStr, RocStr>> =
core::mem::MaybeUninit::uninit();
roc_main(ret.as_mut_ptr());
ret.assume_init()
};
// Verify that it has all the expected traits.
assert!(dict == dict); // PartialEq
assert_eq!(dict.len(), 2); // len
assert!(dict.clone() == dict.clone()); // Clone
assert!(dict.partial_cmp(&dict) == Some(Ordering::Equal)); // PartialOrd
assert!(dict.cmp(&dict) == Ordering::Equal); // Ord
let mut set = HashSet::new();
set.insert(dict.clone()); // Eq, Hash
set.insert(dict.clone());
assert_eq!(set.len(), 1);
println!("dict was: {:?}", dict); // Debug
// Exit code
0
}
// Externs required by roc_std and by the Roc app
use core::ffi::c_void;
use std::ffi::CStr;
use std::os::raw::c_char;
#[no_mangle]
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
return libc::malloc(size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
return libc::realloc(c_ptr, new_size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
return libc::free(c_ptr);
}
#[no_mangle]
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
match tag_id {
0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
_ => todo!(),
}
}
#[no_mangle]
pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
libc::memcpy(dst, src, n)
}
#[no_mangle]
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
libc::memset(dst, c, n)
}

View file

@ -1,11 +0,0 @@
app "app"
packages { pf: "platform.roc" }
imports []
provides [main] to pf
main =
Set.empty {}
|> Set.insert "foo"
|> Set.insert "bar"
|> Set.insert "foo"
|> Set.insert "baz"

View file

@ -1,9 +0,0 @@
platform "test-platform"
requires {} { main : _ }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Set Str
mainForHost = main

View file

@ -1,92 +0,0 @@
mod test_glue;
use roc_std::{RocSet, RocStr};
extern "C" {
#[link_name = "roc__mainForHost_1_exposed_generic"]
fn roc_main(_: *mut RocSet<RocStr>);
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
use std::cmp::Ordering;
use std::collections::hash_set::HashSet;
let set = unsafe {
let mut ret: core::mem::MaybeUninit<RocSet<RocStr>> = core::mem::MaybeUninit::uninit();
roc_main(ret.as_mut_ptr());
ret.assume_init()
};
// Verify that it has all the expected traits.
assert!(set == set); // PartialEq
assert_eq!(set.len(), 3); // len
assert!(set.clone() == set.clone()); // Clone
assert!(set.partial_cmp(&set) == Some(Ordering::Equal)); // PartialOrd
assert!(set.cmp(&set) == Ordering::Equal); // Ord
let mut hash_set = HashSet::new();
hash_set.insert(set.clone()); // Eq, Hash
hash_set.insert(set.clone());
assert_eq!(hash_set.len(), 1);
println!("set was: {:?}", set); // Debug
// Exit code
0
}
// Externs required by roc_std and by the Roc app
use core::ffi::c_void;
use std::ffi::CStr;
use std::os::raw::c_char;
#[no_mangle]
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
return libc::malloc(size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
return libc::realloc(c_ptr, new_size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
return libc::free(c_ptr);
}
#[no_mangle]
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
match tag_id {
0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
_ => todo!(),
}
}
#[no_mangle]
pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
libc::memcpy(dst, src, n)
}
#[no_mangle]
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
libc::memset(dst, c, n)
}

View file

@ -34,7 +34,7 @@ mod test_gen_rs {
target_arch = "x86", target_arch = "x86",
target_arch = "x86_64" target_arch = "x86_64"
))] ))]
#[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(C)] #[repr(C)]
pub struct MyRcd { pub struct MyRcd {
pub b: roc_std::I128, pub b: roc_std::I128,
@ -124,7 +124,7 @@ mod test_gen_rs {
target_arch = "x86", target_arch = "x86",
target_arch = "x86_64" target_arch = "x86_64"
))] ))]
#[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)] #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(C)] #[repr(C)]
pub struct R1 { pub struct R1 {
pub b: roc_std::U128, pub b: roc_std::U128,
@ -212,7 +212,7 @@ mod test_gen_rs {
target_arch = "x86", target_arch = "x86",
target_arch = "x86_64" target_arch = "x86_64"
))] ))]
#[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(u8)] #[repr(u8)]
pub enum Enumeration { pub enum Enumeration {
Bar = 0, Bar = 0,

View file

@ -72,12 +72,6 @@ mod glue_cli_run {
fixtures! { fixtures! {
basic_record:"basic-record" => "Record was: MyRcd { b: 42, a: 1995 }\n", basic_record:"basic-record" => "Record was: MyRcd { b: 42, a: 1995 }\n",
nested_record:"nested-record" => "Record was: Outer { y: \"foo\", z: [1, 2], x: Inner { b: 24.0, a: 5 } }\n", nested_record:"nested-record" => "Record was: Outer { y: \"foo\", z: [1, 2], x: Inner { b: 24.0, a: 5 } }\n",
dict:"dict" => indoc!(r#"
dict was: RocDict {"foo": "bar", "baz": "blah"}
"#),
set:"set" => indoc!(r#"
set was: RocSet {"foo", "bar", "baz"}
"#),
enumeration:"enumeration" => "tag_union was: MyEnum::Foo, Bar is: MyEnum::Bar, Baz is: MyEnum::Baz\n", enumeration:"enumeration" => "tag_union was: MyEnum::Foo, Bar is: MyEnum::Bar, Baz is: MyEnum::Baz\n",
union_with_padding:"union-with-padding" => indoc!(r#" union_with_padding:"union-with-padding" => indoc!(r#"
tag_union was: NonRecursive::Foo("This is a test") tag_union was: NonRecursive::Foo("This is a test")

View file

@ -15,6 +15,7 @@ path = "src/lib.rs"
[dependencies] [dependencies]
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
roc_error_macros = { path = "../error_macros" } roc_error_macros = { path = "../error_macros" }
roc_module = { path = "../compiler/module" }
roc_load = { path = "../compiler/load" } roc_load = { path = "../compiler/load" }
roc_mono = { path = "../compiler/mono" } roc_mono = { path = "../compiler/mono" }
roc_packaging = { path = "../packaging" } roc_packaging = { path = "../packaging" }

View file

@ -1532,7 +1532,16 @@ fn surgery_elf_help(
let func_virt_offset = match app_func_vaddr_map.get(func_name) { let func_virt_offset = match app_func_vaddr_map.get(func_name) {
Some(offset) => *offset as u64, Some(offset) => *offset as u64,
None => { None => {
internal_error!("Function, {}, was not defined by the app", &func_name); eprintln!("Error:");
eprintln!("\n\tFunction, {}, was not defined by the app.", &func_name);
eprintln!("\nPotential causes:");
eprintln!("\n\t- because the platform was built with a non-compatible version of roc compared to the one you are running.");
eprintln!("\n\t\tsolutions:");
eprintln!("\t\t\t+ Downgrade your roc version to the one that was used to build the platform.");
eprintln!("\t\t\t+ Or ask the platform author to release a new version of the platform using a current roc release.");
eprintln!("\n\t- This can also occur due to a bug in the compiler. In that case, file an issue here: https://github.com/roc-lang/roc/issues/new/choose");
std::process::exit(1);
} }
}; };
if verbose { if verbose {

View file

@ -6,7 +6,8 @@
use memmap2::{Mmap, MmapMut}; use memmap2::{Mmap, MmapMut};
use object::Object; use object::Object;
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_load::{EntryPoint, ExecutionMode, LoadConfig, Threading}; use roc_load::{EntryPoint, ExecutionMode, ExposedToHost, LoadConfig, Threading};
use roc_module::symbol::Interns;
use roc_packaging::cache::RocCacheDir; use roc_packaging::cache::RocCacheDir;
use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE}; use roc_reporting::report::{RenderTarget, DEFAULT_PALETTE};
use roc_target::get_target_triple_str; use roc_target::get_target_triple_str;
@ -61,7 +62,7 @@ pub fn supported(link_type: LinkType, target: &Triple) -> bool {
} }
} }
pub const PRECOMPILED_HOST_EXT: &str = "rh1"; // Short for "roc host version 1" (so we can change format in the future) pub const PRECOMPILED_HOST_EXT: &str = "rh"; // Short for "roc host"
pub fn preprocessed_host_filename(target: &Triple) -> Option<String> { pub fn preprocessed_host_filename(target: &Triple) -> Option<String> {
roc_target::get_target_triple_str(target).map(|x| format!("{}.{}", x, PRECOMPILED_HOST_EXT)) roc_target::get_target_triple_str(target).map(|x| format!("{}.{}", x, PRECOMPILED_HOST_EXT))
@ -70,7 +71,7 @@ pub fn preprocessed_host_filename(target: &Triple) -> Option<String> {
fn metadata_file_name(target: &Triple) -> String { fn metadata_file_name(target: &Triple) -> String {
let target_triple_str = get_target_triple_str(target); let target_triple_str = get_target_triple_str(target);
format!("metadata_{}.rm2", target_triple_str.unwrap_or("unknown")) format!("metadata_{}.rm", target_triple_str.unwrap_or("unknown"))
} }
pub fn link_preprocessed_host( pub fn link_preprocessed_host(
@ -111,7 +112,7 @@ pub fn generate_stub_lib(
let exposed_to_host = loaded let exposed_to_host = loaded
.exposed_to_host .exposed_to_host
.values .top_level_values
.keys() .keys()
.map(|x| x.as_str(&loaded.interns).to_string()) .map(|x| x.as_str(&loaded.interns).to_string())
.collect(); .collect();
@ -129,6 +130,11 @@ pub fn generate_stub_lib(
}) })
.collect(); .collect();
let exposed_symbols = ExposedSymbols {
top_level_values: exposed_to_host,
exported_closure_types,
};
if let EntryPoint::Executable { platform_path, .. } = &loaded.entry_point { if let EntryPoint::Executable { platform_path, .. } = &loaded.entry_point {
let stub_lib = if let target_lexicon::OperatingSystem::Windows = triple.operating_system { let stub_lib = if let target_lexicon::OperatingSystem::Windows = triple.operating_system {
platform_path.with_file_name("libapp.obj") platform_path.with_file_name("libapp.obj")
@ -136,7 +142,7 @@ pub fn generate_stub_lib(
platform_path.with_file_name("libapp.so") platform_path.with_file_name("libapp.so")
}; };
let stub_dll_symbols = make_stub_dll_symbols(exposed_to_host, exported_closure_types); let stub_dll_symbols = exposed_symbols.stub_dll_symbols();
generate_dynamic_lib(triple, &stub_dll_symbols, &stub_lib); generate_dynamic_lib(triple, &stub_dll_symbols, &stub_lib);
} else { } else {
unreachable!(); unreachable!();
@ -147,35 +153,87 @@ pub fn generate_stub_lib(
pub fn generate_stub_lib_from_loaded( pub fn generate_stub_lib_from_loaded(
target: &Triple, target: &Triple,
platform_main_roc: &Path, platform_main_roc: &Path,
exposed_to_host: Vec<String>, stub_dll_symbols: &[String],
exported_closure_types: Vec<String>, ) -> PathBuf {
) -> (PathBuf, Vec<String>) { let stub_lib_path = if let target_lexicon::OperatingSystem::Windows = target.operating_system {
let stub_lib = if let target_lexicon::OperatingSystem::Windows = target.operating_system {
platform_main_roc.with_file_name("libapp.dll") platform_main_roc.with_file_name("libapp.dll")
} else { } else {
platform_main_roc.with_file_name("libapp.so") platform_main_roc.with_file_name("libapp.so")
}; };
let stub_dll_symbols = make_stub_dll_symbols(exposed_to_host, exported_closure_types); generate_dynamic_lib(target, stub_dll_symbols, &stub_lib_path);
generate_dynamic_lib(target, &stub_dll_symbols, &stub_lib);
(stub_lib, stub_dll_symbols) stub_lib_path
} }
fn make_stub_dll_symbols( pub struct ExposedSymbols {
exposed_to_host: Vec<String>, // usually just `mainForhost`
exported_closure_types: Vec<String>, pub top_level_values: Vec<String>,
) -> Vec<String> {
// old type exposing mechanism
pub exported_closure_types: Vec<String>,
}
impl ExposedSymbols {
pub fn from_exposed_to_host(interns: &Interns, exposed_to_host: &ExposedToHost) -> Vec<String> {
let mut custom_names = Vec::new(); let mut custom_names = Vec::new();
for sym in exposed_to_host { for x in exposed_to_host.top_level_values.keys() {
let sym = x.as_str(interns);
custom_names.extend([
format!("roc__{}_1_exposed", sym),
format!("roc__{}_1_exposed_generic", sym),
format!("roc__{}_1_exposed_size", sym),
]);
let exported_closure_types = exposed_to_host
.closure_types
.iter()
.map(|x| format!("{}_{}", x.module_string(interns), x.as_str(interns)));
for (i, _) in exported_closure_types.enumerate() {
custom_names.extend([
format!("roc__{}_{i}_caller", sym),
format!("roc__{}_{i}_size", sym),
format!("roc__{}_{i}_result_size", sym),
]);
}
}
for x in &exposed_to_host.getters {
let sym = x.as_str(interns);
custom_names.extend([
sym.to_string(),
format!("{sym}_generic"),
format!("{sym}_size"),
]);
}
for (top_level_value, lambda_set_id) in &exposed_to_host.lambda_sets {
let sym = top_level_value.as_str(interns);
let id = lambda_set_id.0;
custom_names.extend([format!("roc__{sym}_{id}_caller")]);
}
// on windows (PE) binary search is used on the symbols,
// so they must be in alphabetical order
custom_names.sort_unstable();
custom_names
}
pub fn stub_dll_symbols(&self) -> Vec<String> {
let mut custom_names = Vec::new();
for sym in &self.top_level_values {
custom_names.extend([ custom_names.extend([
format!("roc__{}_1_exposed", sym), format!("roc__{}_1_exposed", sym),
format!("roc__{}_1_exposed_generic", sym), format!("roc__{}_1_exposed_generic", sym),
format!("roc__{}_size", sym), format!("roc__{}_size", sym),
]); ]);
for closure_type in &exported_closure_types { for closure_type in &self.exported_closure_types {
custom_names.extend([ custom_names.extend([
format!("roc__{}_1_{}_caller", sym, closure_type), format!("roc__{}_1_{}_caller", sym, closure_type),
format!("roc__{}_1_{}_size", sym, closure_type), format!("roc__{}_1_{}_size", sym, closure_type),
@ -190,6 +248,7 @@ fn make_stub_dll_symbols(
custom_names custom_names
} }
}
fn generate_dynamic_lib(target: &Triple, stub_dll_symbols: &[String], stub_lib_path: &Path) { fn generate_dynamic_lib(target: &Triple, stub_dll_symbols: &[String], stub_lib_path: &Path) {
if !stub_lib_is_up_to_date(target, stub_lib_path, stub_dll_symbols) { if !stub_lib_is_up_to_date(target, stub_lib_path, stub_dll_symbols) {

View file

@ -55,21 +55,15 @@ impl Metadata {
} }
pub fn read_from_file(metadata_filename: &Path) -> Self { pub fn read_from_file(metadata_filename: &Path) -> Self {
let input = let input = std::fs::File::open(metadata_filename).unwrap_or_else(|e| {
std::fs::File::open(metadata_filename) internal_error!(
.unwrap_or_else( r#"
|e| internal_error!(r#"
Error: Error:
{} {}\n"#,
e
> This may occur when using a release of roc that relies on a specific metadata format like 'rm2' and the imported platform only has an older metadata format available, like rm1. )
The platform you are using can be found in the header of your main.roc: `packages {{ pf: <PLATFORM>}}`. });
You should check if a more recent version of the platform is available.
If not, you should notify the author of the platform about this issue.
"#, e)
);
match deserialize_from(BufReader::new(input)) { match deserialize_from(BufReader::new(input)) {
Ok(data) => data, Ok(data) => data,

View file

@ -151,9 +151,9 @@ fn write_archive<W: Write>(path: &Path, writer: W) -> io::Result<()> {
if [ if [
// surgical linker format // surgical linker format
Some("rh1"), Some("rh"),
// metadata file // metadata file
Some("rm2"), Some("rm"),
// legacy linker formats // legacy linker formats
Some("o"), Some("o"),
Some("obj"), Some("obj"),

View file

@ -42,8 +42,13 @@ pub fn gen_and_eval_llvm<'a, I: Iterator<Item = &'a str>>(
} }
}; };
debug_assert_eq!(loaded.exposed_to_host.values.len(), 1); debug_assert_eq!(loaded.exposed_to_host.top_level_values.len(), 1);
let (main_fn_symbol, main_fn_var) = loaded.exposed_to_host.values.iter().next().unwrap(); let (main_fn_symbol, main_fn_var) = loaded
.exposed_to_host
.top_level_values
.iter()
.next()
.unwrap();
let main_fn_symbol = *main_fn_symbol; let main_fn_symbol = *main_fn_symbol;
let main_fn_var = *main_fn_var; let main_fn_var = *main_fn_var;

View file

@ -217,8 +217,8 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
.. ..
} = mono; } = mono;
debug_assert_eq!(exposed_to_host.values.len(), 1); debug_assert_eq!(exposed_to_host.top_level_values.len(), 1);
let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); let (main_fn_symbol, main_fn_var) = exposed_to_host.top_level_values.iter().next().unwrap();
let main_fn_symbol = *main_fn_symbol; let main_fn_symbol = *main_fn_symbol;
let main_fn_var = *main_fn_var; let main_fn_var = *main_fn_var;
@ -242,7 +242,7 @@ pub async fn entrypoint_from_js(src: String) -> Result<String, String> {
module_id, module_id,
stack_bytes: roc_gen_wasm::Env::DEFAULT_STACK_BYTES, stack_bytes: roc_gen_wasm::Env::DEFAULT_STACK_BYTES,
exposed_to_host: exposed_to_host exposed_to_host: exposed_to_host
.values .top_level_values
.keys() .keys()
.copied() .copied()
.collect::<MutSet<_>>(), .collect::<MutSet<_>>(),

View file

@ -129,6 +129,7 @@ impl<K: Debug, V: Debug> Debug for RocDict<K, V> {
/// contiguously in memory. If we separate them at some point, we'll need to /// contiguously in memory. If we separate them at some point, we'll need to
/// change this implementation drastically! /// change this implementation drastically!
#[derive(Eq)] #[derive(Eq)]
#[repr(C)]
union RocDictItem<K, V> { union RocDictItem<K, V> {
key_first: ManuallyDrop<KeyFirst<K, V>>, key_first: ManuallyDrop<KeyFirst<K, V>>,
value_first: ManuallyDrop<ValueFirst<K, V>>, value_first: ManuallyDrop<ValueFirst<K, V>>,

View file

@ -9,7 +9,7 @@ use core::{
intrinsics::copy_nonoverlapping, intrinsics::copy_nonoverlapping,
iter::FromIterator, iter::FromIterator,
mem::{self, ManuallyDrop}, mem::{self, ManuallyDrop},
ops::Deref, ops::{Deref, DerefMut},
ptr::{self, NonNull}, ptr::{self, NonNull},
}; };
@ -131,6 +131,14 @@ impl<T> RocList<T> {
} }
} }
pub fn as_mut_ptr(&mut self) -> *mut T {
self.as_mut_slice().as_mut_ptr()
}
pub fn as_ptr(&self) -> *const T {
self.as_slice().as_ptr()
}
/// Marks a list as readonly. This means that it will be leaked. /// Marks a list as readonly. This means that it will be leaked.
/// For constants passed in from platform to application, this may be reasonable. /// For constants passed in from platform to application, this may be reasonable.
/// ///
@ -164,6 +172,19 @@ impl<T> RocList<T> {
self self
} }
/// Note that there is no way to convert directly to a Vec.
///
/// This is because RocList values are not allocated using the system allocator, so
/// handing off any heap-allocated bytes to a Vec would not work because its Drop
/// implementation would try to free those bytes using the wrong allocator.
///
/// Instead, if you want a Rust Vec, you need to do a fresh allocation and copy the
/// bytes over - in other words, calling this `as_slice` method and then calling `to_vec`
/// on that.
pub fn as_mut_slice(&mut self) -> &mut [T] {
&mut *self
}
#[inline(always)] #[inline(always)]
fn elements_and_storage(&self) -> Option<(NonNull<ManuallyDrop<T>>, &Cell<Storage>)> { fn elements_and_storage(&self) -> Option<(NonNull<ManuallyDrop<T>>, &Cell<Storage>)> {
let elements = self.elements?; let elements = self.elements?;
@ -403,6 +424,19 @@ impl<T> Deref for RocList<T> {
} }
} }
impl<T> DerefMut for RocList<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
if let Some(elements) = self.elements {
let ptr = elements.as_ptr().cast::<T>();
let elements = ptr::slice_from_raw_parts_mut(ptr, self.length);
unsafe { &mut *elements }
} else {
&mut []
}
}
}
impl<T> Default for RocList<T> { impl<T> Default for RocList<T> {
fn default() -> Self { fn default() -> Self {
Self::empty() Self::empty()

View file

@ -26,8 +26,10 @@ fn build_host() {
&target, &target,
&platform_main_roc, &platform_main_roc,
&preprocessed_host_path, &preprocessed_host_path,
vec![String::from("mainForHost")], roc_linker::ExposedSymbols {
vec![], top_level_values: vec![String::from("mainForHost")],
exported_closure_types: vec![],
},
); );
} }

4
examples/.gitignore vendored
View file

@ -2,7 +2,7 @@
libhost.a libhost.a
libapp.so libapp.so
dynhost dynhost
*.rm1 *.rm
*.rh1 *.rh
helloWorld helloWorld

View file

@ -9,10 +9,9 @@ use core::mem::MaybeUninit;
use glue::Metadata; use glue::Metadata;
use roc_std::{RocDict, RocList, RocResult, RocStr}; use roc_std::{RocDict, RocList, RocResult, RocStr};
use std::borrow::{Borrow, Cow}; use std::borrow::{Borrow, Cow};
use std::ffi::{CStr, OsStr}; use std::ffi::{ OsStr};
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::os::raw::c_char;
use std::path::Path; use std::path::Path;
use std::time::Duration; use std::time::Duration;
@ -23,17 +22,17 @@ extern "C" {
#[link_name = "roc__mainForHost_1_exposed_generic"] #[link_name = "roc__mainForHost_1_exposed_generic"]
fn roc_main(output: *mut u8); fn roc_main(output: *mut u8);
#[link_name = "roc__mainForHost_size"] #[link_name = "roc__mainForHost_1_exposed_size"]
fn roc_main_size() -> i64; fn roc_main_size() -> i64;
#[link_name = "roc__mainForHost_1__Fx_caller"] #[link_name = "roc__mainForHost_0_caller"]
fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8); fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8);
#[allow(dead_code)] #[allow(dead_code)]
#[link_name = "roc__mainForHost_1__Fx_size"] #[link_name = "roc__mainForHost_0_size"]
fn size_Fx() -> i64; fn size_Fx() -> i64;
#[link_name = "roc__mainForHost_1__Fx_result_size"] #[link_name = "roc__mainForHost_0_result_size"]
fn size_Fx_result() -> i64; fn size_Fx_result() -> i64;
} }

View file

@ -25,10 +25,10 @@ const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed_generic([*]u8) void; extern fn roc__mainForHost_1_exposed_generic([*]u8) void;
extern fn roc__mainForHost_size() i64; extern fn roc__mainForHost_1_exposed_size() i64;
extern fn roc__mainForHost_1__Fx_caller(*const u8, [*]u8, [*]u8) void; extern fn roc__mainForHost_0_caller(*const u8, [*]u8, [*]u8) void;
extern fn roc__mainForHost_1__Fx_size() i64; extern fn roc__mainForHost_0_size() i64;
extern fn roc__mainForHost_1__Fx_result_size() i64; extern fn roc__mainForHost_0_result_size() i64;
const Align = 2 * @alignOf(usize); const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque; extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
@ -123,7 +123,7 @@ pub export fn main() u8 {
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
// NOTE the return size can be zero, which will segfault. Always allocate at least 8 bytes // NOTE the return size can be zero, which will segfault. Always allocate at least 8 bytes
const size = std.math.max(8, @intCast(usize, roc__mainForHost_size())); const size = std.math.max(8, @intCast(usize, roc__mainForHost_1_exposed_size()));
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable; const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
var output = @ptrCast([*]u8, raw_output); var output = @ptrCast([*]u8, raw_output);
@ -152,7 +152,7 @@ fn to_seconds(tms: std.os.timespec) f64 {
fn call_the_closure(closure_data_pointer: [*]u8) void { fn call_the_closure(closure_data_pointer: [*]u8) void {
const allocator = std.heap.page_allocator; const allocator = std.heap.page_allocator;
const size = roc__mainForHost_1__Fx_result_size(); const size = roc__mainForHost_0_result_size();
if (size == 0) { if (size == 0) {
// the function call returns an empty record // the function call returns an empty record
@ -160,7 +160,7 @@ fn call_the_closure(closure_data_pointer: [*]u8) void {
// So it's special-cased // So it's special-cased
const flags: u8 = 0; const flags: u8 = 0;
var result: [1]u8 = .{0}; var result: [1]u8 = .{0};
roc__mainForHost_1__Fx_caller(&flags, closure_data_pointer, &result); roc__mainForHost_0_caller(&flags, closure_data_pointer, &result);
return; return;
} }
@ -173,7 +173,7 @@ fn call_the_closure(closure_data_pointer: [*]u8) void {
} }
const flags: u8 = 0; const flags: u8 = 0;
roc__mainForHost_1__Fx_caller(&flags, closure_data_pointer, output); roc__mainForHost_0_caller(&flags, closure_data_pointer, output);
return; return;
} }

View file

@ -14,17 +14,17 @@ extern "C" {
#[link_name = "roc__mainForHost_1_exposed_generic"] #[link_name = "roc__mainForHost_1_exposed_generic"]
fn roc_main(output: *mut u8, args: &RocStr); fn roc_main(output: *mut u8, args: &RocStr);
#[link_name = "roc__mainForHost_size"] #[link_name = "roc__mainForHost_1_exposed_size"]
fn roc_main_size() -> i64; fn roc_main_size() -> i64;
#[link_name = "roc__mainForHost_1__Fx_caller"] #[link_name = "roc__mainForHost_0_caller"]
fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8); fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8);
#[allow(dead_code)] #[allow(dead_code)]
#[link_name = "roc__mainForHost_1__Fx_size"] #[link_name = "roc__mainForHost_0_size"]
fn size_Fx() -> i64; fn size_Fx() -> i64;
#[link_name = "roc__mainForHost_1__Fx_result_size"] #[link_name = "roc__mainForHost_0_result_size"]
fn size_Fx_result() -> i64; fn size_Fx_result() -> i64;
} }
@ -171,14 +171,14 @@ pub extern "C" fn roc_fx_getChar() -> u8 {
pub extern "C" fn roc_fx_putLine(line: &RocStr) { pub extern "C" fn roc_fx_putLine(line: &RocStr) {
let string = line.as_str(); let string = line.as_str();
println!("{}", string); println!("{}", string);
std::io::stdout().lock().flush(); let _ = std::io::stdout().lock().flush();
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn roc_fx_putRaw(line: &RocStr) { pub extern "C" fn roc_fx_putRaw(line: &RocStr) {
let string = line.as_str(); let string = line.as_str();
print!("{}", string); print!("{}", string);
std::io::stdout().lock().flush(); let _ = std::io::stdout().lock().flush();
} }
#[no_mangle] #[no_mangle]
@ -207,7 +207,8 @@ pub extern "C" fn roc_fx_getFileBytes(br_ptr: *mut BufReader<File>) -> RocList<u
#[no_mangle] #[no_mangle]
pub extern "C" fn roc_fx_closeFile(br_ptr: *mut BufReader<File>) { pub extern "C" fn roc_fx_closeFile(br_ptr: *mut BufReader<File>) {
unsafe { unsafe {
Box::from_raw(br_ptr); let boxed = Box::from_raw(br_ptr);
drop(boxed)
} }
} }
@ -227,7 +228,7 @@ pub extern "C" fn roc_fx_openFile(name: &RocStr) -> *mut BufReader<File> {
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn roc_fx_withFileOpen(name: &RocStr, buffer: *const u8) { pub extern "C" fn roc_fx_withFileOpen(_name: &RocStr, _buffer: *const u8) {
// TODO: figure out accepting a closure in an fx and passing data to it. // TODO: figure out accepting a closure in an fx and passing data to it.
// let f = File::open(name.as_str()).expect("Unable to open file"); // let f = File::open(name.as_str()).expect("Unable to open file");
// let mut br = BufReader::new(f); // let mut br = BufReader::new(f);

View file

@ -32,12 +32,12 @@ extern fn roc__mainForHost_size() i64;
const ConstModel = [*]const u8; const ConstModel = [*]const u8;
const MutModel = [*]u8; const MutModel = [*]u8;
extern fn roc__mainForHost_1__Init_caller([*]u8, [*]u8, MutModel) void; extern fn roc__mainForHost_0_caller([*]u8, [*]u8, MutModel) void;
extern fn roc__mainForHost_1__Init_size() i64; extern fn roc__mainForHost_0_size() i64;
extern fn roc__mainForHost_1__Init_result_size() i64; extern fn roc__mainForHost_0_result_size() i64;
fn allocate_model(allocator: *Allocator) MutModel { fn allocate_model(allocator: *Allocator) MutModel {
const size = roc__mainForHost_1__Init_result_size(); const size = roc__mainForHost_0_result_size();
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable; const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
var output = @ptrCast([*]u8, raw_output); var output = @ptrCast([*]u8, raw_output);
@ -48,33 +48,33 @@ fn init(allocator: *Allocator) ConstModel {
const closure: [*]u8 = undefined; const closure: [*]u8 = undefined;
const output = allocate_model(allocator); const output = allocate_model(allocator);
roc__mainForHost_1__Init_caller(closure, closure, output); roc__mainForHost_0_caller(closure, closure, output);
return output; return output;
} }
extern fn roc__mainForHost_1__Update_caller(ConstModel, *const RocStr, [*]u8, MutModel) void; extern fn roc__mainForHost_1_caller(ConstModel, *const RocStr, [*]u8, MutModel) void;
extern fn roc__mainForHost_1__Update_size() i64; extern fn roc__mainForHost_1_size() i64;
extern fn roc__mainForHost_1__Update_result_size() i64; extern fn roc__mainForHost_1_result_size() i64;
fn update(allocator: *Allocator, model: ConstModel, msg: RocStr) ConstModel { fn update(allocator: *Allocator, model: ConstModel, msg: RocStr) ConstModel {
const closure: [*]u8 = undefined; const closure: [*]u8 = undefined;
const output = allocate_model(allocator); const output = allocate_model(allocator);
roc__mainForHost_1__Update_caller(model, &msg, closure, output); roc__mainForHost_1_caller(model, &msg, closure, output);
return output; return output;
} }
extern fn roc__mainForHost_1__View_caller(ConstModel, [*]u8, *RocStr) void; extern fn roc__mainForHost_2_caller(ConstModel, [*]u8, *RocStr) void;
extern fn roc__mainForHost_1__View_size() i64; extern fn roc__mainForHost_2_size() i64;
extern fn roc__mainForHost_1__View_result_size() i64; extern fn roc__mainForHost_2_result_size() i64;
fn view(input: ConstModel) RocStr { fn view(input: ConstModel) RocStr {
const closure: [*]u8 = undefined; const closure: [*]u8 = undefined;
var output: RocStr = undefined; var output: RocStr = undefined;
roc__mainForHost_1__View_caller(input, closure, &output); roc__mainForHost_2_caller(input, closure, &output);
return output; return output;
} }

9
examples/glue/glue.roc Normal file
View file

@ -0,0 +1,9 @@
app "rocLovesRust"
packages { pf: "rust-platform/main.roc" }
imports []
provides [main] to pf
main =
StdoutWrite "Roc <3 Rust!\n" \{} ->
StdoutWrite "Roc <3 Rust!\n" \{} ->
Done

View file

@ -0,0 +1,2 @@
[target.x86_64-pc-windows-gnu]
linker = "zig-cc"

37
examples/glue/rust-platform/Cargo.lock generated Normal file
View file

@ -0,0 +1,37 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "arrayvec"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "host"
version = "0.0.1"
dependencies = [
"libc",
"roc_std",
]
[[package]]
name = "libc"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
[[package]]
name = "roc_std"
version = "0.0.1"
dependencies = [
"arrayvec",
"static_assertions",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"

View file

@ -0,0 +1,22 @@
[package]
name = "host"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
links = "app"
[lib]
name = "host"
path = "src/lib.rs"
crate-type = ["staticlib", "rlib"]
[[bin]]
name = "host"
path = "src/main.rs"
[dependencies]
roc_std = { path = "../../../crates/roc_std" }
libc = "0.2"
[workspace]

View file

@ -0,0 +1,9 @@
fn main() {
#[cfg(not(windows))]
println!("cargo:rustc-link-lib=dylib=app");
#[cfg(windows)]
println!("cargo:rustc-link-lib=dylib=libapp");
println!("cargo:rustc-link-search=.");
}

View file

@ -0,0 +1,3 @@
extern int rust_main();
int main() { return rust_main(); }

View file

@ -0,0 +1,17 @@
platform "echo-in-rust"
requires {} { main : _ }
exposes []
packages {}
imports []
provides [mainForHost]
# mainForHost : [StdoutWrite Str (({} -> Op) as Fx0), StderrWrite Str (({} -> Op) as Fx1), Done] as Op
mainForHost : [StdoutWrite Str ({} -> Op), StderrWrite Str ({} -> Op), Done] as Op
mainForHost = main
# mainForHost : { x: Str, y: {} -> Str }
# mainForHost =
# y = "foo"
#
# when main is
# _ -> { x: "bar", y: \{} -> y }

Binary file not shown.

View file

@ -0,0 +1,9 @@
[toolchain]
channel = "1.65.0"
profile = "default"
components = [
# for usages of rust-analyzer or similar tools inside `nix develop`
"rust-src"
]

View file

@ -0,0 +1,744 @@
// ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc glue` CLI command
#![allow(unused_unsafe)]
#![allow(unused_variables)]
#![allow(dead_code)]
#![allow(unused_mut)]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(clippy::undocumented_unsafe_blocks)]
#![allow(clippy::redundant_static_lifetimes)]
#![allow(clippy::unused_unit)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::let_and_return)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::redundant_static_lifetimes)]
#![allow(clippy::needless_borrow)]
#![allow(clippy::clone_on_copy)]
type Op_StderrWrite = roc_std::RocStr;
type Op_StdoutWrite = roc_std::RocStr;
type TODO_roc_function_69 = roc_std::RocStr;
type TODO_roc_function_70 = roc_std::RocStr;
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[repr(u8)]
pub enum discriminant_Op {
Done = 0,
StderrWrite = 1,
StdoutWrite = 2,
}
impl core::fmt::Debug for discriminant_Op {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Done => f.write_str("discriminant_Op::Done"),
Self::StderrWrite => f.write_str("discriminant_Op::StderrWrite"),
Self::StdoutWrite => f.write_str("discriminant_Op::StdoutWrite"),
}
}
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
#[repr(transparent)]
pub struct Op {
pointer: *mut union_Op,
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
#[repr(C)]
union union_Op {
StderrWrite: core::mem::ManuallyDrop<Op_StderrWrite>,
StdoutWrite: core::mem::ManuallyDrop<Op_StdoutWrite>,
_sizer: [u8; 8],
}
#[cfg(any(
target_arch = "arm",
target_arch = "arm",
target_arch = "aarch64",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86",
target_arch = "x86_64",
target_arch = "x86_64"
))]
//TODO HAS CLOSURE 2
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
#[repr(C)]
pub struct RocFunction_66 {
pub closure_data: roc_std::RocList<u8>,
}
impl RocFunction_66 {
pub fn force_thunk(mut self, arg_0: ()) -> Op {
extern "C" {
fn roc__mainForHost_0_caller(arg_0: &(), closure_data: *mut u8, output: *mut Op);
}
let mut output = std::mem::MaybeUninit::uninit();
let ptr = self.closure_data.as_mut_ptr();
unsafe { roc__mainForHost_0_caller(&arg_0, ptr, output.as_mut_ptr()) };
unsafe { output.assume_init() }
}
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
#[repr(C)]
pub struct RocFunction_67 {
pub closure_data: roc_std::RocList<u8>,
}
impl RocFunction_67 {
pub fn force_thunk(mut self, arg_0: ()) -> Op {
extern "C" {
fn roc__mainForHost_1_caller(arg_0: &(), closure_data: *mut u8, output: *mut Op);
}
let mut output = std::mem::MaybeUninit::uninit();
let ptr = self.closure_data.as_mut_ptr();
unsafe { roc__mainForHost_1_caller(&arg_0, ptr, output.as_mut_ptr()) };
unsafe { output.assume_init() }
}
}
impl Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
#[inline(always)]
fn storage(&self) -> Option<&core::cell::Cell<roc_std::Storage>> {
let mask = match std::mem::size_of::<usize>() {
4 => 0b11,
8 => 0b111,
_ => unreachable!(),
};
// NOTE: pointer provenance is probably lost here
let unmasked_address = (self.pointer as usize) & !mask;
let untagged = unmasked_address as *const core::cell::Cell<roc_std::Storage>;
if untagged.is_null() {
None
} else {
unsafe { Some(&*untagged.sub(1)) }
}
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Returns which variant this tag union holds. Note that this never includes a payload!
pub fn discriminant(&self) -> discriminant_Op {
// The discriminant is stored in the unused bytes at the end of the recursive pointer
unsafe { core::mem::transmute::<u8, discriminant_Op>((self.pointer as u8) & 0b11) }
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Internal helper
fn tag_discriminant(pointer: *mut union_Op, discriminant: discriminant_Op) -> *mut union_Op {
// The discriminant is stored in the unused bytes at the end of the union pointer
let untagged = (pointer as usize) & (!0b11 as usize);
let tagged = untagged | (discriminant as usize);
tagged as *mut union_Op
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Internal helper
fn union_pointer(&self) -> *mut union_Op {
// The discriminant is stored in the unused bytes at the end of the union pointer
((self.pointer as usize) & (!0b11 as usize)) as *mut union_Op
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// A tag named Done, which has no payload.
pub const Done: Self = Self {
pointer: core::ptr::null_mut(),
};
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload at index 0.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
pub unsafe fn get_StderrWrite_0(&self) -> roc_std::RocStr {
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
extern "C" {
#[link_name = "roc__getter__2_generic"]
fn getter(_: *mut roc_std::RocStr, _: *const Op);
}
let mut ret = core::mem::MaybeUninit::uninit();
getter(ret.as_mut_ptr(), self);
ret.assume_init()
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload at index 1.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
pub unsafe fn get_StderrWrite_1(&self) -> RocFunction_67 {
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
extern "C" {
#[link_name = "roc__getter__3_size"]
fn size() -> usize;
#[link_name = "roc__getter__3_generic"]
fn getter(_: *mut u8, _: *const Op);
}
// allocate memory to store this variably-sized value
// allocates with roc_alloc, but that likely still uses the heap
let it = std::iter::repeat(0xAAu8).take(size());
let mut bytes = roc_std::RocList::from_iter(it);
getter(bytes.as_mut_ptr(), self);
RocFunction_67 {
closure_data: bytes,
}
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// Construct a tag named `StderrWrite`, with the appropriate payload
pub fn StderrWrite(arg: Op_StderrWrite) -> Self {
let size = core::mem::size_of::<union_Op>();
let align = core::mem::align_of::<union_Op>() as u32;
unsafe {
let ptr = roc_std::roc_alloc_refcounted::<union_Op>();
*ptr = union_Op {
StderrWrite: core::mem::ManuallyDrop::new(arg),
};
Self {
pointer: Self::tag_discriminant(ptr, discriminant_Op::StderrWrite),
}
}
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and convert it to `StderrWrite`'s payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
pub unsafe fn into_StderrWrite(mut self) -> Op_StderrWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
let payload = {
let ptr = (self.pointer as usize & !0b11) as *mut union_Op;
let mut uninitialized = core::mem::MaybeUninit::uninit();
let swapped = unsafe {
core::mem::replace(
&mut (*ptr).StderrWrite,
core::mem::ManuallyDrop::new(uninitialized.assume_init()),
)
};
core::mem::forget(self);
core::mem::ManuallyDrop::into_inner(swapped)
};
payload
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
pub unsafe fn as_StderrWrite(&self) -> &Op_StderrWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
let payload = {
let ptr = (self.pointer as usize & !0b11) as *mut union_Op;
unsafe { &(*ptr).StderrWrite }
};
&payload
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload at index 0.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
pub unsafe fn get_StdoutWrite_0(&self) -> roc_std::RocStr {
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
extern "C" {
#[link_name = "roc__getter__2_generic"]
fn getter(_: *mut roc_std::RocStr, _: *const Op);
}
let mut ret = core::mem::MaybeUninit::uninit();
getter(ret.as_mut_ptr(), self);
ret.assume_init()
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload at index 1.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
pub unsafe fn get_StdoutWrite_1(&self) -> RocFunction_66 {
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
extern "C" {
#[link_name = "roc__getter__3_size"]
fn size() -> usize;
#[link_name = "roc__getter__3_generic"]
fn getter(_: *mut u8, _: *const Op);
}
// allocate memory to store this variably-sized value
// allocates with roc_alloc, but that likely still uses the heap
let it = std::iter::repeat(0xAAu8).take(size());
let mut bytes = roc_std::RocList::from_iter(it);
getter(bytes.as_mut_ptr(), self);
RocFunction_66 {
closure_data: bytes,
}
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// Construct a tag named `StdoutWrite`, with the appropriate payload
pub fn StdoutWrite(arg: Op_StdoutWrite) -> Self {
let size = core::mem::size_of::<union_Op>();
let align = core::mem::align_of::<union_Op>() as u32;
unsafe {
let ptr = roc_std::roc_alloc_refcounted::<union_Op>();
*ptr = union_Op {
StdoutWrite: core::mem::ManuallyDrop::new(arg),
};
Self {
pointer: Self::tag_discriminant(ptr, discriminant_Op::StdoutWrite),
}
}
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and convert it to `StdoutWrite`'s payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
pub unsafe fn into_StdoutWrite(mut self) -> Op_StdoutWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
let payload = {
let ptr = (self.pointer as usize & !0b11) as *mut union_Op;
let mut uninitialized = core::mem::MaybeUninit::uninit();
let swapped = unsafe {
core::mem::replace(
&mut (*ptr).StdoutWrite,
core::mem::ManuallyDrop::new(uninitialized.assume_init()),
)
};
core::mem::forget(self);
core::mem::ManuallyDrop::into_inner(swapped)
};
payload
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
pub unsafe fn as_StdoutWrite(&self) -> &Op_StdoutWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
let payload = {
let ptr = (self.pointer as usize & !0b11) as *mut union_Op;
unsafe { &(*ptr).StdoutWrite }
};
&payload
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Returns which variant this tag union holds. Note that this never includes a payload!
pub fn discriminant(&self) -> discriminant_Op {
// The discriminant is stored in the unused bytes at the end of the recursive pointer
unsafe { core::mem::transmute::<u8, discriminant_Op>((self.pointer as u8) & 0b111) }
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Internal helper
fn tag_discriminant(pointer: *mut union_Op, discriminant: discriminant_Op) -> *mut union_Op {
// The discriminant is stored in the unused bytes at the end of the union pointer
let untagged = (pointer as usize) & (!0b111 as usize);
let tagged = untagged | (discriminant as usize);
tagged as *mut union_Op
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Internal helper
fn union_pointer(&self) -> *mut union_Op {
// The discriminant is stored in the unused bytes at the end of the union pointer
((self.pointer as usize) & (!0b111 as usize)) as *mut union_Op
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and convert it to `StderrWrite`'s payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
pub unsafe fn into_StderrWrite(mut self) -> Op_StderrWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
let payload = {
let ptr = (self.pointer as usize & !0b111) as *mut union_Op;
let mut uninitialized = core::mem::MaybeUninit::uninit();
let swapped = unsafe {
core::mem::replace(
&mut (*ptr).StderrWrite,
core::mem::ManuallyDrop::new(uninitialized.assume_init()),
)
};
core::mem::forget(self);
core::mem::ManuallyDrop::into_inner(swapped)
};
payload
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
pub unsafe fn as_StderrWrite(&self) -> &Op_StderrWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
let payload = {
let ptr = (self.pointer as usize & !0b111) as *mut union_Op;
unsafe { &(*ptr).StderrWrite }
};
&payload
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and convert it to `StdoutWrite`'s payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
pub unsafe fn into_StdoutWrite(mut self) -> Op_StdoutWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
let payload = {
let ptr = (self.pointer as usize & !0b111) as *mut union_Op;
let mut uninitialized = core::mem::MaybeUninit::uninit();
let swapped = unsafe {
core::mem::replace(
&mut (*ptr).StdoutWrite,
core::mem::ManuallyDrop::new(uninitialized.assume_init()),
)
};
core::mem::forget(self);
core::mem::ManuallyDrop::into_inner(swapped)
};
payload
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
pub unsafe fn as_StdoutWrite(&self) -> &Op_StdoutWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
let payload = {
let ptr = (self.pointer as usize & !0b111) as *mut union_Op;
unsafe { &(*ptr).StdoutWrite }
};
&payload
}
}
impl Drop for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn drop(&mut self) {
// We only need to do any work if there's actually a heap-allocated payload.
if let Some(storage) = self.storage() {
let mut new_storage = storage.get();
// Decrement the refcount
let needs_dealloc = !new_storage.is_readonly() && new_storage.decrease();
if needs_dealloc {
// Drop the payload first.
match self.discriminant() {
discriminant_Op::Done => {}
discriminant_Op::StderrWrite => unsafe {
core::mem::ManuallyDrop::drop(&mut (&mut *self.union_pointer()).StderrWrite)
},
discriminant_Op::StdoutWrite => unsafe {
core::mem::ManuallyDrop::drop(&mut (&mut *self.union_pointer()).StdoutWrite)
},
}
// Dealloc the pointer
let alignment =
core::mem::align_of::<Self>().max(core::mem::align_of::<roc_std::Storage>());
unsafe {
crate::roc_dealloc(storage.as_ptr().cast(), alignment as u32);
}
} else {
// Write the storage back.
storage.set(new_storage);
}
}
}
}
impl Eq for Op {}
impl PartialEq for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn eq(&self, other: &Self) -> bool {
if self.discriminant() != other.discriminant() {
return false;
}
unsafe {
match self.discriminant() {
discriminant_Op::Done => true,
discriminant_Op::StderrWrite => {
(&*self.union_pointer()).StderrWrite == (&*other.union_pointer()).StderrWrite
}
discriminant_Op::StdoutWrite => {
(&*self.union_pointer()).StdoutWrite == (&*other.union_pointer()).StdoutWrite
}
}
}
}
}
impl PartialOrd for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
match self.discriminant().partial_cmp(&other.discriminant()) {
Some(core::cmp::Ordering::Equal) => {}
not_eq => return not_eq,
}
unsafe {
match self.discriminant() {
discriminant_Op::Done => Some(core::cmp::Ordering::Equal),
discriminant_Op::StderrWrite => (&*self.union_pointer())
.StderrWrite
.partial_cmp(&(&*other.union_pointer()).StderrWrite),
discriminant_Op::StdoutWrite => (&*self.union_pointer())
.StdoutWrite
.partial_cmp(&(&*other.union_pointer()).StdoutWrite),
}
}
}
}
impl Ord for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match self.discriminant().cmp(&other.discriminant()) {
core::cmp::Ordering::Equal => {}
not_eq => return not_eq,
}
unsafe {
match self.discriminant() {
discriminant_Op::Done => core::cmp::Ordering::Equal,
discriminant_Op::StderrWrite => (&*self.union_pointer())
.StderrWrite
.cmp(&(&*other.union_pointer()).StderrWrite),
discriminant_Op::StdoutWrite => (&*self.union_pointer())
.StdoutWrite
.cmp(&(&*other.union_pointer()).StdoutWrite),
}
}
}
}
impl Clone for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn clone(&self) -> Self {
if let Some(storage) = self.storage() {
let mut new_storage = storage.get();
if !new_storage.is_readonly() {
new_storage.increment_reference_count();
storage.set(new_storage);
}
}
Self {
pointer: self.pointer,
}
}
}
impl core::hash::Hash for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
match self.discriminant() {
discriminant_Op::Done => discriminant_Op::Done.hash(state),
discriminant_Op::StderrWrite => unsafe {
discriminant_Op::StderrWrite.hash(state);
(&*self.union_pointer()).StderrWrite.hash(state);
},
discriminant_Op::StdoutWrite => unsafe {
discriminant_Op::StdoutWrite.hash(state);
(&*self.union_pointer()).StdoutWrite.hash(state);
},
}
}
}
impl core::fmt::Debug for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Op::")?;
unsafe {
match self.discriminant() {
discriminant_Op::Done => f.write_str("Done"),
discriminant_Op::StderrWrite => f
.debug_tuple("StderrWrite")
// TODO HAS CLOSURE
.finish(),
discriminant_Op::StdoutWrite => f
.debug_tuple("StdoutWrite")
// TODO HAS CLOSURE
.finish(),
}
}
}
}

View file

@ -0,0 +1,132 @@
#![allow(non_snake_case)]
mod glue;
use core::ffi::c_void;
use glue::Op;
use roc_std::RocStr;
use std::ffi::CStr;
use std::io::Write;
use std::mem::MaybeUninit;
use std::os::raw::c_char;
extern "C" {
#[link_name = "roc__mainForHost_1_exposed_generic"]
fn roc_main(_: *mut Op);
}
#[no_mangle]
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
return libc::malloc(size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
return libc::realloc(c_ptr, new_size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
return libc::free(c_ptr);
}
#[no_mangle]
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
match tag_id {
0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
_ => todo!(),
}
}
#[no_mangle]
pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
libc::memcpy(dst, src, n)
}
#[no_mangle]
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
libc::memset(dst, c, n)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_getppid() -> libc::pid_t {
libc::getppid()
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_mmap(
addr: *mut libc::c_void,
len: libc::size_t,
prot: libc::c_int,
flags: libc::c_int,
fd: libc::c_int,
offset: libc::off_t,
) -> *mut libc::c_void {
libc::mmap(addr, len, prot, flags, fd, offset)
}
#[cfg(unix)]
#[no_mangle]
pub unsafe extern "C" fn roc_shm_open(
name: *const libc::c_char,
oflag: libc::c_int,
mode: libc::mode_t,
) -> libc::c_int {
libc::shm_open(name, oflag, mode as libc::c_uint)
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
use glue::discriminant_Op::*;
println!("Let's do things!");
let mut op: Op = unsafe {
let mut mem = MaybeUninit::uninit();
roc_main(mem.as_mut_ptr());
mem.assume_init()
};
loop {
match dbg!(op.discriminant()) {
StdoutWrite => {
let output: RocStr = unsafe { op.get_StdoutWrite_0() };
op = unsafe { op.get_StdoutWrite_1().force_thunk(()) };
if let Err(e) = std::io::stdout().write_all(output.as_bytes()) {
panic!("Writing to stdout failed! {:?}", e);
}
}
StderrWrite => {
let output: RocStr = unsafe { op.get_StderrWrite_0() };
op = unsafe { op.get_StderrWrite_1().force_thunk(()) };
if let Err(e) = std::io::stderr().write_all(output.as_bytes()) {
panic!("Writing to stdout failed! {:?}", e);
}
}
Done => {
break;
}
}
}
println!("Done!");
// Exit code
0
}

View file

@ -0,0 +1,3 @@
fn main() {
std::process::exit(host::rust_main() as _);
}

View file

@ -1,3 +1,5 @@
#![allow(unused)]
mod graphics; mod graphics;
mod gui; mod gui;
mod roc; mod roc;

View file

@ -13,26 +13,26 @@ use winit::event::VirtualKeyCode;
extern "C" { extern "C" {
// program // program
#[link_name = "roc__programForHost_1_exposed_generic"] // #[link_name = "roc__programForHost_1_exposed_generic"]
fn roc_program(); // fn roc_program();
#[link_name = "roc__programForHost_size"] // #[link_name = "roc__programForHost_1_exposed_size"]
fn roc_program_size() -> i64; // fn roc_program_size() -> i64;
// init // init
#[link_name = "roc__programForHost_1__Init_caller"] #[link_name = "roc__programForHost_0_caller"]
fn call_init(size: *const Bounds, closure_data: *const u8, output: *mut Model); fn call_init(size: *const Bounds, closure_data: *const u8, output: *mut Model);
#[link_name = "roc__programForHost_1__Init_size"] #[link_name = "roc__programForHost_0_size"]
fn init_size() -> i64; fn init_size() -> i64;
#[link_name = "roc__programForHost_1__Init_result_size"] #[link_name = "roc__programForHost_0_result_size"]
fn init_result_size() -> i64; fn init_result_size() -> i64;
// update // update
#[link_name = "roc__programForHost_1__Update_caller"] #[link_name = "roc__programForHost_1_caller"]
fn call_update( fn call_update(
model: *const Model, model: *const Model,
event: *const RocEvent, event: *const RocEvent,
@ -40,18 +40,18 @@ extern "C" {
output: *mut Model, output: *mut Model,
); );
#[link_name = "roc__programForHost_1__Update_size"] #[link_name = "roc__programForHost_1_size"]
fn update_size() -> i64; fn update_size() -> i64;
#[link_name = "roc__programForHost_1__Update_result_size"] #[link_name = "roc__programForHost_1_result_size"]
fn update_result_size() -> i64; fn update_result_size() -> i64;
// render // render
#[link_name = "roc__programForHost_1__Render_caller"] #[link_name = "roc__programForHost_2_caller"]
fn call_render(model: *const Model, closure_data: *const u8, output: *mut RocList<RocElem>); fn call_render(model: *const Model, closure_data: *const u8, output: *mut RocList<RocElem>);
#[link_name = "roc__programForHost_1__Render_size"] #[link_name = "roc__programForHost_2_size"]
fn roc_render_size() -> i64; fn roc_render_size() -> i64;
} }

View file

@ -1,5 +1,5 @@
app "helloWorld" app "helloWorld"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3/5CcipdhTTAtISf4FwlBNHmyu1unYAV8b0MKRwYiEHys.tar.br" }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf

View file

@ -1,6 +1,6 @@
app "example" app "example"
packages { packages {
cli: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br", cli: "https://github.com/roc-lang/basic-cli/releases/download/0.3/5CcipdhTTAtISf4FwlBNHmyu1unYAV8b0MKRwYiEHys.tar.br",
parser: "../package/main.roc", parser: "../package/main.roc",
} }
imports [ imports [

View file

@ -0,0 +1,744 @@
// ⚠️ GENERATED CODE ⚠️ - this entire file was generated by the `roc glue` CLI command
#![allow(unused_unsafe)]
#![allow(unused_variables)]
#![allow(dead_code)]
#![allow(unused_mut)]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]
#![allow(clippy::undocumented_unsafe_blocks)]
#![allow(clippy::redundant_static_lifetimes)]
#![allow(clippy::unused_unit)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::let_and_return)]
#![allow(clippy::missing_safety_doc)]
#![allow(clippy::redundant_static_lifetimes)]
#![allow(clippy::needless_borrow)]
#![allow(clippy::clone_on_copy)]
type Op_StderrWrite = roc_std::RocStr;
type Op_StdoutWrite = roc_std::RocStr;
type TODO_roc_function_69 = roc_std::RocStr;
type TODO_roc_function_70 = roc_std::RocStr;
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[repr(u8)]
pub enum discriminant_Op {
Done = 0,
StderrWrite = 1,
StdoutWrite = 2,
}
impl core::fmt::Debug for discriminant_Op {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Done => f.write_str("discriminant_Op::Done"),
Self::StderrWrite => f.write_str("discriminant_Op::StderrWrite"),
Self::StdoutWrite => f.write_str("discriminant_Op::StdoutWrite"),
}
}
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
#[repr(transparent)]
pub struct Op {
pointer: *mut union_Op,
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
#[repr(C)]
union union_Op {
StderrWrite: core::mem::ManuallyDrop<Op_StderrWrite>,
StdoutWrite: core::mem::ManuallyDrop<Op_StdoutWrite>,
_sizer: [u8; 8],
}
#[cfg(any(
target_arch = "arm",
target_arch = "arm",
target_arch = "aarch64",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86",
target_arch = "x86_64",
target_arch = "x86_64"
))]
//TODO HAS CLOSURE 2
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
#[repr(C)]
pub struct RocFunction_66 {
pub closure_data: roc_std::RocList<u8>,
}
impl RocFunction_66 {
pub fn force_thunk(mut self, arg_0: ()) -> Op {
extern "C" {
fn roc__mainForHost_0_caller(arg_0: &(), closure_data: *mut u8, output: *mut Op);
}
let mut output = std::mem::MaybeUninit::uninit();
let ptr = self.closure_data.as_mut_ptr();
unsafe { roc__mainForHost_0_caller(&arg_0, ptr, output.as_mut_ptr()) };
unsafe { output.assume_init() }
}
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
#[repr(C)]
pub struct RocFunction_67 {
pub closure_data: roc_std::RocList<u8>,
}
impl RocFunction_67 {
pub fn force_thunk(mut self, arg_0: ()) -> Op {
extern "C" {
fn roc__mainForHost_1_caller(arg_0: &(), closure_data: *mut u8, output: *mut Op);
}
let mut output = std::mem::MaybeUninit::uninit();
let ptr = self.closure_data.as_mut_ptr();
unsafe { roc__mainForHost_1_caller(&arg_0, ptr, output.as_mut_ptr()) };
unsafe { output.assume_init() }
}
}
impl Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
#[inline(always)]
fn storage(&self) -> Option<&core::cell::Cell<roc_std::Storage>> {
let mask = match std::mem::size_of::<usize>() {
4 => 0b11,
8 => 0b111,
_ => unreachable!(),
};
// NOTE: pointer provenance is probably lost here
let unmasked_address = (self.pointer as usize) & !mask;
let untagged = unmasked_address as *const core::cell::Cell<roc_std::Storage>;
if untagged.is_null() {
None
} else {
unsafe { Some(&*untagged.sub(1)) }
}
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Returns which variant this tag union holds. Note that this never includes a payload!
pub fn discriminant(&self) -> discriminant_Op {
// The discriminant is stored in the unused bytes at the end of the recursive pointer
unsafe { core::mem::transmute::<u8, discriminant_Op>((self.pointer as u8) & 0b11) }
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Internal helper
fn tag_discriminant(pointer: *mut union_Op, discriminant: discriminant_Op) -> *mut union_Op {
// The discriminant is stored in the unused bytes at the end of the union pointer
let untagged = (pointer as usize) & (!0b11 as usize);
let tagged = untagged | (discriminant as usize);
tagged as *mut union_Op
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Internal helper
fn union_pointer(&self) -> *mut union_Op {
// The discriminant is stored in the unused bytes at the end of the union pointer
((self.pointer as usize) & (!0b11 as usize)) as *mut union_Op
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// A tag named Done, which has no payload.
pub const Done: Self = Self {
pointer: core::ptr::null_mut(),
};
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload at index 0.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
pub unsafe fn get_StderrWrite_0(&self) -> roc_std::RocStr {
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
extern "C" {
#[link_name = "roc__getter__2_generic"]
fn getter(_: *mut roc_std::RocStr, _: *const Op);
}
let mut ret = core::mem::MaybeUninit::uninit();
getter(ret.as_mut_ptr(), self);
ret.assume_init()
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload at index 1.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
pub unsafe fn get_StderrWrite_1(&self) -> RocFunction_67 {
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
extern "C" {
#[link_name = "roc__getter__3_size"]
fn size() -> usize;
#[link_name = "roc__getter__3_generic"]
fn getter(_: *mut u8, _: *const Op);
}
// allocate memory to store this variably-sized value
// allocates with roc_alloc, but that likely still uses the heap
let it = std::iter::repeat(0xAAu8).take(size());
let mut bytes = roc_std::RocList::from_iter(it);
getter(bytes.as_mut_ptr(), self);
RocFunction_67 {
closure_data: bytes,
}
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// Construct a tag named `StderrWrite`, with the appropriate payload
pub fn StderrWrite(arg: Op_StderrWrite) -> Self {
let size = core::mem::size_of::<union_Op>();
let align = core::mem::align_of::<union_Op>() as u32;
unsafe {
let ptr = roc_std::roc_alloc_refcounted::<union_Op>();
*ptr = union_Op {
StderrWrite: core::mem::ManuallyDrop::new(arg),
};
Self {
pointer: Self::tag_discriminant(ptr, discriminant_Op::StderrWrite),
}
}
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and convert it to `StderrWrite`'s payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
pub unsafe fn into_StderrWrite(mut self) -> Op_StderrWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
let payload = {
let ptr = (self.pointer as usize & !0b11) as *mut union_Op;
let mut uninitialized = core::mem::MaybeUninit::uninit();
let swapped = unsafe {
core::mem::replace(
&mut (*ptr).StderrWrite,
core::mem::ManuallyDrop::new(uninitialized.assume_init()),
)
};
core::mem::forget(self);
core::mem::ManuallyDrop::into_inner(swapped)
};
payload
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
pub unsafe fn as_StderrWrite(&self) -> &Op_StderrWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
let payload = {
let ptr = (self.pointer as usize & !0b11) as *mut union_Op;
unsafe { &(*ptr).StderrWrite }
};
&payload
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload at index 0.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
pub unsafe fn get_StdoutWrite_0(&self) -> roc_std::RocStr {
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
extern "C" {
#[link_name = "roc__getter__2_generic"]
fn getter(_: *mut roc_std::RocStr, _: *const Op);
}
let mut ret = core::mem::MaybeUninit::uninit();
getter(ret.as_mut_ptr(), self);
ret.assume_init()
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload at index 1.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
pub unsafe fn get_StdoutWrite_1(&self) -> RocFunction_66 {
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
extern "C" {
#[link_name = "roc__getter__3_size"]
fn size() -> usize;
#[link_name = "roc__getter__3_generic"]
fn getter(_: *mut u8, _: *const Op);
}
// allocate memory to store this variably-sized value
// allocates with roc_alloc, but that likely still uses the heap
let it = std::iter::repeat(0xAAu8).take(size());
let mut bytes = roc_std::RocList::from_iter(it);
getter(bytes.as_mut_ptr(), self);
RocFunction_66 {
closure_data: bytes,
}
}
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
/// Construct a tag named `StdoutWrite`, with the appropriate payload
pub fn StdoutWrite(arg: Op_StdoutWrite) -> Self {
let size = core::mem::size_of::<union_Op>();
let align = core::mem::align_of::<union_Op>() as u32;
unsafe {
let ptr = roc_std::roc_alloc_refcounted::<union_Op>();
*ptr = union_Op {
StdoutWrite: core::mem::ManuallyDrop::new(arg),
};
Self {
pointer: Self::tag_discriminant(ptr, discriminant_Op::StdoutWrite),
}
}
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and convert it to `StdoutWrite`'s payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
pub unsafe fn into_StdoutWrite(mut self) -> Op_StdoutWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
let payload = {
let ptr = (self.pointer as usize & !0b11) as *mut union_Op;
let mut uninitialized = core::mem::MaybeUninit::uninit();
let swapped = unsafe {
core::mem::replace(
&mut (*ptr).StdoutWrite,
core::mem::ManuallyDrop::new(uninitialized.assume_init()),
)
};
core::mem::forget(self);
core::mem::ManuallyDrop::into_inner(swapped)
};
payload
}
#[cfg(any(target_arch = "arm", target_arch = "wasm32", target_arch = "x86"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
pub unsafe fn as_StdoutWrite(&self) -> &Op_StdoutWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
let payload = {
let ptr = (self.pointer as usize & !0b11) as *mut union_Op;
unsafe { &(*ptr).StdoutWrite }
};
&payload
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Returns which variant this tag union holds. Note that this never includes a payload!
pub fn discriminant(&self) -> discriminant_Op {
// The discriminant is stored in the unused bytes at the end of the recursive pointer
unsafe { core::mem::transmute::<u8, discriminant_Op>((self.pointer as u8) & 0b111) }
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Internal helper
fn tag_discriminant(pointer: *mut union_Op, discriminant: discriminant_Op) -> *mut union_Op {
// The discriminant is stored in the unused bytes at the end of the union pointer
let untagged = (pointer as usize) & (!0b111 as usize);
let tagged = untagged | (discriminant as usize);
tagged as *mut union_Op
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Internal helper
fn union_pointer(&self) -> *mut union_Op {
// The discriminant is stored in the unused bytes at the end of the union pointer
((self.pointer as usize) & (!0b111 as usize)) as *mut union_Op
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and convert it to `StderrWrite`'s payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
pub unsafe fn into_StderrWrite(mut self) -> Op_StderrWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
let payload = {
let ptr = (self.pointer as usize & !0b111) as *mut union_Op;
let mut uninitialized = core::mem::MaybeUninit::uninit();
let swapped = unsafe {
core::mem::replace(
&mut (*ptr).StderrWrite,
core::mem::ManuallyDrop::new(uninitialized.assume_init()),
)
};
core::mem::forget(self);
core::mem::ManuallyDrop::into_inner(swapped)
};
payload
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StderrWrite` and return its payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StderrWrite`.
pub unsafe fn as_StderrWrite(&self) -> &Op_StderrWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StderrWrite);
let payload = {
let ptr = (self.pointer as usize & !0b111) as *mut union_Op;
unsafe { &(*ptr).StderrWrite }
};
&payload
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and convert it to `StdoutWrite`'s payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
pub unsafe fn into_StdoutWrite(mut self) -> Op_StdoutWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
let payload = {
let ptr = (self.pointer as usize & !0b111) as *mut union_Op;
let mut uninitialized = core::mem::MaybeUninit::uninit();
let swapped = unsafe {
core::mem::replace(
&mut (*ptr).StdoutWrite,
core::mem::ManuallyDrop::new(uninitialized.assume_init()),
)
};
core::mem::forget(self);
core::mem::ManuallyDrop::into_inner(swapped)
};
payload
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
/// Unsafely assume this `Op` has a `.discriminant()` of `StdoutWrite` and return its payload.
/// (Always examine `.discriminant()` first to make sure this is the correct variant!)
/// Panics in debug builds if the `.discriminant()` doesn't return `StdoutWrite`.
pub unsafe fn as_StdoutWrite(&self) -> &Op_StdoutWrite {
debug_assert_eq!(self.discriminant(), discriminant_Op::StdoutWrite);
let payload = {
let ptr = (self.pointer as usize & !0b111) as *mut union_Op;
unsafe { &(*ptr).StdoutWrite }
};
&payload
}
}
impl Drop for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn drop(&mut self) {
// We only need to do any work if there's actually a heap-allocated payload.
if let Some(storage) = self.storage() {
let mut new_storage = storage.get();
// Decrement the refcount
let needs_dealloc = !new_storage.is_readonly() && new_storage.decrease();
if needs_dealloc {
// Drop the payload first.
match self.discriminant() {
discriminant_Op::Done => {}
discriminant_Op::StderrWrite => unsafe {
core::mem::ManuallyDrop::drop(&mut (&mut *self.union_pointer()).StderrWrite)
},
discriminant_Op::StdoutWrite => unsafe {
core::mem::ManuallyDrop::drop(&mut (&mut *self.union_pointer()).StdoutWrite)
},
}
// Dealloc the pointer
let alignment =
core::mem::align_of::<Self>().max(core::mem::align_of::<roc_std::Storage>());
unsafe {
crate::roc_dealloc(storage.as_ptr().cast(), alignment as u32);
}
} else {
// Write the storage back.
storage.set(new_storage);
}
}
}
}
impl Eq for Op {}
impl PartialEq for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn eq(&self, other: &Self) -> bool {
if self.discriminant() != other.discriminant() {
return false;
}
unsafe {
match self.discriminant() {
discriminant_Op::Done => true,
discriminant_Op::StderrWrite => {
(&*self.union_pointer()).StderrWrite == (&*other.union_pointer()).StderrWrite
}
discriminant_Op::StdoutWrite => {
(&*self.union_pointer()).StdoutWrite == (&*other.union_pointer()).StdoutWrite
}
}
}
}
}
impl PartialOrd for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
match self.discriminant().partial_cmp(&other.discriminant()) {
Some(core::cmp::Ordering::Equal) => {}
not_eq => return not_eq,
}
unsafe {
match self.discriminant() {
discriminant_Op::Done => Some(core::cmp::Ordering::Equal),
discriminant_Op::StderrWrite => (&*self.union_pointer())
.StderrWrite
.partial_cmp(&(&*other.union_pointer()).StderrWrite),
discriminant_Op::StdoutWrite => (&*self.union_pointer())
.StdoutWrite
.partial_cmp(&(&*other.union_pointer()).StdoutWrite),
}
}
}
}
impl Ord for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match self.discriminant().cmp(&other.discriminant()) {
core::cmp::Ordering::Equal => {}
not_eq => return not_eq,
}
unsafe {
match self.discriminant() {
discriminant_Op::Done => core::cmp::Ordering::Equal,
discriminant_Op::StderrWrite => (&*self.union_pointer())
.StderrWrite
.cmp(&(&*other.union_pointer()).StderrWrite),
discriminant_Op::StdoutWrite => (&*self.union_pointer())
.StdoutWrite
.cmp(&(&*other.union_pointer()).StdoutWrite),
}
}
}
}
impl Clone for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn clone(&self) -> Self {
if let Some(storage) = self.storage() {
let mut new_storage = storage.get();
if !new_storage.is_readonly() {
new_storage.increment_reference_count();
storage.set(new_storage);
}
}
Self {
pointer: self.pointer,
}
}
}
impl core::hash::Hash for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
match self.discriminant() {
discriminant_Op::Done => discriminant_Op::Done.hash(state),
discriminant_Op::StderrWrite => unsafe {
discriminant_Op::StderrWrite.hash(state);
(&*self.union_pointer()).StderrWrite.hash(state);
},
discriminant_Op::StdoutWrite => unsafe {
discriminant_Op::StdoutWrite.hash(state);
(&*self.union_pointer()).StdoutWrite.hash(state);
},
}
}
}
impl core::fmt::Debug for Op {
#[cfg(any(
target_arch = "arm",
target_arch = "aarch64",
target_arch = "wasm32",
target_arch = "x86",
target_arch = "x86_64"
))]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Op::")?;
unsafe {
match self.discriminant() {
discriminant_Op::Done => f.write_str("Done"),
discriminant_Op::StderrWrite => f
.debug_tuple("StderrWrite")
// TODO HAS CLOSURE
.finish(),
discriminant_Op::StdoutWrite => f
.debug_tuple("StdoutWrite")
// TODO HAS CLOSURE
.finish(),
}
}
}
}

View file

@ -307,12 +307,17 @@ fn find_files(dir: &Path, file_paths: &mut Vec<PathBuf>) -> std::io::Result<()>
/// and there seems to be no good way to strip it. So we resort to some string manipulation. /// and there seems to be no good way to strip it. So we resort to some string manipulation.
pub fn strip_windows_prefix(path_buf: PathBuf) -> std::path::PathBuf { pub fn strip_windows_prefix(path_buf: PathBuf) -> std::path::PathBuf {
#[cfg(not(windows))] #[cfg(not(windows))]
return path_buf; {
path_buf
}
#[cfg(windows)]
{
let path_str = path_buf.display().to_string(); let path_str = path_buf.display().to_string();
std::path::Path::new(path_str.trim_start_matches(r"\\?\")).to_path_buf() std::path::Path::new(path_str.trim_start_matches(r"\\?\")).to_path_buf()
} }
}
fn is_roc_code_block(cbk: &pulldown_cmark::CodeBlockKind) -> bool { fn is_roc_code_block(cbk: &pulldown_cmark::CodeBlockKind) -> bool {
match cbk { match cbk {

View file

@ -126,7 +126,7 @@ Make a file named `main.roc` and put this in it:
```roc ```roc
app "hello" app "hello"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3/5CcipdhTTAtISf4FwlBNHmyu1unYAV8b0MKRwYiEHys.tar.br" }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf
@ -1368,7 +1368,7 @@ Let's take a closer look at the part of `main.roc` above the `main` def:
```roc ```roc
app "hello" app "hello"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3/5CcipdhTTAtISf4FwlBNHmyu1unYAV8b0MKRwYiEHys.tar.br" }
imports [pf.Stdout] imports [pf.Stdout]
provides main to pf provides main to pf
``` ```
@ -1380,7 +1380,7 @@ The line `app "hello"` states that this module defines a Roc application, and th
The remaining lines all involve the [platform](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform) this application is built on: The remaining lines all involve the [platform](https://github.com/roc-lang/roc/wiki/Roc-concepts-explained#platform) this application is built on:
```roc ```roc
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3/5CcipdhTTAtISf4FwlBNHmyu1unYAV8b0MKRwYiEHys.tar.br" }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf
``` ```
@ -1466,7 +1466,7 @@ Let's start with a basic "Hello World" program.
```roc ```roc
app "cli-tutorial" app "cli-tutorial"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3/5CcipdhTTAtISf4FwlBNHmyu1unYAV8b0MKRwYiEHys.tar.br" }
imports [pf.Stdout] imports [pf.Stdout]
provides [main] to pf provides [main] to pf
@ -1496,7 +1496,7 @@ Let's change `main` to read a line from `stdin`, and then print it back out agai
```roc ```roc
app "cli-tutorial" app "cli-tutorial"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3/5CcipdhTTAtISf4FwlBNHmyu1unYAV8b0MKRwYiEHys.tar.br" }
imports [pf.Stdout, pf.Stdin, pf.Task] imports [pf.Stdout, pf.Stdin, pf.Task]
provides [main] to pf provides [main] to pf
@ -1537,7 +1537,7 @@ This works, but we can make it a little nicer to read. Let's change it to the fo
```roc ```roc
app "cli-tutorial" app "cli-tutorial"
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.2.1/wx1N6qhU3kKva-4YqsVJde3fho34NqiLD3m620zZ-OI.tar.br" } packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.3/5CcipdhTTAtISf4FwlBNHmyu1unYAV8b0MKRwYiEHys.tar.br" }
imports [pf.Stdout, pf.Stdin, pf.Task.{ await }] imports [pf.Stdout, pf.Stdin, pf.Task.{ await }]
provides [main] to pf provides [main] to pf