# emit_addr.ludic — addresses of lvalues (struct fields and slice elements), # shared by expression loads and assignment stores. Each returns the address # register; the element/field type is written into g_addr_ty. var g_addr_ty: pointer # out-param: the type at the computed address # address of `base.field` function emit_member_addr(e: Node) -> pointer { let base = emit_expr(e.a) let s = layout_node(base.ty) if (s == null) { perr(`member access on non-aggregate {base.ty}`) } let fidx = field_index(s, e.s) if fidx < 0 { perr(`no such field {e.s}`) } g_addr_ty = field_type(s, e.s) let r = nreg() emit(" "); emit(r); emit(" = getelementptr inbounds "); emit(layout_ty(base.ty)) emit(", ptr "); emit(base.code); emit(", i32 0, i32 "); emit(itoa(fidx)); emit("\n") return r } # address of `base[index]` (slices only in this subset) function emit_index_addr(e: Node) -> pointer { let base = emit_expr(e.a) if not is_slice_ty(base.ty) { # a raw pointer: address of element i let bi = emit_expr(e.b) # (evaluate index first — it may set g_addr_ty) if (base.ty == "words") { # a `words` buffer: 32-bit int elements let rw = emit_bind(`getelementptr inbounds i32, ptr {base.code}, i32 {bi.code}`) g_addr_ty = "int" return rw } if (base.ty == "fixeds") { # a `fixeds` buffer: 32-bit fixed elements let rf = emit_bind(`getelementptr inbounds i32, ptr {base.code}, i32 {bi.code}`) g_addr_ty = "fixed" return rf } if (base.ty == "floats") or (base.ty == "doubles") { # IEEE float / double elements var et = "float" if (base.ty == "doubles") { et = "double" } let rd = emit_bind(`getelementptr inbounds {et}, ptr {base.code}, i32 {bi.code}`) g_addr_ty = et return rd } if (base.ty == "pointers") { # a `pointers` buffer: pointer elements let rp = emit_bind(`getelementptr inbounds ptr, ptr {base.code}, i32 {bi.code}`) g_addr_ty = "ptr" return rp } let r = emit_bind(`getelementptr inbounds i8, ptr {base.code}, i32 {bi.code}`) # a ptr/str: bytes g_addr_ty = "byte" # set last so the caller sees it return r } let el = slice_elem(base.ty) # the index is evaluated BEFORE the elements' address is read: evaluating it may grow this # very slice (xs[slot_of(k)] where slot_of pushes), and a push can move the elements let ix = emit_expr(e.b) # load the data pointer from the slice header (field 0) let dp = nreg() emit(" "); emit(dp); emit(" = getelementptr inbounds %LSlice, ptr ") emit(base.code); emit(", i32 0, i32 0\n") let data = nreg() emit(" "); emit(data); emit(" = load ptr, ptr "); emit(dp); emit("\n") # (the index was evaluated first, above, and before g_addr_ty is set # THE INDEX IS CHECKED AGAINST THE LENGTH. A slice has carried { ptr, len, cap } since it # existed, and nothing ever read the len: every index was a bare getelementptr, so running # off the end of one wrote into whatever the allocator had put next and the program died # somewhere else entirely, minutes later, with a stack that named an innocent function. # The comparison is UNSIGNED, which rejects a negative index in the same instruction. if g_bounds { g_uses_bounds = true let lnp = nreg() emit(" "); emit(lnp); emit(" = getelementptr inbounds %LSlice, ptr ") emit(base.code); emit(", i32 0, i32 1\n") let lnv = emit_bind(`load i32, ptr {lnp}`) let okc = emit_bind(`icmp ult i32 {ix.code}, {lnv}`) let lok = lbl("ixok") let lbad = lbl("ixbad") emit(` br i1 {okc}, label %{lok}, label %{lbad}\n`) emit(`{lbad}:\n`) let bmsg = emit_str_const(`{src_name_of(e)}:{itoa(e.line)}: index out of range: `) let bse = emit_bind(stdstream_rhs(2)) emit(` call i32 (ptr, ptr, ...) @fprintf(ptr {bse}, ptr @.fmt_bounds, ptr {bmsg}, i32 {ix.code}, i32 {lnv})\n`) emit(" call void @exit(i32 1)\n unreachable\n") emit(`{lok}:\n`) } let r = nreg() # g_addr_ty — a member-access index emit(" "); emit(r); emit(" = getelementptr inbounds "); emit(llty(el)) # overwrites it) emit(", ptr "); emit(data); emit(", i32 "); emit(ix.code); emit("\n") g_addr_ty = el # set last so the caller sees the element type return r }