Skip to content

Commit 7b5c6f5

Browse files
committed
chore: merge master
2 parents f84e98f + ce68921 commit 7b5c6f5

33 files changed

+704
-1052
lines changed

scripts/build.sh

Lines changed: 63 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -208,117 +208,96 @@ function set_offline() {
208208
fi
209209
}
210210

211+
# Create shared library from static library with platform-specific flags
212+
function create_shared_library() {
213+
local cc=$1
214+
local lib_dir=$2
215+
local target=$3
216+
217+
log "Creating a shared library from the compiled static library"
218+
219+
# Check if we're on macOS and adjust linker flags accordingly
220+
case "$(uname -s)" in
221+
Darwin*)
222+
local cmd_args=("--shared" "-L$lib_dir" "-Wl,-force_load,$lib_dir/libiec61131std.a" "-o" "$lib_dir/libiec61131std.so" "-lm" "-framework" "CoreFoundation")
223+
if [[ -n "$target" ]]; then
224+
cmd_args+=("--target=$target")
225+
fi
226+
log "Running: $cc ${cmd_args[*]}"
227+
"$cc" "${cmd_args[@]}"
228+
;;
229+
*)
230+
local cmd_args=("--shared" "-L$lib_dir" "-Wl,--whole-archive" "-liec61131std" "-o" "$lib_dir/libiec61131std.so" "-Wl,--no-whole-archive" "-lm" "-fuse-ld=lld")
231+
if [[ -n "$target" ]]; then
232+
cmd_args+=("--target=$target")
233+
fi
234+
log "Running: $cc ${cmd_args[*]}"
235+
"$cc" "${cmd_args[@]}"
236+
;;
237+
esac
238+
}
239+
211240
function run_package_std() {
241+
local cc
212242
cc=$(get_compiler)
213-
OUTPUT_DIR=$project_location/output
243+
local OUTPUT_DIR=$project_location/output
244+
local target_dir="$project_location/target"
245+
local include_dir=$OUTPUT_DIR/include
246+
214247
make_dir "$OUTPUT_DIR"
215248
log "Packaging Standard functions"
216249
log "Removing previous output folder"
217-
rm -rf $OUTPUT_DIR
218-
target_dir="$project_location/target"
219-
include_dir=$OUTPUT_DIR/include
220-
make_dir $include_dir
221-
#Copy the iec61131-st folder
250+
rm -rf "$OUTPUT_DIR"
251+
make_dir "$include_dir"
252+
253+
# Copy the iec61131-st folder
222254
cp -r "$project_location"/libs/stdlib/iec61131-st/*.st "$include_dir"
223255

224-
if [[ ! -z $target ]]; then
225-
for val in ${target//,/ }
226-
do
227-
lib_dir=$OUTPUT_DIR/$val/lib
228-
make_dir $lib_dir
256+
if [[ -n "$target" ]]; then
257+
for val in ${target//,/ }; do
258+
local lib_dir=$OUTPUT_DIR/$val/lib
259+
make_dir "$lib_dir"
229260

230-
# if the target ends with -linux-gnu but does not have unknown, add unknown
261+
# Normalize target name for rustc
262+
local rustc_target=$val
231263
if [[ $val == *"-linux-gnu" && $val != *"unknown-linux-gnu" ]]; then
232264
rustc_target="${val/-linux-gnu/-unknown-linux-gnu}"
233-
else
234-
rustc_target=$val
235265
fi
236-
rel_dir="$target_dir/$rustc_target"
266+
267+
# Determine release or debug directory
268+
local rel_dir="$target_dir/$rustc_target"
237269
if [[ $release -ne 0 ]]; then
238270
rel_dir="$rel_dir/release"
239271
else
240272
rel_dir="$rel_dir/debug"
241273
fi
274+
242275
if [[ ! -d "$rel_dir" ]]; then
243276
echo "Compilation directory $rel_dir not found"
244277
exit 1
245278
fi
246-
cp "$rel_dir/"*.a "$lib_dir" 2>/dev/null || log "$rel_dir does not contain *.a files"
247-
# Create an SO file from the copied a file
248-
log "Creating a shared library from the compiled static library"
249-
# Check if we're on macOS and adjust linker flags accordingly
250-
unameOut="$(uname -s)"
251-
case "${unameOut}" in
252-
Darwin*)
253-
log "Running : $cc --shared -L$lib_dir \
254-
-Wl,-force_load,$lib_dir/libiec61131std.a \
255-
-o $lib_dir/libiec61131std.so \
256-
-lm -framework CoreFoundation \
257-
--target=$val"
258-
$cc --shared -L"$lib_dir" \
259-
-Wl,-force_load,"$lib_dir/libiec61131std.a" \
260-
-o "$lib_dir/libiec61131std.so" \
261-
-lm -framework CoreFoundation \
262-
--target="$val"
263-
;;
264-
*)
265-
log "Running : $cc --shared -L$lib_dir \
266-
-Wl,--whole-archive -liec61131std \
267-
-o $lib_dir/libiec61131std.so -Wl,--no-whole-archive \
268-
-lm \
269-
-fuse-ld=lld \
270-
--target=$val"
271-
$cc --shared -L"$lib_dir" \
272-
-Wl,--whole-archive -liec61131std \
273-
-o "$lib_dir/libiec61131std.so" -Wl,--no-whole-archive \
274-
-lm \
275-
-fuse-ld=lld \
276-
--target="$val"
277-
;;
278-
esac
279-
279+
280+
cp "$rel_dir/"*.a "$lib_dir" 2>/dev/null || log "$rel_dir does not contain *.a files"
281+
create_shared_library "$cc" "$lib_dir" "$val"
280282
done
281283
else
282-
lib_dir=$OUTPUT_DIR/lib
283-
make_dir $lib_dir
284+
local lib_dir=$OUTPUT_DIR/lib
285+
make_dir "$lib_dir"
286+
287+
# Determine release or debug directory
288+
local rel_dir="$target_dir"
284289
if [[ $release -ne 0 ]]; then
285-
rel_dir="$target_dir/release"
290+
rel_dir="$rel_dir/release"
286291
else
287-
rel_dir="$target_dir/debug"
292+
rel_dir="$rel_dir/debug"
288293
fi
294+
289295
cp "$rel_dir/"*.a "$lib_dir" 2>/dev/null || log "$rel_dir does not contain *.a files"
290-
# Create an SO file from the copied a file
291-
log "Creating a shared library from the compiled static library"
292-
# Check if we're on macOS and adjust linker flags accordingly
293-
unameOut="$(uname -s)"
294-
case "${unameOut}" in
295-
Darwin*)
296-
log "Running : $cc --shared -L"$lib_dir" \
297-
-Wl,-force_load,$lib_dir/libiec61131std.a \
298-
-o "$lib_dir/libiec61131std.so" \
299-
-lm -framework CoreFoundation"
300-
$cc --shared -L"$lib_dir" \
301-
-Wl,-force_load,"$lib_dir/libiec61131std.a" \
302-
-o "$lib_dir/libiec61131std.so" \
303-
-lm -framework CoreFoundation
304-
;;
305-
*)
306-
log "Running : $cc --shared -L"$lib_dir" \
307-
-Wl,--whole-archive -liec61131std \
308-
-o "$lib_dir/libiec61131std.so" -Wl,--no-whole-archive \
309-
-lm \
310-
-fuse-ld=lld "
311-
$cc --shared -L"$lib_dir" \
312-
-Wl,--whole-archive -liec61131std \
313-
-o "$lib_dir/libiec61131std.so" -Wl,--no-whole-archive \
314-
-lm \
315-
-fuse-ld=lld
316-
;;
317-
esac
296+
create_shared_library "$cc" "$lib_dir" ""
318297
fi
319298

320299
log "Enabling read/write on the output folder"
321-
chmod -R a+rw $OUTPUT_DIR
300+
chmod -R a+rw "$OUTPUT_DIR"
322301

323302
}
324303

@@ -536,9 +515,9 @@ if [[ $coverage -ne 0 ]]; then
536515
run_coverage
537516
fi
538517

539-
if [[ -d $project_location/target/ ]]; then
518+
if [[ -d "$project_location/target/" ]]; then
540519
log "Allow access to target folders"
541-
chmod -R a+rw $project_location/target/
520+
chmod -R a+rw "$project_location/target/"
542521
fi
543522

544523
if [[ $offline -ne 0 ]]; then

src/codegen/debug.rs

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,18 @@ impl<'ink> DebugBuilder<'ink> {
469469
.map(|it| it.to_owned())
470470
}
471471

472+
/// Creates debug information for string types using an array + typedef approach.
473+
///
474+
/// This function generates DWARF debug metadata for string types by creating:
475+
/// 1. A character array type based on the string's encoding (CHAR for UTF-8, WCHAR for UTF-16)
476+
/// 2. A typedef with a unique name based on encoding and length (e.g., "__STRING__81", "__WSTRING__26")
477+
///
478+
/// ## Typedef Naming
479+
///
480+
/// The typedef uses the `__STRING__<grapheme count>` or `__WSTRING__<grapheme count>` pattern where:
481+
/// - Double underscore prefix avoids clashing with user-defined types (reserved for compiler internals)
482+
/// - Length suffix ensures each string type has a unique DWARF reference
483+
/// - Consistent pattern enables easy detection by DWARF parsers
472484
fn create_string_type(
473485
&mut self,
474486
name: &str,
@@ -478,24 +490,37 @@ impl<'ink> DebugBuilder<'ink> {
478490
index: &Index,
479491
types_index: &LlvmTypedIndex,
480492
) -> Result<(), Diagnostic> {
481-
// Register a utf8 or 16 basic type
482-
let inner_type = match encoding {
493+
let char_datatype = match encoding {
483494
StringEncoding::Utf8 => index.get_effective_type_or_void_by_name(CHAR_TYPE),
484495
StringEncoding::Utf16 => index.get_effective_type_or_void_by_name(WCHAR_TYPE),
485496
};
486-
let inner_type = self.get_or_create_debug_type(inner_type, index, types_index)?;
487-
let llvm_type = types_index.get_associated_type(name)?;
488-
let align_bits = self.target_data.get_preferred_alignment(&llvm_type) * 8;
489-
//Register an array
497+
498+
let char_debug_type = self.get_or_create_debug_type(char_datatype, index, types_index)?;
499+
let array_align_bits =
500+
self.target_data.get_preferred_alignment(&types_index.get_associated_type(name)?) * 8;
490501
let array_type = self.debug_info.create_array_type(
491-
inner_type.into(),
502+
char_debug_type.into(),
492503
size,
493-
align_bits,
504+
array_align_bits,
494505
#[allow(clippy::single_range_in_vec_init)]
495-
&[(0..length)],
506+
&[0..length],
496507
);
508+
let typedef_name = match encoding {
509+
StringEncoding::Utf8 => format!("__STRING__{}", length),
510+
StringEncoding::Utf16 => format!("__WSTRING__{}", length),
511+
};
497512

498-
self.register_concrete_type(name, DebugType::Composite(array_type));
513+
let file = self.compile_unit.get_file();
514+
let string_typedef = self.debug_info.create_typedef(
515+
array_type.as_type(),
516+
&typedef_name,
517+
file,
518+
0, // Line 0 for built-in types
519+
file.as_debug_info_scope(),
520+
array_align_bits,
521+
);
522+
523+
self.register_concrete_type(name, DebugType::Derived(string_typedef));
499524
Ok(())
500525
}
501526

src/codegen/generators/data_type_generator.rs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -286,30 +286,29 @@ impl<'ink> DataTypeGenerator<'ink, '_> {
286286
}
287287
}
288288

289-
// TODO(vosa): Is this neccessary? Are the function types indexed and later on used in the expression
290-
// generator? If not this whole commit could be reverted, double-check before merging
291-
fn create_function_type(&mut self, pou_name: &str) -> Result<FunctionType<'ink>, Diagnostic> {
289+
fn create_function_type(&mut self, method_name: &str) -> Result<FunctionType<'ink>, Diagnostic> {
292290
let return_type = self
293291
.types_index
294-
.find_associated_type(self.index.get_return_type_or_void(pou_name).get_name())
292+
.find_associated_type(self.index.get_return_type_or_void(method_name).get_name())
295293
.map(|opt| opt.as_any_type_enum())
296294
.unwrap_or(self.llvm.context.void_type().as_any_type_enum());
297295

298296
let mut parameter_types = Vec::new();
299297

300-
// Methods are defined as functions in the LLVM IR, but carry the underlying POU type as their first
301-
// parameter to operate on them, hence push the POU type to the very first position.
302-
match self.index.find_pou(pou_name) {
298+
match self.index.find_pou(method_name) {
303299
Some(PouIndexEntry::Method { parent_name, .. }) => {
304300
let ty = self.types_index.get_associated_type(parent_name).expect("must exist");
305301
let ty_ptr = ty.ptr_type(AddressSpace::from(ADDRESS_SPACE_GENERIC)).into();
306302

303+
// Methods are defined as functions in the LLVM IR, but carry the underlying POU type as their
304+
// first parameter to operate on them, hence push the POU type to the very first position.
307305
parameter_types.push(ty_ptr);
308-
for parameter in self.index.get_declared_parameters(pou_name) {
309-
// Instead of relying on the LLVM index, we create data-types directly in here because some of
310-
// them may not have been registered yet. For example, at the time of writing this comment the
311-
// `__auto_pointer_to_DINT` type was not present in the index for a VAR_IN_OUT parameter which
312-
// resulted in an error
306+
307+
for parameter in self.index.get_declared_parameters(method_name) {
308+
// Instead of relying on the LLVM index, we create data-types on the fly here because some
309+
// types have not yet been visited and as a result may not be in the index. For example at
310+
// the time of writing this the index was not able to find a input parameter of type
311+
// `__auto_pointer_to_DINT`, consequently panicking
313312
let ty = self.create_type(
314313
parameter.get_name(),
315314
self.index.get_type(&parameter.data_type_name).expect("must exist"),
@@ -319,13 +318,13 @@ impl<'ink> DataTypeGenerator<'ink, '_> {
319318
}
320319
}
321320

322-
// Function blocks are a bit "weird" in that they only expect an instance argument even if they
323-
// define input, output and/or inout parameters. Effectively, while being methods per-se, their
324-
// calling convention differs from regular methods.
325321
Some(PouIndexEntry::FunctionBlock { name, .. }) => {
326322
let ty = self.types_index.get_associated_type(name).expect("must exist");
327323
let ty_ptr = ty.ptr_type(AddressSpace::from(ADDRESS_SPACE_GENERIC)).into();
328324

325+
// Function blocks are a bit "weird" in that they only expect an instance argument even if
326+
// they define input, output and/or inout parameters. Effectively, while being methods per-se,
327+
// their calling convention differs from regular methods.
329328
parameter_types.push(ty_ptr);
330329
}
331330

src/codegen/generators/expression_generator.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,6 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
490490
operator: &AstNode,
491491
parameters: Option<&AstNode>,
492492
) -> Result<ExpressionValue<'ink>, Diagnostic> {
493-
// Check if we are dealing with something alike `foo^(...)`
494493
if self.annotations.get(operator).is_some_and(StatementAnnotation::is_fnptr) {
495494
return self.generate_fnptr_call(operator, parameters);
496495
}
@@ -595,6 +594,8 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
595594
arguments: Option<&AstNode>,
596595
) -> Result<ExpressionValue<'ink>, Diagnostic> {
597596
let Some(ReferenceExpr { base: Some(ref base), .. }) = operator.get_deref_expr() else {
597+
// XXX: This would fail for auto-deref pointers, but given (for now) function pointers are never
598+
// auto-derefed this should be fine.
598599
unreachable!("internal error, invalid method call")
599600
};
600601

@@ -612,9 +613,9 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
612613
ExpressionValue::RValue(_) => unreachable!(),
613614
};
614615

615-
// Generate the argument list; our assumption is function pointers are only supported for methods
616-
// right now, hence we explicitly fetch the instance arguments from the list. In desugared code it we
617-
// would have something alike `fnPtr^(instanceFb, arg1, arg2, ..., argN)`
616+
// Generate the argument list; our assumption is function pointers are only supported for methods and
617+
// direct function block calls, hence we explicitly fetch the instance argument from the list. In
618+
// terms of desugared ST code you can imagine something alike `fnPtr^(instanceFb, arg1, ..., argN)`
618619
let (instance, arguments_raw, arguments_llvm) = {
619620
let arguments = arguments.map(flatten_expression_list).unwrap_or_default();
620621
let (instance, arguments) = match arguments.len() {
@@ -652,13 +653,10 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
652653

653654
let value = match call.try_as_basic_value() {
654655
Either::Left(value) => value,
655-
Either::Right(_) => {
656-
// TODO: When is this neccessary?
657-
get_llvm_int_type(self.llvm.context, INT_SIZE, INT_TYPE)
658-
.ptr_type(AddressSpace::from(ADDRESS_SPACE_CONST))
659-
.const_null()
660-
.as_basic_value_enum()
661-
}
656+
Either::Right(_) => get_llvm_int_type(self.llvm.context, INT_SIZE, INT_TYPE)
657+
.ptr_type(AddressSpace::from(ADDRESS_SPACE_CONST))
658+
.const_null()
659+
.as_basic_value_enum(),
662660
};
663661

664662
// Output variables are assigned after the function block call, effectively gep'ing the instance

src/codegen/llvm_index.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ pub struct LlvmTypedIndex<'ink> {
2727
pub trait TypeHelper<'ink> {
2828
#[allow(clippy::wrong_self_convention)]
2929
fn as_basic_type(self) -> Option<BasicTypeEnum<'ink>>;
30-
3130
fn create_ptr_type(&self, address_space: AddressSpace) -> PointerType<'ink>;
3231
}
3332

0 commit comments

Comments
 (0)